Compare commits

..

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

109 changed files with 4278 additions and 9166 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

@ -70,7 +70,6 @@ Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol
void assignPropDocumentationSymbols(TableType::Props& props, const std::string& baseName);
std::string getBuiltinDefinitionSource();
std::string getTypeFunctionDefinitionSource();
void addGlobalBinding(GlobalTypes& globals, const std::string& name, TypeId ty, const std::string& packageName);
void addGlobalBinding(GlobalTypes& globals, const std::string& name, Binding binding);

View file

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

View file

@ -117,23 +117,18 @@ struct ConstraintGenerator
// Needed to register all available type functions for execution at later stages.
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
DenseHashMap<const AstStatTypeFunction*, ScopePtr> astTypeFunctionEnvironmentScopes{nullptr};
// Needed to resolve modules to make 'require' import types properly.
NotNull<ModuleResolver> moduleResolver;
// Occasionally constraint generation needs to produce an ICE.
const NotNull<InternalErrorReporter> ice;
ScopePtr globalScope;
ScopePtr typeFunctionScope;
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope;
std::vector<RequireCycle> requireCycles;
DenseHashMap<TypeId, TypeIds> localTypes{nullptr};
DenseHashMap<AstExpr*, Inference> inferredExprCache{nullptr};
DcrLogger* logger;
ConstraintGenerator(
@ -145,7 +140,6 @@ struct ConstraintGenerator
NotNull<BuiltinTypes> builtinTypes,
NotNull<InternalErrorReporter> ice,
const ScopePtr& globalScope,
const ScopePtr& typeFunctionScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
DcrLogger* logger,
NotNull<DataFlowGraph> dfg,

View file

@ -365,7 +365,7 @@ public:
* @returns a non-free type that generalizes the argument, or `std::nullopt` if one
* 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

View file

@ -38,6 +38,8 @@ struct DataFlowGraph
DefId getDef(const AstExpr* expr) const;
// Look up the definition optionally, knowing it may not be present.
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;
@ -64,6 +66,10 @@ private:
// All keys in this maps are really only statements that ambiently declares a symbol.
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};
friend struct DataFlowGraphBuilder;
};

View file

@ -49,8 +49,6 @@ struct FragmentAutocompleteAncestryResult
std::vector<AstLocal*> localStack;
std::vector<AstNode*> ancestry;
AstStat* nearestStatement = nullptr;
AstStatBlock* parentBlock = nullptr;
Location fragmentSelectionRegion;
};
struct FragmentParseResult
@ -61,7 +59,6 @@ struct FragmentParseResult
AstStat* nearestStatement = nullptr;
std::vector<Comment> commentLocations;
std::unique_ptr<Allocator> alloc = std::make_unique<Allocator>();
Position scopePos{0, 0};
};
struct FragmentTypeCheckResult
@ -79,29 +76,10 @@ struct FragmentAutocompleteResult
AutocompleteResult acResults;
};
struct FragmentRegion
{
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
);
FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* root, const Position& cursorPos);
std::optional<FragmentParseResult> parseFragment(
AstStatBlock* stale,
AstStatBlock* mostRecentParse,
AstStatBlock* root,
AstNameTable* names,
std::string_view src,
const Position& cursorPos,
@ -115,7 +93,6 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
std::optional<FrontendOptions> opts,
std::string_view src,
std::optional<Position> fragmentEndPosition,
AstStatBlock* recentParse = nullptr,
IFragmentAutocompleteReporter* reporter = nullptr
);
@ -127,7 +104,6 @@ FragmentAutocompleteResult fragmentAutocomplete(
std::optional<FrontendOptions> opts,
StringCompletionCallback callback,
std::optional<Position> fragmentEndPosition = std::nullopt,
AstStatBlock* recentParse = nullptr,
IFragmentAutocompleteReporter* reporter = nullptr
);

View file

@ -10,6 +10,7 @@
#include "Luau/Set.h"
#include "Luau/TypeCheckLimits.h"
#include "Luau/Variant.h"
#include "Luau/AnyTypeSummary.h"
#include <mutex>
#include <string>
@ -33,6 +34,7 @@ struct HotComment;
struct BuildQueueItem;
struct BuildQueueWorkState;
struct FrontendCancellationToken;
struct AnyTypeSummary;
struct LoadDefinitionFileResult
{
@ -215,6 +217,11 @@ struct Frontend
std::function<void(std::function<void()> task)> executeTask = {},
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::vector<ModuleName> getRequiredScripts(const ModuleName& name);
@ -298,7 +305,6 @@ ModulePtr check(
NotNull<ModuleResolver> moduleResolver,
NotNull<FileResolver> fileResolver,
const ScopePtr& globalScope,
const ScopePtr& typeFunctionScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
FrontendOptions options,
TypeCheckLimits limits
@ -313,7 +319,6 @@ ModulePtr check(
NotNull<ModuleResolver> moduleResolver,
NotNull<FileResolver> fileResolver,
const ScopePtr& globalScope,
const ScopePtr& typeFunctionScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
FrontendOptions options,
TypeCheckLimits limits,

View file

@ -12,8 +12,8 @@ std::optional<TypeId> generalize(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
NotNull<DenseHashSet<TypeId>> cachedTypes,
TypeId ty
NotNull<DenseHashSet<TypeId>> bakedTypes,
TypeId ty,
/* avoid sealing tables*/ bool avoidSealingTables = false
);
}

View file

@ -19,9 +19,7 @@ struct GlobalTypes
TypeArena globalTypes;
SourceModule globalNames; // names for symbols entered into globalScope
ScopePtr globalScope; // shared by all modules
ScopePtr globalTypeFunctionScope; // shared by all modules
};
} // namespace Luau

View file

@ -8,6 +8,7 @@
#include "Luau/ParseResult.h"
#include "Luau/Scope.h"
#include "Luau/TypeArena.h"
#include "Luau/AnyTypeSummary.h"
#include "Luau/DataFlowGraph.h"
#include <memory>
@ -15,16 +16,19 @@
#include <unordered_map>
#include <optional>
LUAU_FASTFLAG(LuauIncrementalAutocompleteCommentDetection)
namespace Luau
{
using LogLuauProc = void (*)(std::string_view, std::string_view);
using LogLuauProc = void (*)(std::string_view);
extern LogLuauProc logLuau;
void setLogLuau(LogLuauProc ll);
void resetLogLuauProc();
struct Module;
struct AnyTypeSummary;
using ScopePtr = std::shared_ptr<struct Scope>;
using ModulePtr = std::shared_ptr<Module>;
@ -82,6 +86,10 @@ struct Module
TypeArena interfaceTypes;
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
std::shared_ptr<Allocator> allocator;
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
#pragma once
#include "Luau/DataFlowGraph.h"
#include "Luau/EqSatSimplification.h"
#include "Luau/Module.h"
#include "Luau/NotNull.h"
#include "Luau/DataFlowGraph.h"
namespace Luau
{

View file

@ -35,7 +35,7 @@ struct Scope
explicit Scope(TypePackId returnType); // root scope
explicit Scope(const ScopePtr& parent, int subLevel = 0); // child scope. Parent must not be nullptr.
ScopePtr parent; // null for the root
const ScopePtr parent; // null for the root
// All the children of this scope.
std::vector<NotNull<Scope>> children;
@ -59,8 +59,6 @@ struct Scope
std::optional<TypeId> lookup(Symbol sym) const;
std::optional<TypeId> lookupUnrefinedType(DefId def) const;
std::optional<TypeId> lookupRValueRefinementType(DefId def) const;
std::optional<TypeId> lookup(DefId def) const;
std::optional<std::pair<TypeId, Scope*>> lookupEx(DefId def);
std::optional<std::pair<Binding*, Scope*>> lookupEx(Symbol sym);
@ -73,7 +71,6 @@ struct Scope
// WARNING: This function linearly scans for a string key of equal value! It is thus O(n**2)
std::optional<Binding> linearSearchForBinding(const std::string& name, bool traverseScopeChain = true) const;
std::optional<std::pair<Symbol, Binding>> linearSearchForBindingPair(const std::string& name, bool traverseScopeChain) const;
RefinementMap refinements;

View file

@ -192,6 +192,16 @@ struct TxnLog
// The pointer returned lives until `commit` or `clear` is called.
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
// indexer.
//

View file

@ -19,6 +19,7 @@
#include <optional>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
LUAU_FASTINT(LuauTableTypeMaximumStringifierLength)
@ -37,15 +38,6 @@ struct Constraint;
struct Subtyping;
struct TypeChecker2;
enum struct Polarity : uint8_t
{
None = 0b000,
Positive = 0b001,
Negative = 0b010,
Mixed = 0b011,
Unknown = 0b100,
};
/**
* There are three kinds of type variables:
* - `Free` variables are metavariables, which stand for unconstrained types.
@ -356,8 +348,10 @@ struct FunctionType
);
// Local monomorphic function
FunctionType(TypeLevel level, TypePackId argTypes, TypePackId retTypes, std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
FunctionType(
TypeLevel level,
Scope* scope,
TypePackId argTypes,
TypePackId retTypes,
std::optional<FunctionDefinition> defn = {},
@ -374,6 +368,16 @@ struct FunctionType
std::optional<FunctionDefinition> defn = {},
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;
/// These should all be generic
@ -382,6 +386,7 @@ struct FunctionType
std::vector<std::optional<FunctionArgument>> argNames;
Tags tags;
TypeLevel level;
Scope* scope = nullptr;
TypePackId argTypes;
TypePackId retTypes;
std::shared_ptr<MagicFunction> magic = nullptr;
@ -391,7 +396,6 @@ struct FunctionType
// this flag is used as an optimization to exit early from procedures that manipulate free or generic types.
bool hasNoFreeOrGenericTypes = false;
bool isCheckedFunction = false;
bool isDeprecatedFunction = false;
};
enum class TableState
@ -468,9 +472,7 @@ struct Property
TypeId type() const;
void setType(TypeId ty);
// If this property has a present `writeTy`, set it equal to the `readTy`.
// This is to ensure that if we normalize a property that has divergent
// read and write types, we make them converge (for now).
// Sets the write type of this property to the read type.
void makeShared();
bool isShared() const;
@ -515,6 +517,9 @@ struct TableType
std::optional<TypeId> boundTo;
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.
// Some constraints will use this information to decide whether or not they
// are able to dispatch.
@ -617,6 +622,7 @@ struct UserDefinedFunctionData
AstStatTypeFunction* definition = nullptr;
DenseHashMap<Name, std::pair<AstStatTypeFunction*, size_t>> environment{""};
DenseHashMap<Name, AstStatTypeFunction*> environment_DEPRECATED{""};
};
/**
@ -876,9 +882,6 @@ struct TypeFun
*/
TypeId type;
// The location of where this TypeFun was defined, if available
std::optional<Location> definitionLocation;
TypeFun() = default;
explicit TypeFun(TypeId ty)
@ -886,23 +889,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))
, type(type)
, definitionLocation(definitionLocation)
{
}
TypeFun(
std::vector<GenericTypeDefinition> typeParams,
std::vector<GenericTypePackDefinition> typePackParams,
TypeId type,
std::optional<Location> definitionLocation = std::nullopt
)
TypeFun(std::vector<GenericTypeDefinition> typeParams, std::vector<GenericTypePackDefinition> typePackParams, TypeId type)
: typeParams(std::move(typeParams))
, typePackParams(std::move(typePackParams))
, type(type)
, definitionLocation(definitionLocation)
{
}

View file

@ -13,8 +13,6 @@
#include "Luau/TypeOrPack.h"
#include "Luau/TypeUtils.h"
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
namespace Luau
{
@ -40,29 +38,18 @@ struct Reasonings
std::string toString()
{
if (FFlag::LuauImproveTypePathsInErrors && reasons.empty())
return "";
// DenseHashSet ordering is entirely undefined, so we want to
// sort the reasons here to achieve a stable error
// stringification.
std::sort(reasons.begin(), reasons.end());
std::string allReasons = FFlag::LuauImproveTypePathsInErrors ? "\nthis is because " : "";
std::string allReasons;
bool first = true;
for (const std::string& reason : reasons)
{
if (FFlag::LuauImproveTypePathsInErrors)
{
if (reasons.size() > 1)
allReasons += "\n\t * ";
}
if (first)
first = false;
else
{
if (first)
first = false;
else
allReasons += "\n\t";
}
allReasons += "\n\t";
allReasons += reason;
}

View file

@ -48,9 +48,6 @@ struct TypeFunctionRuntime
// Evaluation of type functions should only be performed in the absence of parse errors in the source module
bool allowEvaluation = true;
// Root scope in which the type function operates in, set up by ConstraintGenerator
ScopePtr rootScope;
// Output created by 'print' function
std::vector<std::string> messages;
@ -177,7 +174,6 @@ struct FunctionGraphReductionResult
DenseHashSet<TypePackId> blockedPacks{nullptr};
DenseHashSet<TypeId> reducedTypes{nullptr};
DenseHashSet<TypePackId> reducedPacks{nullptr};
DenseHashSet<TypeId> irreducibleTypes{nullptr};
};
/**

View file

@ -223,6 +223,8 @@ struct TypeFunctionClassType
std::optional<TypeFunctionTypeId> writeParent;
TypeId classTy;
std::string name_DEPRECATED;
};
struct TypeFunctionGenericType

View file

@ -28,8 +28,14 @@ struct TypeFunctionRuntimeBuilderState
{
NotNull<TypeFunctionContext> ctx;
// Mapping of class name to ClassType
// Invariant: users can not create a new class types -> any class types that get deserialized must have been an argument to the type function
// Using this invariant, whenever a ClassType is serialized, we can put it into this map
// whenever a ClassType is deserialized, we can use this map to return the corresponding value
DenseHashMap<std::string, TypeId> classesSerialized_DEPRECATED{{}};
// List of errors that occur during serialization/deserialization
// At every iteration of serialization/deserialization, if this list.size() != 0, we halt the process
// At every iteration of serialization/deserialzation, if this list.size() != 0, we halt the process
std::vector<std::string> errors{};
TypeFunctionRuntimeBuilderState(NotNull<TypeFunctionContext> ctx)

View file

@ -42,19 +42,9 @@ struct Property
/// element.
struct Index
{
enum class Variant
{
Pack,
Union,
Intersection
};
/// The 0-based index to use for the lookup.
size_t index;
/// The sort of thing we're indexing from, this is used in stringifying the type path for errors.
Variant variant;
bool operator==(const Index& other) const;
};
@ -215,9 +205,6 @@ using Path = TypePath::Path;
/// terribly clear to end users of the Luau type system.
std::string toString(const TypePath::Path& path, bool prefixDot = false);
/// Converts a Path to a human readable string for error reporting.
std::string toStringHuman(const TypePath::Path& path);
std::optional<TypeOrPack> traverse(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);
std::optional<TypeOrPack> traverse(TypePackId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);

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

@ -1065,11 +1065,6 @@ struct AstJsonEncoder : public AstVisitor
);
}
void write(class AstTypeOptional* node)
{
writeNode(node, "AstTypeOptional", [&]() {});
}
void write(class AstTypeUnion* node)
{
writeNode(
@ -1151,8 +1146,6 @@ struct AstJsonEncoder : public AstVisitor
return writeString("checked");
case AstAttr::Type::Native:
return writeString("native");
case AstAttr::Type::Deprecated:
return writeString("deprecated");
}
}

View file

@ -30,7 +30,6 @@ LUAU_FASTFLAG(LuauExposeRequireByStringAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUsesModuleForTypeCompatibility)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUnionCopyPreviousSeen)
static const std::unordered_set<std::string> kStatementStartingKeywords =
{"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
@ -485,21 +484,6 @@ static void autocompleteProps(
AutocompleteEntryMap inner;
std::unordered_set<TypeId> innerSeen;
// If we don't do this, and we have the misfortune of receiving a
// recursive union like:
//
// t1 where t1 = t1 | Class
//
// Then we are on a one way journey to a stack overflow.
if (FFlag::LuauAutocompleteUnionCopyPreviousSeen)
{
for (auto ty: seen)
{
if (is<UnionType, IntersectionType>(ty))
innerSeen.insert(ty);
}
}
if (isNil(*iter))
{
++iter;

View file

@ -29,10 +29,11 @@
*/
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauStringFormatErrorSuppression)
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauFreezeIgnorePersistent)
LUAU_FASTFLAGVARIABLE(LuauFollowTableFreeze)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck)
namespace Luau
{
@ -287,22 +288,6 @@ void assignPropDocumentationSymbols(TableType::Props& props, const std::string&
}
}
static void finalizeGlobalBindings(ScopePtr scope)
{
LUAU_ASSERT(FFlag::LuauUserTypeFunTypecheck);
for (const auto& pair : scope->bindings)
{
persist(pair.second.typeId);
if (TableType* ttv = getMutable<TableType>(pair.second.typeId))
{
if (!ttv->name)
ttv->name = "typeof(" + toString(pair.first) + ")";
}
}
}
void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeCheckForAutocomplete)
{
LUAU_ASSERT(!globals.globalTypes.types.isFrozen());
@ -414,21 +399,14 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
// clang-format on
}
if (FFlag::LuauUserTypeFunTypecheck)
for (const auto& pair : globals.globalScope->bindings)
{
finalizeGlobalBindings(globals.globalScope);
}
else
{
for (const auto& pair : globals.globalScope->bindings)
{
persist(pair.second.typeId);
persist(pair.second.typeId);
if (TableType* ttv = getMutable<TableType>(pair.second.typeId))
{
if (!ttv->name)
ttv->name = "typeof(" + toString(pair.first) + ")";
}
if (TableType* ttv = getMutable<TableType>(pair.second.typeId))
{
if (!ttv->name)
ttv->name = "typeof(" + toString(pair.first) + ")";
}
}
@ -489,59 +467,6 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
TypeId requireTy = getGlobalBinding(globals, "require");
attachTag(requireTy, kRequireTagName);
attachMagicFunction(requireTy, std::make_shared<MagicRequire>());
if (FFlag::LuauUserTypeFunTypecheck)
{
// Global scope cannot be the parent of the type checking environment because it can be changed by the embedder
globals.globalTypeFunctionScope->exportedTypeBindings = globals.globalScope->exportedTypeBindings;
globals.globalTypeFunctionScope->builtinTypeNames = globals.globalScope->builtinTypeNames;
// Type function runtime also removes a few standard libraries and globals, so we will take only the ones that are defined
static const char* typeFunctionRuntimeBindings[] = {
// Libraries
"math",
"table",
"string",
"bit32",
"utf8",
"buffer",
// Globals
"assert",
"error",
"print",
"next",
"ipairs",
"pairs",
"select",
"unpack",
"getmetatable",
"setmetatable",
"rawget",
"rawset",
"rawlen",
"rawequal",
"tonumber",
"tostring",
"type",
"typeof",
};
for (auto& name : typeFunctionRuntimeBindings)
{
AstName astName = globals.globalNames.names->get(name);
LUAU_ASSERT(astName.value);
globals.globalTypeFunctionScope->bindings[astName] = globals.globalScope->bindings[astName];
}
LoadDefinitionFileResult typeFunctionLoadResult = frontend.loadDefinitionFile(
globals, globals.globalTypeFunctionScope, getTypeFunctionDefinitionSource(), "@luau", /* captureComments */ false, false
);
LUAU_ASSERT(typeFunctionLoadResult.success);
finalizeGlobalBindings(globals.globalTypeFunctionScope);
}
}
static std::vector<TypeId> parseFormatString(NotNull<BuiltinTypes> builtinTypes, const char* data, size_t size)
@ -711,8 +636,10 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
if (!result.isSubtype)
{
switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy))
if (FFlag::LuauStringFormatErrorSuppression)
{
switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy))
{
case ErrorSuppression::Suppress:
break;
case ErrorSuppression::NormalizationFailed:
@ -722,6 +649,12 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
if (!reasonings.suppressed)
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);
}
}
}
@ -1511,7 +1444,7 @@ bool MagicClone::infer(const MagicFunctionCallContext& context)
return false;
CloneState cloneState{context.solver->builtinTypes};
TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ true);
TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ FFlag::LuauFreezeIgnorePersistent);
if (auto tableType = getMutable<TableType>(resultType))
{
@ -1548,7 +1481,7 @@ static std::optional<TypeId> freezeTable(TypeId inputType, const MagicFunctionCa
{
// Clone the input type, this will become our final result type after we mutate it.
CloneState cloneState{context.solver->builtinTypes};
TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ true);
TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ FFlag::LuauFreezeIgnorePersistent);
auto tableTy = getMutable<TableType>(resultType);
// `clone` should not break this.
LUAU_ASSERT(tableTy);

View file

@ -9,12 +9,13 @@
#include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauFreezeIgnorePersistent)
// For each `Luau::clone` call, we will clone only up to N amount of types _and_ packs, as controlled by this limit.
LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000)
LUAU_FASTFLAGVARIABLE(LuauClonedTableAndFunctionTypesMustHaveScopes)
LUAU_FASTFLAGVARIABLE(LuauDoNotClonePersistentBindings)
LUAU_FASTFLAG(LuauIncrementalAutocompleteDemandBasedCloning)
namespace Luau
{
@ -133,7 +134,7 @@ protected:
ty = follow(ty, FollowOption::DisableLazyTypeThunks);
if (auto it = types->find(ty); it != types->end())
return it->second;
else if (ty->persistent && ty != forceTy)
else if (ty->persistent && (!FFlag::LuauFreezeIgnorePersistent || ty != forceTy))
return ty;
return std::nullopt;
}
@ -143,7 +144,7 @@ protected:
tp = follow(tp);
if (auto it = packs->find(tp); it != packs->end())
return it->second;
else if (tp->persistent && tp != forceTp)
else if (tp->persistent && (!FFlag::LuauFreezeIgnorePersistent || tp != forceTp))
return tp;
return std::nullopt;
}
@ -169,7 +170,7 @@ public:
if (auto clone = find(ty))
return *clone;
else if (ty->persistent && ty != forceTy)
else if (ty->persistent && (!FFlag::LuauFreezeIgnorePersistent || ty != forceTy))
return ty;
TypeId target = arena->addType(ty->ty);
@ -179,6 +180,8 @@ public:
generic->scope = nullptr;
else if (auto free = getMutable<FreeType>(target))
free->scope = nullptr;
else if (auto fn = getMutable<FunctionType>(target))
fn->scope = nullptr;
else if (auto table = getMutable<TableType>(target))
table->scope = nullptr;
@ -193,7 +196,7 @@ public:
if (auto clone = find(tp))
return *clone;
else if (tp->persistent && tp != forceTp)
else if (tp->persistent && (!FFlag::LuauFreezeIgnorePersistent || tp != forceTp))
return tp;
TypePackId target = arena->addTypePack(tp->ty);
@ -395,7 +398,7 @@ private:
ty = shallowClone(ty);
}
virtual void cloneChildren(LazyType* t)
void cloneChildren(LazyType* t)
{
if (auto unwrapped = t->unwrapped.load())
t->unwrapped.store(shallowClone(unwrapped));
@ -502,7 +505,7 @@ public:
if (auto clone = find(ty))
return *clone;
else if (ty->persistent && ty != forceTy)
else if (ty->persistent && (!FFlag::LuauFreezeIgnorePersistent || ty != forceTy))
return ty;
TypeId target = arena->addType(ty->ty);
@ -519,6 +522,11 @@ public:
if (FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes)
tt->scope = replacementForNullScope;
}
else if (auto fn = getMutable<FunctionType>(target))
{
if (FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes)
fn->scope = replacementForNullScope;
}
(*types)[ty] = target;
queue.emplace_back(target);
@ -531,7 +539,7 @@ public:
if (auto clone = find(tp))
return *clone;
else if (tp->persistent && tp != forceTp)
else if (tp->persistent && (!FFlag::LuauFreezeIgnorePersistent || tp != forceTp))
return tp;
TypePackId target = arena->addTypePack(tp->ty);
@ -545,16 +553,6 @@ public:
queue.emplace_back(target);
return target;
}
void cloneChildren(LazyType* t) override
{
// Do not clone lazy types
if (!FFlag::LuauIncrementalAutocompleteDemandBasedCloning)
{
if (auto unwrapped = t->unwrapped.load())
t->unwrapped.store(shallowClone(unwrapped));
}
}
};
@ -562,7 +560,7 @@ public:
TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState, bool ignorePersistent)
{
if (tp->persistent && !ignorePersistent)
if (tp->persistent && (!FFlag::LuauFreezeIgnorePersistent || !ignorePersistent))
return tp;
TypeCloner cloner{
@ -571,7 +569,7 @@ TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState,
NotNull{&cloneState.seenTypes},
NotNull{&cloneState.seenTypePacks},
nullptr,
ignorePersistent ? tp : nullptr
FFlag::LuauFreezeIgnorePersistent && ignorePersistent ? tp : nullptr
};
return cloner.shallowClone(tp);
@ -579,7 +577,7 @@ TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState,
TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool ignorePersistent)
{
if (typeId->persistent && !ignorePersistent)
if (typeId->persistent && (!FFlag::LuauFreezeIgnorePersistent || !ignorePersistent))
return typeId;
TypeCloner cloner{
@ -587,7 +585,7 @@ TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool
cloneState.builtinTypes,
NotNull{&cloneState.seenTypes},
NotNull{&cloneState.seenTypePacks},
ignorePersistent ? typeId : nullptr,
FFlag::LuauFreezeIgnorePersistent && ignorePersistent ? typeId : nullptr,
nullptr
};

View file

@ -33,10 +33,10 @@ LUAU_FASTINT(LuauCheckRecursionLimit)
LUAU_FASTFLAG(DebugLuauLogSolverToJson)
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAGVARIABLE(LuauPropagateExpectedTypesForCalls)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauDeferBidirectionalInferenceForTableAssignment)
LUAU_FASTFLAGVARIABLE(LuauUngeneralizedTypesForRecursiveFunctions)
LUAU_FASTFLAGVARIABLE(LuauGlobalSelfAssignmentCycle)
@ -44,12 +44,6 @@ LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauInferLocalTypesInMultipleAssignments)
LUAU_FASTFLAGVARIABLE(LuauDoNotLeakNilInRefinement)
LUAU_FASTFLAGVARIABLE(LuauExtraFollows)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauRetainDefinitionAliasLocations)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAGVARIABLE(LuauCacheInferencePerAstExpr)
LUAU_FASTFLAGVARIABLE(LuauAlwaysResolveAstTypes)
namespace Luau
{
@ -190,7 +184,6 @@ ConstraintGenerator::ConstraintGenerator(
NotNull<BuiltinTypes> builtinTypes,
NotNull<InternalErrorReporter> ice,
const ScopePtr& globalScope,
const ScopePtr& typeFunctionScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
DcrLogger* logger,
NotNull<DataFlowGraph> dfg,
@ -207,7 +200,6 @@ ConstraintGenerator::ConstraintGenerator(
, moduleResolver(moduleResolver)
, ice(ice)
, globalScope(globalScope)
, typeFunctionScope(typeFunctionScope)
, prepareModuleScope(std::move(prepareModuleScope))
, requireCycles(std::move(requireCycles))
, logger(logger)
@ -229,15 +221,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
rootScope->returnType = freshTypePack(scope);
if (FFlag::LuauUserTypeFunTypecheck)
{
// Create module-local scope for the type function environment
ScopePtr localTypeFunctionScope = std::make_shared<Scope>(typeFunctionScope);
localTypeFunctionScope->location = block->location;
typeFunctionRuntime->rootScope = localTypeFunctionScope;
}
TypeId moduleFnTy = arena->addType(FunctionType{TypeLevel{}, builtinTypes->anyTypePack, rootScope->returnType});
TypeId moduleFnTy = arena->addType(FunctionType{TypeLevel{}, rootScope, builtinTypes->anyTypePack, rootScope->returnType});
interiorTypes.emplace_back();
prepopulateGlobalScope(scope, block);
@ -552,7 +536,7 @@ void ConstraintGenerator::computeRefinement(
refis->get(proposition->key->def)->shouldAppendNilType =
(sense || !eq) && containsSubscriptedDefinition(proposition->key->def) && !proposition->implicitFromCall;
}
else
else
{
refis->get(proposition->key->def)->shouldAppendNilType = (sense || !eq) && containsSubscriptedDefinition(proposition->key->def);
}
@ -715,9 +699,6 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
std::unordered_map<Name, Location> aliasDefinitionLocations;
std::unordered_map<Name, Location> classDefinitionLocations;
bool hasTypeFunction = false;
ScopePtr typeFunctionEnvScope;
// In order to enable mutually-recursive type aliases, we need to
// populate the type bindings before we actually check any of the
// alias statements.
@ -753,9 +734,6 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
initialFun.typePackParams.push_back(genPack);
}
if (FFlag::LuauRetainDefinitionAliasLocations)
initialFun.definitionLocation = alias->location;
if (alias->exported)
scope->exportedTypeBindings[alias->name.value] = std::move(initialFun);
else
@ -766,9 +744,6 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
}
else if (auto function = stat->as<AstStatTypeFunction>())
{
if (FFlag::LuauUserTypeFunTypecheck)
hasTypeFunction = true;
// If a type function w/ same name has already been defined, error for having duplicates
if (scope->exportedTypeBindings.count(function->name.value) || scope->privateTypeBindings.count(function->name.value))
{
@ -778,8 +753,7 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
continue;
}
// Variable becomes unused with the removal of FFlag::LuauUserTypeFunTypecheck
ScopePtr defnScope = FFlag::LuauUserTypeFunTypecheck ? nullptr : childScope(function, scope);
ScopePtr defnScope = childScope(function, scope);
// Create TypeFunctionInstanceType
@ -813,9 +787,6 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
TypeFun typeFunction{std::move(quantifiedTypeParams), typeFunctionTy};
if (FFlag::LuauRetainDefinitionAliasLocations)
typeFunction.definitionLocation = function->location;
// Set type bindings and definition locations for this user-defined type function
if (function->exported)
scope->exportedTypeBindings[function->name.value] = std::move(typeFunction);
@ -843,30 +814,17 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
TypeId initialType = arena->addType(BlockedType{});
TypeFun initialFun{initialType};
if (FFlag::LuauRetainDefinitionAliasLocations)
initialFun.definitionLocation = classDeclaration->location;
scope->exportedTypeBindings[classDeclaration->name.value] = std::move(initialFun);
classDefinitionLocations[classDeclaration->name.value] = classDeclaration->location;
}
}
if (FFlag::LuauUserTypeFunTypecheck && hasTypeFunction)
typeFunctionEnvScope = std::make_shared<Scope>(typeFunctionRuntime->rootScope);
// Additional pass for user-defined type functions to fill in their environments completely
for (AstStat* stat : block->body)
{
if (auto function = stat->as<AstStatTypeFunction>())
{
if (FFlag::LuauUserTypeFunTypecheck)
{
// Similar to global pre-population, create a binding for each type function in the scope upfront
TypeId bt = arena->addType(BlockedType{});
typeFunctionEnvScope->bindings[function->name] = Binding{bt, function->location};
astTypeFunctionEnvironmentScopes[function] = typeFunctionEnvScope;
}
// Find the type function we have already created
TypeFunctionInstanceType* mainTypeFun = nullptr;
@ -885,60 +843,51 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
UserDefinedFunctionData& userFuncData = mainTypeFun->userFuncData;
size_t level = 0;
if (FFlag::LuauUserTypeFunTypecheck)
for (Scope* curr = scope.get(); curr; curr = curr->parent.get())
{
auto addToEnvironment = [this](UserDefinedFunctionData& userFuncData, ScopePtr scope, const Name& name, TypeId type, size_t level)
for (auto& [name, tf] : curr->privateTypeBindings)
{
if (userFuncData.environment.find(name))
return;
continue;
if (auto ty = get<TypeFunctionInstanceType>(type); ty && ty->userFuncData.definition)
{
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
userFuncData.environment[name] = std::make_pair(ty->userFuncData.definition, level);
if (auto it = astTypeFunctionEnvironmentScopes.find(ty->userFuncData.definition))
{
if (auto existing = (*it)->linearSearchForBinding(name, /* traverseScopeChain */ false))
scope->bindings[ty->userFuncData.definition->name] =
Binding{existing->typeId, ty->userFuncData.definition->location};
}
}
};
for (Scope* curr = scope.get(); curr; curr = curr->parent.get())
{
for (auto& [name, tf] : curr->privateTypeBindings)
addToEnvironment(userFuncData, typeFunctionEnvScope, name, tf.type, level);
for (auto& [name, tf] : curr->exportedTypeBindings)
addToEnvironment(userFuncData, typeFunctionEnvScope, name, tf.type, level);
level++;
}
}
else
{
for (Scope* curr = scope.get(); curr; curr = curr->parent.get())
for (auto& [name, tf] : curr->exportedTypeBindings)
{
for (auto& [name, tf] : curr->privateTypeBindings)
{
if (userFuncData.environment.find(name))
continue;
if (userFuncData.environment.find(name))
continue;
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
userFuncData.environment[name] = std::make_pair(ty->userFuncData.definition, level);
}
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
userFuncData.environment[name] = std::make_pair(ty->userFuncData.definition, level);
}
for (auto& [name, tf] : curr->exportedTypeBindings)
{
if (userFuncData.environment.find(name))
continue;
level++;
}
}
else if (mainTypeFun)
{
UserDefinedFunctionData& userFuncData = mainTypeFun->userFuncData;
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
userFuncData.environment[name] = std::make_pair(ty->userFuncData.definition, level);
}
for (Scope* curr = scope.get(); curr; curr = curr->parent.get())
{
for (auto& [name, tf] : curr->privateTypeBindings)
{
if (userFuncData.environment_DEPRECATED.find(name))
continue;
level++;
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
userFuncData.environment_DEPRECATED[name] = ty->userFuncData.definition;
}
for (auto& [name, tf] : curr->exportedTypeBindings)
{
if (userFuncData.environment_DEPRECATED.find(name))
continue;
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
userFuncData.environment_DEPRECATED[name] = ty->userFuncData.definition;
}
}
}
@ -1370,23 +1319,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatRepeat* rep
return ControlFlow::None;
}
static void propagateDeprecatedAttributeToConstraint(ConstraintV& c, const AstExprFunction* func)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
if (GeneralizationConstraint* genConstraint = c.get_if<GeneralizationConstraint>())
{
genConstraint->hasDeprecatedAttribute = func->hasAttribute(AstAttr::Type::Deprecated);
}
}
static void propagateDeprecatedAttributeToType(TypeId signature, const AstExprFunction* func)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
FunctionType* fty = getMutable<FunctionType>(signature);
LUAU_ASSERT(fty);
fty->isDeprecatedFunction = func->hasAttribute(AstAttr::Type::Deprecated);
}
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFunction* function)
{
// Local
@ -1424,9 +1356,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti
std::unique_ptr<Constraint> c =
std::make_unique<Constraint>(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature});
if (FFlag::LuauDeprecatedAttribute)
propagateDeprecatedAttributeToConstraint(c->c, function->func);
Constraint* previous = nullptr;
forEachConstraint(
start,
@ -1450,11 +1379,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti
module->astTypes[function->func] = functionType;
}
else
{
module->astTypes[function->func] = sig.signature;
if (FFlag::LuauDeprecatedAttribute)
propagateDeprecatedAttributeToType(sig.signature, function->func);
}
return ControlFlow::None;
}
@ -1495,11 +1420,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
TypeId generalizedType = arena->addType(BlockedType{});
if (sigFullyDefined)
{
emplaceType<BoundType>(asMutable(generalizedType), sig.signature);
if (FFlag::LuauDeprecatedAttribute)
propagateDeprecatedAttributeToType(sig.signature, function->func);
}
else
{
const ScopePtr& constraintScope = sig.signatureScope ? sig.signatureScope : sig.bodyScope;
@ -1507,9 +1428,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
NotNull<Constraint> c = addConstraint(constraintScope, function->name->location, GeneralizationConstraint{generalizedType, sig.signature});
getMutable<BlockedType>(generalizedType)->setOwner(c);
if (FFlag::LuauDeprecatedAttribute)
propagateDeprecatedAttributeToConstraint(c->c, function->func);
Constraint* previous = nullptr;
forEachConstraint(
start,
@ -1771,64 +1689,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeAlias*
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunction* function)
{
if (!FFlag::LuauUserTypeFunTypecheck)
return ControlFlow::None;
auto scopePtr = astTypeFunctionEnvironmentScopes.find(function);
LUAU_ASSERT(scopePtr);
Checkpoint startCheckpoint = checkpoint(this);
FunctionSignature sig = checkFunctionSignature(*scopePtr, function->body, /* expectedType */ std::nullopt);
// Place this function as a child of the non-type function scope
scope->children.push_back(NotNull{sig.signatureScope.get()});
interiorTypes.push_back(std::vector<TypeId>{});
checkFunctionBody(sig.bodyScope, function->body);
Checkpoint endCheckpoint = checkpoint(this);
TypeId generalizedTy = arena->addType(BlockedType{});
NotNull<Constraint> gc = addConstraint(
sig.signatureScope,
function->location,
GeneralizationConstraint{
generalizedTy, sig.signature, FFlag::LuauTrackInteriorFreeTypesOnScope ? std::vector<TypeId>{} : std::move(interiorTypes.back())
}
);
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
sig.signatureScope->interiorFreeTypes = std::move(interiorTypes.back());
getMutable<BlockedType>(generalizedTy)->setOwner(gc);
interiorTypes.pop_back();
Constraint* previous = nullptr;
forEachConstraint(
startCheckpoint,
endCheckpoint,
this,
[gc, &previous](const ConstraintPtr& constraint)
{
gc->dependencies.emplace_back(constraint.get());
if (auto psc = get<PackSubtypeConstraint>(*constraint); psc && psc->returns)
{
if (previous)
constraint->dependencies.push_back(NotNull{previous});
previous = constraint.get();
}
}
);
std::optional<TypeId> existingFunctionTy = (*scopePtr)->lookup(function->name);
if (!existingFunctionTy)
ice->ice("checkAliases did not populate type function name", function->nameLocation);
if (auto bt = get<BlockedType>(*existingFunctionTy); bt && nullptr == bt->getOwner())
emplaceType<BoundType>(asMutable(*existingFunctionTy), generalizedTy);
return ControlFlow::None;
}
@ -2022,11 +1882,9 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareFunc
defn.varargLocation = global->vararg ? std::make_optional(global->varargLocation) : std::nullopt;
defn.originalNameLocation = global->nameLocation;
TypeId fnType = arena->addType(FunctionType{TypeLevel{}, std::move(genericTys), std::move(genericTps), paramPack, retPack, defn});
TypeId fnType = arena->addType(FunctionType{TypeLevel{}, funScope.get(), std::move(genericTys), std::move(genericTps), paramPack, retPack, defn});
FunctionType* ftv = getMutable<FunctionType>(fnType);
ftv->isCheckedFunction = global->isCheckedFunction();
if (FFlag::LuauDeprecatedAttribute)
ftv->isDeprecatedFunction = global->hasAttribute(AstAttr::Type::Deprecated);
ftv->argNames.reserve(global->paramNames.size);
for (const auto& el : global->paramNames)
@ -2194,23 +2052,13 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
}
else if (i < exprArgs.size() - 1 || !(arg->is<AstExprCall>() || arg->is<AstExprVarargs>()))
{
std::optional<TypeId> expectedType = std::nullopt;
if (FFlag::LuauPropagateExpectedTypesForCalls && i < expectedTypesForCall.size())
{
expectedType = expectedTypesForCall[i];
}
auto [ty, refinement] = check(scope, arg, expectedType, /*forceSingleton*/ false, /*generalize*/ false);
auto [ty, refinement] = check(scope, arg, /*expectedType*/ std::nullopt, /*forceSingleton*/ false, /*generalize*/ false);
args.push_back(ty);
argumentRefinements.push_back(refinement);
}
else
{
std::vector<std::optional<Luau::TypeId>> expectedTypes = {};
if (FFlag::LuauPropagateExpectedTypesForCalls && i < expectedTypesForCall.size())
{
expectedTypes.insert(expectedTypes.end(), expectedTypesForCall.begin() + int(i), expectedTypesForCall.end());
}
auto [tp, refis] = checkPack(scope, arg, expectedTypes);
auto [tp, refis] = checkPack(scope, arg, {});
argTail = tp;
argumentRefinements.insert(argumentRefinements.end(), refis.begin(), refis.end());
}
@ -2312,7 +2160,7 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
// TODO: How do expectedTypes play into this? Do they?
TypePackId rets = arena->addTypePack(BlockedTypePack{});
TypePackId argPack = addTypePack(std::move(args), argTail);
FunctionType ftv(TypeLevel{}, argPack, rets, std::nullopt, call->self);
FunctionType ftv(TypeLevel{}, scope.get(), argPack, rets, std::nullopt, call->self);
/*
* To make bidirectional type checking work, we need to solve these constraints in a particular order:
@ -2379,16 +2227,6 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExpr* expr, std::
return Inference{builtinTypes->errorRecoveryType()};
}
// We may recurse a given expression more than once when checking compound
// assignment, so we store and cache expressions here s.t. when we generate
// constraints for something like:
//
// a[b] += c
//
// We only solve _one_ set of constraints for `b`.
if (FFlag::LuauCacheInferencePerAstExpr && inferredExprCache.contains(expr))
return inferredExprCache[expr];
Inference result;
if (auto group = expr->as<AstExprGroup>())
@ -2441,9 +2279,6 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExpr* expr, std::
result = Inference{freshType(scope)};
}
if (FFlag::LuauCacheInferencePerAstExpr)
inferredExprCache[expr] = result;
LUAU_ASSERT(result.ty);
module->astTypes[expr] = result.ty;
if (expectedType)
@ -2483,7 +2318,8 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool*
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprLocal* local)
{
const RefinementKey* key = dfg->getRefinementKey(local);
LUAU_ASSERT(key);
std::optional<DefId> rvalueDef = dfg->getRValueDefForCompoundAssign(local);
LUAU_ASSERT(key || rvalueDef);
std::optional<TypeId> maybeTy;
@ -2491,6 +2327,11 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprLocal* local)
if (key)
maybeTy = lookup(scope, local->location, key->def);
// if the current def doesn't have a type, we might be doing a compound assignment
// and therefore might need to look at the rvalue def instead.
if (!maybeTy && rvalueDef)
maybeTy = lookup(scope, local->location, *rvalueDef);
if (maybeTy)
{
TypeId ty = follow(*maybeTy);
@ -2506,9 +2347,11 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprLocal* local)
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprGlobal* global)
{
const RefinementKey* key = dfg->getRefinementKey(global);
LUAU_ASSERT(key);
std::optional<DefId> rvalueDef = dfg->getRValueDefForCompoundAssign(global);
LUAU_ASSERT(key || rvalueDef);
DefId def = key->def;
// we'll use whichever of the two definitions we have here.
DefId def = key ? key->def : *rvalueDef;
/* prepopulateGlobalScope() has already added all global functions to the environment by this point, so any
* global that is not already in-scope is definitely an unknown symbol.
@ -3181,17 +3024,49 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
if (expectedType)
{
addConstraint(
scope,
expr->location,
TableCheckConstraint{
*expectedType,
ty,
expr,
NotNull{&module->astTypes},
NotNull{&module->astExpectedTypes},
if (FFlag::LuauDeferBidirectionalInferenceForTableAssignment)
{
addConstraint(
scope,
expr->location,
TableCheckConstraint{
*expectedType,
ty,
expr,
NotNull{&module->astTypes},
NotNull{&module->astExpectedTypes},
}
);
}
else
{
Unifier2 unifier{arena, builtinTypes, NotNull{scope.get()}, ice};
Subtyping sp{builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, ice};
std::vector<TypeId> toBlock;
// This logic is incomplete as we want to re-run this
// _after_ blocked types have resolved, but this
// allows us to do some bidirectional inference.
toBlock = findBlockedTypesIn(expr, NotNull{&module->astTypes});
if (toBlock.empty())
{
matchLiteralType(
NotNull{&module->astTypes},
NotNull{&module->astExpectedTypes},
builtinTypes,
arena,
NotNull{&unifier},
NotNull{&sp},
*expectedType,
ty,
expr,
toBlock
);
// The visitor we ran prior should ensure that there are no
// blocked types that we would encounter while matching on
// this expression.
LUAU_ASSERT(toBlock.empty());
}
);
}
}
return Inference{ty};
@ -3219,7 +3094,7 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
signatureScope = childScope(fn, parent);
// We need to assign returnType before creating bodyScope so that the
// return type gets propagated to bodyScope.
// return type gets propogated to bodyScope.
returnType = freshTypePack(signatureScope);
signatureScope->returnType = returnType;
@ -3362,7 +3237,7 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
// TODO: Preserve argument names in the function's type.
FunctionType actualFunction{TypeLevel{}, arena->addTypePack(argTypes, varargPack), returnType};
FunctionType actualFunction{TypeLevel{}, parent.get(), arena->addTypePack(argTypes, varargPack), returnType};
actualFunction.generics = std::move(genericTypes);
actualFunction.genericPacks = std::move(genericTypePacks);
actualFunction.argNames = std::move(argNames);
@ -3599,10 +3474,8 @@ TypeId ConstraintGenerator::resolveFunctionType(
// TODO: FunctionType needs a pointer to the scope so that we know
// how to quantify/instantiate it.
FunctionType ftv{TypeLevel{}, {}, {}, argTypes, returnTypes};
FunctionType ftv{TypeLevel{}, scope.get(), {}, {}, argTypes, returnTypes};
ftv.isCheckedFunction = fn->isCheckedFunction();
if (FFlag::LuauDeprecatedAttribute)
ftv.isDeprecatedFunction = fn->hasAttribute(AstAttr::Type::Deprecated);
// This replicates the behavior of the appropriate FunctionType
// constructors.
@ -3647,80 +3520,37 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
TypeId exprType = check(scope, tof->expr).ty;
result = exprType;
}
else if (ty->is<AstTypeOptional>())
{
if (FFlag::LuauAlwaysResolveAstTypes)
result = builtinTypes->nilType;
else
return builtinTypes->nilType;
}
else if (auto unionAnnotation = ty->as<AstTypeUnion>())
{
if (FFlag::LuauAlwaysResolveAstTypes)
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
{
if (unionAnnotation->types.size == 1)
result = resolveType(scope, unionAnnotation->types.data[0], inTypeArguments);
else
{
std::vector<TypeId> parts;
for (AstType* part : unionAnnotation->types)
{
parts.push_back(resolveType(scope, part, inTypeArguments));
}
result = arena->addType(UnionType{parts});
}
return resolveType(scope, unionAnnotation->types.data[0], inTypeArguments);
}
else
std::vector<TypeId> parts;
for (AstType* part : unionAnnotation->types)
{
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
{
if (unionAnnotation->types.size == 1)
return resolveType(scope, unionAnnotation->types.data[0], inTypeArguments);
}
std::vector<TypeId> parts;
for (AstType* part : unionAnnotation->types)
{
parts.push_back(resolveType(scope, part, inTypeArguments));
}
result = arena->addType(UnionType{parts});
parts.push_back(resolveType(scope, part, inTypeArguments));
}
result = arena->addType(UnionType{parts});
}
else if (auto intersectionAnnotation = ty->as<AstTypeIntersection>())
{
if (FFlag::LuauAlwaysResolveAstTypes)
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
{
if (intersectionAnnotation->types.size == 1)
result = resolveType(scope, intersectionAnnotation->types.data[0], inTypeArguments);
else
{
std::vector<TypeId> parts;
for (AstType* part : intersectionAnnotation->types)
{
parts.push_back(resolveType(scope, part, inTypeArguments));
}
result = arena->addType(IntersectionType{parts});
}
return resolveType(scope, intersectionAnnotation->types.data[0], inTypeArguments);
}
else
std::vector<TypeId> parts;
for (AstType* part : intersectionAnnotation->types)
{
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
{
if (intersectionAnnotation->types.size == 1)
return resolveType(scope, intersectionAnnotation->types.data[0], inTypeArguments);
}
std::vector<TypeId> parts;
for (AstType* part : intersectionAnnotation->types)
{
parts.push_back(resolveType(scope, part, inTypeArguments));
}
result = arena->addType(IntersectionType{parts});
parts.push_back(resolveType(scope, part, inTypeArguments));
}
result = arena->addType(IntersectionType{parts});
}
else if (auto typeGroupAnnotation = ty->as<AstTypeGroup>())
{
@ -4052,18 +3882,9 @@ struct GlobalPrepopulator : AstVisitor
void ConstraintGenerator::prepopulateGlobalScopeForFragmentTypecheck(const ScopePtr& globalScope, const ScopePtr& resumeScope, AstStatBlock* program)
{
FragmentTypeCheckGlobalPrepopulator gp{NotNull{globalScope.get()}, NotNull{resumeScope.get()}, dfg, arena};
if (prepareModuleScope)
prepareModuleScope(module->name, resumeScope);
program->visit(&gp);
if (FFlag::LuauUserTypeFunTypecheck)
{
// Handle type function globals as well, without preparing a module scope since they have a separate environment
GlobalPrepopulator tfgp{NotNull{typeFunctionRuntime->rootScope.get()}, arena, dfg};
program->visit(&tfgp);
}
}
void ConstraintGenerator::prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program)
@ -4074,13 +3895,6 @@ void ConstraintGenerator::prepopulateGlobalScope(const ScopePtr& globalScope, As
prepareModuleScope(module->name, globalScope);
program->visit(&gp);
if (FFlag::LuauUserTypeFunTypecheck)
{
// Handle type function globals as well, without preparing a module scope since they have a separate environment
GlobalPrepopulator tfgp{NotNull{typeFunctionRuntime->rootScope.get()}, arena, dfg};
program->visit(&tfgp);
}
}
bool ConstraintGenerator::recordPropertyAssignment(TypeId ty)

View file

@ -35,13 +35,9 @@ LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500)
LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTablesOnScope)
LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock)
LUAU_FASTFLAGVARIABLE(LuauPrecalculateMutatedFreeTypes2)
LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauSearchForRefineableType)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall)
namespace Luau
{
@ -361,19 +357,32 @@ ConstraintSolver::ConstraintSolver(
{
unsolvedConstraints.emplace_back(c);
auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes();
for (auto ty : maybeMutatedTypesPerConstraint)
if (FFlag::LuauPrecalculateMutatedFreeTypes2)
{
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
refCount += 1;
if (FFlag::DebugLuauGreedyGeneralization)
auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes();
for (auto ty : maybeMutatedTypesPerConstraint)
{
auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, DenseHashSet<const Constraint*>{nullptr});
it->second.insert(c.get());
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
refCount += 1;
if (FFlag::DebugLuauGreedyGeneralization)
{
auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, DenseHashSet<const Constraint*>{nullptr});
it->second.insert(c.get());
}
}
maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint);
}
else
{
// initialize the reference counts for the free types in this constraint.
for (auto ty : c->getMaybeMutatedFreeTypes())
{
// increment the reference count for `ty`
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
refCount += 1;
}
}
maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint);
for (NotNull<const Constraint> dep : c->dependencies)
@ -464,24 +473,48 @@ void ConstraintSolver::run()
unblock(c);
unsolvedConstraints.erase(unsolvedConstraints.begin() + ptrdiff_t(i));
const auto maybeMutated = maybeMutatedFreeTypes.find(c);
if (maybeMutated != maybeMutatedFreeTypes.end())
if (FFlag::LuauPrecalculateMutatedFreeTypes2)
{
DenseHashSet<TypeId> seen{nullptr};
for (auto ty : maybeMutated->second)
const auto maybeMutated = maybeMutatedFreeTypes.find(c);
if (maybeMutated != maybeMutatedFreeTypes.end())
{
// There is a high chance that this type has been rebound
// across blocked types, rebound free types, pending
// expansion types, etc, so we need to follow it.
ty = follow(ty);
if (FFlag::DebugLuauGreedyGeneralization)
DenseHashSet<TypeId> seen{nullptr};
for (auto ty : maybeMutated->second)
{
if (seen.contains(ty))
continue;
seen.insert(ty);
}
// There is a high chance that this type has been rebound
// across blocked types, rebound free types, pending
// expansion types, etc, so we need to follow it.
ty = follow(ty);
if (FFlag::DebugLuauGreedyGeneralization)
{
if (seen.contains(ty))
continue;
seen.insert(ty);
}
size_t& refCount = unresolvedConstraints[ty];
if (refCount > 0)
refCount -= 1;
// We have two constraints that are designed to wait for the
// refCount on a free type to be equal to 1: the
// PrimitiveTypeConstraint and ReduceConstraint. We
// therefore wake any constraint waiting for a free type's
// refcount to be 1 or 0.
if (refCount <= 1)
unblock(ty, Location{});
if (FFlag::DebugLuauGreedyGeneralization && refCount == 0)
generalizeOneType(ty);
}
}
}
else
{
// decrement the referenced free types for this constraint if we dispatched successfully!
for (auto ty : c->getMaybeMutatedFreeTypes())
{
size_t& refCount = unresolvedConstraints[ty];
if (refCount > 0)
refCount -= 1;
@ -493,9 +526,6 @@ void ConstraintSolver::run()
// refcount to be 1 or 0.
if (refCount <= 1)
unblock(ty, Location{});
if (FFlag::DebugLuauGreedyGeneralization && refCount == 0)
generalizeOneType(ty);
}
}
@ -595,6 +625,14 @@ bool ConstraintSolver::isDone() const
struct TypeSearcher : TypeVisitor
{
enum struct Polarity: uint8_t
{
None = 0b00,
Positive = 0b01,
Negative = 0b10,
Mixed = 0b11,
};
TypeId needle;
Polarity current = Polarity::Positive;
@ -710,12 +748,12 @@ void ConstraintSolver::generalizeOneType(TypeId ty)
switch (ts.result)
{
case Polarity::None:
case TypeSearcher::Polarity::None:
asMutable(ty)->reassign(Type{BoundType{upperBound}});
break;
case Polarity::Negative:
case Polarity::Mixed:
case TypeSearcher::Polarity::Negative:
case TypeSearcher::Polarity::Mixed:
if (get<UnknownType>(upperBound) && ts.count > 1)
{
asMutable(ty)->reassign(Type{GenericType{tyScope}});
@ -725,17 +763,15 @@ void ConstraintSolver::generalizeOneType(TypeId ty)
asMutable(ty)->reassign(Type{BoundType{upperBound}});
break;
case Polarity::Positive:
if (get<UnknownType>(lowerBound) && ts.count > 1)
{
asMutable(ty)->reassign(Type{GenericType{tyScope}});
function->generics.emplace_back(ty);
}
else
asMutable(ty)->reassign(Type{BoundType{lowerBound}});
break;
default:
LUAU_ASSERT(!"Unreachable");
case TypeSearcher::Polarity::Positive:
if (get<UnknownType>(lowerBound) && ts.count > 1)
{
asMutable(ty)->reassign(Type{GenericType{tyScope}});
function->generics.emplace_back(ty);
}
else
asMutable(ty)->reassign(Type{BoundType{lowerBound}});
break;
}
}
}
@ -882,15 +918,6 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
bind(constraint, generalizedType, *generalizedTy);
else
unify(constraint, generalizedType, *generalizedTy);
if (FFlag::LuauDeprecatedAttribute)
{
if (FunctionType* fty = getMutable<FunctionType>(follow(generalizedType)))
{
if (c.hasDeprecatedAttribute)
fty->isDeprecatedFunction = true;
}
}
}
else
{
@ -904,12 +931,12 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
// clang-tidy doesn't understand this is safe.
if (constraint->scope->interiorFreeTypes)
for (TypeId ty : *constraint->scope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access)
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty);
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty, /* avoidSealingTables */ false);
}
else
{
for (TypeId ty : c.interiorTypes)
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty);
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty, /* avoidSealingTables */ false);
}
@ -1325,7 +1352,7 @@ void ConstraintSolver::fillInDiscriminantTypes(NotNull<const Constraint> constra
if (isBlocked(*ty))
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
// We also need to unconditionally unblock these types, otherwise
// you end up with funky looking "Blocked on *no-refine*."
unblock(*ty, constraint->location);
@ -1483,7 +1510,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
if (status == OverloadResolver::Analysis::Ok)
overloadToUse = overload;
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, argsPack, c.result});
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, constraint->scope.get(), argsPack, c.result});
Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}};
const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy);
@ -1518,11 +1545,6 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
queuer.traverse(overloadToUse);
queuer.traverse(inferredTy);
// This can potentially contain free types if the return type of
// `inferredTy` is never unified elsewhere.
if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInferredFunctionTypeFromCall)
trackInteriorFreeType(constraint->scope, inferredTy);
unblock(c.result, constraint->location);
return true;
@ -1536,43 +1558,6 @@ static AstExpr* unwrapGroup(AstExpr* expr)
return expr;
}
struct ContainsGenerics : public TypeOnceVisitor
{
DenseHashSet<const void*> generics{nullptr};
bool found = false;
bool visit(TypeId ty) override
{
return !found;
}
bool visit(TypeId ty, const GenericType&) override
{
found |= generics.contains(ty);
return true;
}
bool visit(TypeId ty, const TypeFunctionInstanceType&) override
{
return !found;
}
bool visit(TypePackId tp, const GenericTypePack&) override
{
found |= generics.contains(tp);
return !found;
}
bool hasGeneric(TypeId ty)
{
traverse(ty);
auto ret = found;
found = false;
return ret;
}
};
bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<const Constraint> constraint)
{
TypeId fn = follow(c.fn);
@ -1615,49 +1600,36 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
DenseHashMap<TypeId, TypeId> replacements{nullptr};
DenseHashMap<TypePackId, TypePackId> replacementPacks{nullptr};
ContainsGenerics containsGenerics;
for (auto generic : ftv->generics)
{
replacements[generic] = builtinTypes->unknownType;
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
containsGenerics.generics.insert(generic);
}
for (auto genericPack : ftv->genericPacks)
{
replacementPacks[genericPack] = builtinTypes->unknownTypePack;
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
containsGenerics.generics.insert(genericPack);
}
// If the type of the function has generics, we don't actually want to push any of the generics themselves
// into the argument types as expected types because this creates an unnecessary loop. Instead, we want to
// replace these types with `unknown` (and `...unknown`) to keep any structure but not create the cycle.
if (!FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
if (!replacements.empty() || !replacementPacks.empty())
{
if (!replacements.empty() || !replacementPacks.empty())
Replacer replacer{arena, std::move(replacements), std::move(replacementPacks)};
std::optional<TypeId> res = replacer.substitute(fn);
if (res)
{
Replacer replacer{arena, std::move(replacements), std::move(replacementPacks)};
std::optional<TypeId> res = replacer.substitute(fn);
if (res)
if (*res != fn)
{
if (*res != fn)
{
FunctionType* ftvMut = getMutable<FunctionType>(*res);
LUAU_ASSERT(ftvMut);
ftvMut->generics.clear();
ftvMut->genericPacks.clear();
}
fn = *res;
ftv = get<FunctionType>(*res);
LUAU_ASSERT(ftv);
// we've potentially copied type functions here, so we need to reproduce their reduce constraint.
reproduceConstraints(constraint->scope, constraint->location, replacer);
FunctionType* ftvMut = getMutable<FunctionType>(*res);
LUAU_ASSERT(ftvMut);
ftvMut->generics.clear();
ftvMut->genericPacks.clear();
}
fn = *res;
ftv = get<FunctionType>(*res);
LUAU_ASSERT(ftv);
// we've potentially copied type functions here, so we need to reproduce their reduce constraint.
reproduceConstraints(constraint->scope, constraint->location, replacer);
}
}
@ -1676,10 +1648,6 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
(*c.astExpectedTypes)[expr] = expectedArgTy;
// Generic types are skipped over entirely, for now.
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes && containsGenerics.hasGeneric(expectedArgTy))
continue;
const FunctionType* expectedLambdaTy = get<FunctionType>(expectedArgTy);
const FunctionType* lambdaTy = get<FunctionType>(actualArgTy);
const AstExprFunction* lambdaExpr = expr->as<AstExprFunction>();
@ -1784,16 +1752,8 @@ bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNull<const Con
LUAU_ASSERT(get<BlockedType>(resultType));
LUAU_ASSERT(canMutate(resultType, constraint));
if (FFlag::LuauHasPropProperBlock)
{
if (isBlocked(subjectType))
return block(subjectType, constraint);
}
else
{
if (isBlocked(subjectType) || get<PendingExpansionType>(subjectType) || get<TypeFunctionInstanceType>(subjectType))
return block(subjectType, constraint);
}
if (isBlocked(subjectType) || get<PendingExpansionType>(subjectType) || get<TypeFunctionInstanceType>(subjectType))
return block(subjectType, constraint);
if (const TableType* subjectTable = getTableType(subjectType))
{
@ -2399,18 +2359,11 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Cons
for (TypePackId r : result.reducedPacks)
unblock(r, constraint->location);
if (FFlag::LuauNewTypeFunReductionChecks2)
{
for (TypeId ity : result.irreducibleTypes)
uninhabitedTypeFunctions.insert(ity);
}
bool reductionFinished = result.blockedTypes.empty() && result.blockedPacks.empty();
ty = follow(ty);
// If we couldn't reduce this type function, stick it in the set!
if (get<TypeFunctionInstanceType>(ty) && (!FFlag::LuauNewTypeFunReductionChecks2 || !result.irreducibleTypes.find(ty)))
if (get<TypeFunctionInstanceType>(ty))
typeFunctionsToFinalize[ty] = constraint;
if (force || reductionFinished)
@ -3330,7 +3283,7 @@ void ConstraintSolver::shiftReferences(TypeId source, TypeId target)
}
}
std::optional<TypeId> ConstraintSolver::generalizeFreeType(NotNull<Scope> scope, TypeId type)
std::optional<TypeId> ConstraintSolver::generalizeFreeType(NotNull<Scope> scope, TypeId type, bool avoidSealingTables)
{
TypeId t = follow(type);
if (get<FreeType>(t))
@ -3345,7 +3298,7 @@ std::optional<TypeId> ConstraintSolver::generalizeFreeType(NotNull<Scope> scope,
// that until all constraint generation is complete.
}
return generalize(NotNull{arena}, builtinTypes, scope, generalizedTypes, type);
return generalize(NotNull{arena}, builtinTypes, scope, generalizedTypes, type, avoidSealingTables);
}
bool ConstraintSolver::hasUnresolvedConstraints(TypeId ty)

View file

@ -13,22 +13,32 @@
LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauPreprocessTypestatedArgument)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackTrueReset)
namespace Luau
{
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
{
ScopeStack& stack;
size_t previousSize;
PushScope(ScopeStack& stack, DfgScope* scope)
: stack(stack)
, previousSize(stack.size())
{
// `scope` should never be `nullptr` here.
LUAU_ASSERT(scope);
@ -37,18 +47,7 @@ struct 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();
}
else
{
stack.pop_back();
}
stack.pop_back();
}
};
@ -83,6 +82,12 @@ std::optional<DefId> DataFlowGraph::getDefOptional(const AstExpr* expr) const
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
{
auto def = localDefs.find(local);
@ -873,12 +878,6 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
{
visitExpr(c->func);
if (FFlag::LuauPreprocessTypestatedArgument)
{
for (AstExpr* arg : c->args)
visitExpr(arg);
}
if (shouldTypestateForFirstArgument(*c) && c->args.size > 1 && isLValue(*c->args.begin()))
{
AstExpr* firstArg = *c->args.begin();
@ -909,11 +908,8 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
visitLValue(firstArg, def);
}
if (!FFlag::LuauPreprocessTypestatedArgument)
{
for (AstExpr* arg : c->args)
visitExpr(arg);
}
for (AstExpr* arg : c->args)
visitExpr(arg);
// We treat function calls as "subscripted" as they could potentially
// return a subscripted value, consider:
@ -1172,8 +1168,6 @@ void DataFlowGraphBuilder::visitType(AstType* t)
return visitType(f);
else if (auto tyof = t->as<AstTypeTypeof>())
return visitType(tyof);
else if (auto o = t->as<AstTypeOptional>())
return;
else if (auto u = t->as<AstTypeUnion>())
return visitType(u);
else if (auto i = t->as<AstTypeIntersection>())

View file

@ -1,6 +1,8 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAGVARIABLE(LuauDebugInfoDefn)
namespace Luau
{
@ -213,6 +215,15 @@ declare debug: {
)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(
declare utf8: {
@ -298,7 +309,7 @@ std::string getBuiltinDefinitionSource()
result += kBuiltinDefinitionOsSrc;
result += kBuiltinDefinitionCoroutineSrc;
result += kBuiltinDefinitionTableSrc;
result += kBuiltinDefinitionDebugSrc;
result += FFlag::LuauDebugInfoDefn ? kBuiltinDefinitionDebugSrc : kBuiltinDefinitionDebugSrc_DEPRECATED;
result += kBuiltinDefinitionUtf8Src;
result += kBuiltinDefinitionBufferSrc;
result += kBuiltinDefinitionVectorSrc;
@ -306,83 +317,4 @@ std::string getBuiltinDefinitionSource()
return result;
}
// TODO: split into separate tagged unions when the new solver can appropriately handle that.
static const std::string kBuiltinDefinitionTypesSrc = R"BUILTIN_SRC(
export type type = {
tag: "nil" | "unknown" | "never" | "any" | "boolean" | "number" | "string" | "buffer" | "thread" |
"singleton" | "negation" | "union" | "intesection" | "table" | "function" | "class" | "generic",
is: (self: type, arg: string) -> boolean,
-- for singleton type
value: (self: type) -> (string | boolean | nil),
-- for negation type
inner: (self: type) -> type,
-- for union and intersection types
components: (self: type) -> {type},
-- for table type
setproperty: (self: type, key: type, value: type?) -> (),
setreadproperty: (self: type, key: type, value: type?) -> (),
setwriteproperty: (self: type, key: type, value: type?) -> (),
readproperty: (self: type, key: type) -> type?,
writeproperty: (self: type, key: type) -> type?,
properties: (self: type) -> { [type]: { read: type?, write: type? } },
setindexer: (self: type, index: type, result: type) -> (),
setreadindexer: (self: type, index: type, result: type) -> (),
setwriteindexer: (self: type, index: type, result: type) -> (),
indexer: (self: type) -> { index: type, readresult: type, writeresult: type }?,
readindexer: (self: type) -> { index: type, result: type }?,
writeindexer: (self: type) -> { index: type, result: type }?,
setmetatable: (self: type, arg: type) -> (),
metatable: (self: type) -> type?,
-- for function type
setparameters: (self: type, head: {type}?, tail: type?) -> (),
parameters: (self: type) -> { head: {type}?, tail: type? },
setreturns: (self: type, head: {type}?, tail: type? ) -> (),
returns: (self: type) -> { head: {type}?, tail: type? },
setgenerics: (self: type, {type}?) -> (),
generics: (self: type) -> {type},
-- for class type
-- 'properties', 'metatable', 'indexer', 'readindexer' and 'writeindexer' are shared with table type
readparent: (self: type) -> type?,
writeparent: (self: type) -> type?,
-- for generic type
name: (self: type) -> string?,
ispack: (self: type) -> boolean,
}
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,
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()
{
return kBuiltinDefinitionTypesSrc;
}
} // namespace Luau

View file

@ -8,7 +8,6 @@
#include "Luau/StringUtils.h"
#include "Luau/ToString.h"
#include "Luau/Type.h"
#include "Luau/TypeChecker2.h"
#include "Luau/TypeFunction.h"
#include <optional>
@ -18,7 +17,6 @@
#include <unordered_set>
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
LUAU_FASTFLAG(LuauNonStrictFuncDefErrorFix)
static std::string wrongNumberOfArgsString(
size_t expectedCount,
@ -118,10 +116,7 @@ struct ErrorConverter
size_t luauIndentTypeMismatchMaxTypeLength = size_t(FInt::LuauIndentTypeMismatchMaxTypeLength);
if (givenType.length() <= luauIndentTypeMismatchMaxTypeLength || wantedType.length() <= luauIndentTypeMismatchMaxTypeLength)
return "Type " + given + " could not be converted into " + wanted;
if (FFlag::LuauImproveTypePathsInErrors)
return "Type\n\t" + given + "\ncould not be converted into\n\t" + wanted;
else
return "Type\n " + given + "\ncould not be converted into\n " + wanted;
return "Type\n " + given + "\ncould not be converted into\n " + wanted;
};
if (givenTypeName == wantedTypeName)
@ -756,15 +751,8 @@ struct ErrorConverter
std::string operator()(const NonStrictFunctionDefinitionError& e) const
{
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";
}
else
{
return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' in function '" + e.functionName +
"' is used in a way that will run time error";
}
return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' in function '" + e.functionName +
"' is used in a way that will run time error";
}
std::string operator()(const PropertyAccessViolation& e) const

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,7 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Frontend.h"
#include "Luau/AnyTypeSummary.h"
#include "Luau/BuiltinDefinitions.h"
#include "Luau/Clone.h"
#include "Luau/Common.h"
@ -39,7 +40,6 @@ LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRethrowKnownExceptions, false)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile)
LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes)
@ -47,8 +47,13 @@ LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode)
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false)
LUAU_FASTFLAGVARIABLE(LuauModuleHoldsAstRoot)
LUAU_FASTFLAGVARIABLE(LuauFixMultithreadTypecheck)
LUAU_FASTFLAG(StudioReportLuauAny2)
LUAU_FASTFLAGVARIABLE(LuauSelectivelyRetainDFGArena)
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete)
namespace Luau
{
@ -456,6 +461,20 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
if (item.name == name)
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;
@ -477,6 +496,11 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
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);
if (FFlag::LuauSolverV2)
frontendOptions.forAutocomplete = false;
@ -661,6 +685,247 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
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)
{
if (FFlag::LuauSolverV2)
@ -1102,27 +1367,13 @@ void Frontend::performQueueItemTask(std::shared_ptr<BuildQueueWorkState> state,
{
BuildQueueItem& item = state->buildQueueItems[itemPos];
if (DFFlag::LuauRethrowKnownExceptions)
try
{
try
{
checkBuildQueueItem(item);
}
catch (const Luau::InternalCompilerError&)
{
item.exception = std::current_exception();
}
checkBuildQueueItem(item);
}
else
catch (...)
{
try
{
checkBuildQueueItem(item);
}
catch (...)
{
item.exception = std::current_exception();
}
item.exception = std::current_exception();
}
{
@ -1281,7 +1532,6 @@ ModulePtr check(
NotNull<ModuleResolver> moduleResolver,
NotNull<FileResolver> fileResolver,
const ScopePtr& parentScope,
const ScopePtr& typeFunctionScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
FrontendOptions options,
TypeCheckLimits limits,
@ -1298,7 +1548,6 @@ ModulePtr check(
moduleResolver,
fileResolver,
parentScope,
typeFunctionScope,
std::move(prepareModuleScope),
options,
limits,
@ -1360,7 +1609,6 @@ ModulePtr check(
NotNull<ModuleResolver> moduleResolver,
NotNull<FileResolver> fileResolver,
const ScopePtr& parentScope,
const ScopePtr& typeFunctionScope,
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
FrontendOptions options,
TypeCheckLimits limits,
@ -1381,7 +1629,8 @@ ModulePtr check(
result->interfaceTypes.owningModule = result.get();
result->allocator = sourceModule.allocator;
result->names = sourceModule.names;
result->root = sourceModule.root;
if (FFlag::LuauModuleHoldsAstRoot)
result->root = sourceModule.root;
iceHandler->moduleName = sourceModule.name;
@ -1406,7 +1655,7 @@ ModulePtr check(
SimplifierPtr simplifier = newSimplifier(NotNull{&result->internalTypes}, builtinTypes);
TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}};
typeFunctionRuntime.allowEvaluation = FFlag::LuauTypeFunResultInAutocomplete || sourceModule.parseErrors.empty();
typeFunctionRuntime.allowEvaluation = sourceModule.parseErrors.empty();
ConstraintGenerator cg{
result,
@ -1417,7 +1666,6 @@ ModulePtr check(
builtinTypes,
iceHandler,
parentScope,
typeFunctionScope,
std::move(prepareModuleScope),
logger.get(),
NotNull{&dfg},
@ -1600,7 +1848,6 @@ ModulePtr Frontend::check(
NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver},
NotNull{fileResolver},
environmentScope ? *environmentScope : globals.globalScope,
globals.globalTypeFunctionScope,
prepareModuleScopeWrap,
options,
typeCheckLimits,

View file

@ -12,6 +12,7 @@
#include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauGeneralizationRemoveRecursiveUpperBound2)
namespace Luau
{
@ -29,6 +30,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
std::vector<TypePackId> genericPacks;
bool isWithinFunction = false;
bool avoidSealingTables = false;
MutatingGeneralizer(
NotNull<TypeArena> arena,
@ -36,7 +38,8 @@ struct MutatingGeneralizer : TypeOnceVisitor
NotNull<Scope> scope,
NotNull<DenseHashSet<TypeId>> cachedTypes,
DenseHashMap<const void*, size_t> positiveTypes,
DenseHashMap<const void*, size_t> negativeTypes
DenseHashMap<const void*, size_t> negativeTypes,
bool avoidSealingTables
)
: TypeOnceVisitor(/* skipBoundTypes */ true)
, arena(arena)
@ -45,6 +48,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
, cachedTypes(cachedTypes)
, positiveTypes(std::move(positiveTypes))
, negativeTypes(std::move(negativeTypes))
, avoidSealingTables(avoidSealingTables)
{
}
@ -95,7 +99,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
LUAU_ASSERT(onlyType != haystack);
emplaceType<BoundType>(asMutable(haystack), onlyType);
}
else if (ut->options.empty())
else if (FFlag::LuauGeneralizationRemoveRecursiveUpperBound2 && ut->options.empty())
{
emplaceType<BoundType>(asMutable(haystack), builtinTypes->neverType);
}
@ -141,8 +145,8 @@ struct MutatingGeneralizer : TypeOnceVisitor
TypeId onlyType = it->parts[0];
LUAU_ASSERT(onlyType != needle);
emplaceType<BoundType>(asMutable(needle), onlyType);
}
else if (it->parts.empty())
}
else if (FFlag::LuauGeneralizationRemoveRecursiveUpperBound2 && it->parts.empty())
{
emplaceType<BoundType>(asMutable(needle), builtinTypes->unknownType);
}
@ -288,7 +292,8 @@ struct MutatingGeneralizer : TypeOnceVisitor
TableType* tt = getMutable<TableType>(ty);
LUAU_ASSERT(tt);
tt->state = TableState::Sealed;
if (!avoidSealingTables)
tt->state = TableState::Sealed;
return true;
}
@ -327,19 +332,26 @@ struct FreeTypeSearcher : TypeVisitor
{
}
Polarity polarity = Polarity::Positive;
enum Polarity
{
Positive,
Negative,
Both,
};
Polarity polarity = Positive;
void flip()
{
switch (polarity)
{
case Polarity::Positive:
polarity = Polarity::Negative;
case Positive:
polarity = Negative;
break;
case Polarity::Negative:
polarity = Polarity::Positive;
case Negative:
polarity = Positive;
break;
default:
case Both:
break;
}
}
@ -347,11 +359,11 @@ struct FreeTypeSearcher : TypeVisitor
DenseHashSet<const void*> seenPositive{nullptr};
DenseHashSet<const void*> seenNegative{nullptr};
bool seenWithCurrentPolarity(const void* ty)
bool seenWithPolarity(const void* ty)
{
switch (polarity)
{
case Polarity::Positive:
case Positive:
{
if (seenPositive.contains(ty))
return true;
@ -359,7 +371,7 @@ struct FreeTypeSearcher : TypeVisitor
seenPositive.insert(ty);
return false;
}
case Polarity::Negative:
case Negative:
{
if (seenNegative.contains(ty))
return true;
@ -367,7 +379,7 @@ struct FreeTypeSearcher : TypeVisitor
seenNegative.insert(ty);
return false;
}
case Polarity::Mixed:
case Both:
{
if (seenPositive.contains(ty) && seenNegative.contains(ty))
return true;
@ -376,8 +388,6 @@ struct FreeTypeSearcher : TypeVisitor
seenNegative.insert(ty);
return false;
}
default:
LUAU_ASSERT(!"Unreachable");
}
return false;
@ -391,7 +401,7 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypeId ty) override
{
if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
if (cachedTypes->contains(ty) || seenWithPolarity(ty))
return false;
LUAU_ASSERT(ty);
@ -400,7 +410,7 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypeId ty, const FreeType& ft) override
{
if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
if (cachedTypes->contains(ty) || seenWithPolarity(ty))
return false;
if (!subsumes(scope, ft.scope))
@ -408,18 +418,16 @@ struct FreeTypeSearcher : TypeVisitor
switch (polarity)
{
case Polarity::Positive:
case Positive:
positiveTypes[ty]++;
break;
case Polarity::Negative:
case Negative:
negativeTypes[ty]++;
break;
case Polarity::Mixed:
case Both:
positiveTypes[ty]++;
negativeTypes[ty]++;
break;
default:
LUAU_ASSERT(!"Unreachable");
}
return true;
@ -427,25 +435,23 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypeId ty, const TableType& tt) override
{
if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
if (cachedTypes->contains(ty) || seenWithPolarity(ty))
return false;
if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope))
{
switch (polarity)
{
case Polarity::Positive:
case Positive:
positiveTypes[ty]++;
break;
case Polarity::Negative:
case Negative:
negativeTypes[ty]++;
break;
case Polarity::Mixed:
case Both:
positiveTypes[ty]++;
negativeTypes[ty]++;
break;
default:
LUAU_ASSERT(!"Unreachable");
}
}
@ -458,7 +464,7 @@ struct FreeTypeSearcher : TypeVisitor
LUAU_ASSERT(prop.isShared() || FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete);
Polarity p = polarity;
polarity = Polarity::Mixed;
polarity = Both;
traverse(prop.type());
polarity = p;
}
@ -475,7 +481,7 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypeId ty, const FunctionType& ft) override
{
if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
if (cachedTypes->contains(ty) || seenWithPolarity(ty))
return false;
flip();
@ -494,7 +500,7 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypePackId tp, const FreeTypePack& ftp) override
{
if (seenWithCurrentPolarity(tp))
if (seenWithPolarity(tp))
return false;
if (!subsumes(scope, ftp.scope))
@ -502,18 +508,16 @@ struct FreeTypeSearcher : TypeVisitor
switch (polarity)
{
case Polarity::Positive:
case Positive:
positiveTypes[tp]++;
break;
case Polarity::Negative:
case Negative:
negativeTypes[tp]++;
break;
case Polarity::Mixed:
case Both:
positiveTypes[tp]++;
negativeTypes[tp]++;
break;
default:
LUAU_ASSERT(!"Unreachable");
}
return true;
@ -543,7 +547,7 @@ struct TypeCacher : TypeOnceVisitor
{
}
void cache(TypeId ty) const
void cache(TypeId ty)
{
cachedTypes->insert(ty);
}
@ -968,7 +972,8 @@ std::optional<TypeId> generalize(
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
NotNull<DenseHashSet<TypeId>> cachedTypes,
TypeId ty
TypeId ty,
bool avoidSealingTables
)
{
ty = follow(ty);
@ -979,7 +984,7 @@ std::optional<TypeId> generalize(
FreeTypeSearcher fts{scope, cachedTypes};
fts.traverse(ty);
MutatingGeneralizer gen{arena, builtinTypes, scope, cachedTypes, std::move(fts.positiveTypes), std::move(fts.negativeTypes)};
MutatingGeneralizer gen{arena, builtinTypes, scope, cachedTypes, std::move(fts.positiveTypes), std::move(fts.negativeTypes), avoidSealingTables};
gen.traverse(ty);

View file

@ -9,7 +9,6 @@ GlobalTypes::GlobalTypes(NotNull<BuiltinTypes> builtinTypes)
: builtinTypes(builtinTypes)
{
globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
globalTypeFunctionScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
globalScope->addBuiltinTypeBinding("any", TypeFun{{}, builtinTypes->anyType});
globalScope->addBuiltinTypeBinding("nil", TypeFun{{}, builtinTypes->nilType});

View file

@ -61,7 +61,7 @@ TypeId Instantiation::clean(TypeId ty)
const FunctionType* ftv = log->getMutable<FunctionType>(ty);
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.tags = ftv->tags;
clone.argNames = ftv->argNames;

View file

@ -19,8 +19,6 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAttribute)
LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
namespace Luau
{
@ -2282,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
{
if (std::optional<TypeId> ty = context->getType(node->expr))
@ -2378,59 +2325,18 @@ private:
if (prop && prop->deprecated)
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))
{
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
if (tty->name && tty->name->compare(0, 7, "typeof(") == 0 && tty->name->back() == ')')
report(node->location, prop->second, tty->name->substr(7, tty->name->length() - 8).c_str(), node->index.value);
else
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);
}
}
}
// strip synthetic typeof() for builtin tables
if (tty->name && tty->name->compare(0, 7, "typeof(") == 0 && tty->name->back() == ')')
report(node->location, prop->second, tty->name->substr(7, tty->name->length() - 8).c_str(), node->index.value);
else
report(node->location, prop->second, tty->name ? tty->name->c_str() : nullptr, node->index.value);
}
}
}
@ -2449,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)
{
std::string suggestion = prop.deprecatedSuggestion.empty() ? "" : format(", use '%s' instead", prop.deprecatedSuggestion.c_str());
@ -2478,63 +2364,6 @@ private:
else
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

View file

@ -15,12 +15,12 @@
#include <algorithm>
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteCommentDetection)
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 xml parsing done by the dcr script.
@ -38,6 +38,21 @@ void resetLogLuauProc()
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)
{
if (comment.location.contains(pos))
@ -61,8 +76,11 @@ bool isWithinComment(const std::vector<Comment>& commentLocations, Position pos)
Comment{Lexeme::Comment, Location{pos, pos}},
[](const Comment& a, const Comment& b)
{
if (a.type == Lexeme::Comment)
return a.location.end.line < b.location.end.line;
if (FFlag::LuauIncrementalAutocompleteCommentDetection)
{
if (a.type == Lexeme::Comment)
return a.location.end.line < b.location.end.line;
}
return a.location.end < b.location.end;
}
);
@ -70,7 +88,7 @@ bool isWithinComment(const std::vector<Comment>& commentLocations, Position pos)
if (iter == commentLocations.end())
return false;
if (contains(pos, *iter))
if (FFlag::LuauIncrementalAutocompleteCommentDetection ? contains(pos, *iter) : contains_DEPRECATED(pos, *iter))
return true;
// 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};
if (FFlag::LuauSolverV2)
ftv->scope = nullptr;
}
else if (TableType* ttv = getMutable<TableType>(result))
{
@ -265,10 +285,7 @@ struct ClonePublicInterface : Substitution
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/Ast.h"
#include "Luau/AstQuery.h"
#include "Luau/Common.h"
#include "Luau/Simplify.h"
#include "Luau/Type.h"
@ -23,7 +22,6 @@
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals)
LUAU_FASTFLAGVARIABLE(LuauNonStrictFuncDefErrorFix)
namespace Luau
{
@ -765,17 +763,7 @@ struct NonStrictTypeChecker
for (AstLocal* local : exprFn->args)
{
if (std::optional<TypeId> ty = willRunTimeErrorFunctionDefinition(local, remainder))
{
if (FFlag::LuauNonStrictFuncDefErrorFix)
{
const char* debugname = exprFn->debugname.value;
reportError(NonStrictFunctionDefinitionError{debugname ? debugname : "", local->name.value, *ty}, local->location);
}
else
{
reportError(NonStrictFunctionDefinitionError{exprFn->debugname.value, local->name.value, *ty}, local->location);
}
}
reportError(NonStrictFunctionDefinitionError{exprFn->debugname.value, local->name.value, *ty}, local->location);
remainder.remove(dfg->getDef(local));
}
return remainder;

View file

@ -17,16 +17,12 @@
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant)
LUAU_FASTFLAGVARIABLE(LuauNormalizeNegatedErrorToAnError)
LUAU_FASTFLAGVARIABLE(LuauNormalizeIntersectErrorToAnError)
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000)
LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauNormalizeNegationFix)
LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization)
LUAU_FASTFLAGVARIABLE(LuauNormalizedBufferIsNotUnknown)
LUAU_FASTFLAGVARIABLE(LuauNormalizeLimitFunctionSet)
LUAU_FASTFLAGVARIABLE(LuauNormalizationCatchMetatableCycles)
LUAU_FASTFLAGVARIABLE(LuauFixNormalizedIntersectionOfNegatedClass)
namespace Luau
{
@ -308,9 +304,7 @@ bool NormalizedType::isUnknown() const
// Otherwise, we can still be unknown!
bool hasAllPrimitives = isPrim(booleans, PrimitiveType::Boolean) && isPrim(nils, PrimitiveType::NilType) && isNumber(numbers) &&
strings.isString() &&
(FFlag::LuauNormalizedBufferIsNotUnknown ? isThread(threads) && isBuffer(buffers)
: isPrim(threads, PrimitiveType::Thread) && isThread(threads));
strings.isString() && isPrim(threads, PrimitiveType::Thread) && isThread(threads);
// Check is class
bool isTopClass = false;
@ -585,7 +579,7 @@ NormalizationResult Normalizer::isIntersectionInhabited(TypeId left, TypeId righ
{
left = follow(left);
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)
{
@ -1691,13 +1685,6 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali
return res;
}
if (FFlag::LuauNormalizeLimitFunctionSet)
{
// Limit based on worst-case expansion of the function unions
if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeUnionLimit))
return NormalizationResult::HitLimits;
}
here.booleans = unionOfBools(here.booleans, there.booleans);
unionClasses(here.classes, there.classes);
@ -1709,7 +1696,6 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali
here.buffers = (get<NeverType>(there.buffers) ? here.buffers : there.buffers);
unionFunctions(here.functions, there.functions);
unionTables(here.tables, there.tables);
return NormalizationResult::True;
}
@ -1749,7 +1735,7 @@ NormalizationResult Normalizer::intersectNormalWithNegationTy(TypeId toNegate, N
return NormalizationResult::True;
}
// See above for an explanation of `ignoreSmallerTyvars`.
// See above for an explaination of `ignoreSmallerTyvars`.
NormalizationResult Normalizer::unionNormalWithTy(
NormalizedType& here,
TypeId there,
@ -2303,7 +2289,7 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th
for (auto nIt = negations.begin(); nIt != negations.end();)
{
if (isSubclass(there, *nIt))
if (FFlag::LuauFixNormalizedIntersectionOfNegatedClass && isSubclass(there, *nIt))
{
// Hitting this block means that the incoming class is a
// subclass of this type, _and_ one of its negations is a
@ -3064,7 +3050,7 @@ NormalizationResult Normalizer::intersectTyvarsWithTy(
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)
{
RecursionCounter _rc(&sharedState->counters.recursionCount);
@ -3082,17 +3068,11 @@ NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const Nor
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
if (here.tables.size() * there.tables.size() >= size_t(FInt::LuauNormalizeIntersectionLimit))
return NormalizationResult::HitLimits;
if (FFlag::LuauNormalizeLimitFunctionSet)
{
if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeIntersectionLimit))
return NormalizationResult::HitLimits;
}
here.booleans = intersectionOfBools(here.booleans, there.booleans);
intersectClasses(here.classes, there.classes);
@ -3228,7 +3208,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
{
TypeId errors = here.errors;
clearNormal(here);
here.errors = FFlag::LuauNormalizeIntersectErrorToAnError && get<ErrorType>(errors) ? errors : there;
here.errors = errors;
}
else if (const PrimitiveType* ptv = get<PrimitiveType>(there))
{
@ -3325,16 +3305,11 @@ NormalizationResult Normalizer::intersectNormalWithTy(
clearNormal(here);
return NormalizationResult::True;
}
else if (FFlag::LuauNormalizeNegatedErrorToAnError && get<ErrorType>(t))
{
// ~error is still an error, so intersecting with the negation is the same as intersecting with a type
TypeId errors = here.errors;
clearNormal(here);
here.errors = FFlag::LuauNormalizeIntersectErrorToAnError && get<ErrorType>(errors) ? errors : t;
}
else if (auto nt = get<NegationType>(t))
{
here.tyvars = std::move(tyvars);
if (FFlag::LuauNormalizeNegationFix)
here.tyvars = std::move(tyvars);
return intersectNormalWithTy(here, nt->ty, seenTablePropPairs, seenSetTypes);
}
else
@ -3364,43 +3339,19 @@ NormalizationResult Normalizer::intersectNormalWithTy(
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)
{
DenseHashSet<TypeId> seen{nullptr};
makeTableShared(ty, seen);
ty = follow(ty);
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
@ -3502,10 +3453,7 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
result.reserve(result.size() + norm.tables.size());
for (auto table : norm.tables)
{
if (FFlag::LuauNormalizationCatchMetatableCycles)
makeTableShared(table);
else
makeTableShared_DEPRECATED(table);
makeTableShared(table);
result.push_back(table);
}
}

View file

@ -454,7 +454,7 @@ SolveResult solveFunctionCall(
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};
const bool occursCheckPassed = u2.unify(*overloadToUse, inferredTy);

View file

@ -4,6 +4,8 @@
#include "Luau/Ast.h"
#include "Luau/Module.h"
LUAU_FASTFLAGVARIABLE(LuauExtendedSimpleRequire)
namespace Luau
{
@ -104,50 +106,96 @@ struct RequireTracer : AstVisitor
{
ModuleInfo moduleContext{currentModuleName};
// seed worklist with require arguments
work.reserve(requireCalls.size());
for (AstExprCall* require : requireCalls)
work.push_back(require->args.data[0]);
// push all dependent expressions to the work stack; note that the vector is modified during traversal
for (size_t i = 0; i < work.size(); ++i)
if (FFlag::LuauExtendedSimpleRequire)
{
if (AstNode* dep = getDependent(work[i]))
work.push_back(dep);
}
// seed worklist with require arguments
work.reserve(requireCalls.size());
// resolve all expressions to a module info
for (size_t i = work.size(); i > 0; --i)
{
AstNode* expr = work[i - 1];
for (AstExprCall* require : requireCalls)
work.push_back(require->args.data[0]);
// when multiple expressions depend on the same one we push it to work queue multiple times
if (result.exprs.contains(expr))
continue;
std::optional<ModuleInfo> info;
if (AstNode* dep = getDependent(expr))
// push all dependent expressions to the work stack; note that the vector is modified during traversal
for (size_t i = 0; i < work.size(); ++i)
{
const ModuleInfo* context = result.exprs.find(dep);
if (AstNode* dep = getDependent(work[i]))
work.push_back(dep);
}
if (context && expr->is<AstExprLocal>())
info = *context; // locals just inherit their dependent context, no resolution required
else if (context && (expr->is<AstExprGroup>() || expr->is<AstTypeGroup>()))
info = *context; // simple group nodes propagate their value
else if (context && (expr->is<AstTypeTypeof>() || expr->is<AstExprTypeAssertion>()))
info = *context; // typeof type annotations will resolve to the typeof content
// resolve all expressions to a module info
for (size_t i = work.size(); i > 0; --i)
{
AstNode* expr = work[i - 1];
// when multiple expressions depend on the same one we push it to work queue multiple times
if (result.exprs.contains(expr))
continue;
std::optional<ModuleInfo> info;
if (AstNode* dep = getDependent(expr))
{
const ModuleInfo* context = result.exprs.find(dep);
if (context && expr->is<AstExprLocal>())
info = *context; // locals just inherit their dependent context, no resolution required
else if (context && (expr->is<AstExprGroup>() || expr->is<AstTypeGroup>()))
info = *context; // simple group nodes propagate their value
else if (context && (expr->is<AstTypeTypeof>() || expr->is<AstExprTypeAssertion>()))
info = *context; // typeof type annotations will resolve to the typeof content
else if (AstExpr* asExpr = expr->asExpr())
info = fileResolver->resolveModule(context, asExpr);
}
else if (AstExpr* asExpr = expr->asExpr())
info = fileResolver->resolveModule(context, asExpr);
}
else if (AstExpr* asExpr = expr->asExpr())
{
info = fileResolver->resolveModule(&moduleContext, asExpr);
}
{
info = fileResolver->resolveModule(&moduleContext, asExpr);
}
if (info)
result.exprs[expr] = std::move(*info);
if (info)
result.exprs[expr] = std::move(*info);
}
}
else
{
// seed worklist with require arguments
work_DEPRECATED.reserve(requireCalls.size());
for (AstExprCall* require : requireCalls)
work_DEPRECATED.push_back(require->args.data[0]);
// push all dependent expressions to the work stack; note that the vector is modified during traversal
for (size_t i = 0; i < work_DEPRECATED.size(); ++i)
if (AstExpr* dep = getDependent_DEPRECATED(work_DEPRECATED[i]))
work_DEPRECATED.push_back(dep);
// resolve all expressions to a module info
for (size_t i = work_DEPRECATED.size(); i > 0; --i)
{
AstExpr* expr = work_DEPRECATED[i - 1];
// when multiple expressions depend on the same one we push it to work queue multiple times
if (result.exprs.contains(expr))
continue;
std::optional<ModuleInfo> info;
if (AstExpr* dep = getDependent_DEPRECATED(expr))
{
const ModuleInfo* context = result.exprs.find(dep);
// locals just inherit their dependent context, no resolution required
if (expr->is<AstExprLocal>())
info = context ? std::optional<ModuleInfo>(*context) : std::nullopt;
else
info = fileResolver->resolveModule(context, expr);
}
else
{
info = fileResolver->resolveModule(&moduleContext, expr);
}
if (info)
result.exprs[expr] = std::move(*info);
}
}
// resolve all requires according to their argument
@ -176,6 +224,7 @@ struct RequireTracer : AstVisitor
ModuleName currentModuleName;
DenseHashMap<AstLocal*, AstExpr*> locals;
std::vector<AstExpr*> work_DEPRECATED;
std::vector<AstNode*> work;
std::vector<AstExprCall*> requireCalls;
};

View file

@ -84,17 +84,6 @@ std::optional<TypeId> Scope::lookupUnrefinedType(DefId def) const
return std::nullopt;
}
std::optional<TypeId> Scope::lookupRValueRefinementType(DefId def) const
{
for (const Scope* current = this; current; current = current->parent.get())
{
if (auto ty = current->rvalueRefinements.find(def))
return *ty;
}
return std::nullopt;
}
std::optional<TypeId> Scope::lookup(DefId def) const
{
for (const Scope* current = this; current; current = current->parent.get())
@ -192,29 +181,6 @@ std::optional<Binding> Scope::linearSearchForBinding(const std::string& name, bo
return std::nullopt;
}
std::optional<std::pair<Symbol, Binding>> Scope::linearSearchForBindingPair(const std::string& name, bool traverseScopeChain) const
{
const Scope* scope = this;
while (scope)
{
for (auto& [n, binding] : scope->bindings)
{
if (n.local && n.local->name == name.c_str())
return {{n, binding}};
else if (n.global.value && n.global == name.c_str())
return {{n, binding}};
}
scope = scope->parent.get();
if (!traverseScopeChain)
break;
}
return std::nullopt;
}
// Updates the `this` scope with the assignments from the `childScope` including ones that doesn't exist in `this`.
void Scope::inheritAssignments(const ScopePtr& childScope)
{

View file

@ -13,7 +13,6 @@ LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256)
LUAU_FASTFLAG(LuauSyntheticErrors)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
namespace Luau
{
@ -96,15 +95,13 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a
return dest.addType(a);
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.genericPacks = a.genericPacks;
clone.magic = a.magic;
clone.tags = a.tags;
clone.argNames = a.argNames;
clone.isCheckedFunction = a.isCheckedFunction;
if (FFlag::LuauDeprecatedAttribute)
clone.isDeprecatedFunction = a.isDeprecatedFunction;
return dest.addType(std::move(clone));
}
else if constexpr (std::is_same_v<T, TableType>)

View file

@ -22,7 +22,7 @@
#include <algorithm>
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
LUAU_FASTFLAGVARIABLE(LuauSubtypingStopAtNormFail)
LUAU_FASTFLAGVARIABLE(LuauSubtypingFixTailPack)
namespace Luau
{
@ -416,14 +416,6 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope
SubtypingResult result = isCovariantWith(env, subTy, superTy, scope);
if (FFlag::LuauSubtypingStopAtNormFail && result.normalizationTooComplex)
{
if (result.isCacheable)
resultCache[{subTy, superTy}] = result;
return result;
}
for (const auto& [subTy, bounds] : env.mappedGenerics)
{
const auto& lb = bounds.lowerBound;
@ -601,12 +593,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
if (!result.isSubtype && !result.normalizationTooComplex)
{
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope);
if (FFlag::LuauSubtypingStopAtNormFail && semantic.normalizationTooComplex)
{
result = semantic;
}
else if (semantic.isSubtype)
if (semantic.isSubtype)
{
semantic.reasoning.clear();
result = semantic;
@ -621,12 +608,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
if (!result.isSubtype && !result.normalizationTooComplex)
{
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope);
if (FFlag::LuauSubtypingStopAtNormFail && semantic.normalizationTooComplex)
{
result = semantic;
}
else if (semantic.isSubtype)
if (semantic.isSubtype)
{
// Clear the semantic reasoning, as any reasonings within
// potentially contain invalid paths.
@ -772,8 +754,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
// Match head types pairwise
for (size_t i = 0; i < headSize; ++i)
results.push_back(isCovariantWith(env, subHead[i], superHead[i], scope).withBothComponent(TypePath::Index{i, TypePath::Index::Variant::Pack})
);
results.push_back(isCovariantWith(env, subHead[i], superHead[i], scope).withBothComponent(TypePath::Index{i}));
// Handle mismatched head sizes
@ -786,7 +767,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
for (size_t i = headSize; i < superHead.size(); ++i)
results.push_back(isCovariantWith(env, vt->ty, superHead[i], scope)
.withSubPath(TypePath::PathBuilder().tail().variadic().build())
.withSuperComponent(TypePath::Index{i, TypePath::Index::Variant::Pack}));
.withSuperComponent(TypePath::Index{i}));
}
else if (auto gt = get<GenericTypePack>(*subTail))
{
@ -840,7 +821,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
{
for (size_t i = headSize; i < subHead.size(); ++i)
results.push_back(isCovariantWith(env, subHead[i], vt->ty, scope)
.withSubComponent(TypePath::Index{i, TypePath::Index::Variant::Pack})
.withSubComponent(TypePath::Index{i})
.withSuperPath(TypePath::PathBuilder().tail().variadic().build()));
}
else if (auto gt = get<GenericTypePack>(*superTail))
@ -878,7 +859,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
else
return SubtypingResult{false}
.withSuperComponent(TypePath::PackField::Tail)
.withError({scope->location, UnexpectedTypePackInSubtyping{*superTail}});
.withError({scope->location, UnexpectedTypePackInSubtyping{FFlag::LuauSubtypingFixTailPack ? *superTail : *subTail}});
}
else
return {false};
@ -1101,10 +1082,6 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
for (TypeId ty : superUnion)
{
SubtypingResult next = isCovariantWith(env, subTy, ty, scope);
if (FFlag::LuauSubtypingStopAtNormFail && next.normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
if (next.isSubtype)
return SubtypingResult{true};
}
@ -1123,13 +1100,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Unio
std::vector<SubtypingResult> subtypings;
size_t i = 0;
for (TypeId ty : subUnion)
{
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Union}));
if (FFlag::LuauSubtypingStopAtNormFail && subtypings.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++}));
return SubtypingResult::all(subtypings);
}
@ -1139,13 +1110,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
std::vector<SubtypingResult> subtypings;
size_t i = 0;
for (TypeId ty : superIntersection)
{
subtypings.push_back(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
if (FFlag::LuauSubtypingStopAtNormFail && subtypings.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
subtypings.push_back(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++}));
return SubtypingResult::all(subtypings);
}
@ -1155,13 +1120,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Inte
std::vector<SubtypingResult> subtypings;
size_t i = 0;
for (TypeId ty : subIntersection)
{
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
if (FFlag::LuauSubtypingStopAtNormFail && subtypings.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++}));
return SubtypingResult::any(subtypings);
}
@ -1451,7 +1410,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Meta
// of the supertype table.
//
// 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
// compatible properties/shapes. We'll revisit this later when we have a
// better understanding of how important this is.
@ -1801,12 +1760,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
{
results.emplace_back();
for (TypeId superTy : superTypes)
{
results.back().orElse(isCovariantWith(env, subTy, superTy, scope));
if (FFlag::LuauSubtypingStopAtNormFail && results.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
}
return SubtypingResult::all(results);

View file

@ -13,10 +13,9 @@
#include "Luau/TypeUtils.h"
#include "Luau/Unifier2.h"
LUAU_FASTFLAGVARIABLE(LuauDontInPlaceMutateTableType)
LUAU_FASTFLAGVARIABLE(LuauAllowNonSharedTableTypesInLiteral)
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceUpcast)
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAGVARIABLE(LuauBidirectionalFailsafe)
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceElideAssert)
namespace Luau
{
@ -139,13 +138,14 @@ TypeId matchLiteralType(
* things like replace explicit named properties with indexers as required
* by the expected type.
*/
if (!isLiteral(expr))
{
if (FFlag::LuauBidirectionalInferenceUpcast)
{
auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope);
return result.isSubtype ? expectedType : exprType;
return result.isSubtype
? expectedType
: exprType;
}
else
return exprType;
@ -154,23 +154,11 @@ TypeId matchLiteralType(
expectedType = follow(expectedType);
exprType = follow(exprType);
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
if (get<AnyType>(expectedType) || get<UnknownType>(expectedType))
{
// 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;
// "Narrowing" to unknown or any is not going to do anything useful.
return exprType;
}
else
{
if (get<AnyType>(expectedType) || get<UnknownType>(expectedType))
{
// "Narrowing" to unknown or any is not going to do anything useful.
return exprType;
}
}
if (expr->is<AstExprConstantString>())
{
@ -252,15 +240,6 @@ TypeId matchLiteralType(
if (auto exprTable = expr->as<AstExprTable>())
{
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);
const TableType* expectedTableTy = get<TableType>(expectedType);
@ -287,9 +266,6 @@ TypeId matchLiteralType(
DenseHashSet<AstExprConstantString*> keysToDelete{nullptr};
DenseHashSet<TypeId> indexerKeyTypes{nullptr};
DenseHashSet<TypeId> indexerValueTypes{nullptr};
for (const AstExprTable::Item& item : exprTable->items)
{
if (isRecord(item))
@ -297,20 +273,23 @@ TypeId matchLiteralType(
const AstArray<char>& s = item.key->as<AstExprConstantString>()->value;
std::string keyStr{s.data, s.data + s.size};
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());
Property& prop = it->second;
// If we encounter a duplcate property, we may have already
// set it to be read-only. If that's the case, the only thing
// that will definitely crash is trying to access a write
// only property.
LUAU_ASSERT(!prop.isWriteOnly());
if (FFlag::LuauAllowNonSharedTableTypesInLiteral)
{
// If we encounter a duplcate property, we may have already
// set it to be read-only. If that's the case, the only thing
// that will definitely crash is trying to access a write
// only property.
LUAU_ASSERT(!prop.isWriteOnly());
}
else
{
// Table literals always initially result in shared read-write types
LUAU_ASSERT(prop.isShared());
}
TypeId propTy = *prop.readTy;
auto it2 = expectedTableTy->props.find(keyStr);
@ -338,20 +317,15 @@ TypeId matchLiteralType(
toBlock
);
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
{
indexerKeyTypes.insert(arena->addType(SingletonType{StringSingleton{keyStr}}));
indexerValueTypes.insert(matchedType);
}
if (tableTy->indexer)
unifier->unify(matchedType, tableTy->indexer->indexResultType);
else
{
if (tableTy->indexer)
unifier->unify(matchedType, tableTy->indexer->indexResultType);
else
tableTy->indexer = TableIndexer{expectedTableTy->indexer->indexType, matchedType};
}
tableTy->indexer = TableIndexer{expectedTableTy->indexer->indexType, matchedType};
keysToDelete.insert(item.key->as<AstExprConstantString>());
if (FFlag::LuauDontInPlaceMutateTableType)
keysToDelete.insert(item.key->as<AstExprConstantString>());
else
tableTy->props.erase(keyStr);
}
@ -407,16 +381,10 @@ TypeId matchLiteralType(
LUAU_ASSERT(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)
{
if (!FFlag::LuauBidirectionalInferenceCollectIndexerTypes || !FFlag::LuauBidirectionalInferenceElideAssert)
LUAU_ASSERT(tableTy->indexer);
LUAU_ASSERT(tableTy->indexer);
if (expectedTableTy->indexer)
{
@ -437,18 +405,9 @@ TypeId matchLiteralType(
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 (tableTy->indexer->indexResultType == *propTy)
tableTy->indexer->indexResultType = matchedType;
}
// if the index result type is the prop type, we can replace it with the matched type here.
if (tableTy->indexer->indexResultType == *propTy)
tableTy->indexer->indexResultType = matchedType;
}
}
else if (item.kind == AstExprTable::Item::General)
@ -470,23 +429,19 @@ TypeId matchLiteralType(
// 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)
(*astExpectedTypes)[item.key] = expectedTableTy->indexer->indexType;
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
{
indexerKeyTypes.insert(tKey);
indexerValueTypes.insert(tProp);
}
}
else
LUAU_ASSERT(!"Unexpected");
}
for (const auto& key : keysToDelete)
if (FFlag::LuauDontInPlaceMutateTableType)
{
const AstArray<char>& s = key->value;
std::string keyStr{s.data, s.data + s.size};
tableTy->props.erase(keyStr);
for (const auto& key : keysToDelete)
{
const AstArray<char>& s = key->value;
std::string keyStr{s.data, s.data + s.size};
tableTy->props.erase(keyStr);
}
}
// Keys that the expectedType says we should have, but that aren't
@ -538,39 +493,9 @@ TypeId matchLiteralType(
// have one too.
// TODO: If the expected table also has an indexer, we might want to
// push the expected indexer's types into it.
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes && expectedTableTy->indexer)
if (expectedTableTy->indexer && !tableTy->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)
{
tableTy->indexer = expectedTableTy->indexer;
}
tableTy->indexer = expectedTableTy->indexer;
}
}

View file

@ -10,12 +10,10 @@
#include <limits>
#include <math.h>
LUAU_FASTFLAG(LuauStoreCSTData2)
LUAU_FASTFLAG(LuauStoreCSTData)
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
LUAU_FASTFLAG(LuauParseOptionalAsNode2)
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
namespace
{
@ -168,7 +166,7 @@ struct StringWriter : Writer
void symbol(std::string_view s) override
{
if (FFlag::LuauStoreCSTData2)
if (FFlag::LuauStoreCSTData)
{
write(s);
}
@ -258,7 +256,7 @@ public:
first = !first;
else
{
if (FFlag::LuauStoreCSTData2 && commaPosition)
if (FFlag::LuauStoreCSTData && commaPosition)
{
writer.advance(*commaPosition);
commaPosition++;
@ -273,43 +271,6 @@ private:
const Position* commaPosition;
};
class ArgNameInserter
{
public:
ArgNameInserter(Writer& w, AstArray<std::optional<AstArgumentName>> names, AstArray<std::optional<Position>> colonPositions)
: writer(w)
, names(names)
, colonPositions(colonPositions)
{
}
void operator()()
{
if (idx < names.size)
{
const auto name = names.data[idx];
if (name.has_value())
{
writer.advance(name->second.begin);
writer.identifier(name->first.value);
if (idx < colonPositions.size)
{
LUAU_ASSERT(colonPositions.data[idx].has_value());
writer.advance(*colonPositions.data[idx]);
}
writer.symbol(":");
}
}
idx++;
}
private:
Writer& writer;
AstArray<std::optional<AstArgumentName>> names;
AstArray<std::optional<Position>> colonPositions;
size_t idx = 0;
};
struct Printer_DEPRECATED
{
explicit Printer_DEPRECATED(Writer& writer)
@ -1230,18 +1191,9 @@ struct Printer_DEPRECATED
AstType* l = a->types.data[0];
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>();
if (lta && lta->name == "nil")
std::swap(l, r);
}
auto lta = l->as<AstTypeReference>();
if (lta && lta->name == "nil")
std::swap(l, r);
// it's still possible that we had a (T | U) or (T | nil) and not (nil | T)
auto rta = r->as<AstTypeReference>();
@ -1264,15 +1216,6 @@ struct Printer_DEPRECATED
for (size_t i = 0; i < a->types.size; ++i)
{
if (FFlag::LuauParseOptionalAsNode2)
{
if (a->types.data[i]->is<AstTypeOptional>())
{
writer.symbol("?");
continue;
}
}
if (i > 0)
{
writer.maybeSpace(a->types.data[i]->location.begin, 2);
@ -1369,7 +1312,7 @@ struct Printer
}
}
void visualizeTypePackAnnotation(AstTypePack& annotation, bool forVarArg)
void visualizeTypePackAnnotation(const AstTypePack& annotation, bool forVarArg)
{
advance(annotation.location.begin);
if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>())
@ -1379,22 +1322,15 @@ struct Printer
visualizeTypeAnnotation(*variadicTp->variadicType);
}
else if (AstTypePackGeneric* genericTp = annotation.as<AstTypePackGeneric>())
else if (const AstTypePackGeneric* genericTp = annotation.as<AstTypePackGeneric>())
{
writer.symbol(genericTp->genericName.value);
if (const auto cstNode = lookupCstNode<CstTypePackGeneric>(genericTp))
advance(cstNode->ellipsisPosition);
writer.symbol("...");
}
else if (AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>())
else if (const AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>())
{
LUAU_ASSERT(!forVarArg);
if (const auto cstNode = lookupCstNode<CstTypePackExplicit>(explicitTp))
visualizeTypeList(
explicitTp->typeList, true, cstNode->openParenthesesPosition, cstNode->closeParenthesesPosition, cstNode->commaPositions
);
else
visualizeTypeList(explicitTp->typeList, true);
visualizeTypeList(explicitTp->typeList, true);
}
else
{
@ -1402,37 +1338,19 @@ struct Printer
}
}
void visualizeNamedTypeList(
const AstTypeList& list,
bool unconditionallyParenthesize,
std::optional<Position> openParenthesesPosition,
std::optional<Position> closeParenthesesPosition,
AstArray<Position> commaPositions,
AstArray<std::optional<AstArgumentName>> argNames,
AstArray<std::optional<Position>> argNamesColonPositions
)
void visualizeTypeList(const AstTypeList& list, bool unconditionallyParenthesize)
{
size_t typeCount = list.types.size + (list.tailType != nullptr ? 1 : 0);
if (typeCount == 0)
{
if (openParenthesesPosition)
advance(*openParenthesesPosition);
writer.symbol("(");
if (closeParenthesesPosition)
advance(*closeParenthesesPosition);
writer.symbol(")");
}
else if (typeCount == 1)
{
bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>());
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
{
if (openParenthesesPosition)
advance(*openParenthesesPosition);
writer.symbol("(");
}
ArgNameInserter(writer, argNames, argNamesColonPositions)();
// Only variadic tail
if (list.types.size == 0)
@ -1445,50 +1363,33 @@ struct Printer
}
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
{
if (closeParenthesesPosition)
advance(*closeParenthesesPosition);
writer.symbol(")");
}
}
else
{
if (openParenthesesPosition)
advance(*openParenthesesPosition);
writer.symbol("(");
CommaSeparatorInserter comma(writer, commaPositions.size > 0 ? commaPositions.begin() : nullptr);
ArgNameInserter argName(writer, argNames, argNamesColonPositions);
bool first = true;
for (const auto& el : list.types)
{
comma();
argName();
if (first)
first = false;
else
writer.symbol(",");
visualizeTypeAnnotation(*el);
}
if (list.tailType)
{
comma();
writer.symbol(",");
visualizeTypePackAnnotation(*list.tailType, false);
}
if (closeParenthesesPosition)
advance(*closeParenthesesPosition);
writer.symbol(")");
}
}
void visualizeTypeList(
const AstTypeList& list,
bool unconditionallyParenthesize,
std::optional<Position> openParenthesesPosition = std::nullopt,
std::optional<Position> closeParenthesesPosition = std::nullopt,
AstArray<Position> commaPositions = {}
)
{
visualizeNamedTypeList(list, unconditionallyParenthesize, openParenthesesPosition, closeParenthesesPosition, commaPositions, {}, {});
}
bool isIntegerish(double d)
{
if (d <= std::numeric_limits<int>::max() && d >= std::numeric_limits<int>::min())
@ -1499,14 +1400,13 @@ struct Printer
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>())
{
writer.symbol("(");
visualize(*a->expr);
advanceBefore(a->location.end, 1);
advance(Position{a->location.end.line, a->location.end.column - 1});
writer.symbol(")");
}
else if (expr.is<AstExprConstantNil>())
@ -1634,17 +1534,6 @@ struct Printer
}
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");
visualizeFunctionBody(*a);
}
@ -1886,18 +1775,9 @@ struct Printer
writer.advance(newPos);
}
void advanceBefore(const Position& newPos, unsigned int tokenLength)
{
if (newPos.column >= tokenLength)
advance(Position{newPos.line, newPos.column - tokenLength});
else
advance(newPos);
}
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>())
{
@ -1937,8 +1817,8 @@ struct Printer
visualizeBlock(*a->body);
if (const auto cstNode = lookupCstNode<CstStatRepeat>(a))
writer.advance(cstNode->untilPosition);
else
advanceBefore(a->condition->location.begin, 6);
else if (a->condition->location.begin.column > 5)
writer.advance(Position{a->condition->location.begin.line, a->condition->location.begin.column - 6});
writer.keyword("until");
visualize(*a->condition);
}
@ -2134,36 +2014,13 @@ struct Printer
}
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");
visualize(*a->name);
visualizeFunctionBody(*a->func);
}
else if (const auto& a = program.as<AstStatLocalFunction>())
{
for (const auto& attribute : a->func->attributes)
visualizeAttribute(*attribute);
const auto cstNode = lookupCstNode<CstStatLocalFunction>(a);
if (FFlag::LuauFixFunctionWithAttributesStartLocation)
{
if (cstNode)
advance(cstNode->localKeywordPosition);
}
else
{
advance(a->location.begin);
}
writer.keyword("local");
@ -2264,20 +2121,7 @@ struct Printer
{
if (writeTypes)
{
const auto cstNode = lookupCstNode<CstStatTypeFunction>(t);
if (t->exported)
writer.keyword("export");
if (cstNode)
advance(cstNode->typeKeywordPosition);
else
writer.space();
writer.keyword("type");
if (cstNode)
advance(cstNode->functionKeywordPosition);
else
writer.space();
writer.keyword("function");
advance(t->nameLocation.begin);
writer.keyword("type function");
writer.identifier(t->name.value);
visualizeFunctionBody(*t->body);
}
@ -2307,23 +2151,17 @@ struct Printer
if (program.hasSemicolon)
{
if (FFlag::LuauStoreCSTData2)
advanceBefore(program.location.end, 1);
if (FFlag::LuauStoreCSTData)
advance(Position{program.location.end.line, program.location.end.column - 1});
writer.symbol(";");
}
}
void visualizeFunctionBody(AstExprFunction& func)
{
const auto cstNode = lookupCstNode<CstExprFunction>(&func);
// TODO(CLI-139347): need to handle return type (incl. parentheses of return type)
if (func.generics.size > 0 || func.genericPacks.size > 0)
{
CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr);
if (cstNode)
advance(cstNode->openGenericsPosition);
CommaSeparatorInserter comma(writer);
writer.symbol("<");
for (const auto& o : func.generics)
{
@ -2338,19 +2176,13 @@ struct Printer
writer.advance(o->location.begin);
writer.identifier(o->name.value);
if (const auto* genericTypePackCstNode = lookupCstNode<CstGenericTypePack>(o))
advance(genericTypePackCstNode->ellipsisPosition);
writer.symbol("...");
}
if (cstNode)
advance(cstNode->closeGenericsPosition);
writer.symbol(">");
}
if (func.argLocation)
advance(func.argLocation->begin);
writer.symbol("(");
CommaSeparatorInserter comma(writer, cstNode ? cstNode->argsCommaPositions.begin() : nullptr);
CommaSeparatorInserter comma(writer);
for (size_t i = 0; i < func.args.size; ++i)
{
@ -2380,14 +2212,10 @@ struct Printer
}
}
if (func.argLocation)
advanceBefore(func.argLocation->end, 1);
writer.symbol(")");
if (writeTypes && func.returnAnnotation)
{
if (cstNode)
advance(cstNode->returnSpecifierPosition);
writer.symbol(":");
writer.space();
@ -2473,23 +2301,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)
{
advance(typeAnnotation.location.begin);
@ -2529,13 +2340,9 @@ struct Printer
}
else if (const auto& a = typeAnnotation.as<AstTypeFunction>())
{
const auto cstNode = lookupCstNode<CstTypeFunction>(a);
if (a->generics.size > 0 || a->genericPacks.size > 0)
{
CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr);
if (cstNode)
advance(cstNode->openGenericsPosition);
CommaSeparatorInserter comma(writer);
writer.symbol("<");
for (const auto& o : a->generics)
{
@ -2550,29 +2357,15 @@ struct Printer
writer.advance(o->location.begin);
writer.identifier(o->name.value);
if (const auto* genericTypePackCstNode = lookupCstNode<CstGenericTypePack>(o))
advance(genericTypePackCstNode->ellipsisPosition);
writer.symbol("...");
}
if (cstNode)
advance(cstNode->closeGenericsPosition);
writer.symbol(">");
}
{
visualizeNamedTypeList(
a->argTypes,
true,
cstNode ? std::make_optional(cstNode->openArgsPosition) : std::nullopt,
cstNode ? std::make_optional(cstNode->closeArgsPosition) : std::nullopt,
cstNode ? cstNode->argumentsCommaPositions : Luau::AstArray<Position>{},
a->argNames,
cstNode ? cstNode->argumentNameColonPositions : Luau::AstArray<std::optional<Position>>{}
);
visualizeTypeList(a->argTypes, true);
}
if (cstNode)
advance(cstNode->returnArrowPosition);
writer.symbol("->");
visualizeTypeList(a->returnTypes, true);
}
@ -2734,25 +2527,14 @@ struct Printer
}
else if (const auto& a = typeAnnotation.as<AstTypeUnion>())
{
const auto cstNode = lookupCstNode<CstTypeUnion>(a);
if (!cstNode && a->types.size == 2)
if (a->types.size == 2)
{
AstType* l = a->types.data[0];
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>();
if (lta && lta->name == "nil")
std::swap(l, r);
}
auto lta = l->as<AstTypeReference>();
if (lta && lta->name == "nil")
std::swap(l, r);
// it's still possible that we had a (T | U) or (T | nil) and not (nil | T)
auto rta = r->as<AstTypeReference>();
@ -2773,39 +2555,15 @@ 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)
{
if (FFlag::LuauParseOptionalAsNode2)
{
if (const auto optional = a->types.data[i]->as<AstTypeOptional>())
{
advance(optional->location.begin);
writer.symbol("?");
continue;
}
}
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("|");
}
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)
writer.symbol("(");
@ -2818,27 +2576,15 @@ struct Printer
}
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)
{
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("&");
}
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)
writer.symbol("(");
@ -2853,7 +2599,7 @@ struct Printer
{
writer.symbol("(");
visualizeTypeAnnotation(*a->type);
advanceBefore(a->location.end, 1);
advance(Position{a->location.end.line, a->location.end.column - 1});
writer.symbol(")");
}
else if (const auto& a = typeAnnotation.as<AstTypeSingletonBool>())
@ -2887,7 +2633,7 @@ std::string toString(AstNode* node)
StringWriter writer;
writer.pos = node->location.begin;
if (FFlag::LuauStoreCSTData2)
if (FFlag::LuauStoreCSTData)
{
Printer printer(writer, CstNodeMap{nullptr});
printer.writeTypes = true;
@ -2923,7 +2669,7 @@ void dump(AstNode* node)
std::string transpile(AstStatBlock& block, const CstNodeMap& cstNodeMap)
{
StringWriter writer;
if (FFlag::LuauStoreCSTData2)
if (FFlag::LuauStoreCSTData)
{
Printer(writer, cstNodeMap).visualizeBlock(block);
}
@ -2937,7 +2683,7 @@ std::string transpile(AstStatBlock& block, const CstNodeMap& cstNodeMap)
std::string transpileWithTypes(AstStatBlock& block, const CstNodeMap& cstNodeMap)
{
StringWriter writer;
if (FFlag::LuauStoreCSTData2)
if (FFlag::LuauStoreCSTData)
{
Printer printer(writer, cstNodeMap);
printer.writeTypes = true;

View file

@ -407,6 +407,41 @@ PendingTypePack* TxnLog::changeLevel(TypePackId tp, TypeLevel newLevel)
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)
{
LUAU_ASSERT(get<TableType>(ty));

View file

@ -630,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(
std::vector<TypeId> generics,
std::vector<TypePackId> genericPacks,
@ -666,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(

View file

@ -13,8 +13,6 @@
#include <string>
LUAU_FASTFLAG(LuauStoreCSTData2)
static char* allocateString(Luau::Allocator& allocator, std::string_view contents)
{
char* result = (char*)allocator.allocate(contents.size() + 1);
@ -307,8 +305,7 @@ public:
std::optional<AstArgumentName>* arg = &argNames.data[i++];
if (el)
new (arg)
std::optional<AstArgumentName>(AstArgumentName(AstName(el->name.c_str()), FFlag::LuauStoreCSTData2 ? Location() : el->location));
new (arg) std::optional<AstArgumentName>(AstArgumentName(AstName(el->name.c_str()), el->location));
else
new (arg) std::optional<AstArgumentName>();
}

View file

@ -26,14 +26,10 @@
#include "Luau/VisitType.h"
#include <algorithm>
#include <sstream>
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats)
namespace Luau
{
@ -1205,8 +1201,7 @@ void TypeChecker2::visit(AstStatTypeAlias* stat)
void TypeChecker2::visit(AstStatTypeFunction* stat)
{
if (FFlag::LuauUserTypeFunTypecheck)
visit(stat->body);
// TODO: add type checking for user-defined type functions
}
void TypeChecker2::visit(AstTypeList types)
@ -2230,21 +2225,10 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey)
return builtinTypes->numberType;
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(rightType, builtinTypes->stringType, expr->right->location);
}
testIsSubtype(leftType, builtinTypes->stringType, expr->left->location);
testIsSubtype(rightType, builtinTypes->stringType, expr->right->location);
return builtinTypes->stringType;
}
case AstExprBinary::Op::CompareGe:
case AstExprBinary::Op::CompareGt:
case AstExprBinary::Op::CompareLe:
@ -2717,61 +2701,20 @@ Reasonings TypeChecker2::explainReasonings_(TID subTy, TID superTy, Location loc
if (!subLeafTy && !superLeafTy && !subLeafTp && !superLeafTp)
ice->ice("Subtyping test returned a reasoning where one path ends at a type and the other ends at a pack.", location);
if (FFlag::LuauImproveTypePathsInErrors)
{
std::string relation = "a subtype of";
if (reasoning.variance == SubtypingVariance::Invariant)
relation = "exactly";
else if (reasoning.variance == SubtypingVariance::Contravariant)
relation = "a supertype of";
std::string relation = "a subtype of";
if (reasoning.variance == SubtypingVariance::Invariant)
relation = "exactly";
else if (reasoning.variance == SubtypingVariance::Contravariant)
relation = "a supertype of";
std::string subLeafAsString = toString(subLeaf);
// if the string is empty, it must be an empty type pack
if (subLeafAsString.empty())
subLeafAsString = "()";
std::string superLeafAsString = toString(superLeaf);
// if the string is empty, it must be an empty type pack
if (superLeafAsString.empty())
superLeafAsString = "()";
std::stringstream baseReasonBuilder;
baseReasonBuilder << "`" << subLeafAsString << "` is not " << relation << " `" << superLeafAsString << "`";
std::string baseReason = baseReasonBuilder.str();
std::stringstream reason;
if (reasoning.subPath == reasoning.superPath)
reason << toStringHuman(reasoning.subPath) << "`" << subLeafAsString << "` in the former type and `" << superLeafAsString
<< "` in the latter type, and " << baseReason;
else if (!reasoning.subPath.empty() && !reasoning.superPath.empty())
reason << toStringHuman(reasoning.subPath) << "`" << subLeafAsString << "` and " << toStringHuman(reasoning.superPath) << "`"
<< superLeafAsString << "`, and " << baseReason;
else if (!reasoning.subPath.empty())
reason << toStringHuman(reasoning.subPath) << "`" << subLeafAsString << "`, which is not " << relation << " `" << superLeafAsString
<< "`";
else
reason << toStringHuman(reasoning.superPath) << "`" << superLeafAsString << "`, and " << baseReason;
reasons.push_back(reason.str());
}
std::string reason;
if (reasoning.subPath == reasoning.superPath)
reason = "at " + toString(reasoning.subPath) + ", " + toString(subLeaf) + " is not " + relation + " " + toString(superLeaf);
else
{
std::string relation = "a subtype of";
if (reasoning.variance == SubtypingVariance::Invariant)
relation = "exactly";
else if (reasoning.variance == SubtypingVariance::Contravariant)
relation = "a supertype of";
reason = "type " + toString(subTy) + toString(reasoning.subPath, /* prefixDot */ true) + " (" + toString(subLeaf) + ") is not " +
relation + " " + toString(superTy) + toString(reasoning.superPath, /* prefixDot */ true) + " (" + toString(superLeaf) + ")";
std::string reason;
if (reasoning.subPath == reasoning.superPath)
reason = "at " + toString(reasoning.subPath) + ", " + toString(subLeaf) + " is not " + relation + " " + toString(superLeaf);
else
reason = "type " + toString(subTy) + toString(reasoning.subPath, /* prefixDot */ true) + " (" + toString(subLeaf) + ") is not " +
relation + " " + toString(superTy) + toString(reasoning.superPath, /* prefixDot */ true) + " (" + toString(superLeaf) + ")";
reasons.push_back(reason);
}
reasons.push_back(reason);
// if we haven't already proved this isn't suppressing, we have to keep checking.
if (suppressed)

File diff suppressed because it is too large Load diff

View file

@ -13,7 +13,11 @@
#include <set>
#include <vector>
LUAU_FASTFLAGVARIABLE(LuauTypeFunFixHydratedClasses)
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
LUAU_FASTFLAGVARIABLE(LuauTypeFunSingletonEquality)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypeofReturnsType)
LUAU_FASTFLAGVARIABLE(LuauTypeFunPrintFix)
LUAU_FASTFLAGVARIABLE(LuauTypeFunReadWriteParents)
namespace Luau
@ -1613,8 +1617,11 @@ void registerTypeUserData(lua_State* L)
// Create and register metatable for type userdata
luaL_newmetatable(L, "type");
lua_pushstring(L, "type");
lua_setfield(L, -2, "__type");
if (FFlag::LuauUserTypeFunTypeofReturnsType)
{
lua_pushstring(L, "type");
lua_setfield(L, -2, "__type");
}
// Protect metatable from being changed
lua_pushstring(L, "The metatable is locked");
@ -1655,7 +1662,10 @@ static int print(lua_State* L)
const char* s = luaL_tolstring(L, i, &l); // convert to string using __tostring et al
if (i > 1)
{
result.append(1, '\t');
if (FFlag::LuauTypeFunPrintFix)
result.append(1, '\t');
else
result.append('\t', 1);
}
result.append(s, l);
lua_pop(L, 1);
@ -1748,14 +1758,14 @@ bool areEqual(SeenSet& seen, const TypeFunctionSingletonType& lhs, const TypeFun
{
const TypeFunctionBooleanSingleton* lp = get<TypeFunctionBooleanSingleton>(&lhs);
const TypeFunctionBooleanSingleton* rp = get<TypeFunctionBooleanSingleton>(&rhs);
const TypeFunctionBooleanSingleton* rp = get<TypeFunctionBooleanSingleton>(FFlag::LuauTypeFunSingletonEquality ? &rhs : &lhs);
if (lp && rp)
return lp->value == rp->value;
}
{
const TypeFunctionStringSingleton* lp = get<TypeFunctionStringSingleton>(&lhs);
const TypeFunctionStringSingleton* rp = get<TypeFunctionStringSingleton>(&rhs);
const TypeFunctionStringSingleton* rp = get<TypeFunctionStringSingleton>(FFlag::LuauTypeFunSingletonEquality ? &rhs : &lhs);
if (lp && rp)
return lp->value == rp->value;
}
@ -1908,7 +1918,10 @@ bool areEqual(SeenSet& seen, const TypeFunctionClassType& lhs, const TypeFunctio
if (seenSetContains(seen, &lhs, &rhs))
return true;
return lhs.classTy == rhs.classTy;
if (FFlag::LuauTypeFunFixHydratedClasses)
return lhs.classTy == rhs.classTy;
else
return lhs.name_DEPRECATED == rhs.name_DEPRECATED;
}
bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType& rhs)

View file

@ -19,6 +19,7 @@
// used to control the recursion limit of any operations done by user-defined type functions
// currently, controls serialization, deserialization, and `type.copy`
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000);
LUAU_FASTFLAG(LuauTypeFunFixHydratedClasses)
LUAU_FASTFLAG(LuauTypeFunReadWriteParents)
namespace Luau
@ -208,11 +209,19 @@ private:
}
else if (auto c = get<ClassType>(ty))
{
// Since there aren't any new class types being created in type functions, we will deserialize by using a direct reference to the original
// class
target = typeFunctionRuntime->typeArena.allocate(
TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty}
);
if (FFlag::LuauTypeFunFixHydratedClasses)
{
// Since there aren't any new class types being created in type functions, we will deserialize by using a direct reference to the
// original class
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty});
}
else
{
state->classesSerialized_DEPRECATED[c->name] = ty;
target = typeFunctionRuntime->typeArena.allocate(
TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, /* classTy */ nullptr, c->name}
);
}
}
else if (auto g = get<GenericType>(ty))
{
@ -704,7 +713,17 @@ private:
}
else if (auto c = get<TypeFunctionClassType>(ty))
{
target = c->classTy;
if (FFlag::LuauTypeFunFixHydratedClasses)
{
target = c->classTy;
}
else
{
if (auto result = state->classesSerialized_DEPRECATED.find(c->name_DEPRECATED))
target = *result;
else
state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious class type is being deserialized");
}
}
else if (auto g = get<TypeFunctionGenericType>(ty))
{

View file

@ -34,7 +34,8 @@ LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification)
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
LUAU_FASTFLAG(LuauModuleHoldsAstRoot)
namespace Luau
{
@ -255,7 +256,8 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
currentModule->type = module.type;
currentModule->allocator = module.allocator;
currentModule->names = module.names;
currentModule->root = module.root;
if (FFlag::LuauModuleHoldsAstRoot)
currentModule->root = module.root;
iceHandler->moduleName = module.name;
normalizer.arena = &currentModule->internalTypes;
@ -1656,10 +1658,7 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea
FreeType* ftv = getMutable<FreeType>(ty);
LUAU_ASSERT(ftv);
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->typeAliasNameLocations[name] = typealias.nameLocation;
@ -1704,10 +1703,7 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareClass& de
TypeId metaTy = addType(TableType{TableState::Sealed, scope->level});
ctv->metatable = metaTy;
if (FFlag::LuauRetainDefinitionAliasLocations)
scope->exportedTypeBindings[className] = TypeFun{{}, classTy, declaredClass.location};
else
scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
}
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass)
@ -5725,10 +5721,6 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno
TypeId ty = checkExpr(scope, *typeOf->expr).type;
return ty;
}
else if (annotation.is<AstTypeOptional>())
{
return builtinTypes->nilType;
}
else if (const auto& un = annotation.as<AstTypeUnion>())
{
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)

View file

@ -14,8 +14,7 @@
#include <type_traits>
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAGVARIABLE(LuauDisableNewSolverAssertsInMixedMode)
LUAU_FASTFLAGVARIABLE(LuauDisableNewSolverAssertsInMixedMode);
// Maximum number of steps to follow when traversing a path. May not always
// equate to the number of components in a path, depending on the traversal
// logic.
@ -639,247 +638,6 @@ std::string toString(const TypePath::Path& path, bool prefixDot)
return result.str();
}
std::string toStringHuman(const TypePath::Path& path)
{
LUAU_ASSERT(FFlag::LuauSolverV2);
enum class State
{
Initial,
Normal,
Property,
PendingIs,
PendingAs,
PendingWhich,
};
std::stringstream result;
State state = State::Initial;
bool last = false;
auto strComponent = [&](auto&& c)
{
using T = std::decay_t<decltype(c)>;
if constexpr (std::is_same_v<T, TypePath::Property>)
{
if (state == State::PendingIs)
result << ", ";
switch (state)
{
case State::Initial:
case State::PendingIs:
if (c.isRead)
result << "accessing `";
else
result << "writing to `";
break;
case State::Property:
// if the previous state was a property, then we're doing a sequence of indexing
result << '.';
break;
default:
break;
}
result << c.name;
state = State::Property;
}
else if constexpr (std::is_same_v<T, TypePath::Index>)
{
size_t humanIndex = c.index + 1;
if (state == State::Initial && !last)
result << "in" << ' ';
else if (state == State::PendingIs)
result << ' ' << "has" << ' ';
else if (state == State::Property)
result << '`' << ' ' << "has" << ' ';
result << "the " << humanIndex;
switch (humanIndex)
{
case 1:
result << "st";
break;
case 2:
result << "nd";
break;
case 3:
result << "rd";
break;
default:
result << "th";
}
switch (c.variant)
{
case TypePath::Index::Variant::Pack:
result << ' ' << "entry in the type pack";
break;
case TypePath::Index::Variant::Union:
result << ' ' << "component of the union";
break;
case TypePath::Index::Variant::Intersection:
result << ' ' << "component of the intersection";
break;
}
if (state == State::PendingWhich)
result << ' ' << "which";
if (state == State::PendingIs || state == State::Property)
state = State::PendingAs;
else
state = State::PendingIs;
}
else if constexpr (std::is_same_v<T, TypePath::TypeField>)
{
if (state == State::Initial && !last)
result << "in" << ' ';
else if (state == State::PendingIs)
result << ", ";
else if (state == State::Property)
result << '`' << ' ' << "has" << ' ';
switch (c)
{
case TypePath::TypeField::Table:
result << "the table portion";
if (state == State::Property)
state = State::PendingAs;
else
state = State::PendingIs;
break;
case TypePath::TypeField::Metatable:
result << "the metatable portion";
if (state == State::Property)
state = State::PendingAs;
else
state = State::PendingIs;
break;
case TypePath::TypeField::LowerBound:
result << "the lower bound of" << ' ';
state = State::Normal;
break;
case TypePath::TypeField::UpperBound:
result << "the upper bound of" << ' ';
state = State::Normal;
break;
case TypePath::TypeField::IndexLookup:
result << "the index type";
if (state == State::Property)
state = State::PendingAs;
else
state = State::PendingIs;
break;
case TypePath::TypeField::IndexResult:
result << "the result of indexing";
if (state == State::Property)
state = State::PendingAs;
else
state = State::PendingIs;
break;
case TypePath::TypeField::Negated:
result << "the negation" << ' ';
state = State::Normal;
break;
case TypePath::TypeField::Variadic:
result << "the variadic" << ' ';
state = State::Normal;
break;
}
}
else if constexpr (std::is_same_v<T, TypePath::PackField>)
{
if (state == State::PendingIs)
result << ", ";
else if (state == State::Property)
result << "`, ";
switch (c)
{
case TypePath::PackField::Arguments:
if (state == State::Initial)
result << "it" << ' ';
else if (state == State::PendingIs)
result << "the function" << ' ';
result << "takes";
break;
case TypePath::PackField::Returns:
if (state == State::Initial)
result << "it" << ' ';
else if (state == State::PendingIs)
result << "the function" << ' ';
result << "returns";
break;
case TypePath::PackField::Tail:
if (state == State::Initial)
result << "it has" << ' ';
result << "a tail of";
break;
}
if (state == State::PendingIs)
{
result << ' ';
state = State::PendingWhich;
}
else
{
result << ' ';
state = State::Normal;
}
}
else if constexpr (std::is_same_v<T, TypePath::Reduction>)
{
if (state == State::Initial)
result << "it" << ' ';
result << "reduces to" << ' ';
state = State::Normal;
}
else
{
static_assert(always_false_v<T>, "Unhandled Component variant");
}
};
size_t count = 0;
for (const TypePath::Component& component : path.components)
{
count++;
if (count == path.components.size())
last = true;
Luau::visit(strComponent, component);
}
switch (state)
{
case State::Property:
result << "` results in ";
break;
case State::PendingWhich:
// pending `which` becomes `is` if it's at the end
result << "is" << ' ';
break;
case State::PendingIs:
result << ' ' << "is" << ' ';
break;
case State::PendingAs:
result << ' ' << "as" << ' ';
break;
default:
break;
}
return result.str();
}
static bool traverse(TraversalState& state, const Path& path)
{
auto step = [&state](auto&& c)

View file

@ -433,6 +433,9 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable)
superTypePackParamsIter++;
}
if (subTable->selfTy && superTable->selfTy)
result &= unify(*subTable->selfTy, *superTable->selfTy);
if (subTable->indexer && superTable->indexer)
{
result &= unify(subTable->indexer->indexType, superTable->indexer->indexType);
@ -647,33 +650,38 @@ struct FreeTypeSearcher : TypeVisitor
{
}
Polarity polarity = Polarity::Positive;
enum Polarity
{
Positive,
Negative,
Both,
};
Polarity polarity = Positive;
void flip()
{
switch (polarity)
{
case Polarity::Positive:
polarity = Polarity::Negative;
case Positive:
polarity = Negative;
break;
case Polarity::Negative:
polarity = Polarity::Positive;
case Negative:
polarity = Positive;
break;
case Polarity::Mixed:
case Both:
break;
default:
LUAU_ASSERT(!"Unreachable");
}
}
DenseHashSet<const void*> seenPositive{nullptr};
DenseHashSet<const void*> seenNegative{nullptr};
bool seenWithCurrentPolarity(const void* ty)
bool seenWithPolarity(const void* ty)
{
switch (polarity)
{
case Polarity::Positive:
case Positive:
{
if (seenPositive.contains(ty))
return true;
@ -681,7 +689,7 @@ struct FreeTypeSearcher : TypeVisitor
seenPositive.insert(ty);
return false;
}
case Polarity::Negative:
case Negative:
{
if (seenNegative.contains(ty))
return true;
@ -689,7 +697,7 @@ struct FreeTypeSearcher : TypeVisitor
seenNegative.insert(ty);
return false;
}
case Polarity::Mixed:
case Both:
{
if (seenPositive.contains(ty) && seenNegative.contains(ty))
return true;
@ -698,8 +706,6 @@ struct FreeTypeSearcher : TypeVisitor
seenNegative.insert(ty);
return false;
}
default:
LUAU_ASSERT(!"Unreachable");
}
return false;
@ -713,7 +719,7 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypeId ty) override
{
if (seenWithCurrentPolarity(ty))
if (seenWithPolarity(ty))
return false;
LUAU_ASSERT(ty);
@ -722,7 +728,7 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypeId ty, const FreeType& ft) override
{
if (seenWithCurrentPolarity(ty))
if (seenWithPolarity(ty))
return false;
if (!subsumes(scope, ft.scope))
@ -730,18 +736,16 @@ struct FreeTypeSearcher : TypeVisitor
switch (polarity)
{
case Polarity::Positive:
case Positive:
positiveTypes[ty]++;
break;
case Polarity::Negative:
case Negative:
negativeTypes[ty]++;
break;
case Polarity::Mixed:
case Both:
positiveTypes[ty]++;
negativeTypes[ty]++;
break;
default:
LUAU_ASSERT(!"Unreachable");
}
return true;
@ -749,25 +753,23 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypeId ty, const TableType& tt) override
{
if (seenWithCurrentPolarity(ty))
if (seenWithPolarity(ty))
return false;
if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope))
{
switch (polarity)
{
case Polarity::Positive:
case Positive:
positiveTypes[ty]++;
break;
case Polarity::Negative:
case Negative:
negativeTypes[ty]++;
break;
case Polarity::Mixed:
case Both:
positiveTypes[ty]++;
negativeTypes[ty]++;
break;
default:
LUAU_ASSERT(!"Unreachable");
}
}
@ -780,7 +782,7 @@ struct FreeTypeSearcher : TypeVisitor
LUAU_ASSERT(prop.isShared());
Polarity p = polarity;
polarity = Polarity::Mixed;
polarity = Both;
traverse(prop.type());
polarity = p;
}
@ -797,7 +799,7 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypeId ty, const FunctionType& ft) override
{
if (seenWithCurrentPolarity(ty))
if (seenWithPolarity(ty))
return false;
flip();
@ -816,7 +818,7 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypePackId tp, const FreeTypePack& ftp) override
{
if (seenWithCurrentPolarity(tp))
if (seenWithPolarity(tp))
return false;
if (!subsumes(scope, ftp.scope))
@ -824,18 +826,16 @@ struct FreeTypeSearcher : TypeVisitor
switch (polarity)
{
case Polarity::Positive:
case Positive:
positiveTypes[tp]++;
break;
case Polarity::Negative:
case Negative:
negativeTypes[tp]++;
break;
case Polarity::Mixed:
case Both:
positiveTypes[tp]++;
negativeTypes[tp]++;
break;
default:
LUAU_ASSERT(!"Unreachable");
}
return true;

View file

@ -194,7 +194,6 @@ public:
{
Checked,
Native,
Deprecated,
};
AstAttr(const Location& location, Type type);
@ -454,7 +453,6 @@ public:
void visit(AstVisitor* visitor) override;
bool hasNativeAttribute() const;
bool hasAttribute(AstAttr::Type attributeType) const;
AstArray<AstAttr*> attributes;
AstArray<AstGenericType*> generics;
@ -892,22 +890,14 @@ class AstStatTypeFunction : public AstStat
public:
LUAU_RTTI(AstStatTypeFunction);
AstStatTypeFunction(
const Location& location,
const AstName& name,
const Location& nameLocation,
AstExprFunction* body,
bool exported,
bool hasErrors
);
AstStatTypeFunction(const Location& location, const AstName& name, const Location& nameLocation, AstExprFunction* body, bool exported);
void visit(AstVisitor* visitor) override;
AstName name;
Location nameLocation;
AstExprFunction* body = nullptr;
bool exported = false;
bool hasErrors = false;
AstExprFunction* body;
bool exported;
};
class AstStatDeclareGlobal : public AstStat
@ -960,7 +950,6 @@ public:
void visit(AstVisitor* visitor) override;
bool isCheckedFunction() const;
bool hasAttribute(AstAttr::Type attributeType) const;
AstArray<AstAttr*> attributes;
AstName name;
@ -1117,7 +1106,6 @@ public:
void visit(AstVisitor* visitor) override;
bool isCheckedFunction() const;
bool hasAttribute(AstAttr::Type attributeType) const;
AstArray<AstAttr*> attributes;
AstArray<AstGenericType*> generics;
@ -1139,16 +1127,6 @@ public:
AstExpr* expr;
};
class AstTypeOptional : public AstType
{
public:
LUAU_RTTI(AstTypeOptional)
AstTypeOptional(const Location& location);
void visit(AstVisitor* visitor) override;
};
class AstTypeUnion : public AstType
{
public:
@ -1471,10 +1449,6 @@ public:
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatTypeFunction* node)
{
return visit(static_cast<AstStat*>(node));
}
virtual bool visit(class AstStatDeclareFunction* node)
{
return visit(static_cast<AstStat*>(node));
@ -1514,10 +1488,6 @@ public:
{
return visit(static_cast<AstType*>(node));
}
virtual bool visit(class AstTypeOptional* node)
{
return visit(static_cast<AstType*>(node));
}
virtual bool visit(class AstTypeUnion* node)
{
return visit(static_cast<AstType*>(node));

View file

@ -105,21 +105,6 @@ public:
Position closeBracketPosition;
};
class CstExprFunction : public CstNode
{
public:
LUAU_CST_RTTI(CstExprFunction)
CstExprFunction();
Position functionKeywordPosition{0, 0};
Position openGenericsPosition{0,0};
AstArray<Position> genericsCommaPositions;
Position closeGenericsPosition{0,0};
AstArray<Position> argsCommaPositions;
Position returnSpecifierPosition{0,0};
};
class CstExprTable : public CstNode
{
public:
@ -275,24 +260,13 @@ public:
Position opPosition;
};
class CstStatFunction : public CstNode
{
public:
LUAU_CST_RTTI(CstStatFunction)
explicit CstStatFunction(Position functionKeywordPosition);
Position functionKeywordPosition;
};
class CstStatLocalFunction : public CstNode
{
public:
LUAU_CST_RTTI(CstStatLocalFunction)
explicit CstStatLocalFunction(Position localKeywordPosition, Position functionKeywordPosition);
explicit CstStatLocalFunction(Position functionKeywordPosition);
Position localKeywordPosition;
Position functionKeywordPosition;
};
@ -337,17 +311,6 @@ public:
Position equalsPosition;
};
class CstStatTypeFunction : public CstNode
{
public:
LUAU_CST_RTTI(CstStatTypeFunction)
CstStatTypeFunction(Position typeKeywordPosition, Position functionKeywordPosition);
Position typeKeywordPosition;
Position functionKeywordPosition;
};
class CstTypeReference : public CstNode
{
public:
@ -396,32 +359,6 @@ public:
bool isArray = false;
};
class CstTypeFunction : public CstNode
{
public:
LUAU_CST_RTTI(CstTypeFunction)
CstTypeFunction(
Position openGenericsPosition,
AstArray<Position> genericsCommaPositions,
Position closeGenericsPosition,
Position openArgsPosition,
AstArray<std::optional<Position>> argumentNameColonPositions,
AstArray<Position> argumentsCommaPositions,
Position closeArgsPosition,
Position returnArrowPosition
);
Position openGenericsPosition;
AstArray<Position> genericsCommaPositions;
Position closeGenericsPosition;
Position openArgsPosition;
AstArray<std::optional<Position>> argumentNameColonPositions;
AstArray<Position> argumentsCommaPositions;
Position closeArgsPosition;
Position returnArrowPosition;
};
class CstTypeTypeof : public CstNode
{
public:
@ -433,28 +370,6 @@ public:
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
{
public:
@ -467,26 +382,4 @@ public:
unsigned int blockDepth;
};
class CstTypePackExplicit : public CstNode
{
public:
LUAU_CST_RTTI(CstTypePackExplicit)
CstTypePackExplicit(Position openParenthesesPosition, Position closeParenthesesPosition, AstArray<Position> commaPositions);
Position openParenthesesPosition;
Position closeParenthesesPosition;
AstArray<Position> commaPositions;
};
class CstTypePackGeneric : public CstNode
{
public:
LUAU_CST_RTTI(CstTypePackGeneric)
explicit CstTypePackGeneric(Position ellipsisPosition);
Position ellipsisPosition;
};
} // namespace Luau

View file

@ -125,7 +125,7 @@ private:
AstStat* parseFor();
// funcname ::= Name {`.' Name} [`:' Name]
AstExpr* parseFunctionName(bool& hasself, AstName& debugname);
AstExpr* parseFunctionName(Location start_DEPRECATED, bool& hasself, AstName& debugname);
// function funcname funcbody
LUAU_FORCEINLINE AstStat* parseFunctionStat(const AstArray<AstAttr*>& attributes = {nullptr, 0});
@ -155,11 +155,9 @@ private:
AstStat* parseTypeAlias(const Location& start, bool exported, Position typeKeywordPosition);
// type function Name ... end
AstStat* parseTypeFunction(const Location& start, bool exported, Position typeKeywordPosition);
AstDeclaredClassProp parseDeclaredClassMethod(const AstArray<AstAttr*>& attributes);
AstDeclaredClassProp parseDeclaredClassMethod_DEPRECATED();
AstStat* parseTypeFunction(const Location& start, bool exported);
AstDeclaredClassProp parseDeclaredClassMethod();
// `declare global' Name: Type |
// `declare function' Name`(' [parlist] `)' [`:` Type]
@ -194,8 +192,7 @@ private:
std::tuple<bool, Location, AstTypePack*> parseBindingList(
TempVector<Binding>& result,
bool allowDot3 = false,
AstArray<Position>* commaPositions = nullptr,
std::optional<Position> initialCommaPosition = std::nullopt
TempVector<Position>* commaPositions = nullptr
);
AstType* parseOptionalType();
@ -212,14 +209,9 @@ private:
// | `(' [TypeList] `)' `->` ReturnType
// Returns the variadic annotation, if it exists.
AstTypePack* parseTypeList(
TempVector<AstType*>& result,
TempVector<std::optional<AstArgumentName>>& resultNames,
TempVector<Position>* commaPositions = nullptr,
TempVector<std::optional<Position>>* nameColonPositions = nullptr
);
AstTypePack* parseTypeList(TempVector<AstType*>& result, TempVector<std::optional<AstArgumentName>>& resultNames);
std::optional<AstTypeList> parseOptionalReturnType(Position* returnSpecifierPosition = nullptr);
std::optional<AstTypeList> parseOptionalReturnType();
std::pair<Location, AstTypeList> parseReturnType();
struct TableIndexerResult
@ -230,9 +222,9 @@ private:
Position colonPosition;
};
TableIndexerResult parseTableIndexer(AstTableAccess access, std::optional<Location> accessLocation, Lexeme begin);
// Remove with FFlagLuauStoreCSTData2
AstTableIndexer* parseTableIndexer_DEPRECATED(AstTableAccess access, std::optional<Location> accessLocation, Lexeme begin);
TableIndexerResult parseTableIndexer(AstTableAccess access, std::optional<Location> accessLocation);
// Remove with FFlagLuauStoreCSTData
AstTableIndexer* parseTableIndexer_DEPRECATED(AstTableAccess access, std::optional<Location> accessLocation);
AstTypeOrPack parseFunctionType(bool allowPack, const AstArray<AstAttr*>& attributes);
AstType* parseFunctionTypeTail(
@ -313,7 +305,7 @@ private:
std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> parseGenericTypeList(
bool withDefaultValues,
Position* openPosition = nullptr,
AstArray<Position>* commaPositions = nullptr,
TempVector<Position>* commaPositions = nullptr,
Position* closePosition = nullptr
);
@ -499,7 +491,6 @@ private:
std::vector<AstGenericTypePack*> scratchGenericTypePacks;
std::vector<std::optional<AstArgumentName>> scratchOptArgName;
std::vector<Position> scratchPosition;
std::vector<std::optional<Position>> scratchOptPosition;
std::string scratchData;
CstNodeMap cstNodeMap;

View file

@ -3,24 +3,9 @@
#include "Luau/Common.h"
LUAU_FASTFLAG(LuauDeprecatedAttribute);
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)
{
for (AstType* ty : list.types)
@ -292,13 +277,6 @@ bool AstExprFunction::hasNativeAttribute() const
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)
: AstExpr(ClassIndex(), location)
, items(items)
@ -813,15 +791,13 @@ AstStatTypeFunction::AstStatTypeFunction(
const AstName& name,
const Location& nameLocation,
AstExprFunction* body,
bool exported,
bool hasErrors
bool exported
)
: AstStat(ClassIndex(), location)
, name(name)
, nameLocation(nameLocation)
, body(body)
, exported(exported)
, hasErrors(hasErrors)
{
}
@ -918,13 +894,6 @@ bool AstStatDeclareFunction::isCheckedFunction() const
return false;
}
bool AstStatDeclareFunction::hasAttribute(AstAttr::Type attributeType) const
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
return hasAttributeInArray(attributes, attributeType);
}
AstStatDeclareClass::AstStatDeclareClass(
const Location& location,
const AstName& name,
@ -1088,13 +1057,6 @@ bool AstTypeFunction::isCheckedFunction() const
return false;
}
bool AstTypeFunction::hasAttribute(AstAttr::Type attributeType) const
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
return hasAttributeInArray(attributes, attributeType);
}
AstTypeTypeof::AstTypeTypeof(const Location& location, AstExpr* expr)
: AstType(ClassIndex(), location)
, expr(expr)
@ -1107,16 +1069,6 @@ void AstTypeTypeof::visit(AstVisitor* visitor)
expr->visit(visitor);
}
AstTypeOptional::AstTypeOptional(const Location& location)
: AstType(ClassIndex(), location)
{
}
void AstTypeOptional::visit(AstVisitor* visitor)
{
visitor->visit(this);
}
AstTypeUnion::AstTypeUnion(const Location& location, const AstArray<AstType*>& types)
: AstType(ClassIndex(), location)
, types(types)

View file

@ -38,10 +38,6 @@ CstExprIndexExpr::CstExprIndexExpr(Position openBracketPosition, Position closeB
{
}
CstExprFunction::CstExprFunction() : CstNode(CstClassIndex())
{
}
CstExprTable::CstExprTable(const AstArray<Item>& items)
: CstNode(CstClassIndex())
, items(items)
@ -129,19 +125,12 @@ CstStatCompoundAssign::CstStatCompoundAssign(Position opPosition)
{
}
CstStatFunction::CstStatFunction(Position functionKeywordPosition)
CstStatLocalFunction::CstStatLocalFunction(Position functionKeywordPosition)
: CstNode(CstClassIndex())
, functionKeywordPosition(functionKeywordPosition)
{
}
CstStatLocalFunction::CstStatLocalFunction(Position localKeywordPosition, Position functionKeywordPosition)
: CstNode(CstClassIndex())
, localKeywordPosition(localKeywordPosition)
, functionKeywordPosition(functionKeywordPosition)
{
}
CstGenericType::CstGenericType(std::optional<Position> defaultEqualsPosition)
: CstNode(CstClassIndex())
, defaultEqualsPosition(defaultEqualsPosition)
@ -171,13 +160,6 @@ CstStatTypeAlias::CstStatTypeAlias(
{
}
CstStatTypeFunction::CstStatTypeFunction(Position typeKeywordPosition, Position functionKeywordPosition)
: CstNode(CstClassIndex())
, typeKeywordPosition(typeKeywordPosition)
, functionKeywordPosition(functionKeywordPosition)
{
}
CstTypeReference::CstTypeReference(
std::optional<Position> prefixPointPosition,
Position openParametersPosition,
@ -199,28 +181,6 @@ CstTypeTable::CstTypeTable(AstArray<Item> items, bool isArray)
{
}
CstTypeFunction::CstTypeFunction(
Position openGenericsPosition,
AstArray<Position> genericsCommaPositions,
Position closeGenericsPosition,
Position openArgsPosition,
AstArray<std::optional<Position>> argumentNameColonPositions,
AstArray<Position> argumentsCommaPositions,
Position closeArgsPosition,
Position returnArrowPosition
)
: CstNode(CstClassIndex())
, openGenericsPosition(openGenericsPosition)
, genericsCommaPositions(genericsCommaPositions)
, closeGenericsPosition(closeGenericsPosition)
, openArgsPosition(openArgsPosition)
, argumentNameColonPositions(argumentNameColonPositions)
, argumentsCommaPositions(argumentsCommaPositions)
, closeArgsPosition(closeArgsPosition)
, returnArrowPosition(returnArrowPosition)
{
}
CstTypeTypeof::CstTypeTypeof(Position openPosition, Position closePosition)
: CstNode(CstClassIndex())
, openPosition(openPosition)
@ -228,20 +188,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)
: CstNode(CstClassIndex())
, sourceString(sourceString)
@ -251,18 +197,4 @@ CstTypeSingletonString::CstTypeSingletonString(AstArray<char> sourceString, CstE
LUAU_ASSERT(quoteStyle != CstExprConstantString::QuotedInterp);
}
CstTypePackExplicit::CstTypePackExplicit(Position openParenthesesPosition, Position closeParenthesesPosition, AstArray<Position> commaPositions)
: CstNode(CstClassIndex())
, openParenthesesPosition(openParenthesesPosition)
, closeParenthesesPosition(closeParenthesesPosition)
, commaPositions(commaPositions)
{
}
CstTypePackGeneric::CstTypePackGeneric(Position ellipsisPosition)
: CstNode(CstClassIndex())
, ellipsisPosition(ellipsisPosition)
{
}
} // namespace Luau

View file

@ -8,6 +8,8 @@
#include <limits.h>
LUAU_FASTFLAGVARIABLE(LexerFixInterpStringStart)
namespace Luau
{
@ -787,7 +789,7 @@ Lexeme Lexer::readNext()
return Lexeme(Location(start, 1), '}');
}
return readInterpolatedStringSection(start, Lexeme::InterpStringMid, Lexeme::InterpStringEnd);
return readInterpolatedStringSection(FFlag::LexerFixInterpStringStart ? start : position(), Lexeme::InterpStringMid, Lexeme::InterpStringEnd);
}
case '=':

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,7 @@ if(EXT_PLATFORM_STRING)
return()
endif()
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.0)
option(LUAU_BUILD_CLI "Build CLI" ON)
option(LUAU_BUILD_TESTS "Build tests" ON)

View file

@ -125,10 +125,6 @@ static LuauBytecodeType getType(
{
return getType(group->type, generics, typeAliases, resolveAliases, hostVectorType, userdataTypes, bytecode);
}
else if (const AstTypeOptional* optional = ty->as<AstTypeOptional>())
{
return LBC_TYPE_NIL;
}
return LBC_TYPE_ANY;
}

View file

@ -169,6 +169,7 @@ target_sources(Luau.CodeGen PRIVATE
# Luau.Analysis Sources
target_sources(Luau.Analysis PRIVATE
Analysis/include/Luau/Anyification.h
Analysis/include/Luau/AnyTypeSummary.h
Analysis/include/Luau/ApplyTypeFunction.h
Analysis/include/Luau/AstJsonEncoder.h
Analysis/include/Luau/AstQuery.h
@ -247,6 +248,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/include/Luau/VisitType.h
Analysis/src/Anyification.cpp
Analysis/src/AnyTypeSummary.cpp
Analysis/src/ApplyTypeFunction.cpp
Analysis/src/AstJsonEncoder.cpp
Analysis/src/AstQuery.cpp
@ -431,6 +433,7 @@ endif()
if(TARGET Luau.UnitTest)
# Luau.UnitTest Sources
target_sources(Luau.UnitTest PRIVATE
tests/AnyTypeSummary.test.cpp
tests/AssemblyBuilderA64.test.cpp
tests/AssemblyBuilderX64.test.cpp
tests/AstJsonEncoder.test.cpp

View file

@ -443,7 +443,7 @@ static void shrinkstack(lua_State* L)
if (3 * size_t(s_used) < size_t(L->stacksize) && 2 * (BASIC_STACK_SIZE + EXTRA_STACK) < L->stacksize)
luaD_reallocstack(L, L->stacksize / 2, 0); // still big enough...
condhardstacktests(luaD_reallocstack(L, s_used, 0));
condhardstacktests(luaD_reallocstack(L, s_used));
}
/*

View file

@ -76,7 +76,7 @@
#define luaC_checkGC(L) \
{ \
condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK, 0)); \
condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK)); \
if (luaC_needsGC(L)) \
{ \
condhardmemtests(luaC_validate(L), 1); \

View file

@ -134,7 +134,6 @@ int registerTypes(Luau::Frontend& frontend, Luau::GlobalTypes& globals, bool for
getMutable<TableType>(vector3MetaType)->props = {
{"__add", {makeFunction(arena, nullopt, {vector3InstanceType, vector3InstanceType}, {vector3InstanceType})}},
};
getMutable<TableType>(vector3MetaType)->state = TableState::Sealed;
globals.globalScope->exportedTypeBindings["Vector3"] = TypeFun{{}, vector3InstanceType};

File diff suppressed because it is too large Load diff

View file

@ -12,7 +12,6 @@
using namespace Luau;
LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
struct JsonEncoderFixture
{
@ -441,9 +440,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstAttr")
AstStat* expr = expectParseStatement("@checked function a(b) return c end");
std::string_view expected =
FFlag::LuauFixFunctionWithAttributesStartLocation
? R"({"type":"AstStatFunction","location":"0,0 - 0,35","name":{"type":"AstExprGlobal","location":"0,18 - 0,19","global":"a"},"func":{"type":"AstExprFunction","location":"0,0 - 0,35","attributes":[{"type":"AstAttr","location":"0,0 - 0,8","name":"checked"}],"generics":[],"genericPacks":[],"args":[{"luauType":null,"name":"b","type":"AstLocal","location":"0,20 - 0,21"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,22 - 0,32","hasEnd":true,"body":[{"type":"AstStatReturn","location":"0,23 - 0,31","list":[{"type":"AstExprGlobal","location":"0,30 - 0,31","global":"c"}]}]},"functionDepth":1,"debugname":"a"}})"
: R"({"type":"AstStatFunction","location":"0,9 - 0,35","name":{"type":"AstExprGlobal","location":"0,18 - 0,19","global":"a"},"func":{"type":"AstExprFunction","location":"0,9 - 0,35","attributes":[{"type":"AstAttr","location":"0,0 - 0,8","name":"checked"}],"generics":[],"genericPacks":[],"args":[{"luauType":null,"name":"b","type":"AstLocal","location":"0,20 - 0,21"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,22 - 0,32","hasEnd":true,"body":[{"type":"AstStatReturn","location":"0,23 - 0,31","list":[{"type":"AstExprGlobal","location":"0,30 - 0,31","global":"c"}]}]},"functionDepth":1,"debugname":"a"}})";
R"({"type":"AstStatFunction","location":"0,9 - 0,35","name":{"type":"AstExprGlobal","location":"0,18 - 0,19","global":"a"},"func":{"type":"AstExprFunction","location":"0,9 - 0,35","attributes":[{"type":"AstAttr","location":"0,0 - 0,8","name":"checked"}],"generics":[],"genericPacks":[],"args":[{"luauType":null,"name":"b","type":"AstLocal","location":"0,20 - 0,21"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,22 - 0,32","hasEnd":true,"body":[{"type":"AstStatReturn","location":"0,23 - 0,31","list":[{"type":"AstExprGlobal","location":"0,30 - 0,31","global":"c"}]}]},"functionDepth":1,"debugname":"a"}})";
CHECK(toJson(expr) == expected);
}

View file

@ -7,8 +7,6 @@
#include "Luau/VisitType.h"
#include "Luau/StringUtils.h"
#include "ClassFixture.h"
#include "Fixture.h"
#include "ScopedFlags.h"
@ -21,9 +19,6 @@ LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauExposeRequireByStringAutocomplete)
LUAU_FASTFLAG(LuauAutocompleteUnionCopyPreviousSeen)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete)
using namespace Luau;
@ -159,10 +154,6 @@ struct ACBuiltinsFixture : ACFixtureImpl<BuiltinsFixture>
{
};
struct ACClassFixture : ACFixtureImpl<ClassFixture>
{
};
TEST_SUITE_BEGIN("AutocompleteTest");
TEST_CASE_FIXTURE(ACFixture, "empty_program")
@ -4425,99 +4416,4 @@ local x = 1 + result.
CHECK(ac.entryMap.count("x"));
}
TEST_CASE_FIXTURE(ACClassFixture, "ac_dont_overflow_on_recursive_union")
{
ScopedFastFlag _{FFlag::LuauAutocompleteUnionCopyPreviousSeen, true};
check(R"(
local table1: {ChildClass} = {}
local table2 = {}
for index, value in table2[1] do
table.insert(table1, value)
value.@1
end
)");
auto ac = autocomplete('1');
// RIDE-11517: This should *really* be the members of `ChildClass`, but
// would previously stack overflow.
CHECK(ac.entryMap.empty());
}
TEST_CASE_FIXTURE(ACBuiltinsFixture, "type_function_has_types_definitions")
{
// Needs new global initialization in the Fixture, but can't place the flag inside the base Fixture
if (!FFlag::LuauUserTypeFunTypecheck)
return;
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
check(R"(
type function foo()
types.@1
end
)");
auto ac = autocomplete('1');
CHECK_EQ(ac.entryMap.count("singleton"), 1);
}
TEST_CASE_FIXTURE(ACBuiltinsFixture, "type_function_private_scope")
{
// Needs new global initialization in the Fixture, but can't place the flag inside the base Fixture
if (!FFlag::LuauUserTypeFunTypecheck)
return;
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
// Global scope polution by the embedder has no effect
addGlobalBinding(frontend.globals, "thisAlsoShouldNotBeThere", Binding{builtinTypes->anyType});
addGlobalBinding(frontend.globalsForAutocomplete, "thisAlsoShouldNotBeThere", Binding{builtinTypes->anyType});
check(R"(
local function thisShouldNotBeThere() end
type function thisShouldBeThere() end
type function foo()
this@1
end
this@2
)");
auto ac = autocomplete('1');
CHECK_EQ(ac.entryMap.count("thisShouldNotBeThere"), 0);
CHECK_EQ(ac.entryMap.count("thisAlsoShouldNotBeThere"), 0);
CHECK_EQ(ac.entryMap.count("thisShouldBeThere"), 1);
ac = autocomplete('2');
CHECK_EQ(ac.entryMap.count("thisShouldNotBeThere"), 1);
CHECK_EQ(ac.entryMap.count("thisAlsoShouldNotBeThere"), 1);
CHECK_EQ(ac.entryMap.count("thisShouldBeThere"), 0);
}
TEST_CASE_FIXTURE(ACBuiltinsFixture, "type_function_eval_in_autocomplete")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauTypeFunResultInAutocomplete{FFlag::LuauTypeFunResultInAutocomplete, true};
check(R"(
type function foo(x)
local tbl = types.newtable(nil, nil, nil)
tbl:setproperty(types.singleton("boolean"), x)
tbl:setproperty(types.singleton("number"), types.number)
return tbl
end
local function test(a: foo<string>)
return a.@1
end
)");
auto ac = autocomplete('1');
CHECK_EQ(ac.entryMap.count("boolean"), 1);
CHECK_EQ(ac.entryMap.count("number"), 1);
}
TEST_SUITE_END();

View file

@ -9,8 +9,7 @@ using std::nullopt;
namespace Luau
{
ClassFixture::ClassFixture(bool prepareAutocomplete)
: BuiltinsFixture(prepareAutocomplete)
ClassFixture::ClassFixture()
{
GlobalTypes& globals = frontend.globals;
TypeArena& arena = globals.globalTypes;

View file

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

View file

@ -34,7 +34,6 @@ void ConstraintGeneratorFixture::generateConstraints(const std::string& code)
builtinTypes,
NotNull(&ice),
frontend.globals.globalScope,
frontend.globals.globalTypeFunctionScope,
/*prepareModuleScope*/ nullptr,
&logger,
NotNull{dfg.get()},

View file

@ -323,7 +323,6 @@ AstStatBlock* Fixture::parse(const std::string& source, const ParseOptions& pars
NotNull{&moduleResolver},
NotNull{&fileResolver},
frontend.globals.globalScope,
frontend.globals.globalTypeFunctionScope,
/*prepareModuleScope*/ nullptr,
frontend.options,
{},
@ -372,7 +371,7 @@ LintResult Fixture::lint(const std::string& source, const std::optional<LintOpti
fileResolver.source[mm] = std::move(source);
frontend.markDirty(mm);
return lintModule(mm, lintOptions);
return lintModule(mm);
}
LintResult Fixture::lintModule(const ModuleName& moduleName, const std::optional<LintOptions>& lintOptions)

File diff suppressed because it is too large Load diff

View file

@ -17,7 +17,7 @@ LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauSelectivelyRetainDFGArena)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauModuleHoldsAstRoot)
namespace
{
@ -920,17 +920,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_f
// When this test fails, it is because the TypeIds needed by the error have been deallocated.
// It is thus basically impossible to predict what will happen when this assert is evaluated.
// It could segfault, or you could see weird type names like the empty string or <VALUELESS BY EXCEPTION>
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
{
REQUIRE_EQ(
"Type\n\t"
"'{ count: string }'"
"\ncould not be converted into\n\t"
"'{ Count: number }'",
toString(result.errors[0])
);
}
else if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2)
REQUIRE_EQ(
R"(Type
'{ count: string }'
@ -1554,6 +1544,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "check_module_references_allocator")
TEST_CASE_FIXTURE(FrontendFixture, "check_module_references_correct_ast_root")
{
ScopedFastFlag sff{FFlag::LuauModuleHoldsAstRoot, true};
fileResolver.source["game/workspace/MyScript"] = R"(
print("Hello World")
)";
@ -1791,96 +1782,4 @@ TEST_CASE_FIXTURE(FrontendFixture, "test_invalid_dependency_tracking_per_module_
CHECK(frontend.allModuleDependenciesValid("game/Gui/Modules/A", opts.forAutocomplete));
}
TEST_CASE_FIXTURE(FrontendFixture, "queue_check_simple")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
--!strict
return {hello=5, world=true}
)";
fileResolver.source["game/Gui/Modules/B"] = R"(
--!strict
local Modules = game:GetService('Gui').Modules
local A = require(Modules.A)
return {b_value = A.hello}
)";
frontend.queueModuleCheck("game/Gui/Modules/B");
frontend.checkQueuedModules();
auto result = frontend.getCheckResult("game/Gui/Modules/B", true);
REQUIRE(result);
LUAU_REQUIRE_NO_ERRORS(*result);
}
TEST_CASE_FIXTURE(FrontendFixture, "queue_check_cycle_instant")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
--!strict
local Modules = game:GetService('Gui').Modules
local B = require(Modules.B)
return {a_value = B.hello}
)";
fileResolver.source["game/Gui/Modules/B"] = R"(
--!strict
local Modules = game:GetService('Gui').Modules
local A = require(Modules.A)
return {b_value = A.hello}
)";
frontend.queueModuleCheck("game/Gui/Modules/B");
frontend.checkQueuedModules();
auto result = frontend.getCheckResult("game/Gui/Modules/B", true);
REQUIRE(result);
LUAU_REQUIRE_ERROR_COUNT(2, *result);
CHECK(toString(result->errors[0]) == "Cyclic module dependency: game/Gui/Modules/B -> game/Gui/Modules/A");
CHECK(toString(result->errors[1]) == "Cyclic module dependency: game/Gui/Modules/A -> game/Gui/Modules/B");
}
TEST_CASE_FIXTURE(FrontendFixture, "queue_check_cycle_delayed")
{
fileResolver.source["game/Gui/Modules/C"] = R"(
--!strict
return {c_value = 5}
)";
fileResolver.source["game/Gui/Modules/A"] = R"(
--!strict
local Modules = game:GetService('Gui').Modules
local C = require(Modules.C)
local B = require(Modules.B)
return {a_value = B.hello + C.c_value}
)";
fileResolver.source["game/Gui/Modules/B"] = R"(
--!strict
local Modules = game:GetService('Gui').Modules
local C = require(Modules.C)
local A = require(Modules.A)
return {b_value = A.hello + C.c_value}
)";
frontend.queueModuleCheck("game/Gui/Modules/B");
frontend.checkQueuedModules();
auto result = frontend.getCheckResult("game/Gui/Modules/B", true);
REQUIRE(result);
LUAU_REQUIRE_ERROR_COUNT(2, *result);
CHECK(toString(result->errors[0]) == "Cyclic module dependency: game/Gui/Modules/B -> game/Gui/Modules/A");
CHECK(toString(result->errors[1]) == "Cyclic module dependency: game/Gui/Modules/A -> game/Gui/Modules/B");
}
TEST_CASE_FIXTURE(FrontendFixture, "queue_check_propagates_ice")
{
ScopedFastFlag sffs{FFlag::DebugLuauMagicTypes, true};
ModuleName mm = fromString("MainModule");
fileResolver.source[mm] = R"(
--!strict
local a: _luau_ice = 55
)";
frontend.markDirty(mm);
frontend.queueModuleCheck("MainModule");
CHECK_THROWS_AS(frontend.checkQueuedModules(), InternalCompilerError);
}
TEST_SUITE_END();

View file

@ -15,9 +15,6 @@
using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauForbidInternalTypes)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall)
TEST_SUITE_BEGIN("Generalization");
@ -253,42 +250,4 @@ end
)");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "generalization_should_not_leak_free_type")
{
ScopedFastFlag sffs[] = {
{FFlag::DebugLuauForbidInternalTypes, true},
{FFlag::LuauTrackInteriorFreeTypesOnScope, true},
{FFlag::LuauTrackInferredFunctionTypeFromCall, true}
};
// This test case should just not assert
CheckResult result = check(R"(
function foo()
local productButtonPairs = {}
local func
local dir = -1
local function updateSearch()
for product, button in pairs(productButtonPairs) do
-- This line may have a floating free type pack.
button.LayoutOrder = func(product) * dir
end
end
function(mode)
if mode == 'New'then
func = function(p)
return p.id
end
elseif mode == 'Price'then
func = function(p)
return p.price
end
end
end
end
)");
}
TEST_SUITE_END();

View file

@ -8,6 +8,8 @@
using namespace Luau;
LUAU_FASTFLAG(LexerFixInterpStringStart)
TEST_SUITE_BEGIN("LexerTests");
TEST_CASE("broken_string_works")
@ -154,7 +156,7 @@ TEST_CASE("string_interpolation_basic")
Lexeme interpEnd = lexer.next();
CHECK_EQ(interpEnd.type, Lexeme::InterpStringEnd);
// The InterpStringEnd should start with }, not `.
CHECK_EQ(interpEnd.location.begin.column, 11);
CHECK_EQ(interpEnd.location.begin.column, FFlag::LexerFixInterpStringStart ? 11 : 12);
}
TEST_CASE("string_interpolation_full")
@ -175,7 +177,7 @@ TEST_CASE("string_interpolation_full")
Lexeme interpMid = lexer.next();
CHECK_EQ(interpMid.type, Lexeme::InterpStringMid);
CHECK_EQ(interpMid.toString(), "} {");
CHECK_EQ(interpMid.location.begin.column, 11);
CHECK_EQ(interpMid.location.begin.column, FFlag::LexerFixInterpStringStart ? 11 : 12);
Lexeme quote2 = lexer.next();
CHECK_EQ(quote2.type, Lexeme::QuotedString);
@ -184,7 +186,7 @@ TEST_CASE("string_interpolation_full")
Lexeme interpEnd = lexer.next();
CHECK_EQ(interpEnd.type, Lexeme::InterpStringEnd);
CHECK_EQ(interpEnd.toString(), "} end`");
CHECK_EQ(interpEnd.location.begin.column, 19);
CHECK_EQ(interpEnd.location.begin.column, FFlag::LexerFixInterpStringStart ? 19 : 20);
}
TEST_CASE("string_interpolation_double_brace")

View file

@ -9,7 +9,6 @@
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LintRedundantNativeAttribute);
LUAU_FASTFLAG(LuauDeprecatedAttribute);
using namespace Luau;
@ -1601,326 +1600,6 @@ setfenv(h :: any, {})
CHECK_EQ(result.warnings[3].location.begin.line + 1, 11);
}
static void checkDeprecatedWarning(const Luau::LintWarning& warning, const Luau::Position& begin, const Luau::Position& end, const char* msg)
{
CHECK_EQ(warning.code, LintWarning::Code_DeprecatedApi);
CHECK_EQ(warning.location, Location(begin, end));
CHECK_EQ(warning.text, msg);
}
TEST_CASE_FIXTURE(Fixture, "DeprecatedAttribute")
{
ScopedFastFlag sff[] = {{FFlag::LuauDeprecatedAttribute, true}, {FFlag::LuauSolverV2, true}};
// @deprecated works on local functions
{
LintResult result = lint(R"(
@deprecated
local function testfun(x)
return x + 1
end
testfun(1)
)");
REQUIRE(1 == result.warnings.size());
checkDeprecatedWarning(result.warnings[0], Position(6, 0), Position(6, 7), "Function 'testfun' is deprecated");
}
// @deprecated works on globals functions
{
LintResult result = lint(R"(
@deprecated
function testfun(x)
return x + 1
end
testfun(1)
)");
REQUIRE(1 == result.warnings.size());
checkDeprecatedWarning(result.warnings[0], Position(6, 0), Position(6, 7), "Function 'testfun' is deprecated");
}
// @deprecated works on fully typed functions
{
LintResult result = lint(R"(
@deprecated
local function testfun(x:number):number
return x + 1
end
if math.random(2) == 2 then
testfun(1)
end
)");
REQUIRE(1 == result.warnings.size());
checkDeprecatedWarning(result.warnings[0], Position(7, 4), Position(7, 11), "Function 'testfun' is deprecated");
}
// @deprecated works on functions without an explicit return type
{
LintResult result = lint(R"(
@deprecated
local function testfun(x:number)
return x + 1
end
g(testfun)
)");
REQUIRE(1 == result.warnings.size());
checkDeprecatedWarning(result.warnings[0], Position(6, 2), Position(6, 9), "Function 'testfun' is deprecated");
}
// @deprecated works on functions without an explicit argument type
{
LintResult result = lint(R"(
@deprecated
local function testfun(x):number
if x == 1 then
return x
else
return 1 + testfun(x - 1)
end
end
testfun(1)
)");
REQUIRE(1 == result.warnings.size());
checkDeprecatedWarning(result.warnings[0], Position(10, 0), Position(10, 7), "Function 'testfun' is deprecated");
}
// @deprecated works on inner functions
{
LintResult result = lint(R"(
function flipFlop()
local state = false
@deprecated
local function invert()
state = !state
return state
end
return invert
end
f = flipFlop()
assert(f() == true)
)");
REQUIRE(2 == result.warnings.size());
checkDeprecatedWarning(result.warnings[0], Position(10, 11), Position(10, 17), "Function 'invert' is deprecated");
checkDeprecatedWarning(result.warnings[1], Position(14, 7), Position(14, 8), "Function 'f' is deprecated");
}
// @deprecated does not automatically apply to inner functions
{
LintResult result = lint(R"(
@deprecated
function flipFlop()
local state = false
local function invert()
state = !state
return state
end
return invert
end
f = flipFlop()
assert(f() == true)
)");
REQUIRE(1 == result.warnings.size());
checkDeprecatedWarning(result.warnings[0], Position(13, 4), Position(13, 12), "Function 'flipFlop' is deprecated");
}
// @deprecated works correctly if deprecated function is shadowed
{
LintResult result = lint(R"(
@deprecated
local function doTheThing()
print("doing")
end
doTheThing()
local function shadow()
local function doTheThing()
print("doing!")
end
doTheThing()
end
shadow()
)");
REQUIRE(1 == result.warnings.size());
checkDeprecatedWarning(result.warnings[0], Position(6, 0), Position(6, 10), "Function 'doTheThing' is deprecated");
}
// @deprecated does not issue warnings if a deprecated function uses itself
{
LintResult result = lint(R"(
@deprecated
function fibonacci(n)
if n == 0 then
return 0
elseif n == 1 then
return 1
else
return fibonacci(n - 1) + fibonacci(n - 2)
end
end
fibonacci(5)
)");
REQUIRE(1 == result.warnings.size());
checkDeprecatedWarning(result.warnings[0], Position(12, 0), Position(12, 9), "Function 'fibonacci' is deprecated");
}
// @deprecated works for mutually recursive functions
{
LintResult result = lint(R"(
@deprecated
function odd(x)
if x == 0 then
return false
else
return even(x - 1)
end
end
@deprecated
function even(x)
if x == 0 then
return true
else
return odd(x - 1)
end
end
assert(odd(1) == true)
assert(even(0) == true)
)");
REQUIRE(4 == result.warnings.size());
checkDeprecatedWarning(result.warnings[0], Position(6, 15), Position(6, 19), "Function 'even' is deprecated");
checkDeprecatedWarning(result.warnings[1], Position(15, 15), Position(15, 18), "Function 'odd' is deprecated");
checkDeprecatedWarning(result.warnings[2], Position(19, 7), Position(19, 10), "Function 'odd' is deprecated");
checkDeprecatedWarning(result.warnings[3], Position(20, 7), Position(20, 11), "Function 'even' is deprecated");
}
// @deprecated works for methods with a literal class name
{
LintResult result = lint(R"(
Account = { balance=0 }
@deprecated
function Account:deposit(v)
self.balance = self.balance + v
end
Account:deposit(200.00)
)");
REQUIRE(1 == result.warnings.size());
checkDeprecatedWarning(result.warnings[0], Position(8, 0), Position(8, 15), "Member 'Account.deposit' is deprecated");
}
// @deprecated works for methods with a compound expression class name
{
LintResult result = lint(R"(
Account = { balance=0 }
function getAccount()
return Account
end
@deprecated
function Account:deposit (v)
self.balance = self.balance + v
end
(getAccount()):deposit(200.00)
)");
REQUIRE(1 == result.warnings.size());
checkDeprecatedWarning(result.warnings[0], Position(12, 0), Position(12, 22), "Member 'deposit' is deprecated");
}
}
TEST_CASE_FIXTURE(Fixture, "DeprecatedAttributeFunctionDeclaration")
{
ScopedFastFlag sff[] = {{FFlag::LuauDeprecatedAttribute, true}, {FFlag::LuauSolverV2, true}};
// @deprecated works on function type declarations
loadDefinition(R"(
@deprecated declare function bar(x: number): string
)");
LintResult result = lint(R"(
bar(2)
)");
REQUIRE(1 == result.warnings.size());
checkDeprecatedWarning(result.warnings[0], Position(1, 0), Position(1, 3), "Function 'bar' is deprecated");
}
TEST_CASE_FIXTURE(Fixture, "DeprecatedAttributeTableDeclaration")
{
ScopedFastFlag sff[] = {{FFlag::LuauDeprecatedAttribute, true}, {FFlag::LuauSolverV2, true}};
// @deprecated works on table type declarations
loadDefinition(R"(
declare Hooty : {
tooty : @deprecated @checked (number) -> number
}
)");
LintResult result = lint(R"(
print(Hooty:tooty(2.0))
)");
REQUIRE(1 == result.warnings.size());
checkDeprecatedWarning(result.warnings[0], Position(1, 6), Position(1, 17), "Member 'Hooty.tooty' is deprecated");
}
TEST_CASE_FIXTURE(Fixture, "DeprecatedAttributeMethodDeclaration")
{
ScopedFastFlag sff[] = {{FFlag::LuauDeprecatedAttribute, true}, {FFlag::LuauSolverV2, true}};
// @deprecated works on table type declarations
loadDefinition(R"(
declare class Foo
@deprecated
function bar(self, value: number) : number
end
declare Foo: {
new: () -> Foo
}
)");
LintResult result = lint(R"(
local foo = Foo.new()
print(foo:bar(2.0))
)");
REQUIRE(1 == result.warnings.size());
checkDeprecatedWarning(result.warnings[0], Position(2, 6), Position(2, 13), "Member 'bar' is deprecated");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "TableOperations")
{
LintResult result = lint(R"(

View file

@ -17,8 +17,6 @@
LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals)
LUAU_FASTFLAG(LuauNonStrictVisitorImprovements)
LUAU_FASTFLAG(LuauNonStrictFuncDefErrorFix)
LUAU_FASTFLAG(LuauNormalizedBufferIsNotUnknown)
using namespace Luau;
@ -361,23 +359,6 @@ end
NONSTRICT_REQUIRE_FUNC_DEFINITION_ERR(Position(1, 11), "x", result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_sequencing_errors_2")
{
ScopedFastFlag luauNonStrictFuncDefErrorFix{FFlag::LuauNonStrictFuncDefErrorFix, true};
ScopedFastFlag luauNonStrictVisitorImprovements{FFlag::LuauNonStrictVisitorImprovements, true};
CheckResult result = checkNonStrict(R"(
local t = {function(x)
abs(x)
lower(x)
end}
)");
LUAU_REQUIRE_ERROR_COUNT(3, result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 8), "abs", result);
NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 10), "lower", result);
CHECK(toString(result.errors[2]) == "Argument x with type 'unknown' is used in a way that will run time error");
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "local_fn_produces_error")
{
CheckResult result = checkNonStrict(R"(
@ -668,17 +649,4 @@ TEST_CASE_FIXTURE(Fixture, "unknown_globals_in_non_strict")
LUAU_REQUIRE_ERROR_COUNT(2, result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "buffer_is_not_unknown")
{
ScopedFastFlag luauNormalizedBufferIsNotUnknown{FFlag::LuauNormalizedBufferIsNotUnknown, true};
CheckResult result = check(Mode::Nonstrict, R"(
local function wrap(b: buffer, i: number, v: number)
buffer.writeu32(b, i * 4, v)
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_SUITE_END();

View file

@ -10,16 +10,10 @@
#include "Luau/Normalize.h"
#include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAG(LuauNormalizeNegatedErrorToAnError)
LUAU_FASTFLAG(LuauNormalizeIntersectErrorToAnError)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTINT(LuauNormalizeIntersectionLimit)
LUAU_FASTINT(LuauNormalizeUnionLimit)
LUAU_FASTFLAG(LuauNormalizeLimitFunctionSet)
LUAU_FASTFLAG(LuauSubtypingStopAtNormFail)
LUAU_FASTFLAG(LuauNormalizationCatchMetatableCycles)
LUAU_FASTFLAG(LuauFixNormalizedIntersectionOfNegatedClass)
LUAU_FASTFLAG(LuauNormalizeNegationFix)
using namespace Luau;
namespace
@ -600,25 +594,6 @@ TEST_CASE_FIXTURE(NormalizeFixture, "intersect_truthy_expressed_as_intersection"
)")));
}
TEST_CASE_FIXTURE(NormalizeFixture, "intersect_error")
{
ScopedFastFlag luauNormalizeIntersectErrorToAnError{FFlag::LuauNormalizeIntersectErrorToAnError, true};
std::shared_ptr<const NormalizedType> norm = toNormalizedType(R"(string & AAA)", 1);
REQUIRE(norm);
CHECK("*error-type*" == toString(normalizer.typeFromNormal(*norm)));
}
TEST_CASE_FIXTURE(NormalizeFixture, "intersect_not_error")
{
ScopedFastFlag luauNormalizeIntersectErrorToAnError{FFlag::LuauNormalizeIntersectErrorToAnError, true};
ScopedFastFlag luauNormalizeNegatedErrorToAnError{FFlag::LuauNormalizeNegatedErrorToAnError, true};
std::shared_ptr<const NormalizedType> norm = toNormalizedType(R"(string & Not<)", 1);
REQUIRE(norm);
CHECK("*error-type*" == toString(normalizer.typeFromNormal(*norm)));
}
TEST_CASE_FIXTURE(NormalizeFixture, "union_of_union")
{
CHECK(R"("alpha" | "beta" | "gamma")" == toString(normal(R"(
@ -877,6 +852,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "crazy_metatable")
TEST_CASE_FIXTURE(NormalizeFixture, "negations_of_classes")
{
ScopedFastFlag _{FFlag::LuauFixNormalizedIntersectionOfNegatedClass, true};
createSomeClasses(&frontend);
CHECK("(Parent & ~Child) | Unrelated" == toString(normal("(Parent & Not<Child>) | Unrelated")));
CHECK("((class & ~Child) | boolean | buffer | function | number | string | table | thread)?" == toString(normal("Not<Child>")));
@ -1058,6 +1034,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "free_type_and_not_truthy")
{
ScopedFastFlag sff[] = {
{FFlag::LuauSolverV2, true}, // Only because it affects the stringification of free types
{FFlag::LuauNormalizeNegationFix, true},
};
TypeId freeTy = arena.freshType(builtinTypes, &globalScope);
@ -1073,19 +1050,6 @@ TEST_CASE_FIXTURE(NormalizeFixture, "free_type_and_not_truthy")
CHECK("'a & (false?)" == toString(result));
}
TEST_CASE_FIXTURE(NormalizeFixture, "normalize_recursive_metatable")
{
ScopedFastFlag sff[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauNormalizationCatchMetatableCycles, true}};
TypeId root = arena.addType(BlockedType{});
TypeId emptyTable = arena.addType(TableType(TableState::Sealed, {}));
TypeId metatable = arena.addType(MetatableType{emptyTable, root});
emplaceType<BoundType>(asMutable(root), metatable);
auto normalized = normalizer.normalize(root);
REQUIRE(normalized);
CHECK_EQ("t1 where t1 = { @metatable t1, { } }", toString(normalizer.typeFromNormal(*normalized)));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "normalizer_should_be_able_to_detect_cyclic_tables_and_not_stack_overflow")
{
if (!FFlag::LuauSolverV2)
@ -1191,40 +1155,4 @@ end
)");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_limit_function_intersection_complexity")
{
ScopedFastInt luauNormalizeIntersectionLimit{FInt::LuauNormalizeIntersectionLimit, 50};
ScopedFastInt luauNormalizeUnionLimit{FInt::LuauNormalizeUnionLimit, 20};
ScopedFastFlag luauNormalizeLimitFunctionSet{FFlag::LuauNormalizeLimitFunctionSet, true};
ScopedFastFlag luauSubtypingStopAtNormFail{FFlag::LuauSubtypingStopAtNormFail, true};
CheckResult result = check(R"(
function _(_).readu32(l0)
return ({[_(_(_))]=_,[_(if _ then _)]=_,n0=_,})[_],nil
end
_(_)[_(n32)] %= _(_(_))
)");
LUAU_REQUIRE_ERRORS(result);
}
#if !(defined(_WIN32) && !(defined(_M_X64) || defined(_M_ARM64)))
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_propagate_normalization_failures")
{
ScopedFastInt luauNormalizeIntersectionLimit{FInt::LuauNormalizeIntersectionLimit, 50};
ScopedFastInt luauNormalizeUnionLimit{FInt::LuauNormalizeUnionLimit, 20};
ScopedFastFlag luauNormalizeLimitFunctionSet{FFlag::LuauNormalizeLimitFunctionSet, true};
ScopedFastFlag luauSubtypingStopAtNormFail{FFlag::LuauSubtypingStopAtNormFail, true};
CheckResult result = check(R"(
function _(_,"").readu32(l0)
return ({[_(_(_))]=_,[_(if _ then _,_())]=_,[""]=_,})[_],nil
end
_().readu32 %= _(_(_(_),_))
)");
LUAU_REQUIRE_ERRORS(result);
}
#endif
TEST_SUITE_END();

View file

@ -18,17 +18,11 @@ LUAU_FASTINT(LuauParseErrorLimit)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAllowComplexTypesInGenericParams)
LUAU_FASTFLAG(LuauErrorRecoveryForTableTypes)
LUAU_FASTFLAG(LuauFixFunctionNameStartPosition)
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
LUAU_FASTFLAG(LuauParseOptionalAsNode2)
LUAU_FASTFLAG(LuauParseStringIndexer)
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
LUAU_DYNAMIC_FASTFLAG(DebugLuauReportReturnTypeVariadicWithTypeSuffix)
// Clip with DebugLuauReportReturnTypeVariadicWithTypeSuffix
extern bool luau_telemetry_parsed_return_type_variadic_with_type_suffix;
namespace
{
@ -2546,40 +2540,6 @@ TEST_CASE_FIXTURE(Fixture, "do_block_end_location_is_after_end_token")
CHECK_EQ(block->location, Location{{1, 8}, {3, 11}});
}
TEST_CASE_FIXTURE(Fixture, "function_start_locations_are_before_attributes")
{
ScopedFastFlag _{FFlag::LuauFixFunctionWithAttributesStartLocation, true};
AstStatBlock* stat = parse(R"(
@native
function globalFunction()
end
@native
local function localFunction()
end
local _ = @native function()
end
)");
REQUIRE(stat);
REQUIRE_EQ(3, stat->body.size);
auto globalFunction = stat->body.data[0]->as<AstStatFunction>();
REQUIRE(globalFunction);
CHECK_EQ(globalFunction->location, Location({1, 8}, {3, 11}));
auto localFunction = stat->body.data[1]->as<AstStatLocalFunction>();
REQUIRE(localFunction);
CHECK_EQ(localFunction->location, Location({5, 8}, {7, 11}));
auto localVariable = stat->body.data[2]->as<AstStatLocal>();
REQUIRE(localVariable);
REQUIRE_EQ(localVariable->values.size, 1);
auto anonymousFunction = localVariable->values.data[0]->as<AstExprFunction>();
CHECK_EQ(anonymousFunction->location, Location({9, 18}, {10, 11}));
}
TEST_SUITE_END();
TEST_SUITE_BEGIN("ParseErrorRecovery");
@ -3858,10 +3818,7 @@ TEST_CASE_FIXTURE(Fixture, "grouped_function_type")
}
else
CHECK(unionTy->types.data[0]->is<AstTypeFunction>()); // () -> ()
if (FFlag::LuauParseOptionalAsNode2)
CHECK(unionTy->types.data[1]->is<AstTypeOptional>()); // ?
else
CHECK(unionTy->types.data[1]->is<AstTypeReference>()); // nil
CHECK(unionTy->types.data[1]->is<AstTypeReference>()); // nil
}
TEST_CASE_FIXTURE(Fixture, "complex_union_in_generic_ty")
@ -3919,6 +3876,7 @@ TEST_CASE_FIXTURE(Fixture, "recover_from_bad_table_type")
TEST_CASE_FIXTURE(Fixture, "function_name_has_correct_start_location")
{
ScopedFastFlag _{FFlag::LuauFixFunctionNameStartPosition, true};
AstStatBlock* block = parse(R"(
function simple()
end
@ -3967,8 +3925,6 @@ TEST_CASE_FIXTURE(Fixture, "stat_end_includes_semicolon_position")
TEST_CASE_FIXTURE(Fixture, "parsing_type_suffix_for_return_type_with_variadic")
{
ScopedFastFlag sff{DFFlag::DebugLuauReportReturnTypeVariadicWithTypeSuffix, true};
ParseResult result = tryParse(R"(
function foo(): (string, ...number) | boolean
end
@ -3976,13 +3932,7 @@ TEST_CASE_FIXTURE(Fixture, "parsing_type_suffix_for_return_type_with_variadic")
// TODO(CLI-140667): this should produce a ParseError in future when we fix the invalid syntax
CHECK(result.errors.size() == 0);
CHECK_EQ(luau_telemetry_parsed_return_type_variadic_with_type_suffix, true);
}
TEST_CASE_FIXTURE(Fixture, "parsing_string_union_indexers")
{
ScopedFastFlag _{FFlag::LuauParseStringIndexer, true};
parse(R"(type foo = { ["bar" | "baz"]: number })");
}
TEST_SUITE_END();

View file

@ -6,6 +6,8 @@
#include "doctest.h"
LUAU_FASTFLAG(LuauExtendedSimpleRequire)
using namespace Luau;
namespace
@ -180,6 +182,8 @@ TEST_CASE_FIXTURE(RequireTracerFixture, "follow_string_indexexpr")
TEST_CASE_FIXTURE(RequireTracerFixture, "follow_group")
{
ScopedFastFlag luauExtendedSimpleRequire{FFlag::LuauExtendedSimpleRequire, true};
AstStatBlock* block = parse(R"(
local R = (((game).Test))
require(R)
@ -196,6 +200,8 @@ TEST_CASE_FIXTURE(RequireTracerFixture, "follow_group")
TEST_CASE_FIXTURE(RequireTracerFixture, "follow_type_annotation")
{
ScopedFastFlag luauExtendedSimpleRequire{FFlag::LuauExtendedSimpleRequire, true};
AstStatBlock* block = parse(R"(
local R = game.Test :: (typeof(game.Redirect))
require(R)
@ -212,6 +218,8 @@ TEST_CASE_FIXTURE(RequireTracerFixture, "follow_type_annotation")
TEST_CASE_FIXTURE(RequireTracerFixture, "follow_type_annotation_2")
{
ScopedFastFlag luauExtendedSimpleRequire{FFlag::LuauExtendedSimpleRequire, true};
AstStatBlock* block = parse(R"(
local R = game.Test :: (typeof(game.Redirect))
local N = R.Nested

View file

@ -3,7 +3,7 @@
#include "Luau/Common.h"
#include <vector>
#include <string.h>
template<typename T>
struct [[nodiscard]] ScopedFValue
@ -49,4 +49,4 @@ public:
};
using ScopedFastFlag = ScopedFValue<bool>;
using ScopedFastInt = ScopedFValue<int>;
using ScopedFastInt = ScopedFValue<int>;

View file

@ -15,8 +15,7 @@
#include <initializer_list>
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauNormalizedBufferIsNotUnknown)
LUAU_FASTFLAG(LuauSolverV2);
using namespace Luau;
@ -962,20 +961,6 @@ TEST_IS_NOT_SUBTYPE(childClass, negate(rootClass));
TEST_IS_NOT_SUBTYPE(childClass, meet(builtinTypes->classType, negate(rootClass)));
TEST_IS_SUBTYPE(anotherChildClass, meet(builtinTypes->classType, negate(childClass)));
// Negated primitives against unknown
TEST_IS_NOT_SUBTYPE(builtinTypes->unknownType, negate(builtinTypes->booleanType));
TEST_IS_NOT_SUBTYPE(builtinTypes->unknownType, negate(builtinTypes->numberType));
TEST_IS_NOT_SUBTYPE(builtinTypes->unknownType, negate(builtinTypes->stringType));
TEST_IS_NOT_SUBTYPE(builtinTypes->unknownType, negate(builtinTypes->threadType));
TEST_CASE_FIXTURE(SubtypeFixture, "unknown <!: ~buffer")
{
// TODO: replace with TEST_IS_NOT_SUBTYPE on flag removal
ScopedFastFlag luauNormalizedBufferIsNotUnknown{FFlag::LuauNormalizedBufferIsNotUnknown, true};
CHECK_IS_NOT_SUBTYPE(builtinTypes->unknownType, negate(builtinTypes->bufferType));
}
TEST_CASE_FIXTURE(SubtypeFixture, "Root <: class")
{
CHECK_IS_SUBTYPE(rootClass, builtinTypes->classType);

View file

@ -5,16 +5,14 @@
#include "Fixture.h"
#include "Luau/TypeChecker2.h"
#include "ScopedFlags.h"
#include "doctest.h"
using namespace Luau;
LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAttributeSyntax)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction);
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauAttributeSyntax);
TEST_SUITE_BEGIN("ToString");
@ -873,28 +871,9 @@ TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch")
)");
std::string expected;
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
expected =
"Type pack '{ a: number, b: string, c: { d: string } }' could not be converted into '{ a: number, b: string, c: { d: number } }'; \n"
"this is because in the 1st entry in the type pack, accessing `c.d` results in `string` in the former type and `number` in the latter "
"type, and `string` is not exactly `number`";
else if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2)
expected =
R"(Type pack '{ a: number, b: string, c: { d: string } }' could not be converted into '{ a: number, b: string, c: { d: number } }'; at [0][read "c"][read "d"], string is not exactly number)";
else if (FFlag::LuauImproveTypePathsInErrors)
expected = R"(Type
'{ a: number, b: string, c: { d: string } }'
could not be converted into
'{| a: number, b: string, c: {| d: number |} |}'
caused by:
Property 'c' is not compatible.
Type
'{ d: string }'
could not be converted into
'{| d: number |}'
caused by:
Property 'd' is not compatible.
Type 'string' could not be converted into 'number' in an invariant context)";
else
expected = R"(Type
'{ a: number, b: string, c: { d: string } }'

File diff suppressed because it is too large Load diff

View file

@ -13,15 +13,8 @@
using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauIndexTypeFunctionImprovements)
LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
LUAU_FASTFLAG(LuauIndexTypeFunctionFunctionMetamethods)
LUAU_FASTFLAG(LuauMetatableTypeFunctions)
LUAU_FASTFLAG(LuauMetatablesHaveLength)
LUAU_FASTFLAG(LuauIndexAnyIsAny)
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAG(LuauHasPropProperBlock)
LUAU_FASTFLAG(LuauFixCyclicIndexInIndexer)
struct TypeFunctionFixture : Fixture
{
@ -149,17 +142,19 @@ TEST_CASE_FIXTURE(TypeFunctionFixture, "unsolvable_function")
if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag luauNewTypeFunReductionChecks2{FFlag::LuauNewTypeFunReductionChecks2, true};
CheckResult result = check(R"(
local impossible: <T>(Swap<T>) -> Swap<Swap<T>>
local a = impossible(123)
local b = impossible(true)
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK(toString(result.errors[0]) == "Type 'number' could not be converted into 'never'");
CHECK(toString(result.errors[1]) == "Type 'boolean' could not be converted into 'never'");
LUAU_REQUIRE_ERROR_COUNT(6, result);
CHECK(toString(result.errors[0]) == "Type function instance Swap<Swap<T>> is uninhabited");
CHECK(toString(result.errors[1]) == "Type function instance Swap<T> is uninhabited");
CHECK(toString(result.errors[2]) == "Type function instance Swap<Swap<T>> is uninhabited");
CHECK(toString(result.errors[3]) == "Type function instance Swap<T> is uninhabited");
CHECK(toString(result.errors[4]) == "Type function instance Swap<Swap<T>> is uninhabited");
CHECK(toString(result.errors[5]) == "Type function instance Swap<T> is uninhabited");
}
TEST_CASE_FIXTURE(TypeFunctionFixture, "table_internal_functions")
@ -909,91 +904,6 @@ end
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "index_of_any_is_any")
{
if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag sff{FFlag::LuauIndexAnyIsAny, true};
CheckResult result = check(R"(
type T = index<any, "a">
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK(toString(requireTypeAlias("T")) == "any");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "index_should_not_crash_on_cyclic_stuff")
{
if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag sff{FFlag::LuauFixCyclicIndexInIndexer, true};
CheckResult result = check(R"(
local PlayerData = {}
type Keys = index<typeof(PlayerData), true>
local function UpdateData(key: Keys)
PlayerData[key] = 4
end
)");
LUAU_REQUIRE_ERRORS(result);
CHECK(toString(requireTypeAlias("Keys")) == "index<PlayerData, true>");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "index_should_not_crash_on_cyclic_stuff2")
{
if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag sff{FFlag::LuauFixCyclicIndexInIndexer, true};
CheckResult result = check(R"(
local PlayerData = {}
type Keys = index<typeof(PlayerData), number>
local function UpdateData(key: Keys)
PlayerData[key] = 4
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK(toString(requireTypeAlias("Keys")) == "number");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "index_should_not_crash_on_cyclic_stuff3")
{
if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag sff{FFlag::LuauFixCyclicIndexInIndexer, true};
CheckResult result = check(R"(
local PlayerData = {
Coins = 0,
Level = 1,
Exp = 0,
MapExp = 100,
}
type Keys = index<typeof(PlayerData), true>
local function UpdateData(key: Keys, value)
PlayerData[key] = value
end
UpdateData("Coins", 2)
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK(toString(requireTypeAlias("Keys")) == "unknown");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works")
{
if (!FFlag::LuauSolverV2)
@ -1055,31 +965,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_w_array")
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "cyclic_metatable_should_not_crash_index")
{
if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag sff{FFlag::LuauIndexTypeFunctionImprovements, true};
// t :: t1 where t1 = {metatable {__index: t1, __tostring: (t1) -> string}}
CheckResult result = check(R"(
local mt = {}
local t = setmetatable({}, mt)
mt.__index = t
function mt:__tostring()
return t.p
end
type IndexFromT = index<typeof(t), "p">
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ("Type 't' does not have key 'p'", toString(result.errors[0]));
CHECK_EQ("Property '\"p\"' does not exist on type 't'", toString(result.errors[1]));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_w_generic_types")
{
if (!FFlag::LuauSolverV2)
@ -1118,61 +1003,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_errors_w_bad_indexer")
CHECK(toString(result.errors[1]) == "Property 'boolean' does not exist on type 'MyObject'");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_on_function_metamethods")
{
if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag sff[]
{
{FFlag::LuauIndexTypeFunctionFunctionMetamethods, true},
{FFlag::LuauIndexTypeFunctionImprovements, true},
};
CheckResult result = check(R"(
type Foo = {x: string}
local t = {}
setmetatable(t, {
__index = function(x: string): Foo
return {x = x}
end
})
type Bar = index<typeof(t), "bar">
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireTypeAlias("Bar"), {true}), "{ x: string }");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_on_function_metamethods2")
{
if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag sff[]
{
{FFlag::LuauIndexTypeFunctionFunctionMetamethods, true},
{FFlag::LuauIndexTypeFunctionImprovements, true},
};
CheckResult result = check(R"(
type Foo = {x: string}
local t = {}
setmetatable(t, {
__index = function(x: string): Foo
return {x = x}
end
})
type Bar = index<typeof(t), number>
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_errors_w_var_indexer")
{
if (!FFlag::LuauSolverV2)
@ -1624,53 +1454,4 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_respects_metatable_metamethod")
CHECK_EQ(toString(requireTypeAlias("Metatable")), "string");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "type_function_correct_cycle_check")
{
if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag luauNewTypeFunReductionChecks2{FFlag::LuauNewTypeFunReductionChecks2, true};
CheckResult result = check(R"(
type foo<T> = { a: add<T, T>, b : add<T, T> }
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "len_typefun_on_metatable")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauMetatablesHaveLength{FFlag::LuauMetatablesHaveLength, true};
CheckResult result = check(R"(
local t = setmetatable({}, { __mode = "v" })
local function f()
table.insert(t, {})
print(#t * 100)
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "has_prop_on_irreducible_type_function")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauHasPropProperBlock{FFlag::LuauHasPropProperBlock, true};
CheckResult result = check(R"(
local test = "a" + "b"
print(test.a)
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK(
"Operator '+' could not be applied to operands of types string and string; there is no corresponding overload for __add" ==
toString(result.errors[0])
);
CHECK("Type 'add<string, string>' does not have key 'a'" == toString(result.errors[1]));
}
TEST_SUITE_END();

View file

@ -7,12 +7,13 @@
using namespace Luau;
LUAU_FASTFLAG(LuauTypeFunFixHydratedClasses)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauTypeFunSingletonEquality)
LUAU_FASTFLAG(LuauUserTypeFunTypeofReturnsType)
LUAU_FASTFLAG(LuauTypeFunReadWriteParents)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAG(LuauTypeFunPrintFix)
TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");
@ -612,12 +613,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_methods_work")
ty:setreturns(nil, types.boolean) -- (string, number) -> (...boolean)
if ty:is("function") then
-- creating a copy of `ty` parameters
local arr: {type} = {}
local args = ty:parameters().head
if args then
for index, val in args do
table.insert(arr, val)
end
local arr = {}
for index, val in ty:parameters().head do
table.insert(arr, val)
end
return types.newfunction({head = arr}, ty:returns()) -- (string, number) -> (...boolean)
end
@ -650,6 +648,7 @@ TEST_CASE_FIXTURE(ClassFixture, "udtf_class_serialization_works")
TEST_CASE_FIXTURE(ClassFixture, "udtf_class_serialization_works2")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauTypeFunFixHydratedClasses{FFlag::LuauTypeFunFixHydratedClasses, true};
CheckResult result = check(R"(
type function serialize_class(arg)
@ -720,7 +719,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_check_mutability")
readresult = types.boolean,
writeresult = types.boolean,
}
local ty = types.newtable(nil, indexer, nil) -- {[number]: boolean}
local ty = types.newtable(props, indexer, nil) -- {[number]: boolean}
ty:setproperty(types.singleton("string"), types.number) -- {string: number, [number]: boolean}
local metatbl = types.newtable(nil, nil, ty) -- { { }, @metatable { [number]: boolean, string: number } }
-- mutate the table
@ -895,7 +894,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_type_overrides_eq_metamethod")
if p1 == p2 and t1 == t2 then
return types.number
end
return types.unknown
end
local function ok(idx: hello<>): number return idx end
)");
@ -990,37 +988,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_each_other_3")
end
)");
if (FFlag::LuauUserTypeFunTypecheck)
{
LUAU_REQUIRE_ERROR_COUNT(5, result);
CHECK(toString(result.errors[0]) == R"(Unknown global 'fourth')");
CHECK(toString(result.errors[1]) == R"('third' type function errored at runtime: [string "first"]:4: attempt to call a nil value)");
}
else
{
LUAU_REQUIRE_ERROR_COUNT(4, result);
CHECK(toString(result.errors[0]) == R"('third' type function errored at runtime: [string "first"]:4: attempt to call a nil value)");
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_each_other_unordered")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
CheckResult result = check(R"(
type function bar()
return types.singleton(foo())
end
type function foo()
return "hi"
end
local function ok(idx: bar<>): nil return idx end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
REQUIRE(tpm);
CHECK(toString(tpm->givenTp) == "\"hi\"");
LUAU_REQUIRE_ERROR_COUNT(4, result);
CHECK(toString(result.errors[0]) == R"('third' type function errored at runtime: [string "first"]:4: attempt to call a nil value)");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_no_shared_state")
@ -1044,21 +1013,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_no_shared_state")
local function ok2(idx: bar<'y'>): nil return idx end
)");
if (FFlag::LuauUserTypeFunTypecheck)
{
// We are only checking first errors, others are mostly duplicates
LUAU_REQUIRE_ERROR_COUNT(9, result);
CHECK(toString(result.errors[0]) == R"(Unknown global 'glob')");
CHECK(toString(result.errors[1]) == R"('bar' type function errored at runtime: [string "foo"]:4: attempt to modify a readonly table)");
CHECK(toString(result.errors[2]) == R"(Type function instance bar<"x"> is uninhabited)");
}
else
{
// We are only checking first errors, others are mostly duplicates
LUAU_REQUIRE_ERROR_COUNT(8, result);
CHECK(toString(result.errors[0]) == R"('bar' type function errored at runtime: [string "foo"]:4: attempt to modify a readonly table)");
CHECK(toString(result.errors[1]) == R"(Type function instance bar<"x"> is uninhabited)");
}
// We are only checking first errors, others are mostly duplicates
LUAU_REQUIRE_ERROR_COUNT(8, result);
CHECK(toString(result.errors[0]) == R"('bar' type function errored at runtime: [string "foo"]:4: attempt to modify a readonly table)");
CHECK(toString(result.errors[1]) == R"(Type function instance bar<"x"> is uninhabited)");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_math_reset")
@ -1117,23 +1075,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_illegal_global")
local function ok(idx: illegal<number>): nil return idx end
)");
if (FFlag::LuauUserTypeFunTypecheck)
{
// We are only checking first errors, others are mostly duplicates
LUAU_REQUIRE_ERROR_COUNT(5, result);
CHECK(toString(result.errors[0]) == R"(Unknown global 'gcinfo')");
CHECK(
toString(result.errors[1]) ==
R"('illegal' type function errored at runtime: [string "illegal"]:3: this function is not supported in type functions)"
);
}
else
{
LUAU_REQUIRE_ERROR_COUNT(4, result); // There are 2 type function uninhabited error, 2 user defined type function error
UserDefinedTypeFunctionError* e = get<UserDefinedTypeFunctionError>(result.errors[0]);
REQUIRE(e);
CHECK(e->message == "'illegal' type function errored at runtime: [string \"illegal\"]:3: this function is not supported in type functions");
}
LUAU_REQUIRE_ERROR_COUNT(4, result); // There are 2 type function uninhabited error, 2 user defined type function error
UserDefinedTypeFunctionError* e = get<UserDefinedTypeFunctionError>(result.errors[0]);
REQUIRE(e);
CHECK(e->message == "'illegal' type function errored at runtime: [string \"illegal\"]:3: this function is not supported in type functions");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_recursion_and_gc")
@ -1227,7 +1172,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "no_type_methods_on_types")
CheckResult result = check(R"(
type function test(x)
return if (types :: any).is(x, "number") then types.string else types.boolean
return if types.is(x, "number") then types.string else types.boolean
end
local function ok(tbl: test<number>): never return tbl end
)");
@ -1298,36 +1243,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tag_field")
)");
LUAU_REQUIRE_ERROR_COUNT(3, result);
if (FFlag::LuauImproveTypePathsInErrors)
{
CHECK(
toString(result.errors[0]) ==
"Type pack '\"number\"' could not be converted into 'never'; \n"
R"(this is because the 1st entry in the type pack is `"number"` in the former type and `never` in the latter type, and `"number"` is not a subtype of `never`)"
);
CHECK(
toString(result.errors[1]) ==
"Type pack '\"string\"' could not be converted into 'never'; \n"
R"(this is because the 1st entry in the type pack is `"string"` in the former type and `never` in the latter type, and `"string"` is not a subtype of `never`)"
);
CHECK(
toString(result.errors[2]) ==
"Type pack '\"table\"' could not be converted into 'never'; \n"
R"(this is because the 1st entry in the type pack is `"table"` in the former type and `never` in the latter type, and `"table"` is not a subtype of `never`)"
);
}
else
{
CHECK(
toString(result.errors[0]) == R"(Type pack '"number"' could not be converted into 'never'; at [0], "number" is not a subtype of never)"
);
CHECK(
toString(result.errors[1]) == R"(Type pack '"string"' could not be converted into 'never'; at [0], "string" is not a subtype of never)"
);
CHECK(toString(result.errors[2]) == R"(Type pack '"table"' could not be converted into 'never'; at [0], "table" is not a subtype of never)");
}
CHECK(toString(result.errors[0]) == R"(Type pack '"number"' could not be converted into 'never'; at [0], "number" is not a subtype of never)");
CHECK(toString(result.errors[1]) == R"(Type pack '"string"' could not be converted into 'never'; at [0], "string" is not a subtype of never)");
CHECK(toString(result.errors[2]) == R"(Type pack '"table"' could not be converted into 'never'; at [0], "table" is not a subtype of never)");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_serialization")
@ -1375,12 +1293,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "implicit_export")
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
fileResolver.source["game/A"] = R"(
type function concat(a: type, b: type)
local as = a:value()
local bs = b:value()
assert(typeof(as) == "string")
assert(typeof(bs) == "string")
return types.singleton(as .. bs)
type function concat(a, b)
return types.singleton(a:value() .. b:value())
end
export type Concat<T, U> = concat<T, U>
local a: concat<'first', 'second'>
@ -1428,12 +1342,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "explicit_export")
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
fileResolver.source["game/A"] = R"(
export type function concat(a: type, b: type)
local as = a:value()
local bs = b:value()
assert(typeof(as) == "string")
assert(typeof(bs) == "string")
return types.singleton(as .. bs)
export type function concat(a, b)
return types.singleton(a:value() .. b:value())
end
local a: concat<'first', 'second'>
return {}
@ -1982,6 +1892,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_eqsat_opaque")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_singleton_equality_bool")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauTypeFunSingletonEquality{FFlag::LuauTypeFunSingletonEquality, true};
CheckResult result = check(R"(
type function compare(arg)
@ -1998,6 +1909,7 @@ local function ok(idx: compare<true>): false return idx end
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_singleton_equality_string")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauTypeFunSingletonEquality{FFlag::LuauTypeFunSingletonEquality, true};
CheckResult result = check(R"(
type function compare(arg)
@ -2014,6 +1926,7 @@ local function ok(idx: compare<"a">): false return idx end
TEST_CASE_FIXTURE(BuiltinsFixture, "typeof_type_userdata_returns_type")
{
ScopedFastFlag solverV2{FFlag::LuauSolverV2, true};
ScopedFastFlag luauUserTypeFunTypeofReturnsType{FFlag::LuauUserTypeFunTypeofReturnsType, true};
CheckResult result = check(R"(
type function test(t)
@ -2030,7 +1943,7 @@ local _:test<number>
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_print_tab_char_fix")
{
ScopedFastFlag solverV2{FFlag::LuauSolverV2, true};
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauTypeFunPrintFix, true}};
CheckResult result = check(R"(
type function test(t)
@ -2069,139 +1982,4 @@ TEST_CASE_FIXTURE(ClassFixture, "udtf_class_parent_ops")
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_success")
{
// Needs new global initialization in the Fixture, but can't place the flag inside the base Fixture
if (!FFlag::LuauUserTypeFunTypecheck)
return;
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
CheckResult result = check(R"(
type function foo(x: type)
return types.singleton(x.tag)
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_failure")
{
// Needs new global initialization in the Fixture, but can't place the flag inside the base Fixture
if (!FFlag::LuauUserTypeFunTypecheck)
return;
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
CheckResult result = check(R"(
type function foo()
return types.singleton({1})
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "outer_generics_irreducible")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauNewTypeFunReductionChecks2{FFlag::LuauNewTypeFunReductionChecks2, true};
CheckResult result = check(R"(
type function func(t)
return t
end
type wrap<T> = { a: func<T?> }
local x: wrap<string> = nil :: any
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK(toString(requireType("x"), ToStringOptions{true}) == "{ a: string? }");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "inner_generics_reducible")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
CheckResult result = check(R"(
type function func(t)
return t
end
type wrap<T> = { a: func<<T>(T) -> number>, b: T }
local x: wrap<string> = nil :: any
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK(toString(requireType("x"), ToStringOptions{true}) == "{ a: <T>(T) -> number, b: string }");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "blocking_nested_pending_expansions")
{
if (!FFlag::LuauSolverV2)
return;
CheckResult result = check(R"(
type function func(t)
return t
end
type test<T> = { x: T, y: T? }
type wrap<T> = { a: func<(string, keyof<test<T>>) -> number>, b: T }
local x: wrap<string>
local y: keyof<typeof(x)>
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK(toString(requireType("x"), ToStringOptions{true}) == R"({ a: (string, "x" | "y") -> number, b: string })");
CHECK(toString(requireType("y"), ToStringOptions{true}) == R"("a" | "b")");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "blocking_nested_pending_expansions_2")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
CheckResult result = check(R"(
type function foo(t)
return types.unionof(t, types.singleton(nil))
end
local x: foo<{a: foo<string>, b: foo<number>}> = nil
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK(toString(requireType("x"), ToStringOptions{true}) == "{ a: string?, b: number? }?");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "irreducible_pending_expansions")
{
if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag luauNewTypeFunReductionChecks2{FFlag::LuauNewTypeFunReductionChecks2, true};
CheckResult result = check(R"(
type function foo(t)
return types.unionof(t, types.singleton(nil))
end
type table<T> = { a: index<T, "a"> }
type wrap<T> = foo<table<T>>
local x: wrap<{a: number}> = { a = 2 }
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK(toString(requireType("x"), ToStringOptions{true}) == "{ a: number }?");
}
TEST_SUITE_END();

View file

@ -11,10 +11,6 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauFixInfiniteRecursionInNormalization)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauBidirectionalInferenceUpcast)
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
TEST_SUITE_BEGIN("TypeAliases");
@ -220,11 +216,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors)
? "Type '{ v: string }' could not be converted into 'T<number>'; \n"
"this is because accessing `v` results in `string` in the former type and `number` in the latter type, and "
"`string` is not exactly `number`"
: R"(Type '{ v: string }' could not be converted into 'T<number>'; at [read "v"], string is not exactly number)";
const std::string expected = R"(Type '{ v: string }' could not be converted into 'T<number>'; at [read "v"], string is not exactly number)";
CHECK(result.errors[0].location == Location{{4, 31}, {4, 44}});
CHECK_EQ(expected, toString(result.errors[0]));
}
@ -244,11 +236,7 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected =
(FFlag::LuauImproveTypePathsInErrors)
? "Type '{ t: { v: string } }' could not be converted into 'U<number>'; \n"
"this is because accessing `t.v` results in `string` in the former type and `number` in the latter type, and `string` is not exactly "
"`number`"
: R"(Type '{ t: { v: string } }' could not be converted into 'U<number>'; at [read "t"][read "v"], string is not exactly number)";
R"(Type '{ t: { v: string } }' could not be converted into 'U<number>'; at [read "t"][read "v"], string is not exactly number)";
CHECK(result.errors[0].location == Location{{4, 31}, {4, 52}});
CHECK_EQ(expected, toString(result.errors[0]));
@ -256,10 +244,8 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
TEST_CASE_FIXTURE(Fixture, "mutually_recursive_generic_aliases")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauBidirectionalInferenceUpcast, true},
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
};
// CLI-116108
DOES_NOT_PASS_NEW_SOLVER_GUARD();
CheckResult result = check(R"(
--!strict
@ -1223,40 +1209,4 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gh1632_no_infinite_recursion_in_normalizatio
LUAU_CHECK_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "exported_alias_location_is_accessible_on_module")
{
ScopedFastFlag sff{FFlag::LuauRetainDefinitionAliasLocations, true};
CheckResult result = check(R"(
export type Value = string
)");
LUAU_REQUIRE_NO_ERRORS(result);
auto module = getMainModule();
auto tfun = module->exportedTypeBindings.find("Value");
REQUIRE(tfun != module->exportedTypeBindings.end());
CHECK_EQ(tfun->second.definitionLocation, Location{{1, 8}, {1, 34}});
}
TEST_CASE_FIXTURE(Fixture, "exported_type_function_location_is_accessible_on_module")
{
ScopedFastFlag flags[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauRetainDefinitionAliasLocations, true},
};
CheckResult result = check(R"(
export type function Apply()
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
auto module = getMainModule();
auto tfun = module->exportedTypeBindings.find("Apply");
REQUIRE(tfun != module->exportedTypeBindings.end());
CHECK_EQ(tfun->second.definitionLocation, Location{{1, 8}, {2, 11}});
}
TEST_SUITE_END();

View file

@ -11,7 +11,8 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauTableCloneClonesType3)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauStringFormatErrorSuppression)
LUAU_FASTFLAG(LuauFreezeIgnorePersistent)
TEST_SUITE_BEGIN("BuiltinTests");
@ -145,20 +146,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_bad_predicate")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t"
"'(number, number) -> boolean'"
"\ncould not be converted into\n\t"
"'((string, string) -> boolean)?'"
"\ncaused by:\n"
" None of the union options are compatible. For example:\n"
"Type\n\t"
"'(number, number) -> boolean'"
"\ncould not be converted into\n\t"
"'(string, string) -> boolean'"
"\ncaused by:\n"
" Argument #1 type is not compatible.\n"
"Type 'string' could not be converted into 'number'"
: R"(Type
const std::string expected = R"(Type
'(number, number) -> boolean'
could not be converted into
'((string, string) -> boolean)?'
@ -997,14 +985,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tonumber_returns_optional_number_type")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
CHECK_EQ(
"Type 'number?' could not be converted into 'number'; \n"
"this is because the 2nd component of the union is `nil`, which is not a subtype of `number`",
toString(result.errors[0])
);
else if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2)
CHECK_EQ(
"Type 'number?' could not be converted into 'number'; type number?[1] (nil) is not a subtype of number (number)",
toString(result.errors[0])
@ -1275,6 +1256,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_errors_on_non_tables")
TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_persistent_skip")
{
ScopedFastFlag luauFreezeIgnorePersistent{FFlag::LuauFreezeIgnorePersistent, true};
CheckResult result = check(R"(
table.freeze(table)
)");
@ -1284,6 +1267,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_persistent_skip")
TEST_CASE_FIXTURE(BuiltinsFixture, "table_clone_persistent_skip")
{
ScopedFastFlag luauFreezeIgnorePersistent{FFlag::LuauFreezeIgnorePersistent, true};
CheckResult result = check(R"(
table.clone(table)
)");
@ -1671,7 +1656,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_should_support_any")
print(string.format("Hello, %s!", x))
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauStringFormatErrorSuppression)
LUAU_REQUIRE_NO_ERRORS(result);
else
LUAU_REQUIRE_ERROR_COUNT(1, result);
}
TEST_SUITE_END();

View file

@ -13,8 +13,7 @@
using namespace Luau;
using std::nullopt;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauSolverV2);
TEST_SUITE_BEGIN("TypeInferClasses");
@ -546,13 +545,7 @@ local b: B = a
LUAU_REQUIRE_ERRORS(result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
CHECK(
"Type 'A' could not be converted into 'B'; \n"
"this is because accessing `x` results in `ChildClass` in the former type and `BaseClass` in the latter type, and `ChildClass` is not "
"exactly `BaseClass`" == toString(result.errors.at(0))
);
else if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2)
CHECK(toString(result.errors.at(0)) == "Type 'A' could not be converted into 'B'; at [read \"x\"], ChildClass is not exactly BaseClass");
else
{

View file

@ -9,7 +9,9 @@
using namespace Luau;
LUAU_FASTFLAG(LuauClipNestedAndRecursiveUnion)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauPreventReentrantTypeFunctionReduction)
LUAU_FASTFLAG(LuauDontForgetToReduceUnionFunc)
TEST_SUITE_BEGIN("DefinitionTests");
@ -543,6 +545,8 @@ TEST_CASE_FIXTURE(Fixture, "definition_file_has_source_module_name_set")
TEST_CASE_FIXTURE(Fixture, "recursive_redefinition_reduces_rightfully")
{
ScopedFastFlag _{FFlag::LuauClipNestedAndRecursiveUnion, true};
LUAU_REQUIRE_NO_ERRORS(check(R"(
local t: {[string]: string} = {}
@ -588,6 +592,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cli_142285_reduce_minted_union_func")
TEST_CASE_FIXTURE(Fixture, "vector3_overflow")
{
ScopedFastFlag _{FFlag::LuauPreventReentrantTypeFunctionReduction, true};
// We set this to zero to ensure that we either run to completion or stack overflow here.
ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0};

View file

@ -22,9 +22,8 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauSubtypingFixTailPack)
LUAU_FASTFLAG(LuauUngeneralizedTypesForRecursiveFunctions)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauReduceUnionFollowUnionType)
TEST_SUITE_BEGIN("TypeInferFunctions");
@ -1307,16 +1306,7 @@ f(function(a, b, c, ...) return a + b end)
LUAU_REQUIRE_ERRORS(result);
std::string expected;
if (FFlag::LuauInstantiateInSubtyping && FFlag::LuauImproveTypePathsInErrors)
{
expected = "Type\n\t"
"'<a>(number, number, a) -> number'"
"\ncould not be converted into\n\t"
"'(number, number) -> number'"
"\ncaused by:\n"
" Argument count mismatch. Function expects 3 arguments, but only 2 are specified";
}
else if (FFlag::LuauInstantiateInSubtyping)
if (FFlag::LuauInstantiateInSubtyping)
{
expected = R"(Type
'<a>(number, number, a) -> number'
@ -1325,15 +1315,6 @@ could not be converted into
caused by:
Argument count mismatch. Function expects 3 arguments, but only 2 are specified)";
}
else if (FFlag::LuauImproveTypePathsInErrors)
{
expected = "Type\n\t"
"'(number, number, *error-type*) -> number'"
"\ncould not be converted into\n\t"
"'(number, number) -> number'"
"\ncaused by:\n"
" Argument count mismatch. Function expects 3 arguments, but only 2 are specified";
}
else
{
expected = R"(Type
@ -1538,14 +1519,7 @@ local b: B = a
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors)
? "Type\n\t"
"'(number, number) -> string'"
"\ncould not be converted into\n\t"
"'(number) -> string'"
"\ncaused by:\n"
" Argument count mismatch. Function expects 2 arguments, but only 1 is specified"
: R"(Type
const std::string expected = R"(Type
'(number, number) -> string'
could not be converted into
'(number) -> string'
@ -1568,14 +1542,7 @@ local b: B = a
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t"
"'(number, number) -> string'"
"\ncould not be converted into\n\t"
"'(number, string) -> string'"
"\ncaused by:\n"
" Argument #2 type is not compatible.\n"
"Type 'string' could not be converted into 'number'"
: R"(Type
const std::string expected = R"(Type
'(number, number) -> string'
could not be converted into
'(number, string) -> string'
@ -1599,13 +1566,7 @@ local b: B = a
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t"
"'(number, number) -> number'"
"\ncould not be converted into\n\t"
"'(number, number) -> (number, boolean)'"
"\ncaused by:\n"
" Function only returns 1 value, but 2 are required here"
: R"(Type
const std::string expected = R"(Type
'(number, number) -> number'
could not be converted into
'(number, number) -> (number, boolean)'
@ -1628,14 +1589,7 @@ local b: B = a
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t"
"'(number, number) -> string'"
"\ncould not be converted into\n\t"
"'(number, number) -> number'"
"\ncaused by:\n"
" Return type is not compatible.\n"
"Type 'string' could not be converted into 'number'"
: R"(Type
const std::string expected = R"(Type
'(number, number) -> string'
could not be converted into
'(number, number) -> number'
@ -1659,14 +1613,7 @@ local b: B = a
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t"
"'(number, number) -> (number, string)'"
"\ncould not be converted into\n\t"
"'(number, number) -> (number, boolean)'"
"\ncaused by:\n"
" Return #2 type is not compatible.\n"
"Type 'string' could not be converted into 'boolean'"
: R"(Type
const std::string expected = R"(Type
'(number, number) -> (number, string)'
could not be converted into
'(number, number) -> (number, boolean)'
@ -1822,24 +1769,6 @@ end
R"(Type function instance add<a, number> depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this time)"
);
}
else if (FFlag::LuauImproveTypePathsInErrors)
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(toString(result.errors[0]), R"(Type
'(string) -> string'
could not be converted into
'((number) -> number)?'
caused by:
None of the union options are compatible. For example:
Type
'(string) -> string'
could not be converted into
'(number) -> number'
caused by:
Argument #1 type is not compatible.
Type 'number' could not be converted into 'string')");
CHECK_EQ(toString(result.errors[1]), R"(Type 'string' could not be converted into 'number')");
}
else
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
@ -1883,31 +1812,15 @@ function t:b() return 2 end -- not OK
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauImproveTypePathsInErrors)
{
CHECK_EQ(
"Type\n\t"
"'(*error-type*) -> number'"
"\ncould not be converted into\n\t"
"'() -> number'\n"
"caused by:\n"
" Argument count mismatch. Function expects 1 argument, but none are specified",
toString(result.errors[0])
);
}
else
{
CHECK_EQ(
R"(Type
CHECK_EQ(
R"(Type
'(*error-type*) -> number'
could not be converted into
'() -> number'
caused by:
Argument count mismatch. Function expects 1 argument, but none are specified)",
toString(result.errors[0])
);
}
toString(result.errors[0])
);
}
TEST_CASE_FIXTURE(Fixture, "too_few_arguments_variadic")
@ -2165,13 +2078,7 @@ z = y -- Not OK, so the line is colorable
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected =
(FFlag::LuauImproveTypePathsInErrors)
? "Type\n\t"
R"('(("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> boolean) & (("blue" | "red") -> ("blue") -> ("blue") -> false) & (("blue" | "red") -> ("red") -> ("red") -> false) & (("blue") -> ("blue") -> ("blue" | "red") -> false) & (("red") -> ("red") -> ("blue" | "red") -> false)')"
"\ncould not be converted into\n\t"
R"('("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> false'; none of the intersection parts are compatible)"
: R"(Type
const std::string expected = R"(Type
'(("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> boolean) & (("blue" | "red") -> ("blue") -> ("blue") -> false) & (("blue" | "red") -> ("red") -> ("red") -> false) & (("blue") -> ("blue") -> ("blue" | "red") -> false) & (("red") -> ("red") -> ("blue" | "red") -> false)'
could not be converted into
'("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> false'; none of the intersection parts are compatible)";
@ -2505,14 +2412,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "num_is_solved_before_num_or_str")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
CHECK(
"Type pack 'string' could not be converted into 'number'; \n"
"this is because the 1st entry in the type pack is `string` in the former type and `number` in the latter type, and `string` is not a "
"subtype of `number`" == toString(result.errors.at(0))
);
else if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2)
CHECK(toString(result.errors.at(0)) == "Type pack 'string' could not be converted into 'number'; at [0], string is not a subtype of number");
else
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0]));
@ -2537,13 +2437,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "num_is_solved_after_num_or_str")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
CHECK(
"Type pack 'string' could not be converted into 'number'; \n"
"this is because the 1st entry in the type pack is `string` in the former type and `number` in the latter type, and `string` is not a "
"subtype of `number`" == toString(result.errors.at(0))
);
else if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2)
CHECK(toString(result.errors.at(0)) == "Type pack 'string' could not be converted into 'number'; at [0], string is not a subtype of number");
else
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0]));
@ -3139,6 +3033,8 @@ TEST_CASE_FIXTURE(Fixture, "hidden_variadics_should_not_break_subtyping")
TEST_CASE_FIXTURE(BuiltinsFixture, "coroutine_wrap_result_call")
{
ScopedFastFlag luauSubtypingFixTailPack{FFlag::LuauSubtypingFixTailPack, true};
CheckResult result = check(R"(
function foo(a, b)
coroutine.wrap(a)(b)
@ -3192,30 +3088,4 @@ TEST_CASE_FIXTURE(Fixture, "recursive_function_calls_should_not_use_the_generali
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "fuzz_unwind_mutually_recursive_union_type_func")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauReduceUnionFollowUnionType, true}
};
// This block ends up minting a type like:
//
// t2 where t1 = union<t2, t1> | union<t2, t1> | union<t2, t1> ; t2 = union<t2, t1>
//
CheckResult result = check(R"(
local _ = ...
function _()
_ = _
end
_[function(...) repeat until _(_[l100]) _ = _ end] += _
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
auto err0 = get<UnknownSymbol>(result.errors[0]);
CHECK(err0);
CHECK_EQ(err0->name, "l100");
auto err1 = get<NotATable>(result.errors[1]);
CHECK(err1);
}
TEST_SUITE_END();

View file

@ -10,9 +10,9 @@
#include "ScopedFlags.h"
#include "doctest.h"
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauInstantiateInSubtyping);
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment)
using namespace Luau;
@ -855,6 +855,8 @@ end
TEST_CASE_FIXTURE(Fixture, "generic_functions_should_be_memory_safe")
{
ScopedFastFlag _{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true};
CheckResult result = check(R"(
--!strict
-- At one point this produced a UAF
@ -873,13 +875,7 @@ y.a.c = y
CHECK(mismatch);
CHECK_EQ(toString(mismatch->givenType), "{ a: { c: T<string>?, d: number }, b: number }");
CHECK_EQ(toString(mismatch->wantedType), "T<string>");
std::string reason =
(FFlag::LuauImproveTypePathsInErrors)
? "\nthis is because \n\t"
" * accessing `a.d` results in `number` in the former type and `string` in the latter type, and `number` is not exactly "
"`string`\n\t"
" * accessing `b` results in `number` in the former type and `string` in the latter type, and `number` is not exactly `string`"
: "at [read \"a\"][read \"d\"], number is not exactly string\n\tat [read \"b\"], number is not exactly string";
std::string reason = "at [read \"a\"][read \"d\"], number is not exactly string\n\tat [read \"b\"], number is not exactly string";
CHECK_EQ(mismatch->reason, reason);
}
else

View file

@ -9,8 +9,7 @@
using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauSolverV2);
TEST_SUITE_BEGIN("IntersectionTypes");
@ -358,21 +357,12 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect")
else
{
LUAU_REQUIRE_ERROR_COUNT(4, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors)
? "Type\n\t"
"'(string, number) -> string'"
"\ncould not be converted into\n\t"
"'(string) -> string'\n"
"caused by:\n"
" Argument count mismatch. Function expects 2 arguments, but only 1 is specified"
: R"(Type
const std::string expected = R"(Type
'(string, number) -> string'
could not be converted into
'(string) -> string'
caused by:
Argument count mismatch. Function expects 2 arguments, but only 1 is specified)";
CHECK_EQ(expected, toString(result.errors[0]));
CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'X & Y'");
CHECK_EQ(toString(result.errors[2]), "Type 'number' could not be converted into 'string'");
@ -397,14 +387,7 @@ TEST_CASE_FIXTURE(Fixture, "table_write_sealed_indirect")
)");
LUAU_REQUIRE_ERROR_COUNT(4, result);
const std::string expected = (FFlag::LuauImproveTypePathsInErrors)
? "Type\n\t"
"'(string, number) -> string'"
"\ncould not be converted into\n\t"
"'(string) -> string'\n"
"caused by:\n"
" Argument count mismatch. Function expects 2 arguments, but only 1 is specified"
: R"(Type
const std::string expected = R"(Type
'(string, number) -> string'
could not be converted into
'(string) -> string'
@ -447,20 +430,7 @@ Type 'number' could not be converted into 'X')";
R"(Type 'number' could not be converted into 'X & Y & Z'; type number (number) is not a subtype of X & Y & Z[0] (X)
type number (number) is not a subtype of X & Y & Z[1] (Y)
type number (number) is not a subtype of X & Y & Z[2] (Z))";
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
{
const std::string expected = "Type "
"'number'"
" could not be converted into "
"'X & Y & Z'; \n"
"this is because \n\t"
" * the 1st component of the intersection is `X`, and `number` is not a subtype of `X`\n\t"
" * the 2nd component of the intersection is `Y`, and `number` is not a subtype of `Y`\n\t"
" * the 3rd component of the intersection is `Z`, and `number` is not a subtype of `Z`";
CHECK_EQ(expected, toString(result.errors[0]));
}
else if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2)
CHECK_EQ(dcrExprected, toString(result.errors[0]));
else
CHECK_EQ(expected, toString(result.errors[0]));
@ -480,23 +450,7 @@ end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
{
const std::string expected = "Type pack "
"'X & Y & Z'"
" could not be converted into "
"'number'; \n"
"this is because \n\t"
" * in the 1st entry in the type pack has the 1st component of the intersection as `X` and the 1st entry in the "
"type pack is `number`, and `X` is not a subtype of `number`\n\t"
" * in the 1st entry in the type pack has the 2nd component of the intersection as `Y` and the 1st entry in the "
"type pack is `number`, and `Y` is not a subtype of `number`\n\t"
" * in the 1st entry in the type pack has the 3rd component of the intersection as `Z` and the 1st entry in the "
"type pack is `number`, and `Z` is not a subtype of `number`";
CHECK_EQ(expected, toString(result.errors[0]));
}
else if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2)
{
CHECK_EQ(
R"(Type pack 'X & Y & Z' could not be converted into 'number'; type X & Y & Z[0][0] (X) is not a subtype of number[0] (number)
@ -549,19 +503,7 @@ TEST_CASE_FIXTURE(Fixture, "intersect_bool_and_false")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
{
const std::string expected = "Type "
"'boolean & false'"
" could not be converted into "
"'true'; \n"
"this is because \n\t"
" * the 1st component of the intersection is `boolean`, which is not a subtype of `true`\n\t"
" * the 2nd component of the intersection is `false`, which is not a subtype of `true`";
CHECK_EQ(expected, toString(result.errors[0]));
}
else if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2)
{
CHECK_EQ(
R"(Type 'boolean & false' could not be converted into 'true'; type boolean & false[0] (boolean) is not a subtype of true (true)
@ -585,21 +527,8 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
// TODO: odd stringification of `false & (boolean & false)`.)
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
{
const std::string expected = "Type "
"'boolean & false & false'"
" could not be converted into "
"'true'; \n"
"this is because \n\t"
" * the 1st component of the intersection is `false`, which is not a subtype of `true`\n\t"
" * the 2nd component of the intersection is `boolean`, which is not a subtype of `true`\n\t"
" * the 3rd component of the intersection is `false`, which is not a subtype of `true`";
CHECK_EQ(expected, toString(result.errors[0]));
}
else if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2)
CHECK_EQ(
R"(Type 'boolean & false & false' could not be converted into 'true'; type boolean & false & false[0] (false) is not a subtype of true (true)
type boolean & false & false[1] (boolean) is not a subtype of true (true)
@ -621,39 +550,7 @@ TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions")
local z : (number) -> number = x -- Not OK
end
)");
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
{
const std::string expected1 =
"Type\n\t"
"'((number?) -> number?) & ((string?) -> string?)'"
"\ncould not be converted into\n\t"
"'(nil) -> nil'; \n"
"this is because \n\t"
" * in the 1st component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
"union as `number` and it returns the 1st entry in the type pack is `nil`, and `number` is not a subtype of `nil`\n\t"
" * in the 2nd component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
"union as `string` and it returns the 1st entry in the type pack is `nil`, and `string` is not a subtype of `nil`";
const std::string expected2 =
"Type\n\t"
"'((number?) -> number?) & ((string?) -> string?)'"
"\ncould not be converted into\n\t"
"'(number) -> number'; \n"
"this is because \n\t"
" * in the 1st component of the intersection, the function returns the 1st entry in the type pack which has the 2nd component of the "
"union as `nil` and it returns the 1st entry in the type pack is `number`, and `nil` is not a subtype of `number`\n\t"
" * in the 2nd component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
"union as `string` and it returns the 1st entry in the type pack is `number`, and `string` is not a subtype of `number`\n\t"
" * in the 2nd component of the intersection, the function returns the 1st entry in the type pack which has the 2nd component of the "
"union as `nil` and it returns the 1st entry in the type pack is `number`, and `nil` is not a subtype of `number`\n\t"
" * in the 2nd component of the intersection, the function takes the 1st entry in the type pack which is `string?` and it takes the 1st "
"entry in the type pack is `number`, and `string?` is not a supertype of `number`";
CHECK_EQ(expected1, toString(result.errors[0]));
CHECK_EQ(expected2, toString(result.errors[1]));
}
else if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2)
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
const std::string expected1 = R"(Type
@ -671,15 +568,6 @@ could not be converted into
CHECK_EQ(expected1, toString(result.errors[0]));
CHECK_EQ(expected2, toString(result.errors[1]));
}
else if (FFlag::LuauImproveTypePathsInErrors)
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
'((number?) -> number?) & ((string?) -> string?)'
could not be converted into
'(number) -> number'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -704,23 +592,11 @@ TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauImproveTypePathsInErrors)
{
const std::string expected = "Type\n\t"
"'((number) -> number) & ((string) -> string)'"
"\ncould not be converted into\n\t"
"'(boolean | number) -> boolean | number'; none of the intersection parts are compatible";
CHECK_EQ(expected, toString(result.errors[0]));
}
else
{
const std::string expected = R"(Type
const std::string expected = R"(Type
'((number) -> number) & ((string) -> string)'
could not be converted into
'(boolean | number) -> boolean | number'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
CHECK_EQ(expected, toString(result.errors[0]));
}
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
@ -733,42 +609,16 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
{
const std::string expected = "Type "
"'{ p: number?, q: number?, r: number? } & { p: number?, q: string? }'"
" could not be converted into "
"'{ p: nil }'; \n"
"this is because \n\t"
" * in the 1st component of the intersection, accessing `p` has the 1st component of the union as `number` and "
"accessing `p` results in `nil`, and `number` is not exactly `nil`\n\t"
" * in the 2nd component of the intersection, accessing `p` has the 1st component of the union as `number` and "
"accessing `p` results in `nil`, and `number` is not exactly `nil`";
CHECK_EQ(expected, toString(result.errors[0]));
}
else if (FFlag::LuauImproveTypePathsInErrors)
{
const std::string expected =
R"(Type
'{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}'
could not be converted into
'{| p: nil |}'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
else
{
const std::string expected =
(FFlag::LuauSolverV2)
? R"(Type '{ p: number?, q: number?, r: number? } & { p: number?, q: string? }' could not be converted into '{ p: nil }'; type { p: number?, q: number?, r: number? } & { p: number?, q: string? }[0][read "p"][0] (number) is not exactly { p: nil }[read "p"] (nil)
const std::string expected =
(FFlag::LuauSolverV2)
? R"(Type '{ p: number?, q: number?, r: number? } & { p: number?, q: string? }' could not be converted into '{ p: nil }'; type { p: number?, q: number?, r: number? } & { p: number?, q: string? }[0][read "p"][0] (number) is not exactly { p: nil }[read "p"] (nil)
type { p: number?, q: number?, r: number? } & { p: number?, q: string? }[1][read "p"][0] (number) is not exactly { p: nil }[read "p"] (nil))"
:
R"(Type
:
R"(Type
'{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}'
could not be converted into
'{| p: nil |}'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
CHECK_EQ(expected, toString(result.errors[0]));
}
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
@ -780,28 +630,7 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
end
)");
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
{
const std::string expected = "Type\n\t"
"'{ p: number?, q: any } & { p: unknown, q: string? }'"
"\ncould not be converted into\n\t"
"'{ p: string?, q: number? }'; \n"
"this is because \n\t"
" * in the 1st component of the intersection, accessing `p` has the 1st component of the union as `number` and "
"accessing `p` results in `string?`, and `number` is not exactly `string?`\n\t"
" * in the 1st component of the intersection, accessing `p` results in `number?` and accessing `p` has the 1st "
"component of the union as `string`, and `number?` is not exactly `string`\n\t"
" * in the 1st component of the intersection, accessing `q` results in `any` and accessing `q` results in "
"`number?`, and `any` is not exactly `number?`\n\t"
" * in the 2nd component of the intersection, accessing `p` results in `unknown` and accessing `p` results in "
"`string?`, and `unknown` is not exactly `string?`\n\t"
" * in the 2nd component of the intersection, accessing `q` has the 1st component of the union as `string` and "
"accessing `q` results in `number?`, and `string` is not exactly `number?`\n\t"
" * in the 2nd component of the intersection, accessing `q` results in `string?` and accessing `q` has the 1st "
"component of the union as `number`, and `string?` is not exactly `number`";
CHECK_EQ(expected, toString(result.errors[0]));
}
else if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2)
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(
@ -817,15 +646,6 @@ could not be converted into
toString(result.errors[0])
);
}
else if (FFlag::LuauImproveTypePathsInErrors)
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
'{| p: number?, q: any |} & {| p: unknown, q: string? |}'
could not be converted into
'{| p: string?, q: number? |}'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -858,52 +678,7 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
end
)");
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
{
const std::string expected1 =
"Type\n\t"
"'((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })'"
"\ncould not be converted into\n\t"
"'(nil) -> { p: number, q: number, r: number }'; \n"
"this is because \n\t"
" * in the 1st component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
"intersection as `{ p: number }` and it returns the 1st entry in the type pack is `{ p: number, q: number, r: number }`, and `{ p: "
"number }` is not a subtype of `{ p: number, q: number, r: number }`\n\t"
" * in the 1st component of the intersection, the function returns the 1st entry in the type pack which has the 2nd component of the "
"intersection as `{ q: number }` and it returns the 1st entry in the type pack is `{ p: number, q: number, r: number }`, and `{ q: "
"number }` is not a subtype of `{ p: number, q: number, r: number }`\n\t"
" * in the 2nd component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
"intersection as `{ p: number }` and it returns the 1st entry in the type pack is `{ p: number, q: number, r: number }`, and `{ p: "
"number }` is not a subtype of `{ p: number, q: number, r: number }`\n\t"
" * in the 2nd component of the intersection, the function returns the 1st entry in the type pack which has the 2nd component of the "
"intersection as `{ r: number }` and it returns the 1st entry in the type pack is `{ p: number, q: number, r: number }`, and `{ r: "
"number }` is not a subtype of `{ p: number, q: number, r: number }`";
const std::string expected2 =
"Type\n\t"
"'((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })'"
"\ncould not be converted into\n\t"
"'(number?) -> { p: number, q: number, r: number }'; \n"
"this is because \n\t"
" * in the 1st component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
"intersection as `{ p: number }` and it returns the 1st entry in the type pack is `{ p: number, q: number, r: number }`, and `{ p: "
"number }` is not a subtype of `{ p: number, q: number, r: number }`\n\t"
" * in the 1st component of the intersection, the function returns the 1st entry in the type pack which has the 2nd component of the "
"intersection as `{ q: number }` and it returns the 1st entry in the type pack is `{ p: number, q: number, r: number }`, and `{ q: "
"number }` is not a subtype of `{ p: number, q: number, r: number }`\n\t"
" * in the 2nd component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
"intersection as `{ p: number }` and it returns the 1st entry in the type pack is `{ p: number, q: number, r: number }`, and `{ p: "
"number }` is not a subtype of `{ p: number, q: number, r: number }`\n\t"
" * in the 2nd component of the intersection, the function returns the 1st entry in the type pack which has the 2nd component of the "
"intersection as `{ r: number }` and it returns the 1st entry in the type pack is `{ p: number, q: number, r: number }`, and `{ r: "
"number }` is not a subtype of `{ p: number, q: number, r: number }`\n\t"
" * in the 2nd component of the intersection, the function takes the 1st entry in the type pack which is `string?` and it takes the 1st "
"entry in the type pack has the 1st component of the union as `number`, and `string?` is not a supertype of `number`";
CHECK_EQ(expected1, toString(result.errors[0]));
CHECK_EQ(expected2, toString(result.errors[1]));
}
else if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2)
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(
@ -928,17 +703,6 @@ could not be converted into
toString(result.errors[1])
);
}
else if (FFlag::LuauImproveTypePathsInErrors)
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(
R"(Type
'((number?) -> {| p: number |} & {| q: number |}) & ((string?) -> {| p: number |} & {| r: number |})'
could not be converted into
'(number?) -> {| p: number, q: number, r: number |}'; none of the intersection parts are compatible)",
toString(result.errors[0])
);
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -966,15 +730,6 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic")
{
LUAU_REQUIRE_ERROR_COUNT(0, result);
}
else if (FFlag::LuauImproveTypePathsInErrors)
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
'((number?) -> a | number) & ((string?) -> a | string)'
could not be converted into
'(number?) -> a'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -1002,15 +757,6 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics")
{
LUAU_REQUIRE_NO_ERRORS(result);
}
else if (FFlag::LuauImproveTypePathsInErrors)
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
'((a?) -> a | b) & ((c?) -> b | c)'
could not be converted into
'(a?) -> (a & c) | b'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -1032,35 +778,7 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs")
end
end
)");
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
{
const std::string expected1 =
"Type\n\t"
"'((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))'"
"\ncould not be converted into\n\t"
"'(nil, a...) -> (nil, b...)'; \n"
"this is because \n\t"
" * in the 1st component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
"union as `number` and it returns the 1st entry in the type pack is `nil`, and `number` is not a subtype of `nil`\n\t"
" * in the 2nd component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
"union as `string` and it returns the 1st entry in the type pack is `nil`, and `string` is not a subtype of `nil`";
const std::string expected2 =
"Type\n\t"
"'((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))'"
"\ncould not be converted into\n\t"
"'(nil, b...) -> (nil, a...)'; \n"
"this is because \n\t"
" * in the 1st component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
"union as `number` and it returns the 1st entry in the type pack is `nil`, and `number` is not a subtype of `nil`\n\t"
" * in the 2nd component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
"union as `string` and it returns the 1st entry in the type pack is `nil`, and `string` is not a subtype of `nil`";
CHECK_EQ(expected1, toString(result.errors[0]));
CHECK_EQ(expected2, toString(result.errors[1]));
}
else if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2)
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(
@ -1080,15 +798,6 @@ could not be converted into
toString(result.errors[1])
);
}
else if (FFlag::LuauImproveTypePathsInErrors)
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
'((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))'
could not be converted into
'(nil, b...) -> (nil, a...)'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -1115,23 +824,11 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauImproveTypePathsInErrors)
{
const std::string expected = "Type\n\t"
"'((nil) -> unknown) & ((number) -> number)'"
"\ncould not be converted into\n\t"
"'(number?) -> number?'; none of the intersection parts are compatible";
CHECK_EQ(expected, toString(result.errors[0]));
}
else
{
const std::string expected = R"(Type
const std::string expected = R"(Type
'((nil) -> unknown) & ((number) -> number)'
could not be converted into
'(number?) -> number?'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
CHECK_EQ(expected, toString(result.errors[0]));
}
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments")
@ -1149,23 +846,11 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauImproveTypePathsInErrors)
{
const std::string expected = "Type\n\t"
"'((number) -> number?) & ((unknown) -> string?)'"
"\ncould not be converted into\n\t"
"'(number?) -> nil'; none of the intersection parts are compatible";
CHECK_EQ(expected, toString(result.errors[0]));
}
else
{
const std::string expected = R"(Type
const std::string expected = R"(Type
'((number) -> number?) & ((unknown) -> string?)'
could not be converted into
'(number?) -> nil'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
CHECK_EQ(expected, toString(result.errors[0]));
}
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
@ -1179,36 +864,7 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
end
)");
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
{
const std::string expected1 =
"Type\n\t"
"'((nil) -> never) & ((number) -> number)'"
"\ncould not be converted into\n\t"
"'(number?) -> number'; \n"
"this is because \n\t"
" * in the 1st component of the intersection, the function takes the 1st entry in the type pack which is `number` and it takes the 1st "
"entry in the type pack has the 2nd component of the union as `nil`, and `number` is not a supertype of `nil`\n\t"
" * in the 2nd component of the intersection, the function takes the 1st entry in the type pack which is `nil` and it takes the 1st "
"entry in the type pack has the 1st component of the union as `number`, and `nil` is not a supertype of `number`";
const std::string expected2 =
"Type\n\t"
"'((nil) -> never) & ((number) -> number)'"
"\ncould not be converted into\n\t"
"'(number?) -> never'; \n"
"this is because \n\t"
" * in the 1st component of the intersection, the function returns the 1st entry in the type pack which is `number` and it returns the "
"1st entry in the type pack is `never`, and `number` is not a subtype of `never`\n\t"
" * in the 1st component of the intersection, the function takes the 1st entry in the type pack which is `number` and it takes the 1st "
"entry in the type pack has the 2nd component of the union as `nil`, and `number` is not a supertype of `nil`\n\t"
" * in the 2nd component of the intersection, the function takes the 1st entry in the type pack which is `nil` and it takes the 1st "
"entry in the type pack has the 1st component of the union as `number`, and `nil` is not a supertype of `number`";
CHECK_EQ(expected1, toString(result.errors[0]));
CHECK_EQ(expected2, toString(result.errors[1]));
}
else if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2)
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(
@ -1229,15 +885,6 @@ could not be converted into
toString(result.errors[1])
);
}
else if (FFlag::LuauImproveTypePathsInErrors)
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
'((nil) -> never) & ((number) -> number)'
could not be converted into
'(number?) -> never'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -1260,40 +907,7 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments")
end
)");
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
{
const std::string expected1 =
"Type\n\t"
"'((never) -> string?) & ((number) -> number?)'"
"\ncould not be converted into\n\t"
"'(never) -> nil'; \n"
"this is because \n\t"
" * in the 1st component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
"union as `number` and it returns the 1st entry in the type pack is `nil`, and `number` is not a subtype of `nil`\n\t"
" * in the 2nd component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
"union as `string` and it returns the 1st entry in the type pack is `nil`, and `string` is not a subtype of `nil`";
const std::string expected2 =
"Type\n\t"
"'((never) -> string?) & ((number) -> number?)'"
"\ncould not be converted into\n\t"
"'(number?) -> nil'; \n"
"this is because \n\t"
" * in the 1st component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
"union as `number` and it returns the 1st entry in the type pack is `nil`, and `number` is not a subtype of `nil`\n\t"
" * in the 1st component of the intersection, the function takes the 1st entry in the type pack which is `number` and it takes the 1st "
"entry in the type pack has the 2nd component of the union as `nil`, and `number` is not a supertype of `nil`\n\t"
" * in the 2nd component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
"union as `string` and it returns the 1st entry in the type pack is `nil`, and `string` is not a subtype of `nil`\n\t"
" * in the 2nd component of the intersection, the function takes the 1st entry in the type pack which is `never` and it takes the 1st "
"entry in the type pack has the 1st component of the union as `number`, and `never` is not a supertype of `number`\n\t"
" * in the 2nd component of the intersection, the function takes the 1st entry in the type pack which is `never` and it takes the 1st "
"entry in the type pack has the 2nd component of the union as `nil`, and `never` is not a supertype of `nil`";
CHECK_EQ(expected1, toString(result.errors[0]));
CHECK_EQ(expected2, toString(result.errors[1]));
}
else if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2)
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
const std::string expected1 = R"(Type
@ -1312,15 +926,6 @@ could not be converted into
CHECK_EQ(expected1, toString(result.errors[0]));
CHECK_EQ(expected2, toString(result.errors[1]));
}
else if (FFlag::LuauImproveTypePathsInErrors)
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
'((never) -> string?) & ((number) -> number?)'
could not be converted into
'(number?) -> nil'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -1345,23 +950,11 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauImproveTypePathsInErrors)
{
const std::string expected = "Type\n\t"
"'((number?) -> (...number)) & ((string?) -> number | string)'"
"\ncould not be converted into\n\t"
"'(number | string) -> (number, number?)'; none of the intersection parts are compatible";
CHECK(expected == toString(result.errors[0]));
}
else
{
const std::string expected = R"(Type
const std::string expected = R"(Type
'((number?) -> (...number)) & ((string?) -> number | string)'
could not be converted into
'(number | string) -> (number, number?)'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
CHECK_EQ(expected, toString(result.errors[0]));
}
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_1")
@ -1429,15 +1022,6 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3")
{
LUAU_REQUIRE_NO_ERRORS(result);
}
else if (FFlag::LuauImproveTypePathsInErrors)
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
'(() -> (a...)) & (() -> (number?, a...))'
could not be converted into
'() -> number'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -1461,21 +1045,7 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
{
const std::string expected = "Type\n\t"
"'((a...) -> ()) & ((number, a...) -> number)'"
"\ncould not be converted into\n\t"
"'((a...) -> ()) & ((number, a...) -> number)'; \n"
"this is because \n\t"
" * in the 1st component of the intersection, the function returns is `()` in the former type and `number` in "
"the latter type, and `()` is not a subtype of `number`\n\t"
" * in the 2nd component of the intersection, the function takes a tail of `a...` and in the 1st component of "
"the intersection, the function takes a tail of `a...`, and `a...` is not a supertype of `a...`";
CHECK(expected == toString(result.errors[0]));
}
else if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2)
{
CHECK_EQ(
R"(Type
@ -1486,16 +1056,6 @@ could not be converted into
toString(result.errors[0])
);
}
else if (FFlag::LuauImproveTypePathsInErrors)
{
CHECK_EQ(
R"(Type
'((a...) -> ()) & ((number, a...) -> number)'
could not be converted into
'(number?) -> ()'; none of the intersection parts are compatible)",
toString(result.errors[0])
);
}
else
{
CHECK_EQ(

View file

@ -12,7 +12,6 @@
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
using namespace Luau;
@ -462,14 +461,7 @@ local b: B.T = a
CheckResult result = frontend.check("game/C");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
{
const std::string expected = "Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'; \n"
"this is because accessing `x` results in `number` in the former type and `string` in the latter type, and "
"`number` is not exactly `string`";
CHECK(expected == toString(result.errors[0]));
}
else if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2)
{
CHECK(
toString(result.errors.at(0)) ==
@ -515,14 +507,7 @@ local b: B.T = a
CheckResult result = frontend.check("game/D");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
{
const std::string expected = "Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'; \n"
"this is because accessing `x` results in `number` in the former type and `string` in the latter type, and "
"`number` is not exactly `string`";
CHECK(expected == toString(result.errors[0]));
}
else if (FFlag::LuauSolverV2)
if (FFlag::LuauSolverV2)
{
CHECK(
toString(result.errors.at(0)) ==

View file

@ -79,5 +79,4 @@ end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_SUITE_END();

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