mirror of
https://github.com/luau-lang/luau.git
synced 2025-04-18 10:53:45 +01:00
Compare commits
No commits in common. "master" and "0.663" have entirely different histories.
180 changed files with 5796 additions and 14685 deletions
148
Analysis/include/Luau/AnyTypeSummary.h
Normal file
148
Analysis/include/Luau/AnyTypeSummary.h
Normal 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
|
|
@ -70,7 +70,6 @@ Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol
|
|||
void assignPropDocumentationSymbols(TableType::Props& props, const std::string& baseName);
|
||||
|
||||
std::string getBuiltinDefinitionSource();
|
||||
std::string getTypeFunctionDefinitionSource();
|
||||
|
||||
void addGlobalBinding(GlobalTypes& globals, const std::string& name, TypeId ty, const std::string& packageName);
|
||||
void addGlobalBinding(GlobalTypes& globals, const std::string& name, Binding binding);
|
||||
|
|
|
@ -40,9 +40,4 @@ TypeId clone(TypeId tp, TypeArena& dest, CloneState& cloneState);
|
|||
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState);
|
||||
Binding clone(const Binding& binding, TypeArena& dest, CloneState& cloneState);
|
||||
|
||||
TypePackId cloneIncremental(TypePackId tp, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes);
|
||||
TypeId cloneIncremental(TypeId typeId, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes);
|
||||
TypeFun cloneIncremental(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes);
|
||||
Binding cloneIncremental(const Binding& binding, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes);
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -50,7 +50,6 @@ struct GeneralizationConstraint
|
|||
TypeId sourceType;
|
||||
|
||||
std::vector<TypeId> interiorTypes;
|
||||
bool hasDeprecatedAttribute = false;
|
||||
};
|
||||
|
||||
// variables ~ iterate iterator
|
||||
|
|
|
@ -11,14 +11,15 @@
|
|||
#include "Luau/ModuleResolver.h"
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/Polarity.h"
|
||||
#include "Luau/Refinement.h"
|
||||
#include "Luau/Symbol.h"
|
||||
#include "Luau/TypeFwd.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -116,23 +117,18 @@ struct ConstraintGenerator
|
|||
|
||||
// Needed to register all available type functions for execution at later stages.
|
||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
|
||||
DenseHashMap<const AstStatTypeFunction*, ScopePtr> astTypeFunctionEnvironmentScopes{nullptr};
|
||||
|
||||
// Needed to resolve modules to make 'require' import types properly.
|
||||
NotNull<ModuleResolver> moduleResolver;
|
||||
// Occasionally constraint generation needs to produce an ICE.
|
||||
const NotNull<InternalErrorReporter> ice;
|
||||
|
||||
ScopePtr globalScope;
|
||||
ScopePtr typeFunctionScope;
|
||||
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope;
|
||||
std::vector<RequireCycle> requireCycles;
|
||||
|
||||
DenseHashMap<TypeId, TypeIds> localTypes{nullptr};
|
||||
|
||||
DenseHashMap<AstExpr*, Inference> inferredExprCache{nullptr};
|
||||
|
||||
DcrLogger* logger;
|
||||
|
||||
ConstraintGenerator(
|
||||
|
@ -144,7 +140,6 @@ struct ConstraintGenerator
|
|||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<InternalErrorReporter> ice,
|
||||
const ScopePtr& globalScope,
|
||||
const ScopePtr& typeFunctionScope,
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||
DcrLogger* logger,
|
||||
NotNull<DataFlowGraph> dfg,
|
||||
|
@ -161,26 +156,19 @@ struct ConstraintGenerator
|
|||
void visitFragmentRoot(const ScopePtr& resumeScope, AstStatBlock* block);
|
||||
|
||||
private:
|
||||
struct InteriorFreeTypes
|
||||
{
|
||||
std::vector<TypeId> types;
|
||||
std::vector<TypePackId> typePacks;
|
||||
};
|
||||
|
||||
std::vector<std::vector<TypeId>> DEPRECATED_interiorTypes;
|
||||
std::vector<InteriorFreeTypes> interiorFreeTypes;
|
||||
std::vector<std::vector<TypeId>> interiorTypes;
|
||||
|
||||
/**
|
||||
* Fabricates a new free type belonging to a given scope.
|
||||
* @param scope the scope the free type belongs to.
|
||||
*/
|
||||
TypeId freshType(const ScopePtr& scope, Polarity polarity = Polarity::Unknown);
|
||||
TypeId freshType(const ScopePtr& scope);
|
||||
|
||||
/**
|
||||
* Fabricates a new free type pack belonging to a given scope.
|
||||
* @param scope the scope the free type pack belongs to.
|
||||
*/
|
||||
TypePackId freshTypePack(const ScopePtr& scope, Polarity polarity = Polarity::Unknown);
|
||||
TypePackId freshTypePack(const ScopePtr& scope);
|
||||
|
||||
/**
|
||||
* Allocate a new TypePack with the given head and tail.
|
||||
|
@ -301,7 +289,7 @@ private:
|
|||
);
|
||||
|
||||
Inference check(const ScopePtr& scope, AstExprConstantString* string, std::optional<TypeId> expectedType, bool forceSingleton);
|
||||
Inference check(const ScopePtr& scope, AstExprConstantBool* boolExpr, std::optional<TypeId> expectedType, bool forceSingleton);
|
||||
Inference check(const ScopePtr& scope, AstExprConstantBool* bool_, std::optional<TypeId> expectedType, bool forceSingleton);
|
||||
Inference check(const ScopePtr& scope, AstExprLocal* local);
|
||||
Inference check(const ScopePtr& scope, AstExprGlobal* global);
|
||||
Inference checkIndexName(const ScopePtr& scope, const RefinementKey* key, AstExpr* indexee, const std::string& index, Location indexLocation);
|
||||
|
@ -377,11 +365,6 @@ private:
|
|||
**/
|
||||
TypeId resolveType(const ScopePtr& scope, AstType* ty, bool inTypeArguments, bool replaceErrorWithFresh = false);
|
||||
|
||||
// resolveType() is recursive, but we only want to invoke
|
||||
// inferGenericPolarities() once at the very end. We thus isolate the
|
||||
// recursive part of the algorithm to this internal helper.
|
||||
TypeId resolveType_(const ScopePtr& scope, AstType* ty, bool inTypeArguments, bool replaceErrorWithFresh = false);
|
||||
|
||||
/**
|
||||
* Resolves a type pack from its AST annotation.
|
||||
* @param scope the scope that the type annotation appears within.
|
||||
|
@ -391,9 +374,6 @@ private:
|
|||
**/
|
||||
TypePackId resolveTypePack(const ScopePtr& scope, AstTypePack* tp, bool inTypeArguments, bool replaceErrorWithFresh = false);
|
||||
|
||||
// Inner hepler for resolveTypePack
|
||||
TypePackId resolveTypePack_(const ScopePtr& scope, AstTypePack* tp, bool inTypeArguments, bool replaceErrorWithFresh = false);
|
||||
|
||||
/**
|
||||
* Resolves a type pack from its AST annotation.
|
||||
* @param scope the scope that the type annotation appears within.
|
||||
|
@ -432,7 +412,7 @@ private:
|
|||
**/
|
||||
std::vector<std::pair<Name, GenericTypePackDefinition>> createGenericPacks(
|
||||
const ScopePtr& scope,
|
||||
AstArray<AstGenericTypePack*> generics,
|
||||
AstArray<AstGenericTypePack*> packs,
|
||||
bool useCache = false,
|
||||
bool addTypes = true
|
||||
);
|
||||
|
|
|
@ -365,7 +365,7 @@ public:
|
|||
* @returns a non-free type that generalizes the argument, or `std::nullopt` if one
|
||||
* does not exist
|
||||
*/
|
||||
std::optional<TypeId> generalizeFreeType(NotNull<Scope> scope, TypeId type);
|
||||
std::optional<TypeId> generalizeFreeType(NotNull<Scope> scope, TypeId type, bool avoidSealingTables = false);
|
||||
|
||||
/**
|
||||
* Checks the existing set of constraints to see if there exist any that contain
|
||||
|
|
|
@ -38,6 +38,8 @@ struct DataFlowGraph
|
|||
DefId getDef(const AstExpr* expr) const;
|
||||
// Look up the definition optionally, knowing it may not be present.
|
||||
std::optional<DefId> getDefOptional(const AstExpr* expr) const;
|
||||
// Look up for the rvalue def for a compound assignment.
|
||||
std::optional<DefId> getRValueDefForCompoundAssign(const AstExpr* expr) const;
|
||||
|
||||
DefId getDef(const AstLocal* local) const;
|
||||
|
||||
|
@ -64,6 +66,10 @@ private:
|
|||
// All keys in this maps are really only statements that ambiently declares a symbol.
|
||||
DenseHashMap<const AstStat*, const Def*> declaredDefs{nullptr};
|
||||
|
||||
// Compound assignments are in a weird situation where the local being assigned to is also being used at its
|
||||
// previous type implicitly in an rvalue position. This map provides the previous binding.
|
||||
DenseHashMap<const AstExpr*, const Def*> compoundAssignDefs{nullptr};
|
||||
|
||||
DenseHashMap<const AstExpr*, const RefinementKey*> astRefinementKeys{nullptr};
|
||||
friend struct DataFlowGraphBuilder;
|
||||
};
|
||||
|
@ -129,8 +135,8 @@ private:
|
|||
|
||||
/// A stack of scopes used by the visitor to see where we are.
|
||||
ScopeStack scopeStack;
|
||||
NotNull<DfgScope> currentScope();
|
||||
DfgScope* currentScope_DEPRECATED();
|
||||
|
||||
DfgScope* currentScope();
|
||||
|
||||
struct FunctionCapture
|
||||
{
|
||||
|
@ -148,8 +154,8 @@ private:
|
|||
void joinBindings(DfgScope* p, const DfgScope& a, const DfgScope& b);
|
||||
void joinProps(DfgScope* p, const DfgScope& a, const DfgScope& b);
|
||||
|
||||
DefId lookup(Symbol symbol, Location location);
|
||||
DefId lookup(DefId def, const std::string& key, Location location);
|
||||
DefId lookup(Symbol symbol);
|
||||
DefId lookup(DefId def, const std::string& key);
|
||||
|
||||
ControlFlow visit(AstStatBlock* b);
|
||||
ControlFlow visitBlockWithoutChildScope(AstStatBlock* b);
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
#include "Luau/NotNull.h"
|
||||
#include "Luau/TypedAllocator.h"
|
||||
#include "Luau/Variant.h"
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/Symbol.h"
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
|
@ -14,7 +13,6 @@ namespace Luau
|
|||
|
||||
struct Def;
|
||||
using DefId = NotNull<const Def>;
|
||||
struct AstLocal;
|
||||
|
||||
/**
|
||||
* A cell is a "single-object" value.
|
||||
|
@ -66,8 +64,6 @@ struct Def
|
|||
using V = Variant<struct Cell, struct Phi>;
|
||||
|
||||
V v;
|
||||
Symbol name;
|
||||
Location location;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
|
@ -83,7 +79,7 @@ struct DefArena
|
|||
{
|
||||
TypedAllocator<Def> allocator;
|
||||
|
||||
DefId freshCell(Symbol sym, Location location, bool subscripted = false);
|
||||
DefId freshCell(bool subscripted = false);
|
||||
DefId phi(DefId a, DefId b);
|
||||
DefId phi(const std::vector<DefId>& defs);
|
||||
};
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
|
@ -33,71 +32,15 @@ struct ModuleInfo
|
|||
bool optional = false;
|
||||
};
|
||||
|
||||
struct RequireAlias
|
||||
{
|
||||
std::string alias; // Unprefixed alias name (no leading `@`).
|
||||
std::vector<std::string> tags = {};
|
||||
};
|
||||
|
||||
struct RequireNode
|
||||
{
|
||||
virtual ~RequireNode() {}
|
||||
|
||||
// Get the path component representing this node.
|
||||
virtual std::string getPathComponent() const = 0;
|
||||
|
||||
// Get the displayed user-facing label for this node, defaults to getPathComponent()
|
||||
virtual std::string getLabel() const
|
||||
{
|
||||
return getPathComponent();
|
||||
}
|
||||
|
||||
// Get tags to attach to this node's RequireSuggestion (defaults to none).
|
||||
virtual std::vector<std::string> getTags() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
// TODO: resolvePathToNode() can ultimately be replaced with a call into
|
||||
// require-by-string's path resolution algorithm. This will first require
|
||||
// generalizing that algorithm to work with a virtual file system.
|
||||
virtual std::unique_ptr<RequireNode> resolvePathToNode(const std::string& path) const = 0;
|
||||
|
||||
// Get children of this node, if any (if this node represents a directory).
|
||||
virtual std::vector<std::unique_ptr<RequireNode>> getChildren() const = 0;
|
||||
|
||||
// A list of the aliases available to this node.
|
||||
virtual std::vector<RequireAlias> getAvailableAliases() const = 0;
|
||||
};
|
||||
|
||||
struct RequireSuggestion
|
||||
{
|
||||
std::string label;
|
||||
std::string fullPath;
|
||||
std::vector<std::string> tags;
|
||||
};
|
||||
using RequireSuggestions = std::vector<RequireSuggestion>;
|
||||
|
||||
struct RequireSuggester
|
||||
{
|
||||
virtual ~RequireSuggester() {}
|
||||
std::optional<RequireSuggestions> getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& pathString) const;
|
||||
|
||||
protected:
|
||||
virtual std::unique_ptr<RequireNode> getNode(const ModuleName& name) const = 0;
|
||||
|
||||
private:
|
||||
std::optional<RequireSuggestions> getRequireSuggestionsImpl(const ModuleName& requirer, const std::optional<std::string>& path) const;
|
||||
};
|
||||
|
||||
struct FileResolver
|
||||
{
|
||||
FileResolver() = default;
|
||||
FileResolver(std::shared_ptr<RequireSuggester> requireSuggester)
|
||||
: requireSuggester(std::move(requireSuggester))
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~FileResolver() {}
|
||||
|
||||
virtual std::optional<SourceCode> readSource(const ModuleName& name) = 0;
|
||||
|
@ -117,10 +60,10 @@ struct FileResolver
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Make non-virtual when removing FFlagLuauImproveRequireByStringAutocomplete.
|
||||
virtual std::optional<RequireSuggestions> getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& pathString) const;
|
||||
|
||||
std::shared_ptr<RequireSuggester> requireSuggester;
|
||||
virtual std::optional<RequireSuggestions> getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& pathString) const
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
struct NullFileResolver : FileResolver
|
||||
|
|
|
@ -15,28 +15,6 @@ namespace Luau
|
|||
{
|
||||
struct FrontendOptions;
|
||||
|
||||
enum class FragmentAutocompleteWaypoint
|
||||
{
|
||||
ParseFragmentEnd,
|
||||
CloneModuleStart,
|
||||
CloneModuleEnd,
|
||||
DfgBuildEnd,
|
||||
CloneAndSquashScopeStart,
|
||||
CloneAndSquashScopeEnd,
|
||||
ConstraintSolverStart,
|
||||
ConstraintSolverEnd,
|
||||
TypecheckFragmentEnd,
|
||||
AutocompleteEnd,
|
||||
COUNT,
|
||||
};
|
||||
|
||||
class IFragmentAutocompleteReporter
|
||||
{
|
||||
public:
|
||||
virtual void reportWaypoint(FragmentAutocompleteWaypoint) = 0;
|
||||
virtual void reportFragmentString(std::string_view) = 0;
|
||||
};
|
||||
|
||||
enum class FragmentTypeCheckStatus
|
||||
{
|
||||
SkipAutocomplete,
|
||||
|
@ -49,8 +27,6 @@ struct FragmentAutocompleteAncestryResult
|
|||
std::vector<AstLocal*> localStack;
|
||||
std::vector<AstNode*> ancestry;
|
||||
AstStat* nearestStatement = nullptr;
|
||||
AstStatBlock* parentBlock = nullptr;
|
||||
Location fragmentSelectionRegion;
|
||||
};
|
||||
|
||||
struct FragmentParseResult
|
||||
|
@ -61,7 +37,6 @@ struct FragmentParseResult
|
|||
AstStat* nearestStatement = nullptr;
|
||||
std::vector<Comment> commentLocations;
|
||||
std::unique_ptr<Allocator> alloc = std::make_unique<Allocator>();
|
||||
Position scopePos{0, 0};
|
||||
};
|
||||
|
||||
struct FragmentTypeCheckResult
|
||||
|
@ -75,34 +50,14 @@ struct FragmentAutocompleteResult
|
|||
{
|
||||
ModulePtr incrementalModule;
|
||||
Scope* freshScope;
|
||||
TypeArena arenaForAutocomplete_DEPRECATED;
|
||||
TypeArena arenaForAutocomplete;
|
||||
AutocompleteResult acResults;
|
||||
};
|
||||
|
||||
struct FragmentRegion
|
||||
{
|
||||
Location fragmentLocation;
|
||||
AstStat* nearestStatement = nullptr; // used for tests
|
||||
AstStatBlock* parentBlock = nullptr; // used for scope detection
|
||||
};
|
||||
|
||||
std::optional<Position> blockDiffStart(AstStatBlock* blockOld, AstStatBlock* blockNew, AstStat* nearestStatementNewAst);
|
||||
FragmentRegion getFragmentRegion(AstStatBlock* root, const Position& cursorPosition);
|
||||
FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* stale, const Position& cursorPos, AstStatBlock* lastGoodParse);
|
||||
FragmentAutocompleteAncestryResult findAncestryForFragmentParse_DEPRECATED(AstStatBlock* root, const Position& cursorPos);
|
||||
|
||||
std::optional<FragmentParseResult> parseFragment_DEPRECATED(
|
||||
AstStatBlock* root,
|
||||
AstNameTable* names,
|
||||
std::string_view src,
|
||||
const Position& cursorPos,
|
||||
std::optional<Position> fragmentEndPosition
|
||||
);
|
||||
FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* root, const Position& cursorPos);
|
||||
|
||||
std::optional<FragmentParseResult> parseFragment(
|
||||
AstStatBlock* stale,
|
||||
AstStatBlock* mostRecentParse,
|
||||
AstNameTable* names,
|
||||
const SourceModule& srcModule,
|
||||
std::string_view src,
|
||||
const Position& cursorPos,
|
||||
std::optional<Position> fragmentEndPosition
|
||||
|
@ -114,9 +69,7 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
|
|||
const Position& cursorPos,
|
||||
std::optional<FrontendOptions> opts,
|
||||
std::string_view src,
|
||||
std::optional<Position> fragmentEndPosition,
|
||||
AstStatBlock* recentParse = nullptr,
|
||||
IFragmentAutocompleteReporter* reporter = nullptr
|
||||
std::optional<Position> fragmentEndPosition
|
||||
);
|
||||
|
||||
FragmentAutocompleteResult fragmentAutocomplete(
|
||||
|
@ -126,9 +79,7 @@ FragmentAutocompleteResult fragmentAutocomplete(
|
|||
Position cursorPosition,
|
||||
std::optional<FrontendOptions> opts,
|
||||
StringCompletionCallback callback,
|
||||
std::optional<Position> fragmentEndPosition = std::nullopt,
|
||||
AstStatBlock* recentParse = nullptr,
|
||||
IFragmentAutocompleteReporter* reporter = nullptr
|
||||
std::optional<Position> fragmentEndPosition = std::nullopt
|
||||
);
|
||||
|
||||
enum class FragmentAutocompleteStatus
|
||||
|
@ -147,10 +98,9 @@ struct FragmentAutocompleteStatusResult
|
|||
struct FragmentContext
|
||||
{
|
||||
std::string_view newSrc;
|
||||
const ParseResult& freshParse;
|
||||
const ParseResult& newAstRoot;
|
||||
std::optional<FrontendOptions> opts;
|
||||
std::optional<Position> DEPRECATED_fragmentEndPosition;
|
||||
IFragmentAutocompleteReporter* reporter = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "Luau/Set.h"
|
||||
#include "Luau/TypeCheckLimits.h"
|
||||
#include "Luau/Variant.h"
|
||||
#include "Luau/AnyTypeSummary.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
@ -31,8 +32,8 @@ struct ModuleResolver;
|
|||
struct ParseResult;
|
||||
struct HotComment;
|
||||
struct BuildQueueItem;
|
||||
struct BuildQueueWorkState;
|
||||
struct FrontendCancellationToken;
|
||||
struct AnyTypeSummary;
|
||||
|
||||
struct LoadDefinitionFileResult
|
||||
{
|
||||
|
@ -250,9 +251,6 @@ private:
|
|||
void checkBuildQueueItem(BuildQueueItem& item);
|
||||
void checkBuildQueueItems(std::vector<BuildQueueItem>& items);
|
||||
void recordItemResult(const BuildQueueItem& item);
|
||||
void performQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos);
|
||||
void sendQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos);
|
||||
void sendQueueCycleItemTask(std::shared_ptr<BuildQueueWorkState> state);
|
||||
|
||||
static LintResult classifyLints(const std::vector<LintWarning>& warnings, const Config& config);
|
||||
|
||||
|
@ -298,7 +296,6 @@ ModulePtr check(
|
|||
NotNull<ModuleResolver> moduleResolver,
|
||||
NotNull<FileResolver> fileResolver,
|
||||
const ScopePtr& globalScope,
|
||||
const ScopePtr& typeFunctionScope,
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||
FrontendOptions options,
|
||||
TypeCheckLimits limits
|
||||
|
@ -313,7 +310,6 @@ ModulePtr check(
|
|||
NotNull<ModuleResolver> moduleResolver,
|
||||
NotNull<FileResolver> fileResolver,
|
||||
const ScopePtr& globalScope,
|
||||
const ScopePtr& typeFunctionScope,
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||
FrontendOptions options,
|
||||
TypeCheckLimits limits,
|
||||
|
|
|
@ -8,39 +8,12 @@
|
|||
namespace Luau
|
||||
{
|
||||
|
||||
template<typename TID>
|
||||
struct GeneralizationParams
|
||||
{
|
||||
bool foundOutsideFunctions = false;
|
||||
size_t useCount = 0;
|
||||
Polarity polarity = Polarity::None;
|
||||
};
|
||||
|
||||
// Replace a single free type by its bounds according to the polarity provided.
|
||||
std::optional<TypeId> generalizeType(
|
||||
NotNull<TypeArena> arena,
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<Scope> scope,
|
||||
TypeId freeTy,
|
||||
const GeneralizationParams<TypeId>& params
|
||||
);
|
||||
|
||||
// Generalize one type pack
|
||||
std::optional<TypePackId> generalizeTypePack(
|
||||
NotNull<TypeArena> arena,
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<Scope> scope,
|
||||
TypePackId tp,
|
||||
const GeneralizationParams<TypePackId>& params
|
||||
);
|
||||
|
||||
void sealTable(NotNull<Scope> scope, TypeId ty);
|
||||
|
||||
std::optional<TypeId> generalize(
|
||||
NotNull<TypeArena> arena,
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<Scope> scope,
|
||||
NotNull<DenseHashSet<TypeId>> cachedTypes,
|
||||
TypeId ty
|
||||
NotNull<DenseHashSet<TypeId>> bakedTypes,
|
||||
TypeId ty,
|
||||
/* avoid sealing tables*/ bool avoidSealingTables = false
|
||||
);
|
||||
}
|
||||
|
|
|
@ -19,9 +19,7 @@ struct GlobalTypes
|
|||
|
||||
TypeArena globalTypes;
|
||||
SourceModule globalNames; // names for symbols entered into globalScope
|
||||
|
||||
ScopePtr globalScope; // shared by all modules
|
||||
ScopePtr globalTypeFunctionScope; // shared by all modules
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/TypeFwd.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct Scope;
|
||||
struct TypeArena;
|
||||
|
||||
void inferGenericPolarities(NotNull<TypeArena> arena, NotNull<Scope> scope, TypeId ty);
|
||||
void inferGenericPolarities(NotNull<TypeArena> arena, NotNull<Scope> scope, TypePackId tp);
|
||||
|
||||
} // namespace Luau
|
|
@ -67,19 +67,6 @@ public:
|
|||
return &pairs.at(it->second).second;
|
||||
}
|
||||
|
||||
V& operator[](const K& k)
|
||||
{
|
||||
auto it = indices.find(k);
|
||||
if (it == indices.end())
|
||||
{
|
||||
pairs.push_back(std::make_pair(k, V()));
|
||||
indices[k] = pairs.size() - 1;
|
||||
return pairs.back().second;
|
||||
}
|
||||
else
|
||||
return pairs.at(it->second).second;
|
||||
}
|
||||
|
||||
const_iterator begin() const
|
||||
{
|
||||
return pairs.begin();
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "Luau/ParseResult.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/AnyTypeSummary.h"
|
||||
#include "Luau/DataFlowGraph.h"
|
||||
|
||||
#include <memory>
|
||||
|
@ -15,16 +16,19 @@
|
|||
#include <unordered_map>
|
||||
#include <optional>
|
||||
|
||||
LUAU_FASTFLAG(LuauIncrementalAutocompleteCommentDetection)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
using LogLuauProc = void (*)(std::string_view, std::string_view);
|
||||
using LogLuauProc = void (*)(std::string_view);
|
||||
extern LogLuauProc logLuau;
|
||||
|
||||
void setLogLuau(LogLuauProc ll);
|
||||
void resetLogLuauProc();
|
||||
|
||||
struct Module;
|
||||
struct AnyTypeSummary;
|
||||
|
||||
using ScopePtr = std::shared_ptr<struct Scope>;
|
||||
using ModulePtr = std::shared_ptr<Module>;
|
||||
|
@ -82,10 +86,13 @@ struct Module
|
|||
TypeArena interfaceTypes;
|
||||
TypeArena internalTypes;
|
||||
|
||||
// Summary of Ast Nodes that either contain
|
||||
// user annotated anys or typechecker inferred anys
|
||||
AnyTypeSummary ats{};
|
||||
|
||||
// Scopes and AST types refer to parse data, so we need to keep that alive
|
||||
std::shared_ptr<Allocator> allocator;
|
||||
std::shared_ptr<AstNameTable> names;
|
||||
AstStatBlock* root = nullptr;
|
||||
|
||||
std::vector<std::pair<Location, ScopePtr>> scopes; // never empty
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/DataFlowGraph.h"
|
||||
#include "Luau/EqSatSimplification.h"
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/DataFlowGraph.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
enum struct Polarity : uint8_t
|
||||
{
|
||||
None = 0b000,
|
||||
Positive = 0b001,
|
||||
Negative = 0b010,
|
||||
Mixed = 0b011,
|
||||
Unknown = 0b100,
|
||||
};
|
||||
|
||||
inline Polarity operator|(Polarity lhs, Polarity rhs)
|
||||
{
|
||||
return Polarity(uint8_t(lhs) | uint8_t(rhs));
|
||||
}
|
||||
|
||||
inline Polarity& operator|=(Polarity& lhs, Polarity rhs)
|
||||
{
|
||||
lhs = lhs | rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline Polarity operator&(Polarity lhs, Polarity rhs)
|
||||
{
|
||||
return Polarity(uint8_t(lhs) & uint8_t(rhs));
|
||||
}
|
||||
|
||||
inline Polarity& operator&=(Polarity& lhs, Polarity rhs)
|
||||
{
|
||||
lhs = lhs & rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline bool isPositive(Polarity p)
|
||||
{
|
||||
return bool(p & Polarity::Positive);
|
||||
}
|
||||
|
||||
inline bool isNegative(Polarity p)
|
||||
{
|
||||
return bool(p & Polarity::Negative);
|
||||
}
|
||||
|
||||
inline bool isKnown(Polarity p)
|
||||
{
|
||||
return p != Polarity::Unknown;
|
||||
}
|
||||
|
||||
inline Polarity invert(Polarity p)
|
||||
{
|
||||
switch (p)
|
||||
{
|
||||
case Polarity::Positive:
|
||||
return Polarity::Negative;
|
||||
case Polarity::Negative:
|
||||
return Polarity::Positive;
|
||||
default:
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Luau
|
|
@ -31,4 +31,13 @@ struct OrderedMap
|
|||
}
|
||||
};
|
||||
|
||||
struct QuantifierResult
|
||||
{
|
||||
TypeId result;
|
||||
OrderedMap<TypeId, TypeId> insertedGenerics;
|
||||
OrderedMap<TypePackId, TypePackId> insertedGenericPacks;
|
||||
};
|
||||
|
||||
std::optional<QuantifierResult> quantify(TypeArena* arena, TypeId ty, Scope* scope);
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -53,7 +53,6 @@ struct Proposition
|
|||
{
|
||||
const RefinementKey* key;
|
||||
TypeId discriminantTy;
|
||||
bool implicitFromCall;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
|
@ -70,7 +69,6 @@ struct RefinementArena
|
|||
RefinementId disjunction(RefinementId lhs, RefinementId rhs);
|
||||
RefinementId equivalence(RefinementId lhs, RefinementId rhs);
|
||||
RefinementId proposition(const RefinementKey* key, TypeId discriminantTy);
|
||||
RefinementId implicitProposition(const RefinementKey* key, TypeId discriminantTy);
|
||||
|
||||
private:
|
||||
TypedAllocator<Refinement> allocator;
|
||||
|
|
|
@ -35,12 +35,12 @@ struct Scope
|
|||
explicit Scope(TypePackId returnType); // root scope
|
||||
explicit Scope(const ScopePtr& parent, int subLevel = 0); // child scope. Parent must not be nullptr.
|
||||
|
||||
ScopePtr parent; // null for the root
|
||||
const ScopePtr parent; // null for the root
|
||||
|
||||
// All the children of this scope.
|
||||
std::vector<NotNull<Scope>> children;
|
||||
std::unordered_map<Symbol, Binding> bindings;
|
||||
TypePackId returnType = nullptr;
|
||||
TypePackId returnType;
|
||||
std::optional<TypePackId> varargPack;
|
||||
|
||||
TypeLevel level;
|
||||
|
@ -59,8 +59,6 @@ struct Scope
|
|||
|
||||
std::optional<TypeId> lookup(Symbol sym) const;
|
||||
std::optional<TypeId> lookupUnrefinedType(DefId def) const;
|
||||
|
||||
std::optional<TypeId> lookupRValueRefinementType(DefId def) const;
|
||||
std::optional<TypeId> lookup(DefId def) const;
|
||||
std::optional<std::pair<TypeId, Scope*>> lookupEx(DefId def);
|
||||
std::optional<std::pair<Binding*, Scope*>> lookupEx(Symbol sym);
|
||||
|
@ -73,7 +71,6 @@ struct Scope
|
|||
|
||||
// WARNING: This function linearly scans for a string key of equal value! It is thus O(n**2)
|
||||
std::optional<Binding> linearSearchForBinding(const std::string& name, bool traverseScopeChain = true) const;
|
||||
std::optional<std::pair<Symbol, Binding>> linearSearchForBindingPair(const std::string& name, bool traverseScopeChain) const;
|
||||
|
||||
RefinementMap refinements;
|
||||
|
||||
|
@ -100,7 +97,6 @@ struct Scope
|
|||
std::unordered_map<Name, TypePackId> typeAliasTypePackParameters;
|
||||
|
||||
std::optional<std::vector<TypeId>> interiorFreeTypes;
|
||||
std::optional<std::vector<TypePackId>> interiorFreeTypePacks;
|
||||
};
|
||||
|
||||
// Returns true iff the left scope encloses the right scope. A Scope* equal to
|
||||
|
|
|
@ -14,7 +14,6 @@ namespace Luau
|
|||
struct TypeArena;
|
||||
struct BuiltinTypes;
|
||||
struct Unifier2;
|
||||
struct Subtyping;
|
||||
class AstExpr;
|
||||
|
||||
TypeId matchLiteralType(
|
||||
|
@ -23,7 +22,6 @@ TypeId matchLiteralType(
|
|||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<TypeArena> arena,
|
||||
NotNull<Unifier2> unifier,
|
||||
NotNull<Subtyping> subtyping,
|
||||
TypeId expectedType,
|
||||
TypeId exprType,
|
||||
const AstExpr* expr,
|
||||
|
|
|
@ -192,6 +192,16 @@ struct TxnLog
|
|||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingTypePack* changeLevel(TypePackId tp, TypeLevel newLevel);
|
||||
|
||||
// Queues the replacement of a type's scope with the provided scope.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingType* changeScope(TypeId ty, NotNull<Scope> scope);
|
||||
|
||||
// Queues the replacement of a type pack's scope with the provided scope.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingTypePack* changeScope(TypePackId tp, NotNull<Scope> scope);
|
||||
|
||||
// Queues a replacement of a table type with another table type with a new
|
||||
// indexer.
|
||||
//
|
||||
|
|
|
@ -5,11 +5,10 @@
|
|||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Refinement.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/Polarity.h"
|
||||
#include "Luau/Predicate.h"
|
||||
#include "Luau/Refinement.h"
|
||||
#include "Luau/Unifiable.h"
|
||||
#include "Luau/Variant.h"
|
||||
#include "Luau/VecDeque.h"
|
||||
|
@ -20,6 +19,7 @@
|
|||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
LUAU_FASTINT(LuauTableTypeMaximumStringifierLength)
|
||||
|
@ -72,7 +72,7 @@ struct FreeType
|
|||
// New constructors
|
||||
explicit FreeType(TypeLevel level, TypeId lowerBound, TypeId upperBound);
|
||||
// This one got promoted to explicit
|
||||
explicit FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound, Polarity polarity = Polarity::Unknown);
|
||||
explicit FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound);
|
||||
explicit FreeType(Scope* scope, TypeLevel level, TypeId lowerBound, TypeId upperBound);
|
||||
// Old constructors
|
||||
explicit FreeType(TypeLevel level);
|
||||
|
@ -91,8 +91,6 @@ struct FreeType
|
|||
// Only used under local type inference
|
||||
TypeId lowerBound = nullptr;
|
||||
TypeId upperBound = nullptr;
|
||||
|
||||
Polarity polarity = Polarity::Unknown;
|
||||
};
|
||||
|
||||
struct GenericType
|
||||
|
@ -101,8 +99,8 @@ struct GenericType
|
|||
GenericType();
|
||||
|
||||
explicit GenericType(TypeLevel level);
|
||||
explicit GenericType(const Name& name, Polarity polarity = Polarity::Unknown);
|
||||
explicit GenericType(Scope* scope, Polarity polarity = Polarity::Unknown);
|
||||
explicit GenericType(const Name& name);
|
||||
explicit GenericType(Scope* scope);
|
||||
|
||||
GenericType(TypeLevel level, const Name& name);
|
||||
GenericType(Scope* scope, const Name& name);
|
||||
|
@ -112,8 +110,6 @@ struct GenericType
|
|||
Scope* scope = nullptr;
|
||||
Name name;
|
||||
bool explicitName = false;
|
||||
|
||||
Polarity polarity = Polarity::Unknown;
|
||||
};
|
||||
|
||||
// When an equality constraint is found, it is then "bound" to that type,
|
||||
|
@ -352,8 +348,10 @@ struct FunctionType
|
|||
);
|
||||
|
||||
// Local monomorphic function
|
||||
FunctionType(TypeLevel level, TypePackId argTypes, TypePackId retTypes, std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
||||
FunctionType(
|
||||
TypeLevel level,
|
||||
Scope* scope,
|
||||
TypePackId argTypes,
|
||||
TypePackId retTypes,
|
||||
std::optional<FunctionDefinition> defn = {},
|
||||
|
@ -370,6 +368,16 @@ struct FunctionType
|
|||
std::optional<FunctionDefinition> defn = {},
|
||||
bool hasSelf = false
|
||||
);
|
||||
FunctionType(
|
||||
TypeLevel level,
|
||||
Scope* scope,
|
||||
std::vector<TypeId> generics,
|
||||
std::vector<TypePackId> genericPacks,
|
||||
TypePackId argTypes,
|
||||
TypePackId retTypes,
|
||||
std::optional<FunctionDefinition> defn = {},
|
||||
bool hasSelf = false
|
||||
);
|
||||
|
||||
std::optional<FunctionDefinition> definition;
|
||||
/// These should all be generic
|
||||
|
@ -378,6 +386,7 @@ struct FunctionType
|
|||
std::vector<std::optional<FunctionArgument>> argNames;
|
||||
Tags tags;
|
||||
TypeLevel level;
|
||||
Scope* scope = nullptr;
|
||||
TypePackId argTypes;
|
||||
TypePackId retTypes;
|
||||
std::shared_ptr<MagicFunction> magic = nullptr;
|
||||
|
@ -387,7 +396,6 @@ struct FunctionType
|
|||
// this flag is used as an optimization to exit early from procedures that manipulate free or generic types.
|
||||
bool hasNoFreeOrGenericTypes = false;
|
||||
bool isCheckedFunction = false;
|
||||
bool isDeprecatedFunction = false;
|
||||
};
|
||||
|
||||
enum class TableState
|
||||
|
@ -464,9 +472,7 @@ struct Property
|
|||
TypeId type() const;
|
||||
void setType(TypeId ty);
|
||||
|
||||
// If this property has a present `writeTy`, set it equal to the `readTy`.
|
||||
// This is to ensure that if we normalize a property that has divergent
|
||||
// read and write types, we make them converge (for now).
|
||||
// Sets the write type of this property to the read type.
|
||||
void makeShared();
|
||||
|
||||
bool isShared() const;
|
||||
|
@ -511,6 +517,9 @@ struct TableType
|
|||
std::optional<TypeId> boundTo;
|
||||
Tags tags;
|
||||
|
||||
// Methods of this table that have an untyped self will use the same shared self type.
|
||||
std::optional<TypeId> selfTy;
|
||||
|
||||
// We track the number of as-yet-unadded properties to unsealed tables.
|
||||
// Some constraints will use this information to decide whether or not they
|
||||
// are able to dispatch.
|
||||
|
@ -613,6 +622,7 @@ struct UserDefinedFunctionData
|
|||
AstStatTypeFunction* definition = nullptr;
|
||||
|
||||
DenseHashMap<Name, std::pair<AstStatTypeFunction*, size_t>> environment{""};
|
||||
DenseHashMap<Name, AstStatTypeFunction*> environment_DEPRECATED{""};
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -872,9 +882,6 @@ struct TypeFun
|
|||
*/
|
||||
TypeId type;
|
||||
|
||||
// The location of where this TypeFun was defined, if available
|
||||
std::optional<Location> definitionLocation;
|
||||
|
||||
TypeFun() = default;
|
||||
|
||||
explicit TypeFun(TypeId ty)
|
||||
|
@ -882,23 +889,16 @@ struct TypeFun
|
|||
{
|
||||
}
|
||||
|
||||
TypeFun(std::vector<GenericTypeDefinition> typeParams, TypeId type, std::optional<Location> definitionLocation = std::nullopt)
|
||||
TypeFun(std::vector<GenericTypeDefinition> typeParams, TypeId type)
|
||||
: typeParams(std::move(typeParams))
|
||||
, type(type)
|
||||
, definitionLocation(definitionLocation)
|
||||
{
|
||||
}
|
||||
|
||||
TypeFun(
|
||||
std::vector<GenericTypeDefinition> typeParams,
|
||||
std::vector<GenericTypePackDefinition> typePackParams,
|
||||
TypeId type,
|
||||
std::optional<Location> definitionLocation = std::nullopt
|
||||
)
|
||||
TypeFun(std::vector<GenericTypeDefinition> typeParams, std::vector<GenericTypePackDefinition> typePackParams, TypeId type)
|
||||
: typeParams(std::move(typeParams))
|
||||
, typePackParams(std::move(typePackParams))
|
||||
, type(type)
|
||||
, definitionLocation(definitionLocation)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -1202,7 +1202,7 @@ private:
|
|||
}
|
||||
};
|
||||
|
||||
TypeId freshType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, Scope* scope, Polarity polarity = Polarity::Unknown);
|
||||
TypeId freshType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, Scope* scope);
|
||||
|
||||
using TypeIdPredicate = std::function<std::optional<TypeId>(TypeId)>;
|
||||
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Polarity.h"
|
||||
#include "Luau/TypedAllocator.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypePack.h"
|
||||
|
@ -41,7 +40,7 @@ struct TypeArena
|
|||
TypeId freshType_DEPRECATED(Scope* scope);
|
||||
TypeId freshType_DEPRECATED(Scope* scope, TypeLevel level);
|
||||
|
||||
TypePackId freshTypePack(Scope* scope, Polarity polarity = Polarity::Unknown);
|
||||
TypePackId freshTypePack(Scope* scope);
|
||||
|
||||
TypePackId addTypePack(std::initializer_list<TypeId> types);
|
||||
TypePackId addTypePack(std::vector<TypeId> types, std::optional<TypePackId> tail = {});
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
#include "Luau/TypeOrPack.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -40,29 +38,18 @@ struct Reasonings
|
|||
|
||||
std::string toString()
|
||||
{
|
||||
if (FFlag::LuauImproveTypePathsInErrors && reasons.empty())
|
||||
return "";
|
||||
|
||||
// DenseHashSet ordering is entirely undefined, so we want to
|
||||
// sort the reasons here to achieve a stable error
|
||||
// stringification.
|
||||
std::sort(reasons.begin(), reasons.end());
|
||||
std::string allReasons = FFlag::LuauImproveTypePathsInErrors ? "\nthis is because " : "";
|
||||
std::string allReasons;
|
||||
bool first = true;
|
||||
for (const std::string& reason : reasons)
|
||||
{
|
||||
if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
if (reasons.size() > 1)
|
||||
allReasons += "\n\t * ";
|
||||
}
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
allReasons += "\n\t";
|
||||
}
|
||||
allReasons += "\n\t";
|
||||
|
||||
allReasons += reason;
|
||||
}
|
||||
|
|
|
@ -48,9 +48,6 @@ struct TypeFunctionRuntime
|
|||
// Evaluation of type functions should only be performed in the absence of parse errors in the source module
|
||||
bool allowEvaluation = true;
|
||||
|
||||
// Root scope in which the type function operates in, set up by ConstraintGenerator
|
||||
ScopePtr rootScope;
|
||||
|
||||
// Output created by 'print' function
|
||||
std::vector<std::string> messages;
|
||||
|
||||
|
@ -177,7 +174,6 @@ struct FunctionGraphReductionResult
|
|||
DenseHashSet<TypePackId> blockedPacks{nullptr};
|
||||
DenseHashSet<TypeId> reducedTypes{nullptr};
|
||||
DenseHashSet<TypePackId> reducedPacks{nullptr};
|
||||
DenseHashSet<TypeId> irreducibleTypes{nullptr};
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -248,8 +244,6 @@ struct BuiltinTypeFunctions
|
|||
TypeFunction setmetatableFunc;
|
||||
TypeFunction getmetatableFunc;
|
||||
|
||||
TypeFunction weakoptionalFunc;
|
||||
|
||||
void addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const;
|
||||
};
|
||||
|
||||
|
|
|
@ -223,6 +223,8 @@ struct TypeFunctionClassType
|
|||
std::optional<TypeFunctionTypeId> writeParent;
|
||||
|
||||
TypeId classTy;
|
||||
|
||||
std::string name_DEPRECATED;
|
||||
};
|
||||
|
||||
struct TypeFunctionGenericType
|
||||
|
|
|
@ -28,8 +28,14 @@ struct TypeFunctionRuntimeBuilderState
|
|||
{
|
||||
NotNull<TypeFunctionContext> ctx;
|
||||
|
||||
// Mapping of class name to ClassType
|
||||
// Invariant: users can not create a new class types -> any class types that get deserialized must have been an argument to the type function
|
||||
// Using this invariant, whenever a ClassType is serialized, we can put it into this map
|
||||
// whenever a ClassType is deserialized, we can use this map to return the corresponding value
|
||||
DenseHashMap<std::string, TypeId> classesSerialized_DEPRECATED{{}};
|
||||
|
||||
// List of errors that occur during serialization/deserialization
|
||||
// At every iteration of serialization/deserialization, if this list.size() != 0, we halt the process
|
||||
// At every iteration of serialization/deserialzation, if this list.size() != 0, we halt the process
|
||||
std::vector<std::string> errors{};
|
||||
|
||||
TypeFunctionRuntimeBuilderState(NotNull<TypeFunctionContext> ctx)
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/Polarity.h"
|
||||
#include "Luau/TypeFwd.h"
|
||||
#include "Luau/Unifiable.h"
|
||||
#include "Luau/Variant.h"
|
||||
#include "Luau/TypeFwd.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <optional>
|
||||
#include <set>
|
||||
|
@ -27,14 +26,12 @@ struct TypeFunctionInstanceTypePack;
|
|||
struct FreeTypePack
|
||||
{
|
||||
explicit FreeTypePack(TypeLevel level);
|
||||
explicit FreeTypePack(Scope* scope, Polarity polarity = Polarity::Unknown);
|
||||
explicit FreeTypePack(Scope* scope);
|
||||
FreeTypePack(Scope* scope, TypeLevel level);
|
||||
|
||||
int index;
|
||||
TypeLevel level;
|
||||
Scope* scope = nullptr;
|
||||
|
||||
Polarity polarity = Polarity::Unknown;
|
||||
};
|
||||
|
||||
struct GenericTypePack
|
||||
|
@ -43,7 +40,7 @@ struct GenericTypePack
|
|||
GenericTypePack();
|
||||
explicit GenericTypePack(TypeLevel level);
|
||||
explicit GenericTypePack(const Name& name);
|
||||
explicit GenericTypePack(Scope* scope, Polarity polarity = Polarity::Unknown);
|
||||
explicit GenericTypePack(Scope* scope);
|
||||
GenericTypePack(TypeLevel level, const Name& name);
|
||||
GenericTypePack(Scope* scope, const Name& name);
|
||||
|
||||
|
@ -52,8 +49,6 @@ struct GenericTypePack
|
|||
Scope* scope = nullptr;
|
||||
Name name;
|
||||
bool explicitName = false;
|
||||
|
||||
Polarity polarity = Polarity::Unknown;
|
||||
};
|
||||
|
||||
using BoundTypePack = Unifiable::Bound<TypePackId>;
|
||||
|
@ -105,9 +100,9 @@ struct TypeFunctionInstanceTypePack
|
|||
|
||||
struct TypePackVar
|
||||
{
|
||||
explicit TypePackVar(const TypePackVariant& tp);
|
||||
explicit TypePackVar(TypePackVariant&& tp);
|
||||
TypePackVar(TypePackVariant&& tp, bool persistent);
|
||||
explicit TypePackVar(const TypePackVariant& ty);
|
||||
explicit TypePackVar(TypePackVariant&& ty);
|
||||
TypePackVar(TypePackVariant&& ty, bool persistent);
|
||||
|
||||
bool operator==(const TypePackVar& rhs) const;
|
||||
|
||||
|
@ -174,7 +169,6 @@ struct TypePackIterator
|
|||
|
||||
private:
|
||||
TypePackId currentTypePack = nullptr;
|
||||
TypePackId tailCycleCheck = nullptr;
|
||||
const TypePack* tp = nullptr;
|
||||
size_t currentIndex = 0;
|
||||
|
||||
|
|
|
@ -42,19 +42,9 @@ struct Property
|
|||
/// element.
|
||||
struct Index
|
||||
{
|
||||
enum class Variant
|
||||
{
|
||||
Pack,
|
||||
Union,
|
||||
Intersection
|
||||
};
|
||||
|
||||
/// The 0-based index to use for the lookup.
|
||||
size_t index;
|
||||
|
||||
/// The sort of thing we're indexing from, this is used in stringifying the type path for errors.
|
||||
Variant variant;
|
||||
|
||||
bool operator==(const Index& other) const;
|
||||
};
|
||||
|
||||
|
@ -215,9 +205,6 @@ using Path = TypePath::Path;
|
|||
/// terribly clear to end users of the Luau type system.
|
||||
std::string toString(const TypePath::Path& path, bool prefixDot = false);
|
||||
|
||||
/// Converts a Path to a human readable string for error reporting.
|
||||
std::string toStringHuman(const TypePath::Path& path);
|
||||
|
||||
std::optional<TypeOrPack> traverse(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);
|
||||
std::optional<TypeOrPack> traverse(TypePackId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);
|
||||
|
||||
|
|
|
@ -289,6 +289,4 @@ std::vector<TypeId> findBlockedArgTypesIn(AstExprCall* expr, NotNull<DenseHashMa
|
|||
*/
|
||||
void trackInteriorFreeType(Scope* scope, TypeId ty);
|
||||
|
||||
void trackInteriorFreeTypePack(Scope* scope, TypePackId tp);
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -44,12 +44,6 @@ struct Unifier2
|
|||
// Mapping from generic type packs to `TypePack`s of free types to be used in instantiation.
|
||||
DenseHashMap<TypePackId, TypePackId> genericPackSubstitutions{nullptr};
|
||||
|
||||
// Unification sometimes results in the creation of new free types.
|
||||
// We collect them here so that other systems can perform necessary
|
||||
// bookkeeping.
|
||||
std::vector<TypeId> newFreshTypes;
|
||||
std::vector<TypePackId> newFreshTypePacks;
|
||||
|
||||
int recursionCount = 0;
|
||||
int recursionLimit = 0;
|
||||
|
||||
|
@ -93,9 +87,6 @@ struct Unifier2
|
|||
bool unify(const AnyType* subAny, const TableType* superTable);
|
||||
bool unify(const TableType* subTable, const AnyType* superAny);
|
||||
|
||||
bool unify(const MetatableType* subMetatable, const AnyType*);
|
||||
bool unify(const AnyType*, const MetatableType* superMetatable);
|
||||
|
||||
// TODO think about this one carefully. We don't do unions or intersections of type packs
|
||||
bool unify(TypePackId subTp, TypePackId superTp);
|
||||
|
||||
|
@ -119,9 +110,6 @@ private:
|
|||
// Returns true if needle occurs within haystack already. ie if we bound
|
||||
// needle to haystack, would a cyclic TypePack result?
|
||||
OccursCheckResult occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack);
|
||||
|
||||
TypeId freshType(NotNull<Scope> scope, Polarity polarity);
|
||||
TypePackId freshTypePack(NotNull<Scope> scope, Polarity polarity);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
|
902
Analysis/src/AnyTypeSummary.cpp
Normal file
902
Analysis/src/AnyTypeSummary.cpp
Normal 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
|
|
@ -1065,11 +1065,6 @@ struct AstJsonEncoder : public AstVisitor
|
|||
);
|
||||
}
|
||||
|
||||
void write(class AstTypeOptional* node)
|
||||
{
|
||||
writeNode(node, "AstTypeOptional", [&]() {});
|
||||
}
|
||||
|
||||
void write(class AstTypeUnion* node)
|
||||
{
|
||||
writeNode(
|
||||
|
@ -1151,8 +1146,6 @@ struct AstJsonEncoder : public AstVisitor
|
|||
return writeString("checked");
|
||||
case AstAttr::Type::Native:
|
||||
return writeString("native");
|
||||
case AstAttr::Type::Deprecated:
|
||||
return writeString("deprecated");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
|
||||
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -41,13 +43,24 @@ struct AutocompleteNodeFinder : public AstVisitor
|
|||
|
||||
bool visit(AstStat* stat) override
|
||||
{
|
||||
// Consider 'local myLocal = 4;|' and 'local myLocal = 4', where '|' is the cursor position. In both cases, the cursor position is equal
|
||||
// to `AstStatLocal.location.end`. However, in the first case (semicolon), we are starting a new statement, whilst in the second case
|
||||
// (no semicolon) we are still part of the AstStatLocal, hence the different comparison check.
|
||||
if (stat->location.begin < pos && (stat->hasSemicolon ? pos < stat->location.end : pos <= stat->location.end))
|
||||
if (FFlag::LuauExtendStatEndPosWithSemicolon)
|
||||
{
|
||||
ancestry.push_back(stat);
|
||||
return true;
|
||||
// Consider 'local myLocal = 4;|' and 'local myLocal = 4', where '|' is the cursor position. In both cases, the cursor position is equal
|
||||
// to `AstStatLocal.location.end`. However, in the first case (semicolon), we are starting a new statement, whilst in the second case
|
||||
// (no semicolon) we are still part of the AstStatLocal, hence the different comparison check.
|
||||
if (stat->location.begin < pos && (stat->hasSemicolon ? pos < stat->location.end : pos <= stat->location.end))
|
||||
{
|
||||
ancestry.push_back(stat);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (stat->location.begin < pos && pos <= stat->location.end)
|
||||
{
|
||||
ancestry.push_back(stat);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
@ -23,14 +23,10 @@
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTINT(LuauTypeInferIterationLimit)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames)
|
||||
|
||||
LUAU_FASTFLAG(LuauExposeRequireByStringAutocomplete)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteRefactorsForIncrementalAutocomplete)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUsesModuleForTypeCompatibility)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUnionCopyPreviousSeen)
|
||||
|
||||
static const std::unordered_set<std::string> kStatementStartingKeywords =
|
||||
{"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
|
||||
|
@ -485,21 +481,6 @@ static void autocompleteProps(
|
|||
AutocompleteEntryMap inner;
|
||||
std::unordered_set<TypeId> innerSeen;
|
||||
|
||||
// If we don't do this, and we have the misfortune of receiving a
|
||||
// recursive union like:
|
||||
//
|
||||
// t1 where t1 = t1 | Class
|
||||
//
|
||||
// Then we are on a one way journey to a stack overflow.
|
||||
if (FFlag::LuauAutocompleteUnionCopyPreviousSeen)
|
||||
{
|
||||
for (auto ty : seen)
|
||||
{
|
||||
if (is<UnionType, IntersectionType>(ty))
|
||||
innerSeen.insert(ty);
|
||||
}
|
||||
}
|
||||
|
||||
if (isNil(*iter))
|
||||
{
|
||||
++iter;
|
||||
|
@ -1362,15 +1343,6 @@ static AutocompleteContext autocompleteExpression(
|
|||
|
||||
AstNode* node = ancestry.rbegin()[0];
|
||||
|
||||
if (FFlag::DebugLuauMagicVariableNames)
|
||||
{
|
||||
InternalErrorReporter ice;
|
||||
if (auto local = node->as<AstExprLocal>(); local && local->local->name == "_luau_autocomplete_ice")
|
||||
ice.ice("_luau_autocomplete_ice encountered", local->location);
|
||||
if (auto global = node->as<AstExprGlobal>(); global && global->name == "_luau_autocomplete_ice")
|
||||
ice.ice("_luau_autocomplete_ice encountered", global->location);
|
||||
}
|
||||
|
||||
if (node->is<AstExprIndexName>())
|
||||
{
|
||||
if (auto it = module.astTypes.find(node->asExpr()))
|
||||
|
@ -1537,14 +1509,10 @@ static std::optional<AutocompleteEntryMap> convertRequireSuggestionsToAutocomple
|
|||
return std::nullopt;
|
||||
|
||||
AutocompleteEntryMap result;
|
||||
for (RequireSuggestion& suggestion : *suggestions)
|
||||
for (const RequireSuggestion& suggestion : *suggestions)
|
||||
{
|
||||
AutocompleteEntry entry = {AutocompleteEntryKind::RequirePath};
|
||||
entry.insertText = std::move(suggestion.fullPath);
|
||||
if (FFlag::LuauExposeRequireByStringAutocomplete)
|
||||
{
|
||||
entry.tags = std::move(suggestion.tags);
|
||||
}
|
||||
result[std::move(suggestion.label)] = std::move(entry);
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -3,23 +3,22 @@
|
|||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Clone.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/ConstraintGenerator.h"
|
||||
#include "Luau/ConstraintSolver.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Frontend.h"
|
||||
#include "Luau/InferPolarity.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/Subtyping.h"
|
||||
#include "Luau/Symbol.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/ConstraintSolver.h"
|
||||
#include "Luau/ConstraintGenerator.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/TypeChecker2.h"
|
||||
#include "Luau/TypeFunction.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Subtyping.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
|
@ -30,12 +29,11 @@
|
|||
*/
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauNonReentrantGeneralization)
|
||||
LUAU_FASTFLAGVARIABLE(LuauStringFormatErrorSuppression)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
|
||||
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFreezeIgnorePersistent)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFollowTableFreeze)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -249,7 +247,6 @@ void addGlobalBinding(GlobalTypes& globals, const ScopePtr& scope, const std::st
|
|||
|
||||
void addGlobalBinding(GlobalTypes& globals, const ScopePtr& scope, const std::string& name, Binding binding)
|
||||
{
|
||||
inferGenericPolarities(NotNull{&globals.globalTypes}, NotNull{scope.get()}, binding.typeId);
|
||||
scope->bindings[globals.globalNames.names->getOrAdd(name.c_str())] = binding;
|
||||
}
|
||||
|
||||
|
@ -291,22 +288,6 @@ void assignPropDocumentationSymbols(TableType::Props& props, const std::string&
|
|||
}
|
||||
}
|
||||
|
||||
static void finalizeGlobalBindings(ScopePtr scope)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauUserTypeFunTypecheck);
|
||||
|
||||
for (const auto& pair : scope->bindings)
|
||||
{
|
||||
persist(pair.second.typeId);
|
||||
|
||||
if (TableType* ttv = getMutable<TableType>(pair.second.typeId))
|
||||
{
|
||||
if (!ttv->name)
|
||||
ttv->name = "typeof(" + toString(pair.first) + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeCheckForAutocomplete)
|
||||
{
|
||||
LUAU_ASSERT(!globals.globalTypes.types.isFrozen());
|
||||
|
@ -314,9 +295,6 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
|||
|
||||
TypeArena& arena = globals.globalTypes;
|
||||
NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes;
|
||||
Scope* globalScope = nullptr; // NotNull<Scope> when removing FFlag::LuauNonReentrantGeneralization
|
||||
if (FFlag::LuauNonReentrantGeneralization)
|
||||
globalScope = globals.globalScope.get();
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
builtinTypeFunctions().addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()});
|
||||
|
@ -326,8 +304,8 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
|||
);
|
||||
LUAU_ASSERT(loadResult.success);
|
||||
|
||||
TypeId genericK = arena.addType(GenericType{globalScope, "K"});
|
||||
TypeId genericV = arena.addType(GenericType{globalScope, "V"});
|
||||
TypeId genericK = arena.addType(GenericType{"K"});
|
||||
TypeId genericV = arena.addType(GenericType{"V"});
|
||||
TypeId mapOfKtoV = arena.addType(TableType{{}, TableIndexer(genericK, genericV), globals.globalScope->level, TableState::Generic});
|
||||
|
||||
std::optional<TypeId> stringMetatableTy = getMetatable(builtinTypes->stringType, builtinTypes);
|
||||
|
@ -375,7 +353,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
|||
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil)
|
||||
addGlobalBinding(globals, "pairs", arena.addType(FunctionType{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
|
||||
|
||||
TypeId genericMT = arena.addType(GenericType{globalScope, "MT"});
|
||||
TypeId genericMT = arena.addType(GenericType{"MT"});
|
||||
|
||||
TableType tab{TableState::Generic, globals.globalScope->level};
|
||||
TypeId tabTy = arena.addType(tab);
|
||||
|
@ -387,7 +365,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
|||
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
TypeId genericT = arena.addType(GenericType{globalScope, "T"});
|
||||
TypeId genericT = arena.addType(GenericType{"T"});
|
||||
TypeId tMetaMT = arena.addType(MetatableType{genericT, genericMT});
|
||||
|
||||
// clang-format off
|
||||
|
@ -421,21 +399,14 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
|||
// clang-format on
|
||||
}
|
||||
|
||||
if (FFlag::LuauUserTypeFunTypecheck)
|
||||
for (const auto& pair : globals.globalScope->bindings)
|
||||
{
|
||||
finalizeGlobalBindings(globals.globalScope);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto& pair : globals.globalScope->bindings)
|
||||
{
|
||||
persist(pair.second.typeId);
|
||||
persist(pair.second.typeId);
|
||||
|
||||
if (TableType* ttv = getMutable<TableType>(pair.second.typeId))
|
||||
{
|
||||
if (!ttv->name)
|
||||
ttv->name = "typeof(" + toString(pair.first) + ")";
|
||||
}
|
||||
if (TableType* ttv = getMutable<TableType>(pair.second.typeId))
|
||||
{
|
||||
if (!ttv->name)
|
||||
ttv->name = "typeof(" + toString(pair.first) + ")";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -444,7 +415,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
|||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
// declare function assert<T>(value: T, errorMessage: string?): intersect<T, ~(false?)>
|
||||
TypeId genericT = arena.addType(GenericType{globalScope, "T"});
|
||||
TypeId genericT = arena.addType(GenericType{"T"});
|
||||
TypeId refinedTy = arena.addType(TypeFunctionInstanceType{
|
||||
NotNull{&builtinTypeFunctions().intersectFunc}, {genericT, arena.addType(NegationType{builtinTypes->falsyType})}, {}
|
||||
});
|
||||
|
@ -467,16 +438,12 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
|||
// the top table type. We do the best we can by modelling these
|
||||
// functions using unconstrained generics. It's not quite right,
|
||||
// but it'll be ok for now.
|
||||
TypeId genericTy = arena.addType(GenericType{globalScope, "T"});
|
||||
TypeId genericTy = arena.addType(GenericType{"T"});
|
||||
TypePackId thePack = arena.addTypePack({genericTy});
|
||||
TypeId idTyWithMagic = arena.addType(FunctionType{{genericTy}, {}, thePack, thePack});
|
||||
ttv->props["freeze"] = makeProperty(idTyWithMagic, "@luau/global/table.freeze");
|
||||
if (globalScope)
|
||||
inferGenericPolarities(NotNull{&globals.globalTypes}, NotNull{globalScope}, idTyWithMagic);
|
||||
|
||||
TypeId idTy = arena.addType(FunctionType{{genericTy}, {}, thePack, thePack});
|
||||
if (globalScope)
|
||||
inferGenericPolarities(NotNull{&globals.globalTypes}, NotNull{globalScope}, idTy);
|
||||
ttv->props["clone"] = makeProperty(idTy, "@luau/global/table.clone");
|
||||
}
|
||||
else
|
||||
|
@ -500,59 +467,6 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
|||
TypeId requireTy = getGlobalBinding(globals, "require");
|
||||
attachTag(requireTy, kRequireTagName);
|
||||
attachMagicFunction(requireTy, std::make_shared<MagicRequire>());
|
||||
|
||||
if (FFlag::LuauUserTypeFunTypecheck)
|
||||
{
|
||||
// Global scope cannot be the parent of the type checking environment because it can be changed by the embedder
|
||||
globals.globalTypeFunctionScope->exportedTypeBindings = globals.globalScope->exportedTypeBindings;
|
||||
globals.globalTypeFunctionScope->builtinTypeNames = globals.globalScope->builtinTypeNames;
|
||||
|
||||
// Type function runtime also removes a few standard libraries and globals, so we will take only the ones that are defined
|
||||
static const char* typeFunctionRuntimeBindings[] = {
|
||||
// Libraries
|
||||
"math",
|
||||
"table",
|
||||
"string",
|
||||
"bit32",
|
||||
"utf8",
|
||||
"buffer",
|
||||
|
||||
// Globals
|
||||
"assert",
|
||||
"error",
|
||||
"print",
|
||||
"next",
|
||||
"ipairs",
|
||||
"pairs",
|
||||
"select",
|
||||
"unpack",
|
||||
"getmetatable",
|
||||
"setmetatable",
|
||||
"rawget",
|
||||
"rawset",
|
||||
"rawlen",
|
||||
"rawequal",
|
||||
"tonumber",
|
||||
"tostring",
|
||||
"type",
|
||||
"typeof",
|
||||
};
|
||||
|
||||
for (auto& name : typeFunctionRuntimeBindings)
|
||||
{
|
||||
AstName astName = globals.globalNames.names->get(name);
|
||||
LUAU_ASSERT(astName.value);
|
||||
|
||||
globals.globalTypeFunctionScope->bindings[astName] = globals.globalScope->bindings[astName];
|
||||
}
|
||||
|
||||
LoadDefinitionFileResult typeFunctionLoadResult = frontend.loadDefinitionFile(
|
||||
globals, globals.globalTypeFunctionScope, getTypeFunctionDefinitionSource(), "@luau", /* captureComments */ false, false
|
||||
);
|
||||
LUAU_ASSERT(typeFunctionLoadResult.success);
|
||||
|
||||
finalizeGlobalBindings(globals.globalTypeFunctionScope);
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<TypeId> parseFormatString(NotNull<BuiltinTypes> builtinTypes, const char* data, size_t size)
|
||||
|
@ -722,17 +636,25 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
|
|||
|
||||
if (!result.isSubtype)
|
||||
{
|
||||
switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy))
|
||||
if (FFlag::LuauStringFormatErrorSuppression)
|
||||
{
|
||||
case ErrorSuppression::Suppress:
|
||||
break;
|
||||
case ErrorSuppression::NormalizationFailed:
|
||||
break;
|
||||
case ErrorSuppression::DoNotSuppress:
|
||||
Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result);
|
||||
switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy))
|
||||
{
|
||||
case ErrorSuppression::Suppress:
|
||||
break;
|
||||
case ErrorSuppression::NormalizationFailed:
|
||||
break;
|
||||
case ErrorSuppression::DoNotSuppress:
|
||||
Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result);
|
||||
|
||||
if (!reasonings.suppressed)
|
||||
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
|
||||
if (!reasonings.suppressed)
|
||||
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result);
|
||||
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1522,7 +1444,7 @@ bool MagicClone::infer(const MagicFunctionCallContext& context)
|
|||
return false;
|
||||
|
||||
CloneState cloneState{context.solver->builtinTypes};
|
||||
TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ true);
|
||||
TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ FFlag::LuauFreezeIgnorePersistent);
|
||||
|
||||
if (auto tableType = getMutable<TableType>(resultType))
|
||||
{
|
||||
|
@ -1559,7 +1481,7 @@ static std::optional<TypeId> freezeTable(TypeId inputType, const MagicFunctionCa
|
|||
{
|
||||
// Clone the input type, this will become our final result type after we mutate it.
|
||||
CloneState cloneState{context.solver->builtinTypes};
|
||||
TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ true);
|
||||
TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ FFlag::LuauFreezeIgnorePersistent);
|
||||
auto tableTy = getMutable<TableType>(resultType);
|
||||
// `clone` should not break this.
|
||||
LUAU_ASSERT(tableTy);
|
||||
|
@ -1609,17 +1531,6 @@ bool MagicFreeze::infer(const MagicFunctionCallContext& context)
|
|||
std::optional<DefId> resultDef = dfg->getDefOptional(targetExpr);
|
||||
std::optional<TypeId> resultTy = resultDef ? scope->lookup(*resultDef) : std::nullopt;
|
||||
|
||||
if (FFlag::LuauMagicFreezeCheckBlocked)
|
||||
{
|
||||
if (resultTy && !get<BlockedType>(resultTy))
|
||||
{
|
||||
// If there's an existing result type but it's _not_ blocked, then
|
||||
// we aren't type stating this builtin and should fall back to
|
||||
// regular inference.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<TypeId> frozenType = freezeTable(inputType, context);
|
||||
|
||||
if (!frozenType)
|
||||
|
|
|
@ -1,20 +1,17 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Clone.h"
|
||||
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/Unifiable.h"
|
||||
#include "Luau/VisitType.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauFreezeIgnorePersistent)
|
||||
|
||||
// For each `Luau::clone` call, we will clone only up to N amount of types _and_ packs, as controlled by this limit.
|
||||
LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000)
|
||||
LUAU_FASTFLAGVARIABLE(LuauClonedTableAndFunctionTypesMustHaveScopes)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDoNotClonePersistentBindings)
|
||||
LUAU_FASTFLAG(LuauIncrementalAutocompleteDemandBasedCloning)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -31,8 +28,6 @@ const T* get(const Kind& kind)
|
|||
|
||||
class TypeCloner
|
||||
{
|
||||
|
||||
protected:
|
||||
NotNull<TypeArena> arena;
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
|
||||
|
@ -67,8 +62,6 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual ~TypeCloner() = default;
|
||||
|
||||
TypeId clone(TypeId ty)
|
||||
{
|
||||
shallowClone(ty);
|
||||
|
@ -127,13 +120,12 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
std::optional<TypeId> find(TypeId ty) const
|
||||
{
|
||||
ty = follow(ty, FollowOption::DisableLazyTypeThunks);
|
||||
if (auto it = types->find(ty); it != types->end())
|
||||
return it->second;
|
||||
else if (ty->persistent && ty != forceTy)
|
||||
else if (ty->persistent && (!FFlag::LuauFreezeIgnorePersistent || ty != forceTy))
|
||||
return ty;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
@ -143,7 +135,7 @@ protected:
|
|||
tp = follow(tp);
|
||||
if (auto it = packs->find(tp); it != packs->end())
|
||||
return it->second;
|
||||
else if (tp->persistent && tp != forceTp)
|
||||
else if (tp->persistent && (!FFlag::LuauFreezeIgnorePersistent || tp != forceTp))
|
||||
return tp;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
@ -162,14 +154,14 @@ protected:
|
|||
}
|
||||
|
||||
public:
|
||||
virtual TypeId shallowClone(TypeId ty)
|
||||
TypeId shallowClone(TypeId ty)
|
||||
{
|
||||
// We want to [`Luau::follow`] but without forcing the expansion of [`LazyType`]s.
|
||||
ty = follow(ty, FollowOption::DisableLazyTypeThunks);
|
||||
|
||||
if (auto clone = find(ty))
|
||||
return *clone;
|
||||
else if (ty->persistent && ty != forceTy)
|
||||
else if (ty->persistent && (!FFlag::LuauFreezeIgnorePersistent || ty != forceTy))
|
||||
return ty;
|
||||
|
||||
TypeId target = arena->addType(ty->ty);
|
||||
|
@ -179,6 +171,8 @@ public:
|
|||
generic->scope = nullptr;
|
||||
else if (auto free = getMutable<FreeType>(target))
|
||||
free->scope = nullptr;
|
||||
else if (auto fn = getMutable<FunctionType>(target))
|
||||
fn->scope = nullptr;
|
||||
else if (auto table = getMutable<TableType>(target))
|
||||
table->scope = nullptr;
|
||||
|
||||
|
@ -187,13 +181,13 @@ public:
|
|||
return target;
|
||||
}
|
||||
|
||||
virtual TypePackId shallowClone(TypePackId tp)
|
||||
TypePackId shallowClone(TypePackId tp)
|
||||
{
|
||||
tp = follow(tp);
|
||||
|
||||
if (auto clone = find(tp))
|
||||
return *clone;
|
||||
else if (tp->persistent && tp != forceTp)
|
||||
else if (tp->persistent && (!FFlag::LuauFreezeIgnorePersistent || tp != forceTp))
|
||||
return tp;
|
||||
|
||||
TypePackId target = arena->addTypePack(tp->ty);
|
||||
|
@ -395,7 +389,7 @@ private:
|
|||
ty = shallowClone(ty);
|
||||
}
|
||||
|
||||
virtual void cloneChildren(LazyType* t)
|
||||
void cloneChildren(LazyType* t)
|
||||
{
|
||||
if (auto unwrapped = t->unwrapped.load())
|
||||
t->unwrapped.store(shallowClone(unwrapped));
|
||||
|
@ -475,94 +469,11 @@ private:
|
|||
}
|
||||
};
|
||||
|
||||
class FragmentAutocompleteTypeCloner final : public TypeCloner
|
||||
{
|
||||
Scope* replacementForNullScope = nullptr;
|
||||
|
||||
public:
|
||||
FragmentAutocompleteTypeCloner(
|
||||
NotNull<TypeArena> arena,
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<SeenTypes> types,
|
||||
NotNull<SeenTypePacks> packs,
|
||||
TypeId forceTy,
|
||||
TypePackId forceTp,
|
||||
Scope* replacementForNullScope
|
||||
)
|
||||
: TypeCloner(arena, builtinTypes, types, packs, forceTy, forceTp)
|
||||
, replacementForNullScope(replacementForNullScope)
|
||||
{
|
||||
LUAU_ASSERT(replacementForNullScope);
|
||||
}
|
||||
|
||||
TypeId shallowClone(TypeId ty) override
|
||||
{
|
||||
// We want to [`Luau::follow`] but without forcing the expansion of [`LazyType`]s.
|
||||
ty = follow(ty, FollowOption::DisableLazyTypeThunks);
|
||||
|
||||
if (auto clone = find(ty))
|
||||
return *clone;
|
||||
else if (ty->persistent && ty != forceTy)
|
||||
return ty;
|
||||
|
||||
TypeId target = arena->addType(ty->ty);
|
||||
asMutable(target)->documentationSymbol = ty->documentationSymbol;
|
||||
|
||||
if (auto generic = getMutable<GenericType>(target))
|
||||
generic->scope = nullptr;
|
||||
else if (auto free = getMutable<FreeType>(target))
|
||||
{
|
||||
free->scope = replacementForNullScope;
|
||||
}
|
||||
else if (auto tt = getMutable<TableType>(target))
|
||||
{
|
||||
if (FFlag::LuauClonedTableAndFunctionTypesMustHaveScopes)
|
||||
tt->scope = replacementForNullScope;
|
||||
}
|
||||
|
||||
(*types)[ty] = target;
|
||||
queue.emplace_back(target);
|
||||
return target;
|
||||
}
|
||||
|
||||
TypePackId shallowClone(TypePackId tp) override
|
||||
{
|
||||
tp = follow(tp);
|
||||
|
||||
if (auto clone = find(tp))
|
||||
return *clone;
|
||||
else if (tp->persistent && tp != forceTp)
|
||||
return tp;
|
||||
|
||||
TypePackId target = arena->addTypePack(tp->ty);
|
||||
|
||||
if (auto generic = getMutable<GenericTypePack>(target))
|
||||
generic->scope = nullptr;
|
||||
else if (auto free = getMutable<FreeTypePack>(target))
|
||||
free->scope = replacementForNullScope;
|
||||
|
||||
(*packs)[tp] = target;
|
||||
queue.emplace_back(target);
|
||||
return target;
|
||||
}
|
||||
|
||||
void cloneChildren(LazyType* t) override
|
||||
{
|
||||
// Do not clone lazy types
|
||||
if (!FFlag::LuauIncrementalAutocompleteDemandBasedCloning)
|
||||
{
|
||||
if (auto unwrapped = t->unwrapped.load())
|
||||
t->unwrapped.store(shallowClone(unwrapped));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState, bool ignorePersistent)
|
||||
{
|
||||
if (tp->persistent && !ignorePersistent)
|
||||
if (tp->persistent && (!FFlag::LuauFreezeIgnorePersistent || !ignorePersistent))
|
||||
return tp;
|
||||
|
||||
TypeCloner cloner{
|
||||
|
@ -571,7 +482,7 @@ TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState,
|
|||
NotNull{&cloneState.seenTypes},
|
||||
NotNull{&cloneState.seenTypePacks},
|
||||
nullptr,
|
||||
ignorePersistent ? tp : nullptr
|
||||
FFlag::LuauFreezeIgnorePersistent && ignorePersistent ? tp : nullptr
|
||||
};
|
||||
|
||||
return cloner.shallowClone(tp);
|
||||
|
@ -579,7 +490,7 @@ TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState,
|
|||
|
||||
TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool ignorePersistent)
|
||||
{
|
||||
if (typeId->persistent && !ignorePersistent)
|
||||
if (typeId->persistent && (!FFlag::LuauFreezeIgnorePersistent || !ignorePersistent))
|
||||
return typeId;
|
||||
|
||||
TypeCloner cloner{
|
||||
|
@ -587,7 +498,7 @@ TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool
|
|||
cloneState.builtinTypes,
|
||||
NotNull{&cloneState.seenTypes},
|
||||
NotNull{&cloneState.seenTypePacks},
|
||||
ignorePersistent ? typeId : nullptr,
|
||||
FFlag::LuauFreezeIgnorePersistent && ignorePersistent ? typeId : nullptr,
|
||||
nullptr
|
||||
};
|
||||
|
||||
|
@ -653,96 +564,4 @@ Binding clone(const Binding& binding, TypeArena& dest, CloneState& cloneState)
|
|||
return b;
|
||||
}
|
||||
|
||||
TypePackId cloneIncremental(TypePackId tp, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes)
|
||||
{
|
||||
if (tp->persistent)
|
||||
return tp;
|
||||
|
||||
FragmentAutocompleteTypeCloner cloner{
|
||||
NotNull{&dest},
|
||||
cloneState.builtinTypes,
|
||||
NotNull{&cloneState.seenTypes},
|
||||
NotNull{&cloneState.seenTypePacks},
|
||||
nullptr,
|
||||
nullptr,
|
||||
freshScopeForFreeTypes
|
||||
};
|
||||
return cloner.clone(tp);
|
||||
}
|
||||
|
||||
TypeId cloneIncremental(TypeId typeId, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes)
|
||||
{
|
||||
if (typeId->persistent)
|
||||
return typeId;
|
||||
|
||||
FragmentAutocompleteTypeCloner cloner{
|
||||
NotNull{&dest},
|
||||
cloneState.builtinTypes,
|
||||
NotNull{&cloneState.seenTypes},
|
||||
NotNull{&cloneState.seenTypePacks},
|
||||
nullptr,
|
||||
nullptr,
|
||||
freshScopeForFreeTypes
|
||||
};
|
||||
return cloner.clone(typeId);
|
||||
}
|
||||
|
||||
TypeFun cloneIncremental(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes)
|
||||
{
|
||||
FragmentAutocompleteTypeCloner cloner{
|
||||
NotNull{&dest},
|
||||
cloneState.builtinTypes,
|
||||
NotNull{&cloneState.seenTypes},
|
||||
NotNull{&cloneState.seenTypePacks},
|
||||
nullptr,
|
||||
nullptr,
|
||||
freshScopeForFreeTypes
|
||||
};
|
||||
|
||||
TypeFun copy = typeFun;
|
||||
|
||||
for (auto& param : copy.typeParams)
|
||||
{
|
||||
param.ty = cloner.clone(param.ty);
|
||||
|
||||
if (param.defaultValue)
|
||||
param.defaultValue = cloner.clone(*param.defaultValue);
|
||||
}
|
||||
|
||||
for (auto& param : copy.typePackParams)
|
||||
{
|
||||
param.tp = cloner.clone(param.tp);
|
||||
|
||||
if (param.defaultValue)
|
||||
param.defaultValue = cloner.clone(*param.defaultValue);
|
||||
}
|
||||
|
||||
copy.type = cloner.clone(copy.type);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
Binding cloneIncremental(const Binding& binding, TypeArena& dest, CloneState& cloneState, Scope* freshScopeForFreeTypes)
|
||||
{
|
||||
FragmentAutocompleteTypeCloner cloner{
|
||||
NotNull{&dest},
|
||||
cloneState.builtinTypes,
|
||||
NotNull{&cloneState.seenTypes},
|
||||
NotNull{&cloneState.seenTypePacks},
|
||||
nullptr,
|
||||
nullptr,
|
||||
freshScopeForFreeTypes
|
||||
};
|
||||
|
||||
Binding b;
|
||||
b.deprecated = binding.deprecated;
|
||||
b.deprecatedSuggestion = binding.deprecatedSuggestion;
|
||||
b.documentationSymbol = binding.documentationSymbol;
|
||||
b.location = binding.location;
|
||||
b.typeId = FFlag::LuauDoNotClonePersistentBindings && binding.typeId->persistent ? binding.typeId : cloner.clone(binding.typeId);
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Luau
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -35,14 +35,8 @@ LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500)
|
|||
LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
|
||||
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTablesOnScope)
|
||||
LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock)
|
||||
LUAU_FASTFLAGVARIABLE(LuauPrecalculateMutatedFreeTypes2)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization)
|
||||
LUAU_FASTFLAG(LuauSearchForRefineableType)
|
||||
LUAU_FASTFLAG(LuauDeprecatedAttribute)
|
||||
LUAU_FASTFLAG(LuauNonReentrantGeneralization)
|
||||
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -362,19 +356,32 @@ ConstraintSolver::ConstraintSolver(
|
|||
{
|
||||
unsolvedConstraints.emplace_back(c);
|
||||
|
||||
auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes();
|
||||
for (auto ty : maybeMutatedTypesPerConstraint)
|
||||
if (FFlag::LuauPrecalculateMutatedFreeTypes2)
|
||||
{
|
||||
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
|
||||
refCount += 1;
|
||||
|
||||
if (FFlag::DebugLuauGreedyGeneralization)
|
||||
auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes();
|
||||
for (auto ty : maybeMutatedTypesPerConstraint)
|
||||
{
|
||||
auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, DenseHashSet<const Constraint*>{nullptr});
|
||||
it->second.insert(c.get());
|
||||
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
|
||||
refCount += 1;
|
||||
|
||||
if (FFlag::DebugLuauGreedyGeneralization)
|
||||
{
|
||||
auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, DenseHashSet<const Constraint*>{nullptr});
|
||||
it->second.insert(c.get());
|
||||
}
|
||||
}
|
||||
maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint);
|
||||
}
|
||||
else
|
||||
{
|
||||
// initialize the reference counts for the free types in this constraint.
|
||||
for (auto ty : c->getMaybeMutatedFreeTypes())
|
||||
{
|
||||
// increment the reference count for `ty`
|
||||
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
|
||||
refCount += 1;
|
||||
}
|
||||
}
|
||||
maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint);
|
||||
|
||||
|
||||
for (NotNull<const Constraint> dep : c->dependencies)
|
||||
|
@ -465,24 +472,48 @@ void ConstraintSolver::run()
|
|||
unblock(c);
|
||||
unsolvedConstraints.erase(unsolvedConstraints.begin() + ptrdiff_t(i));
|
||||
|
||||
const auto maybeMutated = maybeMutatedFreeTypes.find(c);
|
||||
if (maybeMutated != maybeMutatedFreeTypes.end())
|
||||
if (FFlag::LuauPrecalculateMutatedFreeTypes2)
|
||||
{
|
||||
DenseHashSet<TypeId> seen{nullptr};
|
||||
for (auto ty : maybeMutated->second)
|
||||
const auto maybeMutated = maybeMutatedFreeTypes.find(c);
|
||||
if (maybeMutated != maybeMutatedFreeTypes.end())
|
||||
{
|
||||
// There is a high chance that this type has been rebound
|
||||
// across blocked types, rebound free types, pending
|
||||
// expansion types, etc, so we need to follow it.
|
||||
ty = follow(ty);
|
||||
|
||||
if (FFlag::DebugLuauGreedyGeneralization)
|
||||
DenseHashSet<TypeId> seen{nullptr};
|
||||
for (auto ty : maybeMutated->second)
|
||||
{
|
||||
if (seen.contains(ty))
|
||||
continue;
|
||||
seen.insert(ty);
|
||||
}
|
||||
// There is a high chance that this type has been rebound
|
||||
// across blocked types, rebound free types, pending
|
||||
// expansion types, etc, so we need to follow it.
|
||||
ty = follow(ty);
|
||||
|
||||
if (FFlag::DebugLuauGreedyGeneralization)
|
||||
{
|
||||
if (seen.contains(ty))
|
||||
continue;
|
||||
seen.insert(ty);
|
||||
}
|
||||
|
||||
size_t& refCount = unresolvedConstraints[ty];
|
||||
if (refCount > 0)
|
||||
refCount -= 1;
|
||||
|
||||
// We have two constraints that are designed to wait for the
|
||||
// refCount on a free type to be equal to 1: the
|
||||
// PrimitiveTypeConstraint and ReduceConstraint. We
|
||||
// therefore wake any constraint waiting for a free type's
|
||||
// refcount to be 1 or 0.
|
||||
if (refCount <= 1)
|
||||
unblock(ty, Location{});
|
||||
|
||||
if (FFlag::DebugLuauGreedyGeneralization && refCount == 0)
|
||||
generalizeOneType(ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// decrement the referenced free types for this constraint if we dispatched successfully!
|
||||
for (auto ty : c->getMaybeMutatedFreeTypes())
|
||||
{
|
||||
size_t& refCount = unresolvedConstraints[ty];
|
||||
if (refCount > 0)
|
||||
refCount -= 1;
|
||||
|
@ -494,9 +525,6 @@ void ConstraintSolver::run()
|
|||
// refcount to be 1 or 0.
|
||||
if (refCount <= 1)
|
||||
unblock(ty, Location{});
|
||||
|
||||
if (FFlag::DebugLuauGreedyGeneralization && refCount == 0)
|
||||
generalizeOneType(ty);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -596,30 +624,32 @@ bool ConstraintSolver::isDone() const
|
|||
|
||||
struct TypeSearcher : TypeVisitor
|
||||
{
|
||||
enum struct Polarity: uint8_t
|
||||
{
|
||||
None = 0b00,
|
||||
Positive = 0b01,
|
||||
Negative = 0b10,
|
||||
Mixed = 0b11,
|
||||
};
|
||||
|
||||
TypeId needle;
|
||||
Polarity current = Polarity::Positive;
|
||||
|
||||
size_t count = 0;
|
||||
Polarity result = Polarity::None;
|
||||
|
||||
explicit TypeSearcher(TypeId needle)
|
||||
: TypeSearcher(needle, Polarity::Positive)
|
||||
{
|
||||
}
|
||||
{}
|
||||
|
||||
explicit TypeSearcher(TypeId needle, Polarity initialPolarity)
|
||||
: needle(needle)
|
||||
, current(initialPolarity)
|
||||
{
|
||||
}
|
||||
{}
|
||||
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
if (ty == needle)
|
||||
{
|
||||
++count;
|
||||
result = Polarity(size_t(result) | size_t(current));
|
||||
}
|
||||
result = Polarity(int(result) | int(current));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -628,14 +658,14 @@ struct TypeSearcher : TypeVisitor
|
|||
{
|
||||
switch (current)
|
||||
{
|
||||
case Polarity::Positive:
|
||||
current = Polarity::Negative;
|
||||
break;
|
||||
case Polarity::Negative:
|
||||
current = Polarity::Positive;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case Polarity::Positive:
|
||||
current = Polarity::Negative;
|
||||
break;
|
||||
case Polarity::Negative:
|
||||
current = Polarity::Positive;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -713,23 +743,23 @@ void ConstraintSolver::generalizeOneType(TypeId ty)
|
|||
|
||||
switch (ts.result)
|
||||
{
|
||||
case Polarity::None:
|
||||
asMutable(ty)->reassign(Type{BoundType{upperBound}});
|
||||
break;
|
||||
|
||||
case Polarity::Negative:
|
||||
case Polarity::Mixed:
|
||||
if (get<UnknownType>(upperBound) && ts.count > 1)
|
||||
{
|
||||
asMutable(ty)->reassign(Type{GenericType{tyScope}});
|
||||
function->generics.emplace_back(ty);
|
||||
}
|
||||
else
|
||||
case TypeSearcher::Polarity::None:
|
||||
asMutable(ty)->reassign(Type{BoundType{upperBound}});
|
||||
break;
|
||||
break;
|
||||
|
||||
case Polarity::Positive:
|
||||
if (get<UnknownType>(lowerBound) && ts.count > 1)
|
||||
case TypeSearcher::Polarity::Negative:
|
||||
case TypeSearcher::Polarity::Mixed:
|
||||
if (get<UnknownType>(upperBound))
|
||||
{
|
||||
asMutable(ty)->reassign(Type{GenericType{tyScope}});
|
||||
function->generics.emplace_back(ty);
|
||||
}
|
||||
else
|
||||
asMutable(ty)->reassign(Type{BoundType{upperBound}});
|
||||
break;
|
||||
|
||||
case TypeSearcher::Polarity::Positive:
|
||||
if (get<UnknownType>(lowerBound))
|
||||
{
|
||||
asMutable(ty)->reassign(Type{GenericType{tyScope}});
|
||||
function->generics.emplace_back(ty);
|
||||
|
@ -737,8 +767,6 @@ void ConstraintSolver::generalizeOneType(TypeId ty)
|
|||
else
|
||||
asMutable(ty)->reassign(Type{BoundType{lowerBound}});
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -750,16 +778,7 @@ void ConstraintSolver::bind(NotNull<const Constraint> constraint, TypeId ty, Typ
|
|||
|
||||
boundTo = follow(boundTo);
|
||||
if (get<BlockedType>(ty) && ty == boundTo)
|
||||
{
|
||||
emplace<FreeType>(
|
||||
constraint, ty, constraint->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed
|
||||
); // FIXME? Is this the right polarity?
|
||||
|
||||
if (FFlag::LuauNonReentrantGeneralization)
|
||||
trackInteriorFreeType(constraint->scope, ty);
|
||||
|
||||
return;
|
||||
}
|
||||
return emplace<FreeType>(constraint, ty, constraint->scope, builtinTypes->neverType, builtinTypes->unknownType);
|
||||
|
||||
shiftReferences(ty, boundTo);
|
||||
emplaceType<BoundType>(asMutable(ty), boundTo);
|
||||
|
@ -884,25 +903,26 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
|
|||
else if (get<PendingExpansionType>(generalizedType))
|
||||
return block(generalizedType, constraint);
|
||||
|
||||
std::optional<QuantifierResult> generalized;
|
||||
|
||||
std::optional<TypeId> generalizedTy = generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, c.sourceType);
|
||||
if (!generalizedTy)
|
||||
if (generalizedTy)
|
||||
generalized = QuantifierResult{*generalizedTy}; // FIXME insertedGenerics and insertedGenericPacks
|
||||
else
|
||||
reportError(CodeTooComplex{}, constraint->location);
|
||||
|
||||
if (generalizedTy)
|
||||
if (generalized)
|
||||
{
|
||||
if (get<BlockedType>(generalizedType))
|
||||
bind(constraint, generalizedType, *generalizedTy);
|
||||
bind(constraint, generalizedType, generalized->result);
|
||||
else
|
||||
unify(constraint, generalizedType, *generalizedTy);
|
||||
unify(constraint, generalizedType, generalized->result);
|
||||
|
||||
if (FFlag::LuauDeprecatedAttribute)
|
||||
{
|
||||
if (FunctionType* fty = getMutable<FunctionType>(follow(generalizedType)))
|
||||
{
|
||||
if (c.hasDeprecatedAttribute)
|
||||
fty->isDeprecatedFunction = true;
|
||||
}
|
||||
}
|
||||
for (auto [free, gen] : generalized->insertedGenerics.pairings)
|
||||
unify(constraint, free, gen);
|
||||
|
||||
for (auto [free, gen] : generalized->insertedGenericPacks.pairings)
|
||||
unify(constraint, free, gen);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -915,53 +935,13 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
|
|||
// We check if this member is initialized and then access it, but
|
||||
// clang-tidy doesn't understand this is safe.
|
||||
if (constraint->scope->interiorFreeTypes)
|
||||
{
|
||||
for (TypeId ty : *constraint->scope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access)
|
||||
{
|
||||
if (FFlag::LuauNonReentrantGeneralization)
|
||||
{
|
||||
ty = follow(ty);
|
||||
if (auto freeTy = get<FreeType>(ty))
|
||||
{
|
||||
GeneralizationParams<TypeId> params;
|
||||
params.foundOutsideFunctions = true;
|
||||
params.useCount = 1;
|
||||
params.polarity = freeTy->polarity;
|
||||
|
||||
generalizeType(arena, builtinTypes, constraint->scope, ty, params);
|
||||
}
|
||||
else if (get<TableType>(ty))
|
||||
sealTable(constraint->scope, ty);
|
||||
}
|
||||
else
|
||||
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty);
|
||||
}
|
||||
}
|
||||
|
||||
if (FFlag::LuauNonReentrantGeneralization)
|
||||
{
|
||||
if (constraint->scope->interiorFreeTypePacks)
|
||||
{
|
||||
for (TypePackId tp : *constraint->scope->interiorFreeTypePacks) // NOLINT(bugprone-unchecked-optional-access)
|
||||
{
|
||||
tp = follow(tp);
|
||||
if (auto freeTp = get<FreeTypePack>(tp))
|
||||
{
|
||||
GeneralizationParams<TypePackId> params;
|
||||
params.foundOutsideFunctions = true;
|
||||
params.useCount = 1;
|
||||
params.polarity = freeTp->polarity;
|
||||
LUAU_ASSERT(isKnown(params.polarity));
|
||||
generalizeTypePack(arena, builtinTypes, constraint->scope, tp, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty, /* avoidSealingTables */ false);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (TypeId ty : c.interiorTypes)
|
||||
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty);
|
||||
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty, /* avoidSealingTables */ false);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1037,8 +1017,8 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Co
|
|||
TypeId nextTy = follow(iterator.head[0]);
|
||||
if (get<FreeType>(nextTy))
|
||||
{
|
||||
TypeId keyTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed);
|
||||
TypeId valueTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed);
|
||||
TypeId keyTy = freshType(arena, builtinTypes, constraint->scope);
|
||||
TypeId valueTy = freshType(arena, builtinTypes, constraint->scope);
|
||||
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
|
||||
{
|
||||
trackInteriorFreeType(constraint->scope, keyTy);
|
||||
|
@ -1372,29 +1352,15 @@ void ConstraintSolver::fillInDiscriminantTypes(NotNull<const Constraint> constra
|
|||
if (!ty)
|
||||
continue;
|
||||
|
||||
if (FFlag::LuauSearchForRefineableType)
|
||||
// If the discriminant type has been transmuted, we need to unblock them.
|
||||
if (!isBlocked(*ty))
|
||||
{
|
||||
if (isBlocked(*ty))
|
||||
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
|
||||
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
|
||||
|
||||
// We also need to unconditionally unblock these types, otherwise
|
||||
// you end up with funky looking "Blocked on *no-refine*."
|
||||
unblock(*ty, constraint->location);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// If the discriminant type has been transmuted, we need to unblock them.
|
||||
if (!isBlocked(*ty))
|
||||
{
|
||||
unblock(*ty, constraint->location);
|
||||
continue;
|
||||
}
|
||||
|
||||
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
|
||||
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
|
||||
}
|
||||
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
|
||||
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1404,17 +1370,9 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
|||
TypePackId argsPack = follow(c.argsPack);
|
||||
TypePackId result = follow(c.result);
|
||||
|
||||
if (FFlag::DebugLuauGreedyGeneralization)
|
||||
if (isBlocked(fn) || hasUnresolvedConstraints(fn))
|
||||
{
|
||||
if (isBlocked(fn))
|
||||
return block(c.fn, constraint);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isBlocked(fn) || hasUnresolvedConstraints(fn))
|
||||
{
|
||||
return block(c.fn, constraint);
|
||||
}
|
||||
return block(c.fn, constraint);
|
||||
}
|
||||
|
||||
if (get<AnyType>(fn))
|
||||
|
@ -1497,8 +1455,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
|||
|
||||
argsPack = arena->addTypePack(TypePack{std::move(argsHead), argsTail});
|
||||
fn = follow(*callMm);
|
||||
emplace<FreeTypePack>(constraint, c.result, constraint->scope, Polarity::Positive);
|
||||
trackInteriorFreeTypePack(constraint->scope, c.result);
|
||||
emplace<FreeTypePack>(constraint, c.result, constraint->scope);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1515,10 +1472,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
|||
}
|
||||
|
||||
if (!usedMagic)
|
||||
{
|
||||
emplace<FreeTypePack>(constraint, c.result, constraint->scope, Polarity::Positive);
|
||||
trackInteriorFreeTypePack(constraint->scope, c.result);
|
||||
}
|
||||
emplace<FreeTypePack>(constraint, c.result, constraint->scope);
|
||||
}
|
||||
|
||||
fillInDiscriminantTypes(constraint, c.discriminantTypes);
|
||||
|
@ -1539,19 +1493,11 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
|||
if (status == OverloadResolver::Analysis::Ok)
|
||||
overloadToUse = overload;
|
||||
|
||||
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, argsPack, c.result});
|
||||
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, constraint->scope.get(), argsPack, c.result});
|
||||
Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}};
|
||||
|
||||
const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy);
|
||||
|
||||
if (FFlag::LuauNonReentrantGeneralization)
|
||||
{
|
||||
for (TypeId freeTy : u2.newFreshTypes)
|
||||
trackInteriorFreeType(constraint->scope, freeTy);
|
||||
for (TypePackId freeTp : u2.newFreshTypePacks)
|
||||
trackInteriorFreeTypePack(constraint->scope, freeTp);
|
||||
}
|
||||
|
||||
if (!u2.genericSubstitutions.empty() || !u2.genericPackSubstitutions.empty())
|
||||
{
|
||||
std::optional<TypePackId> subst = instantiate2(arena, std::move(u2.genericSubstitutions), std::move(u2.genericPackSubstitutions), result);
|
||||
|
@ -1582,11 +1528,6 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
|||
queuer.traverse(overloadToUse);
|
||||
queuer.traverse(inferredTy);
|
||||
|
||||
// This can potentially contain free types if the return type of
|
||||
// `inferredTy` is never unified elsewhere.
|
||||
if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInferredFunctionTypeFromCall)
|
||||
trackInteriorFreeType(constraint->scope, inferredTy);
|
||||
|
||||
unblock(c.result, constraint->location);
|
||||
|
||||
return true;
|
||||
|
@ -1600,43 +1541,6 @@ static AstExpr* unwrapGroup(AstExpr* expr)
|
|||
return expr;
|
||||
}
|
||||
|
||||
struct ContainsGenerics : public TypeOnceVisitor
|
||||
{
|
||||
DenseHashSet<const void*> generics{nullptr};
|
||||
|
||||
bool found = false;
|
||||
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
return !found;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const GenericType&) override
|
||||
{
|
||||
found |= generics.contains(ty);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const TypeFunctionInstanceType&) override
|
||||
{
|
||||
return !found;
|
||||
}
|
||||
|
||||
bool visit(TypePackId tp, const GenericTypePack&) override
|
||||
{
|
||||
found |= generics.contains(tp);
|
||||
return !found;
|
||||
}
|
||||
|
||||
bool hasGeneric(TypeId ty)
|
||||
{
|
||||
traverse(ty);
|
||||
auto ret = found;
|
||||
found = false;
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<const Constraint> constraint)
|
||||
{
|
||||
TypeId fn = follow(c.fn);
|
||||
|
@ -1679,49 +1583,36 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
|
|||
DenseHashMap<TypeId, TypeId> replacements{nullptr};
|
||||
DenseHashMap<TypePackId, TypePackId> replacementPacks{nullptr};
|
||||
|
||||
ContainsGenerics containsGenerics;
|
||||
|
||||
for (auto generic : ftv->generics)
|
||||
{
|
||||
replacements[generic] = builtinTypes->unknownType;
|
||||
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
containsGenerics.generics.insert(generic);
|
||||
}
|
||||
|
||||
for (auto genericPack : ftv->genericPacks)
|
||||
{
|
||||
replacementPacks[genericPack] = builtinTypes->unknownTypePack;
|
||||
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
containsGenerics.generics.insert(genericPack);
|
||||
}
|
||||
|
||||
// If the type of the function has generics, we don't actually want to push any of the generics themselves
|
||||
// into the argument types as expected types because this creates an unnecessary loop. Instead, we want to
|
||||
// replace these types with `unknown` (and `...unknown`) to keep any structure but not create the cycle.
|
||||
if (!FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
if (!replacements.empty() || !replacementPacks.empty())
|
||||
{
|
||||
if (!replacements.empty() || !replacementPacks.empty())
|
||||
Replacer replacer{arena, std::move(replacements), std::move(replacementPacks)};
|
||||
|
||||
std::optional<TypeId> res = replacer.substitute(fn);
|
||||
if (res)
|
||||
{
|
||||
Replacer replacer{arena, std::move(replacements), std::move(replacementPacks)};
|
||||
|
||||
std::optional<TypeId> res = replacer.substitute(fn);
|
||||
if (res)
|
||||
if (*res != fn)
|
||||
{
|
||||
if (*res != fn)
|
||||
{
|
||||
FunctionType* ftvMut = getMutable<FunctionType>(*res);
|
||||
LUAU_ASSERT(ftvMut);
|
||||
ftvMut->generics.clear();
|
||||
ftvMut->genericPacks.clear();
|
||||
}
|
||||
|
||||
fn = *res;
|
||||
ftv = get<FunctionType>(*res);
|
||||
LUAU_ASSERT(ftv);
|
||||
|
||||
// we've potentially copied type functions here, so we need to reproduce their reduce constraint.
|
||||
reproduceConstraints(constraint->scope, constraint->location, replacer);
|
||||
FunctionType* ftvMut = getMutable<FunctionType>(*res);
|
||||
LUAU_ASSERT(ftvMut);
|
||||
ftvMut->generics.clear();
|
||||
ftvMut->genericPacks.clear();
|
||||
}
|
||||
|
||||
fn = *res;
|
||||
ftv = get<FunctionType>(*res);
|
||||
LUAU_ASSERT(ftv);
|
||||
|
||||
// we've potentially copied type functions here, so we need to reproduce their reduce constraint.
|
||||
reproduceConstraints(constraint->scope, constraint->location, replacer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1740,10 +1631,6 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
|
|||
|
||||
(*c.astExpectedTypes)[expr] = expectedArgTy;
|
||||
|
||||
// Generic types are skipped over entirely, for now.
|
||||
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes && containsGenerics.hasGeneric(expectedArgTy))
|
||||
continue;
|
||||
|
||||
const FunctionType* expectedLambdaTy = get<FunctionType>(expectedArgTy);
|
||||
const FunctionType* lambdaTy = get<FunctionType>(actualArgTy);
|
||||
const AstExprFunction* lambdaExpr = expr->as<AstExprFunction>();
|
||||
|
@ -1771,11 +1658,8 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
|
|||
else if (expr->is<AstExprTable>())
|
||||
{
|
||||
Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}};
|
||||
Subtyping sp{builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, NotNull{&iceReporter}};
|
||||
std::vector<TypeId> toBlock;
|
||||
(void)matchLiteralType(
|
||||
c.astTypes, c.astExpectedTypes, builtinTypes, arena, NotNull{&u2}, NotNull{&sp}, expectedArgTy, actualArgTy, expr, toBlock
|
||||
);
|
||||
(void)matchLiteralType(c.astTypes, c.astExpectedTypes, builtinTypes, arena, NotNull{&u2}, expectedArgTy, actualArgTy, expr, toBlock);
|
||||
LUAU_ASSERT(toBlock.empty());
|
||||
}
|
||||
}
|
||||
|
@ -1799,9 +1683,8 @@ bool ConstraintSolver::tryDispatch(const TableCheckConstraint& c, NotNull<const
|
|||
return false;
|
||||
|
||||
Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}};
|
||||
Subtyping sp{builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, NotNull{&iceReporter}};
|
||||
std::vector<TypeId> toBlock;
|
||||
(void)matchLiteralType(c.astTypes, c.astExpectedTypes, builtinTypes, arena, NotNull{&u2}, NotNull{&sp}, c.expectedType, c.exprType, c.table, toBlock);
|
||||
(void)matchLiteralType(c.astTypes, c.astExpectedTypes, builtinTypes, arena, NotNull{&u2}, c.expectedType, c.exprType, c.table, toBlock);
|
||||
LUAU_ASSERT(toBlock.empty());
|
||||
return true;
|
||||
}
|
||||
|
@ -1848,16 +1731,8 @@ bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNull<const Con
|
|||
LUAU_ASSERT(get<BlockedType>(resultType));
|
||||
LUAU_ASSERT(canMutate(resultType, constraint));
|
||||
|
||||
if (FFlag::LuauHasPropProperBlock)
|
||||
{
|
||||
if (isBlocked(subjectType))
|
||||
return block(subjectType, constraint);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isBlocked(subjectType) || get<PendingExpansionType>(subjectType) || get<TypeFunctionInstanceType>(subjectType))
|
||||
return block(subjectType, constraint);
|
||||
}
|
||||
if (isBlocked(subjectType) || get<PendingExpansionType>(subjectType) || get<TypeFunctionInstanceType>(subjectType))
|
||||
return block(subjectType, constraint);
|
||||
|
||||
if (const TableType* subjectTable = getTableType(subjectType))
|
||||
{
|
||||
|
@ -1919,7 +1794,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(
|
|||
else if (auto mt = get<MetatableType>(follow(ft->upperBound)))
|
||||
return tryDispatchHasIndexer(recursionDepth, constraint, mt->table, indexType, resultType, seen);
|
||||
|
||||
FreeType freeResult{ft->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed};
|
||||
FreeType freeResult{ft->scope, builtinTypes->neverType, builtinTypes->unknownType};
|
||||
emplace<FreeType>(constraint, resultType, freeResult);
|
||||
|
||||
TypeId upperBound =
|
||||
|
@ -1942,10 +1817,8 @@ bool ConstraintSolver::tryDispatchHasIndexer(
|
|||
{
|
||||
// FIXME this is greedy.
|
||||
|
||||
FreeType freeResult{tt->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed};
|
||||
FreeType freeResult{tt->scope, builtinTypes->neverType, builtinTypes->unknownType};
|
||||
emplace<FreeType>(constraint, resultType, freeResult);
|
||||
if (FFlag::LuauNonReentrantGeneralization)
|
||||
trackInteriorFreeType(constraint->scope, resultType);
|
||||
|
||||
tt->indexer = TableIndexer{indexType, resultType};
|
||||
return true;
|
||||
|
@ -2167,7 +2040,11 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
|
|||
if (maybeTy)
|
||||
{
|
||||
TypeId propTy = *maybeTy;
|
||||
bind(constraint, c.propType, isIndex ? arena->addType(UnionType{{propTy, builtinTypes->nilType}}) : propTy);
|
||||
bind(
|
||||
constraint,
|
||||
c.propType,
|
||||
isIndex ? arena->addType(UnionType{{propTy, builtinTypes->nilType}}) : propTy
|
||||
);
|
||||
unify(constraint, rhsType, propTy);
|
||||
return true;
|
||||
}
|
||||
|
@ -2261,7 +2138,11 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull<const
|
|||
{
|
||||
unify(constraint, indexType, lhsTable->indexer->indexType);
|
||||
unify(constraint, rhsType, lhsTable->indexer->indexResultType);
|
||||
bind(constraint, c.propType, arena->addType(UnionType{{lhsTable->indexer->indexResultType, builtinTypes->nilType}}));
|
||||
bind(
|
||||
constraint,
|
||||
c.propType,
|
||||
arena->addType(UnionType{{lhsTable->indexer->indexResultType, builtinTypes->nilType}})
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -2310,7 +2191,11 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull<const
|
|||
{
|
||||
unify(constraint, indexType, lhsClass->indexer->indexType);
|
||||
unify(constraint, rhsType, lhsClass->indexer->indexResultType);
|
||||
bind(constraint, c.propType, arena->addType(UnionType{{lhsClass->indexer->indexResultType, builtinTypes->nilType}}));
|
||||
bind(
|
||||
constraint,
|
||||
c.propType,
|
||||
arena->addType(UnionType{{lhsClass->indexer->indexResultType, builtinTypes->nilType}})
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -2404,7 +2289,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
|
|||
// is only blocked on itself. This doesn't actually
|
||||
// constitute any meaningful constraint, so we replace it
|
||||
// with a free type.
|
||||
TypeId f = freshType(arena, builtinTypes, constraint->scope, Polarity::Positive); // FIXME? Is this the right polarity?
|
||||
TypeId f = freshType(arena, builtinTypes, constraint->scope);
|
||||
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
|
||||
trackInteriorFreeType(constraint->scope, f);
|
||||
shiftReferences(resultTy, f);
|
||||
|
@ -2453,18 +2338,11 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Cons
|
|||
for (TypePackId r : result.reducedPacks)
|
||||
unblock(r, constraint->location);
|
||||
|
||||
if (FFlag::LuauNewTypeFunReductionChecks2)
|
||||
{
|
||||
for (TypeId ity : result.irreducibleTypes)
|
||||
uninhabitedTypeFunctions.insert(ity);
|
||||
}
|
||||
|
||||
bool reductionFinished = result.blockedTypes.empty() && result.blockedPacks.empty();
|
||||
|
||||
ty = follow(ty);
|
||||
|
||||
// If we couldn't reduce this type function, stick it in the set!
|
||||
if (get<TypeFunctionInstanceType>(ty) && (!FFlag::LuauNewTypeFunReductionChecks2 || !result.irreducibleTypes.find(ty)))
|
||||
if (get<TypeFunctionInstanceType>(ty))
|
||||
typeFunctionsToFinalize[ty] = constraint;
|
||||
|
||||
if (force || reductionFinished)
|
||||
|
@ -2547,8 +2425,8 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
|||
|
||||
if (get<FreeType>(iteratorTy))
|
||||
{
|
||||
TypeId keyTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed);
|
||||
TypeId valueTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed);
|
||||
TypeId keyTy = freshType(arena, builtinTypes, constraint->scope);
|
||||
TypeId valueTy = freshType(arena, builtinTypes, constraint->scope);
|
||||
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
|
||||
{
|
||||
trackInteriorFreeType(constraint->scope, keyTy);
|
||||
|
@ -2809,7 +2687,7 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
|
|||
|
||||
if (ttv->state == TableState::Free)
|
||||
{
|
||||
TypeId result = freshType(arena, builtinTypes, ttv->scope, Polarity::Mixed);
|
||||
TypeId result = freshType(arena, builtinTypes, ttv->scope);
|
||||
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
|
||||
trackInteriorFreeType(ttv->scope, result);
|
||||
switch (context)
|
||||
|
@ -2923,7 +2801,7 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
|
|||
|
||||
TableType* tt = getMutable<TableType>(newUpperBound);
|
||||
LUAU_ASSERT(tt);
|
||||
TypeId propType = freshType(arena, builtinTypes, scope, Polarity::Mixed);
|
||||
TypeId propType = freshType(arena, builtinTypes, scope);
|
||||
|
||||
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
|
||||
trackInteriorFreeType(scope, propType);
|
||||
|
@ -3384,7 +3262,7 @@ void ConstraintSolver::shiftReferences(TypeId source, TypeId target)
|
|||
}
|
||||
}
|
||||
|
||||
std::optional<TypeId> ConstraintSolver::generalizeFreeType(NotNull<Scope> scope, TypeId type)
|
||||
std::optional<TypeId> ConstraintSolver::generalizeFreeType(NotNull<Scope> scope, TypeId type, bool avoidSealingTables)
|
||||
{
|
||||
TypeId t = follow(type);
|
||||
if (get<FreeType>(t))
|
||||
|
@ -3399,7 +3277,7 @@ std::optional<TypeId> ConstraintSolver::generalizeFreeType(NotNull<Scope> scope,
|
|||
// that until all constraint generation is complete.
|
||||
}
|
||||
|
||||
return generalize(NotNull{arena}, builtinTypes, scope, generalizedTypes, type);
|
||||
return generalize(NotNull{arena}, builtinTypes, scope, generalizedTypes, type, avoidSealingTables);
|
||||
}
|
||||
|
||||
bool ConstraintSolver::hasUnresolvedConstraints(TypeId ty)
|
||||
|
|
|
@ -13,22 +13,32 @@
|
|||
|
||||
LUAU_FASTFLAG(DebugLuauFreezeArena)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauPreprocessTypestatedArgument)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackTrueReset)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
bool doesCallError(const AstExprCall* call); // TypeInfer.cpp
|
||||
|
||||
struct ReferencedDefFinder : public AstVisitor
|
||||
{
|
||||
bool visit(AstExprLocal* local) override
|
||||
{
|
||||
referencedLocalDefs.push_back(local->local);
|
||||
return true;
|
||||
}
|
||||
// ast defs is just a mapping from expr -> def in general
|
||||
// will get built up by the dfg builder
|
||||
|
||||
// localDefs, we need to copy over
|
||||
std::vector<AstLocal*> referencedLocalDefs;
|
||||
};
|
||||
|
||||
struct PushScope
|
||||
{
|
||||
ScopeStack& stack;
|
||||
size_t previousSize;
|
||||
|
||||
PushScope(ScopeStack& stack, DfgScope* scope)
|
||||
: stack(stack)
|
||||
, previousSize(stack.size())
|
||||
{
|
||||
// `scope` should never be `nullptr` here.
|
||||
LUAU_ASSERT(scope);
|
||||
|
@ -37,18 +47,7 @@ struct PushScope
|
|||
|
||||
~PushScope()
|
||||
{
|
||||
if (FFlag::LuauDfgScopeStackTrueReset)
|
||||
{
|
||||
// If somehow this stack has _shrunk_ to be smaller than we expect,
|
||||
// something very strange has happened.
|
||||
LUAU_ASSERT(stack.size() > previousSize);
|
||||
while (stack.size() > previousSize)
|
||||
stack.pop_back();
|
||||
}
|
||||
else
|
||||
{
|
||||
stack.pop_back();
|
||||
}
|
||||
stack.pop_back();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -83,6 +82,12 @@ std::optional<DefId> DataFlowGraph::getDefOptional(const AstExpr* expr) const
|
|||
return NotNull{*def};
|
||||
}
|
||||
|
||||
std::optional<DefId> DataFlowGraph::getRValueDefForCompoundAssign(const AstExpr* expr) const
|
||||
{
|
||||
auto def = compoundAssignDefs.find(expr);
|
||||
return def ? std::optional<DefId>(*def) : std::nullopt;
|
||||
}
|
||||
|
||||
DefId DataFlowGraph::getDef(const AstLocal* local) const
|
||||
{
|
||||
auto def = localDefs.find(local);
|
||||
|
@ -196,15 +201,7 @@ DataFlowGraph DataFlowGraphBuilder::build(
|
|||
|
||||
DataFlowGraphBuilder builder(defArena, keyArena);
|
||||
builder.handle = handle;
|
||||
|
||||
DfgScope* moduleScope;
|
||||
// We're not explicitly calling makeChildScope here because that function relies on currentScope
|
||||
// which guarantees that the scope being returned is NotNull
|
||||
// This means that while the scope stack is empty, we'll have to manually initialize the global scope
|
||||
if (FFlag::LuauDfgScopeStackNotNull)
|
||||
moduleScope = builder.scopes.emplace_back(new DfgScope{nullptr, DfgScope::ScopeType::Linear}).get();
|
||||
else
|
||||
moduleScope = builder.makeChildScope();
|
||||
DfgScope* moduleScope = builder.makeChildScope();
|
||||
PushScope ps{builder.scopeStack, moduleScope};
|
||||
builder.visitBlockWithoutChildScope(block);
|
||||
builder.resolveCaptures();
|
||||
|
@ -236,13 +233,7 @@ void DataFlowGraphBuilder::resolveCaptures()
|
|||
}
|
||||
}
|
||||
|
||||
NotNull<DfgScope> DataFlowGraphBuilder::currentScope()
|
||||
{
|
||||
LUAU_ASSERT(!scopeStack.empty());
|
||||
return NotNull{scopeStack.back()};
|
||||
}
|
||||
|
||||
DfgScope* DataFlowGraphBuilder::currentScope_DEPRECATED()
|
||||
DfgScope* DataFlowGraphBuilder::currentScope()
|
||||
{
|
||||
if (scopeStack.empty())
|
||||
return nullptr; // nullptr is the root DFG scope.
|
||||
|
@ -251,10 +242,7 @@ DfgScope* DataFlowGraphBuilder::currentScope_DEPRECATED()
|
|||
|
||||
DfgScope* DataFlowGraphBuilder::makeChildScope(DfgScope::ScopeType scopeType)
|
||||
{
|
||||
if (FFlag::LuauDfgScopeStackNotNull)
|
||||
return scopes.emplace_back(new DfgScope{currentScope(), scopeType}).get();
|
||||
else
|
||||
return scopes.emplace_back(new DfgScope{currentScope_DEPRECATED(), scopeType}).get();
|
||||
return scopes.emplace_back(new DfgScope{currentScope(), scopeType}).get();
|
||||
}
|
||||
|
||||
void DataFlowGraphBuilder::join(DfgScope* p, DfgScope* a, DfgScope* b)
|
||||
|
@ -329,9 +317,9 @@ void DataFlowGraphBuilder::joinProps(DfgScope* result, const DfgScope& a, const
|
|||
}
|
||||
}
|
||||
|
||||
DefId DataFlowGraphBuilder::lookup(Symbol symbol, Location location)
|
||||
DefId DataFlowGraphBuilder::lookup(Symbol symbol)
|
||||
{
|
||||
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
|
||||
DfgScope* scope = currentScope();
|
||||
|
||||
// true if any of the considered scopes are a loop.
|
||||
bool outsideLoopScope = false;
|
||||
|
@ -356,15 +344,15 @@ DefId DataFlowGraphBuilder::lookup(Symbol symbol, Location location)
|
|||
}
|
||||
}
|
||||
|
||||
DefId result = defArena->freshCell(symbol, location);
|
||||
DefId result = defArena->freshCell();
|
||||
scope->bindings[symbol] = result;
|
||||
captures[symbol].allVersions.push_back(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key, Location location)
|
||||
DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key)
|
||||
{
|
||||
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
|
||||
DfgScope* scope = currentScope();
|
||||
for (DfgScope* current = scope; current; current = current->parent)
|
||||
{
|
||||
if (auto props = current->props.find(def))
|
||||
|
@ -374,7 +362,7 @@ DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key, Location l
|
|||
}
|
||||
else if (auto phi = get<Phi>(def); phi && phi->operands.empty()) // Unresolved phi nodes
|
||||
{
|
||||
DefId result = defArena->freshCell(def->name, location);
|
||||
DefId result = defArena->freshCell();
|
||||
scope->props[def][key] = result;
|
||||
return result;
|
||||
}
|
||||
|
@ -384,7 +372,7 @@ DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key, Location l
|
|||
{
|
||||
std::vector<DefId> defs;
|
||||
for (DefId operand : phi->operands)
|
||||
defs.push_back(lookup(operand, key, location));
|
||||
defs.push_back(lookup(operand, key));
|
||||
|
||||
DefId result = defArena->phi(defs);
|
||||
scope->props[def][key] = result;
|
||||
|
@ -392,7 +380,7 @@ DefId DataFlowGraphBuilder::lookup(DefId def, const std::string& key, Location l
|
|||
}
|
||||
else if (get<Cell>(def))
|
||||
{
|
||||
DefId result = defArena->freshCell(def->name, location);
|
||||
DefId result = defArena->freshCell();
|
||||
scope->props[def][key] = result;
|
||||
return result;
|
||||
}
|
||||
|
@ -410,10 +398,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatBlock* b)
|
|||
cf = visitBlockWithoutChildScope(b);
|
||||
}
|
||||
|
||||
if (FFlag::LuauDfgScopeStackNotNull)
|
||||
currentScope()->inherit(child);
|
||||
else
|
||||
currentScope_DEPRECATED()->inherit(child);
|
||||
currentScope()->inherit(child);
|
||||
return cf;
|
||||
}
|
||||
|
||||
|
@ -498,7 +483,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatIf* i)
|
|||
elsecf = visit(i->elsebody);
|
||||
}
|
||||
|
||||
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
|
||||
DfgScope* scope = currentScope();
|
||||
if (thencf != ControlFlow::None && elsecf == ControlFlow::None)
|
||||
join(scope, scope, elseScope);
|
||||
else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
|
||||
|
@ -525,10 +510,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatWhile* w)
|
|||
visit(w->body);
|
||||
}
|
||||
|
||||
if (FFlag::LuauDfgScopeStackNotNull)
|
||||
currentScope()->inherit(whileScope);
|
||||
else
|
||||
currentScope_DEPRECATED()->inherit(whileScope);
|
||||
currentScope()->inherit(whileScope);
|
||||
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
@ -544,10 +526,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatRepeat* r)
|
|||
visitExpr(r->condition);
|
||||
}
|
||||
|
||||
if (FFlag::LuauDfgScopeStackNotNull)
|
||||
currentScope()->inherit(repeatScope);
|
||||
else
|
||||
currentScope_DEPRECATED()->inherit(repeatScope);
|
||||
currentScope()->inherit(repeatScope);
|
||||
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
@ -596,7 +575,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatLocal* l)
|
|||
// We need to create a new def to intentionally avoid alias tracking, but we'd like to
|
||||
// make sure that the non-aliased defs are also marked as a subscript for refinements.
|
||||
bool subscripted = i < defs.size() && containsSubscriptedDefinition(defs[i]);
|
||||
DefId def = defArena->freshCell(local, local->location, subscripted);
|
||||
DefId def = defArena->freshCell(subscripted);
|
||||
if (i < l->values.size)
|
||||
{
|
||||
AstExpr* e = l->values.data[i];
|
||||
|
@ -606,10 +585,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatLocal* l)
|
|||
}
|
||||
}
|
||||
graph.localDefs[local] = def;
|
||||
if (FFlag::LuauDfgScopeStackNotNull)
|
||||
currentScope()->bindings[local] = def;
|
||||
else
|
||||
currentScope_DEPRECATED()->bindings[local] = def;
|
||||
currentScope()->bindings[local] = def;
|
||||
captures[local].allVersions.push_back(def);
|
||||
}
|
||||
|
||||
|
@ -631,22 +607,16 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatFor* f)
|
|||
if (f->var->annotation)
|
||||
visitType(f->var->annotation);
|
||||
|
||||
DefId def = defArena->freshCell(f->var, f->var->location);
|
||||
DefId def = defArena->freshCell();
|
||||
graph.localDefs[f->var] = def;
|
||||
if (FFlag::LuauDfgScopeStackNotNull)
|
||||
currentScope()->bindings[f->var] = def;
|
||||
else
|
||||
currentScope_DEPRECATED()->bindings[f->var] = def;
|
||||
currentScope()->bindings[f->var] = def;
|
||||
captures[f->var].allVersions.push_back(def);
|
||||
|
||||
// TODO(controlflow): entry point has a back edge from exit point
|
||||
visit(f->body);
|
||||
}
|
||||
|
||||
if (FFlag::LuauDfgScopeStackNotNull)
|
||||
currentScope()->inherit(forScope);
|
||||
else
|
||||
currentScope_DEPRECATED()->inherit(forScope);
|
||||
currentScope()->inherit(forScope);
|
||||
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
@ -663,12 +633,9 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatForIn* f)
|
|||
if (local->annotation)
|
||||
visitType(local->annotation);
|
||||
|
||||
DefId def = defArena->freshCell(local, local->location);
|
||||
DefId def = defArena->freshCell();
|
||||
graph.localDefs[local] = def;
|
||||
if (FFlag::LuauDfgScopeStackNotNull)
|
||||
currentScope()->bindings[local] = def;
|
||||
else
|
||||
currentScope_DEPRECATED()->bindings[local] = def;
|
||||
currentScope()->bindings[local] = def;
|
||||
captures[local].allVersions.push_back(def);
|
||||
}
|
||||
|
||||
|
@ -679,10 +646,8 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatForIn* f)
|
|||
|
||||
visit(f->body);
|
||||
}
|
||||
if (FFlag::LuauDfgScopeStackNotNull)
|
||||
currentScope()->inherit(forScope);
|
||||
else
|
||||
currentScope_DEPRECATED()->inherit(forScope);
|
||||
|
||||
currentScope()->inherit(forScope);
|
||||
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
@ -697,7 +662,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatAssign* a)
|
|||
for (size_t i = 0; i < a->vars.size; ++i)
|
||||
{
|
||||
AstExpr* v = a->vars.data[i];
|
||||
visitLValue(v, i < defs.size() ? defs[i] : defArena->freshCell(Symbol{}, v->location));
|
||||
visitLValue(v, i < defs.size() ? defs[i] : defArena->freshCell());
|
||||
}
|
||||
|
||||
return ControlFlow::None;
|
||||
|
@ -723,7 +688,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatFunction* f)
|
|||
//
|
||||
// which is evidence that references to variables must be a phi node of all possible definitions,
|
||||
// but for bug compatibility, we'll assume the same thing here.
|
||||
visitLValue(f->name, defArena->freshCell(Symbol{}, f->name->location));
|
||||
visitLValue(f->name, defArena->freshCell());
|
||||
visitExpr(f->func);
|
||||
|
||||
if (auto local = f->name->as<AstExprLocal>())
|
||||
|
@ -743,12 +708,9 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatFunction* f)
|
|||
|
||||
ControlFlow DataFlowGraphBuilder::visit(AstStatLocalFunction* l)
|
||||
{
|
||||
DefId def = defArena->freshCell(l->name, l->location);
|
||||
DefId def = defArena->freshCell();
|
||||
graph.localDefs[l->name] = def;
|
||||
if (FFlag::LuauDfgScopeStackNotNull)
|
||||
currentScope()->bindings[l->name] = def;
|
||||
else
|
||||
currentScope_DEPRECATED()->bindings[l->name] = def;
|
||||
currentScope()->bindings[l->name] = def;
|
||||
captures[l->name].allVersions.push_back(def);
|
||||
visitExpr(l->func);
|
||||
|
||||
|
@ -779,12 +741,9 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatTypeFunction* f)
|
|||
|
||||
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareGlobal* d)
|
||||
{
|
||||
DefId def = defArena->freshCell(d->name, d->nameLocation);
|
||||
DefId def = defArena->freshCell();
|
||||
graph.declaredDefs[d] = def;
|
||||
if (FFlag::LuauDfgScopeStackNotNull)
|
||||
currentScope()->bindings[d->name] = def;
|
||||
else
|
||||
currentScope_DEPRECATED()->bindings[d->name] = def;
|
||||
currentScope()->bindings[d->name] = def;
|
||||
captures[d->name].allVersions.push_back(def);
|
||||
|
||||
visitType(d->type);
|
||||
|
@ -794,12 +753,9 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareGlobal* d)
|
|||
|
||||
ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareFunction* d)
|
||||
{
|
||||
DefId def = defArena->freshCell(d->name, d->nameLocation);
|
||||
DefId def = defArena->freshCell();
|
||||
graph.declaredDefs[d] = def;
|
||||
if (FFlag::LuauDfgScopeStackNotNull)
|
||||
currentScope()->bindings[d->name] = def;
|
||||
else
|
||||
currentScope_DEPRECATED()->bindings[d->name] = def;
|
||||
currentScope()->bindings[d->name] = def;
|
||||
captures[d->name].allVersions.push_back(def);
|
||||
|
||||
DfgScope* unreachable = makeChildScope();
|
||||
|
@ -854,19 +810,19 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExpr* e)
|
|||
if (auto g = e->as<AstExprGroup>())
|
||||
return visitExpr(g);
|
||||
else if (auto c = e->as<AstExprConstantNil>())
|
||||
return {defArena->freshCell(Symbol{}, c->location), nullptr}; // ok
|
||||
return {defArena->freshCell(), nullptr}; // ok
|
||||
else if (auto c = e->as<AstExprConstantBool>())
|
||||
return {defArena->freshCell(Symbol{}, c->location), nullptr}; // ok
|
||||
return {defArena->freshCell(), nullptr}; // ok
|
||||
else if (auto c = e->as<AstExprConstantNumber>())
|
||||
return {defArena->freshCell(Symbol{}, c->location), nullptr}; // ok
|
||||
return {defArena->freshCell(), nullptr}; // ok
|
||||
else if (auto c = e->as<AstExprConstantString>())
|
||||
return {defArena->freshCell(Symbol{}, c->location), nullptr}; // ok
|
||||
return {defArena->freshCell(), nullptr}; // ok
|
||||
else if (auto l = e->as<AstExprLocal>())
|
||||
return visitExpr(l);
|
||||
else if (auto g = e->as<AstExprGlobal>())
|
||||
return visitExpr(g);
|
||||
else if (auto v = e->as<AstExprVarargs>())
|
||||
return {defArena->freshCell(Symbol{}, v->location), nullptr}; // ok
|
||||
return {defArena->freshCell(), nullptr}; // ok
|
||||
else if (auto c = e->as<AstExprCall>())
|
||||
return visitExpr(c);
|
||||
else if (auto i = e->as<AstExprIndexName>())
|
||||
|
@ -907,14 +863,14 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprGroup* group)
|
|||
|
||||
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprLocal* l)
|
||||
{
|
||||
DefId def = lookup(l->local, l->local->location);
|
||||
DefId def = lookup(l->local);
|
||||
const RefinementKey* key = keyArena->leaf(def);
|
||||
return {def, key};
|
||||
}
|
||||
|
||||
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprGlobal* g)
|
||||
{
|
||||
DefId def = lookup(g->name, g->location);
|
||||
DefId def = lookup(g->name);
|
||||
return {def, keyArena->leaf(def)};
|
||||
}
|
||||
|
||||
|
@ -922,12 +878,6 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
|
|||
{
|
||||
visitExpr(c->func);
|
||||
|
||||
if (FFlag::LuauPreprocessTypestatedArgument)
|
||||
{
|
||||
for (AstExpr* arg : c->args)
|
||||
visitExpr(arg);
|
||||
}
|
||||
|
||||
if (shouldTypestateForFirstArgument(*c) && c->args.size > 1 && isLValue(*c->args.begin()))
|
||||
{
|
||||
AstExpr* firstArg = *c->args.begin();
|
||||
|
@ -958,23 +908,11 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
|
|||
visitLValue(firstArg, def);
|
||||
}
|
||||
|
||||
if (!FFlag::LuauPreprocessTypestatedArgument)
|
||||
{
|
||||
for (AstExpr* arg : c->args)
|
||||
visitExpr(arg);
|
||||
}
|
||||
for (AstExpr* arg : c->args)
|
||||
visitExpr(arg);
|
||||
|
||||
// We treat function calls as "subscripted" as they could potentially
|
||||
// return a subscripted value, consider:
|
||||
//
|
||||
// local function foo(tbl: {[string]: woof)
|
||||
// return tbl["foobarbaz"]
|
||||
// end
|
||||
//
|
||||
// local v = foo({})
|
||||
//
|
||||
// We want to consider `v` to be subscripted here.
|
||||
return {defArena->freshCell(Symbol{}, c->location, /*subscripted=*/true)};
|
||||
// calls should be treated as subscripted.
|
||||
return {defArena->freshCell(/* subscripted */ true), nullptr};
|
||||
}
|
||||
|
||||
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIndexName* i)
|
||||
|
@ -982,7 +920,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIndexName* i)
|
|||
auto [parentDef, parentKey] = visitExpr(i->expr);
|
||||
std::string index = i->index.value;
|
||||
|
||||
DefId def = lookup(parentDef, index, i->location);
|
||||
DefId def = lookup(parentDef, index);
|
||||
return {def, keyArena->node(parentKey, def, index)};
|
||||
}
|
||||
|
||||
|
@ -995,11 +933,11 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIndexExpr* i)
|
|||
{
|
||||
std::string index{string->value.data, string->value.size};
|
||||
|
||||
DefId def = lookup(parentDef, index, i->location);
|
||||
DefId def = lookup(parentDef, index);
|
||||
return {def, keyArena->node(parentKey, def, index)};
|
||||
}
|
||||
|
||||
return {defArena->freshCell(Symbol{}, i->location, /* subscripted= */ true), nullptr};
|
||||
return {defArena->freshCell(/* subscripted= */ true), nullptr};
|
||||
}
|
||||
|
||||
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
|
||||
|
@ -1012,7 +950,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
|
|||
// There's no syntax for `self` to have an annotation if using `function t:m()`
|
||||
LUAU_ASSERT(!self->annotation);
|
||||
|
||||
DefId def = defArena->freshCell(f->debugname, f->location);
|
||||
DefId def = defArena->freshCell();
|
||||
graph.localDefs[self] = def;
|
||||
signatureScope->bindings[self] = def;
|
||||
captures[self].allVersions.push_back(def);
|
||||
|
@ -1023,7 +961,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
|
|||
if (param->annotation)
|
||||
visitType(param->annotation);
|
||||
|
||||
DefId def = defArena->freshCell(param, param->location);
|
||||
DefId def = defArena->freshCell();
|
||||
graph.localDefs[param] = def;
|
||||
signatureScope->bindings[param] = def;
|
||||
captures[param].allVersions.push_back(def);
|
||||
|
@ -1045,16 +983,13 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f)
|
|||
// g() --> 5
|
||||
visit(f->body);
|
||||
|
||||
return {defArena->freshCell(f->debugname, f->location), nullptr};
|
||||
return {defArena->freshCell(), nullptr};
|
||||
}
|
||||
|
||||
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTable* t)
|
||||
{
|
||||
DefId tableCell = defArena->freshCell(Symbol{}, t->location);
|
||||
if (FFlag::LuauDfgScopeStackNotNull)
|
||||
currentScope()->props[tableCell] = {};
|
||||
else
|
||||
currentScope_DEPRECATED()->props[tableCell] = {};
|
||||
DefId tableCell = defArena->freshCell();
|
||||
currentScope()->props[tableCell] = {};
|
||||
for (AstExprTable::Item item : t->items)
|
||||
{
|
||||
DataFlowResult result = visitExpr(item.value);
|
||||
|
@ -1062,12 +997,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTable* t)
|
|||
{
|
||||
visitExpr(item.key);
|
||||
if (auto string = item.key->as<AstExprConstantString>())
|
||||
{
|
||||
if (FFlag::LuauDfgScopeStackNotNull)
|
||||
currentScope()->props[tableCell][string->value.data] = result.def;
|
||||
else
|
||||
currentScope_DEPRECATED()->props[tableCell][string->value.data] = result.def;
|
||||
}
|
||||
currentScope()->props[tableCell][string->value.data] = result.def;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1078,7 +1008,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprUnary* u)
|
|||
{
|
||||
visitExpr(u->expr);
|
||||
|
||||
return {defArena->freshCell(Symbol{}, u->location), nullptr};
|
||||
return {defArena->freshCell(), nullptr};
|
||||
}
|
||||
|
||||
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b)
|
||||
|
@ -1086,7 +1016,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b)
|
|||
visitExpr(b->left);
|
||||
visitExpr(b->right);
|
||||
|
||||
return {defArena->freshCell(Symbol{}, b->location), nullptr};
|
||||
return {defArena->freshCell(), nullptr};
|
||||
}
|
||||
|
||||
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTypeAssertion* t)
|
||||
|
@ -1103,7 +1033,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIfElse* i)
|
|||
visitExpr(i->trueExpr);
|
||||
visitExpr(i->falseExpr);
|
||||
|
||||
return {defArena->freshCell(Symbol{}, i->location), nullptr};
|
||||
return {defArena->freshCell(), nullptr};
|
||||
}
|
||||
|
||||
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprInterpString* i)
|
||||
|
@ -1111,7 +1041,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprInterpString* i)
|
|||
for (AstExpr* e : i->expressions)
|
||||
visitExpr(e);
|
||||
|
||||
return {defArena->freshCell(Symbol{}, i->location), nullptr};
|
||||
return {defArena->freshCell(), nullptr};
|
||||
}
|
||||
|
||||
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprError* error)
|
||||
|
@ -1122,7 +1052,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprError* error)
|
|||
for (AstExpr* e : error->expressions)
|
||||
visitExpr(e);
|
||||
|
||||
return {defArena->freshCell(Symbol{}, error->location), nullptr};
|
||||
return {defArena->freshCell(), nullptr};
|
||||
}
|
||||
|
||||
void DataFlowGraphBuilder::visitLValue(AstExpr* e, DefId incomingDef)
|
||||
|
@ -1148,12 +1078,12 @@ void DataFlowGraphBuilder::visitLValue(AstExpr* e, DefId incomingDef)
|
|||
|
||||
DefId DataFlowGraphBuilder::visitLValue(AstExprLocal* l, DefId incomingDef)
|
||||
{
|
||||
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
|
||||
DfgScope* scope = currentScope();
|
||||
|
||||
// In order to avoid alias tracking, we need to clip the reference to the parent def.
|
||||
if (scope->canUpdateDefinition(l->local))
|
||||
{
|
||||
DefId updated = defArena->freshCell(l->local, l->location, containsSubscriptedDefinition(incomingDef));
|
||||
DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
|
||||
scope->bindings[l->local] = updated;
|
||||
captures[l->local].allVersions.push_back(updated);
|
||||
return updated;
|
||||
|
@ -1164,12 +1094,12 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprLocal* l, DefId incomingDef)
|
|||
|
||||
DefId DataFlowGraphBuilder::visitLValue(AstExprGlobal* g, DefId incomingDef)
|
||||
{
|
||||
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
|
||||
DfgScope* scope = currentScope();
|
||||
|
||||
// In order to avoid alias tracking, we need to clip the reference to the parent def.
|
||||
if (scope->canUpdateDefinition(g->name))
|
||||
{
|
||||
DefId updated = defArena->freshCell(g->name, g->location, containsSubscriptedDefinition(incomingDef));
|
||||
DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
|
||||
scope->bindings[g->name] = updated;
|
||||
captures[g->name].allVersions.push_back(updated);
|
||||
return updated;
|
||||
|
@ -1182,10 +1112,10 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprIndexName* i, DefId incomingDef)
|
|||
{
|
||||
DefId parentDef = visitExpr(i->expr).def;
|
||||
|
||||
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
|
||||
DfgScope* scope = currentScope();
|
||||
if (scope->canUpdateDefinition(parentDef, i->index.value))
|
||||
{
|
||||
DefId updated = defArena->freshCell(i->index, i->location, containsSubscriptedDefinition(incomingDef));
|
||||
DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
|
||||
scope->props[parentDef][i->index.value] = updated;
|
||||
return updated;
|
||||
}
|
||||
|
@ -1198,12 +1128,12 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprIndexExpr* i, DefId incomingDef)
|
|||
DefId parentDef = visitExpr(i->expr).def;
|
||||
visitExpr(i->index);
|
||||
|
||||
DfgScope* scope = FFlag::LuauDfgScopeStackNotNull ? currentScope() : currentScope_DEPRECATED();
|
||||
DfgScope* scope = currentScope();
|
||||
if (auto string = i->index->as<AstExprConstantString>())
|
||||
{
|
||||
if (scope->canUpdateDefinition(parentDef, string->value.data))
|
||||
{
|
||||
DefId updated = defArena->freshCell(Symbol{}, i->location, containsSubscriptedDefinition(incomingDef));
|
||||
DefId updated = defArena->freshCell(containsSubscriptedDefinition(incomingDef));
|
||||
scope->props[parentDef][string->value.data] = updated;
|
||||
return updated;
|
||||
}
|
||||
|
@ -1211,7 +1141,7 @@ DefId DataFlowGraphBuilder::visitLValue(AstExprIndexExpr* i, DefId incomingDef)
|
|||
return visitExpr(static_cast<AstExpr*>(i)).def;
|
||||
}
|
||||
else
|
||||
return defArena->freshCell(Symbol{}, i->location, /*subscripted=*/true);
|
||||
return defArena->freshCell(/*subscripted=*/true);
|
||||
}
|
||||
|
||||
DefId DataFlowGraphBuilder::visitLValue(AstExprError* error, DefId incomingDef)
|
||||
|
@ -1229,8 +1159,6 @@ void DataFlowGraphBuilder::visitType(AstType* t)
|
|||
return visitType(f);
|
||||
else if (auto tyof = t->as<AstTypeTypeof>())
|
||||
return visitType(tyof);
|
||||
else if (auto o = t->as<AstTypeOptional>())
|
||||
return;
|
||||
else if (auto u = t->as<AstTypeUnion>())
|
||||
return visitType(u);
|
||||
else if (auto i = t->as<AstTypeIntersection>())
|
||||
|
|
|
@ -36,9 +36,9 @@ void collectOperands(DefId def, std::vector<DefId>* operands)
|
|||
}
|
||||
}
|
||||
|
||||
DefId DefArena::freshCell(Symbol sym, Location location, bool subscripted)
|
||||
DefId DefArena::freshCell(bool subscripted)
|
||||
{
|
||||
return NotNull{allocator.allocate(Def{Cell{subscripted}, sym, location})};
|
||||
return NotNull{allocator.allocate(Def{Cell{subscripted}})};
|
||||
}
|
||||
|
||||
DefId DefArena::phi(DefId a, DefId b)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauDebugInfoDefn)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -213,6 +215,15 @@ declare debug: {
|
|||
|
||||
)BUILTIN_SRC";
|
||||
|
||||
static const std::string kBuiltinDefinitionDebugSrc_DEPRECATED = R"BUILTIN_SRC(
|
||||
|
||||
declare debug: {
|
||||
info: (<R...>(thread: thread, level: number, options: string) -> R...) & (<R...>(level: number, options: string) -> R...) & (<A..., R1..., R2...>(func: (A...) -> R1..., options: string) -> R2...),
|
||||
traceback: ((message: string?, level: number?) -> string) & ((thread: thread, message: string?, level: number?) -> string),
|
||||
}
|
||||
|
||||
)BUILTIN_SRC";
|
||||
|
||||
static const std::string kBuiltinDefinitionUtf8Src = R"BUILTIN_SRC(
|
||||
|
||||
declare utf8: {
|
||||
|
@ -298,7 +309,7 @@ std::string getBuiltinDefinitionSource()
|
|||
result += kBuiltinDefinitionOsSrc;
|
||||
result += kBuiltinDefinitionCoroutineSrc;
|
||||
result += kBuiltinDefinitionTableSrc;
|
||||
result += kBuiltinDefinitionDebugSrc;
|
||||
result += FFlag::LuauDebugInfoDefn ? kBuiltinDefinitionDebugSrc : kBuiltinDefinitionDebugSrc_DEPRECATED;
|
||||
result += kBuiltinDefinitionUtf8Src;
|
||||
result += kBuiltinDefinitionBufferSrc;
|
||||
result += kBuiltinDefinitionVectorSrc;
|
||||
|
@ -306,83 +317,4 @@ std::string getBuiltinDefinitionSource()
|
|||
return result;
|
||||
}
|
||||
|
||||
// TODO: split into separate tagged unions when the new solver can appropriately handle that.
|
||||
static const std::string kBuiltinDefinitionTypesSrc = R"BUILTIN_SRC(
|
||||
|
||||
export type type = {
|
||||
tag: "nil" | "unknown" | "never" | "any" | "boolean" | "number" | "string" | "buffer" | "thread" |
|
||||
"singleton" | "negation" | "union" | "intesection" | "table" | "function" | "class" | "generic",
|
||||
|
||||
is: (self: type, arg: string) -> boolean,
|
||||
|
||||
-- for singleton type
|
||||
value: (self: type) -> (string | boolean | nil),
|
||||
|
||||
-- for negation type
|
||||
inner: (self: type) -> type,
|
||||
|
||||
-- for union and intersection types
|
||||
components: (self: type) -> {type},
|
||||
|
||||
-- for table type
|
||||
setproperty: (self: type, key: type, value: type?) -> (),
|
||||
setreadproperty: (self: type, key: type, value: type?) -> (),
|
||||
setwriteproperty: (self: type, key: type, value: type?) -> (),
|
||||
readproperty: (self: type, key: type) -> type?,
|
||||
writeproperty: (self: type, key: type) -> type?,
|
||||
properties: (self: type) -> { [type]: { read: type?, write: type? } },
|
||||
setindexer: (self: type, index: type, result: type) -> (),
|
||||
setreadindexer: (self: type, index: type, result: type) -> (),
|
||||
setwriteindexer: (self: type, index: type, result: type) -> (),
|
||||
indexer: (self: type) -> { index: type, readresult: type, writeresult: type }?,
|
||||
readindexer: (self: type) -> { index: type, result: type }?,
|
||||
writeindexer: (self: type) -> { index: type, result: type }?,
|
||||
setmetatable: (self: type, arg: type) -> (),
|
||||
metatable: (self: type) -> type?,
|
||||
|
||||
-- for function type
|
||||
setparameters: (self: type, head: {type}?, tail: type?) -> (),
|
||||
parameters: (self: type) -> { head: {type}?, tail: type? },
|
||||
setreturns: (self: type, head: {type}?, tail: type? ) -> (),
|
||||
returns: (self: type) -> { head: {type}?, tail: type? },
|
||||
setgenerics: (self: type, {type}?) -> (),
|
||||
generics: (self: type) -> {type},
|
||||
|
||||
-- for class type
|
||||
-- 'properties', 'metatable', 'indexer', 'readindexer' and 'writeindexer' are shared with table type
|
||||
readparent: (self: type) -> type?,
|
||||
writeparent: (self: type) -> type?,
|
||||
|
||||
-- for generic type
|
||||
name: (self: type) -> string?,
|
||||
ispack: (self: type) -> boolean,
|
||||
}
|
||||
|
||||
declare types: {
|
||||
unknown: type,
|
||||
never: type,
|
||||
any: type,
|
||||
boolean: type,
|
||||
number: type,
|
||||
string: type,
|
||||
thread: type,
|
||||
buffer: type,
|
||||
|
||||
singleton: @checked (arg: string | boolean | nil) -> type,
|
||||
generic: @checked (name: string, ispack: boolean?) -> type,
|
||||
negationof: @checked (arg: type) -> type,
|
||||
unionof: @checked (...type) -> type,
|
||||
intersectionof: @checked (...type) -> type,
|
||||
newtable: @checked (props: {[type]: type} | {[type]: { read: type, write: type } } | nil, indexer: { index: type, readresult: type, writeresult: type }?, metatable: type?) -> type,
|
||||
newfunction: @checked (parameters: { head: {type}?, tail: type? }?, returns: { head: {type}?, tail: type? }?, generics: {type}?) -> type,
|
||||
copy: @checked (arg: type) -> type,
|
||||
}
|
||||
|
||||
)BUILTIN_SRC";
|
||||
|
||||
std::string getTypeFunctionDefinitionSource()
|
||||
{
|
||||
return kBuiltinDefinitionTypesSrc;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeChecker2.h"
|
||||
#include "Luau/TypeFunction.h"
|
||||
|
||||
#include <optional>
|
||||
|
@ -18,7 +17,6 @@
|
|||
#include <unordered_set>
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
|
||||
LUAU_FASTFLAG(LuauNonStrictFuncDefErrorFix)
|
||||
|
||||
static std::string wrongNumberOfArgsString(
|
||||
size_t expectedCount,
|
||||
|
@ -118,10 +116,7 @@ struct ErrorConverter
|
|||
size_t luauIndentTypeMismatchMaxTypeLength = size_t(FInt::LuauIndentTypeMismatchMaxTypeLength);
|
||||
if (givenType.length() <= luauIndentTypeMismatchMaxTypeLength || wantedType.length() <= luauIndentTypeMismatchMaxTypeLength)
|
||||
return "Type " + given + " could not be converted into " + wanted;
|
||||
if (FFlag::LuauImproveTypePathsInErrors)
|
||||
return "Type\n\t" + given + "\ncould not be converted into\n\t" + wanted;
|
||||
else
|
||||
return "Type\n " + given + "\ncould not be converted into\n " + wanted;
|
||||
return "Type\n " + given + "\ncould not be converted into\n " + wanted;
|
||||
};
|
||||
|
||||
if (givenTypeName == wantedTypeName)
|
||||
|
@ -606,7 +601,7 @@ struct ErrorConverter
|
|||
auto tfit = get<TypeFunctionInstanceType>(e.ty);
|
||||
LUAU_ASSERT(tfit); // Luau analysis has actually done something wrong if this type is not a type function.
|
||||
if (!tfit)
|
||||
return "Internal error: Unexpected type " + Luau::toString(e.ty) + " flagged as an uninhabited type function.";
|
||||
return "Unexpected type " + Luau::toString(e.ty) + " flagged as an uninhabited type function.";
|
||||
|
||||
// unary operators
|
||||
if (auto unaryString = kUnaryOps.find(tfit->function->name); unaryString != kUnaryOps.end())
|
||||
|
@ -756,15 +751,8 @@ struct ErrorConverter
|
|||
|
||||
std::string operator()(const NonStrictFunctionDefinitionError& e) const
|
||||
{
|
||||
if (FFlag::LuauNonStrictFuncDefErrorFix && e.functionName.empty())
|
||||
{
|
||||
return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' is used in a way that will run time error";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' in function '" + e.functionName +
|
||||
"' is used in a way that will run time error";
|
||||
}
|
||||
return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' in function '" + e.functionName +
|
||||
"' is used in a way that will run time error";
|
||||
}
|
||||
|
||||
std::string operator()(const PropertyAccessViolation& e) const
|
||||
|
|
|
@ -1,172 +0,0 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/FileResolver.h"
|
||||
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauExposeRequireByStringAutocomplete)
|
||||
LUAU_FASTFLAGVARIABLE(LuauEscapeCharactersInRequireSuggestions)
|
||||
LUAU_FASTFLAGVARIABLE(LuauHideImpossibleRequireSuggestions)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
static std::optional<RequireSuggestions> processRequireSuggestions(std::optional<RequireSuggestions> suggestions)
|
||||
{
|
||||
if (!suggestions)
|
||||
return suggestions;
|
||||
|
||||
if (FFlag::LuauEscapeCharactersInRequireSuggestions)
|
||||
{
|
||||
for (RequireSuggestion& suggestion : *suggestions)
|
||||
{
|
||||
suggestion.fullPath = escape(suggestion.fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
static RequireSuggestions makeSuggestionsFromAliases(std::vector<RequireAlias> aliases)
|
||||
{
|
||||
RequireSuggestions result;
|
||||
for (RequireAlias& alias : aliases)
|
||||
{
|
||||
RequireSuggestion suggestion;
|
||||
suggestion.label = "@" + std::move(alias.alias);
|
||||
suggestion.fullPath = suggestion.label;
|
||||
suggestion.tags = std::move(alias.tags);
|
||||
result.push_back(std::move(suggestion));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static RequireSuggestions makeSuggestionsForFirstComponent(std::unique_ptr<RequireNode> node)
|
||||
{
|
||||
RequireSuggestions result = makeSuggestionsFromAliases(node->getAvailableAliases());
|
||||
result.push_back(RequireSuggestion{"./", "./", {}});
|
||||
result.push_back(RequireSuggestion{"../", "../", {}});
|
||||
return result;
|
||||
}
|
||||
|
||||
static RequireSuggestions makeSuggestionsFromNode(std::unique_ptr<RequireNode> node, const std::string_view path, bool isPartialPath)
|
||||
{
|
||||
LUAU_ASSERT(!path.empty());
|
||||
|
||||
RequireSuggestions result;
|
||||
|
||||
const size_t lastSlashInPath = path.find_last_of('/');
|
||||
|
||||
if (lastSlashInPath != std::string_view::npos)
|
||||
{
|
||||
// Add a suggestion for the parent directory
|
||||
RequireSuggestion parentSuggestion;
|
||||
parentSuggestion.label = "..";
|
||||
|
||||
// TODO: after exposing require-by-string's path normalization API, this
|
||||
// if-else can be replaced. Instead, we can simply normalize the result
|
||||
// of inserting ".." at the end of the current path.
|
||||
if (lastSlashInPath >= 2 && path.substr(lastSlashInPath - 2, 3) == "../")
|
||||
{
|
||||
parentSuggestion.fullPath = path.substr(0, lastSlashInPath + 1);
|
||||
parentSuggestion.fullPath += "..";
|
||||
}
|
||||
else
|
||||
{
|
||||
parentSuggestion.fullPath = path.substr(0, lastSlashInPath);
|
||||
}
|
||||
|
||||
result.push_back(std::move(parentSuggestion));
|
||||
}
|
||||
|
||||
std::string fullPathPrefix;
|
||||
if (isPartialPath)
|
||||
{
|
||||
// ./path/to/chi -> ./path/to/
|
||||
fullPathPrefix += path.substr(0, lastSlashInPath + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (path.back() == '/')
|
||||
{
|
||||
// ./path/to/ -> ./path/to/
|
||||
fullPathPrefix += path;
|
||||
}
|
||||
else
|
||||
{
|
||||
// ./path/to -> ./path/to/
|
||||
fullPathPrefix += path;
|
||||
fullPathPrefix += "/";
|
||||
}
|
||||
}
|
||||
|
||||
for (const std::unique_ptr<RequireNode>& child : node->getChildren())
|
||||
{
|
||||
if (!child)
|
||||
continue;
|
||||
|
||||
std::string pathComponent = child->getPathComponent();
|
||||
if (FFlag::LuauHideImpossibleRequireSuggestions)
|
||||
{
|
||||
// If path component contains a slash, it cannot be required by string.
|
||||
// There's no point suggesting it.
|
||||
if (pathComponent.find('/') != std::string::npos)
|
||||
continue;
|
||||
}
|
||||
|
||||
RequireSuggestion suggestion;
|
||||
suggestion.label = isPartialPath || path.back() == '/' ? child->getLabel() : "/" + child->getLabel();
|
||||
suggestion.fullPath = fullPathPrefix + std::move(pathComponent);
|
||||
suggestion.tags = child->getTags();
|
||||
result.push_back(std::move(suggestion));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<RequireSuggestions> RequireSuggester::getRequireSuggestionsImpl(const ModuleName& requirer, const std::optional<std::string>& path)
|
||||
const
|
||||
{
|
||||
if (!path)
|
||||
return std::nullopt;
|
||||
|
||||
std::unique_ptr<RequireNode> requirerNode = getNode(requirer);
|
||||
if (!requirerNode)
|
||||
return std::nullopt;
|
||||
|
||||
const size_t slashPos = path->find_last_of('/');
|
||||
|
||||
if (slashPos == std::string::npos)
|
||||
return makeSuggestionsForFirstComponent(std::move(requirerNode));
|
||||
|
||||
// If path already points at a Node, return the Node's children as paths.
|
||||
if (std::unique_ptr<RequireNode> node = requirerNode->resolvePathToNode(*path))
|
||||
return makeSuggestionsFromNode(std::move(node), *path, /* isPartialPath = */ false);
|
||||
|
||||
// Otherwise, recover a partial path and use this to generate suggestions.
|
||||
if (std::unique_ptr<RequireNode> partialNode = requirerNode->resolvePathToNode(path->substr(0, slashPos)))
|
||||
return makeSuggestionsFromNode(std::move(partialNode), *path, /* isPartialPath = */ true);
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<RequireSuggestions> RequireSuggester::getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& path) const
|
||||
{
|
||||
return processRequireSuggestions(getRequireSuggestionsImpl(requirer, path));
|
||||
}
|
||||
|
||||
std::optional<RequireSuggestions> FileResolver::getRequireSuggestions(const ModuleName& requirer, const std::optional<std::string>& path) const
|
||||
{
|
||||
if (!FFlag::LuauExposeRequireByStringAutocomplete)
|
||||
return std::nullopt;
|
||||
|
||||
return requireSuggester ? requireSuggester->getRequireSuggestions(requirer, path) : std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,7 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Frontend.h"
|
||||
|
||||
#include "Luau/AnyTypeSummary.h"
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
#include "Luau/Clone.h"
|
||||
#include "Luau/Common.h"
|
||||
|
@ -39,15 +40,19 @@ LUAU_FASTINT(LuauTarjanChildLimit)
|
|||
LUAU_FASTFLAG(LuauInferInNoCheckMode)
|
||||
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRethrowKnownExceptions, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode)
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauBetterReverseDependencyTracking)
|
||||
|
||||
LUAU_FASTFLAG(StudioReportLuauAny2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauStoreSolverTypeOnModule)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauSelectivelyRetainDFGArena)
|
||||
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -77,20 +82,6 @@ struct BuildQueueItem
|
|||
Frontend::Stats stats;
|
||||
};
|
||||
|
||||
struct BuildQueueWorkState
|
||||
{
|
||||
std::function<void(std::function<void()> task)> executeTask;
|
||||
|
||||
std::vector<BuildQueueItem> buildQueueItems;
|
||||
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
std::vector<size_t> readyQueueItems;
|
||||
|
||||
size_t processing = 0;
|
||||
size_t remaining = 0;
|
||||
};
|
||||
|
||||
std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments)
|
||||
{
|
||||
for (const HotComment& hc : hotcomments)
|
||||
|
@ -455,6 +446,20 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
|||
|
||||
if (item.name == name)
|
||||
checkResult.lintResult = item.module->lintResult;
|
||||
|
||||
if (FFlag::StudioReportLuauAny2 && item.options.retainFullTypeGraphs)
|
||||
{
|
||||
if (item.module)
|
||||
{
|
||||
const SourceModule& sourceModule = *item.sourceModule;
|
||||
if (sourceModule.mode == Luau::Mode::Strict)
|
||||
{
|
||||
item.module->ats.root = toString(sourceModule.root);
|
||||
}
|
||||
item.module->ats.rootSrc = sourceModule.root;
|
||||
item.module->ats.traverse(item.module.get(), sourceModule.root, NotNull{&builtinTypes_});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return checkResult;
|
||||
|
@ -485,8 +490,7 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
|
|||
std::swap(currModuleQueue, moduleQueue);
|
||||
|
||||
DenseHashSet<Luau::ModuleName> seen{{}};
|
||||
|
||||
std::shared_ptr<BuildQueueWorkState> state = std::make_shared<BuildQueueWorkState>();
|
||||
std::vector<BuildQueueItem> buildQueueItems;
|
||||
|
||||
for (const ModuleName& name : currModuleQueue)
|
||||
{
|
||||
|
@ -510,18 +514,18 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
|
|||
}
|
||||
);
|
||||
|
||||
addBuildQueueItems(state->buildQueueItems, queue, cycleDetected, seen, frontendOptions);
|
||||
addBuildQueueItems(buildQueueItems, queue, cycleDetected, seen, frontendOptions);
|
||||
}
|
||||
|
||||
if (state->buildQueueItems.empty())
|
||||
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 < state->buildQueueItems.size(); i++)
|
||||
for (size_t i = 0; i < buildQueueItems.size(); i++)
|
||||
{
|
||||
BuildQueueItem& item = state->buildQueueItems[i];
|
||||
BuildQueueItem& item = buildQueueItems[i];
|
||||
moduleNameToQueue[item.name] = i;
|
||||
}
|
||||
|
||||
|
@ -534,13 +538,67 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
|
|||
};
|
||||
}
|
||||
|
||||
state->executeTask = executeTask;
|
||||
state->remaining = state->buildQueueItems.size();
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
std::vector<size_t> readyQueueItems;
|
||||
|
||||
// Record dependencies between modules
|
||||
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
|
||||
size_t processing = 0;
|
||||
size_t remaining = buildQueueItems.size();
|
||||
|
||||
auto itemTask = [&](size_t i)
|
||||
{
|
||||
BuildQueueItem& item = state->buildQueueItems[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)
|
||||
{
|
||||
|
@ -550,45 +608,41 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
|
|||
{
|
||||
item.dirtyDependencies++;
|
||||
|
||||
state->buildQueueItems[moduleNameToQueue[dep]].reverseDeps.push_back(i);
|
||||
buildQueueItems[moduleNameToQueue[dep]].reverseDeps.push_back(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item.dirtyDependencies == 0)
|
||||
sendItemTask(i);
|
||||
}
|
||||
|
||||
// In the first pass, check all modules with no pending dependencies
|
||||
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
|
||||
{
|
||||
if (state->buildQueueItems[i].dirtyDependencies == 0)
|
||||
sendQueueItemTask(state, i);
|
||||
}
|
||||
|
||||
// If not a single item was found, a cycle in the graph was hit
|
||||
if (state->processing == 0)
|
||||
sendQueueCycleItemTask(state);
|
||||
// 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 (state->remaining != 0)
|
||||
while (remaining != 0)
|
||||
{
|
||||
{
|
||||
std::unique_lock guard(state->mtx);
|
||||
std::unique_lock guard(mtx);
|
||||
|
||||
// If nothing is ready yet, wait
|
||||
state->cv.wait(
|
||||
cv.wait(
|
||||
guard,
|
||||
[state]
|
||||
[&readyQueueItems]
|
||||
{
|
||||
return !state->readyQueueItems.empty();
|
||||
return !readyQueueItems.empty();
|
||||
}
|
||||
);
|
||||
|
||||
// Handle checked items
|
||||
for (size_t i : state->readyQueueItems)
|
||||
for (size_t i : readyQueueItems)
|
||||
{
|
||||
const BuildQueueItem& item = state->buildQueueItems[i];
|
||||
const BuildQueueItem& item = buildQueueItems[i];
|
||||
|
||||
// If exception was thrown, stop adding new items and wait for processing items to complete
|
||||
if (item.exception)
|
||||
|
@ -605,7 +659,7 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
|
|||
// Notify items that were waiting for this dependency
|
||||
for (size_t reverseDep : item.reverseDeps)
|
||||
{
|
||||
BuildQueueItem& reverseDepItem = state->buildQueueItems[reverseDep];
|
||||
BuildQueueItem& reverseDepItem = buildQueueItems[reverseDep];
|
||||
|
||||
LUAU_ASSERT(reverseDepItem.dirtyDependencies != 0);
|
||||
reverseDepItem.dirtyDependencies--;
|
||||
|
@ -616,26 +670,26 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
|
|||
}
|
||||
}
|
||||
|
||||
LUAU_ASSERT(state->processing >= state->readyQueueItems.size());
|
||||
state->processing -= state->readyQueueItems.size();
|
||||
LUAU_ASSERT(processing >= readyQueueItems.size());
|
||||
processing -= readyQueueItems.size();
|
||||
|
||||
LUAU_ASSERT(state->remaining >= state->readyQueueItems.size());
|
||||
state->remaining -= state->readyQueueItems.size();
|
||||
state->readyQueueItems.clear();
|
||||
LUAU_ASSERT(remaining >= readyQueueItems.size());
|
||||
remaining -= readyQueueItems.size();
|
||||
readyQueueItems.clear();
|
||||
}
|
||||
|
||||
if (progress)
|
||||
{
|
||||
if (!progress(state->buildQueueItems.size() - state->remaining, state->buildQueueItems.size()))
|
||||
if (!progress(buildQueueItems.size() - remaining, buildQueueItems.size()))
|
||||
cancelled = true;
|
||||
}
|
||||
|
||||
// Items cannot be submitted while holding the lock
|
||||
for (size_t i : nextItems)
|
||||
sendQueueItemTask(state, i);
|
||||
sendItemTask(i);
|
||||
nextItems.clear();
|
||||
|
||||
if (state->processing == 0)
|
||||
if (processing == 0)
|
||||
{
|
||||
// Typechecking might have been cancelled by user, don't return partial results
|
||||
if (cancelled)
|
||||
|
@ -643,19 +697,19 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
|
|||
|
||||
// We might have stopped because of a pending exception
|
||||
if (itemWithException)
|
||||
recordItemResult(state->buildQueueItems[*itemWithException]);
|
||||
recordItemResult(buildQueueItems[*itemWithException]);
|
||||
}
|
||||
|
||||
// If we aren't done, but don't have anything processing, we hit a cycle
|
||||
if (state->remaining != 0 && state->processing == 0)
|
||||
sendQueueCycleItemTask(state);
|
||||
if (remaining != 0 && processing == 0)
|
||||
sendCycleItemTask();
|
||||
}
|
||||
|
||||
std::vector<ModuleName> checkedModules;
|
||||
checkedModules.reserve(state->buildQueueItems.size());
|
||||
checkedModules.reserve(buildQueueItems.size());
|
||||
|
||||
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
|
||||
checkedModules.push_back(std::move(state->buildQueueItems[i].name));
|
||||
for (size_t i = 0; i < buildQueueItems.size(); i++)
|
||||
checkedModules.push_back(std::move(buildQueueItems[i].name));
|
||||
|
||||
return checkedModules;
|
||||
}
|
||||
|
@ -768,11 +822,14 @@ bool Frontend::parseGraph(
|
|||
|
||||
buildQueue.push_back(top->name);
|
||||
|
||||
// at this point we know all valid dependencies are processed into SourceNodes
|
||||
for (const ModuleName& dep : top->requireSet)
|
||||
if (FFlag::LuauBetterReverseDependencyTracking)
|
||||
{
|
||||
if (auto it = sourceNodes.find(dep); it != sourceNodes.end())
|
||||
it->second->dependents.insert(top->name);
|
||||
// at this point we know all valid dependencies are processed into SourceNodes
|
||||
for (const ModuleName& dep : top->requireSet)
|
||||
{
|
||||
if (auto it = sourceNodes.find(dep); it != sourceNodes.end())
|
||||
it->second->dependents.insert(top->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -951,7 +1008,7 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
|
|||
item.stats.timeCheck += duration;
|
||||
item.stats.filesStrict += 1;
|
||||
|
||||
if (item.options.customModuleCheck)
|
||||
if (DFFlag::LuauRunCustomModuleChecks && item.options.customModuleCheck)
|
||||
item.options.customModuleCheck(sourceModule, *moduleForAutocomplete);
|
||||
|
||||
item.module = moduleForAutocomplete;
|
||||
|
@ -971,7 +1028,7 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
|
|||
item.stats.filesStrict += mode == Mode::Strict;
|
||||
item.stats.filesNonstrict += mode == Mode::Nonstrict;
|
||||
|
||||
if (item.options.customModuleCheck)
|
||||
if (DFFlag::LuauRunCustomModuleChecks && item.options.customModuleCheck)
|
||||
item.options.customModuleCheck(sourceModule, *module);
|
||||
|
||||
if (FFlag::LuauSolverV2 && mode == Mode::NoCheck)
|
||||
|
@ -1061,35 +1118,51 @@ void Frontend::recordItemResult(const BuildQueueItem& item)
|
|||
if (item.exception)
|
||||
std::rethrow_exception(item.exception);
|
||||
|
||||
bool replacedModule = false;
|
||||
if (item.options.forAutocomplete)
|
||||
if (FFlag::LuauBetterReverseDependencyTracking)
|
||||
{
|
||||
replacedModule = moduleResolverForAutocomplete.setModule(item.name, item.module);
|
||||
item.sourceNode->dirtyModuleForAutocomplete = false;
|
||||
bool replacedModule = false;
|
||||
if (item.options.forAutocomplete)
|
||||
{
|
||||
replacedModule = moduleResolverForAutocomplete.setModule(item.name, item.module);
|
||||
item.sourceNode->dirtyModuleForAutocomplete = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
replacedModule = moduleResolver.setModule(item.name, item.module);
|
||||
item.sourceNode->dirtyModule = false;
|
||||
}
|
||||
|
||||
if (replacedModule)
|
||||
{
|
||||
LUAU_TIMETRACE_SCOPE("Frontend::invalidateDependentModules", "Frontend");
|
||||
LUAU_TIMETRACE_ARGUMENT("name", item.name.c_str());
|
||||
traverseDependents(
|
||||
item.name,
|
||||
[forAutocomplete = item.options.forAutocomplete](SourceNode& sourceNode)
|
||||
{
|
||||
bool traverseSubtree = !sourceNode.hasInvalidModuleDependency(forAutocomplete);
|
||||
sourceNode.setInvalidModuleDependency(true, forAutocomplete);
|
||||
return traverseSubtree;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
item.sourceNode->setInvalidModuleDependency(false, item.options.forAutocomplete);
|
||||
}
|
||||
else
|
||||
{
|
||||
replacedModule = moduleResolver.setModule(item.name, item.module);
|
||||
item.sourceNode->dirtyModule = false;
|
||||
if (item.options.forAutocomplete)
|
||||
{
|
||||
moduleResolverForAutocomplete.setModule(item.name, item.module);
|
||||
item.sourceNode->dirtyModuleForAutocomplete = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
moduleResolver.setModule(item.name, item.module);
|
||||
item.sourceNode->dirtyModule = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (replacedModule)
|
||||
{
|
||||
LUAU_TIMETRACE_SCOPE("Frontend::invalidateDependentModules", "Frontend");
|
||||
LUAU_TIMETRACE_ARGUMENT("name", item.name.c_str());
|
||||
traverseDependents(
|
||||
item.name,
|
||||
[forAutocomplete = item.options.forAutocomplete](SourceNode& sourceNode)
|
||||
{
|
||||
bool traverseSubtree = !sourceNode.hasInvalidModuleDependency(forAutocomplete);
|
||||
sourceNode.setInvalidModuleDependency(true, forAutocomplete);
|
||||
return traverseSubtree;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
item.sourceNode->setInvalidModuleDependency(false, item.options.forAutocomplete);
|
||||
|
||||
stats.timeCheck += item.stats.timeCheck;
|
||||
stats.timeLint += item.stats.timeLint;
|
||||
|
||||
|
@ -1097,72 +1170,6 @@ void Frontend::recordItemResult(const BuildQueueItem& item)
|
|||
stats.filesNonstrict += item.stats.filesNonstrict;
|
||||
}
|
||||
|
||||
void Frontend::performQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos)
|
||||
{
|
||||
BuildQueueItem& item = state->buildQueueItems[itemPos];
|
||||
|
||||
if (DFFlag::LuauRethrowKnownExceptions)
|
||||
{
|
||||
try
|
||||
{
|
||||
checkBuildQueueItem(item);
|
||||
}
|
||||
catch (const Luau::InternalCompilerError&)
|
||||
{
|
||||
item.exception = std::current_exception();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
checkBuildQueueItem(item);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
item.exception = std::current_exception();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock guard(state->mtx);
|
||||
state->readyQueueItems.push_back(itemPos);
|
||||
}
|
||||
|
||||
state->cv.notify_one();
|
||||
}
|
||||
|
||||
void Frontend::sendQueueItemTask(std::shared_ptr<BuildQueueWorkState> state, size_t itemPos)
|
||||
{
|
||||
BuildQueueItem& item = state->buildQueueItems[itemPos];
|
||||
|
||||
LUAU_ASSERT(!item.processing);
|
||||
item.processing = true;
|
||||
|
||||
state->processing++;
|
||||
|
||||
state->executeTask(
|
||||
[this, state, itemPos]()
|
||||
{
|
||||
performQueueItemTask(state, itemPos);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void Frontend::sendQueueCycleItemTask(std::shared_ptr<BuildQueueWorkState> state)
|
||||
{
|
||||
for (size_t i = 0; i < state->buildQueueItems.size(); i++)
|
||||
{
|
||||
BuildQueueItem& item = state->buildQueueItems[i];
|
||||
|
||||
if (!item.processing)
|
||||
{
|
||||
sendQueueItemTask(state, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config& config, bool forAutocomplete) const
|
||||
{
|
||||
ScopePtr result;
|
||||
|
@ -1192,6 +1199,7 @@ ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config
|
|||
|
||||
bool Frontend::allModuleDependenciesValid(const ModuleName& name, bool forAutocomplete) const
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauBetterReverseDependencyTracking);
|
||||
auto it = sourceNodes.find(name);
|
||||
return it != sourceNodes.end() && !it->second->hasInvalidModuleDependency(forAutocomplete);
|
||||
}
|
||||
|
@ -1213,27 +1221,72 @@ void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* marked
|
|||
LUAU_TIMETRACE_SCOPE("Frontend::markDirty", "Frontend");
|
||||
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
|
||||
|
||||
traverseDependents(
|
||||
name,
|
||||
[markedDirty](SourceNode& sourceNode)
|
||||
if (FFlag::LuauBetterReverseDependencyTracking)
|
||||
{
|
||||
traverseDependents(
|
||||
name,
|
||||
[markedDirty](SourceNode& sourceNode)
|
||||
{
|
||||
if (markedDirty)
|
||||
markedDirty->push_back(sourceNode.name);
|
||||
|
||||
if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
|
||||
return false;
|
||||
|
||||
sourceNode.dirtySourceModule = true;
|
||||
sourceNode.dirtyModule = true;
|
||||
sourceNode.dirtyModuleForAutocomplete = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sourceNodes.count(name) == 0)
|
||||
return;
|
||||
|
||||
std::unordered_map<ModuleName, std::vector<ModuleName>> reverseDeps;
|
||||
for (const auto& module : sourceNodes)
|
||||
{
|
||||
for (const auto& dep : module.second->requireSet)
|
||||
reverseDeps[dep].push_back(module.first);
|
||||
}
|
||||
|
||||
std::vector<ModuleName> queue{name};
|
||||
|
||||
while (!queue.empty())
|
||||
{
|
||||
ModuleName next = std::move(queue.back());
|
||||
queue.pop_back();
|
||||
|
||||
LUAU_ASSERT(sourceNodes.count(next) > 0);
|
||||
SourceNode& sourceNode = *sourceNodes[next];
|
||||
|
||||
if (markedDirty)
|
||||
markedDirty->push_back(sourceNode.name);
|
||||
markedDirty->push_back(next);
|
||||
|
||||
if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
|
||||
return false;
|
||||
continue;
|
||||
|
||||
sourceNode.dirtySourceModule = true;
|
||||
sourceNode.dirtyModule = true;
|
||||
sourceNode.dirtyModuleForAutocomplete = true;
|
||||
|
||||
return true;
|
||||
if (0 == reverseDeps.count(next))
|
||||
continue;
|
||||
|
||||
sourceModules.erase(next);
|
||||
|
||||
const std::vector<ModuleName>& dependents = reverseDeps[next];
|
||||
queue.insert(queue.end(), dependents.begin(), dependents.end());
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void Frontend::traverseDependents(const ModuleName& name, std::function<bool(SourceNode&)> processSubtree)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauBetterReverseDependencyTracking);
|
||||
LUAU_TIMETRACE_SCOPE("Frontend::traverseDependents", "Frontend");
|
||||
|
||||
if (sourceNodes.count(name) == 0)
|
||||
|
@ -1280,7 +1333,6 @@ ModulePtr check(
|
|||
NotNull<ModuleResolver> moduleResolver,
|
||||
NotNull<FileResolver> fileResolver,
|
||||
const ScopePtr& parentScope,
|
||||
const ScopePtr& typeFunctionScope,
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||
FrontendOptions options,
|
||||
TypeCheckLimits limits,
|
||||
|
@ -1297,7 +1349,6 @@ ModulePtr check(
|
|||
moduleResolver,
|
||||
fileResolver,
|
||||
parentScope,
|
||||
typeFunctionScope,
|
||||
std::move(prepareModuleScope),
|
||||
options,
|
||||
limits,
|
||||
|
@ -1359,7 +1410,6 @@ ModulePtr check(
|
|||
NotNull<ModuleResolver> moduleResolver,
|
||||
NotNull<FileResolver> fileResolver,
|
||||
const ScopePtr& parentScope,
|
||||
const ScopePtr& typeFunctionScope,
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||
FrontendOptions options,
|
||||
TypeCheckLimits limits,
|
||||
|
@ -1372,7 +1422,8 @@ ModulePtr check(
|
|||
LUAU_TIMETRACE_ARGUMENT("name", sourceModule.humanReadableName.c_str());
|
||||
|
||||
ModulePtr result = std::make_shared<Module>();
|
||||
result->checkedInNewSolver = true;
|
||||
if (FFlag::LuauStoreSolverTypeOnModule)
|
||||
result->checkedInNewSolver = true;
|
||||
result->name = sourceModule.name;
|
||||
result->humanReadableName = sourceModule.humanReadableName;
|
||||
result->mode = mode;
|
||||
|
@ -1380,7 +1431,6 @@ ModulePtr check(
|
|||
result->interfaceTypes.owningModule = result.get();
|
||||
result->allocator = sourceModule.allocator;
|
||||
result->names = sourceModule.names;
|
||||
result->root = sourceModule.root;
|
||||
|
||||
iceHandler->moduleName = sourceModule.name;
|
||||
|
||||
|
@ -1405,7 +1455,7 @@ ModulePtr check(
|
|||
SimplifierPtr simplifier = newSimplifier(NotNull{&result->internalTypes}, builtinTypes);
|
||||
TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}};
|
||||
|
||||
typeFunctionRuntime.allowEvaluation = FFlag::LuauTypeFunResultInAutocomplete || sourceModule.parseErrors.empty();
|
||||
typeFunctionRuntime.allowEvaluation = sourceModule.parseErrors.empty();
|
||||
|
||||
ConstraintGenerator cg{
|
||||
result,
|
||||
|
@ -1416,7 +1466,6 @@ ModulePtr check(
|
|||
builtinTypes,
|
||||
iceHandler,
|
||||
parentScope,
|
||||
typeFunctionScope,
|
||||
std::move(prepareModuleScope),
|
||||
logger.get(),
|
||||
NotNull{&dfg},
|
||||
|
@ -1599,7 +1648,6 @@ ModulePtr Frontend::check(
|
|||
NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver},
|
||||
NotNull{fileResolver},
|
||||
environmentScope ? *environmentScope : globals.globalScope,
|
||||
globals.globalTypeFunctionScope,
|
||||
prepareModuleScopeWrap,
|
||||
options,
|
||||
typeCheckLimits,
|
||||
|
@ -1698,11 +1746,14 @@ std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(const ModuleName&
|
|||
sourceNode->name = sourceModule->name;
|
||||
sourceNode->humanReadableName = sourceModule->humanReadableName;
|
||||
|
||||
// clear all prior dependents. we will re-add them after parsing the rest of the graph
|
||||
for (const auto& [moduleName, _] : sourceNode->requireLocations)
|
||||
if (FFlag::LuauBetterReverseDependencyTracking)
|
||||
{
|
||||
if (auto depIt = sourceNodes.find(moduleName); depIt != sourceNodes.end())
|
||||
depIt->second->dependents.erase(sourceNode->name);
|
||||
// clear all prior dependents. we will re-add them after parsing the rest of the graph
|
||||
for (const auto& [moduleName, _] : sourceNode->requireLocations)
|
||||
{
|
||||
if (auto depIt = sourceNodes.find(moduleName); depIt != sourceNodes.end())
|
||||
depIt->second->dependents.erase(sourceNode->name);
|
||||
}
|
||||
}
|
||||
|
||||
sourceNode->requireSet.clear();
|
||||
|
@ -1830,9 +1881,17 @@ bool FrontendModuleResolver::setModule(const ModuleName& moduleName, ModulePtr m
|
|||
{
|
||||
std::scoped_lock lock(moduleMutex);
|
||||
|
||||
bool replaced = modules.count(moduleName) > 0;
|
||||
modules[moduleName] = std::move(module);
|
||||
return replaced;
|
||||
if (FFlag::LuauBetterReverseDependencyTracking)
|
||||
{
|
||||
bool replaced = modules.count(moduleName) > 0;
|
||||
modules[moduleName] = std::move(module);
|
||||
return replaced;
|
||||
}
|
||||
else
|
||||
{
|
||||
modules[moduleName] = std::move(module);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void FrontendModuleResolver::clearModules()
|
||||
|
|
|
@ -4,84 +4,19 @@
|
|||
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/InsertionOrderedMap.h"
|
||||
#include "Luau/Polarity.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/VisitType.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauNonReentrantGeneralization)
|
||||
LUAU_FASTFLAGVARIABLE(LuauGeneralizationRemoveRecursiveUpperBound2)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
struct OrderedSet
|
||||
{
|
||||
using iterator = typename std::vector<T>::iterator;
|
||||
using const_iterator = typename std::vector<T>::const_iterator;
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return elements.empty();
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return elements.size();
|
||||
}
|
||||
|
||||
void insert(T t)
|
||||
{
|
||||
if (!elementSet.contains(t))
|
||||
{
|
||||
elementSet.insert(t);
|
||||
elements.push_back(t);
|
||||
}
|
||||
}
|
||||
|
||||
iterator begin()
|
||||
{
|
||||
return elements.begin();
|
||||
}
|
||||
|
||||
const_iterator begin() const
|
||||
{
|
||||
return elements.begin();
|
||||
}
|
||||
|
||||
iterator end()
|
||||
{
|
||||
return elements.end();
|
||||
}
|
||||
|
||||
const_iterator end() const
|
||||
{
|
||||
return elements.end();
|
||||
}
|
||||
|
||||
/// Move the underlying vector out of the OrderedSet.
|
||||
std::vector<T> takeVector()
|
||||
{
|
||||
elementSet.clear();
|
||||
return std::move(elements);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<T> elements;
|
||||
DenseHashSet<T> elementSet{nullptr};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
struct MutatingGeneralizer : TypeOnceVisitor
|
||||
{
|
||||
NotNull<TypeArena> arena;
|
||||
|
@ -95,6 +30,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
|||
std::vector<TypePackId> genericPacks;
|
||||
|
||||
bool isWithinFunction = false;
|
||||
bool avoidSealingTables = false;
|
||||
|
||||
MutatingGeneralizer(
|
||||
NotNull<TypeArena> arena,
|
||||
|
@ -102,7 +38,8 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
|||
NotNull<Scope> scope,
|
||||
NotNull<DenseHashSet<TypeId>> cachedTypes,
|
||||
DenseHashMap<const void*, size_t> positiveTypes,
|
||||
DenseHashMap<const void*, size_t> negativeTypes
|
||||
DenseHashMap<const void*, size_t> negativeTypes,
|
||||
bool avoidSealingTables
|
||||
)
|
||||
: TypeOnceVisitor(/* skipBoundTypes */ true)
|
||||
, arena(arena)
|
||||
|
@ -111,6 +48,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
|||
, cachedTypes(cachedTypes)
|
||||
, positiveTypes(std::move(positiveTypes))
|
||||
, negativeTypes(std::move(negativeTypes))
|
||||
, avoidSealingTables(avoidSealingTables)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -161,7 +99,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
|||
LUAU_ASSERT(onlyType != haystack);
|
||||
emplaceType<BoundType>(asMutable(haystack), onlyType);
|
||||
}
|
||||
else if (ut->options.empty())
|
||||
else if (FFlag::LuauGeneralizationRemoveRecursiveUpperBound2 && ut->options.empty())
|
||||
{
|
||||
emplaceType<BoundType>(asMutable(haystack), builtinTypes->neverType);
|
||||
}
|
||||
|
@ -208,7 +146,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
|||
LUAU_ASSERT(onlyType != needle);
|
||||
emplaceType<BoundType>(asMutable(needle), onlyType);
|
||||
}
|
||||
else if (it->parts.empty())
|
||||
else if (FFlag::LuauGeneralizationRemoveRecursiveUpperBound2 && it->parts.empty())
|
||||
{
|
||||
emplaceType<BoundType>(asMutable(needle), builtinTypes->unknownType);
|
||||
}
|
||||
|
@ -336,15 +274,6 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
|||
return 0;
|
||||
}
|
||||
|
||||
template<typename TID>
|
||||
static size_t getCount(const DenseHashMap<TID, size_t>& map, TID ty)
|
||||
{
|
||||
if (const size_t* count = map.find(ty))
|
||||
return *count;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const TableType&) override
|
||||
{
|
||||
if (cachedTypes->contains(ty))
|
||||
|
@ -363,7 +292,8 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
|||
TableType* tt = getMutable<TableType>(ty);
|
||||
LUAU_ASSERT(tt);
|
||||
|
||||
tt->state = TableState::Sealed;
|
||||
if (!avoidSealingTables)
|
||||
tt->state = TableState::Sealed;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -402,22 +332,38 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
{
|
||||
}
|
||||
|
||||
bool isWithinFunction = false;
|
||||
Polarity polarity = Polarity::Positive;
|
||||
enum Polarity
|
||||
{
|
||||
Positive,
|
||||
Negative,
|
||||
Both,
|
||||
};
|
||||
|
||||
Polarity polarity = Positive;
|
||||
|
||||
void flip()
|
||||
{
|
||||
polarity = invert(polarity);
|
||||
switch (polarity)
|
||||
{
|
||||
case Positive:
|
||||
polarity = Negative;
|
||||
break;
|
||||
case Negative:
|
||||
polarity = Positive;
|
||||
break;
|
||||
case Both:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DenseHashSet<const void*> seenPositive{nullptr};
|
||||
DenseHashSet<const void*> seenNegative{nullptr};
|
||||
|
||||
bool seenWithCurrentPolarity(const void* ty)
|
||||
bool seenWithPolarity(const void* ty)
|
||||
{
|
||||
switch (polarity)
|
||||
{
|
||||
case Polarity::Positive:
|
||||
case Positive:
|
||||
{
|
||||
if (seenPositive.contains(ty))
|
||||
return true;
|
||||
|
@ -425,7 +371,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
seenPositive.insert(ty);
|
||||
return false;
|
||||
}
|
||||
case Polarity::Negative:
|
||||
case Negative:
|
||||
{
|
||||
if (seenNegative.contains(ty))
|
||||
return true;
|
||||
|
@ -433,7 +379,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
seenNegative.insert(ty);
|
||||
return false;
|
||||
}
|
||||
case Polarity::Mixed:
|
||||
case Both:
|
||||
{
|
||||
if (seenPositive.contains(ty) && seenNegative.contains(ty))
|
||||
return true;
|
||||
|
@ -442,24 +388,20 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
seenNegative.insert(ty);
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// The keys in these maps are either TypeIds or TypePackIds. It's safe to
|
||||
// mix them because we only use these pointers as unique keys. We never
|
||||
// indirect them.
|
||||
DenseHashMap<const void*, size_t> negativeTypes{0};
|
||||
DenseHashMap<const void*, size_t> positiveTypes{0};
|
||||
|
||||
InsertionOrderedMap<TypeId, GeneralizationParams<TypeId>> types;
|
||||
InsertionOrderedMap<TypePackId, GeneralizationParams<TypePackId>> typePacks;
|
||||
|
||||
OrderedSet<TypeId> unsealedTables;
|
||||
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
|
||||
if (cachedTypes->contains(ty) || seenWithPolarity(ty))
|
||||
return false;
|
||||
|
||||
LUAU_ASSERT(ty);
|
||||
|
@ -468,45 +410,24 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
bool visit(TypeId ty, const FreeType& ft) override
|
||||
{
|
||||
if (FFlag::LuauNonReentrantGeneralization)
|
||||
if (cachedTypes->contains(ty) || seenWithPolarity(ty))
|
||||
return false;
|
||||
|
||||
if (!subsumes(scope, ft.scope))
|
||||
return true;
|
||||
|
||||
switch (polarity)
|
||||
{
|
||||
if (!subsumes(scope, ft.scope))
|
||||
return true;
|
||||
|
||||
GeneralizationParams<TypeId>& params = types[ty];
|
||||
++params.useCount;
|
||||
|
||||
if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
|
||||
return false;
|
||||
|
||||
if (!isWithinFunction)
|
||||
params.foundOutsideFunctions = true;
|
||||
|
||||
params.polarity |= polarity;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
|
||||
return false;
|
||||
|
||||
if (!subsumes(scope, ft.scope))
|
||||
return true;
|
||||
|
||||
switch (polarity)
|
||||
{
|
||||
case Polarity::Positive:
|
||||
positiveTypes[ty]++;
|
||||
break;
|
||||
case Polarity::Negative:
|
||||
negativeTypes[ty]++;
|
||||
break;
|
||||
case Polarity::Mixed:
|
||||
positiveTypes[ty]++;
|
||||
negativeTypes[ty]++;
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
case Positive:
|
||||
positiveTypes[ty]++;
|
||||
break;
|
||||
case Negative:
|
||||
negativeTypes[ty]++;
|
||||
break;
|
||||
case Both:
|
||||
positiveTypes[ty]++;
|
||||
negativeTypes[ty]++;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -514,30 +435,23 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
bool visit(TypeId ty, const TableType& tt) override
|
||||
{
|
||||
if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
|
||||
if (cachedTypes->contains(ty) || seenWithPolarity(ty))
|
||||
return false;
|
||||
|
||||
if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope))
|
||||
{
|
||||
if (FFlag::LuauNonReentrantGeneralization)
|
||||
unsealedTables.insert(ty);
|
||||
else
|
||||
switch (polarity)
|
||||
{
|
||||
switch (polarity)
|
||||
{
|
||||
case Polarity::Positive:
|
||||
positiveTypes[ty]++;
|
||||
break;
|
||||
case Polarity::Negative:
|
||||
negativeTypes[ty]++;
|
||||
break;
|
||||
case Polarity::Mixed:
|
||||
positiveTypes[ty]++;
|
||||
negativeTypes[ty]++;
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
case Positive:
|
||||
positiveTypes[ty]++;
|
||||
break;
|
||||
case Negative:
|
||||
negativeTypes[ty]++;
|
||||
break;
|
||||
case Both:
|
||||
positiveTypes[ty]++;
|
||||
negativeTypes[ty]++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -550,7 +464,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
LUAU_ASSERT(prop.isShared() || FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete);
|
||||
|
||||
Polarity p = polarity;
|
||||
polarity = Polarity::Mixed;
|
||||
polarity = Both;
|
||||
traverse(prop.type());
|
||||
polarity = p;
|
||||
}
|
||||
|
@ -558,27 +472,8 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
if (tt.indexer)
|
||||
{
|
||||
if (FFlag::LuauNonReentrantGeneralization)
|
||||
{
|
||||
// {[K]: V} is equivalent to three functions: get, set, and iterate
|
||||
//
|
||||
// (K) -> V
|
||||
// (K, V) -> ()
|
||||
// () -> {K}
|
||||
//
|
||||
// K and V therefore both have mixed polarity.
|
||||
|
||||
const Polarity p = polarity;
|
||||
polarity = Polarity::Mixed;
|
||||
traverse(tt.indexer->indexType);
|
||||
traverse(tt.indexer->indexResultType);
|
||||
polarity = p;
|
||||
}
|
||||
else
|
||||
{
|
||||
traverse(tt.indexer->indexType);
|
||||
traverse(tt.indexer->indexResultType);
|
||||
}
|
||||
traverse(tt.indexer->indexType);
|
||||
traverse(tt.indexer->indexResultType);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -586,20 +481,15 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
bool visit(TypeId ty, const FunctionType& ft) override
|
||||
{
|
||||
if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
|
||||
if (cachedTypes->contains(ty) || seenWithPolarity(ty))
|
||||
return false;
|
||||
|
||||
const bool oldValue = isWithinFunction;
|
||||
isWithinFunction = true;
|
||||
|
||||
flip();
|
||||
traverse(ft.argTypes);
|
||||
flip();
|
||||
|
||||
traverse(ft.retTypes);
|
||||
|
||||
isWithinFunction = oldValue;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -610,39 +500,24 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
bool visit(TypePackId tp, const FreeTypePack& ftp) override
|
||||
{
|
||||
if (seenWithCurrentPolarity(tp))
|
||||
if (seenWithPolarity(tp))
|
||||
return false;
|
||||
|
||||
if (!subsumes(scope, ftp.scope))
|
||||
return true;
|
||||
|
||||
if (FFlag::LuauNonReentrantGeneralization)
|
||||
switch (polarity)
|
||||
{
|
||||
GeneralizationParams<TypePackId>& params = typePacks[tp];
|
||||
++params.useCount;
|
||||
|
||||
if (!isWithinFunction)
|
||||
params.foundOutsideFunctions = true;
|
||||
|
||||
params.polarity |= polarity;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (polarity)
|
||||
{
|
||||
case Polarity::Positive:
|
||||
positiveTypes[tp]++;
|
||||
break;
|
||||
case Polarity::Negative:
|
||||
negativeTypes[tp]++;
|
||||
break;
|
||||
case Polarity::Mixed:
|
||||
positiveTypes[tp]++;
|
||||
negativeTypes[tp]++;
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
case Positive:
|
||||
positiveTypes[tp]++;
|
||||
break;
|
||||
case Negative:
|
||||
negativeTypes[tp]++;
|
||||
break;
|
||||
case Both:
|
||||
positiveTypes[tp]++;
|
||||
negativeTypes[tp]++;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -672,7 +547,7 @@ struct TypeCacher : TypeOnceVisitor
|
|||
{
|
||||
}
|
||||
|
||||
void cache(TypeId ty) const
|
||||
void cache(TypeId ty)
|
||||
{
|
||||
cachedTypes->insert(ty);
|
||||
}
|
||||
|
@ -1092,227 +967,13 @@ struct TypeCacher : TypeOnceVisitor
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove occurrences of `needle` within `haystack`. This is used to cull cyclic bounds from free types.
|
||||
*
|
||||
* @param haystack Either the upper or lower bound of a free type.
|
||||
* @param needle The type to be removed.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
static TypeId removeType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, DenseHashSet<TypeId>& seen, TypeId haystack, TypeId needle)
|
||||
{
|
||||
haystack = follow(haystack);
|
||||
|
||||
if (seen.find(haystack))
|
||||
return haystack;
|
||||
seen.insert(haystack);
|
||||
|
||||
if (const UnionType* ut = get<UnionType>(haystack))
|
||||
{
|
||||
OrderedSet<TypeId> newOptions;
|
||||
|
||||
for (TypeId option : ut)
|
||||
{
|
||||
if (option == needle)
|
||||
continue;
|
||||
|
||||
if (get<NeverType>(option))
|
||||
continue;
|
||||
|
||||
LUAU_ASSERT(!get<UnionType>(option));
|
||||
|
||||
if (get<IntersectionType>(option))
|
||||
newOptions.insert(removeType(arena, builtinTypes, seen, option, needle));
|
||||
else
|
||||
newOptions.insert(option);
|
||||
}
|
||||
|
||||
if (newOptions.empty())
|
||||
return builtinTypes->neverType;
|
||||
else if (newOptions.size() == 1)
|
||||
{
|
||||
TypeId onlyType = *newOptions.begin();
|
||||
LUAU_ASSERT(onlyType != haystack);
|
||||
return onlyType;
|
||||
}
|
||||
else
|
||||
return arena->addType(UnionType{newOptions.takeVector()});
|
||||
}
|
||||
|
||||
if (const IntersectionType* it = get<IntersectionType>(haystack))
|
||||
{
|
||||
OrderedSet<TypeId> newParts;
|
||||
|
||||
for (TypeId part : it)
|
||||
{
|
||||
part = follow(part);
|
||||
|
||||
if (part == needle)
|
||||
continue;
|
||||
|
||||
if (get<UnknownType>(part))
|
||||
continue;
|
||||
|
||||
LUAU_ASSERT(!get<IntersectionType>(follow(part)));
|
||||
|
||||
if (get<UnionType>(part))
|
||||
newParts.insert(removeType(arena, builtinTypes, seen, part, needle));
|
||||
else
|
||||
newParts.insert(part);
|
||||
}
|
||||
|
||||
if (newParts.empty())
|
||||
return builtinTypes->unknownType;
|
||||
else if (newParts.size() == 1)
|
||||
{
|
||||
TypeId onlyType = *newParts.begin();
|
||||
LUAU_ASSERT(onlyType != needle);
|
||||
return onlyType;
|
||||
}
|
||||
else
|
||||
return arena->addType(IntersectionType{newParts.takeVector()});
|
||||
}
|
||||
|
||||
return haystack;
|
||||
}
|
||||
|
||||
std::optional<TypeId> generalizeType(
|
||||
NotNull<TypeArena> arena,
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<Scope> scope,
|
||||
TypeId freeTy,
|
||||
const GeneralizationParams<TypeId>& params
|
||||
)
|
||||
{
|
||||
freeTy = follow(freeTy);
|
||||
|
||||
FreeType* ft = getMutable<FreeType>(freeTy);
|
||||
LUAU_ASSERT(ft);
|
||||
|
||||
LUAU_ASSERT(isPositive(params.polarity) || isNegative(params.polarity));
|
||||
|
||||
const bool hasLowerBound = !get<NeverType>(follow(ft->lowerBound));
|
||||
const bool hasUpperBound = !get<UnknownType>(follow(ft->upperBound));
|
||||
|
||||
const bool isWithinFunction = !params.foundOutsideFunctions;
|
||||
|
||||
if (!hasLowerBound && !hasUpperBound)
|
||||
{
|
||||
if ((params.polarity != Polarity::Mixed && params.useCount == 1) || !isWithinFunction)
|
||||
emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType);
|
||||
else
|
||||
{
|
||||
emplaceType<GenericType>(asMutable(freeTy), scope, params.polarity);
|
||||
return freeTy;
|
||||
}
|
||||
}
|
||||
// It is possible that this free type has other free types in its upper
|
||||
// or lower bounds. If this is the case, we must replace those
|
||||
// references with never (for the lower bound) or unknown (for the upper
|
||||
// bound).
|
||||
//
|
||||
// If we do not do this, we get tautological bounds like a <: a <: unknown.
|
||||
else if (isPositive(params.polarity) && !hasUpperBound)
|
||||
{
|
||||
TypeId lb = follow(ft->lowerBound);
|
||||
if (FreeType* lowerFree = getMutable<FreeType>(lb); lowerFree && lowerFree->upperBound == freeTy)
|
||||
lowerFree->upperBound = builtinTypes->unknownType;
|
||||
else
|
||||
{
|
||||
DenseHashSet<TypeId> replaceSeen{nullptr};
|
||||
lb = removeType(arena, builtinTypes, replaceSeen, lb, freeTy);
|
||||
ft->lowerBound = lb;
|
||||
}
|
||||
|
||||
if (follow(lb) != freeTy)
|
||||
emplaceType<BoundType>(asMutable(freeTy), lb);
|
||||
else if (!isWithinFunction || params.useCount == 1)
|
||||
emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType);
|
||||
else
|
||||
{
|
||||
// if the lower bound is the type in question (eg 'a <: 'a), we don't actually have a lower bound.
|
||||
emplaceType<GenericType>(asMutable(freeTy), scope, params.polarity);
|
||||
return freeTy;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeId ub = follow(ft->upperBound);
|
||||
if (FreeType* upperFree = getMutable<FreeType>(ub); upperFree && upperFree->lowerBound == freeTy)
|
||||
upperFree->lowerBound = builtinTypes->neverType;
|
||||
else
|
||||
{
|
||||
// If the free type appears within its own upper bound, cull that cycle.
|
||||
DenseHashSet<TypeId> replaceSeen{nullptr};
|
||||
ub = removeType(arena, builtinTypes, replaceSeen, ub, freeTy);
|
||||
ft->upperBound = ub;
|
||||
}
|
||||
|
||||
if (follow(ub) != freeTy)
|
||||
emplaceType<BoundType>(asMutable(freeTy), ub);
|
||||
else if (!isWithinFunction || params.useCount == 1)
|
||||
emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType);
|
||||
else
|
||||
{
|
||||
// if the upper bound is the type in question, we don't actually have an upper bound.
|
||||
emplaceType<GenericType>(asMutable(freeTy), scope, params.polarity);
|
||||
return freeTy;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<TypePackId> generalizeTypePack(
|
||||
NotNull<TypeArena> arena,
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<Scope> scope,
|
||||
TypePackId tp,
|
||||
const GeneralizationParams<TypePackId>& params
|
||||
)
|
||||
{
|
||||
tp = follow(tp);
|
||||
|
||||
if (tp->owningArena != arena)
|
||||
return std::nullopt;
|
||||
|
||||
const FreeTypePack* ftp = get<FreeTypePack>(tp);
|
||||
if (!ftp)
|
||||
return std::nullopt;
|
||||
|
||||
if (!subsumes(scope, ftp->scope))
|
||||
return std::nullopt;
|
||||
|
||||
if (1 == params.useCount)
|
||||
emplaceTypePack<BoundTypePack>(asMutable(tp), builtinTypes->unknownTypePack);
|
||||
else
|
||||
{
|
||||
emplaceTypePack<GenericTypePack>(asMutable(tp), scope, params.polarity);
|
||||
return tp;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void sealTable(NotNull<Scope> scope, TypeId ty)
|
||||
{
|
||||
TableType* tableTy = getMutable<TableType>(follow(ty));
|
||||
if (!tableTy)
|
||||
return;
|
||||
|
||||
if (!subsumes(scope, tableTy->scope))
|
||||
return;
|
||||
|
||||
if (tableTy->state == TableState::Unsealed || tableTy->state == TableState::Free)
|
||||
tableTy->state = TableState::Sealed;
|
||||
}
|
||||
|
||||
std::optional<TypeId> generalize(
|
||||
NotNull<TypeArena> arena,
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<Scope> scope,
|
||||
NotNull<DenseHashSet<TypeId>> cachedTypes,
|
||||
TypeId ty
|
||||
TypeId ty,
|
||||
bool avoidSealingTables
|
||||
)
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
@ -1323,74 +984,35 @@ std::optional<TypeId> generalize(
|
|||
FreeTypeSearcher fts{scope, cachedTypes};
|
||||
fts.traverse(ty);
|
||||
|
||||
if (FFlag::LuauNonReentrantGeneralization)
|
||||
MutatingGeneralizer gen{arena, builtinTypes, scope, cachedTypes, std::move(fts.positiveTypes), std::move(fts.negativeTypes), avoidSealingTables};
|
||||
|
||||
gen.traverse(ty);
|
||||
|
||||
/* MutatingGeneralizer mutates types in place, so it is possible that ty has
|
||||
* been transmuted to a BoundType. We must follow it again and verify that
|
||||
* we are allowed to mutate it before we attach generics to it.
|
||||
*/
|
||||
ty = follow(ty);
|
||||
|
||||
if (ty->owningArena != arena || ty->persistent)
|
||||
return ty;
|
||||
|
||||
TypeCacher cacher{cachedTypes};
|
||||
cacher.traverse(ty);
|
||||
|
||||
FunctionType* ftv = getMutable<FunctionType>(ty);
|
||||
if (ftv)
|
||||
{
|
||||
FunctionType* functionTy = getMutable<FunctionType>(ty);
|
||||
auto pushGeneric = [&](TypeId t)
|
||||
// If we're generalizing a function type, add any of the newly inferred
|
||||
// generics to the list of existing generic types.
|
||||
for (const auto g : std::move(gen.generics))
|
||||
{
|
||||
if (functionTy)
|
||||
functionTy->generics.push_back(t);
|
||||
};
|
||||
|
||||
auto pushGenericPack = [&](TypePackId tp)
|
||||
{
|
||||
if (functionTy)
|
||||
functionTy->genericPacks.push_back(tp);
|
||||
};
|
||||
|
||||
for (const auto& [freeTy, params] : fts.types)
|
||||
{
|
||||
if (std::optional<TypeId> genericTy = generalizeType(arena, builtinTypes, scope, freeTy, params))
|
||||
pushGeneric(*genericTy);
|
||||
ftv->generics.push_back(g);
|
||||
}
|
||||
|
||||
for (TypeId unsealedTableTy : fts.unsealedTables)
|
||||
sealTable(scope, unsealedTableTy);
|
||||
|
||||
for (const auto& [freePackId, params] : fts.typePacks)
|
||||
// Ditto for generic packs.
|
||||
for (const auto gp : std::move(gen.genericPacks))
|
||||
{
|
||||
TypePackId freePack = follow(freePackId);
|
||||
std::optional<TypePackId> generalizedTp = generalizeTypePack(arena, builtinTypes, scope, freePack, params);
|
||||
|
||||
if (generalizedTp)
|
||||
pushGenericPack(freePack);
|
||||
}
|
||||
|
||||
TypeCacher cacher{cachedTypes};
|
||||
cacher.traverse(ty);
|
||||
}
|
||||
else
|
||||
{
|
||||
MutatingGeneralizer gen{arena, builtinTypes, scope, cachedTypes, std::move(fts.positiveTypes), std::move(fts.negativeTypes)};
|
||||
|
||||
gen.traverse(ty);
|
||||
|
||||
/* MutatingGeneralizer mutates types in place, so it is possible that ty has
|
||||
* been transmuted to a BoundType. We must follow it again and verify that
|
||||
* we are allowed to mutate it before we attach generics to it.
|
||||
*/
|
||||
ty = follow(ty);
|
||||
|
||||
if (ty->owningArena != arena || ty->persistent)
|
||||
return ty;
|
||||
|
||||
TypeCacher cacher{cachedTypes};
|
||||
cacher.traverse(ty);
|
||||
|
||||
FunctionType* ftv = getMutable<FunctionType>(ty);
|
||||
if (ftv)
|
||||
{
|
||||
// If we're generalizing a function type, add any of the newly inferred
|
||||
// generics to the list of existing generic types.
|
||||
for (const auto g : std::move(gen.generics))
|
||||
{
|
||||
ftv->generics.push_back(g);
|
||||
}
|
||||
// Ditto for generic packs.
|
||||
for (const auto gp : std::move(gen.genericPacks))
|
||||
{
|
||||
ftv->genericPacks.push_back(gp);
|
||||
}
|
||||
ftv->genericPacks.push_back(gp);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ GlobalTypes::GlobalTypes(NotNull<BuiltinTypes> builtinTypes)
|
|||
: builtinTypes(builtinTypes)
|
||||
{
|
||||
globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
|
||||
globalTypeFunctionScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
|
||||
|
||||
globalScope->addBuiltinTypeBinding("any", TypeFun{{}, builtinTypes->anyType});
|
||||
globalScope->addBuiltinTypeBinding("nil", TypeFun{{}, builtinTypes->nilType});
|
||||
|
|
|
@ -1,169 +0,0 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Polarity.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/VisitType.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauNonReentrantGeneralization)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct InferPolarity : TypeVisitor
|
||||
{
|
||||
NotNull<TypeArena> arena;
|
||||
NotNull<Scope> scope;
|
||||
|
||||
DenseHashMap<TypeId, Polarity> types{nullptr};
|
||||
DenseHashMap<TypePackId, Polarity> packs{nullptr};
|
||||
|
||||
Polarity polarity = Polarity::Positive;
|
||||
|
||||
explicit InferPolarity(NotNull<TypeArena> arena, NotNull<Scope> scope)
|
||||
: arena(arena)
|
||||
, scope(scope)
|
||||
{
|
||||
}
|
||||
|
||||
void flip()
|
||||
{
|
||||
polarity = invert(polarity);
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const GenericType& gt) override
|
||||
{
|
||||
if (ty->owningArena != arena)
|
||||
return false;
|
||||
|
||||
if (subsumes(scope, gt.scope))
|
||||
types[ty] |= polarity;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const TableType& tt) override
|
||||
{
|
||||
if (ty->owningArena != arena)
|
||||
return false;
|
||||
|
||||
const Polarity p = polarity;
|
||||
for (const auto& [name, prop] : tt.props)
|
||||
{
|
||||
if (prop.isShared())
|
||||
{
|
||||
polarity = Polarity::Mixed;
|
||||
traverse(prop.type());
|
||||
}
|
||||
else if (prop.isReadOnly())
|
||||
{
|
||||
polarity = p;
|
||||
traverse(*prop.readTy);
|
||||
}
|
||||
else if (prop.isWriteOnly())
|
||||
{
|
||||
polarity = invert(p);
|
||||
traverse(*prop.writeTy);
|
||||
}
|
||||
else
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
|
||||
if (tt.indexer)
|
||||
{
|
||||
polarity = Polarity::Mixed;
|
||||
traverse(tt.indexer->indexType);
|
||||
traverse(tt.indexer->indexResultType);
|
||||
}
|
||||
|
||||
polarity = p;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const FunctionType& ft) override
|
||||
{
|
||||
if (ty->owningArena != arena)
|
||||
return false;
|
||||
|
||||
const Polarity p = polarity;
|
||||
|
||||
polarity = Polarity::Positive;
|
||||
|
||||
// If these types actually occur within the function signature, their
|
||||
// polarity will be overwritten. If not, we infer that they are phantom
|
||||
// types.
|
||||
for (TypeId generic : ft.generics)
|
||||
{
|
||||
const auto gen = get<GenericType>(generic);
|
||||
LUAU_ASSERT(gen);
|
||||
if (subsumes(scope, gen->scope))
|
||||
types[generic] = Polarity::None;
|
||||
}
|
||||
for (TypePackId genericPack : ft.genericPacks)
|
||||
{
|
||||
const auto gen = get<GenericTypePack>(genericPack);
|
||||
LUAU_ASSERT(gen);
|
||||
if (subsumes(scope, gen->scope))
|
||||
packs[genericPack] = Polarity::None;
|
||||
}
|
||||
|
||||
flip();
|
||||
traverse(ft.argTypes);
|
||||
flip();
|
||||
traverse(ft.retTypes);
|
||||
|
||||
polarity = p;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId, const ClassType&) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypePackId tp, const GenericTypePack& gtp) override
|
||||
{
|
||||
packs[tp] |= polarity;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename TID>
|
||||
static void inferGenericPolarities_(NotNull<TypeArena> arena, NotNull<Scope> scope, TID ty)
|
||||
{
|
||||
if (!FFlag::LuauNonReentrantGeneralization)
|
||||
return;
|
||||
|
||||
InferPolarity infer{arena, scope};
|
||||
infer.traverse(ty);
|
||||
|
||||
for (const auto& [ty, polarity] : infer.types)
|
||||
{
|
||||
auto gt = getMutable<GenericType>(ty);
|
||||
LUAU_ASSERT(gt);
|
||||
gt->polarity = polarity;
|
||||
}
|
||||
|
||||
for (const auto& [tp, polarity] : infer.packs)
|
||||
{
|
||||
if (tp->owningArena != arena)
|
||||
continue;
|
||||
auto gp = getMutable<GenericTypePack>(tp);
|
||||
LUAU_ASSERT(gp);
|
||||
gp->polarity = polarity;
|
||||
}
|
||||
}
|
||||
|
||||
void inferGenericPolarities(NotNull<TypeArena> arena, NotNull<Scope> scope, TypeId ty)
|
||||
{
|
||||
inferGenericPolarities_(arena, scope, ty);
|
||||
}
|
||||
|
||||
void inferGenericPolarities(NotNull<TypeArena> arena, NotNull<Scope> scope, TypePackId tp)
|
||||
{
|
||||
inferGenericPolarities_(arena, scope, tp);
|
||||
}
|
||||
|
||||
} // namespace Luau
|
|
@ -61,7 +61,7 @@ TypeId Instantiation::clean(TypeId ty)
|
|||
const FunctionType* ftv = log->getMutable<FunctionType>(ty);
|
||||
LUAU_ASSERT(ftv);
|
||||
|
||||
FunctionType clone = FunctionType{level, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf};
|
||||
FunctionType clone = FunctionType{level, scope, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf};
|
||||
clone.magic = ftv->magic;
|
||||
clone.tags = ftv->tags;
|
||||
clone.argNames = ftv->argNames;
|
||||
|
|
|
@ -19,8 +19,6 @@ LUAU_FASTFLAG(LuauSolverV2)
|
|||
LUAU_FASTFLAG(LuauAttribute)
|
||||
LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute)
|
||||
|
||||
LUAU_FASTFLAG(LuauDeprecatedAttribute)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -2282,57 +2280,6 @@ private:
|
|||
{
|
||||
}
|
||||
|
||||
bool visit(AstExprLocal* node) override
|
||||
{
|
||||
if (FFlag::LuauDeprecatedAttribute)
|
||||
{
|
||||
|
||||
const FunctionType* fty = getFunctionType(node);
|
||||
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
|
||||
|
||||
if (shouldReport)
|
||||
report(node->location, node->local->name.value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstExprGlobal* node) override
|
||||
{
|
||||
if (FFlag::LuauDeprecatedAttribute)
|
||||
{
|
||||
const FunctionType* fty = getFunctionType(node);
|
||||
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
|
||||
|
||||
if (shouldReport)
|
||||
report(node->location, node->name.value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstStatLocalFunction* node) override
|
||||
{
|
||||
if (FFlag::LuauDeprecatedAttribute)
|
||||
{
|
||||
check(node->func);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstStatFunction* node) override
|
||||
{
|
||||
if (FFlag::LuauDeprecatedAttribute)
|
||||
{
|
||||
check(node->func);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstExprIndexName* node) override
|
||||
{
|
||||
if (std::optional<TypeId> ty = context->getType(node->expr))
|
||||
|
@ -2378,59 +2325,18 @@ private:
|
|||
|
||||
if (prop && prop->deprecated)
|
||||
report(node->location, *prop, cty->name.c_str(), node->index.value);
|
||||
else if (FFlag::LuauDeprecatedAttribute && prop)
|
||||
{
|
||||
if (std::optional<TypeId> ty = prop->readTy)
|
||||
{
|
||||
const FunctionType* fty = get<FunctionType>(follow(ty));
|
||||
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
|
||||
|
||||
if (shouldReport)
|
||||
{
|
||||
const char* className = nullptr;
|
||||
if (AstExprGlobal* global = node->expr->as<AstExprGlobal>())
|
||||
className = global->name.value;
|
||||
|
||||
const char* functionName = node->index.value;
|
||||
|
||||
report(node->location, className, functionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (const TableType* tty = get<TableType>(ty))
|
||||
{
|
||||
auto prop = tty->props.find(node->index.value);
|
||||
|
||||
if (prop != tty->props.end())
|
||||
if (prop != tty->props.end() && prop->second.deprecated)
|
||||
{
|
||||
if (prop->second.deprecated)
|
||||
{
|
||||
// strip synthetic typeof() for builtin tables
|
||||
if (tty->name && tty->name->compare(0, 7, "typeof(") == 0 && tty->name->back() == ')')
|
||||
report(node->location, prop->second, tty->name->substr(7, tty->name->length() - 8).c_str(), node->index.value);
|
||||
else
|
||||
report(node->location, prop->second, tty->name ? tty->name->c_str() : nullptr, node->index.value);
|
||||
}
|
||||
else if (FFlag::LuauDeprecatedAttribute)
|
||||
{
|
||||
if (std::optional<TypeId> ty = prop->second.readTy)
|
||||
{
|
||||
const FunctionType* fty = get<FunctionType>(follow(ty));
|
||||
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
|
||||
|
||||
if (shouldReport)
|
||||
{
|
||||
const char* className = nullptr;
|
||||
if (AstExprGlobal* global = node->expr->as<AstExprGlobal>())
|
||||
className = global->name.value;
|
||||
|
||||
const char* functionName = node->index.value;
|
||||
|
||||
report(node->location, className, functionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
// strip synthetic typeof() for builtin tables
|
||||
if (tty->name && tty->name->compare(0, 7, "typeof(") == 0 && tty->name->back() == ')')
|
||||
report(node->location, prop->second, tty->name->substr(7, tty->name->length() - 8).c_str(), node->index.value);
|
||||
else
|
||||
report(node->location, prop->second, tty->name ? tty->name->c_str() : nullptr, node->index.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2449,26 +2355,6 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
void check(AstExprFunction* func)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||
LUAU_ASSERT(func);
|
||||
|
||||
const FunctionType* fty = getFunctionType(func);
|
||||
bool isDeprecated = fty && fty->isDeprecatedFunction;
|
||||
|
||||
// If a function is deprecated, we don't want to flag its recursive uses.
|
||||
// So we push it on a stack while its body is being analyzed.
|
||||
// When a deprecated function is used, we check the stack to ensure that we are not inside that function.
|
||||
if (isDeprecated)
|
||||
pushScope(fty);
|
||||
|
||||
func->visit(this);
|
||||
|
||||
if (isDeprecated)
|
||||
popScope(fty);
|
||||
}
|
||||
|
||||
void report(const Location& location, const Property& prop, const char* container, const char* field)
|
||||
{
|
||||
std::string suggestion = prop.deprecatedSuggestion.empty() ? "" : format(", use '%s' instead", prop.deprecatedSuggestion.c_str());
|
||||
|
@ -2478,63 +2364,6 @@ private:
|
|||
else
|
||||
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s' is deprecated%s", field, suggestion.c_str());
|
||||
}
|
||||
|
||||
void report(const Location& location, const char* tableName, const char* functionName)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||
|
||||
if (tableName)
|
||||
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s.%s' is deprecated", tableName, functionName);
|
||||
else
|
||||
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s' is deprecated", functionName);
|
||||
}
|
||||
|
||||
void report(const Location& location, const char* functionName)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||
|
||||
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Function '%s' is deprecated", functionName);
|
||||
}
|
||||
|
||||
std::vector<const FunctionType*> functionTypeScopeStack;
|
||||
|
||||
void pushScope(const FunctionType* fty)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||
LUAU_ASSERT(fty);
|
||||
|
||||
functionTypeScopeStack.push_back(fty);
|
||||
}
|
||||
|
||||
void popScope(const FunctionType* fty)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||
LUAU_ASSERT(fty);
|
||||
|
||||
LUAU_ASSERT(fty == functionTypeScopeStack.back());
|
||||
functionTypeScopeStack.pop_back();
|
||||
}
|
||||
|
||||
bool inScope(const FunctionType* fty) const
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||
LUAU_ASSERT(fty);
|
||||
|
||||
return std::find(functionTypeScopeStack.begin(), functionTypeScopeStack.end(), fty) != functionTypeScopeStack.end();
|
||||
}
|
||||
|
||||
const FunctionType* getFunctionType(AstExpr* node)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||
|
||||
std::optional<TypeId> ty = context->getType(node);
|
||||
if (!ty)
|
||||
return nullptr;
|
||||
|
||||
const FunctionType* fty = get<FunctionType>(follow(ty));
|
||||
|
||||
return fty;
|
||||
}
|
||||
};
|
||||
|
||||
class LintTableOperations : AstVisitor
|
||||
|
|
|
@ -15,12 +15,12 @@
|
|||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteCommentDetection)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
static void defaultLogLuau(std::string_view context, std::string_view input)
|
||||
static void defaultLogLuau(std::string_view input)
|
||||
{
|
||||
// The default is to do nothing because we don't want to mess with
|
||||
// the xml parsing done by the dcr script.
|
||||
|
@ -38,6 +38,21 @@ void resetLogLuauProc()
|
|||
logLuau = &defaultLogLuau;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static bool contains_DEPRECATED(Position pos, Comment comment)
|
||||
{
|
||||
if (comment.location.contains(pos))
|
||||
return true;
|
||||
else if (comment.type == Lexeme::BrokenComment && comment.location.begin <= pos) // Broken comments are broken specifically because they don't
|
||||
// have an end
|
||||
return true;
|
||||
else if (comment.type == Lexeme::Comment && comment.location.end == pos)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool contains(Position pos, Comment comment)
|
||||
{
|
||||
if (comment.location.contains(pos))
|
||||
|
@ -61,8 +76,11 @@ bool isWithinComment(const std::vector<Comment>& commentLocations, Position pos)
|
|||
Comment{Lexeme::Comment, Location{pos, pos}},
|
||||
[](const Comment& a, const Comment& b)
|
||||
{
|
||||
if (a.type == Lexeme::Comment)
|
||||
return a.location.end.line < b.location.end.line;
|
||||
if (FFlag::LuauIncrementalAutocompleteCommentDetection)
|
||||
{
|
||||
if (a.type == Lexeme::Comment)
|
||||
return a.location.end.line < b.location.end.line;
|
||||
}
|
||||
return a.location.end < b.location.end;
|
||||
}
|
||||
);
|
||||
|
@ -70,7 +88,7 @@ bool isWithinComment(const std::vector<Comment>& commentLocations, Position pos)
|
|||
if (iter == commentLocations.end())
|
||||
return false;
|
||||
|
||||
if (contains(pos, *iter))
|
||||
if (FFlag::LuauIncrementalAutocompleteCommentDetection ? contains(pos, *iter) : contains_DEPRECATED(pos, *iter))
|
||||
return true;
|
||||
|
||||
// Due to the nature of std::lower_bound, it is possible that iter points at a comment that ends
|
||||
|
@ -154,6 +172,8 @@ struct ClonePublicInterface : Substitution
|
|||
}
|
||||
|
||||
ftv->level = TypeLevel{0, 0};
|
||||
if (FFlag::LuauSolverV2)
|
||||
ftv->scope = nullptr;
|
||||
}
|
||||
else if (TableType* ttv = getMutable<TableType>(result))
|
||||
{
|
||||
|
@ -265,10 +285,7 @@ struct ClonePublicInterface : Substitution
|
|||
|
||||
TypeId type = cloneType(tf.type);
|
||||
|
||||
if (FFlag::LuauRetainDefinitionAliasLocations)
|
||||
return TypeFun{typeParams, typePackParams, type, tf.definitionLocation};
|
||||
else
|
||||
return TypeFun{typeParams, typePackParams, type};
|
||||
return TypeFun{typeParams, typePackParams, type};
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#include "Luau/NonStrictTypeChecker.h"
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/AstQuery.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Simplify.h"
|
||||
#include "Luau/Type.h"
|
||||
|
@ -23,7 +22,6 @@
|
|||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNonStrictFuncDefErrorFix)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -765,17 +763,7 @@ struct NonStrictTypeChecker
|
|||
for (AstLocal* local : exprFn->args)
|
||||
{
|
||||
if (std::optional<TypeId> ty = willRunTimeErrorFunctionDefinition(local, remainder))
|
||||
{
|
||||
if (FFlag::LuauNonStrictFuncDefErrorFix)
|
||||
{
|
||||
const char* debugname = exprFn->debugname.value;
|
||||
reportError(NonStrictFunctionDefinitionError{debugname ? debugname : "", local->name.value, *ty}, local->location);
|
||||
}
|
||||
else
|
||||
{
|
||||
reportError(NonStrictFunctionDefinitionError{exprFn->debugname.value, local->name.value, *ty}, local->location);
|
||||
}
|
||||
}
|
||||
reportError(NonStrictFunctionDefinitionError{exprFn->debugname.value, local->name.value, *ty}, local->location);
|
||||
remainder.remove(dfg->getDef(local));
|
||||
}
|
||||
return remainder;
|
||||
|
|
|
@ -17,16 +17,11 @@
|
|||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeNegatedErrorToAnError)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeIntersectErrorToAnError)
|
||||
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000)
|
||||
LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
|
||||
LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizedBufferIsNotUnknown)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeLimitFunctionSet)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizationCatchMetatableCycles)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixNormalizedIntersectionOfNegatedClass)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -308,9 +303,7 @@ bool NormalizedType::isUnknown() const
|
|||
|
||||
// Otherwise, we can still be unknown!
|
||||
bool hasAllPrimitives = isPrim(booleans, PrimitiveType::Boolean) && isPrim(nils, PrimitiveType::NilType) && isNumber(numbers) &&
|
||||
strings.isString() &&
|
||||
(FFlag::LuauNormalizedBufferIsNotUnknown ? isThread(threads) && isBuffer(buffers)
|
||||
: isPrim(threads, PrimitiveType::Thread) && isThread(threads));
|
||||
strings.isString() && isPrim(threads, PrimitiveType::Thread) && isThread(threads);
|
||||
|
||||
// Check is class
|
||||
bool isTopClass = false;
|
||||
|
@ -585,7 +578,7 @@ NormalizationResult Normalizer::isIntersectionInhabited(TypeId left, TypeId righ
|
|||
{
|
||||
left = follow(left);
|
||||
right = follow(right);
|
||||
// We're asking if intersection is inhabited between left and right but we've already seen them ....
|
||||
// We're asking if intersection is inahbited between left and right but we've already seen them ....
|
||||
|
||||
if (cacheInhabitance)
|
||||
{
|
||||
|
@ -1691,13 +1684,6 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali
|
|||
return res;
|
||||
}
|
||||
|
||||
if (FFlag::LuauNormalizeLimitFunctionSet)
|
||||
{
|
||||
// Limit based on worst-case expansion of the function unions
|
||||
if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeUnionLimit))
|
||||
return NormalizationResult::HitLimits;
|
||||
}
|
||||
|
||||
here.booleans = unionOfBools(here.booleans, there.booleans);
|
||||
unionClasses(here.classes, there.classes);
|
||||
|
||||
|
@ -1709,7 +1695,6 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali
|
|||
here.buffers = (get<NeverType>(there.buffers) ? here.buffers : there.buffers);
|
||||
unionFunctions(here.functions, there.functions);
|
||||
unionTables(here.tables, there.tables);
|
||||
|
||||
return NormalizationResult::True;
|
||||
}
|
||||
|
||||
|
@ -1749,7 +1734,7 @@ NormalizationResult Normalizer::intersectNormalWithNegationTy(TypeId toNegate, N
|
|||
return NormalizationResult::True;
|
||||
}
|
||||
|
||||
// See above for an explanation of `ignoreSmallerTyvars`.
|
||||
// See above for an explaination of `ignoreSmallerTyvars`.
|
||||
NormalizationResult Normalizer::unionNormalWithTy(
|
||||
NormalizedType& here,
|
||||
TypeId there,
|
||||
|
@ -2303,7 +2288,7 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th
|
|||
|
||||
for (auto nIt = negations.begin(); nIt != negations.end();)
|
||||
{
|
||||
if (isSubclass(there, *nIt))
|
||||
if (FFlag::LuauFixNormalizedIntersectionOfNegatedClass && isSubclass(there, *nIt))
|
||||
{
|
||||
// Hitting this block means that the incoming class is a
|
||||
// subclass of this type, _and_ one of its negations is a
|
||||
|
@ -3064,7 +3049,7 @@ NormalizationResult Normalizer::intersectTyvarsWithTy(
|
|||
return NormalizationResult::True;
|
||||
}
|
||||
|
||||
// See above for an explanation of `ignoreSmallerTyvars`.
|
||||
// See above for an explaination of `ignoreSmallerTyvars`.
|
||||
NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars)
|
||||
{
|
||||
RecursionCounter _rc(&sharedState->counters.recursionCount);
|
||||
|
@ -3082,17 +3067,11 @@ NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const Nor
|
|||
return unionNormals(here, there, ignoreSmallerTyvars);
|
||||
}
|
||||
|
||||
// Limit based on worst-case expansion of the table/function intersections
|
||||
// Limit based on worst-case expansion of the table intersection
|
||||
// This restriction can be relaxed when table intersection simplification is improved
|
||||
if (here.tables.size() * there.tables.size() >= size_t(FInt::LuauNormalizeIntersectionLimit))
|
||||
return NormalizationResult::HitLimits;
|
||||
|
||||
if (FFlag::LuauNormalizeLimitFunctionSet)
|
||||
{
|
||||
if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeIntersectionLimit))
|
||||
return NormalizationResult::HitLimits;
|
||||
}
|
||||
|
||||
here.booleans = intersectionOfBools(here.booleans, there.booleans);
|
||||
|
||||
intersectClasses(here.classes, there.classes);
|
||||
|
@ -3228,7 +3207,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
|
|||
{
|
||||
TypeId errors = here.errors;
|
||||
clearNormal(here);
|
||||
here.errors = FFlag::LuauNormalizeIntersectErrorToAnError && get<ErrorType>(errors) ? errors : there;
|
||||
here.errors = errors;
|
||||
}
|
||||
else if (const PrimitiveType* ptv = get<PrimitiveType>(there))
|
||||
{
|
||||
|
@ -3325,18 +3304,8 @@ NormalizationResult Normalizer::intersectNormalWithTy(
|
|||
clearNormal(here);
|
||||
return NormalizationResult::True;
|
||||
}
|
||||
else if (FFlag::LuauNormalizeNegatedErrorToAnError && get<ErrorType>(t))
|
||||
{
|
||||
// ~error is still an error, so intersecting with the negation is the same as intersecting with a type
|
||||
TypeId errors = here.errors;
|
||||
clearNormal(here);
|
||||
here.errors = FFlag::LuauNormalizeIntersectErrorToAnError && get<ErrorType>(errors) ? errors : t;
|
||||
}
|
||||
else if (auto nt = get<NegationType>(t))
|
||||
{
|
||||
here.tyvars = std::move(tyvars);
|
||||
return intersectNormalWithTy(here, nt->ty, seenTablePropPairs, seenSetTypes);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO negated unions, intersections, table, and function.
|
||||
|
@ -3364,43 +3333,19 @@ NormalizationResult Normalizer::intersectNormalWithTy(
|
|||
return NormalizationResult::True;
|
||||
}
|
||||
|
||||
void makeTableShared_DEPRECATED(TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
if (auto tableTy = getMutable<TableType>(ty))
|
||||
{
|
||||
for (auto& [_, prop] : tableTy->props)
|
||||
prop.makeShared();
|
||||
}
|
||||
else if (auto metatableTy = get<MetatableType>(ty))
|
||||
{
|
||||
makeTableShared_DEPRECATED(metatableTy->metatable);
|
||||
makeTableShared_DEPRECATED(metatableTy->table);
|
||||
}
|
||||
}
|
||||
|
||||
void makeTableShared(TypeId ty, DenseHashSet<TypeId>& seen)
|
||||
{
|
||||
ty = follow(ty);
|
||||
if (seen.contains(ty))
|
||||
return;
|
||||
seen.insert(ty);
|
||||
if (auto tableTy = getMutable<TableType>(ty))
|
||||
{
|
||||
for (auto& [_, prop] : tableTy->props)
|
||||
prop.makeShared();
|
||||
}
|
||||
else if (auto metatableTy = get<MetatableType>(ty))
|
||||
{
|
||||
makeTableShared(metatableTy->metatable, seen);
|
||||
makeTableShared(metatableTy->table, seen);
|
||||
}
|
||||
}
|
||||
|
||||
void makeTableShared(TypeId ty)
|
||||
{
|
||||
DenseHashSet<TypeId> seen{nullptr};
|
||||
makeTableShared(ty, seen);
|
||||
ty = follow(ty);
|
||||
if (auto tableTy = getMutable<TableType>(ty))
|
||||
{
|
||||
for (auto& [_, prop] : tableTy->props)
|
||||
prop.makeShared();
|
||||
}
|
||||
else if (auto metatableTy = get<MetatableType>(ty))
|
||||
{
|
||||
makeTableShared(metatableTy->metatable);
|
||||
makeTableShared(metatableTy->table);
|
||||
}
|
||||
}
|
||||
|
||||
// -------- Convert back from a normalized type to a type
|
||||
|
@ -3502,10 +3447,7 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
|
|||
result.reserve(result.size() + norm.tables.size());
|
||||
for (auto table : norm.tables)
|
||||
{
|
||||
if (FFlag::LuauNormalizationCatchMetatableCycles)
|
||||
makeTableShared(table);
|
||||
else
|
||||
makeTableShared_DEPRECATED(table);
|
||||
makeTableShared(table);
|
||||
result.push_back(table);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -454,7 +454,7 @@ SolveResult solveFunctionCall(
|
|||
|
||||
TypePackId resultPack = arena->freshTypePack(scope);
|
||||
|
||||
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, argsPack, resultPack});
|
||||
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, scope.get(), argsPack, resultPack});
|
||||
Unifier2 u2{NotNull{arena}, builtinTypes, scope, iceReporter};
|
||||
|
||||
const bool occursCheckPassed = u2.unify(*overloadToUse, inferredTy);
|
||||
|
|
|
@ -107,4 +107,134 @@ void quantify(TypeId ty, TypeLevel level)
|
|||
ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end());
|
||||
}
|
||||
|
||||
struct PureQuantifier : Substitution
|
||||
{
|
||||
Scope* scope;
|
||||
OrderedMap<TypeId, TypeId> insertedGenerics;
|
||||
OrderedMap<TypePackId, TypePackId> insertedGenericPacks;
|
||||
bool seenMutableType = false;
|
||||
bool seenGenericType = false;
|
||||
|
||||
PureQuantifier(TypeArena* arena, Scope* scope)
|
||||
: Substitution(TxnLog::empty(), arena)
|
||||
, scope(scope)
|
||||
{
|
||||
}
|
||||
|
||||
bool isDirty(TypeId ty) override
|
||||
{
|
||||
LUAU_ASSERT(ty == follow(ty));
|
||||
|
||||
if (auto ftv = get<FreeType>(ty))
|
||||
{
|
||||
bool result = subsumes(scope, ftv->scope);
|
||||
seenMutableType |= result;
|
||||
return result;
|
||||
}
|
||||
else if (auto ttv = get<TableType>(ty))
|
||||
{
|
||||
if (ttv->state == TableState::Free)
|
||||
seenMutableType = true;
|
||||
else if (ttv->state == TableState::Generic)
|
||||
seenGenericType = true;
|
||||
|
||||
return (ttv->state == TableState::Unsealed || ttv->state == TableState::Free) && subsumes(scope, ttv->scope);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isDirty(TypePackId tp) override
|
||||
{
|
||||
if (auto ftp = get<FreeTypePack>(tp))
|
||||
{
|
||||
return subsumes(scope, ftp->scope);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
TypeId clean(TypeId ty) override
|
||||
{
|
||||
if (auto ftv = get<FreeType>(ty))
|
||||
{
|
||||
TypeId result = arena->addType(GenericType{scope});
|
||||
insertedGenerics.push(ty, result);
|
||||
return result;
|
||||
}
|
||||
else if (auto ttv = get<TableType>(ty))
|
||||
{
|
||||
TypeId result = arena->addType(TableType{});
|
||||
TableType* resultTable = getMutable<TableType>(result);
|
||||
LUAU_ASSERT(resultTable);
|
||||
|
||||
*resultTable = *ttv;
|
||||
resultTable->level = TypeLevel{};
|
||||
resultTable->scope = scope;
|
||||
|
||||
if (ttv->state == TableState::Free)
|
||||
{
|
||||
resultTable->state = TableState::Generic;
|
||||
insertedGenerics.push(ty, result);
|
||||
}
|
||||
else if (ttv->state == TableState::Unsealed)
|
||||
resultTable->state = TableState::Sealed;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return ty;
|
||||
}
|
||||
|
||||
TypePackId clean(TypePackId tp) override
|
||||
{
|
||||
if (auto ftp = get<FreeTypePack>(tp))
|
||||
{
|
||||
TypePackId result = arena->addTypePack(TypePackVar{GenericTypePack{scope}});
|
||||
insertedGenericPacks.push(tp, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
return tp;
|
||||
}
|
||||
|
||||
bool ignoreChildren(TypeId ty) override
|
||||
{
|
||||
if (get<ClassType>(ty))
|
||||
return true;
|
||||
|
||||
return ty->persistent;
|
||||
}
|
||||
bool ignoreChildren(TypePackId ty) override
|
||||
{
|
||||
return ty->persistent;
|
||||
}
|
||||
};
|
||||
|
||||
std::optional<QuantifierResult> quantify(TypeArena* arena, TypeId ty, Scope* scope)
|
||||
{
|
||||
PureQuantifier quantifier{arena, scope};
|
||||
std::optional<TypeId> result = quantifier.substitute(ty);
|
||||
if (!result)
|
||||
return std::nullopt;
|
||||
|
||||
FunctionType* ftv = getMutable<FunctionType>(*result);
|
||||
LUAU_ASSERT(ftv);
|
||||
ftv->scope = scope;
|
||||
|
||||
for (auto k : quantifier.insertedGenerics.keys)
|
||||
{
|
||||
TypeId g = quantifier.insertedGenerics.pairings[k];
|
||||
if (get<GenericType>(g))
|
||||
ftv->generics.push_back(g);
|
||||
}
|
||||
|
||||
for (auto k : quantifier.insertedGenericPacks.keys)
|
||||
ftv->genericPacks.push_back(quantifier.insertedGenericPacks.pairings[k]);
|
||||
|
||||
ftv->hasNoFreeOrGenericTypes = ftv->generics.empty() && ftv->genericPacks.empty() && !quantifier.seenGenericType && !quantifier.seenMutableType;
|
||||
|
||||
return std::optional<QuantifierResult>({*result, std::move(quantifier.insertedGenerics), std::move(quantifier.insertedGenericPacks)});
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -54,15 +54,7 @@ RefinementId RefinementArena::proposition(const RefinementKey* key, TypeId discr
|
|||
if (!key)
|
||||
return nullptr;
|
||||
|
||||
return NotNull{allocator.allocate(Proposition{key, discriminantTy, false})};
|
||||
}
|
||||
|
||||
RefinementId RefinementArena::implicitProposition(const RefinementKey* key, TypeId discriminantTy)
|
||||
{
|
||||
if (!key)
|
||||
return nullptr;
|
||||
|
||||
return NotNull{allocator.allocate(Proposition{key, discriminantTy, true})};
|
||||
return NotNull{allocator.allocate(Proposition{key, discriminantTy})};
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
#include "Luau/Ast.h"
|
||||
#include "Luau/Module.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauExtendedSimpleRequire)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -104,50 +106,96 @@ struct RequireTracer : AstVisitor
|
|||
{
|
||||
ModuleInfo moduleContext{currentModuleName};
|
||||
|
||||
// seed worklist with require arguments
|
||||
work.reserve(requireCalls.size());
|
||||
|
||||
for (AstExprCall* require : requireCalls)
|
||||
work.push_back(require->args.data[0]);
|
||||
|
||||
// push all dependent expressions to the work stack; note that the vector is modified during traversal
|
||||
for (size_t i = 0; i < work.size(); ++i)
|
||||
if (FFlag::LuauExtendedSimpleRequire)
|
||||
{
|
||||
if (AstNode* dep = getDependent(work[i]))
|
||||
work.push_back(dep);
|
||||
}
|
||||
// seed worklist with require arguments
|
||||
work.reserve(requireCalls.size());
|
||||
|
||||
// resolve all expressions to a module info
|
||||
for (size_t i = work.size(); i > 0; --i)
|
||||
{
|
||||
AstNode* expr = work[i - 1];
|
||||
for (AstExprCall* require : requireCalls)
|
||||
work.push_back(require->args.data[0]);
|
||||
|
||||
// when multiple expressions depend on the same one we push it to work queue multiple times
|
||||
if (result.exprs.contains(expr))
|
||||
continue;
|
||||
|
||||
std::optional<ModuleInfo> info;
|
||||
|
||||
if (AstNode* dep = getDependent(expr))
|
||||
// push all dependent expressions to the work stack; note that the vector is modified during traversal
|
||||
for (size_t i = 0; i < work.size(); ++i)
|
||||
{
|
||||
const ModuleInfo* context = result.exprs.find(dep);
|
||||
if (AstNode* dep = getDependent(work[i]))
|
||||
work.push_back(dep);
|
||||
}
|
||||
|
||||
if (context && expr->is<AstExprLocal>())
|
||||
info = *context; // locals just inherit their dependent context, no resolution required
|
||||
else if (context && (expr->is<AstExprGroup>() || expr->is<AstTypeGroup>()))
|
||||
info = *context; // simple group nodes propagate their value
|
||||
else if (context && (expr->is<AstTypeTypeof>() || expr->is<AstExprTypeAssertion>()))
|
||||
info = *context; // typeof type annotations will resolve to the typeof content
|
||||
// resolve all expressions to a module info
|
||||
for (size_t i = work.size(); i > 0; --i)
|
||||
{
|
||||
AstNode* expr = work[i - 1];
|
||||
|
||||
// when multiple expressions depend on the same one we push it to work queue multiple times
|
||||
if (result.exprs.contains(expr))
|
||||
continue;
|
||||
|
||||
std::optional<ModuleInfo> info;
|
||||
|
||||
if (AstNode* dep = getDependent(expr))
|
||||
{
|
||||
const ModuleInfo* context = result.exprs.find(dep);
|
||||
|
||||
if (context && expr->is<AstExprLocal>())
|
||||
info = *context; // locals just inherit their dependent context, no resolution required
|
||||
else if (context && (expr->is<AstExprGroup>() || expr->is<AstTypeGroup>()))
|
||||
info = *context; // simple group nodes propagate their value
|
||||
else if (context && (expr->is<AstTypeTypeof>() || expr->is<AstExprTypeAssertion>()))
|
||||
info = *context; // typeof type annotations will resolve to the typeof content
|
||||
else if (AstExpr* asExpr = expr->asExpr())
|
||||
info = fileResolver->resolveModule(context, asExpr);
|
||||
}
|
||||
else if (AstExpr* asExpr = expr->asExpr())
|
||||
info = fileResolver->resolveModule(context, asExpr);
|
||||
}
|
||||
else if (AstExpr* asExpr = expr->asExpr())
|
||||
{
|
||||
info = fileResolver->resolveModule(&moduleContext, asExpr);
|
||||
}
|
||||
{
|
||||
info = fileResolver->resolveModule(&moduleContext, asExpr);
|
||||
}
|
||||
|
||||
if (info)
|
||||
result.exprs[expr] = std::move(*info);
|
||||
if (info)
|
||||
result.exprs[expr] = std::move(*info);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// seed worklist with require arguments
|
||||
work_DEPRECATED.reserve(requireCalls.size());
|
||||
|
||||
for (AstExprCall* require : requireCalls)
|
||||
work_DEPRECATED.push_back(require->args.data[0]);
|
||||
|
||||
// push all dependent expressions to the work stack; note that the vector is modified during traversal
|
||||
for (size_t i = 0; i < work_DEPRECATED.size(); ++i)
|
||||
if (AstExpr* dep = getDependent_DEPRECATED(work_DEPRECATED[i]))
|
||||
work_DEPRECATED.push_back(dep);
|
||||
|
||||
// resolve all expressions to a module info
|
||||
for (size_t i = work_DEPRECATED.size(); i > 0; --i)
|
||||
{
|
||||
AstExpr* expr = work_DEPRECATED[i - 1];
|
||||
|
||||
// when multiple expressions depend on the same one we push it to work queue multiple times
|
||||
if (result.exprs.contains(expr))
|
||||
continue;
|
||||
|
||||
std::optional<ModuleInfo> info;
|
||||
|
||||
if (AstExpr* dep = getDependent_DEPRECATED(expr))
|
||||
{
|
||||
const ModuleInfo* context = result.exprs.find(dep);
|
||||
|
||||
// locals just inherit their dependent context, no resolution required
|
||||
if (expr->is<AstExprLocal>())
|
||||
info = context ? std::optional<ModuleInfo>(*context) : std::nullopt;
|
||||
else
|
||||
info = fileResolver->resolveModule(context, expr);
|
||||
}
|
||||
else
|
||||
{
|
||||
info = fileResolver->resolveModule(&moduleContext, expr);
|
||||
}
|
||||
|
||||
if (info)
|
||||
result.exprs[expr] = std::move(*info);
|
||||
}
|
||||
}
|
||||
|
||||
// resolve all requires according to their argument
|
||||
|
@ -176,6 +224,7 @@ struct RequireTracer : AstVisitor
|
|||
ModuleName currentModuleName;
|
||||
|
||||
DenseHashMap<AstLocal*, AstExpr*> locals;
|
||||
std::vector<AstExpr*> work_DEPRECATED;
|
||||
std::vector<AstNode*> work;
|
||||
std::vector<AstExprCall*> requireCalls;
|
||||
};
|
||||
|
|
|
@ -84,17 +84,6 @@ std::optional<TypeId> Scope::lookupUnrefinedType(DefId def) const
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<TypeId> Scope::lookupRValueRefinementType(DefId def) const
|
||||
{
|
||||
for (const Scope* current = this; current; current = current->parent.get())
|
||||
{
|
||||
if (auto ty = current->rvalueRefinements.find(def))
|
||||
return *ty;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<TypeId> Scope::lookup(DefId def) const
|
||||
{
|
||||
for (const Scope* current = this; current; current = current->parent.get())
|
||||
|
@ -192,29 +181,6 @@ std::optional<Binding> Scope::linearSearchForBinding(const std::string& name, bo
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::pair<Symbol, Binding>> Scope::linearSearchForBindingPair(const std::string& name, bool traverseScopeChain) const
|
||||
{
|
||||
const Scope* scope = this;
|
||||
|
||||
while (scope)
|
||||
{
|
||||
for (auto& [n, binding] : scope->bindings)
|
||||
{
|
||||
if (n.local && n.local->name == name.c_str())
|
||||
return {{n, binding}};
|
||||
else if (n.global.value && n.global == name.c_str())
|
||||
return {{n, binding}};
|
||||
}
|
||||
|
||||
scope = scope->parent.get();
|
||||
|
||||
if (!traverseScopeChain)
|
||||
break;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Updates the `this` scope with the assignments from the `childScope` including ones that doesn't exist in `this`.
|
||||
void Scope::inheritAssignments(const ScopePtr& childScope)
|
||||
{
|
||||
|
|
|
@ -13,7 +13,6 @@ LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256)
|
||||
LUAU_FASTFLAG(LuauSyntheticErrors)
|
||||
LUAU_FASTFLAG(LuauDeprecatedAttribute)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -96,15 +95,13 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a
|
|||
return dest.addType(a);
|
||||
else if constexpr (std::is_same_v<T, FunctionType>)
|
||||
{
|
||||
FunctionType clone = FunctionType{a.level, a.argTypes, a.retTypes, a.definition, a.hasSelf};
|
||||
FunctionType clone = FunctionType{a.level, a.scope, a.argTypes, a.retTypes, a.definition, a.hasSelf};
|
||||
clone.generics = a.generics;
|
||||
clone.genericPacks = a.genericPacks;
|
||||
clone.magic = a.magic;
|
||||
clone.tags = a.tags;
|
||||
clone.argNames = a.argNames;
|
||||
clone.isCheckedFunction = a.isCheckedFunction;
|
||||
if (FFlag::LuauDeprecatedAttribute)
|
||||
clone.isDeprecatedFunction = a.isDeprecatedFunction;
|
||||
return dest.addType(std::move(clone));
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, TableType>)
|
||||
|
|
|
@ -22,9 +22,7 @@
|
|||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSubtypingStopAtNormFail)
|
||||
LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSubtypingEnableReasoningLimit)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSubtypingFixTailPack)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -102,9 +100,6 @@ static SubtypingReasonings mergeReasonings(const SubtypingReasonings& a, const S
|
|||
else
|
||||
result.insert(r);
|
||||
}
|
||||
|
||||
if (FFlag::LuauSubtypingEnableReasoningLimit && result.size() >= size_t(FInt::LuauSubtypingReasoningLimit))
|
||||
return result;
|
||||
}
|
||||
|
||||
for (const SubtypingReasoning& r : b)
|
||||
|
@ -121,9 +116,6 @@ static SubtypingReasonings mergeReasonings(const SubtypingReasonings& a, const S
|
|||
else
|
||||
result.insert(r);
|
||||
}
|
||||
|
||||
if (FFlag::LuauSubtypingEnableReasoningLimit && result.size() >= size_t(FInt::LuauSubtypingReasoningLimit))
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -424,14 +416,6 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope
|
|||
|
||||
SubtypingResult result = isCovariantWith(env, subTy, superTy, scope);
|
||||
|
||||
if (FFlag::LuauSubtypingStopAtNormFail && result.normalizationTooComplex)
|
||||
{
|
||||
if (result.isCacheable)
|
||||
resultCache[{subTy, superTy}] = result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
for (const auto& [subTy, bounds] : env.mappedGenerics)
|
||||
{
|
||||
const auto& lb = bounds.lowerBound;
|
||||
|
@ -609,12 +593,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
|||
if (!result.isSubtype && !result.normalizationTooComplex)
|
||||
{
|
||||
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope);
|
||||
|
||||
if (FFlag::LuauSubtypingStopAtNormFail && semantic.normalizationTooComplex)
|
||||
{
|
||||
result = semantic;
|
||||
}
|
||||
else if (semantic.isSubtype)
|
||||
if (semantic.isSubtype)
|
||||
{
|
||||
semantic.reasoning.clear();
|
||||
result = semantic;
|
||||
|
@ -629,12 +608,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
|||
if (!result.isSubtype && !result.normalizationTooComplex)
|
||||
{
|
||||
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope);
|
||||
|
||||
if (FFlag::LuauSubtypingStopAtNormFail && semantic.normalizationTooComplex)
|
||||
{
|
||||
result = semantic;
|
||||
}
|
||||
else if (semantic.isSubtype)
|
||||
if (semantic.isSubtype)
|
||||
{
|
||||
// Clear the semantic reasoning, as any reasonings within
|
||||
// potentially contain invalid paths.
|
||||
|
@ -780,8 +754,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
|||
// Match head types pairwise
|
||||
|
||||
for (size_t i = 0; i < headSize; ++i)
|
||||
results.push_back(isCovariantWith(env, subHead[i], superHead[i], scope).withBothComponent(TypePath::Index{i, TypePath::Index::Variant::Pack})
|
||||
);
|
||||
results.push_back(isCovariantWith(env, subHead[i], superHead[i], scope).withBothComponent(TypePath::Index{i}));
|
||||
|
||||
// Handle mismatched head sizes
|
||||
|
||||
|
@ -794,7 +767,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
|||
for (size_t i = headSize; i < superHead.size(); ++i)
|
||||
results.push_back(isCovariantWith(env, vt->ty, superHead[i], scope)
|
||||
.withSubPath(TypePath::PathBuilder().tail().variadic().build())
|
||||
.withSuperComponent(TypePath::Index{i, TypePath::Index::Variant::Pack}));
|
||||
.withSuperComponent(TypePath::Index{i}));
|
||||
}
|
||||
else if (auto gt = get<GenericTypePack>(*subTail))
|
||||
{
|
||||
|
@ -848,7 +821,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
|||
{
|
||||
for (size_t i = headSize; i < subHead.size(); ++i)
|
||||
results.push_back(isCovariantWith(env, subHead[i], vt->ty, scope)
|
||||
.withSubComponent(TypePath::Index{i, TypePath::Index::Variant::Pack})
|
||||
.withSubComponent(TypePath::Index{i})
|
||||
.withSuperPath(TypePath::PathBuilder().tail().variadic().build()));
|
||||
}
|
||||
else if (auto gt = get<GenericTypePack>(*superTail))
|
||||
|
@ -886,7 +859,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
|||
else
|
||||
return SubtypingResult{false}
|
||||
.withSuperComponent(TypePath::PackField::Tail)
|
||||
.withError({scope->location, UnexpectedTypePackInSubtyping{*superTail}});
|
||||
.withError({scope->location, UnexpectedTypePackInSubtyping{FFlag::LuauSubtypingFixTailPack ? *superTail : *subTail}});
|
||||
}
|
||||
else
|
||||
return {false};
|
||||
|
@ -1109,10 +1082,6 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
|||
for (TypeId ty : superUnion)
|
||||
{
|
||||
SubtypingResult next = isCovariantWith(env, subTy, ty, scope);
|
||||
|
||||
if (FFlag::LuauSubtypingStopAtNormFail && next.normalizationTooComplex)
|
||||
return SubtypingResult{false, /* normalizationTooComplex */ true};
|
||||
|
||||
if (next.isSubtype)
|
||||
return SubtypingResult{true};
|
||||
}
|
||||
|
@ -1131,13 +1100,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Unio
|
|||
std::vector<SubtypingResult> subtypings;
|
||||
size_t i = 0;
|
||||
for (TypeId ty : subUnion)
|
||||
{
|
||||
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Union}));
|
||||
|
||||
if (FFlag::LuauSubtypingStopAtNormFail && subtypings.back().normalizationTooComplex)
|
||||
return SubtypingResult{false, /* normalizationTooComplex */ true};
|
||||
}
|
||||
|
||||
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++}));
|
||||
return SubtypingResult::all(subtypings);
|
||||
}
|
||||
|
||||
|
@ -1147,13 +1110,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
|||
std::vector<SubtypingResult> subtypings;
|
||||
size_t i = 0;
|
||||
for (TypeId ty : superIntersection)
|
||||
{
|
||||
subtypings.push_back(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
|
||||
|
||||
if (FFlag::LuauSubtypingStopAtNormFail && subtypings.back().normalizationTooComplex)
|
||||
return SubtypingResult{false, /* normalizationTooComplex */ true};
|
||||
}
|
||||
|
||||
subtypings.push_back(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++}));
|
||||
return SubtypingResult::all(subtypings);
|
||||
}
|
||||
|
||||
|
@ -1163,13 +1120,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Inte
|
|||
std::vector<SubtypingResult> subtypings;
|
||||
size_t i = 0;
|
||||
for (TypeId ty : subIntersection)
|
||||
{
|
||||
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
|
||||
|
||||
if (FFlag::LuauSubtypingStopAtNormFail && subtypings.back().normalizationTooComplex)
|
||||
return SubtypingResult{false, /* normalizationTooComplex */ true};
|
||||
}
|
||||
|
||||
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++}));
|
||||
return SubtypingResult::any(subtypings);
|
||||
}
|
||||
|
||||
|
@ -1459,7 +1410,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Meta
|
|||
// of the supertype table.
|
||||
//
|
||||
// There's a flaw here in that if the __index metamethod contributes a new
|
||||
// field that would satisfy the subtyping relationship, we'll erroneously say
|
||||
// field that would satisfy the subtyping relationship, we'll erronously say
|
||||
// that the metatable isn't a subtype of the table, even though they have
|
||||
// compatible properties/shapes. We'll revisit this later when we have a
|
||||
// better understanding of how important this is.
|
||||
|
@ -1809,12 +1760,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
|
|||
{
|
||||
results.emplace_back();
|
||||
for (TypeId superTy : superTypes)
|
||||
{
|
||||
results.back().orElse(isCovariantWith(env, subTy, superTy, scope));
|
||||
|
||||
if (FFlag::LuauSubtypingStopAtNormFail && results.back().normalizationTooComplex)
|
||||
return SubtypingResult{false, /* normalizationTooComplex */ true};
|
||||
}
|
||||
}
|
||||
|
||||
return SubtypingResult::all(results);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "Luau/Common.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSymbolEquality)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -14,8 +15,10 @@ bool Symbol::operator==(const Symbol& rhs) const
|
|||
return local == rhs.local;
|
||||
else if (global.value)
|
||||
return rhs.global.value && global == rhs.global.value; // Subtlety: AstName::operator==(const char*) uses strcmp, not pointer identity.
|
||||
else
|
||||
else if (FFlag::LuauSolverV2 || FFlag::LuauSymbolEquality)
|
||||
return !rhs.local && !rhs.global.value; // Reflexivity: we already know `this` Symbol is empty, so check that rhs is.
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string toString(const Symbol& name)
|
||||
|
|
|
@ -1,22 +1,16 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#include "Luau/TableLiteralInference.h"
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/Simplify.h"
|
||||
#include "Luau/Subtyping.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Unifier2.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceUpcast)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBidirectionalFailsafe)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceElideAssert)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDontInPlaceMutateTableType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAllowNonSharedTableTypesInLiteral)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -118,7 +112,6 @@ TypeId matchLiteralType(
|
|||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<TypeArena> arena,
|
||||
NotNull<Unifier2> unifier,
|
||||
NotNull<Subtyping> subtyping,
|
||||
TypeId expectedType,
|
||||
TypeId exprType,
|
||||
const AstExpr* expr,
|
||||
|
@ -139,38 +132,17 @@ TypeId matchLiteralType(
|
|||
* things like replace explicit named properties with indexers as required
|
||||
* by the expected type.
|
||||
*/
|
||||
|
||||
if (!isLiteral(expr))
|
||||
{
|
||||
if (FFlag::LuauBidirectionalInferenceUpcast)
|
||||
{
|
||||
auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope);
|
||||
return result.isSubtype ? expectedType : exprType;
|
||||
}
|
||||
else
|
||||
return exprType;
|
||||
}
|
||||
return exprType;
|
||||
|
||||
expectedType = follow(expectedType);
|
||||
exprType = follow(exprType);
|
||||
|
||||
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
if (get<AnyType>(expectedType) || get<UnknownType>(expectedType))
|
||||
{
|
||||
// The intent of `matchLiteralType` is to upcast values when it's safe
|
||||
// to do so. it's always safe to upcast to `any` or `unknown`, so we
|
||||
// can unconditionally do so here.
|
||||
if (is<AnyType, UnknownType>(expectedType))
|
||||
return expectedType;
|
||||
// "Narrowing" to unknown or any is not going to do anything useful.
|
||||
return exprType;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (get<AnyType>(expectedType) || get<UnknownType>(expectedType))
|
||||
{
|
||||
// "Narrowing" to unknown or any is not going to do anything useful.
|
||||
return exprType;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (expr->is<AstExprConstantString>())
|
||||
{
|
||||
|
@ -238,27 +210,11 @@ TypeId matchLiteralType(
|
|||
return exprType;
|
||||
}
|
||||
|
||||
|
||||
if (FFlag::LuauBidirectionalInferenceUpcast && expr->is<AstExprFunction>())
|
||||
{
|
||||
// TODO: Push argument / return types into the lambda. For now, just do
|
||||
// the non-literal thing: check for a subtype and upcast if valid.
|
||||
auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope);
|
||||
return result.isSubtype ? expectedType : exprType;
|
||||
}
|
||||
// TODO: lambdas
|
||||
|
||||
if (auto exprTable = expr->as<AstExprTable>())
|
||||
{
|
||||
TableType* const tableTy = getMutable<TableType>(exprType);
|
||||
|
||||
// This can occur if we have an expression like:
|
||||
//
|
||||
// { x = {}, x = 42 }
|
||||
//
|
||||
// The type of this will be `{ x: number }`
|
||||
if (FFlag::LuauBidirectionalFailsafe && !tableTy)
|
||||
return exprType;
|
||||
|
||||
LUAU_ASSERT(tableTy);
|
||||
|
||||
const TableType* expectedTableTy = get<TableType>(expectedType);
|
||||
|
@ -273,7 +229,7 @@ TypeId matchLiteralType(
|
|||
|
||||
if (tt)
|
||||
{
|
||||
TypeId res = matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *tt, exprType, expr, toBlock);
|
||||
TypeId res = matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, *tt, exprType, expr, toBlock);
|
||||
|
||||
parts.push_back(res);
|
||||
return arena->addType(UnionType{std::move(parts)});
|
||||
|
@ -285,9 +241,6 @@ TypeId matchLiteralType(
|
|||
|
||||
DenseHashSet<AstExprConstantString*> keysToDelete{nullptr};
|
||||
|
||||
DenseHashSet<TypeId> indexerKeyTypes{nullptr};
|
||||
DenseHashSet<TypeId> indexerValueTypes{nullptr};
|
||||
|
||||
for (const AstExprTable::Item& item : exprTable->items)
|
||||
{
|
||||
if (isRecord(item))
|
||||
|
@ -295,20 +248,23 @@ TypeId matchLiteralType(
|
|||
const AstArray<char>& s = item.key->as<AstExprConstantString>()->value;
|
||||
std::string keyStr{s.data, s.data + s.size};
|
||||
auto it = tableTy->props.find(keyStr);
|
||||
|
||||
// This can occur, potentially, if we are re-entrant.
|
||||
if (FFlag::LuauBidirectionalFailsafe && it == tableTy->props.end())
|
||||
continue;
|
||||
|
||||
LUAU_ASSERT(it != tableTy->props.end());
|
||||
|
||||
Property& prop = it->second;
|
||||
|
||||
// If we encounter a duplcate property, we may have already
|
||||
// set it to be read-only. If that's the case, the only thing
|
||||
// that will definitely crash is trying to access a write
|
||||
// only property.
|
||||
LUAU_ASSERT(!prop.isWriteOnly());
|
||||
if (FFlag::LuauAllowNonSharedTableTypesInLiteral)
|
||||
{
|
||||
// If we encounter a duplcate property, we may have already
|
||||
// set it to be read-only. If that's the case, the only thing
|
||||
// that will definitely crash is trying to access a write
|
||||
// only property.
|
||||
LUAU_ASSERT(!prop.isWriteOnly());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Table literals always initially result in shared read-write types
|
||||
LUAU_ASSERT(prop.isShared());
|
||||
}
|
||||
TypeId propTy = *prop.readTy;
|
||||
|
||||
auto it2 = expectedTableTy->props.find(keyStr);
|
||||
|
@ -329,27 +285,21 @@ TypeId matchLiteralType(
|
|||
builtinTypes,
|
||||
arena,
|
||||
unifier,
|
||||
subtyping,
|
||||
expectedTableTy->indexer->indexResultType,
|
||||
propTy,
|
||||
item.value,
|
||||
toBlock
|
||||
);
|
||||
|
||||
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
{
|
||||
indexerKeyTypes.insert(arena->addType(SingletonType{StringSingleton{keyStr}}));
|
||||
indexerValueTypes.insert(matchedType);
|
||||
}
|
||||
if (tableTy->indexer)
|
||||
unifier->unify(matchedType, tableTy->indexer->indexResultType);
|
||||
else
|
||||
{
|
||||
if (tableTy->indexer)
|
||||
unifier->unify(matchedType, tableTy->indexer->indexResultType);
|
||||
else
|
||||
tableTy->indexer = TableIndexer{expectedTableTy->indexer->indexType, matchedType};
|
||||
}
|
||||
tableTy->indexer = TableIndexer{expectedTableTy->indexer->indexType, matchedType};
|
||||
|
||||
keysToDelete.insert(item.key->as<AstExprConstantString>());
|
||||
if (FFlag::LuauDontInPlaceMutateTableType)
|
||||
keysToDelete.insert(item.key->as<AstExprConstantString>());
|
||||
else
|
||||
tableTy->props.erase(keyStr);
|
||||
}
|
||||
|
||||
// If it's just an extra property and the expected type
|
||||
|
@ -372,25 +322,22 @@ TypeId matchLiteralType(
|
|||
// quadratic in a hurry.
|
||||
if (expectedProp.isShared())
|
||||
{
|
||||
matchedType = matchLiteralType(
|
||||
astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *expectedReadTy, propTy, item.value, toBlock
|
||||
);
|
||||
matchedType =
|
||||
matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, *expectedReadTy, propTy, item.value, toBlock);
|
||||
prop.readTy = matchedType;
|
||||
prop.writeTy = matchedType;
|
||||
}
|
||||
else if (expectedReadTy)
|
||||
{
|
||||
matchedType = matchLiteralType(
|
||||
astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *expectedReadTy, propTy, item.value, toBlock
|
||||
);
|
||||
matchedType =
|
||||
matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, *expectedReadTy, propTy, item.value, toBlock);
|
||||
prop.readTy = matchedType;
|
||||
prop.writeTy.reset();
|
||||
}
|
||||
else if (expectedWriteTy)
|
||||
{
|
||||
matchedType = matchLiteralType(
|
||||
astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *expectedWriteTy, propTy, item.value, toBlock
|
||||
);
|
||||
matchedType =
|
||||
matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, *expectedWriteTy, propTy, item.value, toBlock);
|
||||
prop.readTy.reset();
|
||||
prop.writeTy = matchedType;
|
||||
}
|
||||
|
@ -407,16 +354,10 @@ TypeId matchLiteralType(
|
|||
LUAU_ASSERT(matchedType);
|
||||
|
||||
(*astExpectedTypes)[item.value] = matchedType;
|
||||
// NOTE: We do *not* add to the potential indexer types here.
|
||||
// I think this is correct to support something like:
|
||||
//
|
||||
// { [string]: number, foo: boolean }
|
||||
//
|
||||
}
|
||||
else if (item.kind == AstExprTable::Item::List)
|
||||
{
|
||||
if (!FFlag::LuauBidirectionalInferenceCollectIndexerTypes || !FFlag::LuauBidirectionalInferenceElideAssert)
|
||||
LUAU_ASSERT(tableTy->indexer);
|
||||
LUAU_ASSERT(tableTy->indexer);
|
||||
|
||||
if (expectedTableTy->indexer)
|
||||
{
|
||||
|
@ -430,24 +371,15 @@ TypeId matchLiteralType(
|
|||
builtinTypes,
|
||||
arena,
|
||||
unifier,
|
||||
subtyping,
|
||||
expectedTableTy->indexer->indexResultType,
|
||||
*propTy,
|
||||
item.value,
|
||||
toBlock
|
||||
);
|
||||
|
||||
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
{
|
||||
indexerKeyTypes.insert(builtinTypes->numberType);
|
||||
indexerValueTypes.insert(matchedType);
|
||||
}
|
||||
else
|
||||
{
|
||||
// if the index result type is the prop type, we can replace it with the matched type here.
|
||||
if (tableTy->indexer->indexResultType == *propTy)
|
||||
tableTy->indexer->indexResultType = matchedType;
|
||||
}
|
||||
// if the index result type is the prop type, we can replace it with the matched type here.
|
||||
if (tableTy->indexer->indexResultType == *propTy)
|
||||
tableTy->indexer->indexResultType = matchedType;
|
||||
}
|
||||
}
|
||||
else if (item.kind == AstExprTable::Item::General)
|
||||
|
@ -469,22 +401,19 @@ TypeId matchLiteralType(
|
|||
// Populate expected types for non-string keys declared with [] (the code below will handle the case where they are strings)
|
||||
if (!item.key->as<AstExprConstantString>() && expectedTableTy->indexer)
|
||||
(*astExpectedTypes)[item.key] = expectedTableTy->indexer->indexType;
|
||||
|
||||
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
{
|
||||
indexerKeyTypes.insert(tKey);
|
||||
indexerValueTypes.insert(tProp);
|
||||
}
|
||||
}
|
||||
else
|
||||
LUAU_ASSERT(!"Unexpected");
|
||||
}
|
||||
|
||||
for (const auto& key : keysToDelete)
|
||||
if (FFlag::LuauDontInPlaceMutateTableType)
|
||||
{
|
||||
const AstArray<char>& s = key->value;
|
||||
std::string keyStr{s.data, s.data + s.size};
|
||||
tableTy->props.erase(keyStr);
|
||||
for (const auto& key : keysToDelete)
|
||||
{
|
||||
const AstArray<char>& s = key->value;
|
||||
std::string keyStr{s.data, s.data + s.size};
|
||||
tableTy->props.erase(keyStr);
|
||||
}
|
||||
}
|
||||
|
||||
// Keys that the expectedType says we should have, but that aren't
|
||||
|
@ -536,39 +465,9 @@ TypeId matchLiteralType(
|
|||
// have one too.
|
||||
// TODO: If the expected table also has an indexer, we might want to
|
||||
// push the expected indexer's types into it.
|
||||
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes && expectedTableTy->indexer)
|
||||
if (expectedTableTy->indexer && !tableTy->indexer)
|
||||
{
|
||||
if (indexerValueTypes.size() > 0 && indexerKeyTypes.size() > 0)
|
||||
{
|
||||
TypeId inferredKeyType = builtinTypes->neverType;
|
||||
TypeId inferredValueType = builtinTypes->neverType;
|
||||
for (auto kt : indexerKeyTypes)
|
||||
{
|
||||
auto simplified = simplifyUnion(builtinTypes, arena, inferredKeyType, kt);
|
||||
inferredKeyType = simplified.result;
|
||||
}
|
||||
for (auto vt : indexerValueTypes)
|
||||
{
|
||||
auto simplified = simplifyUnion(builtinTypes, arena, inferredValueType, vt);
|
||||
inferredValueType = simplified.result;
|
||||
}
|
||||
tableTy->indexer = TableIndexer{inferredKeyType, inferredValueType};
|
||||
auto keyCheck = subtyping->isSubtype(inferredKeyType, expectedTableTy->indexer->indexType, unifier->scope);
|
||||
if (keyCheck.isSubtype)
|
||||
tableTy->indexer->indexType = expectedTableTy->indexer->indexType;
|
||||
auto valueCheck = subtyping->isSubtype(inferredValueType, expectedTableTy->indexer->indexResultType, unifier->scope);
|
||||
if (valueCheck.isSubtype)
|
||||
tableTy->indexer->indexResultType = expectedTableTy->indexer->indexResultType;
|
||||
}
|
||||
else
|
||||
LUAU_ASSERT(indexerKeyTypes.empty() && indexerValueTypes.empty());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (expectedTableTy->indexer && !tableTy->indexer)
|
||||
{
|
||||
tableTy->indexer = expectedTableTy->indexer;
|
||||
}
|
||||
tableTy->indexer = expectedTableTy->indexer;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -301,28 +301,6 @@ struct StringifierState
|
|||
emit(std::to_string(i).c_str());
|
||||
}
|
||||
|
||||
void emit(Polarity p)
|
||||
{
|
||||
switch (p)
|
||||
{
|
||||
case Polarity::None:
|
||||
emit(" ");
|
||||
break;
|
||||
case Polarity::Negative:
|
||||
emit(" -");
|
||||
break;
|
||||
case Polarity::Positive:
|
||||
emit("+ ");
|
||||
break;
|
||||
case Polarity::Mixed:
|
||||
emit("+-");
|
||||
break;
|
||||
default:
|
||||
emit("!!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void indent()
|
||||
{
|
||||
indentation += 4;
|
||||
|
@ -504,8 +482,6 @@ struct TypeStringifier
|
|||
{
|
||||
state.emit("'");
|
||||
state.emit(state.getName(ty));
|
||||
if (FInt::DebugLuauVerboseTypeNames >= 1)
|
||||
state.emit(ftv.polarity);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -518,9 +494,6 @@ struct TypeStringifier
|
|||
state.emit("'");
|
||||
state.emit(state.getName(ty));
|
||||
|
||||
if (FInt::DebugLuauVerboseTypeNames >= 1)
|
||||
state.emit(ftv.polarity);
|
||||
|
||||
if (!get<UnknownType>(upperBound))
|
||||
{
|
||||
state.emit(" <: ");
|
||||
|
@ -536,9 +509,6 @@ struct TypeStringifier
|
|||
|
||||
state.emit(state.getName(ty));
|
||||
|
||||
if (FFlag::LuauSolverV2 && FInt::DebugLuauVerboseTypeNames >= 1)
|
||||
state.emit(ftv.polarity);
|
||||
|
||||
if (FInt::DebugLuauVerboseTypeNames >= 2)
|
||||
{
|
||||
state.emit("-");
|
||||
|
@ -568,9 +538,6 @@ struct TypeStringifier
|
|||
else
|
||||
state.emit(state.getName(ty));
|
||||
|
||||
if (FInt::DebugLuauVerboseTypeNames >= 1)
|
||||
state.emit(gtv.polarity);
|
||||
|
||||
if (FInt::DebugLuauVerboseTypeNames >= 2)
|
||||
{
|
||||
state.emit("-");
|
||||
|
@ -1255,9 +1222,6 @@ struct TypePackStringifier
|
|||
state.emit(state.getName(tp));
|
||||
}
|
||||
|
||||
if (FInt::DebugLuauVerboseTypeNames >= 1)
|
||||
state.emit(pack.polarity);
|
||||
|
||||
if (FInt::DebugLuauVerboseTypeNames >= 2)
|
||||
{
|
||||
state.emit("-");
|
||||
|
@ -1277,9 +1241,6 @@ struct TypePackStringifier
|
|||
state.emit("free-");
|
||||
state.emit(state.getName(tp));
|
||||
|
||||
if (FInt::DebugLuauVerboseTypeNames >= 1)
|
||||
state.emit(pack.polarity);
|
||||
|
||||
if (FInt::DebugLuauVerboseTypeNames >= 2)
|
||||
{
|
||||
state.emit("-");
|
||||
|
|
|
@ -10,11 +10,10 @@
|
|||
#include <limits>
|
||||
#include <math.h>
|
||||
|
||||
LUAU_FASTFLAG(LuauStoreCSTData2)
|
||||
LUAU_FASTFLAG(LuauAstTypeGroup3)
|
||||
LUAU_FASTFLAG(LuauStoreCSTData)
|
||||
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
|
||||
LUAU_FASTFLAG(LuauAstTypeGroup2)
|
||||
LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
|
||||
LUAU_FASTFLAG(LuauParseOptionalAsNode2)
|
||||
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -167,7 +166,7 @@ struct StringWriter : Writer
|
|||
|
||||
void symbol(std::string_view s) override
|
||||
{
|
||||
if (FFlag::LuauStoreCSTData2)
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
{
|
||||
write(s);
|
||||
}
|
||||
|
@ -257,7 +256,7 @@ public:
|
|||
first = !first;
|
||||
else
|
||||
{
|
||||
if (FFlag::LuauStoreCSTData2 && commaPosition)
|
||||
if (FFlag::LuauStoreCSTData && commaPosition)
|
||||
{
|
||||
writer.advance(*commaPosition);
|
||||
commaPosition++;
|
||||
|
@ -272,43 +271,6 @@ private:
|
|||
const Position* commaPosition;
|
||||
};
|
||||
|
||||
class ArgNameInserter
|
||||
{
|
||||
public:
|
||||
ArgNameInserter(Writer& w, AstArray<std::optional<AstArgumentName>> names, AstArray<std::optional<Position>> colonPositions)
|
||||
: writer(w)
|
||||
, names(names)
|
||||
, colonPositions(colonPositions)
|
||||
{
|
||||
}
|
||||
|
||||
void operator()()
|
||||
{
|
||||
if (idx < names.size)
|
||||
{
|
||||
const auto name = names.data[idx];
|
||||
if (name.has_value())
|
||||
{
|
||||
writer.advance(name->second.begin);
|
||||
writer.identifier(name->first.value);
|
||||
if (idx < colonPositions.size)
|
||||
{
|
||||
LUAU_ASSERT(colonPositions.data[idx].has_value());
|
||||
writer.advance(*colonPositions.data[idx]);
|
||||
}
|
||||
writer.symbol(":");
|
||||
}
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
|
||||
private:
|
||||
Writer& writer;
|
||||
AstArray<std::optional<AstArgumentName>> names;
|
||||
AstArray<std::optional<Position>> colonPositions;
|
||||
size_t idx = 0;
|
||||
};
|
||||
|
||||
struct Printer_DEPRECATED
|
||||
{
|
||||
explicit Printer_DEPRECATED(Writer& writer)
|
||||
|
@ -368,7 +330,7 @@ struct Printer_DEPRECATED
|
|||
else if (typeCount == 1)
|
||||
{
|
||||
bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>());
|
||||
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
|
||||
if (FFlag::LuauAstTypeGroup2 ? shouldParenthesize : unconditionallyParenthesize)
|
||||
writer.symbol("(");
|
||||
|
||||
// Only variadic tail
|
||||
|
@ -381,7 +343,7 @@ struct Printer_DEPRECATED
|
|||
visualizeTypeAnnotation(*list.types.data[0]);
|
||||
}
|
||||
|
||||
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
|
||||
if (FFlag::LuauAstTypeGroup2 ? shouldParenthesize : unconditionallyParenthesize)
|
||||
writer.symbol(")");
|
||||
}
|
||||
else
|
||||
|
@ -1229,18 +1191,9 @@ struct Printer_DEPRECATED
|
|||
AstType* l = a->types.data[0];
|
||||
AstType* r = a->types.data[1];
|
||||
|
||||
if (FFlag::LuauParseOptionalAsNode2)
|
||||
{
|
||||
auto lta = l->as<AstTypeReference>();
|
||||
if (lta && lta->name == "nil" && !r->is<AstTypeOptional>())
|
||||
std::swap(l, r);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto lta = l->as<AstTypeReference>();
|
||||
if (lta && lta->name == "nil")
|
||||
std::swap(l, r);
|
||||
}
|
||||
auto lta = l->as<AstTypeReference>();
|
||||
if (lta && lta->name == "nil")
|
||||
std::swap(l, r);
|
||||
|
||||
// it's still possible that we had a (T | U) or (T | nil) and not (nil | T)
|
||||
auto rta = r->as<AstTypeReference>();
|
||||
|
@ -1263,15 +1216,6 @@ struct Printer_DEPRECATED
|
|||
|
||||
for (size_t i = 0; i < a->types.size; ++i)
|
||||
{
|
||||
if (FFlag::LuauParseOptionalAsNode2)
|
||||
{
|
||||
if (a->types.data[i]->is<AstTypeOptional>())
|
||||
{
|
||||
writer.symbol("?");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
writer.maybeSpace(a->types.data[i]->location.begin, 2);
|
||||
|
@ -1368,7 +1312,7 @@ struct Printer
|
|||
}
|
||||
}
|
||||
|
||||
void visualizeTypePackAnnotation(AstTypePack& annotation, bool forVarArg)
|
||||
void visualizeTypePackAnnotation(const AstTypePack& annotation, bool forVarArg)
|
||||
{
|
||||
advance(annotation.location.begin);
|
||||
if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>())
|
||||
|
@ -1378,22 +1322,15 @@ struct Printer
|
|||
|
||||
visualizeTypeAnnotation(*variadicTp->variadicType);
|
||||
}
|
||||
else if (AstTypePackGeneric* genericTp = annotation.as<AstTypePackGeneric>())
|
||||
else if (const AstTypePackGeneric* genericTp = annotation.as<AstTypePackGeneric>())
|
||||
{
|
||||
writer.symbol(genericTp->genericName.value);
|
||||
if (const auto cstNode = lookupCstNode<CstTypePackGeneric>(genericTp))
|
||||
advance(cstNode->ellipsisPosition);
|
||||
writer.symbol("...");
|
||||
}
|
||||
else if (AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>())
|
||||
else if (const AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>())
|
||||
{
|
||||
LUAU_ASSERT(!forVarArg);
|
||||
if (const auto cstNode = lookupCstNode<CstTypePackExplicit>(explicitTp))
|
||||
visualizeTypeList(
|
||||
explicitTp->typeList, true, cstNode->openParenthesesPosition, cstNode->closeParenthesesPosition, cstNode->commaPositions
|
||||
);
|
||||
else
|
||||
visualizeTypeList(explicitTp->typeList, true);
|
||||
visualizeTypeList(explicitTp->typeList, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1401,37 +1338,19 @@ struct Printer
|
|||
}
|
||||
}
|
||||
|
||||
void visualizeNamedTypeList(
|
||||
const AstTypeList& list,
|
||||
bool unconditionallyParenthesize,
|
||||
std::optional<Position> openParenthesesPosition,
|
||||
std::optional<Position> closeParenthesesPosition,
|
||||
AstArray<Position> commaPositions,
|
||||
AstArray<std::optional<AstArgumentName>> argNames,
|
||||
AstArray<std::optional<Position>> argNamesColonPositions
|
||||
)
|
||||
void visualizeTypeList(const AstTypeList& list, bool unconditionallyParenthesize)
|
||||
{
|
||||
size_t typeCount = list.types.size + (list.tailType != nullptr ? 1 : 0);
|
||||
if (typeCount == 0)
|
||||
{
|
||||
if (openParenthesesPosition)
|
||||
advance(*openParenthesesPosition);
|
||||
writer.symbol("(");
|
||||
if (closeParenthesesPosition)
|
||||
advance(*closeParenthesesPosition);
|
||||
writer.symbol(")");
|
||||
}
|
||||
else if (typeCount == 1)
|
||||
{
|
||||
bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>());
|
||||
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
|
||||
{
|
||||
if (openParenthesesPosition)
|
||||
advance(*openParenthesesPosition);
|
||||
if (FFlag::LuauAstTypeGroup2 ? shouldParenthesize : unconditionallyParenthesize)
|
||||
writer.symbol("(");
|
||||
}
|
||||
|
||||
ArgNameInserter(writer, argNames, argNamesColonPositions)();
|
||||
|
||||
// Only variadic tail
|
||||
if (list.types.size == 0)
|
||||
|
@ -1443,51 +1362,34 @@ struct Printer
|
|||
visualizeTypeAnnotation(*list.types.data[0]);
|
||||
}
|
||||
|
||||
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
|
||||
{
|
||||
if (closeParenthesesPosition)
|
||||
advance(*closeParenthesesPosition);
|
||||
if (FFlag::LuauAstTypeGroup2 ? shouldParenthesize : unconditionallyParenthesize)
|
||||
writer.symbol(")");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (openParenthesesPosition)
|
||||
advance(*openParenthesesPosition);
|
||||
writer.symbol("(");
|
||||
|
||||
CommaSeparatorInserter comma(writer, commaPositions.size > 0 ? commaPositions.begin() : nullptr);
|
||||
ArgNameInserter argName(writer, argNames, argNamesColonPositions);
|
||||
bool first = true;
|
||||
for (const auto& el : list.types)
|
||||
{
|
||||
comma();
|
||||
argName();
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
writer.symbol(",");
|
||||
|
||||
visualizeTypeAnnotation(*el);
|
||||
}
|
||||
|
||||
if (list.tailType)
|
||||
{
|
||||
comma();
|
||||
writer.symbol(",");
|
||||
visualizeTypePackAnnotation(*list.tailType, false);
|
||||
}
|
||||
|
||||
if (closeParenthesesPosition)
|
||||
advance(*closeParenthesesPosition);
|
||||
writer.symbol(")");
|
||||
}
|
||||
}
|
||||
|
||||
void visualizeTypeList(
|
||||
const AstTypeList& list,
|
||||
bool unconditionallyParenthesize,
|
||||
std::optional<Position> openParenthesesPosition = std::nullopt,
|
||||
std::optional<Position> closeParenthesesPosition = std::nullopt,
|
||||
AstArray<Position> commaPositions = {}
|
||||
)
|
||||
{
|
||||
visualizeNamedTypeList(list, unconditionallyParenthesize, openParenthesesPosition, closeParenthesesPosition, commaPositions, {}, {});
|
||||
}
|
||||
|
||||
bool isIntegerish(double d)
|
||||
{
|
||||
if (d <= std::numeric_limits<int>::max() && d >= std::numeric_limits<int>::min())
|
||||
|
@ -1498,14 +1400,13 @@ struct Printer
|
|||
|
||||
void visualize(AstExpr& expr)
|
||||
{
|
||||
if (!expr.is<AstExprFunction>() || FFlag::LuauFixFunctionWithAttributesStartLocation)
|
||||
advance(expr.location.begin);
|
||||
advance(expr.location.begin);
|
||||
|
||||
if (const auto& a = expr.as<AstExprGroup>())
|
||||
{
|
||||
writer.symbol("(");
|
||||
visualize(*a->expr);
|
||||
advanceBefore(a->location.end, 1);
|
||||
advance(Position{a->location.end.line, a->location.end.column - 1});
|
||||
writer.symbol(")");
|
||||
}
|
||||
else if (expr.is<AstExprConstantNil>())
|
||||
|
@ -1633,17 +1534,6 @@ struct Printer
|
|||
}
|
||||
else if (const auto& a = expr.as<AstExprFunction>())
|
||||
{
|
||||
for (const auto& attribute : a->attributes)
|
||||
visualizeAttribute(*attribute);
|
||||
if (FFlag::LuauFixFunctionWithAttributesStartLocation)
|
||||
{
|
||||
if (const auto cstNode = lookupCstNode<CstExprFunction>(a))
|
||||
advance(cstNode->functionKeywordPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
advance(a->location.begin);
|
||||
}
|
||||
writer.keyword("function");
|
||||
visualizeFunctionBody(*a);
|
||||
}
|
||||
|
@ -1885,18 +1775,9 @@ struct Printer
|
|||
writer.advance(newPos);
|
||||
}
|
||||
|
||||
void advanceBefore(const Position& newPos, unsigned int tokenLength)
|
||||
{
|
||||
if (newPos.column >= tokenLength)
|
||||
advance(Position{newPos.line, newPos.column - tokenLength});
|
||||
else
|
||||
advance(newPos);
|
||||
}
|
||||
|
||||
void visualize(AstStat& program)
|
||||
{
|
||||
if ((!program.is<AstStatLocalFunction>() && !program.is<AstStatFunction>()) || FFlag::LuauFixFunctionWithAttributesStartLocation)
|
||||
advance(program.location.begin);
|
||||
advance(program.location.begin);
|
||||
|
||||
if (const auto& block = program.as<AstStatBlock>())
|
||||
{
|
||||
|
@ -1936,8 +1817,8 @@ struct Printer
|
|||
visualizeBlock(*a->body);
|
||||
if (const auto cstNode = lookupCstNode<CstStatRepeat>(a))
|
||||
writer.advance(cstNode->untilPosition);
|
||||
else
|
||||
advanceBefore(a->condition->location.begin, 6);
|
||||
else if (a->condition->location.begin.column > 5)
|
||||
writer.advance(Position{a->condition->location.begin.line, a->condition->location.begin.column - 6});
|
||||
writer.keyword("until");
|
||||
visualize(*a->condition);
|
||||
}
|
||||
|
@ -2133,36 +2014,13 @@ struct Printer
|
|||
}
|
||||
else if (const auto& a = program.as<AstStatFunction>())
|
||||
{
|
||||
for (const auto& attribute : a->func->attributes)
|
||||
visualizeAttribute(*attribute);
|
||||
if (FFlag::LuauFixFunctionWithAttributesStartLocation)
|
||||
{
|
||||
if (const auto cstNode = lookupCstNode<CstStatFunction>(a))
|
||||
advance(cstNode->functionKeywordPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
advance(a->location.begin);
|
||||
}
|
||||
writer.keyword("function");
|
||||
visualize(*a->name);
|
||||
visualizeFunctionBody(*a->func);
|
||||
}
|
||||
else if (const auto& a = program.as<AstStatLocalFunction>())
|
||||
{
|
||||
for (const auto& attribute : a->func->attributes)
|
||||
visualizeAttribute(*attribute);
|
||||
|
||||
const auto cstNode = lookupCstNode<CstStatLocalFunction>(a);
|
||||
if (FFlag::LuauFixFunctionWithAttributesStartLocation)
|
||||
{
|
||||
if (cstNode)
|
||||
advance(cstNode->localKeywordPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
advance(a->location.begin);
|
||||
}
|
||||
|
||||
writer.keyword("local");
|
||||
|
||||
|
@ -2263,20 +2121,7 @@ struct Printer
|
|||
{
|
||||
if (writeTypes)
|
||||
{
|
||||
const auto cstNode = lookupCstNode<CstStatTypeFunction>(t);
|
||||
if (t->exported)
|
||||
writer.keyword("export");
|
||||
if (cstNode)
|
||||
advance(cstNode->typeKeywordPosition);
|
||||
else
|
||||
writer.space();
|
||||
writer.keyword("type");
|
||||
if (cstNode)
|
||||
advance(cstNode->functionKeywordPosition);
|
||||
else
|
||||
writer.space();
|
||||
writer.keyword("function");
|
||||
advance(t->nameLocation.begin);
|
||||
writer.keyword("type function");
|
||||
writer.identifier(t->name.value);
|
||||
visualizeFunctionBody(*t->body);
|
||||
}
|
||||
|
@ -2306,23 +2151,17 @@ struct Printer
|
|||
|
||||
if (program.hasSemicolon)
|
||||
{
|
||||
if (FFlag::LuauStoreCSTData2)
|
||||
advanceBefore(program.location.end, 1);
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
advance(Position{program.location.end.line, program.location.end.column - 1});
|
||||
writer.symbol(";");
|
||||
}
|
||||
}
|
||||
|
||||
void visualizeFunctionBody(AstExprFunction& func)
|
||||
{
|
||||
const auto cstNode = lookupCstNode<CstExprFunction>(&func);
|
||||
|
||||
// TODO(CLI-139347): need to handle return type (incl. parentheses of return type)
|
||||
|
||||
if (func.generics.size > 0 || func.genericPacks.size > 0)
|
||||
{
|
||||
CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr);
|
||||
if (cstNode)
|
||||
advance(cstNode->openGenericsPosition);
|
||||
CommaSeparatorInserter comma(writer);
|
||||
writer.symbol("<");
|
||||
for (const auto& o : func.generics)
|
||||
{
|
||||
|
@ -2337,19 +2176,13 @@ struct Printer
|
|||
|
||||
writer.advance(o->location.begin);
|
||||
writer.identifier(o->name.value);
|
||||
if (const auto* genericTypePackCstNode = lookupCstNode<CstGenericTypePack>(o))
|
||||
advance(genericTypePackCstNode->ellipsisPosition);
|
||||
writer.symbol("...");
|
||||
}
|
||||
if (cstNode)
|
||||
advance(cstNode->closeGenericsPosition);
|
||||
writer.symbol(">");
|
||||
}
|
||||
|
||||
if (func.argLocation)
|
||||
advance(func.argLocation->begin);
|
||||
writer.symbol("(");
|
||||
CommaSeparatorInserter comma(writer, cstNode ? cstNode->argsCommaPositions.begin() : nullptr);
|
||||
CommaSeparatorInserter comma(writer);
|
||||
|
||||
for (size_t i = 0; i < func.args.size; ++i)
|
||||
{
|
||||
|
@ -2379,14 +2212,10 @@ struct Printer
|
|||
}
|
||||
}
|
||||
|
||||
if (func.argLocation)
|
||||
advanceBefore(func.argLocation->end, 1);
|
||||
writer.symbol(")");
|
||||
|
||||
if (writeTypes && func.returnAnnotation)
|
||||
{
|
||||
if (cstNode)
|
||||
advance(cstNode->returnSpecifierPosition);
|
||||
writer.symbol(":");
|
||||
writer.space();
|
||||
|
||||
|
@ -2472,23 +2301,6 @@ struct Printer
|
|||
}
|
||||
}
|
||||
|
||||
void visualizeAttribute(AstAttr& attribute)
|
||||
{
|
||||
advance(attribute.location.begin);
|
||||
switch (attribute.type)
|
||||
{
|
||||
case AstAttr::Checked:
|
||||
writer.keyword("@checked");
|
||||
break;
|
||||
case AstAttr::Native:
|
||||
writer.keyword("@native");
|
||||
break;
|
||||
case AstAttr::Deprecated:
|
||||
writer.keyword("@deprecated");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void visualizeTypeAnnotation(AstType& typeAnnotation)
|
||||
{
|
||||
advance(typeAnnotation.location.begin);
|
||||
|
@ -2528,13 +2340,9 @@ struct Printer
|
|||
}
|
||||
else if (const auto& a = typeAnnotation.as<AstTypeFunction>())
|
||||
{
|
||||
const auto cstNode = lookupCstNode<CstTypeFunction>(a);
|
||||
|
||||
if (a->generics.size > 0 || a->genericPacks.size > 0)
|
||||
{
|
||||
CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr);
|
||||
if (cstNode)
|
||||
advance(cstNode->openGenericsPosition);
|
||||
CommaSeparatorInserter comma(writer);
|
||||
writer.symbol("<");
|
||||
for (const auto& o : a->generics)
|
||||
{
|
||||
|
@ -2549,29 +2357,15 @@ struct Printer
|
|||
|
||||
writer.advance(o->location.begin);
|
||||
writer.identifier(o->name.value);
|
||||
if (const auto* genericTypePackCstNode = lookupCstNode<CstGenericTypePack>(o))
|
||||
advance(genericTypePackCstNode->ellipsisPosition);
|
||||
writer.symbol("...");
|
||||
}
|
||||
if (cstNode)
|
||||
advance(cstNode->closeGenericsPosition);
|
||||
writer.symbol(">");
|
||||
}
|
||||
|
||||
{
|
||||
visualizeNamedTypeList(
|
||||
a->argTypes,
|
||||
true,
|
||||
cstNode ? std::make_optional(cstNode->openArgsPosition) : std::nullopt,
|
||||
cstNode ? std::make_optional(cstNode->closeArgsPosition) : std::nullopt,
|
||||
cstNode ? cstNode->argumentsCommaPositions : Luau::AstArray<Position>{},
|
||||
a->argNames,
|
||||
cstNode ? cstNode->argumentNameColonPositions : Luau::AstArray<std::optional<Position>>{}
|
||||
);
|
||||
visualizeTypeList(a->argTypes, true);
|
||||
}
|
||||
|
||||
if (cstNode)
|
||||
advance(cstNode->returnArrowPosition);
|
||||
writer.symbol("->");
|
||||
visualizeTypeList(a->returnTypes, true);
|
||||
}
|
||||
|
@ -2733,25 +2527,14 @@ struct Printer
|
|||
}
|
||||
else if (const auto& a = typeAnnotation.as<AstTypeUnion>())
|
||||
{
|
||||
const auto cstNode = lookupCstNode<CstTypeUnion>(a);
|
||||
|
||||
if (!cstNode && a->types.size == 2)
|
||||
if (a->types.size == 2)
|
||||
{
|
||||
AstType* l = a->types.data[0];
|
||||
AstType* r = a->types.data[1];
|
||||
|
||||
if (FFlag::LuauParseOptionalAsNode2)
|
||||
{
|
||||
auto lta = l->as<AstTypeReference>();
|
||||
if (lta && lta->name == "nil" && !r->is<AstTypeOptional>())
|
||||
std::swap(l, r);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto lta = l->as<AstTypeReference>();
|
||||
if (lta && lta->name == "nil")
|
||||
std::swap(l, r);
|
||||
}
|
||||
auto lta = l->as<AstTypeReference>();
|
||||
if (lta && lta->name == "nil")
|
||||
std::swap(l, r);
|
||||
|
||||
// it's still possible that we had a (T | U) or (T | nil) and not (nil | T)
|
||||
auto rta = r->as<AstTypeReference>();
|
||||
|
@ -2772,39 +2555,15 @@ struct Printer
|
|||
}
|
||||
}
|
||||
|
||||
if (cstNode && cstNode->leadingPosition)
|
||||
{
|
||||
advance(*cstNode->leadingPosition);
|
||||
writer.symbol("|");
|
||||
}
|
||||
|
||||
size_t separatorIndex = 0;
|
||||
for (size_t i = 0; i < a->types.size; ++i)
|
||||
{
|
||||
if (FFlag::LuauParseOptionalAsNode2)
|
||||
{
|
||||
if (const auto optional = a->types.data[i]->as<AstTypeOptional>())
|
||||
{
|
||||
advance(optional->location.begin);
|
||||
writer.symbol("?");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
if (cstNode && FFlag::LuauParseOptionalAsNode2)
|
||||
{
|
||||
// separatorIndex is only valid if `?` is handled as an AstTypeOptional
|
||||
advance(cstNode->separatorPositions.data[separatorIndex]);
|
||||
separatorIndex++;
|
||||
}
|
||||
else
|
||||
writer.maybeSpace(a->types.data[i]->location.begin, 2);
|
||||
writer.maybeSpace(a->types.data[i]->location.begin, 2);
|
||||
writer.symbol("|");
|
||||
}
|
||||
|
||||
bool wrap = !cstNode && (a->types.data[i]->as<AstTypeIntersection>() || a->types.data[i]->as<AstTypeFunction>());
|
||||
bool wrap = a->types.data[i]->as<AstTypeIntersection>() || a->types.data[i]->as<AstTypeFunction>();
|
||||
|
||||
if (wrap)
|
||||
writer.symbol("(");
|
||||
|
@ -2817,27 +2576,15 @@ struct Printer
|
|||
}
|
||||
else if (const auto& a = typeAnnotation.as<AstTypeIntersection>())
|
||||
{
|
||||
const auto cstNode = lookupCstNode<CstTypeIntersection>(a);
|
||||
|
||||
// If the sizes are equal, we know there is a leading & token
|
||||
if (cstNode && cstNode->leadingPosition)
|
||||
{
|
||||
advance(*cstNode->leadingPosition);
|
||||
writer.symbol("&");
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < a->types.size; ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
if (cstNode)
|
||||
advance(cstNode->separatorPositions.data[i - 1]);
|
||||
else
|
||||
writer.maybeSpace(a->types.data[i]->location.begin, 2);
|
||||
writer.maybeSpace(a->types.data[i]->location.begin, 2);
|
||||
writer.symbol("&");
|
||||
}
|
||||
|
||||
bool wrap = !cstNode && (a->types.data[i]->as<AstTypeUnion>() || a->types.data[i]->as<AstTypeFunction>());
|
||||
bool wrap = a->types.data[i]->as<AstTypeUnion>() || a->types.data[i]->as<AstTypeFunction>();
|
||||
|
||||
if (wrap)
|
||||
writer.symbol("(");
|
||||
|
@ -2852,7 +2599,7 @@ struct Printer
|
|||
{
|
||||
writer.symbol("(");
|
||||
visualizeTypeAnnotation(*a->type);
|
||||
advanceBefore(a->location.end, 1);
|
||||
advance(Position{a->location.end.line, a->location.end.column - 1});
|
||||
writer.symbol(")");
|
||||
}
|
||||
else if (const auto& a = typeAnnotation.as<AstTypeSingletonBool>())
|
||||
|
@ -2886,7 +2633,7 @@ std::string toString(AstNode* node)
|
|||
StringWriter writer;
|
||||
writer.pos = node->location.begin;
|
||||
|
||||
if (FFlag::LuauStoreCSTData2)
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
{
|
||||
Printer printer(writer, CstNodeMap{nullptr});
|
||||
printer.writeTypes = true;
|
||||
|
@ -2922,7 +2669,7 @@ void dump(AstNode* node)
|
|||
std::string transpile(AstStatBlock& block, const CstNodeMap& cstNodeMap)
|
||||
{
|
||||
StringWriter writer;
|
||||
if (FFlag::LuauStoreCSTData2)
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
{
|
||||
Printer(writer, cstNodeMap).visualizeBlock(block);
|
||||
}
|
||||
|
@ -2936,7 +2683,7 @@ std::string transpile(AstStatBlock& block, const CstNodeMap& cstNodeMap)
|
|||
std::string transpileWithTypes(AstStatBlock& block, const CstNodeMap& cstNodeMap)
|
||||
{
|
||||
StringWriter writer;
|
||||
if (FFlag::LuauStoreCSTData2)
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
{
|
||||
Printer printer(writer, cstNodeMap);
|
||||
printer.writeTypes = true;
|
||||
|
|
|
@ -407,6 +407,41 @@ PendingTypePack* TxnLog::changeLevel(TypePackId tp, TypeLevel newLevel)
|
|||
return newTp;
|
||||
}
|
||||
|
||||
PendingType* TxnLog::changeScope(TypeId ty, NotNull<Scope> newScope)
|
||||
{
|
||||
LUAU_ASSERT(get<FreeType>(ty) || get<TableType>(ty) || get<FunctionType>(ty));
|
||||
|
||||
PendingType* newTy = queue(ty);
|
||||
if (FreeType* ftv = Luau::getMutable<FreeType>(newTy))
|
||||
{
|
||||
ftv->scope = newScope;
|
||||
}
|
||||
else if (TableType* ttv = Luau::getMutable<TableType>(newTy))
|
||||
{
|
||||
LUAU_ASSERT(ttv->state == TableState::Free || ttv->state == TableState::Generic);
|
||||
ttv->scope = newScope;
|
||||
}
|
||||
else if (FunctionType* ftv = Luau::getMutable<FunctionType>(newTy))
|
||||
{
|
||||
ftv->scope = newScope;
|
||||
}
|
||||
|
||||
return newTy;
|
||||
}
|
||||
|
||||
PendingTypePack* TxnLog::changeScope(TypePackId tp, NotNull<Scope> newScope)
|
||||
{
|
||||
LUAU_ASSERT(get<FreeTypePack>(tp));
|
||||
|
||||
PendingTypePack* newTp = queue(tp);
|
||||
if (FreeTypePack* ftp = Luau::getMutable<FreeTypePack>(newTp))
|
||||
{
|
||||
ftp->scope = newScope;
|
||||
}
|
||||
|
||||
return newTp;
|
||||
}
|
||||
|
||||
PendingType* TxnLog::changeIndexer(TypeId ty, std::optional<TableIndexer> indexer)
|
||||
{
|
||||
LUAU_ASSERT(get<TableType>(ty));
|
||||
|
|
|
@ -488,12 +488,11 @@ FreeType::FreeType(TypeLevel level, TypeId lowerBound, TypeId upperBound)
|
|||
{
|
||||
}
|
||||
|
||||
FreeType::FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound, Polarity polarity)
|
||||
FreeType::FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound)
|
||||
: index(Unifiable::freshIndex())
|
||||
, scope(scope)
|
||||
, lowerBound(lowerBound)
|
||||
, upperBound(upperBound)
|
||||
, polarity(polarity)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -544,18 +543,16 @@ GenericType::GenericType(TypeLevel level)
|
|||
{
|
||||
}
|
||||
|
||||
GenericType::GenericType(const Name& name, Polarity polarity)
|
||||
GenericType::GenericType(const Name& name)
|
||||
: index(Unifiable::freshIndex())
|
||||
, name(name)
|
||||
, explicitName(true)
|
||||
, polarity(polarity)
|
||||
{
|
||||
}
|
||||
|
||||
GenericType::GenericType(Scope* scope, Polarity polarity)
|
||||
GenericType::GenericType(Scope* scope)
|
||||
: index(Unifiable::freshIndex())
|
||||
, scope(scope)
|
||||
, polarity(polarity)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -633,6 +630,23 @@ FunctionType::FunctionType(TypeLevel level, TypePackId argTypes, TypePackId retT
|
|||
{
|
||||
}
|
||||
|
||||
FunctionType::FunctionType(
|
||||
TypeLevel level,
|
||||
Scope* scope,
|
||||
TypePackId argTypes,
|
||||
TypePackId retTypes,
|
||||
std::optional<FunctionDefinition> defn,
|
||||
bool hasSelf
|
||||
)
|
||||
: definition(std::move(defn))
|
||||
, level(level)
|
||||
, scope(scope)
|
||||
, argTypes(argTypes)
|
||||
, retTypes(retTypes)
|
||||
, hasSelf(hasSelf)
|
||||
{
|
||||
}
|
||||
|
||||
FunctionType::FunctionType(
|
||||
std::vector<TypeId> generics,
|
||||
std::vector<TypePackId> genericPacks,
|
||||
|
@ -669,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(
|
||||
|
@ -1271,9 +1306,9 @@ IntersectionTypeIterator end(const IntersectionType* itv)
|
|||
return IntersectionTypeIterator{};
|
||||
}
|
||||
|
||||
TypeId freshType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, Scope* scope, Polarity polarity)
|
||||
TypeId freshType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, Scope* scope)
|
||||
{
|
||||
return arena->addType(FreeType{scope, builtinTypes->neverType, builtinTypes->unknownType, polarity});
|
||||
return arena->addType(FreeType{scope, builtinTypes->neverType, builtinTypes->unknownType});
|
||||
}
|
||||
|
||||
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate)
|
||||
|
|
|
@ -77,9 +77,9 @@ TypeId TypeArena::freshType_DEPRECATED(Scope* scope, TypeLevel level)
|
|||
return allocated;
|
||||
}
|
||||
|
||||
TypePackId TypeArena::freshTypePack(Scope* scope, Polarity polarity)
|
||||
TypePackId TypeArena::freshTypePack(Scope* scope)
|
||||
{
|
||||
TypePackId allocated = typePacks.allocate(FreeTypePack{scope, polarity});
|
||||
TypePackId allocated = typePacks.allocate(FreeTypePack{scope});
|
||||
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
|
||||
#include <string>
|
||||
|
||||
LUAU_FASTFLAG(LuauStoreCSTData2)
|
||||
|
||||
static char* allocateString(Luau::Allocator& allocator, std::string_view contents)
|
||||
{
|
||||
char* result = (char*)allocator.allocate(contents.size() + 1);
|
||||
|
@ -307,8 +305,7 @@ public:
|
|||
std::optional<AstArgumentName>* arg = &argNames.data[i++];
|
||||
|
||||
if (el)
|
||||
new (arg)
|
||||
std::optional<AstArgumentName>(AstArgumentName(AstName(el->name.c_str()), FFlag::LuauStoreCSTData2 ? Location() : el->location));
|
||||
new (arg) std::optional<AstArgumentName>(AstArgumentName(AstName(el->name.c_str()), el->location));
|
||||
else
|
||||
new (arg) std::optional<AstArgumentName>();
|
||||
}
|
||||
|
|
|
@ -26,14 +26,10 @@
|
|||
#include "Luau/VisitType.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||
|
||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
LUAU_FASTFLAGVARIABLE(LuauImproveTypePathsInErrors)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -1205,8 +1201,7 @@ void TypeChecker2::visit(AstStatTypeAlias* stat)
|
|||
|
||||
void TypeChecker2::visit(AstStatTypeFunction* stat)
|
||||
{
|
||||
if (FFlag::LuauUserTypeFunTypecheck)
|
||||
visit(stat->body);
|
||||
// TODO: add type checking for user-defined type functions
|
||||
}
|
||||
|
||||
void TypeChecker2::visit(AstTypeList types)
|
||||
|
@ -2230,21 +2225,10 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey)
|
|||
|
||||
return builtinTypes->numberType;
|
||||
case AstExprBinary::Op::Concat:
|
||||
{
|
||||
if (FFlag::LuauTypeCheckerAcceptNumberConcats)
|
||||
{
|
||||
const TypeId numberOrString = module->internalTypes.addType(UnionType{{builtinTypes->numberType, builtinTypes->stringType}});
|
||||
testIsSubtype(leftType, numberOrString, expr->left->location);
|
||||
testIsSubtype(rightType, numberOrString, expr->right->location);
|
||||
}
|
||||
else
|
||||
{
|
||||
testIsSubtype(leftType, builtinTypes->stringType, expr->left->location);
|
||||
testIsSubtype(rightType, builtinTypes->stringType, expr->right->location);
|
||||
}
|
||||
testIsSubtype(leftType, builtinTypes->stringType, expr->left->location);
|
||||
testIsSubtype(rightType, builtinTypes->stringType, expr->right->location);
|
||||
|
||||
return builtinTypes->stringType;
|
||||
}
|
||||
case AstExprBinary::Op::CompareGe:
|
||||
case AstExprBinary::Op::CompareGt:
|
||||
case AstExprBinary::Op::CompareLe:
|
||||
|
@ -2717,61 +2701,20 @@ Reasonings TypeChecker2::explainReasonings_(TID subTy, TID superTy, Location loc
|
|||
if (!subLeafTy && !superLeafTy && !subLeafTp && !superLeafTp)
|
||||
ice->ice("Subtyping test returned a reasoning where one path ends at a type and the other ends at a pack.", location);
|
||||
|
||||
if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
std::string relation = "a subtype of";
|
||||
if (reasoning.variance == SubtypingVariance::Invariant)
|
||||
relation = "exactly";
|
||||
else if (reasoning.variance == SubtypingVariance::Contravariant)
|
||||
relation = "a supertype of";
|
||||
std::string relation = "a subtype of";
|
||||
if (reasoning.variance == SubtypingVariance::Invariant)
|
||||
relation = "exactly";
|
||||
else if (reasoning.variance == SubtypingVariance::Contravariant)
|
||||
relation = "a supertype of";
|
||||
|
||||
std::string subLeafAsString = toString(subLeaf);
|
||||
// if the string is empty, it must be an empty type pack
|
||||
if (subLeafAsString.empty())
|
||||
subLeafAsString = "()";
|
||||
|
||||
std::string superLeafAsString = toString(superLeaf);
|
||||
// if the string is empty, it must be an empty type pack
|
||||
if (superLeafAsString.empty())
|
||||
superLeafAsString = "()";
|
||||
|
||||
std::stringstream baseReasonBuilder;
|
||||
baseReasonBuilder << "`" << subLeafAsString << "` is not " << relation << " `" << superLeafAsString << "`";
|
||||
std::string baseReason = baseReasonBuilder.str();
|
||||
|
||||
std::stringstream reason;
|
||||
|
||||
if (reasoning.subPath == reasoning.superPath)
|
||||
reason << toStringHuman(reasoning.subPath) << "`" << subLeafAsString << "` in the former type and `" << superLeafAsString
|
||||
<< "` in the latter type, and " << baseReason;
|
||||
else if (!reasoning.subPath.empty() && !reasoning.superPath.empty())
|
||||
reason << toStringHuman(reasoning.subPath) << "`" << subLeafAsString << "` and " << toStringHuman(reasoning.superPath) << "`"
|
||||
<< superLeafAsString << "`, and " << baseReason;
|
||||
else if (!reasoning.subPath.empty())
|
||||
reason << toStringHuman(reasoning.subPath) << "`" << subLeafAsString << "`, which is not " << relation << " `" << superLeafAsString
|
||||
<< "`";
|
||||
else
|
||||
reason << toStringHuman(reasoning.superPath) << "`" << superLeafAsString << "`, and " << baseReason;
|
||||
|
||||
reasons.push_back(reason.str());
|
||||
}
|
||||
std::string reason;
|
||||
if (reasoning.subPath == reasoning.superPath)
|
||||
reason = "at " + toString(reasoning.subPath) + ", " + toString(subLeaf) + " is not " + relation + " " + toString(superLeaf);
|
||||
else
|
||||
{
|
||||
std::string relation = "a subtype of";
|
||||
if (reasoning.variance == SubtypingVariance::Invariant)
|
||||
relation = "exactly";
|
||||
else if (reasoning.variance == SubtypingVariance::Contravariant)
|
||||
relation = "a supertype of";
|
||||
reason = "type " + toString(subTy) + toString(reasoning.subPath, /* prefixDot */ true) + " (" + toString(subLeaf) + ") is not " +
|
||||
relation + " " + toString(superTy) + toString(reasoning.superPath, /* prefixDot */ true) + " (" + toString(superLeaf) + ")";
|
||||
|
||||
std::string reason;
|
||||
if (reasoning.subPath == reasoning.superPath)
|
||||
reason = "at " + toString(reasoning.subPath) + ", " + toString(subLeaf) + " is not " + relation + " " + toString(superLeaf);
|
||||
else
|
||||
reason = "type " + toString(subTy) + toString(reasoning.subPath, /* prefixDot */ true) + " (" + toString(subLeaf) + ") is not " +
|
||||
relation + " " + toString(superTy) + toString(reasoning.superPath, /* prefixDot */ true) + " (" + toString(superLeaf) + ")";
|
||||
|
||||
reasons.push_back(reason);
|
||||
}
|
||||
reasons.push_back(reason);
|
||||
|
||||
// if we haven't already proved this isn't suppressing, we have to keep checking.
|
||||
if (suppressed)
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -13,7 +13,11 @@
|
|||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeFunFixHydratedClasses)
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeFunSingletonEquality)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypeofReturnsType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeFunPrintFix)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeFunReadWriteParents)
|
||||
|
||||
namespace Luau
|
||||
|
@ -1613,8 +1617,11 @@ void registerTypeUserData(lua_State* L)
|
|||
// Create and register metatable for type userdata
|
||||
luaL_newmetatable(L, "type");
|
||||
|
||||
lua_pushstring(L, "type");
|
||||
lua_setfield(L, -2, "__type");
|
||||
if (FFlag::LuauUserTypeFunTypeofReturnsType)
|
||||
{
|
||||
lua_pushstring(L, "type");
|
||||
lua_setfield(L, -2, "__type");
|
||||
}
|
||||
|
||||
// Protect metatable from being changed
|
||||
lua_pushstring(L, "The metatable is locked");
|
||||
|
@ -1655,7 +1662,10 @@ static int print(lua_State* L)
|
|||
const char* s = luaL_tolstring(L, i, &l); // convert to string using __tostring et al
|
||||
if (i > 1)
|
||||
{
|
||||
result.append(1, '\t');
|
||||
if (FFlag::LuauTypeFunPrintFix)
|
||||
result.append(1, '\t');
|
||||
else
|
||||
result.append('\t', 1);
|
||||
}
|
||||
result.append(s, l);
|
||||
lua_pop(L, 1);
|
||||
|
@ -1748,14 +1758,14 @@ bool areEqual(SeenSet& seen, const TypeFunctionSingletonType& lhs, const TypeFun
|
|||
|
||||
{
|
||||
const TypeFunctionBooleanSingleton* lp = get<TypeFunctionBooleanSingleton>(&lhs);
|
||||
const TypeFunctionBooleanSingleton* rp = get<TypeFunctionBooleanSingleton>(&rhs);
|
||||
const TypeFunctionBooleanSingleton* rp = get<TypeFunctionBooleanSingleton>(FFlag::LuauTypeFunSingletonEquality ? &rhs : &lhs);
|
||||
if (lp && rp)
|
||||
return lp->value == rp->value;
|
||||
}
|
||||
|
||||
{
|
||||
const TypeFunctionStringSingleton* lp = get<TypeFunctionStringSingleton>(&lhs);
|
||||
const TypeFunctionStringSingleton* rp = get<TypeFunctionStringSingleton>(&rhs);
|
||||
const TypeFunctionStringSingleton* rp = get<TypeFunctionStringSingleton>(FFlag::LuauTypeFunSingletonEquality ? &rhs : &lhs);
|
||||
if (lp && rp)
|
||||
return lp->value == rp->value;
|
||||
}
|
||||
|
@ -1908,7 +1918,10 @@ bool areEqual(SeenSet& seen, const TypeFunctionClassType& lhs, const TypeFunctio
|
|||
if (seenSetContains(seen, &lhs, &rhs))
|
||||
return true;
|
||||
|
||||
return lhs.classTy == rhs.classTy;
|
||||
if (FFlag::LuauTypeFunFixHydratedClasses)
|
||||
return lhs.classTy == rhs.classTy;
|
||||
else
|
||||
return lhs.name_DEPRECATED == rhs.name_DEPRECATED;
|
||||
}
|
||||
|
||||
bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType& rhs)
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
// used to control the recursion limit of any operations done by user-defined type functions
|
||||
// currently, controls serialization, deserialization, and `type.copy`
|
||||
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000);
|
||||
LUAU_FASTFLAG(LuauTypeFunFixHydratedClasses)
|
||||
LUAU_FASTFLAG(LuauTypeFunReadWriteParents)
|
||||
|
||||
namespace Luau
|
||||
|
@ -208,11 +209,19 @@ private:
|
|||
}
|
||||
else if (auto c = get<ClassType>(ty))
|
||||
{
|
||||
// Since there aren't any new class types being created in type functions, we will deserialize by using a direct reference to the original
|
||||
// class
|
||||
target = typeFunctionRuntime->typeArena.allocate(
|
||||
TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty}
|
||||
);
|
||||
if (FFlag::LuauTypeFunFixHydratedClasses)
|
||||
{
|
||||
// Since there aren't any new class types being created in type functions, we will deserialize by using a direct reference to the
|
||||
// original class
|
||||
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty});
|
||||
}
|
||||
else
|
||||
{
|
||||
state->classesSerialized_DEPRECATED[c->name] = ty;
|
||||
target = typeFunctionRuntime->typeArena.allocate(
|
||||
TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, /* classTy */ nullptr, c->name}
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (auto g = get<GenericType>(ty))
|
||||
{
|
||||
|
@ -704,7 +713,17 @@ private:
|
|||
}
|
||||
else if (auto c = get<TypeFunctionClassType>(ty))
|
||||
{
|
||||
target = c->classTy;
|
||||
if (FFlag::LuauTypeFunFixHydratedClasses)
|
||||
{
|
||||
target = c->classTy;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto result = state->classesSerialized_DEPRECATED.find(c->name_DEPRECATED))
|
||||
target = *result;
|
||||
else
|
||||
state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious class type is being deserialized");
|
||||
}
|
||||
}
|
||||
else if (auto g = get<TypeFunctionGenericType>(ty))
|
||||
{
|
||||
|
|
|
@ -32,10 +32,9 @@ LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
|
|||
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAGVARIABLE(LuauOldSolverCreatesChildScopePointers)
|
||||
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
|
||||
LUAU_FASTFLAGVARIABLE(LuauStatForInFix)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -256,7 +255,6 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
|
|||
currentModule->type = module.type;
|
||||
currentModule->allocator = module.allocator;
|
||||
currentModule->names = module.names;
|
||||
currentModule->root = module.root;
|
||||
|
||||
iceHandler->moduleName = module.name;
|
||||
normalizer.arena = ¤tModule->internalTypes;
|
||||
|
@ -1318,24 +1316,8 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin)
|
|||
// Extract the remaining return values of the call
|
||||
// and check them against the parameter types of the iterator function.
|
||||
auto [types, tail] = flatten(callRetPack);
|
||||
|
||||
if (FFlag::LuauStatForInFix)
|
||||
{
|
||||
if (!types.empty())
|
||||
{
|
||||
std::vector<TypeId> argTypes = std::vector<TypeId>(types.begin() + 1, types.end());
|
||||
argPack = addTypePack(TypePackVar{TypePack{std::move(argTypes), tail}});
|
||||
}
|
||||
else
|
||||
{
|
||||
argPack = addTypePack(TypePack{});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<TypeId> argTypes = std::vector<TypeId>(types.begin() + 1, types.end());
|
||||
argPack = addTypePack(TypePackVar{TypePack{std::move(argTypes), tail}});
|
||||
}
|
||||
std::vector<TypeId> argTypes = std::vector<TypeId>(types.begin() + 1, types.end());
|
||||
argPack = addTypePack(TypePackVar{TypePack{std::move(argTypes), tail}});
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1673,10 +1655,7 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea
|
|||
FreeType* ftv = getMutable<FreeType>(ty);
|
||||
LUAU_ASSERT(ftv);
|
||||
ftv->forwardedTypeAlias = true;
|
||||
if (FFlag::LuauRetainDefinitionAliasLocations)
|
||||
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty, typealias.location};
|
||||
else
|
||||
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
|
||||
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
|
||||
|
||||
scope->typeAliasLocations[name] = typealias.location;
|
||||
scope->typeAliasNameLocations[name] = typealias.nameLocation;
|
||||
|
@ -1721,10 +1700,7 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareClass& de
|
|||
TypeId metaTy = addType(TableType{TableState::Sealed, scope->level});
|
||||
|
||||
ctv->metatable = metaTy;
|
||||
if (FFlag::LuauRetainDefinitionAliasLocations)
|
||||
scope->exportedTypeBindings[className] = TypeFun{{}, classTy, declaredClass.location};
|
||||
else
|
||||
scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
|
||||
scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
|
||||
}
|
||||
|
||||
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass)
|
||||
|
@ -5236,9 +5212,12 @@ LUAU_NOINLINE void TypeChecker::reportErrorCodeTooComplex(const Location& locati
|
|||
ScopePtr TypeChecker::childFunctionScope(const ScopePtr& parent, const Location& location, int subLevel)
|
||||
{
|
||||
ScopePtr scope = std::make_shared<Scope>(parent, subLevel);
|
||||
scope->location = location;
|
||||
scope->returnType = parent->returnType;
|
||||
parent->children.emplace_back(scope.get());
|
||||
if (FFlag::LuauOldSolverCreatesChildScopePointers)
|
||||
{
|
||||
scope->location = location;
|
||||
scope->returnType = parent->returnType;
|
||||
parent->children.emplace_back(scope.get());
|
||||
}
|
||||
|
||||
currentModule->scopes.push_back(std::make_pair(location, scope));
|
||||
return scope;
|
||||
|
@ -5250,9 +5229,12 @@ ScopePtr TypeChecker::childScope(const ScopePtr& parent, const Location& locatio
|
|||
ScopePtr scope = std::make_shared<Scope>(parent);
|
||||
scope->level = parent->level;
|
||||
scope->varargPack = parent->varargPack;
|
||||
scope->location = location;
|
||||
scope->returnType = parent->returnType;
|
||||
parent->children.emplace_back(scope.get());
|
||||
if (FFlag::LuauOldSolverCreatesChildScopePointers)
|
||||
{
|
||||
scope->location = location;
|
||||
scope->returnType = parent->returnType;
|
||||
parent->children.emplace_back(scope.get());
|
||||
}
|
||||
|
||||
currentModule->scopes.push_back(std::make_pair(location, scope));
|
||||
return scope;
|
||||
|
@ -5742,10 +5724,6 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno
|
|||
TypeId ty = checkExpr(scope, *typeOf->expr).type;
|
||||
return ty;
|
||||
}
|
||||
else if (annotation.is<AstTypeOptional>())
|
||||
{
|
||||
return builtinTypes->nilType;
|
||||
}
|
||||
else if (const auto& un = annotation.as<AstTypeUnion>())
|
||||
{
|
||||
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypePackDetectCycles)
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -18,11 +18,10 @@ FreeTypePack::FreeTypePack(TypeLevel level)
|
|||
{
|
||||
}
|
||||
|
||||
FreeTypePack::FreeTypePack(Scope* scope, Polarity polarity)
|
||||
FreeTypePack::FreeTypePack(Scope* scope)
|
||||
: index(Unifiable::freshIndex())
|
||||
, level{}
|
||||
, scope(scope)
|
||||
, polarity(polarity)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -53,10 +52,9 @@ GenericTypePack::GenericTypePack(const Name& name)
|
|||
{
|
||||
}
|
||||
|
||||
GenericTypePack::GenericTypePack(Scope* scope, Polarity polarity)
|
||||
GenericTypePack::GenericTypePack(Scope* scope)
|
||||
: index(Unifiable::freshIndex())
|
||||
, scope(scope)
|
||||
, polarity(polarity)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -149,15 +147,6 @@ TypePackIterator& TypePackIterator::operator++()
|
|||
currentTypePack = tp->tail ? log->follow(*tp->tail) : nullptr;
|
||||
tp = currentTypePack ? log->getMutable<TypePack>(currentTypePack) : nullptr;
|
||||
|
||||
if (FFlag::LuauTypePackDetectCycles && tp)
|
||||
{
|
||||
// Step twice on each iteration to detect cycles
|
||||
tailCycleCheck = tp->tail ? log->follow(*tp->tail) : nullptr;
|
||||
|
||||
if (currentTypePack == tailCycleCheck)
|
||||
throw InternalCompilerError("TypePackIterator detected a type pack cycle");
|
||||
}
|
||||
|
||||
currentIndex = 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,8 +14,7 @@
|
|||
#include <type_traits>
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAGVARIABLE(LuauDisableNewSolverAssertsInMixedMode)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauDisableNewSolverAssertsInMixedMode);
|
||||
// Maximum number of steps to follow when traversing a path. May not always
|
||||
// equate to the number of components in a path, depending on the traversal
|
||||
// logic.
|
||||
|
@ -639,247 +638,6 @@ std::string toString(const TypePath::Path& path, bool prefixDot)
|
|||
return result.str();
|
||||
}
|
||||
|
||||
std::string toStringHuman(const TypePath::Path& path)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauSolverV2);
|
||||
|
||||
enum class State
|
||||
{
|
||||
Initial,
|
||||
Normal,
|
||||
Property,
|
||||
PendingIs,
|
||||
PendingAs,
|
||||
PendingWhich,
|
||||
};
|
||||
|
||||
std::stringstream result;
|
||||
State state = State::Initial;
|
||||
bool last = false;
|
||||
|
||||
auto strComponent = [&](auto&& c)
|
||||
{
|
||||
using T = std::decay_t<decltype(c)>;
|
||||
if constexpr (std::is_same_v<T, TypePath::Property>)
|
||||
{
|
||||
if (state == State::PendingIs)
|
||||
result << ", ";
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case State::Initial:
|
||||
case State::PendingIs:
|
||||
if (c.isRead)
|
||||
result << "accessing `";
|
||||
else
|
||||
result << "writing to `";
|
||||
break;
|
||||
case State::Property:
|
||||
// if the previous state was a property, then we're doing a sequence of indexing
|
||||
result << '.';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
result << c.name;
|
||||
|
||||
state = State::Property;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, TypePath::Index>)
|
||||
{
|
||||
size_t humanIndex = c.index + 1;
|
||||
|
||||
if (state == State::Initial && !last)
|
||||
result << "in" << ' ';
|
||||
else if (state == State::PendingIs)
|
||||
result << ' ' << "has" << ' ';
|
||||
else if (state == State::Property)
|
||||
result << '`' << ' ' << "has" << ' ';
|
||||
|
||||
result << "the " << humanIndex;
|
||||
switch (humanIndex)
|
||||
{
|
||||
case 1:
|
||||
result << "st";
|
||||
break;
|
||||
case 2:
|
||||
result << "nd";
|
||||
break;
|
||||
case 3:
|
||||
result << "rd";
|
||||
break;
|
||||
default:
|
||||
result << "th";
|
||||
}
|
||||
|
||||
switch (c.variant)
|
||||
{
|
||||
case TypePath::Index::Variant::Pack:
|
||||
result << ' ' << "entry in the type pack";
|
||||
break;
|
||||
case TypePath::Index::Variant::Union:
|
||||
result << ' ' << "component of the union";
|
||||
break;
|
||||
case TypePath::Index::Variant::Intersection:
|
||||
result << ' ' << "component of the intersection";
|
||||
break;
|
||||
}
|
||||
|
||||
if (state == State::PendingWhich)
|
||||
result << ' ' << "which";
|
||||
|
||||
if (state == State::PendingIs || state == State::Property)
|
||||
state = State::PendingAs;
|
||||
else
|
||||
state = State::PendingIs;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, TypePath::TypeField>)
|
||||
{
|
||||
if (state == State::Initial && !last)
|
||||
result << "in" << ' ';
|
||||
else if (state == State::PendingIs)
|
||||
result << ", ";
|
||||
else if (state == State::Property)
|
||||
result << '`' << ' ' << "has" << ' ';
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case TypePath::TypeField::Table:
|
||||
result << "the table portion";
|
||||
if (state == State::Property)
|
||||
state = State::PendingAs;
|
||||
else
|
||||
state = State::PendingIs;
|
||||
break;
|
||||
case TypePath::TypeField::Metatable:
|
||||
result << "the metatable portion";
|
||||
if (state == State::Property)
|
||||
state = State::PendingAs;
|
||||
else
|
||||
state = State::PendingIs;
|
||||
break;
|
||||
case TypePath::TypeField::LowerBound:
|
||||
result << "the lower bound of" << ' ';
|
||||
state = State::Normal;
|
||||
break;
|
||||
case TypePath::TypeField::UpperBound:
|
||||
result << "the upper bound of" << ' ';
|
||||
state = State::Normal;
|
||||
break;
|
||||
case TypePath::TypeField::IndexLookup:
|
||||
result << "the index type";
|
||||
if (state == State::Property)
|
||||
state = State::PendingAs;
|
||||
else
|
||||
state = State::PendingIs;
|
||||
break;
|
||||
case TypePath::TypeField::IndexResult:
|
||||
result << "the result of indexing";
|
||||
if (state == State::Property)
|
||||
state = State::PendingAs;
|
||||
else
|
||||
state = State::PendingIs;
|
||||
break;
|
||||
case TypePath::TypeField::Negated:
|
||||
result << "the negation" << ' ';
|
||||
state = State::Normal;
|
||||
break;
|
||||
case TypePath::TypeField::Variadic:
|
||||
result << "the variadic" << ' ';
|
||||
state = State::Normal;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, TypePath::PackField>)
|
||||
{
|
||||
if (state == State::PendingIs)
|
||||
result << ", ";
|
||||
else if (state == State::Property)
|
||||
result << "`, ";
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case TypePath::PackField::Arguments:
|
||||
if (state == State::Initial)
|
||||
result << "it" << ' ';
|
||||
else if (state == State::PendingIs)
|
||||
result << "the function" << ' ';
|
||||
|
||||
result << "takes";
|
||||
break;
|
||||
case TypePath::PackField::Returns:
|
||||
if (state == State::Initial)
|
||||
result << "it" << ' ';
|
||||
else if (state == State::PendingIs)
|
||||
result << "the function" << ' ';
|
||||
|
||||
result << "returns";
|
||||
break;
|
||||
case TypePath::PackField::Tail:
|
||||
if (state == State::Initial)
|
||||
result << "it has" << ' ';
|
||||
result << "a tail of";
|
||||
break;
|
||||
}
|
||||
|
||||
if (state == State::PendingIs)
|
||||
{
|
||||
result << ' ';
|
||||
state = State::PendingWhich;
|
||||
}
|
||||
else
|
||||
{
|
||||
result << ' ';
|
||||
state = State::Normal;
|
||||
}
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, TypePath::Reduction>)
|
||||
{
|
||||
if (state == State::Initial)
|
||||
result << "it" << ' ';
|
||||
result << "reduces to" << ' ';
|
||||
state = State::Normal;
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(always_false_v<T>, "Unhandled Component variant");
|
||||
}
|
||||
};
|
||||
|
||||
size_t count = 0;
|
||||
|
||||
for (const TypePath::Component& component : path.components)
|
||||
{
|
||||
count++;
|
||||
if (count == path.components.size())
|
||||
last = true;
|
||||
|
||||
Luau::visit(strComponent, component);
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case State::Property:
|
||||
result << "` results in ";
|
||||
break;
|
||||
case State::PendingWhich:
|
||||
// pending `which` becomes `is` if it's at the end
|
||||
result << "is" << ' ';
|
||||
break;
|
||||
case State::PendingIs:
|
||||
result << ' ' << "is" << ' ';
|
||||
break;
|
||||
case State::PendingAs:
|
||||
result << ' ' << "as" << ' ';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return result.str();
|
||||
}
|
||||
|
||||
static bool traverse(TraversalState& state, const Path& path)
|
||||
{
|
||||
auto step = [&state](auto&& c)
|
||||
|
|
|
@ -14,9 +14,7 @@ LUAU_FASTFLAG(LuauSolverV2);
|
|||
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete);
|
||||
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope);
|
||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
LUAU_FASTFLAG(LuauNonReentrantGeneralization)
|
||||
LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -306,11 +304,7 @@ TypePack extendTypePack(
|
|||
// also have to create a new tail.
|
||||
|
||||
TypePack newPack;
|
||||
newPack.tail = arena.freshTypePack(ftp->scope, ftp->polarity);
|
||||
|
||||
if (FFlag::LuauNonReentrantGeneralization)
|
||||
trackInteriorFreeTypePack(ftp->scope, *newPack.tail);
|
||||
|
||||
newPack.tail = arena.freshTypePack(ftp->scope);
|
||||
if (FFlag::LuauSolverV2)
|
||||
result.tail = newPack.tail;
|
||||
size_t overridesIndex = 0;
|
||||
|
@ -325,7 +319,7 @@ TypePack extendTypePack(
|
|||
{
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
FreeType ft{ftp->scope, builtinTypes->neverType, builtinTypes->unknownType, ftp->polarity};
|
||||
FreeType ft{ftp->scope, builtinTypes->neverType, builtinTypes->unknownType};
|
||||
t = arena.addType(ft);
|
||||
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
|
||||
trackInteriorFreeType(ftp->scope, t);
|
||||
|
@ -574,24 +568,4 @@ void trackInteriorFreeType(Scope* scope, TypeId ty)
|
|||
LUAU_ASSERT(!"No scopes in parent chain had a present `interiorFreeTypes` member.");
|
||||
}
|
||||
|
||||
void trackInteriorFreeTypePack(Scope* scope, TypePackId tp)
|
||||
{
|
||||
LUAU_ASSERT(tp);
|
||||
if (!FFlag::LuauNonReentrantGeneralization)
|
||||
return;
|
||||
|
||||
for (; scope; scope = scope->parent.get())
|
||||
{
|
||||
if (scope->interiorFreeTypePacks)
|
||||
{
|
||||
scope->interiorFreeTypePacks->push_back(tp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// There should at least be *one* generalization constraint per module
|
||||
// where `interiorFreeTypes` is present, which would be the one made
|
||||
// by ConstraintGenerator::visitModuleRoot.
|
||||
LUAU_ASSERT(!"No scopes in parent chain had a present `interiorFreeTypePacks` member.");
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -24,7 +24,6 @@ const size_t kPageSize = sysconf(_SC_PAGESIZE);
|
|||
#endif
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauFreezeArena)
|
||||
|
|
|
@ -18,9 +18,6 @@
|
|||
#include <optional>
|
||||
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnifyMetatableWithAny)
|
||||
LUAU_FASTFLAG(LuauExtraFollows)
|
||||
LUAU_FASTFLAG(LuauNonReentrantGeneralization)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -238,10 +235,6 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy)
|
|||
auto superMetatable = get<MetatableType>(superTy);
|
||||
if (subMetatable && superMetatable)
|
||||
return unify(subMetatable, superMetatable);
|
||||
else if (FFlag::LuauUnifyMetatableWithAny && subMetatable && superAny)
|
||||
return unify(subMetatable, superAny);
|
||||
else if (FFlag::LuauUnifyMetatableWithAny && subAny && superMetatable)
|
||||
return unify(subAny, superMetatable);
|
||||
else if (subMetatable) // if we only have one metatable, unify with the inner table
|
||||
return unify(subMetatable->table, superTy);
|
||||
else if (superMetatable) // if we only have one metatable, unify with the inner table
|
||||
|
@ -284,7 +277,7 @@ bool Unifier2::unifyFreeWithType(TypeId subTy, TypeId superTy)
|
|||
if (superArgTail)
|
||||
return doDefault();
|
||||
|
||||
const IntersectionType* upperBoundIntersection = get<IntersectionType>(FFlag::LuauExtraFollows ? upperBound : subFree->upperBound);
|
||||
const IntersectionType* upperBoundIntersection = get<IntersectionType>(subFree->upperBound);
|
||||
if (!upperBoundIntersection)
|
||||
return doDefault();
|
||||
|
||||
|
@ -322,23 +315,10 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn)
|
|||
if (shouldInstantiate)
|
||||
{
|
||||
for (auto generic : subFn->generics)
|
||||
{
|
||||
const GenericType* gen = get<GenericType>(generic);
|
||||
LUAU_ASSERT(gen);
|
||||
genericSubstitutions[generic] = freshType(scope, gen->polarity);
|
||||
}
|
||||
genericSubstitutions[generic] = freshType(arena, builtinTypes, scope);
|
||||
|
||||
for (auto genericPack : subFn->genericPacks)
|
||||
{
|
||||
if (FFlag::LuauNonReentrantGeneralization)
|
||||
{
|
||||
const GenericTypePack* gen = get<GenericTypePack>(genericPack);
|
||||
LUAU_ASSERT(gen);
|
||||
genericPackSubstitutions[genericPack] = freshTypePack(scope, gen->polarity);
|
||||
}
|
||||
else
|
||||
genericPackSubstitutions[genericPack] = arena->freshTypePack(scope);
|
||||
}
|
||||
genericPackSubstitutions[genericPack] = arena->freshTypePack(scope);
|
||||
}
|
||||
|
||||
bool argResult = unify(superFn->argTypes, subFn->argTypes);
|
||||
|
@ -447,6 +427,9 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable)
|
|||
superTypePackParamsIter++;
|
||||
}
|
||||
|
||||
if (subTable->selfTy && superTable->selfTy)
|
||||
result &= unify(*subTable->selfTy, *superTable->selfTy);
|
||||
|
||||
if (subTable->indexer && superTable->indexer)
|
||||
{
|
||||
result &= unify(subTable->indexer->indexType, superTable->indexer->indexType);
|
||||
|
@ -541,16 +524,6 @@ bool Unifier2::unify(const TableType* subTable, const AnyType* superAny)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Unifier2::unify(const MetatableType* subMetatable, const AnyType*)
|
||||
{
|
||||
return unify(subMetatable->metatable, builtinTypes->anyType) && unify(subMetatable->table, builtinTypes->anyType);
|
||||
}
|
||||
|
||||
bool Unifier2::unify(const AnyType*, const MetatableType* superMetatable)
|
||||
{
|
||||
return unify(builtinTypes->anyType, superMetatable->metatable) && unify(builtinTypes->anyType, superMetatable->table);
|
||||
}
|
||||
|
||||
// FIXME? This should probably return an ErrorVec or an optional<TypeError>
|
||||
// rather than a boolean to signal an occurs check failure.
|
||||
bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
|
||||
|
@ -661,33 +634,38 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
{
|
||||
}
|
||||
|
||||
Polarity polarity = Polarity::Positive;
|
||||
enum Polarity
|
||||
{
|
||||
Positive,
|
||||
Negative,
|
||||
Both,
|
||||
};
|
||||
|
||||
Polarity polarity = Positive;
|
||||
|
||||
void flip()
|
||||
{
|
||||
switch (polarity)
|
||||
{
|
||||
case Polarity::Positive:
|
||||
polarity = Polarity::Negative;
|
||||
case Positive:
|
||||
polarity = Negative;
|
||||
break;
|
||||
case Polarity::Negative:
|
||||
polarity = Polarity::Positive;
|
||||
case Negative:
|
||||
polarity = Positive;
|
||||
break;
|
||||
case Polarity::Mixed:
|
||||
case Both:
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
DenseHashSet<const void*> seenPositive{nullptr};
|
||||
DenseHashSet<const void*> seenNegative{nullptr};
|
||||
|
||||
bool seenWithCurrentPolarity(const void* ty)
|
||||
bool seenWithPolarity(const void* ty)
|
||||
{
|
||||
switch (polarity)
|
||||
{
|
||||
case Polarity::Positive:
|
||||
case Positive:
|
||||
{
|
||||
if (seenPositive.contains(ty))
|
||||
return true;
|
||||
|
@ -695,7 +673,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
seenPositive.insert(ty);
|
||||
return false;
|
||||
}
|
||||
case Polarity::Negative:
|
||||
case Negative:
|
||||
{
|
||||
if (seenNegative.contains(ty))
|
||||
return true;
|
||||
|
@ -703,7 +681,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
seenNegative.insert(ty);
|
||||
return false;
|
||||
}
|
||||
case Polarity::Mixed:
|
||||
case Both:
|
||||
{
|
||||
if (seenPositive.contains(ty) && seenNegative.contains(ty))
|
||||
return true;
|
||||
|
@ -712,8 +690,6 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
seenNegative.insert(ty);
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -727,7 +703,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
if (seenWithCurrentPolarity(ty))
|
||||
if (seenWithPolarity(ty))
|
||||
return false;
|
||||
|
||||
LUAU_ASSERT(ty);
|
||||
|
@ -736,7 +712,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
bool visit(TypeId ty, const FreeType& ft) override
|
||||
{
|
||||
if (seenWithCurrentPolarity(ty))
|
||||
if (seenWithPolarity(ty))
|
||||
return false;
|
||||
|
||||
if (!subsumes(scope, ft.scope))
|
||||
|
@ -744,18 +720,16 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
switch (polarity)
|
||||
{
|
||||
case Polarity::Positive:
|
||||
case Positive:
|
||||
positiveTypes[ty]++;
|
||||
break;
|
||||
case Polarity::Negative:
|
||||
case Negative:
|
||||
negativeTypes[ty]++;
|
||||
break;
|
||||
case Polarity::Mixed:
|
||||
case Both:
|
||||
positiveTypes[ty]++;
|
||||
negativeTypes[ty]++;
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -763,25 +737,23 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
bool visit(TypeId ty, const TableType& tt) override
|
||||
{
|
||||
if (seenWithCurrentPolarity(ty))
|
||||
if (seenWithPolarity(ty))
|
||||
return false;
|
||||
|
||||
if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope))
|
||||
{
|
||||
switch (polarity)
|
||||
{
|
||||
case Polarity::Positive:
|
||||
case Positive:
|
||||
positiveTypes[ty]++;
|
||||
break;
|
||||
case Polarity::Negative:
|
||||
case Negative:
|
||||
negativeTypes[ty]++;
|
||||
break;
|
||||
case Polarity::Mixed:
|
||||
case Both:
|
||||
positiveTypes[ty]++;
|
||||
negativeTypes[ty]++;
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -794,7 +766,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
LUAU_ASSERT(prop.isShared());
|
||||
|
||||
Polarity p = polarity;
|
||||
polarity = Polarity::Mixed;
|
||||
polarity = Both;
|
||||
traverse(prop.type());
|
||||
polarity = p;
|
||||
}
|
||||
|
@ -811,7 +783,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
bool visit(TypeId ty, const FunctionType& ft) override
|
||||
{
|
||||
if (seenWithCurrentPolarity(ty))
|
||||
if (seenWithPolarity(ty))
|
||||
return false;
|
||||
|
||||
flip();
|
||||
|
@ -830,7 +802,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
bool visit(TypePackId tp, const FreeTypePack& ftp) override
|
||||
{
|
||||
if (seenWithCurrentPolarity(tp))
|
||||
if (seenWithPolarity(tp))
|
||||
return false;
|
||||
|
||||
if (!subsumes(scope, ftp.scope))
|
||||
|
@ -838,18 +810,16 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
switch (polarity)
|
||||
{
|
||||
case Polarity::Positive:
|
||||
case Positive:
|
||||
positiveTypes[tp]++;
|
||||
break;
|
||||
case Polarity::Negative:
|
||||
case Negative:
|
||||
negativeTypes[tp]++;
|
||||
break;
|
||||
case Polarity::Mixed:
|
||||
case Both:
|
||||
positiveTypes[tp]++;
|
||||
negativeTypes[tp]++;
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -955,23 +925,4 @@ OccursCheckResult Unifier2::occursCheck(DenseHashSet<TypePackId>& seen, TypePack
|
|||
return OccursCheckResult::Pass;
|
||||
}
|
||||
|
||||
TypeId Unifier2::freshType(NotNull<Scope> scope, Polarity polarity)
|
||||
{
|
||||
TypeId result = ::Luau::freshType(arena, builtinTypes, scope.get(), polarity);
|
||||
newFreshTypes.emplace_back(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
TypePackId Unifier2::freshTypePack(NotNull<Scope> scope, Polarity polarity)
|
||||
{
|
||||
TypePackId result = arena->freshTypePack(scope.get());
|
||||
|
||||
auto ftp = getMutable<FreeTypePack>(result);
|
||||
LUAU_ASSERT(ftp);
|
||||
ftp->polarity = polarity;
|
||||
|
||||
newFreshTypePacks.emplace_back(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -194,7 +194,6 @@ public:
|
|||
{
|
||||
Checked,
|
||||
Native,
|
||||
Deprecated,
|
||||
};
|
||||
|
||||
AstAttr(const Location& location, Type type);
|
||||
|
@ -454,7 +453,6 @@ public:
|
|||
void visit(AstVisitor* visitor) override;
|
||||
|
||||
bool hasNativeAttribute() const;
|
||||
bool hasAttribute(AstAttr::Type attributeType) const;
|
||||
|
||||
AstArray<AstAttr*> attributes;
|
||||
AstArray<AstGenericType*> generics;
|
||||
|
@ -892,22 +890,14 @@ class AstStatTypeFunction : public AstStat
|
|||
public:
|
||||
LUAU_RTTI(AstStatTypeFunction);
|
||||
|
||||
AstStatTypeFunction(
|
||||
const Location& location,
|
||||
const AstName& name,
|
||||
const Location& nameLocation,
|
||||
AstExprFunction* body,
|
||||
bool exported,
|
||||
bool hasErrors
|
||||
);
|
||||
AstStatTypeFunction(const Location& location, const AstName& name, const Location& nameLocation, AstExprFunction* body, bool exported);
|
||||
|
||||
void visit(AstVisitor* visitor) override;
|
||||
|
||||
AstName name;
|
||||
Location nameLocation;
|
||||
AstExprFunction* body = nullptr;
|
||||
bool exported = false;
|
||||
bool hasErrors = false;
|
||||
AstExprFunction* body;
|
||||
bool exported;
|
||||
};
|
||||
|
||||
class AstStatDeclareGlobal : public AstStat
|
||||
|
@ -960,7 +950,6 @@ public:
|
|||
void visit(AstVisitor* visitor) override;
|
||||
|
||||
bool isCheckedFunction() const;
|
||||
bool hasAttribute(AstAttr::Type attributeType) const;
|
||||
|
||||
AstArray<AstAttr*> attributes;
|
||||
AstName name;
|
||||
|
@ -1117,7 +1106,6 @@ public:
|
|||
void visit(AstVisitor* visitor) override;
|
||||
|
||||
bool isCheckedFunction() const;
|
||||
bool hasAttribute(AstAttr::Type attributeType) const;
|
||||
|
||||
AstArray<AstAttr*> attributes;
|
||||
AstArray<AstGenericType*> generics;
|
||||
|
@ -1139,16 +1127,6 @@ public:
|
|||
AstExpr* expr;
|
||||
};
|
||||
|
||||
class AstTypeOptional : public AstType
|
||||
{
|
||||
public:
|
||||
LUAU_RTTI(AstTypeOptional)
|
||||
|
||||
AstTypeOptional(const Location& location);
|
||||
|
||||
void visit(AstVisitor* visitor) override;
|
||||
};
|
||||
|
||||
class AstTypeUnion : public AstType
|
||||
{
|
||||
public:
|
||||
|
@ -1471,10 +1449,6 @@ public:
|
|||
{
|
||||
return visit(static_cast<AstStat*>(node));
|
||||
}
|
||||
virtual bool visit(class AstStatTypeFunction* node)
|
||||
{
|
||||
return visit(static_cast<AstStat*>(node));
|
||||
}
|
||||
virtual bool visit(class AstStatDeclareFunction* node)
|
||||
{
|
||||
return visit(static_cast<AstStat*>(node));
|
||||
|
@ -1514,10 +1488,6 @@ public:
|
|||
{
|
||||
return visit(static_cast<AstType*>(node));
|
||||
}
|
||||
virtual bool visit(class AstTypeOptional* node)
|
||||
{
|
||||
return visit(static_cast<AstType*>(node));
|
||||
}
|
||||
virtual bool visit(class AstTypeUnion* node)
|
||||
{
|
||||
return visit(static_cast<AstType*>(node));
|
||||
|
|
|
@ -105,21 +105,6 @@ public:
|
|||
Position closeBracketPosition;
|
||||
};
|
||||
|
||||
class CstExprFunction : public CstNode
|
||||
{
|
||||
public:
|
||||
LUAU_CST_RTTI(CstExprFunction)
|
||||
|
||||
CstExprFunction();
|
||||
|
||||
Position functionKeywordPosition{0, 0};
|
||||
Position openGenericsPosition{0, 0};
|
||||
AstArray<Position> genericsCommaPositions;
|
||||
Position closeGenericsPosition{0, 0};
|
||||
AstArray<Position> argsCommaPositions;
|
||||
Position returnSpecifierPosition{0, 0};
|
||||
};
|
||||
|
||||
class CstExprTable : public CstNode
|
||||
{
|
||||
public:
|
||||
|
@ -275,24 +260,13 @@ public:
|
|||
Position opPosition;
|
||||
};
|
||||
|
||||
class CstStatFunction : public CstNode
|
||||
{
|
||||
public:
|
||||
LUAU_CST_RTTI(CstStatFunction)
|
||||
|
||||
explicit CstStatFunction(Position functionKeywordPosition);
|
||||
|
||||
Position functionKeywordPosition;
|
||||
};
|
||||
|
||||
class CstStatLocalFunction : public CstNode
|
||||
{
|
||||
public:
|
||||
LUAU_CST_RTTI(CstStatLocalFunction)
|
||||
|
||||
explicit CstStatLocalFunction(Position localKeywordPosition, Position functionKeywordPosition);
|
||||
explicit CstStatLocalFunction(Position functionKeywordPosition);
|
||||
|
||||
Position localKeywordPosition;
|
||||
Position functionKeywordPosition;
|
||||
};
|
||||
|
||||
|
@ -337,17 +311,6 @@ public:
|
|||
Position equalsPosition;
|
||||
};
|
||||
|
||||
class CstStatTypeFunction : public CstNode
|
||||
{
|
||||
public:
|
||||
LUAU_CST_RTTI(CstStatTypeFunction)
|
||||
|
||||
CstStatTypeFunction(Position typeKeywordPosition, Position functionKeywordPosition);
|
||||
|
||||
Position typeKeywordPosition;
|
||||
Position functionKeywordPosition;
|
||||
};
|
||||
|
||||
class CstTypeReference : public CstNode
|
||||
{
|
||||
public:
|
||||
|
@ -396,32 +359,6 @@ public:
|
|||
bool isArray = false;
|
||||
};
|
||||
|
||||
class CstTypeFunction : public CstNode
|
||||
{
|
||||
public:
|
||||
LUAU_CST_RTTI(CstTypeFunction)
|
||||
|
||||
CstTypeFunction(
|
||||
Position openGenericsPosition,
|
||||
AstArray<Position> genericsCommaPositions,
|
||||
Position closeGenericsPosition,
|
||||
Position openArgsPosition,
|
||||
AstArray<std::optional<Position>> argumentNameColonPositions,
|
||||
AstArray<Position> argumentsCommaPositions,
|
||||
Position closeArgsPosition,
|
||||
Position returnArrowPosition
|
||||
);
|
||||
|
||||
Position openGenericsPosition;
|
||||
AstArray<Position> genericsCommaPositions;
|
||||
Position closeGenericsPosition;
|
||||
Position openArgsPosition;
|
||||
AstArray<std::optional<Position>> argumentNameColonPositions;
|
||||
AstArray<Position> argumentsCommaPositions;
|
||||
Position closeArgsPosition;
|
||||
Position returnArrowPosition;
|
||||
};
|
||||
|
||||
class CstTypeTypeof : public CstNode
|
||||
{
|
||||
public:
|
||||
|
@ -433,28 +370,6 @@ public:
|
|||
Position closePosition;
|
||||
};
|
||||
|
||||
class CstTypeUnion : public CstNode
|
||||
{
|
||||
public:
|
||||
LUAU_CST_RTTI(CstTypeUnion)
|
||||
|
||||
CstTypeUnion(std::optional<Position> leadingPosition, AstArray<Position> separatorPositions);
|
||||
|
||||
std::optional<Position> leadingPosition;
|
||||
AstArray<Position> separatorPositions;
|
||||
};
|
||||
|
||||
class CstTypeIntersection : public CstNode
|
||||
{
|
||||
public:
|
||||
LUAU_CST_RTTI(CstTypeIntersection)
|
||||
|
||||
explicit CstTypeIntersection(std::optional<Position> leadingPosition, AstArray<Position> separatorPositions);
|
||||
|
||||
std::optional<Position> leadingPosition;
|
||||
AstArray<Position> separatorPositions;
|
||||
};
|
||||
|
||||
class CstTypeSingletonString : public CstNode
|
||||
{
|
||||
public:
|
||||
|
@ -467,26 +382,4 @@ public:
|
|||
unsigned int blockDepth;
|
||||
};
|
||||
|
||||
class CstTypePackExplicit : public CstNode
|
||||
{
|
||||
public:
|
||||
LUAU_CST_RTTI(CstTypePackExplicit)
|
||||
|
||||
CstTypePackExplicit(Position openParenthesesPosition, Position closeParenthesesPosition, AstArray<Position> commaPositions);
|
||||
|
||||
Position openParenthesesPosition;
|
||||
Position closeParenthesesPosition;
|
||||
AstArray<Position> commaPositions;
|
||||
};
|
||||
|
||||
class CstTypePackGeneric : public CstNode
|
||||
{
|
||||
public:
|
||||
LUAU_CST_RTTI(CstTypePackGeneric)
|
||||
|
||||
explicit CstTypePackGeneric(Position ellipsisPosition);
|
||||
|
||||
Position ellipsisPosition;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -125,7 +125,7 @@ private:
|
|||
AstStat* parseFor();
|
||||
|
||||
// funcname ::= Name {`.' Name} [`:' Name]
|
||||
AstExpr* parseFunctionName(bool& hasself, AstName& debugname);
|
||||
AstExpr* parseFunctionName(Location start_DEPRECATED, bool& hasself, AstName& debugname);
|
||||
|
||||
// function funcname funcbody
|
||||
LUAU_FORCEINLINE AstStat* parseFunctionStat(const AstArray<AstAttr*>& attributes = {nullptr, 0});
|
||||
|
@ -155,11 +155,9 @@ private:
|
|||
AstStat* parseTypeAlias(const Location& start, bool exported, Position typeKeywordPosition);
|
||||
|
||||
// type function Name ... end
|
||||
AstStat* parseTypeFunction(const Location& start, bool exported, Position typeKeywordPosition);
|
||||
|
||||
AstDeclaredClassProp parseDeclaredClassMethod(const AstArray<AstAttr*>& attributes);
|
||||
AstDeclaredClassProp parseDeclaredClassMethod_DEPRECATED();
|
||||
AstStat* parseTypeFunction(const Location& start, bool exported);
|
||||
|
||||
AstDeclaredClassProp parseDeclaredClassMethod();
|
||||
|
||||
// `declare global' Name: Type |
|
||||
// `declare function' Name`(' [parlist] `)' [`:` Type]
|
||||
|
@ -194,8 +192,7 @@ private:
|
|||
std::tuple<bool, Location, AstTypePack*> parseBindingList(
|
||||
TempVector<Binding>& result,
|
||||
bool allowDot3 = false,
|
||||
AstArray<Position>* commaPositions = nullptr,
|
||||
std::optional<Position> initialCommaPosition = std::nullopt
|
||||
TempVector<Position>* commaPositions = nullptr
|
||||
);
|
||||
|
||||
AstType* parseOptionalType();
|
||||
|
@ -212,14 +209,9 @@ private:
|
|||
// | `(' [TypeList] `)' `->` ReturnType
|
||||
|
||||
// Returns the variadic annotation, if it exists.
|
||||
AstTypePack* parseTypeList(
|
||||
TempVector<AstType*>& result,
|
||||
TempVector<std::optional<AstArgumentName>>& resultNames,
|
||||
TempVector<Position>* commaPositions = nullptr,
|
||||
TempVector<std::optional<Position>>* nameColonPositions = nullptr
|
||||
);
|
||||
AstTypePack* parseTypeList(TempVector<AstType*>& result, TempVector<std::optional<AstArgumentName>>& resultNames);
|
||||
|
||||
std::optional<AstTypeList> parseOptionalReturnType(Position* returnSpecifierPosition = nullptr);
|
||||
std::optional<AstTypeList> parseOptionalReturnType();
|
||||
std::pair<Location, AstTypeList> parseReturnType();
|
||||
|
||||
struct TableIndexerResult
|
||||
|
@ -230,9 +222,9 @@ private:
|
|||
Position colonPosition;
|
||||
};
|
||||
|
||||
TableIndexerResult parseTableIndexer(AstTableAccess access, std::optional<Location> accessLocation, Lexeme begin);
|
||||
// Remove with FFlagLuauStoreCSTData2
|
||||
AstTableIndexer* parseTableIndexer_DEPRECATED(AstTableAccess access, std::optional<Location> accessLocation, Lexeme begin);
|
||||
TableIndexerResult parseTableIndexer(AstTableAccess access, std::optional<Location> accessLocation);
|
||||
// Remove with FFlagLuauStoreCSTData
|
||||
AstTableIndexer* parseTableIndexer_DEPRECATED(AstTableAccess access, std::optional<Location> accessLocation);
|
||||
|
||||
AstTypeOrPack parseFunctionType(bool allowPack, const AstArray<AstAttr*>& attributes);
|
||||
AstType* parseFunctionTypeTail(
|
||||
|
@ -313,7 +305,7 @@ private:
|
|||
std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> parseGenericTypeList(
|
||||
bool withDefaultValues,
|
||||
Position* openPosition = nullptr,
|
||||
AstArray<Position>* commaPositions = nullptr,
|
||||
TempVector<Position>* commaPositions = nullptr,
|
||||
Position* closePosition = nullptr
|
||||
);
|
||||
|
||||
|
@ -499,7 +491,6 @@ private:
|
|||
std::vector<AstGenericTypePack*> scratchGenericTypePacks;
|
||||
std::vector<std::optional<AstArgumentName>> scratchOptArgName;
|
||||
std::vector<Position> scratchPosition;
|
||||
std::vector<std::optional<Position>> scratchOptPosition;
|
||||
std::string scratchData;
|
||||
|
||||
CstNodeMap cstNodeMap;
|
||||
|
|
|
@ -3,24 +3,9 @@
|
|||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauDeprecatedAttribute);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
static bool hasAttributeInArray(const AstArray<AstAttr*> attributes, AstAttr::Type attributeType)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||
|
||||
for (const auto attribute : attributes)
|
||||
{
|
||||
if (attribute->type == attributeType)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void visitTypeList(AstVisitor* visitor, const AstTypeList& list)
|
||||
{
|
||||
for (AstType* ty : list.types)
|
||||
|
@ -292,13 +277,6 @@ bool AstExprFunction::hasNativeAttribute() const
|
|||
return false;
|
||||
}
|
||||
|
||||
bool AstExprFunction::hasAttribute(const AstAttr::Type attributeType) const
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||
|
||||
return hasAttributeInArray(attributes, attributeType);
|
||||
}
|
||||
|
||||
AstExprTable::AstExprTable(const Location& location, const AstArray<Item>& items)
|
||||
: AstExpr(ClassIndex(), location)
|
||||
, items(items)
|
||||
|
@ -813,15 +791,13 @@ AstStatTypeFunction::AstStatTypeFunction(
|
|||
const AstName& name,
|
||||
const Location& nameLocation,
|
||||
AstExprFunction* body,
|
||||
bool exported,
|
||||
bool hasErrors
|
||||
bool exported
|
||||
)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, name(name)
|
||||
, nameLocation(nameLocation)
|
||||
, body(body)
|
||||
, exported(exported)
|
||||
, hasErrors(hasErrors)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -918,13 +894,6 @@ bool AstStatDeclareFunction::isCheckedFunction() const
|
|||
return false;
|
||||
}
|
||||
|
||||
bool AstStatDeclareFunction::hasAttribute(AstAttr::Type attributeType) const
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||
|
||||
return hasAttributeInArray(attributes, attributeType);
|
||||
}
|
||||
|
||||
AstStatDeclareClass::AstStatDeclareClass(
|
||||
const Location& location,
|
||||
const AstName& name,
|
||||
|
@ -1088,13 +1057,6 @@ bool AstTypeFunction::isCheckedFunction() const
|
|||
return false;
|
||||
}
|
||||
|
||||
bool AstTypeFunction::hasAttribute(AstAttr::Type attributeType) const
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||
|
||||
return hasAttributeInArray(attributes, attributeType);
|
||||
}
|
||||
|
||||
AstTypeTypeof::AstTypeTypeof(const Location& location, AstExpr* expr)
|
||||
: AstType(ClassIndex(), location)
|
||||
, expr(expr)
|
||||
|
@ -1107,16 +1069,6 @@ void AstTypeTypeof::visit(AstVisitor* visitor)
|
|||
expr->visit(visitor);
|
||||
}
|
||||
|
||||
AstTypeOptional::AstTypeOptional(const Location& location)
|
||||
: AstType(ClassIndex(), location)
|
||||
{
|
||||
}
|
||||
|
||||
void AstTypeOptional::visit(AstVisitor* visitor)
|
||||
{
|
||||
visitor->visit(this);
|
||||
}
|
||||
|
||||
AstTypeUnion::AstTypeUnion(const Location& location, const AstArray<AstType*>& types)
|
||||
: AstType(ClassIndex(), location)
|
||||
, types(types)
|
||||
|
|
|
@ -38,11 +38,6 @@ CstExprIndexExpr::CstExprIndexExpr(Position openBracketPosition, Position closeB
|
|||
{
|
||||
}
|
||||
|
||||
CstExprFunction::CstExprFunction()
|
||||
: CstNode(CstClassIndex())
|
||||
{
|
||||
}
|
||||
|
||||
CstExprTable::CstExprTable(const AstArray<Item>& items)
|
||||
: CstNode(CstClassIndex())
|
||||
, items(items)
|
||||
|
@ -130,19 +125,12 @@ CstStatCompoundAssign::CstStatCompoundAssign(Position opPosition)
|
|||
{
|
||||
}
|
||||
|
||||
CstStatFunction::CstStatFunction(Position functionKeywordPosition)
|
||||
CstStatLocalFunction::CstStatLocalFunction(Position functionKeywordPosition)
|
||||
: CstNode(CstClassIndex())
|
||||
, functionKeywordPosition(functionKeywordPosition)
|
||||
{
|
||||
}
|
||||
|
||||
CstStatLocalFunction::CstStatLocalFunction(Position localKeywordPosition, Position functionKeywordPosition)
|
||||
: CstNode(CstClassIndex())
|
||||
, localKeywordPosition(localKeywordPosition)
|
||||
, functionKeywordPosition(functionKeywordPosition)
|
||||
{
|
||||
}
|
||||
|
||||
CstGenericType::CstGenericType(std::optional<Position> defaultEqualsPosition)
|
||||
: CstNode(CstClassIndex())
|
||||
, defaultEqualsPosition(defaultEqualsPosition)
|
||||
|
@ -172,13 +160,6 @@ CstStatTypeAlias::CstStatTypeAlias(
|
|||
{
|
||||
}
|
||||
|
||||
CstStatTypeFunction::CstStatTypeFunction(Position typeKeywordPosition, Position functionKeywordPosition)
|
||||
: CstNode(CstClassIndex())
|
||||
, typeKeywordPosition(typeKeywordPosition)
|
||||
, functionKeywordPosition(functionKeywordPosition)
|
||||
{
|
||||
}
|
||||
|
||||
CstTypeReference::CstTypeReference(
|
||||
std::optional<Position> prefixPointPosition,
|
||||
Position openParametersPosition,
|
||||
|
@ -200,28 +181,6 @@ CstTypeTable::CstTypeTable(AstArray<Item> items, bool isArray)
|
|||
{
|
||||
}
|
||||
|
||||
CstTypeFunction::CstTypeFunction(
|
||||
Position openGenericsPosition,
|
||||
AstArray<Position> genericsCommaPositions,
|
||||
Position closeGenericsPosition,
|
||||
Position openArgsPosition,
|
||||
AstArray<std::optional<Position>> argumentNameColonPositions,
|
||||
AstArray<Position> argumentsCommaPositions,
|
||||
Position closeArgsPosition,
|
||||
Position returnArrowPosition
|
||||
)
|
||||
: CstNode(CstClassIndex())
|
||||
, openGenericsPosition(openGenericsPosition)
|
||||
, genericsCommaPositions(genericsCommaPositions)
|
||||
, closeGenericsPosition(closeGenericsPosition)
|
||||
, openArgsPosition(openArgsPosition)
|
||||
, argumentNameColonPositions(argumentNameColonPositions)
|
||||
, argumentsCommaPositions(argumentsCommaPositions)
|
||||
, closeArgsPosition(closeArgsPosition)
|
||||
, returnArrowPosition(returnArrowPosition)
|
||||
{
|
||||
}
|
||||
|
||||
CstTypeTypeof::CstTypeTypeof(Position openPosition, Position closePosition)
|
||||
: CstNode(CstClassIndex())
|
||||
, openPosition(openPosition)
|
||||
|
@ -229,20 +188,6 @@ CstTypeTypeof::CstTypeTypeof(Position openPosition, Position closePosition)
|
|||
{
|
||||
}
|
||||
|
||||
CstTypeUnion::CstTypeUnion(std::optional<Position> leadingPosition, AstArray<Position> separatorPositions)
|
||||
: CstNode(CstClassIndex())
|
||||
, leadingPosition(leadingPosition)
|
||||
, separatorPositions(separatorPositions)
|
||||
{
|
||||
}
|
||||
|
||||
CstTypeIntersection::CstTypeIntersection(std::optional<Position> leadingPosition, AstArray<Position> separatorPositions)
|
||||
: CstNode(CstClassIndex())
|
||||
, leadingPosition(leadingPosition)
|
||||
, separatorPositions(separatorPositions)
|
||||
{
|
||||
}
|
||||
|
||||
CstTypeSingletonString::CstTypeSingletonString(AstArray<char> sourceString, CstExprConstantString::QuoteStyle quoteStyle, unsigned int blockDepth)
|
||||
: CstNode(CstClassIndex())
|
||||
, sourceString(sourceString)
|
||||
|
@ -252,18 +197,4 @@ CstTypeSingletonString::CstTypeSingletonString(AstArray<char> sourceString, CstE
|
|||
LUAU_ASSERT(quoteStyle != CstExprConstantString::QuotedInterp);
|
||||
}
|
||||
|
||||
CstTypePackExplicit::CstTypePackExplicit(Position openParenthesesPosition, Position closeParenthesesPosition, AstArray<Position> commaPositions)
|
||||
: CstNode(CstClassIndex())
|
||||
, openParenthesesPosition(openParenthesesPosition)
|
||||
, closeParenthesesPosition(closeParenthesesPosition)
|
||||
, commaPositions(commaPositions)
|
||||
{
|
||||
}
|
||||
|
||||
CstTypePackGeneric::CstTypePackGeneric(Position ellipsisPosition)
|
||||
: CstNode(CstClassIndex())
|
||||
, ellipsisPosition(ellipsisPosition)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
|
||||
#include <limits.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LexerResumesFromPosition2)
|
||||
LUAU_FASTFLAGVARIABLE(LexerFixInterpStringStart)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -339,9 +342,12 @@ Lexer::Lexer(const char* buffer, size_t bufferSize, AstNameTable& names, Positio
|
|||
: buffer(buffer)
|
||||
, bufferSize(bufferSize)
|
||||
, offset(0)
|
||||
, line(startPosition.line)
|
||||
, lineOffset(0u - startPosition.column)
|
||||
, lexeme((Location(Position(startPosition.line, startPosition.column), 0)), Lexeme::Eof)
|
||||
, line(FFlag::LexerResumesFromPosition2 ? startPosition.line : 0)
|
||||
, lineOffset(FFlag::LexerResumesFromPosition2 ? 0u - startPosition.column : 0)
|
||||
, lexeme(
|
||||
(FFlag::LexerResumesFromPosition2 ? Location(Position(startPosition.line, startPosition.column), 0) : Location(Position(0, 0), 0)),
|
||||
Lexeme::Eof
|
||||
)
|
||||
, names(names)
|
||||
, skipComments(false)
|
||||
, readNames(true)
|
||||
|
@ -787,7 +793,7 @@ Lexeme Lexer::readNext()
|
|||
return Lexeme(Location(start, 1), '}');
|
||||
}
|
||||
|
||||
return readInterpolatedStringSection(start, Lexeme::InterpStringMid, Lexeme::InterpStringEnd);
|
||||
return readInterpolatedStringSection(FFlag::LexerFixInterpStringStart ? start : position(), Lexeme::InterpStringMid, Lexeme::InterpStringEnd);
|
||||
}
|
||||
|
||||
case '=':
|
||||
|
|
1130
Ast/src/Parser.cpp
1130
Ast/src/Parser.cpp
File diff suppressed because it is too large
Load diff
|
@ -1,35 +0,0 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/RequireNavigator.h"
|
||||
#include "Luau/RequirerUtils.h"
|
||||
|
||||
struct FileNavigationContext : Luau::Require::NavigationContext
|
||||
{
|
||||
using NavigateResult = Luau::Require::NavigationContext::NavigateResult;
|
||||
|
||||
FileNavigationContext(std::string requirerPath);
|
||||
|
||||
std::string getRequirerIdentifier() const override;
|
||||
|
||||
// Navigation interface
|
||||
NavigateResult reset(const std::string& requirerChunkname) override;
|
||||
NavigateResult jumpToAlias(const std::string& path) override;
|
||||
|
||||
NavigateResult toParent() override;
|
||||
NavigateResult toChild(const std::string& component) override;
|
||||
|
||||
bool isConfigPresent() const override;
|
||||
std::optional<std::string> getConfig() const override;
|
||||
|
||||
// Custom capabilities
|
||||
bool isModulePresent() const;
|
||||
std::optional<std::string> getIdentifier() const;
|
||||
|
||||
std::string path;
|
||||
std::string suffix;
|
||||
std::string requirerPath;
|
||||
|
||||
private:
|
||||
NavigateResult storePathResult(PathResult result);
|
||||
};
|
|
@ -1,32 +0,0 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Require.h"
|
||||
|
||||
#include "Luau/Compiler.h"
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
void requireConfigInit(luarequire_Configuration* config);
|
||||
|
||||
struct ReplRequirer
|
||||
{
|
||||
ReplRequirer(
|
||||
std::function<Luau::CompileOptions()> copts,
|
||||
std::function<bool()> coverageActive,
|
||||
std::function<bool()> codegenEnabled,
|
||||
std::function<void(lua_State*, int)> coverageTrack
|
||||
);
|
||||
|
||||
std::function<Luau::CompileOptions()> copts;
|
||||
std::function<bool()> coverageActive;
|
||||
std::function<bool()> codegenEnabled;
|
||||
std::function<void(lua_State*, int)> coverageTrack;
|
||||
|
||||
std::string absPath;
|
||||
std::string relPath;
|
||||
std::string suffix;
|
||||
};
|
84
CLI/include/Luau/Require.h
Normal file
84
CLI/include/Luau/Require.h
Normal file
|
@ -0,0 +1,84 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Config.h"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
class RequireResolver
|
||||
{
|
||||
public:
|
||||
enum class ModuleStatus
|
||||
{
|
||||
Cached,
|
||||
FileRead,
|
||||
ErrorReported
|
||||
};
|
||||
|
||||
struct ResolvedRequire
|
||||
{
|
||||
ModuleStatus status;
|
||||
std::string identifier;
|
||||
std::string absolutePath;
|
||||
std::string sourceCode;
|
||||
};
|
||||
|
||||
struct RequireContext
|
||||
{
|
||||
virtual ~RequireContext() = default;
|
||||
virtual std::string getPath() = 0;
|
||||
virtual bool isRequireAllowed() = 0;
|
||||
virtual bool isStdin() = 0;
|
||||
virtual std::string createNewIdentifer(const std::string& path) = 0;
|
||||
};
|
||||
|
||||
struct CacheManager
|
||||
{
|
||||
virtual ~CacheManager() = default;
|
||||
virtual bool isCached(const std::string& path)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct ErrorHandler
|
||||
{
|
||||
virtual ~ErrorHandler() = default;
|
||||
virtual void reportError(const std::string message) {}
|
||||
};
|
||||
|
||||
RequireResolver(std::string pathToResolve, RequireContext& requireContext, CacheManager& cacheManager, ErrorHandler& errorHandler);
|
||||
|
||||
[[nodiscard]] ResolvedRequire resolveRequire(std::function<void(const ModuleStatus)> completionCallback = nullptr);
|
||||
|
||||
private:
|
||||
std::string pathToResolve;
|
||||
|
||||
RequireContext& requireContext;
|
||||
CacheManager& cacheManager;
|
||||
ErrorHandler& errorHandler;
|
||||
|
||||
ResolvedRequire resolvedRequire;
|
||||
bool isRequireResolved = false;
|
||||
|
||||
Luau::Config config;
|
||||
std::string lastSearchedDir;
|
||||
bool isConfigFullyResolved = false;
|
||||
|
||||
[[nodiscard]] bool initialize();
|
||||
|
||||
ModuleStatus findModule();
|
||||
ModuleStatus findModuleImpl();
|
||||
|
||||
[[nodiscard]] bool resolveAndStoreDefaultPaths();
|
||||
std::optional<std::string> getRequiringContextAbsolute();
|
||||
std::string getRequiringContextRelative();
|
||||
|
||||
[[nodiscard]] bool substituteAliasIfPresent(std::string& path);
|
||||
std::optional<std::string> getAlias(std::string alias);
|
||||
|
||||
[[nodiscard]] bool parseNextConfig();
|
||||
[[nodiscard]] bool parseConfigInDirectory(const std::string& directory);
|
||||
};
|
|
@ -1,36 +0,0 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
struct PathResult
|
||||
{
|
||||
enum class Status
|
||||
{
|
||||
SUCCESS,
|
||||
AMBIGUOUS,
|
||||
NOT_FOUND
|
||||
};
|
||||
|
||||
Status status;
|
||||
std::string absPath;
|
||||
std::string relPath;
|
||||
std::string suffix;
|
||||
};
|
||||
|
||||
PathResult getStdInResult();
|
||||
|
||||
PathResult getAbsolutePathResult(const std::string& path);
|
||||
|
||||
// If given an absolute path, this will implicitly call getAbsolutePathResult.
|
||||
// Aliases prevent us from solely operating on relative paths, so we need to
|
||||
// be able to fall back to operating on absolute paths if needed.
|
||||
PathResult tryGetRelativePathResult(const std::string& path);
|
||||
|
||||
PathResult getParent(const std::string& absPath, const std::string& relPath);
|
||||
PathResult getChild(const std::string& absPath, const std::string& relPath, const std::string& name);
|
||||
|
||||
bool isFilePresent(const std::string& path, const std::string& suffix);
|
||||
std::optional<std::string> getFileContents(const std::string& path, const std::string& suffix);
|
|
@ -7,10 +7,9 @@
|
|||
#include "Luau/TypeAttach.h"
|
||||
#include "Luau/Transpiler.h"
|
||||
|
||||
#include "Luau/AnalyzeRequirer.h"
|
||||
#include "Luau/FileUtils.h"
|
||||
#include "Luau/Flags.h"
|
||||
#include "Luau/RequireNavigator.h"
|
||||
#include "Luau/Require.h"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
|
@ -174,18 +173,15 @@ struct CliFileResolver : Luau::FileResolver
|
|||
{
|
||||
std::string path{expr->value.data, expr->value.size};
|
||||
|
||||
FileNavigationContext navigationContext{context->name};
|
||||
Luau::Require::ErrorHandler nullErrorHandler{};
|
||||
AnalysisRequireContext requireContext{context->name};
|
||||
AnalysisCacheManager cacheManager;
|
||||
AnalysisErrorHandler errorHandler;
|
||||
|
||||
Luau::Require::Navigator navigator(navigationContext, nullErrorHandler);
|
||||
if (navigator.navigate(path) != Luau::Require::Navigator::Status::Success)
|
||||
return std::nullopt;
|
||||
RequireResolver resolver(path, requireContext, cacheManager, errorHandler);
|
||||
RequireResolver::ResolvedRequire resolvedRequire = resolver.resolveRequire();
|
||||
|
||||
if (!navigationContext.isModulePresent())
|
||||
return std::nullopt;
|
||||
|
||||
if (std::optional<std::string> identifier = navigationContext.getIdentifier())
|
||||
return {{*identifier}};
|
||||
if (resolvedRequire.status == RequireResolver::ModuleStatus::FileRead)
|
||||
return {{resolvedRequire.identifier}};
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
|
@ -197,6 +193,48 @@ struct CliFileResolver : Luau::FileResolver
|
|||
return "stdin";
|
||||
return name;
|
||||
}
|
||||
|
||||
private:
|
||||
struct AnalysisRequireContext : RequireResolver::RequireContext
|
||||
{
|
||||
explicit AnalysisRequireContext(std::string path)
|
||||
: path(std::move(path))
|
||||
{
|
||||
}
|
||||
|
||||
std::string getPath() override
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
bool isRequireAllowed() override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isStdin() override
|
||||
{
|
||||
return path == "-";
|
||||
}
|
||||
|
||||
std::string createNewIdentifer(const std::string& path) override
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string path;
|
||||
};
|
||||
|
||||
struct AnalysisCacheManager : public RequireResolver::CacheManager
|
||||
{
|
||||
AnalysisCacheManager() = default;
|
||||
};
|
||||
|
||||
struct AnalysisErrorHandler : RequireResolver::ErrorHandler
|
||||
{
|
||||
AnalysisErrorHandler() = default;
|
||||
};
|
||||
};
|
||||
|
||||
struct CliConfigResolver : Luau::ConfigResolver
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/AnalyzeRequirer.h"
|
||||
|
||||
#include "Luau/RequireNavigator.h"
|
||||
#include "Luau/RequirerUtils.h"
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
Luau::Require::NavigationContext::NavigateResult FileNavigationContext::storePathResult(PathResult result)
|
||||
{
|
||||
if (result.status == PathResult::Status::AMBIGUOUS)
|
||||
return Luau::Require::NavigationContext::NavigateResult::Ambiguous;
|
||||
|
||||
if (result.status == PathResult::Status::NOT_FOUND)
|
||||
return Luau::Require::NavigationContext::NavigateResult::NotFound;
|
||||
|
||||
path = result.absPath;
|
||||
suffix = result.suffix;
|
||||
|
||||
return Luau::Require::NavigationContext::NavigateResult::Success;
|
||||
}
|
||||
|
||||
FileNavigationContext::FileNavigationContext(std::string requirerPath)
|
||||
{
|
||||
std::string_view path = requirerPath;
|
||||
if (path.size() >= 10 && path.substr(path.size() - 10) == "/init.luau")
|
||||
{
|
||||
path.remove_suffix(10);
|
||||
}
|
||||
else if (path.size() >= 9 && path.substr(path.size() - 9) == "/init.lua")
|
||||
{
|
||||
path.remove_suffix(9);
|
||||
}
|
||||
else if (path.size() >= 5 && path.substr(path.size() - 5) == ".luau")
|
||||
{
|
||||
path.remove_suffix(5);
|
||||
}
|
||||
else if (path.size() >= 4 && path.substr(path.size() - 4) == ".lua")
|
||||
{
|
||||
path.remove_suffix(4);
|
||||
}
|
||||
|
||||
this->requirerPath = path;
|
||||
}
|
||||
|
||||
std::string FileNavigationContext::getRequirerIdentifier() const
|
||||
{
|
||||
return requirerPath;
|
||||
}
|
||||
|
||||
Luau::Require::NavigationContext::NavigateResult FileNavigationContext::reset(const std::string& requirerChunkname)
|
||||
{
|
||||
if (requirerChunkname == "-")
|
||||
{
|
||||
return storePathResult(getStdInResult());
|
||||
}
|
||||
|
||||
return storePathResult(tryGetRelativePathResult(requirerChunkname));
|
||||
}
|
||||
|
||||
Luau::Require::NavigationContext::NavigateResult FileNavigationContext::jumpToAlias(const std::string& path)
|
||||
{
|
||||
Luau::Require::NavigationContext::NavigateResult result = storePathResult(getAbsolutePathResult(path));
|
||||
if (result != Luau::Require::NavigationContext::NavigateResult::Success)
|
||||
return result;
|
||||
|
||||
return Luau::Require::NavigationContext::NavigateResult::Success;
|
||||
}
|
||||
|
||||
Luau::Require::NavigationContext::NavigateResult FileNavigationContext::toParent()
|
||||
{
|
||||
return storePathResult(getParent(path, path));
|
||||
}
|
||||
|
||||
Luau::Require::NavigationContext::NavigateResult FileNavigationContext::toChild(const std::string& component)
|
||||
{
|
||||
return storePathResult(getChild(path, path, component));
|
||||
}
|
||||
|
||||
bool FileNavigationContext::isModulePresent() const
|
||||
{
|
||||
return isFilePresent(path, suffix);
|
||||
}
|
||||
|
||||
std::optional<std::string> FileNavigationContext::getIdentifier() const
|
||||
{
|
||||
return path + suffix;
|
||||
}
|
||||
|
||||
bool FileNavigationContext::isConfigPresent() const
|
||||
{
|
||||
return isFilePresent(path, "/.luaurc");
|
||||
}
|
||||
|
||||
std::optional<std::string> FileNavigationContext::getConfig() const
|
||||
{
|
||||
return getFileContents(path, "/.luaurc");
|
||||
}
|
|
@ -31,7 +31,7 @@ static void setLuauFlags(bool state)
|
|||
void setLuauFlagsDefault()
|
||||
{
|
||||
for (Luau::FValue<bool>* flag = Luau::FValue<bool>::list; flag; flag = flag->next)
|
||||
if (strncmp(flag->name, "Luau", 4) == 0 && !Luau::isAnalysisFlagExperimental(flag->name))
|
||||
if (strncmp(flag->name, "Luau", 4) == 0 && !Luau::isFlagExperimental(flag->name))
|
||||
flag->value = true;
|
||||
}
|
||||
|
||||
|
|
212
CLI/src/Repl.cpp
212
CLI/src/Repl.cpp
|
@ -14,7 +14,6 @@
|
|||
#include "Luau/FileUtils.h"
|
||||
#include "Luau/Flags.h"
|
||||
#include "Luau/Profiler.h"
|
||||
#include "Luau/ReplRequirer.h"
|
||||
#include "Luau/Require.h"
|
||||
|
||||
#include "isocline.h"
|
||||
|
@ -114,6 +113,172 @@ static int lua_loadstring(lua_State* L)
|
|||
return 2; // return nil plus error message
|
||||
}
|
||||
|
||||
static int finishrequire(lua_State* L)
|
||||
{
|
||||
if (lua_isstring(L, -1))
|
||||
lua_error(L);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct RuntimeRequireContext : public RequireResolver::RequireContext
|
||||
{
|
||||
// In the context of the REPL, source is the calling context's chunkname.
|
||||
//
|
||||
// These chunknames have certain prefixes that indicate context. These
|
||||
// are used when displaying debug information (see luaO_chunkid).
|
||||
//
|
||||
// Generally, the '@' prefix is used for filepaths, and the '=' prefix is
|
||||
// used for custom chunknames, such as =stdin.
|
||||
explicit RuntimeRequireContext(std::string source)
|
||||
: source(std::move(source))
|
||||
{
|
||||
}
|
||||
|
||||
std::string getPath() override
|
||||
{
|
||||
return source.substr(1);
|
||||
}
|
||||
|
||||
bool isRequireAllowed() override
|
||||
{
|
||||
return isStdin() || (!source.empty() && source[0] == '@');
|
||||
}
|
||||
|
||||
bool isStdin() override
|
||||
{
|
||||
return source == "=stdin";
|
||||
}
|
||||
|
||||
std::string createNewIdentifer(const std::string& path) override
|
||||
{
|
||||
return "@" + path;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string source;
|
||||
};
|
||||
|
||||
struct RuntimeCacheManager : public RequireResolver::CacheManager
|
||||
{
|
||||
explicit RuntimeCacheManager(lua_State* L)
|
||||
: L(L)
|
||||
{
|
||||
}
|
||||
|
||||
bool isCached(const std::string& path) override
|
||||
{
|
||||
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
|
||||
lua_getfield(L, -1, path.c_str());
|
||||
bool cached = !lua_isnil(L, -1);
|
||||
lua_pop(L, 2);
|
||||
|
||||
if (cached)
|
||||
cacheKey = path;
|
||||
|
||||
return cached;
|
||||
}
|
||||
|
||||
std::string cacheKey;
|
||||
|
||||
private:
|
||||
lua_State* L;
|
||||
};
|
||||
|
||||
struct RuntimeErrorHandler : RequireResolver::ErrorHandler
|
||||
{
|
||||
explicit RuntimeErrorHandler(lua_State* L)
|
||||
: L(L)
|
||||
{
|
||||
}
|
||||
|
||||
void reportError(const std::string message) override
|
||||
{
|
||||
luaL_errorL(L, "%s", message.c_str());
|
||||
}
|
||||
|
||||
private:
|
||||
lua_State* L;
|
||||
};
|
||||
|
||||
static int lua_require(lua_State* L)
|
||||
{
|
||||
std::string name = luaL_checkstring(L, 1);
|
||||
|
||||
RequireResolver::ResolvedRequire resolvedRequire;
|
||||
{
|
||||
lua_Debug ar;
|
||||
lua_getinfo(L, 1, "s", &ar);
|
||||
|
||||
RuntimeRequireContext requireContext{ar.source};
|
||||
RuntimeCacheManager cacheManager{L};
|
||||
RuntimeErrorHandler errorHandler{L};
|
||||
|
||||
RequireResolver resolver(std::move(name), requireContext, cacheManager, errorHandler);
|
||||
|
||||
resolvedRequire = resolver.resolveRequire(
|
||||
[L, &cacheKey = cacheManager.cacheKey](const RequireResolver::ModuleStatus status)
|
||||
{
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, "_MODULES");
|
||||
if (status == RequireResolver::ModuleStatus::Cached)
|
||||
lua_getfield(L, -1, cacheKey.c_str());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (resolvedRequire.status == RequireResolver::ModuleStatus::Cached)
|
||||
return finishrequire(L);
|
||||
|
||||
// module needs to run in a new thread, isolated from the rest
|
||||
// note: we create ML on main thread so that it doesn't inherit environment of L
|
||||
lua_State* GL = lua_mainthread(L);
|
||||
lua_State* ML = lua_newthread(GL);
|
||||
lua_xmove(GL, L, 1);
|
||||
|
||||
// new thread needs to have the globals sandboxed
|
||||
luaL_sandboxthread(ML);
|
||||
|
||||
// now we can compile & run module on the new thread
|
||||
std::string bytecode = Luau::compile(resolvedRequire.sourceCode, copts());
|
||||
if (luau_load(ML, resolvedRequire.identifier.c_str(), bytecode.data(), bytecode.size(), 0) == 0)
|
||||
{
|
||||
if (codegen)
|
||||
{
|
||||
Luau::CodeGen::CompilationOptions nativeOptions;
|
||||
Luau::CodeGen::compile(ML, -1, nativeOptions);
|
||||
}
|
||||
|
||||
if (coverageActive())
|
||||
coverageTrack(ML, -1);
|
||||
|
||||
int status = lua_resume(ML, L, 0);
|
||||
|
||||
if (status == 0)
|
||||
{
|
||||
if (lua_gettop(ML) == 0)
|
||||
lua_pushstring(ML, "module must return a value");
|
||||
else if (!lua_istable(ML, -1) && !lua_isfunction(ML, -1))
|
||||
lua_pushstring(ML, "module must return a table or function");
|
||||
}
|
||||
else if (status == LUA_YIELD)
|
||||
{
|
||||
lua_pushstring(ML, "module can not yield");
|
||||
}
|
||||
else if (!lua_isstring(ML, -1))
|
||||
{
|
||||
lua_pushstring(ML, "unknown error while running module");
|
||||
}
|
||||
}
|
||||
|
||||
// there's now a return value on top of ML; L stack: _MODULES ML
|
||||
lua_xmove(ML, L, 1);
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setfield(L, -4, resolvedRequire.absolutePath.c_str());
|
||||
|
||||
// L stack: _MODULES ML result
|
||||
return finishrequire(L);
|
||||
}
|
||||
|
||||
static int lua_collectgarbage(lua_State* L)
|
||||
{
|
||||
const char* option = luaL_optstring(L, 1, "collect");
|
||||
|
@ -164,39 +329,6 @@ static int lua_callgrind(lua_State* L)
|
|||
}
|
||||
#endif
|
||||
|
||||
static void* createCliRequireContext(lua_State* L)
|
||||
{
|
||||
void* ctx = lua_newuserdatadtor(
|
||||
L,
|
||||
sizeof(ReplRequirer),
|
||||
[](void* ptr)
|
||||
{
|
||||
static_cast<ReplRequirer*>(ptr)->~ReplRequirer();
|
||||
}
|
||||
);
|
||||
|
||||
if (!ctx)
|
||||
luaL_error(L, "unable to allocate ReplRequirer");
|
||||
|
||||
ctx = new (ctx) ReplRequirer{
|
||||
copts,
|
||||
coverageActive,
|
||||
[]()
|
||||
{
|
||||
return codegen;
|
||||
},
|
||||
coverageTrack,
|
||||
};
|
||||
|
||||
// Store ReplRequirer in the registry to keep it alive for the lifetime of
|
||||
// this lua_State. Memory address is used as a key to avoid collisions.
|
||||
lua_pushlightuserdata(L, ctx);
|
||||
lua_insert(L, -2);
|
||||
lua_settable(L, LUA_REGISTRYINDEX);
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void setupState(lua_State* L)
|
||||
{
|
||||
if (codegen)
|
||||
|
@ -206,6 +338,7 @@ void setupState(lua_State* L)
|
|||
|
||||
static const luaL_Reg funcs[] = {
|
||||
{"loadstring", lua_loadstring},
|
||||
{"require", lua_require},
|
||||
{"collectgarbage", lua_collectgarbage},
|
||||
#ifdef CALLGRIND
|
||||
{"callgrind", lua_callgrind},
|
||||
|
@ -217,8 +350,6 @@ void setupState(lua_State* L)
|
|||
luaL_register(L, NULL, funcs);
|
||||
lua_pop(L, 1);
|
||||
|
||||
luaopen_require(L, requireConfigInit, createCliRequireContext(L));
|
||||
|
||||
luaL_sandbox(L);
|
||||
}
|
||||
|
||||
|
@ -581,14 +712,7 @@ static bool runFile(const char* name, lua_State* GL, bool repl)
|
|||
// new thread needs to have the globals sandboxed
|
||||
luaL_sandboxthread(L);
|
||||
|
||||
// ignore file extension when storing module's chunkname
|
||||
std::string chunkname = "@";
|
||||
std::string_view nameView = name;
|
||||
if (size_t dotPos = nameView.find_last_of('.'); dotPos != std::string_view::npos)
|
||||
{
|
||||
nameView.remove_suffix(nameView.size() - dotPos);
|
||||
}
|
||||
chunkname += nameView;
|
||||
std::string chunkname = "@" + std::string(name);
|
||||
|
||||
std::string bytecode = Luau::compile(*source, copts());
|
||||
int status = 0;
|
||||
|
|
|
@ -1,221 +0,0 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/ReplRequirer.h"
|
||||
|
||||
#include "Luau/CodeGen.h"
|
||||
#include "Luau/CodeGenOptions.h"
|
||||
#include "Luau/Require.h"
|
||||
|
||||
#include "Luau/RequirerUtils.h"
|
||||
#include "lua.h"
|
||||
#include "lualib.h"
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
static luarequire_WriteResult write(std::optional<std::string> contents, char* buffer, size_t bufferSize, size_t* sizeOut)
|
||||
{
|
||||
if (!contents)
|
||||
return luarequire_WriteResult::WRITE_FAILURE;
|
||||
|
||||
size_t nullTerminatedSize = contents->size() + 1;
|
||||
|
||||
if (bufferSize < nullTerminatedSize)
|
||||
{
|
||||
*sizeOut = nullTerminatedSize;
|
||||
return luarequire_WriteResult::WRITE_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
*sizeOut = nullTerminatedSize;
|
||||
memcpy(buffer, contents->c_str(), nullTerminatedSize);
|
||||
return luarequire_WriteResult::WRITE_SUCCESS;
|
||||
}
|
||||
|
||||
static luarequire_NavigateResult storePathResult(ReplRequirer* req, PathResult result)
|
||||
{
|
||||
if (result.status == PathResult::Status::AMBIGUOUS)
|
||||
return NAVIGATE_AMBIGUOUS;
|
||||
|
||||
if (result.status == PathResult::Status::NOT_FOUND)
|
||||
return NAVIGATE_NOT_FOUND;
|
||||
|
||||
req->absPath = result.absPath;
|
||||
req->relPath = result.relPath;
|
||||
req->suffix = result.suffix;
|
||||
|
||||
return NAVIGATE_SUCCESS;
|
||||
}
|
||||
|
||||
static bool is_require_allowed(lua_State* L, void* ctx, const char* requirer_chunkname)
|
||||
{
|
||||
std::string_view chunkname = requirer_chunkname;
|
||||
return chunkname == "=stdin" || (!chunkname.empty() && chunkname[0] == '@');
|
||||
}
|
||||
|
||||
static luarequire_NavigateResult reset(lua_State* L, void* ctx, const char* requirer_chunkname)
|
||||
{
|
||||
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
|
||||
|
||||
std::string chunkname = requirer_chunkname;
|
||||
if (chunkname == "=stdin")
|
||||
{
|
||||
return storePathResult(req, getStdInResult());
|
||||
}
|
||||
else if (!chunkname.empty() && chunkname[0] == '@')
|
||||
{
|
||||
return storePathResult(req, tryGetRelativePathResult(chunkname.substr(1)));
|
||||
}
|
||||
|
||||
return NAVIGATE_NOT_FOUND;
|
||||
}
|
||||
|
||||
static luarequire_NavigateResult jump_to_alias(lua_State* L, void* ctx, const char* path)
|
||||
{
|
||||
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
|
||||
|
||||
luarequire_NavigateResult result = storePathResult(req, getAbsolutePathResult(path));
|
||||
if (result != NAVIGATE_SUCCESS)
|
||||
return result;
|
||||
|
||||
// Jumping to an absolute path breaks the relative-require chain. The best
|
||||
// we can do is to store the absolute path itself.
|
||||
req->relPath = req->absPath;
|
||||
return NAVIGATE_SUCCESS;
|
||||
}
|
||||
|
||||
static luarequire_NavigateResult to_parent(lua_State* L, void* ctx)
|
||||
{
|
||||
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
|
||||
return storePathResult(req, getParent(req->absPath, req->relPath));
|
||||
}
|
||||
|
||||
static luarequire_NavigateResult to_child(lua_State* L, void* ctx, const char* name)
|
||||
{
|
||||
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
|
||||
return storePathResult(req, getChild(req->absPath, req->relPath, name));
|
||||
}
|
||||
|
||||
static bool is_module_present(lua_State* L, void* ctx)
|
||||
{
|
||||
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
|
||||
return isFilePresent(req->absPath, req->suffix);
|
||||
}
|
||||
|
||||
static luarequire_WriteResult get_contents(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out)
|
||||
{
|
||||
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
|
||||
return write(getFileContents(req->absPath, req->suffix), buffer, buffer_size, size_out);
|
||||
}
|
||||
|
||||
static luarequire_WriteResult get_chunkname(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out)
|
||||
{
|
||||
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
|
||||
return write("@" + req->relPath, buffer, buffer_size, size_out);
|
||||
}
|
||||
|
||||
static luarequire_WriteResult get_cache_key(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out)
|
||||
{
|
||||
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
|
||||
return write(req->absPath + req->suffix, buffer, buffer_size, size_out);
|
||||
}
|
||||
|
||||
static bool is_config_present(lua_State* L, void* ctx)
|
||||
{
|
||||
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
|
||||
return isFilePresent(req->absPath, "/.luaurc");
|
||||
}
|
||||
|
||||
static luarequire_WriteResult get_config(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out)
|
||||
{
|
||||
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
|
||||
return write(getFileContents(req->absPath, "/.luaurc"), buffer, buffer_size, size_out);
|
||||
}
|
||||
|
||||
static int load(lua_State* L, void* ctx, const char* chunkname, const char* contents)
|
||||
{
|
||||
ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
|
||||
|
||||
// module needs to run in a new thread, isolated from the rest
|
||||
// note: we create ML on main thread so that it doesn't inherit environment of L
|
||||
lua_State* GL = lua_mainthread(L);
|
||||
lua_State* ML = lua_newthread(GL);
|
||||
lua_xmove(GL, L, 1);
|
||||
|
||||
// new thread needs to have the globals sandboxed
|
||||
luaL_sandboxthread(ML);
|
||||
|
||||
// now we can compile & run module on the new thread
|
||||
std::string bytecode = Luau::compile(contents, req->copts());
|
||||
if (luau_load(ML, chunkname, bytecode.data(), bytecode.size(), 0) == 0)
|
||||
{
|
||||
if (req->codegenEnabled())
|
||||
{
|
||||
Luau::CodeGen::CompilationOptions nativeOptions;
|
||||
Luau::CodeGen::compile(ML, -1, nativeOptions);
|
||||
}
|
||||
|
||||
if (req->coverageActive())
|
||||
req->coverageTrack(ML, -1);
|
||||
|
||||
int status = lua_resume(ML, L, 0);
|
||||
|
||||
if (status == 0)
|
||||
{
|
||||
if (lua_gettop(ML) == 0)
|
||||
lua_pushstring(ML, "module must return a value");
|
||||
else if (!lua_istable(ML, -1) && !lua_isfunction(ML, -1))
|
||||
lua_pushstring(ML, "module must return a table or function");
|
||||
}
|
||||
else if (status == LUA_YIELD)
|
||||
{
|
||||
lua_pushstring(ML, "module can not yield");
|
||||
}
|
||||
else if (!lua_isstring(ML, -1))
|
||||
{
|
||||
lua_pushstring(ML, "unknown error while running module");
|
||||
}
|
||||
}
|
||||
|
||||
// add ML result to L stack
|
||||
lua_xmove(ML, L, 1);
|
||||
if (lua_isstring(L, -1))
|
||||
lua_error(L);
|
||||
|
||||
// remove ML thread from L stack
|
||||
lua_remove(L, -2);
|
||||
|
||||
// added one value to L stack: module result
|
||||
return 1;
|
||||
}
|
||||
|
||||
void requireConfigInit(luarequire_Configuration* config)
|
||||
{
|
||||
if (config == nullptr)
|
||||
return;
|
||||
|
||||
config->is_require_allowed = is_require_allowed;
|
||||
config->reset = reset;
|
||||
config->jump_to_alias = jump_to_alias;
|
||||
config->to_parent = to_parent;
|
||||
config->to_child = to_child;
|
||||
config->is_module_present = is_module_present;
|
||||
config->get_contents = get_contents;
|
||||
config->is_config_present = is_config_present;
|
||||
config->get_chunkname = get_chunkname;
|
||||
config->get_cache_key = get_cache_key;
|
||||
config->get_config = get_config;
|
||||
config->load = load;
|
||||
}
|
||||
|
||||
ReplRequirer::ReplRequirer(
|
||||
std::function<Luau::CompileOptions()> copts,
|
||||
std::function<bool()> coverageActive,
|
||||
std::function<bool()> codegenEnabled,
|
||||
std::function<void(lua_State*, int)> coverageTrack
|
||||
)
|
||||
: copts(std::move(copts))
|
||||
, coverageActive(std::move(coverageActive))
|
||||
, codegenEnabled(std::move(codegenEnabled))
|
||||
, coverageTrack(std::move(coverageTrack))
|
||||
{
|
||||
}
|
313
CLI/src/Require.cpp
Normal file
313
CLI/src/Require.cpp
Normal file
|
@ -0,0 +1,313 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Require.h"
|
||||
|
||||
#include "Luau/FileUtils.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <utility>
|
||||
|
||||
static constexpr char kRequireErrorGeneric[] = "error requiring module";
|
||||
|
||||
RequireResolver::RequireResolver(std::string path, RequireContext& requireContext, CacheManager& cacheManager, ErrorHandler& errorHandler)
|
||||
: pathToResolve(std::move(path))
|
||||
, requireContext(requireContext)
|
||||
, cacheManager(cacheManager)
|
||||
, errorHandler(errorHandler)
|
||||
{
|
||||
}
|
||||
|
||||
RequireResolver::ResolvedRequire RequireResolver::resolveRequire(std::function<void(const ModuleStatus)> completionCallback)
|
||||
{
|
||||
if (isRequireResolved)
|
||||
{
|
||||
errorHandler.reportError("require statement has already been resolved");
|
||||
return ResolvedRequire{ModuleStatus::ErrorReported};
|
||||
}
|
||||
|
||||
if (!initialize())
|
||||
return ResolvedRequire{ModuleStatus::ErrorReported};
|
||||
|
||||
resolvedRequire.status = findModule();
|
||||
|
||||
if (completionCallback)
|
||||
completionCallback(resolvedRequire.status);
|
||||
|
||||
isRequireResolved = true;
|
||||
return resolvedRequire;
|
||||
}
|
||||
|
||||
static bool hasValidPrefix(std::string_view path)
|
||||
{
|
||||
return path.compare(0, 2, "./") == 0 || path.compare(0, 3, "../") == 0 || path.compare(0, 1, "@") == 0;
|
||||
}
|
||||
|
||||
static bool isPathAmbiguous(const std::string& path)
|
||||
{
|
||||
bool found = false;
|
||||
for (const char* suffix : {".luau", ".lua"})
|
||||
{
|
||||
if (isFile(path + suffix))
|
||||
{
|
||||
if (found)
|
||||
return true;
|
||||
else
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (isDirectory(path) && found)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RequireResolver::initialize()
|
||||
{
|
||||
if (!requireContext.isRequireAllowed())
|
||||
{
|
||||
errorHandler.reportError("require is not supported in this context");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isAbsolutePath(pathToResolve))
|
||||
{
|
||||
errorHandler.reportError("cannot require an absolute path");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::replace(pathToResolve.begin(), pathToResolve.end(), '\\', '/');
|
||||
|
||||
if (!hasValidPrefix(pathToResolve))
|
||||
{
|
||||
errorHandler.reportError("require path must start with a valid prefix: ./, ../, or @");
|
||||
return false;
|
||||
}
|
||||
|
||||
return substituteAliasIfPresent(pathToResolve);
|
||||
}
|
||||
|
||||
RequireResolver::ModuleStatus RequireResolver::findModule()
|
||||
{
|
||||
if (!resolveAndStoreDefaultPaths())
|
||||
return ModuleStatus::ErrorReported;
|
||||
|
||||
if (isPathAmbiguous(resolvedRequire.absolutePath))
|
||||
{
|
||||
errorHandler.reportError("require path could not be resolved to a unique file");
|
||||
return ModuleStatus::ErrorReported;
|
||||
}
|
||||
|
||||
static constexpr std::array<const char*, 4> possibleSuffixes = {".luau", ".lua", "/init.luau", "/init.lua"};
|
||||
size_t unsuffixedAbsolutePathSize = resolvedRequire.absolutePath.size();
|
||||
|
||||
for (const char* possibleSuffix : possibleSuffixes)
|
||||
{
|
||||
resolvedRequire.absolutePath += possibleSuffix;
|
||||
|
||||
if (cacheManager.isCached(resolvedRequire.absolutePath))
|
||||
return ModuleStatus::Cached;
|
||||
|
||||
// Try to read the matching file
|
||||
if (std::optional<std::string> source = readFile(resolvedRequire.absolutePath))
|
||||
{
|
||||
resolvedRequire.identifier = requireContext.createNewIdentifer(resolvedRequire.identifier + possibleSuffix);
|
||||
resolvedRequire.sourceCode = *source;
|
||||
return ModuleStatus::FileRead;
|
||||
}
|
||||
|
||||
resolvedRequire.absolutePath.resize(unsuffixedAbsolutePathSize); // truncate to remove suffix
|
||||
}
|
||||
|
||||
if (hasFileExtension(resolvedRequire.absolutePath, {".luau", ".lua"}) && isFile(resolvedRequire.absolutePath))
|
||||
{
|
||||
errorHandler.reportError("error requiring module: consider removing the file extension");
|
||||
return ModuleStatus::ErrorReported;
|
||||
}
|
||||
|
||||
errorHandler.reportError(kRequireErrorGeneric);
|
||||
return ModuleStatus::ErrorReported;
|
||||
}
|
||||
|
||||
bool RequireResolver::resolveAndStoreDefaultPaths()
|
||||
{
|
||||
if (!isAbsolutePath(pathToResolve))
|
||||
{
|
||||
std::string identifierContext = getRequiringContextRelative();
|
||||
std::optional<std::string> absolutePathContext = getRequiringContextAbsolute();
|
||||
|
||||
if (!absolutePathContext)
|
||||
return false;
|
||||
|
||||
// resolvePath automatically sanitizes/normalizes the paths
|
||||
std::optional<std::string> identifier = resolvePath(pathToResolve, identifierContext);
|
||||
std::optional<std::string> absolutePath = resolvePath(pathToResolve, *absolutePathContext);
|
||||
|
||||
if (!identifier || !absolutePath)
|
||||
{
|
||||
errorHandler.reportError("could not resolve require path");
|
||||
return false;
|
||||
}
|
||||
|
||||
resolvedRequire.identifier = std::move(*identifier);
|
||||
resolvedRequire.absolutePath = std::move(*absolutePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Here we must explicitly sanitize, as the path is taken as is
|
||||
std::string sanitizedPath = normalizePath(pathToResolve);
|
||||
resolvedRequire.identifier = sanitizedPath;
|
||||
resolvedRequire.absolutePath = std::move(sanitizedPath);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<std::string> RequireResolver::getRequiringContextAbsolute()
|
||||
{
|
||||
std::string requiringFile;
|
||||
if (isAbsolutePath(requireContext.getPath()))
|
||||
{
|
||||
// We already have an absolute path for the requiring file
|
||||
requiringFile = requireContext.getPath();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Requiring file's stored path is relative to the CWD, must make absolute
|
||||
std::optional<std::string> cwd = getCurrentWorkingDirectory();
|
||||
if (!cwd)
|
||||
{
|
||||
errorHandler.reportError("could not determine current working directory");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (requireContext.isStdin())
|
||||
{
|
||||
// Require statement is being executed from REPL input prompt
|
||||
// The requiring context is the pseudo-file "stdin" in the CWD
|
||||
requiringFile = joinPaths(*cwd, "stdin");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Require statement is being executed in a file, must resolve relative to CWD
|
||||
requiringFile = normalizePath(joinPaths(*cwd, requireContext.getPath()));
|
||||
}
|
||||
}
|
||||
std::replace(requiringFile.begin(), requiringFile.end(), '\\', '/');
|
||||
return requiringFile;
|
||||
}
|
||||
|
||||
std::string RequireResolver::getRequiringContextRelative()
|
||||
{
|
||||
return requireContext.isStdin() ? "./" : requireContext.getPath();
|
||||
}
|
||||
|
||||
bool RequireResolver::substituteAliasIfPresent(std::string& path)
|
||||
{
|
||||
if (path.size() < 1 || path[0] != '@')
|
||||
return true;
|
||||
|
||||
// To ignore the '@' alias prefix when processing the alias
|
||||
const size_t aliasStartPos = 1;
|
||||
|
||||
// If a directory separator was found, the length of the alias is the
|
||||
// distance between the start of the alias and the separator. Otherwise,
|
||||
// the whole string after the alias symbol is the alias.
|
||||
size_t aliasLen = path.find_first_of("\\/");
|
||||
if (aliasLen != std::string::npos)
|
||||
aliasLen -= aliasStartPos;
|
||||
|
||||
const std::string potentialAlias = path.substr(aliasStartPos, aliasLen);
|
||||
|
||||
// Not worth searching when potentialAlias cannot be an alias
|
||||
if (!Luau::isValidAlias(potentialAlias))
|
||||
{
|
||||
errorHandler.reportError("@" + potentialAlias + " is not a valid alias");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (std::optional<std::string> alias = getAlias(potentialAlias))
|
||||
{
|
||||
path = *alias + path.substr(potentialAlias.size() + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
errorHandler.reportError("@" + potentialAlias + " is not a valid alias");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<std::string> RequireResolver::getAlias(std::string alias)
|
||||
{
|
||||
std::transform(
|
||||
alias.begin(),
|
||||
alias.end(),
|
||||
alias.begin(),
|
||||
[](unsigned char c)
|
||||
{
|
||||
return ('A' <= c && c <= 'Z') ? (c + ('a' - 'A')) : c;
|
||||
}
|
||||
);
|
||||
while (!config.aliases.contains(alias) && !isConfigFullyResolved)
|
||||
{
|
||||
if (!parseNextConfig())
|
||||
return std::nullopt; // error parsing config
|
||||
}
|
||||
if (!config.aliases.contains(alias) && isConfigFullyResolved)
|
||||
return std::nullopt; // could not find alias
|
||||
|
||||
const Luau::Config::AliasInfo& aliasInfo = config.aliases[alias];
|
||||
return resolvePath(aliasInfo.value, aliasInfo.configLocation);
|
||||
}
|
||||
|
||||
bool RequireResolver::parseNextConfig()
|
||||
{
|
||||
if (isConfigFullyResolved)
|
||||
return true; // no config files left to parse
|
||||
|
||||
std::optional<std::string> directory;
|
||||
if (lastSearchedDir.empty())
|
||||
{
|
||||
std::optional<std::string> requiringFile = getRequiringContextAbsolute();
|
||||
if (!requiringFile)
|
||||
return false;
|
||||
|
||||
directory = getParentPath(*requiringFile);
|
||||
}
|
||||
else
|
||||
directory = getParentPath(lastSearchedDir);
|
||||
|
||||
if (directory)
|
||||
{
|
||||
lastSearchedDir = *directory;
|
||||
if (!parseConfigInDirectory(*directory))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
isConfigFullyResolved = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RequireResolver::parseConfigInDirectory(const std::string& directory)
|
||||
{
|
||||
std::string configPath = joinPaths(directory, Luau::kConfigName);
|
||||
|
||||
Luau::ConfigOptions::AliasOptions aliasOpts;
|
||||
aliasOpts.configLocation = configPath;
|
||||
aliasOpts.overwriteAliases = false;
|
||||
|
||||
Luau::ConfigOptions opts;
|
||||
opts.aliasOptions = std::move(aliasOpts);
|
||||
|
||||
if (std::optional<std::string> contents = readFile(configPath))
|
||||
{
|
||||
std::optional<std::string> error = Luau::parseConfig(*contents, config, opts);
|
||||
if (error)
|
||||
{
|
||||
errorHandler.reportError("error parsing " + configPath + "(" + *error + ")");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/RequirerUtils.h"
|
||||
|
||||
#include "Luau/FileUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
static std::pair<PathResult::Status, std::string> getSuffixWithAmbiguityCheck(const std::string& path)
|
||||
{
|
||||
bool found = false;
|
||||
std::string suffix;
|
||||
|
||||
for (const char* potentialSuffix : {".luau", ".lua"})
|
||||
{
|
||||
if (isFile(path + potentialSuffix))
|
||||
{
|
||||
if (found)
|
||||
return {PathResult::Status::AMBIGUOUS, ""};
|
||||
|
||||
suffix = potentialSuffix;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (isDirectory(path))
|
||||
{
|
||||
if (found)
|
||||
return {PathResult::Status::AMBIGUOUS, ""};
|
||||
|
||||
for (const char* potentialSuffix : {"/init.luau", "/init.lua"})
|
||||
{
|
||||
if (isFile(path + potentialSuffix))
|
||||
{
|
||||
if (found)
|
||||
return {PathResult::Status::AMBIGUOUS, ""};
|
||||
|
||||
suffix = potentialSuffix;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
return {PathResult::Status::NOT_FOUND, ""};
|
||||
|
||||
return {PathResult::Status::SUCCESS, suffix};
|
||||
}
|
||||
|
||||
static PathResult addSuffix(PathResult partialResult)
|
||||
{
|
||||
if (partialResult.status != PathResult::Status::SUCCESS)
|
||||
return partialResult;
|
||||
|
||||
auto [status, suffix] = getSuffixWithAmbiguityCheck(partialResult.absPath);
|
||||
if (status != PathResult::Status::SUCCESS)
|
||||
return PathResult{status};
|
||||
|
||||
partialResult.suffix = std::move(suffix);
|
||||
return partialResult;
|
||||
}
|
||||
|
||||
PathResult getStdInResult()
|
||||
{
|
||||
std::optional<std::string> cwd = getCurrentWorkingDirectory();
|
||||
if (!cwd)
|
||||
return PathResult{PathResult::Status::NOT_FOUND};
|
||||
|
||||
std::replace(cwd->begin(), cwd->end(), '\\', '/');
|
||||
|
||||
return PathResult{PathResult::Status::SUCCESS, *cwd + "/stdin", "./stdin", ""};
|
||||
}
|
||||
|
||||
PathResult getAbsolutePathResult(const std::string& path)
|
||||
{
|
||||
return addSuffix(PathResult{PathResult::Status::SUCCESS, path});
|
||||
}
|
||||
|
||||
PathResult tryGetRelativePathResult(const std::string& path)
|
||||
{
|
||||
if (isAbsolutePath(path))
|
||||
return getAbsolutePathResult(path);
|
||||
|
||||
std::optional<std::string> cwd = getCurrentWorkingDirectory();
|
||||
if (!cwd)
|
||||
return PathResult{PathResult::Status::NOT_FOUND};
|
||||
|
||||
std::optional<std::string> resolvedAbsPath = resolvePath(path, *cwd + "/stdin");
|
||||
if (!resolvedAbsPath)
|
||||
return PathResult{PathResult::Status::NOT_FOUND};
|
||||
|
||||
return addSuffix(PathResult{PathResult::Status::SUCCESS, std::move(*resolvedAbsPath), path});
|
||||
}
|
||||
|
||||
PathResult getParent(const std::string& absPath, const std::string& relPath)
|
||||
{
|
||||
std::optional<std::string> parent = getParentPath(absPath);
|
||||
if (!parent)
|
||||
return PathResult{PathResult::Status::NOT_FOUND};
|
||||
|
||||
return addSuffix(PathResult{PathResult::Status::SUCCESS, *parent, normalizePath(relPath + "/..")});
|
||||
}
|
||||
|
||||
PathResult getChild(const std::string& absPath, const std::string& relPath, const std::string& name)
|
||||
{
|
||||
return addSuffix(PathResult{PathResult::Status::SUCCESS, joinPaths(absPath, name), joinPaths(relPath, name)});
|
||||
}
|
||||
|
||||
bool isFilePresent(const std::string& path, const std::string& suffix)
|
||||
{
|
||||
return isFile(path + suffix);
|
||||
}
|
||||
|
||||
std::optional<std::string> getFileContents(const std::string& path, const std::string& suffix)
|
||||
{
|
||||
return readFile(path + suffix);
|
||||
}
|
|
@ -4,7 +4,7 @@ if(EXT_PLATFORM_STRING)
|
|||
return()
|
||||
endif()
|
||||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
|
||||
option(LUAU_BUILD_CLI "Build CLI" ON)
|
||||
option(LUAU_BUILD_TESTS "Build tests" ON)
|
||||
|
@ -31,8 +31,6 @@ add_library(Luau.Analysis STATIC)
|
|||
add_library(Luau.EqSat STATIC)
|
||||
add_library(Luau.CodeGen STATIC)
|
||||
add_library(Luau.VM STATIC)
|
||||
add_library(Luau.Require STATIC)
|
||||
add_library(Luau.RequireNavigator STATIC)
|
||||
add_library(isocline STATIC)
|
||||
|
||||
if(LUAU_BUILD_CLI)
|
||||
|
@ -103,15 +101,6 @@ target_compile_features(Luau.VM PRIVATE cxx_std_11)
|
|||
target_include_directories(Luau.VM PUBLIC VM/include)
|
||||
target_link_libraries(Luau.VM PUBLIC Luau.Common)
|
||||
|
||||
target_compile_features(Luau.Require PUBLIC cxx_std_17)
|
||||
target_include_directories(Luau.Require PUBLIC Require/Runtime/include)
|
||||
target_link_libraries(Luau.Require PUBLIC Luau.VM)
|
||||
target_link_libraries(Luau.Require PRIVATE Luau.RequireNavigator)
|
||||
|
||||
target_compile_features(Luau.RequireNavigator PUBLIC cxx_std_17)
|
||||
target_include_directories(Luau.RequireNavigator PUBLIC Require/Navigator/include)
|
||||
target_link_libraries(Luau.RequireNavigator PUBLIC Luau.Config)
|
||||
|
||||
target_include_directories(isocline PUBLIC extern/isocline/include)
|
||||
|
||||
target_include_directories(Luau.VM.Internals INTERFACE VM/src)
|
||||
|
@ -226,12 +215,12 @@ if(LUAU_BUILD_CLI)
|
|||
|
||||
target_include_directories(Luau.Repl.CLI PRIVATE extern extern/isocline/include)
|
||||
|
||||
target_link_libraries(Luau.Repl.CLI PRIVATE Luau.Compiler Luau.Config Luau.CodeGen Luau.VM Luau.Require Luau.CLI.lib isocline)
|
||||
target_link_libraries(Luau.Repl.CLI PRIVATE Luau.Compiler Luau.Config Luau.CodeGen Luau.VM Luau.CLI.lib isocline)
|
||||
|
||||
target_link_libraries(Luau.Repl.CLI PRIVATE osthreads)
|
||||
target_link_libraries(Luau.Analyze.CLI PRIVATE osthreads)
|
||||
|
||||
target_link_libraries(Luau.Analyze.CLI PRIVATE Luau.Analysis Luau.CLI.lib Luau.RequireNavigator)
|
||||
target_link_libraries(Luau.Analyze.CLI PRIVATE Luau.Analysis Luau.CLI.lib)
|
||||
|
||||
target_link_libraries(Luau.Ast.CLI PRIVATE Luau.Ast Luau.Analysis Luau.CLI.lib)
|
||||
|
||||
|
@ -263,7 +252,7 @@ if(LUAU_BUILD_TESTS)
|
|||
|
||||
target_compile_options(Luau.CLI.Test PRIVATE ${LUAU_OPTIONS})
|
||||
target_include_directories(Luau.CLI.Test PRIVATE extern CLI)
|
||||
target_link_libraries(Luau.CLI.Test PRIVATE Luau.Compiler Luau.Config Luau.CodeGen Luau.VM Luau.Require Luau.CLI.lib isocline)
|
||||
target_link_libraries(Luau.CLI.Test PRIVATE Luau.Compiler Luau.Config Luau.CodeGen Luau.VM Luau.CLI.lib isocline)
|
||||
target_link_libraries(Luau.CLI.Test PRIVATE osthreads)
|
||||
|
||||
endif()
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue