Compare commits

..

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

223 changed files with 7311 additions and 15804 deletions

View file

@ -0,0 +1,148 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/AstQuery.h"
#include "Luau/Config.h"
#include "Luau/ModuleResolver.h"
#include "Luau/Scope.h"
#include "Luau/Variant.h"
#include "Luau/Normalize.h"
#include "Luau/TypePack.h"
#include "Luau/TypeArena.h"
#include <mutex>
#include <string>
#include <vector>
#include <optional>
namespace Luau
{
class AstStat;
class ParseError;
struct TypeError;
struct LintWarning;
struct GlobalTypes;
struct ModuleResolver;
struct ParseResult;
struct DcrLogger;
struct TelemetryTypePair
{
std::string annotatedType;
std::string inferredType;
};
struct AnyTypeSummary
{
TypeArena arena;
AstStatBlock* rootSrc = nullptr;
DenseHashSet<TypeId> seenTypeFamilyInstances{nullptr};
int recursionCount = 0;
std::string root;
int strictCount = 0;
DenseHashMap<const void*, bool> seen{nullptr};
AnyTypeSummary();
void traverse(const Module* module, AstStat* src, NotNull<BuiltinTypes> builtinTypes);
std::pair<bool, TypeId> checkForAnyCast(const Scope* scope, AstExprTypeAssertion* expr);
bool containsAny(TypePackId typ);
bool containsAny(TypeId typ);
bool isAnyCast(const Scope* scope, AstExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
bool isAnyCall(const Scope* scope, AstExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
bool hasVariadicAnys(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
bool hasArgAnys(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
bool hasAnyReturns(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
TypeId checkForFamilyInhabitance(const TypeId instance, Location location);
TypeId lookupType(const AstExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
TypePackId reconstructTypePack(const AstArray<AstExpr*> exprs, const Module* module, NotNull<BuiltinTypes> builtinTypes);
DenseHashSet<TypeId> seenTypeFunctionInstances{nullptr};
TypeId lookupAnnotation(AstType* annotation, const Module* module, NotNull<BuiltinTypes> builtintypes);
std::optional<TypePackId> lookupPackAnnotation(AstTypePack* annotation, const Module* module);
TypeId checkForTypeFunctionInhabitance(const TypeId instance, const Location location);
enum Pattern : uint64_t
{
Casts,
FuncArg,
FuncRet,
FuncApp,
VarAnnot,
VarAny,
TableProp,
Alias,
Assign,
TypePk
};
struct TypeInfo
{
Pattern code;
std::string node;
TelemetryTypePair type;
explicit TypeInfo(Pattern code, std::string node, TelemetryTypePair type);
};
struct FindReturnAncestry final : public AstVisitor
{
AstNode* currNode{nullptr};
AstNode* stat{nullptr};
Position rootEnd;
bool found = false;
explicit FindReturnAncestry(AstNode* stat, Position rootEnd);
bool visit(AstType* node) override;
bool visit(AstNode* node) override;
bool visit(AstStatFunction* node) override;
bool visit(AstStatLocalFunction* node) override;
};
std::vector<TypeInfo> typeInfo;
/**
* Fabricates a scope that is a child of another scope.
* @param node the lexical node that the scope belongs to.
* @param parent the parent scope of the new scope. Must not be null.
*/
const Scope* childScope(const AstNode* node, const Scope* parent);
std::optional<AstExpr*> matchRequire(const AstExprCall& call);
AstNode* getNode(AstStatBlock* root, AstNode* node);
const Scope* findInnerMostScope(const Location location, const Module* module);
const AstNode* findAstAncestryAtLocation(const AstStatBlock* root, AstNode* node);
void visit(const Scope* scope, AstStat* stat, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatBlock* block, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatIf* ifStatement, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatWhile* while_, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatRepeat* repeat, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatReturn* ret, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatLocal* local, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatFor* for_, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatForIn* forIn, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatAssign* assign, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatCompoundAssign* assign, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatFunction* function, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatLocalFunction* function, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatTypeAlias* alias, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatDeclareGlobal* declareGlobal, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatDeclareClass* declareClass, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatDeclareFunction* declareFunction, const Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(const Scope* scope, AstStatError* error, const Module* module, NotNull<BuiltinTypes> builtinTypes);
};
} // namespace Luau

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 ExternType*> containingExternType = std::nullopt; std::optional<const ClassType*> containingClass = 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 ExternType*> ctx, std::optional<std::string> contents)>; std::function<std::optional<AutocompleteEntryMap>(std::string tag, std::optional<const ClassType*> ctx, std::optional<std::string> contents)>;
constexpr char kGeneratedAnonymousFunctionEntryName[] = "function (anonymous autofilled)"; constexpr char kGeneratedAnonymousFunctionEntryName[] = "function (anonymous autofilled)";

View file

@ -50,7 +50,6 @@ struct GeneralizationConstraint
TypeId sourceType; TypeId sourceType;
std::vector<TypeId> interiorTypes; std::vector<TypeId> interiorTypes;
bool hasDeprecatedAttribute = false;
}; };
// variables ~ iterate iterator // variables ~ iterate iterator

View file

@ -3,7 +3,6 @@
#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"
@ -12,14 +11,15 @@
#include "Luau/ModuleResolver.h" #include "Luau/ModuleResolver.h"
#include "Luau/Normalize.h" #include "Luau/Normalize.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/Polarity.h"
#include "Luau/Refinement.h" #include "Luau/Refinement.h"
#include "Luau/Symbol.h" #include "Luau/Symbol.h"
#include "Luau/TypeFwd.h" #include "Luau/TypeFwd.h"
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
#include "Luau/Variant.h"
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <unordered_map>
namespace Luau namespace Luau
{ {
@ -92,8 +92,9 @@ struct ConstraintGenerator
// Constraints that go straight to the solver. // Constraints that go straight to the solver.
std::vector<ConstraintPtr> constraints; std::vector<ConstraintPtr> constraints;
// The set of all free types introduced during constraint generation. // Constraints that do not go to the solver right away. Other constraints
DenseHashSet<TypeId> freeTypes{nullptr}; // will enqueue them during solving.
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};
@ -131,8 +132,6 @@ struct ConstraintGenerator
DenseHashMap<TypeId, TypeIds> localTypes{nullptr}; DenseHashMap<TypeId, TypeIds> localTypes{nullptr};
DenseHashMap<AstExpr*, Inference> inferredExprCache{nullptr};
DcrLogger* logger; DcrLogger* logger;
ConstraintGenerator( ConstraintGenerator(
@ -151,9 +150,6 @@ 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.
@ -164,26 +160,19 @@ struct ConstraintGenerator
void visitFragmentRoot(const ScopePtr& resumeScope, AstStatBlock* block); void visitFragmentRoot(const ScopePtr& resumeScope, AstStatBlock* block);
private: private:
struct InteriorFreeTypes std::vector<std::vector<TypeId>> interiorTypes;
{
std::vector<TypeId> types;
std::vector<TypePackId> typePacks;
};
std::vector<std::vector<TypeId>> DEPRECATED_interiorTypes;
std::vector<InteriorFreeTypes> interiorFreeTypes;
/** /**
* Fabricates a new free type belonging to a given scope. * Fabricates a new free type belonging to a given scope.
* @param scope the scope the free type belongs to. * @param scope the scope the free type belongs to.
*/ */
TypeId freshType(const ScopePtr& scope, Polarity polarity = Polarity::Unknown); TypeId freshType(const ScopePtr& scope);
/** /**
* Fabricates a new free type pack belonging to a given scope. * Fabricates a new free type pack belonging to a given scope.
* @param scope the scope the free type pack belongs to. * @param scope the scope the free type pack belongs to.
*/ */
TypePackId freshTypePack(const ScopePtr& scope, Polarity polarity = Polarity::Unknown); TypePackId freshTypePack(const ScopePtr& scope);
/** /**
* Allocate a new TypePack with the given head and tail. * Allocate a new TypePack with the given head and tail.
@ -272,7 +261,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, AstStatDeclareExternType* declareExternType); ControlFlow visit(const ScopePtr& scope, AstStatDeclareClass* declareClass);
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);
@ -304,7 +293,7 @@ private:
); );
Inference check(const ScopePtr& scope, AstExprConstantString* string, std::optional<TypeId> expectedType, bool forceSingleton); Inference check(const ScopePtr& scope, AstExprConstantString* string, std::optional<TypeId> expectedType, bool forceSingleton);
Inference check(const ScopePtr& scope, AstExprConstantBool* boolExpr, std::optional<TypeId> expectedType, bool forceSingleton); Inference check(const ScopePtr& scope, AstExprConstantBool* bool_, std::optional<TypeId> expectedType, bool forceSingleton);
Inference check(const ScopePtr& scope, AstExprLocal* local); Inference check(const ScopePtr& scope, AstExprLocal* local);
Inference check(const ScopePtr& scope, AstExprGlobal* global); Inference check(const ScopePtr& scope, AstExprGlobal* global);
Inference checkIndexName(const ScopePtr& scope, const RefinementKey* key, AstExpr* indexee, const std::string& index, Location indexLocation); Inference checkIndexName(const ScopePtr& scope, const RefinementKey* key, AstExpr* indexee, const std::string& index, Location indexLocation);
@ -380,11 +369,6 @@ private:
**/ **/
TypeId resolveType(const ScopePtr& scope, AstType* ty, bool inTypeArguments, bool replaceErrorWithFresh = false); TypeId resolveType(const ScopePtr& scope, AstType* ty, bool inTypeArguments, bool replaceErrorWithFresh = false);
// resolveType() is recursive, but we only want to invoke
// inferGenericPolarities() once at the very end. We thus isolate the
// recursive part of the algorithm to this internal helper.
TypeId resolveType_(const ScopePtr& scope, AstType* ty, bool inTypeArguments, bool replaceErrorWithFresh = false);
/** /**
* Resolves a type pack from its AST annotation. * Resolves a type pack from its AST annotation.
* @param scope the scope that the type annotation appears within. * @param scope the scope that the type annotation appears within.
@ -394,9 +378,6 @@ private:
**/ **/
TypePackId resolveTypePack(const ScopePtr& scope, AstTypePack* tp, bool inTypeArguments, bool replaceErrorWithFresh = false); TypePackId resolveTypePack(const ScopePtr& scope, AstTypePack* tp, bool inTypeArguments, bool replaceErrorWithFresh = false);
// Inner hepler for resolveTypePack
TypePackId resolveTypePack_(const ScopePtr& scope, AstTypePack* tp, bool inTypeArguments, bool replaceErrorWithFresh = false);
/** /**
* Resolves a type pack from its AST annotation. * Resolves a type pack from its AST annotation.
* @param scope the scope that the type annotation appears within. * @param scope the scope that the type annotation appears within.
@ -435,7 +416,7 @@ private:
**/ **/
std::vector<std::pair<Name, GenericTypePackDefinition>> createGenericPacks( std::vector<std::pair<Name, GenericTypePackDefinition>> createGenericPacks(
const ScopePtr& scope, const ScopePtr& scope,
AstArray<AstGenericTypePack*> generics, AstArray<AstGenericTypePack*> packs,
bool useCache = false, bool useCache = false,
bool addTypes = true bool addTypes = true
); );
@ -484,4 +465,9 @@ 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

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

View file

@ -3,7 +3,6 @@
#pragma once #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"
@ -88,7 +87,6 @@ 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;
@ -142,19 +140,6 @@ 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,
@ -189,9 +174,6 @@ 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);
/** /**
@ -383,7 +365,7 @@ public:
* @returns a non-free type that generalizes the argument, or `std::nullopt` if one * @returns a non-free type that generalizes the argument, or `std::nullopt` if one
* does not exist * does not exist
*/ */
std::optional<TypeId> generalizeFreeType(NotNull<Scope> scope, TypeId type); std::optional<TypeId> generalizeFreeType(NotNull<Scope> scope, TypeId type, bool avoidSealingTables = false);
/** /**
* Checks the existing set of constraints to see if there exist any that contain * Checks the existing set of constraints to see if there exist any that contain
@ -450,10 +432,6 @@ 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

@ -38,6 +38,8 @@ struct DataFlowGraph
DefId getDef(const AstExpr* expr) const; DefId getDef(const AstExpr* expr) const;
// Look up the definition optionally, knowing it may not be present. // Look up the definition optionally, knowing it may not be present.
std::optional<DefId> getDefOptional(const AstExpr* expr) const; std::optional<DefId> getDefOptional(const AstExpr* expr) const;
// Look up for the rvalue def for a compound assignment.
std::optional<DefId> getRValueDefForCompoundAssign(const AstExpr* expr) const;
DefId getDef(const AstLocal* local) const; DefId getDef(const AstLocal* local) const;
@ -64,6 +66,10 @@ private:
// All keys in this maps are really only statements that ambiently declares a symbol. // All keys in this maps are really only statements that ambiently declares a symbol.
DenseHashMap<const AstStat*, const Def*> declaredDefs{nullptr}; DenseHashMap<const AstStat*, const Def*> declaredDefs{nullptr};
// Compound assignments are in a weird situation where the local being assigned to is also being used at its
// previous type implicitly in an rvalue position. This map provides the previous binding.
DenseHashMap<const AstExpr*, const Def*> compoundAssignDefs{nullptr};
DenseHashMap<const AstExpr*, const RefinementKey*> astRefinementKeys{nullptr}; DenseHashMap<const AstExpr*, const RefinementKey*> astRefinementKeys{nullptr};
friend struct DataFlowGraphBuilder; friend struct DataFlowGraphBuilder;
}; };
@ -129,8 +135,8 @@ private:
/// A stack of scopes used by the visitor to see where we are. /// A stack of scopes used by the visitor to see where we are.
ScopeStack scopeStack; ScopeStack scopeStack;
NotNull<DfgScope> currentScope();
DfgScope* currentScope_DEPRECATED(); DfgScope* currentScope();
struct FunctionCapture struct FunctionCapture
{ {
@ -148,8 +154,8 @@ private:
void joinBindings(DfgScope* p, const DfgScope& a, const DfgScope& b); void joinBindings(DfgScope* p, const DfgScope& a, const DfgScope& b);
void joinProps(DfgScope* p, const DfgScope& a, const DfgScope& b); void joinProps(DfgScope* p, const DfgScope& a, const DfgScope& b);
DefId lookup(Symbol symbol, Location location); DefId lookup(Symbol symbol);
DefId lookup(DefId def, const std::string& key, Location location); DefId lookup(DefId def, const std::string& key);
ControlFlow visit(AstStatBlock* b); ControlFlow visit(AstStatBlock* b);
ControlFlow visitBlockWithoutChildScope(AstStatBlock* b); ControlFlow visitBlockWithoutChildScope(AstStatBlock* b);
@ -173,7 +179,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(AstStatDeclareExternType* d); ControlFlow visit(AstStatDeclareClass* d);
ControlFlow visit(AstStatError* error); ControlFlow visit(AstStatError* error);
DataFlowResult visitExpr(AstExpr* e); DataFlowResult visitExpr(AstExpr* e);

View file

@ -4,8 +4,7 @@
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/TypedAllocator.h" #include "Luau/TypedAllocator.h"
#include "Luau/Variant.h" #include "Luau/Variant.h"
#include "Luau/Location.h"
#include "Luau/Symbol.h"
#include <string> #include <string>
#include <optional> #include <optional>
@ -14,7 +13,6 @@ namespace Luau
struct Def; struct Def;
using DefId = NotNull<const Def>; using DefId = NotNull<const Def>;
struct AstLocal;
/** /**
* A cell is a "single-object" value. * A cell is a "single-object" value.
@ -66,8 +64,6 @@ struct Def
using V = Variant<struct Cell, struct Phi>; using V = Variant<struct Cell, struct Phi>;
V v; V v;
Symbol name;
Location location;
}; };
template<typename T> template<typename T>
@ -83,7 +79,7 @@ struct DefArena
{ {
TypedAllocator<Def> allocator; TypedAllocator<Def> allocator;
DefId freshCell(Symbol sym, Location location, bool subscripted = false); DefId freshCell(bool subscripted = false);
DefId phi(DefId a, DefId b); DefId phi(DefId a, DefId b);
DefId phi(const std::vector<DefId>& defs); DefId phi(const std::vector<DefId>& defs);
}; };

View file

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

View file

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

View file

@ -49,8 +49,6 @@ struct FragmentAutocompleteAncestryResult
std::vector<AstLocal*> localStack; std::vector<AstLocal*> localStack;
std::vector<AstNode*> ancestry; std::vector<AstNode*> ancestry;
AstStat* nearestStatement = nullptr; AstStat* nearestStatement = nullptr;
AstStatBlock* parentBlock = nullptr;
Location fragmentSelectionRegion;
}; };
struct FragmentParseResult struct FragmentParseResult
@ -61,7 +59,6 @@ struct FragmentParseResult
AstStat* nearestStatement = nullptr; AstStat* nearestStatement = nullptr;
std::vector<Comment> commentLocations; std::vector<Comment> commentLocations;
std::unique_ptr<Allocator> alloc = std::make_unique<Allocator>(); std::unique_ptr<Allocator> alloc = std::make_unique<Allocator>();
Position scopePos{0, 0};
}; };
struct FragmentTypeCheckResult struct FragmentTypeCheckResult
@ -75,33 +72,14 @@ struct FragmentAutocompleteResult
{ {
ModulePtr incrementalModule; ModulePtr incrementalModule;
Scope* freshScope; Scope* freshScope;
TypeArena arenaForAutocomplete_DEPRECATED; TypeArena arenaForAutocomplete;
AutocompleteResult acResults; AutocompleteResult acResults;
}; };
struct FragmentRegion FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* root, const Position& cursorPos);
{
Location fragmentLocation;
AstStat* nearestStatement = nullptr; // used for tests
AstStatBlock* parentBlock = nullptr; // used for scope detection
};
std::optional<Position> blockDiffStart(AstStatBlock* blockOld, AstStatBlock* blockNew, AstStat* nearestStatementNewAst);
FragmentRegion getFragmentRegion(AstStatBlock* root, const Position& cursorPosition);
FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* stale, const Position& cursorPos, AstStatBlock* lastGoodParse);
FragmentAutocompleteAncestryResult findAncestryForFragmentParse_DEPRECATED(AstStatBlock* root, const Position& cursorPos);
std::optional<FragmentParseResult> parseFragment_DEPRECATED(
AstStatBlock* root,
AstNameTable* names,
std::string_view src,
const Position& cursorPos,
std::optional<Position> fragmentEndPosition
);
std::optional<FragmentParseResult> parseFragment( std::optional<FragmentParseResult> parseFragment(
AstStatBlock* stale, AstStatBlock* root,
AstStatBlock* mostRecentParse,
AstNameTable* names, AstNameTable* names,
std::string_view src, std::string_view src,
const Position& cursorPos, const Position& cursorPos,
@ -115,7 +93,6 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
std::optional<FrontendOptions> opts, std::optional<FrontendOptions> opts,
std::string_view src, std::string_view src,
std::optional<Position> fragmentEndPosition, std::optional<Position> fragmentEndPosition,
AstStatBlock* recentParse = nullptr,
IFragmentAutocompleteReporter* reporter = nullptr IFragmentAutocompleteReporter* reporter = nullptr
); );
@ -127,7 +104,6 @@ FragmentAutocompleteResult fragmentAutocomplete(
std::optional<FrontendOptions> opts, std::optional<FrontendOptions> opts,
StringCompletionCallback callback, StringCompletionCallback callback,
std::optional<Position> fragmentEndPosition = std::nullopt, std::optional<Position> fragmentEndPosition = std::nullopt,
AstStatBlock* recentParse = nullptr,
IFragmentAutocompleteReporter* reporter = nullptr IFragmentAutocompleteReporter* reporter = nullptr
); );

View file

@ -10,6 +10,7 @@
#include "Luau/Set.h" #include "Luau/Set.h"
#include "Luau/TypeCheckLimits.h" #include "Luau/TypeCheckLimits.h"
#include "Luau/Variant.h" #include "Luau/Variant.h"
#include "Luau/AnyTypeSummary.h"
#include <mutex> #include <mutex>
#include <string> #include <string>
@ -33,6 +34,7 @@ struct HotComment;
struct BuildQueueItem; struct BuildQueueItem;
struct BuildQueueWorkState; struct BuildQueueWorkState;
struct FrontendCancellationToken; struct FrontendCancellationToken;
struct AnyTypeSummary;
struct LoadDefinitionFileResult struct LoadDefinitionFileResult
{ {
@ -215,6 +217,11 @@ struct Frontend
std::function<void(std::function<void()> task)> executeTask = {}, std::function<void(std::function<void()> task)> executeTask = {},
std::function<bool(size_t done, size_t total)> progress = {} std::function<bool(size_t done, size_t total)> progress = {}
); );
std::vector<ModuleName> checkQueuedModules_DEPRECATED(
std::optional<FrontendOptions> optionOverride = {},
std::function<void(std::function<void()> task)> executeTask = {},
std::function<bool(size_t done, size_t total)> progress = {}
);
std::optional<CheckResult> getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete = false); std::optional<CheckResult> getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete = false);
std::vector<ModuleName> getRequiredScripts(const ModuleName& name); std::vector<ModuleName> getRequiredScripts(const ModuleName& name);

View file

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

View file

@ -1,16 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/NotNull.h"
#include "Luau/TypeFwd.h"
namespace Luau
{
struct Scope;
struct TypeArena;
void inferGenericPolarities(NotNull<TypeArena> arena, NotNull<Scope> scope, TypeId ty);
void inferGenericPolarities(NotNull<TypeArena> arena, NotNull<Scope> scope, TypePackId tp);
} // namespace Luau

View file

@ -67,19 +67,6 @@ public:
return &pairs.at(it->second).second; return &pairs.at(it->second).second;
} }
V& operator[](const K& k)
{
auto it = indices.find(k);
if (it == indices.end())
{
pairs.push_back(std::make_pair(k, V()));
indices[k] = pairs.size() - 1;
return pairs.back().second;
}
else
return pairs.at(it->second).second;
}
const_iterator begin() const const_iterator begin() const
{ {
return pairs.begin(); return pairs.begin();

View file

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

View file

@ -8,6 +8,7 @@
#include "Luau/ParseResult.h" #include "Luau/ParseResult.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/TypeArena.h" #include "Luau/TypeArena.h"
#include "Luau/AnyTypeSummary.h"
#include "Luau/DataFlowGraph.h" #include "Luau/DataFlowGraph.h"
#include <memory> #include <memory>
@ -15,16 +16,19 @@
#include <unordered_map> #include <unordered_map>
#include <optional> #include <optional>
LUAU_FASTFLAG(LuauIncrementalAutocompleteCommentDetection)
namespace Luau namespace Luau
{ {
using LogLuauProc = void (*)(std::string_view, std::string_view); using LogLuauProc = void (*)(std::string_view);
extern LogLuauProc logLuau; extern LogLuauProc logLuau;
void setLogLuau(LogLuauProc ll); void setLogLuau(LogLuauProc ll);
void resetLogLuauProc(); void resetLogLuauProc();
struct Module; struct Module;
struct AnyTypeSummary;
using ScopePtr = std::shared_ptr<struct Scope>; using ScopePtr = std::shared_ptr<struct Scope>;
using ModulePtr = std::shared_ptr<Module>; using ModulePtr = std::shared_ptr<Module>;
@ -82,6 +86,10 @@ struct Module
TypeArena interfaceTypes; TypeArena interfaceTypes;
TypeArena internalTypes; TypeArena internalTypes;
// Summary of Ast Nodes that either contain
// user annotated anys or typechecker inferred anys
AnyTypeSummary ats{};
// Scopes and AST types refer to parse data, so we need to keep that alive // Scopes and AST types refer to parse data, so we need to keep that alive
std::shared_ptr<Allocator> allocator; std::shared_ptr<Allocator> allocator;
std::shared_ptr<AstNameTable> names; std::shared_ptr<AstNameTable> names;

View file

@ -1,10 +1,9 @@
// 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
#pragma once #pragma once
#include "Luau/DataFlowGraph.h"
#include "Luau/EqSatSimplification.h"
#include "Luau/Module.h" #include "Luau/Module.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/DataFlowGraph.h"
namespace Luau namespace Luau
{ {

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 NormalizedExternType struct NormalizedClassType
{ {
/** Has the following structure: /** Has the following structure:
* *
@ -192,7 +192,7 @@ struct NormalizedExternType
* *
* Each TypeId is a class type. * Each TypeId is a class type.
*/ */
std::unordered_map<TypeId, TypeIds> externTypes; std::unordered_map<TypeId, TypeIds> classes;
/** /**
* In order to maintain a consistent insertion order, we use this vector to * 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, extern types and the error type) // * P is a union of primitive types (including singletons, classes and the error type)
// * T is a union of table types // * 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;
NormalizedExternType externTypes; NormalizedClassType classes;
// 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 hasExternTypes() const; bool hasClasses() 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 unionExternTypesWithExternType(TypeIds& heres, TypeId there); void unionClassesWithClass(TypeIds& heres, TypeId there);
void unionExternTypes(TypeIds& heres, const TypeIds& theres); void unionClasses(TypeIds& heres, const TypeIds& theres);
void unionExternTypesWithExternType(NormalizedExternType& heres, TypeId there); void unionClassesWithClass(NormalizedClassType& heres, TypeId there);
void unionExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres); void unionClasses(NormalizedClassType& heres, const NormalizedClassType& 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 intersectExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres); void intersectClasses(NormalizedClassType& heres, const NormalizedClassType& theres);
void intersectExternTypesWithExternType(NormalizedExternType& heres, TypeId there); void intersectClassesWithClass(NormalizedClassType& 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

@ -1,68 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include <cstdint>
namespace Luau
{
enum struct Polarity : uint8_t
{
None = 0b000,
Positive = 0b001,
Negative = 0b010,
Mixed = 0b011,
Unknown = 0b100,
};
inline Polarity operator|(Polarity lhs, Polarity rhs)
{
return Polarity(uint8_t(lhs) | uint8_t(rhs));
}
inline Polarity& operator|=(Polarity& lhs, Polarity rhs)
{
lhs = lhs | rhs;
return lhs;
}
inline Polarity operator&(Polarity lhs, Polarity rhs)
{
return Polarity(uint8_t(lhs) & uint8_t(rhs));
}
inline Polarity& operator&=(Polarity& lhs, Polarity rhs)
{
lhs = lhs & rhs;
return lhs;
}
inline bool isPositive(Polarity p)
{
return bool(p & Polarity::Positive);
}
inline bool isNegative(Polarity p)
{
return bool(p & Polarity::Negative);
}
inline bool isKnown(Polarity p)
{
return p != Polarity::Unknown;
}
inline Polarity invert(Polarity p)
{
switch (p)
{
case Polarity::Positive:
return Polarity::Negative;
case Polarity::Negative:
return Polarity::Positive;
default:
return p;
}
}
} // namespace Luau

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 NormalizedExternType // TODO: This is eerily similar to the pattern that NormalizedClassType
// 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

@ -40,7 +40,7 @@ struct Scope
// All the children of this scope. // All the children of this scope.
std::vector<NotNull<Scope>> children; std::vector<NotNull<Scope>> children;
std::unordered_map<Symbol, Binding> bindings; std::unordered_map<Symbol, Binding> bindings;
TypePackId returnType = nullptr; TypePackId returnType;
std::optional<TypePackId> varargPack; std::optional<TypePackId> varargPack;
TypeLevel level; TypeLevel level;
@ -100,7 +100,6 @@ struct Scope
std::unordered_map<Name, TypePackId> typeAliasTypePackParameters; std::unordered_map<Name, TypePackId> typeAliasTypePackParameters;
std::optional<std::vector<TypeId>> interiorFreeTypes; std::optional<std::vector<TypeId>> interiorFreeTypes;
std::optional<std::vector<TypePackId>> interiorFreeTypePacks;
}; };
// Returns true iff the left scope encloses the right scope. A Scope* equal to // Returns true iff the left scope encloses the right scope. A Scope* equal to

View file

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

View file

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

View file

@ -22,7 +22,7 @@ struct InternalErrorReporter;
class TypeIds; class TypeIds;
class Normalizer; class Normalizer;
struct NormalizedExternType; struct NormalizedClassType;
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_extern_types for * See the test cyclic_tables_are_assumed_to_be_compatible_with_classes for
* details. * details.
* *
* An empty value is equivalent to a nonexistent key. * An empty value is equivalent to a nonexistent key.
@ -229,8 +229,9 @@ private:
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const TableType* superTable, NotNull<Scope> scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const 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 ExternType* subExternType, const ExternType* superExternType, NotNull<Scope> scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ExternType* subExternType, TypeId superTy, const TableType* superTable, NotNull<Scope>); SubtypingResult
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,
@ -258,11 +259,11 @@ private:
); );
SubtypingResult isCovariantWith( SubtypingResult isCovariantWith(
SubtypingEnvironment& env, SubtypingEnvironment& env,
const NormalizedExternType& subExternType, const NormalizedClassType& subClass,
const NormalizedExternType& superExternType, const NormalizedClassType& superClass,
NotNull<Scope> scope NotNull<Scope> scope
); );
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedExternType& subExternType, const TypeIds& superTables, NotNull<Scope> scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const TypeIds& superTables, NotNull<Scope> scope);
SubtypingResult isCovariantWith( SubtypingResult isCovariantWith(
SubtypingEnvironment& env, SubtypingEnvironment& env,
const NormalizedStringType& subString, const NormalizedStringType& subString,

View file

@ -44,7 +44,6 @@ 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

@ -192,6 +192,16 @@ struct TxnLog
// The pointer returned lives until `commit` or `clear` is called. // The pointer returned lives until `commit` or `clear` is called.
PendingTypePack* changeLevel(TypePackId tp, TypeLevel newLevel); PendingTypePack* changeLevel(TypePackId tp, TypeLevel newLevel);
// Queues the replacement of a type's scope with the provided scope.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingType* changeScope(TypeId ty, NotNull<Scope> scope);
// Queues the replacement of a type pack's scope with the provided scope.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingTypePack* changeScope(TypePackId tp, NotNull<Scope> scope);
// Queues a replacement of a table type with another table type with a new // Queues a replacement of a table type with another table type with a new
// indexer. // indexer.
// //

View file

@ -5,11 +5,10 @@
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/Refinement.h"
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/Polarity.h"
#include "Luau/Predicate.h" #include "Luau/Predicate.h"
#include "Luau/Refinement.h"
#include "Luau/Unifiable.h" #include "Luau/Unifiable.h"
#include "Luau/Variant.h" #include "Luau/Variant.h"
#include "Luau/VecDeque.h" #include "Luau/VecDeque.h"
@ -20,6 +19,7 @@
#include <optional> #include <optional>
#include <set> #include <set>
#include <string> #include <string>
#include <unordered_map>
#include <vector> #include <vector>
LUAU_FASTINT(LuauTableTypeMaximumStringifierLength) LUAU_FASTINT(LuauTableTypeMaximumStringifierLength)
@ -72,8 +72,12 @@ struct FreeType
// New constructors // New constructors
explicit FreeType(TypeLevel level, TypeId lowerBound, TypeId upperBound); explicit FreeType(TypeLevel level, TypeId lowerBound, TypeId upperBound);
// This one got promoted to explicit // This one got promoted to explicit
explicit FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound, Polarity polarity = Polarity::Unknown); explicit FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound);
explicit FreeType(Scope* scope, TypeLevel level, TypeId lowerBound, TypeId upperBound); explicit FreeType(Scope* scope, TypeLevel level, TypeId lowerBound, TypeId upperBound);
// Old constructors
explicit FreeType(TypeLevel level);
explicit FreeType(Scope* scope);
FreeType(Scope* scope, TypeLevel level);
int index; int index;
TypeLevel level; TypeLevel level;
@ -87,8 +91,6 @@ struct FreeType
// Only used under local type inference // Only used under local type inference
TypeId lowerBound = nullptr; TypeId lowerBound = nullptr;
TypeId upperBound = nullptr; TypeId upperBound = nullptr;
Polarity polarity = Polarity::Unknown;
}; };
struct GenericType struct GenericType
@ -97,8 +99,8 @@ struct GenericType
GenericType(); GenericType();
explicit GenericType(TypeLevel level); explicit GenericType(TypeLevel level);
explicit GenericType(const Name& name, Polarity polarity = Polarity::Unknown); explicit GenericType(const Name& name);
explicit GenericType(Scope* scope, Polarity polarity = Polarity::Unknown); explicit GenericType(Scope* scope);
GenericType(TypeLevel level, const Name& name); GenericType(TypeLevel level, const Name& name);
GenericType(Scope* scope, const Name& name); GenericType(Scope* scope, const Name& name);
@ -108,8 +110,6 @@ struct GenericType
Scope* scope = nullptr; Scope* scope = nullptr;
Name name; Name name;
bool explicitName = false; bool explicitName = false;
Polarity polarity = Polarity::Unknown;
}; };
// When an equality constraint is found, it is then "bound" to that type, // When an equality constraint is found, it is then "bound" to that type,
@ -287,7 +287,7 @@ struct MagicFunctionCallContext
{ {
NotNull<struct ConstraintSolver> solver; NotNull<struct ConstraintSolver> solver;
NotNull<const Constraint> constraint; NotNull<const Constraint> constraint;
NotNull<const AstExprCall> callSite; const class AstExprCall* callSite;
TypePackId arguments; TypePackId arguments;
TypePackId result; TypePackId result;
}; };
@ -348,8 +348,10 @@ struct FunctionType
); );
// Local monomorphic function // Local monomorphic function
FunctionType(TypeLevel level, TypePackId argTypes, TypePackId retTypes, std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
FunctionType( FunctionType(
TypeLevel level, TypeLevel level,
Scope* scope,
TypePackId argTypes, TypePackId argTypes,
TypePackId retTypes, TypePackId retTypes,
std::optional<FunctionDefinition> defn = {}, std::optional<FunctionDefinition> defn = {},
@ -366,6 +368,16 @@ struct FunctionType
std::optional<FunctionDefinition> defn = {}, std::optional<FunctionDefinition> defn = {},
bool hasSelf = false bool hasSelf = false
); );
FunctionType(
TypeLevel level,
Scope* scope,
std::vector<TypeId> generics,
std::vector<TypePackId> genericPacks,
TypePackId argTypes,
TypePackId retTypes,
std::optional<FunctionDefinition> defn = {},
bool hasSelf = false
);
std::optional<FunctionDefinition> definition; std::optional<FunctionDefinition> definition;
/// These should all be generic /// These should all be generic
@ -374,6 +386,7 @@ struct FunctionType
std::vector<std::optional<FunctionArgument>> argNames; std::vector<std::optional<FunctionArgument>> argNames;
Tags tags; Tags tags;
TypeLevel level; TypeLevel level;
Scope* scope = nullptr;
TypePackId argTypes; TypePackId argTypes;
TypePackId retTypes; TypePackId retTypes;
std::shared_ptr<MagicFunction> magic = nullptr; std::shared_ptr<MagicFunction> magic = nullptr;
@ -383,7 +396,6 @@ struct FunctionType
// this flag is used as an optimization to exit early from procedures that manipulate free or generic types. // this flag is used as an optimization to exit early from procedures that manipulate free or generic types.
bool hasNoFreeOrGenericTypes = false; bool hasNoFreeOrGenericTypes = false;
bool isCheckedFunction = false; bool isCheckedFunction = false;
bool isDeprecatedFunction = false;
}; };
enum class TableState enum class TableState
@ -460,9 +472,7 @@ struct Property
TypeId type() const; TypeId type() const;
void setType(TypeId ty); void setType(TypeId ty);
// If this property has a present `writeTy`, set it equal to the `readTy`. // Sets the write type of this property to the read type.
// This is to ensure that if we normalize a property that has divergent
// read and write types, we make them converge (for now).
void makeShared(); void makeShared();
bool isShared() const; bool isShared() const;
@ -507,6 +517,9 @@ struct TableType
std::optional<TypeId> boundTo; std::optional<TypeId> boundTo;
Tags tags; Tags tags;
// Methods of this table that have an untyped self will use the same shared self type.
std::optional<TypeId> selfTy;
// We track the number of as-yet-unadded properties to unsealed tables. // We track the number of as-yet-unadded properties to unsealed tables.
// Some constraints will use this information to decide whether or not they // Some constraints will use this information to decide whether or not they
// are able to dispatch. // are able to dispatch.
@ -532,15 +545,15 @@ struct ClassUserData
virtual ~ClassUserData() {} virtual ~ClassUserData() {}
}; };
/** The type of an external userdata exposed to Luau. /** The type of a class.
* *
* Extern types behave like tables in many ways, but there are some important differences: * Classes behave like tables in many ways, but there are some important differences:
* *
* The properties of a class are always exactly known. * The properties of a class are always exactly known.
* Extern types optionally have a parent type. * Classes optionally have a parent class.
* Two different extern types that share the same properties are nevertheless distinct and mutually incompatible. * Two different classes that share the same properties are nevertheless distinct and mutually incompatible.
*/ */
struct ExternType struct ClassType
{ {
using Props = TableType::Props; using Props = TableType::Props;
@ -554,7 +567,7 @@ struct ExternType
std::optional<Location> definitionLocation; std::optional<Location> definitionLocation;
std::optional<TableIndexer> indexer; std::optional<TableIndexer> indexer;
ExternType( ClassType(
Name name, Name name,
Props props, Props props,
std::optional<TypeId> parent, std::optional<TypeId> parent,
@ -575,7 +588,7 @@ struct ExternType
{ {
} }
ExternType( ClassType(
Name name, Name name,
Props props, Props props,
std::optional<TypeId> parent, std::optional<TypeId> parent,
@ -775,7 +788,7 @@ using TypeVariant = Unifiable::Variant<
FunctionType, FunctionType,
TableType, TableType,
MetatableType, MetatableType,
ExternType, ClassType,
AnyType, AnyType,
UnionType, UnionType,
IntersectionType, IntersectionType,
@ -868,9 +881,6 @@ struct TypeFun
*/ */
TypeId type; TypeId type;
// The location of where this TypeFun was defined, if available
std::optional<Location> definitionLocation;
TypeFun() = default; TypeFun() = default;
explicit TypeFun(TypeId ty) explicit TypeFun(TypeId ty)
@ -878,23 +888,16 @@ struct TypeFun
{ {
} }
TypeFun(std::vector<GenericTypeDefinition> typeParams, TypeId type, std::optional<Location> definitionLocation = std::nullopt) TypeFun(std::vector<GenericTypeDefinition> typeParams, TypeId type)
: typeParams(std::move(typeParams)) : typeParams(std::move(typeParams))
, type(type) , type(type)
, definitionLocation(definitionLocation)
{ {
} }
TypeFun( TypeFun(std::vector<GenericTypeDefinition> typeParams, std::vector<GenericTypePackDefinition> typePackParams, TypeId type)
std::vector<GenericTypeDefinition> typeParams,
std::vector<GenericTypePackDefinition> typePackParams,
TypeId type,
std::optional<Location> definitionLocation = std::nullopt
)
: typeParams(std::move(typeParams)) : typeParams(std::move(typeParams))
, typePackParams(std::move(typePackParams)) , typePackParams(std::move(typePackParams))
, type(type) , type(type)
, definitionLocation(definitionLocation)
{ {
} }
@ -986,7 +989,7 @@ public:
const TypeId threadType; const TypeId threadType;
const TypeId bufferType; const TypeId bufferType;
const TypeId functionType; const TypeId functionType;
const TypeId externType; const TypeId classType;
const TypeId tableType; const TypeId tableType;
const TypeId emptyTableType; const TypeId emptyTableType;
const TypeId trueType; const TypeId trueType;
@ -998,7 +1001,6 @@ 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;
@ -1019,10 +1021,10 @@ TypeLevel* getMutableLevel(TypeId ty);
std::optional<TypeLevel> getLevel(TypePackId tp); std::optional<TypeLevel> getLevel(TypePackId tp);
const Property* lookupExternTypeProp(const ExternType* cls, const Name& name); const Property* lookupClassProp(const ClassType* cls, const Name& name);
// Whether `cls` is a subclass of `parent` // Whether `cls` is a subclass of `parent`
bool isSubclass(const ExternType* cls, const ExternType* parent); bool isSubclass(const ClassType* cls, const ClassType* parent);
Type* asMutable(TypeId ty); Type* asMutable(TypeId ty);
@ -1199,7 +1201,7 @@ private:
} }
}; };
TypeId freshType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, Scope* scope, Polarity polarity = Polarity::Unknown); TypeId freshType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, Scope* scope);
using TypeIdPredicate = std::function<std::optional<TypeId>(TypeId)>; using TypeIdPredicate = std::function<std::optional<TypeId>(TypeId)>;
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate); std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate);

View file

@ -1,7 +1,6 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once #pragma once
#include "Luau/Polarity.h"
#include "Luau/TypedAllocator.h" #include "Luau/TypedAllocator.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
@ -37,7 +36,11 @@ struct TypeArena
TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope); TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope);
TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope, TypeLevel level); TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope, TypeLevel level);
TypePackId freshTypePack(Scope* scope, Polarity polarity = Polarity::Unknown); TypeId freshType_DEPRECATED(TypeLevel level);
TypeId freshType_DEPRECATED(Scope* scope);
TypeId freshType_DEPRECATED(Scope* scope, TypeLevel level);
TypePackId freshTypePack(Scope* scope);
TypePackId addTypePack(std::initializer_list<TypeId> types); TypePackId addTypePack(std::initializer_list<TypeId> types);
TypePackId addTypePack(std::vector<TypeId> types, std::optional<TypePackId> tail = {}); TypePackId addTypePack(std::vector<TypeId> types, std::optional<TypePackId> tail = {});

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 expandExternTypeProps = false; bool expandClassProps = 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(AstStatDeclareExternType* stat); void visit(AstStatDeclareClass* 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

@ -155,9 +155,6 @@ struct TypeFunction
/// The reducer function for the type function. /// The reducer function for the type function.
ReducerFunction<TypeId> reducer; ReducerFunction<TypeId> reducer;
/// If true, this type function can reduce even if it is parameterized on a generic.
bool canReduceGenerics = false;
}; };
/// Represents a type function that may be applied to map a series of types and /// Represents a type function that may be applied to map a series of types and
@ -170,9 +167,6 @@ struct TypePackFunction
/// The reducer function for the type pack function. /// The reducer function for the type pack function.
ReducerFunction<TypePackId> reducer; ReducerFunction<TypePackId> reducer;
/// If true, this type function can reduce even if it is parameterized on a generic.
bool canReduceGenerics = false;
}; };
struct FunctionGraphReductionResult struct FunctionGraphReductionResult
@ -183,7 +177,6 @@ struct FunctionGraphReductionResult
DenseHashSet<TypePackId> blockedPacks{nullptr}; DenseHashSet<TypePackId> blockedPacks{nullptr};
DenseHashSet<TypeId> reducedTypes{nullptr}; DenseHashSet<TypeId> reducedTypes{nullptr};
DenseHashSet<TypePackId> reducedPacks{nullptr}; DenseHashSet<TypePackId> reducedPacks{nullptr};
DenseHashSet<TypeId> irreducibleTypes{nullptr};
}; };
/** /**
@ -254,8 +247,6 @@ struct BuiltinTypeFunctions
TypeFunction setmetatableFunc; TypeFunction setmetatableFunc;
TypeFunction getmetatableFunc; TypeFunction getmetatableFunc;
TypeFunction weakoptionalFunc;
void addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const; void addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const;
}; };

View file

@ -205,7 +205,7 @@ struct TypeFunctionTableType
std::optional<TypeFunctionTypeId> metatable; std::optional<TypeFunctionTypeId> metatable;
}; };
struct TypeFunctionExternType struct TypeFunctionClassType
{ {
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 TypeFunctionExternType
std::optional<TypeFunctionTypeId> readParent; std::optional<TypeFunctionTypeId> readParent;
std::optional<TypeFunctionTypeId> writeParent; std::optional<TypeFunctionTypeId> writeParent;
TypeId externTy; TypeId classTy;
}; };
struct TypeFunctionGenericType struct TypeFunctionGenericType
@ -244,7 +244,7 @@ using TypeFunctionTypeVariant = Luau::Variant<
TypeFunctionNegationType, TypeFunctionNegationType,
TypeFunctionFunctionType, TypeFunctionFunctionType,
TypeFunctionTableType, TypeFunctionTableType,
TypeFunctionExternType, TypeFunctionClassType,
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 ExternType; struct ClassType;
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 AstStatDeclareExternType& declaredExternType); ControlFlow check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass);
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 AstStatDeclareExternType& declaredExternType); void prototype(const ScopePtr& scope, const AstStatDeclareClass& declaredClass);
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);
@ -130,7 +130,6 @@ struct TypeChecker
const PredicateVec& predicates = {} const PredicateVec& predicates = {}
); );
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprBinary& expr, std::optional<TypeId> expectedType = std::nullopt); WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprBinary& expr, std::optional<TypeId> expectedType = std::nullopt);
WithPredicate<TypeId> checkExpr_DEPRECATED(const ScopePtr& scope, const AstExprBinary& expr, std::optional<TypeId> expectedType = std::nullopt);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprTypeAssertion& expr); WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprTypeAssertion& expr);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprError& expr); WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprError& expr);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprIfElse& expr, std::optional<TypeId> expectedType = std::nullopt); WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprIfElse& expr, std::optional<TypeId> expectedType = std::nullopt);
@ -487,7 +486,7 @@ private:
/** /**
* A set of incorrect class definitions which is used to avoid a second-pass analysis. * A set of incorrect class definitions which is used to avoid a second-pass analysis.
*/ */
DenseHashSet<const AstStatDeclareExternType*> incorrectExternTypeDefinitions{nullptr}; DenseHashSet<const AstStatDeclareClass*> incorrectClassDefinitions{nullptr};
std::vector<std::pair<TypeId, ScopePtr>> deferredQuantification; std::vector<std::pair<TypeId, ScopePtr>> deferredQuantification;
}; };

View file

@ -1,12 +1,11 @@
// 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
#pragma once #pragma once
#include "Luau/Common.h"
#include "Luau/NotNull.h"
#include "Luau/Polarity.h"
#include "Luau/TypeFwd.h"
#include "Luau/Unifiable.h" #include "Luau/Unifiable.h"
#include "Luau/Variant.h" #include "Luau/Variant.h"
#include "Luau/TypeFwd.h"
#include "Luau/NotNull.h"
#include "Luau/Common.h"
#include <optional> #include <optional>
#include <set> #include <set>
@ -27,14 +26,12 @@ struct TypeFunctionInstanceTypePack;
struct FreeTypePack struct FreeTypePack
{ {
explicit FreeTypePack(TypeLevel level); explicit FreeTypePack(TypeLevel level);
explicit FreeTypePack(Scope* scope, Polarity polarity = Polarity::Unknown); explicit FreeTypePack(Scope* scope);
FreeTypePack(Scope* scope, TypeLevel level); FreeTypePack(Scope* scope, TypeLevel level);
int index; int index;
TypeLevel level; TypeLevel level;
Scope* scope = nullptr; Scope* scope = nullptr;
Polarity polarity = Polarity::Unknown;
}; };
struct GenericTypePack struct GenericTypePack
@ -43,7 +40,7 @@ struct GenericTypePack
GenericTypePack(); GenericTypePack();
explicit GenericTypePack(TypeLevel level); explicit GenericTypePack(TypeLevel level);
explicit GenericTypePack(const Name& name); explicit GenericTypePack(const Name& name);
explicit GenericTypePack(Scope* scope, Polarity polarity = Polarity::Unknown); explicit GenericTypePack(Scope* scope);
GenericTypePack(TypeLevel level, const Name& name); GenericTypePack(TypeLevel level, const Name& name);
GenericTypePack(Scope* scope, const Name& name); GenericTypePack(Scope* scope, const Name& name);
@ -52,8 +49,6 @@ struct GenericTypePack
Scope* scope = nullptr; Scope* scope = nullptr;
Name name; Name name;
bool explicitName = false; bool explicitName = false;
Polarity polarity = Polarity::Unknown;
}; };
using BoundTypePack = Unifiable::Bound<TypePackId>; using BoundTypePack = Unifiable::Bound<TypePackId>;
@ -105,9 +100,9 @@ struct TypeFunctionInstanceTypePack
struct TypePackVar struct TypePackVar
{ {
explicit TypePackVar(const TypePackVariant& tp); explicit TypePackVar(const TypePackVariant& ty);
explicit TypePackVar(TypePackVariant&& tp); explicit TypePackVar(TypePackVariant&& ty);
TypePackVar(TypePackVariant&& tp, bool persistent); TypePackVar(TypePackVariant&& ty, bool persistent);
bool operator==(const TypePackVar& rhs) const; bool operator==(const TypePackVar& rhs) const;
@ -174,7 +169,6 @@ struct TypePackIterator
private: private:
TypePackId currentTypePack = nullptr; TypePackId currentTypePack = nullptr;
TypePackId tailCycleCheck = nullptr;
const TypePack* tp = nullptr; const TypePack* tp = nullptr;
size_t currentIndex = 0; size_t currentIndex = 0;
@ -185,8 +179,6 @@ TypePackIterator begin(TypePackId tp);
TypePackIterator begin(TypePackId tp, const TxnLog* log); TypePackIterator begin(TypePackId tp, const TxnLog* log);
TypePackIterator end(TypePackId tp); TypePackIterator end(TypePackId tp);
TypePackId getTail(TypePackId tp);
using SeenSet = std::set<std::pair<const void*, const void*>>; using SeenSet = std::set<std::pair<const void*, const void*>>;
bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs); bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs);

View file

@ -289,6 +289,4 @@ std::vector<TypeId> findBlockedArgTypesIn(AstExprCall* expr, NotNull<DenseHashMa
*/ */
void trackInteriorFreeType(Scope* scope, TypeId ty); void trackInteriorFreeType(Scope* scope, TypeId ty);
void trackInteriorFreeTypePack(Scope* scope, TypePackId tp);
} // namespace Luau } // namespace Luau

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 tryUnifyWithExternType(TypeId subTy, TypeId superTy, bool reversed); void tryUnifyWithClass(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

@ -44,12 +44,6 @@ struct Unifier2
// Mapping from generic type packs to `TypePack`s of free types to be used in instantiation. // Mapping from generic type packs to `TypePack`s of free types to be used in instantiation.
DenseHashMap<TypePackId, TypePackId> genericPackSubstitutions{nullptr}; DenseHashMap<TypePackId, TypePackId> genericPackSubstitutions{nullptr};
// Unification sometimes results in the creation of new free types.
// We collect them here so that other systems can perform necessary
// bookkeeping.
std::vector<TypeId> newFreshTypes;
std::vector<TypePackId> newFreshTypePacks;
int recursionCount = 0; int recursionCount = 0;
int recursionLimit = 0; int recursionLimit = 0;
@ -119,9 +113,6 @@ private:
// Returns true if needle occurs within haystack already. ie if we bound // Returns true if needle occurs within haystack already. ie if we bound
// needle to haystack, would a cyclic TypePack result? // needle to haystack, would a cyclic TypePack result?
OccursCheckResult occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack); OccursCheckResult occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack);
TypeId freshType(NotNull<Scope> scope, Polarity polarity);
TypePackId freshTypePack(NotNull<Scope> scope, Polarity polarity);
}; };
} // namespace Luau } // namespace Luau

View file

@ -126,7 +126,7 @@ struct GenericTypeVisitor
{ {
return visit(ty); return visit(ty);
} }
virtual bool visit(TypeId ty, const ExternType& etv) virtual bool visit(TypeId ty, const ClassType& ctv)
{ {
return visit(ty); return visit(ty);
} }
@ -313,11 +313,11 @@ struct GenericTypeVisitor
traverse(mtv->metatable); traverse(mtv->metatable);
} }
} }
else if (auto etv = get<ExternType>(ty)) else if (auto ctv = get<ClassType>(ty))
{ {
if (visit(ty, *etv)) if (visit(ty, *ctv))
{ {
for (const auto& [name, prop] : etv->props) for (const auto& [name, prop] : ctv->props)
{ {
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
@ -335,16 +335,16 @@ struct GenericTypeVisitor
traverse(prop.type()); traverse(prop.type());
} }
if (etv->parent) if (ctv->parent)
traverse(*etv->parent); traverse(*ctv->parent);
if (etv->metatable) if (ctv->metatable)
traverse(*etv->metatable); traverse(*ctv->metatable);
if (etv->indexer) if (ctv->indexer)
{ {
traverse(etv->indexer->indexType); traverse(ctv->indexer->indexType);
traverse(etv->indexer->indexResultType); traverse(ctv->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 ExternType // Asserting also makes no sense, because the type _will_ happen here, most likely as a property of some ClassType
// that doesn't need to be expanded. // that doesn't need to be expanded.
} }
else if (auto stv = get<SingletonType>(ty)) else if (auto stv = get<SingletonType>(ty))

View file

@ -0,0 +1,902 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/AnyTypeSummary.h"
#include "Luau/BuiltinDefinitions.h"
#include "Luau/Clone.h"
#include "Luau/Common.h"
#include "Luau/Config.h"
#include "Luau/ConstraintGenerator.h"
#include "Luau/ConstraintSolver.h"
#include "Luau/DataFlowGraph.h"
#include "Luau/DcrLogger.h"
#include "Luau/Module.h"
#include "Luau/Parser.h"
#include "Luau/Scope.h"
#include "Luau/StringUtils.h"
#include "Luau/TimeTrace.h"
#include "Luau/ToString.h"
#include "Luau/Transpiler.h"
#include "Luau/TypeArena.h"
#include "Luau/TypeChecker2.h"
#include "Luau/NonStrictTypeChecker.h"
#include "Luau/TypeInfer.h"
#include "Luau/Variant.h"
#include "Luau/VisitType.h"
#include "Luau/TypePack.h"
#include "Luau/TypeOrPack.h"
#include <algorithm>
#include <memory>
#include <chrono>
#include <condition_variable>
#include <exception>
#include <mutex>
#include <stdexcept>
#include <string>
#include <iostream>
#include <stdio.h>
LUAU_FASTFLAGVARIABLE(StudioReportLuauAny2);
LUAU_FASTINTVARIABLE(LuauAnySummaryRecursionLimit, 300);
LUAU_FASTFLAG(DebugLuauMagicTypes);
namespace Luau
{
void AnyTypeSummary::traverse(const Module* module, AstStat* src, NotNull<BuiltinTypes> builtinTypes)
{
visit(findInnerMostScope(src->location, module), src, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStat* stat, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
RecursionLimiter limiter{&recursionCount, FInt::LuauAnySummaryRecursionLimit};
if (auto s = stat->as<AstStatBlock>())
return visit(scope, s, module, builtinTypes);
else if (auto i = stat->as<AstStatIf>())
return visit(scope, i, module, builtinTypes);
else if (auto s = stat->as<AstStatWhile>())
return visit(scope, s, module, builtinTypes);
else if (auto s = stat->as<AstStatRepeat>())
return visit(scope, s, module, builtinTypes);
else if (auto r = stat->as<AstStatReturn>())
return visit(scope, r, module, builtinTypes);
else if (auto e = stat->as<AstStatExpr>())
return visit(scope, e, module, builtinTypes);
else if (auto s = stat->as<AstStatLocal>())
return visit(scope, s, module, builtinTypes);
else if (auto s = stat->as<AstStatFor>())
return visit(scope, s, module, builtinTypes);
else if (auto s = stat->as<AstStatForIn>())
return visit(scope, s, module, builtinTypes);
else if (auto a = stat->as<AstStatAssign>())
return visit(scope, a, module, builtinTypes);
else if (auto a = stat->as<AstStatCompoundAssign>())
return visit(scope, a, module, builtinTypes);
else if (auto f = stat->as<AstStatFunction>())
return visit(scope, f, module, builtinTypes);
else if (auto f = stat->as<AstStatLocalFunction>())
return visit(scope, f, module, builtinTypes);
else if (auto a = stat->as<AstStatTypeAlias>())
return visit(scope, a, module, builtinTypes);
else if (auto s = stat->as<AstStatDeclareGlobal>())
return visit(scope, s, module, builtinTypes);
else if (auto s = stat->as<AstStatDeclareFunction>())
return visit(scope, s, module, builtinTypes);
else if (auto s = stat->as<AstStatDeclareClass>())
return visit(scope, s, module, builtinTypes);
else if (auto s = stat->as<AstStatError>())
return visit(scope, s, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStatBlock* block, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
RecursionCounter counter{&recursionCount};
if (recursionCount >= FInt::LuauAnySummaryRecursionLimit)
return; // don't report
for (AstStat* stat : block->body)
visit(scope, stat, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStatIf* ifStatement, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (ifStatement->thenbody)
{
const Scope* thenScope = findInnerMostScope(ifStatement->thenbody->location, module);
visit(thenScope, ifStatement->thenbody, module, builtinTypes);
}
if (ifStatement->elsebody)
{
const Scope* elseScope = findInnerMostScope(ifStatement->elsebody->location, module);
visit(elseScope, ifStatement->elsebody, module, builtinTypes);
}
}
void AnyTypeSummary::visit(const Scope* scope, AstStatWhile* while_, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
const Scope* whileScope = findInnerMostScope(while_->location, module);
visit(whileScope, while_->body, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStatRepeat* repeat, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
const Scope* repeatScope = findInnerMostScope(repeat->location, module);
visit(repeatScope, repeat->body, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStatReturn* ret, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
const Scope* retScope = findInnerMostScope(ret->location, module);
auto ctxNode = getNode(rootSrc, ret);
bool seenTP = false;
for (auto val : ret->list)
{
if (isAnyCall(retScope, val, module, builtinTypes))
{
TelemetryTypePair types;
types.inferredType = toString(lookupType(val, module, builtinTypes));
TypeInfo ti{Pattern::FuncApp, toString(ctxNode), types};
typeInfo.push_back(ti);
}
if (isAnyCast(retScope, val, module, builtinTypes))
{
if (auto cast = val->as<AstExprTypeAssertion>())
{
TelemetryTypePair types;
types.annotatedType = toString(lookupAnnotation(cast->annotation, module, builtinTypes));
types.inferredType = toString(lookupType(cast->expr, module, builtinTypes));
TypeInfo ti{Pattern::Casts, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
if (ret->list.size > 1 && !seenTP)
{
if (containsAny(retScope->returnType))
{
seenTP = true;
TelemetryTypePair types;
types.inferredType = toString(retScope->returnType);
TypeInfo ti{Pattern::TypePk, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
}
}
void AnyTypeSummary::visit(const Scope* scope, AstStatLocal* local, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
auto ctxNode = getNode(rootSrc, local);
TypePackId values = reconstructTypePack(local->values, module, builtinTypes);
auto [head, tail] = flatten(values);
size_t posn = 0;
for (AstLocal* loc : local->vars)
{
if (local->vars.data[0] == loc && posn < local->values.size)
{
if (loc->annotation)
{
auto annot = lookupAnnotation(loc->annotation, module, builtinTypes);
if (containsAny(annot))
{
TelemetryTypePair types;
types.annotatedType = toString(annot);
types.inferredType = toString(lookupType(local->values.data[posn], module, builtinTypes));
TypeInfo ti{Pattern::VarAnnot, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
const AstExprTypeAssertion* maybeRequire = local->values.data[posn]->as<AstExprTypeAssertion>();
if (!maybeRequire)
continue;
if (std::min(local->values.size - 1, posn) < head.size())
{
if (isAnyCast(scope, local->values.data[posn], module, builtinTypes))
{
TelemetryTypePair types;
types.inferredType = toString(head[std::min(local->values.size - 1, posn)]);
TypeInfo ti{Pattern::Casts, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
}
else
{
if (std::min(local->values.size - 1, posn) < head.size())
{
if (loc->annotation)
{
auto annot = lookupAnnotation(loc->annotation, module, builtinTypes);
if (containsAny(annot))
{
TelemetryTypePair types;
types.annotatedType = toString(annot);
types.inferredType = toString(head[std::min(local->values.size - 1, posn)]);
TypeInfo ti{Pattern::VarAnnot, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
}
else
{
if (tail)
{
if (containsAny(*tail))
{
TelemetryTypePair types;
types.inferredType = toString(*tail);
TypeInfo ti{Pattern::VarAny, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
}
}
++posn;
}
}
void AnyTypeSummary::visit(const Scope* scope, AstStatFor* for_, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
const Scope* forScope = findInnerMostScope(for_->location, module);
visit(forScope, for_->body, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStatForIn* forIn, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
const Scope* loopScope = findInnerMostScope(forIn->location, module);
visit(loopScope, forIn->body, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStatAssign* assign, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
auto ctxNode = getNode(rootSrc, assign);
TypePackId values = reconstructTypePack(assign->values, module, builtinTypes);
auto [head, tail] = flatten(values);
size_t posn = 0;
for (AstExpr* var : assign->vars)
{
TypeId tp = lookupType(var, module, builtinTypes);
if (containsAny(tp))
{
TelemetryTypePair types;
types.annotatedType = toString(tp);
auto loc = std::min(assign->vars.size - 1, posn);
if (head.size() >= assign->vars.size && posn < head.size())
{
types.inferredType = toString(head[posn]);
}
else if (loc < head.size())
types.inferredType = toString(head[loc]);
else
types.inferredType = toString(builtinTypes->nilType);
TypeInfo ti{Pattern::Assign, toString(ctxNode), types};
typeInfo.push_back(ti);
}
++posn;
}
for (AstExpr* val : assign->values)
{
if (isAnyCall(scope, val, module, builtinTypes))
{
TelemetryTypePair types;
types.inferredType = toString(lookupType(val, module, builtinTypes));
TypeInfo ti{Pattern::FuncApp, toString(ctxNode), types};
typeInfo.push_back(ti);
}
if (isAnyCast(scope, val, module, builtinTypes))
{
if (auto cast = val->as<AstExprTypeAssertion>())
{
TelemetryTypePair types;
types.annotatedType = toString(lookupAnnotation(cast->annotation, module, builtinTypes));
types.inferredType = toString(lookupType(val, module, builtinTypes));
TypeInfo ti{Pattern::Casts, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
}
if (tail)
{
if (containsAny(*tail))
{
TelemetryTypePair types;
types.inferredType = toString(*tail);
TypeInfo ti{Pattern::Assign, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
}
void AnyTypeSummary::visit(const Scope* scope, AstStatCompoundAssign* assign, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
auto ctxNode = getNode(rootSrc, assign);
TelemetryTypePair types;
types.inferredType = toString(lookupType(assign->value, module, builtinTypes));
types.annotatedType = toString(lookupType(assign->var, module, builtinTypes));
if (module->astTypes.contains(assign->var))
{
if (containsAny(*module->astTypes.find(assign->var)))
{
TypeInfo ti{Pattern::Assign, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
else if (module->astTypePacks.contains(assign->var))
{
if (containsAny(*module->astTypePacks.find(assign->var)))
{
TypeInfo ti{Pattern::Assign, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
if (isAnyCall(scope, assign->value, module, builtinTypes))
{
TypeInfo ti{Pattern::FuncApp, toString(ctxNode), types};
typeInfo.push_back(ti);
}
if (isAnyCast(scope, assign->value, module, builtinTypes))
{
if (auto cast = assign->value->as<AstExprTypeAssertion>())
{
types.annotatedType = toString(lookupAnnotation(cast->annotation, module, builtinTypes));
types.inferredType = toString(lookupType(cast->expr, module, builtinTypes));
TypeInfo ti{Pattern::Casts, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
}
void AnyTypeSummary::visit(const Scope* scope, AstStatFunction* function, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
TelemetryTypePair types;
types.inferredType = toString(lookupType(function->func, module, builtinTypes));
if (hasVariadicAnys(scope, function->func, module, builtinTypes))
{
TypeInfo ti{Pattern::VarAny, toString(function), types};
typeInfo.push_back(ti);
}
if (hasArgAnys(scope, function->func, module, builtinTypes))
{
TypeInfo ti{Pattern::FuncArg, toString(function), types};
typeInfo.push_back(ti);
}
if (hasAnyReturns(scope, function->func, module, builtinTypes))
{
TypeInfo ti{Pattern::FuncRet, toString(function), types};
typeInfo.push_back(ti);
}
if (function->func->body->body.size > 0)
visit(scope, function->func->body, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStatLocalFunction* function, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
TelemetryTypePair types;
if (hasVariadicAnys(scope, function->func, module, builtinTypes))
{
types.inferredType = toString(lookupType(function->func, module, builtinTypes));
TypeInfo ti{Pattern::VarAny, toString(function), types};
typeInfo.push_back(ti);
}
if (hasArgAnys(scope, function->func, module, builtinTypes))
{
types.inferredType = toString(lookupType(function->func, module, builtinTypes));
TypeInfo ti{Pattern::FuncArg, toString(function), types};
typeInfo.push_back(ti);
}
if (hasAnyReturns(scope, function->func, module, builtinTypes))
{
types.inferredType = toString(lookupType(function->func, module, builtinTypes));
TypeInfo ti{Pattern::FuncRet, toString(function), types};
typeInfo.push_back(ti);
}
if (function->func->body->body.size > 0)
visit(scope, function->func->body, module, builtinTypes);
}
void AnyTypeSummary::visit(const Scope* scope, AstStatTypeAlias* alias, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
auto ctxNode = getNode(rootSrc, alias);
auto annot = lookupAnnotation(alias->type, module, builtinTypes);
if (containsAny(annot))
{
// no expr => no inference for aliases
TelemetryTypePair types;
types.annotatedType = toString(annot);
TypeInfo ti{Pattern::Alias, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
void AnyTypeSummary::visit(const Scope* scope, AstStatExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
auto ctxNode = getNode(rootSrc, expr);
if (isAnyCall(scope, expr->expr, module, builtinTypes))
{
TelemetryTypePair types;
types.inferredType = toString(lookupType(expr->expr, module, builtinTypes));
TypeInfo ti{Pattern::FuncApp, toString(ctxNode), types};
typeInfo.push_back(ti);
}
}
void AnyTypeSummary::visit(const Scope* scope, AstStatDeclareGlobal* declareGlobal, const Module* module, NotNull<BuiltinTypes> builtinTypes) {}
void AnyTypeSummary::visit(const Scope* scope, AstStatDeclareClass* declareClass, const Module* module, NotNull<BuiltinTypes> builtinTypes) {}
void AnyTypeSummary::visit(const Scope* scope, AstStatDeclareFunction* declareFunction, const Module* module, NotNull<BuiltinTypes> builtinTypes) {}
void AnyTypeSummary::visit(const Scope* scope, AstStatError* error, const Module* module, NotNull<BuiltinTypes> builtinTypes) {}
TypeId AnyTypeSummary::checkForFamilyInhabitance(const TypeId instance, const Location location)
{
if (seenTypeFamilyInstances.find(instance))
return instance;
seenTypeFamilyInstances.insert(instance);
return instance;
}
TypeId AnyTypeSummary::lookupType(const AstExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
const TypeId* ty = module->astTypes.find(expr);
if (ty)
return checkForFamilyInhabitance(follow(*ty), expr->location);
const TypePackId* tp = module->astTypePacks.find(expr);
if (tp)
{
if (auto fst = first(*tp, /*ignoreHiddenVariadics*/ false))
return checkForFamilyInhabitance(*fst, expr->location);
else if (finite(*tp) && size(*tp) == 0)
return checkForFamilyInhabitance(builtinTypes->nilType, expr->location);
}
return builtinTypes->errorRecoveryType();
}
TypePackId AnyTypeSummary::reconstructTypePack(AstArray<AstExpr*> exprs, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (exprs.size == 0)
return arena.addTypePack(TypePack{{}, std::nullopt});
std::vector<TypeId> head;
for (size_t i = 0; i < exprs.size - 1; ++i)
{
head.push_back(lookupType(exprs.data[i], module, builtinTypes));
}
const TypePackId* tail = module->astTypePacks.find(exprs.data[exprs.size - 1]);
if (tail)
return arena.addTypePack(TypePack{std::move(head), follow(*tail)});
else
return arena.addTypePack(TypePack{std::move(head), builtinTypes->errorRecoveryTypePack()});
}
bool AnyTypeSummary::isAnyCall(const Scope* scope, AstExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (auto call = expr->as<AstExprCall>())
{
TypePackId args = reconstructTypePack(call->args, module, builtinTypes);
if (containsAny(args))
return true;
TypeId func = lookupType(call->func, module, builtinTypes);
if (containsAny(func))
return true;
}
return false;
}
bool AnyTypeSummary::hasVariadicAnys(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (expr->vararg && expr->varargAnnotation)
{
auto annot = lookupPackAnnotation(expr->varargAnnotation, module);
if (annot && containsAny(*annot))
{
return true;
}
}
return false;
}
bool AnyTypeSummary::hasArgAnys(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (expr->args.size > 0)
{
for (const AstLocal* arg : expr->args)
{
if (arg->annotation)
{
auto annot = lookupAnnotation(arg->annotation, module, builtinTypes);
if (containsAny(annot))
{
return true;
}
}
}
}
return false;
}
bool AnyTypeSummary::hasAnyReturns(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (!expr->returnAnnotation)
{
return false;
}
for (AstType* ret : expr->returnAnnotation->types)
{
if (containsAny(lookupAnnotation(ret, module, builtinTypes)))
{
return true;
}
}
if (expr->returnAnnotation->tailType)
{
auto annot = lookupPackAnnotation(expr->returnAnnotation->tailType, module);
if (annot && containsAny(*annot))
{
return true;
}
}
return false;
}
bool AnyTypeSummary::isAnyCast(const Scope* scope, AstExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (auto cast = expr->as<AstExprTypeAssertion>())
{
auto annot = lookupAnnotation(cast->annotation, module, builtinTypes);
if (containsAny(annot))
{
return true;
}
}
return false;
}
TypeId AnyTypeSummary::lookupAnnotation(AstType* annotation, const Module* module, NotNull<BuiltinTypes> builtintypes)
{
if (FFlag::DebugLuauMagicTypes)
{
if (auto ref = annotation->as<AstTypeReference>(); ref && ref->parameters.size > 0)
{
if (auto ann = ref->parameters.data[0].type)
{
TypeId argTy = lookupAnnotation(ref->parameters.data[0].type, module, builtintypes);
return follow(argTy);
}
}
}
const TypeId* ty = module->astResolvedTypes.find(annotation);
if (ty)
return checkForTypeFunctionInhabitance(follow(*ty), annotation->location);
else
return checkForTypeFunctionInhabitance(builtintypes->errorRecoveryType(), annotation->location);
}
TypeId AnyTypeSummary::checkForTypeFunctionInhabitance(const TypeId instance, const Location location)
{
if (seenTypeFunctionInstances.find(instance))
return instance;
seenTypeFunctionInstances.insert(instance);
return instance;
}
std::optional<TypePackId> AnyTypeSummary::lookupPackAnnotation(AstTypePack* annotation, const Module* module)
{
const TypePackId* tp = module->astResolvedTypePacks.find(annotation);
if (tp != nullptr)
return {follow(*tp)};
return {};
}
bool AnyTypeSummary::containsAny(TypeId typ)
{
typ = follow(typ);
if (auto t = seen.find(typ); t && !*t)
{
return false;
}
seen[typ] = false;
RecursionCounter counter{&recursionCount};
if (recursionCount >= FInt::LuauAnySummaryRecursionLimit)
{
return false;
}
bool found = false;
if (auto ty = get<AnyType>(typ))
{
found = true;
}
else if (auto ty = get<UnknownType>(typ))
{
found = true;
}
else if (auto ty = get<TableType>(typ))
{
for (auto& [_name, prop] : ty->props)
{
if (FFlag::LuauSolverV2)
{
if (auto newT = follow(prop.readTy))
{
if (containsAny(*newT))
found = true;
}
else if (auto newT = follow(prop.writeTy))
{
if (containsAny(*newT))
found = true;
}
}
else
{
if (containsAny(prop.type()))
found = true;
}
}
}
else if (auto ty = get<IntersectionType>(typ))
{
for (auto part : ty->parts)
{
if (containsAny(part))
{
found = true;
}
}
}
else if (auto ty = get<UnionType>(typ))
{
for (auto option : ty->options)
{
if (containsAny(option))
{
found = true;
}
}
}
else if (auto ty = get<FunctionType>(typ))
{
if (containsAny(ty->argTypes))
found = true;
else if (containsAny(ty->retTypes))
found = true;
}
seen[typ] = found;
return found;
}
bool AnyTypeSummary::containsAny(TypePackId typ)
{
typ = follow(typ);
if (auto t = seen.find(typ); t && !*t)
{
return false;
}
seen[typ] = false;
auto [head, tail] = flatten(typ);
bool found = false;
for (auto tp : head)
{
if (containsAny(tp))
found = true;
}
if (tail)
{
if (auto vtp = get<VariadicTypePack>(tail))
{
if (auto ty = get<AnyType>(follow(vtp->ty)))
{
found = true;
}
}
else if (auto tftp = get<TypeFunctionInstanceTypePack>(tail))
{
for (TypePackId tp : tftp->packArguments)
{
if (containsAny(tp))
{
found = true;
}
}
for (TypeId t : tftp->typeArguments)
{
if (containsAny(t))
{
found = true;
}
}
}
}
seen[typ] = found;
return found;
}
const Scope* AnyTypeSummary::findInnerMostScope(const Location location, const Module* module)
{
const Scope* bestScope = module->getModuleScope().get();
bool didNarrow = false;
do
{
didNarrow = false;
for (auto scope : bestScope->children)
{
if (scope->location.encloses(location))
{
bestScope = scope.get();
didNarrow = true;
break;
}
}
} while (didNarrow && bestScope->children.size() > 0);
return bestScope;
}
std::optional<AstExpr*> AnyTypeSummary::matchRequire(const AstExprCall& call)
{
const char* require = "require";
if (call.args.size != 1)
return std::nullopt;
const AstExprGlobal* funcAsGlobal = call.func->as<AstExprGlobal>();
if (!funcAsGlobal || funcAsGlobal->name != require)
return std::nullopt;
if (call.args.size != 1)
return std::nullopt;
return call.args.data[0];
}
AstNode* AnyTypeSummary::getNode(AstStatBlock* root, AstNode* node)
{
FindReturnAncestry finder(node, root->location.end);
root->visit(&finder);
if (!finder.currNode)
finder.currNode = node;
LUAU_ASSERT(finder.found && finder.currNode);
return finder.currNode;
}
bool AnyTypeSummary::FindReturnAncestry::visit(AstStatLocalFunction* node)
{
currNode = node;
return !found;
}
bool AnyTypeSummary::FindReturnAncestry::visit(AstStatFunction* node)
{
currNode = node;
return !found;
}
bool AnyTypeSummary::FindReturnAncestry::visit(AstType* node)
{
return !found;
}
bool AnyTypeSummary::FindReturnAncestry::visit(AstNode* node)
{
if (node == stat)
{
found = true;
}
if (node->location.end == rootEnd && stat->location.end >= rootEnd)
{
currNode = node;
found = true;
}
return !found;
}
AnyTypeSummary::TypeInfo::TypeInfo(Pattern code, std::string node, TelemetryTypePair type)
: code(code)
, node(node)
, type(type)
{
}
AnyTypeSummary::FindReturnAncestry::FindReturnAncestry(AstNode* stat, Position rootEnd)
: stat(stat)
, rootEnd(rootEnd)
{
}
AnyTypeSummary::AnyTypeSummary() {}
} // namespace Luau

View file

@ -88,7 +88,7 @@ TypePackId Anyification::clean(TypePackId tp)
bool Anyification::ignoreChildren(TypeId ty) bool Anyification::ignoreChildren(TypeId ty)
{ {
if (get<ExternType>(ty)) if (get<ClassType>(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<ExternType>(ty)) else if (get<ClassType>(ty))
return true; return true;
else else
return false; return false;

View file

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

View file

@ -13,6 +13,8 @@
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
namespace Luau namespace Luau
{ {
@ -40,6 +42,8 @@ struct AutocompleteNodeFinder : public AstVisitor
} }
bool visit(AstStat* stat) override bool visit(AstStat* stat) override
{
if (FFlag::LuauExtendStatEndPosWithSemicolon)
{ {
// Consider 'local myLocal = 4;|' and 'local myLocal = 4', where '|' is the cursor position. In both cases, the cursor position is equal // Consider 'local myLocal = 4;|' and 'local myLocal = 4', where '|' is the cursor position. In both cases, the cursor position is equal
// to `AstStatLocal.location.end`. However, in the first case (semicolon), we are starting a new statement, whilst in the second case // to `AstStatLocal.location.end`. However, in the first case (semicolon), we are starting a new statement, whilst in the second case
@ -49,6 +53,15 @@ struct AutocompleteNodeFinder : public AstVisitor
ancestry.push_back(stat); ancestry.push_back(stat);
return true; return true;
} }
}
else
{
if (stat->location.begin < pos && pos <= stat->location.end)
{
ancestry.push_back(stat);
return true;
}
}
return false; return false;
} }
@ -574,11 +587,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 ExternType* etv = get<ExternType>(parentTy)) else if (const ClassType* ctv = get<ClassType>(parentTy))
{ {
while (etv) while (ctv)
{ {
if (auto propIt = etv->props.find(indexName->index.value); propIt != etv->props.end()) if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end())
{ {
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
@ -590,7 +603,7 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol
); );
} }
etv = etv->parent ? Luau::get<Luau::ExternType>(*etv->parent) : nullptr; ctv = ctv->parent ? Luau::get<Luau::ClassType>(*ctv->parent) : nullptr;
} }
} }
else if (const PrimitiveType* ptv = get<PrimitiveType>(parentTy); ptv && ptv->metatable) else if (const PrimitiveType* ptv = get<PrimitiveType>(parentTy); ptv && ptv->metatable)

View file

@ -24,10 +24,13 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(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_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"};
@ -80,8 +83,6 @@ static ParenthesesRecommendation getParenRecommendationForIntersect(const Inters
ParenthesesRecommendation rec = ParenthesesRecommendation::None; ParenthesesRecommendation rec = ParenthesesRecommendation::None;
for (Luau::TypeId partId : intersect->parts) for (Luau::TypeId partId : intersect->parts)
{ {
if (FFlag::LuauAutocompleteMissingFollows)
partId = follow(partId);
if (auto partFunc = Luau::get<FunctionType>(partId)) if (auto partFunc = Luau::get<FunctionType>(partId))
{ {
rec = std::max(rec, getParenRecommendationForFunc(partFunc, nodes)); rec = std::max(rec, getParenRecommendationForFunc(partFunc, nodes));
@ -307,7 +308,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 ExternType*> containingExternType = std::nullopt std::optional<const ClassType*> containingClass = std::nullopt
) )
{ {
rootTy = follow(rootTy); rootTy = follow(rootTy);
@ -330,8 +331,8 @@ static void autocompleteProps(
if (calledWithSelf == ftv->hasSelf) if (calledWithSelf == ftv->hasSelf)
return true; return true;
// Calls on extern types require strict match between how function is declared and how it's called // Calls on classes require strict match between how function is declared and how it's called
if (get<ExternType>(rootTy)) if (get<ClassType>(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
@ -364,7 +365,7 @@ static void autocompleteProps(
return calledWithSelf; return calledWithSelf;
}; };
auto fillProps = [&](const ExternType::Props& props) auto fillProps = [&](const ClassType::Props& props)
{ {
for (const auto& [name, prop] : props) for (const auto& [name, prop] : props)
{ {
@ -397,7 +398,7 @@ static void autocompleteProps(
prop.deprecated, prop.deprecated,
isWrongIndexer(type), isWrongIndexer(type),
typeCorrect, typeCorrect,
containingExternType, containingClass,
&prop, &prop,
prop.documentationSymbol, prop.documentationSymbol,
{}, {},
@ -428,12 +429,12 @@ static void autocompleteProps(
} }
}; };
if (auto cls = get<ExternType>(ty)) if (auto cls = get<ClassType>(ty))
{ {
containingExternType = containingExternType.value_or(cls); containingClass = containingClass.value_or(cls);
fillProps(cls->props); fillProps(cls->props);
if (cls->parent) if (cls->parent)
autocompleteProps(module, typeArena, builtinTypes, rootTy, *cls->parent, indexType, nodes, result, seen, containingExternType); autocompleteProps(module, typeArena, builtinTypes, rootTy, *cls->parent, indexType, nodes, result, seen, containingClass);
} }
else if (auto tbl = get<TableType>(ty)) else if (auto tbl = get<TableType>(ty))
fillProps(tbl->props); fillProps(tbl->props);
@ -487,12 +488,12 @@ 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 | ExternType // t1 where t1 = t1 | Class
// //
// 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)
{ {
for (auto ty : seen) for (auto ty: seen)
{ {
if (is<UnionType, IntersectionType>(ty)) if (is<UnionType, IntersectionType>(ty))
innerSeen.insert(ty); innerSeen.insert(ty);
@ -587,7 +588,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 = scopeAtPosition; ScopePtr startScope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position);
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())
@ -699,30 +700,6 @@ 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);
@ -743,17 +720,9 @@ 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 (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (auto element = findTypeElementAt(type->returnTypes, ftv->retTypes, position)) if (auto element = findTypeElementAt(type->returnTypes, ftv->retTypes, position))
return element; 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
return {}; return {};
@ -761,7 +730,7 @@ static std::optional<TypeId> findTypeElementAt(AstType* astType, TypeId ty, Posi
std::optional<TypeId> getLocalTypeInScopeAt(const Module& module, const ScopePtr& scopeAtPosition, Position position, AstLocal* local) std::optional<TypeId> getLocalTypeInScopeAt(const Module& module, const ScopePtr& scopeAtPosition, Position position, AstLocal* local)
{ {
if (ScopePtr scope = scopeAtPosition) if (ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position))
{ {
for (const auto& [name, binding] : scope->bindings) for (const auto& [name, binding] : scope->bindings)
{ {
@ -903,7 +872,7 @@ AutocompleteEntryMap autocompleteTypeNames(
{ {
AutocompleteEntryMap result; AutocompleteEntryMap result;
ScopePtr startScope = scopeAtPosition; ScopePtr startScope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position);
for (ScopePtr scope = startScope; scope; scope = scope->parent) for (ScopePtr scope = startScope; scope; scope = scope->parent)
{ {
@ -1082,16 +1051,12 @@ AutocompleteEntryMap autocompleteTypeNames(
} }
} }
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (!node->returnAnnotation) if (!node->returnAnnotation)
return result; return result;
if (const auto typePack = node->returnAnnotation->as<AstTypePackExplicit>()) for (size_t i = 0; i < node->returnAnnotation->types.size; i++)
{ {
for (size_t i = 0; i < typePack->typeList.types.size; i++) AstType* ret = node->returnAnnotation->types.data[i];
{
AstType* ret = typePack->typeList.types.data[i];
if (ret->location.containsClosed(position)) if (ret->location.containsClosed(position))
{ {
@ -1106,7 +1071,7 @@ AutocompleteEntryMap autocompleteTypeNames(
} }
} }
if (AstTypePack* retTp = typePack->typeList.tailType) if (AstTypePack* retTp = node->returnAnnotation->tailType)
{ {
if (auto variadic = retTp->as<AstTypePackVariadic>()) if (auto variadic = retTp->as<AstTypePackVariadic>())
{ {
@ -1121,56 +1086,6 @@ AutocompleteEntryMap autocompleteTypeNames(
} }
} }
} }
else if (auto variadic = node->returnAnnotation->as<AstTypePackVariadic>())
{
if (variadic->location.containsClosed(position))
{
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
{
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, ~0u))
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
}
}
}
}
else
{
if (!node->returnAnnotation_DEPRECATED)
return result;
for (size_t i = 0; i < node->returnAnnotation_DEPRECATED->types.size; i++)
{
AstType* ret = node->returnAnnotation_DEPRECATED->types.data[i];
if (ret->location.containsClosed(position))
{
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
{
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, i))
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
}
// TODO: with additional type information, we could suggest inferred return type here
break;
}
}
if (AstTypePack* retTp = node->returnAnnotation_DEPRECATED->tailType)
{
if (auto variadic = retTp->as<AstTypePackVariadic>())
{
if (variadic->location.containsClosed(position))
{
if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node))
{
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, ~0u))
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
}
}
}
}
}
}
return result; return result;
} }
@ -1290,7 +1205,7 @@ static AutocompleteEntryMap autocompleteStatement(
) )
{ {
// This is inefficient. :( // This is inefficient. :(
ScopePtr scope = scopeAtPosition; ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position);
AutocompleteEntryMap result; AutocompleteEntryMap result;
@ -1468,7 +1383,7 @@ static AutocompleteContext autocompleteExpression(
else else
{ {
// This is inefficient. :( // This is inefficient. :(
ScopePtr scope = scopeAtPosition; ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position);
while (scope) while (scope)
{ {
@ -1537,7 +1452,7 @@ static AutocompleteResult autocompleteExpression(
return {result, ancestry, context}; return {result, ancestry, context};
} }
static std::optional<const ExternType*> getMethodContainingExternType(const ModulePtr& module, AstExpr* funcExpr) static std::optional<const ClassType*> getMethodContainingClass(const ModulePtr& module, AstExpr* funcExpr)
{ {
AstExpr* parentExpr = nullptr; AstExpr* parentExpr = nullptr;
if (auto indexName = funcExpr->as<AstExprIndexName>()) if (auto indexName = funcExpr->as<AstExprIndexName>())
@ -1561,14 +1476,14 @@ static std::optional<const ExternType*> getMethodContainingExternType(const Modu
Luau::TypeId parentType = Luau::follow(*parentIt); Luau::TypeId parentType = Luau::follow(*parentIt);
if (auto parentExternType = Luau::get<ExternType>(parentType)) if (auto parentClass = Luau::get<ClassType>(parentType))
{ {
return parentExternType; return parentClass;
} }
if (auto parentUnion = Luau::get<UnionType>(parentType)) if (auto parentUnion = Luau::get<UnionType>(parentType))
{ {
return returnFirstNonnullOptionOfType<ExternType>(parentUnion); return returnFirstNonnullOptionOfType<ClassType>(parentUnion);
} }
return std::nullopt; return std::nullopt;
@ -1626,7 +1541,10 @@ 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;
@ -1687,7 +1605,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, getMethodContainingExternType(module, candidate->func), candidateString)) if (std::optional<AutocompleteEntryMap> ret = callback(tag, getMethodContainingClass(module, candidate->func), candidateString))
{ {
return ret; return ret;
} }
@ -1705,8 +1623,6 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(
{ {
for (TypeId part : intersect->parts) for (TypeId part : intersect->parts)
{ {
if (FFlag::LuauAutocompleteMissingFollows)
part = follow(part);
if (auto candidateFunctionType = Luau::get<FunctionType>(part)) if (auto candidateFunctionType = Luau::get<FunctionType>(part))
{ {
if (std::optional<AutocompleteEntryMap> ret = performCallback(candidateFunctionType)) if (std::optional<AutocompleteEntryMap> ret = performCallback(candidateFunctionType))
@ -1855,7 +1771,7 @@ static std::optional<AutocompleteEntry> makeAnonymousAutofilled(
if (!type) if (!type)
return std::nullopt; return std::nullopt;
const ScopePtr scope = scopeAtPosition; const ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(*module, position);
if (!scope) if (!scope)
return std::nullopt; return std::nullopt;

View file

@ -3,23 +3,22 @@
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/Clone.h" #include "Luau/Clone.h"
#include "Luau/Common.h"
#include "Luau/ConstraintGenerator.h"
#include "Luau/ConstraintSolver.h"
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include "Luau/Error.h" #include "Luau/Error.h"
#include "Luau/Frontend.h" #include "Luau/Frontend.h"
#include "Luau/InferPolarity.h"
#include "Luau/NotNull.h"
#include "Luau/Subtyping.h"
#include "Luau/Symbol.h" #include "Luau/Symbol.h"
#include "Luau/Common.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/Type.h" #include "Luau/ConstraintSolver.h"
#include "Luau/ConstraintGenerator.h"
#include "Luau/NotNull.h"
#include "Luau/TypeInfer.h"
#include "Luau/TypeChecker2.h" #include "Luau/TypeChecker2.h"
#include "Luau/TypeFunction.h" #include "Luau/TypeFunction.h"
#include "Luau/TypeInfer.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
#include "Luau/Type.h"
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
#include "Luau/Subtyping.h"
#include <algorithm> #include <algorithm>
@ -30,11 +29,11 @@
*/ */
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAGVARIABLE(LuauStringFormatErrorSuppression)
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3) LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauFollowTableFreeze)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked)
LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition)
namespace Luau namespace Luau
{ {
@ -248,7 +247,6 @@ void addGlobalBinding(GlobalTypes& globals, const ScopePtr& scope, const std::st
void addGlobalBinding(GlobalTypes& globals, const ScopePtr& scope, const std::string& name, Binding binding) void addGlobalBinding(GlobalTypes& globals, const ScopePtr& scope, const std::string& name, Binding binding)
{ {
inferGenericPolarities(NotNull{&globals.globalTypes}, NotNull{scope.get()}, binding.typeId);
scope->bindings[globals.globalNames.names->getOrAdd(name.c_str())] = binding; scope->bindings[globals.globalNames.names->getOrAdd(name.c_str())] = binding;
} }
@ -313,9 +311,6 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
TypeArena& arena = globals.globalTypes; TypeArena& arena = globals.globalTypes;
NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes; NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes;
Scope* globalScope = nullptr; // NotNull<Scope> when removing FFlag::LuauNonReentrantGeneralization2
if (FFlag::LuauNonReentrantGeneralization2)
globalScope = globals.globalScope.get();
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
builtinTypeFunctions().addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()}); builtinTypeFunctions().addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()});
@ -325,8 +320,8 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
); );
LUAU_ASSERT(loadResult.success); LUAU_ASSERT(loadResult.success);
TypeId genericK = arena.addType(GenericType{globalScope, "K"}); TypeId genericK = arena.addType(GenericType{"K"});
TypeId genericV = arena.addType(GenericType{globalScope, "V"}); TypeId genericV = arena.addType(GenericType{"V"});
TypeId mapOfKtoV = arena.addType(TableType{{}, TableIndexer(genericK, genericV), globals.globalScope->level, TableState::Generic}); TypeId mapOfKtoV = arena.addType(TableType{{}, TableIndexer(genericK, genericV), globals.globalScope->level, TableState::Generic});
std::optional<TypeId> stringMetatableTy = getMetatable(builtinTypes->stringType, builtinTypes); std::optional<TypeId> stringMetatableTy = getMetatable(builtinTypes->stringType, builtinTypes);
@ -343,7 +338,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;
ExternType* vectorCls = getMutable<ExternType>(vectorTy); ClassType* vectorCls = getMutable<ClassType>(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);
@ -374,7 +369,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil) // pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil)
addGlobalBinding(globals, "pairs", arena.addType(FunctionType{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau"); addGlobalBinding(globals, "pairs", arena.addType(FunctionType{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
TypeId genericMT = arena.addType(GenericType{globalScope, "MT"}); TypeId genericMT = arena.addType(GenericType{"MT"});
TableType tab{TableState::Generic, globals.globalScope->level}; TableType tab{TableState::Generic, globals.globalScope->level};
TypeId tabTy = arena.addType(tab); TypeId tabTy = arena.addType(tab);
@ -386,7 +381,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
TypeId genericT = arena.addType(GenericType{globalScope, "T"}); TypeId genericT = arena.addType(GenericType{"T"});
TypeId tMetaMT = arena.addType(MetatableType{genericT, genericMT}); TypeId tMetaMT = arena.addType(MetatableType{genericT, genericMT});
// clang-format off // clang-format off
@ -443,7 +438,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
// declare function assert<T>(value: T, errorMessage: string?): intersect<T, ~(false?)> // declare function assert<T>(value: T, errorMessage: string?): intersect<T, ~(false?)>
TypeId genericT = arena.addType(GenericType{globalScope, "T"}); TypeId genericT = arena.addType(GenericType{"T"});
TypeId refinedTy = arena.addType(TypeFunctionInstanceType{ TypeId refinedTy = arena.addType(TypeFunctionInstanceType{
NotNull{&builtinTypeFunctions().intersectFunc}, {genericT, arena.addType(NegationType{builtinTypes->falsyType})}, {} NotNull{&builtinTypeFunctions().intersectFunc}, {genericT, arena.addType(NegationType{builtinTypes->falsyType})}, {}
}); });
@ -466,16 +461,12 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
// the top table type. We do the best we can by modelling these // the top table type. We do the best we can by modelling these
// functions using unconstrained generics. It's not quite right, // functions using unconstrained generics. It's not quite right,
// but it'll be ok for now. // but it'll be ok for now.
TypeId genericTy = arena.addType(GenericType{globalScope, "T"}); TypeId genericTy = arena.addType(GenericType{"T"});
TypePackId thePack = arena.addTypePack({genericTy}); TypePackId thePack = arena.addTypePack({genericTy});
TypeId idTyWithMagic = arena.addType(FunctionType{{genericTy}, {}, thePack, thePack}); TypeId idTyWithMagic = arena.addType(FunctionType{{genericTy}, {}, thePack, thePack});
ttv->props["freeze"] = makeProperty(idTyWithMagic, "@luau/global/table.freeze"); ttv->props["freeze"] = makeProperty(idTyWithMagic, "@luau/global/table.freeze");
if (globalScope)
inferGenericPolarities(NotNull{&globals.globalTypes}, NotNull{globalScope}, idTyWithMagic);
TypeId idTy = arena.addType(FunctionType{{genericTy}, {}, thePack, thePack}); TypeId idTy = arena.addType(FunctionType{{genericTy}, {}, thePack, thePack});
if (globalScope)
inferGenericPolarities(NotNull{&globals.globalTypes}, NotNull{globalScope}, idTy);
ttv->props["clone"] = makeProperty(idTy, "@luau/global/table.clone"); ttv->props["clone"] = makeProperty(idTy, "@luau/global/table.clone");
} }
else else
@ -704,14 +695,6 @@ 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);
@ -723,13 +706,13 @@ 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 = FFlag::LuauFormatUseLastPosition Location location = context.callSite->args.data[i + (calledWithSelf ? 0 : paramOffset)]->location;
? 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);
if (!result.isSubtype) if (!result.isSubtype)
{
if (FFlag::LuauStringFormatErrorSuppression)
{ {
switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy)) switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy))
{ {
@ -744,6 +727,12 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location); context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
} }
} }
else
{
Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result);
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
}
}
} }
return true; return true;
@ -1538,6 +1527,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});
@ -1549,6 +1539,7 @@ bool MagicClone::infer(const MagicFunctionCallContext& context)
static std::optional<TypeId> freezeTable(TypeId inputType, const MagicFunctionCallContext& context) static std::optional<TypeId> freezeTable(TypeId inputType, const MagicFunctionCallContext& context)
{ {
TypeArena* arena = context.solver->arena; TypeArena* arena = context.solver->arena;
if (FFlag::LuauFollowTableFreeze)
inputType = follow(inputType); inputType = follow(inputType);
if (auto mt = get<MetatableType>(inputType)) if (auto mt = get<MetatableType>(inputType))
{ {
@ -1616,17 +1607,6 @@ bool MagicFreeze::infer(const MagicFunctionCallContext& context)
std::optional<DefId> resultDef = dfg->getDefOptional(targetExpr); std::optional<DefId> resultDef = dfg->getDefOptional(targetExpr);
std::optional<TypeId> resultTy = resultDef ? scope->lookup(*resultDef) : std::nullopt; std::optional<TypeId> resultTy = resultDef ? scope->lookup(*resultDef) : std::nullopt;
if (FFlag::LuauMagicFreezeCheckBlocked)
{
if (resultTy && !get<BlockedType>(resultTy))
{
// If there's an existing result type but it's _not_ blocked, then
// we aren't type stating this builtin and should fall back to
// regular inference.
return false;
}
}
std::optional<TypeId> frozenType = freezeTable(inputType, context); std::optional<TypeId> frozenType = freezeTable(inputType, context);
if (!frozenType) if (!frozenType)

View file

@ -179,6 +179,8 @@ public:
generic->scope = nullptr; generic->scope = nullptr;
else if (auto free = getMutable<FreeType>(target)) else if (auto free = getMutable<FreeType>(target))
free->scope = nullptr; free->scope = nullptr;
else if (auto fn = getMutable<FunctionType>(target))
fn->scope = nullptr;
else if (auto table = getMutable<TableType>(target)) else if (auto table = getMutable<TableType>(target))
table->scope = nullptr; table->scope = nullptr;
@ -355,7 +357,7 @@ private:
t->metatable = shallowClone(t->metatable); t->metatable = shallowClone(t->metatable);
} }
void cloneChildren(ExternType* t) void cloneChildren(ClassType* t)
{ {
for (auto& [_, p] : t->props) for (auto& [_, p] : t->props)
p = shallowClone(p); p = shallowClone(p);
@ -519,6 +521,11 @@ public:
if (FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes) if (FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes)
tt->scope = replacementForNullScope; tt->scope = replacementForNullScope;
} }
else if (auto fn = getMutable<FunctionType>(target))
{
if (FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes)
fn->scope = replacementForNullScope;
}
(*types)[ty] = target; (*types)[ty] = target;
queue.emplace_back(target); queue.emplace_back(target);

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -13,25 +13,32 @@
LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauPreprocessTypestatedArgument)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackTrueReset)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAGVARIABLE(LuauDfgIfBlocksShouldRespectControlFlow)
namespace Luau namespace Luau
{ {
bool doesCallError(const AstExprCall* call); // TypeInfer.cpp bool doesCallError(const AstExprCall* call); // TypeInfer.cpp
struct ReferencedDefFinder : public AstVisitor
{
bool visit(AstExprLocal* local) override
{
referencedLocalDefs.push_back(local->local);
return true;
}
// ast defs is just a mapping from expr -> def in general
// will get built up by the dfg builder
// localDefs, we need to copy over
std::vector<AstLocal*> referencedLocalDefs;
};
struct PushScope struct PushScope
{ {
ScopeStack& stack; ScopeStack& stack;
size_t previousSize;
PushScope(ScopeStack& stack, DfgScope* scope) PushScope(ScopeStack& stack, DfgScope* scope)
: stack(stack) : stack(stack)
, previousSize(stack.size())
{ {
// `scope` should never be `nullptr` here. // `scope` should never be `nullptr` here.
LUAU_ASSERT(scope); LUAU_ASSERT(scope);
@ -40,19 +47,8 @@ struct PushScope
~PushScope() ~PushScope()
{ {
if (FFlag::LuauDfgScopeStackTrueReset)
{
// If somehow this stack has _shrunk_ to be smaller than we expect,
// something very strange has happened.
LUAU_ASSERT(stack.size() > previousSize);
while (stack.size() > previousSize)
stack.pop_back(); stack.pop_back();
} }
else
{
stack.pop_back();
}
}
}; };
const RefinementKey* RefinementKeyArena::leaf(DefId def) const RefinementKey* RefinementKeyArena::leaf(DefId def)
@ -86,6 +82,12 @@ std::optional<DefId> DataFlowGraph::getDefOptional(const AstExpr* expr) const
return NotNull{*def}; return NotNull{*def};
} }
std::optional<DefId> DataFlowGraph::getRValueDefForCompoundAssign(const AstExpr* expr) const
{
auto def = compoundAssignDefs.find(expr);
return def ? std::optional<DefId>(*def) : std::nullopt;
}
DefId DataFlowGraph::getDef(const AstLocal* local) const DefId DataFlowGraph::getDef(const AstLocal* local) const
{ {
auto def = localDefs.find(local); auto def = localDefs.find(local);
@ -199,15 +201,7 @@ DataFlowGraph DataFlowGraphBuilder::build(
DataFlowGraphBuilder builder(defArena, keyArena); DataFlowGraphBuilder builder(defArena, keyArena);
builder.handle = handle; builder.handle = handle;
DfgScope* moduleScope = builder.makeChildScope();
DfgScope* moduleScope;
// We're not explicitly calling makeChildScope here because that function relies on currentScope
// which guarantees that the scope being returned is NotNull
// This means that while the scope stack is empty, we'll have to manually initialize the global scope
if (FFlag::LuauDfgScopeStackNotNull)
moduleScope = builder.scopes.emplace_back(new DfgScope{nullptr, DfgScope::ScopeType::Linear}).get();
else
moduleScope = builder.makeChildScope();
PushScope ps{builder.scopeStack, moduleScope}; PushScope ps{builder.scopeStack, moduleScope};
builder.visitBlockWithoutChildScope(block); builder.visitBlockWithoutChildScope(block);
builder.resolveCaptures(); builder.resolveCaptures();
@ -239,13 +233,7 @@ void DataFlowGraphBuilder::resolveCaptures()
} }
} }
NotNull<DfgScope> DataFlowGraphBuilder::currentScope() DfgScope* DataFlowGraphBuilder::currentScope()
{
LUAU_ASSERT(!scopeStack.empty());
return NotNull{scopeStack.back()};
}
DfgScope* DataFlowGraphBuilder::currentScope_DEPRECATED()
{ {
if (scopeStack.empty()) if (scopeStack.empty())
return nullptr; // nullptr is the root DFG scope. return nullptr; // nullptr is the root DFG scope.
@ -254,10 +242,7 @@ DfgScope* DataFlowGraphBuilder::currentScope_DEPRECATED()
DfgScope* DataFlowGraphBuilder::makeChildScope(DfgScope::ScopeType scopeType) DfgScope* DataFlowGraphBuilder::makeChildScope(DfgScope::ScopeType scopeType)
{ {
if (FFlag::LuauDfgScopeStackNotNull)
return scopes.emplace_back(new DfgScope{currentScope(), scopeType}).get(); return scopes.emplace_back(new DfgScope{currentScope(), scopeType}).get();
else
return scopes.emplace_back(new DfgScope{currentScope_DEPRECATED(), scopeType}).get();
} }
void DataFlowGraphBuilder::join(DfgScope* p, DfgScope* a, DfgScope* b) void DataFlowGraphBuilder::join(DfgScope* p, DfgScope* a, DfgScope* b)
@ -332,9 +317,9 @@ void DataFlowGraphBuilder::joinProps(DfgScope* result, const DfgScope& a, const
} }
} }
DefId DataFlowGraphBuilder::lookup(Symbol symbol, Location location) DefId DataFlowGraphBuilder::lookup(Symbol symbol)
{ {
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED(); DfgScope* scope = currentScope();
// true if any of the considered scopes are a loop. // true if any of the considered scopes are a loop.
bool outsideLoopScope = false; bool outsideLoopScope = false;
@ -359,15 +344,15 @@ DefId DataFlowGraphBuilder::lookup(Symbol symbol, Location location)
} }
} }
DefId result = defArena->freshCell(symbol, location); DefId result = defArena->freshCell();
scope->bindings[symbol] = result; scope->bindings[symbol] = result;
captures[symbol].allVersions.push_back(result); captures[symbol].allVersions.push_back(result);
return result; return result;
} }
DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key, Location location) DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key)
{ {
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED(); DfgScope* scope = currentScope();
for (DfgScope* current = scope; current; current = current->parent) for (DfgScope* current = scope; current; current = current->parent)
{ {
if (auto props = current->props.find(def)) if (auto props = current->props.find(def))
@ -377,7 +362,7 @@ DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key, Location l
} }
else if (auto phi = get<Phi>(def); phi && phi->operands.empty()) // Unresolved phi nodes else if (auto phi = get<Phi>(def); phi && phi->operands.empty()) // Unresolved phi nodes
{ {
DefId result = defArena->freshCell(def->name, location); DefId result = defArena->freshCell();
scope->props[def][key] = result; scope->props[def][key] = result;
return result; return result;
} }
@ -387,7 +372,7 @@ DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key, Location l
{ {
std::vector<DefId> defs; std::vector<DefId> defs;
for (DefId operand : phi->operands) for (DefId operand : phi->operands)
defs.push_back(lookup(operand, key, location)); defs.push_back(lookup(operand, key));
DefId result = defArena->phi(defs); DefId result = defArena->phi(defs);
scope->props[def][key] = result; scope->props[def][key] = result;
@ -395,7 +380,7 @@ DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key, Location l
} }
else if (get<Cell>(def)) else if (get<Cell>(def))
{ {
DefId result = defArena->freshCell(def->name, location); DefId result = defArena->freshCell();
scope->props[def][key] = result; scope->props[def][key] = result;
return result; return result;
} }
@ -413,10 +398,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatBlock* b)
cf = visitBlockWithoutChildScope(b); cf = visitBlockWithoutChildScope(b);
} }
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(child); currentScope()->inherit(child);
else
currentScope_DEPRECATED()->inherit(child);
return cf; return cf;
} }
@ -473,7 +455,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<AstStatDeclareExternType>()) else if (auto d = s->as<AstStatDeclareClass>())
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);
@ -501,27 +483,13 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatIf* i)
elsecf = visit(i->elsebody); elsecf = visit(i->elsebody);
} }
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED(); DfgScope* scope = currentScope();
if (FFlag::LuauDfgIfBlocksShouldRespectControlFlow)
{
// If the control flow from the `if` or `else` block is non-linear,
// then we should assume that the _other_ branch is the one taken.
if (thencf != ControlFlow::None && elsecf == ControlFlow::None)
scope->inherit(elseScope);
else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
scope->inherit(thenScope);
else if ((thencf | elsecf) == ControlFlow::None)
join(scope, thenScope, elseScope);
}
else
{
if (thencf != ControlFlow::None && elsecf == ControlFlow::None) if (thencf != ControlFlow::None && elsecf == ControlFlow::None)
join(scope, scope, elseScope); join(scope, scope, elseScope);
else if (thencf == ControlFlow::None && elsecf != ControlFlow::None) else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
join(scope, thenScope, scope); join(scope, thenScope, scope);
else if ((thencf | elsecf) == ControlFlow::None) else if ((thencf | elsecf) == ControlFlow::None)
join(scope, thenScope, elseScope); join(scope, thenScope, elseScope);
}
if (thencf == elsecf) if (thencf == elsecf)
return thencf; return thencf;
@ -542,10 +510,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatWhile* w)
visit(w->body); visit(w->body);
} }
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(whileScope); currentScope()->inherit(whileScope);
else
currentScope_DEPRECATED()->inherit(whileScope);
return ControlFlow::None; return ControlFlow::None;
} }
@ -561,10 +526,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatRepeat* r)
visitExpr(r->condition); visitExpr(r->condition);
} }
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(repeatScope); currentScope()->inherit(repeatScope);
else
currentScope_DEPRECATED()->inherit(repeatScope);
return ControlFlow::None; return ControlFlow::None;
} }
@ -613,7 +575,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatLocal* l)
// We need to create a new def to intentionally avoid alias tracking, but we'd like to // We need to create a new def to intentionally avoid alias tracking, but we'd like to
// make sure that the non-aliased defs are also marked as a subscript for refinements. // make sure that the non-aliased defs are also marked as a subscript for refinements.
bool subscripted = i < defs.size() && containsSubscriptedDefinition(defs[i]); bool subscripted = i < defs.size() && containsSubscriptedDefinition(defs[i]);
DefId def = defArena->freshCell(local, local->location, subscripted); DefId def = defArena->freshCell(subscripted);
if (i < l->values.size) if (i < l->values.size)
{ {
AstExpr* e = l->values.data[i]; AstExpr* e = l->values.data[i];
@ -623,10 +585,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatLocal* l)
} }
} }
graph.localDefs[local] = def; graph.localDefs[local] = def;
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[local] = def; currentScope()->bindings[local] = def;
else
currentScope_DEPRECATED()->bindings[local] = def;
captures[local].allVersions.push_back(def); captures[local].allVersions.push_back(def);
} }
@ -648,22 +607,16 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatFor* f)
if (f->var->annotation) if (f->var->annotation)
visitType(f->var->annotation); visitType(f->var->annotation);
DefId def = defArena->freshCell(f->var, f->var->location); DefId def = defArena->freshCell();
graph.localDefs[f->var] = def; graph.localDefs[f->var] = def;
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[f->var] = def; currentScope()->bindings[f->var] = def;
else
currentScope_DEPRECATED()->bindings[f->var] = def;
captures[f->var].allVersions.push_back(def); captures[f->var].allVersions.push_back(def);
// TODO(controlflow): entry point has a back edge from exit point // TODO(controlflow): entry point has a back edge from exit point
visit(f->body); visit(f->body);
} }
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(forScope); currentScope()->inherit(forScope);
else
currentScope_DEPRECATED()->inherit(forScope);
return ControlFlow::None; return ControlFlow::None;
} }
@ -680,12 +633,9 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatForIn* f)
if (local->annotation) if (local->annotation)
visitType(local->annotation); visitType(local->annotation);
DefId def = defArena->freshCell(local, local->location); DefId def = defArena->freshCell();
graph.localDefs[local] = def; graph.localDefs[local] = def;
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[local] = def; currentScope()->bindings[local] = def;
else
currentScope_DEPRECATED()->bindings[local] = def;
captures[local].allVersions.push_back(def); captures[local].allVersions.push_back(def);
} }
@ -696,10 +646,8 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatForIn* f)
visit(f->body); visit(f->body);
} }
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(forScope); currentScope()->inherit(forScope);
else
currentScope_DEPRECATED()->inherit(forScope);
return ControlFlow::None; return ControlFlow::None;
} }
@ -714,7 +662,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatAssign* a)
for (size_t i = 0; i < a->vars.size; ++i) for (size_t i = 0; i < a->vars.size; ++i)
{ {
AstExpr* v = a->vars.data[i]; AstExpr* v = a->vars.data[i];
visitLValue(v, i < defs.size() ? defs[i] : defArena->freshCell(Symbol{}, v->location)); visitLValue(v, i < defs.size() ? defs[i] : defArena->freshCell());
} }
return ControlFlow::None; return ControlFlow::None;
@ -740,7 +688,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatFunction* f)
// //
// which is evidence that references to variables must be a phi node of all possible definitions, // which is evidence that references to variables must be a phi node of all possible definitions,
// but for bug compatibility, we'll assume the same thing here. // but for bug compatibility, we'll assume the same thing here.
visitLValue(f->name, defArena->freshCell(Symbol{}, f->name->location)); visitLValue(f->name, defArena->freshCell());
visitExpr(f->func); visitExpr(f->func);
if (auto local = f->name->as<AstExprLocal>()) if (auto local = f->name->as<AstExprLocal>())
@ -760,12 +708,9 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatFunction* f)
ControlFlow DataFlowGraphBuilder::visit(AstStatLocalFunction* l) ControlFlow DataFlowGraphBuilder::visit(AstStatLocalFunction* l)
{ {
DefId def = defArena->freshCell(l->name, l->location); DefId def = defArena->freshCell();
graph.localDefs[l->name] = def; graph.localDefs[l->name] = def;
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[l->name] = def; currentScope()->bindings[l->name] = def;
else
currentScope_DEPRECATED()->bindings[l->name] = def;
captures[l->name].allVersions.push_back(def); captures[l->name].allVersions.push_back(def);
visitExpr(l->func); visitExpr(l->func);
@ -796,12 +741,9 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatTypeFunction* f)
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareGlobal* d) ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareGlobal* d)
{ {
DefId def = defArena->freshCell(d->name, d->nameLocation); DefId def = defArena->freshCell();
graph.declaredDefs[d] = def; graph.declaredDefs[d] = def;
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[d->name] = def; currentScope()->bindings[d->name] = def;
else
currentScope_DEPRECATED()->bindings[d->name] = def;
captures[d->name].allVersions.push_back(def); captures[d->name].allVersions.push_back(def);
visitType(d->type); visitType(d->type);
@ -811,12 +753,9 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareGlobal* d)
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareFunction* d) ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareFunction* d)
{ {
DefId def = defArena->freshCell(d->name, d->nameLocation); DefId def = defArena->freshCell();
graph.declaredDefs[d] = def; graph.declaredDefs[d] = def;
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[d->name] = def; currentScope()->bindings[d->name] = def;
else
currentScope_DEPRECATED()->bindings[d->name] = def;
captures[d->name].allVersions.push_back(def); captures[d->name].allVersions.push_back(def);
DfgScope* unreachable = makeChildScope(); DfgScope* unreachable = makeChildScope();
@ -825,15 +764,12 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareFunction* d)
visitGenerics(d->generics); visitGenerics(d->generics);
visitGenericPacks(d->genericPacks); visitGenericPacks(d->genericPacks);
visitTypeList(d->params); visitTypeList(d->params);
if (FFlag::LuauStoreReturnTypesAsPackOnAst) visitTypeList(d->retTypes);
visitTypePack(d->retTypes);
else
visitTypeList(d->retTypes_DEPRECATED);
return ControlFlow::None; return ControlFlow::None;
} }
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareExternType* d) ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareClass* 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
@ -841,7 +777,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareExternType* d)
DfgScope* unreachable = makeChildScope(); DfgScope* unreachable = makeChildScope();
PushScope ps{scopeStack, unreachable}; PushScope ps{scopeStack, unreachable};
for (AstDeclaredExternTypeProperty prop : d->props) for (AstDeclaredClassProp prop : d->props)
visitType(prop.ty); visitType(prop.ty);
return ControlFlow::None; return ControlFlow::None;
@ -874,19 +810,19 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExpr* e)
if (auto g = e->as<AstExprGroup>()) if (auto g = e->as<AstExprGroup>())
return visitExpr(g); return visitExpr(g);
else if (auto c = e->as<AstExprConstantNil>()) else if (auto c = e->as<AstExprConstantNil>())
return {defArena->freshCell(Symbol{}, c->location), nullptr}; // ok return {defArena->freshCell(), nullptr}; // ok
else if (auto c = e->as<AstExprConstantBool>()) else if (auto c = e->as<AstExprConstantBool>())
return {defArena->freshCell(Symbol{}, c->location), nullptr}; // ok return {defArena->freshCell(), nullptr}; // ok
else if (auto c = e->as<AstExprConstantNumber>()) else if (auto c = e->as<AstExprConstantNumber>())
return {defArena->freshCell(Symbol{}, c->location), nullptr}; // ok return {defArena->freshCell(), nullptr}; // ok
else if (auto c = e->as<AstExprConstantString>()) else if (auto c = e->as<AstExprConstantString>())
return {defArena->freshCell(Symbol{}, c->location), nullptr}; // ok return {defArena->freshCell(), nullptr}; // ok
else if (auto l = e->as<AstExprLocal>()) else if (auto l = e->as<AstExprLocal>())
return visitExpr(l); return visitExpr(l);
else if (auto g = e->as<AstExprGlobal>()) else if (auto g = e->as<AstExprGlobal>())
return visitExpr(g); return visitExpr(g);
else if (auto v = e->as<AstExprVarargs>()) else if (auto v = e->as<AstExprVarargs>())
return {defArena->freshCell(Symbol{}, v->location), nullptr}; // ok return {defArena->freshCell(), nullptr}; // ok
else if (auto c = e->as<AstExprCall>()) else if (auto c = e->as<AstExprCall>())
return visitExpr(c); return visitExpr(c);
else if (auto i = e->as<AstExprIndexName>()) else if (auto i = e->as<AstExprIndexName>())
@ -927,14 +863,14 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprGroup* group)
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprLocal* l) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprLocal* l)
{ {
DefId def = lookup(l->local, l->local->location); DefId def = lookup(l->local);
const RefinementKey* key = keyArena->leaf(def); const RefinementKey* key = keyArena->leaf(def);
return {def, key}; return {def, key};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprGlobal* g) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprGlobal* g)
{ {
DefId def = lookup(g->name, g->location); DefId def = lookup(g->name);
return {def, keyArena->leaf(def)}; return {def, keyArena->leaf(def)};
} }
@ -942,12 +878,6 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
{ {
visitExpr(c->func); visitExpr(c->func);
if (FFlag::LuauPreprocessTypestatedArgument)
{
for (AstExpr* arg : c->args)
visitExpr(arg);
}
if (shouldTypestateForFirstArgument(*c) && c->args.size > 1 && isLValue(*c->args.begin())) if (shouldTypestateForFirstArgument(*c) && c->args.size > 1 && isLValue(*c->args.begin()))
{ {
AstExpr* firstArg = *c->args.begin(); AstExpr* firstArg = *c->args.begin();
@ -978,11 +908,8 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
visitLValue(firstArg, def); visitLValue(firstArg, def);
} }
if (!FFlag::LuauPreprocessTypestatedArgument)
{
for (AstExpr* arg : c->args) for (AstExpr* arg : c->args)
visitExpr(arg); visitExpr(arg);
}
// We treat function calls as "subscripted" as they could potentially // We treat function calls as "subscripted" as they could potentially
// return a subscripted value, consider: // return a subscripted value, consider:
@ -994,7 +921,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
// local v = foo({}) // local v = foo({})
// //
// We want to consider `v` to be subscripted here. // We want to consider `v` to be subscripted here.
return {defArena->freshCell(Symbol{}, c->location, /*subscripted=*/true)}; return {defArena->freshCell(/*subscripted=*/true)};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIndexName* i) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIndexName* i)
@ -1002,7 +929,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIndexName* i)
auto [parentDef, parentKey] = visitExpr(i->expr); auto [parentDef, parentKey] = visitExpr(i->expr);
std::string index = i->index.value; std::string index = i->index.value;
DefId def = lookup(parentDef, index, i->location); DefId def = lookup(parentDef, index);
return {def, keyArena->node(parentKey, def, index)}; return {def, keyArena->node(parentKey, def, index)};
} }
@ -1015,11 +942,11 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIndexExpr* i)
{ {
std::string index{string->value.data, string->value.size}; std::string index{string->value.data, string->value.size};
DefId def = lookup(parentDef, index, i->location); DefId def = lookup(parentDef, index);
return {def, keyArena->node(parentKey, def, index)}; return {def, keyArena->node(parentKey, def, index)};
} }
return {defArena->freshCell(Symbol{}, i->location, /* subscripted= */ true), nullptr}; return {defArena->freshCell(/* subscripted= */ true), nullptr};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
@ -1032,7 +959,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
// There's no syntax for `self` to have an annotation if using `function t:m()` // There's no syntax for `self` to have an annotation if using `function t:m()`
LUAU_ASSERT(!self->annotation); LUAU_ASSERT(!self->annotation);
DefId def = defArena->freshCell(f->debugname, f->location); DefId def = defArena->freshCell();
graph.localDefs[self] = def; graph.localDefs[self] = def;
signatureScope->bindings[self] = def; signatureScope->bindings[self] = def;
captures[self].allVersions.push_back(def); captures[self].allVersions.push_back(def);
@ -1043,7 +970,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
if (param->annotation) if (param->annotation)
visitType(param->annotation); visitType(param->annotation);
DefId def = defArena->freshCell(param, param->location); DefId def = defArena->freshCell();
graph.localDefs[param] = def; graph.localDefs[param] = def;
signatureScope->bindings[param] = def; signatureScope->bindings[param] = def;
captures[param].allVersions.push_back(def); captures[param].allVersions.push_back(def);
@ -1052,16 +979,8 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
if (f->varargAnnotation) if (f->varargAnnotation)
visitTypePack(f->varargAnnotation); visitTypePack(f->varargAnnotation);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (f->returnAnnotation) if (f->returnAnnotation)
visitTypePack(f->returnAnnotation); visitTypeList(*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
@ -1073,16 +992,13 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
// g() --> 5 // g() --> 5
visit(f->body); visit(f->body);
return {defArena->freshCell(f->debugname, f->location), nullptr}; return {defArena->freshCell(), nullptr};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTable* t) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTable* t)
{ {
DefId tableCell = defArena->freshCell(Symbol{}, t->location); DefId tableCell = defArena->freshCell();
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->props[tableCell] = {}; currentScope()->props[tableCell] = {};
else
currentScope_DEPRECATED()->props[tableCell] = {};
for (AstExprTable::Item item : t->items) for (AstExprTable::Item item : t->items)
{ {
DataFlowResult result = visitExpr(item.value); DataFlowResult result = visitExpr(item.value);
@ -1090,12 +1006,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTable* t)
{ {
visitExpr(item.key); visitExpr(item.key);
if (auto string = item.key->as<AstExprConstantString>()) if (auto string = item.key->as<AstExprConstantString>())
{
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->props[tableCell][string->value.data] = result.def; currentScope()->props[tableCell][string->value.data] = result.def;
else
currentScope_DEPRECATED()->props[tableCell][string->value.data] = result.def;
}
} }
} }
@ -1106,7 +1017,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprUnary* u)
{ {
visitExpr(u->expr); visitExpr(u->expr);
return {defArena->freshCell(Symbol{}, u->location), nullptr}; return {defArena->freshCell(), nullptr};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b)
@ -1114,7 +1025,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b)
visitExpr(b->left); visitExpr(b->left);
visitExpr(b->right); visitExpr(b->right);
return {defArena->freshCell(Symbol{}, b->location), nullptr}; return {defArena->freshCell(), nullptr};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTypeAssertion* t) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTypeAssertion* t)
@ -1131,7 +1042,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIfElse* i)
visitExpr(i->trueExpr); visitExpr(i->trueExpr);
visitExpr(i->falseExpr); visitExpr(i->falseExpr);
return {defArena->freshCell(Symbol{}, i->location), nullptr}; return {defArena->freshCell(), nullptr};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprInterpString* i) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprInterpString* i)
@ -1139,7 +1050,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprInterpString* i)
for (AstExpr* e : i->expressions) for (AstExpr* e : i->expressions)
visitExpr(e); visitExpr(e);
return {defArena->freshCell(Symbol{}, i->location), nullptr}; return {defArena->freshCell(), nullptr};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprError* error) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprError* error)
@ -1150,7 +1061,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprError* error)
for (AstExpr* e : error->expressions) for (AstExpr* e : error->expressions)
visitExpr(e); visitExpr(e);
return {defArena->freshCell(Symbol{}, error->location), nullptr}; return {defArena->freshCell(), nullptr};
} }
void DataFlowGraphBuilder::visitLValue(AstExpr* e, DefId incomingDef) void DataFlowGraphBuilder::visitLValue(AstExpr* e, DefId incomingDef)
@ -1176,12 +1087,12 @@ void DataFlowGraphBuilder::visitLValue(AstExpr* e, DefId incomingDef)
DefId DataFlowGraphBuilder::visitLValue(AstExprLocal* l, DefId incomingDef) DefId DataFlowGraphBuilder::visitLValue(AstExprLocal* l, DefId incomingDef)
{ {
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED(); DfgScope* scope = currentScope();
// In order to avoid alias tracking, we need to clip the reference to the parent def. // In order to avoid alias tracking, we need to clip the reference to the parent def.
if (scope->canUpdateDefinition(l->local) && !(FFlag::LuauDoNotAddUpvalueTypesToLocalType && l->upvalue)) if (scope->canUpdateDefinition(l->local))
{ {
DefId updated = defArena->freshCell(l->local, l->location, containsSubscriptedDefinition(incomingDef)); DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
scope->bindings[l->local] = updated; scope->bindings[l->local] = updated;
captures[l->local].allVersions.push_back(updated); captures[l->local].allVersions.push_back(updated);
return updated; return updated;
@ -1192,12 +1103,12 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprLocal* l, DefId incomingDef)
DefId DataFlowGraphBuilder::visitLValue(AstExprGlobal* g, DefId incomingDef) DefId DataFlowGraphBuilder::visitLValue(AstExprGlobal* g, DefId incomingDef)
{ {
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED(); DfgScope* scope = currentScope();
// In order to avoid alias tracking, we need to clip the reference to the parent def. // In order to avoid alias tracking, we need to clip the reference to the parent def.
if (scope->canUpdateDefinition(g->name)) if (scope->canUpdateDefinition(g->name))
{ {
DefId updated = defArena->freshCell(g->name, g->location, containsSubscriptedDefinition(incomingDef)); DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
scope->bindings[g->name] = updated; scope->bindings[g->name] = updated;
captures[g->name].allVersions.push_back(updated); captures[g->name].allVersions.push_back(updated);
return updated; return updated;
@ -1210,10 +1121,10 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprIndexName* i, DefId incomingDef)
{ {
DefId parentDef = visitExpr(i->expr).def; DefId parentDef = visitExpr(i->expr).def;
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED(); DfgScope* scope = currentScope();
if (scope->canUpdateDefinition(parentDef, i->index.value)) if (scope->canUpdateDefinition(parentDef, i->index.value))
{ {
DefId updated = defArena->freshCell(i->index, i->location, containsSubscriptedDefinition(incomingDef)); DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
scope->props[parentDef][i->index.value] = updated; scope->props[parentDef][i->index.value] = updated;
return updated; return updated;
} }
@ -1226,12 +1137,12 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprIndexExpr* i, DefId incomingDef)
DefId parentDef = visitExpr(i->expr).def; DefId parentDef = visitExpr(i->expr).def;
visitExpr(i->index); visitExpr(i->index);
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED(); DfgScope* scope = currentScope();
if (auto string = i->index->as<AstExprConstantString>()) if (auto string = i->index->as<AstExprConstantString>())
{ {
if (scope->canUpdateDefinition(parentDef, string->value.data)) if (scope->canUpdateDefinition(parentDef, string->value.data))
{ {
DefId updated = defArena->freshCell(Symbol{}, i->location, containsSubscriptedDefinition(incomingDef)); DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
scope->props[parentDef][string->value.data] = updated; scope->props[parentDef][string->value.data] = updated;
return updated; return updated;
} }
@ -1239,7 +1150,7 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprIndexExpr* i, DefId incomingDef)
return visitExpr(static_cast<AstExpr*>(i)).def; return visitExpr(static_cast<AstExpr*>(i)).def;
} }
else else
return defArena->freshCell(Symbol{}, i->location, /*subscripted=*/true); return defArena->freshCell(/*subscripted=*/true);
} }
DefId DataFlowGraphBuilder::visitLValue(AstExprError* error, DefId incomingDef) DefId DataFlowGraphBuilder::visitLValue(AstExprError* error, DefId incomingDef)
@ -1303,10 +1214,7 @@ void DataFlowGraphBuilder::visitType(AstTypeFunction* f)
visitGenerics(f->generics); visitGenerics(f->generics);
visitGenericPacks(f->genericPacks); visitGenericPacks(f->genericPacks);
visitTypeList(f->argTypes); visitTypeList(f->argTypes);
if (FFlag::LuauStoreReturnTypesAsPackOnAst) visitTypeList(f->returnTypes);
visitTypePack(f->returnTypes);
else
visitTypeList(f->returnTypes_DEPRECATED);
} }
void DataFlowGraphBuilder::visitType(AstTypeTypeof* t) void DataFlowGraphBuilder::visitType(AstTypeTypeof* t)

View file

@ -36,9 +36,9 @@ void collectOperands(DefId def, std::vector<DefId>* operands)
} }
} }
DefId DefArena::freshCell(Symbol sym, Location location, bool subscripted) DefId DefArena::freshCell(bool subscripted)
{ {
return NotNull{allocator.allocate(Def{Cell{subscripted}, sym, location})}; return NotNull{allocator.allocate(Def{Cell{subscripted}})};
} }
DefId DefArena::phi(DefId a, DefId b) DefId DefArena::phi(DefId a, DefId b)

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 diffExternType(DifferEnvironment& env, TypeId left, TypeId right); static DifferResult diffClass(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 diffExternType(DifferEnvironment& env, TypeId left, TypeId right) static DifferResult diffClass(DifferEnvironment& env, TypeId left, TypeId right)
{ {
const ExternType* leftExternType = get<ExternType>(left); const ClassType* leftClass = get<ClassType>(left);
const ExternType* rightExternType = get<ExternType>(right); const ClassType* rightClass = get<ClassType>(right);
LUAU_ASSERT(leftExternType); LUAU_ASSERT(leftClass);
LUAU_ASSERT(rightExternType); LUAU_ASSERT(rightClass);
if (leftExternType == rightExternType) if (leftClass == rightClass)
{ {
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<ExternType>(left)) else if (auto lc = get<ClassType>(left))
{ {
return diffExternType(env, left, right); return diffClass(env, left, right);
} }
throw InternalCompilerError{"Unimplemented Simple TypeId variant for diffing"}; 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<ExternType>(ty) || return get<PrimitiveType>(ty) || get<SingletonType>(ty) || get<AnyType>(ty) || get<NegationType>(ty) || get<ClassType>(ty) ||
get<UnknownType>(ty) || get<NeverType>(ty); get<UnknownType>(ty) || get<NeverType>(ty);
} }

View file

@ -1,8 +1,7 @@
// 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) LUAU_FASTFLAGVARIABLE(LuauDebugInfoDefn)
LUAU_FASTFLAG(LuauTypeFunOptional)
namespace Luau namespace Luau
{ {
@ -216,6 +215,15 @@ declare debug: {
)BUILTIN_SRC"; )BUILTIN_SRC";
static const std::string kBuiltinDefinitionDebugSrc_DEPRECATED = R"BUILTIN_SRC(
declare debug: {
info: (<R...>(thread: thread, level: number, options: string) -> R...) & (<R...>(level: number, options: string) -> R...) & (<A..., R1..., R2...>(func: (A...) -> R1..., options: string) -> R2...),
traceback: ((message: string?, level: number?) -> string) & ((thread: thread, message: string?, level: number?) -> string),
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionUtf8Src = R"BUILTIN_SRC( static const std::string kBuiltinDefinitionUtf8Src = R"BUILTIN_SRC(
declare utf8: { declare utf8: {
@ -262,37 +270,7 @@ declare buffer: {
)BUILTIN_SRC"; )BUILTIN_SRC";
static const std::string kBuiltinDefinitionVectorSrc = (FFlag::LuauDeclareExternType) static const std::string kBuiltinDefinitionVectorSrc = R"BUILTIN_SRC(
? 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
@ -331,7 +309,7 @@ std::string getBuiltinDefinitionSource()
result += kBuiltinDefinitionOsSrc; result += kBuiltinDefinitionOsSrc;
result += kBuiltinDefinitionCoroutineSrc; result += kBuiltinDefinitionCoroutineSrc;
result += kBuiltinDefinitionTableSrc; result += kBuiltinDefinitionTableSrc;
result += kBuiltinDefinitionDebugSrc; result += FFlag::LuauDebugInfoDefn ? kBuiltinDefinitionDebugSrc : kBuiltinDefinitionDebugSrc_DEPRECATED;
result += kBuiltinDefinitionUtf8Src; result += kBuiltinDefinitionUtf8Src;
result += kBuiltinDefinitionBufferSrc; result += kBuiltinDefinitionBufferSrc;
result += kBuiltinDefinitionVectorSrc; result += kBuiltinDefinitionVectorSrc;
@ -340,11 +318,11 @@ std::string getBuiltinDefinitionSource()
} }
// TODO: split into separate tagged unions when the new solver can appropriately handle that. // TODO: split into separate tagged unions when the new solver can appropriately handle that.
static const std::string kBuiltinDefinitionTypeMethodSrc = R"BUILTIN_SRC( static const std::string kBuiltinDefinitionTypesSrc = R"BUILTIN_SRC(
export type type = { export type type = {
tag: "nil" | "unknown" | "never" | "any" | "boolean" | "number" | "string" | "buffer" | "thread" | tag: "nil" | "unknown" | "never" | "any" | "boolean" | "number" | "string" | "buffer" | "thread" |
"singleton" | "negation" | "union" | "intersection" | "table" | "function" | "class" | "generic", "singleton" | "negation" | "union" | "intesection" | "table" | "function" | "class" | "generic",
is: (self: type, arg: string) -> boolean, is: (self: type, arg: string) -> boolean,
@ -391,10 +369,6 @@ export type type = {
ispack: (self: type) -> boolean, ispack: (self: type) -> boolean,
} }
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionTypesLibSrc = R"BUILTIN_SRC(
declare types: { declare types: {
unknown: type, unknown: type,
never: type, never: type,
@ -414,44 +388,12 @@ declare types: {
newfunction: @checked (parameters: { head: {type}?, tail: type? }?, returns: { head: {type}?, tail: type? }?, generics: {type}?) -> type, newfunction: @checked (parameters: { head: {type}?, tail: type? }?, returns: { head: {type}?, tail: type? }?, generics: {type}?) -> type,
copy: @checked (arg: type) -> type, copy: @checked (arg: type) -> type,
} }
)BUILTIN_SRC"; )BUILTIN_SRC";
static const std::string kBuiltinDefinitionTypesLibWithOptionalSrc = R"BUILTIN_SRC(
declare types: {
unknown: type,
never: type,
any: type,
boolean: type,
number: type,
string: type,
thread: type,
buffer: type,
singleton: @checked (arg: string | boolean | nil) -> type,
optional: @checked (arg: type) -> type,
generic: @checked (name: string, ispack: boolean?) -> type,
negationof: @checked (arg: type) -> type,
unionof: @checked (...type) -> type,
intersectionof: @checked (...type) -> type,
newtable: @checked (props: {[type]: type} | {[type]: { read: type, write: type } } | nil, indexer: { index: type, readresult: type, writeresult: type }?, metatable: type?) -> type,
newfunction: @checked (parameters: { head: {type}?, tail: type? }?, returns: { head: {type}?, tail: type? }?, generics: {type}?) -> type,
copy: @checked (arg: type) -> type,
}
)BUILTIN_SRC";
std::string getTypeFunctionDefinitionSource() std::string getTypeFunctionDefinitionSource()
{ {
return kBuiltinDefinitionTypesSrc;
std::string result = kBuiltinDefinitionTypeMethodSrc;
if (FFlag::LuauTypeFunOptional)
result += kBuiltinDefinitionTypesLibWithOptionalSrc;
else
result += kBuiltinDefinitionTypesLibSrc;
return result;
} }
} // namespace Luau } // namespace Luau

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->externType) else if (ty == builtinTypes->classType)
return egraph.add(TTopClass{}); return egraph.add(TTopClass{});
else if (get<ExternType>(ty)) else if (get<ClassType>(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->externType; return builtinTypes->classType;
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 ExternType* ct = get<ExternType>(cls->value()); const ClassType* ct = get<ClassType>(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 ExternType* leftExternType = Luau::get<ExternType>(leftClass->value()); const ClassType* leftClassType = Luau::get<ClassType>(leftClass->value());
const ExternType* rightExternType = Luau::get<ExternType>(rightClass->value()); const ClassType* rightClassType = Luau::get<ClassType>(rightClass->value());
if (isSubclass(leftExternType, rightExternType)) if (isSubclass(leftClassType, rightClassType))
return RightSuper; return RightSuper;
else if (isSubclass(rightExternType, leftExternType)) else if (isSubclass(rightClassType, leftClassType))
return LeftSuper; return LeftSuper;
else else
return Unrelated; return Unrelated;

View file

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

View file

@ -10,6 +10,10 @@
#include <string_view> #include <string_view>
#include <utility> #include <utility>
LUAU_FASTFLAGVARIABLE(LuauExposeRequireByStringAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauEscapeCharactersInRequireSuggestions)
LUAU_FASTFLAGVARIABLE(LuauHideImpossibleRequireSuggestions)
namespace Luau namespace Luau
{ {
@ -18,10 +22,13 @@ 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;
} }
@ -105,11 +112,13 @@ 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();
@ -154,6 +163,9 @@ std::optional<RequireSuggestions> RequireSuggester::getRequireSuggestions(const
std::optional<RequireSuggestions> FileResolver::getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& path) const 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

@ -22,25 +22,26 @@
#include "Luau/Module.h" #include "Luau/Module.h"
#include "Luau/Clone.h" #include "Luau/Clone.h"
#include "AutocompleteCore.h" #include "AutocompleteCore.h"
#include <optional>
LUAU_FASTINT(LuauTypeInferRecursionLimit); LUAU_FASTINT(LuauTypeInferRecursionLimit);
LUAU_FASTINT(LuauTypeInferIterationLimit); LUAU_FASTINT(LuauTypeInferIterationLimit);
LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(DebugLogFragmentsFromAutocomplete) LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteBugfixes)
LUAU_FASTFLAGVARIABLE(LuauMixedModeDefFinderTraversesTypeOf)
LUAU_FASTFLAGVARIABLE(LuauCloneIncrementalModule)
LUAU_FASTFLAGVARIABLE(LogFragmentsFromAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauBetterCursorInCommentDetection) LUAU_FASTFLAGVARIABLE(LuauBetterCursorInCommentDetection)
LUAU_FASTFLAGVARIABLE(LuauAllFreeTypesHaveScopes) LUAU_FASTFLAGVARIABLE(LuauAllFreeTypesHaveScopes)
LUAU_FASTFLAGVARIABLE(LuauFragmentAcSupportsReporter)
LUAU_FASTFLAGVARIABLE(LuauPersistConstraintGenerationScopes) LUAU_FASTFLAGVARIABLE(LuauPersistConstraintGenerationScopes)
LUAU_FASTFLAG(LuauModuleHoldsAstRoot)
LUAU_FASTFLAGVARIABLE(LuauCloneTypeAliasBindings) LUAU_FASTFLAGVARIABLE(LuauCloneTypeAliasBindings)
LUAU_FASTFLAGVARIABLE(LuauCloneReturnTypePack)
LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteDemandBasedCloning) LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteDemandBasedCloning)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauFragmentNoTypeFunEval) LUAU_FASTFLAGVARIABLE(LuauFragmentNoTypeFunEval)
LUAU_FASTFLAGVARIABLE(LuauBetterScopeSelection)
LUAU_FASTFLAGVARIABLE(LuauBlockDiffFragmentSelection)
LUAU_FASTFLAGVARIABLE(LuauFragmentAcMemoryLeak)
LUAU_FASTFLAGVARIABLE(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace namespace
{ {
@ -86,437 +87,6 @@ void cloneModuleMap(
} }
} }
static std::pair<size_t, size_t> getDocumentOffsets(std::string_view src, const Position& startPos, const Position& endPos);
// when typing a function partially, get the span of the first line
// e.g. local function fn() : ... - typically we want to provide autocomplete results if you're
// editing type annotations in this range
Location getFunctionDeclarationExtents(AstExprFunction* exprFn, AstExpr* exprName = nullptr, AstLocal* localName = nullptr)
{
auto fnBegin = exprFn->location.begin;
auto fnEnd = exprFn->location.end;
if (auto returnAnnot = exprFn->returnAnnotation; FFlag::LuauStoreReturnTypesAsPackOnAst && returnAnnot)
{
fnEnd = returnAnnot->location.end;
}
else if (auto returnAnnot = exprFn->returnAnnotation_DEPRECATED; !FFlag::LuauStoreReturnTypesAsPackOnAst && returnAnnot)
{
if (returnAnnot->tailType)
fnEnd = returnAnnot->tailType->location.end;
else if (returnAnnot->types.size != 0)
fnEnd = returnAnnot->types.data[returnAnnot->types.size - 1]->location.end;
}
else if (exprFn->args.size != 0)
{
auto last = exprFn->args.data[exprFn->args.size - 1];
if (last->annotation)
fnEnd = last->annotation->location.end;
else
fnEnd = last->location.end;
}
else if (exprFn->genericPacks.size != 0)
fnEnd = exprFn->genericPacks.data[exprFn->genericPacks.size - 1]->location.end;
else if (exprFn->generics.size != 0)
fnEnd = exprFn->generics.data[exprFn->generics.size - 1]->location.end;
else if (exprName)
fnEnd = exprName->location.end;
else if (localName)
fnEnd = localName->location.end;
return Location{fnBegin, fnEnd};
};
Location getAstStatForExtents(AstStatFor* forStat)
{
auto begin = forStat->location.begin;
auto end = forStat->location.end;
if (forStat->step)
end = forStat->step->location.end;
else if (forStat->to)
end = forStat->to->location.end;
else if (forStat->from)
end = forStat->from->location.end;
else if (forStat->var)
end = forStat->var->location.end;
return Location{begin, end};
}
Location getFragmentLocation(AstStat* nearestStatement, const Position& cursorPosition)
{
Location empty{cursorPosition, cursorPosition};
if (nearestStatement)
{
Location nonEmpty{nearestStatement->location.begin, cursorPosition};
// If your sibling is a do block, do nothing
if (auto doEnd = nearestStatement->as<AstStatBlock>())
return empty;
// If you're inside the body of the function and this is your sibling, empty fragment
// If you're outside the body (e.g. you're typing stuff out, non-empty)
if (auto fn = nearestStatement->as<AstStatFunction>())
{
auto loc = getFunctionDeclarationExtents(fn->func, fn->name, /* local */ nullptr);
if (loc.containsClosed(cursorPosition))
return nonEmpty;
else if (fn->func->body->location.containsClosed(cursorPosition) || fn->location.end <= cursorPosition)
return empty;
else if (fn->func->location.contains(cursorPosition))
return nonEmpty;
}
if (auto fn = nearestStatement->as<AstStatLocalFunction>())
{
auto loc = getFunctionDeclarationExtents(fn->func, /* global func */ nullptr, fn->name);
if (loc.containsClosed(cursorPosition))
return nonEmpty;
else if (fn->func->body->location.containsClosed(cursorPosition) || fn->location.end <= cursorPosition)
return empty;
else if (fn->func->location.contains(cursorPosition))
return nonEmpty;
}
if (auto wh = nearestStatement->as<AstStatWhile>())
{
if (!wh->hasDo)
return nonEmpty;
else
return empty;
}
if (auto forStat = nearestStatement->as<AstStatFor>())
{
if (!forStat->hasDo)
return nonEmpty;
else
return empty;
}
if (auto forIn = nearestStatement->as<AstStatForIn>())
{
// If we don't have a do statement
if (!forIn->hasDo)
return nonEmpty;
else
return empty;
}
if (auto ifS = nearestStatement->as<AstStatIf>())
{
auto conditionExtents = Location{ifS->location.begin, ifS->condition->location.end};
if (conditionExtents.containsClosed(cursorPosition))
return nonEmpty;
else if (ifS->thenbody->location.containsClosed(cursorPosition))
return empty;
else if (auto elseS = ifS->elsebody)
{
if (auto elseIf = ifS->elsebody->as<AstStatIf>())
{
if (elseIf->thenbody->hasEnd)
return empty;
else
return {elseS->location.begin, cursorPosition};
}
return empty;
}
}
return nonEmpty;
}
return empty;
}
struct NearestStatementFinder : public AstVisitor
{
explicit NearestStatementFinder(const Position& cursorPosition)
: cursor(cursorPosition)
{
}
bool visit(AstStatBlock* block) override
{
if (block->location.containsClosed(cursor))
{
parent = block;
for (auto v : block->body)
{
if (v->location.begin <= cursor)
{
nearest = v;
}
}
return true;
}
else
return false;
}
const Position& cursor;
AstStat* nearest = nullptr;
AstStatBlock* parent = nullptr;
};
// This struct takes a block found in a updated AST and looks for the corresponding block in a different ast.
// This is a best effort check - we are looking for the block that is as close in location, ideally the same
// block as the one from the updated AST
struct NearestLikelyBlockFinder : public AstVisitor
{
explicit NearestLikelyBlockFinder(NotNull<AstStatBlock> stmtBlockRecentAst)
: stmtBlockRecentAst(stmtBlockRecentAst)
{
}
bool visit(AstStatBlock* block) override
{
if (block->location.begin <= stmtBlockRecentAst->location.begin)
{
if (found)
{
if (found.value()->location.begin < block->location.begin)
found.emplace(block);
}
else
{
found.emplace(block);
}
}
return true;
}
NotNull<AstStatBlock> stmtBlockRecentAst;
std::optional<AstStatBlock*> found = std::nullopt;
};
// Diffs two ast stat blocks. Once at the first difference, consume between that range and the end of the nearest statement
std::optional<Position> blockDiffStart(AstStatBlock* blockOld, AstStatBlock* blockNew, AstStat* nearestStatementNewAst)
{
AstArray<AstStat*> _old = blockOld->body;
AstArray<AstStat*> _new = blockNew->body;
size_t oldSize = _old.size;
size_t stIndex = 0;
// We couldn't find a nearest statement
if (nearestStatementNewAst == blockNew)
return std::nullopt;
bool found = false;
for (auto st : _new)
{
if (st == nearestStatementNewAst)
{
found = true;
break;
}
stIndex++;
}
if (!found)
return std::nullopt;
// Take care of some easy cases!
if (oldSize == 0 && _new.size >= 0)
return {_new.data[0]->location.begin};
if (_new.size < oldSize)
return std::nullopt;
for (size_t i = 0; i < std::min(oldSize, stIndex + 1); i++)
{
AstStat* oldStat = _old.data[i];
AstStat* newStat = _new.data[i];
bool isSame = oldStat->classIndex == newStat->classIndex && oldStat->location == newStat->location;
if (!isSame)
return {oldStat->location.begin};
}
if (oldSize <= stIndex)
return {_new.data[oldSize]->location.begin};
return std::nullopt;
}
FragmentRegion getFragmentRegion(AstStatBlock* root, const Position& cursorPosition)
{
NearestStatementFinder nsf{cursorPosition};
root->visit(&nsf);
AstStatBlock* parent = root;
if (nsf.parent)
parent = nsf.parent;
return FragmentRegion{getFragmentLocation(nsf.nearest, cursorPosition), nsf.nearest, parent};
};
FragmentRegion getFragmentRegionWithBlockDiff(AstStatBlock* stale, AstStatBlock* fresh, const Position& cursorPos)
{
// Visit the new ast
NearestStatementFinder nsf{cursorPos};
fresh->visit(&nsf);
// parent must always be non-null
NotNull<AstStatBlock> parent{nsf.parent ? nsf.parent : fresh};
NotNull<AstStat> nearest{nsf.nearest ? nsf.nearest : fresh};
// Grab the same start block in the stale ast
NearestLikelyBlockFinder lsf{parent};
stale->visit(&lsf);
if (auto sameBlock = lsf.found)
{
if (std::optional<Position> fd = blockDiffStart(*sameBlock, parent, nearest))
return FragmentRegion{Location{*fd, cursorPos}, nearest, parent};
}
return FragmentRegion{getFragmentLocation(nsf.nearest, cursorPos), nearest, parent};
}
FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* stale, const Position& cursorPos, AstStatBlock* lastGoodParse)
{
// the freshest ast can sometimes be null if the parse was bad.
if (lastGoodParse == nullptr)
return {};
FragmentRegion region = FFlag::LuauBlockDiffFragmentSelection ? getFragmentRegionWithBlockDiff(stale, lastGoodParse, cursorPos)
: getFragmentRegion(lastGoodParse, cursorPos);
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(stale, cursorPos);
LUAU_ASSERT(ancestry.size() >= 1);
// We should only pick up locals that are before the region
DenseHashMap<AstName, AstLocal*> localMap{AstName()};
std::vector<AstLocal*> localStack;
for (AstNode* node : ancestry)
{
if (auto block = node->as<AstStatBlock>())
{
for (auto stat : block->body)
{
if (stat->location.begin < region.fragmentLocation.begin)
{
// This statement precedes the current one
if (auto statLoc = stat->as<AstStatLocal>())
{
for (auto v : statLoc->vars)
{
localStack.push_back(v);
localMap[v->name] = v;
}
}
else if (auto locFun = stat->as<AstStatLocalFunction>())
{
localStack.push_back(locFun->name);
localMap[locFun->name->name] = locFun->name;
if (locFun->location.contains(cursorPos))
{
for (AstLocal* loc : locFun->func->args)
{
localStack.push_back(loc);
localMap[loc->name] = loc;
}
}
}
else if (auto globFun = stat->as<AstStatFunction>())
{
if (globFun->location.contains(cursorPos))
{
for (AstLocal* loc : globFun->func->args)
{
localStack.push_back(loc);
localMap[loc->name] = loc;
}
}
}
else if (auto typeFun = stat->as<AstStatTypeFunction>(); typeFun)
{
if (typeFun->location.contains(cursorPos))
{
for (AstLocal* loc : typeFun->body->args)
{
localStack.push_back(loc);
localMap[loc->name] = loc;
}
}
}
else if (auto forL = stat->as<AstStatFor>())
{
if (forL->var && forL->var->location.begin < region.fragmentLocation.begin)
{
localStack.push_back(forL->var);
localMap[forL->var->name] = forL->var;
}
}
else if (auto forIn = stat->as<AstStatForIn>())
{
for (auto var : forIn->vars)
{
if (var->location.begin < region.fragmentLocation.begin)
{
localStack.push_back(var);
localMap[var->name] = var;
}
}
}
}
}
}
if (auto exprFunc = node->as<AstExprFunction>())
{
if (exprFunc->location.contains(cursorPos))
{
for (auto v : exprFunc->args)
{
localStack.push_back(v);
localMap[v->name] = v;
}
}
}
}
return {localMap, localStack, ancestry, region.nearestStatement, region.parentBlock, region.fragmentLocation};
}
std::optional<FragmentParseResult> parseFragment(
AstStatBlock* stale,
AstStatBlock* mostRecentParse,
AstNameTable* names,
std::string_view src,
const Position& cursorPos,
std::optional<Position> fragmentEndPosition
)
{
if (mostRecentParse == nullptr)
return std::nullopt;
FragmentAutocompleteAncestryResult result = findAncestryForFragmentParse(stale, cursorPos, mostRecentParse);
AstStat* nearestStatement = result.nearestStatement;
Position startPos = result.fragmentSelectionRegion.begin;
Position endPos = fragmentEndPosition.value_or(result.fragmentSelectionRegion.end);
auto [offsetStart, parseLength] = getDocumentOffsets(src, startPos, endPos);
const char* srcStart = src.data() + offsetStart;
std::string_view dbg = src.substr(offsetStart, parseLength);
FragmentParseResult fragmentResult;
fragmentResult.fragmentToParse = std::string(dbg);
// For the duration of the incremental parse, we want to allow the name table to re-use duplicate names
if (FFlag::DebugLogFragmentsFromAutocomplete)
logLuau("Fragment Selected", dbg);
ParseOptions opts;
opts.allowDeclarationSyntax = false;
opts.captureComments = true;
opts.parseFragment = FragmentParseResumeSettings{std::move(result.localMap), std::move(result.localStack), startPos};
ParseResult p = Luau::Parser::parse(srcStart, parseLength, *names, *fragmentResult.alloc, opts);
// This means we threw a ParseError and we should decline to offer autocomplete here.
if (p.root == nullptr)
return std::nullopt;
std::vector<AstNode*> fabricatedAncestry = std::move(result.ancestry);
std::vector<AstNode*> fragmentAncestry = findAncestryAtPositionForAutocomplete(p.root, cursorPos);
fabricatedAncestry.insert(fabricatedAncestry.end(), fragmentAncestry.begin(), fragmentAncestry.end());
if (nearestStatement == nullptr)
nearestStatement = p.root;
fragmentResult.root = p.root;
fragmentResult.ancestry = std::move(fabricatedAncestry);
fragmentResult.nearestStatement = nearestStatement;
fragmentResult.commentLocations = std::move(p.commentLocations);
fragmentResult.scopePos = result.parentBlock->location.begin;
return fragmentResult;
}
struct UsageFinder : public AstVisitor struct UsageFinder : public AstVisitor
{ {
@ -544,11 +114,6 @@ 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));
@ -576,32 +141,12 @@ struct UsageFinder : public AstVisitor
return true; return true;
} }
bool visit(AstExprGlobal* global) override
{
if (FFlag::LuauGlobalVariableModuleIsolation)
globalDefsToPrePopulate.emplace_back(global->name, dfg->getDef(global));
return true;
}
bool visit(AstStatFunction* function) override
{
if (FFlag::LuauGlobalVariableModuleIsolation)
{
if (AstExprGlobal* g = function->name->as<AstExprGlobal>())
globalFunctionsReferenced.emplace_back(g->name);
}
return true;
}
NotNull<DataFlowGraph> dfg; NotNull<DataFlowGraph> dfg;
DenseHashSet<Name> declaredAliases{""}; DenseHashSet<Name> declaredAliases{""};
std::vector<std::pair<const Def*, AstLocal*>> localBindingsReferenced; std::vector<std::pair<const Def*, AstLocal*>> localBindingsReferenced;
DenseHashSet<const Def*> mentionedDefs{nullptr}; DenseHashSet<const Def*> mentionedDefs{nullptr};
std::vector<Name> referencedBindings{""}; std::vector<Name> referencedBindings{""};
std::vector<std::pair<Name, Name>> referencedImportedBindings{{"", ""}}; std::vector<std::pair<Name, Name>> referencedImportedBindings{{"", ""}};
std::vector<std::pair<AstName, const Def*>> globalDefsToPrePopulate;
std::vector<AstName> globalFunctionsReferenced;
}; };
// Runs the `UsageFinder` traversal on the fragment and grabs all of the types that are // Runs the `UsageFinder` traversal on the fragment and grabs all of the types that are
@ -613,7 +158,6 @@ void cloneTypesFromFragment(
const ModulePtr& staleModule, const ModulePtr& staleModule,
NotNull<TypeArena> destArena, NotNull<TypeArena> destArena,
NotNull<DataFlowGraph> dfg, NotNull<DataFlowGraph> dfg,
NotNull<BuiltinTypes> builtins,
AstStatBlock* program, AstStatBlock* program,
Scope* destScope Scope* destScope
) )
@ -644,13 +188,6 @@ void cloneTypesFromFragment(
destScope->lvalueTypes[d] = Luau::cloneIncremental(pair->second.typeId, *destArena, cloneState, destScope); destScope->lvalueTypes[d] = Luau::cloneIncremental(pair->second.typeId, *destArena, cloneState, destScope);
destScope->bindings[pair->first] = Luau::cloneIncremental(pair->second, *destArena, cloneState, destScope); destScope->bindings[pair->first] = Luau::cloneIncremental(pair->second, *destArena, cloneState, destScope);
} }
else if (FFlag::LuauBetterScopeSelection && !FFlag::LuauBlockDiffFragmentSelection)
{
destScope->lvalueTypes[d] = builtins->unknownType;
Binding b;
b.typeId = builtins->unknownType;
destScope->bindings[Symbol(loc)] = b;
}
} }
// Second - any referenced type alias bindings need to be placed in scope so type annotation can be resolved. // Second - any referenced type alias bindings need to be placed in scope so type annotation can be resolved.
@ -675,45 +212,7 @@ void cloneTypesFromFragment(
} }
} }
if (FFlag::LuauGlobalVariableModuleIsolation) // Finally - clone the returnType on the staleScope. This helps avoid potential leaks of free types.
{
// Fourth - prepopulate the global function types
for (const auto& name : f.globalFunctionsReferenced)
{
if (auto ty = staleModule->getModuleScope()->lookup(name))
{
destScope->bindings[name] = Binding{Luau::cloneIncremental(*ty, *destArena, cloneState, destScope)};
}
else
{
TypeId bt = destArena->addType(BlockedType{});
destScope->bindings[name] = Binding{bt};
}
}
// Fifth - prepopulate the globals here
for (const auto& [name, def] : f.globalDefsToPrePopulate)
{
if (auto ty = staleModule->getModuleScope()->lookup(name))
{
destScope->lvalueTypes[def] = Luau::cloneIncremental(*ty, *destArena, cloneState, destScope);
}
else if (auto ty = destScope->lookup(name))
{
// This branch is a little strange - we are looking up a symbol in the destScope
// This scope has no parent pointer, and only cloned types are written to it, so this is a
// safe operation to do without cloning.
// The reason we do this, is the usage finder will traverse the global functions referenced first
// If there is no name associated with this function at the global scope, it must appear first in the fragment and we must
// create a blocked type for it. We write this blocked type directly into the `destScope` bindings
// Then when we go to traverse the `AstExprGlobal` associated with this function, we need to ensure that we map the def -> blockedType
// in `lvalueTypes`, which was previously written into `destScope`
destScope->lvalueTypes[def] = *ty;
}
}
}
// Finally, clone the returnType on the staleScope. This helps avoid potential leaks of free types.
if (staleScope->returnType) if (staleScope->returnType)
destScope->returnType = Luau::cloneIncremental(staleScope->returnType, *destArena, cloneState, destScope); destScope->returnType = Luau::cloneIncremental(staleScope->returnType, *destArena, cloneState, destScope);
} }
@ -735,7 +234,7 @@ struct MixedModeIncrementalTCDefFinder : public AstVisitor
// requires that we find the local/global `m` and place it in the environment. // requires that we find the local/global `m` and place it in the environment.
// The default behaviour here is to return false, and have individual visitors override // The default behaviour here is to return false, and have individual visitors override
// the specific behaviour they need. // the specific behaviour they need.
return true; return FFlag::LuauMixedModeDefFinderTraversesTypeOf;
} }
bool visit(AstStatTypeAlias* alias) override bool visit(AstStatTypeAlias* alias) override
@ -885,7 +384,7 @@ void cloneAndSquashScopes(
} }
} }
if (destScope->returnType) if (FFlag::LuauCloneReturnTypePack && destScope->returnType)
destScope->returnType = Luau::cloneIncremental(destScope->returnType, *destArena, cloneState, destScope); destScope->returnType = Luau::cloneIncremental(destScope->returnType, *destArena, cloneState, destScope);
return; return;
@ -901,10 +400,15 @@ static FrontendModuleResolver& getModuleResolver(Frontend& frontend, std::option
bool statIsBeforePos(const AstNode* stat, const Position& cursorPos) bool statIsBeforePos(const AstNode* stat, const Position& cursorPos)
{ {
if (FFlag::LuauIncrementalAutocompleteBugfixes)
{
return (stat->location.begin < cursorPos); return (stat->location.begin < cursorPos);
}
return stat->location.begin < cursorPos && stat->location.begin.line < cursorPos.line;
} }
FragmentAutocompleteAncestryResult findAncestryForFragmentParse_DEPRECATED(AstStatBlock* root, const Position& cursorPos) FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* root, const Position& cursorPos)
{ {
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(root, cursorPos); std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(root, cursorPos);
// Should always contain the root AstStat // Should always contain the root AstStat
@ -933,7 +437,7 @@ FragmentAutocompleteAncestryResult findAncestryForFragmentParse_DEPRECATED(AstSt
{ {
for (auto stat : block->body) for (auto stat : block->body)
{ {
if (statIsBeforePos(stat, nearestStatement->location.begin)) if (statIsBeforePos(stat, FFlag::LuauIncrementalAutocompleteBugfixes ? nearestStatement->location.begin : cursorPos))
{ {
// This statement precedes the current one // This statement precedes the current one
if (auto statLoc = stat->as<AstStatLocal>()) if (auto statLoc = stat->as<AstStatLocal>())
@ -982,6 +486,8 @@ FragmentAutocompleteAncestryResult findAncestryForFragmentParse_DEPRECATED(AstSt
} }
} }
} }
if (FFlag::LuauIncrementalAutocompleteBugfixes)
{
if (auto exprFunc = node->as<AstExprFunction>()) if (auto exprFunc = node->as<AstExprFunction>())
{ {
if (exprFunc->location.contains(cursorPos)) if (exprFunc->location.contains(cursorPos))
@ -994,6 +500,7 @@ FragmentAutocompleteAncestryResult findAncestryForFragmentParse_DEPRECATED(AstSt
} }
} }
} }
}
return {std::move(localMap), std::move(localStack), std::move(ancestry), std::move(nearestStatement)}; return {std::move(localMap), std::move(localStack), std::move(ancestry), std::move(nearestStatement)};
} }
@ -1006,7 +513,7 @@ FragmentAutocompleteAncestryResult findAncestryForFragmentParse_DEPRECATED(AstSt
* Example - your document is "foo bar baz" and getDocumentOffsets is passed (0, 4), (0, 8). This function returns the pair {3, 5} * Example - your document is "foo bar baz" and getDocumentOffsets is passed (0, 4), (0, 8). This function returns the pair {3, 5}
* which corresponds to the string " bar " * which corresponds to the string " bar "
*/ */
static std::pair<size_t, size_t> getDocumentOffsets(std::string_view src, const Position& startPos, const Position& endPos) std::pair<size_t, size_t> getDocumentOffsets(const std::string_view& src, const Position& startPos, const Position& endPos)
{ {
size_t lineCount = 0; size_t lineCount = 0;
size_t colCount = 0; size_t colCount = 0;
@ -1063,14 +570,14 @@ static std::pair<size_t, size_t> getDocumentOffsets(std::string_view src, const
return {min, len}; return {min, len};
} }
ScopePtr findClosestScope_DEPRECATED(const ModulePtr& module, const AstStat* nearestStatement) ScopePtr findClosestScope(const ModulePtr& module, const AstStat* nearestStatement)
{ {
LUAU_ASSERT(module->hasModuleScope()); LUAU_ASSERT(module->hasModuleScope());
ScopePtr closest = module->getModuleScope(); ScopePtr closest = module->getModuleScope();
// find the scope the nearest statement belonged to. // find the scope the nearest statement belonged to.
for (const auto& [loc, sc] : module->scopes) for (auto [loc, sc] : module->scopes)
{ {
if (loc.encloses(nearestStatement->location) && closest->location.begin <= loc.begin) if (loc.encloses(nearestStatement->location) && closest->location.begin <= loc.begin)
closest = sc; closest = sc;
@ -1079,38 +586,7 @@ ScopePtr findClosestScope_DEPRECATED(const ModulePtr& module, const AstStat* nea
return closest; return closest;
} }
ScopePtr findClosestScope(const ModulePtr& module, const Position& scopePos) std::optional<FragmentParseResult> parseFragment(
{
LUAU_ASSERT(module->hasModuleScope());
if (FFlag::LuauBlockDiffFragmentSelection)
{
ScopePtr closest = module->getModuleScope();
// find the scope the nearest statement belonged to.
for (const auto& [loc, sc] : module->scopes)
{
// We bias towards the later scopes because those correspond to inner scopes.
// in the case of if statements, we create two scopes at the same location for the body of the then
// and else branches, so we need to bias later. This is why the closest update condition has a <=
// instead of a <
if (sc->location.contains(scopePos) && closest->location.begin <= sc->location.begin)
closest = sc;
}
return closest;
}
else
{
ScopePtr closest = module->getModuleScope();
// find the scope the nearest statement belonged to.
for (const auto& [loc, sc] : module->scopes)
{
if (sc->location.contains(scopePos) && closest->location.begin < sc->location.begin)
closest = sc;
}
return closest;
}
}
std::optional<FragmentParseResult> parseFragment_DEPRECATED(
AstStatBlock* root, AstStatBlock* root,
AstNameTable* names, AstNameTable* names,
std::string_view src, std::string_view src,
@ -1118,7 +594,7 @@ std::optional<FragmentParseResult> parseFragment_DEPRECATED(
std::optional<Position> fragmentEndPosition std::optional<Position> fragmentEndPosition
) )
{ {
FragmentAutocompleteAncestryResult result = findAncestryForFragmentParse_DEPRECATED(root, cursorPos); FragmentAutocompleteAncestryResult result = findAncestryForFragmentParse(root, cursorPos);
AstStat* nearestStatement = result.nearestStatement; AstStat* nearestStatement = result.nearestStatement;
const Location& rootSpan = root->location; const Location& rootSpan = root->location;
@ -1149,8 +625,8 @@ std::optional<FragmentParseResult> parseFragment_DEPRECATED(
FragmentParseResult fragmentResult; FragmentParseResult fragmentResult;
fragmentResult.fragmentToParse = std::string(dbg.data(), parseLength); fragmentResult.fragmentToParse = std::string(dbg.data(), parseLength);
// For the duration of the incremental parse, we want to allow the name table to re-use duplicate names // For the duration of the incremental parse, we want to allow the name table to re-use duplicate names
if (FFlag::DebugLogFragmentsFromAutocomplete) if (FFlag::LogFragmentsFromAutocomplete)
logLuau("Fragment Selected", dbg); logLuau(dbg);
ParseOptions opts; ParseOptions opts;
opts.allowDeclarationSyntax = false; opts.allowDeclarationSyntax = false;
@ -1225,6 +701,31 @@ ModulePtr cloneModule(CloneState& cloneState, const ModulePtr& source, std::uniq
return incremental; return incremental;
} }
ModulePtr copyModule(const ModulePtr& result, std::unique_ptr<Allocator> alloc)
{
ModulePtr incrementalModule = std::make_shared<Module>();
incrementalModule->name = result->name;
incrementalModule->humanReadableName = "Incremental$" + result->humanReadableName;
incrementalModule->internalTypes.owningModule = incrementalModule.get();
incrementalModule->interfaceTypes.owningModule = incrementalModule.get();
incrementalModule->allocator = std::move(alloc);
// Don't need to keep this alive (it's already on the source module)
copyModuleVec(incrementalModule->scopes, result->scopes);
copyModuleMap(incrementalModule->astTypes, result->astTypes);
copyModuleMap(incrementalModule->astTypePacks, result->astTypePacks);
copyModuleMap(incrementalModule->astExpectedTypes, result->astExpectedTypes);
// Don't need to clone astOriginalCallTypes
copyModuleMap(incrementalModule->astOverloadResolvedTypes, result->astOverloadResolvedTypes);
// Don't need to clone astForInNextTypes
copyModuleMap(incrementalModule->astForInNextTypes, result->astForInNextTypes);
// Don't need to clone astResolvedTypes
// Don't need to clone astResolvedTypePacks
// Don't need to clone upperBoundContributors
copyModuleMap(incrementalModule->astScopes, result->astScopes);
// Don't need to clone declared Globals;
return incrementalModule;
}
void mixedModeCompatibility( void mixedModeCompatibility(
const ScopePtr& bottomScopeStale, const ScopePtr& bottomScopeStale,
const ScopePtr& myFakeScope, const ScopePtr& myFakeScope,
@ -1253,7 +754,7 @@ void mixedModeCompatibility(
static void reportWaypoint(IFragmentAutocompleteReporter* reporter, FragmentAutocompleteWaypoint type) static void reportWaypoint(IFragmentAutocompleteReporter* reporter, FragmentAutocompleteWaypoint type)
{ {
if (!reporter) if (!FFlag::LuauFragmentAcSupportsReporter || !reporter)
return; return;
reporter->reportWaypoint(type); reporter->reportWaypoint(type);
@ -1261,7 +762,7 @@ static void reportWaypoint(IFragmentAutocompleteReporter* reporter, FragmentAuto
static void reportFragmentString(IFragmentAutocompleteReporter* reporter, std::string_view fragment) static void reportFragmentString(IFragmentAutocompleteReporter* reporter, std::string_view fragment)
{ {
if (!reporter) if (!FFlag::LuauFragmentAcSupportsReporter || !reporter)
return; return;
reporter->reportFragmentString(fragment); reporter->reportFragmentString(fragment);
@ -1288,8 +789,10 @@ FragmentTypeCheckResult typecheckFragmentHelper_DEPRECATED(
ModulePtr incrementalModule = nullptr; ModulePtr incrementalModule = nullptr;
if (FFlag::LuauAllFreeTypesHaveScopes) if (FFlag::LuauAllFreeTypesHaveScopes)
incrementalModule = cloneModule(cloneState, stale, std::move(astAllocator), freshChildOfNearestScope.get()); incrementalModule = cloneModule(cloneState, stale, std::move(astAllocator), freshChildOfNearestScope.get());
else else if (FFlag::LuauCloneIncrementalModule)
incrementalModule = cloneModule_DEPRECATED(cloneState, stale, std::move(astAllocator)); incrementalModule = cloneModule_DEPRECATED(cloneState, stale, std::move(astAllocator));
else
incrementalModule = copyModule(stale, std::move(astAllocator));
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneModuleEnd); reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneModuleEnd);
incrementalModule->checkedInNewSolver = true; incrementalModule->checkedInNewSolver = true;
@ -1343,6 +846,8 @@ FragmentTypeCheckResult typecheckFragmentHelper_DEPRECATED(
}; };
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeStart); reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeStart);
if (FFlag::LuauCloneIncrementalModule)
{
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope); incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
cg.rootScope = freshChildOfNearestScope.get(); cg.rootScope = freshChildOfNearestScope.get();
@ -1363,7 +868,22 @@ FragmentTypeCheckResult typecheckFragmentHelper_DEPRECATED(
for (auto p : cg.scopes) for (auto p : cg.scopes)
incrementalModule->scopes.emplace_back(std::move(p)); incrementalModule->scopes.emplace_back(std::move(p));
} }
}
else
{
// Any additions to the scope must occur in a fresh scope
cg.rootScope = stale->getModuleScope().get();
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
mixedModeCompatibility(closestScope, freshChildOfNearestScope, stale, NotNull{&dfg}, root);
// closest Scope -> children = { ...., freshChildOfNearestScope}
// We need to trim nearestChild from the scope hierarchy
closestScope->children.emplace_back(freshChildOfNearestScope.get());
cg.visitFragmentRoot(freshChildOfNearestScope, root);
// Trim nearestChild from the closestScope
Scope* back = closestScope->children.back().get();
LUAU_ASSERT(back == freshChildOfNearestScope.get());
closestScope->children.pop_back();
}
reportWaypoint(reporter, FragmentAutocompleteWaypoint::ConstraintSolverStart); reportWaypoint(reporter, FragmentAutocompleteWaypoint::ConstraintSolverStart);
if (FFlag::LuauAllFreeTypesHaveScopes) if (FFlag::LuauAllFreeTypesHaveScopes)
@ -1372,8 +892,6 @@ FragmentTypeCheckResult typecheckFragmentHelper_DEPRECATED(
{ {
if (!sc->interiorFreeTypes.has_value()) if (!sc->interiorFreeTypes.has_value())
sc->interiorFreeTypes.emplace(); sc->interiorFreeTypes.emplace();
if (!sc->interiorFreeTypePacks.has_value())
sc->interiorFreeTypePacks.emplace();
} }
} }
@ -1473,7 +991,7 @@ FragmentTypeCheckResult typecheckFragment_(
SimplifierPtr simplifier = newSimplifier(NotNull{&incrementalModule->internalTypes}, frontend.builtinTypes); SimplifierPtr simplifier = newSimplifier(NotNull{&incrementalModule->internalTypes}, frontend.builtinTypes);
FrontendModuleResolver& resolver = getModuleResolver(frontend, opts); FrontendModuleResolver& resolver = getModuleResolver(frontend, opts);
std::shared_ptr<Scope> freshChildOfNearestScope = std::make_shared<Scope>(nullptr);
/// Contraint Generator /// Contraint Generator
ConstraintGenerator cg{ ConstraintGenerator cg{
incrementalModule, incrementalModule,
@ -1483,7 +1001,7 @@ FragmentTypeCheckResult typecheckFragment_(
NotNull{&resolver}, NotNull{&resolver},
frontend.builtinTypes, frontend.builtinTypes,
iceHandler, iceHandler,
FFlag::LuauGlobalVariableModuleIsolation ? freshChildOfNearestScope : stale->getModuleScope(), stale->getModuleScope(),
frontend.globals.globalTypeFunctionScope, frontend.globals.globalTypeFunctionScope,
nullptr, nullptr,
nullptr, nullptr,
@ -1492,9 +1010,9 @@ FragmentTypeCheckResult typecheckFragment_(
}; };
CloneState cloneState{frontend.builtinTypes}; CloneState cloneState{frontend.builtinTypes};
std::shared_ptr<Scope> freshChildOfNearestScope = std::make_shared<Scope>(nullptr);
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope); incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
freshChildOfNearestScope->interiorFreeTypes.emplace(); freshChildOfNearestScope->interiorFreeTypes.emplace();
freshChildOfNearestScope->interiorFreeTypePacks.emplace();
cg.rootScope = freshChildOfNearestScope.get(); cg.rootScope = freshChildOfNearestScope.get();
if (FFlag::LuauUserTypeFunTypecheck) if (FFlag::LuauUserTypeFunTypecheck)
@ -1507,14 +1025,7 @@ FragmentTypeCheckResult typecheckFragment_(
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeStart); reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeStart);
cloneTypesFromFragment( cloneTypesFromFragment(
cloneState, cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get()
closestScope.get(),
stale,
NotNull{&incrementalModule->internalTypes},
NotNull{&dfg},
frontend.builtinTypes,
root,
freshChildOfNearestScope.get()
); );
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeEnd); reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeEnd);
@ -1575,7 +1086,6 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
std::optional<FrontendOptions> opts, std::optional<FrontendOptions> opts,
std::string_view src, std::string_view src,
std::optional<Position> fragmentEndPosition, std::optional<Position> fragmentEndPosition,
AstStatBlock* recentParse,
IFragmentAutocompleteReporter* reporter IFragmentAutocompleteReporter* reporter
) )
{ {
@ -1594,9 +1104,30 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
} }
std::optional<FragmentParseResult> tryParse; std::optional<FragmentParseResult> tryParse;
tryParse = FFlag::LuauBetterScopeSelection ? parseFragment(module->root, recentParse, module->names.get(), src, cursorPos, fragmentEndPosition) if (FFlag::LuauModuleHoldsAstRoot)
: parseFragment_DEPRECATED(module->root, module->names.get(), src, cursorPos, fragmentEndPosition); {
tryParse = parseFragment(module->root, module->names.get(), src, cursorPos, fragmentEndPosition);
}
else
{
const SourceModule* sourceModule = frontend.getSourceModule(moduleName);
if (!sourceModule)
{
LUAU_ASSERT(!"Expected Source Module for fragment typecheck");
return {};
}
if (FFlag::LuauIncrementalAutocompleteBugfixes)
{
if (sourceModule->allocator.get() != module->allocator.get())
{
return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
}
}
tryParse = parseFragment(sourceModule->root, sourceModule->names.get(), src, cursorPos, fragmentEndPosition);
reportWaypoint(reporter, FragmentAutocompleteWaypoint::ParseFragmentEnd);
}
if (!tryParse) if (!tryParse)
return {FragmentTypeCheckStatus::SkipAutocomplete, {}}; return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
@ -1607,8 +1138,7 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
return {FragmentTypeCheckStatus::SkipAutocomplete, {}}; return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
FrontendOptions frontendOptions = opts.value_or(frontend.options); FrontendOptions frontendOptions = opts.value_or(frontend.options);
const ScopePtr& closestScope = FFlag::LuauBetterScopeSelection ? findClosestScope(module, parseResult.scopePos) const ScopePtr& closestScope = findClosestScope(module, parseResult.nearestStatement);
: findClosestScope_DEPRECATED(module, parseResult.nearestStatement);
FragmentTypeCheckResult result = FragmentTypeCheckResult result =
FFlag::LuauIncrementalAutocompleteDemandBasedCloning FFlag::LuauIncrementalAutocompleteDemandBasedCloning
? typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter) ? typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter)
@ -1644,15 +1174,14 @@ FragmentAutocompleteStatusResult tryFragmentAutocomplete(
context.opts, context.opts,
std::move(stringCompletionCB), std::move(stringCompletionCB),
context.DEPRECATED_fragmentEndPosition, context.DEPRECATED_fragmentEndPosition,
context.freshParse.root, FFlag::LuauFragmentAcSupportsReporter ? context.reporter : nullptr
context.reporter
); );
return {FragmentAutocompleteStatus::Success, std::move(fragmentAutocomplete)}; return {FragmentAutocompleteStatus::Success, std::move(fragmentAutocomplete)};
} }
catch (const Luau::InternalCompilerError& e) catch (const Luau::InternalCompilerError& e)
{ {
if (FFlag::DebugLogFragmentsFromAutocomplete) if (FFlag::LogFragmentsFromAutocomplete)
logLuau("tryFragmentAutocomplete exception", e.what()); logLuau(e.what());
return {FragmentAutocompleteStatus::InternalIce, std::nullopt}; return {FragmentAutocompleteStatus::InternalIce, std::nullopt};
} }
} }
@ -1665,28 +1194,40 @@ FragmentAutocompleteResult fragmentAutocomplete(
std::optional<FrontendOptions> opts, std::optional<FrontendOptions> opts,
StringCompletionCallback callback, StringCompletionCallback callback,
std::optional<Position> fragmentEndPosition, std::optional<Position> fragmentEndPosition,
AstStatBlock* recentParse,
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());
auto [tcStatus, tcResult] = typecheckFragment(frontend, moduleName, cursorPosition, opts, src, fragmentEndPosition, recentParse, reporter); if (!FFlag::LuauModuleHoldsAstRoot)
{
const SourceModule* sourceModule = frontend.getSourceModule(moduleName);
if (!sourceModule)
{
LUAU_ASSERT(!"Expected Source Module for fragment typecheck");
return {};
}
// If the cursor is within a comment in the stale source module we should avoid providing a recommendation
if (isWithinComment(*sourceModule, fragmentEndPosition.value_or(cursorPosition)))
return {};
}
auto [tcStatus, tcResult] = typecheckFragment(frontend, moduleName, cursorPosition, opts, src, fragmentEndPosition, reporter);
if (tcStatus == FragmentTypeCheckStatus::SkipAutocomplete) if (tcStatus == FragmentTypeCheckStatus::SkipAutocomplete)
return {}; return {};
reportWaypoint(reporter, FragmentAutocompleteWaypoint::TypecheckFragmentEnd); reportWaypoint(reporter, FragmentAutocompleteWaypoint::TypecheckFragmentEnd);
auto globalScope = (opts && opts->forAutocomplete) ? frontend.globalsForAutocomplete.globalScope.get() : frontend.globals.globalScope.get(); auto globalScope = (opts && opts->forAutocomplete) ? frontend.globalsForAutocomplete.globalScope.get() : frontend.globals.globalScope.get();
if (FFlag::DebugLogFragmentsFromAutocomplete) if (FFlag::LogFragmentsFromAutocomplete)
logLuau("Fragment Autocomplete Source Script", src); logLuau(src);
TypeArena arenaForAutocomplete_DEPRECATED; TypeArena arenaForFragmentAutocomplete;
if (FFlag::LuauFragmentAcMemoryLeak)
unfreeze(tcResult.incrementalModule->internalTypes);
auto result = Luau::autocomplete_( auto result = Luau::autocomplete_(
tcResult.incrementalModule, tcResult.incrementalModule,
frontend.builtinTypes, frontend.builtinTypes,
FFlag::LuauFragmentAcMemoryLeak ? &tcResult.incrementalModule->internalTypes : &arenaForAutocomplete_DEPRECATED, &arenaForFragmentAutocomplete,
tcResult.ancestry, tcResult.ancestry,
globalScope, globalScope,
tcResult.freshScope, tcResult.freshScope,
@ -1694,10 +1235,9 @@ FragmentAutocompleteResult fragmentAutocomplete(
frontend.fileResolver, frontend.fileResolver,
callback callback
); );
if (FFlag::LuauFragmentAcMemoryLeak)
freeze(tcResult.incrementalModule->internalTypes);
reportWaypoint(reporter, FragmentAutocompleteWaypoint::AutocompleteEnd); reportWaypoint(reporter, FragmentAutocompleteWaypoint::AutocompleteEnd);
return {std::move(tcResult.incrementalModule), tcResult.freshScope.get(), std::move(arenaForAutocomplete_DEPRECATED), std::move(result)}; return {std::move(tcResult.incrementalModule), tcResult.freshScope.get(), std::move(arenaForFragmentAutocomplete), std::move(result)};
} }
} // namespace Luau } // namespace Luau

View file

@ -1,6 +1,7 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Frontend.h" #include "Luau/Frontend.h"
#include "Luau/AnyTypeSummary.h"
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
#include "Luau/Clone.h" #include "Luau/Clone.h"
#include "Luau/Common.h" #include "Luau/Common.h"
@ -39,15 +40,20 @@ LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauInferInNoCheckMode) LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3) LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
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)
LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode) LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode)
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode) LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false)
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete) LUAU_FASTFLAGVARIABLE(LuauModuleHoldsAstRoot)
LUAU_FASTFLAGVARIABLE(LuauFixMultithreadTypecheck)
LUAU_FASTFLAG(StudioReportLuauAny2)
LUAU_FASTFLAGVARIABLE(LuauSelectivelyRetainDFGArena)
namespace Luau namespace Luau
{ {
@ -129,9 +135,9 @@ static void generateDocumentationSymbols(TypeId ty, const std::string& rootName)
prop.documentationSymbol = rootName + "." + name; prop.documentationSymbol = rootName + "." + name;
} }
} }
else if (ExternType* etv = getMutable<ExternType>(ty)) else if (ClassType* ctv = getMutable<ClassType>(ty))
{ {
for (auto& [name, prop] : etv->props) for (auto& [name, prop] : ctv->props)
{ {
prop.documentationSymbol = rootName + "." + name; prop.documentationSymbol = rootName + "." + name;
} }
@ -455,6 +461,20 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
if (item.name == name) if (item.name == name)
checkResult.lintResult = item.module->lintResult; checkResult.lintResult = item.module->lintResult;
if (FFlag::StudioReportLuauAny2 && item.options.retainFullTypeGraphs)
{
if (item.module)
{
const SourceModule& sourceModule = *item.sourceModule;
if (sourceModule.mode == Luau::Mode::Strict)
{
item.module->ats.root = toString(sourceModule.root);
}
item.module->ats.rootSrc = sourceModule.root;
item.module->ats.traverse(item.module.get(), sourceModule.root, NotNull{&builtinTypes_});
}
}
} }
return checkResult; return checkResult;
@ -476,6 +496,11 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
std::function<bool(size_t done, size_t total)> progress std::function<bool(size_t done, size_t total)> progress
) )
{ {
if (!FFlag::LuauFixMultithreadTypecheck)
{
return checkQueuedModules_DEPRECATED(optionOverride, executeTask, progress);
}
FrontendOptions frontendOptions = optionOverride.value_or(options); FrontendOptions frontendOptions = optionOverride.value_or(options);
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
frontendOptions.forAutocomplete = false; frontendOptions.forAutocomplete = false;
@ -660,6 +685,247 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
return checkedModules; return checkedModules;
} }
std::vector<ModuleName> Frontend::checkQueuedModules_DEPRECATED(
std::optional<FrontendOptions> optionOverride,
std::function<void(std::function<void()> task)> executeTask,
std::function<bool(size_t done, size_t total)> progress
)
{
LUAU_ASSERT(!FFlag::LuauFixMultithreadTypecheck);
FrontendOptions frontendOptions = optionOverride.value_or(options);
if (FFlag::LuauSolverV2)
frontendOptions.forAutocomplete = false;
// By taking data into locals, we make sure queue is cleared at the end, even if an ICE or a different exception is thrown
std::vector<ModuleName> currModuleQueue;
std::swap(currModuleQueue, moduleQueue);
DenseHashSet<Luau::ModuleName> seen{{}};
std::vector<BuildQueueItem> buildQueueItems;
for (const ModuleName& name : currModuleQueue)
{
if (seen.contains(name))
continue;
if (!isDirty(name, frontendOptions.forAutocomplete))
{
seen.insert(name);
continue;
}
std::vector<ModuleName> queue;
bool cycleDetected = parseGraph(
queue,
name,
frontendOptions.forAutocomplete,
[&seen](const ModuleName& name)
{
return seen.contains(name);
}
);
addBuildQueueItems(buildQueueItems, queue, cycleDetected, seen, frontendOptions);
}
if (buildQueueItems.empty())
return {};
// We need a mapping from modules to build queue slots
std::unordered_map<ModuleName, size_t> moduleNameToQueue;
for (size_t i = 0; i < buildQueueItems.size(); i++)
{
BuildQueueItem& item = buildQueueItems[i];
moduleNameToQueue[item.name] = i;
}
// Default task execution is single-threaded and immediate
if (!executeTask)
{
executeTask = [](std::function<void()> task)
{
task();
};
}
std::mutex mtx;
std::condition_variable cv;
std::vector<size_t> readyQueueItems;
size_t processing = 0;
size_t remaining = buildQueueItems.size();
auto itemTask = [&](size_t i)
{
BuildQueueItem& item = buildQueueItems[i];
try
{
checkBuildQueueItem(item);
}
catch (...)
{
item.exception = std::current_exception();
}
{
std::unique_lock guard(mtx);
readyQueueItems.push_back(i);
}
cv.notify_one();
};
auto sendItemTask = [&](size_t i)
{
BuildQueueItem& item = buildQueueItems[i];
item.processing = true;
processing++;
executeTask(
[&itemTask, i]()
{
itemTask(i);
}
);
};
auto sendCycleItemTask = [&]
{
for (size_t i = 0; i < buildQueueItems.size(); i++)
{
BuildQueueItem& item = buildQueueItems[i];
if (!item.processing)
{
sendItemTask(i);
break;
}
}
};
// In a first pass, check modules that have no dependencies and record info of those modules that wait
for (size_t i = 0; i < buildQueueItems.size(); i++)
{
BuildQueueItem& item = buildQueueItems[i];
for (const ModuleName& dep : item.sourceNode->requireSet)
{
if (auto it = sourceNodes.find(dep); it != sourceNodes.end())
{
if (it->second->hasDirtyModule(frontendOptions.forAutocomplete))
{
item.dirtyDependencies++;
buildQueueItems[moduleNameToQueue[dep]].reverseDeps.push_back(i);
}
}
}
if (item.dirtyDependencies == 0)
sendItemTask(i);
}
// Not a single item was found, a cycle in the graph was hit
if (processing == 0)
sendCycleItemTask();
std::vector<size_t> nextItems;
std::optional<size_t> itemWithException;
bool cancelled = false;
while (remaining != 0)
{
{
std::unique_lock guard(mtx);
// If nothing is ready yet, wait
cv.wait(
guard,
[&readyQueueItems]
{
return !readyQueueItems.empty();
}
);
// Handle checked items
for (size_t i : readyQueueItems)
{
const BuildQueueItem& item = buildQueueItems[i];
// If exception was thrown, stop adding new items and wait for processing items to complete
if (item.exception)
itemWithException = i;
if (item.module && item.module->cancelled)
cancelled = true;
if (itemWithException || cancelled)
break;
recordItemResult(item);
// Notify items that were waiting for this dependency
for (size_t reverseDep : item.reverseDeps)
{
BuildQueueItem& reverseDepItem = buildQueueItems[reverseDep];
LUAU_ASSERT(reverseDepItem.dirtyDependencies != 0);
reverseDepItem.dirtyDependencies--;
// In case of a module cycle earlier, check if unlocked an item that was already processed
if (!reverseDepItem.processing && reverseDepItem.dirtyDependencies == 0)
nextItems.push_back(reverseDep);
}
}
LUAU_ASSERT(processing >= readyQueueItems.size());
processing -= readyQueueItems.size();
LUAU_ASSERT(remaining >= readyQueueItems.size());
remaining -= readyQueueItems.size();
readyQueueItems.clear();
}
if (progress)
{
if (!progress(buildQueueItems.size() - remaining, buildQueueItems.size()))
cancelled = true;
}
// Items cannot be submitted while holding the lock
for (size_t i : nextItems)
sendItemTask(i);
nextItems.clear();
if (processing == 0)
{
// Typechecking might have been cancelled by user, don't return partial results
if (cancelled)
return {};
// We might have stopped because of a pending exception
if (itemWithException)
recordItemResult(buildQueueItems[*itemWithException]);
}
// If we aren't done, but don't have anything processing, we hit a cycle
if (remaining != 0 && processing == 0)
sendCycleItemTask();
}
std::vector<ModuleName> checkedModules;
checkedModules.reserve(buildQueueItems.size());
for (size_t i = 0; i < buildQueueItems.size(); i++)
checkedModules.push_back(std::move(buildQueueItems[i].name));
return checkedModules;
}
std::optional<CheckResult> Frontend::getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete) std::optional<CheckResult> Frontend::getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete)
{ {
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
@ -951,7 +1217,7 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
item.stats.timeCheck += duration; item.stats.timeCheck += duration;
item.stats.filesStrict += 1; item.stats.filesStrict += 1;
if (item.options.customModuleCheck) if (DFFlag::LuauRunCustomModuleChecks && item.options.customModuleCheck)
item.options.customModuleCheck(sourceModule, *moduleForAutocomplete); item.options.customModuleCheck(sourceModule, *moduleForAutocomplete);
item.module = moduleForAutocomplete; item.module = moduleForAutocomplete;
@ -971,7 +1237,7 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
item.stats.filesStrict += mode == Mode::Strict; item.stats.filesStrict += mode == Mode::Strict;
item.stats.filesNonstrict += mode == Mode::Nonstrict; item.stats.filesNonstrict += mode == Mode::Nonstrict;
if (item.options.customModuleCheck) if (DFFlag::LuauRunCustomModuleChecks && item.options.customModuleCheck)
item.options.customModuleCheck(sourceModule, *module); item.options.customModuleCheck(sourceModule, *module);
if (FFlag::LuauSolverV2 && mode == Mode::NoCheck) if (FFlag::LuauSolverV2 && mode == Mode::NoCheck)
@ -1003,8 +1269,11 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
freeze(module->interfaceTypes); freeze(module->interfaceTypes);
module->internalTypes.clear(); module->internalTypes.clear();
if (FFlag::LuauSelectivelyRetainDFGArena)
{
module->defArena.allocator.clear(); module->defArena.allocator.clear();
module->keyArena.allocator.clear(); module->keyArena.allocator.clear();
}
module->astTypes.clear(); module->astTypes.clear();
module->astTypePacks.clear(); module->astTypePacks.clear();
@ -1098,19 +1367,6 @@ void Frontend::performQueueItemTask(std::shared_ptr<BuildQueueWorkState> state,
{ {
BuildQueueItem& item = state->buildQueueItems[itemPos]; BuildQueueItem& item = state->buildQueueItems[itemPos];
if (DFFlag::LuauRethrowKnownExceptions)
{
try
{
checkBuildQueueItem(item);
}
catch (const Luau::InternalCompilerError&)
{
item.exception = std::current_exception();
}
}
else
{
try try
{ {
checkBuildQueueItem(item); checkBuildQueueItem(item);
@ -1119,7 +1375,6 @@ void Frontend::performQueueItemTask(std::shared_ptr<BuildQueueWorkState> state,
{ {
item.exception = std::current_exception(); item.exception = std::current_exception();
} }
}
{ {
std::unique_lock guard(state->mtx); std::unique_lock guard(state->mtx);
@ -1305,7 +1560,7 @@ ModulePtr check(
struct InternalTypeFinder : TypeOnceVisitor struct InternalTypeFinder : TypeOnceVisitor
{ {
bool visit(TypeId, const ExternType&) override bool visit(TypeId, const ClassType&) override
{ {
return false; return false;
} }
@ -1377,6 +1632,7 @@ ModulePtr check(
result->interfaceTypes.owningModule = result.get(); result->interfaceTypes.owningModule = result.get();
result->allocator = sourceModule.allocator; result->allocator = sourceModule.allocator;
result->names = sourceModule.names; result->names = sourceModule.names;
if (FFlag::LuauModuleHoldsAstRoot)
result->root = sourceModule.root; result->root = sourceModule.root;
iceHandler->moduleName = sourceModule.name; iceHandler->moduleName = sourceModule.name;
@ -1402,7 +1658,7 @@ ModulePtr check(
SimplifierPtr simplifier = newSimplifier(NotNull{&result->internalTypes}, builtinTypes); SimplifierPtr simplifier = newSimplifier(NotNull{&result->internalTypes}, builtinTypes);
TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}}; TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}};
typeFunctionRuntime.allowEvaluation = FFlag::LuauTypeFunResultInAutocomplete || sourceModule.parseErrors.empty(); typeFunctionRuntime.allowEvaluation = sourceModule.parseErrors.empty();
ConstraintGenerator cg{ ConstraintGenerator cg{
result, result,
@ -1420,36 +1676,10 @@ ModulePtr check(
requireCycles requireCycles
}; };
// FIXME: Delete this flag when clipping FFlag::DebugLuauGreedyGeneralization.
//
// This optional<> only exists so that we can run one constructor when the flag
// is set, and another when it is unset.
std::optional<ConstraintSolver> cs;
if (FFlag::DebugLuauGreedyGeneralization)
{
ConstraintSet constraintSet = cg.run(sourceModule.root);
result->errors = std::move(constraintSet.errors);
cs.emplace(
NotNull{&normalizer},
NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime},
result->name,
moduleResolver,
requireCycles,
logger.get(),
NotNull{&dfg},
limits,
std::move(constraintSet)
);
}
else
{
cg.visitModuleRoot(sourceModule.root); cg.visitModuleRoot(sourceModule.root);
result->errors = std::move(cg.errors); result->errors = std::move(cg.errors);
cs.emplace( ConstraintSolver cs{
NotNull{&normalizer}, NotNull{&normalizer},
NotNull{simplifier.get()}, NotNull{simplifier.get()},
NotNull{&typeFunctionRuntime}, NotNull{&typeFunctionRuntime},
@ -1462,17 +1692,14 @@ ModulePtr check(
logger.get(), logger.get(),
NotNull{&dfg}, NotNull{&dfg},
limits 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&)
{ {
@ -1492,12 +1719,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

@ -4,85 +4,19 @@
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include "Luau/InsertionOrderedMap.h"
#include "Luau/Polarity.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/ToString.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/ToString.h"
#include "Luau/TypeArena.h" #include "Luau/TypeArena.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
#include "Luau/Substitution.h"
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauGeneralizationRemoveRecursiveUpperBound2)
LUAU_FASTFLAGVARIABLE(LuauNonReentrantGeneralization2)
namespace Luau namespace Luau
{ {
namespace
{
template<typename T>
struct OrderedSet
{
using iterator = typename std::vector<T>::iterator;
using const_iterator = typename std::vector<T>::const_iterator;
bool empty() const
{
return elements.empty();
}
size_t size() const
{
return elements.size();
}
void insert(T t)
{
if (!elementSet.contains(t))
{
elementSet.insert(t);
elements.push_back(t);
}
}
iterator begin()
{
return elements.begin();
}
const_iterator begin() const
{
return elements.begin();
}
iterator end()
{
return elements.end();
}
const_iterator end() const
{
return elements.end();
}
/// Move the underlying vector out of the OrderedSet.
std::vector<T> takeVector()
{
elementSet.clear();
return std::move(elements);
}
private:
std::vector<T> elements;
DenseHashSet<T> elementSet{nullptr};
};
} // namespace
struct MutatingGeneralizer : TypeOnceVisitor struct MutatingGeneralizer : TypeOnceVisitor
{ {
NotNull<TypeArena> arena; NotNull<TypeArena> arena;
@ -96,6 +30,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
std::vector<TypePackId> genericPacks; std::vector<TypePackId> genericPacks;
bool isWithinFunction = false; bool isWithinFunction = false;
bool avoidSealingTables = false;
MutatingGeneralizer( MutatingGeneralizer(
NotNull<TypeArena> arena, NotNull<TypeArena> arena,
@ -103,7 +38,8 @@ struct MutatingGeneralizer : TypeOnceVisitor
NotNull<Scope> scope, NotNull<Scope> scope,
NotNull<DenseHashSet<TypeId>> cachedTypes, NotNull<DenseHashSet<TypeId>> cachedTypes,
DenseHashMap<const void*, size_t> positiveTypes, DenseHashMap<const void*, size_t> positiveTypes,
DenseHashMap<const void*, size_t> negativeTypes DenseHashMap<const void*, size_t> negativeTypes,
bool avoidSealingTables
) )
: TypeOnceVisitor(/* skipBoundTypes */ true) : TypeOnceVisitor(/* skipBoundTypes */ true)
, arena(arena) , arena(arena)
@ -112,6 +48,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
, cachedTypes(cachedTypes) , cachedTypes(cachedTypes)
, positiveTypes(std::move(positiveTypes)) , positiveTypes(std::move(positiveTypes))
, negativeTypes(std::move(negativeTypes)) , negativeTypes(std::move(negativeTypes))
, avoidSealingTables(avoidSealingTables)
{ {
} }
@ -162,7 +99,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
LUAU_ASSERT(onlyType != haystack); LUAU_ASSERT(onlyType != haystack);
emplaceType<BoundType>(asMutable(haystack), onlyType); emplaceType<BoundType>(asMutable(haystack), onlyType);
} }
else if (ut->options.empty()) else if (FFlag::LuauGeneralizationRemoveRecursiveUpperBound2 && ut->options.empty())
{ {
emplaceType<BoundType>(asMutable(haystack), builtinTypes->neverType); emplaceType<BoundType>(asMutable(haystack), builtinTypes->neverType);
} }
@ -209,7 +146,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
LUAU_ASSERT(onlyType != needle); LUAU_ASSERT(onlyType != needle);
emplaceType<BoundType>(asMutable(needle), onlyType); emplaceType<BoundType>(asMutable(needle), onlyType);
} }
else if (it->parts.empty()) else if (FFlag::LuauGeneralizationRemoveRecursiveUpperBound2 && it->parts.empty())
{ {
emplaceType<BoundType>(asMutable(needle), builtinTypes->unknownType); emplaceType<BoundType>(asMutable(needle), builtinTypes->unknownType);
} }
@ -337,15 +274,6 @@ struct MutatingGeneralizer : TypeOnceVisitor
return 0; return 0;
} }
template<typename TID>
static size_t getCount(const DenseHashMap<TID, size_t>& map, TID ty)
{
if (const size_t* count = map.find(ty))
return *count;
else
return 0;
}
bool visit(TypeId ty, const TableType&) override bool visit(TypeId ty, const TableType&) override
{ {
if (cachedTypes->contains(ty)) if (cachedTypes->contains(ty))
@ -364,6 +292,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
TableType* tt = getMutable<TableType>(ty); TableType* tt = getMutable<TableType>(ty);
LUAU_ASSERT(tt); LUAU_ASSERT(tt);
if (!avoidSealingTables)
tt->state = TableState::Sealed; tt->state = TableState::Sealed;
return true; return true;
@ -403,12 +332,28 @@ struct FreeTypeSearcher : TypeVisitor
{ {
} }
bool isWithinFunction = false; enum Polarity
Polarity polarity = Polarity::Positive; {
Positive,
Negative,
Both,
};
Polarity polarity = Positive;
void flip() void flip()
{ {
polarity = invert(polarity); switch (polarity)
{
case Positive:
polarity = Negative;
break;
case Negative:
polarity = Positive;
break;
case Both:
break;
}
} }
DenseHashSet<const void*> seenPositive{nullptr}; DenseHashSet<const void*> seenPositive{nullptr};
@ -418,7 +363,7 @@ struct FreeTypeSearcher : TypeVisitor
{ {
switch (polarity) switch (polarity)
{ {
case Polarity::Positive: case Positive:
{ {
if (seenPositive.contains(ty)) if (seenPositive.contains(ty))
return true; return true;
@ -426,7 +371,7 @@ struct FreeTypeSearcher : TypeVisitor
seenPositive.insert(ty); seenPositive.insert(ty);
return false; return false;
} }
case Polarity::Negative: case Negative:
{ {
if (seenNegative.contains(ty)) if (seenNegative.contains(ty))
return true; return true;
@ -434,7 +379,7 @@ struct FreeTypeSearcher : TypeVisitor
seenNegative.insert(ty); seenNegative.insert(ty);
return false; return false;
} }
case Polarity::Mixed: case Both:
{ {
if (seenPositive.contains(ty) && seenNegative.contains(ty)) if (seenPositive.contains(ty) && seenNegative.contains(ty))
return true; return true;
@ -443,21 +388,17 @@ struct FreeTypeSearcher : TypeVisitor
seenNegative.insert(ty); seenNegative.insert(ty);
return false; return false;
} }
default:
LUAU_ASSERT(!"Unreachable");
} }
return false; return false;
} }
// The keys in these maps are either TypeIds or TypePackIds. It's safe to
// mix them because we only use these pointers as unique keys. We never
// indirect them.
DenseHashMap<const void*, size_t> negativeTypes{0}; DenseHashMap<const void*, size_t> negativeTypes{0};
DenseHashMap<const void*, size_t> positiveTypes{0}; DenseHashMap<const void*, size_t> positiveTypes{0};
InsertionOrderedMap<TypeId, GeneralizationParams<TypeId>> types;
InsertionOrderedMap<TypePackId, GeneralizationParams<TypePackId>> typePacks;
OrderedSet<TypeId> unsealedTables;
bool visit(TypeId ty) override bool visit(TypeId ty) override
{ {
if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty)) if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
@ -468,24 +409,6 @@ struct FreeTypeSearcher : TypeVisitor
} }
bool visit(TypeId ty, const FreeType& ft) override bool visit(TypeId ty, const FreeType& ft) override
{
if (FFlag::LuauNonReentrantGeneralization2)
{
if (!subsumes(scope, ft.scope))
return true;
GeneralizationParams<TypeId>& params = types[ty];
++params.useCount;
if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
return false;
if (!isWithinFunction)
params.foundOutsideFunctions = true;
params.polarity |= polarity;
}
else
{ {
if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty)) if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
return false; return false;
@ -495,19 +418,16 @@ struct FreeTypeSearcher : TypeVisitor
switch (polarity) switch (polarity)
{ {
case Polarity::Positive: case Positive:
positiveTypes[ty]++; positiveTypes[ty]++;
break; break;
case Polarity::Negative: case Negative:
negativeTypes[ty]++; negativeTypes[ty]++;
break; break;
case Polarity::Mixed: case Both:
positiveTypes[ty]++; positiveTypes[ty]++;
negativeTypes[ty]++; negativeTypes[ty]++;
break; break;
default:
LUAU_ASSERT(!"Unreachable");
}
} }
return true; return true;
@ -519,26 +439,19 @@ struct FreeTypeSearcher : TypeVisitor
return false; return false;
if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope)) if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope))
{
if (FFlag::LuauNonReentrantGeneralization2)
unsealedTables.insert(ty);
else
{ {
switch (polarity) switch (polarity)
{ {
case Polarity::Positive: case Positive:
positiveTypes[ty]++; positiveTypes[ty]++;
break; break;
case Polarity::Negative: case Negative:
negativeTypes[ty]++; negativeTypes[ty]++;
break; break;
case Polarity::Mixed: case Both:
positiveTypes[ty]++; positiveTypes[ty]++;
negativeTypes[ty]++; negativeTypes[ty]++;
break; break;
default:
LUAU_ASSERT(!"Unreachable");
}
} }
} }
@ -548,10 +461,10 @@ struct FreeTypeSearcher : TypeVisitor
traverse(*prop.readTy); traverse(*prop.readTy);
else else
{ {
LUAU_ASSERT(prop.isShared()); LUAU_ASSERT(prop.isShared() || FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete);
Polarity p = polarity; Polarity p = polarity;
polarity = Polarity::Mixed; polarity = Both;
traverse(prop.type()); traverse(prop.type());
polarity = p; polarity = p;
} }
@ -559,27 +472,8 @@ struct FreeTypeSearcher : TypeVisitor
if (tt.indexer) if (tt.indexer)
{ {
if (FFlag::LuauNonReentrantGeneralization2)
{
// {[K]: V} is equivalent to three functions: get, set, and iterate
//
// (K) -> V
// (K, V) -> ()
// () -> {K}
//
// K and V therefore both have mixed polarity.
const Polarity p = polarity;
polarity = Polarity::Mixed;
traverse(tt.indexer->indexType); traverse(tt.indexer->indexType);
traverse(tt.indexer->indexResultType); traverse(tt.indexer->indexResultType);
polarity = p;
}
else
{
traverse(tt.indexer->indexType);
traverse(tt.indexer->indexResultType);
}
} }
return false; return false;
@ -590,21 +484,16 @@ struct FreeTypeSearcher : TypeVisitor
if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty)) if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
return false; return false;
const bool oldValue = isWithinFunction;
isWithinFunction = true;
flip(); flip();
traverse(ft.argTypes); traverse(ft.argTypes);
flip(); flip();
traverse(ft.retTypes); traverse(ft.retTypes);
isWithinFunction = oldValue;
return false; return false;
} }
bool visit(TypeId, const ExternType&) override bool visit(TypeId, const ClassType&) override
{ {
return false; return false;
} }
@ -617,33 +506,18 @@ struct FreeTypeSearcher : TypeVisitor
if (!subsumes(scope, ftp.scope)) if (!subsumes(scope, ftp.scope))
return true; return true;
if (FFlag::LuauNonReentrantGeneralization2)
{
GeneralizationParams<TypePackId>& params = typePacks[tp];
++params.useCount;
if (!isWithinFunction)
params.foundOutsideFunctions = true;
params.polarity |= polarity;
}
else
{
switch (polarity) switch (polarity)
{ {
case Polarity::Positive: case Positive:
positiveTypes[tp]++; positiveTypes[tp]++;
break; break;
case Polarity::Negative: case Negative:
negativeTypes[tp]++; negativeTypes[tp]++;
break; break;
case Polarity::Mixed: case Both:
positiveTypes[tp]++; positiveTypes[tp]++;
negativeTypes[tp]++; negativeTypes[tp]++;
break; break;
default:
LUAU_ASSERT(!"Unreachable");
}
} }
return true; return true;
@ -896,7 +770,7 @@ struct TypeCacher : TypeOnceVisitor
return false; return false;
} }
bool visit(TypeId ty, const ExternType&) override bool visit(TypeId ty, const ClassType&) override
{ {
cache(ty); cache(ty);
return false; return false;
@ -1093,277 +967,13 @@ struct TypeCacher : TypeOnceVisitor
} }
}; };
struct RemoveType : Substitution // NOLINT
{
NotNull<BuiltinTypes> builtinTypes;
TypeId needle;
RemoveType(NotNull<BuiltinTypes> builtinTypes, TypeArena* arena, TypeId needle)
: Substitution(arena)
, builtinTypes(builtinTypes)
, needle(needle)
{
}
bool ignoreChildren(TypeId ty) override
{
if (get<UnionType>(ty) || get<IntersectionType>(ty))
return false;
else
return true;
}
bool isDirty(TypeId ty) override
{
// A union or intersection is dirty if it contains the needle or if it has any duplicate members.
if (auto ut = get<UnionType>(ty))
{
DenseHashSet<TypeId> distinctParts{nullptr};
size_t count = 0;
for (TypeId part : ut)
{
++count;
if (part == needle)
return true;
distinctParts.insert(follow(part));
}
return distinctParts.size() != count;
}
else if (auto it = get<IntersectionType>(ty))
{
DenseHashSet<TypeId> distinctParts{nullptr};
size_t count = 0;
for (TypeId part : it)
{
++count;
if (part == needle)
return true;
distinctParts.insert(follow(part));
}
return distinctParts.size() != count;
}
return false;
}
bool isDirty(TypePackId tp) override
{
return false;
}
TypeId clean(TypeId ty) override
{
if (auto ut = get<UnionType>(ty))
{
OrderedSet<TypeId> newParts;
for (TypeId ty : ut)
{
if (ty != needle)
newParts.insert(ty);
}
if (newParts.empty())
return builtinTypes->neverType;
else if (newParts.size() == 1)
{
TypeId onlyType = *newParts.begin();
LUAU_ASSERT(onlyType != needle);
return onlyType;
}
else
return arena->addType(UnionType{newParts.takeVector()});
}
else if (auto it = get<IntersectionType>(ty))
{
OrderedSet<TypeId> newParts;
for (TypeId ty : it)
{
if (ty != needle)
newParts.insert(ty);
}
if (newParts.empty())
return builtinTypes->unknownType;
else if (newParts.size() == 1)
{
TypeId onlyType = *newParts.begin();
LUAU_ASSERT(onlyType != needle);
return onlyType;
}
else
return arena->addType(IntersectionType{newParts.takeVector()});
}
else
return ty;
}
TypePackId clean(TypePackId tp) override
{
return tp;
}
};
/**
* Remove occurrences of `needle` within `haystack`. This is used to cull cyclic bounds from free types.
*
* @param haystack Either the upper or lower bound of a free type.
* @param needle The type to be removed.
*/
[[nodiscard]]
static std::optional<
TypeId> removeType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, TypeId haystack, TypeId needle)
{
RemoveType rt{builtinTypes, arena, needle};
return rt.substitute(haystack);
}
GeneralizationResult<TypeId> generalizeType(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
TypeId freeTy,
const GeneralizationParams<TypeId>& params
)
{
freeTy = follow(freeTy);
FreeType* ft = getMutable<FreeType>(freeTy);
LUAU_ASSERT(ft);
LUAU_ASSERT(isKnown(params.polarity));
const bool hasLowerBound = !get<NeverType>(follow(ft->lowerBound));
const bool hasUpperBound = !get<UnknownType>(follow(ft->upperBound));
const bool isWithinFunction = !params.foundOutsideFunctions;
if (!hasLowerBound && !hasUpperBound)
{
if (!isWithinFunction || (!FFlag::DebugLuauGreedyGeneralization && (params.polarity != Polarity::Mixed && params.useCount == 1)))
emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType);
else
{
emplaceType<GenericType>(asMutable(freeTy), scope, params.polarity);
return {freeTy, /*wasReplacedByGeneric*/ true};
}
}
// It is possible that this free type has other free types in its upper
// or lower bounds. If this is the case, we must replace those
// references with never (for the lower bound) or unknown (for the upper
// bound).
//
// If we do not do this, we get tautological bounds like a <: a <: unknown.
else if (isPositive(params.polarity) && !hasUpperBound)
{
TypeId lb = follow(ft->lowerBound);
if (FreeType* lowerFree = getMutable<FreeType>(lb); lowerFree && lowerFree->upperBound == freeTy)
lowerFree->upperBound = builtinTypes->unknownType;
else
{
std::optional<TypeId> removed = removeType(arena, builtinTypes, lb, freeTy);
if (removed)
lb = *removed;
else
return {std::nullopt, false, /*resourceLimitsExceeded*/ true};
ft->lowerBound = lb;
}
if (follow(lb) != freeTy)
emplaceType<BoundType>(asMutable(freeTy), lb);
else if (!isWithinFunction || (!FFlag::DebugLuauGreedyGeneralization && params.useCount == 1))
emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType);
else
{
// if the lower bound is the type in question (eg 'a <: 'a), we don't actually have a lower bound.
emplaceType<GenericType>(asMutable(freeTy), scope, params.polarity);
return {freeTy, /*wasReplacedByGeneric*/ true};
}
}
else
{
TypeId ub = follow(ft->upperBound);
if (FreeType* upperFree = getMutable<FreeType>(ub); upperFree && upperFree->lowerBound == freeTy)
upperFree->lowerBound = builtinTypes->neverType;
else
{
// If the free type appears within its own upper bound, cull that cycle.
std::optional<TypeId> removed = removeType(arena, builtinTypes, ub, freeTy);
if (removed)
ub = *removed;
else
return {std::nullopt, false, /*resourceLimitsExceeded*/ true};
ft->upperBound = ub;
}
if (follow(ub) != freeTy)
emplaceType<BoundType>(asMutable(freeTy), ub);
else if (!isWithinFunction || params.useCount == 1)
emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType);
else
{
// if the upper bound is the type in question, we don't actually have an upper bound.
emplaceType<GenericType>(asMutable(freeTy), scope, params.polarity);
return {freeTy, /*wasReplacedByGeneric*/ true};
}
}
return {freeTy, /*wasReplacedByGeneric*/ false};
}
GeneralizationResult<TypePackId> generalizeTypePack(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
TypePackId tp,
const GeneralizationParams<TypePackId>& params
)
{
tp = follow(tp);
if (tp->owningArena != arena)
return {tp, /*wasReplacedByGeneric*/ false};
const FreeTypePack* ftp = get<FreeTypePack>(tp);
if (!ftp)
return {tp, /*wasReplacedByGeneric*/ false};
if (!subsumes(scope, ftp->scope))
return {tp, /*wasReplacedByGeneric*/ false};
if (1 == params.useCount)
emplaceTypePack<BoundTypePack>(asMutable(tp), builtinTypes->unknownTypePack);
else
{
emplaceTypePack<GenericTypePack>(asMutable(tp), scope, params.polarity);
return {tp, /*wasReplacedByGeneric*/ true};
}
return {tp, /*wasReplacedByGeneric*/ false};
}
void sealTable(NotNull<Scope> scope, TypeId ty)
{
TableType* tableTy = getMutable<TableType>(follow(ty));
if (!tableTy)
return;
if (!subsumes(scope, tableTy->scope))
return;
if (tableTy->state == TableState::Unsealed || tableTy->state == TableState::Free)
tableTy->state = TableState::Sealed;
}
std::optional<TypeId> generalize( std::optional<TypeId> generalize(
NotNull<TypeArena> arena, NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope, NotNull<Scope> scope,
NotNull<DenseHashSet<TypeId>> cachedTypes, NotNull<DenseHashSet<TypeId>> cachedTypes,
TypeId ty, TypeId ty,
std::optional<TypeId> generalizationTarget bool avoidSealingTables
) )
{ {
ty = follow(ty); ty = follow(ty);
@ -1374,61 +984,7 @@ std::optional<TypeId> generalize(
FreeTypeSearcher fts{scope, cachedTypes}; FreeTypeSearcher fts{scope, cachedTypes};
fts.traverse(ty); fts.traverse(ty);
if (FFlag::LuauNonReentrantGeneralization2) MutatingGeneralizer gen{arena, builtinTypes, scope, cachedTypes, std::move(fts.positiveTypes), std::move(fts.negativeTypes), avoidSealingTables};
{
FunctionType* functionTy = getMutable<FunctionType>(ty);
auto pushGeneric = [&](TypeId t)
{
if (functionTy)
functionTy->generics.push_back(t);
};
auto pushGenericPack = [&](TypePackId tp)
{
if (functionTy)
functionTy->genericPacks.push_back(tp);
};
for (const auto& [freeTy, params] : fts.types)
{
if (!generalizationTarget || freeTy == *generalizationTarget)
{
GeneralizationResult<TypeId> res = generalizeType(arena, builtinTypes, scope, freeTy, params);
if (res.resourceLimitsExceeded)
return std::nullopt;
if (res && res.wasReplacedByGeneric)
pushGeneric(*res.result);
}
}
for (TypeId unsealedTableTy : fts.unsealedTables)
{
if (!generalizationTarget || unsealedTableTy == *generalizationTarget)
sealTable(scope, unsealedTableTy);
}
for (const auto& [freePackId, params] : fts.typePacks)
{
TypePackId freePack = follow(freePackId);
if (!generalizationTarget)
{
GeneralizationResult<TypePackId> generalizedTp = generalizeTypePack(arena, builtinTypes, scope, freePack, params);
if (generalizedTp.resourceLimitsExceeded)
return std::nullopt;
if (generalizedTp && generalizedTp.wasReplacedByGeneric)
pushGenericPack(freePack);
}
}
TypeCacher cacher{cachedTypes};
cacher.traverse(ty);
}
else
{
MutatingGeneralizer gen{arena, builtinTypes, scope, cachedTypes, std::move(fts.positiveTypes), std::move(fts.negativeTypes)};
gen.traverse(ty); gen.traverse(ty);
@ -1459,202 +1015,8 @@ std::optional<TypeId> generalize(
ftv->genericPacks.push_back(gp); ftv->genericPacks.push_back(gp);
} }
} }
}
return ty; return ty;
} }
struct GenericCounter : TypeVisitor
{
struct CounterState
{
size_t count = 0;
Polarity polarity = Polarity::None;
};
NotNull<DenseHashSet<TypeId>> cachedTypes;
DenseHashMap<TypeId, CounterState> generics{nullptr};
DenseHashMap<TypePackId, CounterState> genericPacks{nullptr};
Polarity polarity = Polarity::Positive;
explicit GenericCounter(NotNull<DenseHashSet<TypeId>> cachedTypes)
: cachedTypes(cachedTypes)
{
}
bool visit(TypeId ty, const FunctionType& ft) override
{
if (ty->persistent)
return false;
polarity = invert(polarity);
traverse(ft.argTypes);
polarity = invert(polarity);
traverse(ft.retTypes);
return false;
}
bool visit(TypeId ty, const TableType& tt) override
{
if (ty->persistent)
return false;
const Polarity previous = polarity;
for (const auto& [_name, prop] : tt.props)
{
if (prop.isReadOnly())
traverse(*prop.readTy);
else
{
LUAU_ASSERT(prop.isShared());
polarity = Polarity::Mixed;
traverse(prop.type());
polarity = previous;
}
}
if (tt.indexer)
{
polarity = Polarity::Mixed;
traverse(tt.indexer->indexType);
traverse(tt.indexer->indexResultType);
polarity = previous;
}
return false;
}
bool visit(TypeId ty, const ExternType&) override
{
return false;
}
bool visit(TypeId ty, const GenericType&) override
{
auto state = generics.find(ty);
if (state)
{
++state->count;
state->polarity |= polarity;
}
return false;
}
bool visit(TypePackId tp, const GenericTypePack&) override
{
auto state = genericPacks.find(tp);
if (state)
{
++state->count;
state->polarity |= polarity;
}
return false;
}
};
void pruneUnnecessaryGenerics(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
NotNull<DenseHashSet<TypeId>> cachedTypes,
TypeId ty
)
{
if (!FFlag::DebugLuauGreedyGeneralization)
return;
ty = follow(ty);
if (ty->owningArena != arena || ty->persistent)
return;
FunctionType* functionTy = getMutable<FunctionType>(ty);
if (!functionTy)
return;
// If a generic has no explicit name and is only referred to in one place in
// the function's signature, it can be replaced with unknown.
GenericCounter counter{cachedTypes};
for (TypeId generic : functionTy->generics)
{
generic = follow(generic);
auto g = get<GenericType>(generic);
if (g && !g->explicitName)
counter.generics[generic] = {};
}
// It is sometimes the case that a pack in the generic list will become a
// pack that (transitively) has a generic tail. If it does, we need to add
// that generic tail to the generic pack list.
for (size_t i = 0; i < functionTy->genericPacks.size(); ++i)
{
TypePackId genericPack = follow(functionTy->genericPacks[i]);
TypePackId tail = getTail(genericPack);
if (tail != genericPack)
functionTy->genericPacks.push_back(tail);
if (auto g = get<GenericTypePack>(tail); g && !g->explicitName)
counter.genericPacks[genericPack] = {};
}
counter.traverse(ty);
for (const auto& [generic, state] : counter.generics)
{
if (state.count == 1 && state.polarity != Polarity::Mixed)
emplaceType<BoundType>(asMutable(generic), builtinTypes->unknownType);
}
// Remove duplicates and types that aren't actually generics.
DenseHashSet<TypeId> seen{nullptr};
auto it = std::remove_if(
functionTy->generics.begin(),
functionTy->generics.end(),
[&](TypeId ty)
{
ty = follow(ty);
if (seen.contains(ty))
return true;
seen.insert(ty);
return !get<GenericType>(ty);
}
);
functionTy->generics.erase(it, functionTy->generics.end());
for (const auto& [genericPack, state] : counter.genericPacks)
{
if (state.count == 1)
emplaceTypePack<BoundTypePack>(asMutable(genericPack), builtinTypes->unknownTypePack);
}
DenseHashSet<TypePackId> seen2{nullptr};
auto it2 = std::remove_if(
functionTy->genericPacks.begin(),
functionTy->genericPacks.end(),
[&](TypePackId tp)
{
tp = follow(tp);
if (seen2.contains(tp))
return true;
seen2.insert(tp);
return !get<GenericTypePack>(tp);
}
);
functionTy->genericPacks.erase(it2, functionTy->genericPacks.end());
}
} // namespace Luau } // namespace Luau

View file

@ -1,169 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/DenseHash.h"
#include "Luau/Polarity.h"
#include "Luau/Scope.h"
#include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
namespace Luau
{
struct InferPolarity : TypeVisitor
{
NotNull<TypeArena> arena;
NotNull<Scope> scope;
DenseHashMap<TypeId, Polarity> types{nullptr};
DenseHashMap<TypePackId, Polarity> packs{nullptr};
Polarity polarity = Polarity::Positive;
explicit InferPolarity(NotNull<TypeArena> arena, NotNull<Scope> scope)
: arena(arena)
, scope(scope)
{
}
void flip()
{
polarity = invert(polarity);
}
bool visit(TypeId ty, const GenericType& gt) override
{
if (ty->owningArena != arena)
return false;
if (subsumes(scope, gt.scope))
types[ty] |= polarity;
return false;
}
bool visit(TypeId ty, const TableType& tt) override
{
if (ty->owningArena != arena)
return false;
const Polarity p = polarity;
for (const auto& [name, prop] : tt.props)
{
if (prop.isShared())
{
polarity = Polarity::Mixed;
traverse(prop.type());
}
else if (prop.isReadOnly())
{
polarity = p;
traverse(*prop.readTy);
}
else if (prop.isWriteOnly())
{
polarity = invert(p);
traverse(*prop.writeTy);
}
else
LUAU_ASSERT(!"Unreachable");
}
if (tt.indexer)
{
polarity = Polarity::Mixed;
traverse(tt.indexer->indexType);
traverse(tt.indexer->indexResultType);
}
polarity = p;
return false;
}
bool visit(TypeId ty, const FunctionType& ft) override
{
if (ty->owningArena != arena)
return false;
const Polarity p = polarity;
polarity = Polarity::Positive;
// If these types actually occur within the function signature, their
// polarity will be overwritten. If not, we infer that they are phantom
// types.
for (TypeId generic : ft.generics)
{
generic = follow(generic);
const auto gen = get<GenericType>(generic);
if (gen && subsumes(scope, gen->scope))
types[generic] = Polarity::None;
}
for (TypePackId genericPack : ft.genericPacks)
{
genericPack = follow(genericPack);
const auto gen = get<GenericTypePack>(genericPack);
if (gen && subsumes(scope, gen->scope))
packs[genericPack] = Polarity::None;
}
flip();
traverse(ft.argTypes);
flip();
traverse(ft.retTypes);
polarity = p;
return false;
}
bool visit(TypeId, const ExternType&) override
{
return false;
}
bool visit(TypePackId tp, const GenericTypePack& gtp) override
{
packs[tp] |= polarity;
return false;
}
};
template<typename TID>
static void inferGenericPolarities_(NotNull<TypeArena> arena, NotNull<Scope> scope, TID ty)
{
if (!FFlag::LuauNonReentrantGeneralization2)
return;
InferPolarity infer{arena, scope};
infer.traverse(ty);
for (const auto& [ty, polarity] : infer.types)
{
auto gt = getMutable<GenericType>(ty);
LUAU_ASSERT(gt);
gt->polarity = polarity;
}
for (const auto& [tp, polarity] : infer.packs)
{
if (tp->owningArena != arena)
continue;
auto gp = getMutable<GenericTypePack>(tp);
LUAU_ASSERT(gp);
gp->polarity = polarity;
}
}
void inferGenericPolarities(NotNull<TypeArena> arena, NotNull<Scope> scope, TypeId ty)
{
inferGenericPolarities_(arena, scope, ty);
}
void inferGenericPolarities(NotNull<TypeArena> arena, NotNull<Scope> scope, TypePackId tp)
{
inferGenericPolarities_(arena, scope, tp);
}
} // namespace Luau

View file

@ -11,6 +11,7 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
namespace Luau namespace Luau
{ {
@ -49,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<ExternType>(ty)) else if (get<ClassType>(ty))
return true; return true;
else else
return false; return false;
@ -60,7 +61,7 @@ TypeId Instantiation::clean(TypeId ty)
const FunctionType* ftv = log->getMutable<FunctionType>(ty); const FunctionType* ftv = log->getMutable<FunctionType>(ty);
LUAU_ASSERT(ftv); LUAU_ASSERT(ftv);
FunctionType clone = FunctionType{level, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf}; FunctionType clone = FunctionType{level, scope, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf};
clone.magic = ftv->magic; clone.magic = ftv->magic;
clone.tags = ftv->tags; clone.tags = ftv->tags;
clone.argNames = ftv->argNames; clone.argNames = ftv->argNames;
@ -119,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<ExternType>(ty)) else if (get<ClassType>(ty))
return true; return true;
else else
{ {
@ -163,7 +164,7 @@ TypeId ReplaceGenerics::clean(TypeId ty)
} }
else else
{ {
return arena->freshType(builtinTypes, scope, level); return FFlag::LuauFreeTypesMustHaveBounds ? arena->freshType(builtinTypes, scope, level) : addType(FreeType{scope, level});
} }
} }

View file

@ -6,7 +6,7 @@ namespace Luau
bool Instantiation2::ignoreChildren(TypeId ty) bool Instantiation2::ignoreChildren(TypeId ty)
{ {
if (get<ExternType>(ty)) if (get<ClassType>(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, DynamicPropertyLookupOnExternTypesUnsafe>) else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnClassesUnsafe>)
stream << "DynamicPropertyLookupOnExternTypesUnsafe { " << toString(err.ty) << " }"; stream << "DynamicPropertyLookupOnClassesUnsafe { " << 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>)
@ -229,8 +229,6 @@ static void errorToString(std::ostream& stream, const T& err)
stream << "UnexpectedTypePackInSubtyping { tp = '" + toString(err.tp) + "' }"; stream << "UnexpectedTypePackInSubtyping { tp = '" + toString(err.tp) + "' }";
else if constexpr (std::is_same_v<T, UserDefinedTypeFunctionError>) else if constexpr (std::is_same_v<T, UserDefinedTypeFunctionError>)
stream << "UserDefinedTypeFunctionError { " << err.message << " }"; stream << "UserDefinedTypeFunctionError { " << err.message << " }";
else if constexpr (std::is_same_v<T, ReservedIdentifier>)
stream << "ReservedIdentifier { " << err.name << " }";
else if constexpr (std::is_same_v<T, CannotAssignToNever>) else if constexpr (std::is_same_v<T, CannotAssignToNever>)
{ {
stream << "CannotAssignToNever { rvalueType = '" << toString(err.rhsType) << "', reason = '" << err.reason << "', cause = { "; stream << "CannotAssignToNever { rvalueType = '" << toString(err.rhsType) << "', reason = '" << err.reason << "', cause = { ";

View file

@ -19,9 +19,6 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAttribute) LUAU_FASTFLAG(LuauAttribute)
LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute) LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau namespace Luau
{ {
@ -909,11 +906,6 @@ 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)
@ -1976,11 +1968,6 @@ 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)
@ -2293,57 +2280,6 @@ private:
{ {
} }
bool visit(AstExprLocal* node) override
{
if (FFlag::LuauDeprecatedAttribute)
{
const FunctionType* fty = getFunctionType(node);
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
if (shouldReport)
report(node->location, node->local->name.value);
}
return true;
}
bool visit(AstExprGlobal* node) override
{
if (FFlag::LuauDeprecatedAttribute)
{
const FunctionType* fty = getFunctionType(node);
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
if (shouldReport)
report(node->location, node->name.value);
}
return true;
}
bool visit(AstStatLocalFunction* node) override
{
if (FFlag::LuauDeprecatedAttribute)
{
check(node->func);
return false;
}
else
return true;
}
bool visit(AstStatFunction* node) override
{
if (FFlag::LuauDeprecatedAttribute)
{
check(node->func);
return false;
}
else
return true;
}
bool visit(AstExprIndexName* node) override bool visit(AstExprIndexName* node) override
{ {
if (std::optional<TypeId> ty = context->getType(node->expr)) if (std::optional<TypeId> ty = context->getType(node->expr))
@ -2383,39 +2319,18 @@ private:
void check(AstExprIndexName* node, TypeId ty) void check(AstExprIndexName* node, TypeId ty)
{ {
if (const ExternType* cty = get<ExternType>(ty)) if (const ClassType* cty = get<ClassType>(ty))
{ {
const Property* prop = lookupExternTypeProp(cty, node->index.value); const Property* prop = lookupClassProp(cty, node->index.value);
if (prop && prop->deprecated) 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);
else if (FFlag::LuauDeprecatedAttribute && prop)
{
if (std::optional<TypeId> ty = prop->readTy)
{
const FunctionType* fty = get<FunctionType>(follow(ty));
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
if (shouldReport)
{
const char* className = nullptr;
if (AstExprGlobal* global = node->expr->as<AstExprGlobal>())
className = global->name.value;
const char* functionName = node->index.value;
report(node->location, className, functionName);
}
}
}
} }
else if (const TableType* tty = get<TableType>(ty)) else if (const TableType* tty = get<TableType>(ty))
{ {
auto prop = tty->props.find(node->index.value); auto prop = tty->props.find(node->index.value);
if (prop != tty->props.end()) if (prop != tty->props.end() && prop->second.deprecated)
{
if (prop->second.deprecated)
{ {
// strip synthetic typeof() for builtin tables // strip synthetic typeof() for builtin tables
if (tty->name && tty->name->compare(0, 7, "typeof(") == 0 && tty->name->back() == ')') if (tty->name && tty->name->compare(0, 7, "typeof(") == 0 && tty->name->back() == ')')
@ -2423,26 +2338,6 @@ private:
else else
report(node->location, prop->second, tty->name ? tty->name->c_str() : nullptr, node->index.value); report(node->location, prop->second, tty->name ? tty->name->c_str() : nullptr, node->index.value);
} }
else if (FFlag::LuauDeprecatedAttribute)
{
if (std::optional<TypeId> ty = prop->second.readTy)
{
const FunctionType* fty = get<FunctionType>(follow(ty));
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
if (shouldReport)
{
const char* className = nullptr;
if (AstExprGlobal* global = node->expr->as<AstExprGlobal>())
className = global->name.value;
const char* functionName = node->index.value;
report(node->location, className, functionName);
}
}
}
}
} }
} }
@ -2460,26 +2355,6 @@ private:
} }
} }
void check(AstExprFunction* func)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
LUAU_ASSERT(func);
const FunctionType* fty = getFunctionType(func);
bool isDeprecated = fty && fty->isDeprecatedFunction;
// If a function is deprecated, we don't want to flag its recursive uses.
// So we push it on a stack while its body is being analyzed.
// When a deprecated function is used, we check the stack to ensure that we are not inside that function.
if (isDeprecated)
pushScope(fty);
func->visit(this);
if (isDeprecated)
popScope(fty);
}
void report(const Location& location, const Property& prop, const char* container, const char* field) void report(const Location& location, const Property& prop, const char* container, const char* field)
{ {
std::string suggestion = prop.deprecatedSuggestion.empty() ? "" : format(", use '%s' instead", prop.deprecatedSuggestion.c_str()); std::string suggestion = prop.deprecatedSuggestion.empty() ? "" : format(", use '%s' instead", prop.deprecatedSuggestion.c_str());
@ -2489,63 +2364,6 @@ private:
else else
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s' is deprecated%s", field, suggestion.c_str()); emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s' is deprecated%s", field, suggestion.c_str());
} }
void report(const Location& location, const char* tableName, const char* functionName)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
if (tableName)
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s.%s' is deprecated", tableName, functionName);
else
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s' is deprecated", functionName);
}
void report(const Location& location, const char* functionName)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Function '%s' is deprecated", functionName);
}
std::vector<const FunctionType*> functionTypeScopeStack;
void pushScope(const FunctionType* fty)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
LUAU_ASSERT(fty);
functionTypeScopeStack.push_back(fty);
}
void popScope(const FunctionType* fty)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
LUAU_ASSERT(fty);
LUAU_ASSERT(fty == functionTypeScopeStack.back());
functionTypeScopeStack.pop_back();
}
bool inScope(const FunctionType* fty) const
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
LUAU_ASSERT(fty);
return std::find(functionTypeScopeStack.begin(), functionTypeScopeStack.end(), fty) != functionTypeScopeStack.end();
}
const FunctionType* getFunctionType(AstExpr* node)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
std::optional<TypeId> ty = context->getType(node);
if (!ty)
return nullptr;
const FunctionType* fty = get<FunctionType>(follow(ty));
return fty;
}
}; };
class LintTableOperations : AstVisitor class LintTableOperations : AstVisitor

View file

@ -15,12 +15,12 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations) LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteCommentDetection)
namespace Luau namespace Luau
{ {
static void defaultLogLuau(std::string_view context, std::string_view input) static void defaultLogLuau(std::string_view input)
{ {
// The default is to do nothing because we don't want to mess with // The default is to do nothing because we don't want to mess with
// the xml parsing done by the dcr script. // the xml parsing done by the dcr script.
@ -38,6 +38,21 @@ void resetLogLuauProc()
logLuau = &defaultLogLuau; logLuau = &defaultLogLuau;
} }
static bool contains_DEPRECATED(Position pos, Comment comment)
{
if (comment.location.contains(pos))
return true;
else if (comment.type == Lexeme::BrokenComment && comment.location.begin <= pos) // Broken comments are broken specifically because they don't
// have an end
return true;
else if (comment.type == Lexeme::Comment && comment.location.end == pos)
return true;
else
return false;
}
static bool contains(Position pos, Comment comment) static bool contains(Position pos, Comment comment)
{ {
if (comment.location.contains(pos)) if (comment.location.contains(pos))
@ -60,9 +75,12 @@ bool isWithinComment(const std::vector<Comment>& commentLocations, Position pos)
commentLocations.end(), commentLocations.end(),
Comment{Lexeme::Comment, Location{pos, pos}}, Comment{Lexeme::Comment, Location{pos, pos}},
[](const Comment& a, const Comment& b) [](const Comment& a, const Comment& b)
{
if (FFlag::LuauIncrementalAutocompleteCommentDetection)
{ {
if (a.type == Lexeme::Comment) if (a.type == Lexeme::Comment)
return a.location.end.line < b.location.end.line; return a.location.end.line < b.location.end.line;
}
return a.location.end < b.location.end; return a.location.end < b.location.end;
} }
); );
@ -70,7 +88,7 @@ bool isWithinComment(const std::vector<Comment>& commentLocations, Position pos)
if (iter == commentLocations.end()) if (iter == commentLocations.end())
return false; return false;
if (contains(pos, *iter)) if (FFlag::LuauIncrementalAutocompleteCommentDetection ? contains(pos, *iter) : contains_DEPRECATED(pos, *iter))
return true; return true;
// Due to the nature of std::lower_bound, it is possible that iter points at a comment that ends // Due to the nature of std::lower_bound, it is possible that iter points at a comment that ends
@ -154,6 +172,8 @@ struct ClonePublicInterface : Substitution
} }
ftv->level = TypeLevel{0, 0}; ftv->level = TypeLevel{0, 0};
if (FFlag::LuauSolverV2)
ftv->scope = nullptr;
} }
else if (TableType* ttv = getMutable<TableType>(result)) else if (TableType* ttv = getMutable<TableType>(result))
{ {
@ -265,9 +285,6 @@ struct ClonePublicInterface : Substitution
TypeId type = cloneType(tf.type); TypeId type = cloneType(tf.type);
if (FFlag::LuauRetainDefinitionAliasLocations)
return TypeFun{typeParams, typePackParams, type, tf.definitionLocation};
else
return TypeFun{typeParams, typePackParams, type}; return TypeFun{typeParams, typePackParams, type};
} }
}; };

View file

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

View file

@ -19,10 +19,10 @@ LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant)
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000) LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000)
LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200) LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauNormalizeNegationFix)
LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization) LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization)
LUAU_FASTFLAGVARIABLE(LuauNormalizationCatchMetatableCycles) LUAU_FASTFLAGVARIABLE(LuauNormalizedBufferIsNotUnknown)
namespace Luau namespace Luau
{ {
@ -249,23 +249,23 @@ bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& s
return true; return true;
} }
void NormalizedExternType::pushPair(TypeId ty, TypeIds negations) void NormalizedClassType::pushPair(TypeId ty, TypeIds negations)
{ {
auto result = externTypes.insert(std::make_pair(ty, std::move(negations))); auto result = classes.insert(std::make_pair(ty, std::move(negations)));
if (result.second) if (result.second)
ordering.push_back(ty); ordering.push_back(ty);
LUAU_ASSERT(ordering.size() == externTypes.size()); LUAU_ASSERT(ordering.size() == classes.size());
} }
void NormalizedExternType::resetToNever() void NormalizedClassType::resetToNever()
{ {
ordering.clear(); ordering.clear();
externTypes.clear(); classes.clear();
} }
bool NormalizedExternType::isNever() const bool NormalizedClassType::isNever() const
{ {
return externTypes.empty(); return classes.empty();
} }
void NormalizedFunctionType::resetToTop() void NormalizedFunctionType::resetToTop()
@ -304,17 +304,19 @@ bool NormalizedType::isUnknown() const
// Otherwise, we can still be unknown! // Otherwise, we can still be unknown!
bool hasAllPrimitives = isPrim(booleans, PrimitiveType::Boolean) && isPrim(nils, PrimitiveType::NilType) && isNumber(numbers) && bool hasAllPrimitives = isPrim(booleans, PrimitiveType::Boolean) && isPrim(nils, PrimitiveType::NilType) && isNumber(numbers) &&
strings.isString() && isThread(threads) && isBuffer(buffers); strings.isString() &&
(FFlag::LuauNormalizedBufferIsNotUnknown ? isThread(threads) && isBuffer(buffers)
: isPrim(threads, PrimitiveType::Thread) && isThread(threads));
// Check is class // Check is class
bool isTopExternType = false; bool isTopClass = false;
for (const auto& [t, disj] : externTypes.externTypes) for (auto [t, disj] : classes.classes)
{ {
if (auto ct = get<ExternType>(t)) if (auto ct = get<ClassType>(t))
{ {
if (ct->name == "class" && disj.empty()) if (ct->name == "class" && disj.empty())
{ {
isTopExternType = true; isTopClass = true;
break; break;
} }
} }
@ -330,24 +332,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 && isTopExternType && isTopTable && functions.isTop; return get<NeverType>(errors) && hasAllPrimitives && isTopClass && isTopTable && functions.isTop;
} }
bool NormalizedType::isExactlyNumber() const bool NormalizedType::isExactlyNumber() const
{ {
return hasNumbers() && !hasTops() && !hasBooleans() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasStrings() && !hasThreads() && return hasNumbers() && !hasTops() && !hasBooleans() && !hasClasses() && !hasErrors() && !hasNils() && !hasStrings() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars(); !hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
} }
bool NormalizedType::isSubtypeOfString() const bool NormalizedType::isSubtypeOfString() const
{ {
return hasStrings() && !hasTops() && !hasBooleans() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasNumbers() && !hasThreads() && return hasStrings() && !hasTops() && !hasBooleans() && !hasClasses() && !hasErrors() && !hasNils() && !hasNumbers() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars(); !hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
} }
bool NormalizedType::isSubtypeOfBooleans() const bool NormalizedType::isSubtypeOfBooleans() const
{ {
return hasBooleans() && !hasTops() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasNumbers() && !hasStrings() && !hasThreads() && return hasBooleans() && !hasTops() && !hasClasses() && !hasErrors() && !hasNils() && !hasNumbers() && !hasStrings() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars(); !hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
} }
@ -380,9 +382,9 @@ bool NormalizedType::hasBooleans() const
return !get<NeverType>(booleans); return !get<NeverType>(booleans);
} }
bool NormalizedType::hasExternTypes() const bool NormalizedType::hasClasses() const
{ {
return !externTypes.isNever(); return !classes.isNever();
} }
bool NormalizedType::hasErrors() const bool NormalizedType::hasErrors() const
@ -440,7 +442,7 @@ bool NormalizedType::isFalsy() const
hasAFalse = !bs->value; hasAFalse = !bs->value;
} }
return (hasAFalse || hasNils()) && (!hasTops() && !hasExternTypes() && !hasErrors() && !hasNumbers() && !hasStrings() && !hasThreads() && return (hasAFalse || hasNils()) && (!hasTops() && !hasClasses() && !hasErrors() && !hasNumbers() && !hasStrings() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars()); !hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars());
} }
@ -452,7 +454,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.externTypes.isNever() || !get<NeverType>(norm.errors) || return !get<NeverType>(norm.tops) || !get<NeverType>(norm.booleans) || !norm.classes.isNever() || !get<NeverType>(norm.errors) ||
!get<NeverType>(norm.nils) || !get<NeverType>(norm.numbers) || !norm.strings.isNever() || !get<NeverType>(norm.threads) || !get<NeverType>(norm.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 +473,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->externTypes.isNever() || !get<NeverType>(norm->numbers) || !get<NeverType>(norm->threads) || !get<NeverType>(norm->buffers) || !norm->classes.isNever() ||
!norm->strings.isNever() || !norm->functions.isNever()) !norm->strings.isNever() || !norm->functions.isNever())
return NormalizationResult::True; return NormalizationResult::True;
@ -579,7 +581,7 @@ NormalizationResult Normalizer::isIntersectionInhabited(TypeId left, TypeId righ
{ {
left = follow(left); left = follow(left);
right = follow(right); right = follow(right);
// We're asking if intersection is inhabited between left and right but we've already seen them .... // We're asking if intersection is inahbited between left and right but we've already seen them ....
if (cacheInhabitance) if (cacheInhabitance)
{ {
@ -619,13 +621,13 @@ static int tyvarIndex(TypeId ty)
return 0; return 0;
} }
static bool isTop(NotNull<BuiltinTypes> builtinTypes, const NormalizedExternType& externTypes) static bool isTop(NotNull<BuiltinTypes> builtinTypes, const NormalizedClassType& classes)
{ {
if (externTypes.externTypes.size() != 1) if (classes.classes.size() != 1)
return false; return false;
auto first = externTypes.externTypes.begin(); auto first = classes.classes.begin();
if (first->first != builtinTypes->externType) if (first->first != builtinTypes->classType)
return false; return false;
if (!first->second.empty()) if (!first->second.empty())
@ -634,11 +636,11 @@ static bool isTop(NotNull<BuiltinTypes> builtinTypes, const NormalizedExternType
return true; return true;
} }
static void resetToTop(NotNull<BuiltinTypes> builtinTypes, NormalizedExternType& externTypes) static void resetToTop(NotNull<BuiltinTypes> builtinTypes, NormalizedClassType& classes)
{ {
externTypes.ordering.clear(); classes.ordering.clear();
externTypes.externTypes.clear(); classes.classes.clear();
externTypes.pushPair(builtinTypes->externType, TypeIds{}); classes.pushPair(builtinTypes->classType, TypeIds{});
} }
#ifdef LUAU_ASSERTENABLED #ifdef LUAU_ASSERTENABLED
@ -762,50 +764,50 @@ static bool areNormalizedTables(const TypeIds& tys)
return true; return true;
} }
static bool areNormalizedExternTypes(const NormalizedExternType& tys) static bool areNormalizedClasses(const NormalizedClassType& tys)
{ {
for (const auto& [ty, negations] : tys.externTypes) for (const auto& [ty, negations] : tys.classes)
{ {
const ExternType* etv = get<ExternType>(ty); const ClassType* ctv = get<ClassType>(ty);
if (!etv) if (!ctv)
{ {
return false; return false;
} }
for (TypeId negation : negations) for (TypeId negation : negations)
{ {
const ExternType* nctv = get<ExternType>(negation); const ClassType* nctv = get<ClassType>(negation);
if (!nctv) if (!nctv)
{ {
return false; return false;
} }
if (!isSubclass(nctv, etv)) if (!isSubclass(nctv, ctv))
{ {
return false; return false;
} }
} }
for (const auto& [otherTy, otherNegations] : tys.externTypes) for (const auto& [otherTy, otherNegations] : tys.classes)
{ {
if (otherTy == ty) if (otherTy == ty)
continue; continue;
const ExternType* octv = get<ExternType>(otherTy); const ClassType* octv = get<ClassType>(otherTy);
if (!octv) if (!octv)
{ {
return false; return false;
} }
if (isSubclass(etv, octv)) if (isSubclass(ctv, octv))
{ {
auto iss = [etv](TypeId t) auto iss = [ctv](TypeId t)
{ {
const ExternType* c = get<ExternType>(t); const ClassType* c = get<ClassType>(t);
if (!c) if (!c)
return false; return false;
return isSubclass(etv, c); return isSubclass(ctv, c);
}; };
if (!std::any_of(otherNegations.begin(), otherNegations.end(), iss)) if (!std::any_of(otherNegations.begin(), otherNegations.end(), iss))
@ -847,7 +849,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(areNormalizedExternTypes(norm.externTypes)); LUAU_ASSERT(areNormalizedClasses(norm.classes));
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 +990,7 @@ void Normalizer::clearNormal(NormalizedType& norm)
{ {
norm.tops = builtinTypes->neverType; norm.tops = builtinTypes->neverType;
norm.booleans = builtinTypes->neverType; norm.booleans = builtinTypes->neverType;
norm.externTypes.resetToNever(); norm.classes.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 +1140,17 @@ TypeId Normalizer::unionOfBools(TypeId here, TypeId there)
return builtinTypes->booleanType; return builtinTypes->booleanType;
} }
void Normalizer::unionExternTypesWithExternType(TypeIds& heres, TypeId there) void Normalizer::unionClassesWithClass(TypeIds& heres, TypeId there)
{ {
if (heres.count(there)) if (heres.count(there))
return; return;
const ExternType* tctv = get<ExternType>(there); const ClassType* tctv = get<ClassType>(there);
for (auto it = heres.begin(); it != heres.end();) for (auto it = heres.begin(); it != heres.end();)
{ {
TypeId here = *it; TypeId here = *it;
const ExternType* hctv = get<ExternType>(here); const ClassType* hctv = get<ClassType>(here);
if (isSubclass(tctv, hctv)) if (isSubclass(tctv, hctv))
return; return;
else if (isSubclass(hctv, tctv)) else if (isSubclass(hctv, tctv))
@ -1160,16 +1162,16 @@ void Normalizer::unionExternTypesWithExternType(TypeIds& heres, TypeId there)
heres.insert(there); heres.insert(there);
} }
void Normalizer::unionExternTypes(TypeIds& heres, const TypeIds& theres) void Normalizer::unionClasses(TypeIds& heres, const TypeIds& theres)
{ {
for (TypeId there : theres) for (TypeId there : theres)
unionExternTypesWithExternType(heres, there); unionClassesWithClass(heres, there);
} }
static bool isSubclass(TypeId test, TypeId parent) static bool isSubclass(TypeId test, TypeId parent)
{ {
const ExternType* testCtv = get<ExternType>(test); const ClassType* testCtv = get<ClassType>(test);
const ExternType* parentCtv = get<ExternType>(parent); const ClassType* parentCtv = get<ClassType>(parent);
LUAU_ASSERT(testCtv); LUAU_ASSERT(testCtv);
LUAU_ASSERT(parentCtv); LUAU_ASSERT(parentCtv);
@ -1177,12 +1179,12 @@ static bool isSubclass(TypeId test, TypeId parent)
return isSubclass(testCtv, parentCtv); return isSubclass(testCtv, parentCtv);
} }
void Normalizer::unionExternTypesWithExternType(NormalizedExternType& heres, TypeId there) void Normalizer::unionClassesWithClass(NormalizedClassType& heres, TypeId there)
{ {
for (auto it = heres.ordering.begin(); it != heres.ordering.end();) for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{ {
TypeId hereTy = *it; TypeId hereTy = *it;
TypeIds& hereNegations = heres.externTypes.at(hereTy); TypeIds& hereNegations = heres.classes.at(hereTy);
// If the incoming class is a subclass of another class in the map, we // 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 +1206,7 @@ void Normalizer::unionExternTypesWithExternType(NormalizedExternType& heres, Typ
} }
// 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 extern types // removed. This is also true if they are equal. Since classes
// 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 +1233,7 @@ void Normalizer::unionExternTypesWithExternType(NormalizedExternType& heres, Typ
{ {
TypeIds negations = std::move(hereNegations); TypeIds negations = std::move(hereNegations);
it = heres.ordering.erase(it); it = heres.ordering.erase(it);
heres.externTypes.erase(hereTy); heres.classes.erase(hereTy);
heres.pushPair(there, std::move(negations)); heres.pushPair(there, std::move(negations));
return; return;
@ -1248,10 +1250,10 @@ void Normalizer::unionExternTypesWithExternType(NormalizedExternType& heres, Typ
heres.pushPair(there, TypeIds{}); heres.pushPair(there, TypeIds{});
} }
void Normalizer::unionExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres) void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassType& theres)
{ {
// This method bears much similarity with unionExternTypesWithExternType, but is // This method bears much similarity with unionClassesWithClass, but is
// solving a more general problem. In unionExternTypesWithExternType, we are dealing // solving a more general problem. In unionClassesWithClass, 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 +1262,9 @@ void Normalizer::unionExternTypes(NormalizedExternType& heres, const NormalizedE
for (const TypeId thereTy : theres.ordering) for (const TypeId thereTy : theres.ordering)
{ {
const TypeIds& thereNegations = theres.externTypes.at(thereTy); const TypeIds& thereNegations = theres.classes.at(thereTy);
// If it happens that there are _no_ extern types in the current map, or the // If it happens that there are _no_ classes in the current map, or the
// incoming class is completely unrelated to any class in the current // 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 +1272,7 @@ void Normalizer::unionExternTypes(NormalizedExternType& heres, const NormalizedE
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.externTypes.at(hereTy); TypeIds& hereNegations = heres.classes.at(hereTy);
if (isSubclass(thereTy, hereTy)) if (isSubclass(thereTy, hereTy))
{ {
@ -1294,7 +1296,7 @@ void Normalizer::unionExternTypes(NormalizedExternType& heres, const NormalizedE
// 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
// extern types are, at this time, entirely persistent (we do not // classes are, at this time, entirely persistent (we do not
// clone them), a pointer identity check is sufficient. // clone them), a pointer identity check is sufficient.
else if (isSubclass(hereNegateTy, thereTy)) else if (isSubclass(hereNegateTy, thereTy))
{ {
@ -1319,17 +1321,17 @@ void Normalizer::unionExternTypes(NormalizedExternType& heres, const NormalizedE
else if (isSubclass(hereTy, thereTy)) else if (isSubclass(hereTy, thereTy))
{ {
TypeIds negations = std::move(hereNegations); TypeIds negations = std::move(hereNegations);
unionExternTypes(negations, thereNegations); unionClasses(negations, thereNegations);
it = heres.ordering.erase(it); it = heres.ordering.erase(it);
heres.externTypes.erase(hereTy); heres.classes.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)
{ {
unionExternTypes(hereNegations, thereNegations); unionClasses(hereNegations, thereNegations);
insert = false; insert = false;
break; break;
} }
@ -1685,12 +1687,8 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali
return res; return res;
} }
// Limit based on worst-case expansion of the function unions
if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeUnionLimit))
return NormalizationResult::HitLimits;
here.booleans = unionOfBools(here.booleans, there.booleans); here.booleans = unionOfBools(here.booleans, there.booleans);
unionExternTypes(here.externTypes, there.externTypes); unionClasses(here.classes, there.classes);
here.errors = (get<NeverType>(there.errors) ? here.errors : there.errors); here.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);
@ -1700,7 +1698,6 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali
here.buffers = (get<NeverType>(there.buffers) ? here.buffers : there.buffers); here.buffers = (get<NeverType>(there.buffers) ? here.buffers : there.buffers);
unionFunctions(here.functions, there.functions); unionFunctions(here.functions, there.functions);
unionTables(here.tables, there.tables); unionTables(here.tables, there.tables);
return NormalizationResult::True; return NormalizationResult::True;
} }
@ -1740,7 +1737,7 @@ NormalizationResult Normalizer::intersectNormalWithNegationTy(TypeId toNegate, N
return NormalizationResult::True; return NormalizationResult::True;
} }
// See above for an explanation of `ignoreSmallerTyvars`. // See above for an explaination of `ignoreSmallerTyvars`.
NormalizationResult Normalizer::unionNormalWithTy( NormalizationResult Normalizer::unionNormalWithTy(
NormalizedType& here, NormalizedType& here,
TypeId there, TypeId there,
@ -1830,8 +1827,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<ExternType>(there)) else if (get<ClassType>(there))
unionExternTypesWithExternType(here.externTypes, there); unionClassesWithClass(here.classes, 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 +1941,29 @@ std::optional<NormalizedType> Normalizer::negateNormal(const NormalizedType& her
result.booleans = builtinTypes->trueType; result.booleans = builtinTypes->trueType;
} }
if (here.externTypes.isNever()) if (here.classes.isNever())
{ {
resetToTop(builtinTypes, result.externTypes); resetToTop(builtinTypes, result.classes);
} }
else if (isTop(builtinTypes, result.externTypes)) else if (isTop(builtinTypes, result.classes))
{ {
result.externTypes.resetToNever(); result.classes.resetToNever();
} }
else else
{ {
TypeIds rootNegations{}; TypeIds rootNegations{};
for (const auto& [hereParent, hereNegations] : here.externTypes.externTypes) for (const auto& [hereParent, hereNegations] : here.classes.classes)
{ {
if (hereParent != builtinTypes->externType) if (hereParent != builtinTypes->classType)
rootNegations.insert(hereParent); rootNegations.insert(hereParent);
for (TypeId hereNegation : hereNegations) for (TypeId hereNegation : hereNegations)
unionExternTypesWithExternType(result.externTypes, hereNegation); unionClassesWithClass(result.classes, hereNegation);
} }
if (!rootNegations.empty()) if (!rootNegations.empty())
result.externTypes.pushPair(builtinTypes->externType, rootNegations); result.classes.pushPair(builtinTypes->classType, rootNegations);
} }
result.nils = get<NeverType>(here.nils) ? builtinTypes->nilType : builtinTypes->neverType; result.nils = get<NeverType>(here.nils) ? builtinTypes->nilType : builtinTypes->neverType;
@ -2144,7 +2141,7 @@ TypeId Normalizer::intersectionOfBools(TypeId here, TypeId there)
return there; return there;
} }
void Normalizer::intersectExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres) void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedClassType& theres)
{ {
if (theres.isNever()) if (theres.isNever())
{ {
@ -2178,12 +2175,12 @@ void Normalizer::intersectExternTypes(NormalizedExternType& heres, const Normali
// 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.externTypes.at(thereTy); const TypeIds& thereNegations = theres.classes.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.externTypes.at(hereTy); TypeIds& hereNegations = heres.classes.at(hereTy);
if (isSubclass(thereTy, hereTy)) if (isSubclass(thereTy, hereTy))
{ {
@ -2206,10 +2203,10 @@ void Normalizer::intersectExternTypes(NormalizedExternType& heres, const Normali
} }
} }
unionExternTypes(negations, thereNegations); unionClasses(negations, thereNegations);
it = heres.ordering.erase(it); it = heres.ordering.erase(it);
heres.externTypes.erase(hereTy); heres.classes.erase(hereTy);
heres.pushPair(thereTy, std::move(negations)); heres.pushPair(thereTy, std::move(negations));
break; break;
} }
@ -2234,15 +2231,15 @@ void Normalizer::intersectExternTypes(NormalizedExternType& heres, const Normali
{ {
if (isSubclass(hereTy, *nIt)) if (isSubclass(hereTy, *nIt))
{ {
// eg SomeExternType & (class & ~SomeExternType) // eg SomeClass & (class & ~SomeClass)
// or SomeExternType & (class & ~ParentExternType) // or SomeClass & (class & ~ParentClass)
heres.externTypes.erase(hereTy); heres.classes.erase(hereTy);
it = heres.ordering.erase(it); it = heres.ordering.erase(it);
erasedHere = true; erasedHere = true;
break; break;
} }
// eg SomeExternType & (class & ~Unrelated) // eg SomeClass & (class & ~Unrelated)
if (!isSubclass(*nIt, hereTy)) if (!isSubclass(*nIt, hereTy))
nIt = negations.erase(nIt); nIt = negations.erase(nIt);
else else
@ -2251,30 +2248,30 @@ void Normalizer::intersectExternTypes(NormalizedExternType& heres, const Normali
if (!erasedHere) if (!erasedHere)
{ {
unionExternTypes(hereNegations, negations); unionClasses(hereNegations, negations);
++it; ++it;
} }
} }
else if (hereTy == thereTy) else if (hereTy == thereTy)
{ {
unionExternTypes(hereNegations, thereNegations); unionClasses(hereNegations, thereNegations);
break; break;
} }
else else
{ {
it = heres.ordering.erase(it); it = heres.ordering.erase(it);
heres.externTypes.erase(hereTy); heres.classes.erase(hereTy);
} }
} }
} }
} }
void Normalizer::intersectExternTypesWithExternType(NormalizedExternType& heres, TypeId there) void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId there)
{ {
for (auto it = heres.ordering.begin(); it != heres.ordering.end();) for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{ {
TypeId hereTy = *it; TypeId hereTy = *it;
const TypeIds& hereNegations = heres.externTypes.at(hereTy); const TypeIds& hereNegations = heres.classes.at(hereTy);
// If the incoming class _is_ the current class, we skip it. Maybe // 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 +2316,7 @@ void Normalizer::intersectExternTypesWithExternType(NormalizedExternType& heres,
} }
it = heres.ordering.erase(it); it = heres.ordering.erase(it);
heres.externTypes.erase(hereTy); heres.classes.erase(hereTy);
if (!emptyIntersectWithNegation) if (!emptyIntersectWithNegation)
heres.pushPair(there, std::move(negations)); heres.pushPair(there, std::move(negations));
break; break;
@ -2335,7 +2332,7 @@ void Normalizer::intersectExternTypesWithExternType(NormalizedExternType& heres,
else else
{ {
it = heres.ordering.erase(it); it = heres.ordering.erase(it);
heres.externTypes.erase(hereTy); heres.classes.erase(hereTy);
} }
} }
} }
@ -3055,7 +3052,7 @@ NormalizationResult Normalizer::intersectTyvarsWithTy(
return NormalizationResult::True; return NormalizationResult::True;
} }
// See above for an explanation of `ignoreSmallerTyvars`. // See above for an explaination of `ignoreSmallerTyvars`.
NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars) NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars)
{ {
RecursionCounter _rc(&sharedState->counters.recursionCount); RecursionCounter _rc(&sharedState->counters.recursionCount);
@ -3073,17 +3070,14 @@ NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const Nor
return unionNormals(here, there, ignoreSmallerTyvars); return unionNormals(here, there, ignoreSmallerTyvars);
} }
// Limit based on worst-case expansion of the table/function intersections // Limit based on worst-case expansion of the table intersection
// This restriction can be relaxed when table intersection simplification is improved // This restriction can be relaxed when table intersection simplification is improved
if (here.tables.size() * there.tables.size() >= size_t(FInt::LuauNormalizeIntersectionLimit)) if (here.tables.size() * there.tables.size() >= size_t(FInt::LuauNormalizeIntersectionLimit))
return NormalizationResult::HitLimits; return NormalizationResult::HitLimits;
if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeIntersectionLimit))
return NormalizationResult::HitLimits;
here.booleans = intersectionOfBools(here.booleans, there.booleans); here.booleans = intersectionOfBools(here.booleans, there.booleans);
intersectExternTypes(here.externTypes, there.externTypes); intersectClasses(here.classes, there.classes);
here.errors = (get<NeverType>(there.errors) ? there.errors : here.errors); here.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,18 +3199,18 @@ 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<ExternType>(there)) else if (get<ClassType>(there))
{ {
NormalizedExternType nct = std::move(here.externTypes); NormalizedClassType nct = std::move(here.classes);
clearNormal(here); clearNormal(here);
intersectExternTypesWithExternType(nct, there); intersectClassesWithClass(nct, there);
here.externTypes = std::move(nct); here.classes = std::move(nct);
} }
else if (get<ErrorType>(there)) else if (get<ErrorType>(there))
{ {
TypeId errors = here.errors; TypeId errors = here.errors;
clearNormal(here); clearNormal(here);
here.errors = get<ErrorType>(errors) ? errors : there; here.errors = errors;
} }
else if (const PrimitiveType* ptv = get<PrimitiveType>(there)) else if (const PrimitiveType* ptv = get<PrimitiveType>(there))
{ {
@ -3274,7 +3268,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<ExternType>(t)) else if (get<ClassType>(t))
{ {
NormalizationResult res = intersectNormalWithNegationTy(t, here); NormalizationResult res = intersectNormalWithNegationTy(t, here);
if (shouldEarlyExit(res)) if (shouldEarlyExit(res))
@ -3313,16 +3307,11 @@ NormalizationResult Normalizer::intersectNormalWithTy(
clearNormal(here); clearNormal(here);
return NormalizationResult::True; return NormalizationResult::True;
} }
else if (get<ErrorType>(t))
{
// ~error is still an error, so intersecting with the negation is the same as intersecting with a type
TypeId errors = here.errors;
clearNormal(here);
here.errors = get<ErrorType>(errors) ? errors : t;
}
else if (auto nt = get<NegationType>(t)) else if (auto nt = get<NegationType>(t))
{ {
if (FFlag::LuauNormalizeNegationFix)
here.tyvars = std::move(tyvars); here.tyvars = std::move(tyvars);
return intersectNormalWithTy(here, nt->ty, seenTablePropPairs, seenSetTypes); return intersectNormalWithTy(here, nt->ty, seenTablePropPairs, seenSetTypes);
} }
else else
@ -3334,7 +3323,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
} }
else if (get<NeverType>(there)) else if (get<NeverType>(there))
{ {
here.externTypes.resetToNever(); here.classes.resetToNever();
} }
else if (get<NoRefineType>(there)) else if (get<NoRefineType>(there))
{ {
@ -3352,43 +3341,19 @@ NormalizationResult Normalizer::intersectNormalWithTy(
return NormalizationResult::True; return NormalizationResult::True;
} }
void makeTableShared_DEPRECATED(TypeId ty)
{
ty = follow(ty);
if (auto tableTy = getMutable<TableType>(ty))
{
for (auto& [_, prop] : tableTy->props)
prop.makeShared();
}
else if (auto metatableTy = get<MetatableType>(ty))
{
makeTableShared_DEPRECATED(metatableTy->metatable);
makeTableShared_DEPRECATED(metatableTy->table);
}
}
void makeTableShared(TypeId ty, DenseHashSet<TypeId>& seen)
{
ty = follow(ty);
if (seen.contains(ty))
return;
seen.insert(ty);
if (auto tableTy = getMutable<TableType>(ty))
{
for (auto& [_, prop] : tableTy->props)
prop.makeShared();
}
else if (auto metatableTy = get<MetatableType>(ty))
{
makeTableShared(metatableTy->metatable, seen);
makeTableShared(metatableTy->table, seen);
}
}
void makeTableShared(TypeId ty) void makeTableShared(TypeId ty)
{ {
DenseHashSet<TypeId> seen{nullptr}; ty = follow(ty);
makeTableShared(ty, seen); if (auto tableTy = getMutable<TableType>(ty))
{
for (auto& [_, prop] : tableTy->props)
prop.makeShared();
}
else if (auto metatableTy = get<MetatableType>(ty))
{
makeTableShared(metatableTy->metatable);
makeTableShared(metatableTy->table);
}
} }
// -------- Convert back from a normalized type to a type // -------- Convert back from a normalized type to a type
@ -3403,18 +3368,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.externTypes)) if (isTop(builtinTypes, norm.classes))
{ {
result.push_back(builtinTypes->externType); result.push_back(builtinTypes->classType);
} }
else if (!norm.externTypes.isNever()) else if (!norm.classes.isNever())
{ {
std::vector<TypeId> parts; std::vector<TypeId> parts;
parts.reserve(norm.externTypes.externTypes.size()); parts.reserve(norm.classes.classes.size());
for (const TypeId normTy : norm.externTypes.ordering) for (const TypeId normTy : norm.classes.ordering)
{ {
const TypeIds& normNegations = norm.externTypes.externTypes.at(normTy); const TypeIds& normNegations = norm.classes.classes.at(normTy);
if (normNegations.empty()) if (normNegations.empty())
{ {
@ -3490,10 +3455,7 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
result.reserve(result.size() + norm.tables.size()); result.reserve(result.size() + norm.tables.size());
for (auto table : norm.tables) for (auto table : norm.tables)
{ {
if (FFlag::LuauNormalizationCatchMetatableCycles)
makeTableShared(table); makeTableShared(table);
else
makeTableShared_DEPRECATED(table);
result.push_back(table); result.push_back(table);
} }
} }

View file

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

View file

@ -4,8 +4,6 @@
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/Module.h" #include "Luau/Module.h"
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau namespace Luau
{ {
@ -67,12 +65,6 @@ 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

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

View file

@ -2,23 +2,24 @@
#include "Luau/Substitution.h" #include "Luau/Substitution.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/Clone.h"
#include "Luau/TxnLog.h" #include "Luau/TxnLog.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include <algorithm> #include <algorithm>
#include <stdexcept>
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000) LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256) LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256)
LUAU_FASTFLAG(LuauSyntheticErrors) LUAU_FASTFLAG(LuauSyntheticErrors)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
namespace Luau namespace Luau
{ {
static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log) static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool alwaysClone)
{ {
auto go = [ty, &dest](auto&& a) auto go = [ty, &dest, alwaysClone](auto&& a)
{ {
using T = std::decay_t<decltype(a)>; using T = std::decay_t<decltype(a)>;
@ -94,15 +95,13 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log)
return dest.addType(a); return dest.addType(a);
else if constexpr (std::is_same_v<T, FunctionType>) else if constexpr (std::is_same_v<T, FunctionType>)
{ {
FunctionType clone = FunctionType{a.level, a.argTypes, a.retTypes, a.definition, a.hasSelf}; FunctionType clone = FunctionType{a.level, a.scope, a.argTypes, a.retTypes, a.definition, a.hasSelf};
clone.generics = a.generics; clone.generics = a.generics;
clone.genericPacks = a.genericPacks; clone.genericPacks = a.genericPacks;
clone.magic = a.magic; clone.magic = a.magic;
clone.tags = a.tags; clone.tags = a.tags;
clone.argNames = a.argNames; clone.argNames = a.argNames;
clone.isCheckedFunction = a.isCheckedFunction; clone.isCheckedFunction = a.isCheckedFunction;
if (FFlag::LuauDeprecatedAttribute)
clone.isDeprecatedFunction = a.isDeprecatedFunction;
return dest.addType(std::move(clone)); return dest.addType(std::move(clone));
} }
else if constexpr (std::is_same_v<T, TableType>) else if constexpr (std::is_same_v<T, TableType>)
@ -136,11 +135,16 @@ 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, ExternType>) else if constexpr (std::is_same_v<T, ClassType>)
{ {
ExternType clone{a.name, a.props, a.parent, a.metatable, a.tags, a.userData, a.definitionModuleName, a.definitionLocation, a.indexer}; if (alwaysClone)
{
ClassType clone{a.name, a.props, a.parent, a.metatable, a.tags, a.userData, a.definitionModuleName, a.definitionLocation, a.indexer};
return dest.addType(std::move(clone)); return dest.addType(std::move(clone));
} }
else
return ty;
}
else if constexpr (std::is_same_v<T, NegationType>) else if constexpr (std::is_same_v<T, NegationType>)
return dest.addType(NegationType{a.ty}); return dest.addType(NegationType{a.ty});
else if constexpr (std::is_same_v<T, TypeFunctionInstanceType>) else if constexpr (std::is_same_v<T, TypeFunctionInstanceType>)
@ -252,21 +256,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 ExternType* etv = get<ExternType>(ty)) else if (const ClassType* ctv = get<ClassType>(ty))
{ {
for (const auto& [name, prop] : etv->props) for (const auto& [name, prop] : ctv->props)
visitChild(prop.type()); visitChild(prop.type());
if (etv->parent) if (ctv->parent)
visitChild(*etv->parent); visitChild(*ctv->parent);
if (etv->metatable) if (ctv->metatable)
visitChild(*etv->metatable); visitChild(*ctv->metatable);
if (etv->indexer) if (ctv->indexer)
{ {
visitChild(etv->indexer->indexType); visitChild(ctv->indexer->indexType);
visitChild(etv->indexer->indexResultType); visitChild(ctv->indexer->indexResultType);
} }
} }
else if (const NegationType* ntv = get<NegationType>(ty)) else if (const NegationType* ntv = get<NegationType>(ty))
@ -540,27 +544,6 @@ void Tarjan::visitSCC(int index)
} }
} }
bool Tarjan::ignoreChildren(TypeId ty)
{
return false;
}
bool Tarjan::ignoreChildren(TypePackId ty)
{
return false;
}
// Some subclasses might ignore children visit, but not other actions like replacing the children
bool Tarjan::ignoreChildrenVisit(TypeId ty)
{
return ignoreChildren(ty);
}
bool Tarjan::ignoreChildrenVisit(TypePackId ty)
{
return ignoreChildren(ty);
}
TarjanResult Tarjan::findDirty(TypeId ty) TarjanResult Tarjan::findDirty(TypeId ty)
{ {
return visitRoot(ty); return visitRoot(ty);
@ -571,11 +554,6 @@ TarjanResult Tarjan::findDirty(TypePackId tp)
return visitRoot(tp); return visitRoot(tp);
} }
Substitution::Substitution(TypeArena* arena)
: Substitution(TxnLog::empty(), arena)
{
}
Substitution::Substitution(const TxnLog* log_, TypeArena* arena) Substitution::Substitution(const TxnLog* log_, TypeArena* arena)
: arena(arena) : arena(arena)
{ {
@ -676,7 +654,7 @@ void Substitution::resetState(const TxnLog* log, TypeArena* arena)
TypeId Substitution::clone(TypeId ty) TypeId Substitution::clone(TypeId ty)
{ {
return shallowClone(ty, *arena, log); return shallowClone(ty, *arena, log, /* alwaysClone */ true);
} }
TypePackId Substitution::clone(TypePackId tp) TypePackId Substitution::clone(TypePackId tp)
@ -838,21 +816,21 @@ void Substitution::replaceChildren(TypeId ty)
for (TypePackId& a : tfit->packArguments) for (TypePackId& a : tfit->packArguments)
a = replace(a); a = replace(a);
} }
else if (ExternType* etv = getMutable<ExternType>(ty)) else if (ClassType* ctv = getMutable<ClassType>(ty))
{ {
for (auto& [name, prop] : etv->props) for (auto& [name, prop] : ctv->props)
prop.setType(replace(prop.type())); prop.setType(replace(prop.type()));
if (etv->parent) if (ctv->parent)
etv->parent = replace(*etv->parent); ctv->parent = replace(*ctv->parent);
if (etv->metatable) if (ctv->metatable)
etv->metatable = replace(*etv->metatable); ctv->metatable = replace(*ctv->metatable);
if (etv->indexer) if (ctv->indexer)
{ {
etv->indexer->indexType = replace(etv->indexer->indexType); ctv->indexer->indexType = replace(ctv->indexer->indexType);
etv->indexer->indexResultType = replace(etv->indexer->indexResultType); ctv->indexer->indexResultType = replace(ctv->indexer->indexResultType);
} }
} }
else if (NegationType* ntv = getMutable<NegationType>(ty)) else if (NegationType* ntv = getMutable<NegationType>(ty))
@ -892,13 +870,4 @@ void Substitution::replaceChildren(TypePackId tp)
} }
} }
template<typename Ty>
std::optional<Ty> Substitution::replace(std::optional<Ty> ty)
{
if (ty)
return replace(*ty);
else
return std::nullopt;
}
} // namespace Luau } // namespace Luau

View file

@ -7,11 +7,13 @@
#include "Luau/Normalize.h" #include "Luau/Normalize.h"
#include "Luau/RecursionCounter.h" #include "Luau/RecursionCounter.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/StringUtils.h"
#include "Luau/Substitution.h" #include "Luau/Substitution.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/TxnLog.h" #include "Luau/TxnLog.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypeArena.h" #include "Luau/TypeArena.h"
#include "Luau/TypeCheckLimits.h"
#include "Luau/TypeFunction.h" #include "Luau/TypeFunction.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
#include "Luau/TypePath.h" #include "Luau/TypePath.h"
@ -20,8 +22,6 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity) LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauSubtypingEnableReasoningLimit)
namespace Luau namespace Luau
{ {
@ -31,7 +31,7 @@ struct VarianceFlipper
Subtyping::Variance* variance; Subtyping::Variance* variance;
Subtyping::Variance oldValue; Subtyping::Variance oldValue;
explicit VarianceFlipper(Subtyping::Variance* v) VarianceFlipper(Subtyping::Variance* v)
: variance(v) : variance(v)
, oldValue(*v) , oldValue(*v)
{ {
@ -99,9 +99,6 @@ static SubtypingReasonings mergeReasonings(const SubtypingReasonings& a, const S
else else
result.insert(r); result.insert(r);
} }
if (FFlag::LuauSubtypingEnableReasoningLimit && result.size() >= size_t(FInt::LuauSubtypingReasoningLimit))
return result;
} }
for (const SubtypingReasoning& r : b) for (const SubtypingReasoning& r : b)
@ -118,9 +115,6 @@ static SubtypingReasonings mergeReasonings(const SubtypingReasonings& a, const S
else else
result.insert(r); result.insert(r);
} }
if (FFlag::LuauSubtypingEnableReasoningLimit && result.size() >= size_t(FInt::LuauSubtypingReasoningLimit))
return result;
} }
return result; return result;
@ -313,7 +307,7 @@ struct ApplyMappedGenerics : Substitution
bool ignoreChildren(TypeId ty) override bool ignoreChildren(TypeId ty) override
{ {
if (get<ExternType>(ty)) if (get<ClassType>(ty))
return true; return true;
return ty->persistent; return ty->persistent;
@ -421,14 +415,6 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope
SubtypingResult result = isCovariantWith(env, subTy, superTy, scope); SubtypingResult result = isCovariantWith(env, subTy, superTy, scope);
if (result.normalizationTooComplex)
{
if (result.isCacheable)
resultCache[{subTy, superTy}] = result;
return result;
}
for (const auto& [subTy, bounds] : env.mappedGenerics) for (const auto& [subTy, bounds] : env.mappedGenerics)
{ {
const auto& lb = bounds.lowerBound; const auto& lb = bounds.lowerBound;
@ -606,12 +592,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
if (!result.isSubtype && !result.normalizationTooComplex) if (!result.isSubtype && !result.normalizationTooComplex)
{ {
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope); SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope);
if (semantic.isSubtype)
if (semantic.normalizationTooComplex)
{
result = semantic;
}
else if (semantic.isSubtype)
{ {
semantic.reasoning.clear(); semantic.reasoning.clear();
result = semantic; result = semantic;
@ -626,12 +607,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
if (!result.isSubtype && !result.normalizationTooComplex) if (!result.isSubtype && !result.normalizationTooComplex)
{ {
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope); SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope);
if (semantic.isSubtype)
if (semantic.normalizationTooComplex)
{
result = semantic;
}
else if (semantic.isSubtype)
{ {
// Clear the semantic reasoning, as any reasonings within // Clear the semantic reasoning, as any reasonings within
// potentially contain invalid paths. // potentially contain invalid paths.
@ -742,9 +718,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<ExternType, ExternType>(subTy, superTy)) else if (auto p = get2<ClassType, ClassType>(subTy, superTy))
result = isCovariantWith(env, p, scope); result = isCovariantWith(env, p, scope);
else if (auto p = get2<ExternType, TableType>(subTy, superTy)) else if (auto p = get2<ClassType, TableType>(subTy, superTy))
result = isCovariantWith(env, subTy, p.first, superTy, p.second, scope); 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);
@ -1106,10 +1082,6 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
for (TypeId ty : superUnion) for (TypeId ty : superUnion)
{ {
SubtypingResult next = isCovariantWith(env, subTy, ty, scope); SubtypingResult next = isCovariantWith(env, subTy, ty, scope);
if (next.normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
if (next.isSubtype) if (next.isSubtype)
return SubtypingResult{true}; return SubtypingResult{true};
} }
@ -1128,13 +1100,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Unio
std::vector<SubtypingResult> subtypings; std::vector<SubtypingResult> subtypings;
size_t i = 0; size_t i = 0;
for (TypeId ty : subUnion) for (TypeId ty : subUnion)
{
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Union})); subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Union}));
if (subtypings.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
return SubtypingResult::all(subtypings); return SubtypingResult::all(subtypings);
} }
@ -1144,13 +1110,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
std::vector<SubtypingResult> subtypings; std::vector<SubtypingResult> subtypings;
size_t i = 0; size_t i = 0;
for (TypeId ty : superIntersection) for (TypeId ty : superIntersection)
{
subtypings.push_back(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection})); subtypings.push_back(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
if (subtypings.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
return SubtypingResult::all(subtypings); return SubtypingResult::all(subtypings);
} }
@ -1160,13 +1120,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Inte
std::vector<SubtypingResult> subtypings; std::vector<SubtypingResult> subtypings;
size_t i = 0; size_t i = 0;
for (TypeId ty : subIntersection) for (TypeId ty : subIntersection)
{
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection})); subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
if (subtypings.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
return SubtypingResult::any(subtypings); return SubtypingResult::any(subtypings);
} }
@ -1334,7 +1288,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<ExternType, PrimitiveType>(subTy, negatedTy)) else if (auto p = get2<ClassType, 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};
@ -1342,9 +1296,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<ExternType, ExternType>(subTy, negatedTy)) else if (auto p = get2<ClassType, ClassType>(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, ExternType>(subTy, negatedTy)) else if (get2<FunctionType, ClassType>(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");
@ -1456,7 +1410,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Meta
// of the supertype table. // of the supertype table.
// //
// There's a flaw here in that if the __index metamethod contributes a new // There's a flaw here in that if the __index metamethod contributes a new
// field that would satisfy the subtyping relationship, we'll erroneously say // field that would satisfy the subtyping relationship, we'll erronously say
// that the metatable isn't a subtype of the table, even though they have // that the metatable isn't a subtype of the table, even though they have
// compatible properties/shapes. We'll revisit this later when we have a // compatible properties/shapes. We'll revisit this later when we have a
// better understanding of how important this is. // better understanding of how important this is.
@ -1469,15 +1423,15 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Meta
} }
} }
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const ExternType* subExternType, const ExternType* superExternType, NotNull<Scope> scope) SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass, NotNull<Scope> scope)
{ {
return {isSubclass(subExternType, superExternType)}; return {isSubclass(subClass, superClass)};
} }
SubtypingResult Subtyping::isCovariantWith( SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env, SubtypingEnvironment& env,
TypeId subTy, TypeId subTy,
const ExternType* subExternType, const ClassType* subClass,
TypeId superTy, TypeId superTy,
const TableType* superTable, const TableType* superTable,
NotNull<Scope> scope NotNull<Scope> scope
@ -1489,7 +1443,7 @@ SubtypingResult Subtyping::isCovariantWith(
for (const auto& [name, prop] : superTable->props) for (const auto& [name, prop] : superTable->props)
{ {
if (auto classProp = lookupExternTypeProp(subExternType, name)) if (auto classProp = lookupClassProp(subClass, name))
{ {
result.andAlso(isCovariantWith(env, *classProp, prop, name, scope)); result.andAlso(isCovariantWith(env, *classProp, prop, name, scope));
} }
@ -1659,7 +1613,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->externTypes, superNorm->externTypes, scope).orElse(isCovariantWith(env, subNorm->externTypes, superNorm->tables, scope)) isCovariantWith(env, subNorm->classes, superNorm->classes, scope).orElse(isCovariantWith(env, subNorm->classes, superNorm->tables, scope))
); );
result.andAlso(isCovariantWith(env, subNorm->errors, superNorm->errors, scope)); result.andAlso(isCovariantWith(env, subNorm->errors, superNorm->errors, scope));
result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils, scope)); result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils, scope));
@ -1676,24 +1630,24 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult Subtyping::isCovariantWith( SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env, SubtypingEnvironment& env,
const NormalizedExternType& subExternType, const NormalizedClassType& subClass,
const NormalizedExternType& superExternType, const NormalizedClassType& superClass,
NotNull<Scope> scope NotNull<Scope> scope
) )
{ {
for (const auto& [subExternTypeTy, _] : subExternType.externTypes) for (const auto& [subClassTy, _] : subClass.classes)
{ {
SubtypingResult result; SubtypingResult result;
for (const auto& [superExternTypeTy, superNegations] : superExternType.externTypes) for (const auto& [superClassTy, superNegations] : superClass.classes)
{ {
result.orElse(isCovariantWith(env, subExternTypeTy, superExternTypeTy, scope)); result.orElse(isCovariantWith(env, subClassTy, superClassTy, scope));
if (!result.isSubtype) if (!result.isSubtype)
continue; continue;
for (TypeId negation : superNegations) for (TypeId negation : superNegations)
{ {
result.andAlso(SubtypingResult::negate(isCovariantWith(env, subExternTypeTy, negation, scope))); result.andAlso(SubtypingResult::negate(isCovariantWith(env, subClassTy, negation, scope)));
if (result.isSubtype) if (result.isSubtype)
break; break;
} }
@ -1708,17 +1662,17 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult Subtyping::isCovariantWith( SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env, SubtypingEnvironment& env,
const NormalizedExternType& subExternType, const NormalizedClassType& subClass,
const TypeIds& superTables, const TypeIds& superTables,
NotNull<Scope> scope NotNull<Scope> scope
) )
{ {
for (const auto& [subExternTypeTy, _] : subExternType.externTypes) for (const auto& [subClassTy, _] : subClass.classes)
{ {
SubtypingResult result; SubtypingResult result;
for (TypeId superTableTy : superTables) for (TypeId superTableTy : superTables)
result.orElse(isCovariantWith(env, subExternTypeTy, superTableTy, scope)); result.orElse(isCovariantWith(env, subClassTy, superTableTy, scope));
if (!result.isSubtype) if (!result.isSubtype)
return result; return result;
@ -1806,12 +1760,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
{ {
results.emplace_back(); results.emplace_back();
for (TypeId superTy : superTypes) for (TypeId superTy : superTypes)
{
results.back().orElse(isCovariantWith(env, subTy, superTy, scope)); results.back().orElse(isCovariantWith(env, subTy, superTy, scope));
if (results.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
} }
return SubtypingResult::all(results); return SubtypingResult::all(results);

View file

@ -13,9 +13,7 @@
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
#include "Luau/Unifier2.h" #include "Luau/Unifier2.h"
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceCollectIndexerTypes) LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceUpcast)
LUAU_FASTFLAGVARIABLE(LuauBidirectionalFailsafe)
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceElideAssert)
namespace Luau namespace Luau
{ {
@ -138,33 +136,27 @@ TypeId matchLiteralType(
* things like replace explicit named properties with indexers as required * things like replace explicit named properties with indexers as required
* by the expected type. * by the expected type.
*/ */
if (!isLiteral(expr)) if (!isLiteral(expr))
{
if (FFlag::LuauBidirectionalInferenceUpcast)
{ {
auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope); auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope);
return result.isSubtype ? expectedType : exprType; return result.isSubtype
? expectedType
: exprType;
}
else
return exprType;
} }
expectedType = follow(expectedType); expectedType = follow(expectedType);
exprType = follow(exprType); exprType = follow(exprType);
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
{
// The intent of `matchLiteralType` is to upcast values when it's safe
// to do so. it's always safe to upcast to `any` or `unknown`, so we
// can unconditionally do so here.
if (is<AnyType, UnknownType>(expectedType))
return expectedType;
}
else
{
if (get<AnyType>(expectedType) || get<UnknownType>(expectedType)) if (get<AnyType>(expectedType) || get<UnknownType>(expectedType))
{ {
// "Narrowing" to unknown or any is not going to do anything useful. // "Narrowing" to unknown or any is not going to do anything useful.
return exprType; return exprType;
} }
}
if (expr->is<AstExprConstantString>()) if (expr->is<AstExprConstantString>())
{ {
@ -233,26 +225,19 @@ TypeId matchLiteralType(
} }
if (expr->is<AstExprFunction>()) if (FFlag::LuauBidirectionalInferenceUpcast && expr->is<AstExprFunction>())
{ {
// TODO: Push argument / return types into the lambda. For now, just do // TODO: Push argument / return types into the lambda. For now, just do
// the non-literal thing: check for a subtype and upcast if valid. // the non-literal thing: check for a subtype and upcast if valid.
auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope); auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope);
return result.isSubtype ? expectedType : exprType; return result.isSubtype
? expectedType
: exprType;
} }
if (auto exprTable = expr->as<AstExprTable>()) if (auto exprTable = expr->as<AstExprTable>())
{ {
TableType* const tableTy = getMutable<TableType>(exprType); TableType* const tableTy = getMutable<TableType>(exprType);
// This can occur if we have an expression like:
//
// { x = {}, x = 42 }
//
// The type of this will be `{ x: number }`
if (FFlag::LuauBidirectionalFailsafe && !tableTy)
return exprType;
LUAU_ASSERT(tableTy); LUAU_ASSERT(tableTy);
const TableType* expectedTableTy = get<TableType>(expectedType); const TableType* expectedTableTy = get<TableType>(expectedType);
@ -279,9 +264,6 @@ TypeId matchLiteralType(
DenseHashSet<AstExprConstantString*> keysToDelete{nullptr}; DenseHashSet<AstExprConstantString*> keysToDelete{nullptr};
DenseHashSet<TypeId> indexerKeyTypes{nullptr};
DenseHashSet<TypeId> indexerValueTypes{nullptr};
for (const AstExprTable::Item& item : exprTable->items) for (const AstExprTable::Item& item : exprTable->items)
{ {
if (isRecord(item)) if (isRecord(item))
@ -289,11 +271,6 @@ TypeId matchLiteralType(
const AstArray<char>& s = item.key->as<AstExprConstantString>()->value; const AstArray<char>& s = item.key->as<AstExprConstantString>()->value;
std::string keyStr{s.data, s.data + s.size}; std::string keyStr{s.data, s.data + s.size};
auto it = tableTy->props.find(keyStr); auto it = tableTy->props.find(keyStr);
// This can occur, potentially, if we are re-entrant.
if (FFlag::LuauBidirectionalFailsafe && it == tableTy->props.end())
continue;
LUAU_ASSERT(it != tableTy->props.end()); LUAU_ASSERT(it != tableTy->props.end());
Property& prop = it->second; Property& prop = it->second;
@ -330,20 +307,13 @@ TypeId matchLiteralType(
toBlock toBlock
); );
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
{
indexerKeyTypes.insert(arena->addType(SingletonType{StringSingleton{keyStr}}));
indexerValueTypes.insert(matchedType);
}
else
{
if (tableTy->indexer) if (tableTy->indexer)
unifier->unify(matchedType, tableTy->indexer->indexResultType); unifier->unify(matchedType, tableTy->indexer->indexResultType);
else else
tableTy->indexer = TableIndexer{expectedTableTy->indexer->indexType, matchedType}; tableTy->indexer = TableIndexer{expectedTableTy->indexer->indexType, matchedType};
}
keysToDelete.insert(item.key->as<AstExprConstantString>()); keysToDelete.insert(item.key->as<AstExprConstantString>());
} }
// If it's just an extra property and the expected type // If it's just an extra property and the expected type
@ -366,25 +336,22 @@ TypeId matchLiteralType(
// quadratic in a hurry. // quadratic in a hurry.
if (expectedProp.isShared()) if (expectedProp.isShared())
{ {
matchedType = matchLiteralType( matchedType =
astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *expectedReadTy, propTy, item.value, toBlock matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *expectedReadTy, propTy, item.value, toBlock);
);
prop.readTy = matchedType; prop.readTy = matchedType;
prop.writeTy = matchedType; prop.writeTy = matchedType;
} }
else if (expectedReadTy) else if (expectedReadTy)
{ {
matchedType = matchLiteralType( matchedType =
astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *expectedReadTy, propTy, item.value, toBlock matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *expectedReadTy, propTy, item.value, toBlock);
);
prop.readTy = matchedType; prop.readTy = matchedType;
prop.writeTy.reset(); prop.writeTy.reset();
} }
else if (expectedWriteTy) else if (expectedWriteTy)
{ {
matchedType = matchLiteralType( matchedType =
astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *expectedWriteTy, propTy, item.value, toBlock matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *expectedWriteTy, propTy, item.value, toBlock);
);
prop.readTy.reset(); prop.readTy.reset();
prop.writeTy = matchedType; prop.writeTy = matchedType;
} }
@ -401,15 +368,9 @@ TypeId matchLiteralType(
LUAU_ASSERT(matchedType); LUAU_ASSERT(matchedType);
(*astExpectedTypes)[item.value] = matchedType; (*astExpectedTypes)[item.value] = matchedType;
// NOTE: We do *not* add to the potential indexer types here.
// I think this is correct to support something like:
//
// { [string]: number, foo: boolean }
//
} }
else if (item.kind == AstExprTable::Item::List) else if (item.kind == AstExprTable::Item::List)
{ {
if (!FFlag::LuauBidirectionalInferenceCollectIndexerTypes || !FFlag::LuauBidirectionalInferenceElideAssert)
LUAU_ASSERT(tableTy->indexer); LUAU_ASSERT(tableTy->indexer);
if (expectedTableTy->indexer) if (expectedTableTy->indexer)
@ -431,19 +392,11 @@ TypeId matchLiteralType(
toBlock toBlock
); );
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
{
indexerKeyTypes.insert(builtinTypes->numberType);
indexerValueTypes.insert(matchedType);
}
else
{
// if the index result type is the prop type, we can replace it with the matched type here. // if the index result type is the prop type, we can replace it with the matched type here.
if (tableTy->indexer->indexResultType == *propTy) if (tableTy->indexer->indexResultType == *propTy)
tableTy->indexer->indexResultType = matchedType; tableTy->indexer->indexResultType = matchedType;
} }
} }
}
else if (item.kind == AstExprTable::Item::General) else if (item.kind == AstExprTable::Item::General)
{ {
@ -463,12 +416,6 @@ TypeId matchLiteralType(
// Populate expected types for non-string keys declared with [] (the code below will handle the case where they are strings) // Populate expected types for non-string keys declared with [] (the code below will handle the case where they are strings)
if (!item.key->as<AstExprConstantString>() && expectedTableTy->indexer) if (!item.key->as<AstExprConstantString>() && expectedTableTy->indexer)
(*astExpectedTypes)[item.key] = expectedTableTy->indexer->indexType; (*astExpectedTypes)[item.key] = expectedTableTy->indexer->indexType;
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
{
indexerKeyTypes.insert(tKey);
indexerValueTypes.insert(tProp);
}
} }
else else
LUAU_ASSERT(!"Unexpected"); LUAU_ASSERT(!"Unexpected");
@ -530,41 +477,11 @@ TypeId matchLiteralType(
// have one too. // have one too.
// TODO: If the expected table also has an indexer, we might want to // TODO: If the expected table also has an indexer, we might want to
// push the expected indexer's types into it. // push the expected indexer's types into it.
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes && expectedTableTy->indexer)
{
if (indexerValueTypes.size() > 0 && indexerKeyTypes.size() > 0)
{
TypeId inferredKeyType = builtinTypes->neverType;
TypeId inferredValueType = builtinTypes->neverType;
for (auto kt : indexerKeyTypes)
{
auto simplified = simplifyUnion(builtinTypes, arena, inferredKeyType, kt);
inferredKeyType = simplified.result;
}
for (auto vt : indexerValueTypes)
{
auto simplified = simplifyUnion(builtinTypes, arena, inferredValueType, vt);
inferredValueType = simplified.result;
}
tableTy->indexer = TableIndexer{inferredKeyType, inferredValueType};
auto keyCheck = subtyping->isSubtype(inferredKeyType, expectedTableTy->indexer->indexType, unifier->scope);
if (keyCheck.isSubtype)
tableTy->indexer->indexType = expectedTableTy->indexer->indexType;
auto valueCheck = subtyping->isSubtype(inferredValueType, expectedTableTy->indexer->indexResultType, unifier->scope);
if (valueCheck.isSubtype)
tableTy->indexer->indexResultType = expectedTableTy->indexer->indexResultType;
}
else
LUAU_ASSERT(indexerKeyTypes.empty() && indexerValueTypes.empty());
}
else
{
if (expectedTableTy->indexer && !tableTy->indexer) if (expectedTableTy->indexer && !tableTy->indexer)
{ {
tableTy->indexer = expectedTableTy->indexer; tableTy->indexer = expectedTableTy->indexer;
} }
} }
}
return exprType; return exprType;
} }

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, ExternType>) else if constexpr (std::is_same_v<T, ClassType>)
{ {
formatAppend(result, "ExternType %s", t.name.c_str()); formatAppend(result, "ClassType %s", t.name.c_str());
finishNodeLabel(ty); finishNodeLabel(ty);
finishNode(); finishNode();

View file

@ -19,11 +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)
/* /*
* Enables increasing levels of verbosity for Luau type names when stringifying. * Enables increasing levels of verbosity for Luau type names when stringifying.
@ -123,7 +120,7 @@ struct FindCyclicTypes final : TypeVisitor
return true; return true;
} }
bool visit(TypeId ty, const ExternType&) override bool visit(TypeId ty, const ClassType&) override
{ {
return false; return false;
} }
@ -304,28 +301,6 @@ struct StringifierState
emit(std::to_string(i).c_str()); emit(std::to_string(i).c_str());
} }
void emit(Polarity p)
{
switch (p)
{
case Polarity::None:
emit(" ");
break;
case Polarity::Negative:
emit(" -");
break;
case Polarity::Positive:
emit("+ ");
break;
case Polarity::Mixed:
emit("+-");
break;
default:
emit("!!");
break;
}
}
void indent() void indent()
{ {
indentation += 4; indentation += 4;
@ -507,8 +482,6 @@ struct TypeStringifier
{ {
state.emit("'"); state.emit("'");
state.emit(state.getName(ty)); state.emit(state.getName(ty));
if (FInt::DebugLuauVerboseTypeNames >= 1)
state.emit(ftv.polarity);
} }
else else
{ {
@ -521,9 +494,6 @@ struct TypeStringifier
state.emit("'"); state.emit("'");
state.emit(state.getName(ty)); state.emit(state.getName(ty));
if (FInt::DebugLuauVerboseTypeNames >= 1)
state.emit(ftv.polarity);
if (!get<UnknownType>(upperBound)) if (!get<UnknownType>(upperBound))
{ {
state.emit(" <: "); state.emit(" <: ");
@ -539,9 +509,6 @@ struct TypeStringifier
state.emit(state.getName(ty)); state.emit(state.getName(ty));
if (FFlag::LuauSolverV2 && FInt::DebugLuauVerboseTypeNames >= 1)
state.emit(ftv.polarity);
if (FInt::DebugLuauVerboseTypeNames >= 2) if (FInt::DebugLuauVerboseTypeNames >= 2)
{ {
state.emit("-"); state.emit("-");
@ -571,9 +538,6 @@ struct TypeStringifier
else else
state.emit(state.getName(ty)); state.emit(state.getName(ty));
if (FInt::DebugLuauVerboseTypeNames >= 1)
state.emit(gtv.polarity);
if (FInt::DebugLuauVerboseTypeNames >= 2) if (FInt::DebugLuauVerboseTypeNames >= 2)
{ {
state.emit("-"); state.emit("-");
@ -722,13 +686,7 @@ struct TypeStringifier
if (ttv.boundTo) if (ttv.boundTo)
return stringify(*ttv.boundTo); return stringify(*ttv.boundTo);
bool showName = !state.exhaustive; if (!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)
{ {
@ -751,10 +709,6 @@ 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;
@ -893,9 +847,9 @@ struct TypeStringifier
state.emit(" }"); state.emit(" }");
} }
void operator()(TypeId, const ExternType& etv) void operator()(TypeId, const ClassType& ctv)
{ {
state.emit(etv.name); state.emit(ctv.name);
} }
void operator()(TypeId, const AnyType&) void operator()(TypeId, const AnyType&)
@ -923,9 +877,6 @@ struct TypeStringifier
bool hasNonNilDisjunct = false; bool hasNonNilDisjunct = false;
std::vector<std::string> results = {}; std::vector<std::string> results = {};
size_t resultsLength = 0;
bool lengthLimitHit = false;
for (auto el : &uv) for (auto el : &uv)
{ {
el = follow(el); el = follow(el);
@ -952,34 +903,14 @@ struct TypeStringifier
if (needParens) if (needParens)
state.emit(")"); state.emit(")");
if (FFlag::LuauStringPartLengthLimit)
resultsLength += state.result.name.length();
results.push_back(std::move(state.result.name)); results.push_back(std::move(state.result.name));
state.result.name = std::move(saved); state.result.name = std::move(saved);
if (FFlag::LuauStringPartLengthLimit)
{
lengthLimitHit = state.opts.maxTypeLength > 0 && resultsLength > state.opts.maxTypeLength;
if (lengthLimitHit)
break;
}
} }
state.unsee(&uv); state.unsee(&uv);
if (FFlag::LuauStringPartLengthLimit)
{
if (!lengthLimitHit && !FFlag::DebugLuauToStringNoLexicalSort)
std::sort(results.begin(), results.end());
}
else
{
if (!FFlag::DebugLuauToStringNoLexicalSort) if (!FFlag::DebugLuauToStringNoLexicalSort)
std::sort(results.begin(), results.end()); std::sort(results.begin(), results.end());
}
if (optional && results.size() > 1) if (optional && results.size() > 1)
state.emit("("); state.emit("(");
@ -1023,9 +954,6 @@ struct TypeStringifier
} }
std::vector<std::string> results = {}; std::vector<std::string> results = {};
size_t resultsLength = 0;
bool lengthLimitHit = false;
for (auto el : uv.parts) for (auto el : uv.parts)
{ {
el = follow(el); el = follow(el);
@ -1042,34 +970,14 @@ struct TypeStringifier
if (needParens) if (needParens)
state.emit(")"); state.emit(")");
if (FFlag::LuauStringPartLengthLimit)
resultsLength += state.result.name.length();
results.push_back(std::move(state.result.name)); results.push_back(std::move(state.result.name));
state.result.name = std::move(saved); state.result.name = std::move(saved);
if (FFlag::LuauStringPartLengthLimit)
{
lengthLimitHit = state.opts.maxTypeLength > 0 && resultsLength > state.opts.maxTypeLength;
if (lengthLimitHit)
break;
}
} }
state.unsee(&uv); state.unsee(&uv);
if (FFlag::LuauStringPartLengthLimit)
{
if (!lengthLimitHit && !FFlag::DebugLuauToStringNoLexicalSort)
std::sort(results.begin(), results.end());
}
else
{
if (!FFlag::DebugLuauToStringNoLexicalSort) if (!FFlag::DebugLuauToStringNoLexicalSort)
std::sort(results.begin(), results.end()); std::sort(results.begin(), results.end());
}
bool first = true; bool first = true;
bool shouldPlaceOnNewlines = results.size() > state.opts.compositeTypesSingleLineLimit || isOverloadedFunction(ty); bool shouldPlaceOnNewlines = results.size() > state.opts.compositeTypesSingleLineLimit || isOverloadedFunction(ty);
@ -1314,9 +1222,6 @@ struct TypePackStringifier
state.emit(state.getName(tp)); state.emit(state.getName(tp));
} }
if (FInt::DebugLuauVerboseTypeNames >= 1)
state.emit(pack.polarity);
if (FInt::DebugLuauVerboseTypeNames >= 2) if (FInt::DebugLuauVerboseTypeNames >= 2)
{ {
state.emit("-"); state.emit("-");
@ -1336,9 +1241,6 @@ struct TypePackStringifier
state.emit("free-"); state.emit("free-");
state.emit(state.getName(tp)); state.emit(state.getName(tp));
if (FInt::DebugLuauVerboseTypeNames >= 1)
state.emit(pack.polarity);
if (FInt::DebugLuauVerboseTypeNames >= 2) if (FInt::DebugLuauVerboseTypeNames >= 2)
{ {
state.emit("-"); state.emit("-");

View file

@ -41,8 +41,6 @@
#include <stdexcept> #include <stdexcept>
#include <optional> #include <optional>
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau namespace Luau
{ {
@ -299,11 +297,6 @@ 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

@ -10,11 +10,11 @@
#include <limits> #include <limits>
#include <math.h> #include <math.h>
LUAU_FASTFLAG(LuauStoreCSTData2) LUAU_FASTFLAG(LuauStoreCSTData)
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
LUAU_FASTFLAG(LuauAstTypeGroup3) LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauParseOptionalAsNode2) LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation) LUAU_FASTFLAG(LuauParseOptionalAsNode)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace namespace
{ {
@ -167,7 +167,7 @@ struct StringWriter : Writer
void symbol(std::string_view s) override void symbol(std::string_view s) override
{ {
if (FFlag::LuauStoreCSTData2) if (FFlag::LuauStoreCSTData)
{ {
write(s); write(s);
} }
@ -257,7 +257,7 @@ public:
first = !first; first = !first;
else else
{ {
if (FFlag::LuauStoreCSTData2 && commaPosition) if (FFlag::LuauStoreCSTData && commaPosition)
{ {
writer.advance(*commaPosition); writer.advance(*commaPosition);
commaPosition++; commaPosition++;
@ -331,7 +331,7 @@ struct Printer_DEPRECATED
} }
} }
void visualizeTypePackAnnotation(const AstTypePack& annotation, bool forVarArg, bool unconditionallyParenthesize = true) void visualizeTypePackAnnotation(const AstTypePack& annotation, bool forVarArg)
{ {
advance(annotation.location.begin); advance(annotation.location.begin);
if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>()) if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>())
@ -349,7 +349,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, unconditionallyParenthesize); visualizeTypeList(explicitTp->typeList, true);
} }
else else
{ {
@ -705,6 +705,8 @@ struct Printer_DEPRECATED
writer.keyword("do"); writer.keyword("do");
for (const auto& s : block->body) for (const auto& s : block->body)
visualize(*s); visualize(*s);
if (!FFlag::LuauFixDoBlockEndLocation)
writer.advance(block->location.end);
writeEnd(program.location); writeEnd(program.location);
} }
else if (const auto& a = program.as<AstStatIf>()) else if (const auto& a = program.as<AstStatIf>())
@ -1063,15 +1065,12 @@ struct Printer_DEPRECATED
writer.symbol(")"); writer.symbol(")");
if (writeTypes && (FFlag::LuauStoreReturnTypesAsPackOnAst ? func.returnAnnotation != nullptr : func.returnAnnotation_DEPRECATED.has_value())) if (writeTypes && func.returnAnnotation)
{ {
writer.symbol(":"); writer.symbol(":");
writer.space(); writer.space();
if (FFlag::LuauStoreReturnTypesAsPackOnAst) visualizeTypeList(*func.returnAnnotation, false);
visualizeTypePackAnnotation(*func.returnAnnotation, false, false);
else
visualizeTypeList(*func.returnAnnotation_DEPRECATED, false);
} }
visualizeBlock(*func.body); visualizeBlock(*func.body);
@ -1175,10 +1174,7 @@ struct Printer_DEPRECATED
} }
writer.symbol("->"); writer.symbol("->");
if (FFlag::LuauStoreReturnTypesAsPackOnAst) visualizeTypeList(a->returnTypes, true);
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>())
{ {
@ -1233,18 +1229,9 @@ struct Printer_DEPRECATED
AstType* l = a->types.data[0]; AstType* l = a->types.data[0];
AstType* r = a->types.data[1]; AstType* r = a->types.data[1];
if (FFlag::LuauParseOptionalAsNode2)
{
auto lta = l->as<AstTypeReference>();
if (lta && lta->name == "nil" && !r->is<AstTypeOptional>())
std::swap(l, r);
}
else
{
auto lta = l->as<AstTypeReference>(); auto lta = l->as<AstTypeReference>();
if (lta && lta->name == "nil") if (lta && lta->name == "nil")
std::swap(l, r); std::swap(l, r);
}
// it's still possible that we had a (T | U) or (T | nil) and not (nil | T) // it's still possible that we had a (T | U) or (T | nil) and not (nil | T)
auto rta = r->as<AstTypeReference>(); auto rta = r->as<AstTypeReference>();
@ -1267,7 +1254,7 @@ struct Printer_DEPRECATED
for (size_t i = 0; i < a->types.size; ++i) for (size_t i = 0; i < a->types.size; ++i)
{ {
if (FFlag::LuauParseOptionalAsNode2) if (FFlag::LuauParseOptionalAsNode)
{ {
if (a->types.data[i]->is<AstTypeOptional>()) if (a->types.data[i]->is<AstTypeOptional>())
{ {
@ -1372,7 +1359,7 @@ struct Printer
} }
} }
void visualizeTypePackAnnotation(AstTypePack& annotation, bool forVarArg, bool unconditionallyParenthesize = true) void visualizeTypePackAnnotation(AstTypePack& annotation, bool forVarArg)
{ {
advance(annotation.location.begin); advance(annotation.location.begin);
if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>()) if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>())
@ -1394,10 +1381,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, FFlag::LuauStoreReturnTypesAsPackOnAst ? cstNode->hasParentheses : true, cstNode->openParenthesesPosition, cstNode->closeParenthesesPosition, cstNode->commaPositions explicitTp->typeList, true, cstNode->openParenthesesPosition, cstNode->closeParenthesesPosition, cstNode->commaPositions
); );
else else
visualizeTypeList(explicitTp->typeList, unconditionallyParenthesize); visualizeTypeList(explicitTp->typeList, true);
} }
else else
{ {
@ -1502,7 +1489,6 @@ struct Printer
void visualize(AstExpr& expr) void visualize(AstExpr& expr)
{ {
if (!expr.is<AstExprFunction>() || FFlag::LuauFixFunctionWithAttributesStartLocation)
advance(expr.location.begin); advance(expr.location.begin);
if (const auto& a = expr.as<AstExprGroup>()) if (const auto& a = expr.as<AstExprGroup>())
@ -1637,17 +1623,6 @@ struct Printer
} }
else if (const auto& a = expr.as<AstExprFunction>()) else if (const auto& a = expr.as<AstExprFunction>())
{ {
for (const auto& attribute : a->attributes)
visualizeAttribute(*attribute);
if (FFlag::LuauFixFunctionWithAttributesStartLocation)
{
if (const auto cstNode = lookupCstNode<CstExprFunction>(a))
advance(cstNode->functionKeywordPosition);
}
else
{
advance(a->location.begin);
}
writer.keyword("function"); writer.keyword("function");
visualizeFunctionBody(*a); visualizeFunctionBody(*a);
} }
@ -1899,7 +1874,6 @@ struct Printer
void visualize(AstStat& program) void visualize(AstStat& program)
{ {
if ((!program.is<AstStatLocalFunction>() && !program.is<AstStatFunction>()) || FFlag::LuauFixFunctionWithAttributesStartLocation)
advance(program.location.begin); advance(program.location.begin);
if (const auto& block = program.as<AstStatBlock>()) if (const auto& block = program.as<AstStatBlock>())
@ -2137,36 +2111,13 @@ struct Printer
} }
else if (const auto& a = program.as<AstStatFunction>()) else if (const auto& a = program.as<AstStatFunction>())
{ {
for (const auto& attribute : a->func->attributes)
visualizeAttribute(*attribute);
if (FFlag::LuauFixFunctionWithAttributesStartLocation)
{
if (const auto cstNode = lookupCstNode<CstStatFunction>(a))
advance(cstNode->functionKeywordPosition);
}
else
{
advance(a->location.begin);
}
writer.keyword("function"); writer.keyword("function");
visualize(*a->name); visualize(*a->name);
visualizeFunctionBody(*a->func); visualizeFunctionBody(*a->func);
} }
else if (const auto& a = program.as<AstStatLocalFunction>()) else if (const auto& a = program.as<AstStatLocalFunction>())
{ {
for (const auto& attribute : a->func->attributes)
visualizeAttribute(*attribute);
const auto cstNode = lookupCstNode<CstStatLocalFunction>(a); const auto cstNode = lookupCstNode<CstStatLocalFunction>(a);
if (FFlag::LuauFixFunctionWithAttributesStartLocation)
{
if (cstNode)
advance(cstNode->localKeywordPosition);
}
else
{
advance(a->location.begin);
}
writer.keyword("local"); writer.keyword("local");
@ -2310,7 +2261,7 @@ struct Printer
if (program.hasSemicolon) if (program.hasSemicolon)
{ {
if (FFlag::LuauStoreCSTData2) if (FFlag::LuauStoreCSTData)
advanceBefore(program.location.end, 1); advanceBefore(program.location.end, 1);
writer.symbol(";"); writer.symbol(";");
} }
@ -2320,6 +2271,8 @@ struct Printer
{ {
const auto cstNode = lookupCstNode<CstExprFunction>(&func); const auto cstNode = lookupCstNode<CstExprFunction>(&func);
// TODO(CLI-139347): need to handle attributes, argument types, and return type (incl. parentheses of return type)
if (func.generics.size > 0 || func.genericPacks.size > 0) if (func.generics.size > 0 || func.genericPacks.size > 0)
{ {
CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr); CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr);
@ -2385,23 +2338,14 @@ struct Printer
advanceBefore(func.argLocation->end, 1); advanceBefore(func.argLocation->end, 1);
writer.symbol(")"); writer.symbol(")");
if (writeTypes && FFlag::LuauStoreReturnTypesAsPackOnAst ? func.returnAnnotation != nullptr : func.returnAnnotation_DEPRECATED.has_value()) if (writeTypes && func.returnAnnotation)
{ {
if (cstNode) if (cstNode)
advance(cstNode->returnSpecifierPosition); advance(cstNode->returnSpecifierPosition);
writer.symbol(":"); writer.symbol(":");
writer.space();
if (FFlag::LuauStoreReturnTypesAsPackOnAst) visualizeTypeList(*func.returnAnnotation, false);
{
if (!cstNode)
writer.space();
visualizeTypePackAnnotation(*func.returnAnnotation, false, false);
}
else
{
writer.space();
visualizeTypeList(*func.returnAnnotation_DEPRECATED, false);
}
} }
visualizeBlock(*func.body); visualizeBlock(*func.body);
@ -2483,23 +2427,6 @@ struct Printer
} }
} }
void visualizeAttribute(AstAttr& attribute)
{
advance(attribute.location.begin);
switch (attribute.type)
{
case AstAttr::Checked:
writer.keyword("@checked");
break;
case AstAttr::Native:
writer.keyword("@native");
break;
case AstAttr::Deprecated:
writer.keyword("@deprecated");
break;
}
}
void visualizeTypeAnnotation(AstType& typeAnnotation) void visualizeTypeAnnotation(AstType& typeAnnotation)
{ {
advance(typeAnnotation.location.begin); advance(typeAnnotation.location.begin);
@ -2584,10 +2511,7 @@ struct Printer
if (cstNode) if (cstNode)
advance(cstNode->returnArrowPosition); advance(cstNode->returnArrowPosition);
writer.symbol("->"); writer.symbol("->");
if (FFlag::LuauStoreReturnTypesAsPackOnAst) visualizeTypeList(a->returnTypes, true);
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>())
{ {
@ -2660,7 +2584,6 @@ struct Printer
{ {
advance(item.indexerOpenPosition); advance(item.indexerOpenPosition);
writer.symbol("["); writer.symbol("[");
advance(item.stringPosition);
writer.sourceString( writer.sourceString(
std::string_view(item.stringInfo->sourceString.data, item.stringInfo->sourceString.size), std::string_view(item.stringInfo->sourceString.data, item.stringInfo->sourceString.size),
item.stringInfo->quoteStyle, item.stringInfo->quoteStyle,
@ -2748,25 +2671,14 @@ struct Printer
} }
else if (const auto& a = typeAnnotation.as<AstTypeUnion>()) else if (const auto& a = typeAnnotation.as<AstTypeUnion>())
{ {
const auto cstNode = lookupCstNode<CstTypeUnion>(a); if (a->types.size == 2)
if (!cstNode && a->types.size == 2)
{ {
AstType* l = a->types.data[0]; AstType* l = a->types.data[0];
AstType* r = a->types.data[1]; AstType* r = a->types.data[1];
if (FFlag::LuauParseOptionalAsNode2)
{
auto lta = l->as<AstTypeReference>();
if (lta && lta->name == "nil" && !r->is<AstTypeOptional>())
std::swap(l, r);
}
else
{
auto lta = l->as<AstTypeReference>(); auto lta = l->as<AstTypeReference>();
if (lta && lta->name == "nil") if (lta && lta->name == "nil")
std::swap(l, r); std::swap(l, r);
}
// it's still possible that we had a (T | U) or (T | nil) and not (nil | T) // it's still possible that we had a (T | U) or (T | nil) and not (nil | T)
auto rta = r->as<AstTypeReference>(); auto rta = r->as<AstTypeReference>();
@ -2787,20 +2699,12 @@ struct Printer
} }
} }
if (cstNode && cstNode->leadingPosition)
{
advance(*cstNode->leadingPosition);
writer.symbol("|");
}
size_t separatorIndex = 0;
for (size_t i = 0; i < a->types.size; ++i) for (size_t i = 0; i < a->types.size; ++i)
{ {
if (FFlag::LuauParseOptionalAsNode2) if (FFlag::LuauParseOptionalAsNode)
{ {
if (const auto optional = a->types.data[i]->as<AstTypeOptional>()) if (a->types.data[i]->is<AstTypeOptional>())
{ {
advance(optional->location.begin);
writer.symbol("?"); writer.symbol("?");
continue; continue;
} }
@ -2808,18 +2712,11 @@ struct Printer
if (i > 0) if (i > 0)
{ {
if (cstNode && FFlag::LuauParseOptionalAsNode2)
{
// separatorIndex is only valid if `?` is handled as an AstTypeOptional
advance(cstNode->separatorPositions.data[separatorIndex]);
separatorIndex++;
}
else
writer.maybeSpace(a->types.data[i]->location.begin, 2); writer.maybeSpace(a->types.data[i]->location.begin, 2);
writer.symbol("|"); writer.symbol("|");
} }
bool wrap = !cstNode && (a->types.data[i]->as<AstTypeIntersection>() || a->types.data[i]->as<AstTypeFunction>()); bool wrap = a->types.data[i]->as<AstTypeIntersection>() || a->types.data[i]->as<AstTypeFunction>();
if (wrap) if (wrap)
writer.symbol("("); writer.symbol("(");
@ -2832,27 +2729,15 @@ struct Printer
} }
else if (const auto& a = typeAnnotation.as<AstTypeIntersection>()) else if (const auto& a = typeAnnotation.as<AstTypeIntersection>())
{ {
const auto cstNode = lookupCstNode<CstTypeIntersection>(a);
// If the sizes are equal, we know there is a leading & token
if (cstNode && cstNode->leadingPosition)
{
advance(*cstNode->leadingPosition);
writer.symbol("&");
}
for (size_t i = 0; i < a->types.size; ++i) for (size_t i = 0; i < a->types.size; ++i)
{ {
if (i > 0) if (i > 0)
{ {
if (cstNode)
advance(cstNode->separatorPositions.data[i - 1]);
else
writer.maybeSpace(a->types.data[i]->location.begin, 2); writer.maybeSpace(a->types.data[i]->location.begin, 2);
writer.symbol("&"); writer.symbol("&");
} }
bool wrap = !cstNode && (a->types.data[i]->as<AstTypeUnion>() || a->types.data[i]->as<AstTypeFunction>()); bool wrap = a->types.data[i]->as<AstTypeUnion>() || a->types.data[i]->as<AstTypeFunction>();
if (wrap) if (wrap)
writer.symbol("("); writer.symbol("(");
@ -2901,7 +2786,7 @@ std::string toString(AstNode* node)
StringWriter writer; StringWriter writer;
writer.pos = node->location.begin; writer.pos = node->location.begin;
if (FFlag::LuauStoreCSTData2) if (FFlag::LuauStoreCSTData)
{ {
Printer printer(writer, CstNodeMap{nullptr}); Printer printer(writer, CstNodeMap{nullptr});
printer.writeTypes = true; printer.writeTypes = true;
@ -2937,7 +2822,7 @@ void dump(AstNode* node)
std::string transpile(AstStatBlock& block, const CstNodeMap& cstNodeMap) std::string transpile(AstStatBlock& block, const CstNodeMap& cstNodeMap)
{ {
StringWriter writer; StringWriter writer;
if (FFlag::LuauStoreCSTData2) if (FFlag::LuauStoreCSTData)
{ {
Printer(writer, cstNodeMap).visualizeBlock(block); Printer(writer, cstNodeMap).visualizeBlock(block);
} }
@ -2951,7 +2836,7 @@ std::string transpile(AstStatBlock& block, const CstNodeMap& cstNodeMap)
std::string transpileWithTypes(AstStatBlock& block, const CstNodeMap& cstNodeMap) std::string transpileWithTypes(AstStatBlock& block, const CstNodeMap& cstNodeMap)
{ {
StringWriter writer; StringWriter writer;
if (FFlag::LuauStoreCSTData2) if (FFlag::LuauStoreCSTData)
{ {
Printer printer(writer, cstNodeMap); Printer printer(writer, cstNodeMap);
printer.writeTypes = true; printer.writeTypes = true;

View file

@ -407,6 +407,41 @@ PendingTypePack* TxnLog::changeLevel(TypePackId tp, TypeLevel newLevel)
return newTp; return newTp;
} }
PendingType* TxnLog::changeScope(TypeId ty, NotNull<Scope> newScope)
{
LUAU_ASSERT(get<FreeType>(ty) || get<TableType>(ty) || get<FunctionType>(ty));
PendingType* newTy = queue(ty);
if (FreeType* ftv = Luau::getMutable<FreeType>(newTy))
{
ftv->scope = newScope;
}
else if (TableType* ttv = Luau::getMutable<TableType>(newTy))
{
LUAU_ASSERT(ttv->state == TableState::Free || ttv->state == TableState::Generic);
ttv->scope = newScope;
}
else if (FunctionType* ftv = Luau::getMutable<FunctionType>(newTy))
{
ftv->scope = newScope;
}
return newTy;
}
PendingTypePack* TxnLog::changeScope(TypePackId tp, NotNull<Scope> newScope)
{
LUAU_ASSERT(get<FreeTypePack>(tp));
PendingTypePack* newTp = queue(tp);
if (FreeTypePack* ftp = Luau::getMutable<FreeTypePack>(newTp))
{
ftp->scope = newScope;
}
return newTp;
}
PendingType* TxnLog::changeIndexer(TypeId ty, std::optional<TableIndexer> indexer) PendingType* TxnLog::changeIndexer(TypeId ty, std::optional<TableIndexer> indexer)
{ {
LUAU_ASSERT(get<TableType>(ty)); LUAU_ASSERT(get<TableType>(ty));

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 ExternType* externType = get<ExternType>(type)) else if (const ClassType* classType = get<ClassType>(type))
return externType->metatable; return classType->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 etv = get<ExternType>(type)) else if (auto ctv = get<ClassType>(type))
{ {
if (!etv->definitionModuleName.empty()) if (!ctv->definitionModuleName.empty())
return etv->definitionModuleName; return ctv->definitionModuleName;
} }
return std::nullopt; return std::nullopt;
@ -488,12 +488,11 @@ FreeType::FreeType(TypeLevel level, TypeId lowerBound, TypeId upperBound)
{ {
} }
FreeType::FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound, Polarity polarity) FreeType::FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound)
: index(Unifiable::freshIndex()) : index(Unifiable::freshIndex())
, scope(scope) , scope(scope)
, lowerBound(lowerBound) , lowerBound(lowerBound)
, upperBound(upperBound) , upperBound(upperBound)
, polarity(polarity)
{ {
} }
@ -506,6 +505,31 @@ FreeType::FreeType(Scope* scope, TypeLevel level, TypeId lowerBound, TypeId uppe
{ {
} }
// Old constructors
FreeType::FreeType(TypeLevel level)
: index(Unifiable::freshIndex())
, level(level)
, scope(nullptr)
{
LUAU_ASSERT(!FFlag::LuauFreeTypesMustHaveBounds);
}
FreeType::FreeType(Scope* scope)
: index(Unifiable::freshIndex())
, level{}
, scope(scope)
{
LUAU_ASSERT(!FFlag::LuauFreeTypesMustHaveBounds);
}
FreeType::FreeType(Scope* scope, TypeLevel level)
: index(Unifiable::freshIndex())
, level(level)
, scope(scope)
{
LUAU_ASSERT(!FFlag::LuauFreeTypesMustHaveBounds);
}
GenericType::GenericType() GenericType::GenericType()
: index(Unifiable::freshIndex()) : index(Unifiable::freshIndex())
, name("g" + std::to_string(index)) , name("g" + std::to_string(index))
@ -519,18 +543,16 @@ GenericType::GenericType(TypeLevel level)
{ {
} }
GenericType::GenericType(const Name& name, Polarity polarity) GenericType::GenericType(const Name& name)
: index(Unifiable::freshIndex()) : index(Unifiable::freshIndex())
, name(name) , name(name)
, explicitName(true) , explicitName(true)
, polarity(polarity)
{ {
} }
GenericType::GenericType(Scope* scope, Polarity polarity) GenericType::GenericType(Scope* scope)
: index(Unifiable::freshIndex()) : index(Unifiable::freshIndex())
, scope(scope) , scope(scope)
, polarity(polarity)
{ {
} }
@ -608,6 +630,23 @@ FunctionType::FunctionType(TypeLevel level, TypePackId argTypes, TypePackId retT
{ {
} }
FunctionType::FunctionType(
TypeLevel level,
Scope* scope,
TypePackId argTypes,
TypePackId retTypes,
std::optional<FunctionDefinition> defn,
bool hasSelf
)
: definition(std::move(defn))
, level(level)
, scope(scope)
, argTypes(argTypes)
, retTypes(retTypes)
, hasSelf(hasSelf)
{
}
FunctionType::FunctionType( FunctionType::FunctionType(
std::vector<TypeId> generics, std::vector<TypeId> generics,
std::vector<TypePackId> genericPacks, std::vector<TypePackId> genericPacks,
@ -644,6 +683,27 @@ FunctionType::FunctionType(
{ {
} }
FunctionType::FunctionType(
TypeLevel level,
Scope* scope,
std::vector<TypeId> generics,
std::vector<TypePackId> genericPacks,
TypePackId argTypes,
TypePackId retTypes,
std::optional<FunctionDefinition> defn,
bool hasSelf
)
: definition(std::move(defn))
, generics(generics)
, genericPacks(genericPacks)
, level(level)
, scope(scope)
, argTypes(argTypes)
, retTypes(retTypes)
, hasSelf(hasSelf)
{
}
Property::Property() {} Property::Property() {}
Property::Property( Property::Property(
@ -989,7 +1049,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}))
, externType(arena->addType(Type{ExternType{"class", {}, std::nullopt, std::nullopt, {}, {}, {}, {}}, /*persistent*/ true})) , classType(arena->addType(Type{ClassType{"class", {}, std::nullopt, std::nullopt, {}, {}, {}, {}}, /*persistent*/ true}))
, tableType(arena->addType(Type{PrimitiveType{PrimitiveType::Table}, /*persistent*/ true})) , 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}))
@ -1001,7 +1061,6 @@ 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}))
@ -1080,9 +1139,9 @@ void persist(TypeId ty)
queue.push_back(ttv->indexer->indexResultType); queue.push_back(ttv->indexer->indexResultType);
} }
} }
else if (auto etv= get<ExternType>(t)) else if (auto ctv = get<ClassType>(t))
{ {
for (const auto& [_name, prop] : etv->props) for (const auto& [_name, prop] : ctv->props)
queue.push_back(prop.type()); queue.push_back(prop.type());
} }
else if (auto utv = get<UnionType>(t)) else if (auto utv = get<UnionType>(t))
@ -1182,7 +1241,7 @@ std::optional<TypeLevel> getLevel(TypePackId tp)
return std::nullopt; return std::nullopt;
} }
const Property* lookupExternTypeProp(const ExternType* cls, const Name& name) const Property* lookupClassProp(const ClassType* cls, const Name& name)
{ {
while (cls) while (cls)
{ {
@ -1191,7 +1250,7 @@ const Property* lookupExternTypeProp(const ExternType* cls, const Name& name)
return &it->second; return &it->second;
if (cls->parent) if (cls->parent)
cls = get<ExternType>(*cls->parent); cls = get<ClassType>(*cls->parent);
else else
return nullptr; return nullptr;
@ -1201,7 +1260,7 @@ const Property* lookupExternTypeProp(const ExternType* cls, const Name& name)
return nullptr; return nullptr;
} }
bool isSubclass(const ExternType* cls, const ExternType* parent) bool isSubclass(const ClassType* cls, const ClassType* parent)
{ {
while (cls) while (cls)
{ {
@ -1210,7 +1269,7 @@ bool isSubclass(const ExternType* cls, const ExternType* parent)
else if (!cls->parent) else if (!cls->parent)
return false; return false;
cls = get<ExternType>(*cls->parent); cls = get<ClassType>(*cls->parent);
LUAU_ASSERT(cls); LUAU_ASSERT(cls);
} }
@ -1247,9 +1306,9 @@ IntersectionTypeIterator end(const IntersectionType* itv)
return IntersectionTypeIterator{}; return IntersectionTypeIterator{};
} }
TypeId freshType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, Scope* scope, Polarity polarity) TypeId freshType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, Scope* scope)
{ {
return arena->addType(FreeType{scope, builtinTypes->neverType, builtinTypes->unknownType, polarity}); return arena->addType(FreeType{scope, builtinTypes->neverType, builtinTypes->unknownType});
} }
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate) std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate)
@ -1279,8 +1338,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 etv = getMutable<ExternType>(ty)) else if (auto ctv = getMutable<ClassType>(ty))
return &etv->tags; return &ctv->tags;
return nullptr; return nullptr;
} }
@ -1310,19 +1369,19 @@ bool hasTag(TypeId ty, const std::string& tagName)
{ {
ty = follow(ty); ty = follow(ty);
// We special case extern types because getTags only returns a pointer to one vector of tags. // We special case classes because getTags only returns a pointer to one vector of tags.
// But extern types has multiple vector of tags, represented throughout the hierarchy. // But classes has multiple vector of tags, represented throughout the hierarchy.
if (auto etv = get<ExternType>(ty)) if (auto ctv = get<ClassType>(ty))
{ {
while (etv) while (ctv)
{ {
if (hasTag(etv->tags, tagName)) if (hasTag(ctv->tags, tagName))
return true; return true;
else if (!etv->parent) else if (!ctv->parent)
return false; return false;
etv = get<ExternType>(*etv->parent); ctv = get<ClassType>(*ctv->parent);
LUAU_ASSERT(etv); LUAU_ASSERT(ctv);
} }
} }
else if (auto tags = getTags(ty)) else if (auto tags = getTags(ty))

View file

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

View file

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

@ -30,11 +30,9 @@
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauImproveTypePathsInErrors) LUAU_FASTFLAGVARIABLE(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerStricterIndexCheck)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau namespace Luau
{ {
@ -662,7 +660,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<AstStatDeclareExternType>()) else if (auto s = stat->as<AstStatDeclareClass>())
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);
@ -1222,10 +1220,7 @@ void TypeChecker2::visit(AstStatDeclareFunction* stat)
{ {
visitGenerics(stat->generics, stat->genericPacks); visitGenerics(stat->generics, stat->genericPacks);
visit(stat->params); visit(stat->params);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visit(stat->retTypes); visit(stat->retTypes);
else
visit(stat->retTypes_DEPRECATED);
} }
void TypeChecker2::visit(AstStatDeclareGlobal* stat) void TypeChecker2::visit(AstStatDeclareGlobal* stat)
@ -1233,9 +1228,9 @@ void TypeChecker2::visit(AstStatDeclareGlobal* stat)
visit(stat->type); visit(stat->type);
} }
void TypeChecker2::visit(AstStatDeclareExternType* stat) void TypeChecker2::visit(AstStatDeclareClass* stat)
{ {
for (const AstDeclaredExternTypeProperty& prop : stat->props) for (const AstDeclaredClassProp& prop : stat->props)
visit(prop.ty); visit(prop.ty);
} }
@ -1679,12 +1674,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<ExternType>(exprType)) else if (auto cls = get<ClassType>(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(DynamicPropertyLookupOnExternTypesUnsafe{exprType}, indexExpr->location); reportError(DynamicPropertyLookupOnClassesUnsafe{exprType}, indexExpr->location);
} }
else if (get<UnionType>(exprType) && isOptional(exprType)) else if (get<UnionType>(exprType) && isOptional(exprType))
{ {
@ -1825,16 +1820,8 @@ 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 (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (fn->returnAnnotation) if (fn->returnAnnotation)
visit(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
@ -2048,7 +2035,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, extern types, etc) // TODO: Maybe add more checks here (e.g. for functions, classes, 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;
@ -2113,7 +2100,10 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey)
} }
else else
{ {
expectedRets = module->internalTypes.addTypePack({module->internalTypes.freshType(builtinTypes, scope, TypeLevel{})}); expectedRets = module->internalTypes.addTypePack(
{FFlag::LuauFreeTypesMustHaveBounds ? module->internalTypes.freshType(builtinTypes, scope, TypeLevel{})
: module->internalTypes.freshType_DEPRECATED(scope, TypeLevel{})}
);
} }
TypeId expectedTy = module->internalTypes.addType(FunctionType(expectedArgs, expectedRets)); TypeId expectedTy = module->internalTypes.addType(FunctionType(expectedArgs, expectedRets));
@ -2239,21 +2229,10 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey)
return builtinTypes->numberType; return builtinTypes->numberType;
case AstExprBinary::Op::Concat: case AstExprBinary::Op::Concat:
{
if (FFlag::LuauTypeCheckerAcceptNumberConcats)
{
const TypeId numberOrString = module->internalTypes.addType(UnionType{{builtinTypes->numberType, builtinTypes->stringType}});
testIsSubtype(leftType, numberOrString, expr->left->location);
testIsSubtype(rightType, numberOrString, expr->right->location);
}
else
{
testIsSubtype(leftType, builtinTypes->stringType, expr->left->location); testIsSubtype(leftType, builtinTypes->stringType, expr->left->location);
testIsSubtype(rightType, builtinTypes->stringType, expr->right->location); testIsSubtype(rightType, builtinTypes->stringType, expr->right->location);
}
return builtinTypes->stringType; return builtinTypes->stringType;
}
case AstExprBinary::Op::CompareGe: case AstExprBinary::Op::CompareGe:
case AstExprBinary::Op::CompareGt: case AstExprBinary::Op::CompareGt:
case AstExprBinary::Op::CompareLe: case AstExprBinary::Op::CompareLe:
@ -2376,7 +2355,8 @@ TypeId TypeChecker2::flattenPack(TypePackId pack)
return *fst; return *fst;
else if (auto ftp = get<FreeTypePack>(pack)) else if (auto ftp = get<FreeTypePack>(pack))
{ {
TypeId result = module->internalTypes.freshType(builtinTypes, ftp->scope); TypeId result = FFlag::LuauFreeTypesMustHaveBounds ? module->internalTypes.freshType(builtinTypes, ftp->scope)
: module->internalTypes.addType(FreeType{ftp->scope});
TypePackId freeTail = module->internalTypes.addTypePack(FreeTypePack{ftp->scope}); TypePackId freeTail = module->internalTypes.addTypePack(FreeTypePack{ftp->scope});
TypePack* resultPack = emplaceTypePack<TypePack>(asMutable(pack)); TypePack* resultPack = emplaceTypePack<TypePack>(asMutable(pack));
@ -2621,10 +2601,7 @@ void TypeChecker2::visit(AstTypeFunction* ty)
{ {
visitGenerics(ty->generics, ty->genericPacks); visitGenerics(ty->generics, ty->genericPacks);
visit(ty->argTypes); visit(ty->argTypes);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
visit(ty->returnTypes); visit(ty->returnTypes);
else
visit(ty->returnTypes_DEPRECATED);
} }
void TypeChecker2::visit(AstTypeTypeof* ty) void TypeChecker2::visit(AstTypeTypeof* ty)
@ -2948,7 +2925,7 @@ PropertyTypes TypeChecker2::lookupProp(
if (normValid) if (normValid)
{ {
for (const auto& [ty, _negations] : norm->externTypes.externTypes) for (const auto& [ty, _negations] : norm->classes.classes)
{ {
fetch(ty); fetch(ty);
@ -3043,10 +3020,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 extern types come into being with full knowledge of their // because classes come into being with full knowledge of their
// shape. We instead want to report the unknown property error of // shape. We instead want to report the unknown property error of
// the `else` branch. // the `else` branch.
else if (context == ValueContext::LValue && !get<ExternType>(tableTy)) else if (context == ValueContext::LValue && !get<ClassType>(tableTy))
{ {
const auto lvPropTypes = lookupProp(norm.get(), prop, ValueContext::RValue, location, astIndexExprType, dummy); const auto lvPropTypes = lookupProp(norm.get(), prop, ValueContext::RValue, location, astIndexExprType, dummy);
if (lvPropTypes.foundOneProp() && lvPropTypes.noneMissingProp()) if (lvPropTypes.foundOneProp() && lvPropTypes.noneMissingProp())
@ -3056,7 +3033,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<ExternType>(tableTy)) else if (context == ValueContext::RValue && !get<ClassType>(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())
@ -3109,25 +3086,19 @@ PropertyType TypeChecker2::hasIndexTypeFromType(
return {NormalizationResult::True, {tt->indexer->indexResultType}}; return {NormalizationResult::True, {tt->indexer->indexResultType}};
} }
if (FFlag::LuauTypeCheckerStricterIndexCheck)
{
return {NormalizationResult::False, {builtinTypes->unknownType}};
}
else
{
// if we are in a conditional context, we treat the property as present and `unknown` because // 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 // 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. // 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}}; 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 = lookupExternTypeProp(cls, prop)) if (auto property = lookupClassProp(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)
{ {
@ -3200,17 +3171,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 etv = get<ExternType>(follow(utk->table))) else if (auto ctv = get<ClassType>(follow(utk->table)))
{ {
while (etv) while (ctv)
{ {
accumulate(etv->props); accumulate(ctv->props);
if (!etv->parent) if (!ctv->parent)
break; break;
etv = get<ExternType>(*etv->parent); ctv = get<ClassType>(*ctv->parent);
LUAU_ASSERT(etv); LUAU_ASSERT(ctv);
} }
} }

File diff suppressed because it is too large Load diff

View file

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

View file

@ -14,8 +14,8 @@
#include <vector> #include <vector>
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit) LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
LUAU_FASTFLAGVARIABLE(LuauTypeFunPrintFix)
LUAU_FASTFLAGVARIABLE(LuauTypeFunReadWriteParents) LUAU_FASTFLAGVARIABLE(LuauTypeFunReadWriteParents)
LUAU_FASTFLAGVARIABLE(LuauTypeFunOptional)
namespace Luau namespace Luau
{ {
@ -155,7 +155,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<TypeFunctionExternType>(ty)) else if (get<TypeFunctionClassType>(ty))
return "class"; return "class";
else if (get<TypeFunctionGenericType>(ty)) else if (get<TypeFunctionGenericType>(ty))
return "generic"; return "generic";
@ -316,38 +316,6 @@ static int getSingletonValue(lua_State* L)
luaL_error(L, "type.value: can't call `value` method on `%s` type", getTag(L, self).c_str()); luaL_error(L, "type.value: can't call `value` method on `%s` type", getTag(L, self).c_str());
} }
// Luau: `types.optional(ty: type) -> type`
// Returns the type instance representing an optional version of `ty`.
// If `ty` is a union, this adds `nil` to the components of the union.
// Otherwise, makes a union of the two things.
static int createOptional(lua_State* L)
{
LUAU_ASSERT(FFlag::LuauTypeFunOptional);
int argumentCount = lua_gettop(L);
if (argumentCount != 1)
luaL_error(L, "types.optional: expected 1 argument, but got %d", argumentCount);
TypeFunctionTypeId argument = getTypeUserData(L, 1);
std::vector<TypeFunctionTypeId> components;
if (auto unionTy = get<TypeFunctionUnionType>(argument))
{
components.reserve(unionTy->components.size() + 1);
components.insert(components.begin(), unionTy->components.begin(), unionTy->components.end());
}
else
components.emplace_back(argument);
components.emplace_back(allocateTypeFunctionType(L, TypeFunctionPrimitiveType(TypeFunctionPrimitiveType::NilType)));
allocTypeUserData(L, TypeFunctionUnionType{components});
return 1;
}
// Luau: `types.unionof(...: type) -> type` // Luau: `types.unionof(...: type) -> type`
// Returns the type instance representing union // Returns the type instance representing union
static int createUnion(lua_State* L) static int createUnion(lua_State* L)
@ -1147,7 +1115,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<TypeFunctionExternType>(self); auto tfct = get<TypeFunctionClassType>(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());
@ -1169,7 +1137,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<TypeFunctionExternType>(self); auto tfct = get<TypeFunctionClassType>(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());
@ -1191,7 +1159,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<TypeFunctionExternType>(self); auto tfct = get<TypeFunctionClassType>(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());
@ -1275,7 +1243,7 @@ static int getProps(lua_State* L)
return 1; return 1;
} }
if (auto tfct = get<TypeFunctionExternType>(self)) if (auto tfct = get<TypeFunctionClassType>(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)
@ -1338,7 +1306,7 @@ static int getIndexer(lua_State* L)
return 1; return 1;
} }
if (auto tfct = get<TypeFunctionExternType>(self)) if (auto tfct = get<TypeFunctionClassType>(self))
{ {
// if the indexer does not exist, we should return nil // if the indexer does not exist, we should return nil
if (!tfct->indexer.has_value()) if (!tfct->indexer.has_value())
@ -1386,7 +1354,7 @@ static int getReadIndexer(lua_State* L)
return 1; return 1;
} }
if (auto tfct = get<TypeFunctionExternType>(self)) if (auto tfct = get<TypeFunctionClassType>(self))
{ {
// if the indexer does not exist, we should return nil // if the indexer does not exist, we should return nil
if (!tfct->indexer.has_value()) if (!tfct->indexer.has_value())
@ -1432,7 +1400,7 @@ static int getWriteIndexer(lua_State* L)
return 1; return 1;
} }
if (auto tfct = get<TypeFunctionExternType>(self)) if (auto tfct = get<TypeFunctionClassType>(self))
{ {
// if the indexer does not exist, we should return nil // if the indexer does not exist, we should return nil
if (!tfct->indexer.has_value()) if (!tfct->indexer.has_value())
@ -1472,7 +1440,7 @@ static int getMetatable(lua_State* L)
return 1; return 1;
} }
if (auto tfct = get<TypeFunctionExternType>(self)) if (auto tfct = get<TypeFunctionClassType>(self))
{ {
// if the metatable does not exist, we should return nil // if the metatable does not exist, we should return nil
if (!tfct->metatable.has_value()) if (!tfct->metatable.has_value())
@ -1557,7 +1525,6 @@ void registerTypesLibrary(lua_State* L)
{"copy", deepCopy}, {"copy", deepCopy},
{"generic", createGeneric}, {"generic", createGeneric},
{(FFlag::LuauTypeFunOptional) ? "optional" : nullptr, (FFlag::LuauTypeFunOptional) ? createOptional : nullptr},
{nullptr, nullptr} {nullptr, nullptr}
}; };
@ -1627,7 +1594,7 @@ void registerTypeUserData(lua_State* L)
// Union and Intersection type methods // Union and Intersection type methods
{"components", getComponents}, {"components", getComponents},
// Extern type methods // Class 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.)
@ -1638,7 +1605,7 @@ void registerTypeUserData(lua_State* L)
{"name", getGenericName}, {"name", getGenericName},
{"ispack", getGenericIsPack}, {"ispack", getGenericIsPack},
// move this under extern type methods when removing FFlagLuauTypeFunReadWriteParents // move this under Class type methods when removing FFlagLuauTypeFunReadWriteParents
{FFlag::LuauTypeFunReadWriteParents ? "writeparent" : nullptr, FFlag::LuauTypeFunReadWriteParents ? getWriteParent : nullptr}, {FFlag::LuauTypeFunReadWriteParents ? "writeparent" : nullptr, FFlag::LuauTypeFunReadWriteParents ? getWriteParent : nullptr},
{nullptr, nullptr} {nullptr, nullptr}
@ -1689,7 +1656,10 @@ static int print(lua_State* L)
const char* s = luaL_tolstring(L, i, &l); // convert to string using __tostring et al const char* s = luaL_tolstring(L, i, &l); // convert to string using __tostring et al
if (i > 1) if (i > 1)
{ {
if (FFlag::LuauTypeFunPrintFix)
result.append(1, '\t'); result.append(1, '\t');
else
result.append('\t', 1);
} }
result.append(s, l); result.append(s, l);
lua_pop(L, 1); lua_pop(L, 1);
@ -1937,12 +1907,12 @@ bool areEqual(SeenSet& seen, const TypeFunctionFunctionType& lhs, const TypeFunc
return true; return true;
} }
bool areEqual(SeenSet& seen, const TypeFunctionExternType& lhs, const TypeFunctionExternType& rhs) bool areEqual(SeenSet& seen, const TypeFunctionClassType& lhs, const TypeFunctionClassType& rhs)
{ {
if (seenSetContains(seen, &lhs, &rhs)) if (seenSetContains(seen, &lhs, &rhs))
return true; return true;
return lhs.externTy == rhs.externTy; return lhs.classTy == rhs.classTy;
} }
bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType& rhs) bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType& rhs)
@ -2010,8 +1980,8 @@ bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType
} }
{ {
const TypeFunctionExternType* lf = get<TypeFunctionExternType>(&lhs); const TypeFunctionClassType* lf = get<TypeFunctionClassType>(&lhs);
const TypeFunctionExternType* rf = get<TypeFunctionExternType>(&rhs); const TypeFunctionClassType* rf = get<TypeFunctionClassType>(&rhs);
if (lf && rf) if (lf && rf)
return areEqual(seen, *lf, *rf); return areEqual(seen, *lf, *rf);
} }
@ -2300,7 +2270,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<TypeFunctionExternType>(ty)) else if (auto c = get<TypeFunctionClassType>(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});
@ -2355,7 +2325,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<TypeFunctionExternType>(ty), getMutable<TypeFunctionExternType>(tfti)}; c1 && c2) else if (auto [c1, c2] = std::tuple{getMutable<TypeFunctionClassType>(ty), getMutable<TypeFunctionClassType>(tfti)}; c1 && c2)
cloneChildren(c1, c2); 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);
@ -2465,7 +2435,7 @@ private:
f2->retTypes = shallowClone(f1->retTypes); f2->retTypes = shallowClone(f1->retTypes);
} }
void cloneChildren(TypeFunctionExternType* c1, TypeFunctionExternType* c2) void cloneChildren(TypeFunctionClassType* c1, TypeFunctionClassType* 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<ExternType>(ty)) else if (auto c = get<ClassType>(ty))
{ {
// Since there aren't any new class types being created in type functions, we will deserialize by using a direct reference to the original // 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(
TypeFunctionExternType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty} TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty}
); );
} }
else if (auto g = get<GenericType>(ty)) 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<ExternType>(ty), getMutable<TypeFunctionExternType>(tfti)}; c1 && c2) else if (auto [c1, c2] = std::tuple{get<ClassType>(ty), getMutable<TypeFunctionClassType>(tfti)}; c1 && c2)
serializeChildren(c1, c2); 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 ExternType* c1, TypeFunctionExternType* c2) void serializeChildren(const ClassType* c1, TypeFunctionClassType* 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<TypeFunctionExternType>(ty)) else if (auto c = get<TypeFunctionClassType>(ty))
{ {
target = c->externTy; target = c->classTy;
} }
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<ExternType>(ty), getMutable<TypeFunctionExternType>(tfti)}; c1 && c2) else if (auto [c1, c2] = std::tuple{getMutable<ClassType>(ty), getMutable<TypeFunctionClassType>(tfti)}; c1 && c2)
deserializeChildren(c2, c1); 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(TypeFunctionExternType* c2, ExternType* c1) void deserializeChildren(TypeFunctionClassType* c2, ClassType* c1)
{ {
// noop. // noop.
} }

View file

@ -32,19 +32,17 @@ LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations) LUAU_FASTFLAG(LuauModuleHoldsAstRoot)
LUAU_FASTFLAGVARIABLE(LuauStatForInFix)
LUAU_FASTFLAGVARIABLE(LuauReduceCheckBinaryExprStackPressure)
LUAU_FASTFLAGVARIABLE(LuauLimitIterationWhenCheckingArgumentCounts)
namespace Luau namespace Luau
{ {
static bool typeCouldHaveMetatable(TypeId ty) static bool typeCouldHaveMetatable(TypeId ty)
{ {
return get<TableType>(follow(ty)) || get<ExternType>(follow(ty)) || get<MetatableType>(follow(ty)); return get<TableType>(follow(ty)) || get<ClassType>(follow(ty)) || get<MetatableType>(follow(ty));
} }
static void defaultLuauPrintLine(const std::string& s) static void defaultLuauPrintLine(const std::string& s)
@ -258,6 +256,7 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
currentModule->type = module.type; currentModule->type = module.type;
currentModule->allocator = module.allocator; currentModule->allocator = module.allocator;
currentModule->names = module.names; currentModule->names = module.names;
if (FFlag::LuauModuleHoldsAstRoot)
currentModule->root = module.root; currentModule->root = module.root;
iceHandler->moduleName = module.name; iceHandler->moduleName = module.name;
@ -318,7 +317,7 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
unifierState.skipCacheForType.clear(); unifierState.skipCacheForType.clear();
duplicateTypeAliases.clear(); duplicateTypeAliases.clear();
incorrectExternTypeDefinitions.clear(); incorrectClassDefinitions.clear();
return std::move(currentModule); return std::move(currentModule);
} }
@ -383,7 +382,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<AstStatDeclareExternType>()) else if (auto global = program.as<AstStatDeclareClass>())
return check(scope, *global); return check(scope, *global);
else if (auto errorStatement = program.as<AstStatError>()) else if (auto errorStatement = program.as<AstStatError>())
{ {
@ -498,9 +497,9 @@ ControlFlow TypeChecker::checkBlockWithoutRecursionCheck(const ScopePtr& scope,
prototype(scope, *typealias, subLevel); prototype(scope, *typealias, subLevel);
++subLevel; ++subLevel;
} }
else if (const auto& declaredExternType = stat->as<AstStatDeclareExternType>()) else if (const auto& declaredClass = stat->as<AstStatDeclareClass>())
{ {
prototype(scope, *declaredExternType); prototype(scope, *declaredClass);
} }
} }
@ -788,7 +787,7 @@ struct Demoter : Substitution
bool ignoreChildren(TypeId ty) override bool ignoreChildren(TypeId ty) override
{ {
if (get<ExternType>(ty)) if (get<ClassType>(ty))
return true; return true;
return false; return false;
@ -798,7 +797,8 @@ struct Demoter : Substitution
{ {
auto ftv = get<FreeType>(ty); auto ftv = get<FreeType>(ty);
LUAU_ASSERT(ftv); LUAU_ASSERT(ftv);
return arena->freshType(builtins, demotedLevel(ftv->level)); return FFlag::LuauFreeTypesMustHaveBounds ? arena->freshType(builtins, demotedLevel(ftv->level))
: addType(FreeType{demotedLevel(ftv->level)});
} }
TypePackId clean(TypePackId tp) override TypePackId clean(TypePackId tp) override
@ -1319,26 +1319,10 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin)
// Extract the remaining return values of the call // Extract the remaining return values of the call
// and check them against the parameter types of the iterator function. // and check them against the parameter types of the iterator function.
auto [types, tail] = flatten(callRetPack); auto [types, tail] = flatten(callRetPack);
if (FFlag::LuauStatForInFix)
{
if (!types.empty())
{
std::vector<TypeId> argTypes = std::vector<TypeId>(types.begin() + 1, types.end()); std::vector<TypeId> argTypes = std::vector<TypeId>(types.begin() + 1, types.end());
argPack = addTypePack(TypePackVar{TypePack{std::move(argTypes), tail}}); argPack = addTypePack(TypePackVar{TypePack{std::move(argTypes), tail}});
} }
else else
{
argPack = addTypePack(TypePack{});
}
}
else
{
std::vector<TypeId> argTypes = std::vector<TypeId>(types.begin() + 1, types.end());
argPack = addTypePack(TypePackVar{TypePack{std::move(argTypes), tail}});
}
}
else
{ {
// Check if iterator function accepts 0 arguments // Check if iterator function accepts 0 arguments
argPack = addTypePack(TypePack{}); argPack = addTypePack(TypePack{});
@ -1674,9 +1658,6 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea
FreeType* ftv = getMutable<FreeType>(ty); FreeType* ftv = getMutable<FreeType>(ty);
LUAU_ASSERT(ftv); LUAU_ASSERT(ftv);
ftv->forwardedTypeAlias = true; ftv->forwardedTypeAlias = true;
if (FFlag::LuauRetainDefinitionAliasLocations)
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty, typealias.location};
else
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty}; bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
scope->typeAliasLocations[name] = typealias.location; scope->typeAliasLocations[name] = typealias.location;
@ -1685,82 +1666,79 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea
} }
} }
void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareExternType& declaredExternType) void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareClass& declaredClass)
{ {
std::optional<TypeId> superTy = std::make_optional(builtinTypes->externType); std::optional<TypeId> superTy = std::make_optional(builtinTypes->classType);
if (declaredExternType.superName) if (declaredClass.superName)
{ {
Name superName = Name(declaredExternType.superName->value); Name superName = Name(declaredClass.superName->value);
std::optional<TypeFun> lookupType = scope->lookupType(superName); std::optional<TypeFun> lookupType = scope->lookupType(superName);
if (!lookupType) if (!lookupType)
{ {
reportError(declaredExternType.location, UnknownSymbol{superName, UnknownSymbol::Type}); reportError(declaredClass.location, UnknownSymbol{superName, UnknownSymbol::Type});
incorrectExternTypeDefinitions.insert(&declaredExternType); incorrectClassDefinitions.insert(&declaredClass);
return; return;
} }
// We don't have generic extern types, so this assertion _should_ never be hit. // We don't have generic classes, so this assertion _should_ never be hit.
LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0); LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0);
superTy = lookupType->type; superTy = lookupType->type;
if (!get<ExternType>(follow(*superTy))) if (!get<ClassType>(follow(*superTy)))
{ {
reportError( reportError(
declaredExternType.location, declaredClass.location,
GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredExternType.name.value)} GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredClass.name.value)}
); );
incorrectExternTypeDefinitions.insert(&declaredExternType); incorrectClassDefinitions.insert(&declaredClass);
return; return;
} }
} }
Name className(declaredExternType.name.value); Name className(declaredClass.name.value);
TypeId classTy = addType(ExternType(className, {}, superTy, std::nullopt, {}, {}, currentModule->name, declaredExternType.location)); TypeId classTy = addType(ClassType(className, {}, superTy, std::nullopt, {}, {}, currentModule->name, declaredClass.location));
ExternType* etv = getMutable<ExternType>(classTy); ClassType* ctv = getMutable<ClassType>(classTy);
TypeId metaTy = addType(TableType{TableState::Sealed, scope->level}); TypeId metaTy = addType(TableType{TableState::Sealed, scope->level});
etv->metatable = metaTy; ctv->metatable = metaTy;
if (FFlag::LuauRetainDefinitionAliasLocations)
scope->exportedTypeBindings[className] = TypeFun{{}, classTy, declaredExternType.location};
else
scope->exportedTypeBindings[className] = TypeFun{{}, classTy}; scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
} }
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareExternType& declaredExternType) ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass)
{ {
Name className(declaredExternType.name.value); Name className(declaredClass.name.value);
// Don't bother checking if the class definition was incorrect // Don't bother checking if the class definition was incorrect
if (incorrectExternTypeDefinitions.find(&declaredExternType)) if (incorrectClassDefinitions.find(&declaredClass))
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 extern type definition must have been `prototype()`d first. // This class definition must have been `prototype()`d first.
if (!binding) if (!binding)
ice("Extern type not predeclared"); ice("Class not predeclared");
TypeId externTy = binding->type; TypeId classTy = binding->type;
ExternType* etv = getMutable<ExternType>(externTy); ClassType* ctv = getMutable<ClassType>(classTy);
if (!etv->metatable) if (!ctv->metatable)
ice("No metatable for declared extern type"); ice("No metatable for declared class");
if (const auto& indexer = declaredExternType.indexer) if (const auto& indexer = declaredClass.indexer)
etv->indexer = TableIndexer(resolveType(scope, *indexer->indexType), resolveType(scope, *indexer->resultType)); ctv->indexer = TableIndexer(resolveType(scope, *indexer->indexType), resolveType(scope, *indexer->resultType));
TableType* metatable = getMutable<TableType>(*etv->metatable); TableType* metatable = getMutable<TableType>(*ctv->metatable);
for (const AstDeclaredExternTypeProperty& prop : declaredExternType.props) for (const AstDeclaredClassProp& prop : declaredClass.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::ExternType::Props& assignTo = assignToMetatable ? metatable->props : etv->props; Luau::ClassType::Props& assignTo = assignToMetatable ? metatable->props : ctv->props;
// Function types always take 'self', but this isn't reflected in the // Function types always take 'self', but this isn't reflected in the
// parsed annotation. Add it here. // parsed annotation. Add it here.
@ -1769,7 +1747,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareExtern
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{{externTy}, ftv->argTypes}); ftv->argTypes = addTypePack(TypePack{{classTy}, ftv->argTypes});
ftv->hasSelf = true; ftv->hasSelf = true;
FunctionDefinition defn; FunctionDefinition defn;
@ -1812,7 +1790,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareExtern
} }
else else
{ {
reportError(declaredExternType.location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())}); reportError(declaredClass.location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())});
} }
} }
} }
@ -1851,8 +1829,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareFuncti
); );
TypePackId argPack = resolveTypePack(funScope, global.params); TypePackId argPack = resolveTypePack(funScope, global.params);
TypePackId retPack = TypePackId retPack = resolveTypePack(funScope, global.retTypes);
FFlag::LuauStoreReturnTypesAsPackOnAst ? resolveTypePack(funScope, *global.retTypes) : resolveTypePack(funScope, global.retTypes_DEPRECATED);
FunctionDefinition defn; FunctionDefinition defn;
@ -1926,7 +1903,7 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
else if (auto a = expr.as<AstExprUnary>()) else if (auto a = expr.as<AstExprUnary>())
result = checkExpr(scope, *a); result = checkExpr(scope, *a);
else if (auto a = expr.as<AstExprBinary>()) else if (auto a = expr.as<AstExprBinary>())
result = FFlag::LuauReduceCheckBinaryExprStackPressure ? checkExpr(scope, *a, expectedType) : checkExpr_DEPRECATED(scope, *a, expectedType); result = checkExpr(scope, *a, expectedType);
else if (auto a = expr.as<AstExprTypeAssertion>()) else if (auto a = expr.as<AstExprTypeAssertion>())
result = checkExpr(scope, *a); result = checkExpr(scope, *a);
else if (auto a = expr.as<AstExprError>()) else if (auto a = expr.as<AstExprError>())
@ -2137,9 +2114,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 ExternType* cls = get<ExternType>(type)) else if (const ClassType* cls = get<ClassType>(type))
{ {
const Property* prop = lookupExternTypeProp(cls, name); const Property* prop = lookupClassProp(cls, name);
if (prop) if (prop)
return prop->type(); return prop->type();
@ -3188,9 +3165,6 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
return {result, {OrPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}}; return {result, {OrPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
} }
else if (expr.op == AstExprBinary::CompareEq || expr.op == AstExprBinary::CompareNe) else if (expr.op == AstExprBinary::CompareEq || expr.op == AstExprBinary::CompareNe)
{
// Defer the stack allocation of lhs, predicate etc until this lambda is called.
auto checkExprOr = [&]() -> WithPredicate<TypeId>
{ {
// For these, passing expectedType is worse than simply forcing them, because their implementation // For these, passing expectedType is worse than simply forcing them, because their implementation
// may inadvertently check if expectedTypes exist first and use it, instead of forceSingleton first. // may inadvertently check if expectedTypes exist first and use it, instead of forceSingleton first.
@ -3202,68 +3176,9 @@ WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExp
PredicateVec predicates; PredicateVec predicates;
if (auto lvalue = tryGetLValue(*expr.left))
predicates.emplace_back(EqPredicate{std::move(*lvalue), rhs.type, expr.location});
if (auto lvalue = tryGetLValue(*expr.right))
predicates.emplace_back(EqPredicate{std::move(*lvalue), lhs.type, expr.location});
if (!predicates.empty() && expr.op == AstExprBinary::CompareNe)
predicates = {NotPredicate{std::move(predicates)}};
return {checkBinaryOperation(scope, expr, lhs.type, rhs.type), std::move(predicates)};
};
return checkExprOr();
}
else
{
// Expected types are not useful for other binary operators.
WithPredicate<TypeId> lhs = checkExpr(scope, *expr.left);
WithPredicate<TypeId> rhs = checkExpr(scope, *expr.right);
// Intentionally discarding predicates with other operators.
return WithPredicate{checkBinaryOperation(scope, expr, lhs.type, rhs.type, lhs.predicates)};
}
}
WithPredicate<TypeId> TypeChecker::checkExpr_DEPRECATED(const ScopePtr& scope, const AstExprBinary& expr, std::optional<TypeId> expectedType)
{
if (expr.op == AstExprBinary::And)
{
auto [lhsTy, lhsPredicates] = checkExpr(scope, *expr.left, expectedType);
ScopePtr innerScope = childScope(scope, expr.location);
resolve(lhsPredicates, innerScope, true);
auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right, expectedType);
return {checkBinaryOperation(scope, expr, lhsTy, rhsTy), {AndPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
}
else if (expr.op == AstExprBinary::Or)
{
auto [lhsTy, lhsPredicates] = checkExpr(scope, *expr.left, expectedType);
ScopePtr innerScope = childScope(scope, expr.location);
resolve(lhsPredicates, innerScope, false);
auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right, expectedType);
// Because of C++, I'm not sure if lhsPredicates was not moved out by the time we call checkBinaryOperation.
TypeId result = checkBinaryOperation(scope, expr, lhsTy, rhsTy, lhsPredicates);
return {result, {OrPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
}
else if (expr.op == AstExprBinary::CompareEq || expr.op == AstExprBinary::CompareNe)
{
// For these, passing expectedType is worse than simply forcing them, because their implementation
// may inadvertently check if expectedTypes exist first and use it, instead of forceSingleton first.
WithPredicate<TypeId> lhs = checkExpr(scope, *expr.left, std::nullopt, /*forceSingleton=*/true);
WithPredicate<TypeId> rhs = checkExpr(scope, *expr.right, std::nullopt, /*forceSingleton=*/true);
if (auto predicate = tryGetTypeGuardPredicate(expr))
return {booleanType, {std::move(*predicate)}};
PredicateVec predicates;
if (auto lvalue = tryGetLValue(*expr.left)) if (auto lvalue = tryGetLValue(*expr.left))
predicates.push_back(EqPredicate{std::move(*lvalue), rhs.type, expr.location}); predicates.push_back(EqPredicate{std::move(*lvalue), rhs.type, expr.location});
if (auto lvalue = tryGetLValue(*expr.right)) if (auto lvalue = tryGetLValue(*expr.right))
predicates.push_back(EqPredicate{std::move(*lvalue), lhs.type, expr.location}); predicates.push_back(EqPredicate{std::move(*lvalue), lhs.type, expr.location});
@ -3462,14 +3377,14 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
return errorRecoveryType(scope); return errorRecoveryType(scope);
} }
} }
else if (const ExternType* lhsExternType = get<ExternType>(lhs)) else if (const ClassType* lhsClass = get<ClassType>(lhs))
{ {
if (const Property* prop = lookupExternTypeProp(lhsExternType, name)) if (const Property* prop = lookupClassProp(lhsClass, name))
{ {
return prop->type(); return prop->type();
} }
if (auto indexer = lhsExternType->indexer) if (auto indexer = lhsClass->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 +3436,14 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
if (value) if (value)
{ {
if (const ExternType* exprExternType = get<ExternType>(exprType)) if (const ClassType* exprClass = get<ClassType>(exprType))
{ {
if (const Property* prop = lookupExternTypeProp(exprExternType, value->value.data)) if (const Property* prop = lookupClassProp(exprClass, value->value.data))
{ {
return prop->type(); return prop->type();
} }
if (auto indexer = exprExternType->indexer) if (auto indexer = exprClass->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 +3469,20 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
} }
else else
{ {
if (const ExternType* exprExternType = get<ExternType>(exprType)) if (const ClassType* exprClass = get<ClassType>(exprType))
{ {
if (auto indexer = exprExternType->indexer) if (auto indexer = exprClass->indexer)
{ {
unify(indexType, indexer->indexType, scope, expr.index->location); unify(indexType, indexer->indexType, scope, expr.index->location);
return indexer->indexResultType; return indexer->indexResultType;
} }
} }
if (const ExternType* exprExternType = get<ExternType>(exprType)) if (const ClassType* exprClass = get<ClassType>(exprType))
{ {
if (isNonstrictMode()) if (isNonstrictMode())
return unknownType; return unknownType;
reportError(TypeError{expr.location, DynamicPropertyLookupOnExternTypesUnsafe{exprType}}); reportError(TypeError{expr.location, DynamicPropertyLookupOnClassesUnsafe{exprType}});
return errorRecoveryType(scope); return errorRecoveryType(scope);
} }
} }
@ -3848,10 +3763,8 @@ 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 (FFlag::LuauStoreReturnTypesAsPackOnAst && expr.returnAnnotation) if (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())
@ -4058,8 +3971,7 @@ void TypeChecker::checkFunctionBody(const ScopePtr& scope, TypeId ty, const AstE
// If we're in nonstrict mode we want to only report this missing return // 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() || if (!isNonstrictMode() || function.returnAnnotation)
(FFlag::LuauStoreReturnTypesAsPackOnAst ? function.returnAnnotation != nullptr : function.returnAnnotation_DEPRECATED.has_value()))
{ {
reportError(getEndLocation(function), FunctionExitsWithoutReturning{funTy->retTypes}); reportError(getEndLocation(function), FunctionExitsWithoutReturning{funTy->retTypes});
} }
@ -4117,23 +4029,6 @@ void TypeChecker::checkArgumentList(
size_t paramIndex = 0; size_t paramIndex = 0;
int loopCount = 0;
auto exceedsLoopCount = [&]()
{
if (FFlag::LuauLimitIterationWhenCheckingArgumentCounts)
{
++loopCount;
if (loopCount > FInt::LuauTypeInferTypePackLoopLimit)
{
state.reportError(TypeError{state.location, CodeTooComplex{}});
reportErrorCodeTooComplex(state.location);
return true;
}
}
return false;
};
auto reportCountMismatchError = [&state, &argLocations, paramPack, argPack, &funName]() auto reportCountMismatchError = [&state, &argLocations, paramPack, argPack, &funName]()
{ {
// For this case, we want the error span to cover every errant extra parameter // For this case, we want the error span to cover every errant extra parameter
@ -4208,17 +4103,12 @@ void TypeChecker::checkArgumentList(
} }
else if (auto vtp = state.log.getMutable<VariadicTypePack>(tail)) else if (auto vtp = state.log.getMutable<VariadicTypePack>(tail))
{ {
loopCount = 0;
// Function is variadic and requires that all subsequent parameters // Function is variadic and requires that all subsequent parameters
// be compatible with a type. // be compatible with a type.
while (paramIter != endIter) while (paramIter != endIter)
{ {
state.tryUnify(vtp->ty, *paramIter); state.tryUnify(vtp->ty, *paramIter);
++paramIter; ++paramIter;
if (exceedsLoopCount())
return;
} }
return; return;
@ -4227,16 +4117,10 @@ void TypeChecker::checkArgumentList(
{ {
std::vector<TypeId> rest; std::vector<TypeId> rest;
rest.reserve(std::distance(paramIter, endIter)); rest.reserve(std::distance(paramIter, endIter));
loopCount = 0;
while (paramIter != endIter) while (paramIter != endIter)
{ {
rest.push_back(*paramIter); rest.push_back(*paramIter);
++paramIter; ++paramIter;
if (exceedsLoopCount())
return;
} }
TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, paramIter.tail()}}); TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, paramIter.tail()}});
@ -4280,17 +4164,12 @@ void TypeChecker::checkArgumentList(
// too many parameters passed // too many parameters passed
if (!paramIter.tail()) if (!paramIter.tail())
{ {
loopCount = 0;
while (argIter != endIter) while (argIter != endIter)
{ {
// The use of unify here is deliberate. We don't want this unification // The use of unify here is deliberate. We don't want this unification
// to be undoable. // to be undoable.
unify(errorRecoveryType(scope), *argIter, scope, state.location); unify(errorRecoveryType(scope), *argIter, scope, state.location);
++argIter; ++argIter;
if (exceedsLoopCount())
return;
} }
reportCountMismatchError(); reportCountMismatchError();
return; return;
@ -4304,8 +4183,6 @@ void TypeChecker::checkArgumentList(
} }
else if (auto vtp = state.log.getMutable<VariadicTypePack>(tail)) else if (auto vtp = state.log.getMutable<VariadicTypePack>(tail))
{ {
loopCount = 0;
// Function is variadic and requires that all subsequent parameters // Function is variadic and requires that all subsequent parameters
// be compatible with a type. // be compatible with a type.
size_t argIndex = paramIndex; size_t argIndex = paramIndex;
@ -4321,17 +4198,12 @@ void TypeChecker::checkArgumentList(
++argIter; ++argIter;
++argIndex; ++argIndex;
if (exceedsLoopCount())
return;
} }
return; return;
} }
else if (state.log.getMutable<FreeTypePack>(tail)) else if (state.log.getMutable<FreeTypePack>(tail))
{ {
loopCount = 0;
// Create a type pack out of the remaining argument types // Create a type pack out of the remaining argument types
// and unify it with the tail. // and unify it with the tail.
std::vector<TypeId> rest; std::vector<TypeId> rest;
@ -4340,9 +4212,6 @@ void TypeChecker::checkArgumentList(
{ {
rest.push_back(*argIter); rest.push_back(*argIter);
++argIter; ++argIter;
if (exceedsLoopCount())
return;
} }
TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, argIter.tail()}}); TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, argIter.tail()}});
@ -4614,9 +4483,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 ExternType* etv = get<ExternType>(fn); etv && etv->metatable) else if (const ClassType* ctv = get<ClassType>(fn); ctv && ctv->metatable)
{ {
callTy = getIndexTypeFromType(scope, *etv->metatable, "__call", expr.func->location, /* addErrors= */ false); callTy = getIndexTypeFromType(scope, *ctv->metatable, "__call", expr.func->location, /* addErrors= */ false);
} }
if (callTy) if (callTy)
@ -5319,17 +5188,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 etv = get<ExternType>(follow(utk->table))) else if (auto ctv = get<ClassType>(follow(utk->table)))
{ {
while (etv) while (ctv)
{ {
accumulate(etv->props); accumulate(ctv->props);
if (!etv->parent) if (!ctv->parent)
break; break;
etv = get<ExternType>(*etv->parent); ctv = get<ClassType>(*ctv->parent);
LUAU_ASSERT(etv); LUAU_ASSERT(ctv);
} }
} }
@ -5408,7 +5277,8 @@ TypeId TypeChecker::freshType(const ScopePtr& scope)
TypeId TypeChecker::freshType(TypeLevel level) TypeId TypeChecker::freshType(TypeLevel level)
{ {
return currentModule->internalTypes.freshType(builtinTypes, level); return FFlag::LuauFreeTypesMustHaveBounds ? currentModule->internalTypes.freshType(builtinTypes, level)
: currentModule->internalTypes.addType(Type(FreeType(level)));
} }
TypeId TypeChecker::singletonType(bool value) TypeId TypeChecker::singletonType(bool value)
@ -5805,8 +5675,7 @@ 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 = FFlag::LuauStoreReturnTypesAsPackOnAst ? resolveTypePack(funcScope, *func->returnTypes) TypePackId retTypes = 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());
@ -5857,9 +5726,13 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno
return builtinTypes->nilType; return builtinTypes->nilType;
} }
else if (const auto& un = annotation.as<AstTypeUnion>()) else if (const auto& un = annotation.as<AstTypeUnion>())
{
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
{ {
if (un->types.size == 1) if (un->types.size == 1)
return resolveType(scope, *un->types.data[0]); 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));
@ -5867,9 +5740,13 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno
return addType(UnionType{types}); return addType(UnionType{types});
} }
else if (const auto& un = annotation.as<AstTypeIntersection>()) else if (const auto& un = annotation.as<AstTypeIntersection>())
{
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
{ {
if (un->types.size == 1) if (un->types.size == 1)
return resolveType(scope, *un->types.data[0]); 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));
@ -6476,7 +6353,7 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& r
return refine( return refine(
[](TypeId ty) -> bool [](TypeId ty) -> bool
{ {
return get<ExternType>(ty); return get<ClassType>(ty);
} }
); );
} }
@ -6491,13 +6368,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->externType) if (type == builtinTypes->classType)
{ {
return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope)); return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
} }
// We're only interested in the root type of any extern type. // We're only interested in the root class of any classes.
if (auto etv = get<ExternType>(type); !etv || (etv->parent != builtinTypes->externType && !hasTag(type, kTypeofRootTag))) if (auto ctv = get<ClassType>(type); !ctv || (ctv->parent != builtinTypes->classType && !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

@ -6,7 +6,7 @@
#include <stdexcept> #include <stdexcept>
LUAU_FASTFLAGVARIABLE(LuauTypePackDetectCycles) LUAU_FASTFLAG(LuauSolverV2);
namespace Luau namespace Luau
{ {
@ -18,11 +18,10 @@ FreeTypePack::FreeTypePack(TypeLevel level)
{ {
} }
FreeTypePack::FreeTypePack(Scope* scope, Polarity polarity) FreeTypePack::FreeTypePack(Scope* scope)
: index(Unifiable::freshIndex()) : index(Unifiable::freshIndex())
, level{} , level{}
, scope(scope) , scope(scope)
, polarity(polarity)
{ {
} }
@ -53,10 +52,9 @@ GenericTypePack::GenericTypePack(const Name& name)
{ {
} }
GenericTypePack::GenericTypePack(Scope* scope, Polarity polarity) GenericTypePack::GenericTypePack(Scope* scope)
: index(Unifiable::freshIndex()) : index(Unifiable::freshIndex())
, scope(scope) , scope(scope)
, polarity(polarity)
{ {
} }
@ -149,15 +147,6 @@ TypePackIterator& TypePackIterator::operator++()
currentTypePack = tp->tail ? log->follow(*tp->tail) : nullptr; currentTypePack = tp->tail ? log->follow(*tp->tail) : nullptr;
tp = currentTypePack ? log->getMutable<TypePack>(currentTypePack) : nullptr; tp = currentTypePack ? log->getMutable<TypePack>(currentTypePack) : nullptr;
if (FFlag::LuauTypePackDetectCycles && tp)
{
// Step twice on each iteration to detect cycles
tailCycleCheck = tp->tail ? log->follow(*tp->tail) : nullptr;
if (currentTypePack == tailCycleCheck)
throw InternalCompilerError("TypePackIterator detected a type pack cycle");
}
currentIndex = 0; currentIndex = 0;
} }
@ -208,26 +197,6 @@ TypePackIterator end(TypePackId tp)
return TypePackIterator{}; return TypePackIterator{};
} }
TypePackId getTail(TypePackId tp)
{
DenseHashSet<TypePackId> seen{nullptr};
while (tp)
{
tp = follow(tp);
if (seen.contains(tp))
break;
seen.insert(tp);
if (auto pack = get<TypePack>(tp); pack && pack->tail)
tp = *pack->tail;
else
break;
}
return follow(tp);
}
bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs) bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs)
{ {
TypePackId lhsId = const_cast<TypePackId>(&lhs); TypePackId lhsId = const_cast<TypePackId>(&lhs);

View file

@ -307,9 +307,9 @@ struct TraversalState
prop = &it->second; prop = &it->second;
} }
} }
else if (auto c = get<ExternType>(*currentType)) else if (auto c = get<ClassType>(*currentType))
{ {
prop = lookupExternTypeProp(c, property.name); prop = lookupClassProp(c, property.name);
} }
// For a metatable type, the table takes priority; check that before // 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<ExternType>(current); ct && ct->indexer) else if (auto ct = get<ClassType>(current); ct && ct->indexer)
indexer = &(*ct->indexer); indexer = &(*ct->indexer);
if (indexer) if (indexer)

View file

@ -11,9 +11,10 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete);
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope);
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode) LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode)
namespace Luau namespace Luau
{ {
@ -303,11 +304,7 @@ TypePack extendTypePack(
// also have to create a new tail. // also have to create a new tail.
TypePack newPack; TypePack newPack;
newPack.tail = arena.freshTypePack(ftp->scope, ftp->polarity); newPack.tail = arena.freshTypePack(ftp->scope);
if (FFlag::LuauNonReentrantGeneralization2)
trackInteriorFreeTypePack(ftp->scope, *newPack.tail);
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
result.tail = newPack.tail; result.tail = newPack.tail;
size_t overridesIndex = 0; size_t overridesIndex = 0;
@ -322,12 +319,13 @@ TypePack extendTypePack(
{ {
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
FreeType ft{ftp->scope, builtinTypes->neverType, builtinTypes->unknownType, ftp->polarity}; FreeType ft{ftp->scope, builtinTypes->neverType, builtinTypes->unknownType};
t = arena.addType(ft); t = arena.addType(ft);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
trackInteriorFreeType(ftp->scope, t); trackInteriorFreeType(ftp->scope, t);
} }
else else
t = arena.freshType(builtinTypes, ftp->scope); t = FFlag::LuauFreeTypesMustHaveBounds ? arena.freshType(builtinTypes, ftp->scope) : arena.freshType_DEPRECATED(ftp->scope);
} }
newPack.head.push_back(t); newPack.head.push_back(t);
@ -434,6 +432,7 @@ 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)
@ -551,8 +550,10 @@ 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::LuauSolverV2); LUAU_ASSERT(FFlag::LuauTrackInteriorFreeTypesOnScope);
else
LUAU_ASSERT(FFlag::LuauSolverV2 && FFlag::LuauTrackInteriorFreeTypesOnScope);
for (; scope; scope = scope->parent.get()) for (; scope; scope = scope->parent.get())
{ {
if (scope->interiorFreeTypes) if (scope->interiorFreeTypes)
@ -567,24 +568,4 @@ void trackInteriorFreeType(Scope* scope, TypeId ty)
LUAU_ASSERT(!"No scopes in parent chain had a present `interiorFreeTypes` member."); LUAU_ASSERT(!"No scopes in parent chain had a present `interiorFreeTypes` member.");
} }
void trackInteriorFreeTypePack(Scope* scope, TypePackId tp)
{
LUAU_ASSERT(tp);
if (!FFlag::LuauNonReentrantGeneralization2)
return;
for (; scope; scope = scope->parent.get())
{
if (scope->interiorFreeTypePacks)
{
scope->interiorFreeTypePacks->push_back(tp);
return;
}
}
// There should at least be *one* generalization constraint per module
// where `interiorFreeTypes` is present, which would be the one made
// by ConstraintGenerator::visitModuleRoot.
LUAU_ASSERT(!"No scopes in parent chain had a present `interiorFreeTypePacks` member.");
}
} // namespace Luau } // namespace Luau

View file

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

View file

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

View file

@ -87,8 +87,8 @@ struct AstLocal
template<typename T> template<typename T>
struct AstArray struct AstArray
{ {
T* data = nullptr; T* data;
size_t size = 0; size_t size;
const T* begin() const const T* begin() const
{ {
@ -194,7 +194,6 @@ public:
{ {
Checked, Checked,
Native, Native,
Deprecated,
}; };
AstAttr(const Location& location, Type type); AstAttr(const Location& location, Type type);
@ -446,25 +445,7 @@ public:
AstStatBlock* body, AstStatBlock* body,
size_t functionDepth, size_t functionDepth,
const AstName& debugname, const AstName& debugname,
AstTypePack* returnAnnotation, const std::optional<AstTypeList>& 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
); );
@ -472,16 +453,13 @@ public:
void visit(AstVisitor* visitor) override; void visit(AstVisitor* visitor) override;
bool hasNativeAttribute() const; bool hasNativeAttribute() const;
bool hasAttribute(AstAttr::Type attributeType) const;
AstArray<AstAttr*> attributes; AstArray<AstAttr*> attributes;
AstArray<AstGenericType*> generics; AstArray<AstGenericType*> generics;
AstArray<AstGenericTypePack*> genericPacks; AstArray<AstGenericTypePack*> genericPacks;
AstLocal* self; AstLocal* self;
AstArray<AstLocal*> args; AstArray<AstLocal*> args;
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst std::optional<AstTypeList> returnAnnotation;
std::optional<AstTypeList> returnAnnotation_DEPRECATED;
AstTypePack* returnAnnotation = nullptr;
bool vararg = false; bool vararg = false;
Location varargLocation; Location varargLocation;
AstTypePack* varargAnnotation; AstTypePack* varargAnnotation;
@ -912,22 +890,14 @@ class AstStatTypeFunction : public AstStat
public: public:
LUAU_RTTI(AstStatTypeFunction); LUAU_RTTI(AstStatTypeFunction);
AstStatTypeFunction( AstStatTypeFunction(const Location& location, const AstName& name, const Location& nameLocation, AstExprFunction* body, bool exported);
const Location& location,
const AstName& name,
const Location& nameLocation,
AstExprFunction* body,
bool exported,
bool hasErrors
);
void visit(AstVisitor* visitor) override; void visit(AstVisitor* visitor) override;
AstName name; AstName name;
Location nameLocation; Location nameLocation;
AstExprFunction* body = nullptr; AstExprFunction* body;
bool exported = false; bool exported;
bool hasErrors = false;
}; };
class AstStatDeclareGlobal : public AstStat class AstStatDeclareGlobal : public AstStat
@ -949,36 +919,6 @@ 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,
@ -992,7 +932,6 @@ 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,
@ -1011,7 +950,6 @@ public:
void visit(AstVisitor* visitor) override; void visit(AstVisitor* visitor) override;
bool isCheckedFunction() const; bool isCheckedFunction() const;
bool hasAttribute(AstAttr::Type attributeType) const;
AstArray<AstAttr*> attributes; AstArray<AstAttr*> attributes;
AstName name; AstName name;
@ -1022,12 +960,10 @@ public:
AstArray<AstArgumentName> paramNames; AstArray<AstArgumentName> paramNames;
bool vararg = false; bool vararg = false;
Location varargLocation; Location varargLocation;
AstTypePack* retTypes; AstTypeList retTypes;
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstTypeList retTypes_DEPRECATED;
}; };
struct AstDeclaredExternTypeProperty struct AstDeclaredClassProp
{ {
AstName name; AstName name;
Location nameLocation; Location nameLocation;
@ -1053,16 +989,16 @@ struct AstTableIndexer
std::optional<Location> accessLocation; std::optional<Location> accessLocation;
}; };
class AstStatDeclareExternType : public AstStat class AstStatDeclareClass : public AstStat
{ {
public: public:
LUAU_RTTI(AstStatDeclareExternType) LUAU_RTTI(AstStatDeclareClass)
AstStatDeclareExternType( AstStatDeclareClass(
const Location& location, const Location& location,
const AstName& name, const AstName& name,
std::optional<AstName> superName, std::optional<AstName> superName,
const AstArray<AstDeclaredExternTypeProperty>& props, const AstArray<AstDeclaredClassProp>& props,
AstTableIndexer* indexer = nullptr AstTableIndexer* indexer = nullptr
); );
@ -1071,7 +1007,7 @@ public:
AstName name; AstName name;
std::optional<AstName> superName; std::optional<AstName> superName;
AstArray<AstDeclaredExternTypeProperty> props; AstArray<AstDeclaredClassProp> props;
AstTableIndexer* indexer; AstTableIndexer* indexer;
}; };
@ -1148,26 +1084,6 @@ 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,
@ -1177,7 +1093,6 @@ 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,
@ -1191,16 +1106,13 @@ public:
void visit(AstVisitor* visitor) override; void visit(AstVisitor* visitor) override;
bool isCheckedFunction() const; bool isCheckedFunction() const;
bool hasAttribute(AstAttr::Type attributeType) const;
AstArray<AstAttr*> attributes; AstArray<AstAttr*> attributes;
AstArray<AstGenericType*> generics; AstArray<AstGenericType*> generics;
AstArray<AstGenericTypePack*> genericPacks; AstArray<AstGenericTypePack*> genericPacks;
AstTypeList argTypes; AstTypeList argTypes;
AstArray<std::optional<AstArgumentName>> argNames; AstArray<std::optional<AstArgumentName>> argNames;
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst AstTypeList returnTypes;
AstTypeList returnTypes_DEPRECATED;
AstTypePack* returnTypes;
}; };
class AstTypeTypeof : public AstType class AstTypeTypeof : public AstType
@ -1547,10 +1459,6 @@ public:
{ {
return visit(static_cast<AstStat*>(node)); return visit(static_cast<AstStat*>(node));
} }
virtual bool visit(class AstStatTypeFunction* node)
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatDeclareFunction* node) virtual bool visit(class AstStatDeclareFunction* node)
{ {
return visit(static_cast<AstStat*>(node)); return visit(static_cast<AstStat*>(node));
@ -1559,7 +1467,7 @@ public:
{ {
return visit(static_cast<AstStat*>(node)); return visit(static_cast<AstStat*>(node));
} }
virtual bool visit(class AstStatDeclareExternType* node) virtual bool visit(class AstStatDeclareClass* node)
{ {
return visit(static_cast<AstStat*>(node)); return visit(static_cast<AstStat*>(node));
} }

View file

@ -112,12 +112,11 @@ public:
CstExprFunction(); CstExprFunction();
Position functionKeywordPosition{0, 0}; Position openGenericsPosition{0,0};
Position openGenericsPosition{0, 0};
AstArray<Position> genericsCommaPositions; AstArray<Position> genericsCommaPositions;
Position closeGenericsPosition{0, 0}; Position closeGenericsPosition{0,0};
AstArray<Position> argsCommaPositions; AstArray<Position> argsCommaPositions;
Position returnSpecifierPosition{0, 0}; Position returnSpecifierPosition{0,0};
}; };
class CstExprTable : public CstNode class CstExprTable : public CstNode
@ -275,24 +274,13 @@ public:
Position opPosition; Position opPosition;
}; };
class CstStatFunction : public CstNode
{
public:
LUAU_CST_RTTI(CstStatFunction)
explicit CstStatFunction(Position functionKeywordPosition);
Position functionKeywordPosition;
};
class CstStatLocalFunction : public CstNode class CstStatLocalFunction : public CstNode
{ {
public: public:
LUAU_CST_RTTI(CstStatLocalFunction) LUAU_CST_RTTI(CstStatLocalFunction)
explicit CstStatLocalFunction(Position localKeywordPosition, Position functionKeywordPosition); explicit CstStatLocalFunction(Position functionKeywordPosition);
Position localKeywordPosition;
Position functionKeywordPosition; Position functionKeywordPosition;
}; };
@ -388,7 +376,6 @@ public:
std::optional<Position> separatorPosition; std::optional<Position> separatorPosition;
CstExprConstantString* stringInfo = nullptr; // only if Kind == StringProperty CstExprConstantString* stringInfo = nullptr; // only if Kind == StringProperty
Position stringPosition{0, 0}; // only if Kind == StringProperty
}; };
CstTypeTable(AstArray<Item> items, bool isArray); CstTypeTable(AstArray<Item> items, bool isArray);
@ -434,28 +421,6 @@ public:
Position closePosition; Position closePosition;
}; };
class CstTypeUnion : public CstNode
{
public:
LUAU_CST_RTTI(CstTypeUnion)
CstTypeUnion(std::optional<Position> leadingPosition, AstArray<Position> separatorPositions);
std::optional<Position> leadingPosition;
AstArray<Position> separatorPositions;
};
class CstTypeIntersection : public CstNode
{
public:
LUAU_CST_RTTI(CstTypeIntersection)
explicit CstTypeIntersection(std::optional<Position> leadingPosition, AstArray<Position> separatorPositions);
std::optional<Position> leadingPosition;
AstArray<Position> separatorPositions;
};
class CstTypeSingletonString : public CstNode class CstTypeSingletonString : public CstNode
{ {
public: public:
@ -473,10 +438,8 @@ class CstTypePackExplicit : public CstNode
public: public:
LUAU_CST_RTTI(CstTypePackExplicit) LUAU_CST_RTTI(CstTypePackExplicit)
explicit CstTypePackExplicit(); CstTypePackExplicit(Position openParenthesesPosition, Position closeParenthesesPosition, AstArray<Position> commaPositions);
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

@ -125,7 +125,7 @@ private:
AstStat* parseFor(); AstStat* parseFor();
// funcname ::= Name {`.' Name} [`:' Name] // funcname ::= Name {`.' Name} [`:' Name]
AstExpr* parseFunctionName(bool& hasself, AstName& debugname); AstExpr* parseFunctionName(Location start_DEPRECATED, bool& hasself, AstName& debugname);
// function funcname funcbody // function funcname funcbody
LUAU_FORCEINLINE AstStat* parseFunctionStat(const AstArray<AstAttr*>& attributes = {nullptr, 0}); LUAU_FORCEINLINE AstStat* parseFunctionStat(const AstArray<AstAttr*>& attributes = {nullptr, 0});
@ -157,9 +157,7 @@ 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);
AstDeclaredExternTypeProperty parseDeclaredExternTypeMethod(const AstArray<AstAttr*>& attributes); AstDeclaredClassProp parseDeclaredClassMethod();
AstDeclaredExternTypeProperty parseDeclaredExternTypeMethod_DEPRECATED();
// `declare global' Name: Type | // `declare global' Name: Type |
// `declare function' Name`(' [parlist] `)' [`:` Type] // `declare function' Name`(' [parlist] `)' [`:` Type]
@ -182,14 +180,6 @@ 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);
@ -227,12 +217,8 @@ private:
TempVector<std::optional<Position>>* nameColonPositions = nullptr TempVector<std::optional<Position>>* nameColonPositions = nullptr
); );
AstTypePack* parseOptionalReturnType(Position* returnSpecifierPosition = nullptr); std::optional<AstTypeList> parseOptionalReturnType(Position* returnSpecifierPosition = nullptr);
AstTypePack* parseReturnType(); std::pair<Location, AstTypeList> parseReturnType();
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
std::optional<AstTypeList> parseOptionalReturnType_DEPRECATED(Position* returnSpecifierPosition = nullptr);
std::pair<Location, AstTypeList> parseReturnType_DEPRECATED();
struct TableIndexerResult struct TableIndexerResult
{ {
@ -242,9 +228,9 @@ private:
Position colonPosition; Position colonPosition;
}; };
TableIndexerResult parseTableIndexer(AstTableAccess access, std::optional<Location> accessLocation, Lexeme begin); TableIndexerResult parseTableIndexer(AstTableAccess access, std::optional<Location> accessLocation);
// Remove with FFlagLuauStoreCSTData2 // Remove with FFlagLuauStoreCSTData
AstTableIndexer* parseTableIndexer_DEPRECATED(AstTableAccess access, std::optional<Location> accessLocation, Lexeme begin); AstTableIndexer* parseTableIndexer_DEPRECATED(AstTableAccess access, std::optional<Location> accessLocation);
AstTypeOrPack parseFunctionType(bool allowPack, const AstArray<AstAttr*>& attributes); AstTypeOrPack parseFunctionType(bool allowPack, const AstArray<AstAttr*>& attributes);
AstType* parseFunctionTypeTail( AstType* parseFunctionTypeTail(
@ -503,7 +489,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<AstDeclaredExternTypeProperty> scratchDeclaredClassProps; std::vector<AstDeclaredClassProp> 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

@ -3,25 +3,9 @@
#include "Luau/Common.h" #include "Luau/Common.h"
LUAU_FASTFLAG(LuauDeprecatedAttribute);
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
namespace Luau namespace Luau
{ {
static bool hasAttributeInArray(const AstArray<AstAttr*> attributes, AstAttr::Type attributeType)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
for (const auto attribute : attributes)
{
if (attribute->type == attributeType)
return true;
}
return false;
}
static void visitTypeList(AstVisitor* visitor, const AstTypeList& list) static void visitTypeList(AstVisitor* visitor, const AstTypeList& list)
{ {
for (AstType* ty : list.types) for (AstType* ty : list.types)
@ -242,7 +226,7 @@ AstExprFunction::AstExprFunction(
AstStatBlock* body, AstStatBlock* body,
size_t functionDepth, size_t functionDepth,
const AstName& debugname, const AstName& debugname,
AstTypePack* returnAnnotation, const std::optional<AstTypeList>& returnAnnotation,
AstTypePack* varargAnnotation, AstTypePack* varargAnnotation,
const std::optional<Location>& argLocation const std::optional<Location>& argLocation
) )
@ -261,41 +245,6 @@ 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)
@ -311,16 +260,8 @@ void AstExprFunction::visit(AstVisitor* visitor)
if (varargAnnotation) if (varargAnnotation)
varargAnnotation->visit(visitor); varargAnnotation->visit(visitor);
if (FFlag::LuauStoreReturnTypesAsPackOnAst)
{
if (returnAnnotation) if (returnAnnotation)
returnAnnotation->visit(visitor); visitTypeList(visitor, *returnAnnotation);
}
else
{
if (returnAnnotation_DEPRECATED)
visitTypeList(visitor, *returnAnnotation_DEPRECATED);
}
body->visit(visitor); body->visit(visitor);
} }
@ -336,13 +277,6 @@ bool AstExprFunction::hasNativeAttribute() const
return false; return false;
} }
bool AstExprFunction::hasAttribute(const AstAttr::Type attributeType) const
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
return hasAttributeInArray(attributes, attributeType);
}
AstExprTable::AstExprTable(const Location& location, const AstArray<Item>& items) AstExprTable::AstExprTable(const Location& location, const AstArray<Item>& items)
: AstExpr(ClassIndex(), location) : AstExpr(ClassIndex(), location)
, items(items) , items(items)
@ -857,15 +791,13 @@ AstStatTypeFunction::AstStatTypeFunction(
const AstName& name, const AstName& name,
const Location& nameLocation, const Location& nameLocation,
AstExprFunction* body, AstExprFunction* body,
bool exported, bool exported
bool hasErrors
) )
: AstStat(ClassIndex(), location) : AstStat(ClassIndex(), location)
, name(name) , name(name)
, nameLocation(nameLocation) , nameLocation(nameLocation)
, body(body) , body(body)
, exported(exported) , exported(exported)
, hasErrors(hasErrors)
{ {
} }
@ -889,62 +821,6 @@ void AstStatDeclareGlobal::visit(AstVisitor* visitor)
type->visit(visitor); type->visit(visitor);
} }
AstStatDeclareFunction::AstStatDeclareFunction(
const Location& location,
const AstName& name,
const Location& nameLocation,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& params,
const AstArray<AstArgumentName>& paramNames,
bool vararg,
const Location& varargLocation,
AstTypePack* retTypes
)
: AstStat(ClassIndex(), location)
, attributes()
, name(name)
, nameLocation(nameLocation)
, generics(generics)
, genericPacks(genericPacks)
, params(params)
, paramNames(paramNames)
, vararg(vararg)
, varargLocation(varargLocation)
, retTypes(retTypes)
{
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
}
AstStatDeclareFunction::AstStatDeclareFunction(
const Location& location,
const AstArray<AstAttr*>& attributes,
const AstName& name,
const Location& nameLocation,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& params,
const AstArray<AstArgumentName>& paramNames,
bool vararg,
const Location& varargLocation,
AstTypePack* retTypes
)
: AstStat(ClassIndex(), location)
, attributes(attributes)
, name(name)
, nameLocation(nameLocation)
, generics(generics)
, genericPacks(genericPacks)
, params(params)
, paramNames(paramNames)
, vararg(vararg)
, varargLocation(varargLocation)
, retTypes(retTypes)
{
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
}
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstStatDeclareFunction::AstStatDeclareFunction( AstStatDeclareFunction::AstStatDeclareFunction(
const Location& location, const Location& location,
const AstName& name, const AstName& name,
@ -967,12 +843,10 @@ AstStatDeclareFunction::AstStatDeclareFunction(
, paramNames(paramNames) , paramNames(paramNames)
, vararg(vararg) , vararg(vararg)
, varargLocation(varargLocation) , varargLocation(varargLocation)
, retTypes_DEPRECATED(retTypes) , retTypes(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,
@ -996,9 +870,8 @@ AstStatDeclareFunction::AstStatDeclareFunction(
, paramNames(paramNames) , paramNames(paramNames)
, vararg(vararg) , vararg(vararg)
, varargLocation(varargLocation) , varargLocation(varargLocation)
, retTypes_DEPRECATED(retTypes) , retTypes(retTypes)
{ {
LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst);
} }
void AstStatDeclareFunction::visit(AstVisitor* visitor) void AstStatDeclareFunction::visit(AstVisitor* visitor)
@ -1006,10 +879,7 @@ void AstStatDeclareFunction::visit(AstVisitor* visitor)
if (visitor->visit(this)) if (visitor->visit(this))
{ {
visitTypeList(visitor, params); visitTypeList(visitor, params);
if (FFlag::LuauStoreReturnTypesAsPackOnAst) visitTypeList(visitor, retTypes);
retTypes->visit(visitor);
else
visitTypeList(visitor, retTypes_DEPRECATED);
} }
} }
@ -1024,18 +894,11 @@ bool AstStatDeclareFunction::isCheckedFunction() const
return false; return false;
} }
bool AstStatDeclareFunction::hasAttribute(AstAttr::Type attributeType) const AstStatDeclareClass::AstStatDeclareClass(
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
return hasAttributeInArray(attributes, attributeType);
}
AstStatDeclareExternType::AstStatDeclareExternType(
const Location& location, const Location& location,
const AstName& name, const AstName& name,
std::optional<AstName> superName, std::optional<AstName> superName,
const AstArray<AstDeclaredExternTypeProperty>& props, const AstArray<AstDeclaredClassProp>& props,
AstTableIndexer* indexer AstTableIndexer* indexer
) )
: AstStat(ClassIndex(), location) : AstStat(ClassIndex(), location)
@ -1046,11 +909,11 @@ AstStatDeclareExternType::AstStatDeclareExternType(
{ {
} }
void AstStatDeclareExternType::visit(AstVisitor* visitor) void AstStatDeclareClass::visit(AstVisitor* visitor)
{ {
if (visitor->visit(this)) if (visitor->visit(this))
{ {
for (const AstDeclaredExternTypeProperty& prop : props) for (const AstDeclaredClassProp& prop : props)
prop.ty->visit(visitor); prop.ty->visit(visitor);
} }
} }
@ -1135,48 +998,6 @@ void AstTypeTable::visit(AstVisitor* visitor)
} }
} }
AstTypeFunction::AstTypeFunction(
const Location& location,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& argTypes,
const AstArray<std::optional<AstArgumentName>>& argNames,
AstTypePack* returnTypes
)
: AstType(ClassIndex(), location)
, attributes()
, generics(generics)
, genericPacks(genericPacks)
, argTypes(argTypes)
, argNames(argNames)
, returnTypes(returnTypes)
{
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
LUAU_ASSERT(argNames.size == 0 || argNames.size == argTypes.types.size);
}
AstTypeFunction::AstTypeFunction(
const Location& location,
const AstArray<AstAttr*>& attributes,
const AstArray<AstGenericType*>& generics,
const AstArray<AstGenericTypePack*>& genericPacks,
const AstTypeList& argTypes,
const AstArray<std::optional<AstArgumentName>>& argNames,
AstTypePack* returnTypes
)
: AstType(ClassIndex(), location)
, attributes(attributes)
, generics(generics)
, genericPacks(genericPacks)
, argTypes(argTypes)
, argNames(argNames)
, returnTypes(returnTypes)
{
LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst);
LUAU_ASSERT(argNames.size == 0 || argNames.size == argTypes.types.size);
}
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstTypeFunction::AstTypeFunction( AstTypeFunction::AstTypeFunction(
const Location& location, const Location& location,
const AstArray<AstGenericType*>& generics, const AstArray<AstGenericType*>& generics,
@ -1191,13 +1012,11 @@ AstTypeFunction::AstTypeFunction(
, genericPacks(genericPacks) , genericPacks(genericPacks)
, argTypes(argTypes) , argTypes(argTypes)
, argNames(argNames) , argNames(argNames)
, returnTypes_DEPRECATED(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);
} }
// Clip with FFlagLuauStoreReturnTypesAsPackOnAst
AstTypeFunction::AstTypeFunction( AstTypeFunction::AstTypeFunction(
const Location& location, const Location& location,
const AstArray<AstAttr*>& attributes, const AstArray<AstAttr*>& attributes,
@ -1213,9 +1032,8 @@ AstTypeFunction::AstTypeFunction(
, genericPacks(genericPacks) , genericPacks(genericPacks)
, argTypes(argTypes) , argTypes(argTypes)
, argNames(argNames) , argNames(argNames)
, returnTypes_DEPRECATED(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);
} }
@ -1224,10 +1042,7 @@ void AstTypeFunction::visit(AstVisitor* visitor)
if (visitor->visit(this)) if (visitor->visit(this))
{ {
visitTypeList(visitor, argTypes); visitTypeList(visitor, argTypes);
if (FFlag::LuauStoreReturnTypesAsPackOnAst) visitTypeList(visitor, returnTypes);
returnTypes->visit(visitor);
else
visitTypeList(visitor, returnTypes_DEPRECATED);
} }
} }
@ -1242,13 +1057,6 @@ bool AstTypeFunction::isCheckedFunction() const
return false; return false;
} }
bool AstTypeFunction::hasAttribute(AstAttr::Type attributeType) const
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
return hasAttributeInArray(attributes, attributeType);
}
AstTypeTypeof::AstTypeTypeof(const Location& location, AstExpr* expr) AstTypeTypeof::AstTypeTypeof(const Location& location, AstExpr* expr)
: AstType(ClassIndex(), location) : AstType(ClassIndex(), location)
, expr(expr) , expr(expr)

View file

@ -38,8 +38,7 @@ CstExprIndexExpr::CstExprIndexExpr(Position openBracketPosition, Position closeB
{ {
} }
CstExprFunction::CstExprFunction() CstExprFunction::CstExprFunction() : CstNode(CstClassIndex())
: CstNode(CstClassIndex())
{ {
} }
@ -130,19 +129,12 @@ CstStatCompoundAssign::CstStatCompoundAssign(Position opPosition)
{ {
} }
CstStatFunction::CstStatFunction(Position functionKeywordPosition) CstStatLocalFunction::CstStatLocalFunction(Position functionKeywordPosition)
: CstNode(CstClassIndex()) : CstNode(CstClassIndex())
, functionKeywordPosition(functionKeywordPosition) , functionKeywordPosition(functionKeywordPosition)
{ {
} }
CstStatLocalFunction::CstStatLocalFunction(Position localKeywordPosition, Position functionKeywordPosition)
: CstNode(CstClassIndex())
, localKeywordPosition(localKeywordPosition)
, functionKeywordPosition(functionKeywordPosition)
{
}
CstGenericType::CstGenericType(std::optional<Position> defaultEqualsPosition) CstGenericType::CstGenericType(std::optional<Position> defaultEqualsPosition)
: CstNode(CstClassIndex()) : CstNode(CstClassIndex())
, defaultEqualsPosition(defaultEqualsPosition) , defaultEqualsPosition(defaultEqualsPosition)
@ -229,20 +221,6 @@ CstTypeTypeof::CstTypeTypeof(Position openPosition, Position closePosition)
{ {
} }
CstTypeUnion::CstTypeUnion(std::optional<Position> leadingPosition, AstArray<Position> separatorPositions)
: CstNode(CstClassIndex())
, leadingPosition(leadingPosition)
, separatorPositions(separatorPositions)
{
}
CstTypeIntersection::CstTypeIntersection(std::optional<Position> leadingPosition, AstArray<Position> separatorPositions)
: CstNode(CstClassIndex())
, leadingPosition(leadingPosition)
, separatorPositions(separatorPositions)
{
}
CstTypeSingletonString::CstTypeSingletonString(AstArray<char> sourceString, CstExprConstantString::QuoteStyle quoteStyle, unsigned int blockDepth) CstTypeSingletonString::CstTypeSingletonString(AstArray<char> sourceString, CstExprConstantString::QuoteStyle quoteStyle, unsigned int blockDepth)
: CstNode(CstClassIndex()) : CstNode(CstClassIndex())
, sourceString(sourceString) , sourceString(sourceString)
@ -252,18 +230,8 @@ 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)

File diff suppressed because it is too large Load diff

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