Sync to upstream/release/671

This commit is contained in:
Andy Friesen 2025-04-25 09:31:51 -07:00
parent 217c14d293
commit 2b7a89db49
128 changed files with 4028 additions and 1572 deletions

3
.clang-tidy Normal file
View file

@ -0,0 +1,3 @@
---
InheritParentConfig: true
Checks: '-roblox-*'

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -332,11 +332,11 @@ struct TypePackMismatch
bool operator==(const TypePackMismatch& rhs) const; bool operator==(const TypePackMismatch& rhs) const;
}; };
struct DynamicPropertyLookupOnClassesUnsafe struct DynamicPropertyLookupOnExternTypesUnsafe
{ {
TypeId ty; TypeId ty;
bool operator==(const DynamicPropertyLookupOnClassesUnsafe& rhs) const; bool operator==(const DynamicPropertyLookupOnExternTypesUnsafe& rhs) const;
}; };
struct UninhabitedTypeFunction struct UninhabitedTypeFunction
@ -499,7 +499,7 @@ using TypeErrorData = Variant<
TypesAreUnrelated, TypesAreUnrelated,
NormalizationTooComplex, NormalizationTooComplex,
TypePackMismatch, TypePackMismatch,
DynamicPropertyLookupOnClassesUnsafe, DynamicPropertyLookupOnExternTypesUnsafe,
UninhabitedTypeFunction, UninhabitedTypeFunction,
UninhabitedTypePackFunction, UninhabitedTypePackFunction,
WhereClauseNeeded, WhereClauseNeeded,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -20,7 +20,7 @@ struct ReferenceCountInitializer : TypeOnceVisitor
DenseHashSet<TypeId>* result; DenseHashSet<TypeId>* result;
ReferenceCountInitializer(DenseHashSet<TypeId>* result) explicit ReferenceCountInitializer(DenseHashSet<TypeId>* result)
: result(result) : result(result)
{ {
} }
@ -43,9 +43,9 @@ struct ReferenceCountInitializer : TypeOnceVisitor
return false; return false;
} }
bool visit(TypeId ty, const ClassType&) override bool visit(TypeId ty, const ExternType&) override
{ {
// ClassTypes never contain free types. // ExternTypes never contain free types.
return false; return false;
} }

View file

@ -34,15 +34,12 @@ LUAU_FASTINT(LuauCheckRecursionLimit)
LUAU_FASTFLAG(DebugLuauLogSolverToJson) LUAU_FASTFLAG(DebugLuauLogSolverToJson)
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAGVARIABLE(LuauPropagateExpectedTypesForCalls) LUAU_FASTFLAGVARIABLE(LuauPropagateExpectedTypesForCalls)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauUngeneralizedTypesForRecursiveFunctions) LUAU_FASTFLAGVARIABLE(LuauUngeneralizedTypesForRecursiveFunctions)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauDoNotLeakNilInRefinement)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauRetainDefinitionAliasLocations) LUAU_FASTFLAGVARIABLE(LuauRetainDefinitionAliasLocations)
@ -50,9 +47,11 @@ LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAGVARIABLE(LuauCacheInferencePerAstExpr) LUAU_FASTFLAGVARIABLE(LuauCacheInferencePerAstExpr)
LUAU_FASTFLAGVARIABLE(LuauAlwaysResolveAstTypes) LUAU_FASTFLAGVARIABLE(LuauAlwaysResolveAstTypes)
LUAU_FASTFLAGVARIABLE(LuauWeakNilRefinementType) LUAU_FASTFLAGVARIABLE(LuauWeakNilRefinementType)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation) LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAGVARIABLE(LuauNoTypeFunctionsNamedTypeOf) LUAU_FASTFLAGVARIABLE(LuauNoTypeFunctionsNamedTypeOf)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
namespace Luau namespace Luau
{ {
@ -157,7 +156,7 @@ struct HasFreeType : TypeOnceVisitor
return true; return true;
} }
bool visit(TypeId ty, const ClassType&) override bool visit(TypeId ty, const ExternType&) override
{ {
return false; return false;
} }
@ -218,6 +217,32 @@ ConstraintGenerator::ConstraintGenerator(
LUAU_ASSERT(module); LUAU_ASSERT(module);
} }
ConstraintSet ConstraintGenerator::run(AstStatBlock* block)
{
visitModuleRoot(block);
return ConstraintSet{
NotNull{rootScope},
std::move(constraints),
std::move(freeTypes),
std::move(scopeToFunction),
std::move(errors)
};
}
ConstraintSet ConstraintGenerator::runOnFragment(const ScopePtr& resumeScope, AstStatBlock* block)
{
visitFragmentRoot(resumeScope, block);
return ConstraintSet{
NotNull{rootScope},
std::move(constraints),
std::move(freeTypes),
std::move(scopeToFunction),
std::move(errors)
};
}
void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
{ {
LUAU_TIMETRACE_SCOPE("ConstraintGenerator::visitModuleRoot", "Typechecking"); LUAU_TIMETRACE_SCOPE("ConstraintGenerator::visitModuleRoot", "Typechecking");
@ -263,8 +288,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
GeneralizationConstraint{ GeneralizationConstraint{
result, result,
moduleFnTy, moduleFnTy,
(FFlag::LuauNonReentrantGeneralization2 || FFlag::LuauTrackInteriorFreeTypesOnScope) ? std::vector<TypeId>{} std::vector<TypeId>{},
: std::move(DEPRECATED_interiorTypes.back())
} }
); );
@ -273,7 +297,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
scope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); scope->interiorFreeTypes = std::move(interiorFreeTypes.back().types);
scope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); scope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks);
} }
else if (FFlag::LuauTrackInteriorFreeTypesOnScope) else
scope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back()); scope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back());
getMutable<BlockedType>(result)->setOwner(genConstraint); getMutable<BlockedType>(result)->setOwner(genConstraint);
@ -359,17 +383,17 @@ TypeId ConstraintGenerator::freshType(const ScopePtr& scope, Polarity polarity)
{ {
auto ft = Luau::freshType(arena, builtinTypes, scope.get(), polarity); auto ft = Luau::freshType(arena, builtinTypes, scope.get(), polarity);
interiorFreeTypes.back().types.push_back(ft); interiorFreeTypes.back().types.push_back(ft);
return ft;
} if (FFlag::DebugLuauGreedyGeneralization)
else if (FFlag::LuauTrackInteriorFreeTypesOnScope) freeTypes.insert(ft);
{
auto ft = Luau::freshType(arena, builtinTypes, scope.get());
DEPRECATED_interiorTypes.back().push_back(ft);
return ft; return ft;
} }
else else
{ {
return Luau::freshType(arena, builtinTypes, scope.get()); auto ft = Luau::freshType(arena, builtinTypes, scope.get());
DEPRECATED_interiorTypes.back().push_back(ft);
return ft;
} }
} }
@ -578,15 +602,8 @@ void ConstraintGenerator::computeRefinement(
// When the top-level expression is `t[x]`, we want to refine it into `nil`, not `never`. // When the top-level expression is `t[x]`, we want to refine it into `nil`, not `never`.
LUAU_ASSERT(refis->get(proposition->key->def)); LUAU_ASSERT(refis->get(proposition->key->def));
if (FFlag::LuauDoNotLeakNilInRefinement) refis->get(proposition->key->def)->shouldAppendNilType =
{ (sense || !eq) && containsSubscriptedDefinition(proposition->key->def) && !proposition->implicitFromCall;
refis->get(proposition->key->def)->shouldAppendNilType =
(sense || !eq) && containsSubscriptedDefinition(proposition->key->def) && !proposition->implicitFromCall;
}
else
{
refis->get(proposition->key->def)->shouldAppendNilType = (sense || !eq) && containsSubscriptedDefinition(proposition->key->def);
}
} }
} }
@ -633,7 +650,7 @@ struct FindSimplificationBlockers : TypeOnceVisitor
return false; return false;
} }
bool visit(TypeId, const ClassType&) override bool visit(TypeId, const ExternType&) override
{ {
return false; return false;
} }
@ -858,7 +875,7 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
aliasDefinitionLocations[function->name.value] = function->location; aliasDefinitionLocations[function->name.value] = function->location;
} }
else if (auto classDeclaration = stat->as<AstStatDeclareClass>()) else if (auto classDeclaration = stat->as<AstStatDeclareExternType>())
{ {
if (scope->exportedTypeBindings.count(classDeclaration->name.value)) if (scope->exportedTypeBindings.count(classDeclaration->name.value))
{ {
@ -1052,7 +1069,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStat* stat)
return visit(scope, s); return visit(scope, s);
else if (auto s = stat->as<AstStatDeclareFunction>()) else if (auto s = stat->as<AstStatDeclareFunction>())
return visit(scope, s); return visit(scope, s);
else if (auto s = stat->as<AstStatDeclareClass>()) else if (auto s = stat->as<AstStatDeclareExternType>())
return visit(scope, s); return visit(scope, s);
else if (auto s = stat->as<AstStatError>()) else if (auto s = stat->as<AstStatError>())
return visit(scope, s); return visit(scope, s);
@ -1273,11 +1290,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forI
for (AstLocal* var : forIn->vars) for (AstLocal* var : forIn->vars)
{ {
TypeId assignee = arena->addType(BlockedType{});
variableTypes.push_back(assignee);
TypeId loopVar = arena->addType(BlockedType{}); TypeId loopVar = arena->addType(BlockedType{});
localTypes[loopVar].insert(assignee); variableTypes.push_back(loopVar);
if (var->annotation) if (var->annotation)
{ {
@ -1296,6 +1310,23 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forI
loopScope, getLocation(forIn->values), IterableConstraint{iterator, variableTypes, forIn->values.data[0], &module->astForInNextTypes} loopScope, getLocation(forIn->values), IterableConstraint{iterator, variableTypes, forIn->values.data[0], &module->astForInNextTypes}
); );
if (FFlag::LuauAddCallConstraintForIterableFunctions)
{
// Add an intersection ReduceConstraint for the key variable to denote that it can't be nil
AstLocal* keyVar = *forIn->vars.begin();
const DefId keyDef = dfg->getDef(keyVar);
const TypeId loopVar = loopScope->lvalueTypes[keyDef];
const TypeId intersectionTy =
createTypeFunctionInstance(builtinTypeFunctions().intersectFunc, {loopVar, builtinTypes->notNilType}, {}, loopScope, keyVar->location);
loopScope->bindings[keyVar] = Binding{intersectionTy, keyVar->location};
loopScope->lvalueTypes[keyDef] = intersectionTy;
auto c = addConstraint(loopScope, keyVar->location, ReduceConstraint{intersectionTy});
c->dependencies.push_back(iterable);
}
for (TypeId var : variableTypes) for (TypeId var : variableTypes)
{ {
auto bt = getMutable<BlockedType>(var); auto bt = getMutable<BlockedType>(var);
@ -1779,7 +1810,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
GeneralizationConstraint{ GeneralizationConstraint{
generalizedTy, generalizedTy,
sig.signature, sig.signature,
FFlag::LuauTrackInteriorFreeTypesOnScope ? std::vector<TypeId>{} : std::move(DEPRECATED_interiorTypes.back()) std::vector<TypeId>{},
} }
); );
@ -1788,7 +1819,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types);
sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks);
} }
else if (FFlag::LuauTrackInteriorFreeTypesOnScope) else
sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back()); sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back());
getMutable<BlockedType>(generalizedTy)->setOwner(gc); getMutable<BlockedType>(generalizedTy)->setOwner(gc);
@ -1852,71 +1883,71 @@ static bool isMetamethod(const Name& name)
name == "__idiv"; name == "__idiv";
} }
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClass* declaredClass) ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareExternType* declaredExternType)
{ {
// If a class with the same name was already defined, we skip over // If a class with the same name was already defined, we skip over
auto bindingIt = scope->exportedTypeBindings.find(declaredClass->name.value); auto bindingIt = scope->exportedTypeBindings.find(declaredExternType->name.value);
if (bindingIt == scope->exportedTypeBindings.end()) if (bindingIt == scope->exportedTypeBindings.end())
return ControlFlow::None; return ControlFlow::None;
std::optional<TypeId> superTy = std::make_optional(builtinTypes->classType); std::optional<TypeId> superTy = std::make_optional(builtinTypes->externType);
if (declaredClass->superName) if (declaredExternType->superName)
{ {
Name superName = Name(declaredClass->superName->value); Name superName = Name(declaredExternType->superName->value);
std::optional<TypeFun> lookupType = scope->lookupType(superName); std::optional<TypeFun> lookupType = scope->lookupType(superName);
if (!lookupType) if (!lookupType)
{ {
reportError(declaredClass->location, UnknownSymbol{superName, UnknownSymbol::Type}); reportError(declaredExternType->location, UnknownSymbol{superName, UnknownSymbol::Type});
return ControlFlow::None; return ControlFlow::None;
} }
// We don't have generic classes, so this assertion _should_ never be hit. // We don't have generic extern types, so this assertion _should_ never be hit.
LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0); LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0);
superTy = follow(lookupType->type); superTy = follow(lookupType->type);
if (!get<ClassType>(follow(*superTy))) if (!get<ExternType>(follow(*superTy)))
{ {
reportError( reportError(
declaredClass->location, declaredExternType->location,
GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredClass->name.value)} GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredExternType->name.value)}
); );
return ControlFlow::None; return ControlFlow::None;
} }
} }
Name className(declaredClass->name.value); Name className(declaredExternType->name.value);
TypeId classTy = arena->addType(ClassType(className, {}, superTy, std::nullopt, {}, {}, module->name, declaredClass->location)); TypeId externTy = arena->addType(ExternType(className, {}, superTy, std::nullopt, {}, {}, module->name, declaredExternType->location));
ClassType* ctv = getMutable<ClassType>(classTy); ExternType* etv = getMutable<ExternType>(externTy);
TypeId metaTy = arena->addType(TableType{TableState::Sealed, scope->level, scope.get()}); TypeId metaTy = arena->addType(TableType{TableState::Sealed, scope->level, scope.get()});
TableType* metatable = getMutable<TableType>(metaTy); TableType* metatable = getMutable<TableType>(metaTy);
ctv->metatable = metaTy; etv->metatable = metaTy;
TypeId classBindTy = bindingIt->second.type; TypeId classBindTy = bindingIt->second.type;
emplaceType<BoundType>(asMutable(classBindTy), classTy); emplaceType<BoundType>(asMutable(classBindTy), externTy);
if (declaredClass->indexer) if (declaredExternType->indexer)
{ {
RecursionCounter counter{&recursionCount}; RecursionCounter counter{&recursionCount};
if (recursionCount >= FInt::LuauCheckRecursionLimit) if (recursionCount >= FInt::LuauCheckRecursionLimit)
{ {
reportCodeTooComplex(declaredClass->indexer->location); reportCodeTooComplex(declaredExternType->indexer->location);
} }
else else
{ {
ctv->indexer = TableIndexer{ etv->indexer = TableIndexer{
resolveType(scope, declaredClass->indexer->indexType, /* inTypeArguments */ false), resolveType(scope, declaredExternType->indexer->indexType, /* inTypeArguments */ false),
resolveType(scope, declaredClass->indexer->resultType, /* inTypeArguments */ false), resolveType(scope, declaredExternType->indexer->resultType, /* inTypeArguments */ false),
}; };
} }
} }
for (const AstDeclaredClassProp& prop : declaredClass->props) for (const AstDeclaredExternTypeProperty& prop : declaredExternType->props)
{ {
Name propName(prop.name.value); Name propName(prop.name.value);
TypeId propTy = resolveType(scope, prop.ty, /* inTypeArguments */ false); TypeId propTy = resolveType(scope, prop.ty, /* inTypeArguments */ false);
@ -1930,7 +1961,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas
if (FunctionType* ftv = getMutable<FunctionType>(propTy)) if (FunctionType* ftv = getMutable<FunctionType>(propTy))
{ {
ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}}); ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}});
ftv->argTypes = addTypePack({classTy}, ftv->argTypes); ftv->argTypes = addTypePack({externTy}, ftv->argTypes);
ftv->hasSelf = true; ftv->hasSelf = true;
@ -1945,7 +1976,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas
} }
} }
TableType::Props& props = assignToMetatable ? metatable->props : ctv->props; TableType::Props& props = assignToMetatable ? metatable->props : etv->props;
if (props.count(propName) == 0) if (props.count(propName) == 0)
{ {
@ -1976,7 +2007,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas
} }
else else
{ {
reportError(declaredClass->location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())}); reportError(declaredExternType->location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())});
} }
} }
} }
@ -2008,7 +2039,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareFunc
funScope = childScope(global, scope); funScope = childScope(global, scope);
TypePackId paramPack = resolveTypePack(funScope, global->params, /* inTypeArguments */ false); TypePackId paramPack = resolveTypePack(funScope, global->params, /* inTypeArguments */ false);
TypePackId retPack = resolveTypePack(funScope, global->retTypes, /* inTypeArguments */ false); TypePackId retPack = FFlag::LuauStoreReturnTypesAsPackOnAst ? resolveTypePack(funScope, global->retTypes, /* inTypeArguments */ false)
: resolveTypePack(funScope, global->retTypes_DEPRECATED, /* inTypeArguments */ false);
FunctionDefinition defn; FunctionDefinition defn;
@ -2658,8 +2690,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
GeneralizationConstraint{ GeneralizationConstraint{
generalizedTy, generalizedTy,
sig.signature, sig.signature,
(FFlag::LuauNonReentrantGeneralization2 || FFlag::LuauTrackInteriorFreeTypesOnScope) ? std::vector<TypeId>{} std::vector<TypeId>{},
: std::move(DEPRECATED_interiorTypes.back())
} }
); );
@ -2673,9 +2704,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
} }
else else
{ {
if (FFlag::LuauTrackInteriorFreeTypesOnScope) sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back());
sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back());
getMutable<BlockedType>(generalizedTy)->setOwner(gc); getMutable<BlockedType>(generalizedTy)->setOwner(gc);
DEPRECATED_interiorTypes.pop_back(); DEPRECATED_interiorTypes.pop_back();
} }
@ -2963,7 +2992,7 @@ std::tuple<TypeId, TypeId, RefinementId> ConstraintGenerator::checkBinary(
else if (typeguard->type == "userdata") else if (typeguard->type == "userdata")
{ {
// For now, we don't really care about being accurate with userdata if the typeguard was using typeof. // For now, we don't really care about being accurate with userdata if the typeguard was using typeof.
discriminantTy = builtinTypes->classType; discriminantTy = builtinTypes->externType;
} }
else if (!typeguard->isTypeof && typeguard->type == "vector") else if (!typeguard->isTypeof && typeguard->type == "vector")
discriminantTy = builtinTypes->neverType; // TODO: figure out a way to deal with this quirky type discriminantTy = builtinTypes->neverType; // TODO: figure out a way to deal with this quirky type
@ -2973,8 +3002,8 @@ std::tuple<TypeId, TypeId, RefinementId> ConstraintGenerator::checkBinary(
{ {
TypeId ty = follow(typeFun->type); TypeId ty = follow(typeFun->type);
// We're only interested in the root class of any classes. // We're only interested in the root type of any extern type.
if (auto ctv = get<ClassType>(ty); ctv && (ctv->parent == builtinTypes->classType || hasTag(ty, kTypeofRootTag))) if (auto etv = get<ExternType>(ty); etv && (etv->parent == builtinTypes->externType || hasTag(ty, kTypeofRootTag)))
discriminantTy = ty; discriminantTy = ty;
} }
@ -3381,14 +3410,29 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
bodyScope->varargPack = std::nullopt; bodyScope->varargPack = std::nullopt;
} }
if (FFlag::DebugLuauGreedyGeneralization)
{
genericTypes = argTypes;
}
LUAU_ASSERT(nullptr != varargPack); LUAU_ASSERT(nullptr != varargPack);
// If there is both an annotation and an expected type, the annotation wins. // If there is both an annotation and an expected type, the annotation wins.
// Type checking will sort out any discrepancies later. // Type checking will sort out any discrepancies later.
if (fn->returnAnnotation) if (FFlag::LuauStoreReturnTypesAsPackOnAst && fn->returnAnnotation)
{ {
TypePackId annotatedRetType = TypePackId annotatedRetType =
resolveTypePack(signatureScope, *fn->returnAnnotation, /* inTypeArguments */ false, /* replaceErrorWithFresh*/ true); resolveTypePack(signatureScope, fn->returnAnnotation, /* inTypeArguments */ false, /* replaceErrorWithFresh*/ true);
// We bind the annotated type directly here so that, when we need to
// generate constraints for return types, we have a guarantee that we
// know the annotated return type already, if one was provided.
LUAU_ASSERT(get<FreeTypePack>(returnType));
emplaceTypePack<BoundTypePack>(asMutable(returnType), annotatedRetType);
}
else if (!FFlag::LuauStoreReturnTypesAsPackOnAst && fn->returnAnnotation_DEPRECATED)
{
TypePackId annotatedRetType =
resolveTypePack(signatureScope, *fn->returnAnnotation_DEPRECATED, /* inTypeArguments */ false, /* replaceErrorWithFresh*/ true);
// We bind the annotated type directly here so that, when we need to // We bind the annotated type directly here so that, when we need to
// generate constraints for return types, we have a guarantee that we // generate constraints for return types, we have a guarantee that we
// know the annotated return type already, if one was provided. // know the annotated return type already, if one was provided.
@ -3639,8 +3683,16 @@ TypeId ConstraintGenerator::resolveFunctionType(
AstTypePackExplicit tempArgTypes{Location{}, fn->argTypes}; AstTypePackExplicit tempArgTypes{Location{}, fn->argTypes};
TypePackId argTypes = resolveTypePack_(signatureScope, &tempArgTypes, inTypeArguments, replaceErrorWithFresh); TypePackId argTypes = resolveTypePack_(signatureScope, &tempArgTypes, inTypeArguments, replaceErrorWithFresh);
AstTypePackExplicit tempRetTypes{Location{}, fn->returnTypes}; TypePackId returnTypes;
TypePackId returnTypes = resolveTypePack_(signatureScope, &tempRetTypes, inTypeArguments, replaceErrorWithFresh); if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
returnTypes = resolveTypePack_(signatureScope, fn->returnTypes, inTypeArguments, replaceErrorWithFresh);
}
else
{
AstTypePackExplicit tempRetTypes{Location{}, fn->returnTypes_DEPRECATED};
returnTypes = resolveTypePack_(signatureScope, &tempRetTypes, inTypeArguments, replaceErrorWithFresh);
}
// TODO: FunctionType needs a pointer to the scope so that we know // TODO: FunctionType needs a pointer to the scope so that we know
// how to quantify/instantiate it. // how to quantify/instantiate it.
@ -3725,12 +3777,8 @@ TypeId ConstraintGenerator::resolveType_(const ScopePtr& scope, AstType* ty, boo
} }
else else
{ {
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) if (unionAnnotation->types.size == 1)
{ return resolveType_(scope, unionAnnotation->types.data[0], inTypeArguments);
if (unionAnnotation->types.size == 1)
return resolveType_(scope, unionAnnotation->types.data[0], inTypeArguments);
}
std::vector<TypeId> parts; std::vector<TypeId> parts;
for (AstType* part : unionAnnotation->types) for (AstType* part : unionAnnotation->types)
{ {
@ -3759,12 +3807,8 @@ TypeId ConstraintGenerator::resolveType_(const ScopePtr& scope, AstType* ty, boo
} }
else else
{ {
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) if (intersectionAnnotation->types.size == 1)
{ return resolveType_(scope, intersectionAnnotation->types.data[0], inTypeArguments);
if (intersectionAnnotation->types.size == 1)
return resolveType_(scope, intersectionAnnotation->types.data[0], inTypeArguments);
}
std::vector<TypeId> parts; std::vector<TypeId> parts;
for (AstType* part : intersectionAnnotation->types) for (AstType* part : intersectionAnnotation->types)
{ {
@ -4294,15 +4338,4 @@ TypeId ConstraintGenerator::simplifyUnion(const ScopePtr& scope, Location locati
return ::Luau::simplifyUnion(builtinTypes, arena, left, right).result; return ::Luau::simplifyUnion(builtinTypes, arena, left, right).result;
} }
std::vector<NotNull<Constraint>> borrowConstraints(const std::vector<ConstraintPtr>& constraints)
{
std::vector<NotNull<Constraint>> result;
result.reserve(constraints.size());
for (const auto& c : constraints)
result.emplace_back(c.get());
return result;
}
} // namespace Luau } // namespace Luau

View file

@ -11,7 +11,6 @@
#include "Luau/Location.h" #include "Luau/Location.h"
#include "Luau/ModuleResolver.h" #include "Luau/ModuleResolver.h"
#include "Luau/OverloadResolution.h" #include "Luau/OverloadResolution.h"
#include "Luau/Quantify.h"
#include "Luau/RecursionCounter.h" #include "Luau/RecursionCounter.h"
#include "Luau/Simplify.h" #include "Luau/Simplify.h"
#include "Luau/TableLiteralInference.h" #include "Luau/TableLiteralInference.h"
@ -33,20 +32,20 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverIncludeDependencies)
LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings) LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings)
LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500) LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500)
LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification) LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTablesOnScope)
LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock) LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock)
LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization) LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauSearchForRefineableType)
LUAU_FASTFLAG(LuauDeprecatedAttribute) LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes) LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2) LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall) LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall)
LUAU_FASTFLAGVARIABLE(LuauAddCallConstraintForIterableFunctions)
namespace Luau namespace Luau
{ {
static void dump(ConstraintSolver* cs, ToStringOptions& opts);
size_t HashBlockedConstraintId::operator()(const BlockedConstraintId& bci) const size_t HashBlockedConstraintId::operator()(const BlockedConstraintId& bci) const
{ {
size_t result = 0; size_t result = 0;
@ -276,26 +275,6 @@ size_t HashInstantiationSignature::operator()(const InstantiationSignature& sign
return hash; return hash;
} }
void dump(ConstraintSolver* cs, ToStringOptions& opts)
{
printf("constraints:\n");
for (NotNull<const Constraint> c : cs->unsolvedConstraints)
{
auto it = cs->blockedConstraints.find(c);
int blockCount = it == cs->blockedConstraints.end() ? 0 : int(it->second);
printf("\t%d\t%s\n", blockCount, toString(*c, opts).c_str());
if (FFlag::DebugLuauLogSolverIncludeDependencies)
{
for (NotNull<Constraint> dep : c->dependencies)
{
if (std::find(cs->unsolvedConstraints.begin(), cs->unsolvedConstraints.end(), dep) != cs->unsolvedConstraints.end())
printf("\t\t|\t%s\n", toString(*dep, opts).c_str());
}
}
}
}
struct InstantiationQueuer : TypeOnceVisitor struct InstantiationQueuer : TypeOnceVisitor
{ {
ConstraintSolver* solver; ConstraintSolver* solver;
@ -321,12 +300,44 @@ struct InstantiationQueuer : TypeOnceVisitor
return true; return true;
} }
bool visit(TypeId ty, const ClassType& ctv) override bool visit(TypeId ty, const ExternType& etv) override
{ {
return false; return false;
} }
}; };
ConstraintSolver::ConstraintSolver(
NotNull<Normalizer> normalizer,
NotNull<Simplifier> simplifier,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
ModuleName moduleName,
NotNull<ModuleResolver> moduleResolver,
std::vector<RequireCycle> requireCycles,
DcrLogger* logger,
NotNull<const DataFlowGraph> dfg,
TypeCheckLimits limits,
ConstraintSet constraintSet_
)
: arena(normalizer->arena)
, builtinTypes(normalizer->builtinTypes)
, normalizer(normalizer)
, simplifier(simplifier)
, typeFunctionRuntime(typeFunctionRuntime)
, constraintSet(std::move(constraintSet_))
, constraints(borrowConstraints(constraintSet.constraints))
, scopeToFunction(&constraintSet.scopeToFunction)
, rootScope(constraintSet.rootScope)
, currentModuleName(std::move(moduleName))
, dfg(dfg)
, moduleResolver(moduleResolver)
, requireCycles(std::move(requireCycles))
, logger(logger)
, limits(std::move(limits))
, opts{/*exhaustive*/ true}
{
initFreeTypeTracking();
}
ConstraintSolver::ConstraintSolver( ConstraintSolver::ConstraintSolver(
NotNull<Normalizer> normalizer, NotNull<Normalizer> normalizer,
NotNull<Simplifier> simplifier, NotNull<Simplifier> simplifier,
@ -346,6 +357,7 @@ ConstraintSolver::ConstraintSolver(
, normalizer(normalizer) , normalizer(normalizer)
, simplifier(simplifier) , simplifier(simplifier)
, typeFunctionRuntime(typeFunctionRuntime) , typeFunctionRuntime(typeFunctionRuntime)
, constraintSet{rootScope}
, constraints(std::move(constraints)) , constraints(std::move(constraints))
, scopeToFunction(scopeToFunction) , scopeToFunction(scopeToFunction)
, rootScope(rootScope) , rootScope(rootScope)
@ -355,33 +367,9 @@ ConstraintSolver::ConstraintSolver(
, requireCycles(std::move(requireCycles)) , requireCycles(std::move(requireCycles))
, logger(logger) , logger(logger)
, limits(std::move(limits)) , limits(std::move(limits))
, opts{/*exhaustive*/ true}
{ {
opts.exhaustive = true; initFreeTypeTracking();
for (NotNull<Constraint> c : this->constraints)
{
unsolvedConstraints.emplace_back(c);
auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes();
for (auto ty : maybeMutatedTypesPerConstraint)
{
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
refCount += 1;
if (FFlag::DebugLuauGreedyGeneralization)
{
auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, DenseHashSet<const Constraint*>{nullptr});
it->second.insert(c.get());
}
}
maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint);
for (NotNull<const Constraint> dep : c->dependencies)
{
block(dep, c);
}
}
} }
void ConstraintSolver::randomize(unsigned seed) void ConstraintSolver::randomize(unsigned seed)
@ -426,6 +414,18 @@ void ConstraintSolver::run()
logger->captureInitialSolverState(rootScope, unsolvedConstraints); logger->captureInitialSolverState(rootScope, unsolvedConstraints);
} }
// Free types that have no constraints at all can be generalized right away.
if (FFlag::DebugLuauGreedyGeneralization)
{
for (TypeId ty : constraintSet.freeTypes)
{
if (auto it = mutatedFreeTypeToConstraint.find(ty); it == mutatedFreeTypeToConstraint.end() || it->second.empty())
generalizeOneType(ty);
}
}
constraintSet.freeTypes.clear();
auto runSolverPass = [&](bool force) auto runSolverPass = [&](bool force)
{ {
bool progress = false; bool progress = false;
@ -655,12 +655,40 @@ struct TypeSearcher : TypeVisitor
// } // }
bool visit(TypeId ty, const ClassType&) override bool visit(TypeId ty, const ExternType&) override
{ {
return false; return false;
} }
}; };
void ConstraintSolver::initFreeTypeTracking()
{
for (NotNull<Constraint> c : this->constraints)
{
unsolvedConstraints.emplace_back(c);
auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes();
for (auto ty : maybeMutatedTypesPerConstraint)
{
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
refCount += 1;
if (FFlag::DebugLuauGreedyGeneralization)
{
auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, nullptr);
it->second.insert(c.get());
}
}
maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint);
for (NotNull<const Constraint> dep : c->dependencies)
{
block(dep, c);
}
}
}
void ConstraintSolver::generalizeOneType(TypeId ty) void ConstraintSolver::generalizeOneType(TypeId ty)
{ {
ty = follow(ty); ty = follow(ty);
@ -858,62 +886,53 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
bind(constraint, c.generalizedType, builtinTypes->errorRecoveryType()); bind(constraint, c.generalizedType, builtinTypes->errorRecoveryType());
} }
if (FFlag::LuauTrackInteriorFreeTypesOnScope) // We check if this member is initialized and then access it, but
// clang-tidy doesn't understand this is safe.
if (constraint->scope->interiorFreeTypes)
{ {
// We check if this member is initialized and then access it, but for (TypeId ty : *constraint->scope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access)
// clang-tidy doesn't understand this is safe.
if (constraint->scope->interiorFreeTypes)
{ {
for (TypeId ty : *constraint->scope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access) if (FFlag::LuauNonReentrantGeneralization2)
{ {
if (FFlag::LuauNonReentrantGeneralization2) ty = follow(ty);
if (auto freeTy = get<FreeType>(ty))
{ {
ty = follow(ty); GeneralizationParams<TypeId> params;
if (auto freeTy = get<FreeType>(ty)) params.foundOutsideFunctions = true;
{ params.useCount = 1;
GeneralizationParams<TypeId> params; params.polarity = freeTy->polarity;
params.foundOutsideFunctions = true;
params.useCount = 1;
params.polarity = freeTy->polarity;
GeneralizationResult<TypeId> res = generalizeType(arena, builtinTypes, constraint->scope, ty, params); GeneralizationResult<TypeId> res = generalizeType(arena, builtinTypes, constraint->scope, ty, params);
if (res.resourceLimitsExceeded) if (res.resourceLimitsExceeded)
reportError(CodeTooComplex{}, constraint->scope->location); // FIXME: We don't have a very good location for this. reportError(CodeTooComplex{}, constraint->scope->location); // FIXME: We don't have a very good location for this.
}
else if (get<TableType>(ty))
sealTable(constraint->scope, ty);
} }
else else if (get<TableType>(ty))
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty); sealTable(constraint->scope, ty);
} }
else
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty);
} }
}
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization2)
{
if (constraint->scope->interiorFreeTypePacks)
{ {
if (constraint->scope->interiorFreeTypePacks) for (TypePackId tp : *constraint->scope->interiorFreeTypePacks) // NOLINT(bugprone-unchecked-optional-access)
{ {
for (TypePackId tp : *constraint->scope->interiorFreeTypePacks) // NOLINT(bugprone-unchecked-optional-access) tp = follow(tp);
if (auto freeTp = get<FreeTypePack>(tp))
{ {
tp = follow(tp); GeneralizationParams<TypePackId> params;
if (auto freeTp = get<FreeTypePack>(tp)) params.foundOutsideFunctions = true;
{ params.useCount = 1;
GeneralizationParams<TypePackId> params; params.polarity = freeTp->polarity;
params.foundOutsideFunctions = true; LUAU_ASSERT(isKnown(params.polarity));
params.useCount = 1; generalizeTypePack(arena, builtinTypes, constraint->scope, tp, params);
params.polarity = freeTp->polarity;
LUAU_ASSERT(isKnown(params.polarity));
generalizeTypePack(arena, builtinTypes, constraint->scope, tp, params);
}
} }
} }
} }
} }
else
{
for (TypeId ty : c.interiorTypes)
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty);
}
return true; return true;
} }
@ -989,16 +1008,12 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Co
{ {
TypeId keyTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed); TypeId keyTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed);
TypeId valueTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed); TypeId valueTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed);
if (FFlag::LuauTrackInteriorFreeTypesOnScope) trackInteriorFreeType(constraint->scope, keyTy);
{ trackInteriorFreeType(constraint->scope, valueTy);
trackInteriorFreeType(constraint->scope, keyTy);
trackInteriorFreeType(constraint->scope, valueTy);
}
TypeId tableTy = TypeId tableTy =
arena->addType(TableType{TableType::Props{}, TableIndexer{keyTy, valueTy}, TypeLevel{}, constraint->scope, TableState::Free}); arena->addType(TableType{TableType::Props{}, TableIndexer{keyTy, valueTy}, TypeLevel{}, constraint->scope, TableState::Free});
if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInteriorFreeTablesOnScope) trackInteriorFreeType(constraint->scope, tableTy);
trackInteriorFreeType(constraint->scope, tableTy);
unify(constraint, nextTy, tableTy); unify(constraint, nextTy, tableTy);
@ -1322,29 +1337,14 @@ void ConstraintSolver::fillInDiscriminantTypes(NotNull<const Constraint> constra
if (!ty) if (!ty)
continue; continue;
if (FFlag::LuauSearchForRefineableType) if (isBlocked(*ty))
{
if (isBlocked(*ty))
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
// We also need to unconditionally unblock these types, otherwise
// you end up with funky looking "Blocked on *no-refine*."
unblock(*ty, constraint->location);
}
else
{
// If the discriminant type has been transmuted, we need to unblock them.
if (!isBlocked(*ty))
{
unblock(*ty, constraint->location);
continue;
}
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored. // We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType); emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
}
// We also need to unconditionally unblock these types, otherwise
// you end up with funky looking "Blocked on *no-refine*."
unblock(*ty, constraint->location);
} }
} }
@ -1457,9 +1457,9 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
if (ftv) if (ftv)
{ {
if (ftv->magic) if (ftv->magic && c.callSite)
{ {
usedMagic = ftv->magic->infer(MagicFunctionCallContext{NotNull{this}, constraint, c.callSite, c.argsPack, result}); usedMagic = ftv->magic->infer(MagicFunctionCallContext{NotNull{this}, constraint, NotNull{c.callSite}, c.argsPack, result});
ftv->magic->refine(MagicRefinementContext{constraint->scope, c.callSite, c.discriminantTypes}); ftv->magic->refine(MagicRefinementContext{constraint->scope, c.callSite, c.discriminantTypes});
} }
} }
@ -1534,7 +1534,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
// This can potentially contain free types if the return type of // This can potentially contain free types if the return type of
// `inferredTy` is never unified elsewhere. // `inferredTy` is never unified elsewhere.
if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInferredFunctionTypeFromCall) if (FFlag::LuauTrackInferredFunctionTypeFromCall)
trackInteriorFreeType(constraint->scope, inferredTy); trackInteriorFreeType(constraint->scope, inferredTy);
unblock(c.result, constraint->location); unblock(c.result, constraint->location);
@ -1903,7 +1903,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(
} }
else if (auto mt = get<MetatableType>(subjectType)) else if (auto mt = get<MetatableType>(subjectType))
return tryDispatchHasIndexer(recursionDepth, constraint, mt->table, indexType, resultType, seen); return tryDispatchHasIndexer(recursionDepth, constraint, mt->table, indexType, resultType, seen);
else if (auto ct = get<ClassType>(subjectType)) else if (auto ct = get<ExternType>(subjectType))
{ {
if (auto indexer = ct->indexer) if (auto indexer = ct->indexer)
{ {
@ -2065,9 +2065,9 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
// Important: In every codepath through this function, the type `c.propType` // Important: In every codepath through this function, the type `c.propType`
// must be bound to something, even if it's just the errorType. // must be bound to something, even if it's just the errorType.
if (auto lhsClass = get<ClassType>(lhsType)) if (auto lhsExternType = get<ExternType>(lhsType))
{ {
const Property* prop = lookupClassProp(lhsClass, propName); const Property* prop = lookupExternTypeProp(lhsExternType, propName);
if (!prop || !prop->writeTy.has_value()) if (!prop || !prop->writeTy.has_value())
{ {
bind(constraint, c.propType, builtinTypes->anyType); bind(constraint, c.propType, builtinTypes->anyType);
@ -2088,8 +2088,7 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
{ {
TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, constraint->scope}); TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, constraint->scope});
if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInteriorFreeTablesOnScope) trackInteriorFreeType(constraint->scope, newUpperBound);
trackInteriorFreeType(constraint->scope, newUpperBound);
TableType* upperTable = getMutable<TableType>(newUpperBound); TableType* upperTable = getMutable<TableType>(newUpperBound);
LUAU_ASSERT(upperTable); LUAU_ASSERT(upperTable);
@ -2252,20 +2251,20 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull<const
return *res; return *res;
} }
if (auto lhsClass = get<ClassType>(lhsType)) if (auto lhsExternType = get<ExternType>(lhsType))
{ {
while (true) while (true)
{ {
if (lhsClass->indexer) if (lhsExternType->indexer)
{ {
unify(constraint, indexType, lhsClass->indexer->indexType); unify(constraint, indexType, lhsExternType->indexer->indexType);
unify(constraint, rhsType, lhsClass->indexer->indexResultType); unify(constraint, rhsType, lhsExternType->indexer->indexResultType);
bind(constraint, c.propType, arena->addType(UnionType{{lhsClass->indexer->indexResultType, builtinTypes->nilType}})); bind(constraint, c.propType, arena->addType(UnionType{{lhsExternType->indexer->indexResultType, builtinTypes->nilType}}));
return true; return true;
} }
if (lhsClass->parent) if (lhsExternType->parent)
lhsClass = get<ClassType>(lhsClass->parent); lhsExternType = get<ExternType>(lhsExternType->parent);
else else
break; break;
} }
@ -2292,7 +2291,7 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull<const
parts.insert(rhsType); parts.insert(rhsType);
} }
} }
else if (auto cls = get<ClassType>(follow(t))) else if (auto cls = get<ExternType>(follow(t)))
{ {
while (true) while (true)
{ {
@ -2304,7 +2303,7 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull<const
} }
if (cls->parent) if (cls->parent)
cls = get<ClassType>(cls->parent); cls = get<ExternType>(cls->parent);
else else
break; break;
} }
@ -2355,8 +2354,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
// constitute any meaningful constraint, so we replace it // constitute any meaningful constraint, so we replace it
// with a free type. // with a free type.
TypeId f = freshType(arena, builtinTypes, constraint->scope, Polarity::Positive); // FIXME? Is this the right polarity? TypeId f = freshType(arena, builtinTypes, constraint->scope, Polarity::Positive); // FIXME? Is this the right polarity?
if (FFlag::LuauTrackInteriorFreeTypesOnScope) trackInteriorFreeType(constraint->scope, f);
trackInteriorFreeType(constraint->scope, f);
shiftReferences(resultTy, f); shiftReferences(resultTy, f);
emplaceType<BoundType>(asMutable(resultTy), f); emplaceType<BoundType>(asMutable(resultTy), f);
} }
@ -2499,11 +2497,8 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
{ {
TypeId keyTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed); TypeId keyTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed);
TypeId valueTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed); TypeId valueTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed);
if (FFlag::LuauTrackInteriorFreeTypesOnScope) trackInteriorFreeType(constraint->scope, keyTy);
{ trackInteriorFreeType(constraint->scope, valueTy);
trackInteriorFreeType(constraint->scope, keyTy);
trackInteriorFreeType(constraint->scope, valueTy);
}
TypeId tableTy = arena->addType(TableType{TableState::Sealed, {}, constraint->scope}); TypeId tableTy = arena->addType(TableType{TableState::Sealed, {}, constraint->scope});
getMutable<TableType>(tableTy)->indexer = TableIndexer{keyTy, valueTy}; getMutable<TableType>(tableTy)->indexer = TableIndexer{keyTy, valueTy};
@ -2653,33 +2648,53 @@ bool ConstraintSolver::tryDispatchIterableFunction(TypeId nextTy, TypeId tableTy
const FunctionType* nextFn = get<FunctionType>(nextTy); const FunctionType* nextFn = get<FunctionType>(nextTy);
// If this does not hold, we should've never called `tryDispatchIterableFunction` in the first place. // If this does not hold, we should've never called `tryDispatchIterableFunction` in the first place.
LUAU_ASSERT(nextFn); LUAU_ASSERT(nextFn);
const TypePackId nextRetPack = nextFn->retTypes;
// the type of the `nextAstFragment` is the `nextTy`. // the type of the `nextAstFragment` is the `nextTy`.
(*c.astForInNextTypes)[c.nextAstFragment] = nextTy; (*c.astForInNextTypes)[c.nextAstFragment] = nextTy;
auto it = begin(nextRetPack); if (FFlag::LuauAddCallConstraintForIterableFunctions)
std::vector<TypeId> modifiedNextRetHead;
// The first value is never nil in the context of the loop, even if it's nil
// in the next function's return type, because the loop will not advance if
// it's nil.
if (it != end(nextRetPack))
{ {
TypeId firstRet = *it; // Construct a FunctionCallConstraint, to help us learn about the type of the loop variables being assigned to in this iterable
TypeId modifiedFirstRet = stripNil(builtinTypes, *arena, firstRet); TypePackId tableTyPack = arena->addTypePack({tableTy});
modifiedNextRetHead.push_back(modifiedFirstRet);
++it; TypePackId variablesPack = arena->addTypePack(BlockedTypePack{});
auto callConstraint = pushConstraint(constraint->scope, constraint->location, FunctionCallConstraint{nextTy, tableTyPack, variablesPack});
getMutable<BlockedTypePack>(variablesPack)->owner = callConstraint.get();
auto unpackConstraint = unpackAndAssign(c.variables, variablesPack, constraint);
inheritBlocks(constraint, callConstraint);
inheritBlocks(unpackConstraint, callConstraint);
} }
else
{
const TypePackId nextRetPack = nextFn->retTypes;
TypePackIterator it = begin(nextRetPack);
std::vector<TypeId> modifiedNextRetHead;
for (; it != end(nextRetPack); ++it) // The first value is never nil in the context of the loop, even if it's nil
modifiedNextRetHead.push_back(*it); // in the next function's return type, because the loop will not advance if
// it's nil.
if (it != end(nextRetPack))
{
TypeId firstRet = *it;
TypeId modifiedFirstRet = stripNil(builtinTypes, *arena, firstRet);
modifiedNextRetHead.push_back(modifiedFirstRet);
++it;
}
TypePackId modifiedNextRetPack = arena->addTypePack(std::move(modifiedNextRetHead), it.tail()); for (; it != end(nextRetPack); ++it)
modifiedNextRetHead.push_back(*it);
auto unpackConstraint = unpackAndAssign(c.variables, modifiedNextRetPack, constraint); TypePackId modifiedNextRetPack = arena->addTypePack(std::move(modifiedNextRetHead), it.tail());
inheritBlocks(constraint, unpackConstraint); auto unpackConstraint = unpackAndAssign(c.variables, modifiedNextRetPack, constraint);
inheritBlocks(constraint, unpackConstraint);
}
return true; return true;
} }
@ -2760,8 +2775,7 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
if (ttv->state == TableState::Free) if (ttv->state == TableState::Free)
{ {
TypeId result = freshType(arena, builtinTypes, ttv->scope, Polarity::Mixed); TypeId result = freshType(arena, builtinTypes, ttv->scope, Polarity::Mixed);
if (FFlag::LuauTrackInteriorFreeTypesOnScope) trackInteriorFreeType(ttv->scope, result);
trackInteriorFreeType(ttv->scope, result);
switch (context) switch (context)
{ {
case ValueContext::RValue: case ValueContext::RValue:
@ -2835,9 +2849,9 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
else if (get<MetatableType>(mtt)) else if (get<MetatableType>(mtt))
return lookupTableProp(constraint, mtt, propName, context, inConditional, suppressSimplification, seen); return lookupTableProp(constraint, mtt, propName, context, inConditional, suppressSimplification, seen);
} }
else if (auto ct = get<ClassType>(subjectType)) else if (auto ct = get<ExternType>(subjectType))
{ {
if (auto p = lookupClassProp(ct, propName)) if (auto p = lookupExternTypeProp(ct, propName))
return {{}, context == ValueContext::RValue ? p->readTy : p->writeTy}; return {{}, context == ValueContext::RValue ? p->readTy : p->writeTy};
if (ct->indexer) if (ct->indexer)
{ {
@ -2862,21 +2876,18 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
if (get<TableType>(upperBound) || get<PrimitiveType>(upperBound)) if (get<TableType>(upperBound) || get<PrimitiveType>(upperBound))
return lookupTableProp(constraint, upperBound, propName, context, inConditional, suppressSimplification, seen); return lookupTableProp(constraint, upperBound, propName, context, inConditional, suppressSimplification, seen);
// TODO: The upper bound could be an intersection that contains suitable tables or classes. // TODO: The upper bound could be an intersection that contains suitable tables or extern types.
NotNull<Scope> scope{ft->scope}; NotNull<Scope> scope{ft->scope};
const TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, scope}); const TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, scope});
if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInteriorFreeTablesOnScope) trackInteriorFreeType(constraint->scope, newUpperBound);
trackInteriorFreeType(constraint->scope, newUpperBound);
TableType* tt = getMutable<TableType>(newUpperBound); TableType* tt = getMutable<TableType>(newUpperBound);
LUAU_ASSERT(tt); LUAU_ASSERT(tt);
TypeId propType = freshType(arena, builtinTypes, scope, Polarity::Mixed); TypeId propType = freshType(arena, builtinTypes, scope, Polarity::Mixed);
trackInteriorFreeType(scope, propType);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(scope, propType);
switch (context) switch (context)
{ {
@ -3095,7 +3106,7 @@ struct Blocker : TypeOnceVisitor
return false; return false;
} }
bool visit(TypeId ty, const ClassType&) override bool visit(TypeId ty, const ExternType&) override
{ {
return false; return false;
} }
@ -3471,4 +3482,35 @@ LUAU_NOINLINE void ConstraintSolver::throwUserCancelError() const
template bool ConstraintSolver::unify(NotNull<const Constraint> constraint, TypeId subTy, TypeId superTy); template bool ConstraintSolver::unify(NotNull<const Constraint> constraint, TypeId subTy, TypeId superTy);
template bool ConstraintSolver::unify(NotNull<const Constraint> constraint, TypePackId subTy, TypePackId superTy); template bool ConstraintSolver::unify(NotNull<const Constraint> constraint, TypePackId subTy, TypePackId superTy);
std::vector<NotNull<Constraint>> borrowConstraints(const std::vector<ConstraintPtr>& constraints)
{
std::vector<NotNull<Constraint>> result;
result.reserve(constraints.size());
for (const auto& c : constraints)
result.emplace_back(c.get());
return result;
}
void dump(ConstraintSolver* cs, ToStringOptions& opts)
{
printf("constraints:\n");
for (NotNull<const Constraint> c : cs->unsolvedConstraints)
{
auto it = cs->blockedConstraints.find(c);
int blockCount = it == cs->blockedConstraints.end() ? 0 : int(it->second);
printf("\t%d\t%s\n", blockCount, toString(*c, opts).c_str());
if (FFlag::DebugLuauLogSolverIncludeDependencies)
{
for (NotNull<Constraint> dep : c->dependencies)
{
if (std::find(cs->unsolvedConstraints.begin(), cs->unsolvedConstraints.end(), dep) != cs->unsolvedConstraints.end())
printf("\t\t|\t%s\n", toString(*dep, opts).c_str());
}
}
}
}
} // namespace Luau } // namespace Luau

View file

@ -16,6 +16,8 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauPreprocessTypestatedArgument) LUAU_FASTFLAGVARIABLE(LuauPreprocessTypestatedArgument)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackTrueReset) LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackTrueReset)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull) LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau namespace Luau
{ {
@ -470,7 +472,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStat* s)
return visit(d); return visit(d);
else if (auto d = s->as<AstStatDeclareFunction>()) else if (auto d = s->as<AstStatDeclareFunction>())
return visit(d); return visit(d);
else if (auto d = s->as<AstStatDeclareClass>()) else if (auto d = s->as<AstStatDeclareExternType>())
return visit(d); return visit(d);
else if (auto error = s->as<AstStatError>()) else if (auto error = s->as<AstStatError>())
return visit(error); return visit(error);
@ -808,12 +810,15 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareFunction* d)
visitGenerics(d->generics); visitGenerics(d->generics);
visitGenericPacks(d->genericPacks); visitGenericPacks(d->genericPacks);
visitTypeList(d->params); visitTypeList(d->params);
visitTypeList(d->retTypes); if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visitTypePack(d->retTypes);
else
visitTypeList(d->retTypes_DEPRECATED);
return ControlFlow::None; return ControlFlow::None;
} }
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareClass* d) ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareExternType* d)
{ {
// This declaration does not "introduce" any bindings in value namespace, // This declaration does not "introduce" any bindings in value namespace,
// so there's no symbolic value to begin with. We'll traverse the properties // so there's no symbolic value to begin with. We'll traverse the properties
@ -821,7 +826,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareClass* d)
DfgScope* unreachable = makeChildScope(); DfgScope* unreachable = makeChildScope();
PushScope ps{scopeStack, unreachable}; PushScope ps{scopeStack, unreachable};
for (AstDeclaredClassProp prop : d->props) for (AstDeclaredExternTypeProperty prop : d->props)
visitType(prop.ty); visitType(prop.ty);
return ControlFlow::None; return ControlFlow::None;
@ -1032,8 +1037,16 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
if (f->varargAnnotation) if (f->varargAnnotation)
visitTypePack(f->varargAnnotation); visitTypePack(f->varargAnnotation);
if (f->returnAnnotation) if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visitTypeList(*f->returnAnnotation); {
if (f->returnAnnotation)
visitTypePack(f->returnAnnotation);
}
else
{
if (f->returnAnnotation_DEPRECATED)
visitTypeList(*f->returnAnnotation_DEPRECATED);
}
// TODO: function body can be re-entrant, as in mutations that occurs at the end of the function can also be // TODO: function body can be re-entrant, as in mutations that occurs at the end of the function can also be
// visible to the beginning of the function, so statically speaking, the body of the function has an exit point // visible to the beginning of the function, so statically speaking, the body of the function has an exit point
@ -1275,7 +1288,10 @@ void DataFlowGraphBuilder::visitType(AstTypeFunction* f)
visitGenerics(f->generics); visitGenerics(f->generics);
visitGenericPacks(f->genericPacks); visitGenericPacks(f->genericPacks);
visitTypeList(f->argTypes); visitTypeList(f->argTypes);
visitTypeList(f->returnTypes); if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visitTypePack(f->returnTypes);
else
visitTypeList(f->returnTypes_DEPRECATED);
} }
void DataFlowGraphBuilder::visitType(AstTypeTypeof* t) void DataFlowGraphBuilder::visitType(AstTypeTypeof* t)

View file

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

View file

@ -1,6 +1,8 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAG(LuauDeclareExternType)
namespace Luau namespace Luau
{ {
@ -259,7 +261,37 @@ declare buffer: {
)BUILTIN_SRC"; )BUILTIN_SRC";
static const std::string kBuiltinDefinitionVectorSrc = R"BUILTIN_SRC( static const std::string kBuiltinDefinitionVectorSrc = (FFlag::LuauDeclareExternType)
? R"BUILTIN_SRC(
-- While vector would have been better represented as a built-in primitive type, type solver extern type handling covers most of the properties
declare extern type vector with
x: number
y: number
z: number
end
declare vector: {
create: @checked (x: number, y: number, z: number?) -> vector,
magnitude: @checked (vec: vector) -> number,
normalize: @checked (vec: vector) -> vector,
cross: @checked (vec1: vector, vec2: vector) -> vector,
dot: @checked (vec1: vector, vec2: vector) -> number,
angle: @checked (vec1: vector, vec2: vector, axis: vector?) -> number,
floor: @checked (vec: vector) -> vector,
ceil: @checked (vec: vector) -> vector,
abs: @checked (vec: vector) -> vector,
sign: @checked (vec: vector) -> vector,
clamp: @checked (vec: vector, min: vector, max: vector) -> vector,
max: @checked (vector, ...vector) -> vector,
min: @checked (vector, ...vector) -> vector,
zero: vector,
one: vector,
}
)BUILTIN_SRC"
: R"BUILTIN_SRC(
-- While vector would have been better represented as a built-in primitive type, type solver class handling covers most of the properties -- While vector would have been better represented as a built-in primitive type, type solver class handling covers most of the properties
declare class vector declare class vector

View file

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

View file

@ -203,7 +203,7 @@ struct ErrorConverter
TypeId t = follow(e.table); TypeId t = follow(e.table);
if (get<TableType>(t)) if (get<TableType>(t))
return "Key '" + e.key + "' not found in table '" + Luau::toString(t) + "'"; return "Key '" + e.key + "' not found in table '" + Luau::toString(t) + "'";
else if (get<ClassType>(t)) else if (get<ExternType>(t))
return "Key '" + e.key + "' not found in class '" + Luau::toString(t) + "'"; return "Key '" + e.key + "' not found in class '" + Luau::toString(t) + "'";
else else
return "Type '" + Luau::toString(e.table) + "' does not have key '" + e.key + "'"; return "Type '" + Luau::toString(e.table) + "' does not have key '" + e.key + "'";
@ -371,7 +371,7 @@ struct ErrorConverter
std::string s = "Key '" + e.key + "' not found in "; std::string s = "Key '" + e.key + "' not found in ";
TypeId t = follow(e.table); TypeId t = follow(e.table);
if (get<ClassType>(t)) if (get<ExternType>(t))
s += "class"; s += "class";
else else
s += "table"; s += "table";
@ -402,8 +402,8 @@ struct ErrorConverter
std::optional<TypeId> metatable; std::optional<TypeId> metatable;
if (const MetatableType* mtType = get<MetatableType>(type)) if (const MetatableType* mtType = get<MetatableType>(type))
metatable = mtType->metatable; metatable = mtType->metatable;
else if (const ClassType* classType = get<ClassType>(type)) else if (const ExternType* externType = get<ExternType>(type))
metatable = classType->metatable; metatable = externType->metatable;
if (!metatable) if (!metatable)
return std::nullopt; return std::nullopt;
@ -611,7 +611,7 @@ struct ErrorConverter
return ss; return ss;
} }
std::string operator()(const DynamicPropertyLookupOnClassesUnsafe& e) const std::string operator()(const DynamicPropertyLookupOnExternTypesUnsafe& e) const
{ {
return "Attempting a dynamic property access on type '" + Luau::toString(e.ty) + "' is unsafe and may cause exceptions at runtime"; return "Attempting a dynamic property access on type '" + Luau::toString(e.ty) + "' is unsafe and may cause exceptions at runtime";
} }
@ -1149,7 +1149,7 @@ bool TypePackMismatch::operator==(const TypePackMismatch& rhs) const
return *wantedTp == *rhs.wantedTp && *givenTp == *rhs.givenTp; return *wantedTp == *rhs.wantedTp && *givenTp == *rhs.givenTp;
} }
bool DynamicPropertyLookupOnClassesUnsafe::operator==(const DynamicPropertyLookupOnClassesUnsafe& rhs) const bool DynamicPropertyLookupOnExternTypesUnsafe::operator==(const DynamicPropertyLookupOnExternTypesUnsafe& rhs) const
{ {
return ty == rhs.ty; return ty == rhs.ty;
} }
@ -1391,7 +1391,7 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
e.wantedTp = clone(e.wantedTp); e.wantedTp = clone(e.wantedTp);
e.givenTp = clone(e.givenTp); e.givenTp = clone(e.givenTp);
} }
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnClassesUnsafe>) else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnExternTypesUnsafe>)
e.ty = clone(e.ty); e.ty = clone(e.ty);
else if constexpr (std::is_same_v<T, UninhabitedTypeFunction>) else if constexpr (std::is_same_v<T, UninhabitedTypeFunction>)
e.ty = clone(e.ty); e.ty = clone(e.ty);

View file

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

View file

@ -27,7 +27,6 @@
LUAU_FASTINT(LuauTypeInferRecursionLimit); LUAU_FASTINT(LuauTypeInferRecursionLimit);
LUAU_FASTINT(LuauTypeInferIterationLimit); LUAU_FASTINT(LuauTypeInferIterationLimit);
LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauMixedModeDefFinderTraversesTypeOf) LUAU_FASTFLAGVARIABLE(LuauMixedModeDefFinderTraversesTypeOf)
LUAU_FASTFLAGVARIABLE(LuauCloneIncrementalModule) LUAU_FASTFLAGVARIABLE(LuauCloneIncrementalModule)
@ -43,6 +42,7 @@ LUAU_FASTFLAGVARIABLE(LuauBetterScopeSelection)
LUAU_FASTFLAGVARIABLE(LuauBlockDiffFragmentSelection) LUAU_FASTFLAGVARIABLE(LuauBlockDiffFragmentSelection)
LUAU_FASTFLAGVARIABLE(LuauFragmentAcMemoryLeak) LUAU_FASTFLAGVARIABLE(LuauFragmentAcMemoryLeak)
LUAU_FASTFLAGVARIABLE(LuauGlobalVariableModuleIsolation) LUAU_FASTFLAGVARIABLE(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace namespace
{ {
@ -97,7 +97,11 @@ Location getFunctionDeclarationExtents(AstExprFunction* exprFn, AstExpr* exprNam
{ {
auto fnBegin = exprFn->location.begin; auto fnBegin = exprFn->location.begin;
auto fnEnd = exprFn->location.end; auto fnEnd = exprFn->location.end;
if (auto returnAnnot = exprFn->returnAnnotation) if (auto returnAnnot = exprFn->returnAnnotation; FFlag::LuauStoreReturnTypesAsPackOnAst && returnAnnot)
{
fnEnd = returnAnnot->location.end;
}
else if (auto returnAnnot = exprFn->returnAnnotation_DEPRECATED; !FFlag::LuauStoreReturnTypesAsPackOnAst && returnAnnot)
{ {
if (returnAnnot->tailType) if (returnAnnot->tailType)
fnEnd = returnAnnot->tailType->location.end; fnEnd = returnAnnot->tailType->location.end;
@ -542,6 +546,11 @@ struct UsageFinder : public AstVisitor
return true; return true;
} }
bool visit(AstTypePack* node) override
{
return FFlag::LuauStoreReturnTypesAsPackOnAst;
}
bool visit(AstStatTypeAlias* alias) override bool visit(AstStatTypeAlias* alias) override
{ {
declaredAliases.insert(std::string(alias->name.value)); declaredAliases.insert(std::string(alias->name.value));
@ -1706,7 +1715,6 @@ FragmentAutocompleteResult fragmentAutocomplete(
IFragmentAutocompleteReporter* reporter IFragmentAutocompleteReporter* reporter
) )
{ {
LUAU_ASSERT(FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete);
LUAU_TIMETRACE_SCOPE("Luau::fragmentAutocomplete", "FragmentAutocomplete"); LUAU_TIMETRACE_SCOPE("Luau::fragmentAutocomplete", "FragmentAutocomplete");
LUAU_TIMETRACE_ARGUMENT("name", moduleName.c_str()); LUAU_TIMETRACE_ARGUMENT("name", moduleName.c_str());

View file

@ -40,6 +40,7 @@ LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3) LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRethrowKnownExceptions, false) LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRethrowKnownExceptions, false)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile)
LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes) LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes)
@ -128,9 +129,9 @@ static void generateDocumentationSymbols(TypeId ty, const std::string& rootName)
prop.documentationSymbol = rootName + "." + name; prop.documentationSymbol = rootName + "." + name;
} }
} }
else if (ClassType* ctv = getMutable<ClassType>(ty)) else if (ExternType* etv = getMutable<ExternType>(ty))
{ {
for (auto& [name, prop] : ctv->props) for (auto& [name, prop] : etv->props)
{ {
prop.documentationSymbol = rootName + "." + name; prop.documentationSymbol = rootName + "." + name;
} }
@ -1304,7 +1305,7 @@ ModulePtr check(
struct InternalTypeFinder : TypeOnceVisitor struct InternalTypeFinder : TypeOnceVisitor
{ {
bool visit(TypeId, const ClassType&) override bool visit(TypeId, const ExternType&) override
{ {
return false; return false;
} }
@ -1419,30 +1420,59 @@ ModulePtr check(
requireCycles requireCycles
}; };
cg.visitModuleRoot(sourceModule.root); // FIXME: Delete this flag when clipping FFlag::DebugLuauGreedyGeneralization.
result->errors = std::move(cg.errors); //
// This optional<> only exists so that we can run one constructor when the flag
// is set, and another when it is unset.
std::optional<ConstraintSolver> cs;
ConstraintSolver cs{ if (FFlag::DebugLuauGreedyGeneralization)
NotNull{&normalizer}, {
NotNull{simplifier.get()}, ConstraintSet constraintSet = cg.run(sourceModule.root);
NotNull{&typeFunctionRuntime}, result->errors = std::move(constraintSet.errors);
NotNull(cg.rootScope),
borrowConstraints(cg.constraints), cs.emplace(
NotNull{&cg.scopeToFunction}, NotNull{&normalizer},
result->name, NotNull{simplifier.get()},
moduleResolver, NotNull{&typeFunctionRuntime},
requireCycles, result->name,
logger.get(), moduleResolver,
NotNull{&dfg}, requireCycles,
limits logger.get(),
}; NotNull{&dfg},
limits,
std::move(constraintSet)
);
}
else
{
cg.visitModuleRoot(sourceModule.root);
result->errors = std::move(cg.errors);
cs.emplace(
NotNull{&normalizer},
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime},
NotNull(cg.rootScope),
borrowConstraints(cg.constraints),
NotNull{&cg.scopeToFunction},
result->name,
moduleResolver,
requireCycles,
logger.get(),
NotNull{&dfg},
limits
);
}
LUAU_ASSERT(bool(cs));
if (options.randomizeConstraintResolutionSeed) if (options.randomizeConstraintResolutionSeed)
cs.randomize(*options.randomizeConstraintResolutionSeed); cs->randomize(*options.randomizeConstraintResolutionSeed);
try try
{ {
cs.run(); cs->run();
} }
catch (const TimeLimitError&) catch (const TimeLimitError&)
{ {
@ -1462,12 +1492,12 @@ ModulePtr check(
printf("%s\n", output.c_str()); printf("%s\n", output.c_str());
} }
for (TypeError& e : cs.errors) for (TypeError& e : cs->errors)
result->errors.emplace_back(std::move(e)); result->errors.emplace_back(std::move(e));
result->scopes = std::move(cg.scopes); result->scopes = std::move(cg.scopes);
result->type = sourceModule.type; result->type = sourceModule.type;
result->upperBoundContributors = std::move(cs.upperBoundContributors); result->upperBoundContributors = std::move(cs->upperBoundContributors);
if (result->timeout || result->cancelled) if (result->timeout || result->cancelled)
{ {

View file

@ -15,7 +15,6 @@
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauNonReentrantGeneralization2) LUAU_FASTFLAGVARIABLE(LuauNonReentrantGeneralization2)
@ -549,7 +548,7 @@ struct FreeTypeSearcher : TypeVisitor
traverse(*prop.readTy); traverse(*prop.readTy);
else else
{ {
LUAU_ASSERT(prop.isShared() || FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete); LUAU_ASSERT(prop.isShared());
Polarity p = polarity; Polarity p = polarity;
polarity = Polarity::Mixed; polarity = Polarity::Mixed;
@ -605,7 +604,7 @@ struct FreeTypeSearcher : TypeVisitor
return false; return false;
} }
bool visit(TypeId, const ClassType&) override bool visit(TypeId, const ExternType&) override
{ {
return false; return false;
} }
@ -897,7 +896,7 @@ struct TypeCacher : TypeOnceVisitor
return false; return false;
} }
bool visit(TypeId ty, const ClassType&) override bool visit(TypeId ty, const ExternType&) override
{ {
cache(ty); cache(ty);
return false; return false;
@ -1513,26 +1512,22 @@ void pruneUnnecessaryGenerics(
if (!functionTy) if (!functionTy)
return; return;
// Types (and packs) to be removed from the generics list // If a generic has no explicit name and is only referred to in one place in
DenseHashSet<TypeId> clipTypes{nullptr}; // the function's signature, it can be replaced with unknown.
DenseHashSet<TypePackId> clipTypePacks{nullptr};
GenericCounter counter{cachedTypes}; GenericCounter counter{cachedTypes};
for (TypeId generic : functionTy->generics) for (TypeId generic : functionTy->generics)
{ {
generic = follow(generic);
auto g = get<GenericType>(generic); auto g = get<GenericType>(generic);
LUAU_ASSERT(g); if (g && !g->explicitName)
if (!g)
clipTypes.insert(generic);
else if (!g->explicitName)
counter.generics[generic] = 0; counter.generics[generic] = 0;
} }
for (TypePackId genericPack : functionTy->genericPacks) for (TypePackId genericPack : functionTy->genericPacks)
{ {
genericPack = follow(genericPack);
auto g = get<GenericTypePack>(genericPack); auto g = get<GenericTypePack>(genericPack);
if (!g) if (g && !g->explicitName)
clipTypePacks.insert(genericPack);
else if (!g->explicitName)
counter.genericPacks[genericPack] = 0; counter.genericPacks[genericPack] = 0;
} }
@ -1541,18 +1536,22 @@ void pruneUnnecessaryGenerics(
for (const auto& [generic, count] : counter.generics) for (const auto& [generic, count] : counter.generics)
{ {
if (count == 1) if (count == 1)
{
emplaceType<BoundType>(asMutable(generic), builtinTypes->unknownType); emplaceType<BoundType>(asMutable(generic), builtinTypes->unknownType);
clipTypes.insert(generic);
}
} }
// Remove duplicates and types that aren't actually generics.
DenseHashSet<TypeId> seen{nullptr};
auto it = std::remove_if( auto it = std::remove_if(
functionTy->generics.begin(), functionTy->generics.begin(),
functionTy->generics.end(), functionTy->generics.end(),
[&](TypeId ty) [&](TypeId ty)
{ {
return clipTypes.contains(ty); ty = follow(ty);
if (seen.contains(ty))
return true;
seen.insert(ty);
return !get<GenericType>(ty);
} }
); );
@ -1561,18 +1560,21 @@ void pruneUnnecessaryGenerics(
for (const auto& [genericPack, count] : counter.genericPacks) for (const auto& [genericPack, count] : counter.genericPacks)
{ {
if (count == 1) if (count == 1)
{
emplaceTypePack<BoundTypePack>(asMutable(genericPack), builtinTypes->unknownTypePack); emplaceTypePack<BoundTypePack>(asMutable(genericPack), builtinTypes->unknownTypePack);
clipTypePacks.insert(genericPack);
}
} }
DenseHashSet<TypePackId> seen2{nullptr};
auto it2 = std::remove_if( auto it2 = std::remove_if(
functionTy->genericPacks.begin(), functionTy->genericPacks.begin(),
functionTy->genericPacks.end(), functionTy->genericPacks.end(),
[&](TypePackId tp) [&](TypePackId tp)
{ {
return clipTypePacks.contains(tp); tp = follow(tp);
if (seen2.contains(tp))
return true;
seen2.insert(tp);
return !get<GenericTypePack>(tp);
} }
); );

View file

@ -95,16 +95,16 @@ struct InferPolarity : TypeVisitor
// types. // types.
for (TypeId generic : ft.generics) for (TypeId generic : ft.generics)
{ {
generic = follow(generic);
const auto gen = get<GenericType>(generic); const auto gen = get<GenericType>(generic);
LUAU_ASSERT(gen); if (gen && subsumes(scope, gen->scope))
if (subsumes(scope, gen->scope))
types[generic] = Polarity::None; types[generic] = Polarity::None;
} }
for (TypePackId genericPack : ft.genericPacks) for (TypePackId genericPack : ft.genericPacks)
{ {
genericPack = follow(genericPack);
const auto gen = get<GenericTypePack>(genericPack); const auto gen = get<GenericTypePack>(genericPack);
LUAU_ASSERT(gen); if (gen && subsumes(scope, gen->scope))
if (subsumes(scope, gen->scope))
packs[genericPack] = Polarity::None; packs[genericPack] = Polarity::None;
} }
@ -118,7 +118,7 @@ struct InferPolarity : TypeVisitor
return false; return false;
} }
bool visit(TypeId, const ClassType&) override bool visit(TypeId, const ExternType&) override
{ {
return false; return false;
} }

View file

@ -50,7 +50,7 @@ bool Instantiation::ignoreChildren(TypeId ty)
{ {
if (log->getMutable<FunctionType>(ty)) if (log->getMutable<FunctionType>(ty))
return true; return true;
else if (get<ClassType>(ty)) else if (get<ExternType>(ty))
return true; return true;
else else
return false; return false;
@ -120,7 +120,7 @@ bool ReplaceGenerics::ignoreChildren(TypeId ty)
// whenever we quantify, so the vectors overlap if and only if they are equal. // whenever we quantify, so the vectors overlap if and only if they are equal.
return (!generics.empty() || !genericPacks.empty()) && (ftv->generics == generics) && (ftv->genericPacks == genericPacks); return (!generics.empty() || !genericPacks.empty()) && (ftv->generics == generics) && (ftv->genericPacks == genericPacks);
} }
else if (get<ClassType>(ty)) else if (get<ExternType>(ty))
return true; return true;
else else
{ {

View file

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

View file

@ -193,8 +193,8 @@ static void errorToString(std::ostream& stream, const T& err)
stream << "NormalizationTooComplex { }"; stream << "NormalizationTooComplex { }";
else if constexpr (std::is_same_v<T, TypePackMismatch>) else if constexpr (std::is_same_v<T, TypePackMismatch>)
stream << "TypePackMismatch { wanted = '" + toString(err.wantedTp) + "', given = '" + toString(err.givenTp) + "' }"; stream << "TypePackMismatch { wanted = '" + toString(err.wantedTp) + "', given = '" + toString(err.givenTp) + "' }";
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnClassesUnsafe>) else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnExternTypesUnsafe>)
stream << "DynamicPropertyLookupOnClassesUnsafe { " << toString(err.ty) << " }"; stream << "DynamicPropertyLookupOnExternTypesUnsafe { " << toString(err.ty) << " }";
else if constexpr (std::is_same_v<T, UninhabitedTypeFunction>) else if constexpr (std::is_same_v<T, UninhabitedTypeFunction>)
stream << "UninhabitedTypeFunction { " << toString(err.ty) << " }"; stream << "UninhabitedTypeFunction { " << toString(err.ty) << " }";
else if constexpr (std::is_same_v<T, ExplicitFunctionAnnotationRecommended>) else if constexpr (std::is_same_v<T, ExplicitFunctionAnnotationRecommended>)

View file

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

View file

@ -26,6 +26,7 @@ LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements) LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictVisitTypes) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictVisitTypes)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau namespace Luau
{ {
@ -311,7 +312,7 @@ struct NonStrictTypeChecker
return visit(s); return visit(s);
else if (auto s = stat->as<AstStatDeclareGlobal>()) else if (auto s = stat->as<AstStatDeclareGlobal>())
return visit(s); return visit(s);
else if (auto s = stat->as<AstStatDeclareClass>()) else if (auto s = stat->as<AstStatDeclareExternType>())
return visit(s); return visit(s);
else if (auto s = stat->as<AstStatError>()) else if (auto s = stat->as<AstStatError>())
return visit(s); return visit(s);
@ -544,7 +545,7 @@ struct NonStrictTypeChecker
return {}; return {};
} }
NonStrictContext visit(AstStatDeclareClass* declClass) NonStrictContext visit(AstStatDeclareExternType* declClass)
{ {
if (FFlag::LuauNewNonStrictVisitTypes) if (FFlag::LuauNewNonStrictVisitTypes)
{ {
@ -833,8 +834,16 @@ struct NonStrictTypeChecker
{ {
visitGenerics(exprFn->generics, exprFn->genericPacks); visitGenerics(exprFn->generics, exprFn->genericPacks);
if (exprFn->returnAnnotation) if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visit(*exprFn->returnAnnotation); {
if (exprFn->returnAnnotation)
visit(exprFn->returnAnnotation);
}
else
{
if (exprFn->returnAnnotation_DEPRECATED)
visit(*exprFn->returnAnnotation_DEPRECATED);
}
if (exprFn->varargAnnotation) if (exprFn->varargAnnotation)
visit(exprFn->varargAnnotation); visit(exprFn->varargAnnotation);

View file

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

View file

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

View file

@ -356,7 +356,7 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
// * FunctionType // * FunctionType
// * TableType // * TableType
// * MetatableType // * MetatableType
// * ClassType // * ExternType
// * UnionType // * UnionType
// * IntersectionType // * IntersectionType
// * NegationType // * NegationType
@ -476,13 +476,13 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
return Relation::Disjoint; return Relation::Disjoint;
} }
if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ClassType>(right)) if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ExternType>(right))
return Relation::Disjoint; return Relation::Disjoint;
} }
if (auto ls = get<SingletonType>(left)) if (auto ls = get<SingletonType>(left))
{ {
if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ClassType>(right)) if (get<FunctionType>(right) || get<TableType>(right) || get<MetatableType>(right) || get<ExternType>(right))
return Relation::Disjoint; return Relation::Disjoint;
if (get<PrimitiveType>(right)) if (get<PrimitiveType>(right))
@ -551,9 +551,9 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
return Relation::Disjoint; return Relation::Disjoint;
} }
if (auto ct = get<ClassType>(left)) if (auto ct = get<ExternType>(left))
{ {
if (auto rct = get<ClassType>(right)) if (auto rct = get<ExternType>(right))
{ {
if (isSubclass(ct, rct)) if (isSubclass(ct, rct))
return Relation::Subset; return Relation::Subset;
@ -873,7 +873,7 @@ std::optional<TypeId> TypeSimplifier::basicIntersectWithTruthy(TypeId target) co
if (is<NeverType, ErrorType>(target)) if (is<NeverType, ErrorType>(target))
return target; return target;
if (is<FunctionType, TableType, MetatableType, ClassType>(target)) if (is<FunctionType, TableType, MetatableType, ExternType>(target))
return target; return target;
if (auto pt = get<PrimitiveType>(target)) if (auto pt = get<PrimitiveType>(target))
@ -909,7 +909,7 @@ std::optional<TypeId> TypeSimplifier::basicIntersectWithFalsy(TypeId target) con
if (is<UnknownType>(target)) if (is<UnknownType>(target))
return builtinTypes->falsyType; return builtinTypes->falsyType;
if (is<FunctionType, TableType, MetatableType, ClassType>(target)) if (is<FunctionType, TableType, MetatableType, ExternType>(target))
return builtinTypes->neverType; return builtinTypes->neverType;
if (auto pt = get<PrimitiveType>(target)) if (auto pt = get<PrimitiveType>(target))

View file

@ -136,9 +136,9 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log)
clone.parts = a.parts; clone.parts = a.parts;
return dest.addType(std::move(clone)); return dest.addType(std::move(clone));
} }
else if constexpr (std::is_same_v<T, ClassType>) else if constexpr (std::is_same_v<T, ExternType>)
{ {
ClassType clone{a.name, a.props, a.parent, a.metatable, a.tags, a.userData, a.definitionModuleName, a.definitionLocation, a.indexer}; ExternType 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)); return dest.addType(std::move(clone));
} }
else if constexpr (std::is_same_v<T, NegationType>) else if constexpr (std::is_same_v<T, NegationType>)
@ -252,21 +252,21 @@ void Tarjan::visitChildren(TypeId ty, int index)
for (TypePackId a : tfit->packArguments) for (TypePackId a : tfit->packArguments)
visitChild(a); visitChild(a);
} }
else if (const ClassType* ctv = get<ClassType>(ty)) else if (const ExternType* etv = get<ExternType>(ty))
{ {
for (const auto& [name, prop] : ctv->props) for (const auto& [name, prop] : etv->props)
visitChild(prop.type()); visitChild(prop.type());
if (ctv->parent) if (etv->parent)
visitChild(*ctv->parent); visitChild(*etv->parent);
if (ctv->metatable) if (etv->metatable)
visitChild(*ctv->metatable); visitChild(*etv->metatable);
if (ctv->indexer) if (etv->indexer)
{ {
visitChild(ctv->indexer->indexType); visitChild(etv->indexer->indexType);
visitChild(ctv->indexer->indexResultType); visitChild(etv->indexer->indexResultType);
} }
} }
else if (const NegationType* ntv = get<NegationType>(ty)) else if (const NegationType* ntv = get<NegationType>(ty))
@ -838,21 +838,21 @@ void Substitution::replaceChildren(TypeId ty)
for (TypePackId& a : tfit->packArguments) for (TypePackId& a : tfit->packArguments)
a = replace(a); a = replace(a);
} }
else if (ClassType* ctv = getMutable<ClassType>(ty)) else if (ExternType* etv = getMutable<ExternType>(ty))
{ {
for (auto& [name, prop] : ctv->props) for (auto& [name, prop] : etv->props)
prop.setType(replace(prop.type())); prop.setType(replace(prop.type()));
if (ctv->parent) if (etv->parent)
ctv->parent = replace(*ctv->parent); etv->parent = replace(*etv->parent);
if (ctv->metatable) if (etv->metatable)
ctv->metatable = replace(*ctv->metatable); etv->metatable = replace(*etv->metatable);
if (ctv->indexer) if (etv->indexer)
{ {
ctv->indexer->indexType = replace(ctv->indexer->indexType); etv->indexer->indexType = replace(etv->indexer->indexType);
ctv->indexer->indexResultType = replace(ctv->indexer->indexResultType); etv->indexer->indexResultType = replace(etv->indexer->indexResultType);
} }
} }
else if (NegationType* ntv = getMutable<NegationType>(ty)) else if (NegationType* ntv = getMutable<NegationType>(ty))

View file

@ -315,7 +315,7 @@ struct ApplyMappedGenerics : Substitution
bool ignoreChildren(TypeId ty) override bool ignoreChildren(TypeId ty) override
{ {
if (get<ClassType>(ty)) if (get<ExternType>(ty))
return true; return true;
return ty->persistent; return ty->persistent;
@ -744,9 +744,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
result = isCovariantWith(env, p, scope); result = isCovariantWith(env, p, scope);
else if (auto p = get2<MetatableType, TableType>(subTy, superTy)) else if (auto p = get2<MetatableType, TableType>(subTy, superTy))
result = isCovariantWith(env, p, scope); result = isCovariantWith(env, p, scope);
else if (auto p = get2<ClassType, ClassType>(subTy, superTy)) else if (auto p = get2<ExternType, ExternType>(subTy, superTy))
result = isCovariantWith(env, p, scope); result = isCovariantWith(env, p, scope);
else if (auto p = get2<ClassType, TableType>(subTy, superTy)) else if (auto p = get2<ExternType, TableType>(subTy, superTy))
result = isCovariantWith(env, subTy, p.first, superTy, p.second, scope); result = isCovariantWith(env, subTy, p.first, superTy, p.second, scope);
else if (auto p = get2<TableType, PrimitiveType>(subTy, superTy)) else if (auto p = get2<TableType, PrimitiveType>(subTy, superTy))
result = isCovariantWith(env, p, scope); result = isCovariantWith(env, p, scope);
@ -1336,7 +1336,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
} }
// the top class type is not actually a primitive type, so the negation of // the top class type is not actually a primitive type, so the negation of
// any one of them includes the top class type. // any one of them includes the top class type.
else if (auto p = get2<ClassType, PrimitiveType>(subTy, negatedTy)) else if (auto p = get2<ExternType, PrimitiveType>(subTy, negatedTy))
result = {true}; result = {true};
else if (auto p = get<PrimitiveType>(negatedTy); p && is<TableType, MetatableType>(subTy)) else if (auto p = get<PrimitiveType>(negatedTy); p && is<TableType, MetatableType>(subTy))
result = {p->type != PrimitiveType::Table}; result = {p->type != PrimitiveType::Table};
@ -1344,9 +1344,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
result = {p.second->type != PrimitiveType::Function}; result = {p.second->type != PrimitiveType::Function};
else if (auto p = get2<SingletonType, SingletonType>(subTy, negatedTy)) else if (auto p = get2<SingletonType, SingletonType>(subTy, negatedTy))
result = {*p.first != *p.second}; result = {*p.first != *p.second};
else if (auto p = get2<ClassType, ClassType>(subTy, negatedTy)) else if (auto p = get2<ExternType, ExternType>(subTy, negatedTy))
result = SubtypingResult::negate(isCovariantWith(env, p.first, p.second, scope)); result = SubtypingResult::negate(isCovariantWith(env, p.first, p.second, scope));
else if (get2<FunctionType, ClassType>(subTy, negatedTy)) else if (get2<FunctionType, ExternType>(subTy, negatedTy))
result = {true}; result = {true};
else if (is<ErrorType, FunctionType, TableType, MetatableType>(negatedTy)) else if (is<ErrorType, FunctionType, TableType, MetatableType>(negatedTy))
iceReporter->ice("attempting to negate a non-testable type"); iceReporter->ice("attempting to negate a non-testable type");
@ -1471,15 +1471,15 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Meta
} }
} }
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass, NotNull<Scope> scope) SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const ExternType* subExternType, const ExternType* superExternType, NotNull<Scope> scope)
{ {
return {isSubclass(subClass, superClass)}; return {isSubclass(subExternType, superExternType)};
} }
SubtypingResult Subtyping::isCovariantWith( SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env, SubtypingEnvironment& env,
TypeId subTy, TypeId subTy,
const ClassType* subClass, const ExternType* subExternType,
TypeId superTy, TypeId superTy,
const TableType* superTable, const TableType* superTable,
NotNull<Scope> scope NotNull<Scope> scope
@ -1491,7 +1491,7 @@ SubtypingResult Subtyping::isCovariantWith(
for (const auto& [name, prop] : superTable->props) for (const auto& [name, prop] : superTable->props)
{ {
if (auto classProp = lookupClassProp(subClass, name)) if (auto classProp = lookupExternTypeProp(subExternType, name))
{ {
result.andAlso(isCovariantWith(env, *classProp, prop, name, scope)); result.andAlso(isCovariantWith(env, *classProp, prop, name, scope));
} }
@ -1661,7 +1661,7 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops, scope); SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops, scope);
result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans, scope)); result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans, scope));
result.andAlso( result.andAlso(
isCovariantWith(env, subNorm->classes, superNorm->classes, scope).orElse(isCovariantWith(env, subNorm->classes, superNorm->tables, scope)) isCovariantWith(env, subNorm->externTypes, superNorm->externTypes, scope).orElse(isCovariantWith(env, subNorm->externTypes, superNorm->tables, scope))
); );
result.andAlso(isCovariantWith(env, subNorm->errors, superNorm->errors, scope)); result.andAlso(isCovariantWith(env, subNorm->errors, superNorm->errors, scope));
result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils, scope)); result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils, scope));
@ -1678,24 +1678,24 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult Subtyping::isCovariantWith( SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env, SubtypingEnvironment& env,
const NormalizedClassType& subClass, const NormalizedExternType& subExternType,
const NormalizedClassType& superClass, const NormalizedExternType& superExternType,
NotNull<Scope> scope NotNull<Scope> scope
) )
{ {
for (const auto& [subClassTy, _] : subClass.classes) for (const auto& [subExternTypeTy, _] : subExternType.externTypes)
{ {
SubtypingResult result; SubtypingResult result;
for (const auto& [superClassTy, superNegations] : superClass.classes) for (const auto& [superExternTypeTy, superNegations] : superExternType.externTypes)
{ {
result.orElse(isCovariantWith(env, subClassTy, superClassTy, scope)); result.orElse(isCovariantWith(env, subExternTypeTy, superExternTypeTy, scope));
if (!result.isSubtype) if (!result.isSubtype)
continue; continue;
for (TypeId negation : superNegations) for (TypeId negation : superNegations)
{ {
result.andAlso(SubtypingResult::negate(isCovariantWith(env, subClassTy, negation, scope))); result.andAlso(SubtypingResult::negate(isCovariantWith(env, subExternTypeTy, negation, scope)));
if (result.isSubtype) if (result.isSubtype)
break; break;
} }
@ -1710,17 +1710,17 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult Subtyping::isCovariantWith( SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env, SubtypingEnvironment& env,
const NormalizedClassType& subClass, const NormalizedExternType& subExternType,
const TypeIds& superTables, const TypeIds& superTables,
NotNull<Scope> scope NotNull<Scope> scope
) )
{ {
for (const auto& [subClassTy, _] : subClass.classes) for (const auto& [subExternTypeTy, _] : subExternType.externTypes)
{ {
SubtypingResult result; SubtypingResult result;
for (TypeId superTableTy : superTables) for (TypeId superTableTy : superTables)
result.orElse(isCovariantWith(env, subClassTy, superTableTy, scope)); result.orElse(isCovariantWith(env, subExternTypeTy, superTableTy, scope));
if (!result.isSubtype) if (!result.isSubtype)
return result; return result;

View file

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

View file

@ -19,6 +19,8 @@
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
LUAU_FASTFLAGVARIABLE(LuauEnableDenseTableAlias)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauSyntheticErrors) LUAU_FASTFLAGVARIABLE(LuauSyntheticErrors)
LUAU_FASTFLAGVARIABLE(LuauStringPartLengthLimit) LUAU_FASTFLAGVARIABLE(LuauStringPartLengthLimit)
@ -121,7 +123,7 @@ struct FindCyclicTypes final : TypeVisitor
return true; return true;
} }
bool visit(TypeId ty, const ClassType&) override bool visit(TypeId ty, const ExternType&) override
{ {
return false; return false;
} }
@ -720,7 +722,13 @@ struct TypeStringifier
if (ttv.boundTo) if (ttv.boundTo)
return stringify(*ttv.boundTo); return stringify(*ttv.boundTo);
if (!state.exhaustive) bool showName = !state.exhaustive;
if (FFlag::LuauEnableDenseTableAlias)
{
// if hide table alias expansions are enabled and there is a name found for the table, use it
showName = !state.exhaustive || state.opts.hideTableAliasExpansions;
}
if (showName)
{ {
if (ttv.name) if (ttv.name)
{ {
@ -743,6 +751,10 @@ struct TypeStringifier
stringify(ttv.instantiatedTypeParams, ttv.instantiatedTypePackParams); stringify(ttv.instantiatedTypeParams, ttv.instantiatedTypePackParams);
return; return;
} }
}
if (!state.exhaustive)
{
if (ttv.syntheticName) if (ttv.syntheticName)
{ {
state.result.invalid = true; state.result.invalid = true;
@ -881,9 +893,9 @@ struct TypeStringifier
state.emit(" }"); state.emit(" }");
} }
void operator()(TypeId, const ClassType& ctv) void operator()(TypeId, const ExternType& etv)
{ {
state.emit(ctv.name); state.emit(etv.name);
} }
void operator()(TypeId, const AnyType&) void operator()(TypeId, const AnyType&)

View file

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

View file

@ -15,6 +15,7 @@ LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauFixDoBlockEndLocation) LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
LUAU_FASTFLAG(LuauParseOptionalAsNode2) LUAU_FASTFLAG(LuauParseOptionalAsNode2)
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation) LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace namespace
{ {
@ -331,7 +332,7 @@ struct Printer_DEPRECATED
} }
} }
void visualizeTypePackAnnotation(const AstTypePack& annotation, bool forVarArg) void visualizeTypePackAnnotation(const AstTypePack& annotation, bool forVarArg, bool unconditionallyParenthesize = true)
{ {
advance(annotation.location.begin); advance(annotation.location.begin);
if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>()) if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>())
@ -349,7 +350,7 @@ struct Printer_DEPRECATED
else if (const AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>()) else if (const AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>())
{ {
LUAU_ASSERT(!forVarArg); LUAU_ASSERT(!forVarArg);
visualizeTypeList(explicitTp->typeList, true); visualizeTypeList(explicitTp->typeList, unconditionallyParenthesize);
} }
else else
{ {
@ -1065,12 +1066,15 @@ struct Printer_DEPRECATED
writer.symbol(")"); writer.symbol(")");
if (writeTypes && func.returnAnnotation) if (writeTypes && (FFlag::LuauStoreReturnTypesAsPackOnAst ? func.returnAnnotation != nullptr : func.returnAnnotation_DEPRECATED.has_value()))
{ {
writer.symbol(":"); writer.symbol(":");
writer.space(); writer.space();
visualizeTypeList(*func.returnAnnotation, false); if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visualizeTypePackAnnotation(*func.returnAnnotation, false, false);
else
visualizeTypeList(*func.returnAnnotation_DEPRECATED, false);
} }
visualizeBlock(*func.body); visualizeBlock(*func.body);
@ -1174,7 +1178,10 @@ struct Printer_DEPRECATED
} }
writer.symbol("->"); writer.symbol("->");
visualizeTypeList(a->returnTypes, true); if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visualizeTypePackAnnotation(*a->returnTypes, false);
else
visualizeTypeList(a->returnTypes_DEPRECATED, true);
} }
else if (const auto& a = typeAnnotation.as<AstTypeTable>()) else if (const auto& a = typeAnnotation.as<AstTypeTable>())
{ {
@ -1368,7 +1375,7 @@ struct Printer
} }
} }
void visualizeTypePackAnnotation(AstTypePack& annotation, bool forVarArg) void visualizeTypePackAnnotation(AstTypePack& annotation, bool forVarArg, bool unconditionallyParenthesize = true)
{ {
advance(annotation.location.begin); advance(annotation.location.begin);
if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>()) if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>())
@ -1390,10 +1397,10 @@ struct Printer
LUAU_ASSERT(!forVarArg); LUAU_ASSERT(!forVarArg);
if (const auto cstNode = lookupCstNode<CstTypePackExplicit>(explicitTp)) if (const auto cstNode = lookupCstNode<CstTypePackExplicit>(explicitTp))
visualizeTypeList( visualizeTypeList(
explicitTp->typeList, true, cstNode->openParenthesesPosition, cstNode->closeParenthesesPosition, cstNode->commaPositions explicitTp->typeList, FFlag::LuauStoreReturnTypesAsPackOnAst ? cstNode->hasParentheses : true, cstNode->openParenthesesPosition, cstNode->closeParenthesesPosition, cstNode->commaPositions
); );
else else
visualizeTypeList(explicitTp->typeList, true); visualizeTypeList(explicitTp->typeList, unconditionallyParenthesize);
} }
else else
{ {
@ -2383,14 +2390,17 @@ struct Printer
advanceBefore(func.argLocation->end, 1); advanceBefore(func.argLocation->end, 1);
writer.symbol(")"); writer.symbol(")");
if (writeTypes && func.returnAnnotation) if (writeTypes && FFlag::LuauStoreReturnTypesAsPackOnAst ? func.returnAnnotation != nullptr : func.returnAnnotation_DEPRECATED.has_value())
{ {
if (cstNode) if (cstNode)
advance(cstNode->returnSpecifierPosition); advance(cstNode->returnSpecifierPosition);
writer.symbol(":"); writer.symbol(":");
writer.space(); writer.space();
visualizeTypeList(*func.returnAnnotation, false); if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visualizeTypePackAnnotation(*func.returnAnnotation, false, false);
else
visualizeTypeList(*func.returnAnnotation_DEPRECATED, false);
} }
visualizeBlock(*func.body); visualizeBlock(*func.body);
@ -2573,7 +2583,10 @@ struct Printer
if (cstNode) if (cstNode)
advance(cstNode->returnArrowPosition); advance(cstNode->returnArrowPosition);
writer.symbol("->"); writer.symbol("->");
visualizeTypeList(a->returnTypes, true); if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visualizeTypePackAnnotation(*a->returnTypes, false);
else
visualizeTypeList(a->returnTypes_DEPRECATED, true);
} }
else if (const auto& a = typeAnnotation.as<AstTypeTable>()) else if (const auto& a = typeAnnotation.as<AstTypeTable>())
{ {

View file

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

View file

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

View file

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

View file

@ -58,8 +58,6 @@ LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionFunctionMetamethods)
LUAU_FASTFLAGVARIABLE(LuauIntersectNotNil) LUAU_FASTFLAGVARIABLE(LuauIntersectNotNil)
LUAU_FASTFLAGVARIABLE(LuauSkipNoRefineDuringRefinement) LUAU_FASTFLAGVARIABLE(LuauSkipNoRefineDuringRefinement)
LUAU_FASTFLAGVARIABLE(LuauMetatablesHaveLength) LUAU_FASTFLAGVARIABLE(LuauMetatablesHaveLength)
LUAU_FASTFLAGVARIABLE(LuauDontForgetToReduceUnionFunc)
LUAU_FASTFLAGVARIABLE(LuauSearchForRefineableType)
LUAU_FASTFLAGVARIABLE(LuauIndexAnyIsAny) LUAU_FASTFLAGVARIABLE(LuauIndexAnyIsAny)
LUAU_FASTFLAGVARIABLE(LuauFixCyclicIndexInIndexer) LUAU_FASTFLAGVARIABLE(LuauFixCyclicIndexInIndexer)
LUAU_FASTFLAGVARIABLE(LuauSimplyRefineNotNil) LUAU_FASTFLAGVARIABLE(LuauSimplyRefineNotNil)
@ -68,6 +66,7 @@ LUAU_FASTFLAGVARIABLE(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAGVARIABLE(LuauReduceUnionFollowUnionType) LUAU_FASTFLAGVARIABLE(LuauReduceUnionFollowUnionType)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAGVARIABLE(LuauNarrowIntersectionNevers) LUAU_FASTFLAGVARIABLE(LuauNarrowIntersectionNevers)
LUAU_FASTFLAGVARIABLE(LuauRefineWaitForBlockedTypesInTarget)
namespace Luau namespace Luau
{ {
@ -106,7 +105,7 @@ struct InstanceCollector_DEPRECATED : TypeOnceVisitor
cyclicInstance.push_back(t); cyclicInstance.push_back(t);
} }
bool visit(TypeId ty, const ClassType&) override bool visit(TypeId ty, const ExternType&) override
{ {
return false; return false;
} }
@ -182,7 +181,7 @@ struct InstanceCollector : TypeOnceVisitor
} }
} }
bool visit(TypeId ty, const ClassType&) override bool visit(TypeId ty, const ExternType&) override
{ {
return false; return false;
} }
@ -270,7 +269,7 @@ struct UnscopedGenericFinder : TypeOnceVisitor
return false; return false;
} }
bool visit(TypeId ty, const ClassType&) override bool visit(TypeId ty, const ExternType&) override
{ {
return false; return false;
} }
@ -844,7 +843,7 @@ static std::optional<TypeFunctionReductionResult<TypeId>> tryDistributeTypeFunct
{ {
arguments[unionIndex] = option; arguments[unionIndex] = option;
TypeFunctionReductionResult<TypeId> result = f(instance, arguments, packParams, ctx, args...); TypeFunctionReductionResult<TypeId> result = f(instance, arguments, packParams, ctx, args...); // NOLINT
blockedTypes.insert(blockedTypes.end(), result.blockedTypes.begin(), result.blockedTypes.end()); blockedTypes.insert(blockedTypes.end(), result.blockedTypes.begin(), result.blockedTypes.end());
if (result.reductionStatus != Reduction::MaybeOk) if (result.reductionStatus != Reduction::MaybeOk)
reductionStatus = result.reductionStatus; reductionStatus = result.reductionStatus;
@ -869,7 +868,7 @@ static std::optional<TypeFunctionReductionResult<TypeId>> tryDistributeTypeFunct
{}, {},
}); });
if (FFlag::LuauDontForgetToReduceUnionFunc && ctx->solver) if (ctx->solver)
ctx->pushConstraint(ReduceConstraint{resultTy}); ctx->pushConstraint(ReduceConstraint{resultTy});
return {{resultTy, Reduction::MaybeOk, {}, {}}}; return {{resultTy, Reduction::MaybeOk, {}, {}}};
@ -908,7 +907,7 @@ struct FindUserTypeFunctionBlockers : TypeOnceVisitor
return true; return true;
} }
bool visit(TypeId ty, const ClassType&) override bool visit(TypeId ty, const ExternType&) override
{ {
return false; return false;
} }
@ -2130,7 +2129,7 @@ struct FindRefinementBlockers : TypeOnceVisitor
return false; return false;
} }
bool visit(TypeId ty, const ClassType&) override bool visit(TypeId ty, const ExternType&) override
{ {
return false; return false;
} }
@ -2252,6 +2251,18 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
return {std::nullopt, Reduction::MaybeOk, {t}, {}}; return {std::nullopt, Reduction::MaybeOk, {t}, {}};
} }
} }
if (FFlag::LuauRefineWaitForBlockedTypesInTarget)
{
// If we have a blocked type in the target, we *could* potentially
// refine it, but more likely we end up with some type explosion in
// normalization.
FindRefinementBlockers frb;
frb.traverse(targetTy);
if (!frb.found.empty())
return {std::nullopt, Reduction::MaybeOk, {frb.found.begin(), frb.found.end()}, {}};
}
// Refine a target type and a discriminant one at a time. // Refine a target type and a discriminant one at a time.
// Returns result : TypeId, toBlockOn : vector<TypeId> // Returns result : TypeId, toBlockOn : vector<TypeId>
auto stepRefine = [&ctx](TypeId target, TypeId discriminant) -> std::pair<TypeId, std::vector<TypeId>> auto stepRefine = [&ctx](TypeId target, TypeId discriminant) -> std::pair<TypeId, std::vector<TypeId>>
@ -2282,28 +2293,14 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
} }
else else
{ {
if (FFlag::LuauSearchForRefineableType) // If the discriminant type is only:
{ // - The `*no-refine*` type or,
// If the discriminant type is only: // - tables, metatables, unions, intersections, functions, or negations _containing_ `*no-refine*`.
// - The `*no-refine*` type or, // There's no point in refining against it.
// - tables, metatables, unions, intersections, functions, or negations _containing_ `*no-refine*`. ContainsRefinableType crt;
// There's no point in refining against it. crt.traverse(discriminant);
ContainsRefinableType crt; if (!crt.found)
crt.traverse(discriminant); return {target, {}};
if (!crt.found)
return {target, {}};
}
else
{
if (FFlag::LuauSkipNoRefineDuringRefinement)
if (get<NoRefineType>(discriminant))
return {target, {}};
if (auto nt = get<NegationType>(discriminant))
{
if (get<NoRefineType>(follow(nt->ty)))
return {target, {}};
}
}
if (FFlag::LuauSimplyRefineNotNil) if (FFlag::LuauSimplyRefineNotNil)
{ {
@ -2684,7 +2681,7 @@ bool computeKeysOf(TypeId ty, Set<std::string>& result, DenseHashSet<TypeId>& se
return res; return res;
} }
if (auto classTy = get<ClassType>(ty)) if (auto classTy = get<ExternType>(ty))
{ {
for (auto [key, _] : classTy->props) for (auto [key, _] : classTy->props)
result.insert(key); result.insert(key);
@ -2707,7 +2704,7 @@ bool computeKeysOf(TypeId ty, Set<std::string>& result, DenseHashSet<TypeId>& se
return res; return res;
} }
// this should not be reachable since the type should be a valid tables or classes part from normalization. // this should not be reachable since the type should be a valid tables or extern types part from normalization.
LUAU_ASSERT(false); LUAU_ASSERT(false);
return false; return false;
} }
@ -2733,9 +2730,9 @@ TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
if (!normTy) if (!normTy)
return {std::nullopt, Reduction::MaybeOk, {}, {}}; return {std::nullopt, Reduction::MaybeOk, {}, {}};
// if we don't have either just tables or just classes, we've got nothing to get keys of (at least until a future version perhaps adds classes // if we don't have either just tables or just extern types, we've got nothing to get keys of (at least until a future version perhaps adds extern types
// as well) // as well)
if (normTy->hasTables() == normTy->hasClasses()) if (normTy->hasTables() == normTy->hasExternTypes())
return {std::nullopt, Reduction::Erroneous, {}, {}}; return {std::nullopt, Reduction::Erroneous, {}, {}};
// this is sort of atrocious, but we're trying to reject any type that has not normalized to a table or a union of tables. // this is sort of atrocious, but we're trying to reject any type that has not normalized to a table or a union of tables.
@ -2746,31 +2743,31 @@ TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
// we're going to collect the keys in here // we're going to collect the keys in here
Set<std::string> keys{{}}; Set<std::string> keys{{}};
// computing the keys for classes // computing the keys for extern types
if (normTy->hasClasses()) if (normTy->hasExternTypes())
{ {
LUAU_ASSERT(!normTy->hasTables()); LUAU_ASSERT(!normTy->hasTables());
// seen set for key computation for classes // seen set for key computation for extern types
DenseHashSet<TypeId> seen{{}}; DenseHashSet<TypeId> seen{{}};
auto classesIter = normTy->classes.ordering.begin(); auto externTypeIter = normTy->externTypes.ordering.begin();
auto classesIterEnd = normTy->classes.ordering.end(); auto externTypeIterEnd = normTy->externTypes.ordering.end();
LUAU_ASSERT(classesIter != classesIterEnd); // should be guaranteed by the `hasClasses` check earlier LUAU_ASSERT(externTypeIter != externTypeIterEnd); // should be guaranteed by the `hasExternTypes` check earlier
// collect all the properties from the first class type // collect all the properties from the first class type
if (!computeKeysOf(*classesIter, keys, seen, isRaw, ctx)) if (!computeKeysOf(*externTypeIter, keys, seen, isRaw, ctx))
return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}}; // if it failed, we have a top type! return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}}; // if it failed, we have a top type!
// we need to look at each class to remove any keys that are not common amongst them all // we need to look at each class to remove any keys that are not common amongst them all
while (++classesIter != classesIterEnd) while (++externTypeIter != externTypeIterEnd)
{ {
seen.clear(); // we'll reuse the same seen set seen.clear(); // we'll reuse the same seen set
Set<std::string> localKeys{{}}; Set<std::string> localKeys{{}};
// we can skip to the next class if this one is a top type // we can skip to the next class if this one is a top type
if (!computeKeysOf(*classesIter, localKeys, seen, isRaw, ctx)) if (!computeKeysOf(*externTypeIter, localKeys, seen, isRaw, ctx))
continue; continue;
for (auto& key : keys) for (auto& key : keys)
@ -2785,7 +2782,7 @@ TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
// computing the keys for tables // computing the keys for tables
if (normTy->hasTables()) if (normTy->hasTables())
{ {
LUAU_ASSERT(!normTy->hasClasses()); LUAU_ASSERT(!normTy->hasExternTypes());
// seen set for key computation for tables // seen set for key computation for tables
DenseHashSet<TypeId> seen{{}}; DenseHashSet<TypeId> seen{{}};
@ -2947,7 +2944,7 @@ bool searchPropsAndIndexer(
return false; return false;
} }
/* Handles recursion / metamethods of tables/classes /* Handles recursion / metamethods of tables and extern types
`isRaw` parameter indicates whether or not we should follow __index metamethods `isRaw` parameter indicates whether or not we should follow __index metamethods
returns false if property of `ty` could not be found */ returns false if property of `ty` could not be found */
bool tblIndexInto_DEPRECATED(TypeId indexer, TypeId indexee, DenseHashSet<TypeId>& result, NotNull<TypeFunctionContext> ctx, bool isRaw) bool tblIndexInto_DEPRECATED(TypeId indexer, TypeId indexee, DenseHashSet<TypeId>& result, NotNull<TypeFunctionContext> ctx, bool isRaw)
@ -3122,11 +3119,11 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
return {ctx->builtins->anyType, Reduction::MaybeOk, {}, {}}; return {ctx->builtins->anyType, Reduction::MaybeOk, {}, {}};
} }
// if we don't have either just tables or just classes, we've got nothing to index into // if we don't have either just tables or just extern types, we've got nothing to index into
if (indexeeNormTy->hasTables() == indexeeNormTy->hasClasses()) if (indexeeNormTy->hasTables() == indexeeNormTy->hasExternTypes())
return {std::nullopt, Reduction::Erroneous, {}, {}}; return {std::nullopt, Reduction::Erroneous, {}, {}};
// we're trying to reject any type that has not normalized to a table/class or a union of tables/classes. // we're trying to reject any type that has not normalized to a table or extern type or a union of tables or extern types.
if (indexeeNormTy->hasTops() || indexeeNormTy->hasBooleans() || indexeeNormTy->hasErrors() || indexeeNormTy->hasNils() || if (indexeeNormTy->hasTops() || indexeeNormTy->hasBooleans() || indexeeNormTy->hasErrors() || indexeeNormTy->hasNils() ||
indexeeNormTy->hasNumbers() || indexeeNormTy->hasStrings() || indexeeNormTy->hasThreads() || indexeeNormTy->hasBuffers() || indexeeNormTy->hasNumbers() || indexeeNormTy->hasStrings() || indexeeNormTy->hasThreads() || indexeeNormTy->hasBuffers() ||
indexeeNormTy->hasFunctions() || indexeeNormTy->hasTyvars()) indexeeNormTy->hasFunctions() || indexeeNormTy->hasTyvars())
@ -3157,18 +3154,18 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
DenseHashSet<TypeId> properties{{}}; // vector of types that will be returned DenseHashSet<TypeId> properties{{}}; // vector of types that will be returned
if (indexeeNormTy->hasClasses()) if (indexeeNormTy->hasExternTypes())
{ {
LUAU_ASSERT(!indexeeNormTy->hasTables()); LUAU_ASSERT(!indexeeNormTy->hasTables());
if (isRaw) // rawget should never reduce for classes (to match the behavior of the rawget global function) if (isRaw) // rawget should never reduce for extern types (to match the behavior of the rawget global function)
return {std::nullopt, Reduction::Erroneous, {}, {}}; return {std::nullopt, Reduction::Erroneous, {}, {}};
// at least one class is guaranteed to be in the iterator by .hasClasses() // at least one class is guaranteed to be in the iterator by .hasExternTypes()
for (auto classesIter = indexeeNormTy->classes.ordering.begin(); classesIter != indexeeNormTy->classes.ordering.end(); ++classesIter) for (auto externTypeIter = indexeeNormTy->externTypes.ordering.begin(); externTypeIter != indexeeNormTy->externTypes.ordering.end(); ++externTypeIter)
{ {
auto classTy = get<ClassType>(*classesIter); auto externTy = get<ExternType>(*externTypeIter);
if (!classTy) if (!externTy)
{ {
LUAU_ASSERT(false); // this should not be possible according to normalization's spec LUAU_ASSERT(false); // this should not be possible according to normalization's spec
return {std::nullopt, Reduction::Erroneous, {}, {}}; return {std::nullopt, Reduction::Erroneous, {}, {}};
@ -3177,16 +3174,16 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
for (TypeId ty : *typesToFind) for (TypeId ty : *typesToFind)
{ {
// Search for all instances of indexer in class->props and class->indexer // Search for all instances of indexer in class->props and class->indexer
if (searchPropsAndIndexer(ty, classTy->props, classTy->indexer, properties, ctx)) if (searchPropsAndIndexer(ty, externTy->props, externTy->indexer, properties, ctx))
continue; // Indexer was found in this class, so we can move on to the next continue; // Indexer was found in this class, so we can move on to the next
auto parent = classTy->parent; auto parent = externTy->parent;
bool foundInParent = false; bool foundInParent = false;
while (parent && !foundInParent) while (parent && !foundInParent)
{ {
auto parentClass = get<ClassType>(follow(*parent)); auto parentExternType = get<ExternType>(follow(*parent));
foundInParent = searchPropsAndIndexer(ty, parentClass->props, parentClass->indexer, properties, ctx); foundInParent = searchPropsAndIndexer(ty, parentExternType->props, parentExternType->indexer, properties, ctx);
parent = parentClass->parent; parent = parentExternType->parent;
} }
// we move on to the next type if any of the parents we went through had the property. // we move on to the next type if any of the parents we went through had the property.
@ -3198,7 +3195,7 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
// findMetatableEntry demands the ability to emit errors, so we must give it // findMetatableEntry demands the ability to emit errors, so we must give it
// the necessary state to do that, even if we intend to just eat the errors. // the necessary state to do that, even if we intend to just eat the errors.
ErrorVec dummy; ErrorVec dummy;
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, *classesIter, "__index", Location{}); std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, *externTypeIter, "__index", Location{});
if (!mmType) // if a metatable does not exist, there is no where else to look if (!mmType) // if a metatable does not exist, there is no where else to look
return {std::nullopt, Reduction::Erroneous, {}, {}}; return {std::nullopt, Reduction::Erroneous, {}, {}};
@ -3210,7 +3207,7 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
if (indexeeNormTy->hasTables()) if (indexeeNormTy->hasTables())
{ {
LUAU_ASSERT(!indexeeNormTy->hasClasses()); LUAU_ASSERT(!indexeeNormTy->hasExternTypes());
// at least one table is guaranteed to be in the iterator by .hasTables() // at least one table is guaranteed to be in the iterator by .hasTables()
for (auto tablesIter = indexeeNormTy->tables.begin(); tablesIter != indexeeNormTy->tables.end(); ++tablesIter) for (auto tablesIter = indexeeNormTy->tables.begin(); tablesIter != indexeeNormTy->tables.end(); ++tablesIter)
@ -3305,7 +3302,7 @@ TypeFunctionReductionResult<TypeId> setmetatableTypeFunction(
// we're trying to reject any type that has not normalized to a table or a union/intersection of tables. // we're trying to reject any type that has not normalized to a table or a union/intersection of tables.
if (targetNorm->hasTops() || targetNorm->hasBooleans() || targetNorm->hasErrors() || targetNorm->hasNils() || targetNorm->hasNumbers() || if (targetNorm->hasTops() || targetNorm->hasBooleans() || targetNorm->hasErrors() || targetNorm->hasNils() || targetNorm->hasNumbers() ||
targetNorm->hasStrings() || targetNorm->hasThreads() || targetNorm->hasBuffers() || targetNorm->hasFunctions() || targetNorm->hasTyvars() || targetNorm->hasStrings() || targetNorm->hasThreads() || targetNorm->hasBuffers() || targetNorm->hasFunctions() || targetNorm->hasTyvars() ||
targetNorm->hasClasses()) targetNorm->hasExternTypes())
return {std::nullopt, Reduction::Erroneous, {}, {}}; return {std::nullopt, Reduction::Erroneous, {}, {}};
// if the supposed metatable is not a table, we will fail to reduce. // if the supposed metatable is not a table, we will fail to reduce.
@ -3379,7 +3376,7 @@ static TypeFunctionReductionResult<TypeId> getmetatableHelper(TypeId targetTy, c
erroneous = false; erroneous = false;
} }
if (auto clazz = get<ClassType>(targetTy)) if (auto clazz = get<ExternType>(targetTy))
{ {
metatable = clazz->metatable; metatable = clazz->metatable;
erroneous = false; erroneous = false;

View file

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

View file

@ -154,7 +154,7 @@ static std::string getTag(lua_State* L, TypeFunctionTypeId ty)
return "table"; return "table";
else if (get<TypeFunctionFunctionType>(ty)) else if (get<TypeFunctionFunctionType>(ty))
return "function"; return "function";
else if (get<TypeFunctionClassType>(ty)) else if (get<TypeFunctionExternType>(ty))
return "class"; return "class";
else if (get<TypeFunctionGenericType>(ty)) else if (get<TypeFunctionGenericType>(ty))
return "generic"; return "generic";
@ -1114,7 +1114,7 @@ static int getClassParent_DEPRECATED(lua_State* L)
luaL_error(L, "type.parent: expected 1 arguments, but got %d", argumentCount); luaL_error(L, "type.parent: expected 1 arguments, but got %d", argumentCount);
TypeFunctionTypeId self = getTypeUserData(L, 1); TypeFunctionTypeId self = getTypeUserData(L, 1);
auto tfct = get<TypeFunctionClassType>(self); auto tfct = get<TypeFunctionExternType>(self);
if (!tfct) if (!tfct)
luaL_error(L, "type.parent: expected self to be a class, but got %s instead", getTag(L, self).c_str()); luaL_error(L, "type.parent: expected self to be a class, but got %s instead", getTag(L, self).c_str());
@ -1136,7 +1136,7 @@ static int getReadParent(lua_State* L)
luaL_error(L, "type.parent: expected 1 arguments, but got %d", argumentCount); luaL_error(L, "type.parent: expected 1 arguments, but got %d", argumentCount);
TypeFunctionTypeId self = getTypeUserData(L, 1); TypeFunctionTypeId self = getTypeUserData(L, 1);
auto tfct = get<TypeFunctionClassType>(self); auto tfct = get<TypeFunctionExternType>(self);
if (!tfct) if (!tfct)
luaL_error(L, "type.parent: expected self to be a class, but got %s instead", getTag(L, self).c_str()); luaL_error(L, "type.parent: expected self to be a class, but got %s instead", getTag(L, self).c_str());
@ -1158,7 +1158,7 @@ static int getWriteParent(lua_State* L)
luaL_error(L, "type.parent: expected 1 arguments, but got %d", argumentCount); luaL_error(L, "type.parent: expected 1 arguments, but got %d", argumentCount);
TypeFunctionTypeId self = getTypeUserData(L, 1); TypeFunctionTypeId self = getTypeUserData(L, 1);
auto tfct = get<TypeFunctionClassType>(self); auto tfct = get<TypeFunctionExternType>(self);
if (!tfct) if (!tfct)
luaL_error(L, "type.parent: expected self to be a class, but got %s instead", getTag(L, self).c_str()); luaL_error(L, "type.parent: expected self to be a class, but got %s instead", getTag(L, self).c_str());
@ -1242,7 +1242,7 @@ static int getProps(lua_State* L)
return 1; return 1;
} }
if (auto tfct = get<TypeFunctionClassType>(self)) if (auto tfct = get<TypeFunctionExternType>(self))
{ {
lua_createtable(L, int(tfct->props.size()), 0); lua_createtable(L, int(tfct->props.size()), 0);
for (auto& [name, prop] : tfct->props) for (auto& [name, prop] : tfct->props)
@ -1305,7 +1305,7 @@ static int getIndexer(lua_State* L)
return 1; return 1;
} }
if (auto tfct = get<TypeFunctionClassType>(self)) if (auto tfct = get<TypeFunctionExternType>(self))
{ {
// if the indexer does not exist, we should return nil // if the indexer does not exist, we should return nil
if (!tfct->indexer.has_value()) if (!tfct->indexer.has_value())
@ -1353,7 +1353,7 @@ static int getReadIndexer(lua_State* L)
return 1; return 1;
} }
if (auto tfct = get<TypeFunctionClassType>(self)) if (auto tfct = get<TypeFunctionExternType>(self))
{ {
// if the indexer does not exist, we should return nil // if the indexer does not exist, we should return nil
if (!tfct->indexer.has_value()) if (!tfct->indexer.has_value())
@ -1399,7 +1399,7 @@ static int getWriteIndexer(lua_State* L)
return 1; return 1;
} }
if (auto tfct = get<TypeFunctionClassType>(self)) if (auto tfct = get<TypeFunctionExternType>(self))
{ {
// if the indexer does not exist, we should return nil // if the indexer does not exist, we should return nil
if (!tfct->indexer.has_value()) if (!tfct->indexer.has_value())
@ -1439,7 +1439,7 @@ static int getMetatable(lua_State* L)
return 1; return 1;
} }
if (auto tfct = get<TypeFunctionClassType>(self)) if (auto tfct = get<TypeFunctionExternType>(self))
{ {
// if the metatable does not exist, we should return nil // if the metatable does not exist, we should return nil
if (!tfct->metatable.has_value()) if (!tfct->metatable.has_value())
@ -1593,7 +1593,7 @@ void registerTypeUserData(lua_State* L)
// Union and Intersection type methods // Union and Intersection type methods
{"components", getComponents}, {"components", getComponents},
// Class type methods // Extern type methods
{FFlag::LuauTypeFunReadWriteParents ? "readparent" : "parent", FFlag::LuauTypeFunReadWriteParents ? getReadParent : getClassParent_DEPRECATED}, {FFlag::LuauTypeFunReadWriteParents ? "readparent" : "parent", FFlag::LuauTypeFunReadWriteParents ? getReadParent : getClassParent_DEPRECATED},
// Function type methods (cont.) // Function type methods (cont.)
@ -1604,7 +1604,7 @@ void registerTypeUserData(lua_State* L)
{"name", getGenericName}, {"name", getGenericName},
{"ispack", getGenericIsPack}, {"ispack", getGenericIsPack},
// move this under Class type methods when removing FFlagLuauTypeFunReadWriteParents // move this under extern type methods when removing FFlagLuauTypeFunReadWriteParents
{FFlag::LuauTypeFunReadWriteParents ? "writeparent" : nullptr, FFlag::LuauTypeFunReadWriteParents ? getWriteParent : nullptr}, {FFlag::LuauTypeFunReadWriteParents ? "writeparent" : nullptr, FFlag::LuauTypeFunReadWriteParents ? getWriteParent : nullptr},
{nullptr, nullptr} {nullptr, nullptr}
@ -1903,12 +1903,12 @@ bool areEqual(SeenSet& seen, const TypeFunctionFunctionType& lhs, const TypeFunc
return true; return true;
} }
bool areEqual(SeenSet& seen, const TypeFunctionClassType& lhs, const TypeFunctionClassType& rhs) bool areEqual(SeenSet& seen, const TypeFunctionExternType& lhs, const TypeFunctionExternType& rhs)
{ {
if (seenSetContains(seen, &lhs, &rhs)) if (seenSetContains(seen, &lhs, &rhs))
return true; return true;
return lhs.classTy == rhs.classTy; return lhs.externTy == rhs.externTy;
} }
bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType& rhs) bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType& rhs)
@ -1976,8 +1976,8 @@ bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType
} }
{ {
const TypeFunctionClassType* lf = get<TypeFunctionClassType>(&lhs); const TypeFunctionExternType* lf = get<TypeFunctionExternType>(&lhs);
const TypeFunctionClassType* rf = get<TypeFunctionClassType>(&rhs); const TypeFunctionExternType* rf = get<TypeFunctionExternType>(&rhs);
if (lf && rf) if (lf && rf)
return areEqual(seen, *lf, *rf); return areEqual(seen, *lf, *rf);
} }
@ -2266,7 +2266,7 @@ private:
TypeFunctionTypePackId emptyTypePack = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{}); TypeFunctionTypePackId emptyTypePack = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{});
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{{}, {}, emptyTypePack, emptyTypePack}); target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{{}, {}, emptyTypePack, emptyTypePack});
} }
else if (auto c = get<TypeFunctionClassType>(ty)) else if (auto c = get<TypeFunctionExternType>(ty))
target = ty; // Don't copy a class since they are immutable target = ty; // Don't copy a class since they are immutable
else if (auto g = get<TypeFunctionGenericType>(ty)) else if (auto g = get<TypeFunctionGenericType>(ty))
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionGenericType{g->isNamed, g->isPack, g->name}); target = typeFunctionRuntime->typeArena.allocate(TypeFunctionGenericType{g->isNamed, g->isPack, g->name});
@ -2321,7 +2321,7 @@ private:
cloneChildren(t1, t2); cloneChildren(t1, t2);
else if (auto [f1, f2] = std::tuple{getMutable<TypeFunctionFunctionType>(ty), getMutable<TypeFunctionFunctionType>(tfti)}; f1 && f2) else if (auto [f1, f2] = std::tuple{getMutable<TypeFunctionFunctionType>(ty), getMutable<TypeFunctionFunctionType>(tfti)}; f1 && f2)
cloneChildren(f1, f2); cloneChildren(f1, f2);
else if (auto [c1, c2] = std::tuple{getMutable<TypeFunctionClassType>(ty), getMutable<TypeFunctionClassType>(tfti)}; c1 && c2) else if (auto [c1, c2] = std::tuple{getMutable<TypeFunctionExternType>(ty), getMutable<TypeFunctionExternType>(tfti)}; c1 && c2)
cloneChildren(c1, c2); cloneChildren(c1, c2);
else if (auto [g1, g2] = std::tuple{getMutable<TypeFunctionGenericType>(ty), getMutable<TypeFunctionGenericType>(tfti)}; g1 && g2) else if (auto [g1, g2] = std::tuple{getMutable<TypeFunctionGenericType>(ty), getMutable<TypeFunctionGenericType>(tfti)}; g1 && g2)
cloneChildren(g1, g2); cloneChildren(g1, g2);
@ -2431,7 +2431,7 @@ private:
f2->retTypes = shallowClone(f1->retTypes); f2->retTypes = shallowClone(f1->retTypes);
} }
void cloneChildren(TypeFunctionClassType* c1, TypeFunctionClassType* c2) void cloneChildren(TypeFunctionExternType* c1, TypeFunctionExternType* c2)
{ {
// noop. // noop.
} }

View file

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

View file

@ -32,8 +32,9 @@ LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations) LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
LUAU_FASTFLAGVARIABLE(LuauStatForInFix) LUAU_FASTFLAGVARIABLE(LuauStatForInFix)
LUAU_FASTFLAGVARIABLE(LuauReduceCheckBinaryExprStackPressure) LUAU_FASTFLAGVARIABLE(LuauReduceCheckBinaryExprStackPressure)
@ -44,7 +45,7 @@ namespace Luau
static bool typeCouldHaveMetatable(TypeId ty) static bool typeCouldHaveMetatable(TypeId ty)
{ {
return get<TableType>(follow(ty)) || get<ClassType>(follow(ty)) || get<MetatableType>(follow(ty)); return get<TableType>(follow(ty)) || get<ExternType>(follow(ty)) || get<MetatableType>(follow(ty));
} }
static void defaultLuauPrintLine(const std::string& s) static void defaultLuauPrintLine(const std::string& s)
@ -318,7 +319,7 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
unifierState.skipCacheForType.clear(); unifierState.skipCacheForType.clear();
duplicateTypeAliases.clear(); duplicateTypeAliases.clear();
incorrectClassDefinitions.clear(); incorrectExternTypeDefinitions.clear();
return std::move(currentModule); return std::move(currentModule);
} }
@ -383,7 +384,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStat& program)
} }
else if (auto global = program.as<AstStatDeclareFunction>()) else if (auto global = program.as<AstStatDeclareFunction>())
return check(scope, *global); return check(scope, *global);
else if (auto global = program.as<AstStatDeclareClass>()) else if (auto global = program.as<AstStatDeclareExternType>())
return check(scope, *global); return check(scope, *global);
else if (auto errorStatement = program.as<AstStatError>()) else if (auto errorStatement = program.as<AstStatError>())
{ {
@ -498,9 +499,9 @@ ControlFlow TypeChecker::checkBlockWithoutRecursionCheck(const ScopePtr& scope,
prototype(scope, *typealias, subLevel); prototype(scope, *typealias, subLevel);
++subLevel; ++subLevel;
} }
else if (const auto& declaredClass = stat->as<AstStatDeclareClass>()) else if (const auto& declaredExternType = stat->as<AstStatDeclareExternType>())
{ {
prototype(scope, *declaredClass); prototype(scope, *declaredExternType);
} }
} }
@ -788,7 +789,7 @@ struct Demoter : Substitution
bool ignoreChildren(TypeId ty) override bool ignoreChildren(TypeId ty) override
{ {
if (get<ClassType>(ty)) if (get<ExternType>(ty))
return true; return true;
return false; return false;
@ -1686,82 +1687,82 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea
} }
} }
void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareClass& declaredClass) void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareExternType& declaredExternType)
{ {
std::optional<TypeId> superTy = std::make_optional(builtinTypes->classType); std::optional<TypeId> superTy = std::make_optional(builtinTypes->externType);
if (declaredClass.superName) if (declaredExternType.superName)
{ {
Name superName = Name(declaredClass.superName->value); Name superName = Name(declaredExternType.superName->value);
std::optional<TypeFun> lookupType = scope->lookupType(superName); std::optional<TypeFun> lookupType = scope->lookupType(superName);
if (!lookupType) if (!lookupType)
{ {
reportError(declaredClass.location, UnknownSymbol{superName, UnknownSymbol::Type}); reportError(declaredExternType.location, UnknownSymbol{superName, UnknownSymbol::Type});
incorrectClassDefinitions.insert(&declaredClass); incorrectExternTypeDefinitions.insert(&declaredExternType);
return; return;
} }
// We don't have generic classes, so this assertion _should_ never be hit. // We don't have generic extern types, so this assertion _should_ never be hit.
LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0); LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0);
superTy = lookupType->type; superTy = lookupType->type;
if (!get<ClassType>(follow(*superTy))) if (!get<ExternType>(follow(*superTy)))
{ {
reportError( reportError(
declaredClass.location, declaredExternType.location,
GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredClass.name.value)} GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredExternType.name.value)}
); );
incorrectClassDefinitions.insert(&declaredClass); incorrectExternTypeDefinitions.insert(&declaredExternType);
return; return;
} }
} }
Name className(declaredClass.name.value); Name className(declaredExternType.name.value);
TypeId classTy = addType(ClassType(className, {}, superTy, std::nullopt, {}, {}, currentModule->name, declaredClass.location)); TypeId classTy = addType(ExternType(className, {}, superTy, std::nullopt, {}, {}, currentModule->name, declaredExternType.location));
ClassType* ctv = getMutable<ClassType>(classTy); ExternType* etv = getMutable<ExternType>(classTy);
TypeId metaTy = addType(TableType{TableState::Sealed, scope->level}); TypeId metaTy = addType(TableType{TableState::Sealed, scope->level});
ctv->metatable = metaTy; etv->metatable = metaTy;
if (FFlag::LuauRetainDefinitionAliasLocations) if (FFlag::LuauRetainDefinitionAliasLocations)
scope->exportedTypeBindings[className] = TypeFun{{}, classTy, declaredClass.location}; scope->exportedTypeBindings[className] = TypeFun{{}, classTy, declaredExternType.location};
else else
scope->exportedTypeBindings[className] = TypeFun{{}, classTy}; scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
} }
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass) ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareExternType& declaredExternType)
{ {
Name className(declaredClass.name.value); Name className(declaredExternType.name.value);
// Don't bother checking if the class definition was incorrect // Don't bother checking if the class definition was incorrect
if (incorrectClassDefinitions.find(&declaredClass)) if (incorrectExternTypeDefinitions.find(&declaredExternType))
return ControlFlow::None; return ControlFlow::None;
std::optional<TypeFun> binding; std::optional<TypeFun> binding;
if (auto it = scope->exportedTypeBindings.find(className); it != scope->exportedTypeBindings.end()) if (auto it = scope->exportedTypeBindings.find(className); it != scope->exportedTypeBindings.end())
binding = it->second; binding = it->second;
// This class definition must have been `prototype()`d first. // This extern type definition must have been `prototype()`d first.
if (!binding) if (!binding)
ice("Class not predeclared"); ice("Extern type not predeclared");
TypeId classTy = binding->type; TypeId externTy = binding->type;
ClassType* ctv = getMutable<ClassType>(classTy); ExternType* etv = getMutable<ExternType>(externTy);
if (!ctv->metatable) if (!etv->metatable)
ice("No metatable for declared class"); ice("No metatable for declared extern type");
if (const auto& indexer = declaredClass.indexer) if (const auto& indexer = declaredExternType.indexer)
ctv->indexer = TableIndexer(resolveType(scope, *indexer->indexType), resolveType(scope, *indexer->resultType)); etv->indexer = TableIndexer(resolveType(scope, *indexer->indexType), resolveType(scope, *indexer->resultType));
TableType* metatable = getMutable<TableType>(*ctv->metatable); TableType* metatable = getMutable<TableType>(*etv->metatable);
for (const AstDeclaredClassProp& prop : declaredClass.props) for (const AstDeclaredExternTypeProperty& prop : declaredExternType.props)
{ {
Name propName(prop.name.value); Name propName(prop.name.value);
TypeId propTy = resolveType(scope, *prop.ty); TypeId propTy = resolveType(scope, *prop.ty);
bool assignToMetatable = isMetamethod(propName); bool assignToMetatable = isMetamethod(propName);
Luau::ClassType::Props& assignTo = assignToMetatable ? metatable->props : ctv->props; Luau::ExternType::Props& assignTo = assignToMetatable ? metatable->props : etv->props;
// Function types always take 'self', but this isn't reflected in the // Function types always take 'self', but this isn't reflected in the
// parsed annotation. Add it here. // parsed annotation. Add it here.
@ -1770,7 +1771,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass&
if (FunctionType* ftv = getMutable<FunctionType>(propTy)) if (FunctionType* ftv = getMutable<FunctionType>(propTy))
{ {
ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}}); ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}});
ftv->argTypes = addTypePack(TypePack{{classTy}, ftv->argTypes}); ftv->argTypes = addTypePack(TypePack{{externTy}, ftv->argTypes});
ftv->hasSelf = true; ftv->hasSelf = true;
FunctionDefinition defn; FunctionDefinition defn;
@ -1813,7 +1814,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass&
} }
else else
{ {
reportError(declaredClass.location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())}); reportError(declaredExternType.location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())});
} }
} }
} }
@ -1852,7 +1853,8 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareFuncti
); );
TypePackId argPack = resolveTypePack(funScope, global.params); TypePackId argPack = resolveTypePack(funScope, global.params);
TypePackId retPack = resolveTypePack(funScope, global.retTypes); TypePackId retPack =
FFlag::LuauStoreReturnTypesAsPackOnAst ? resolveTypePack(funScope, *global.retTypes) : resolveTypePack(funScope, global.retTypes_DEPRECATED);
FunctionDefinition defn; FunctionDefinition defn;
@ -2137,9 +2139,9 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromTypeImpl(
if (auto found = findTablePropertyRespectingMeta(type, name, location, addErrors)) if (auto found = findTablePropertyRespectingMeta(type, name, location, addErrors))
return *found; return *found;
} }
else if (const ClassType* cls = get<ClassType>(type)) else if (const ExternType* cls = get<ExternType>(type))
{ {
const Property* prop = lookupClassProp(cls, name); const Property* prop = lookupExternTypeProp(cls, name);
if (prop) if (prop)
return prop->type(); return prop->type();
@ -3462,14 +3464,14 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
return errorRecoveryType(scope); return errorRecoveryType(scope);
} }
} }
else if (const ClassType* lhsClass = get<ClassType>(lhs)) else if (const ExternType* lhsExternType = get<ExternType>(lhs))
{ {
if (const Property* prop = lookupClassProp(lhsClass, name)) if (const Property* prop = lookupExternTypeProp(lhsExternType, name))
{ {
return prop->type(); return prop->type();
} }
if (auto indexer = lhsClass->indexer) if (auto indexer = lhsExternType->indexer)
{ {
Unifier state = mkUnifier(scope, expr.location); Unifier state = mkUnifier(scope, expr.location);
state.tryUnify(stringType, indexer->indexType); state.tryUnify(stringType, indexer->indexType);
@ -3521,14 +3523,14 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
if (value) if (value)
{ {
if (const ClassType* exprClass = get<ClassType>(exprType)) if (const ExternType* exprExternType = get<ExternType>(exprType))
{ {
if (const Property* prop = lookupClassProp(exprClass, value->value.data)) if (const Property* prop = lookupExternTypeProp(exprExternType, value->value.data))
{ {
return prop->type(); return prop->type();
} }
if (auto indexer = exprClass->indexer) if (auto indexer = exprExternType->indexer)
{ {
unify(stringType, indexer->indexType, scope, expr.index->location); unify(stringType, indexer->indexType, scope, expr.index->location);
return indexer->indexResultType; return indexer->indexResultType;
@ -3554,20 +3556,20 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
} }
else else
{ {
if (const ClassType* exprClass = get<ClassType>(exprType)) if (const ExternType* exprExternType = get<ExternType>(exprType))
{ {
if (auto indexer = exprClass->indexer) if (auto indexer = exprExternType->indexer)
{ {
unify(indexType, indexer->indexType, scope, expr.index->location); unify(indexType, indexer->indexType, scope, expr.index->location);
return indexer->indexResultType; return indexer->indexResultType;
} }
} }
if (const ClassType* exprClass = get<ClassType>(exprType)) if (const ExternType* exprExternType = get<ExternType>(exprType))
{ {
if (isNonstrictMode()) if (isNonstrictMode())
return unknownType; return unknownType;
reportError(TypeError{expr.location, DynamicPropertyLookupOnClassesUnsafe{exprType}}); reportError(TypeError{expr.location, DynamicPropertyLookupOnExternTypesUnsafe{exprType}});
return errorRecoveryType(scope); return errorRecoveryType(scope);
} }
} }
@ -3848,8 +3850,10 @@ std::pair<TypeId, ScopePtr> TypeChecker::checkFunctionSignature(
auto [generics, genericPacks] = createGenericTypes(funScope, std::nullopt, expr, expr.generics, expr.genericPacks); auto [generics, genericPacks] = createGenericTypes(funScope, std::nullopt, expr, expr.generics, expr.genericPacks);
TypePackId retPack; TypePackId retPack;
if (expr.returnAnnotation) if (FFlag::LuauStoreReturnTypesAsPackOnAst && expr.returnAnnotation)
retPack = resolveTypePack(funScope, *expr.returnAnnotation); retPack = resolveTypePack(funScope, *expr.returnAnnotation);
else if (!FFlag::LuauStoreReturnTypesAsPackOnAst && expr.returnAnnotation_DEPRECATED)
retPack = resolveTypePack(funScope, *expr.returnAnnotation_DEPRECATED);
else if (isNonstrictMode()) else if (isNonstrictMode())
retPack = anyTypePack; retPack = anyTypePack;
else if (expectedFunctionType && expectedFunctionType->generics.empty() && expectedFunctionType->genericPacks.empty()) else if (expectedFunctionType && expectedFunctionType->generics.empty() && expectedFunctionType->genericPacks.empty())
@ -4056,7 +4060,8 @@ void TypeChecker::checkFunctionBody(const ScopePtr& scope, TypeId ty, const AstE
// If we're in nonstrict mode we want to only report this missing return // If we're in nonstrict mode we want to only report this missing return
// statement if there are type annotations on the function. In strict mode // statement if there are type annotations on the function. In strict mode
// we report it regardless. // we report it regardless.
if (!isNonstrictMode() || function.returnAnnotation) if (!isNonstrictMode() ||
(FFlag::LuauStoreReturnTypesAsPackOnAst ? function.returnAnnotation != nullptr : function.returnAnnotation_DEPRECATED.has_value()))
{ {
reportError(getEndLocation(function), FunctionExitsWithoutReturning{funTy->retTypes}); reportError(getEndLocation(function), FunctionExitsWithoutReturning{funTy->retTypes});
} }
@ -4611,9 +4616,9 @@ std::unique_ptr<WithPredicate<TypePackId>> TypeChecker::checkCallOverload(
{ {
callTy = getIndexTypeFromType(scope, mttv->metatable, "__call", expr.func->location, /* addErrors= */ false); callTy = getIndexTypeFromType(scope, mttv->metatable, "__call", expr.func->location, /* addErrors= */ false);
} }
else if (const ClassType* ctv = get<ClassType>(fn); ctv && ctv->metatable) else if (const ExternType* etv = get<ExternType>(fn); etv && etv->metatable)
{ {
callTy = getIndexTypeFromType(scope, *ctv->metatable, "__call", expr.func->location, /* addErrors= */ false); callTy = getIndexTypeFromType(scope, *etv->metatable, "__call", expr.func->location, /* addErrors= */ false);
} }
if (callTy) if (callTy)
@ -5316,17 +5321,17 @@ void TypeChecker::diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData& d
if (auto ttv = getTableType(utk->table)) if (auto ttv = getTableType(utk->table))
accumulate(ttv->props); accumulate(ttv->props);
else if (auto ctv = get<ClassType>(follow(utk->table))) else if (auto etv = get<ExternType>(follow(utk->table)))
{ {
while (ctv) while (etv)
{ {
accumulate(ctv->props); accumulate(etv->props);
if (!ctv->parent) if (!etv->parent)
break; break;
ctv = get<ClassType>(*ctv->parent); etv = get<ExternType>(*etv->parent);
LUAU_ASSERT(ctv); LUAU_ASSERT(etv);
} }
} }
@ -5803,7 +5808,8 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno
auto [generics, genericPacks] = createGenericTypes(funcScope, std::nullopt, annotation, func->generics, func->genericPacks); auto [generics, genericPacks] = createGenericTypes(funcScope, std::nullopt, annotation, func->generics, func->genericPacks);
TypePackId argTypes = resolveTypePack(funcScope, func->argTypes); TypePackId argTypes = resolveTypePack(funcScope, func->argTypes);
TypePackId retTypes = resolveTypePack(funcScope, func->returnTypes); TypePackId retTypes = FFlag::LuauStoreReturnTypesAsPackOnAst ? resolveTypePack(funcScope, *func->returnTypes)
: resolveTypePack(funcScope, func->returnTypes_DEPRECATED);
std::vector<TypeId> genericTys; std::vector<TypeId> genericTys;
genericTys.reserve(generics.size()); genericTys.reserve(generics.size());
@ -5855,12 +5861,8 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno
} }
else if (const auto& un = annotation.as<AstTypeUnion>()) else if (const auto& un = annotation.as<AstTypeUnion>())
{ {
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) if (un->types.size == 1)
{ return resolveType(scope, *un->types.data[0]);
if (un->types.size == 1)
return resolveType(scope, *un->types.data[0]);
}
std::vector<TypeId> types; std::vector<TypeId> types;
for (AstType* ann : un->types) for (AstType* ann : un->types)
types.push_back(resolveType(scope, *ann)); types.push_back(resolveType(scope, *ann));
@ -5869,12 +5871,8 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno
} }
else if (const auto& un = annotation.as<AstTypeIntersection>()) else if (const auto& un = annotation.as<AstTypeIntersection>())
{ {
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) if (un->types.size == 1)
{ return resolveType(scope, *un->types.data[0]);
if (un->types.size == 1)
return resolveType(scope, *un->types.data[0]);
}
std::vector<TypeId> types; std::vector<TypeId> types;
for (AstType* ann : un->types) for (AstType* ann : un->types)
types.push_back(resolveType(scope, *ann)); types.push_back(resolveType(scope, *ann));
@ -6481,7 +6479,7 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& r
return refine( return refine(
[](TypeId ty) -> bool [](TypeId ty) -> bool
{ {
return get<ClassType>(ty); return get<ExternType>(ty);
} }
); );
} }
@ -6496,13 +6494,13 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& r
TypeId type = follow(typeFun->type); TypeId type = follow(typeFun->type);
// You cannot refine to the top class type. // You cannot refine to the top class type.
if (type == builtinTypes->classType) if (type == builtinTypes->externType)
{ {
return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope)); return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
} }
// We're only interested in the root class of any classes. // We're only interested in the root type of any extern type.
if (auto ctv = get<ClassType>(type); !ctv || (ctv->parent != builtinTypes->classType && !hasTag(type, kTypeofRootTag))) if (auto etv = get<ExternType>(type); !etv || (etv->parent != builtinTypes->externType && !hasTag(type, kTypeofRootTag)))
return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope)); return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
// This probably hints at breaking out type filtering functions from the predicate solver so that typeof is not tightly coupled with IsA. // This probably hints at breaking out type filtering functions from the predicate solver so that typeof is not tightly coupled with IsA.

View file

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

View file

@ -11,8 +11,6 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete);
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope);
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode) LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode)
@ -327,8 +325,7 @@ TypePack extendTypePack(
{ {
FreeType ft{ftp->scope, builtinTypes->neverType, builtinTypes->unknownType, ftp->polarity}; FreeType ft{ftp->scope, builtinTypes->neverType, builtinTypes->unknownType, ftp->polarity};
t = arena.addType(ft); t = arena.addType(ft);
if (FFlag::LuauTrackInteriorFreeTypesOnScope) trackInteriorFreeType(ftp->scope, t);
trackInteriorFreeType(ftp->scope, t);
} }
else else
t = FFlag::LuauFreeTypesMustHaveBounds ? arena.freshType(builtinTypes, ftp->scope) : arena.freshType_DEPRECATED(ftp->scope); t = FFlag::LuauFreeTypesMustHaveBounds ? arena.freshType(builtinTypes, ftp->scope) : arena.freshType_DEPRECATED(ftp->scope);
@ -438,7 +435,6 @@ TypeId stripNil(NotNull<BuiltinTypes> builtinTypes, TypeArena& arena, TypeId ty)
ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypeId ty) ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypeId ty)
{ {
LUAU_ASSERT(FFlag::LuauSolverV2 || FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete);
std::shared_ptr<const NormalizedType> normType = normalizer->normalize(ty); std::shared_ptr<const NormalizedType> normType = normalizer->normalize(ty);
if (!normType) if (!normType)
@ -556,10 +552,8 @@ std::vector<TypeId> findBlockedArgTypesIn(AstExprCall* expr, NotNull<DenseHashMa
void trackInteriorFreeType(Scope* scope, TypeId ty) void trackInteriorFreeType(Scope* scope, TypeId ty)
{ {
if (FFlag::LuauDisableNewSolverAssertsInMixedMode) if (!FFlag::LuauDisableNewSolverAssertsInMixedMode)
LUAU_ASSERT(FFlag::LuauTrackInteriorFreeTypesOnScope); LUAU_ASSERT(FFlag::LuauSolverV2);
else
LUAU_ASSERT(FFlag::LuauSolverV2 && FFlag::LuauTrackInteriorFreeTypesOnScope);
for (; scope; scope = scope->parent.get()) for (; scope; scope = scope->parent.get())
{ {
if (scope->interiorFreeTypes) if (scope->interiorFreeTypes)

View file

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

View file

@ -321,9 +321,9 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn)
{ {
for (TypeId generic : subFn->generics) for (TypeId generic : subFn->generics)
{ {
const GenericType* gen = get<GenericType>(generic); const GenericType* gen = get<GenericType>(follow(generic));
LUAU_ASSERT(gen); if (gen)
genericSubstitutions[generic] = freshType(scope, gen->polarity); genericSubstitutions[generic] = freshType(scope, gen->polarity);
} }
for (TypePackId genericPack : subFn->genericPacks) for (TypePackId genericPack : subFn->genericPacks)
@ -331,8 +331,8 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn)
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization2)
{ {
const GenericTypePack* gen = get<GenericTypePack>(follow(genericPack)); const GenericTypePack* gen = get<GenericTypePack>(follow(genericPack));
LUAU_ASSERT(gen); if (gen)
genericPackSubstitutions[genericPack] = freshTypePack(scope, gen->polarity); genericPackSubstitutions[genericPack] = freshTypePack(scope, gen->polarity);
} }
else else
genericPackSubstitutions[genericPack] = arena->freshTypePack(scope); genericPackSubstitutions[genericPack] = arena->freshTypePack(scope);

View file

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

View file

@ -473,8 +473,10 @@ class CstTypePackExplicit : public CstNode
public: public:
LUAU_CST_RTTI(CstTypePackExplicit) LUAU_CST_RTTI(CstTypePackExplicit)
CstTypePackExplicit(Position openParenthesesPosition, Position closeParenthesesPosition, AstArray<Position> commaPositions); explicit CstTypePackExplicit();
explicit CstTypePackExplicit(Position openParenthesesPosition, Position closeParenthesesPosition, AstArray<Position> commaPositions);
bool hasParentheses;
Position openParenthesesPosition; Position openParenthesesPosition;
Position closeParenthesesPosition; Position closeParenthesesPosition;
AstArray<Position> commaPositions; AstArray<Position> commaPositions;

View file

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

View file

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

View file

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

View file

@ -19,14 +19,14 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
// See docs/SyntaxChanges.md for an explanation. // See docs/SyntaxChanges.md for an explanation.
LUAU_FASTFLAGVARIABLE(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauStoreCSTData2) LUAU_FASTFLAGVARIABLE(LuauStoreCSTData2)
LUAU_FASTFLAGVARIABLE(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAGVARIABLE(LuauAstTypeGroup3) LUAU_FASTFLAGVARIABLE(LuauAstTypeGroup3)
LUAU_FASTFLAGVARIABLE(ParserNoErrorLimit)
LUAU_FASTFLAGVARIABLE(LuauFixDoBlockEndLocation) LUAU_FASTFLAGVARIABLE(LuauFixDoBlockEndLocation)
LUAU_FASTFLAGVARIABLE(LuauParseOptionalAsNode2) LUAU_FASTFLAGVARIABLE(LuauParseOptionalAsNode2)
LUAU_FASTFLAGVARIABLE(LuauDeclareExternType)
LUAU_FASTFLAGVARIABLE(LuauParseStringIndexer) LUAU_FASTFLAGVARIABLE(LuauParseStringIndexer)
LUAU_FASTFLAGVARIABLE(LuauTypeFunResultInAutocomplete) LUAU_FASTFLAGVARIABLE(LuauTypeFunResultInAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauDeprecatedAttribute) LUAU_FASTFLAGVARIABLE(LuauDeprecatedAttribute)
LUAU_FASTFLAGVARIABLE(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauFixFunctionWithAttributesStartLocation) LUAU_FASTFLAGVARIABLE(LuauFixFunctionWithAttributesStartLocation)
LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false) LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false)
@ -788,7 +788,9 @@ AstStat* Parser::parseFunctionStat(const AstArray<AstAttr*>& attributes)
matchRecoveryStopOnToken[Lexeme::ReservedEnd]++; matchRecoveryStopOnToken[Lexeme::ReservedEnd]++;
AstExprFunction* body = parseFunctionBody(hasself, matchFunction, debugname, nullptr, attributes).first; AstExprFunction* body = FFlag::LuauStoreReturnTypesAsPackOnAst
? parseFunctionBody(hasself, matchFunction, debugname, nullptr, attributes).first
: parseFunctionBody_DEPRECATED(hasself, matchFunction, debugname, nullptr, attributes).first;
matchRecoveryStopOnToken[Lexeme::ReservedEnd]--; matchRecoveryStopOnToken[Lexeme::ReservedEnd]--;
@ -941,7 +943,8 @@ AstStat* Parser::parseLocal(const AstArray<AstAttr*>& attributes)
matchRecoveryStopOnToken[Lexeme::ReservedEnd]++; matchRecoveryStopOnToken[Lexeme::ReservedEnd]++;
auto [body, var] = parseFunctionBody(false, matchFunction, name.name, &name, attributes); auto [body, var] = FFlag::LuauStoreReturnTypesAsPackOnAst ? parseFunctionBody(false, matchFunction, name.name, &name, attributes)
: parseFunctionBody_DEPRECATED(false, matchFunction, name.name, &name, attributes);
matchRecoveryStopOnToken[Lexeme::ReservedEnd]--; matchRecoveryStopOnToken[Lexeme::ReservedEnd]--;
@ -1111,7 +1114,10 @@ AstStat* Parser::parseTypeFunction(const Location& start, bool exported, Positio
size_t oldTypeFunctionDepth = typeFunctionDepth; size_t oldTypeFunctionDepth = typeFunctionDepth;
typeFunctionDepth = functionStack.size(); typeFunctionDepth = functionStack.size();
AstExprFunction* body = parseFunctionBody(/* hasself */ false, matchFn, fnName->name, nullptr, AstArray<AstAttr*>({nullptr, 0})).first; AstExprFunction* body =
FFlag::LuauStoreReturnTypesAsPackOnAst
? parseFunctionBody(/* hasself */ false, matchFn, fnName->name, nullptr, AstArray<AstAttr*>({nullptr, 0})).first
: parseFunctionBody_DEPRECATED(/* hasself */ false, matchFn, fnName->name, nullptr, AstArray<AstAttr*>({nullptr, 0})).first;
typeFunctionDepth = oldTypeFunctionDepth; typeFunctionDepth = oldTypeFunctionDepth;
@ -1133,7 +1139,7 @@ AstStat* Parser::parseTypeFunction(const Location& start, bool exported, Positio
} }
} }
AstDeclaredClassProp Parser::parseDeclaredClassMethod(const AstArray<AstAttr*>& attributes) AstDeclaredExternTypeProperty Parser::parseDeclaredExternTypeMethod(const AstArray<AstAttr*>& attributes)
{ {
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute); LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
@ -1164,7 +1170,18 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod(const AstArray<AstAttr*>&
expectMatchAndConsume(')', matchParen); expectMatchAndConsume(')', matchParen);
AstTypeList retTypes = parseOptionalReturnType().value_or(AstTypeList{copy<AstType*>(nullptr, 0), nullptr}); AstTypePack* retTypes;
AstTypeList retTypes_DEPRECATED;
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
retTypes = parseOptionalReturnType();
if (!retTypes)
retTypes = allocator.alloc<AstTypePackExplicit>(lexer.current().location, AstTypeList{copy<AstType*>(nullptr, 0), nullptr});
}
else
{
retTypes_DEPRECATED = parseOptionalReturnType_DEPRECATED().value_or(AstTypeList{copy<AstType*>(nullptr, 0), nullptr});
}
Location end = lexer.previousLocation(); Location end = lexer.previousLocation();
TempVector<AstType*> vars(scratchType); TempVector<AstType*> vars(scratchType);
@ -1172,7 +1189,7 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod(const AstArray<AstAttr*>&
if (args.size() == 0 || args[0].name.name != "self" || args[0].annotation != nullptr) if (args.size() == 0 || args[0].name.name != "self" || args[0].annotation != nullptr)
{ {
return AstDeclaredClassProp{ return AstDeclaredExternTypeProperty{
fnName.name, fnName.location, reportTypeError(Location(start, end), {}, "'self' must be present as the unannotated first parameter"), true fnName.name, fnName.location, reportTypeError(Location(start, end), {}, "'self' must be present as the unannotated first parameter"), true
}; };
} }
@ -1191,14 +1208,25 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod(const AstArray<AstAttr*>&
if (vararg && !varargAnnotation) if (vararg && !varargAnnotation)
report(start, "All declaration parameters aside from 'self' must be annotated"); report(start, "All declaration parameters aside from 'self' must be annotated");
AstType* fnType = allocator.alloc<AstTypeFunction>( AstType* fnType =
Location(start, end), attributes, generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes FFlag::LuauStoreReturnTypesAsPackOnAst
); ? allocator.alloc<AstTypeFunction>(
Location(start, end), attributes, generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes
)
: allocator.alloc<AstTypeFunction>(
Location(start, end),
attributes,
generics,
genericPacks,
AstTypeList{copy(vars), varargAnnotation},
copy(varNames),
retTypes_DEPRECATED
);
return AstDeclaredClassProp{fnName.name, fnName.location, fnType, true, Location(start, end)}; return AstDeclaredExternTypeProperty{fnName.name, fnName.location, fnType, true, Location(start, end)};
} }
AstDeclaredClassProp Parser::parseDeclaredClassMethod_DEPRECATED() AstDeclaredExternTypeProperty Parser::parseDeclaredExternTypeMethod_DEPRECATED()
{ {
Location start = lexer.current().location; Location start = lexer.current().location;
@ -1227,7 +1255,18 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod_DEPRECATED()
expectMatchAndConsume(')', matchParen); expectMatchAndConsume(')', matchParen);
AstTypeList retTypes = parseOptionalReturnType().value_or(AstTypeList{copy<AstType*>(nullptr, 0), nullptr}); AstTypePack* retTypes;
AstTypeList retTypes_DEPRECATED;
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
retTypes = parseOptionalReturnType();
if (!retTypes)
retTypes = allocator.alloc<AstTypePackExplicit>(lexer.current().location, AstTypeList{copy<AstType*>(nullptr, 0), nullptr});
}
else
{
retTypes_DEPRECATED = parseOptionalReturnType_DEPRECATED().value_or(AstTypeList{copy<AstType*>(nullptr, 0), nullptr});
}
Location end = lexer.previousLocation(); Location end = lexer.previousLocation();
TempVector<AstType*> vars(scratchType); TempVector<AstType*> vars(scratchType);
@ -1235,7 +1274,7 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod_DEPRECATED()
if (args.size() == 0 || args[0].name.name != "self" || args[0].annotation != nullptr) if (args.size() == 0 || args[0].name.name != "self" || args[0].annotation != nullptr)
{ {
return AstDeclaredClassProp{ return AstDeclaredExternTypeProperty{
fnName.name, fnName.location, reportTypeError(Location(start, end), {}, "'self' must be present as the unannotated first parameter"), true fnName.name, fnName.location, reportTypeError(Location(start, end), {}, "'self' must be present as the unannotated first parameter"), true
}; };
} }
@ -1254,11 +1293,16 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod_DEPRECATED()
if (vararg && !varargAnnotation) if (vararg && !varargAnnotation)
report(start, "All declaration parameters aside from 'self' must be annotated"); report(start, "All declaration parameters aside from 'self' must be annotated");
AstType* fnType = allocator.alloc<AstTypeFunction>( AstType* fnType =
Location(start, end), generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes FFlag::LuauStoreReturnTypesAsPackOnAst
); ? allocator.alloc<AstTypeFunction>(
Location(start, end), generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes
)
: allocator.alloc<AstTypeFunction>(
Location(start, end), generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes_DEPRECATED
);
return AstDeclaredClassProp{fnName.name, fnName.location, fnType, true, Location(start, end)}; return AstDeclaredExternTypeProperty{fnName.name, fnName.location, fnType, true, Location(start, end)};
} }
AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*>& attributes) AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*>& attributes)
@ -1296,7 +1340,18 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
expectMatchAndConsume(')', matchParen); expectMatchAndConsume(')', matchParen);
AstTypeList retTypes = parseOptionalReturnType().value_or(AstTypeList{copy<AstType*>(nullptr, 0)}); AstTypePack* retTypes;
AstTypeList retTypes_DEPRECATED;
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
retTypes = parseOptionalReturnType();
if (!retTypes)
retTypes = allocator.alloc<AstTypePackExplicit>(lexer.current().location, AstTypeList{copy<AstType*>(nullptr, 0), nullptr});
}
else
{
retTypes_DEPRECATED = parseOptionalReturnType_DEPRECATED().value_or(AstTypeList{copy<AstType*>(nullptr, 0)});
}
Location end = lexer.current().location; Location end = lexer.current().location;
TempVector<AstType*> vars(scratchType); TempVector<AstType*> vars(scratchType);
@ -1314,34 +1369,77 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
if (vararg && !varargAnnotation) if (vararg && !varargAnnotation)
return reportStatError(Location(start, end), {}, {}, "All declaration parameters must be annotated"); return reportStatError(Location(start, end), {}, {}, "All declaration parameters must be annotated");
return allocator.alloc<AstStatDeclareFunction>( if (FFlag::LuauStoreReturnTypesAsPackOnAst)
Location(start, end), {
attributes, return allocator.alloc<AstStatDeclareFunction>(
globalName.name, Location(start, end),
globalName.location, attributes,
generics, globalName.name,
genericPacks, globalName.location,
AstTypeList{copy(vars), varargAnnotation}, generics,
copy(varNames), genericPacks,
vararg, AstTypeList{copy(vars), varargAnnotation},
varargLocation, copy(varNames),
retTypes vararg,
); varargLocation,
retTypes
);
}
else
{
return allocator.alloc<AstStatDeclareFunction>(
Location(start, end),
attributes,
globalName.name,
globalName.location,
generics,
genericPacks,
AstTypeList{copy(vars), varargAnnotation},
copy(varNames),
vararg,
varargLocation,
retTypes_DEPRECATED
);
}
} }
else if (AstName(lexer.current().name) == "class") else if (AstName(lexer.current().name) == "class" || (FFlag::LuauDeclareExternType && AstName(lexer.current().name) == "extern"))
{ {
bool foundExtern = false;
if (FFlag::LuauDeclareExternType)
{
if (AstName(lexer.current().name) == "extern")
{
foundExtern = true;
nextLexeme();
if (AstName(lexer.current().name) != "type")
return reportStatError(lexer.current().location, {}, {}, "Expected `type` keyword after `extern`, but got %s instead", lexer.current().name);
}
}
nextLexeme(); nextLexeme();
Location classStart = lexer.current().location; Location classStart = lexer.current().location;
Name className = parseName("class name"); Name className = parseName(FFlag::LuauDeclareExternType ? "type name" : "class name");
std::optional<AstName> superName = std::nullopt; std::optional<AstName> superName = std::nullopt;
if (AstName(lexer.current().name) == "extends") if (AstName(lexer.current().name) == "extends")
{ {
nextLexeme(); nextLexeme();
superName = parseName("superclass name").name; superName = parseName(FFlag::LuauDeclareExternType ? "supertype name" : "superclass name").name;
} }
TempVector<AstDeclaredClassProp> props(scratchDeclaredClassProps); if (FFlag::LuauDeclareExternType)
{
if (foundExtern)
{
if (AstName(lexer.current().name) != "with")
report(lexer.current().location, "Expected `with` keyword before listing properties of the external type, but got %s instead", lexer.current().name);
else
nextLexeme();
}
}
TempVector<AstDeclaredExternTypeProperty> props(scratchDeclaredClassProps);
AstTableIndexer* indexer = nullptr; AstTableIndexer* indexer = nullptr;
while (lexer.current().type != Lexeme::ReservedEnd) while (lexer.current().type != Lexeme::ReservedEnd)
@ -1368,9 +1466,9 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
if (lexer.current().type == Lexeme::ReservedFunction) if (lexer.current().type == Lexeme::ReservedFunction)
{ {
if (FFlag::LuauDeprecatedAttribute) if (FFlag::LuauDeprecatedAttribute)
props.push_back(parseDeclaredClassMethod(attributes)); props.push_back(parseDeclaredExternTypeMethod(attributes));
else else
props.push_back(parseDeclaredClassMethod_DEPRECATED()); props.push_back(parseDeclaredExternTypeMethod_DEPRECATED());
} }
else if (lexer.current().type == '[') else if (lexer.current().type == '[')
{ {
@ -1393,7 +1491,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
if (chars && !containsNull) if (chars && !containsNull)
{ {
props.push_back(AstDeclaredClassProp{ props.push_back(AstDeclaredExternTypeProperty{
AstName(chars->data), Location(nameBegin, nameEnd), type, false, Location(begin.location, lexer.previousLocation()) AstName(chars->data), Location(nameBegin, nameEnd), type, false, Location(begin.location, lexer.previousLocation())
}); });
} }
@ -1413,7 +1511,10 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
badIndexer = parseTableIndexer_DEPRECATED(AstTableAccess::ReadWrite, std::nullopt, begin); badIndexer = parseTableIndexer_DEPRECATED(AstTableAccess::ReadWrite, std::nullopt, begin);
// we lose all additional indexer expressions from the AST after error recovery here // we lose all additional indexer expressions from the AST after error recovery here
report(badIndexer->location, "Cannot have more than one class indexer"); if (FFlag::LuauDeclareExternType)
report(badIndexer->location, "Cannot have more than one indexer on an extern type");
else
report(badIndexer->location, "Cannot have more than one class indexer");
} }
else else
{ {
@ -1434,7 +1535,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
expectAndConsume(':', "property type annotation"); expectAndConsume(':', "property type annotation");
AstType* propType = parseType(); AstType* propType = parseType();
props.push_back( props.push_back(
AstDeclaredClassProp{propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())} AstDeclaredExternTypeProperty{propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())}
); );
} }
} }
@ -1444,9 +1545,9 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
if (lexer.current().type == Lexeme::ReservedFunction) if (lexer.current().type == Lexeme::ReservedFunction)
{ {
if (FFlag::LuauDeprecatedAttribute) if (FFlag::LuauDeprecatedAttribute)
props.push_back(parseDeclaredClassMethod(attributes)); props.push_back(parseDeclaredExternTypeMethod(attributes));
else else
props.push_back(parseDeclaredClassMethod_DEPRECATED()); props.push_back(parseDeclaredExternTypeMethod_DEPRECATED());
} }
else if (lexer.current().type == '[' && else if (lexer.current().type == '[' &&
(lexer.lookahead().type == Lexeme::RawString || lexer.lookahead().type == Lexeme::QuotedString)) (lexer.lookahead().type == Lexeme::RawString || lexer.lookahead().type == Lexeme::QuotedString))
@ -1468,7 +1569,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
if (chars && !containsNull) if (chars && !containsNull)
{ {
props.push_back(AstDeclaredClassProp{ props.push_back(AstDeclaredExternTypeProperty{
AstName(chars->data), Location(nameBegin, nameEnd), type, false, Location(begin.location, lexer.previousLocation()) AstName(chars->data), Location(nameBegin, nameEnd), type, false, Location(begin.location, lexer.previousLocation())
}); });
} }
@ -1492,7 +1593,10 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
badIndexer = parseTableIndexer_DEPRECATED(AstTableAccess::ReadWrite, std::nullopt, lexer.current()); badIndexer = parseTableIndexer_DEPRECATED(AstTableAccess::ReadWrite, std::nullopt, lexer.current());
// we lose all additional indexer expressions from the AST after error recovery here // we lose all additional indexer expressions from the AST after error recovery here
report(badIndexer->location, "Cannot have more than one class indexer"); if (FFlag::LuauDeclareExternType)
report(badIndexer->location, "Cannot have more than one indexer on an extern type");
else
report(badIndexer->location, "Cannot have more than one class indexer");
} }
else else
{ {
@ -1515,7 +1619,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
expectAndConsume(':', "property type annotation"); expectAndConsume(':', "property type annotation");
AstType* propType = parseType(); AstType* propType = parseType();
props.push_back( props.push_back(
AstDeclaredClassProp{propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())} AstDeclaredExternTypeProperty{propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())}
); );
} }
} }
@ -1524,7 +1628,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
Location classEnd = lexer.current().location; Location classEnd = lexer.current().location;
nextLexeme(); // skip past `end` nextLexeme(); // skip past `end`
return allocator.alloc<AstStatDeclareClass>(Location(classStart, classEnd), className.name, superName, copy(props), indexer); return allocator.alloc<AstStatDeclareExternType>(Location(classStart, classEnd), className.name, superName, copy(props), indexer);
} }
else if (std::optional<Name> globalName = parseNameOpt("global variable name")) else if (std::optional<Name> globalName = parseNameOpt("global variable name"))
{ {
@ -1533,6 +1637,10 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
AstType* type = parseType(/* in declaration context */ true); AstType* type = parseType(/* in declaration context */ true);
return allocator.alloc<AstStatDeclareGlobal>(Location(start, type->location), globalName->name, globalName->location, type); return allocator.alloc<AstStatDeclareGlobal>(Location(start, type->location), globalName->name, globalName->location, type);
} }
else if (FFlag::LuauDeclareExternType)
{
return reportStatError(start, {}, {}, "declare must be followed by an identifier, 'function', or 'extern type'");
}
else else
{ {
return reportStatError(start, {}, {}, "declare must be followed by an identifier, 'function', or 'class'"); return reportStatError(start, {}, {}, "declare must be followed by an identifier, 'function', or 'class'");
@ -1638,6 +1746,8 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
const AstArray<AstAttr*>& attributes const AstArray<AstAttr*>& attributes
) )
{ {
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
Location start = matchFunction.location; Location start = matchFunction.location;
if (FFlag::LuauFixFunctionWithAttributesStartLocation) if (FFlag::LuauFixFunctionWithAttributesStartLocation)
@ -1689,7 +1799,145 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
matchRecoveryStopOnToken[')']--; matchRecoveryStopOnToken[')']--;
std::optional<AstTypeList> typelist = parseOptionalReturnType(cstNode ? &cstNode->returnSpecifierPosition : nullptr); AstTypePack* typelist = parseOptionalReturnType(cstNode ? &cstNode->returnSpecifierPosition : nullptr);
AstLocal* funLocal = nullptr;
if (localName)
funLocal = pushLocal(Binding(*localName, nullptr));
unsigned int localsBegin = saveLocals();
Function fun;
fun.vararg = vararg;
functionStack.emplace_back(fun);
auto [self, vars] = prepareFunctionArguments(start, hasself, args);
AstStatBlock* body = parseBlock();
functionStack.pop_back();
restoreLocals(localsBegin);
Location end = lexer.current().location;
bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchFunction);
body->hasEnd = hasEnd;
if (FFlag::LuauStoreCSTData2)
{
AstExprFunction* node = allocator.alloc<AstExprFunction>(
Location(start, end),
attributes,
generics,
genericPacks,
self,
vars,
vararg,
varargLocation,
body,
functionStack.size(),
debugname,
typelist,
varargAnnotation,
argLocation
);
if (options.storeCstData)
{
cstNode->functionKeywordPosition = matchFunction.location.begin;
cstNodeMap[node] = cstNode;
}
return {node, funLocal};
}
else
{
return {
allocator.alloc<AstExprFunction>(
Location(start, end),
attributes,
generics,
genericPacks,
self,
vars,
vararg,
varargLocation,
body,
functionStack.size(),
debugname,
typelist,
varargAnnotation,
argLocation
),
funLocal
};
}
}
std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody_DEPRECATED(
bool hasself,
const Lexeme& matchFunction,
const AstName& debugname,
const Name* localName,
const AstArray<AstAttr*>& attributes
)
{
LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst);
Location start = matchFunction.location;
if (FFlag::LuauFixFunctionWithAttributesStartLocation)
{
if (attributes.size > 0)
start = attributes.data[0]->location;
}
auto* cstNode = FFlag::LuauStoreCSTData2 && options.storeCstData ? allocator.alloc<CstExprFunction>() : nullptr;
auto [generics, genericPacks] =
FFlag::LuauStoreCSTData2 && cstNode
? parseGenericTypeList(
/* withDefaultValues= */ false, &cstNode->openGenericsPosition, &cstNode->genericsCommaPositions, &cstNode->closeGenericsPosition
)
: parseGenericTypeList(/* withDefaultValues= */ false);
MatchLexeme matchParen = lexer.current();
expectAndConsume('(', "function");
// NOTE: This was added in conjunction with passing `searchForMissing` to
// `expectMatchAndConsume` inside `parseTableType` so that the behavior of
// parsing code like below (note the missing `}`):
//
// function (t: { a: number ) end
//
// ... will still parse as (roughly):
//
// function (t: { a: number }) end
//
matchRecoveryStopOnToken[')']++;
TempVector<Binding> args(scratchBinding);
bool vararg = false;
Location varargLocation;
AstTypePack* varargAnnotation = nullptr;
if (lexer.current().type != ')')
std::tie(vararg, varargLocation, varargAnnotation) =
parseBindingList(args, /* allowDot3= */ true, cstNode ? &cstNode->argsCommaPositions : nullptr);
std::optional<Location> argLocation;
if (matchParen.type == Lexeme::Type('(') && lexer.current().type == Lexeme::Type(')'))
argLocation = Location(matchParen.position, lexer.current().location.end);
expectMatchAndConsume(')', matchParen, true);
matchRecoveryStopOnToken[')']--;
std::optional<AstTypeList> typelist = parseOptionalReturnType_DEPRECATED(cstNode ? &cstNode->returnSpecifierPosition : nullptr);
AstLocal* funLocal = nullptr; AstLocal* funLocal = nullptr;
@ -1916,8 +2164,9 @@ AstTypePack* Parser::parseTypeList(
return nullptr; return nullptr;
} }
std::optional<AstTypeList> Parser::parseOptionalReturnType(Position* returnSpecifierPosition) AstTypePack* Parser::parseOptionalReturnType(Position* returnSpecifierPosition)
{ {
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
if (lexer.current().type == ':' || lexer.current().type == Lexeme::SkinnyArrow) if (lexer.current().type == ':' || lexer.current().type == Lexeme::SkinnyArrow)
{ {
if (lexer.current().type == Lexeme::SkinnyArrow) if (lexer.current().type == Lexeme::SkinnyArrow)
@ -1929,7 +2178,41 @@ std::optional<AstTypeList> Parser::parseOptionalReturnType(Position* returnSpeci
unsigned int oldRecursionCount = recursionCounter; unsigned int oldRecursionCount = recursionCounter;
auto [_location, result] = parseReturnType(); auto result = parseReturnType();
LUAU_ASSERT(result);
// At this point, if we find a , character, it indicates that there are multiple return types
// in this type annotation, but the list wasn't wrapped in parentheses.
if (lexer.current().type == ',')
{
report(lexer.current().location, "Expected a statement, got ','; did you forget to wrap the list of return types in parentheses?");
nextLexeme();
}
recursionCounter = oldRecursionCount;
return result;
}
return nullptr;
}
std::optional<AstTypeList> Parser::parseOptionalReturnType_DEPRECATED(Luau::Position* returnSpecifierPosition)
{
LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst);
if (lexer.current().type == ':' || lexer.current().type == Lexeme::SkinnyArrow)
{
if (lexer.current().type == Lexeme::SkinnyArrow)
report(lexer.current().location, "Function return type annotations are written after ':' instead of '->'");
if (FFlag::LuauStoreCSTData2 && returnSpecifierPosition)
*returnSpecifierPosition = lexer.current().location.begin;
nextLexeme();
unsigned int oldRecursionCount = recursionCounter;
auto [_location, result] = parseReturnType_DEPRECATED();
// At this point, if we find a , character, it indicates that there are multiple return types // At this point, if we find a , character, it indicates that there are multiple return types
// in this type annotation, but the list wasn't wrapped in parentheses. // in this type annotation, but the list wasn't wrapped in parentheses.
@ -1949,8 +2232,119 @@ std::optional<AstTypeList> Parser::parseOptionalReturnType(Position* returnSpeci
} }
// ReturnType ::= Type | `(' TypeList `)' // ReturnType ::= Type | `(' TypeList `)'
std::pair<Location, AstTypeList> Parser::parseReturnType() AstTypePack* Parser::parseReturnType()
{ {
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
incrementRecursionCounter("type annotation");
Lexeme begin = lexer.current();
if (lexer.current().type != '(')
{
if (shouldParseTypePack(lexer))
{
return parseTypePack();
}
else
{
AstType* type = parseType();
AstTypePackExplicit* node = allocator.alloc<AstTypePackExplicit>(type->location, AstTypeList{copy(&type, 1), nullptr});
if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstTypePackExplicit>();
return node;
}
}
nextLexeme();
Location innerBegin = lexer.current().location;
matchRecoveryStopOnToken[Lexeme::SkinnyArrow]++;
TempVector<AstType*> result(scratchType);
TempVector<std::optional<AstArgumentName>> resultNames(scratchOptArgName);
TempVector<Position> commaPositions(scratchPosition);
AstTypePack* varargAnnotation = nullptr;
// possibly () -> ReturnType
if (lexer.current().type != ')')
{
if (options.storeCstData)
varargAnnotation = parseTypeList(result, resultNames, &commaPositions);
else
varargAnnotation = parseTypeList(result, resultNames);
}
const Location location{begin.location, lexer.current().location};
Position closeParenthesesPosition = lexer.current().location.begin;
expectMatchAndConsume(')', begin, true);
matchRecoveryStopOnToken[Lexeme::SkinnyArrow]--;
if (lexer.current().type != Lexeme::SkinnyArrow && resultNames.empty())
{
// If it turns out that it's just '(A)', it's possible that there are unions/intersections to follow, so fold over it.
if (FFlag::LuauAstTypeGroup3)
{
if (result.size() == 1)
{
// TODO(CLI-140667): stop parsing type suffix when varargAnnotation != nullptr - this should be a parse error
AstType* inner = varargAnnotation == nullptr ? allocator.alloc<AstTypeGroup>(location, result[0]) : result[0];
AstType* returnType = parseTypeSuffix(inner, begin.location);
if (DFFlag::DebugLuauReportReturnTypeVariadicWithTypeSuffix && varargAnnotation != nullptr &&
(returnType->is<AstTypeUnion>() || returnType->is<AstTypeIntersection>()))
luau_telemetry_parsed_return_type_variadic_with_type_suffix = true;
// If parseType parses nothing, then returnType->location.end only points at the last non-type-pack
// type to successfully parse. We need the span of the whole annotation.
Position endPos = result.size() == 1 ? location.end : returnType->location.end;
AstTypePackExplicit* node = allocator.alloc<AstTypePackExplicit>(Location{location.begin, endPos}, AstTypeList{copy(&returnType, 1), varargAnnotation});
if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstTypePackExplicit>();
return node;
}
}
else
{
if (result.size() == 1)
{
AstType* returnType = parseTypeSuffix(result[0], innerBegin);
if (DFFlag::DebugLuauReportReturnTypeVariadicWithTypeSuffix && varargAnnotation != nullptr &&
(returnType->is<AstTypeUnion>() || returnType->is<AstTypeIntersection>()))
luau_telemetry_parsed_return_type_variadic_with_type_suffix = true;
// If parseType parses nothing, then returnType->location.end only points at the last non-type-pack
// type to successfully parse. We need the span of the whole annotation.
Position endPos = result.size() == 1 ? location.end : returnType->location.end;
AstTypePackExplicit* node = allocator.alloc<AstTypePackExplicit>(Location{location.begin, endPos}, AstTypeList{copy(&returnType, 1), varargAnnotation});
if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstTypePackExplicit>();
return node;
}
}
AstTypePackExplicit* node = allocator.alloc<AstTypePackExplicit>(location, AstTypeList{copy(result), varargAnnotation});
if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstTypePackExplicit>(location.begin, closeParenthesesPosition, copy(commaPositions));
return node;
}
AstType* tail = parseFunctionTypeTail(begin, {nullptr, 0}, {}, {}, copy(result), copy(resultNames), varargAnnotation);
AstTypePackExplicit* node = allocator.alloc<AstTypePackExplicit>(Location{location, tail->location}, AstTypeList{copy(&tail, 1), nullptr});
if (options.storeCstData)
cstNodeMap[node] = allocator.alloc<CstTypePackExplicit>();
return node;
}
// ReturnType ::= Type | `(' TypeList `)'
std::pair<Location, AstTypeList> Parser::parseReturnType_DEPRECATED()
{
LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst);
incrementRecursionCounter("type annotation"); incrementRecursionCounter("type annotation");
Lexeme begin = lexer.current(); Lexeme begin = lexer.current();
@ -2574,12 +2968,25 @@ AstType* Parser::parseFunctionTypeTail(
expectAndConsume(Lexeme::SkinnyArrow, "function type"); expectAndConsume(Lexeme::SkinnyArrow, "function type");
} }
auto [endLocation, returnTypeList] = parseReturnType(); if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
auto returnType = parseReturnType();
LUAU_ASSERT(returnType);
AstTypeList paramTypes = AstTypeList{params, varargAnnotation}; AstTypeList paramTypes = AstTypeList{params, varargAnnotation};
return allocator.alloc<AstTypeFunction>( return allocator.alloc<AstTypeFunction>(
Location(begin.location, endLocation), attributes, generics, genericPacks, paramTypes, paramNames, returnTypeList Location(begin.location, returnType->location), attributes, generics, genericPacks, paramTypes, paramNames, returnType
); );
}
else
{
auto [endLocation, returnTypeList] = parseReturnType_DEPRECATED();
AstTypeList paramTypes = AstTypeList{params, varargAnnotation};
return allocator.alloc<AstTypeFunction>(
Location(begin.location, endLocation), attributes, generics, genericPacks, paramTypes, paramNames, returnTypeList
);
}
} }
static bool isTypeFollow(Lexeme::Type c) static bool isTypeFollow(Lexeme::Type c)
@ -2693,17 +3100,8 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin)
} }
} }
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) if (parts.size() == 1 && !isUnion && !isIntersection)
{ return parts[0];
if (parts.size() == 1 && !isUnion && !isIntersection)
return parts[0];
}
else
{
if (parts.size() == 1)
return parts[0];
}
if (isUnion && isIntersection) if (isUnion && isIntersection)
{ {
return reportTypeError( return reportTypeError(
@ -3536,7 +3934,10 @@ AstExpr* Parser::parseSimpleExpr()
Lexeme matchFunction = lexer.current(); Lexeme matchFunction = lexer.current();
nextLexeme(); nextLexeme();
return parseFunctionBody(false, matchFunction, AstName(), nullptr, attributes).first; if (FFlag::LuauStoreReturnTypesAsPackOnAst)
return parseFunctionBody(false, matchFunction, AstName(), nullptr, attributes).first;
else
return parseFunctionBody_DEPRECATED(false, matchFunction, AstName(), nullptr, attributes).first;
} }
else if (lexer.current().type == Lexeme::Number) else if (lexer.current().type == Lexeme::Number)
{ {
@ -4666,7 +5067,7 @@ void Parser::report(const Location& location, const char* format, va_list args)
parseErrors.emplace_back(location, message); parseErrors.emplace_back(location, message);
if (parseErrors.size() >= unsigned(FInt::LuauParseErrorLimit) && (!FFlag::ParserNoErrorLimit || !options.noErrorLimit)) if (parseErrors.size() >= unsigned(FInt::LuauParseErrorLimit) && !options.noErrorLimit)
ParseError::raise(location, "Reached error limit (%d)", int(FInt::LuauParseErrorLimit)); ParseError::raise(location, "Reached error limit (%d)", int(FInt::LuauParseErrorLimit));
} }

View file

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

View file

@ -28,6 +28,8 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
LUAU_FASTFLAGVARIABLE(LuauSeparateCompilerTypeInfo) LUAU_FASTFLAGVARIABLE(LuauSeparateCompilerTypeInfo)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau namespace Luau
{ {
@ -4317,26 +4319,54 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c
mainFlags |= LPF_NATIVE_FUNCTION; mainFlags |= LPF_NATIVE_FUNCTION;
} }
AstExprFunction main( if (FFlag::LuauStoreReturnTypesAsPackOnAst)
root->location, {
/* attributes= */ AstArray<AstAttr*>({nullptr, 0}), AstExprFunction main(
/* generics= */ AstArray<AstGenericType*>(), root->location,
/* genericPacks= */ AstArray<AstGenericTypePack*>(), /* attributes= */ AstArray<AstAttr*>({nullptr, 0}),
/* self= */ nullptr, /* generics= */ AstArray<AstGenericType*>(),
AstArray<AstLocal*>(), /* genericPacks= */ AstArray<AstGenericTypePack*>(),
/* vararg= */ true, /* self= */ nullptr,
/* varargLocation= */ Luau::Location(), AstArray<AstLocal*>(),
root, /* vararg= */ true,
/* functionDepth= */ 0, /* varargLocation= */ Luau::Location(),
/* debugname= */ AstName() root,
); /* functionDepth= */ 0,
uint32_t mainid = compiler.compileFunction(&main, mainFlags); /* debugname= */ AstName(),
/* returnAnnotation= */ nullptr
);
uint32_t mainid = compiler.compileFunction(&main, mainFlags);
const Compiler::Function* mainf = compiler.functions.find(&main); const Compiler::Function* mainf = compiler.functions.find(&main);
LUAU_ASSERT(mainf && mainf->upvals.empty()); LUAU_ASSERT(mainf && mainf->upvals.empty());
bytecode.setMainFunction(mainid); bytecode.setMainFunction(mainid);
bytecode.finalize(); bytecode.finalize();
}
else
{
AstExprFunction main(
root->location,
/* attributes= */ AstArray<AstAttr*>({nullptr, 0}),
/* generics= */ AstArray<AstGenericType*>(),
/* genericPacks= */ AstArray<AstGenericTypePack*>(),
/* self= */ nullptr,
AstArray<AstLocal*>(),
/* vararg= */ true,
/* varargLocation= */ Luau::Location(),
root,
/* functionDepth= */ 0,
/* debugname= */ AstName(),
/* returnAnnotation= */ std::nullopt
);
uint32_t mainid = compiler.compileFunction(&main, mainFlags);
const Compiler::Function* mainf = compiler.functions.find(&main);
LUAU_ASSERT(mainf && mainf->upvals.empty());
bytecode.setMainFunction(mainid);
bytecode.finalize();
}
} }
void compileOrThrow(BytecodeBuilder& bytecode, const std::string& source, const CompileOptions& options, const ParseOptions& parseOptions) void compileOrThrow(BytecodeBuilder& bytecode, const std::string& source, const CompileOptions& options, const ParseOptions& parseOptions)

View file

@ -3,7 +3,6 @@
#include "Luau/Config.h" #include "Luau/Config.h"
#include <functional>
#include <optional> #include <optional>
#include <string> #include <string>
#include <string_view> #include <string_view>

View file

@ -107,7 +107,7 @@ struct luarequire_Configuration
// Executes the module and places the result on the stack. Returns the // Executes the module and places the result on the stack. Returns the
// number of results placed on the stack. // number of results placed on the stack.
int (*load)(lua_State* L, void* ctx, const char* chunkname, const char* contents); int (*load)(lua_State* L, void* ctx, const char* path, const char* chunkname, const char* contents);
}; };
// Populates function pointers in the given luarequire_Configuration. // Populates function pointers in the given luarequire_Configuration.
@ -115,7 +115,18 @@ typedef void (*luarequire_Configuration_init)(luarequire_Configuration* config);
// Initializes and pushes the require closure onto the stack without // Initializes and pushes the require closure onto the stack without
// registration. // registration.
LUALIB_API int lua_pushrequire(lua_State* L, luarequire_Configuration_init config_init, void* ctx); LUALIB_API int luarequire_pushrequire(lua_State* L, luarequire_Configuration_init config_init, void* ctx);
// Initializes the require library and registers it globally. // Initializes the require library and registers it globally.
LUALIB_API void luaopen_require(lua_State* L, luarequire_Configuration_init config_init, void* ctx); LUALIB_API void luaopen_require(lua_State* L, luarequire_Configuration_init config_init, void* ctx);
// Initializes and pushes a "proxyrequire" closure onto the stack. This function
// takes two parameters: the string path to resolve and the chunkname of an
// existing module. The path is resolved as if it were being required from the
// module that the chunkname represents.
LUALIB_API int luarequire_pushproxyrequire(lua_State* L, luarequire_Configuration_init config_init, void* ctx);
// Registers an aliased require path to a result. After registration, the given
// result will always be immediately returned when the given path is required.
// Expects the path and table to be passed as arguments on the stack.
LUALIB_API int luarequire_registermodule(lua_State* L);

View file

@ -35,7 +35,13 @@ static void validateConfig(lua_State* L, const luarequire_Configuration& config)
luaL_error(L, "require configuration is missing required function pointer: load"); luaL_error(L, "require configuration is missing required function pointer: load");
} }
int lua_pushrequire(lua_State* L, luarequire_Configuration_init config_init, void* ctx) static int pushrequireclosureinternal(
lua_State* L,
luarequire_Configuration_init config_init,
void* ctx,
lua_CFunction requirelikefunc,
const char* debugname
)
{ {
luarequire_Configuration* config = static_cast<luarequire_Configuration*>(lua_newuserdata(L, sizeof(luarequire_Configuration))); luarequire_Configuration* config = static_cast<luarequire_Configuration*>(lua_newuserdata(L, sizeof(luarequire_Configuration)));
if (!config) if (!config)
@ -46,13 +52,28 @@ int lua_pushrequire(lua_State* L, luarequire_Configuration_init config_init, voi
lua_pushlightuserdata(L, ctx); lua_pushlightuserdata(L, ctx);
// "require" captures config and ctx as upvalues // require-like closure captures config and ctx as upvalues
lua_pushcclosure(L, Luau::Require::lua_require, "require", 2); lua_pushcclosure(L, requirelikefunc, debugname, 2);
return 1; return 1;
} }
int luarequire_pushrequire(lua_State* L, luarequire_Configuration_init config_init, void* ctx)
{
return pushrequireclosureinternal(L, config_init, ctx, Luau::Require::lua_require, "require");
}
void luaopen_require(lua_State* L, luarequire_Configuration_init config_init, void* ctx) void luaopen_require(lua_State* L, luarequire_Configuration_init config_init, void* ctx)
{ {
lua_pushrequire(L, config_init, ctx); luarequire_pushrequire(L, config_init, ctx);
lua_setglobal(L, "require"); lua_setglobal(L, "require");
} }
int luarequire_pushproxyrequire(lua_State* L, luarequire_Configuration_init config_init, void* ctx)
{
return pushrequireclosureinternal(L, config_init, ctx, Luau::Require::lua_proxyrequire, "proxyrequire");
}
int luarequire_registermodule(lua_State* L)
{
return Luau::Require::registerModuleImpl(L);
}

View file

@ -13,7 +13,11 @@
namespace Luau::Require namespace Luau::Require
{ {
static const char* cacheTableKey = "_MODULES"; // Stores explicitly registered modules.
static const char* registeredCacheTableKey = "_REGISTEREDMODULES";
// Stores the results of require calls.
static const char* requiredCacheTableKey = "_MODULES";
struct ResolvedRequire struct ResolvedRequire
{ {
@ -32,7 +36,7 @@ struct ResolvedRequire
static bool isCached(lua_State* L, const std::string& key) static bool isCached(lua_State* L, const std::string& key)
{ {
luaL_findtable(L, LUA_REGISTRYINDEX, cacheTableKey, 1); luaL_findtable(L, LUA_REGISTRYINDEX, requiredCacheTableKey, 1);
lua_getfield(L, -1, key.c_str()); lua_getfield(L, -1, key.c_str());
bool cached = !lua_isnil(L, -1); bool cached = !lua_isnil(L, -1);
lua_pop(L, 2); lua_pop(L, 2);
@ -40,15 +44,12 @@ static bool isCached(lua_State* L, const std::string& key)
return cached; return cached;
} }
static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State* L, void* ctx, std::string path) static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State* L, void* ctx, const char* requirerChunkname, std::string path)
{ {
lua_Debug ar; if (!lrc->is_require_allowed(L, ctx, requirerChunkname))
lua_getinfo(L, 1, "s", &ar);
if (!lrc->is_require_allowed(L, ctx, ar.source))
luaL_error(L, "require is not supported in this context"); luaL_error(L, "require is not supported in this context");
RuntimeNavigationContext navigationContext{lrc, L, ctx, ar.source}; RuntimeNavigationContext navigationContext{lrc, L, ctx, requirerChunkname};
RuntimeErrorHandler errorHandler{L}; // Errors reported directly to lua_State. RuntimeErrorHandler errorHandler{L}; // Errors reported directly to lua_State.
Navigator navigator(navigationContext, errorHandler); Navigator navigator(navigationContext, errorHandler);
@ -74,7 +75,7 @@ static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State*
if (isCached(L, *cacheKey)) if (isCached(L, *cacheKey))
{ {
// Put cached result on top of stack before returning. // Put cached result on top of stack before returning.
lua_getfield(L, LUA_REGISTRYINDEX, cacheTableKey); lua_getfield(L, LUA_REGISTRYINDEX, requiredCacheTableKey);
lua_getfield(L, -1, cacheKey->c_str()); lua_getfield(L, -1, cacheKey->c_str());
lua_remove(L, -2); lua_remove(L, -2);
@ -103,7 +104,21 @@ static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State*
}; };
} }
int lua_require(lua_State* L) static int checkRegisteredModules(lua_State* L, const char* path)
{
luaL_findtable(L, LUA_REGISTRYINDEX, registeredCacheTableKey, 1);
lua_getfield(L, -1, path);
if (lua_isnil(L, -1))
{
lua_pop(L, 2);
return 0;
}
lua_remove(L, -2);
return 1;
}
int lua_requireinternal(lua_State* L, const char* requirerChunkname)
{ {
luarequire_Configuration* lrc = static_cast<luarequire_Configuration*>(lua_touserdata(L, lua_upvalueindex(1))); luarequire_Configuration* lrc = static_cast<luarequire_Configuration*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!lrc) if (!lrc)
@ -113,11 +128,14 @@ int lua_require(lua_State* L)
const char* path = luaL_checkstring(L, 1); const char* path = luaL_checkstring(L, 1);
ResolvedRequire resolvedRequire = resolveRequire(lrc, L, ctx, path); if (checkRegisteredModules(L, path) == 1)
return 1;
ResolvedRequire resolvedRequire = resolveRequire(lrc, L, ctx, requirerChunkname, path);
if (resolvedRequire.status == ResolvedRequire::Status::Cached) if (resolvedRequire.status == ResolvedRequire::Status::Cached)
return 1; return 1;
int numResults = lrc->load(L, ctx, resolvedRequire.chunkname.c_str(), resolvedRequire.contents.c_str()); int numResults = lrc->load(L, ctx, path, resolvedRequire.chunkname.c_str(), resolvedRequire.contents.c_str());
if (numResults > 1) if (numResults > 1)
luaL_error(L, "module must return a single value"); luaL_error(L, "module must return a single value");
@ -127,7 +145,7 @@ int lua_require(lua_State* L)
// Initial stack state // Initial stack state
// (-1) result // (-1) result
lua_getfield(L, LUA_REGISTRYINDEX, cacheTableKey); lua_getfield(L, LUA_REGISTRYINDEX, requiredCacheTableKey);
// (-2) result, (-1) cache table // (-2) result, (-1) cache table
lua_pushvalue(L, -2); lua_pushvalue(L, -2);
@ -143,4 +161,42 @@ int lua_require(lua_State* L)
return numResults; return numResults;
} }
int lua_proxyrequire(lua_State* L)
{
const char* requirerChunkname = luaL_checkstring(L, 2);
return lua_requireinternal(L, requirerChunkname);
}
int lua_require(lua_State* L)
{
lua_Debug ar;
lua_getinfo(L, 1, "s", &ar);
return lua_requireinternal(L, ar.source);
}
int registerModuleImpl(lua_State* L)
{
if (lua_gettop(L) != 2)
luaL_error(L, "expected 2 arguments: aliased require path and desired result");
size_t len;
const char* path = luaL_checklstring(L, 1, &len);
std::string_view pathView(path, len);
if (pathView.empty() || pathView[0] != '@')
luaL_argerrorL(L, 1, "path must begin with '@'");
luaL_findtable(L, LUA_REGISTRYINDEX, registeredCacheTableKey, 1);
// (1) path, (2) result, (3) cache table
lua_insert(L, 1);
// (1) cache table, (2) path, (3) result
lua_settable(L, 1);
// (1) cache table
lua_pop(L, 1);
return 0;
}
} // namespace Luau::Require } // namespace Luau::Require

View file

@ -7,5 +7,8 @@ namespace Luau::Require
{ {
int lua_require(lua_State* L); int lua_require(lua_State* L);
int lua_proxyrequire(lua_State* L);
int registerModuleImpl(lua_State* L);
} // namespace Luau::Require } // namespace Luau::Require

View file

@ -179,6 +179,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/include/Luau/Clone.h Analysis/include/Luau/Clone.h
Analysis/include/Luau/Constraint.h Analysis/include/Luau/Constraint.h
Analysis/include/Luau/ConstraintGenerator.h Analysis/include/Luau/ConstraintGenerator.h
Analysis/include/Luau/ConstraintSet.h
Analysis/include/Luau/ConstraintSolver.h Analysis/include/Luau/ConstraintSolver.h
Analysis/include/Luau/ControlFlow.h Analysis/include/Luau/ControlFlow.h
Analysis/include/Luau/DataFlowGraph.h Analysis/include/Luau/DataFlowGraph.h

View file

@ -58,6 +58,9 @@ LUALIB_API const char* luaL_findtable(lua_State* L, int idx, const char* fname,
LUALIB_API const char* luaL_typename(lua_State* L, int idx); LUALIB_API const char* luaL_typename(lua_State* L, int idx);
// wrapper for making calls from yieldable C functions
LUALIB_API int luaL_callyieldable(lua_State* L, int nargs, int nresults);
/* /*
** =============================================================== ** ===============================================================
** some useful macros ** some useful macros

View file

@ -12,6 +12,7 @@
#include <string.h> #include <string.h>
LUAU_FASTFLAGVARIABLE(LuauLibWhereErrorAutoreserve) LUAU_FASTFLAGVARIABLE(LuauLibWhereErrorAutoreserve)
LUAU_FASTFLAG(LuauYieldableContinuations)
// convert a stack index to positive // convert a stack index to positive
#define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : lua_gettop(L) + (i) + 1) #define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : lua_gettop(L) + (i) + 1)
@ -355,6 +356,22 @@ const char* luaL_typename(lua_State* L, int idx)
return obj ? luaT_objtypename(L, obj) : "no value"; return obj ? luaT_objtypename(L, obj) : "no value";
} }
int luaL_callyieldable(lua_State* L, int nargs, int nresults)
{
LUAU_ASSERT(FFlag::LuauYieldableContinuations);
api_check(L, iscfunction(L->ci->func));
Closure* cl = clvalue(L->ci->func);
api_check(L, cl->c.cont);
lua_call(L, nargs, nresults);
if (L->status == LUA_YIELD || L->status == LUA_BREAK)
return -1; // -1 is a marker for yielding from C
return cl->c.cont(L, LUA_OK);
}
/* /*
** {====================================================== ** {======================================================
** Generic Buffer manipulation ** Generic Buffer manipulation

View file

@ -11,6 +11,8 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
LUAU_FASTFLAG(LuauYieldableContinuations)
static void writestring(const char* s, size_t l) static void writestring(const char* s, size_t l)
{ {
fwrite(s, 1, l, stdout); fwrite(s, 1, l, stdout);
@ -294,10 +296,18 @@ static int luaB_pcally(lua_State* L)
// any errors from this point on are handled by continuation // any errors from this point on are handled by continuation
L->ci->flags |= LUA_CALLINFO_HANDLE; L->ci->flags |= LUA_CALLINFO_HANDLE;
// maintain yieldable invariant (baseCcalls <= nCcalls) if (!FFlag::LuauYieldableContinuations)
L->baseCcalls++; {
// maintain yieldable invariant (baseCcalls <= nCcalls)
L->baseCcalls++;
}
int status = luaD_pcall(L, luaB_pcallrun, func, savestack(L, func), 0); int status = luaD_pcall(L, luaB_pcallrun, func, savestack(L, func), 0);
L->baseCcalls--;
if (!FFlag::LuauYieldableContinuations)
{
L->baseCcalls--;
}
// necessary to accomodate functions that return lots of values // necessary to accomodate functions that return lots of values
expandstacklimit(L, L->top); expandstacklimit(L, L->top);
@ -348,12 +358,20 @@ static int luaB_xpcally(lua_State* L)
StkId errf = L->base; StkId errf = L->base;
StkId func = L->base + 1; StkId func = L->base + 1;
// maintain yieldable invariant (baseCcalls <= nCcalls) if (!FFlag::LuauYieldableContinuations)
L->baseCcalls++; {
int status = luaD_pcall(L, luaB_pcallrun, func, savestack(L, func), savestack(L, errf)); // maintain yieldable invariant (baseCcalls <= nCcalls)
L->baseCcalls--; L->baseCcalls++;
}
// necessary to accomodate functions that return lots of values int status = luaD_pcall(L, luaB_pcallrun, func, savestack(L, func), savestack(L, errf));
if (!FFlag::LuauYieldableContinuations)
{
L->baseCcalls--;
}
// necessary to accommodate functions that return lots of values
expandstacklimit(L, L->top); expandstacklimit(L, L->top);
// yielding means we need to propagate yield; resume will call continuation function later // yielding means we need to propagate yield; resume will call continuation function later

View file

@ -17,6 +17,8 @@
#include <string.h> #include <string.h>
LUAU_FASTFLAGVARIABLE(LuauYieldableContinuations)
// keep max stack allocation request under 1GB // keep max stack allocation request under 1GB
#define MAX_STACK_SIZE (int(1024 / sizeof(TValue)) * 1024 * 1024) #define MAX_STACK_SIZE (int(1024 / sizeof(TValue)) * 1024 * 1024)
@ -260,24 +262,79 @@ void luaD_call(lua_State* L, StkId func, int nresults)
if (++L->nCcalls >= LUAI_MAXCCALLS) if (++L->nCcalls >= LUAI_MAXCCALLS)
luaD_checkCstack(L); luaD_checkCstack(L);
ptrdiff_t old_func = savestack(L, func); if (FFlag::LuauYieldableContinuations)
{
// when called from a yieldable C function, maintain yieldable invariant (baseCcalls <= nCcalls)
bool fromyieldableccall = false;
if (luau_precall(L, func, nresults) == PCRLUA) if (L->ci != L->base_ci)
{ // is a Lua function? {
L->ci->flags |= LUA_CALLINFO_RETURN; // luau_execute will stop after returning from the stack frame Closure* ccl = clvalue(L->ci->func);
bool oldactive = L->isactive; if (ccl->isC && ccl->c.cont)
L->isactive = true; {
luaC_threadbarrier(L); fromyieldableccall = true;
L->baseCcalls++;
}
}
luau_execute(L); // call it ptrdiff_t funcoffset = savestack(L, func);
ptrdiff_t cioffset = saveci(L, L->ci);
if (!oldactive) if (luau_precall(L, func, nresults) == PCRLUA)
L->isactive = false; { // is a Lua function?
L->ci->flags |= LUA_CALLINFO_RETURN; // luau_execute will stop after returning from the stack frame
bool oldactive = L->isactive;
L->isactive = true;
luaC_threadbarrier(L);
luau_execute(L); // call it
if (!oldactive)
L->isactive = false;
}
bool yielded = L->status == LUA_YIELD || L->status == LUA_BREAK;
if (fromyieldableccall)
{
// restore original yieldable invariant
// in case of an error, this would either be restored by luaD_pcall or the thread would no longer be resumable
L->baseCcalls--;
// on yield, we have to set the CallInfo top of the C function including slots for expected results, to restore later
if (yielded)
{
CallInfo* callerci = restoreci(L, cioffset);
callerci->top = restorestack(L, funcoffset) + (nresults != LUA_MULTRET ? nresults : 0);
}
}
if (nresults != LUA_MULTRET && !yielded)
L->top = restorestack(L, funcoffset) + nresults;
} }
else
{
ptrdiff_t old_func = savestack(L, func);
if (nresults != LUA_MULTRET) if (luau_precall(L, func, nresults) == PCRLUA)
L->top = restorestack(L, old_func) + nresults; { // is a Lua function?
L->ci->flags |= LUA_CALLINFO_RETURN; // luau_execute will stop after returning from the stack frame
bool oldactive = L->isactive;
L->isactive = true;
luaC_threadbarrier(L);
luau_execute(L); // call it
if (!oldactive)
L->isactive = false;
}
if (nresults != LUA_MULTRET)
L->top = restorestack(L, old_func) + nresults;
}
L->nCcalls--; L->nCcalls--;
luaC_checkGC(L); luaC_checkGC(L);
@ -323,9 +380,18 @@ static void resume_continue(lua_State* L)
// C continuation; we expect this to be followed by Lua continuations // C continuation; we expect this to be followed by Lua continuations
int n = cl->c.cont(L, 0); int n = cl->c.cont(L, 0);
// Continuation can break again if (FFlag::LuauYieldableContinuations)
if (L->status == LUA_BREAK) {
break; // continuation can break or yield again
if (L->status == LUA_BREAK || L->status == LUA_YIELD)
break;
}
else
{
// Continuation can break again
if (L->status == LUA_BREAK)
break;
}
luau_poscall(L, L->top - n); luau_poscall(L, L->top - n);
} }
@ -370,6 +436,11 @@ static void resume(lua_State* L, void* ud)
// finish interrupted execution of `OP_CALL' // finish interrupted execution of `OP_CALL'
luau_poscall(L, firstArg); luau_poscall(L, firstArg);
} }
else if (FFlag::LuauYieldableContinuations)
{
// restore arguments we have protected for C continuation
L->base = L->ci->base;
}
} }
else else
{ {
@ -576,6 +647,7 @@ static void restore_stack_limit(lua_State* L)
int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t ef) int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t ef)
{ {
unsigned short oldnCcalls = L->nCcalls; unsigned short oldnCcalls = L->nCcalls;
unsigned short oldbaseCcalls = FFlag::LuauYieldableContinuations ? L->baseCcalls : 0;
ptrdiff_t old_ci = saveci(L, L->ci); ptrdiff_t old_ci = saveci(L, L->ci);
bool oldactive = L->isactive; bool oldactive = L->isactive;
int status = luaD_rawrunprotected(L, func, u); int status = luaD_rawrunprotected(L, func, u);
@ -612,6 +684,9 @@ int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t e
// restore nCcalls before calling the debugprotectederror callback which may rely on the proper value to have been restored. // restore nCcalls before calling the debugprotectederror callback which may rely on the proper value to have been restored.
L->nCcalls = oldnCcalls; L->nCcalls = oldnCcalls;
if (FFlag::LuauYieldableContinuations)
L->baseCcalls = oldbaseCcalls;
// an error occurred, check if we have a protected error callback // an error occurred, check if we have a protected error callback
if (yieldable && L->global->cb.debugprotectederror) if (yieldable && L->global->cb.debugprotectederror)
{ {

View file

@ -124,8 +124,8 @@ int registerTypes(Luau::Frontend& frontend, Luau::GlobalTypes& globals, bool for
// Vector3 stub // Vector3 stub
TypeId vector3MetaType = arena.addType(TableType{}); TypeId vector3MetaType = arena.addType(TableType{});
TypeId vector3InstanceType = arena.addType(ClassType{"Vector3", {}, nullopt, vector3MetaType, {}, {}, "Test", {}}); TypeId vector3InstanceType = arena.addType(ExternType{"Vector3", {}, nullopt, vector3MetaType, {}, {}, "Test", {}});
getMutable<ClassType>(vector3InstanceType)->props = { getMutable<ExternType>(vector3InstanceType)->props = {
{"X", {builtinTypes.numberType}}, {"X", {builtinTypes.numberType}},
{"Y", {builtinTypes.numberType}}, {"Y", {builtinTypes.numberType}},
{"Z", {builtinTypes.numberType}}, {"Z", {builtinTypes.numberType}},
@ -139,16 +139,16 @@ int registerTypes(Luau::Frontend& frontend, Luau::GlobalTypes& globals, bool for
globals.globalScope->exportedTypeBindings["Vector3"] = TypeFun{{}, vector3InstanceType}; globals.globalScope->exportedTypeBindings["Vector3"] = TypeFun{{}, vector3InstanceType};
// Instance stub // Instance stub
TypeId instanceType = arena.addType(ClassType{"Instance", {}, nullopt, nullopt, {}, {}, "Test", {}}); TypeId instanceType = arena.addType(ExternType{"Instance", {}, nullopt, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(instanceType)->props = { getMutable<ExternType>(instanceType)->props = {
{"Name", {builtinTypes.stringType}}, {"Name", {builtinTypes.stringType}},
}; };
globals.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, instanceType}; globals.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, instanceType};
// Part stub // Part stub
TypeId partType = arena.addType(ClassType{"Part", {}, instanceType, nullopt, {}, {}, "Test", {}}); TypeId partType = arena.addType(ExternType{"Part", {}, instanceType, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(partType)->props = { getMutable<ExternType>(partType)->props = {
{"Position", {vector3InstanceType}}, {"Position", {vector3InstanceType}},
}; };

View file

@ -36,7 +36,7 @@ static const std::string kTypes[] = {
"vector", "vector",
}; };
static const std::string kClasses[] = { static const std::string kExternTypes[] = {
"Vector3", "Vector3",
"Instance", "Instance",
"Part", "Part",
@ -902,8 +902,8 @@ struct ProtoToLuau
void print(const luau::TypeClass& type) void print(const luau::TypeClass& type)
{ {
size_t index = size_t(type.kind()) % std::size(kClasses); size_t index = size_t(type.kind()) % std::size(kExternTypes);
source += kClasses[index]; source += kExternTypes[index];
} }
void print(const luau::TypeRef& type) void print(const luau::TypeRef& type)

View file

@ -13,6 +13,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauAstTypeGroup3) LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation) LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
struct JsonEncoderFixture struct JsonEncoderFixture
{ {
@ -420,20 +421,39 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareFunction")
{ {
AstStat* statement = expectParseStatement("declare function foo(x: number): string"); AstStat* statement = expectParseStatement("declare function foo(x: number): string");
std::string_view expected = if (FFlag::LuauStoreReturnTypesAsPackOnAst)
R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,39","attributes":[],"name":"foo","nameLocation":"0,17 - 0,20","params":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","nameLocation":"0,24 - 0,30","parameters":[]}]},"paramNames":[{"type":"AstArgumentName","name":"x","location":"0,21 - 0,22"}],"vararg":false,"varargLocation":"0,0 - 0,0","retTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,33 - 0,39","name":"string","nameLocation":"0,33 - 0,39","parameters":[]}]},"generics":[],"genericPacks":[]})"; {
std::string_view expected =
R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,39","attributes":[],"name":"foo","nameLocation":"0,17 - 0,20","params":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","nameLocation":"0,24 - 0,30","parameters":[]}]},"paramNames":[{"type":"AstArgumentName","name":"x","location":"0,21 - 0,22"}],"vararg":false,"varargLocation":"0,0 - 0,0","retTypes":{"type":"AstTypePackExplicit","location":"0,33 - 0,39","typeList":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,33 - 0,39","name":"string","nameLocation":"0,33 - 0,39","parameters":[]}]}},"generics":[],"genericPacks":[]})";
CHECK(toJson(statement) == expected);
}
else
{
std::string_view expected =
R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,39","attributes":[],"name":"foo","nameLocation":"0,17 - 0,20","params":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","nameLocation":"0,24 - 0,30","parameters":[]}]},"paramNames":[{"type":"AstArgumentName","name":"x","location":"0,21 - 0,22"}],"vararg":false,"varargLocation":"0,0 - 0,0","retTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,33 - 0,39","name":"string","nameLocation":"0,33 - 0,39","parameters":[]}]},"generics":[],"genericPacks":[]})";
CHECK(toJson(statement) == expected); CHECK(toJson(statement) == expected);
}
} }
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareFunction2") TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareFunction2")
{ {
AstStat* statement = expectParseStatement("declare function foo(x: number, ...: string): string"); AstStat* statement = expectParseStatement("declare function foo(x: number, ...: string): string");
std::string_view expected = if (FFlag::LuauStoreReturnTypesAsPackOnAst)
R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,52","attributes":[],"name":"foo","nameLocation":"0,17 - 0,20","params":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","nameLocation":"0,24 - 0,30","parameters":[]}],"tailType":{"type":"AstTypePackVariadic","location":"0,37 - 0,43","variadicType":{"type":"AstTypeReference","location":"0,37 - 0,43","name":"string","nameLocation":"0,37 - 0,43","parameters":[]}}},"paramNames":[{"type":"AstArgumentName","name":"x","location":"0,21 - 0,22"}],"vararg":true,"varargLocation":"0,32 - 0,35","retTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,46 - 0,52","name":"string","nameLocation":"0,46 - 0,52","parameters":[]}]},"generics":[],"genericPacks":[]})"; {
std::string_view expected =
R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,52","attributes":[],"name":"foo","nameLocation":"0,17 - 0,20","params":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","nameLocation":"0,24 - 0,30","parameters":[]}],"tailType":{"type":"AstTypePackVariadic","location":"0,37 - 0,43","variadicType":{"type":"AstTypeReference","location":"0,37 - 0,43","name":"string","nameLocation":"0,37 - 0,43","parameters":[]}}},"paramNames":[{"type":"AstArgumentName","name":"x","location":"0,21 - 0,22"}],"vararg":true,"varargLocation":"0,32 - 0,35","retTypes":{"type":"AstTypePackExplicit","location":"0,46 - 0,52","typeList":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,46 - 0,52","name":"string","nameLocation":"0,46 - 0,52","parameters":[]}]}},"generics":[],"genericPacks":[]})";
CHECK(toJson(statement) == expected); CHECK(toJson(statement) == expected);
}
else
{
std::string_view expected =
R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,52","attributes":[],"name":"foo","nameLocation":"0,17 - 0,20","params":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","nameLocation":"0,24 - 0,30","parameters":[]}],"tailType":{"type":"AstTypePackVariadic","location":"0,37 - 0,43","variadicType":{"type":"AstTypeReference","location":"0,37 - 0,43","name":"string","nameLocation":"0,37 - 0,43","parameters":[]}}},"paramNames":[{"type":"AstArgumentName","name":"x","location":"0,21 - 0,22"}],"vararg":true,"varargLocation":"0,32 - 0,35","retTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,46 - 0,52","name":"string","nameLocation":"0,46 - 0,52","parameters":[]}]},"generics":[],"genericPacks":[]})";
CHECK(toJson(statement) == expected);
}
} }
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstAttr") TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstAttr")
@ -463,9 +483,18 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareClass")
REQUIRE(2 == root->body.size); REQUIRE(2 == root->body.size);
std::string_view expected1 = if (FFlag::LuauStoreReturnTypesAsPackOnAst)
R"({"type":"AstStatDeclareClass","location":"1,22 - 4,11","name":"Foo","props":[{"name":"prop","nameLocation":"2,12 - 2,16","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"2,18 - 2,24","name":"number","nameLocation":"2,18 - 2,24","parameters":[]},"location":"2,12 - 2,24"},{"name":"method","nameLocation":"3,21 - 3,27","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeFunction","location":"3,12 - 3,54","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,39 - 3,45","name":"number","nameLocation":"3,39 - 3,45","parameters":[]}]},"argNames":[{"type":"AstArgumentName","name":"foo","location":"3,34 - 3,37"}],"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,48 - 3,54","name":"string","nameLocation":"3,48 - 3,54","parameters":[]}]}},"location":"3,12 - 3,54"}],"indexer":null})"; {
CHECK(toJson(root->body.data[0]) == expected1); std::string_view expected1 =
R"({"type":"AstStatDeclareClass","location":"1,22 - 4,11","name":"Foo","props":[{"name":"prop","nameLocation":"2,12 - 2,16","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"2,18 - 2,24","name":"number","nameLocation":"2,18 - 2,24","parameters":[]},"location":"2,12 - 2,24"},{"name":"method","nameLocation":"3,21 - 3,27","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeFunction","location":"3,12 - 3,54","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,39 - 3,45","name":"number","nameLocation":"3,39 - 3,45","parameters":[]}]},"argNames":[{"type":"AstArgumentName","name":"foo","location":"3,34 - 3,37"}],"returnTypes":{"type":"AstTypePackExplicit","location":"3,48 - 3,54","typeList":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,48 - 3,54","name":"string","nameLocation":"3,48 - 3,54","parameters":[]}]}}},"location":"3,12 - 3,54"}],"indexer":null})";
CHECK(toJson(root->body.data[0]) == expected1);
}
else
{
std::string_view expected1 =
R"({"type":"AstStatDeclareClass","location":"1,22 - 4,11","name":"Foo","props":[{"name":"prop","nameLocation":"2,12 - 2,16","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"2,18 - 2,24","name":"number","nameLocation":"2,18 - 2,24","parameters":[]},"location":"2,12 - 2,24"},{"name":"method","nameLocation":"3,21 - 3,27","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeFunction","location":"3,12 - 3,54","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,39 - 3,45","name":"number","nameLocation":"3,39 - 3,45","parameters":[]}]},"argNames":[{"type":"AstArgumentName","name":"foo","location":"3,34 - 3,37"}],"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,48 - 3,54","name":"string","nameLocation":"3,48 - 3,54","parameters":[]}]}},"location":"3,12 - 3,54"}],"indexer":null})";
CHECK(toJson(root->body.data[0]) == expected1);
}
std::string_view expected2 = std::string_view expected2 =
R"({"type":"AstStatDeclareClass","location":"6,22 - 8,11","name":"Bar","superName":"Foo","props":[{"name":"prop2","nameLocation":"7,12 - 7,17","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"7,19 - 7,25","name":"string","nameLocation":"7,19 - 7,25","parameters":[]},"location":"7,12 - 7,25"}],"indexer":null})"; R"({"type":"AstStatDeclareClass","location":"6,22 - 8,11","name":"Bar","superName":"Foo","props":[{"name":"prop2","nameLocation":"7,12 - 7,17","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"7,19 - 7,25","name":"string","nameLocation":"7,19 - 7,25","parameters":[]},"location":"7,12 - 7,25"}],"indexer":null})";
@ -476,7 +505,19 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_annotation")
{ {
AstStat* statement = expectParseStatement("type T = ((number) -> (string | nil)) & ((string) -> ())"); AstStat* statement = expectParseStatement("type T = ((number) -> (string | nil)) & ((string) -> ())");
if (FFlag::LuauAstTypeGroup3) if (FFlag::LuauStoreReturnTypesAsPackOnAst && FFlag::LuauAstTypeGroup3)
{
std::string_view expected =
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,56","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,56","types":[{"type":"AstTypeGroup","location":"0,9 - 0,37","inner":{"type":"AstTypeFunction","location":"0,10 - 0,36","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypePackExplicit","location":"0,22 - 0,36","typeList":{"type":"AstTypeList","types":[{"type":"AstTypeGroup","location":"0,22 - 0,36","inner":{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}}]}}}},{"type":"AstTypeGroup","location":"0,40 - 0,56","inner":{"type":"AstTypeFunction","location":"0,41 - 0,55","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypePackExplicit","location":"0,53 - 0,55","typeList":{"type":"AstTypeList","types":[]}}}}]},"exported":false})";
CHECK(toJson(statement) == expected);
}
else if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
std::string_view expected =
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,55","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,55","types":[{"type":"AstTypeFunction","location":"0,10 - 0,36","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypePackExplicit","location":"0,22 - 0,36","typeList":{"type":"AstTypeList","types":[{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}]}}},{"type":"AstTypeFunction","location":"0,41 - 0,55","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypePackExplicit","location":"0,53 - 0,55","typeList":{"type":"AstTypeList","types":[]}}}]},"exported":false})";
CHECK(toJson(statement) == expected);
}
else if (FFlag::LuauAstTypeGroup3)
{ {
std::string_view expected = std::string_view expected =
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,56","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,56","types":[{"type":"AstTypeGroup","location":"0,9 - 0,37","inner":{"type":"AstTypeFunction","location":"0,10 - 0,36","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeGroup","location":"0,22 - 0,36","inner":{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}}]}}},{"type":"AstTypeGroup","location":"0,40 - 0,56","inner":{"type":"AstTypeFunction","location":"0,41 - 0,55","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[]}}}]},"exported":false})"; R"({"type":"AstStatTypeAlias","location":"0,0 - 0,56","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,56","types":[{"type":"AstTypeGroup","location":"0,9 - 0,37","inner":{"type":"AstTypeFunction","location":"0,10 - 0,36","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeGroup","location":"0,22 - 0,36","inner":{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}}]}}},{"type":"AstTypeGroup","location":"0,40 - 0,56","inner":{"type":"AstTypeFunction","location":"0,41 - 0,55","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[]}}}]},"exported":false})";
@ -516,10 +557,18 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypeFunction")
{ {
AstStat* statement = expectParseStatement(R"(type fun = (string, bool, named: number) -> ())"); AstStat* statement = expectParseStatement(R"(type fun = (string, bool, named: number) -> ())");
std::string_view expected = if (FFlag::LuauStoreReturnTypesAsPackOnAst)
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,46","name":"fun","generics":[],"genericPacks":[],"value":{"type":"AstTypeFunction","location":"0,11 - 0,46","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,12 - 0,18","name":"string","nameLocation":"0,12 - 0,18","parameters":[]},{"type":"AstTypeReference","location":"0,20 - 0,24","name":"bool","nameLocation":"0,20 - 0,24","parameters":[]},{"type":"AstTypeReference","location":"0,33 - 0,39","name":"number","nameLocation":"0,33 - 0,39","parameters":[]}]},"argNames":[null,null,{"type":"AstArgumentName","name":"named","location":"0,26 - 0,31"}],"returnTypes":{"type":"AstTypeList","types":[]}},"exported":false})"; {
std::string_view expected =
CHECK(toJson(statement) == expected); R"({"type":"AstStatTypeAlias","location":"0,0 - 0,46","name":"fun","generics":[],"genericPacks":[],"value":{"type":"AstTypeFunction","location":"0,11 - 0,46","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,12 - 0,18","name":"string","nameLocation":"0,12 - 0,18","parameters":[]},{"type":"AstTypeReference","location":"0,20 - 0,24","name":"bool","nameLocation":"0,20 - 0,24","parameters":[]},{"type":"AstTypeReference","location":"0,33 - 0,39","name":"number","nameLocation":"0,33 - 0,39","parameters":[]}]},"argNames":[null,null,{"type":"AstArgumentName","name":"named","location":"0,26 - 0,31"}],"returnTypes":{"type":"AstTypePackExplicit","location":"0,44 - 0,46","typeList":{"type":"AstTypeList","types":[]}}},"exported":false})";
CHECK(toJson(statement) == expected);
}
else
{
std::string_view expected =
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,46","name":"fun","generics":[],"genericPacks":[],"value":{"type":"AstTypeFunction","location":"0,11 - 0,46","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,12 - 0,18","name":"string","nameLocation":"0,12 - 0,18","parameters":[]},{"type":"AstTypeReference","location":"0,20 - 0,24","name":"bool","nameLocation":"0,20 - 0,24","parameters":[]},{"type":"AstTypeReference","location":"0,33 - 0,39","name":"number","nameLocation":"0,33 - 0,39","parameters":[]}]},"argNames":[null,null,{"type":"AstArgumentName","name":"named","location":"0,26 - 0,31"}],"returnTypes":{"type":"AstTypeList","types":[]}},"exported":false})";
CHECK(toJson(statement) == expected);
}
} }
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypeError") TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypeError")

View file

@ -20,14 +20,13 @@ LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel) LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauExposeRequireByStringAutocomplete)
LUAU_FASTFLAG(LuauAutocompleteUnionCopyPreviousSeen) LUAU_FASTFLAG(LuauAutocompleteUnionCopyPreviousSeen)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete) LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete)
using namespace Luau; using namespace Luau;
static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ClassType*> ptr, std::optional<std::string> contents) static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ExternType*> ptr, std::optional<std::string> contents)
{ {
return std::nullopt; return std::nullopt;
} }
@ -159,7 +158,7 @@ struct ACBuiltinsFixture : ACFixtureImpl<BuiltinsFixture>
{ {
}; };
struct ACClassFixture : ACFixtureImpl<ClassFixture> struct ACExternTypeFixture : ACFixtureImpl<ExternTypeFixture>
{ {
}; };
@ -3754,7 +3753,7 @@ TEST_CASE_FIXTURE(ACFixture, "string_contents_is_available_to_callback")
bool isCorrect = false; bool isCorrect = false;
auto ac1 = autocomplete( auto ac1 = autocomplete(
'1', '1',
[&isCorrect](std::string, std::optional<const ClassType*>, std::optional<std::string> contents) -> std::optional<AutocompleteEntryMap> [&isCorrect](std::string, std::optional<const ExternType*>, std::optional<std::string> contents) -> std::optional<AutocompleteEntryMap>
{ {
isCorrect = contents && *contents == "testing/"; isCorrect = contents && *contents == "testing/";
return std::nullopt; return std::nullopt;
@ -3766,8 +3765,6 @@ TEST_CASE_FIXTURE(ACFixture, "string_contents_is_available_to_callback")
TEST_CASE_FIXTURE(ACBuiltinsFixture, "require_by_string") TEST_CASE_FIXTURE(ACBuiltinsFixture, "require_by_string")
{ {
ScopedFastFlag sff{FFlag::LuauExposeRequireByStringAutocomplete, true};
fileResolver.source["MainModule"] = R"( fileResolver.source["MainModule"] = R"(
local info = "MainModule serves as the root directory" local info = "MainModule serves as the root directory"
)"; )";
@ -3960,7 +3957,7 @@ TEST_CASE_FIXTURE(ACFixture, "string_completion_outside_quotes")
local x = require(@1"@2"@3) local x = require(@1"@2"@3)
)"); )");
StringCompletionCallback callback = [](std::string, std::optional<const ClassType*>, std::optional<std::string> contents StringCompletionCallback callback = [](std::string, std::optional<const ExternType*>, std::optional<std::string> contents
) -> std::optional<AutocompleteEntryMap> ) -> std::optional<AutocompleteEntryMap>
{ {
Luau::AutocompleteEntryMap results = {{"test", Luau::AutocompleteEntry{Luau::AutocompleteEntryKind::String, std::nullopt, false, false}}}; Luau::AutocompleteEntryMap results = {{"test", Luau::AutocompleteEntry{Luau::AutocompleteEntryKind::String, std::nullopt, false, false}}};
@ -4432,7 +4429,7 @@ local x = 1 + result.
CHECK(ac.entryMap.count("x")); CHECK(ac.entryMap.count("x"));
} }
TEST_CASE_FIXTURE(ACClassFixture, "ac_dont_overflow_on_recursive_union") TEST_CASE_FIXTURE(ACExternTypeFixture, "ac_dont_overflow_on_recursive_union")
{ {
ScopedFastFlag _{FFlag::LuauAutocompleteUnionCopyPreviousSeen, true}; ScopedFastFlag _{FFlag::LuauAutocompleteUnionCopyPreviousSeen, true};
check(R"( check(R"(

View file

@ -28,9 +28,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "lib_documentation_symbols")
{ {
props = &ttv->props; props = &ttv->props;
} }
else if (const ClassType* ctv = get<ClassType>(binding.typeId)) else if (const ExternType* etv = get<ExternType>(binding.typeId))
{ {
props = &ctv->props; props = &etv->props;
} }
if (props) if (props)

View file

@ -9,7 +9,7 @@ using std::nullopt;
namespace Luau namespace Luau
{ {
ClassFixture::ClassFixture(bool prepareAutocomplete) ExternTypeFixture::ExternTypeFixture(bool prepareAutocomplete)
: BuiltinsFixture(prepareAutocomplete) : BuiltinsFixture(prepareAutocomplete)
{ {
GlobalTypes& globals = frontend.globals; GlobalTypes& globals = frontend.globals;
@ -19,22 +19,22 @@ ClassFixture::ClassFixture(bool prepareAutocomplete)
unfreeze(arena); unfreeze(arena);
TypeId connectionType = arena.addType(ClassType{"Connection", {}, nullopt, nullopt, {}, {}, "Connection", {}}); TypeId connectionType = arena.addType(ExternType{"Connection", {}, nullopt, nullopt, {}, {}, "Connection", {}});
TypeId baseClassInstanceType = arena.addType(ClassType{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test", {}}); TypeId baseClassInstanceType = arena.addType(ExternType{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(baseClassInstanceType)->props = { getMutable<ExternType>(baseClassInstanceType)->props = {
{"BaseMethod", Property::readonly(makeFunction(arena, baseClassInstanceType, {numberType}, {}))}, {"BaseMethod", Property::readonly(makeFunction(arena, baseClassInstanceType, {numberType}, {}))},
{"BaseField", {numberType}}, {"BaseField", {numberType}},
{"Touched", Property::readonly(connectionType)}, {"Touched", Property::readonly(connectionType)},
}; };
getMutable<ClassType>(connectionType)->props = { getMutable<ExternType>(connectionType)->props = {
{"Connect", {makeFunction(arena, connectionType, {makeFunction(arena, nullopt, {baseClassInstanceType}, {})}, {})}} {"Connect", {makeFunction(arena, connectionType, {makeFunction(arena, nullopt, {baseClassInstanceType}, {})}, {})}}
}; };
TypeId baseClassType = arena.addType(ClassType{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test", {}}); TypeId baseClassType = arena.addType(ExternType{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(baseClassType)->props = { getMutable<ExternType>(baseClassType)->props = {
{"StaticMethod", {makeFunction(arena, nullopt, {}, {numberType})}}, {"StaticMethod", {makeFunction(arena, nullopt, {}, {numberType})}},
{"Clone", {makeFunction(arena, nullopt, {baseClassInstanceType}, {baseClassInstanceType})}}, {"Clone", {makeFunction(arena, nullopt, {baseClassInstanceType}, {baseClassInstanceType})}},
{"New", {makeFunction(arena, nullopt, {}, {baseClassInstanceType})}}, {"New", {makeFunction(arena, nullopt, {}, {baseClassInstanceType})}},
@ -42,49 +42,49 @@ ClassFixture::ClassFixture(bool prepareAutocomplete)
globals.globalScope->exportedTypeBindings["BaseClass"] = TypeFun{{}, baseClassInstanceType}; globals.globalScope->exportedTypeBindings["BaseClass"] = TypeFun{{}, baseClassInstanceType};
addGlobalBinding(globals, "BaseClass", baseClassType, "@test"); addGlobalBinding(globals, "BaseClass", baseClassType, "@test");
TypeId childClassInstanceType = arena.addType(ClassType{"ChildClass", {}, baseClassInstanceType, nullopt, {}, {}, "Test", {}}); TypeId childClassInstanceType = arena.addType(ExternType{"ChildClass", {}, baseClassInstanceType, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(childClassInstanceType)->props = { getMutable<ExternType>(childClassInstanceType)->props = {
{"Method", {makeFunction(arena, childClassInstanceType, {}, {stringType})}}, {"Method", {makeFunction(arena, childClassInstanceType, {}, {stringType})}},
}; };
TypeId childClassType = arena.addType(ClassType{"ChildClass", {}, baseClassType, nullopt, {}, {}, "Test", {}}); TypeId childClassType = arena.addType(ExternType{"ChildClass", {}, baseClassType, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(childClassType)->props = { getMutable<ExternType>(childClassType)->props = {
{"New", {makeFunction(arena, nullopt, {}, {childClassInstanceType})}}, {"New", {makeFunction(arena, nullopt, {}, {childClassInstanceType})}},
}; };
globals.globalScope->exportedTypeBindings["ChildClass"] = TypeFun{{}, childClassInstanceType}; globals.globalScope->exportedTypeBindings["ChildClass"] = TypeFun{{}, childClassInstanceType};
addGlobalBinding(globals, "ChildClass", childClassType, "@test"); addGlobalBinding(globals, "ChildClass", childClassType, "@test");
TypeId grandChildInstanceType = arena.addType(ClassType{"GrandChild", {}, childClassInstanceType, nullopt, {}, {}, "Test", {}}); TypeId grandChildInstanceType = arena.addType(ExternType{"GrandChild", {}, childClassInstanceType, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(grandChildInstanceType)->props = { getMutable<ExternType>(grandChildInstanceType)->props = {
{"Method", {makeFunction(arena, grandChildInstanceType, {}, {stringType})}}, {"Method", {makeFunction(arena, grandChildInstanceType, {}, {stringType})}},
}; };
TypeId grandChildType = arena.addType(ClassType{"GrandChild", {}, baseClassType, nullopt, {}, {}, "Test", {}}); TypeId grandChildType = arena.addType(ExternType{"GrandChild", {}, baseClassType, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(grandChildType)->props = { getMutable<ExternType>(grandChildType)->props = {
{"New", {makeFunction(arena, nullopt, {}, {grandChildInstanceType})}}, {"New", {makeFunction(arena, nullopt, {}, {grandChildInstanceType})}},
}; };
globals.globalScope->exportedTypeBindings["GrandChild"] = TypeFun{{}, grandChildInstanceType}; globals.globalScope->exportedTypeBindings["GrandChild"] = TypeFun{{}, grandChildInstanceType};
addGlobalBinding(globals, "GrandChild", childClassType, "@test"); addGlobalBinding(globals, "GrandChild", childClassType, "@test");
TypeId anotherChildInstanceType = arena.addType(ClassType{"AnotherChild", {}, baseClassInstanceType, nullopt, {}, {}, "Test", {}}); TypeId anotherChildInstanceType = arena.addType(ExternType{"AnotherChild", {}, baseClassInstanceType, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(anotherChildInstanceType)->props = { getMutable<ExternType>(anotherChildInstanceType)->props = {
{"Method", {makeFunction(arena, anotherChildInstanceType, {}, {stringType})}}, {"Method", {makeFunction(arena, anotherChildInstanceType, {}, {stringType})}},
}; };
TypeId anotherChildType = arena.addType(ClassType{"AnotherChild", {}, baseClassType, nullopt, {}, {}, "Test", {}}); TypeId anotherChildType = arena.addType(ExternType{"AnotherChild", {}, baseClassType, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(anotherChildType)->props = { getMutable<ExternType>(anotherChildType)->props = {
{"New", {makeFunction(arena, nullopt, {}, {anotherChildInstanceType})}}, {"New", {makeFunction(arena, nullopt, {}, {anotherChildInstanceType})}},
}; };
globals.globalScope->exportedTypeBindings["AnotherChild"] = TypeFun{{}, anotherChildInstanceType}; globals.globalScope->exportedTypeBindings["AnotherChild"] = TypeFun{{}, anotherChildInstanceType};
addGlobalBinding(globals, "AnotherChild", childClassType, "@test"); addGlobalBinding(globals, "AnotherChild", childClassType, "@test");
TypeId unrelatedClassInstanceType = arena.addType(ClassType{"UnrelatedClass", {}, nullopt, nullopt, {}, {}, "Test", {}}); TypeId unrelatedClassInstanceType = arena.addType(ExternType{"UnrelatedClass", {}, nullopt, nullopt, {}, {}, "Test", {}});
TypeId unrelatedClassType = arena.addType(ClassType{"UnrelatedClass", {}, nullopt, nullopt, {}, {}, "Test", {}}); TypeId unrelatedClassType = arena.addType(ExternType{"UnrelatedClass", {}, nullopt, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(unrelatedClassType)->props = { getMutable<ExternType>(unrelatedClassType)->props = {
{"New", {makeFunction(arena, nullopt, {}, {unrelatedClassInstanceType})}}, {"New", {makeFunction(arena, nullopt, {}, {unrelatedClassInstanceType})}},
}; };
globals.globalScope->exportedTypeBindings["UnrelatedClass"] = TypeFun{{}, unrelatedClassInstanceType}; globals.globalScope->exportedTypeBindings["UnrelatedClass"] = TypeFun{{}, unrelatedClassInstanceType};
@ -92,14 +92,14 @@ ClassFixture::ClassFixture(bool prepareAutocomplete)
TypeId vector2MetaType = arena.addType(TableType{}); TypeId vector2MetaType = arena.addType(TableType{});
vector2InstanceType = arena.addType(ClassType{"Vector2", {}, nullopt, vector2MetaType, {}, {}, "Test", {}}); vector2InstanceType = arena.addType(ExternType{"Vector2", {}, nullopt, vector2MetaType, {}, {}, "Test", {}});
getMutable<ClassType>(vector2InstanceType)->props = { getMutable<ExternType>(vector2InstanceType)->props = {
{"X", {numberType}}, {"X", {numberType}},
{"Y", {numberType}}, {"Y", {numberType}},
}; };
vector2Type = arena.addType(ClassType{"Vector2", {}, nullopt, nullopt, {}, {}, "Test", {}}); vector2Type = arena.addType(ExternType{"Vector2", {}, nullopt, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(vector2Type)->props = { getMutable<ExternType>(vector2Type)->props = {
{"New", {makeFunction(arena, nullopt, {numberType, numberType}, {vector2InstanceType})}}, {"New", {makeFunction(arena, nullopt, {numberType, numberType}, {vector2InstanceType})}},
}; };
getMutable<TableType>(vector2MetaType)->props = { getMutable<TableType>(vector2MetaType)->props = {
@ -114,7 +114,7 @@ ClassFixture::ClassFixture(bool prepareAutocomplete)
addGlobalBinding(globals, "Vector2", vector2Type, "@test"); addGlobalBinding(globals, "Vector2", vector2Type, "@test");
TypeId callableClassMetaType = arena.addType(TableType{}); TypeId callableClassMetaType = arena.addType(TableType{});
TypeId callableClassType = arena.addType(ClassType{"CallableClass", {}, nullopt, callableClassMetaType, {}, {}, "Test", {}}); TypeId callableClassType = arena.addType(ExternType{"CallableClass", {}, nullopt, callableClassMetaType, {}, {}, "Test", {}});
getMutable<TableType>(callableClassMetaType)->props = { getMutable<TableType>(callableClassMetaType)->props = {
{"__call", {makeFunction(arena, nullopt, {callableClassType, stringType}, {numberType})}}, {"__call", {makeFunction(arena, nullopt, {callableClassType, stringType}, {numberType})}},
}; };
@ -124,7 +124,7 @@ ClassFixture::ClassFixture(bool prepareAutocomplete)
{ {
TypeId indexableClassMetaType = arena.addType(TableType{}); TypeId indexableClassMetaType = arena.addType(TableType{});
TypeId indexableClassType = TypeId indexableClassType =
arena.addType(ClassType{className, {}, nullopt, indexableClassMetaType, {}, {}, "Test", {}, TableIndexer{keyType, returnType}}); arena.addType(ExternType{className, {}, nullopt, indexableClassMetaType, {}, {}, "Test", {}, TableIndexer{keyType, returnType}});
globals.globalScope->exportedTypeBindings[className] = TypeFun{{}, indexableClassType}; globals.globalScope->exportedTypeBindings[className] = TypeFun{{}, indexableClassType};
}; };
@ -134,9 +134,9 @@ ClassFixture::ClassFixture(bool prepareAutocomplete)
addIndexableClass("IndexableNumericKeyClass", numberType, numberType); addIndexableClass("IndexableNumericKeyClass", numberType, numberType);
// Add a confusing derived class which shares the same name internally, but has a unique alias // Add a confusing derived class which shares the same name internally, but has a unique alias
TypeId duplicateBaseClassInstanceType = arena.addType(ClassType{"BaseClass", {}, baseClassInstanceType, nullopt, {}, {}, "Test", {}}); TypeId duplicateBaseClassInstanceType = arena.addType(ExternType{"BaseClass", {}, baseClassInstanceType, nullopt, {}, {}, "Test", {}});
getMutable<ClassType>(duplicateBaseClassInstanceType)->props = { getMutable<ExternType>(duplicateBaseClassInstanceType)->props = {
{"Method", {makeFunction(arena, duplicateBaseClassInstanceType, {}, {stringType})}}, {"Method", {makeFunction(arena, duplicateBaseClassInstanceType, {}, {stringType})}},
}; };

View file

@ -6,9 +6,9 @@
namespace Luau namespace Luau
{ {
struct ClassFixture : BuiltinsFixture struct ExternTypeFixture : BuiltinsFixture
{ {
explicit ClassFixture(bool prepareAutocomplete = false); explicit ExternTypeFixture(bool prepareAutocomplete = false);
TypeId vector2Type; TypeId vector2Type;
TypeId vector2InstanceType; TypeId vector2InstanceType;

View file

@ -35,6 +35,7 @@ LUAU_FASTFLAG(LuauLibWhereErrorAutoreserve)
LUAU_FASTFLAG(DebugLuauAbortingChecks) LUAU_FASTFLAG(DebugLuauAbortingChecks)
LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
LUAU_DYNAMIC_FASTFLAG(LuauStringFormatFixC) LUAU_DYNAMIC_FASTFLAG(LuauStringFormatFixC)
LUAU_FASTFLAG(LuauYieldableContinuations)
static lua_CompileOptions defaultOptions() static lua_CompileOptions defaultOptions()
{ {
@ -823,6 +824,236 @@ TEST_CASE("Pack")
runConformance("tpack.luau"); runConformance("tpack.luau");
} }
int singleYield(lua_State* L)
{
lua_pushnumber(L, 2);
return lua_yield(L, 1);
}
int singleYieldContinuation(lua_State* L, int status)
{
lua_pushnumber(L, 4);
return 1;
}
int multipleYields(lua_State* L)
{
lua_settop(L, 1); // Only 1 argument expected
int base = luaL_checkinteger(L, 1);
luaL_checkstack(L, 2, "cmultiyield");
// current state
int pos = 1;
lua_pushinteger(L, pos);
// return value
lua_pushinteger(L, base + pos);
return lua_yield(L, 1);
}
int multipleYieldsContinuation(lua_State* L, int status)
{
// function arguments are still alive
int base = luaL_checkinteger(L, 1);
// function state is still alive
int pos = luaL_checkinteger(L, 2) + 1;
luaL_checkstack(L, 1, "cmultiyieldcont");
lua_pushinteger(L, pos);
lua_replace(L, 2);
luaL_checkstack(L, 1, "cmultiyieldcont");
if (pos < 4)
{
lua_pushinteger(L, base + pos);
return lua_yield(L, 1);
}
else
{
lua_pushinteger(L, base + pos);
return 1;
}
}
int nestedMultipleYieldHelper(lua_State* L)
{
int context = luaL_checkinteger(L, lua_upvalueindex(1));
lua_pushinteger(L, 100 + context);
return lua_yield(L, 1);
}
int nestedMultipleYieldHelperContinuation(lua_State* L, int status)
{
int context = luaL_checkinteger(L, lua_upvalueindex(1));
lua_pushinteger(L, 110 + context);
return 1;
}
int nestedMultipleYieldHelperNonYielding(lua_State* L)
{
int context = luaL_checkinteger(L, lua_upvalueindex(1));
lua_pushinteger(L, 105 + context);
return 1;
}
int multipleYieldsWithNestedCall(lua_State* L)
{
lua_settop(L, 2); // Only 2 arguments expected
bool nestedShouldYield = luaL_checkboolean(L, 2);
lua_pushinteger(L, 0); // state
lua_pushnumber(L, 5);
if (nestedShouldYield)
lua_pushcclosurek(L, nestedMultipleYieldHelper, nullptr, 1, nestedMultipleYieldHelperContinuation);
else
lua_pushcclosurek(L, nestedMultipleYieldHelperNonYielding, nullptr, 1, nullptr);
return luaL_callyieldable(L, 0, 1);
}
int multipleYieldsWithNestedCallContinuation(lua_State* L, int status)
{
int state = luaL_checkinteger(L, 3);
luaL_checkstack(L, 1, "cnestedmultiyieldcont");
lua_pushinteger(L, state + 1);
lua_replace(L, 3);
if (state == 0)
{
return lua_yield(L, lua_gettop(L) - 3);
}
else if (state == 1)
{
lua_pushnumber(L, luaL_checkinteger(L, 1) + 200);
return lua_yield(L, 1);
}
else
{
lua_pushnumber(L, luaL_checkinteger(L, 1) + 210);
return 1;
}
}
int passthroughCall(lua_State* L)
{
luaL_checkstack(L, 3, "cpass");
lua_pushvalue(L, 1);
lua_pushvalue(L, 2);
lua_pushvalue(L, 3);
return luaL_callyieldable(L, 2, 1);
}
int passthroughCallContinuation(lua_State* L, int status)
{
LUAU_ASSERT(lua_gettop(L) == 4); // 3 original arguments and the return value
LUAU_ASSERT(lua_tonumber(L, -1) == 0.5);
return 1;
}
int passthroughCallMoreResults(lua_State* L)
{
luaL_checkstack(L, 3, "cpass");
lua_pushvalue(L, 1);
lua_pushvalue(L, 2);
lua_pushvalue(L, 3);
return luaL_callyieldable(L, 2, 10);
}
int passthroughCallMoreResultsContinuation(lua_State* L, int status)
{
LUAU_ASSERT(lua_gettop(L) == 13); // 3 original arguments and 10 requested return values
for (int i = 0; i < 9; i++)
{
LUAU_ASSERT(lua_isnil(L, -1));
lua_pop(L, 1);
}
LUAU_ASSERT(lua_tonumber(L, -1) == 0.5);
return 1;
}
int passthroughCallArgReuse(lua_State* L)
{
return luaL_callyieldable(L, 2, 1);
}
int passthroughCallArgReuseContinuation(lua_State* L, int status)
{
LUAU_ASSERT(lua_gettop(L) == 1); // Original arguments were consumed, only return remains
LUAU_ASSERT(lua_tonumber(L, -1) == 0.5);
return 1;
}
int passthroughCallVaradic(lua_State* L)
{
luaL_checkany(L, 1);
return luaL_callyieldable(L, lua_gettop(L) - 1, LUA_MULTRET);
}
int passthroughCallVaradicContinuation(lua_State* L, int status)
{
return lua_gettop(L);
}
int passthroughCallWithState(lua_State* L)
{
luaL_checkany(L, 1);
int args = lua_gettop(L) - 1;
lua_pushnumber(L, 42);
lua_insert(L, 1);
return luaL_callyieldable(L, args, LUA_MULTRET);
}
int passthroughCallWithStateContinuation(lua_State* L, int status)
{
LUAU_ASSERT(luaL_checkinteger(L, 1) == 42);
return lua_gettop(L) - 1;
}
TEST_CASE("CYield")
{
ScopedFastFlag luauYieldableContinuations{FFlag::LuauYieldableContinuations, true};
runConformance(
"cyield.luau",
[](lua_State* L)
{
lua_pushcclosurek(L, singleYield, "singleYield", 0, singleYieldContinuation);
lua_setglobal(L, "singleYield");
lua_pushcclosurek(L, multipleYields, "multipleYields", 0, multipleYieldsContinuation);
lua_setglobal(L, "multipleYields");
lua_pushcclosurek(L, multipleYieldsWithNestedCall, "multipleYieldsWithNestedCall", 0, multipleYieldsWithNestedCallContinuation);
lua_setglobal(L, "multipleYieldsWithNestedCall");
lua_pushcclosurek(L, passthroughCall, "passthroughCall", 0, passthroughCallContinuation);
lua_setglobal(L, "passthroughCall");
lua_pushcclosurek(L, passthroughCallMoreResults, "passthroughCallMoreResults", 0, passthroughCallMoreResultsContinuation);
lua_setglobal(L, "passthroughCallMoreResults");
lua_pushcclosurek(L, passthroughCallArgReuse, "passthroughCallArgReuse", 0, passthroughCallArgReuseContinuation);
lua_setglobal(L, "passthroughCallArgReuse");
lua_pushcclosurek(L, passthroughCallVaradic, "passthroughCallVaradic", 0, passthroughCallVaradicContinuation);
lua_setglobal(L, "passthroughCallVaradic");
lua_pushcclosurek(L, passthroughCallWithState, "passthroughCallWithState", 0, passthroughCallWithStateContinuation);
lua_setglobal(L, "passthroughCallWithState");
}
);
}
TEST_CASE("Vector") TEST_CASE("Vector")
{ {
lua_CompileOptions copts = defaultOptions(); lua_CompileOptions copts = defaultOptions();
@ -957,7 +1188,7 @@ static void populateRTTI(lua_State* L, Luau::TypeId type)
lua_pushstring(L, "function"); lua_pushstring(L, "function");
} }
else if (auto c = Luau::get<Luau::ClassType>(type)) else if (auto c = Luau::get<Luau::ExternType>(type))
{ {
lua_pushstring(L, c->name.c_str()); lua_pushstring(L, c->name.c_str());
} }

View file

@ -1583,7 +1583,7 @@ TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "metatable_metamissing_right")
); );
} }
TEST_CASE_FIXTURE(DifferFixtureGeneric<ClassFixture>, "equal_class") TEST_CASE_FIXTURE(DifferFixtureGeneric<ExternTypeFixture>, "equal_class")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local foo = BaseClass local foo = BaseClass
@ -1594,7 +1594,7 @@ TEST_CASE_FIXTURE(DifferFixtureGeneric<ClassFixture>, "equal_class")
compareTypesEq("foo", "almostFoo"); compareTypesEq("foo", "almostFoo");
} }
TEST_CASE_FIXTURE(DifferFixtureGeneric<ClassFixture>, "class_normal") TEST_CASE_FIXTURE(DifferFixtureGeneric<ExternTypeFixture>, "class_normal")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local foo = BaseClass local foo = BaseClass

View file

@ -33,7 +33,7 @@ struct ESFixture : Fixture
ESFixture() ESFixture()
: simplifier(newSimplifier(arena, builtinTypes)) : simplifier(newSimplifier(arena, builtinTypes))
{ {
createSomeClasses(&frontend); createSomeExternTypes(&frontend);
ScopePtr moduleScope = frontend.globals.globalScope; ScopePtr moduleScope = frontend.globals.globalScope;
@ -234,7 +234,7 @@ TEST_CASE_FIXTURE(ESFixture, "nil | boolean | number | string | thread | functio
builtinTypes->threadType, builtinTypes->threadType,
builtinTypes->functionType, builtinTypes->functionType,
builtinTypes->tableType, builtinTypes->tableType,
builtinTypes->classType, builtinTypes->externType,
builtinTypes->bufferType, builtinTypes->bufferType,
}})) }}))
); );
@ -262,12 +262,12 @@ TEST_CASE_FIXTURE(ESFixture, "Child | Parent")
TEST_CASE_FIXTURE(ESFixture, "class | Child") TEST_CASE_FIXTURE(ESFixture, "class | Child")
{ {
CHECK("class" == simplifyStr(arena->addType(UnionType{{builtinTypes->classType, childClass}}))); CHECK("class" == simplifyStr(arena->addType(UnionType{{builtinTypes->externType, childClass}})));
} }
TEST_CASE_FIXTURE(ESFixture, "Parent | class | Child") TEST_CASE_FIXTURE(ESFixture, "Parent | class | Child")
{ {
CHECK("class" == simplifyStr(arena->addType(UnionType{{parentClass, builtinTypes->classType, childClass}}))); CHECK("class" == simplifyStr(arena->addType(UnionType{{parentClass, builtinTypes->externType, childClass}})));
} }
TEST_CASE_FIXTURE(ESFixture, "Parent | Unrelated") TEST_CASE_FIXTURE(ESFixture, "Parent | Unrelated")
@ -311,7 +311,7 @@ TEST_CASE_FIXTURE(ESFixture, "boolean & (true | number | string | thread | funct
builtinTypes->threadType, builtinTypes->threadType,
builtinTypes->functionType, builtinTypes->functionType,
builtinTypes->tableType, builtinTypes->tableType,
builtinTypes->classType, builtinTypes->externType,
builtinTypes->bufferType, builtinTypes->bufferType,
}}); }});

View file

@ -847,14 +847,14 @@ void registerHiddenTypes(Frontend* frontend)
globalScope->exportedTypeBindings["Not"] = TypeFun{{genericT}, globals.globalTypes.addType(NegationType{t})}; globalScope->exportedTypeBindings["Not"] = TypeFun{{genericT}, globals.globalTypes.addType(NegationType{t})};
globalScope->exportedTypeBindings["Mt"] = TypeFun{{genericT, genericU}, globals.globalTypes.addType(MetatableType{t, u})}; globalScope->exportedTypeBindings["Mt"] = TypeFun{{genericT, genericU}, globals.globalTypes.addType(MetatableType{t, u})};
globalScope->exportedTypeBindings["fun"] = TypeFun{{}, frontend->builtinTypes->functionType}; globalScope->exportedTypeBindings["fun"] = TypeFun{{}, frontend->builtinTypes->functionType};
globalScope->exportedTypeBindings["cls"] = TypeFun{{}, frontend->builtinTypes->classType}; globalScope->exportedTypeBindings["cls"] = TypeFun{{}, frontend->builtinTypes->externType};
globalScope->exportedTypeBindings["err"] = TypeFun{{}, frontend->builtinTypes->errorType}; globalScope->exportedTypeBindings["err"] = TypeFun{{}, frontend->builtinTypes->errorType};
globalScope->exportedTypeBindings["tbl"] = TypeFun{{}, frontend->builtinTypes->tableType}; globalScope->exportedTypeBindings["tbl"] = TypeFun{{}, frontend->builtinTypes->tableType};
freeze(globals.globalTypes); freeze(globals.globalTypes);
} }
void createSomeClasses(Frontend* frontend) void createSomeExternTypes(Frontend* frontend)
{ {
GlobalTypes& globals = frontend->globals; GlobalTypes& globals = frontend->globals;
@ -863,27 +863,27 @@ void createSomeClasses(Frontend* frontend)
ScopePtr moduleScope = globals.globalScope; ScopePtr moduleScope = globals.globalScope;
TypeId parentType = arena.addType(ClassType{"Parent", {}, frontend->builtinTypes->classType, std::nullopt, {}, nullptr, "Test", {}}); TypeId parentType = arena.addType(ExternType{"Parent", {}, frontend->builtinTypes->externType, std::nullopt, {}, nullptr, "Test", {}});
ClassType* parentClass = getMutable<ClassType>(parentType); ExternType* parentExternType = getMutable<ExternType>(parentType);
parentClass->props["method"] = {makeFunction(arena, parentType, {}, {})}; parentExternType->props["method"] = {makeFunction(arena, parentType, {}, {})};
parentClass->props["virtual_method"] = {makeFunction(arena, parentType, {}, {})}; parentExternType->props["virtual_method"] = {makeFunction(arena, parentType, {}, {})};
addGlobalBinding(globals, "Parent", {parentType}); addGlobalBinding(globals, "Parent", {parentType});
moduleScope->exportedTypeBindings["Parent"] = TypeFun{{}, parentType}; moduleScope->exportedTypeBindings["Parent"] = TypeFun{{}, parentType};
TypeId childType = arena.addType(ClassType{"Child", {}, parentType, std::nullopt, {}, nullptr, "Test", {}}); TypeId childType = arena.addType(ExternType{"Child", {}, parentType, std::nullopt, {}, nullptr, "Test", {}});
addGlobalBinding(globals, "Child", {childType}); addGlobalBinding(globals, "Child", {childType});
moduleScope->exportedTypeBindings["Child"] = TypeFun{{}, childType}; moduleScope->exportedTypeBindings["Child"] = TypeFun{{}, childType};
TypeId anotherChildType = arena.addType(ClassType{"AnotherChild", {}, parentType, std::nullopt, {}, nullptr, "Test", {}}); TypeId anotherChildType = arena.addType(ExternType{"AnotherChild", {}, parentType, std::nullopt, {}, nullptr, "Test", {}});
addGlobalBinding(globals, "AnotherChild", {anotherChildType}); addGlobalBinding(globals, "AnotherChild", {anotherChildType});
moduleScope->exportedTypeBindings["AnotherChild"] = TypeFun{{}, anotherChildType}; moduleScope->exportedTypeBindings["AnotherChild"] = TypeFun{{}, anotherChildType};
TypeId unrelatedType = arena.addType(ClassType{"Unrelated", {}, frontend->builtinTypes->classType, std::nullopt, {}, nullptr, "Test", {}}); TypeId unrelatedType = arena.addType(ExternType{"Unrelated", {}, frontend->builtinTypes->externType, std::nullopt, {}, nullptr, "Test", {}});
addGlobalBinding(globals, "Unrelated", {unrelatedType}); addGlobalBinding(globals, "Unrelated", {unrelatedType});
moduleScope->exportedTypeBindings["Unrelated"] = TypeFun{{}, unrelatedType}; moduleScope->exportedTypeBindings["Unrelated"] = TypeFun{{}, unrelatedType};

View file

@ -213,7 +213,7 @@ std::optional<TypeId> lookupName(ScopePtr scope, const std::string& name); // Wa
std::optional<TypeId> linearSearchForBinding(Scope* scope, const char* name); std::optional<TypeId> linearSearchForBinding(Scope* scope, const char* name);
void registerHiddenTypes(Frontend* frontend); void registerHiddenTypes(Frontend* frontend);
void createSomeClasses(Frontend* frontend); void createSomeExternTypes(Frontend* frontend);
template<typename E> template<typename E>
const E* findError(const CheckResult& result) const E* findError(const CheckResult& result)

View file

@ -23,7 +23,6 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTINT(LuauParseErrorLimit) LUAU_FASTINT(LuauParseErrorLimit)
LUAU_FASTFLAG(LuauCloneIncrementalModule) LUAU_FASTFLAG(LuauCloneIncrementalModule)
@ -34,7 +33,6 @@ LUAU_FASTFLAG(LuauBetterReverseDependencyTracking)
LUAU_FASTFLAG(LuauAutocompleteUsesModuleForTypeCompatibility) LUAU_FASTFLAG(LuauAutocompleteUsesModuleForTypeCompatibility)
LUAU_FASTFLAG(LuauBetterCursorInCommentDetection) LUAU_FASTFLAG(LuauBetterCursorInCommentDetection)
LUAU_FASTFLAG(LuauAllFreeTypesHaveScopes) LUAU_FASTFLAG(LuauAllFreeTypesHaveScopes)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAG(LuauClonedTableAndFunctionTypesMustHaveScopes) LUAU_FASTFLAG(LuauClonedTableAndFunctionTypesMustHaveScopes)
LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode) LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode)
LUAU_FASTFLAG(LuauCloneTypeAliasBindings) LUAU_FASTFLAG(LuauCloneTypeAliasBindings)
@ -46,7 +44,7 @@ LUAU_FASTFLAG(LuauBlockDiffFragmentSelection)
LUAU_FASTFLAG(LuauFragmentAcMemoryLeak) LUAU_FASTFLAG(LuauFragmentAcMemoryLeak)
LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation) LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation)
static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ClassType*> ptr, std::optional<std::string> contents) static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ExternType*> ptr, std::optional<std::string> contents)
{ {
return std::nullopt; return std::nullopt;
} }
@ -74,7 +72,6 @@ struct FragmentAutocompleteFixtureImpl : BaseType
{ {
static_assert(std::is_base_of_v<Fixture, BaseType>, "BaseType must be a descendant of Fixture"); static_assert(std::is_base_of_v<Fixture, BaseType>, "BaseType must be a descendant of Fixture");
ScopedFastFlag luauAutocompleteRefactorsForIncrementalAutocomplete{FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete, true};
ScopedFastFlag luauFreeTypesMustHaveBounds{FFlag::LuauFreeTypesMustHaveBounds, true}; ScopedFastFlag luauFreeTypesMustHaveBounds{FFlag::LuauFreeTypesMustHaveBounds, true};
ScopedFastFlag luauCloneIncrementalModule{FFlag::LuauCloneIncrementalModule, true}; ScopedFastFlag luauCloneIncrementalModule{FFlag::LuauCloneIncrementalModule, true};
ScopedFastFlag luauAllFreeTypesHaveScopes{FFlag::LuauAllFreeTypesHaveScopes, true}; ScopedFastFlag luauAllFreeTypesHaveScopes{FFlag::LuauAllFreeTypesHaveScopes, true};
@ -386,7 +383,7 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "inside_do")
R"( R"(
local x = 4 local x = 4
do do
end end
)", )",
{3, 3} {3, 3}
@ -492,7 +489,7 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "writing_func_annotation")
{ {
auto region = getAutocompleteRegion( auto region = getAutocompleteRegion(
R"( R"(
function f(arg1 : T function f(arg1 : T
)", )",
{1, 19} {1, 19}
); );
@ -755,7 +752,7 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_partial")
{ {
auto region = getAutocompleteRegion( auto region = getAutocompleteRegion(
R"( R"(
if if
)", )",
Position{1, 3} Position{1, 3}
); );
@ -768,7 +765,7 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_partial_in_condition_at")
{ {
auto region = getAutocompleteRegion( auto region = getAutocompleteRegion(
R"( R"(
if true if true
)", )",
Position{1, 7} Position{1, 7}
); );
@ -781,7 +778,7 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_partial_in_condition_after")
{ {
auto region = getAutocompleteRegion( auto region = getAutocompleteRegion(
R"( R"(
if true if true
)", )",
Position{1, 8} Position{1, 8}
); );
@ -838,7 +835,7 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_else_if")
auto region = getAutocompleteRegion( auto region = getAutocompleteRegion(
R"( R"(
if true then if true then
elseif elseif
end end
)", )",
@ -854,7 +851,7 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_else_if_no_end")
auto region = getAutocompleteRegion( auto region = getAutocompleteRegion(
R"( R"(
if true then if true then
elseif elseif
)", )",
Position{2, 8} Position{2, 8}
); );
@ -1463,7 +1460,7 @@ local tbl = { abc = 1234}
auto fragment = autocompleteFragment( auto fragment = autocompleteFragment(
R"( R"(
local tbl = { abc = 1234} local tbl = { abc = 1234}
tbl. tbl.
)", )",
Position{2, 5} Position{2, 5}
); );
@ -1589,7 +1586,7 @@ local tbl = { abc = 1234}
)"; )";
const std::string updated = R"( const std::string updated = R"(
local tbl = { abc = 1234} local tbl = { abc = 1234}
tbl. tbl.
)"; )";
autocompleteFragmentInBothSolvers( autocompleteFragmentInBothSolvers(
@ -1644,7 +1641,7 @@ end
local function f2(a2) local function f2(a2)
local l2 = 1; local l2 = 1;
g2 = 1; g2 = 1;
end end
)"; )";
autocompleteFragmentInBothSolvers( autocompleteFragmentInBothSolvers(
@ -3027,7 +3024,6 @@ z:a
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "interior_free_types_assertion_caused_by_free_type_inheriting_null_scope_from_table") TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "interior_free_types_assertion_caused_by_free_type_inheriting_null_scope_from_table")
{ {
ScopedFastFlag sff{FFlag::LuauTrackInteriorFreeTypesOnScope, true};
const std::string source = R"(--!strict const std::string source = R"(--!strict
local foo local foo
local a = foo() local a = foo()

View file

@ -17,7 +17,6 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(DebugLuauForbidInternalTypes) LUAU_FASTFLAG(DebugLuauForbidInternalTypes)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall) LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall)
TEST_SUITE_BEGIN("Generalization"); TEST_SUITE_BEGIN("Generalization");
@ -116,12 +115,12 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "dont_traverse_into_class_types_when_ge
{ {
auto [propTy, _] = freshType(); auto [propTy, _] = freshType();
TypeId cursedClass = arena.addType(ClassType{"Cursed", {{"oh_no", Property::readonly(propTy)}}, std::nullopt, std::nullopt, {}, {}, "", {}}); TypeId cursedExternType = arena.addType(ExternType{"Cursed", {{"oh_no", Property::readonly(propTy)}}, std::nullopt, std::nullopt, {}, {}, "", {}});
auto genClass = generalize(cursedClass); auto genExternType = generalize(cursedExternType);
REQUIRE(genClass); REQUIRE(genExternType);
auto genPropTy = get<ClassType>(*genClass)->props.at("oh_no").readTy; auto genPropTy = get<ExternType>(*genExternType)->props.at("oh_no").readTy;
CHECK(is<FreeType>(*genPropTy)); CHECK(is<FreeType>(*genPropTy));
} }
@ -344,7 +343,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "generalization_should_not_leak_free_type")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::DebugLuauForbidInternalTypes, true}, {FFlag::DebugLuauForbidInternalTypes, true},
{FFlag::LuauTrackInteriorFreeTypesOnScope, true},
{FFlag::LuauTrackInferredFunctionTypeFromCall, true} {FFlag::LuauTrackInferredFunctionTypeFromCall, true}
}; };

View file

@ -1252,15 +1252,19 @@ _ = {
[2] = 2, [2] = 2,
[1] = 3, [1] = 3,
} }
function _foo(): { first: number, second: string, first: boolean }
end
)"); )");
REQUIRE(6 == result.warnings.size()); REQUIRE(7 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Table field 'first' is a duplicate; previously defined at line 3"); CHECK_EQ(result.warnings[0].text, "Table field 'first' is a duplicate; previously defined at line 3");
CHECK_EQ(result.warnings[1].text, "Table field 'first' is a duplicate; previously defined at line 9"); CHECK_EQ(result.warnings[1].text, "Table field 'first' is a duplicate; previously defined at line 9");
CHECK_EQ(result.warnings[2].text, "Table index 1 is a duplicate; previously defined as a list entry"); CHECK_EQ(result.warnings[2].text, "Table index 1 is a duplicate; previously defined as a list entry");
CHECK_EQ(result.warnings[3].text, "Table index 3 is a duplicate; previously defined as a list entry"); CHECK_EQ(result.warnings[3].text, "Table index 3 is a duplicate; previously defined as a list entry");
CHECK_EQ(result.warnings[4].text, "Table type field 'first' is a duplicate; previously defined at line 24"); CHECK_EQ(result.warnings[4].text, "Table type field 'first' is a duplicate; previously defined at line 24");
CHECK_EQ(result.warnings[5].text, "Table index 1 is a duplicate; previously defined at line 36"); CHECK_EQ(result.warnings[5].text, "Table index 1 is a duplicate; previously defined at line 36");
CHECK_EQ(result.warnings[6].text, "Table type field 'first' is a duplicate; previously defined at line 41");
} }
TEST_CASE_FIXTURE(Fixture, "read_write_table_props") TEST_CASE_FIXTURE(Fixture, "read_write_table_props")
@ -1299,6 +1303,19 @@ TEST_CASE_FIXTURE(Fixture, "ImportOnlyUsedInTypeAnnotation")
CHECK_EQ(result.warnings[0].text, "Variable 'x' is never used; prefix with '_' to silence"); CHECK_EQ(result.warnings[0].text, "Variable 'x' is never used; prefix with '_' to silence");
} }
TEST_CASE_FIXTURE(Fixture, "ImportOnlyUsedInReturnType")
{
LintResult result = lint(R"(
local Foo = require(script.Parent.Foo)
function foo(): Foo.Y
end
)");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Function 'foo' is never used; prefix with '_' to silence");
}
TEST_CASE_FIXTURE(Fixture, "DisableUnknownGlobalWithTypeChecking") TEST_CASE_FIXTURE(Fixture, "DisableUnknownGlobalWithTypeChecking")
{ {
LintResult result = lint(R"( LintResult result = lint(R"(
@ -1504,11 +1521,11 @@ TEST_CASE_FIXTURE(Fixture, "LintHygieneUAF")
TEST_CASE_FIXTURE(BuiltinsFixture, "DeprecatedApiTyped") TEST_CASE_FIXTURE(BuiltinsFixture, "DeprecatedApiTyped")
{ {
unfreeze(frontend.globals.globalTypes); unfreeze(frontend.globals.globalTypes);
TypeId instanceType = frontend.globals.globalTypes.addType(ClassType{"Instance", {}, std::nullopt, std::nullopt, {}, {}, "Test", {}}); TypeId instanceType = frontend.globals.globalTypes.addType(ExternType{"Instance", {}, std::nullopt, std::nullopt, {}, {}, "Test", {}});
persist(instanceType); persist(instanceType);
frontend.globals.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, instanceType}; frontend.globals.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, instanceType};
getMutable<ClassType>(instanceType)->props = { getMutable<ExternType>(instanceType)->props = {
{"Name", {builtinTypes->stringType}}, {"Name", {builtinTypes->stringType}},
{"DataCost", {builtinTypes->numberType, /* deprecated= */ true}}, {"DataCost", {builtinTypes->numberType, /* deprecated= */ true}},
{"Wait", {builtinTypes->anyType, /* deprecated= */ true}}, {"Wait", {builtinTypes->anyType, /* deprecated= */ true}},
@ -1827,7 +1844,7 @@ Account = { balance=0 }
function Account:deposit(v) function Account:deposit(v)
self.balance = self.balance + v self.balance = self.balance + v
end end
Account:deposit(200.00) Account:deposit(200.00)
)"); )");
@ -1848,7 +1865,7 @@ end
function Account:deposit (v) function Account:deposit (v)
self.balance = self.balance + v self.balance = self.balance + v
end end
(getAccount()):deposit(200.00) (getAccount()):deposit(200.00)
)"); )");

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