Compare commits

..

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

76 changed files with 3549 additions and 5434 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

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

View file

@ -132,8 +132,6 @@ struct ConstraintGenerator
DenseHashMap<TypeId, TypeIds> localTypes{nullptr}; DenseHashMap<TypeId, TypeIds> localTypes{nullptr};
DenseHashMap<AstExpr*, Inference> inferredExprCache{nullptr};
DcrLogger* logger; DcrLogger* logger;
ConstraintGenerator( ConstraintGenerator(

View file

@ -365,7 +365,7 @@ public:
* @returns a non-free type that generalizes the argument, or `std::nullopt` if one * @returns a non-free type that generalizes the argument, or `std::nullopt` if one
* does not exist * does not exist
*/ */
std::optional<TypeId> generalizeFreeType(NotNull<Scope> scope, TypeId type); std::optional<TypeId> generalizeFreeType(NotNull<Scope> scope, TypeId type, bool avoidSealingTables = false);
/** /**
* Checks the existing set of constraints to see if there exist any that contain * Checks the existing set of constraints to see if there exist any that contain

View file

@ -38,6 +38,8 @@ struct DataFlowGraph
DefId getDef(const AstExpr* expr) const; DefId getDef(const AstExpr* expr) const;
// Look up the definition optionally, knowing it may not be present. // Look up the definition optionally, knowing it may not be present.
std::optional<DefId> getDefOptional(const AstExpr* expr) const; std::optional<DefId> getDefOptional(const AstExpr* expr) const;
// Look up for the rvalue def for a compound assignment.
std::optional<DefId> getRValueDefForCompoundAssign(const AstExpr* expr) const;
DefId getDef(const AstLocal* local) const; DefId getDef(const AstLocal* local) const;
@ -64,6 +66,10 @@ private:
// All keys in this maps are really only statements that ambiently declares a symbol. // All keys in this maps are really only statements that ambiently declares a symbol.
DenseHashMap<const AstStat*, const Def*> declaredDefs{nullptr}; DenseHashMap<const AstStat*, const Def*> declaredDefs{nullptr};
// Compound assignments are in a weird situation where the local being assigned to is also being used at its
// previous type implicitly in an rvalue position. This map provides the previous binding.
DenseHashMap<const AstExpr*, const Def*> compoundAssignDefs{nullptr};
DenseHashMap<const AstExpr*, const RefinementKey*> astRefinementKeys{nullptr}; DenseHashMap<const AstExpr*, const RefinementKey*> astRefinementKeys{nullptr};
friend struct DataFlowGraphBuilder; friend struct DataFlowGraphBuilder;
}; };

View file

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

View file

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

View file

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

View file

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

View file

@ -1,10 +1,9 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once #pragma once
#include "Luau/DataFlowGraph.h"
#include "Luau/EqSatSimplification.h"
#include "Luau/Module.h" #include "Luau/Module.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/DataFlowGraph.h"
namespace Luau namespace Luau
{ {

View file

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

View file

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

View file

@ -177,7 +177,6 @@ struct FunctionGraphReductionResult
DenseHashSet<TypePackId> blockedPacks{nullptr}; DenseHashSet<TypePackId> blockedPacks{nullptr};
DenseHashSet<TypeId> reducedTypes{nullptr}; DenseHashSet<TypeId> reducedTypes{nullptr};
DenseHashSet<TypePackId> reducedPacks{nullptr}; DenseHashSet<TypePackId> reducedPacks{nullptr};
DenseHashSet<TypeId> irreducibleTypes{nullptr};
}; };
/** /**

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

@ -1151,8 +1151,6 @@ struct AstJsonEncoder : public AstVisitor
return writeString("checked"); return writeString("checked");
case AstAttr::Type::Native: case AstAttr::Type::Native:
return writeString("native"); return writeString("native");
case AstAttr::Type::Deprecated:
return writeString("deprecated");
} }
} }

View file

@ -29,6 +29,7 @@
*/ */
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauStringFormatErrorSuppression)
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3) LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauFollowTableFreeze) LUAU_FASTFLAGVARIABLE(LuauFollowTableFreeze)
@ -711,8 +712,10 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
if (!result.isSubtype) if (!result.isSubtype)
{ {
switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy)) if (FFlag::LuauStringFormatErrorSuppression)
{ {
switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy))
{
case ErrorSuppression::Suppress: case ErrorSuppression::Suppress:
break; break;
case ErrorSuppression::NormalizationFailed: case ErrorSuppression::NormalizationFailed:
@ -722,6 +725,12 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
if (!reasonings.suppressed) if (!reasonings.suppressed)
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location); context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
}
}
else
{
Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result);
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
} }
} }
} }

View file

@ -179,6 +179,8 @@ public:
generic->scope = nullptr; generic->scope = nullptr;
else if (auto free = getMutable<FreeType>(target)) else if (auto free = getMutable<FreeType>(target))
free->scope = nullptr; free->scope = nullptr;
else if (auto fn = getMutable<FunctionType>(target))
fn->scope = nullptr;
else if (auto table = getMutable<TableType>(target)) else if (auto table = getMutable<TableType>(target))
table->scope = nullptr; table->scope = nullptr;
@ -519,6 +521,11 @@ public:
if (FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes) if (FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes)
tt->scope = replacementForNullScope; tt->scope = replacementForNullScope;
} }
else if (auto fn = getMutable<FunctionType>(target))
{
if (FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes)
fn->scope = replacementForNullScope;
}
(*types)[ty] = target; (*types)[ty] = target;
queue.emplace_back(target); queue.emplace_back(target);

View file

@ -33,10 +33,10 @@ LUAU_FASTINT(LuauCheckRecursionLimit)
LUAU_FASTFLAG(DebugLuauLogSolverToJson) LUAU_FASTFLAG(DebugLuauLogSolverToJson)
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAGVARIABLE(LuauPropagateExpectedTypesForCalls)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauDeferBidirectionalInferenceForTableAssignment)
LUAU_FASTFLAGVARIABLE(LuauUngeneralizedTypesForRecursiveFunctions) LUAU_FASTFLAGVARIABLE(LuauUngeneralizedTypesForRecursiveFunctions)
LUAU_FASTFLAGVARIABLE(LuauGlobalSelfAssignmentCycle) LUAU_FASTFLAGVARIABLE(LuauGlobalSelfAssignmentCycle)
@ -45,11 +45,6 @@ LUAU_FASTFLAGVARIABLE(LuauInferLocalTypesInMultipleAssignments)
LUAU_FASTFLAGVARIABLE(LuauDoNotLeakNilInRefinement) LUAU_FASTFLAGVARIABLE(LuauDoNotLeakNilInRefinement)
LUAU_FASTFLAGVARIABLE(LuauExtraFollows) LUAU_FASTFLAGVARIABLE(LuauExtraFollows)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauRetainDefinitionAliasLocations)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAGVARIABLE(LuauCacheInferencePerAstExpr)
LUAU_FASTFLAGVARIABLE(LuauAlwaysResolveAstTypes)
namespace Luau namespace Luau
{ {
@ -237,7 +232,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
typeFunctionRuntime->rootScope = localTypeFunctionScope; 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(); interiorTypes.emplace_back();
prepopulateGlobalScope(scope, block); prepopulateGlobalScope(scope, block);
@ -552,7 +547,7 @@ void ConstraintGenerator::computeRefinement(
refis->get(proposition->key->def)->shouldAppendNilType = refis->get(proposition->key->def)->shouldAppendNilType =
(sense || !eq) && containsSubscriptedDefinition(proposition->key->def) && !proposition->implicitFromCall; (sense || !eq) && containsSubscriptedDefinition(proposition->key->def) && !proposition->implicitFromCall;
} }
else else
{ {
refis->get(proposition->key->def)->shouldAppendNilType = (sense || !eq) && containsSubscriptedDefinition(proposition->key->def); refis->get(proposition->key->def)->shouldAppendNilType = (sense || !eq) && containsSubscriptedDefinition(proposition->key->def);
} }
@ -753,9 +748,6 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
initialFun.typePackParams.push_back(genPack); initialFun.typePackParams.push_back(genPack);
} }
if (FFlag::LuauRetainDefinitionAliasLocations)
initialFun.definitionLocation = alias->location;
if (alias->exported) if (alias->exported)
scope->exportedTypeBindings[alias->name.value] = std::move(initialFun); scope->exportedTypeBindings[alias->name.value] = std::move(initialFun);
else else
@ -813,9 +805,6 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
TypeFun typeFunction{std::move(quantifiedTypeParams), typeFunctionTy}; 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 // Set type bindings and definition locations for this user-defined type function
if (function->exported) if (function->exported)
scope->exportedTypeBindings[function->name.value] = std::move(typeFunction); scope->exportedTypeBindings[function->name.value] = std::move(typeFunction);
@ -843,8 +832,6 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
TypeId initialType = arena->addType(BlockedType{}); TypeId initialType = arena->addType(BlockedType{});
TypeFun initialFun{initialType}; TypeFun initialFun{initialType};
if (FFlag::LuauRetainDefinitionAliasLocations)
initialFun.definitionLocation = classDeclaration->location;
scope->exportedTypeBindings[classDeclaration->name.value] = std::move(initialFun); scope->exportedTypeBindings[classDeclaration->name.value] = std::move(initialFun);
classDefinitionLocations[classDeclaration->name.value] = classDeclaration->location; classDefinitionLocations[classDeclaration->name.value] = classDeclaration->location;
@ -1370,23 +1357,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatRepeat* rep
return ControlFlow::None; 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) ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFunction* function)
{ {
// Local // Local
@ -1424,9 +1394,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti
std::unique_ptr<Constraint> c = std::unique_ptr<Constraint> c =
std::make_unique<Constraint>(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature}); std::make_unique<Constraint>(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature});
if (FFlag::LuauDeprecatedAttribute)
propagateDeprecatedAttributeToConstraint(c->c, function->func);
Constraint* previous = nullptr; Constraint* previous = nullptr;
forEachConstraint( forEachConstraint(
start, start,
@ -1450,11 +1417,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti
module->astTypes[function->func] = functionType; module->astTypes[function->func] = functionType;
} }
else else
{
module->astTypes[function->func] = sig.signature; module->astTypes[function->func] = sig.signature;
if (FFlag::LuauDeprecatedAttribute)
propagateDeprecatedAttributeToType(sig.signature, function->func);
}
return ControlFlow::None; return ControlFlow::None;
} }
@ -1495,11 +1458,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
TypeId generalizedType = arena->addType(BlockedType{}); TypeId generalizedType = arena->addType(BlockedType{});
if (sigFullyDefined) if (sigFullyDefined)
{
emplaceType<BoundType>(asMutable(generalizedType), sig.signature); emplaceType<BoundType>(asMutable(generalizedType), sig.signature);
if (FFlag::LuauDeprecatedAttribute)
propagateDeprecatedAttributeToType(sig.signature, function->func);
}
else else
{ {
const ScopePtr& constraintScope = sig.signatureScope ? sig.signatureScope : sig.bodyScope; const ScopePtr& constraintScope = sig.signatureScope ? sig.signatureScope : sig.bodyScope;
@ -1507,9 +1466,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
NotNull<Constraint> c = addConstraint(constraintScope, function->name->location, GeneralizationConstraint{generalizedType, sig.signature}); NotNull<Constraint> c = addConstraint(constraintScope, function->name->location, GeneralizationConstraint{generalizedType, sig.signature});
getMutable<BlockedType>(generalizedType)->setOwner(c); getMutable<BlockedType>(generalizedType)->setOwner(c);
if (FFlag::LuauDeprecatedAttribute)
propagateDeprecatedAttributeToConstraint(c->c, function->func);
Constraint* previous = nullptr; Constraint* previous = nullptr;
forEachConstraint( forEachConstraint(
start, start,
@ -2022,11 +1978,9 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareFunc
defn.varargLocation = global->vararg ? std::make_optional(global->varargLocation) : std::nullopt; defn.varargLocation = global->vararg ? std::make_optional(global->varargLocation) : std::nullopt;
defn.originalNameLocation = global->nameLocation; 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); FunctionType* ftv = getMutable<FunctionType>(fnType);
ftv->isCheckedFunction = global->isCheckedFunction(); ftv->isCheckedFunction = global->isCheckedFunction();
if (FFlag::LuauDeprecatedAttribute)
ftv->isDeprecatedFunction = global->hasAttribute(AstAttr::Type::Deprecated);
ftv->argNames.reserve(global->paramNames.size); ftv->argNames.reserve(global->paramNames.size);
for (const auto& el : global->paramNames) for (const auto& el : global->paramNames)
@ -2194,23 +2148,13 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
} }
else if (i < exprArgs.size() - 1 || !(arg->is<AstExprCall>() || arg->is<AstExprVarargs>())) else if (i < exprArgs.size() - 1 || !(arg->is<AstExprCall>() || arg->is<AstExprVarargs>()))
{ {
std::optional<TypeId> expectedType = std::nullopt; auto [ty, refinement] = check(scope, arg, /*expectedType*/ std::nullopt, /*forceSingleton*/ false, /*generalize*/ false);
if (FFlag::LuauPropagateExpectedTypesForCalls && i < expectedTypesForCall.size())
{
expectedType = expectedTypesForCall[i];
}
auto [ty, refinement] = check(scope, arg, expectedType, /*forceSingleton*/ false, /*generalize*/ false);
args.push_back(ty); args.push_back(ty);
argumentRefinements.push_back(refinement); argumentRefinements.push_back(refinement);
} }
else else
{ {
std::vector<std::optional<Luau::TypeId>> expectedTypes = {}; auto [tp, refis] = checkPack(scope, arg, {});
if (FFlag::LuauPropagateExpectedTypesForCalls && i < expectedTypesForCall.size())
{
expectedTypes.insert(expectedTypes.end(), expectedTypesForCall.begin() + int(i), expectedTypesForCall.end());
}
auto [tp, refis] = checkPack(scope, arg, expectedTypes);
argTail = tp; argTail = tp;
argumentRefinements.insert(argumentRefinements.end(), refis.begin(), refis.end()); argumentRefinements.insert(argumentRefinements.end(), refis.begin(), refis.end());
} }
@ -2312,7 +2256,7 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
// TODO: How do expectedTypes play into this? Do they? // TODO: How do expectedTypes play into this? Do they?
TypePackId rets = arena->addTypePack(BlockedTypePack{}); TypePackId rets = arena->addTypePack(BlockedTypePack{});
TypePackId argPack = addTypePack(std::move(args), argTail); 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: * To make bidirectional type checking work, we need to solve these constraints in a particular order:
@ -2379,16 +2323,6 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExpr* expr, std::
return Inference{builtinTypes->errorRecoveryType()}; 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; Inference result;
if (auto group = expr->as<AstExprGroup>()) if (auto group = expr->as<AstExprGroup>())
@ -2441,9 +2375,6 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExpr* expr, std::
result = Inference{freshType(scope)}; result = Inference{freshType(scope)};
} }
if (FFlag::LuauCacheInferencePerAstExpr)
inferredExprCache[expr] = result;
LUAU_ASSERT(result.ty); LUAU_ASSERT(result.ty);
module->astTypes[expr] = result.ty; module->astTypes[expr] = result.ty;
if (expectedType) if (expectedType)
@ -2483,7 +2414,8 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool*
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprLocal* local) Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprLocal* local)
{ {
const RefinementKey* key = dfg->getRefinementKey(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; std::optional<TypeId> maybeTy;
@ -2491,6 +2423,11 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprLocal* local)
if (key) if (key)
maybeTy = lookup(scope, local->location, key->def); 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) if (maybeTy)
{ {
TypeId ty = follow(*maybeTy); TypeId ty = follow(*maybeTy);
@ -2506,9 +2443,11 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprLocal* local)
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprGlobal* global) Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprGlobal* global)
{ {
const RefinementKey* key = dfg->getRefinementKey(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 /* 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. * global that is not already in-scope is definitely an unknown symbol.
@ -3181,17 +3120,49 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
if (expectedType) if (expectedType)
{ {
addConstraint( if (FFlag::LuauDeferBidirectionalInferenceForTableAssignment)
scope, {
expr->location, addConstraint(
TableCheckConstraint{ scope,
*expectedType, expr->location,
ty, TableCheckConstraint{
expr, *expectedType,
NotNull{&module->astTypes}, ty,
NotNull{&module->astExpectedTypes}, 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}; return Inference{ty};
@ -3362,7 +3333,7 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
// TODO: Preserve argument names in the function's type. // 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.generics = std::move(genericTypes);
actualFunction.genericPacks = std::move(genericTypePacks); actualFunction.genericPacks = std::move(genericTypePacks);
actualFunction.argNames = std::move(argNames); actualFunction.argNames = std::move(argNames);
@ -3599,10 +3570,8 @@ TypeId ConstraintGenerator::resolveFunctionType(
// TODO: FunctionType needs a pointer to the scope so that we know // TODO: FunctionType needs a pointer to the scope so that we know
// how to quantify/instantiate it. // how to quantify/instantiate it.
FunctionType ftv{TypeLevel{}, {}, {}, argTypes, returnTypes}; FunctionType ftv{TypeLevel{}, scope.get(), {}, {}, argTypes, returnTypes};
ftv.isCheckedFunction = fn->isCheckedFunction(); ftv.isCheckedFunction = fn->isCheckedFunction();
if (FFlag::LuauDeprecatedAttribute)
ftv.isDeprecatedFunction = fn->hasAttribute(AstAttr::Type::Deprecated);
// This replicates the behavior of the appropriate FunctionType // This replicates the behavior of the appropriate FunctionType
// constructors. // constructors.
@ -3649,78 +3618,39 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
} }
else if (ty->is<AstTypeOptional>()) else if (ty->is<AstTypeOptional>())
{ {
if (FFlag::LuauAlwaysResolveAstTypes) return builtinTypes->nilType;
result = builtinTypes->nilType;
else
return builtinTypes->nilType;
} }
else if (auto unionAnnotation = ty->as<AstTypeUnion>()) else if (auto unionAnnotation = ty->as<AstTypeUnion>())
{ {
if (FFlag::LuauAlwaysResolveAstTypes) if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
{ {
if (unionAnnotation->types.size == 1) if (unionAnnotation->types.size == 1)
result = resolveType(scope, unionAnnotation->types.data[0], inTypeArguments); return 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});
}
} }
else
std::vector<TypeId> parts;
for (AstType* part : unionAnnotation->types)
{ {
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) parts.push_back(resolveType(scope, part, inTypeArguments));
{
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});
} }
result = arena->addType(UnionType{parts});
} }
else if (auto intersectionAnnotation = ty->as<AstTypeIntersection>()) else if (auto intersectionAnnotation = ty->as<AstTypeIntersection>())
{ {
if (FFlag::LuauAlwaysResolveAstTypes) if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
{ {
if (intersectionAnnotation->types.size == 1) if (intersectionAnnotation->types.size == 1)
result = resolveType(scope, intersectionAnnotation->types.data[0], inTypeArguments); return 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});
}
} }
else
std::vector<TypeId> parts;
for (AstType* part : intersectionAnnotation->types)
{ {
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) parts.push_back(resolveType(scope, part, inTypeArguments));
{
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});
} }
result = arena->addType(IntersectionType{parts});
} }
else if (auto typeGroupAnnotation = ty->as<AstTypeGroup>()) else if (auto typeGroupAnnotation = ty->as<AstTypeGroup>())
{ {

View file

@ -35,13 +35,9 @@ LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500)
LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification) LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTablesOnScope) LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTablesOnScope)
LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock) LUAU_FASTFLAGVARIABLE(LuauPrecalculateMutatedFreeTypes2)
LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization) LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauSearchForRefineableType) LUAU_FASTFLAG(LuauSearchForRefineableType)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall)
namespace Luau namespace Luau
{ {
@ -361,19 +357,32 @@ ConstraintSolver::ConstraintSolver(
{ {
unsolvedConstraints.emplace_back(c); unsolvedConstraints.emplace_back(c);
auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes(); if (FFlag::LuauPrecalculateMutatedFreeTypes2)
for (auto ty : maybeMutatedTypesPerConstraint)
{ {
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0); auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes();
refCount += 1; for (auto ty : maybeMutatedTypesPerConstraint)
if (FFlag::DebugLuauGreedyGeneralization)
{ {
auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, DenseHashSet<const Constraint*>{nullptr}); auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
it->second.insert(c.get()); 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) for (NotNull<const Constraint> dep : c->dependencies)
@ -464,24 +473,48 @@ void ConstraintSolver::run()
unblock(c); unblock(c);
unsolvedConstraints.erase(unsolvedConstraints.begin() + ptrdiff_t(i)); unsolvedConstraints.erase(unsolvedConstraints.begin() + ptrdiff_t(i));
const auto maybeMutated = maybeMutatedFreeTypes.find(c); if (FFlag::LuauPrecalculateMutatedFreeTypes2)
if (maybeMutated != maybeMutatedFreeTypes.end())
{ {
DenseHashSet<TypeId> seen{nullptr}; const auto maybeMutated = maybeMutatedFreeTypes.find(c);
for (auto ty : maybeMutated->second) if (maybeMutated != maybeMutatedFreeTypes.end())
{ {
// There is a high chance that this type has been rebound DenseHashSet<TypeId> seen{nullptr};
// across blocked types, rebound free types, pending for (auto ty : maybeMutated->second)
// expansion types, etc, so we need to follow it.
ty = follow(ty);
if (FFlag::DebugLuauGreedyGeneralization)
{ {
if (seen.contains(ty)) // There is a high chance that this type has been rebound
continue; // across blocked types, rebound free types, pending
seen.insert(ty); // 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]; size_t& refCount = unresolvedConstraints[ty];
if (refCount > 0) if (refCount > 0)
refCount -= 1; refCount -= 1;
@ -493,9 +526,6 @@ void ConstraintSolver::run()
// refcount to be 1 or 0. // refcount to be 1 or 0.
if (refCount <= 1) if (refCount <= 1)
unblock(ty, Location{}); unblock(ty, Location{});
if (FFlag::DebugLuauGreedyGeneralization && refCount == 0)
generalizeOneType(ty);
} }
} }
@ -595,6 +625,14 @@ bool ConstraintSolver::isDone() const
struct TypeSearcher : TypeVisitor struct TypeSearcher : TypeVisitor
{ {
enum struct Polarity: uint8_t
{
None = 0b00,
Positive = 0b01,
Negative = 0b10,
Mixed = 0b11,
};
TypeId needle; TypeId needle;
Polarity current = Polarity::Positive; Polarity current = Polarity::Positive;
@ -710,12 +748,12 @@ void ConstraintSolver::generalizeOneType(TypeId ty)
switch (ts.result) switch (ts.result)
{ {
case Polarity::None: case TypeSearcher::Polarity::None:
asMutable(ty)->reassign(Type{BoundType{upperBound}}); asMutable(ty)->reassign(Type{BoundType{upperBound}});
break; break;
case Polarity::Negative: case TypeSearcher::Polarity::Negative:
case Polarity::Mixed: case TypeSearcher::Polarity::Mixed:
if (get<UnknownType>(upperBound) && ts.count > 1) if (get<UnknownType>(upperBound) && ts.count > 1)
{ {
asMutable(ty)->reassign(Type{GenericType{tyScope}}); asMutable(ty)->reassign(Type{GenericType{tyScope}});
@ -725,17 +763,15 @@ void ConstraintSolver::generalizeOneType(TypeId ty)
asMutable(ty)->reassign(Type{BoundType{upperBound}}); asMutable(ty)->reassign(Type{BoundType{upperBound}});
break; break;
case Polarity::Positive: case TypeSearcher::Polarity::Positive:
if (get<UnknownType>(lowerBound) && ts.count > 1) if (get<UnknownType>(lowerBound) && ts.count > 1)
{ {
asMutable(ty)->reassign(Type{GenericType{tyScope}}); asMutable(ty)->reassign(Type{GenericType{tyScope}});
function->generics.emplace_back(ty); function->generics.emplace_back(ty);
} }
else else
asMutable(ty)->reassign(Type{BoundType{lowerBound}}); asMutable(ty)->reassign(Type{BoundType{lowerBound}});
break; break;
default:
LUAU_ASSERT(!"Unreachable");
} }
} }
} }
@ -882,15 +918,6 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
bind(constraint, generalizedType, *generalizedTy); bind(constraint, generalizedType, *generalizedTy);
else else
unify(constraint, generalizedType, *generalizedTy); unify(constraint, generalizedType, *generalizedTy);
if (FFlag::LuauDeprecatedAttribute)
{
if (FunctionType* fty = getMutable<FunctionType>(follow(generalizedType)))
{
if (c.hasDeprecatedAttribute)
fty->isDeprecatedFunction = true;
}
}
} }
else else
{ {
@ -904,12 +931,12 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
// clang-tidy doesn't understand this is safe. // clang-tidy doesn't understand this is safe.
if (constraint->scope->interiorFreeTypes) if (constraint->scope->interiorFreeTypes)
for (TypeId ty : *constraint->scope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access) 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 else
{ {
for (TypeId ty : c.interiorTypes) 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)) if (isBlocked(*ty))
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored. // We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType); emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
// We also need to unconditionally unblock these types, otherwise // We also need to unconditionally unblock these types, otherwise
// you end up with funky looking "Blocked on *no-refine*." // you end up with funky looking "Blocked on *no-refine*."
unblock(*ty, constraint->location); unblock(*ty, constraint->location);
@ -1483,7 +1510,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
if (status == OverloadResolver::Analysis::Ok) if (status == OverloadResolver::Analysis::Ok)
overloadToUse = overload; 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}}; Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}};
const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy); const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy);
@ -1518,11 +1545,6 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
queuer.traverse(overloadToUse); queuer.traverse(overloadToUse);
queuer.traverse(inferredTy); 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); unblock(c.result, constraint->location);
return true; return true;
@ -1536,43 +1558,6 @@ static AstExpr* unwrapGroup(AstExpr* expr)
return 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) bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<const Constraint> constraint)
{ {
TypeId fn = follow(c.fn); TypeId fn = follow(c.fn);
@ -1615,49 +1600,36 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
DenseHashMap<TypeId, TypeId> replacements{nullptr}; DenseHashMap<TypeId, TypeId> replacements{nullptr};
DenseHashMap<TypePackId, TypePackId> replacementPacks{nullptr}; DenseHashMap<TypePackId, TypePackId> replacementPacks{nullptr};
ContainsGenerics containsGenerics;
for (auto generic : ftv->generics) for (auto generic : ftv->generics)
{
replacements[generic] = builtinTypes->unknownType; replacements[generic] = builtinTypes->unknownType;
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
containsGenerics.generics.insert(generic);
}
for (auto genericPack : ftv->genericPacks) for (auto genericPack : ftv->genericPacks)
{
replacementPacks[genericPack] = builtinTypes->unknownTypePack; 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 // 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 // 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. // 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)}; if (*res != fn)
std::optional<TypeId> res = replacer.substitute(fn);
if (res)
{ {
if (*res != fn) FunctionType* ftvMut = getMutable<FunctionType>(*res);
{ LUAU_ASSERT(ftvMut);
FunctionType* ftvMut = getMutable<FunctionType>(*res); ftvMut->generics.clear();
LUAU_ASSERT(ftvMut); ftvMut->genericPacks.clear();
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);
} }
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; (*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* expectedLambdaTy = get<FunctionType>(expectedArgTy);
const FunctionType* lambdaTy = get<FunctionType>(actualArgTy); const FunctionType* lambdaTy = get<FunctionType>(actualArgTy);
const AstExprFunction* lambdaExpr = expr->as<AstExprFunction>(); 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(get<BlockedType>(resultType));
LUAU_ASSERT(canMutate(resultType, constraint)); LUAU_ASSERT(canMutate(resultType, constraint));
if (FFlag::LuauHasPropProperBlock) if (isBlocked(subjectType) || get<PendingExpansionType>(subjectType) || get<TypeFunctionInstanceType>(subjectType))
{ return block(subjectType, constraint);
if (isBlocked(subjectType))
return block(subjectType, constraint);
}
else
{
if (isBlocked(subjectType) || get<PendingExpansionType>(subjectType) || get<TypeFunctionInstanceType>(subjectType))
return block(subjectType, constraint);
}
if (const TableType* subjectTable = getTableType(subjectType)) if (const TableType* subjectTable = getTableType(subjectType))
{ {
@ -2399,18 +2359,11 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Cons
for (TypePackId r : result.reducedPacks) for (TypePackId r : result.reducedPacks)
unblock(r, constraint->location); unblock(r, constraint->location);
if (FFlag::LuauNewTypeFunReductionChecks2)
{
for (TypeId ity : result.irreducibleTypes)
uninhabitedTypeFunctions.insert(ity);
}
bool reductionFinished = result.blockedTypes.empty() && result.blockedPacks.empty(); bool reductionFinished = result.blockedTypes.empty() && result.blockedPacks.empty();
ty = follow(ty); ty = follow(ty);
// If we couldn't reduce this type function, stick it in the set! // 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; typeFunctionsToFinalize[ty] = constraint;
if (force || reductionFinished) 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); TypeId t = follow(type);
if (get<FreeType>(t)) if (get<FreeType>(t))
@ -3345,7 +3298,7 @@ std::optional<TypeId> ConstraintSolver::generalizeFreeType(NotNull<Scope> scope,
// that until all constraint generation is complete. // 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) bool ConstraintSolver::hasUnresolvedConstraints(TypeId ty)

View file

@ -13,22 +13,32 @@
LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauPreprocessTypestatedArgument)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackTrueReset)
namespace Luau namespace Luau
{ {
bool doesCallError(const AstExprCall* call); // TypeInfer.cpp bool doesCallError(const AstExprCall* call); // TypeInfer.cpp
struct ReferencedDefFinder : public AstVisitor
{
bool visit(AstExprLocal* local) override
{
referencedLocalDefs.push_back(local->local);
return true;
}
// ast defs is just a mapping from expr -> def in general
// will get built up by the dfg builder
// localDefs, we need to copy over
std::vector<AstLocal*> referencedLocalDefs;
};
struct PushScope struct PushScope
{ {
ScopeStack& stack; ScopeStack& stack;
size_t previousSize;
PushScope(ScopeStack& stack, DfgScope* scope) PushScope(ScopeStack& stack, DfgScope* scope)
: stack(stack) : stack(stack)
, previousSize(stack.size())
{ {
// `scope` should never be `nullptr` here. // `scope` should never be `nullptr` here.
LUAU_ASSERT(scope); LUAU_ASSERT(scope);
@ -37,18 +47,7 @@ struct PushScope
~PushScope() ~PushScope()
{ {
if (FFlag::LuauDfgScopeStackTrueReset) stack.pop_back();
{
// 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();
}
} }
}; };
@ -83,6 +82,12 @@ std::optional<DefId> DataFlowGraph::getDefOptional(const AstExpr* expr) const
return NotNull{*def}; return NotNull{*def};
} }
std::optional<DefId> DataFlowGraph::getRValueDefForCompoundAssign(const AstExpr* expr) const
{
auto def = compoundAssignDefs.find(expr);
return def ? std::optional<DefId>(*def) : std::nullopt;
}
DefId DataFlowGraph::getDef(const AstLocal* local) const DefId DataFlowGraph::getDef(const AstLocal* local) const
{ {
auto def = localDefs.find(local); auto def = localDefs.find(local);
@ -873,12 +878,6 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
{ {
visitExpr(c->func); visitExpr(c->func);
if (FFlag::LuauPreprocessTypestatedArgument)
{
for (AstExpr* arg : c->args)
visitExpr(arg);
}
if (shouldTypestateForFirstArgument(*c) && c->args.size > 1 && isLValue(*c->args.begin())) if (shouldTypestateForFirstArgument(*c) && c->args.size > 1 && isLValue(*c->args.begin()))
{ {
AstExpr* firstArg = *c->args.begin(); AstExpr* firstArg = *c->args.begin();
@ -909,11 +908,8 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
visitLValue(firstArg, def); 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 // We treat function calls as "subscripted" as they could potentially
// return a subscripted value, consider: // return a subscripted value, consider:

View file

@ -1,6 +1,8 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAGVARIABLE(LuauDebugInfoDefn)
namespace Luau namespace Luau
{ {
@ -213,6 +215,15 @@ declare debug: {
)BUILTIN_SRC"; )BUILTIN_SRC";
static const std::string kBuiltinDefinitionDebugSrc_DEPRECATED = R"BUILTIN_SRC(
declare debug: {
info: (<R...>(thread: thread, level: number, options: string) -> R...) & (<R...>(level: number, options: string) -> R...) & (<A..., R1..., R2...>(func: (A...) -> R1..., options: string) -> R2...),
traceback: ((message: string?, level: number?) -> string) & ((thread: thread, message: string?, level: number?) -> string),
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionUtf8Src = R"BUILTIN_SRC( static const std::string kBuiltinDefinitionUtf8Src = R"BUILTIN_SRC(
declare utf8: { declare utf8: {
@ -298,7 +309,7 @@ std::string getBuiltinDefinitionSource()
result += kBuiltinDefinitionOsSrc; result += kBuiltinDefinitionOsSrc;
result += kBuiltinDefinitionCoroutineSrc; result += kBuiltinDefinitionCoroutineSrc;
result += kBuiltinDefinitionTableSrc; result += kBuiltinDefinitionTableSrc;
result += kBuiltinDefinitionDebugSrc; result += FFlag::LuauDebugInfoDefn ? kBuiltinDefinitionDebugSrc : kBuiltinDefinitionDebugSrc_DEPRECATED;
result += kBuiltinDefinitionUtf8Src; result += kBuiltinDefinitionUtf8Src;
result += kBuiltinDefinitionBufferSrc; result += kBuiltinDefinitionBufferSrc;
result += kBuiltinDefinitionVectorSrc; result += kBuiltinDefinitionVectorSrc;

View file

@ -22,27 +22,26 @@
#include "Luau/Module.h" #include "Luau/Module.h"
#include "Luau/Clone.h" #include "Luau/Clone.h"
#include "AutocompleteCore.h" #include "AutocompleteCore.h"
#include <optional>
LUAU_FASTINT(LuauTypeInferRecursionLimit); LUAU_FASTINT(LuauTypeInferRecursionLimit);
LUAU_FASTINT(LuauTypeInferIterationLimit); LUAU_FASTINT(LuauTypeInferIterationLimit);
LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete) LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteBugfixes)
LUAU_FASTFLAGVARIABLE(LuauMixedModeDefFinderTraversesTypeOf) LUAU_FASTFLAGVARIABLE(LuauMixedModeDefFinderTraversesTypeOf)
LUAU_FASTFLAGVARIABLE(LuauCloneIncrementalModule) LUAU_FASTFLAGVARIABLE(LuauCloneIncrementalModule)
LUAU_FASTFLAGVARIABLE(DebugLogFragmentsFromAutocomplete) LUAU_FASTFLAGVARIABLE(LogFragmentsFromAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauBetterCursorInCommentDetection) LUAU_FASTFLAGVARIABLE(LuauBetterCursorInCommentDetection)
LUAU_FASTFLAGVARIABLE(LuauAllFreeTypesHaveScopes) LUAU_FASTFLAGVARIABLE(LuauAllFreeTypesHaveScopes)
LUAU_FASTFLAGVARIABLE(LuauFragmentAcSupportsReporter) LUAU_FASTFLAGVARIABLE(LuauFragmentAcSupportsReporter)
LUAU_FASTFLAGVARIABLE(LuauPersistConstraintGenerationScopes) LUAU_FASTFLAGVARIABLE(LuauPersistConstraintGenerationScopes)
LUAU_FASTFLAG(LuauModuleHoldsAstRoot)
LUAU_FASTFLAGVARIABLE(LuauCloneTypeAliasBindings) LUAU_FASTFLAGVARIABLE(LuauCloneTypeAliasBindings)
LUAU_FASTFLAGVARIABLE(LuauCloneReturnTypePack) LUAU_FASTFLAGVARIABLE(LuauCloneReturnTypePack)
LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteDemandBasedCloning) LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteDemandBasedCloning)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauFragmentNoTypeFunEval) LUAU_FASTFLAGVARIABLE(LuauFragmentNoTypeFunEval)
LUAU_FASTFLAGVARIABLE(LuauBetterScopeSelection)
LUAU_FASTFLAGVARIABLE(LuauBlockDiffFragmentSelection)
namespace namespace
{ {
@ -88,433 +87,6 @@ void cloneModuleMap(
} }
} }
static std::pair<size_t, size_t> getDocumentOffsets(std::string_view src, const Position& startPos, const Position& endPos);
// when typing a function partially, get the span of the first line
// e.g. local function fn() : ... - typically we want to provide autocomplete results if you're
// editing type annotations in this range
Location getFunctionDeclarationExtents(AstExprFunction* exprFn, AstExpr* exprName = nullptr, AstLocal* localName = nullptr)
{
auto fnBegin = exprFn->location.begin;
auto fnEnd = exprFn->location.end;
if (auto returnAnnot = exprFn->returnAnnotation)
{
if (returnAnnot->tailType)
fnEnd = returnAnnot->tailType->location.end;
else if (returnAnnot->types.size != 0)
fnEnd = returnAnnot->types.data[returnAnnot->types.size - 1]->location.end;
}
else if (exprFn->args.size != 0)
{
auto last = exprFn->args.data[exprFn->args.size - 1];
if (last->annotation)
fnEnd = last->annotation->location.end;
else
fnEnd = last->location.end;
}
else if (exprFn->genericPacks.size != 0)
fnEnd = exprFn->genericPacks.data[exprFn->genericPacks.size - 1]->location.end;
else if (exprFn->generics.size != 0)
fnEnd = exprFn->generics.data[exprFn->generics.size - 1]->location.end;
else if (exprName)
fnEnd = exprName->location.end;
else if (localName)
fnEnd = localName->location.end;
return Location{fnBegin, fnEnd};
};
Location getAstStatForExtents(AstStatFor* forStat)
{
auto begin = forStat->location.begin;
auto end = forStat->location.end;
if (forStat->step)
end = forStat->step->location.end;
else if (forStat->to)
end = forStat->to->location.end;
else if (forStat->from)
end = forStat->from->location.end;
else if (forStat->var)
end = forStat->var->location.end;
return Location{begin, end};
}
Location getFragmentLocation(AstStat* nearestStatement, const Position& cursorPosition)
{
Location empty{cursorPosition, cursorPosition};
if (nearestStatement)
{
Location nonEmpty{nearestStatement->location.begin, cursorPosition};
// If your sibling is a do block, do nothing
if (auto doEnd = nearestStatement->as<AstStatBlock>())
return empty;
// If you're inside the body of the function and this is your sibling, empty fragment
// If you're outside the body (e.g. you're typing stuff out, non-empty)
if (auto fn = nearestStatement->as<AstStatFunction>())
{
auto loc = getFunctionDeclarationExtents(fn->func, fn->name, /* local */ nullptr);
if (loc.containsClosed(cursorPosition))
return nonEmpty;
else if (fn->func->body->location.containsClosed(cursorPosition) || fn->location.end <= cursorPosition)
return empty;
else if (fn->func->location.contains(cursorPosition))
return nonEmpty;
}
if (auto fn = nearestStatement->as<AstStatLocalFunction>())
{
auto loc = getFunctionDeclarationExtents(fn->func, /* global func */ nullptr, fn->name);
if (loc.containsClosed(cursorPosition))
return nonEmpty;
else if (fn->func->body->location.containsClosed(cursorPosition) || fn->location.end <= cursorPosition)
return empty;
else if (fn->func->location.contains(cursorPosition))
return nonEmpty;
}
if (auto wh = nearestStatement->as<AstStatWhile>())
{
if (!wh->hasDo)
return nonEmpty;
else
return empty;
}
if (auto forStat = nearestStatement->as<AstStatFor>())
{
if (!forStat->hasDo)
return nonEmpty;
else
return empty;
}
if (auto forIn = nearestStatement->as<AstStatForIn>())
{
// If we don't have a do statement
if (!forIn->hasDo)
return nonEmpty;
else
return empty;
}
if (auto ifS = nearestStatement->as<AstStatIf>())
{
auto conditionExtents = Location{ifS->location.begin, ifS->condition->location.end};
if (conditionExtents.containsClosed(cursorPosition))
return nonEmpty;
else if (ifS->thenbody->location.containsClosed(cursorPosition))
return empty;
else if (auto elseS = ifS->elsebody)
{
if (auto elseIf = ifS->elsebody->as<AstStatIf>())
{
if (elseIf->thenbody->hasEnd)
return empty;
else
return {elseS->location.begin, cursorPosition};
}
return empty;
}
}
return nonEmpty;
}
return empty;
}
struct NearestStatementFinder : public AstVisitor
{
explicit NearestStatementFinder(const Position& cursorPosition)
: cursor(cursorPosition)
{
}
bool visit(AstStatBlock* block) override
{
if (block->location.containsClosed(cursor))
{
parent = block;
for (auto v : block->body)
{
if (v->location.begin <= cursor)
{
nearest = v;
}
}
return true;
}
else
return false;
}
const Position& cursor;
AstStat* nearest = nullptr;
AstStatBlock* parent = nullptr;
};
// This struct takes a block found in a updated AST and looks for the corresponding block in a different ast.
// This is a best effort check - we are looking for the block that is as close in location, ideally the same
// block as the one from the updated AST
struct NearestLikelyBlockFinder : public AstVisitor
{
explicit NearestLikelyBlockFinder(NotNull<AstStatBlock> stmtBlockRecentAst)
: stmtBlockRecentAst(stmtBlockRecentAst)
{
}
bool visit(AstStatBlock* block) override
{
if (block->location.begin <= stmtBlockRecentAst->location.begin)
{
if (found)
{
if (found.value()->location.begin < block->location.begin)
found.emplace(block);
}
else
{
found.emplace(block);
}
}
return true;
}
NotNull<AstStatBlock> stmtBlockRecentAst;
std::optional<AstStatBlock*> found = std::nullopt;
};
// Diffs two ast stat blocks. Once at the first difference, consume between that range and the end of the nearest statement
std::optional<Position> blockDiffStart(AstStatBlock* blockOld, AstStatBlock* blockNew, AstStat* nearestStatementNewAst)
{
AstArray<AstStat*> _old = blockOld->body;
AstArray<AstStat*> _new = blockNew->body;
size_t oldSize = _old.size;
size_t stIndex = 0;
// We couldn't find a nearest statement
if (nearestStatementNewAst == blockNew)
return std::nullopt;
bool found = false;
for (auto st : _new)
{
if (st == nearestStatementNewAst)
{
found = true;
break;
}
stIndex++;
}
if (!found)
return std::nullopt;
// Take care of some easy cases!
if (oldSize == 0 && _new.size >= 0)
return {_new.data[0]->location.begin};
if (_new.size < oldSize)
return std::nullopt;
for (size_t i = 0; i < std::min(oldSize, stIndex + 1); i++)
{
AstStat* oldStat = _old.data[i];
AstStat* newStat = _new.data[i];
bool isSame = oldStat->classIndex == newStat->classIndex && oldStat->location == newStat->location;
if (!isSame)
return {oldStat->location.begin};
}
if (oldSize <= stIndex)
return {_new.data[oldSize]->location.begin};
return std::nullopt;
}
FragmentRegion getFragmentRegion(AstStatBlock* root, const Position& cursorPosition)
{
NearestStatementFinder nsf{cursorPosition};
root->visit(&nsf);
AstStatBlock* parent = root;
if (nsf.parent)
parent = nsf.parent;
return FragmentRegion{getFragmentLocation(nsf.nearest, cursorPosition), nsf.nearest, parent};
};
FragmentRegion getFragmentRegionWithBlockDiff(AstStatBlock* stale, AstStatBlock* fresh, const Position& cursorPos)
{
// Visit the new ast
NearestStatementFinder nsf{cursorPos};
fresh->visit(&nsf);
// parent must always be non-null
NotNull<AstStatBlock> parent{nsf.parent ? nsf.parent : fresh};
NotNull<AstStat> nearest{nsf.nearest ? nsf.nearest : fresh};
// Grab the same start block in the stale ast
NearestLikelyBlockFinder lsf{parent};
stale->visit(&lsf);
if (auto sameBlock = lsf.found)
{
if (std::optional<Position> fd = blockDiffStart(*sameBlock, parent, nearest))
return FragmentRegion{Location{*fd, cursorPos}, nearest, parent};
}
return FragmentRegion{getFragmentLocation(nsf.nearest, cursorPos), nearest, parent};
}
FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* stale, const Position& cursorPos, AstStatBlock* lastGoodParse)
{
// the freshest ast can sometimes be null if the parse was bad.
if (lastGoodParse == nullptr)
return {};
FragmentRegion region = FFlag::LuauBlockDiffFragmentSelection ? getFragmentRegionWithBlockDiff(stale, lastGoodParse, cursorPos)
: getFragmentRegion(lastGoodParse, cursorPos);
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(stale, cursorPos);
LUAU_ASSERT(ancestry.size() >= 1);
// We should only pick up locals that are before the region
DenseHashMap<AstName, AstLocal*> localMap{AstName()};
std::vector<AstLocal*> localStack;
for (AstNode* node : ancestry)
{
if (auto block = node->as<AstStatBlock>())
{
for (auto stat : block->body)
{
if (stat->location.begin < region.fragmentLocation.begin)
{
// This statement precedes the current one
if (auto statLoc = stat->as<AstStatLocal>())
{
for (auto v : statLoc->vars)
{
localStack.push_back(v);
localMap[v->name] = v;
}
}
else if (auto locFun = stat->as<AstStatLocalFunction>())
{
localStack.push_back(locFun->name);
localMap[locFun->name->name] = locFun->name;
if (locFun->location.contains(cursorPos))
{
for (AstLocal* loc : locFun->func->args)
{
localStack.push_back(loc);
localMap[loc->name] = loc;
}
}
}
else if (auto globFun = stat->as<AstStatFunction>())
{
if (globFun->location.contains(cursorPos))
{
for (AstLocal* loc : globFun->func->args)
{
localStack.push_back(loc);
localMap[loc->name] = loc;
}
}
}
else if (auto typeFun = stat->as<AstStatTypeFunction>(); typeFun)
{
if (typeFun->location.contains(cursorPos))
{
for (AstLocal* loc : typeFun->body->args)
{
localStack.push_back(loc);
localMap[loc->name] = loc;
}
}
}
else if (auto forL = stat->as<AstStatFor>())
{
if (forL->var && forL->var->location.begin < region.fragmentLocation.begin)
{
localStack.push_back(forL->var);
localMap[forL->var->name] = forL->var;
}
}
else if (auto forIn = stat->as<AstStatForIn>())
{
for (auto var : forIn->vars)
{
if (var->location.begin < region.fragmentLocation.begin)
{
localStack.push_back(var);
localMap[var->name] = var;
}
}
}
}
}
}
if (auto exprFunc = node->as<AstExprFunction>())
{
if (exprFunc->location.contains(cursorPos))
{
for (auto v : exprFunc->args)
{
localStack.push_back(v);
localMap[v->name] = v;
}
}
}
}
return {localMap, localStack, ancestry, region.nearestStatement, region.parentBlock, region.fragmentLocation};
}
std::optional<FragmentParseResult> parseFragment(
AstStatBlock* stale,
AstStatBlock* mostRecentParse,
AstNameTable* names,
std::string_view src,
const Position& cursorPos,
std::optional<Position> fragmentEndPosition
)
{
if (mostRecentParse == nullptr)
return std::nullopt;
FragmentAutocompleteAncestryResult result = findAncestryForFragmentParse(stale, cursorPos, mostRecentParse);
AstStat* nearestStatement = result.nearestStatement;
Position startPos = result.fragmentSelectionRegion.begin;
Position endPos = fragmentEndPosition.value_or(result.fragmentSelectionRegion.end);
auto [offsetStart, parseLength] = getDocumentOffsets(src, startPos, endPos);
const char* srcStart = src.data() + offsetStart;
std::string_view dbg = src.substr(offsetStart, parseLength);
FragmentParseResult fragmentResult;
fragmentResult.fragmentToParse = std::string(dbg);
// For the duration of the incremental parse, we want to allow the name table to re-use duplicate names
if (FFlag::DebugLogFragmentsFromAutocomplete)
logLuau("Fragment Selected", dbg);
ParseOptions opts;
opts.allowDeclarationSyntax = false;
opts.captureComments = true;
opts.parseFragment = FragmentParseResumeSettings{std::move(result.localMap), std::move(result.localStack), startPos};
ParseResult p = Luau::Parser::parse(srcStart, parseLength, *names, *fragmentResult.alloc, opts);
// This means we threw a ParseError and we should decline to offer autocomplete here.
if (p.root == nullptr)
return std::nullopt;
std::vector<AstNode*> fabricatedAncestry = std::move(result.ancestry);
std::vector<AstNode*> fragmentAncestry = findAncestryAtPositionForAutocomplete(p.root, cursorPos);
fabricatedAncestry.insert(fabricatedAncestry.end(), fragmentAncestry.begin(), fragmentAncestry.end());
if (nearestStatement == nullptr)
nearestStatement = p.root;
fragmentResult.root = p.root;
fragmentResult.ancestry = std::move(fabricatedAncestry);
fragmentResult.nearestStatement = nearestStatement;
fragmentResult.commentLocations = std::move(p.commentLocations);
fragmentResult.scopePos = result.parentBlock->location.begin;
return fragmentResult;
}
struct UsageFinder : public AstVisitor struct UsageFinder : public AstVisitor
{ {
@ -586,7 +158,6 @@ void cloneTypesFromFragment(
const ModulePtr& staleModule, const ModulePtr& staleModule,
NotNull<TypeArena> destArena, NotNull<TypeArena> destArena,
NotNull<DataFlowGraph> dfg, NotNull<DataFlowGraph> dfg,
NotNull<BuiltinTypes> builtins,
AstStatBlock* program, AstStatBlock* program,
Scope* destScope Scope* destScope
) )
@ -617,13 +188,6 @@ void cloneTypesFromFragment(
destScope->lvalueTypes[d] = Luau::cloneIncremental(pair->second.typeId, *destArena, cloneState, destScope); destScope->lvalueTypes[d] = Luau::cloneIncremental(pair->second.typeId, *destArena, cloneState, destScope);
destScope->bindings[pair->first] = Luau::cloneIncremental(pair->second, *destArena, cloneState, destScope); destScope->bindings[pair->first] = Luau::cloneIncremental(pair->second, *destArena, cloneState, destScope);
} }
else if (FFlag::LuauBetterScopeSelection && !FFlag::LuauBlockDiffFragmentSelection)
{
destScope->lvalueTypes[d] = builtins->unknownType;
Binding b;
b.typeId = builtins->unknownType;
destScope->bindings[Symbol(loc)] = b;
}
} }
// Second - any referenced type alias bindings need to be placed in scope so type annotation can be resolved. // Second - any referenced type alias bindings need to be placed in scope so type annotation can be resolved.
@ -836,10 +400,15 @@ static FrontendModuleResolver& getModuleResolver(Frontend& frontend, std::option
bool statIsBeforePos(const AstNode* stat, const Position& cursorPos) bool statIsBeforePos(const AstNode* stat, const Position& cursorPos)
{ {
return (stat->location.begin < cursorPos); if (FFlag::LuauIncrementalAutocompleteBugfixes)
{
return (stat->location.begin < cursorPos);
}
return stat->location.begin < cursorPos && stat->location.begin.line < cursorPos.line;
} }
FragmentAutocompleteAncestryResult findAncestryForFragmentParse_DEPRECATED(AstStatBlock* root, const Position& cursorPos) FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* root, const Position& cursorPos)
{ {
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(root, cursorPos); std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(root, cursorPos);
// Should always contain the root AstStat // Should always contain the root AstStat
@ -868,7 +437,7 @@ FragmentAutocompleteAncestryResult findAncestryForFragmentParse_DEPRECATED(AstSt
{ {
for (auto stat : block->body) for (auto stat : block->body)
{ {
if (statIsBeforePos(stat, nearestStatement->location.begin)) if (statIsBeforePos(stat, FFlag::LuauIncrementalAutocompleteBugfixes ? nearestStatement->location.begin : cursorPos))
{ {
// This statement precedes the current one // This statement precedes the current one
if (auto statLoc = stat->as<AstStatLocal>()) if (auto statLoc = stat->as<AstStatLocal>())
@ -917,14 +486,17 @@ FragmentAutocompleteAncestryResult findAncestryForFragmentParse_DEPRECATED(AstSt
} }
} }
} }
if (auto exprFunc = node->as<AstExprFunction>()) if (FFlag::LuauIncrementalAutocompleteBugfixes)
{ {
if (exprFunc->location.contains(cursorPos)) if (auto exprFunc = node->as<AstExprFunction>())
{ {
for (auto v : exprFunc->args) if (exprFunc->location.contains(cursorPos))
{ {
localStack.push_back(v); for (auto v : exprFunc->args)
localMap[v->name] = v; {
localStack.push_back(v);
localMap[v->name] = v;
}
} }
} }
} }
@ -941,7 +513,7 @@ FragmentAutocompleteAncestryResult findAncestryForFragmentParse_DEPRECATED(AstSt
* Example - your document is "foo bar baz" and getDocumentOffsets is passed (0, 4), (0, 8). This function returns the pair {3, 5} * Example - your document is "foo bar baz" and getDocumentOffsets is passed (0, 4), (0, 8). This function returns the pair {3, 5}
* which corresponds to the string " bar " * which corresponds to the string " bar "
*/ */
static std::pair<size_t, size_t> getDocumentOffsets(std::string_view src, const Position& startPos, const Position& endPos) std::pair<size_t, size_t> getDocumentOffsets(const std::string_view& src, const Position& startPos, const Position& endPos)
{ {
size_t lineCount = 0; size_t lineCount = 0;
size_t colCount = 0; size_t colCount = 0;
@ -998,14 +570,14 @@ static std::pair<size_t, size_t> getDocumentOffsets(std::string_view src, const
return {min, len}; return {min, len};
} }
ScopePtr findClosestScope_DEPRECATED(const ModulePtr& module, const AstStat* nearestStatement) ScopePtr findClosestScope(const ModulePtr& module, const AstStat* nearestStatement)
{ {
LUAU_ASSERT(module->hasModuleScope()); LUAU_ASSERT(module->hasModuleScope());
ScopePtr closest = module->getModuleScope(); ScopePtr closest = module->getModuleScope();
// find the scope the nearest statement belonged to. // find the scope the nearest statement belonged to.
for (const auto& [loc, sc] : module->scopes) for (auto [loc, sc] : module->scopes)
{ {
if (loc.encloses(nearestStatement->location) && closest->location.begin <= loc.begin) if (loc.encloses(nearestStatement->location) && closest->location.begin <= loc.begin)
closest = sc; closest = sc;
@ -1014,38 +586,7 @@ ScopePtr findClosestScope_DEPRECATED(const ModulePtr& module, const AstStat* nea
return closest; return closest;
} }
ScopePtr findClosestScope(const ModulePtr& module, const Position& scopePos) std::optional<FragmentParseResult> parseFragment(
{
LUAU_ASSERT(module->hasModuleScope());
if (FFlag::LuauBlockDiffFragmentSelection)
{
ScopePtr closest = module->getModuleScope();
// find the scope the nearest statement belonged to.
for (const auto& [loc, sc] : module->scopes)
{
// We bias towards the later scopes because those correspond to inner scopes.
// in the case of if statements, we create two scopes at the same location for the body of the then
// and else branches, so we need to bias later. This is why the closest update condition has a <=
// instead of a <
if (sc->location.contains(scopePos) && closest->location.begin <= sc->location.begin)
closest = sc;
}
return closest;
}
else
{
ScopePtr closest = module->getModuleScope();
// find the scope the nearest statement belonged to.
for (const auto& [loc, sc] : module->scopes)
{
if (sc->location.contains(scopePos) && closest->location.begin < sc->location.begin)
closest = sc;
}
return closest;
}
}
std::optional<FragmentParseResult> parseFragment_DEPRECATED(
AstStatBlock* root, AstStatBlock* root,
AstNameTable* names, AstNameTable* names,
std::string_view src, std::string_view src,
@ -1053,7 +594,7 @@ std::optional<FragmentParseResult> parseFragment_DEPRECATED(
std::optional<Position> fragmentEndPosition std::optional<Position> fragmentEndPosition
) )
{ {
FragmentAutocompleteAncestryResult result = findAncestryForFragmentParse_DEPRECATED(root, cursorPos); FragmentAutocompleteAncestryResult result = findAncestryForFragmentParse(root, cursorPos);
AstStat* nearestStatement = result.nearestStatement; AstStat* nearestStatement = result.nearestStatement;
const Location& rootSpan = root->location; const Location& rootSpan = root->location;
@ -1084,8 +625,8 @@ std::optional<FragmentParseResult> parseFragment_DEPRECATED(
FragmentParseResult fragmentResult; FragmentParseResult fragmentResult;
fragmentResult.fragmentToParse = std::string(dbg.data(), parseLength); fragmentResult.fragmentToParse = std::string(dbg.data(), parseLength);
// For the duration of the incremental parse, we want to allow the name table to re-use duplicate names // For the duration of the incremental parse, we want to allow the name table to re-use duplicate names
if (FFlag::DebugLogFragmentsFromAutocomplete) if (FFlag::LogFragmentsFromAutocomplete)
logLuau("Fragment Selected", dbg); logLuau(dbg);
ParseOptions opts; ParseOptions opts;
opts.allowDeclarationSyntax = false; opts.allowDeclarationSyntax = false;
@ -1484,14 +1025,7 @@ FragmentTypeCheckResult typecheckFragment_(
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeStart); reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeStart);
cloneTypesFromFragment( cloneTypesFromFragment(
cloneState, cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get()
closestScope.get(),
stale,
NotNull{&incrementalModule->internalTypes},
NotNull{&dfg},
frontend.builtinTypes,
root,
freshChildOfNearestScope.get()
); );
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeEnd); reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeEnd);
@ -1552,7 +1086,6 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
std::optional<FrontendOptions> opts, std::optional<FrontendOptions> opts,
std::string_view src, std::string_view src,
std::optional<Position> fragmentEndPosition, std::optional<Position> fragmentEndPosition,
AstStatBlock* recentParse,
IFragmentAutocompleteReporter* reporter IFragmentAutocompleteReporter* reporter
) )
{ {
@ -1571,9 +1104,30 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
} }
std::optional<FragmentParseResult> tryParse; std::optional<FragmentParseResult> tryParse;
tryParse = FFlag::LuauBetterScopeSelection ? parseFragment(module->root, recentParse, module->names.get(), src, cursorPos, fragmentEndPosition) if (FFlag::LuauModuleHoldsAstRoot)
: parseFragment_DEPRECATED(module->root, module->names.get(), src, cursorPos, fragmentEndPosition); {
tryParse = parseFragment(module->root, module->names.get(), src, cursorPos, fragmentEndPosition);
}
else
{
const SourceModule* sourceModule = frontend.getSourceModule(moduleName);
if (!sourceModule)
{
LUAU_ASSERT(!"Expected Source Module for fragment typecheck");
return {};
}
if (FFlag::LuauIncrementalAutocompleteBugfixes)
{
if (sourceModule->allocator.get() != module->allocator.get())
{
return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
}
}
tryParse = parseFragment(sourceModule->root, sourceModule->names.get(), src, cursorPos, fragmentEndPosition);
reportWaypoint(reporter, FragmentAutocompleteWaypoint::ParseFragmentEnd);
}
if (!tryParse) if (!tryParse)
return {FragmentTypeCheckStatus::SkipAutocomplete, {}}; return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
@ -1584,8 +1138,7 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
return {FragmentTypeCheckStatus::SkipAutocomplete, {}}; return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
FrontendOptions frontendOptions = opts.value_or(frontend.options); FrontendOptions frontendOptions = opts.value_or(frontend.options);
const ScopePtr& closestScope = FFlag::LuauBetterScopeSelection ? findClosestScope(module, parseResult.scopePos) const ScopePtr& closestScope = findClosestScope(module, parseResult.nearestStatement);
: findClosestScope_DEPRECATED(module, parseResult.nearestStatement);
FragmentTypeCheckResult result = FragmentTypeCheckResult result =
FFlag::LuauIncrementalAutocompleteDemandBasedCloning FFlag::LuauIncrementalAutocompleteDemandBasedCloning
? typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter) ? typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter)
@ -1621,15 +1174,14 @@ FragmentAutocompleteStatusResult tryFragmentAutocomplete(
context.opts, context.opts,
std::move(stringCompletionCB), std::move(stringCompletionCB),
context.DEPRECATED_fragmentEndPosition, context.DEPRECATED_fragmentEndPosition,
context.freshParse.root,
FFlag::LuauFragmentAcSupportsReporter ? context.reporter : nullptr FFlag::LuauFragmentAcSupportsReporter ? context.reporter : nullptr
); );
return {FragmentAutocompleteStatus::Success, std::move(fragmentAutocomplete)}; return {FragmentAutocompleteStatus::Success, std::move(fragmentAutocomplete)};
} }
catch (const Luau::InternalCompilerError& e) catch (const Luau::InternalCompilerError& e)
{ {
if (FFlag::DebugLogFragmentsFromAutocomplete) if (FFlag::LogFragmentsFromAutocomplete)
logLuau("tryFragmentAutocomplete exception", e.what()); logLuau(e.what());
return {FragmentAutocompleteStatus::InternalIce, std::nullopt}; return {FragmentAutocompleteStatus::InternalIce, std::nullopt};
} }
} }
@ -1642,7 +1194,6 @@ FragmentAutocompleteResult fragmentAutocomplete(
std::optional<FrontendOptions> opts, std::optional<FrontendOptions> opts,
StringCompletionCallback callback, StringCompletionCallback callback,
std::optional<Position> fragmentEndPosition, std::optional<Position> fragmentEndPosition,
AstStatBlock* recentParse,
IFragmentAutocompleteReporter* reporter IFragmentAutocompleteReporter* reporter
) )
{ {
@ -1650,14 +1201,28 @@ FragmentAutocompleteResult fragmentAutocomplete(
LUAU_TIMETRACE_SCOPE("Luau::fragmentAutocomplete", "FragmentAutocomplete"); LUAU_TIMETRACE_SCOPE("Luau::fragmentAutocomplete", "FragmentAutocomplete");
LUAU_TIMETRACE_ARGUMENT("name", moduleName.c_str()); LUAU_TIMETRACE_ARGUMENT("name", moduleName.c_str());
auto [tcStatus, tcResult] = typecheckFragment(frontend, moduleName, cursorPosition, opts, src, fragmentEndPosition, recentParse, reporter); if (!FFlag::LuauModuleHoldsAstRoot)
{
const SourceModule* sourceModule = frontend.getSourceModule(moduleName);
if (!sourceModule)
{
LUAU_ASSERT(!"Expected Source Module for fragment typecheck");
return {};
}
// If the cursor is within a comment in the stale source module we should avoid providing a recommendation
if (isWithinComment(*sourceModule, fragmentEndPosition.value_or(cursorPosition)))
return {};
}
auto [tcStatus, tcResult] = typecheckFragment(frontend, moduleName, cursorPosition, opts, src, fragmentEndPosition, reporter);
if (tcStatus == FragmentTypeCheckStatus::SkipAutocomplete) if (tcStatus == FragmentTypeCheckStatus::SkipAutocomplete)
return {}; return {};
reportWaypoint(reporter, FragmentAutocompleteWaypoint::TypecheckFragmentEnd); reportWaypoint(reporter, FragmentAutocompleteWaypoint::TypecheckFragmentEnd);
auto globalScope = (opts && opts->forAutocomplete) ? frontend.globalsForAutocomplete.globalScope.get() : frontend.globals.globalScope.get(); auto globalScope = (opts && opts->forAutocomplete) ? frontend.globalsForAutocomplete.globalScope.get() : frontend.globals.globalScope.get();
if (FFlag::DebugLogFragmentsFromAutocomplete) if (FFlag::LogFragmentsFromAutocomplete)
logLuau("Fragment Autocomplete Source Script", src); logLuau(src);
TypeArena arenaForFragmentAutocomplete; TypeArena arenaForFragmentAutocomplete;
auto result = Luau::autocomplete_( auto result = Luau::autocomplete_(
tcResult.incrementalModule, tcResult.incrementalModule,

View file

@ -1,6 +1,7 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Frontend.h" #include "Luau/Frontend.h"
#include "Luau/AnyTypeSummary.h"
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
#include "Luau/Clone.h" #include "Luau/Clone.h"
#include "Luau/Common.h" #include "Luau/Common.h"
@ -39,7 +40,6 @@ LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauInferInNoCheckMode) LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3) LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRethrowKnownExceptions, false)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile)
LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes) LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes)
@ -47,8 +47,13 @@ LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode)
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode) LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false) LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false)
LUAU_FASTFLAGVARIABLE(LuauModuleHoldsAstRoot)
LUAU_FASTFLAGVARIABLE(LuauFixMultithreadTypecheck)
LUAU_FASTFLAG(StudioReportLuauAny2)
LUAU_FASTFLAGVARIABLE(LuauSelectivelyRetainDFGArena) LUAU_FASTFLAGVARIABLE(LuauSelectivelyRetainDFGArena)
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete)
namespace Luau namespace Luau
{ {
@ -456,6 +461,20 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
if (item.name == name) if (item.name == name)
checkResult.lintResult = item.module->lintResult; checkResult.lintResult = item.module->lintResult;
if (FFlag::StudioReportLuauAny2 && item.options.retainFullTypeGraphs)
{
if (item.module)
{
const SourceModule& sourceModule = *item.sourceModule;
if (sourceModule.mode == Luau::Mode::Strict)
{
item.module->ats.root = toString(sourceModule.root);
}
item.module->ats.rootSrc = sourceModule.root;
item.module->ats.traverse(item.module.get(), sourceModule.root, NotNull{&builtinTypes_});
}
}
} }
return checkResult; return checkResult;
@ -477,6 +496,11 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
std::function<bool(size_t done, size_t total)> progress std::function<bool(size_t done, size_t total)> progress
) )
{ {
if (!FFlag::LuauFixMultithreadTypecheck)
{
return checkQueuedModules_DEPRECATED(optionOverride, executeTask, progress);
}
FrontendOptions frontendOptions = optionOverride.value_or(options); FrontendOptions frontendOptions = optionOverride.value_or(options);
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
frontendOptions.forAutocomplete = false; frontendOptions.forAutocomplete = false;
@ -661,6 +685,247 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
return checkedModules; return checkedModules;
} }
std::vector<ModuleName> Frontend::checkQueuedModules_DEPRECATED(
std::optional<FrontendOptions> optionOverride,
std::function<void(std::function<void()> task)> executeTask,
std::function<bool(size_t done, size_t total)> progress
)
{
LUAU_ASSERT(!FFlag::LuauFixMultithreadTypecheck);
FrontendOptions frontendOptions = optionOverride.value_or(options);
if (FFlag::LuauSolverV2)
frontendOptions.forAutocomplete = false;
// By taking data into locals, we make sure queue is cleared at the end, even if an ICE or a different exception is thrown
std::vector<ModuleName> currModuleQueue;
std::swap(currModuleQueue, moduleQueue);
DenseHashSet<Luau::ModuleName> seen{{}};
std::vector<BuildQueueItem> buildQueueItems;
for (const ModuleName& name : currModuleQueue)
{
if (seen.contains(name))
continue;
if (!isDirty(name, frontendOptions.forAutocomplete))
{
seen.insert(name);
continue;
}
std::vector<ModuleName> queue;
bool cycleDetected = parseGraph(
queue,
name,
frontendOptions.forAutocomplete,
[&seen](const ModuleName& name)
{
return seen.contains(name);
}
);
addBuildQueueItems(buildQueueItems, queue, cycleDetected, seen, frontendOptions);
}
if (buildQueueItems.empty())
return {};
// We need a mapping from modules to build queue slots
std::unordered_map<ModuleName, size_t> moduleNameToQueue;
for (size_t i = 0; i < buildQueueItems.size(); i++)
{
BuildQueueItem& item = buildQueueItems[i];
moduleNameToQueue[item.name] = i;
}
// Default task execution is single-threaded and immediate
if (!executeTask)
{
executeTask = [](std::function<void()> task)
{
task();
};
}
std::mutex mtx;
std::condition_variable cv;
std::vector<size_t> readyQueueItems;
size_t processing = 0;
size_t remaining = buildQueueItems.size();
auto itemTask = [&](size_t i)
{
BuildQueueItem& item = buildQueueItems[i];
try
{
checkBuildQueueItem(item);
}
catch (...)
{
item.exception = std::current_exception();
}
{
std::unique_lock guard(mtx);
readyQueueItems.push_back(i);
}
cv.notify_one();
};
auto sendItemTask = [&](size_t i)
{
BuildQueueItem& item = buildQueueItems[i];
item.processing = true;
processing++;
executeTask(
[&itemTask, i]()
{
itemTask(i);
}
);
};
auto sendCycleItemTask = [&]
{
for (size_t i = 0; i < buildQueueItems.size(); i++)
{
BuildQueueItem& item = buildQueueItems[i];
if (!item.processing)
{
sendItemTask(i);
break;
}
}
};
// In a first pass, check modules that have no dependencies and record info of those modules that wait
for (size_t i = 0; i < buildQueueItems.size(); i++)
{
BuildQueueItem& item = buildQueueItems[i];
for (const ModuleName& dep : item.sourceNode->requireSet)
{
if (auto it = sourceNodes.find(dep); it != sourceNodes.end())
{
if (it->second->hasDirtyModule(frontendOptions.forAutocomplete))
{
item.dirtyDependencies++;
buildQueueItems[moduleNameToQueue[dep]].reverseDeps.push_back(i);
}
}
}
if (item.dirtyDependencies == 0)
sendItemTask(i);
}
// Not a single item was found, a cycle in the graph was hit
if (processing == 0)
sendCycleItemTask();
std::vector<size_t> nextItems;
std::optional<size_t> itemWithException;
bool cancelled = false;
while (remaining != 0)
{
{
std::unique_lock guard(mtx);
// If nothing is ready yet, wait
cv.wait(
guard,
[&readyQueueItems]
{
return !readyQueueItems.empty();
}
);
// Handle checked items
for (size_t i : readyQueueItems)
{
const BuildQueueItem& item = buildQueueItems[i];
// If exception was thrown, stop adding new items and wait for processing items to complete
if (item.exception)
itemWithException = i;
if (item.module && item.module->cancelled)
cancelled = true;
if (itemWithException || cancelled)
break;
recordItemResult(item);
// Notify items that were waiting for this dependency
for (size_t reverseDep : item.reverseDeps)
{
BuildQueueItem& reverseDepItem = buildQueueItems[reverseDep];
LUAU_ASSERT(reverseDepItem.dirtyDependencies != 0);
reverseDepItem.dirtyDependencies--;
// In case of a module cycle earlier, check if unlocked an item that was already processed
if (!reverseDepItem.processing && reverseDepItem.dirtyDependencies == 0)
nextItems.push_back(reverseDep);
}
}
LUAU_ASSERT(processing >= readyQueueItems.size());
processing -= readyQueueItems.size();
LUAU_ASSERT(remaining >= readyQueueItems.size());
remaining -= readyQueueItems.size();
readyQueueItems.clear();
}
if (progress)
{
if (!progress(buildQueueItems.size() - remaining, buildQueueItems.size()))
cancelled = true;
}
// Items cannot be submitted while holding the lock
for (size_t i : nextItems)
sendItemTask(i);
nextItems.clear();
if (processing == 0)
{
// Typechecking might have been cancelled by user, don't return partial results
if (cancelled)
return {};
// We might have stopped because of a pending exception
if (itemWithException)
recordItemResult(buildQueueItems[*itemWithException]);
}
// If we aren't done, but don't have anything processing, we hit a cycle
if (remaining != 0 && processing == 0)
sendCycleItemTask();
}
std::vector<ModuleName> checkedModules;
checkedModules.reserve(buildQueueItems.size());
for (size_t i = 0; i < buildQueueItems.size(); i++)
checkedModules.push_back(std::move(buildQueueItems[i].name));
return checkedModules;
}
std::optional<CheckResult> Frontend::getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete) std::optional<CheckResult> Frontend::getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete)
{ {
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
@ -1102,27 +1367,13 @@ void Frontend::performQueueItemTask(std::shared_ptr<BuildQueueWorkState> state,
{ {
BuildQueueItem& item = state->buildQueueItems[itemPos]; BuildQueueItem& item = state->buildQueueItems[itemPos];
if (DFFlag::LuauRethrowKnownExceptions) try
{ {
try checkBuildQueueItem(item);
{
checkBuildQueueItem(item);
}
catch (const Luau::InternalCompilerError&)
{
item.exception = std::current_exception();
}
} }
else catch (...)
{ {
try item.exception = std::current_exception();
{
checkBuildQueueItem(item);
}
catch (...)
{
item.exception = std::current_exception();
}
} }
{ {
@ -1381,7 +1632,8 @@ ModulePtr check(
result->interfaceTypes.owningModule = result.get(); result->interfaceTypes.owningModule = result.get();
result->allocator = sourceModule.allocator; result->allocator = sourceModule.allocator;
result->names = sourceModule.names; result->names = sourceModule.names;
result->root = sourceModule.root; if (FFlag::LuauModuleHoldsAstRoot)
result->root = sourceModule.root;
iceHandler->moduleName = sourceModule.name; iceHandler->moduleName = sourceModule.name;
@ -1406,7 +1658,7 @@ ModulePtr check(
SimplifierPtr simplifier = newSimplifier(NotNull{&result->internalTypes}, builtinTypes); SimplifierPtr simplifier = newSimplifier(NotNull{&result->internalTypes}, builtinTypes);
TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}}; TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}};
typeFunctionRuntime.allowEvaluation = FFlag::LuauTypeFunResultInAutocomplete || sourceModule.parseErrors.empty(); typeFunctionRuntime.allowEvaluation = sourceModule.parseErrors.empty();
ConstraintGenerator cg{ ConstraintGenerator cg{
result, result,

View file

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

View file

@ -61,7 +61,7 @@ TypeId Instantiation::clean(TypeId ty)
const FunctionType* ftv = log->getMutable<FunctionType>(ty); const FunctionType* ftv = log->getMutable<FunctionType>(ty);
LUAU_ASSERT(ftv); LUAU_ASSERT(ftv);
FunctionType clone = FunctionType{level, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf}; FunctionType clone = FunctionType{level, scope, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf};
clone.magic = ftv->magic; clone.magic = ftv->magic;
clone.tags = ftv->tags; clone.tags = ftv->tags;
clone.argNames = ftv->argNames; clone.argNames = ftv->argNames;

View file

@ -19,8 +19,6 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAttribute) LUAU_FASTFLAG(LuauAttribute)
LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute) LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
namespace Luau 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 bool visit(AstExprIndexName* node) override
{ {
if (std::optional<TypeId> ty = context->getType(node->expr)) if (std::optional<TypeId> ty = context->getType(node->expr))
@ -2378,59 +2325,18 @@ private:
if (prop && prop->deprecated) if (prop && prop->deprecated)
report(node->location, *prop, cty->name.c_str(), node->index.value); report(node->location, *prop, cty->name.c_str(), node->index.value);
else if (FFlag::LuauDeprecatedAttribute && prop)
{
if (std::optional<TypeId> ty = prop->readTy)
{
const FunctionType* fty = get<FunctionType>(follow(ty));
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
if (shouldReport)
{
const char* className = nullptr;
if (AstExprGlobal* global = node->expr->as<AstExprGlobal>())
className = global->name.value;
const char* functionName = node->index.value;
report(node->location, className, functionName);
}
}
}
} }
else if (const TableType* tty = get<TableType>(ty)) else if (const TableType* tty = get<TableType>(ty))
{ {
auto prop = tty->props.find(node->index.value); auto prop = tty->props.find(node->index.value);
if (prop != tty->props.end()) if (prop != tty->props.end() && prop->second.deprecated)
{ {
if (prop->second.deprecated) // strip synthetic typeof() for builtin tables
{ if (tty->name && tty->name->compare(0, 7, "typeof(") == 0 && tty->name->back() == ')')
// strip synthetic typeof() for builtin tables report(node->location, prop->second, tty->name->substr(7, tty->name->length() - 8).c_str(), node->index.value);
if (tty->name && tty->name->compare(0, 7, "typeof(") == 0 && tty->name->back() == ')') else
report(node->location, prop->second, tty->name->substr(7, tty->name->length() - 8).c_str(), node->index.value); report(node->location, prop->second, tty->name ? tty->name->c_str() : nullptr, 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);
}
}
}
} }
} }
} }
@ -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) void report(const Location& location, const Property& prop, const char* container, const char* field)
{ {
std::string suggestion = prop.deprecatedSuggestion.empty() ? "" : format(", use '%s' instead", prop.deprecatedSuggestion.c_str()); std::string suggestion = prop.deprecatedSuggestion.empty() ? "" : format(", use '%s' instead", prop.deprecatedSuggestion.c_str());
@ -2478,63 +2364,6 @@ private:
else else
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s' is deprecated%s", field, suggestion.c_str()); emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s' is deprecated%s", field, suggestion.c_str());
} }
void report(const Location& location, const char* tableName, const char* functionName)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
if (tableName)
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s.%s' is deprecated", tableName, functionName);
else
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s' is deprecated", functionName);
}
void report(const Location& location, const char* functionName)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Function '%s' is deprecated", functionName);
}
std::vector<const FunctionType*> functionTypeScopeStack;
void pushScope(const FunctionType* fty)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
LUAU_ASSERT(fty);
functionTypeScopeStack.push_back(fty);
}
void popScope(const FunctionType* fty)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
LUAU_ASSERT(fty);
LUAU_ASSERT(fty == functionTypeScopeStack.back());
functionTypeScopeStack.pop_back();
}
bool inScope(const FunctionType* fty) const
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
LUAU_ASSERT(fty);
return std::find(functionTypeScopeStack.begin(), functionTypeScopeStack.end(), fty) != functionTypeScopeStack.end();
}
const FunctionType* getFunctionType(AstExpr* node)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
std::optional<TypeId> ty = context->getType(node);
if (!ty)
return nullptr;
const FunctionType* fty = get<FunctionType>(follow(ty));
return fty;
}
}; };
class LintTableOperations : AstVisitor class LintTableOperations : AstVisitor

View file

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

View file

@ -2,7 +2,6 @@
#include "Luau/NonStrictTypeChecker.h" #include "Luau/NonStrictTypeChecker.h"
#include "Luau/Ast.h" #include "Luau/Ast.h"
#include "Luau/AstQuery.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/Simplify.h" #include "Luau/Simplify.h"
#include "Luau/Type.h" #include "Luau/Type.h"

View file

@ -17,16 +17,12 @@
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant) LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant)
LUAU_FASTFLAGVARIABLE(LuauNormalizeNegatedErrorToAnError)
LUAU_FASTFLAGVARIABLE(LuauNormalizeIntersectErrorToAnError)
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000) LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000)
LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200) LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauNormalizeNegationFix)
LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization) LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization)
LUAU_FASTFLAGVARIABLE(LuauNormalizedBufferIsNotUnknown) LUAU_FASTFLAGVARIABLE(LuauNormalizedBufferIsNotUnknown)
LUAU_FASTFLAGVARIABLE(LuauNormalizeLimitFunctionSet)
LUAU_FASTFLAGVARIABLE(LuauNormalizationCatchMetatableCycles)
namespace Luau namespace Luau
{ {
@ -585,7 +581,7 @@ NormalizationResult Normalizer::isIntersectionInhabited(TypeId left, TypeId righ
{ {
left = follow(left); left = follow(left);
right = follow(right); right = follow(right);
// We're asking if intersection is inhabited between left and right but we've already seen them .... // We're asking if intersection is inahbited between left and right but we've already seen them ....
if (cacheInhabitance) if (cacheInhabitance)
{ {
@ -1691,13 +1687,6 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali
return res; 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); here.booleans = unionOfBools(here.booleans, there.booleans);
unionClasses(here.classes, there.classes); unionClasses(here.classes, there.classes);
@ -1709,7 +1698,6 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali
here.buffers = (get<NeverType>(there.buffers) ? here.buffers : there.buffers); here.buffers = (get<NeverType>(there.buffers) ? here.buffers : there.buffers);
unionFunctions(here.functions, there.functions); unionFunctions(here.functions, there.functions);
unionTables(here.tables, there.tables); unionTables(here.tables, there.tables);
return NormalizationResult::True; return NormalizationResult::True;
} }
@ -1749,7 +1737,7 @@ NormalizationResult Normalizer::intersectNormalWithNegationTy(TypeId toNegate, N
return NormalizationResult::True; return NormalizationResult::True;
} }
// See above for an explanation of `ignoreSmallerTyvars`. // See above for an explaination of `ignoreSmallerTyvars`.
NormalizationResult Normalizer::unionNormalWithTy( NormalizationResult Normalizer::unionNormalWithTy(
NormalizedType& here, NormalizedType& here,
TypeId there, TypeId there,
@ -3064,7 +3052,7 @@ NormalizationResult Normalizer::intersectTyvarsWithTy(
return NormalizationResult::True; return NormalizationResult::True;
} }
// See above for an explanation of `ignoreSmallerTyvars`. // See above for an explaination of `ignoreSmallerTyvars`.
NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars) NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars)
{ {
RecursionCounter _rc(&sharedState->counters.recursionCount); RecursionCounter _rc(&sharedState->counters.recursionCount);
@ -3082,17 +3070,11 @@ NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const Nor
return unionNormals(here, there, ignoreSmallerTyvars); return unionNormals(here, there, ignoreSmallerTyvars);
} }
// Limit based on worst-case expansion of the table/function intersections // Limit based on worst-case expansion of the table intersection
// This restriction can be relaxed when table intersection simplification is improved // This restriction can be relaxed when table intersection simplification is improved
if (here.tables.size() * there.tables.size() >= size_t(FInt::LuauNormalizeIntersectionLimit)) if (here.tables.size() * there.tables.size() >= size_t(FInt::LuauNormalizeIntersectionLimit))
return NormalizationResult::HitLimits; return NormalizationResult::HitLimits;
if (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); here.booleans = intersectionOfBools(here.booleans, there.booleans);
intersectClasses(here.classes, there.classes); intersectClasses(here.classes, there.classes);
@ -3228,7 +3210,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
{ {
TypeId errors = here.errors; TypeId errors = here.errors;
clearNormal(here); clearNormal(here);
here.errors = FFlag::LuauNormalizeIntersectErrorToAnError && get<ErrorType>(errors) ? errors : there; here.errors = errors;
} }
else if (const PrimitiveType* ptv = get<PrimitiveType>(there)) else if (const PrimitiveType* ptv = get<PrimitiveType>(there))
{ {
@ -3325,16 +3307,11 @@ NormalizationResult Normalizer::intersectNormalWithTy(
clearNormal(here); clearNormal(here);
return NormalizationResult::True; 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)) 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); return intersectNormalWithTy(here, nt->ty, seenTablePropPairs, seenSetTypes);
} }
else else
@ -3364,43 +3341,19 @@ NormalizationResult Normalizer::intersectNormalWithTy(
return NormalizationResult::True; return NormalizationResult::True;
} }
void makeTableShared_DEPRECATED(TypeId ty)
{
ty = follow(ty);
if (auto tableTy = getMutable<TableType>(ty))
{
for (auto& [_, prop] : tableTy->props)
prop.makeShared();
}
else if (auto metatableTy = get<MetatableType>(ty))
{
makeTableShared_DEPRECATED(metatableTy->metatable);
makeTableShared_DEPRECATED(metatableTy->table);
}
}
void makeTableShared(TypeId ty, DenseHashSet<TypeId>& seen)
{
ty = follow(ty);
if (seen.contains(ty))
return;
seen.insert(ty);
if (auto tableTy = getMutable<TableType>(ty))
{
for (auto& [_, prop] : tableTy->props)
prop.makeShared();
}
else if (auto metatableTy = get<MetatableType>(ty))
{
makeTableShared(metatableTy->metatable, seen);
makeTableShared(metatableTy->table, seen);
}
}
void makeTableShared(TypeId ty) void makeTableShared(TypeId ty)
{ {
DenseHashSet<TypeId> seen{nullptr}; ty = follow(ty);
makeTableShared(ty, seen); if (auto tableTy = getMutable<TableType>(ty))
{
for (auto& [_, prop] : tableTy->props)
prop.makeShared();
}
else if (auto metatableTy = get<MetatableType>(ty))
{
makeTableShared(metatableTy->metatable);
makeTableShared(metatableTy->table);
}
} }
// -------- Convert back from a normalized type to a type // -------- Convert back from a normalized type to a type
@ -3502,10 +3455,7 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
result.reserve(result.size() + norm.tables.size()); result.reserve(result.size() + norm.tables.size());
for (auto table : norm.tables) for (auto table : norm.tables)
{ {
if (FFlag::LuauNormalizationCatchMetatableCycles) makeTableShared(table);
makeTableShared(table);
else
makeTableShared_DEPRECATED(table);
result.push_back(table); result.push_back(table);
} }
} }

View file

@ -454,7 +454,7 @@ SolveResult solveFunctionCall(
TypePackId resultPack = arena->freshTypePack(scope); TypePackId resultPack = arena->freshTypePack(scope);
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, argsPack, resultPack}); TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, scope.get(), argsPack, resultPack});
Unifier2 u2{NotNull{arena}, builtinTypes, scope, iceReporter}; Unifier2 u2{NotNull{arena}, builtinTypes, scope, iceReporter};
const bool occursCheckPassed = u2.unify(*overloadToUse, inferredTy); const bool occursCheckPassed = u2.unify(*overloadToUse, inferredTy);

View file

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

View file

@ -22,7 +22,6 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity) LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
LUAU_FASTFLAGVARIABLE(LuauSubtypingStopAtNormFail)
namespace Luau namespace Luau
{ {
@ -416,14 +415,6 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope
SubtypingResult result = isCovariantWith(env, subTy, superTy, scope); SubtypingResult result = isCovariantWith(env, subTy, superTy, scope);
if (FFlag::LuauSubtypingStopAtNormFail && result.normalizationTooComplex)
{
if (result.isCacheable)
resultCache[{subTy, superTy}] = result;
return result;
}
for (const auto& [subTy, bounds] : env.mappedGenerics) for (const auto& [subTy, bounds] : env.mappedGenerics)
{ {
const auto& lb = bounds.lowerBound; const auto& lb = bounds.lowerBound;
@ -601,12 +592,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
if (!result.isSubtype && !result.normalizationTooComplex) if (!result.isSubtype && !result.normalizationTooComplex)
{ {
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope); SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope);
if (semantic.isSubtype)
if (FFlag::LuauSubtypingStopAtNormFail && semantic.normalizationTooComplex)
{
result = semantic;
}
else if (semantic.isSubtype)
{ {
semantic.reasoning.clear(); semantic.reasoning.clear();
result = semantic; result = semantic;
@ -621,12 +607,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
if (!result.isSubtype && !result.normalizationTooComplex) if (!result.isSubtype && !result.normalizationTooComplex)
{ {
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope); SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope);
if (semantic.isSubtype)
if (FFlag::LuauSubtypingStopAtNormFail && semantic.normalizationTooComplex)
{
result = semantic;
}
else if (semantic.isSubtype)
{ {
// Clear the semantic reasoning, as any reasonings within // Clear the semantic reasoning, as any reasonings within
// potentially contain invalid paths. // potentially contain invalid paths.
@ -1101,10 +1082,6 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
for (TypeId ty : superUnion) for (TypeId ty : superUnion)
{ {
SubtypingResult next = isCovariantWith(env, subTy, ty, scope); SubtypingResult next = isCovariantWith(env, subTy, ty, scope);
if (FFlag::LuauSubtypingStopAtNormFail && next.normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
if (next.isSubtype) if (next.isSubtype)
return SubtypingResult{true}; return SubtypingResult{true};
} }
@ -1123,13 +1100,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Unio
std::vector<SubtypingResult> subtypings; std::vector<SubtypingResult> subtypings;
size_t i = 0; size_t i = 0;
for (TypeId ty : subUnion) for (TypeId ty : subUnion)
{
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Union})); subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Union}));
if (FFlag::LuauSubtypingStopAtNormFail && subtypings.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
return SubtypingResult::all(subtypings); return SubtypingResult::all(subtypings);
} }
@ -1139,13 +1110,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
std::vector<SubtypingResult> subtypings; std::vector<SubtypingResult> subtypings;
size_t i = 0; size_t i = 0;
for (TypeId ty : superIntersection) for (TypeId ty : superIntersection)
{
subtypings.push_back(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection})); subtypings.push_back(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
if (FFlag::LuauSubtypingStopAtNormFail && subtypings.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
return SubtypingResult::all(subtypings); return SubtypingResult::all(subtypings);
} }
@ -1155,13 +1120,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Inte
std::vector<SubtypingResult> subtypings; std::vector<SubtypingResult> subtypings;
size_t i = 0; size_t i = 0;
for (TypeId ty : subIntersection) for (TypeId ty : subIntersection)
{
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection})); subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
if (FFlag::LuauSubtypingStopAtNormFail && subtypings.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
return SubtypingResult::any(subtypings); return SubtypingResult::any(subtypings);
} }
@ -1451,7 +1410,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Meta
// of the supertype table. // of the supertype table.
// //
// There's a flaw here in that if the __index metamethod contributes a new // There's a flaw here in that if the __index metamethod contributes a new
// field that would satisfy the subtyping relationship, we'll erroneously say // field that would satisfy the subtyping relationship, we'll erronously say
// that the metatable isn't a subtype of the table, even though they have // that the metatable isn't a subtype of the table, even though they have
// compatible properties/shapes. We'll revisit this later when we have a // compatible properties/shapes. We'll revisit this later when we have a
// better understanding of how important this is. // better understanding of how important this is.
@ -1801,12 +1760,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
{ {
results.emplace_back(); results.emplace_back();
for (TypeId superTy : superTypes) for (TypeId superTy : superTypes)
{
results.back().orElse(isCovariantWith(env, subTy, superTy, scope)); results.back().orElse(isCovariantWith(env, subTy, superTy, scope));
if (FFlag::LuauSubtypingStopAtNormFail && results.back().normalizationTooComplex)
return SubtypingResult{false, /* normalizationTooComplex */ true};
}
} }
return SubtypingResult::all(results); return SubtypingResult::all(results);

View file

@ -14,9 +14,6 @@
#include "Luau/Unifier2.h" #include "Luau/Unifier2.h"
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceUpcast) LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceUpcast)
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAGVARIABLE(LuauBidirectionalFailsafe)
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceElideAssert)
namespace Luau namespace Luau
{ {
@ -139,13 +136,14 @@ TypeId matchLiteralType(
* things like replace explicit named properties with indexers as required * things like replace explicit named properties with indexers as required
* by the expected type. * by the expected type.
*/ */
if (!isLiteral(expr)) if (!isLiteral(expr))
{ {
if (FFlag::LuauBidirectionalInferenceUpcast) if (FFlag::LuauBidirectionalInferenceUpcast)
{ {
auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope); auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope);
return result.isSubtype ? expectedType : exprType; return result.isSubtype
? expectedType
: exprType;
} }
else else
return exprType; return exprType;
@ -154,23 +152,11 @@ TypeId matchLiteralType(
expectedType = follow(expectedType); expectedType = follow(expectedType);
exprType = follow(exprType); 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 // "Narrowing" to unknown or any is not going to do anything useful.
// to do so. it's always safe to upcast to `any` or `unknown`, so we return exprType;
// can unconditionally do so here.
if (is<AnyType, UnknownType>(expectedType))
return expectedType;
} }
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>()) if (expr->is<AstExprConstantString>())
{ {
@ -252,15 +238,6 @@ TypeId matchLiteralType(
if (auto exprTable = expr->as<AstExprTable>()) if (auto exprTable = expr->as<AstExprTable>())
{ {
TableType* const tableTy = getMutable<TableType>(exprType); TableType* const tableTy = getMutable<TableType>(exprType);
// This can occur if we have an expression like:
//
// { x = {}, x = 42 }
//
// The type of this will be `{ x: number }`
if (FFlag::LuauBidirectionalFailsafe && !tableTy)
return exprType;
LUAU_ASSERT(tableTy); LUAU_ASSERT(tableTy);
const TableType* expectedTableTy = get<TableType>(expectedType); const TableType* expectedTableTy = get<TableType>(expectedType);
@ -287,9 +264,6 @@ TypeId matchLiteralType(
DenseHashSet<AstExprConstantString*> keysToDelete{nullptr}; DenseHashSet<AstExprConstantString*> keysToDelete{nullptr};
DenseHashSet<TypeId> indexerKeyTypes{nullptr};
DenseHashSet<TypeId> indexerValueTypes{nullptr};
for (const AstExprTable::Item& item : exprTable->items) for (const AstExprTable::Item& item : exprTable->items)
{ {
if (isRecord(item)) if (isRecord(item))
@ -297,11 +271,6 @@ TypeId matchLiteralType(
const AstArray<char>& s = item.key->as<AstExprConstantString>()->value; const AstArray<char>& s = item.key->as<AstExprConstantString>()->value;
std::string keyStr{s.data, s.data + s.size}; std::string keyStr{s.data, s.data + s.size};
auto it = tableTy->props.find(keyStr); auto it = tableTy->props.find(keyStr);
// This can occur, potentially, if we are re-entrant.
if (FFlag::LuauBidirectionalFailsafe && it == tableTy->props.end())
continue;
LUAU_ASSERT(it != tableTy->props.end()); LUAU_ASSERT(it != tableTy->props.end());
Property& prop = it->second; Property& prop = it->second;
@ -338,18 +307,10 @@ TypeId matchLiteralType(
toBlock toBlock
); );
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes) if (tableTy->indexer)
{ unifier->unify(matchedType, tableTy->indexer->indexResultType);
indexerKeyTypes.insert(arena->addType(SingletonType{StringSingleton{keyStr}}));
indexerValueTypes.insert(matchedType);
}
else else
{ tableTy->indexer = TableIndexer{expectedTableTy->indexer->indexType, matchedType};
if (tableTy->indexer)
unifier->unify(matchedType, tableTy->indexer->indexResultType);
else
tableTy->indexer = TableIndexer{expectedTableTy->indexer->indexType, matchedType};
}
keysToDelete.insert(item.key->as<AstExprConstantString>()); keysToDelete.insert(item.key->as<AstExprConstantString>());
@ -407,16 +368,10 @@ TypeId matchLiteralType(
LUAU_ASSERT(matchedType); LUAU_ASSERT(matchedType);
(*astExpectedTypes)[item.value] = matchedType; (*astExpectedTypes)[item.value] = matchedType;
// NOTE: We do *not* add to the potential indexer types here.
// I think this is correct to support something like:
//
// { [string]: number, foo: boolean }
//
} }
else if (item.kind == AstExprTable::Item::List) else if (item.kind == AstExprTable::Item::List)
{ {
if (!FFlag::LuauBidirectionalInferenceCollectIndexerTypes || !FFlag::LuauBidirectionalInferenceElideAssert) LUAU_ASSERT(tableTy->indexer);
LUAU_ASSERT(tableTy->indexer);
if (expectedTableTy->indexer) if (expectedTableTy->indexer)
{ {
@ -437,18 +392,9 @@ TypeId matchLiteralType(
toBlock toBlock
); );
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes) // if the index result type is the prop type, we can replace it with the matched type here.
{ if (tableTy->indexer->indexResultType == *propTy)
indexerKeyTypes.insert(builtinTypes->numberType); tableTy->indexer->indexResultType = matchedType;
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;
}
} }
} }
else if (item.kind == AstExprTable::Item::General) else if (item.kind == AstExprTable::Item::General)
@ -470,13 +416,6 @@ TypeId matchLiteralType(
// Populate expected types for non-string keys declared with [] (the code below will handle the case where they are strings) // Populate expected types for non-string keys declared with [] (the code below will handle the case where they are strings)
if (!item.key->as<AstExprConstantString>() && expectedTableTy->indexer) if (!item.key->as<AstExprConstantString>() && expectedTableTy->indexer)
(*astExpectedTypes)[item.key] = expectedTableTy->indexer->indexType; (*astExpectedTypes)[item.key] = expectedTableTy->indexer->indexType;
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
{
indexerKeyTypes.insert(tKey);
indexerValueTypes.insert(tProp);
}
} }
else else
LUAU_ASSERT(!"Unexpected"); LUAU_ASSERT(!"Unexpected");
@ -538,39 +477,9 @@ TypeId matchLiteralType(
// have one too. // have one too.
// TODO: If the expected table also has an indexer, we might want to // TODO: If the expected table also has an indexer, we might want to
// push the expected indexer's types into it. // push the expected indexer's types into it.
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes && expectedTableTy->indexer) if (expectedTableTy->indexer && !tableTy->indexer)
{ {
if (indexerValueTypes.size() > 0 && indexerKeyTypes.size() > 0) tableTy->indexer = expectedTableTy->indexer;
{
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;
}
} }
} }

View file

@ -10,12 +10,11 @@
#include <limits> #include <limits>
#include <math.h> #include <math.h>
LUAU_FASTFLAG(LuauStoreCSTData2) LUAU_FASTFLAG(LuauStoreCSTData)
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon) LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
LUAU_FASTFLAG(LuauAstTypeGroup3) LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauFixDoBlockEndLocation) LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
LUAU_FASTFLAG(LuauParseOptionalAsNode2) LUAU_FASTFLAG(LuauParseOptionalAsNode)
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
namespace namespace
{ {
@ -168,7 +167,7 @@ struct StringWriter : Writer
void symbol(std::string_view s) override void symbol(std::string_view s) override
{ {
if (FFlag::LuauStoreCSTData2) if (FFlag::LuauStoreCSTData)
{ {
write(s); write(s);
} }
@ -258,7 +257,7 @@ public:
first = !first; first = !first;
else else
{ {
if (FFlag::LuauStoreCSTData2 && commaPosition) if (FFlag::LuauStoreCSTData && commaPosition)
{ {
writer.advance(*commaPosition); writer.advance(*commaPosition);
commaPosition++; commaPosition++;
@ -1230,18 +1229,9 @@ struct Printer_DEPRECATED
AstType* l = a->types.data[0]; AstType* l = a->types.data[0];
AstType* r = a->types.data[1]; AstType* r = a->types.data[1];
if (FFlag::LuauParseOptionalAsNode2) auto lta = l->as<AstTypeReference>();
{ if (lta && lta->name == "nil")
auto lta = l->as<AstTypeReference>(); std::swap(l, r);
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);
}
// it's still possible that we had a (T | U) or (T | nil) and not (nil | T) // it's still possible that we had a (T | U) or (T | nil) and not (nil | T)
auto rta = r->as<AstTypeReference>(); auto rta = r->as<AstTypeReference>();
@ -1264,7 +1254,7 @@ struct Printer_DEPRECATED
for (size_t i = 0; i < a->types.size; ++i) for (size_t i = 0; i < a->types.size; ++i)
{ {
if (FFlag::LuauParseOptionalAsNode2) if (FFlag::LuauParseOptionalAsNode)
{ {
if (a->types.data[i]->is<AstTypeOptional>()) if (a->types.data[i]->is<AstTypeOptional>())
{ {
@ -1499,8 +1489,7 @@ struct Printer
void visualize(AstExpr& expr) void visualize(AstExpr& expr)
{ {
if (!expr.is<AstExprFunction>() || FFlag::LuauFixFunctionWithAttributesStartLocation) advance(expr.location.begin);
advance(expr.location.begin);
if (const auto& a = expr.as<AstExprGroup>()) if (const auto& a = expr.as<AstExprGroup>())
{ {
@ -1634,17 +1623,6 @@ struct Printer
} }
else if (const auto& a = expr.as<AstExprFunction>()) else if (const auto& a = expr.as<AstExprFunction>())
{ {
for (const auto& attribute : a->attributes)
visualizeAttribute(*attribute);
if (FFlag::LuauFixFunctionWithAttributesStartLocation)
{
if (const auto cstNode = lookupCstNode<CstExprFunction>(a))
advance(cstNode->functionKeywordPosition);
}
else
{
advance(a->location.begin);
}
writer.keyword("function"); writer.keyword("function");
visualizeFunctionBody(*a); visualizeFunctionBody(*a);
} }
@ -1896,8 +1874,7 @@ struct Printer
void visualize(AstStat& program) void visualize(AstStat& program)
{ {
if ((!program.is<AstStatLocalFunction>() && !program.is<AstStatFunction>()) || FFlag::LuauFixFunctionWithAttributesStartLocation) advance(program.location.begin);
advance(program.location.begin);
if (const auto& block = program.as<AstStatBlock>()) if (const auto& block = program.as<AstStatBlock>())
{ {
@ -2134,36 +2111,13 @@ struct Printer
} }
else if (const auto& a = program.as<AstStatFunction>()) else if (const auto& a = program.as<AstStatFunction>())
{ {
for (const auto& attribute : a->func->attributes)
visualizeAttribute(*attribute);
if (FFlag::LuauFixFunctionWithAttributesStartLocation)
{
if (const auto cstNode = lookupCstNode<CstStatFunction>(a))
advance(cstNode->functionKeywordPosition);
}
else
{
advance(a->location.begin);
}
writer.keyword("function"); writer.keyword("function");
visualize(*a->name); visualize(*a->name);
visualizeFunctionBody(*a->func); visualizeFunctionBody(*a->func);
} }
else if (const auto& a = program.as<AstStatLocalFunction>()) else if (const auto& a = program.as<AstStatLocalFunction>())
{ {
for (const auto& attribute : a->func->attributes)
visualizeAttribute(*attribute);
const auto cstNode = lookupCstNode<CstStatLocalFunction>(a); const auto cstNode = lookupCstNode<CstStatLocalFunction>(a);
if (FFlag::LuauFixFunctionWithAttributesStartLocation)
{
if (cstNode)
advance(cstNode->localKeywordPosition);
}
else
{
advance(a->location.begin);
}
writer.keyword("local"); writer.keyword("local");
@ -2307,7 +2261,7 @@ struct Printer
if (program.hasSemicolon) if (program.hasSemicolon)
{ {
if (FFlag::LuauStoreCSTData2) if (FFlag::LuauStoreCSTData)
advanceBefore(program.location.end, 1); advanceBefore(program.location.end, 1);
writer.symbol(";"); writer.symbol(";");
} }
@ -2317,7 +2271,7 @@ struct Printer
{ {
const auto cstNode = lookupCstNode<CstExprFunction>(&func); const auto cstNode = lookupCstNode<CstExprFunction>(&func);
// TODO(CLI-139347): need to handle return type (incl. parentheses of return type) // TODO(CLI-139347): need to handle attributes, argument types, and return type (incl. parentheses of return type)
if (func.generics.size > 0 || func.genericPacks.size > 0) if (func.generics.size > 0 || func.genericPacks.size > 0)
{ {
@ -2473,23 +2427,6 @@ struct Printer
} }
} }
void visualizeAttribute(AstAttr& attribute)
{
advance(attribute.location.begin);
switch (attribute.type)
{
case AstAttr::Checked:
writer.keyword("@checked");
break;
case AstAttr::Native:
writer.keyword("@native");
break;
case AstAttr::Deprecated:
writer.keyword("@deprecated");
break;
}
}
void visualizeTypeAnnotation(AstType& typeAnnotation) void visualizeTypeAnnotation(AstType& typeAnnotation)
{ {
advance(typeAnnotation.location.begin); advance(typeAnnotation.location.begin);
@ -2734,25 +2671,14 @@ struct Printer
} }
else if (const auto& a = typeAnnotation.as<AstTypeUnion>()) else if (const auto& a = typeAnnotation.as<AstTypeUnion>())
{ {
const auto cstNode = lookupCstNode<CstTypeUnion>(a); if (a->types.size == 2)
if (!cstNode && a->types.size == 2)
{ {
AstType* l = a->types.data[0]; AstType* l = a->types.data[0];
AstType* r = a->types.data[1]; AstType* r = a->types.data[1];
if (FFlag::LuauParseOptionalAsNode2) auto lta = l->as<AstTypeReference>();
{ if (lta && lta->name == "nil")
auto lta = l->as<AstTypeReference>(); std::swap(l, r);
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);
}
// it's still possible that we had a (T | U) or (T | nil) and not (nil | T) // it's still possible that we had a (T | U) or (T | nil) and not (nil | T)
auto rta = r->as<AstTypeReference>(); auto rta = r->as<AstTypeReference>();
@ -2773,20 +2699,12 @@ struct Printer
} }
} }
if (cstNode && cstNode->leadingPosition)
{
advance(*cstNode->leadingPosition);
writer.symbol("|");
}
size_t separatorIndex = 0;
for (size_t i = 0; i < a->types.size; ++i) for (size_t i = 0; i < a->types.size; ++i)
{ {
if (FFlag::LuauParseOptionalAsNode2) if (FFlag::LuauParseOptionalAsNode)
{ {
if (const auto optional = a->types.data[i]->as<AstTypeOptional>()) if (a->types.data[i]->is<AstTypeOptional>())
{ {
advance(optional->location.begin);
writer.symbol("?"); writer.symbol("?");
continue; continue;
} }
@ -2794,18 +2712,11 @@ struct Printer
if (i > 0) if (i > 0)
{ {
if (cstNode && FFlag::LuauParseOptionalAsNode2) writer.maybeSpace(a->types.data[i]->location.begin, 2);
{
// 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.symbol("|"); writer.symbol("|");
} }
bool wrap = !cstNode && (a->types.data[i]->as<AstTypeIntersection>() || a->types.data[i]->as<AstTypeFunction>()); bool wrap = a->types.data[i]->as<AstTypeIntersection>() || a->types.data[i]->as<AstTypeFunction>();
if (wrap) if (wrap)
writer.symbol("("); writer.symbol("(");
@ -2818,27 +2729,15 @@ struct Printer
} }
else if (const auto& a = typeAnnotation.as<AstTypeIntersection>()) else if (const auto& a = typeAnnotation.as<AstTypeIntersection>())
{ {
const auto cstNode = lookupCstNode<CstTypeIntersection>(a);
// If the sizes are equal, we know there is a leading & token
if (cstNode && cstNode->leadingPosition)
{
advance(*cstNode->leadingPosition);
writer.symbol("&");
}
for (size_t i = 0; i < a->types.size; ++i) for (size_t i = 0; i < a->types.size; ++i)
{ {
if (i > 0) if (i > 0)
{ {
if (cstNode) writer.maybeSpace(a->types.data[i]->location.begin, 2);
advance(cstNode->separatorPositions.data[i - 1]);
else
writer.maybeSpace(a->types.data[i]->location.begin, 2);
writer.symbol("&"); writer.symbol("&");
} }
bool wrap = !cstNode && (a->types.data[i]->as<AstTypeUnion>() || a->types.data[i]->as<AstTypeFunction>()); bool wrap = a->types.data[i]->as<AstTypeUnion>() || a->types.data[i]->as<AstTypeFunction>();
if (wrap) if (wrap)
writer.symbol("("); writer.symbol("(");
@ -2887,7 +2786,7 @@ std::string toString(AstNode* node)
StringWriter writer; StringWriter writer;
writer.pos = node->location.begin; writer.pos = node->location.begin;
if (FFlag::LuauStoreCSTData2) if (FFlag::LuauStoreCSTData)
{ {
Printer printer(writer, CstNodeMap{nullptr}); Printer printer(writer, CstNodeMap{nullptr});
printer.writeTypes = true; printer.writeTypes = true;
@ -2923,7 +2822,7 @@ void dump(AstNode* node)
std::string transpile(AstStatBlock& block, const CstNodeMap& cstNodeMap) std::string transpile(AstStatBlock& block, const CstNodeMap& cstNodeMap)
{ {
StringWriter writer; StringWriter writer;
if (FFlag::LuauStoreCSTData2) if (FFlag::LuauStoreCSTData)
{ {
Printer(writer, cstNodeMap).visualizeBlock(block); Printer(writer, cstNodeMap).visualizeBlock(block);
} }
@ -2937,7 +2836,7 @@ std::string transpile(AstStatBlock& block, const CstNodeMap& cstNodeMap)
std::string transpileWithTypes(AstStatBlock& block, const CstNodeMap& cstNodeMap) std::string transpileWithTypes(AstStatBlock& block, const CstNodeMap& cstNodeMap)
{ {
StringWriter writer; StringWriter writer;
if (FFlag::LuauStoreCSTData2) if (FFlag::LuauStoreCSTData)
{ {
Printer printer(writer, cstNodeMap); Printer printer(writer, cstNodeMap);
printer.writeTypes = true; printer.writeTypes = true;

View file

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

View file

@ -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( FunctionType::FunctionType(
std::vector<TypeId> generics, std::vector<TypeId> generics,
std::vector<TypePackId> genericPacks, 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() {}
Property::Property( Property::Property(

View file

@ -13,7 +13,7 @@
#include <string> #include <string>
LUAU_FASTFLAG(LuauStoreCSTData2) LUAU_FASTFLAG(LuauStoreCSTData)
static char* allocateString(Luau::Allocator& allocator, std::string_view contents) static char* allocateString(Luau::Allocator& allocator, std::string_view contents)
{ {
@ -308,7 +308,7 @@ public:
if (el) if (el)
new (arg) new (arg)
std::optional<AstArgumentName>(AstArgumentName(AstName(el->name.c_str()), FFlag::LuauStoreCSTData2 ? Location() : el->location)); std::optional<AstArgumentName>(AstArgumentName(AstName(el->name.c_str()), FFlag::LuauStoreCSTData ? Location() : el->location));
else else
new (arg) std::optional<AstArgumentName>(); new (arg) std::optional<AstArgumentName>();
} }

View file

@ -33,7 +33,6 @@ LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAGVARIABLE(LuauImproveTypePathsInErrors) LUAU_FASTFLAGVARIABLE(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats)
namespace Luau namespace Luau
{ {
@ -2230,21 +2229,10 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey)
return builtinTypes->numberType; return builtinTypes->numberType;
case AstExprBinary::Op::Concat: case AstExprBinary::Op::Concat:
{ testIsSubtype(leftType, builtinTypes->stringType, expr->left->location);
if (FFlag::LuauTypeCheckerAcceptNumberConcats) testIsSubtype(rightType, builtinTypes->stringType, expr->right->location);
{
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);
}
return builtinTypes->stringType; return builtinTypes->stringType;
}
case AstExprBinary::Op::CompareGe: case AstExprBinary::Op::CompareGe:
case AstExprBinary::Op::CompareGt: case AstExprBinary::Op::CompareGt:
case AstExprBinary::Op::CompareLe: case AstExprBinary::Op::CompareLe:

View file

@ -48,28 +48,22 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1);
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies) LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies)
LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete)
LUAU_FASTFLAGVARIABLE(LuauMetatableTypeFunctions) LUAU_FASTFLAGVARIABLE(LuauMetatableTypeFunctions)
LUAU_FASTFLAGVARIABLE(LuauClipNestedAndRecursiveUnion)
LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionImprovements) LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionImprovements)
LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionFunctionMetamethods) LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionFunctionMetamethods)
LUAU_FASTFLAGVARIABLE(LuauIntersectNotNil) LUAU_FASTFLAGVARIABLE(LuauIntersectNotNil)
LUAU_FASTFLAGVARIABLE(LuauSkipNoRefineDuringRefinement) LUAU_FASTFLAGVARIABLE(LuauSkipNoRefineDuringRefinement)
LUAU_FASTFLAGVARIABLE(LuauMetatablesHaveLength)
LUAU_FASTFLAGVARIABLE(LuauDontForgetToReduceUnionFunc) LUAU_FASTFLAGVARIABLE(LuauDontForgetToReduceUnionFunc)
LUAU_FASTFLAGVARIABLE(LuauSearchForRefineableType) LUAU_FASTFLAGVARIABLE(LuauSearchForRefineableType)
LUAU_FASTFLAGVARIABLE(LuauIndexAnyIsAny) LUAU_FASTFLAGVARIABLE(LuauIndexAnyIsAny)
LUAU_FASTFLAGVARIABLE(LuauFixCyclicIndexInIndexer)
LUAU_FASTFLAGVARIABLE(LuauSimplyRefineNotNil)
LUAU_FASTFLAGVARIABLE(LuauIndexDeferPendingIndexee)
LUAU_FASTFLAGVARIABLE(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAGVARIABLE(LuauReduceUnionFollowUnionType)
namespace Luau namespace Luau
{ {
using TypeOrTypePackIdSet = DenseHashSet<const void*>; using TypeOrTypePackIdSet = DenseHashSet<const void*>;
struct InstanceCollector_DEPRECATED : TypeOnceVisitor struct InstanceCollector : TypeOnceVisitor
{ {
VecDeque<TypeId> tys; VecDeque<TypeId> tys;
VecDeque<TypePackId> tps; VecDeque<TypePackId> tps;
@ -124,153 +118,6 @@ struct InstanceCollector_DEPRECATED : TypeOnceVisitor
} }
}; };
struct InstanceCollector : TypeOnceVisitor
{
DenseHashSet<TypeId> recordedTys{nullptr};
VecDeque<TypeId> tys;
DenseHashSet<TypePackId> recordedTps{nullptr};
VecDeque<TypePackId> tps;
TypeOrTypePackIdSet shouldGuess{nullptr};
std::vector<const void*> typeFunctionInstanceStack;
std::vector<TypeId> cyclicInstance;
bool visit(TypeId ty, const TypeFunctionInstanceType& tfit) override
{
// TypeVisitor performs a depth-first traversal in the absence of
// cycles. This means that by pushing to the front of the queue, we will
// try to reduce deeper instances first if we start with the first thing
// in the queue. Consider Add<Add<Add<number, number>, number>, number>:
// we want to reduce the innermost Add<number, number> instantiation
// first.
typeFunctionInstanceStack.push_back(ty);
if (DFInt::LuauTypeFamilyUseGuesserDepth >= 0 && int(typeFunctionInstanceStack.size()) > DFInt::LuauTypeFamilyUseGuesserDepth)
shouldGuess.insert(ty);
if (!recordedTys.contains(ty))
{
recordedTys.insert(ty);
tys.push_front(ty);
}
for (TypeId p : tfit.typeArguments)
traverse(p);
for (TypePackId p : tfit.packArguments)
traverse(p);
typeFunctionInstanceStack.pop_back();
return false;
}
void cycle(TypeId ty) override
{
TypeId t = follow(ty);
if (get<TypeFunctionInstanceType>(t))
{
// If we see a type a second time and it's in the type function stack, it's a real cycle
if (std::find(typeFunctionInstanceStack.begin(), typeFunctionInstanceStack.end(), t) != typeFunctionInstanceStack.end())
cyclicInstance.push_back(t);
}
}
bool visit(TypeId ty, const ClassType&) override
{
return false;
}
bool visit(TypePackId tp, const TypeFunctionInstanceTypePack& tfitp) override
{
// TypeVisitor performs a depth-first traversal in the absence of
// cycles. This means that by pushing to the front of the queue, we will
// try to reduce deeper instances first if we start with the first thing
// in the queue. Consider Add<Add<Add<number, number>, number>, number>:
// we want to reduce the innermost Add<number, number> instantiation
// first.
typeFunctionInstanceStack.push_back(tp);
if (DFInt::LuauTypeFamilyUseGuesserDepth >= 0 && int(typeFunctionInstanceStack.size()) > DFInt::LuauTypeFamilyUseGuesserDepth)
shouldGuess.insert(tp);
if (!recordedTps.contains(tp))
{
recordedTps.insert(tp);
tps.push_front(tp);
}
for (TypeId p : tfitp.typeArguments)
traverse(p);
for (TypePackId p : tfitp.packArguments)
traverse(p);
typeFunctionInstanceStack.pop_back();
return false;
}
};
struct UnscopedGenericFinder : TypeOnceVisitor
{
std::vector<TypeId> scopeGenTys;
std::vector<TypePackId> scopeGenTps;
bool foundUnscoped = false;
bool visit(TypeId ty) override
{
// Once we have found an unscoped generic, we will stop the traversal
return !foundUnscoped;
}
bool visit(TypePackId tp) override
{
// Once we have found an unscoped generic, we will stop the traversal
return !foundUnscoped;
}
bool visit(TypeId ty, const GenericType&) override
{
if (std::find(scopeGenTys.begin(), scopeGenTys.end(), ty) == scopeGenTys.end())
foundUnscoped = true;
return false;
}
bool visit(TypePackId tp, const GenericTypePack&) override
{
if (std::find(scopeGenTps.begin(), scopeGenTps.end(), tp) == scopeGenTps.end())
foundUnscoped = true;
return false;
}
bool visit(TypeId ty, const FunctionType& ftv) override
{
size_t startTyCount = scopeGenTys.size();
size_t startTpCount = scopeGenTps.size();
scopeGenTys.insert(scopeGenTys.end(), ftv.generics.begin(), ftv.generics.end());
scopeGenTps.insert(scopeGenTps.end(), ftv.genericPacks.begin(), ftv.genericPacks.end());
traverse(ftv.argTypes);
traverse(ftv.retTypes);
scopeGenTys.resize(startTyCount);
scopeGenTps.resize(startTpCount);
return false;
}
bool visit(TypeId ty, const ClassType&) override
{
return false;
}
};
struct TypeFunctionReducer struct TypeFunctionReducer
{ {
TypeFunctionContext ctx; TypeFunctionContext ctx;
@ -511,6 +358,7 @@ struct TypeFunctionReducer
return false; return false;
} }
void stepType() void stepType()
{ {
TypeId subject = follow(queuedTys.front()); TypeId subject = follow(queuedTys.front());
@ -524,26 +372,6 @@ struct TypeFunctionReducer
if (const TypeFunctionInstanceType* tfit = get<TypeFunctionInstanceType>(subject)) if (const TypeFunctionInstanceType* tfit = get<TypeFunctionInstanceType>(subject))
{ {
if (FFlag::LuauNewTypeFunReductionChecks2 && tfit->function->name == "user")
{
UnscopedGenericFinder finder;
finder.traverse(subject);
if (finder.foundUnscoped)
{
// Do not step into this type again
irreducible.insert(subject);
// Let the caller know this type will not become reducible
result.irreducibleTypes.insert(subject);
if (FFlag::DebugLuauLogTypeFamilies)
printf("Irreducible due to an unscoped generic type\n");
return;
}
}
SkipTestResult testCyclic = testForSkippability(subject); SkipTestResult testCyclic = testForSkippability(subject);
if (!testParameters(subject, tfit) && testCyclic != SkipTestResult::CyclicTypeFunction) if (!testParameters(subject, tfit) && testCyclic != SkipTestResult::CyclicTypeFunction)
@ -652,114 +480,56 @@ static FunctionGraphReductionResult reduceFunctionsInternal(
FunctionGraphReductionResult reduceTypeFunctions(TypeId entrypoint, Location location, TypeFunctionContext ctx, bool force) FunctionGraphReductionResult reduceTypeFunctions(TypeId entrypoint, Location location, TypeFunctionContext ctx, bool force)
{ {
if (FFlag::LuauNewTypeFunReductionChecks2) InstanceCollector collector;
try
{ {
InstanceCollector collector; collector.traverse(entrypoint);
try
{
collector.traverse(entrypoint);
}
catch (RecursionLimitException&)
{
return FunctionGraphReductionResult{};
}
if (collector.tys.empty() && collector.tps.empty())
return {};
return reduceFunctionsInternal(
std::move(collector.tys),
std::move(collector.tps),
std::move(collector.shouldGuess),
std::move(collector.cyclicInstance),
location,
ctx,
force
);
} }
else catch (RecursionLimitException&)
{ {
InstanceCollector_DEPRECATED collector; return FunctionGraphReductionResult{};
try
{
collector.traverse(entrypoint);
}
catch (RecursionLimitException&)
{
return FunctionGraphReductionResult{};
}
if (collector.tys.empty() && collector.tps.empty())
return {};
return reduceFunctionsInternal(
std::move(collector.tys),
std::move(collector.tps),
std::move(collector.shouldGuess),
std::move(collector.cyclicInstance),
location,
ctx,
force
);
} }
if (collector.tys.empty() && collector.tps.empty())
return {};
return reduceFunctionsInternal(
std::move(collector.tys),
std::move(collector.tps),
std::move(collector.shouldGuess),
std::move(collector.cyclicInstance),
location,
ctx,
force
);
} }
FunctionGraphReductionResult reduceTypeFunctions(TypePackId entrypoint, Location location, TypeFunctionContext ctx, bool force) FunctionGraphReductionResult reduceTypeFunctions(TypePackId entrypoint, Location location, TypeFunctionContext ctx, bool force)
{ {
if (FFlag::LuauNewTypeFunReductionChecks2) InstanceCollector collector;
try
{ {
InstanceCollector collector; collector.traverse(entrypoint);
try
{
collector.traverse(entrypoint);
}
catch (RecursionLimitException&)
{
return FunctionGraphReductionResult{};
}
if (collector.tys.empty() && collector.tps.empty())
return {};
return reduceFunctionsInternal(
std::move(collector.tys),
std::move(collector.tps),
std::move(collector.shouldGuess),
std::move(collector.cyclicInstance),
location,
ctx,
force
);
} }
else catch (RecursionLimitException&)
{ {
InstanceCollector_DEPRECATED collector; return FunctionGraphReductionResult{};
try
{
collector.traverse(entrypoint);
}
catch (RecursionLimitException&)
{
return FunctionGraphReductionResult{};
}
if (collector.tys.empty() && collector.tps.empty())
return {};
return reduceFunctionsInternal(
std::move(collector.tys),
std::move(collector.tps),
std::move(collector.shouldGuess),
std::move(collector.cyclicInstance),
location,
ctx,
force
);
} }
if (collector.tys.empty() && collector.tps.empty())
return {};
return reduceFunctionsInternal(
std::move(collector.tys),
std::move(collector.tps),
std::move(collector.shouldGuess),
std::move(collector.cyclicInstance),
location,
ctx,
force
);
} }
bool isPending(TypeId ty, ConstraintSolver* solver) bool isPending(TypeId ty, ConstraintSolver* solver)
@ -854,42 +624,6 @@ static std::optional<TypeFunctionReductionResult<TypeId>> tryDistributeTypeFunct
return std::nullopt; return std::nullopt;
} }
struct FindUserTypeFunctionBlockers : TypeOnceVisitor
{
NotNull<TypeFunctionContext> ctx;
DenseHashSet<TypeId> blockingTypeMap{nullptr};
std::vector<TypeId> blockingTypes;
explicit FindUserTypeFunctionBlockers(NotNull<TypeFunctionContext> ctx)
: TypeOnceVisitor(/* skipBoundTypes */ true)
, ctx(ctx)
{
}
bool visit(TypeId ty) override
{
if (isPending(ty, ctx->solver))
{
if (!blockingTypeMap.contains(ty))
{
blockingTypeMap.insert(ty);
blockingTypes.push_back(ty);
}
}
return true;
}
bool visit(TypePackId tp) override
{
return true;
}
bool visit(TypeId ty, const ClassType&) override
{
return false;
}
};
TypeFunctionReductionResult<TypeId> userDefinedTypeFunction( TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
TypeId instance, TypeId instance,
const std::vector<TypeId>& typeParams, const std::vector<TypeId>& typeParams,
@ -912,38 +646,21 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
} }
// If type functions cannot be evaluated because of errors in the code, we do not generate any additional ones // If type functions cannot be evaluated because of errors in the code, we do not generate any additional ones
if (!ctx->typeFunctionRuntime->allowEvaluation || (FFlag::LuauTypeFunResultInAutocomplete && typeFunction->userFuncData.definition->hasErrors)) if (!ctx->typeFunctionRuntime->allowEvaluation)
return {ctx->builtins->errorRecoveryType(), Reduction::MaybeOk, {}, {}}; return {ctx->builtins->errorRecoveryType(), Reduction::MaybeOk, {}, {}};
if (FFlag::LuauNewTypeFunReductionChecks2) for (auto typeParam : typeParams)
{ {
FindUserTypeFunctionBlockers check{ctx}; TypeId ty = follow(typeParam);
for (auto typeParam : typeParams) // block if we need to
check.traverse(follow(typeParam)); if (isPending(ty, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {ty}, {}};
if (!check.blockingTypes.empty())
return {std::nullopt, Reduction::MaybeOk, check.blockingTypes, {}};
}
else
{
for (auto typeParam : typeParams)
{
TypeId ty = follow(typeParam);
// block if we need to
if (isPending(ty, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {ty}, {}};
}
} }
// Ensure that whole type function environment is registered // Ensure that whole type function environment is registered
for (auto& [name, definition] : typeFunction->userFuncData.environment) for (auto& [name, definition] : typeFunction->userFuncData.environment)
{ {
// Cannot evaluate if a potential dependency couldn't be parsed
if (FFlag::LuauTypeFunResultInAutocomplete && definition.first->hasErrors)
return {ctx->builtins->errorRecoveryType(), Reduction::MaybeOk, {}, {}};
if (std::optional<std::string> error = ctx->typeFunctionRuntime->registerFunction(definition.first)) if (std::optional<std::string> error = ctx->typeFunctionRuntime->registerFunction(definition.first))
{ {
// Failure to register at this point means that original definition had to error out and should not have been present in the // Failure to register at this point means that original definition had to error out and should not have been present in the
@ -1157,16 +874,7 @@ TypeFunctionReductionResult<TypeId> lenTypeFunction(
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, operandTy, "__len", Location{}); std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, operandTy, "__len", Location{});
if (!mmType) if (!mmType)
{
if (FFlag::LuauMetatablesHaveLength)
{
// If we have a metatable type with no __len, this means we still have a table with default length function
if (get<MetatableType>(normalizedOperand))
return {ctx->builtins->numberType, Reduction::MaybeOk, {}, {}};
}
return {std::nullopt, Reduction::Erroneous, {}, {}}; return {std::nullopt, Reduction::Erroneous, {}, {}};
}
mmType = follow(*mmType); mmType = follow(*mmType);
if (isPending(*mmType, ctx->solver)) if (isPending(*mmType, ctx->solver))
@ -1296,10 +1004,6 @@ std::optional<std::string> TypeFunctionRuntime::registerFunction(AstStatTypeFunc
if (!allowEvaluation) if (!allowEvaluation)
return std::nullopt; return std::nullopt;
// Do not evaluate type functions with parse errors inside
if (FFlag::LuauTypeFunResultInAutocomplete && function->hasErrors)
return std::nullopt;
prepareState(); prepareState();
lua_State* global = state.get(); lua_State* global = state.get();
@ -1342,6 +1046,7 @@ std::optional<std::string> TypeFunctionRuntime::registerFunction(AstStatTypeFunc
std::string bytecode = builder.getBytecode(); std::string bytecode = builder.getBytecode();
// Separate sandboxed thread for individual execution and private globals // Separate sandboxed thread for individual execution and private globals
lua_State* L = lua_newthread(global); lua_State* L = lua_newthread(global);
LuauTempThreadPopper popper(global); LuauTempThreadPopper popper(global);
@ -2218,18 +1923,6 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
} }
} }
if (FFlag::LuauSimplyRefineNotNil)
{
if (auto negation = get<NegationType>(discriminant))
{
if (auto primitive = get<PrimitiveType>(follow(negation->ty)); primitive && primitive->type == PrimitiveType::NilType)
{
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant);
return {result.result, {}};
}
}
}
// If the target type is a table, then simplification already implements the logic to deal with refinements properly since the // If the target type is a table, then simplification already implements the logic to deal with refinements properly since the
// type of the discriminant is guaranteed to only ever be an (arbitrarily-nested) table of a single property type. // type of the discriminant is guaranteed to only ever be an (arbitrarily-nested) table of a single property type.
if (get<TableType>(target)) if (get<TableType>(target))
@ -2337,29 +2030,6 @@ struct CollectUnionTypeOptions : TypeOnceVisitor
return false; return false;
} }
bool visit(TypeId ty, const UnionType& ut) override
{
if (FFlag::LuauReduceUnionFollowUnionType)
{
// If we have something like:
//
// union<A | B, C | D>
//
// We probably just want to consider this to be the same as
//
// union<A, B, C, D>
return true;
}
else
{
// Copy of the default visit method.
options.insert(ty);
if (isPending(ty, ctx->solver))
blockingTypes.insert(ty);
return false;
}
}
bool visit(TypeId ty, const TypeFunctionInstanceType& tfit) override bool visit(TypeId ty, const TypeFunctionInstanceType& tfit) override
{ {
if (tfit.function->name != builtinTypeFunctions().unionFunc.name) if (tfit.function->name != builtinTypeFunctions().unionFunc.name)
@ -2772,19 +2442,7 @@ bool searchPropsAndIndexer(
// index into tbl's indexer // index into tbl's indexer
if (tblIndexer) if (tblIndexer)
{ {
TypeId indexType = FFlag::LuauFixCyclicIndexInIndexer ? follow(tblIndexer->indexType) : tblIndexer->indexType; if (isSubtype(ty, tblIndexer->indexType, ctx->scope, ctx->builtins, ctx->simplifier, *ctx->ice))
if (FFlag::LuauFixCyclicIndexInIndexer)
{
if (auto tfit = get<TypeFunctionInstanceType>(indexType))
{
// if we have an index function here, it means we're in a cycle, so let's see if it's well-founded if we tie the knot
if (tfit->function.get() == &builtinTypeFunctions().indexFunc)
indexType = follow(tblIndexer->indexResultType);
}
}
if (isSubtype(ty, indexType, ctx->scope, ctx->builtins, ctx->simplifier, *ctx->ice))
{ {
TypeId idxResultTy = follow(tblIndexer->indexResultType); TypeId idxResultTy = follow(tblIndexer->indexResultType);
@ -2960,10 +2618,6 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
) )
{ {
TypeId indexeeTy = follow(typeParams.at(0)); TypeId indexeeTy = follow(typeParams.at(0));
if (FFlag::LuauIndexDeferPendingIndexee && isPending(indexeeTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {indexeeTy}, {}};
std::shared_ptr<const NormalizedType> indexeeNormTy = ctx->normalizer->normalize(indexeeTy); std::shared_ptr<const NormalizedType> indexeeNormTy = ctx->normalizer->normalize(indexeeTy);
// if the indexee failed to normalize, we can't reduce, but know nothing about inhabitance. // if the indexee failed to normalize, we can't reduce, but know nothing about inhabitance.

View file

@ -14,6 +14,7 @@
#include <vector> #include <vector>
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit) LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
LUAU_FASTFLAGVARIABLE(LuauTypeFunPrintFix)
LUAU_FASTFLAGVARIABLE(LuauTypeFunReadWriteParents) LUAU_FASTFLAGVARIABLE(LuauTypeFunReadWriteParents)
namespace Luau namespace Luau
@ -1655,7 +1656,10 @@ static int print(lua_State* L)
const char* s = luaL_tolstring(L, i, &l); // convert to string using __tostring et al const char* s = luaL_tolstring(L, i, &l); // convert to string using __tostring et al
if (i > 1) if (i > 1)
{ {
result.append(1, '\t'); if (FFlag::LuauTypeFunPrintFix)
result.append(1, '\t');
else
result.append('\t', 1);
} }
result.append(s, l); result.append(s, l);
lua_pop(L, 1); lua_pop(L, 1);

View file

@ -34,7 +34,8 @@ LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
LUAU_FASTFLAG(LuauModuleHoldsAstRoot)
namespace Luau namespace Luau
{ {
@ -255,7 +256,8 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
currentModule->type = module.type; currentModule->type = module.type;
currentModule->allocator = module.allocator; currentModule->allocator = module.allocator;
currentModule->names = module.names; currentModule->names = module.names;
currentModule->root = module.root; if (FFlag::LuauModuleHoldsAstRoot)
currentModule->root = module.root;
iceHandler->moduleName = module.name; iceHandler->moduleName = module.name;
normalizer.arena = &currentModule->internalTypes; normalizer.arena = &currentModule->internalTypes;
@ -1656,10 +1658,7 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea
FreeType* ftv = getMutable<FreeType>(ty); FreeType* ftv = getMutable<FreeType>(ty);
LUAU_ASSERT(ftv); LUAU_ASSERT(ftv);
ftv->forwardedTypeAlias = true; ftv->forwardedTypeAlias = true;
if (FFlag::LuauRetainDefinitionAliasLocations) bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty, typealias.location};
else
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
scope->typeAliasLocations[name] = typealias.location; scope->typeAliasLocations[name] = typealias.location;
scope->typeAliasNameLocations[name] = typealias.nameLocation; 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}); TypeId metaTy = addType(TableType{TableState::Sealed, scope->level});
ctv->metatable = metaTy; ctv->metatable = metaTy;
if (FFlag::LuauRetainDefinitionAliasLocations) scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
scope->exportedTypeBindings[className] = TypeFun{{}, classTy, declaredClass.location};
else
scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
} }
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass) ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass)

View file

@ -433,6 +433,9 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable)
superTypePackParamsIter++; superTypePackParamsIter++;
} }
if (subTable->selfTy && superTable->selfTy)
result &= unify(*subTable->selfTy, *superTable->selfTy);
if (subTable->indexer && superTable->indexer) if (subTable->indexer && superTable->indexer)
{ {
result &= unify(subTable->indexer->indexType, superTable->indexer->indexType); result &= unify(subTable->indexer->indexType, superTable->indexer->indexType);
@ -647,22 +650,27 @@ struct FreeTypeSearcher : TypeVisitor
{ {
} }
Polarity polarity = Polarity::Positive; enum Polarity
{
Positive,
Negative,
Both,
};
Polarity polarity = Positive;
void flip() void flip()
{ {
switch (polarity) switch (polarity)
{ {
case Polarity::Positive: case Positive:
polarity = Polarity::Negative; polarity = Negative;
break; break;
case Polarity::Negative: case Negative:
polarity = Polarity::Positive; polarity = Positive;
break; break;
case Polarity::Mixed: case Both:
break; break;
default:
LUAU_ASSERT(!"Unreachable");
} }
} }
@ -673,7 +681,7 @@ struct FreeTypeSearcher : TypeVisitor
{ {
switch (polarity) switch (polarity)
{ {
case Polarity::Positive: case Positive:
{ {
if (seenPositive.contains(ty)) if (seenPositive.contains(ty))
return true; return true;
@ -681,7 +689,7 @@ struct FreeTypeSearcher : TypeVisitor
seenPositive.insert(ty); seenPositive.insert(ty);
return false; return false;
} }
case Polarity::Negative: case Negative:
{ {
if (seenNegative.contains(ty)) if (seenNegative.contains(ty))
return true; return true;
@ -689,7 +697,7 @@ struct FreeTypeSearcher : TypeVisitor
seenNegative.insert(ty); seenNegative.insert(ty);
return false; return false;
} }
case Polarity::Mixed: case Both:
{ {
if (seenPositive.contains(ty) && seenNegative.contains(ty)) if (seenPositive.contains(ty) && seenNegative.contains(ty))
return true; return true;
@ -698,8 +706,6 @@ struct FreeTypeSearcher : TypeVisitor
seenNegative.insert(ty); seenNegative.insert(ty);
return false; return false;
} }
default:
LUAU_ASSERT(!"Unreachable");
} }
return false; return false;
@ -730,18 +736,16 @@ struct FreeTypeSearcher : TypeVisitor
switch (polarity) switch (polarity)
{ {
case Polarity::Positive: case Positive:
positiveTypes[ty]++; positiveTypes[ty]++;
break; break;
case Polarity::Negative: case Negative:
negativeTypes[ty]++; negativeTypes[ty]++;
break; break;
case Polarity::Mixed: case Both:
positiveTypes[ty]++; positiveTypes[ty]++;
negativeTypes[ty]++; negativeTypes[ty]++;
break; break;
default:
LUAU_ASSERT(!"Unreachable");
} }
return true; return true;
@ -756,18 +760,16 @@ struct FreeTypeSearcher : TypeVisitor
{ {
switch (polarity) switch (polarity)
{ {
case Polarity::Positive: case Positive:
positiveTypes[ty]++; positiveTypes[ty]++;
break; break;
case Polarity::Negative: case Negative:
negativeTypes[ty]++; negativeTypes[ty]++;
break; break;
case Polarity::Mixed: case Both:
positiveTypes[ty]++; positiveTypes[ty]++;
negativeTypes[ty]++; negativeTypes[ty]++;
break; break;
default:
LUAU_ASSERT(!"Unreachable");
} }
} }
@ -780,7 +782,7 @@ struct FreeTypeSearcher : TypeVisitor
LUAU_ASSERT(prop.isShared()); LUAU_ASSERT(prop.isShared());
Polarity p = polarity; Polarity p = polarity;
polarity = Polarity::Mixed; polarity = Both;
traverse(prop.type()); traverse(prop.type());
polarity = p; polarity = p;
} }
@ -824,18 +826,16 @@ struct FreeTypeSearcher : TypeVisitor
switch (polarity) switch (polarity)
{ {
case Polarity::Positive: case Positive:
positiveTypes[tp]++; positiveTypes[tp]++;
break; break;
case Polarity::Negative: case Negative:
negativeTypes[tp]++; negativeTypes[tp]++;
break; break;
case Polarity::Mixed: case Both:
positiveTypes[tp]++; positiveTypes[tp]++;
negativeTypes[tp]++; negativeTypes[tp]++;
break; break;
default:
LUAU_ASSERT(!"Unreachable");
} }
return true; return true;

View file

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

View file

@ -112,7 +112,6 @@ public:
CstExprFunction(); CstExprFunction();
Position functionKeywordPosition{0, 0};
Position openGenericsPosition{0,0}; Position openGenericsPosition{0,0};
AstArray<Position> genericsCommaPositions; AstArray<Position> genericsCommaPositions;
Position closeGenericsPosition{0,0}; Position closeGenericsPosition{0,0};
@ -275,24 +274,13 @@ public:
Position opPosition; Position opPosition;
}; };
class CstStatFunction : public CstNode
{
public:
LUAU_CST_RTTI(CstStatFunction)
explicit CstStatFunction(Position functionKeywordPosition);
Position functionKeywordPosition;
};
class CstStatLocalFunction : public CstNode class CstStatLocalFunction : public CstNode
{ {
public: public:
LUAU_CST_RTTI(CstStatLocalFunction) LUAU_CST_RTTI(CstStatLocalFunction)
explicit CstStatLocalFunction(Position localKeywordPosition, Position functionKeywordPosition); explicit CstStatLocalFunction(Position functionKeywordPosition);
Position localKeywordPosition;
Position functionKeywordPosition; Position functionKeywordPosition;
}; };
@ -433,28 +421,6 @@ public:
Position closePosition; Position closePosition;
}; };
class CstTypeUnion : public CstNode
{
public:
LUAU_CST_RTTI(CstTypeUnion)
CstTypeUnion(std::optional<Position> leadingPosition, AstArray<Position> separatorPositions);
std::optional<Position> leadingPosition;
AstArray<Position> separatorPositions;
};
class CstTypeIntersection : public CstNode
{
public:
LUAU_CST_RTTI(CstTypeIntersection)
explicit CstTypeIntersection(std::optional<Position> leadingPosition, AstArray<Position> separatorPositions);
std::optional<Position> leadingPosition;
AstArray<Position> separatorPositions;
};
class CstTypeSingletonString : public CstNode class CstTypeSingletonString : public CstNode
{ {
public: public:

View file

@ -125,7 +125,7 @@ private:
AstStat* parseFor(); AstStat* parseFor();
// funcname ::= Name {`.' Name} [`:' Name] // funcname ::= Name {`.' Name} [`:' Name]
AstExpr* parseFunctionName(bool& hasself, AstName& debugname); AstExpr* parseFunctionName(Location start_DEPRECATED, bool& hasself, AstName& debugname);
// function funcname funcbody // function funcname funcbody
LUAU_FORCEINLINE AstStat* parseFunctionStat(const AstArray<AstAttr*>& attributes = {nullptr, 0}); LUAU_FORCEINLINE AstStat* parseFunctionStat(const AstArray<AstAttr*>& attributes = {nullptr, 0});
@ -157,9 +157,7 @@ private:
// type function Name ... end // type function Name ... end
AstStat* parseTypeFunction(const Location& start, bool exported, Position typeKeywordPosition); AstStat* parseTypeFunction(const Location& start, bool exported, Position typeKeywordPosition);
AstDeclaredClassProp parseDeclaredClassMethod(const AstArray<AstAttr*>& attributes); AstDeclaredClassProp parseDeclaredClassMethod();
AstDeclaredClassProp parseDeclaredClassMethod_DEPRECATED();
// `declare global' Name: Type | // `declare global' Name: Type |
// `declare function' Name`(' [parlist] `)' [`:` Type] // `declare function' Name`(' [parlist] `)' [`:` Type]
@ -230,9 +228,9 @@ private:
Position colonPosition; Position colonPosition;
}; };
TableIndexerResult parseTableIndexer(AstTableAccess access, std::optional<Location> accessLocation, Lexeme begin); TableIndexerResult parseTableIndexer(AstTableAccess access, std::optional<Location> accessLocation);
// Remove with FFlagLuauStoreCSTData2 // Remove with FFlagLuauStoreCSTData
AstTableIndexer* parseTableIndexer_DEPRECATED(AstTableAccess access, std::optional<Location> accessLocation, Lexeme begin); AstTableIndexer* parseTableIndexer_DEPRECATED(AstTableAccess access, std::optional<Location> accessLocation);
AstTypeOrPack parseFunctionType(bool allowPack, const AstArray<AstAttr*>& attributes); AstTypeOrPack parseFunctionType(bool allowPack, const AstArray<AstAttr*>& attributes);
AstType* parseFunctionTypeTail( AstType* parseFunctionTypeTail(

View file

@ -3,24 +3,9 @@
#include "Luau/Common.h" #include "Luau/Common.h"
LUAU_FASTFLAG(LuauDeprecatedAttribute);
namespace Luau namespace Luau
{ {
static bool hasAttributeInArray(const AstArray<AstAttr*> attributes, AstAttr::Type attributeType)
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
for (const auto attribute : attributes)
{
if (attribute->type == attributeType)
return true;
}
return false;
}
static void visitTypeList(AstVisitor* visitor, const AstTypeList& list) static void visitTypeList(AstVisitor* visitor, const AstTypeList& list)
{ {
for (AstType* ty : list.types) for (AstType* ty : list.types)
@ -292,13 +277,6 @@ bool AstExprFunction::hasNativeAttribute() const
return false; return false;
} }
bool AstExprFunction::hasAttribute(const AstAttr::Type attributeType) const
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
return hasAttributeInArray(attributes, attributeType);
}
AstExprTable::AstExprTable(const Location& location, const AstArray<Item>& items) AstExprTable::AstExprTable(const Location& location, const AstArray<Item>& items)
: AstExpr(ClassIndex(), location) : AstExpr(ClassIndex(), location)
, items(items) , items(items)
@ -813,15 +791,13 @@ AstStatTypeFunction::AstStatTypeFunction(
const AstName& name, const AstName& name,
const Location& nameLocation, const Location& nameLocation,
AstExprFunction* body, AstExprFunction* body,
bool exported, bool exported
bool hasErrors
) )
: AstStat(ClassIndex(), location) : AstStat(ClassIndex(), location)
, name(name) , name(name)
, nameLocation(nameLocation) , nameLocation(nameLocation)
, body(body) , body(body)
, exported(exported) , exported(exported)
, hasErrors(hasErrors)
{ {
} }
@ -918,13 +894,6 @@ bool AstStatDeclareFunction::isCheckedFunction() const
return false; return false;
} }
bool AstStatDeclareFunction::hasAttribute(AstAttr::Type attributeType) const
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
return hasAttributeInArray(attributes, attributeType);
}
AstStatDeclareClass::AstStatDeclareClass( AstStatDeclareClass::AstStatDeclareClass(
const Location& location, const Location& location,
const AstName& name, const AstName& name,
@ -1088,13 +1057,6 @@ bool AstTypeFunction::isCheckedFunction() const
return false; return false;
} }
bool AstTypeFunction::hasAttribute(AstAttr::Type attributeType) const
{
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
return hasAttributeInArray(attributes, attributeType);
}
AstTypeTypeof::AstTypeTypeof(const Location& location, AstExpr* expr) AstTypeTypeof::AstTypeTypeof(const Location& location, AstExpr* expr)
: AstType(ClassIndex(), location) : AstType(ClassIndex(), location)
, expr(expr) , expr(expr)

View file

@ -129,19 +129,12 @@ CstStatCompoundAssign::CstStatCompoundAssign(Position opPosition)
{ {
} }
CstStatFunction::CstStatFunction(Position functionKeywordPosition) CstStatLocalFunction::CstStatLocalFunction(Position functionKeywordPosition)
: CstNode(CstClassIndex()) : CstNode(CstClassIndex())
, functionKeywordPosition(functionKeywordPosition) , functionKeywordPosition(functionKeywordPosition)
{ {
} }
CstStatLocalFunction::CstStatLocalFunction(Position localKeywordPosition, Position functionKeywordPosition)
: CstNode(CstClassIndex())
, localKeywordPosition(localKeywordPosition)
, functionKeywordPosition(functionKeywordPosition)
{
}
CstGenericType::CstGenericType(std::optional<Position> defaultEqualsPosition) CstGenericType::CstGenericType(std::optional<Position> defaultEqualsPosition)
: CstNode(CstClassIndex()) : CstNode(CstClassIndex())
, defaultEqualsPosition(defaultEqualsPosition) , defaultEqualsPosition(defaultEqualsPosition)
@ -228,20 +221,6 @@ CstTypeTypeof::CstTypeTypeof(Position openPosition, Position closePosition)
{ {
} }
CstTypeUnion::CstTypeUnion(std::optional<Position> leadingPosition, AstArray<Position> separatorPositions)
: CstNode(CstClassIndex())
, leadingPosition(leadingPosition)
, separatorPositions(separatorPositions)
{
}
CstTypeIntersection::CstTypeIntersection(std::optional<Position> leadingPosition, AstArray<Position> separatorPositions)
: CstNode(CstClassIndex())
, leadingPosition(leadingPosition)
, separatorPositions(separatorPositions)
{
}
CstTypeSingletonString::CstTypeSingletonString(AstArray<char> sourceString, CstExprConstantString::QuoteStyle quoteStyle, unsigned int blockDepth) CstTypeSingletonString::CstTypeSingletonString(AstArray<char> sourceString, CstExprConstantString::QuoteStyle quoteStyle, unsigned int blockDepth)
: CstNode(CstClassIndex()) : CstNode(CstClassIndex())
, sourceString(sourceString) , sourceString(sourceString)

File diff suppressed because it is too large Load diff

View file

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

View file

@ -169,6 +169,7 @@ target_sources(Luau.CodeGen PRIVATE
# Luau.Analysis Sources # Luau.Analysis Sources
target_sources(Luau.Analysis PRIVATE target_sources(Luau.Analysis PRIVATE
Analysis/include/Luau/Anyification.h Analysis/include/Luau/Anyification.h
Analysis/include/Luau/AnyTypeSummary.h
Analysis/include/Luau/ApplyTypeFunction.h Analysis/include/Luau/ApplyTypeFunction.h
Analysis/include/Luau/AstJsonEncoder.h Analysis/include/Luau/AstJsonEncoder.h
Analysis/include/Luau/AstQuery.h Analysis/include/Luau/AstQuery.h
@ -247,6 +248,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/include/Luau/VisitType.h Analysis/include/Luau/VisitType.h
Analysis/src/Anyification.cpp Analysis/src/Anyification.cpp
Analysis/src/AnyTypeSummary.cpp
Analysis/src/ApplyTypeFunction.cpp Analysis/src/ApplyTypeFunction.cpp
Analysis/src/AstJsonEncoder.cpp Analysis/src/AstJsonEncoder.cpp
Analysis/src/AstQuery.cpp Analysis/src/AstQuery.cpp
@ -431,6 +433,7 @@ endif()
if(TARGET Luau.UnitTest) if(TARGET Luau.UnitTest)
# Luau.UnitTest Sources # Luau.UnitTest Sources
target_sources(Luau.UnitTest PRIVATE target_sources(Luau.UnitTest PRIVATE
tests/AnyTypeSummary.test.cpp
tests/AssemblyBuilderA64.test.cpp tests/AssemblyBuilderA64.test.cpp
tests/AssemblyBuilderX64.test.cpp tests/AssemblyBuilderX64.test.cpp
tests/AstJsonEncoder.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) 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... 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) \ #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)) \ if (luaC_needsGC(L)) \
{ \ { \
condhardmemtests(luaC_validate(L), 1); \ 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 = { getMutable<TableType>(vector3MetaType)->props = {
{"__add", {makeFunction(arena, nullopt, {vector3InstanceType, vector3InstanceType}, {vector3InstanceType})}}, {"__add", {makeFunction(arena, nullopt, {vector3InstanceType, vector3InstanceType}, {vector3InstanceType})}},
}; };
getMutable<TableType>(vector3MetaType)->state = TableState::Sealed;
globals.globalScope->exportedTypeBindings["Vector3"] = TypeFun{{}, vector3InstanceType}; 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; using namespace Luau;
LUAU_FASTFLAG(LuauAstTypeGroup3) LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
struct JsonEncoderFixture struct JsonEncoderFixture
{ {
@ -441,9 +440,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstAttr")
AstStat* expr = expectParseStatement("@checked function a(b) return c end"); AstStat* expr = expectParseStatement("@checked function a(b) return c end");
std::string_view expected = std::string_view expected =
FFlag::LuauFixFunctionWithAttributesStartLocation 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,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"}})";
CHECK(toJson(expr) == expected); CHECK(toJson(expr) == expected);
} }

View file

@ -23,7 +23,6 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauExposeRequireByStringAutocomplete) LUAU_FASTFLAG(LuauExposeRequireByStringAutocomplete)
LUAU_FASTFLAG(LuauAutocompleteUnionCopyPreviousSeen) LUAU_FASTFLAG(LuauAutocompleteUnionCopyPreviousSeen)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete)
using namespace Luau; using namespace Luau;
@ -4497,27 +4496,4 @@ this@2
CHECK_EQ(ac.entryMap.count("thisShouldBeThere"), 0); 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(); TEST_SUITE_END();

View file

@ -372,7 +372,7 @@ LintResult Fixture::lint(const std::string& source, const std::optional<LintOpti
fileResolver.source[mm] = std::move(source); fileResolver.source[mm] = std::move(source);
frontend.markDirty(mm); frontend.markDirty(mm);
return lintModule(mm, lintOptions); return lintModule(mm);
} }
LintResult Fixture::lintModule(const ModuleName& moduleName, const std::optional<LintOptions>& lintOptions) LintResult Fixture::lintModule(const ModuleName& moduleName, const std::optional<LintOptions>& lintOptions)

File diff suppressed because it is too large Load diff

View file

@ -17,6 +17,7 @@ LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauSelectivelyRetainDFGArena) LUAU_FASTFLAG(LuauSelectivelyRetainDFGArena)
LUAU_FASTFLAG(LuauModuleHoldsAstRoot)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
namespace namespace
@ -1554,6 +1555,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "check_module_references_allocator")
TEST_CASE_FIXTURE(FrontendFixture, "check_module_references_correct_ast_root") TEST_CASE_FIXTURE(FrontendFixture, "check_module_references_correct_ast_root")
{ {
ScopedFastFlag sff{FFlag::LuauModuleHoldsAstRoot, true};
fileResolver.source["game/workspace/MyScript"] = R"( fileResolver.source["game/workspace/MyScript"] = R"(
print("Hello World") print("Hello World")
)"; )";
@ -1791,96 +1793,4 @@ TEST_CASE_FIXTURE(FrontendFixture, "test_invalid_dependency_tracking_per_module_
CHECK(frontend.allModuleDependenciesValid("game/Gui/Modules/A", opts.forAutocomplete)); 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(); TEST_SUITE_END();

View file

@ -15,9 +15,6 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauForbidInternalTypes)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall)
TEST_SUITE_BEGIN("Generalization"); 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(); TEST_SUITE_END();

View file

@ -9,7 +9,6 @@
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LintRedundantNativeAttribute); LUAU_FASTFLAG(LintRedundantNativeAttribute);
LUAU_FASTFLAG(LuauDeprecatedAttribute);
using namespace Luau; using namespace Luau;
@ -1601,326 +1600,6 @@ setfenv(h :: any, {})
CHECK_EQ(result.warnings[3].location.begin.line + 1, 11); 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") TEST_CASE_FIXTURE(BuiltinsFixture, "TableOperations")
{ {
LintResult result = lint(R"( LintResult result = lint(R"(

View file

@ -10,16 +10,9 @@
#include "Luau/Normalize.h" #include "Luau/Normalize.h"
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAG(LuauNormalizeNegatedErrorToAnError)
LUAU_FASTFLAG(LuauNormalizeIntersectErrorToAnError)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTINT(LuauNormalizeIntersectionLimit) LUAU_FASTFLAG(LuauNormalizeNegationFix)
LUAU_FASTINT(LuauNormalizeUnionLimit)
LUAU_FASTFLAG(LuauNormalizeLimitFunctionSet)
LUAU_FASTFLAG(LuauSubtypingStopAtNormFail)
LUAU_FASTFLAG(LuauNormalizationCatchMetatableCycles)
using namespace Luau; using namespace Luau;
namespace namespace
@ -600,25 +593,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") TEST_CASE_FIXTURE(NormalizeFixture, "union_of_union")
{ {
CHECK(R"("alpha" | "beta" | "gamma")" == toString(normal(R"( CHECK(R"("alpha" | "beta" | "gamma")" == toString(normal(R"(
@ -1058,6 +1032,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "free_type_and_not_truthy")
{ {
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {
{FFlag::LuauSolverV2, true}, // Only because it affects the stringification of free types {FFlag::LuauSolverV2, true}, // Only because it affects the stringification of free types
{FFlag::LuauNormalizeNegationFix, true},
}; };
TypeId freeTy = arena.freshType(builtinTypes, &globalScope); TypeId freeTy = arena.freshType(builtinTypes, &globalScope);
@ -1073,19 +1048,6 @@ TEST_CASE_FIXTURE(NormalizeFixture, "free_type_and_not_truthy")
CHECK("'a & (false?)" == toString(result)); 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") TEST_CASE_FIXTURE(BuiltinsFixture, "normalizer_should_be_able_to_detect_cyclic_tables_and_not_stack_overflow")
{ {
if (!FFlag::LuauSolverV2) if (!FFlag::LuauSolverV2)
@ -1191,40 +1153,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(); TEST_SUITE_END();

View file

@ -18,17 +18,12 @@ LUAU_FASTINT(LuauParseErrorLimit)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAllowComplexTypesInGenericParams) LUAU_FASTFLAG(LuauAllowComplexTypesInGenericParams)
LUAU_FASTFLAG(LuauErrorRecoveryForTableTypes) LUAU_FASTFLAG(LuauErrorRecoveryForTableTypes)
LUAU_FASTFLAG(LuauFixFunctionNameStartPosition)
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon) LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAG(LuauAstTypeGroup3) LUAU_FASTFLAG(LuauAstTypeGroup3)
LUAU_FASTFLAG(LuauFixDoBlockEndLocation) LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
LUAU_FASTFLAG(LuauParseOptionalAsNode2) LUAU_FASTFLAG(LuauParseOptionalAsNode)
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 namespace
{ {
@ -2546,40 +2541,6 @@ TEST_CASE_FIXTURE(Fixture, "do_block_end_location_is_after_end_token")
CHECK_EQ(block->location, Location{{1, 8}, {3, 11}}); 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_END();
TEST_SUITE_BEGIN("ParseErrorRecovery"); TEST_SUITE_BEGIN("ParseErrorRecovery");
@ -3858,7 +3819,7 @@ TEST_CASE_FIXTURE(Fixture, "grouped_function_type")
} }
else else
CHECK(unionTy->types.data[0]->is<AstTypeFunction>()); // () -> () CHECK(unionTy->types.data[0]->is<AstTypeFunction>()); // () -> ()
if (FFlag::LuauParseOptionalAsNode2) if (FFlag::LuauParseOptionalAsNode)
CHECK(unionTy->types.data[1]->is<AstTypeOptional>()); // ? CHECK(unionTy->types.data[1]->is<AstTypeOptional>()); // ?
else else
CHECK(unionTy->types.data[1]->is<AstTypeReference>()); // nil CHECK(unionTy->types.data[1]->is<AstTypeReference>()); // nil
@ -3919,6 +3880,7 @@ TEST_CASE_FIXTURE(Fixture, "recover_from_bad_table_type")
TEST_CASE_FIXTURE(Fixture, "function_name_has_correct_start_location") TEST_CASE_FIXTURE(Fixture, "function_name_has_correct_start_location")
{ {
ScopedFastFlag _{FFlag::LuauFixFunctionNameStartPosition, true};
AstStatBlock* block = parse(R"( AstStatBlock* block = parse(R"(
function simple() function simple()
end end
@ -3967,8 +3929,6 @@ TEST_CASE_FIXTURE(Fixture, "stat_end_includes_semicolon_position")
TEST_CASE_FIXTURE(Fixture, "parsing_type_suffix_for_return_type_with_variadic") TEST_CASE_FIXTURE(Fixture, "parsing_type_suffix_for_return_type_with_variadic")
{ {
ScopedFastFlag sff{DFFlag::DebugLuauReportReturnTypeVariadicWithTypeSuffix, true};
ParseResult result = tryParse(R"( ParseResult result = tryParse(R"(
function foo(): (string, ...number) | boolean function foo(): (string, ...number) | boolean
end end
@ -3976,13 +3936,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 // TODO(CLI-140667): this should produce a ParseError in future when we fix the invalid syntax
CHECK(result.errors.size() == 0); 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(); TEST_SUITE_END();

View file

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

View file

@ -12,11 +12,9 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauStoreCSTData2) LUAU_FASTFLAG(LuauStoreCSTData)
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon) LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
LUAU_FASTFLAG(LuauAstTypeGroup3); LUAU_FASTFLAG(LuauAstTypeGroup3);
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
LUAU_FASTFLAG(LuauParseOptionalAsNode2)
TEST_SUITE_BEGIN("TranspilerTests"); TEST_SUITE_BEGIN("TranspilerTests");
@ -50,7 +48,7 @@ TEST_CASE("string_literals_containing_utf8")
TEST_CASE("if_stmt_spaces_around_tokens") TEST_CASE("if_stmt_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string one = R"( if This then Once() end)"; const std::string one = R"( if This then Once() end)";
CHECK_EQ(one, transpile(one).code); CHECK_EQ(one, transpile(one).code);
@ -99,7 +97,7 @@ TEST_CASE("elseif_chains_indent_sensibly")
TEST_CASE("strips_type_annotations") TEST_CASE("strips_type_annotations")
{ {
const std::string code = R"( local s: string= 'hello there' )"; const std::string code = R"( local s: string= 'hello there' )";
if (FFlag::LuauStoreCSTData2) if (FFlag::LuauStoreCSTData)
{ {
const std::string expected = R"( local s = 'hello there' )"; const std::string expected = R"( local s = 'hello there' )";
CHECK_EQ(expected, transpile(code).code); CHECK_EQ(expected, transpile(code).code);
@ -114,7 +112,7 @@ TEST_CASE("strips_type_annotations")
TEST_CASE("strips_type_assertion_expressions") TEST_CASE("strips_type_assertion_expressions")
{ {
const std::string code = R"( local s= some_function() :: any+ something_else() :: number )"; const std::string code = R"( local s= some_function() :: any+ something_else() :: number )";
if (FFlag::LuauStoreCSTData2) if (FFlag::LuauStoreCSTData)
{ {
const std::string expected = R"( local s= some_function() + something_else() )"; const std::string expected = R"( local s= some_function() + something_else() )";
CHECK_EQ(expected, transpile(code).code); CHECK_EQ(expected, transpile(code).code);
@ -150,7 +148,7 @@ TEST_CASE("for_loop")
TEST_CASE("for_loop_spaces_around_tokens") TEST_CASE("for_loop_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string one = R"( for index = 1, 10 do call(index) end )"; const std::string one = R"( for index = 1, 10 do call(index) end )";
CHECK_EQ(one, transpile(one).code); CHECK_EQ(one, transpile(one).code);
@ -175,7 +173,7 @@ TEST_CASE("for_in_loop")
TEST_CASE("for_in_loop_spaces_around_tokens") TEST_CASE("for_in_loop_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string one = R"( for k, v in ipairs(x) do end )"; const std::string one = R"( for k, v in ipairs(x) do end )";
CHECK_EQ(one, transpile(one).code); CHECK_EQ(one, transpile(one).code);
@ -200,7 +198,7 @@ TEST_CASE("while_loop")
TEST_CASE("while_loop_spaces_around_tokens") TEST_CASE("while_loop_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string one = R"( while f(x) do print() end )"; const std::string one = R"( while f(x) do print() end )";
CHECK_EQ(one, transpile(one).code); CHECK_EQ(one, transpile(one).code);
@ -222,7 +220,7 @@ TEST_CASE("repeat_until_loop")
TEST_CASE("repeat_until_loop_condition_on_new_line") TEST_CASE("repeat_until_loop_condition_on_new_line")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( const std::string code = R"(
repeat repeat
print() print()
@ -254,7 +252,7 @@ TEST_CASE("local_assignment")
TEST_CASE("local_assignment_spaces_around_tokens") TEST_CASE("local_assignment_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string one = R"( local x = 1 )"; const std::string one = R"( local x = 1 )";
CHECK_EQ(one, transpile(one).code); CHECK_EQ(one, transpile(one).code);
@ -288,7 +286,7 @@ TEST_CASE("local_function")
TEST_CASE("local_function_spaces_around_tokens") TEST_CASE("local_function_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string one = R"( local function p(o, m, ...) end )"; const std::string one = R"( local function p(o, m, ...) end )";
CHECK_EQ(one, transpile(one).code); CHECK_EQ(one, transpile(one).code);
@ -307,7 +305,7 @@ TEST_CASE("function")
TEST_CASE("function_spaces_around_tokens") TEST_CASE("function_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string two = R"( function p(o, m, ...) end )"; const std::string two = R"( function p(o, m, ...) end )";
CHECK_EQ(two, transpile(two).code); CHECK_EQ(two, transpile(two).code);
@ -335,7 +333,7 @@ TEST_CASE("function_spaces_around_tokens")
TEST_CASE("function_with_types_spaces_around_tokens") TEST_CASE("function_with_types_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )"; std::string code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -396,7 +394,7 @@ TEST_CASE("function_with_types_spaces_around_tokens")
TEST_CASE("returns_spaces_around_tokens") TEST_CASE("returns_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string one = R"( return 1 )"; const std::string one = R"( return 1 )";
CHECK_EQ(one, transpile(one).code); CHECK_EQ(one, transpile(one).code);
@ -409,7 +407,7 @@ TEST_CASE("returns_spaces_around_tokens")
TEST_CASE_FIXTURE(Fixture, "type_alias_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "type_alias_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = R"( type Foo = string )"; std::string code = R"( type Foo = string )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -458,7 +456,7 @@ TEST_CASE_FIXTURE(Fixture, "type_alias_spaces_around_tokens")
TEST_CASE_FIXTURE(Fixture, "type_alias_with_defaults_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "type_alias_with_defaults_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = R"( type Foo<X = string, Z... = ...any> = string )"; std::string code = R"( type Foo<X = string, Z... = ...any> = string )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -519,7 +517,7 @@ TEST_CASE("table_literal_closing_brace_at_correct_position")
TEST_CASE("table_literal_with_semicolon_separators") TEST_CASE("table_literal_with_semicolon_separators")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( const std::string code = R"(
local t = { x = 1; y = 2 } local t = { x = 1; y = 2 }
)"; )";
@ -529,7 +527,7 @@ TEST_CASE("table_literal_with_semicolon_separators")
TEST_CASE("table_literal_with_trailing_separators") TEST_CASE("table_literal_with_trailing_separators")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( const std::string code = R"(
local t = { x = 1, y = 2, } local t = { x = 1, y = 2, }
)"; )";
@ -539,7 +537,7 @@ TEST_CASE("table_literal_with_trailing_separators")
TEST_CASE("table_literal_with_spaces_around_separator") TEST_CASE("table_literal_with_spaces_around_separator")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( const std::string code = R"(
local t = { x = 1 , y = 2 } local t = { x = 1 , y = 2 }
)"; )";
@ -549,7 +547,7 @@ TEST_CASE("table_literal_with_spaces_around_separator")
TEST_CASE("table_literal_with_spaces_around_equals") TEST_CASE("table_literal_with_spaces_around_equals")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( const std::string code = R"(
local t = { x = 1 } local t = { x = 1 }
)"; )";
@ -559,7 +557,7 @@ TEST_CASE("table_literal_with_spaces_around_equals")
TEST_CASE("table_literal_multiline_with_indexers") TEST_CASE("table_literal_multiline_with_indexers")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( const std::string code = R"(
local t = { local t = {
["my first value"] = "x"; ["my first value"] = "x";
@ -587,7 +585,7 @@ TEST_CASE("spaces_between_keywords_even_if_it_pushes_the_line_estimation_off")
// Luau::Parser doesn't exactly preserve the string representation of numbers in Lua, so we can find ourselves // Luau::Parser doesn't exactly preserve the string representation of numbers in Lua, so we can find ourselves
// falling out of sync with the original code. We need to push keywords out so that there's at least one space between them. // falling out of sync with the original code. We need to push keywords out so that there's at least one space between them.
const std::string code = R"( if math.abs(raySlope) < .01 then return 0 end )"; const std::string code = R"( if math.abs(raySlope) < .01 then return 0 end )";
if (FFlag::LuauStoreCSTData2) if (FFlag::LuauStoreCSTData)
{ {
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
@ -607,7 +605,7 @@ TEST_CASE("numbers")
TEST_CASE("infinity") TEST_CASE("infinity")
{ {
const std::string code = R"( local a = 1e500 local b = 1e400 )"; const std::string code = R"( local a = 1e500 local b = 1e400 )";
if (FFlag::LuauStoreCSTData2) if (FFlag::LuauStoreCSTData)
{ {
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
@ -620,21 +618,21 @@ TEST_CASE("infinity")
TEST_CASE("numbers_with_separators") TEST_CASE("numbers_with_separators")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( local a = 123_456_789 )"; const std::string code = R"( local a = 123_456_789 )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("hexadecimal_numbers") TEST_CASE("hexadecimal_numbers")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( local a = 0xFFFF )"; const std::string code = R"( local a = 0xFFFF )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("binary_numbers") TEST_CASE("binary_numbers")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( local a = 0b0101 )"; const std::string code = R"( local a = 0b0101 )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
@ -647,28 +645,28 @@ TEST_CASE("single_quoted_strings")
TEST_CASE("double_quoted_strings") TEST_CASE("double_quoted_strings")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( local a = "hello world" )"; const std::string code = R"( local a = "hello world" )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("simple_interp_string") TEST_CASE("simple_interp_string")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( local a = `hello world` )"; const std::string code = R"( local a = `hello world` )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("raw_strings") TEST_CASE("raw_strings")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( local a = [[ hello world ]] )"; const std::string code = R"( local a = [[ hello world ]] )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("raw_strings_with_blocks") TEST_CASE("raw_strings_with_blocks")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( local a = [==[ hello world ]==] )"; const std::string code = R"( local a = [==[ hello world ]==] )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
@ -687,7 +685,7 @@ TEST_CASE("escaped_strings_2")
TEST_CASE("escaped_strings_newline") TEST_CASE("escaped_strings_newline")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( const std::string code = R"(
print("foo \ print("foo \
bar") bar")
@ -697,14 +695,14 @@ TEST_CASE("escaped_strings_newline")
TEST_CASE("escaped_strings_raw") TEST_CASE("escaped_strings_raw")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( local x = [=[\v<((do|load)file|require)\s*\(?['"]\zs[^'"]+\ze['"]]=] )"; const std::string code = R"( local x = [=[\v<((do|load)file|require)\s*\(?['"]\zs[^'"]+\ze['"]]=] )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("position_correctly_updated_when_writing_multiline_string") TEST_CASE("position_correctly_updated_when_writing_multiline_string")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( const std::string code = R"(
call([[ call([[
testing testing
@ -750,56 +748,56 @@ TEST_CASE("function_call_parentheses_multiple_args_no_space")
TEST_CASE("function_call_parentheses_multiple_args_space_before_commas") TEST_CASE("function_call_parentheses_multiple_args_space_before_commas")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( call(arg1 ,arg3 ,arg3) )"; const std::string code = R"( call(arg1 ,arg3 ,arg3) )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("function_call_spaces_before_parentheses") TEST_CASE("function_call_spaces_before_parentheses")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( call () )"; const std::string code = R"( call () )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("function_call_spaces_within_parentheses") TEST_CASE("function_call_spaces_within_parentheses")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( call( ) )"; const std::string code = R"( call( ) )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("function_call_string_double_quotes") TEST_CASE("function_call_string_double_quotes")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( call "string" )"; const std::string code = R"( call "string" )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("function_call_string_single_quotes") TEST_CASE("function_call_string_single_quotes")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( call 'string' )"; const std::string code = R"( call 'string' )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("function_call_string_no_space") TEST_CASE("function_call_string_no_space")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( call'string' )"; const std::string code = R"( call'string' )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("function_call_table_literal") TEST_CASE("function_call_table_literal")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( call { x = 1 } )"; const std::string code = R"( call { x = 1 } )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
TEST_CASE("function_call_table_literal_no_space") TEST_CASE("function_call_table_literal_no_space")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( call{x=1} )"; const std::string code = R"( call{x=1} )";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
@ -844,7 +842,7 @@ TEST_CASE("emit_a_do_block_in_cases_of_potentially_ambiguous_syntax")
TEST_CASE_FIXTURE(Fixture, "parentheses_multiline") TEST_CASE_FIXTURE(Fixture, "parentheses_multiline")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = R"( std::string code = R"(
local test = ( local test = (
x x
@ -857,7 +855,7 @@ local test = (
TEST_CASE_FIXTURE(Fixture, "stmt_semicolon") TEST_CASE_FIXTURE(Fixture, "stmt_semicolon")
{ {
ScopedFastFlag flags[] = { ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreCSTData, true},
{FFlag::LuauExtendStatEndPosWithSemicolon, true}, {FFlag::LuauExtendStatEndPosWithSemicolon, true},
}; };
std::string code = R"( local test = 1; )"; std::string code = R"( local test = 1; )";
@ -880,7 +878,7 @@ TEST_CASE_FIXTURE(Fixture, "do_block_ending_with_semicolon")
TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon") TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon")
{ {
ScopedFastFlag flags[] = { ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreCSTData, true},
{FFlag::LuauExtendStatEndPosWithSemicolon, true}, {FFlag::LuauExtendStatEndPosWithSemicolon, true},
}; };
std::string code = R"( std::string code = R"(
@ -894,7 +892,7 @@ TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon")
TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon_2") TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon_2")
{ {
ScopedFastFlag flags[] = { ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreCSTData, true},
{FFlag::LuauExtendStatEndPosWithSemicolon, true}, {FFlag::LuauExtendStatEndPosWithSemicolon, true},
}; };
std::string code = R"( std::string code = R"(
@ -906,7 +904,7 @@ TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon_2")
TEST_CASE_FIXTURE(Fixture, "for_loop_stmt_semicolon") TEST_CASE_FIXTURE(Fixture, "for_loop_stmt_semicolon")
{ {
ScopedFastFlag flags[] = { ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreCSTData, true},
{FFlag::LuauExtendStatEndPosWithSemicolon, true}, {FFlag::LuauExtendStatEndPosWithSemicolon, true},
}; };
std::string code = R"( std::string code = R"(
@ -919,7 +917,7 @@ TEST_CASE_FIXTURE(Fixture, "for_loop_stmt_semicolon")
TEST_CASE_FIXTURE(Fixture, "while_do_semicolon") TEST_CASE_FIXTURE(Fixture, "while_do_semicolon")
{ {
ScopedFastFlag flags[] = { ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreCSTData, true},
{FFlag::LuauExtendStatEndPosWithSemicolon, true}, {FFlag::LuauExtendStatEndPosWithSemicolon, true},
}; };
std::string code = R"( std::string code = R"(
@ -932,7 +930,7 @@ TEST_CASE_FIXTURE(Fixture, "while_do_semicolon")
TEST_CASE_FIXTURE(Fixture, "function_definition_semicolon") TEST_CASE_FIXTURE(Fixture, "function_definition_semicolon")
{ {
ScopedFastFlag flags[] = { ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreCSTData, true},
{FFlag::LuauExtendStatEndPosWithSemicolon, true}, {FFlag::LuauExtendStatEndPosWithSemicolon, true},
}; };
std::string code = R"( std::string code = R"(
@ -1013,7 +1011,7 @@ TEST_CASE("always_emit_a_space_after_local_keyword")
{ {
std::string code = "do local aZZZZ = Workspace.P1.Shape local bZZZZ = Enum.PartType.Cylinder end"; std::string code = "do local aZZZZ = Workspace.P1.Shape local bZZZZ = Enum.PartType.Cylinder end";
if (FFlag::LuauStoreCSTData2) if (FFlag::LuauStoreCSTData)
{ {
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
} }
@ -1058,7 +1056,7 @@ TEST_CASE_FIXTURE(Fixture, "type_lists_should_be_emitted_correctly")
end end
)"; )";
std::string expected = FFlag::LuauStoreCSTData2 ? R"( std::string expected = FFlag::LuauStoreCSTData ? R"(
local a:(a:string,b:number,...string)->(string,...number)=function(a:string,b:number,...:string): (string,...number) local a:(a:string,b:number,...string)->(string,...number)=function(a:string,b:number,...:string): (string,...number)
end end
@ -1114,7 +1112,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_assertion")
TEST_CASE_FIXTURE(Fixture, "type_assertion_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "type_assertion_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = "local a = 5 :: number"; std::string code = "local a = 5 :: number";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1131,7 +1129,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else")
TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions") TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = "local a = if 1 then 2 elseif 3 then 4 else 5"; std::string code = "local a = if 1 then 2 elseif 3 then 4 else 5";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
@ -1139,7 +1137,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions")
TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions_2") TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions_2")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = R"( std::string code = R"(
local x = if yes local x = if yes
then nil then nil
@ -1155,7 +1153,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions_2")
TEST_CASE_FIXTURE(Fixture, "if_then_else_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "if_then_else_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = "local a = if 1 then 2 else 3"; std::string code = "local a = if 1 then 2 else 3";
CHECK_EQ(code, transpile(code).code); CHECK_EQ(code, transpile(code).code);
@ -1192,7 +1190,7 @@ TEST_CASE_FIXTURE(Fixture, "if_then_else_spaces_around_tokens")
TEST_CASE_FIXTURE(Fixture, "if_then_else_spaces_between_else_if") TEST_CASE_FIXTURE(Fixture, "if_then_else_spaces_between_else_if")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = R"( std::string code = R"(
return return
if a then "was a" else if a then "was a" else
@ -1220,7 +1218,7 @@ local a: Import.Type
TEST_CASE_FIXTURE(Fixture, "transpile_type_reference_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "transpile_type_reference_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = R"( local _: Foo.Type )"; std::string code = R"( local _: Foo.Type )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1259,7 +1257,7 @@ local b: Packed<(number, string)>
TEST_CASE_FIXTURE(Fixture, "type_packs_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "type_packs_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = R"( type _ = Packed< T...> )"; std::string code = R"( type _ = Packed< T...> )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1318,9 +1316,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_union_type_nested_3")
{ {
std::string code = "local a: nil | (string & number)"; std::string code = "local a: nil | (string & number)";
if (FFlag::LuauStoreCSTData2) if (FFlag::LuauAstTypeGroup3)
CHECK_EQ(code, transpile(code, {}, true).code);
else if (FFlag::LuauAstTypeGroup3)
CHECK_EQ("local a: (string & number)?", transpile(code, {}, true).code); CHECK_EQ("local a: (string & number)?", transpile(code, {}, true).code);
else else
CHECK_EQ("local a: ( string & number)?", transpile(code, {}, true).code); CHECK_EQ("local a: ( string & number)?", transpile(code, {}, true).code);
@ -1340,117 +1336,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_intersection_type_nested_2")
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
} }
TEST_CASE_FIXTURE(Fixture, "transpile_leading_union_pipe")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType, true},
{FFlag::LuauParseOptionalAsNode2, true},
};
std::string code = "local a: | string | number";
CHECK_EQ(code, transpile(code, {}, true).code);
code = "local a: | string";
CHECK_EQ(code, transpile(code, {}, true).code);
}
TEST_CASE_FIXTURE(Fixture, "transpile_union_spaces_around_tokens")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType, true},
{FFlag::LuauParseOptionalAsNode2, true},
};
std::string code = "local a: string | number";
CHECK_EQ(code, transpile(code, {}, true).code);
code = "local a: string | number";
CHECK_EQ(code, transpile(code, {}, true).code);
}
TEST_CASE_FIXTURE(Fixture, "transpile_leading_intersection_ampersand")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType, true},
};
std::string code = "local a: & string & number";
CHECK_EQ(code, transpile(code, {}, true).code);
code = "local a: & string";
CHECK_EQ(code, transpile(code, {}, true).code);
}
TEST_CASE_FIXTURE(Fixture, "transpile_intersection_spaces_around_tokens")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType, true},
};
std::string code = "local a: string & number";
CHECK_EQ(code, transpile(code, {}, true).code);
code = "local a: string & number";
CHECK_EQ(code, transpile(code, {}, true).code);
}
TEST_CASE_FIXTURE(Fixture, "transpile_mixed_union_intersection")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauAstTypeGroup3, true},
{FFlag::LuauParseOptionalAsNode2, true},
};
std::string code = "local a: string | (Foo & Bar)";
CHECK_EQ(code, transpile(code, {}, true).code);
code = "local a: string | (Foo & Bar)";
CHECK_EQ(code, transpile(code, {}, true).code);
code = "local a: string | ( Foo & Bar)";
CHECK_EQ(code, transpile(code, {}, true).code);
code = "local a: string | (Foo & Bar )";
CHECK_EQ(code, transpile(code, {}, true).code);
code = "local a: string & (Foo | Bar)";
CHECK_EQ(code, transpile(code, {}, true).code);
code = "local a: string & ( Foo | Bar)";
CHECK_EQ(code, transpile(code, {}, true).code);
code = "local a: string & (Foo | Bar )";
CHECK_EQ(code, transpile(code, {}, true).code);
}
TEST_CASE_FIXTURE(Fixture, "transpile_preserve_union_optional_style")
{
ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true},
{FFlag::LuauParseOptionalAsNode2, true},
};
std::string code = "local a: string | nil";
CHECK_EQ(code, transpile(code, {}, true).code);
code = "local a: string?";
CHECK_EQ(code, transpile(code, {}, true).code);
code = "local a: string???";
CHECK_EQ(code, transpile(code, {}, true).code);
code = "local a: string? | nil";
CHECK_EQ(code, transpile(code, {}, true).code);
code = "local a: string | nil | number";
CHECK_EQ(code, transpile(code, {}, true).code);
code = "local a: string | nil | number?";
CHECK_EQ(code, transpile(code, {}, true).code);
code = "local a: string? | number?";
CHECK_EQ(code, transpile(code, {}, true).code);
}
TEST_CASE_FIXTURE(Fixture, "transpile_varargs") TEST_CASE_FIXTURE(Fixture, "transpile_varargs")
{ {
std::string code = "local function f(...) return ... end"; std::string code = "local function f(...) return ... end";
@ -1460,7 +1345,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_varargs")
TEST_CASE_FIXTURE(Fixture, "index_name_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "index_name_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string one = "local _ = a.name"; std::string one = "local _ = a.name";
CHECK_EQ(one, transpile(one, {}, true).code); CHECK_EQ(one, transpile(one, {}, true).code);
@ -1473,7 +1358,7 @@ TEST_CASE_FIXTURE(Fixture, "index_name_spaces_around_tokens")
TEST_CASE_FIXTURE(Fixture, "index_name_ends_with_digit") TEST_CASE_FIXTURE(Fixture, "index_name_ends_with_digit")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = "sparkles.Color = Color3.new()"; std::string code = "sparkles.Color = Color3.new()";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
} }
@ -1487,7 +1372,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_index_expr")
TEST_CASE_FIXTURE(Fixture, "index_expr_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "index_expr_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string one = "local _ = a[2]"; std::string one = "local _ = a[2]";
CHECK_EQ(one, transpile(one, {}, true).code); CHECK_EQ(one, transpile(one, {}, true).code);
@ -1531,7 +1416,7 @@ local _ = # e
TEST_CASE_FIXTURE(Fixture, "binary_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "binary_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = R"( std::string code = R"(
local _ = 1+1 local _ = 1+1
local _ = 1 +1 local _ = 1 +1
@ -1573,7 +1458,7 @@ a ..= ' - result'
TEST_CASE_FIXTURE(Fixture, "compound_assignment_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "compound_assignment_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string one = R"( a += 1 )"; std::string one = R"( a += 1 )";
CHECK_EQ(one, transpile(one, {}, true).code); CHECK_EQ(one, transpile(one, {}, true).code);
@ -1590,7 +1475,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_assign_multiple")
TEST_CASE_FIXTURE(Fixture, "transpile_assign_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "transpile_assign_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string one = "a = 1"; std::string one = "a = 1";
CHECK_EQ(one, transpile(one).code); CHECK_EQ(one, transpile(one).code);
@ -1627,10 +1512,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_union_reverse")
{ {
std::string code = "local a: nil | number"; std::string code = "local a: nil | number";
if (FFlag::LuauStoreCSTData2) CHECK_EQ("local a: number?", transpile(code, {}, true).code);
CHECK_EQ(code, transpile(code, {}, true).code);
else
CHECK_EQ("local a: number?", transpile(code, {}, true).code);
} }
TEST_CASE_FIXTURE(Fixture, "transpile_for_in_multiple") TEST_CASE_FIXTURE(Fixture, "transpile_for_in_multiple")
@ -1746,7 +1628,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_for_in_multiple_types")
TEST_CASE_FIXTURE(Fixture, "transpile_string_interp") TEST_CASE_FIXTURE(Fixture, "transpile_string_interp")
{ {
ScopedFastFlag fflags[] = { ScopedFastFlag fflags[] = {
{FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreCSTData, true},
}; };
std::string code = R"( local _ = `hello {name}` )"; std::string code = R"( local _ = `hello {name}` )";
@ -1756,7 +1638,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp")
TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline") TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline")
{ {
ScopedFastFlag fflags[] = { ScopedFastFlag fflags[] = {
{FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreCSTData, true},
}; };
std::string code = R"( local _ = `hello { std::string code = R"( local _ = `hello {
name name
@ -1768,7 +1650,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline")
TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_on_new_line") TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_on_new_line")
{ {
ScopedFastFlag fflags[] = { ScopedFastFlag fflags[] = {
{FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreCSTData, true},
}; };
std::string code = R"( std::string code = R"(
error( error(
@ -1781,7 +1663,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_on_new_line")
TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline_escape") TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline_escape")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = R"( local _ = `hello \ std::string code = R"( local _ = `hello \
world!` )"; world!` )";
@ -1791,7 +1673,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline_escape")
TEST_CASE_FIXTURE(Fixture, "transpile_string_literal_escape") TEST_CASE_FIXTURE(Fixture, "transpile_string_literal_escape")
{ {
ScopedFastFlag fflags[] = { ScopedFastFlag fflags[] = {
{FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreCSTData, true},
}; };
std::string code = R"( local _ = ` bracket = \{, backtick = \` = {'ok'} ` )"; std::string code = R"( local _ = ` bracket = \{, backtick = \` = {'ok'} ` )";
@ -1807,7 +1689,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_functions")
TEST_CASE_FIXTURE(Fixture, "transpile_type_functions_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "transpile_type_functions_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = R"( type function foo() end )"; std::string code = R"( type function foo() end )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1823,7 +1705,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_functions_spaces_around_tokens")
TEST_CASE_FIXTURE(Fixture, "transpile_typeof_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "transpile_typeof_spaces_around_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = R"( type X = typeof(x) )"; std::string code = R"( type X = typeof(x) )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1848,14 +1730,14 @@ TEST_CASE("transpile_single_quoted_string_types")
TEST_CASE("transpile_double_quoted_string_types") TEST_CASE("transpile_double_quoted_string_types")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( type a = "hello world" )"; const std::string code = R"( type a = "hello world" )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
} }
TEST_CASE("transpile_raw_string_types") TEST_CASE("transpile_raw_string_types")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = R"( type a = [[ hello world ]] )"; std::string code = R"( type a = [[ hello world ]] )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1865,14 +1747,14 @@ TEST_CASE("transpile_raw_string_types")
TEST_CASE("transpile_escaped_string_types") TEST_CASE("transpile_escaped_string_types")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( type a = "\\b\\t\\n\\\\" )"; const std::string code = R"( type a = "\\b\\t\\n\\\\" )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
} }
TEST_CASE("transpile_type_table_semicolon_separators") TEST_CASE("transpile_type_table_semicolon_separators")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
const std::string code = R"( const std::string code = R"(
type Foo = { type Foo = {
bar: number; bar: number;
@ -1884,7 +1766,7 @@ TEST_CASE("transpile_type_table_semicolon_separators")
TEST_CASE("transpile_type_table_access_modifiers") TEST_CASE("transpile_type_table_access_modifiers")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = R"( std::string code = R"(
type Foo = { type Foo = {
read bar: number, read bar: number,
@ -1905,7 +1787,7 @@ TEST_CASE("transpile_type_table_access_modifiers")
TEST_CASE("transpile_type_table_spaces_between_tokens") TEST_CASE("transpile_type_table_spaces_between_tokens")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = R"( type Foo = { bar: number, } )"; std::string code = R"( type Foo = { bar: number, } )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);
@ -1948,7 +1830,7 @@ TEST_CASE("transpile_type_table_spaces_between_tokens")
TEST_CASE("transpile_type_table_preserve_original_indexer_style") TEST_CASE("transpile_type_table_preserve_original_indexer_style")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = R"( std::string code = R"(
type Foo = { type Foo = {
[number]: string [number]: string
@ -1964,7 +1846,7 @@ TEST_CASE("transpile_type_table_preserve_original_indexer_style")
TEST_CASE("transpile_type_table_preserve_indexer_location") TEST_CASE("transpile_type_table_preserve_indexer_location")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = R"( std::string code = R"(
type Foo = { type Foo = {
[number]: string, [number]: string,
@ -1993,7 +1875,7 @@ TEST_CASE("transpile_type_table_preserve_indexer_location")
TEST_CASE("transpile_type_table_preserve_property_definition_style") TEST_CASE("transpile_type_table_preserve_property_definition_style")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = R"( std::string code = R"(
type Foo = { type Foo = {
["$$typeof1"]: string, ["$$typeof1"]: string,
@ -2006,7 +1888,7 @@ TEST_CASE("transpile_type_table_preserve_property_definition_style")
TEST_CASE("transpile_types_preserve_parentheses_style") TEST_CASE("transpile_types_preserve_parentheses_style")
{ {
ScopedFastFlag flags[] = { ScopedFastFlag flags[] = {
{FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreCSTData, true},
{FFlag::LuauAstTypeGroup3, true}, {FFlag::LuauAstTypeGroup3, true},
}; };
@ -2047,7 +1929,7 @@ end
TEST_CASE("transpile_type_function_unnamed_arguments") TEST_CASE("transpile_type_function_unnamed_arguments")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = R"( type Foo = () -> () )"; std::string code = R"( type Foo = () -> () )";
CHECK_EQ(R"( type Foo = () ->() )", transpile(code, {}, true).code); CHECK_EQ(R"( type Foo = () ->() )", transpile(code, {}, true).code);
@ -2081,7 +1963,7 @@ TEST_CASE("transpile_type_function_unnamed_arguments")
TEST_CASE("transpile_type_function_named_arguments") TEST_CASE("transpile_type_function_named_arguments")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = R"( type Foo = (x: string) -> () )"; std::string code = R"( type Foo = (x: string) -> () )";
CHECK_EQ(R"( type Foo = (x: string) ->() )", transpile(code, {}, true).code); CHECK_EQ(R"( type Foo = (x: string) ->() )", transpile(code, {}, true).code);
@ -2109,7 +1991,7 @@ TEST_CASE("transpile_type_function_named_arguments")
TEST_CASE("transpile_type_function_generics") TEST_CASE("transpile_type_function_generics")
{ {
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
std::string code = R"( type Foo = <X, Y, Z...>() -> () )"; std::string code = R"( type Foo = <X, Y, Z...>() -> () )";
CHECK_EQ(R"( type Foo = <X, Y, Z...>() ->() )", transpile(code, {}, true).code); CHECK_EQ(R"( type Foo = <X, Y, Z...>() ->() )", transpile(code, {}, true).code);
@ -2141,55 +2023,4 @@ TEST_CASE("transpile_type_function_generics")
CHECK_EQ(R"( type Foo = <X, Y, Z...> () ->() )", transpile(code, {}, true).code); CHECK_EQ(R"( type Foo = <X, Y, Z...> () ->() )", transpile(code, {}, true).code);
} }
TEST_CASE("fuzzer_nil_optional")
{
ScopedFastFlag _{FFlag::LuauParseOptionalAsNode2, true};
const std::string code = R"( local x: nil? )";
CHECK_EQ(code, transpile(code, {}, true).code);
}
TEST_CASE("transpile_function_attributes")
{
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
std::string code = R"(
@native
function foo()
end
)";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"(
@native
local function foo()
end
)";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"(
@checked local function foo()
end
)";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"(
local foo = @native function() end
)";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"(
@native
function foo:bar()
end
)";
CHECK_EQ(code, transpile(code, {}, true).code);
code = R"(
@native @checked
function foo:bar()
end
)";
CHECK_EQ(code, transpile(code, {}, true).code);
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -17,11 +17,7 @@ LUAU_FASTFLAG(LuauIndexTypeFunctionImprovements)
LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit) LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
LUAU_FASTFLAG(LuauIndexTypeFunctionFunctionMetamethods) LUAU_FASTFLAG(LuauIndexTypeFunctionFunctionMetamethods)
LUAU_FASTFLAG(LuauMetatableTypeFunctions) LUAU_FASTFLAG(LuauMetatableTypeFunctions)
LUAU_FASTFLAG(LuauMetatablesHaveLength)
LUAU_FASTFLAG(LuauIndexAnyIsAny) LUAU_FASTFLAG(LuauIndexAnyIsAny)
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAG(LuauHasPropProperBlock)
LUAU_FASTFLAG(LuauFixCyclicIndexInIndexer)
struct TypeFunctionFixture : Fixture struct TypeFunctionFixture : Fixture
{ {
@ -149,17 +145,19 @@ TEST_CASE_FIXTURE(TypeFunctionFixture, "unsolvable_function")
if (!FFlag::LuauSolverV2) if (!FFlag::LuauSolverV2)
return; return;
ScopedFastFlag luauNewTypeFunReductionChecks2{FFlag::LuauNewTypeFunReductionChecks2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
local impossible: <T>(Swap<T>) -> Swap<Swap<T>> local impossible: <T>(Swap<T>) -> Swap<Swap<T>>
local a = impossible(123) local a = impossible(123)
local b = impossible(true) local b = impossible(true)
)"); )");
LUAU_REQUIRE_ERROR_COUNT(2, result); LUAU_REQUIRE_ERROR_COUNT(6, result);
CHECK(toString(result.errors[0]) == "Type 'number' could not be converted into 'never'"); CHECK(toString(result.errors[0]) == "Type function instance Swap<Swap<T>> is uninhabited");
CHECK(toString(result.errors[1]) == "Type 'boolean' could not be converted into 'never'"); 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") TEST_CASE_FIXTURE(TypeFunctionFixture, "table_internal_functions")
@ -924,76 +922,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_of_any_is_any")
CHECK(toString(requireTypeAlias("T")) == "any"); 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") TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works")
{ {
if (!FFlag::LuauSolverV2) if (!FFlag::LuauSolverV2)
@ -1624,53 +1552,4 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_respects_metatable_metamethod")
CHECK_EQ(toString(requireTypeAlias("Metatable")), "string"); 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(); TEST_SUITE_END();

View file

@ -10,9 +10,9 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauTypeFunReadWriteParents) LUAU_FASTFLAG(LuauTypeFunReadWriteParents)
LUAU_FASTFLAG(LuauTypeFunPrintFix)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests"); TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");
@ -2030,7 +2030,7 @@ local _:test<number>
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_print_tab_char_fix") 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"( CheckResult result = check(R"(
type function test(t) type function test(t)
@ -2103,105 +2103,4 @@ end
LUAU_REQUIRE_ERROR_COUNT(1, result); 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(); TEST_SUITE_END();

View file

@ -12,9 +12,6 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauFixInfiniteRecursionInNormalization) LUAU_FASTFLAG(LuauFixInfiniteRecursionInNormalization)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauBidirectionalInferenceUpcast)
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
TEST_SUITE_BEGIN("TypeAliases"); TEST_SUITE_BEGIN("TypeAliases");
@ -256,10 +253,8 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
TEST_CASE_FIXTURE(Fixture, "mutually_recursive_generic_aliases") TEST_CASE_FIXTURE(Fixture, "mutually_recursive_generic_aliases")
{ {
ScopedFastFlag sffs[] = { // CLI-116108
{FFlag::LuauBidirectionalInferenceUpcast, true}, DOES_NOT_PASS_NEW_SOLVER_GUARD();
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
@ -1223,40 +1218,4 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gh1632_no_infinite_recursion_in_normalizatio
LUAU_CHECK_NO_ERRORS(result); 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(); TEST_SUITE_END();

View file

@ -11,6 +11,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauTableCloneClonesType3) LUAU_FASTFLAG(LuauTableCloneClonesType3)
LUAU_FASTFLAG(LuauStringFormatErrorSuppression)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
TEST_SUITE_BEGIN("BuiltinTests"); TEST_SUITE_BEGIN("BuiltinTests");
@ -1671,7 +1672,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_should_support_any")
print(string.format("Hello, %s!", x)) 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(); TEST_SUITE_END();

View file

@ -24,7 +24,6 @@ LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauUngeneralizedTypesForRecursiveFunctions) LUAU_FASTFLAG(LuauUngeneralizedTypesForRecursiveFunctions)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauReduceUnionFollowUnionType)
TEST_SUITE_BEGIN("TypeInferFunctions"); TEST_SUITE_BEGIN("TypeInferFunctions");
@ -3192,30 +3191,4 @@ TEST_CASE_FIXTURE(Fixture, "recursive_function_calls_should_not_use_the_generali
LUAU_REQUIRE_NO_ERRORS(result); 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(); TEST_SUITE_END();

View file

@ -12,6 +12,7 @@
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
using namespace Luau; using namespace Luau;
@ -855,6 +856,8 @@ end
TEST_CASE_FIXTURE(Fixture, "generic_functions_should_be_memory_safe") TEST_CASE_FIXTURE(Fixture, "generic_functions_should_be_memory_safe")
{ {
ScopedFastFlag _{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
-- At one point this produced a UAF -- At one point this produced a UAF

View file

@ -13,7 +13,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauStoreCSTData2) LUAU_FASTFLAG(LuauStoreCSTData)
LUAU_FASTINT(LuauNormalizeCacheLimit) LUAU_FASTINT(LuauNormalizeCacheLimit)
LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferIterationLimit)
@ -49,7 +49,7 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
end end
)"; )";
const std::string expected = FFlag::LuauStoreCSTData2 ? R"( const std::string expected = FFlag::LuauStoreCSTData ? R"(
function f(a:{fn:()->(a,b...)}): () function f(a:{fn:()->(a,b...)}): ()
if type(a) == 'boolean' then if type(a) == 'boolean' then
local a1:boolean=a local a1:boolean=a
@ -68,7 +68,7 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
end end
)"; )";
const std::string expectedWithNewSolver = FFlag::LuauStoreCSTData2 ? R"( const std::string expectedWithNewSolver = FFlag::LuauStoreCSTData ? R"(
function f(a:{fn:()->(unknown,...unknown)}): () function f(a:{fn:()->(unknown,...unknown)}): ()
if type(a) == 'boolean' then if type(a) == 'boolean' then
local a1:{fn:()->(unknown,...unknown)}&boolean=a local a1:{fn:()->(unknown,...unknown)}&boolean=a
@ -87,7 +87,7 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
end end
)"; )";
const std::string expectedWithEqSat = FFlag::LuauStoreCSTData2 ? R"( const std::string expectedWithEqSat = FFlag::LuauStoreCSTData ? R"(
function f(a:{fn:()->(unknown,...unknown)}): () function f(a:{fn:()->(unknown,...unknown)}): ()
if type(a) == 'boolean' then if type(a) == 'boolean' then
local a1:{fn:()->(unknown,...unknown)}&boolean=a local a1:{fn:()->(unknown,...unknown)}&boolean=a

View file

@ -9,11 +9,11 @@
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauGeneralizationRemoveRecursiveUpperBound2)
LUAU_FASTFLAG(LuauIntersectNotNil) LUAU_FASTFLAG(LuauIntersectNotNil)
LUAU_FASTFLAG(LuauSkipNoRefineDuringRefinement) LUAU_FASTFLAG(LuauSkipNoRefineDuringRefinement)
LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable) LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable)
LUAU_FASTFLAG(LuauDoNotLeakNilInRefinement) LUAU_FASTFLAG(LuauDoNotLeakNilInRefinement)
LUAU_FASTFLAG(LuauSimplyRefineNotNil)
using namespace Luau; using namespace Luau;
@ -756,24 +756,14 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "nonoptional_type_can_narrow_to_nil_if_sense_
{ {
// CLI-115281 Types produced by refinements do not consistently get simplified // CLI-115281 Types produced by refinements do not consistently get simplified
CHECK_EQ("(nil & string)?", toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil" CHECK_EQ("(nil & string)?", toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil"
CHECK_EQ(
if (FFlag::LuauSimplyRefineNotNil) "(boolean | buffer | class | function | number | string | table | thread) & string", toString(requireTypeAtPosition({6, 24}))
CHECK_EQ( ); // type(v) ~= "nil"
"string & ~nil", toString(requireTypeAtPosition({6, 24}))
); // type(v) ~= "nil"
else
CHECK_EQ(
"(boolean | buffer | class | function | number | string | table | thread) & string", toString(requireTypeAtPosition({6, 24}))
); // type(v) ~= "nil"
CHECK_EQ("(nil & string)?", toString(requireTypeAtPosition({10, 24}))); // equivalent to type(v) == "nil" CHECK_EQ("(nil & string)?", toString(requireTypeAtPosition({10, 24}))); // equivalent to type(v) == "nil"
CHECK_EQ(
if (FFlag::LuauSimplyRefineNotNil) "(boolean | buffer | class | function | number | string | table | thread) & string", toString(requireTypeAtPosition({12, 24}))
CHECK_EQ("string & ~nil", toString(requireTypeAtPosition({12, 24}))); // equivalent to type(v) ~= "nil" ); // equivalent to type(v) ~= "nil"
else
CHECK_EQ(
"(boolean | buffer | class | function | number | string | table | thread) & string", toString(requireTypeAtPosition({12, 24}))
); // equivalent to type(v) ~= "nil"
} }
else else
{ {
@ -2455,6 +2445,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "remove_recursive_upper_bound_when_generalizi
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::DebugLuauEqSatSimplification, true}, {FFlag::DebugLuauEqSatSimplification, true},
{FFlag::LuauGeneralizationRemoveRecursiveUpperBound2, true},
}; };
LUAU_REQUIRE_NO_ERRORS(check(R"( LUAU_REQUIRE_NO_ERRORS(check(R"(

View file

@ -7,7 +7,6 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauPropagateExpectedTypesForCalls)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
TEST_SUITE_BEGIN("TypeSingletons"); TEST_SUITE_BEGIN("TypeSingletons");
@ -153,26 +152,6 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_function_call_with_singletons")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
TEST_CASE_FIXTURE(Fixture, "overloaded_function_resolution_singleton_parameters")
{
ScopedFastFlag sff{FFlag::LuauPropagateExpectedTypesForCalls, true};
CheckResult result = check(R"(
type A = ("A") -> string
type B = ("B") -> number
local function foo(f: A & B)
return f("A"), f("B")
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
TypeId t = requireType("foo");
const FunctionType* fooType = get<FunctionType>(requireType("foo"));
REQUIRE(fooType != nullptr);
CHECK(toString(t) == "(((\"A\") -> string) & ((\"B\") -> number)) -> (string, number)");
}
TEST_CASE_FIXTURE(Fixture, "overloaded_function_call_with_singletons_mismatch") TEST_CASE_FIXTURE(Fixture, "overloaded_function_call_with_singletons_mismatch")
{ {
DOES_NOT_PASS_NEW_SOLVER_GUARD(); DOES_NOT_PASS_NEW_SOLVER_GUARD();

View file

@ -24,13 +24,12 @@ LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAG(LuauTrackInteriorFreeTablesOnScope) LUAU_FASTFLAG(LuauTrackInteriorFreeTablesOnScope)
LUAU_FASTFLAG(LuauFollowTableFreeze) LUAU_FASTFLAG(LuauFollowTableFreeze)
LUAU_FASTFLAG(LuauPrecalculateMutatedFreeTypes2)
LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment)
LUAU_FASTFLAG(LuauBidirectionalInferenceUpcast) LUAU_FASTFLAG(LuauBidirectionalInferenceUpcast)
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
LUAU_FASTFLAG(LuauSearchForRefineableType) LUAU_FASTFLAG(LuauSearchForRefineableType)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
LUAU_FASTFLAG(LuauBidirectionalFailsafe)
LUAU_FASTFLAG(LuauBidirectionalInferenceElideAssert)
TEST_SUITE_BEGIN("TableTests"); TEST_SUITE_BEGIN("TableTests");
@ -5169,33 +5168,34 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_union_type")
TEST_CASE_FIXTURE(Fixture, "function_check_constraint_too_eager") TEST_CASE_FIXTURE(Fixture, "function_check_constraint_too_eager")
{ {
// NOTE: All of these examples should have no errors, but
// bidirectional inference is known to be broken.
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true}, {FFlag::LuauPrecalculateMutatedFreeTypes2, true},
}; };
CheckResult result = check(R"( auto result = check(R"(
local function doTheThing(_: { [string]: unknown }) end local function doTheThing(_: { [string]: unknown }) end
doTheThing({ doTheThing({
['foo'] = 5, ['foo'] = 5,
['bar'] = 'heyo', ['bar'] = 'heyo',
}) })
)"); )");
LUAU_CHECK_ERROR_COUNT(1, result);
LUAU_CHECK_NO_ERROR(result, ConstraintSolvingIncompleteError);
LUAU_REQUIRE_NO_ERRORS(result); LUAU_CHECK_ERROR_COUNT(1, check(R"(
result = check(R"(
type Input = { [string]: unknown } type Input = { [string]: unknown }
local i : Input = { local i : Input = {
[('%s'):format('3.14')]=5, [('%s'):format('3.14')]=5,
['stringField']='Heyo' ['stringField']='Heyo'
} }
)"); )"));
LUAU_REQUIRE_NO_ERRORS(result);
// This example previously asserted due to eagerly mutating the underlying
// table type.
result = check(R"( result = check(R"(
type Input = { [string]: unknown } type Input = { [string]: unknown }
@ -5206,43 +5206,8 @@ TEST_CASE_FIXTURE(Fixture, "function_check_constraint_too_eager")
['stringField']='Heyo' ['stringField']='Heyo'
}) })
)"); )");
LUAU_CHECK_ERROR_COUNT(1, result);
LUAU_REQUIRE_NO_ERRORS(result); LUAU_CHECK_NO_ERROR(result, ConstraintSolvingIncompleteError);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "magic_functions_bidirectionally_inferred")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
};
CheckResult result = check(R"(
local function getStuff(): (string, number, string)
return "hello", 42, "world"
end
local t: { [string]: number } = {
[select(1, getStuff())] = select(2, getStuff()),
[select(3, getStuff())] = select(2, getStuff())
}
)");
LUAU_REQUIRE_NO_ERRORS(result);
result = check(R"(
local function getStuff(): (string, number, string)
return "hello", 42, "world"
end
local t: { [string]: number } = {
[select(1, getStuff())] = select(2, getStuff()),
[select(3, getStuff())] = select(3, getStuff())
}
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
auto err = get<TypeMismatch>(result.errors[0]);
CHECK_EQ("{ [string]: number | string }", toString(err->givenType));
CHECK_EQ("{ [string]: number }", toString(err->wantedType));
} }
@ -5340,6 +5305,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_musnt_assert")
TEST_CASE_FIXTURE(Fixture, "optional_property_with_call") TEST_CASE_FIXTURE(Fixture, "optional_property_with_call")
{ {
ScopedFastFlag _{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true};
LUAU_CHECK_NO_ERRORS(check(R"( LUAU_CHECK_NO_ERRORS(check(R"(
type t = { type t = {
key: boolean?, key: boolean?,
@ -5393,6 +5360,7 @@ TEST_CASE_FIXTURE(Fixture, "inference_in_constructor")
TEST_CASE_FIXTURE(Fixture, "returning_optional_in_table") TEST_CASE_FIXTURE(Fixture, "returning_optional_in_table")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
{FFlag::LuauBidirectionalInferenceUpcast, true}, {FFlag::LuauBidirectionalInferenceUpcast, true},
}; };
@ -5408,6 +5376,7 @@ TEST_CASE_FIXTURE(Fixture, "returning_mismatched_optional_in_table")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
}; };
auto result = check(R"( auto result = check(R"(
@ -5429,6 +5398,7 @@ TEST_CASE_FIXTURE(Fixture, "optional_function_in_table")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
{FFlag::LuauBidirectionalInferenceUpcast, true}, {FFlag::LuauBidirectionalInferenceUpcast, true},
}; };
@ -5454,6 +5424,7 @@ TEST_CASE_FIXTURE(Fixture, "optional_function_in_table")
TEST_CASE_FIXTURE(Fixture, "oss_1596_expression_in_table") TEST_CASE_FIXTURE(Fixture, "oss_1596_expression_in_table")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
{FFlag::LuauBidirectionalInferenceUpcast, true}, {FFlag::LuauBidirectionalInferenceUpcast, true},
}; };
@ -5466,6 +5437,10 @@ TEST_CASE_FIXTURE(Fixture, "oss_1596_expression_in_table")
TEST_CASE_FIXTURE(Fixture, "oss_1615_parametrized_type_alias") TEST_CASE_FIXTURE(Fixture, "oss_1615_parametrized_type_alias")
{ {
ScopedFastFlag sffs[] = {
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
};
LUAU_CHECK_NO_ERRORS(check(R"( LUAU_CHECK_NO_ERRORS(check(R"(
type Pair<Node> = { sep: {}? } type Pair<Node> = { sep: {}? }
local a: Pair<{}> = { local a: Pair<{}> = {
@ -5476,6 +5451,11 @@ TEST_CASE_FIXTURE(Fixture, "oss_1615_parametrized_type_alias")
TEST_CASE_FIXTURE(Fixture, "oss_1543_optional_generic_param") TEST_CASE_FIXTURE(Fixture, "oss_1543_optional_generic_param")
{ {
ScopedFastFlag sffs[] = {
{FFlag::LuauPrecalculateMutatedFreeTypes2, true},
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
};
LUAU_CHECK_NO_ERRORS(check(R"( LUAU_CHECK_NO_ERRORS(check(R"(
type foo<T> = { bar: T? } type foo<T> = { bar: T? }
@ -5489,8 +5469,8 @@ TEST_CASE_FIXTURE(Fixture, "missing_fields_bidirectional_inference")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauBidirectionalInferenceUpcast, true}, {FFlag::LuauPrecalculateMutatedFreeTypes2, true},
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true}, {FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
}; };
auto result = check(R"( auto result = check(R"(
@ -5498,8 +5478,7 @@ TEST_CASE_FIXTURE(Fixture, "missing_fields_bidirectional_inference")
local b: Book = { title = "The Odyssey" } local b: Book = { title = "The Odyssey" }
local t: { Book } = { local t: { Book } = {
{ title = "The Illiad", author = "Homer" }, { title = "The Illiad", author = "Homer" },
{ title = "Inferno", author = "Virgil" }, { author = "Virgil" }
{ author = "Virgil" },
} }
)"); )");
@ -5511,47 +5490,12 @@ TEST_CASE_FIXTURE(Fixture, "missing_fields_bidirectional_inference")
CHECK_EQ(result.errors[0].location, Location{{2, 24}, {2, 49}}); CHECK_EQ(result.errors[0].location, Location{{2, 24}, {2, 49}});
err = get<TypeMismatch>(result.errors[1]); err = get<TypeMismatch>(result.errors[1]);
REQUIRE(err); REQUIRE(err);
// CLI-144203: This could be better. CHECK_EQ(toString(err->givenType), "{{ author: string } | { author: string, title: string }}");
CHECK_EQ(toString(err->givenType), "{{ author: string }}");
CHECK_EQ(toString(err->wantedType), "{Book}"); CHECK_EQ(toString(err->wantedType), "{Book}");
CHECK_EQ(result.errors[1].location, Location{{3, 28}, {7, 9}}); CHECK_EQ(result.errors[1].location, Location{{3, 28}, {6, 9}});
} }
TEST_CASE_FIXTURE(Fixture, "generic_index_syntax_bidirectional_infer_with_tables")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauBidirectionalInferenceUpcast, true},
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
};
auto result = check((R"(
local function getStatus(): string
return "Yeah can you look in returned books?"
end
local function getPratchettStatus()
return { isLate = true }
end
type Status = { isLate: boolean, daysLate: number? }
local key1 = "Great Expecations"
local key2 = "The Outsiders"
local key3 = "Guards! Guards!"
local books: { [string]: Status } = {
[key1] = { isLate = true, daysLate = "coconut" },
[key2] = getStatus(),
[key3] = getPratchettStatus()
}
)"));
LUAU_CHECK_ERROR_COUNT(1, result);
auto err = get<TypeMismatch>(result.errors[0]);
REQUIRE(err);
// NOTE: This is because the inferred keys of `books` are all primitive types.
CHECK_EQ(toString(err->givenType), "{ [string | string | string]: string | { daysLate: string, isLate: boolean } | { isLate: boolean } }");
CHECK_EQ(toString(err->wantedType), "{ [string]: Status }");
}
TEST_CASE_FIXTURE(Fixture, "deeply_nested_classish_inference") TEST_CASE_FIXTURE(Fixture, "deeply_nested_classish_inference")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
@ -5628,61 +5572,5 @@ TEST_CASE_FIXTURE(Fixture, "bigger_nested_table_causes_big_type_error")
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
TEST_CASE_FIXTURE(Fixture, "unsafe_bidirectional_mutation")
{
ScopedFastFlag _{FFlag::LuauBidirectionalFailsafe, true};
// It's kind of suspect that we allow multiple definitions of keys in
// a single table.
LUAU_REQUIRE_NO_ERRORS(check(R"(
type F = {
_G: () -> ()
}
function _()
return
end
local function h(f: F) end
h({
_G = {},
_G = _,
})
)"));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "function_call_in_indexer_with_compound_assign")
{
ScopedFastFlag _{FFlag::LuauBidirectionalFailsafe, true};
// This has a bunch of errors, we really just need it to not crash / assert.
std::ignore = check(R"(
--!strict
local _ = 7143424
_[
setfenv(
...,
{
n0 = _,
}
)
] *= _
)");
}
TEST_CASE_FIXTURE(Fixture, "fuzz_match_literal_type_crash_again")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
{FFlag::LuauBidirectionalInferenceElideAssert, true},
};
CheckResult result = check(R"(
function f(_: { [string]: {unknown}} ) end
f(
{
_ = { 42 },
_ = { x = "foo" },
}
)
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -30,9 +30,6 @@ LUAU_FASTFLAG(LuauInferLocalTypesInMultipleAssignments)
LUAU_FASTFLAG(LuauUnifyMetatableWithAny) LUAU_FASTFLAG(LuauUnifyMetatableWithAny)
LUAU_FASTFLAG(LuauExtraFollows) LUAU_FASTFLAG(LuauExtraFollows)
LUAU_FASTFLAG(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
LUAU_FASTFLAG(LuauTypeCheckerAcceptNumberConcats)
LUAU_FASTFLAG(LuauPreprocessTypestatedArgument)
LUAU_FASTFLAG(LuauCacheInferencePerAstExpr)
using namespace Luau; using namespace Luau;
@ -1865,6 +1862,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_works_with_any")
end, end,
} }
)")); )"));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_infer_any_ret") TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_infer_any_ret")
@ -1939,67 +1937,14 @@ end
TEST_CASE_FIXTURE(Fixture, "concat_string_with_string_union") TEST_CASE_FIXTURE(Fixture, "concat_string_with_string_union")
{ {
ScopedFastFlag _{FFlag::LuauSolverV2, true}; ScopedFastFlag _{FFlag::LuauSolverV2, true};
ScopedFastFlag fixNumberConcats{FFlag::LuauTypeCheckerAcceptNumberConcats, true};
LUAU_REQUIRE_NO_ERRORS(check(R"( LUAU_REQUIRE_NO_ERRORS(check(R"(
local function concat_stuff(x: string, y : string | number) local function foo(n : number): string return "" end
return x .. y local function bar(n: number, m: string) end
local function concat_stuff(x, y)
local z = foo(x)
bar(y, z)
end end
)")); )"));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_local_before_declaration_ice")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauPreprocessTypestatedArgument, true},
};
CheckResult result = check(R"(
local _
table.freeze(_, _)
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
auto err0 = get<TypeMismatch>(result.errors[0]);
CHECK(err0);
CHECK_EQ("nil", toString(err0->givenType));
CHECK_EQ("table", toString(err0->wantedType));
auto err1 = get<CountMismatch>(result.errors[1]);
CHECK(err1);
CHECK_EQ(1, err1->expected);
CHECK_EQ(2, err1->actual);
}
TEST_CASE_FIXTURE(Fixture, "fuzz_dont_double_solve_compound_assignment" * doctest::timeout(1.0))
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauCacheInferencePerAstExpr, true}
};
CheckResult result = check(R"(
local _ = {}
_[function<t0...>(...)
_[function(...)
_[_] %= _
_ = {}
_ = (- _)()
end] %= _
_[_] %= _
end] %= true
)");
LUAU_REQUIRE_ERRORS(result);
LUAU_REQUIRE_NO_ERROR(result, ConstraintSolvingIncompleteError);
}
TEST_CASE_FIXTURE(Fixture, "assert_allows_singleton_union_or_intersection")
{
LUAU_REQUIRE_NO_ERRORS(check(R"(
local x = 42 :: | number
local y = 42 :: & number
)"));
}
TEST_SUITE_END(); TEST_SUITE_END();