Luau header neecessary for analysis
This commit is contained in:
parent
6c6754ebf8
commit
15eb9f8694
|
@ -0,0 +1,42 @@
|
|||
// 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/Substitution.h"
|
||||
#include "Luau/Type.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct TypeArena;
|
||||
struct Scope;
|
||||
struct InternalErrorReporter;
|
||||
using ScopePtr = std::shared_ptr<Scope>;
|
||||
|
||||
// A substitution which replaces free types by any
|
||||
struct Anyification : Substitution
|
||||
{
|
||||
Anyification(TypeArena* arena, NotNull<Scope> scope, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter* iceHandler, TypeId anyType,
|
||||
TypePackId anyTypePack);
|
||||
Anyification(TypeArena* arena, const ScopePtr& scope, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter* iceHandler, TypeId anyType,
|
||||
TypePackId anyTypePack);
|
||||
NotNull<Scope> scope;
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
InternalErrorReporter* iceHandler;
|
||||
|
||||
TypeId anyType;
|
||||
TypePackId anyTypePack;
|
||||
bool normalizationTooComplex = false;
|
||||
bool isDirty(TypeId ty) override;
|
||||
bool isDirty(TypePackId tp) override;
|
||||
TypeId clean(TypeId ty) override;
|
||||
TypePackId clean(TypePackId tp) override;
|
||||
|
||||
bool ignoreChildren(TypeId ty) override;
|
||||
bool ignoreChildren(TypePackId ty) override;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,32 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Substitution.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/Type.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
// A substitution which replaces the type parameters of a type function by arguments
|
||||
struct ApplyTypeFunction : Substitution
|
||||
{
|
||||
ApplyTypeFunction(TypeArena* arena)
|
||||
: Substitution(TxnLog::empty(), arena)
|
||||
, encounteredForwardedType(false)
|
||||
{
|
||||
}
|
||||
|
||||
// Never set under deferred constraint resolution.
|
||||
bool encounteredForwardedType;
|
||||
std::unordered_map<TypeId, TypeId> typeArguments;
|
||||
std::unordered_map<TypePackId, TypePackId> typePackArguments;
|
||||
bool ignoreChildren(TypeId ty) override;
|
||||
bool ignoreChildren(TypePackId tp) override;
|
||||
bool isDirty(TypeId ty) override;
|
||||
bool isDirty(TypePackId tp) override;
|
||||
TypeId clean(TypeId ty) override;
|
||||
TypePackId clean(TypePackId tp) override;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,16 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
class AstNode;
|
||||
struct Comment;
|
||||
|
||||
std::string toJson(AstNode* node);
|
||||
std::string toJson(AstNode* node, const std::vector<Comment>& commentLocations);
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,82 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Documentation.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct Binding;
|
||||
struct SourceModule;
|
||||
struct Module;
|
||||
|
||||
struct Type;
|
||||
using TypeId = const Type*;
|
||||
|
||||
using ScopePtr = std::shared_ptr<struct Scope>;
|
||||
|
||||
struct ExprOrLocal
|
||||
{
|
||||
AstExpr* getExpr()
|
||||
{
|
||||
return expr;
|
||||
}
|
||||
AstLocal* getLocal()
|
||||
{
|
||||
return local;
|
||||
}
|
||||
void setExpr(AstExpr* newExpr)
|
||||
{
|
||||
expr = newExpr;
|
||||
local = nullptr;
|
||||
}
|
||||
void setLocal(AstLocal* newLocal)
|
||||
{
|
||||
local = newLocal;
|
||||
expr = nullptr;
|
||||
}
|
||||
std::optional<Location> getLocation()
|
||||
{
|
||||
return expr ? expr->location : (local ? local->location : std::optional<Location>{});
|
||||
}
|
||||
std::optional<AstName> getName()
|
||||
{
|
||||
if (expr)
|
||||
{
|
||||
if (AstName name = getIdentifier(expr); name.value)
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
else if (local)
|
||||
{
|
||||
return local->name;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
private:
|
||||
AstExpr* expr = nullptr;
|
||||
AstLocal* local = nullptr;
|
||||
};
|
||||
|
||||
std::vector<AstNode*> findAncestryAtPositionForAutocomplete(const SourceModule& source, Position pos);
|
||||
std::vector<AstNode*> findAncestryAtPositionForAutocomplete(AstStatBlock* root, Position pos);
|
||||
std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos, bool includeTypes = false);
|
||||
std::vector<AstNode*> findAstAncestryOfPosition(AstStatBlock* root, Position pos, bool includeTypes = false);
|
||||
AstNode* findNodeAtPosition(const SourceModule& source, Position pos);
|
||||
AstNode* findNodeAtPosition(AstStatBlock* root, Position pos);
|
||||
AstExpr* findExprAtPosition(const SourceModule& source, Position pos);
|
||||
ScopePtr findScopeAtPosition(const Module& module, Position pos);
|
||||
std::optional<Binding> findBindingAtPosition(const Module& module, const SourceModule& source, Position pos);
|
||||
ExprOrLocal findExprOrLocalAtPosition(const SourceModule& source, Position pos);
|
||||
|
||||
std::optional<TypeId> findTypeAtPosition(const Module& module, const SourceModule& sourceModule, Position pos);
|
||||
std::optional<TypeId> findExpectedTypeAtPosition(const Module& module, const SourceModule& sourceModule, Position pos);
|
||||
|
||||
std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const SourceModule& source, const Module& module, Position position);
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,104 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/Type.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct Frontend;
|
||||
struct SourceModule;
|
||||
struct Module;
|
||||
struct TypeChecker;
|
||||
|
||||
using ModulePtr = std::shared_ptr<Module>;
|
||||
|
||||
enum class AutocompleteContext
|
||||
{
|
||||
Unknown,
|
||||
Expression,
|
||||
Statement,
|
||||
Property,
|
||||
Type,
|
||||
Keyword,
|
||||
String,
|
||||
};
|
||||
|
||||
enum class AutocompleteEntryKind
|
||||
{
|
||||
Property,
|
||||
Binding,
|
||||
Keyword,
|
||||
String,
|
||||
Type,
|
||||
Module,
|
||||
GeneratedFunction,
|
||||
};
|
||||
|
||||
enum class ParenthesesRecommendation
|
||||
{
|
||||
None,
|
||||
CursorAfter,
|
||||
CursorInside,
|
||||
};
|
||||
|
||||
enum class TypeCorrectKind
|
||||
{
|
||||
None,
|
||||
Correct,
|
||||
CorrectFunctionResult,
|
||||
};
|
||||
|
||||
struct AutocompleteEntry
|
||||
{
|
||||
AutocompleteEntryKind kind = AutocompleteEntryKind::Property;
|
||||
// Nullopt if kind is Keyword
|
||||
std::optional<TypeId> type = std::nullopt;
|
||||
bool deprecated = false;
|
||||
// Only meaningful if kind is Property.
|
||||
bool wrongIndexType = false;
|
||||
// Set if this suggestion matches the type expected in the context
|
||||
TypeCorrectKind typeCorrect = TypeCorrectKind::None;
|
||||
|
||||
std::optional<const ClassType*> containingClass = std::nullopt;
|
||||
std::optional<const Property*> prop = std::nullopt;
|
||||
std::optional<std::string> documentationSymbol = std::nullopt;
|
||||
Tags tags;
|
||||
ParenthesesRecommendation parens = ParenthesesRecommendation::None;
|
||||
std::optional<std::string> insertText;
|
||||
|
||||
// Only meaningful if kind is Property.
|
||||
bool indexedWithSelf = false;
|
||||
};
|
||||
|
||||
using AutocompleteEntryMap = std::unordered_map<std::string, AutocompleteEntry>;
|
||||
struct AutocompleteResult
|
||||
{
|
||||
AutocompleteEntryMap entryMap;
|
||||
std::vector<AstNode*> ancestry;
|
||||
AutocompleteContext context = AutocompleteContext::Unknown;
|
||||
|
||||
AutocompleteResult() = default;
|
||||
AutocompleteResult(AutocompleteEntryMap entryMap, std::vector<AstNode*> ancestry, AutocompleteContext context)
|
||||
: entryMap(std::move(entryMap))
|
||||
, ancestry(std::move(ancestry))
|
||||
, context(context)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
using ModuleName = std::string;
|
||||
using StringCompletionCallback =
|
||||
std::function<std::optional<AutocompleteEntryMap>(std::string tag, std::optional<const ClassType*> ctx, std::optional<std::string> contents)>;
|
||||
|
||||
AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback);
|
||||
|
||||
constexpr char kGeneratedAnonymousFunctionEntryName[] = "function (anonymous autofilled)";
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,75 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Def.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
using NullableBreadcrumbId = const struct Breadcrumb*;
|
||||
using BreadcrumbId = NotNull<const struct Breadcrumb>;
|
||||
|
||||
struct FieldMetadata
|
||||
{
|
||||
std::string prop;
|
||||
};
|
||||
|
||||
struct SubscriptMetadata
|
||||
{
|
||||
BreadcrumbId key;
|
||||
};
|
||||
|
||||
using Metadata = Variant<FieldMetadata, SubscriptMetadata>;
|
||||
|
||||
struct Breadcrumb
|
||||
{
|
||||
NullableBreadcrumbId previous;
|
||||
DefId def;
|
||||
std::optional<Metadata> metadata;
|
||||
std::vector<BreadcrumbId> children;
|
||||
};
|
||||
|
||||
inline Breadcrumb* asMutable(NullableBreadcrumbId breadcrumb)
|
||||
{
|
||||
LUAU_ASSERT(breadcrumb);
|
||||
return const_cast<Breadcrumb*>(breadcrumb);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const T* getMetadata(NullableBreadcrumbId breadcrumb)
|
||||
{
|
||||
if (!breadcrumb || !breadcrumb->metadata)
|
||||
return nullptr;
|
||||
|
||||
return get_if<T>(&*breadcrumb->metadata);
|
||||
}
|
||||
|
||||
struct BreadcrumbArena
|
||||
{
|
||||
TypedAllocator<Breadcrumb> allocator;
|
||||
|
||||
template<typename... Args>
|
||||
BreadcrumbId add(NullableBreadcrumbId previous, DefId def, Args&&... args)
|
||||
{
|
||||
Breadcrumb* bc = allocator.allocate(Breadcrumb{previous, def, std::forward<Args>(args)...});
|
||||
if (previous)
|
||||
asMutable(previous)->children.push_back(NotNull{bc});
|
||||
return NotNull{bc};
|
||||
}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
BreadcrumbId emplace(NullableBreadcrumbId previous, DefId def, Args&&... args)
|
||||
{
|
||||
Breadcrumb* bc = allocator.allocate(Breadcrumb{previous, def, Metadata{T{std::forward<Args>(args)...}}});
|
||||
if (previous)
|
||||
asMutable(previous)->children.push_back(NotNull{bc});
|
||||
return NotNull{bc};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,61 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/Type.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct Frontend;
|
||||
struct GlobalTypes;
|
||||
struct TypeChecker;
|
||||
struct TypeArena;
|
||||
|
||||
void registerBuiltinTypes(GlobalTypes& globals);
|
||||
|
||||
void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeCheckForAutocomplete = false);
|
||||
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types);
|
||||
TypeId makeIntersection(TypeArena& arena, std::vector<TypeId>&& types);
|
||||
|
||||
/** Build an optional 't'
|
||||
*/
|
||||
TypeId makeOption(NotNull<BuiltinTypes> builtinTypes, TypeArena& arena, TypeId t);
|
||||
|
||||
/** Small utility function for building up type definitions from C++.
|
||||
*/
|
||||
TypeId makeFunction( // Monomorphic
|
||||
TypeArena& arena, std::optional<TypeId> selfType, std::initializer_list<TypeId> paramTypes, std::initializer_list<TypeId> retTypes);
|
||||
|
||||
TypeId makeFunction( // Polymorphic
|
||||
TypeArena& arena, std::optional<TypeId> selfType, std::initializer_list<TypeId> generics, std::initializer_list<TypePackId> genericPacks,
|
||||
std::initializer_list<TypeId> paramTypes, std::initializer_list<TypeId> retTypes);
|
||||
|
||||
TypeId makeFunction( // Monomorphic
|
||||
TypeArena& arena, std::optional<TypeId> selfType, std::initializer_list<TypeId> paramTypes, std::initializer_list<std::string> paramNames,
|
||||
std::initializer_list<TypeId> retTypes);
|
||||
|
||||
TypeId makeFunction( // Polymorphic
|
||||
TypeArena& arena, std::optional<TypeId> selfType, std::initializer_list<TypeId> generics, std::initializer_list<TypePackId> genericPacks,
|
||||
std::initializer_list<TypeId> paramTypes, std::initializer_list<std::string> paramNames, std::initializer_list<TypeId> retTypes);
|
||||
|
||||
void attachMagicFunction(TypeId ty, MagicFunction fn);
|
||||
void attachDcrMagicFunction(TypeId ty, DcrMagicFunction fn);
|
||||
void attachDcrMagicRefinement(TypeId ty, DcrMagicRefinement fn);
|
||||
|
||||
Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol = std::nullopt);
|
||||
void assignPropDocumentationSymbols(TableType::Props& props, const std::string& baseName);
|
||||
|
||||
std::string getBuiltinDefinitionSource();
|
||||
|
||||
void addGlobalBinding(GlobalTypes& globals, const std::string& name, TypeId ty, const std::string& packageName);
|
||||
void addGlobalBinding(GlobalTypes& globals, const std::string& name, Binding binding);
|
||||
void addGlobalBinding(GlobalTypes& globals, const ScopePtr& scope, const std::string& name, TypeId ty, const std::string& packageName);
|
||||
void addGlobalBinding(GlobalTypes& globals, const ScopePtr& scope, const std::string& name, Binding binding);
|
||||
std::optional<Binding> tryGetGlobalBinding(GlobalTypes& globals, const std::string& name);
|
||||
Binding* tryGetGlobalBindingRef(GlobalTypes& globals, const std::string& name);
|
||||
TypeId getGlobalBinding(GlobalTypes& globals, const std::string& name);
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,578 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
// clang-format off
|
||||
|
||||
// This header contains the bytecode definition for Luau interpreter
|
||||
// Creating the bytecode is outside the scope of this file and is handled by bytecode builder (BytecodeBuilder.h) and bytecode compiler (Compiler.h)
|
||||
// Note that ALL enums declared in this file are order-sensitive since the values are baked into bytecode that needs to be processed by legacy clients.
|
||||
|
||||
// # Bytecode definitions
|
||||
// Bytecode instructions are using "word code" - each instruction is one or many 32-bit words.
|
||||
// The first word in the instruction is always the instruction header, and *must* contain the opcode (enum below) in the least significant byte.
|
||||
//
|
||||
// Instruction word can be encoded using one of the following encodings:
|
||||
// ABC - least-significant byte for the opcode, followed by three bytes, A, B and C; each byte declares a register index, small index into some other table or an unsigned integral value
|
||||
// AD - least-significant byte for the opcode, followed by A byte, followed by D half-word (16-bit integer). D is a signed integer that commonly specifies constant table index or jump offset
|
||||
// E - least-significant byte for the opcode, followed by E (24-bit integer). E is a signed integer that commonly specifies a jump offset
|
||||
//
|
||||
// Instruction word is sometimes followed by one extra word, indicated as AUX - this is just a 32-bit word and is decoded according to the specification for each opcode.
|
||||
// For each opcode the encoding is *static* - that is, based on the opcode you know a-priory how large the instruction is, with the exception of NEWCLOSURE
|
||||
|
||||
// # Bytecode indices
|
||||
// Bytecode instructions commonly refer to integer values that define offsets or indices for various entities. For each type, there's a maximum encodable value.
|
||||
// Note that in some cases, the compiler will set a lower limit than the maximum encodable value is to prevent fragile code into bumping against the limits whenever we change the compilation details.
|
||||
// Additionally, in some specific instructions such as ANDK, the limit on the encoded value is smaller; this means that if a value is larger, a different instruction must be selected.
|
||||
//
|
||||
// Registers: 0-254. Registers refer to the values on the function's stack frame, including arguments.
|
||||
// Upvalues: 0-199. Upvalues refer to the values stored in the closure object.
|
||||
// Constants: 0-2^23-1. Constants are stored in a table allocated with each proto; to allow for future bytecode tweaks the encodable value is limited to 23 bits.
|
||||
// Closures: 0-2^15-1. Closures are created from child protos via a child index; the limit is for the number of closures immediately referenced in each function.
|
||||
// Jumps: -2^23..2^23. Jump offsets are specified in word increments, so jumping over an instruction may sometimes require an offset of 2 or more. Note that for jump instructions with AUX, the AUX word is included as part of the jump offset.
|
||||
|
||||
// # Bytecode versions
|
||||
// Bytecode serialized format embeds a version number, that dictates both the serialized form as well as the allowed instructions. As long as the bytecode version falls into supported
|
||||
// range (indicated by LBC_BYTECODE_MIN / LBC_BYTECODE_MAX) and was produced by Luau compiler, it should load and execute correctly.
|
||||
//
|
||||
// Note that Luau runtime doesn't provide indefinite bytecode compatibility: support for older versions gets removed over time. As such, bytecode isn't a durable storage format and it's expected
|
||||
// that Luau users can recompile bytecode from source on Luau version upgrades if necessary.
|
||||
|
||||
// # Bytecode version history
|
||||
//
|
||||
// Note: due to limitations of the versioning scheme, some bytecode blobs that carry version 2 are using features from version 3. Starting from version 3, version should be sufficient to indicate bytecode compatibility.
|
||||
//
|
||||
// Version 1: Baseline version for the open-source release. Supported until 0.521.
|
||||
// Version 2: Adds Proto::linedefined. Supported until 0.544.
|
||||
// Version 3: Adds FORGPREP/JUMPXEQK* and enhances AUX encoding for FORGLOOP. Removes FORGLOOP_NEXT/INEXT and JUMPIFEQK/JUMPIFNOTEQK. Currently supported.
|
||||
// Version 4: Adds Proto::flags, typeinfo, and floor division opcodes IDIV/IDIVK. Currently supported.
|
||||
|
||||
// Bytecode opcode, part of the instruction header
|
||||
enum LuauOpcode
|
||||
{
|
||||
// NOP: noop
|
||||
LOP_NOP,
|
||||
|
||||
// BREAK: debugger break
|
||||
LOP_BREAK,
|
||||
|
||||
// LOADNIL: sets register to nil
|
||||
// A: target register
|
||||
LOP_LOADNIL,
|
||||
|
||||
// LOADB: sets register to boolean and jumps to a given short offset (used to compile comparison results into a boolean)
|
||||
// A: target register
|
||||
// B: value (0/1)
|
||||
// C: jump offset
|
||||
LOP_LOADB,
|
||||
|
||||
// LOADN: sets register to a number literal
|
||||
// A: target register
|
||||
// D: value (-32768..32767)
|
||||
LOP_LOADN,
|
||||
|
||||
// LOADK: sets register to an entry from the constant table from the proto (number/string)
|
||||
// A: target register
|
||||
// D: constant table index (0..32767)
|
||||
LOP_LOADK,
|
||||
|
||||
// MOVE: move (copy) value from one register to another
|
||||
// A: target register
|
||||
// B: source register
|
||||
LOP_MOVE,
|
||||
|
||||
// GETGLOBAL: load value from global table using constant string as a key
|
||||
// A: target register
|
||||
// C: predicted slot index (based on hash)
|
||||
// AUX: constant table index
|
||||
LOP_GETGLOBAL,
|
||||
|
||||
// SETGLOBAL: set value in global table using constant string as a key
|
||||
// A: source register
|
||||
// C: predicted slot index (based on hash)
|
||||
// AUX: constant table index
|
||||
LOP_SETGLOBAL,
|
||||
|
||||
// GETUPVAL: load upvalue from the upvalue table for the current function
|
||||
// A: target register
|
||||
// B: upvalue index
|
||||
LOP_GETUPVAL,
|
||||
|
||||
// SETUPVAL: store value into the upvalue table for the current function
|
||||
// A: target register
|
||||
// B: upvalue index
|
||||
LOP_SETUPVAL,
|
||||
|
||||
// CLOSEUPVALS: close (migrate to heap) all upvalues that were captured for registers >= target
|
||||
// A: target register
|
||||
LOP_CLOSEUPVALS,
|
||||
|
||||
// GETIMPORT: load imported global table global from the constant table
|
||||
// A: target register
|
||||
// D: constant table index (0..32767); we assume that imports are loaded into the constant table
|
||||
// AUX: 3 10-bit indices of constant strings that, combined, constitute an import path; length of the path is set by the top 2 bits (1,2,3)
|
||||
LOP_GETIMPORT,
|
||||
|
||||
// GETTABLE: load value from table into target register using key from register
|
||||
// A: target register
|
||||
// B: table register
|
||||
// C: index register
|
||||
LOP_GETTABLE,
|
||||
|
||||
// SETTABLE: store source register into table using key from register
|
||||
// A: source register
|
||||
// B: table register
|
||||
// C: index register
|
||||
LOP_SETTABLE,
|
||||
|
||||
// GETTABLEKS: load value from table into target register using constant string as a key
|
||||
// A: target register
|
||||
// B: table register
|
||||
// C: predicted slot index (based on hash)
|
||||
// AUX: constant table index
|
||||
LOP_GETTABLEKS,
|
||||
|
||||
// SETTABLEKS: store source register into table using constant string as a key
|
||||
// A: source register
|
||||
// B: table register
|
||||
// C: predicted slot index (based on hash)
|
||||
// AUX: constant table index
|
||||
LOP_SETTABLEKS,
|
||||
|
||||
// GETTABLEN: load value from table into target register using small integer index as a key
|
||||
// A: target register
|
||||
// B: table register
|
||||
// C: index-1 (index is 1..256)
|
||||
LOP_GETTABLEN,
|
||||
|
||||
// SETTABLEN: store source register into table using small integer index as a key
|
||||
// A: source register
|
||||
// B: table register
|
||||
// C: index-1 (index is 1..256)
|
||||
LOP_SETTABLEN,
|
||||
|
||||
// NEWCLOSURE: create closure from a child proto; followed by a CAPTURE instruction for each upvalue
|
||||
// A: target register
|
||||
// D: child proto index (0..32767)
|
||||
LOP_NEWCLOSURE,
|
||||
|
||||
// NAMECALL: prepare to call specified method by name by loading function from source register using constant index into target register and copying source register into target register + 1
|
||||
// A: target register
|
||||
// B: source register
|
||||
// C: predicted slot index (based on hash)
|
||||
// AUX: constant table index
|
||||
// Note that this instruction must be followed directly by CALL; it prepares the arguments
|
||||
// This instruction is roughly equivalent to GETTABLEKS + MOVE pair, but we need a special instruction to support custom __namecall metamethod
|
||||
LOP_NAMECALL,
|
||||
|
||||
// CALL: call specified function
|
||||
// A: register where the function object lives, followed by arguments; results are placed starting from the same register
|
||||
// B: argument count + 1, or 0 to preserve all arguments up to top (MULTRET)
|
||||
// C: result count + 1, or 0 to preserve all values and adjust top (MULTRET)
|
||||
LOP_CALL,
|
||||
|
||||
// RETURN: returns specified values from the function
|
||||
// A: register where the returned values start
|
||||
// B: number of returned values + 1, or 0 to return all values up to top (MULTRET)
|
||||
LOP_RETURN,
|
||||
|
||||
// JUMP: jumps to target offset
|
||||
// D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump")
|
||||
LOP_JUMP,
|
||||
|
||||
// JUMPBACK: jumps to target offset; this is equivalent to JUMP but is used as a safepoint to be able to interrupt while/repeat loops
|
||||
// D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump")
|
||||
LOP_JUMPBACK,
|
||||
|
||||
// JUMPIF: jumps to target offset if register is not nil/false
|
||||
// A: source register
|
||||
// D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump")
|
||||
LOP_JUMPIF,
|
||||
|
||||
// JUMPIFNOT: jumps to target offset if register is nil/false
|
||||
// A: source register
|
||||
// D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump")
|
||||
LOP_JUMPIFNOT,
|
||||
|
||||
// JUMPIFEQ, JUMPIFLE, JUMPIFLT, JUMPIFNOTEQ, JUMPIFNOTLE, JUMPIFNOTLT: jumps to target offset if the comparison is true (or false, for NOT variants)
|
||||
// A: source register 1
|
||||
// D: jump offset (-32768..32767; 1 means "next instruction" aka "don't jump")
|
||||
// AUX: source register 2
|
||||
LOP_JUMPIFEQ,
|
||||
LOP_JUMPIFLE,
|
||||
LOP_JUMPIFLT,
|
||||
LOP_JUMPIFNOTEQ,
|
||||
LOP_JUMPIFNOTLE,
|
||||
LOP_JUMPIFNOTLT,
|
||||
|
||||
// ADD, SUB, MUL, DIV, MOD, POW: compute arithmetic operation between two source registers and put the result into target register
|
||||
// A: target register
|
||||
// B: source register 1
|
||||
// C: source register 2
|
||||
LOP_ADD,
|
||||
LOP_SUB,
|
||||
LOP_MUL,
|
||||
LOP_DIV,
|
||||
LOP_MOD,
|
||||
LOP_POW,
|
||||
|
||||
// ADDK, SUBK, MULK, DIVK, MODK, POWK: compute arithmetic operation between the source register and a constant and put the result into target register
|
||||
// A: target register
|
||||
// B: source register
|
||||
// C: constant table index (0..255)
|
||||
LOP_ADDK,
|
||||
LOP_SUBK,
|
||||
LOP_MULK,
|
||||
LOP_DIVK,
|
||||
LOP_MODK,
|
||||
LOP_POWK,
|
||||
|
||||
// AND, OR: perform `and` or `or` operation (selecting first or second register based on whether the first one is truthy) and put the result into target register
|
||||
// A: target register
|
||||
// B: source register 1
|
||||
// C: source register 2
|
||||
LOP_AND,
|
||||
LOP_OR,
|
||||
|
||||
// ANDK, ORK: perform `and` or `or` operation (selecting source register or constant based on whether the source register is truthy) and put the result into target register
|
||||
// A: target register
|
||||
// B: source register
|
||||
// C: constant table index (0..255)
|
||||
LOP_ANDK,
|
||||
LOP_ORK,
|
||||
|
||||
// CONCAT: concatenate all strings between B and C (inclusive) and put the result into A
|
||||
// A: target register
|
||||
// B: source register start
|
||||
// C: source register end
|
||||
LOP_CONCAT,
|
||||
|
||||
// NOT, MINUS, LENGTH: compute unary operation for source register and put the result into target register
|
||||
// A: target register
|
||||
// B: source register
|
||||
LOP_NOT,
|
||||
LOP_MINUS,
|
||||
LOP_LENGTH,
|
||||
|
||||
// NEWTABLE: create table in target register
|
||||
// A: target register
|
||||
// B: table size, stored as 0 for v=0 and ceil(log2(v))+1 for v!=0
|
||||
// AUX: array size
|
||||
LOP_NEWTABLE,
|
||||
|
||||
// DUPTABLE: duplicate table using the constant table template to target register
|
||||
// A: target register
|
||||
// D: constant table index (0..32767)
|
||||
LOP_DUPTABLE,
|
||||
|
||||
// SETLIST: set a list of values to table in target register
|
||||
// A: target register
|
||||
// B: source register start
|
||||
// C: value count + 1, or 0 to use all values up to top (MULTRET)
|
||||
// AUX: table index to start from
|
||||
LOP_SETLIST,
|
||||
|
||||
// FORNPREP: prepare a numeric for loop, jump over the loop if first iteration doesn't need to run
|
||||
// A: target register; numeric for loops assume a register layout [limit, step, index, variable]
|
||||
// D: jump offset (-32768..32767)
|
||||
// limit/step are immutable, index isn't visible to user code since it's copied into variable
|
||||
LOP_FORNPREP,
|
||||
|
||||
// FORNLOOP: adjust loop variables for one iteration, jump back to the loop header if loop needs to continue
|
||||
// A: target register; see FORNPREP for register layout
|
||||
// D: jump offset (-32768..32767)
|
||||
LOP_FORNLOOP,
|
||||
|
||||
// FORGLOOP: adjust loop variables for one iteration of a generic for loop, jump back to the loop header if loop needs to continue
|
||||
// A: target register; generic for loops assume a register layout [generator, state, index, variables...]
|
||||
// D: jump offset (-32768..32767)
|
||||
// AUX: variable count (1..255) in the low 8 bits, high bit indicates whether to use ipairs-style traversal in the fast path
|
||||
// loop variables are adjusted by calling generator(state, index) and expecting it to return a tuple that's copied to the user variables
|
||||
// the first variable is then copied into index; generator/state are immutable, index isn't visible to user code
|
||||
LOP_FORGLOOP,
|
||||
|
||||
// FORGPREP_INEXT: prepare FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_inext, and jump to FORGLOOP
|
||||
// A: target register (see FORGLOOP for register layout)
|
||||
LOP_FORGPREP_INEXT,
|
||||
|
||||
// removed in v3
|
||||
LOP_DEP_FORGLOOP_INEXT,
|
||||
|
||||
// FORGPREP_NEXT: prepare FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_next, and jump to FORGLOOP
|
||||
// A: target register (see FORGLOOP for register layout)
|
||||
LOP_FORGPREP_NEXT,
|
||||
|
||||
// NATIVECALL: start executing new function in native code
|
||||
// this is a pseudo-instruction that is never emitted by bytecode compiler, but can be constructed at runtime to accelerate native code dispatch
|
||||
LOP_NATIVECALL,
|
||||
|
||||
// GETVARARGS: copy variables into the target register from vararg storage for current function
|
||||
// A: target register
|
||||
// B: variable count + 1, or 0 to copy all variables and adjust top (MULTRET)
|
||||
LOP_GETVARARGS,
|
||||
|
||||
// DUPCLOSURE: create closure from a pre-created function object (reusing it unless environments diverge)
|
||||
// A: target register
|
||||
// D: constant table index (0..32767)
|
||||
LOP_DUPCLOSURE,
|
||||
|
||||
// PREPVARARGS: prepare stack for variadic functions so that GETVARARGS works correctly
|
||||
// A: number of fixed arguments
|
||||
LOP_PREPVARARGS,
|
||||
|
||||
// LOADKX: sets register to an entry from the constant table from the proto (number/string)
|
||||
// A: target register
|
||||
// AUX: constant table index
|
||||
LOP_LOADKX,
|
||||
|
||||
// JUMPX: jumps to the target offset; like JUMPBACK, supports interruption
|
||||
// E: jump offset (-2^23..2^23; 0 means "next instruction" aka "don't jump")
|
||||
LOP_JUMPX,
|
||||
|
||||
// FASTCALL: perform a fast call of a built-in function
|
||||
// A: builtin function id (see LuauBuiltinFunction)
|
||||
// C: jump offset to get to following CALL
|
||||
// FASTCALL is followed by one of (GETIMPORT, MOVE, GETUPVAL) instructions and by CALL instruction
|
||||
// This is necessary so that if FASTCALL can't perform the call inline, it can continue normal execution
|
||||
// If FASTCALL *can* perform the call, it jumps over the instructions *and* over the next CALL
|
||||
// Note that FASTCALL will read the actual call arguments, such as argument/result registers and counts, from the CALL instruction
|
||||
LOP_FASTCALL,
|
||||
|
||||
// COVERAGE: update coverage information stored in the instruction
|
||||
// E: hit count for the instruction (0..2^23-1)
|
||||
// The hit count is incremented by VM every time the instruction is executed, and saturates at 2^23-1
|
||||
LOP_COVERAGE,
|
||||
|
||||
// CAPTURE: capture a local or an upvalue as an upvalue into a newly created closure; only valid after NEWCLOSURE
|
||||
// A: capture type, see LuauCaptureType
|
||||
// B: source register (for VAL/REF) or upvalue index (for UPVAL/UPREF)
|
||||
LOP_CAPTURE,
|
||||
|
||||
// removed in v3
|
||||
LOP_DEP_JUMPIFEQK,
|
||||
LOP_DEP_JUMPIFNOTEQK,
|
||||
|
||||
// FASTCALL1: perform a fast call of a built-in function using 1 register argument
|
||||
// A: builtin function id (see LuauBuiltinFunction)
|
||||
// B: source argument register
|
||||
// C: jump offset to get to following CALL
|
||||
LOP_FASTCALL1,
|
||||
|
||||
// FASTCALL2: perform a fast call of a built-in function using 2 register arguments
|
||||
// A: builtin function id (see LuauBuiltinFunction)
|
||||
// B: source argument register
|
||||
// C: jump offset to get to following CALL
|
||||
// AUX: source register 2 in least-significant byte
|
||||
LOP_FASTCALL2,
|
||||
|
||||
// FASTCALL2K: perform a fast call of a built-in function using 1 register argument and 1 constant argument
|
||||
// A: builtin function id (see LuauBuiltinFunction)
|
||||
// B: source argument register
|
||||
// C: jump offset to get to following CALL
|
||||
// AUX: constant index
|
||||
LOP_FASTCALL2K,
|
||||
|
||||
// FORGPREP: prepare loop variables for a generic for loop, jump to the loop backedge unconditionally
|
||||
// A: target register; generic for loops assume a register layout [generator, state, index, variables...]
|
||||
// D: jump offset (-32768..32767)
|
||||
LOP_FORGPREP,
|
||||
|
||||
// JUMPXEQKNIL, JUMPXEQKB: jumps to target offset if the comparison with constant is true (or false, see AUX)
|
||||
// A: source register 1
|
||||
// D: jump offset (-32768..32767; 1 means "next instruction" aka "don't jump")
|
||||
// AUX: constant value (for boolean) in low bit, NOT flag (that flips comparison result) in high bit
|
||||
LOP_JUMPXEQKNIL,
|
||||
LOP_JUMPXEQKB,
|
||||
|
||||
// JUMPXEQKN, JUMPXEQKS: jumps to target offset if the comparison with constant is true (or false, see AUX)
|
||||
// A: source register 1
|
||||
// D: jump offset (-32768..32767; 1 means "next instruction" aka "don't jump")
|
||||
// AUX: constant table index in low 24 bits, NOT flag (that flips comparison result) in high bit
|
||||
LOP_JUMPXEQKN,
|
||||
LOP_JUMPXEQKS,
|
||||
|
||||
// IDIV: compute floor division between two source registers and put the result into target register
|
||||
// A: target register
|
||||
// B: source register 1
|
||||
// C: source register 2
|
||||
LOP_IDIV,
|
||||
|
||||
// IDIVK compute floor division between the source register and a constant and put the result into target register
|
||||
// A: target register
|
||||
// B: source register
|
||||
// C: constant table index (0..255)
|
||||
LOP_IDIVK,
|
||||
|
||||
// Enum entry for number of opcodes, not a valid opcode by itself!
|
||||
LOP__COUNT
|
||||
};
|
||||
|
||||
// Bytecode instruction header: it's always a 32-bit integer, with low byte (first byte in little endian) containing the opcode
|
||||
// Some instruction types require more data and have more 32-bit integers following the header
|
||||
#define LUAU_INSN_OP(insn) ((insn) & 0xff)
|
||||
|
||||
// ABC encoding: three 8-bit values, containing registers or small numbers
|
||||
#define LUAU_INSN_A(insn) (((insn) >> 8) & 0xff)
|
||||
#define LUAU_INSN_B(insn) (((insn) >> 16) & 0xff)
|
||||
#define LUAU_INSN_C(insn) (((insn) >> 24) & 0xff)
|
||||
|
||||
// AD encoding: one 8-bit value, one signed 16-bit value
|
||||
#define LUAU_INSN_D(insn) (int32_t(insn) >> 16)
|
||||
|
||||
// E encoding: one signed 24-bit value
|
||||
#define LUAU_INSN_E(insn) (int32_t(insn) >> 8)
|
||||
|
||||
// Bytecode tags, used internally for bytecode encoded as a string
|
||||
enum LuauBytecodeTag
|
||||
{
|
||||
// Bytecode version; runtime supports [MIN, MAX], compiler emits TARGET by default but may emit a higher version when flags are enabled
|
||||
LBC_VERSION_MIN = 3,
|
||||
LBC_VERSION_MAX = 4,
|
||||
LBC_VERSION_TARGET = 3,
|
||||
// Type encoding version
|
||||
LBC_TYPE_VERSION = 1,
|
||||
// Types of constant table entries
|
||||
LBC_CONSTANT_NIL = 0,
|
||||
LBC_CONSTANT_BOOLEAN,
|
||||
LBC_CONSTANT_NUMBER,
|
||||
LBC_CONSTANT_STRING,
|
||||
LBC_CONSTANT_IMPORT,
|
||||
LBC_CONSTANT_TABLE,
|
||||
LBC_CONSTANT_CLOSURE,
|
||||
};
|
||||
|
||||
// Type table tags
|
||||
enum LuauBytecodeType
|
||||
{
|
||||
LBC_TYPE_NIL = 0,
|
||||
LBC_TYPE_BOOLEAN,
|
||||
LBC_TYPE_NUMBER,
|
||||
LBC_TYPE_STRING,
|
||||
LBC_TYPE_TABLE,
|
||||
LBC_TYPE_FUNCTION,
|
||||
LBC_TYPE_THREAD,
|
||||
LBC_TYPE_USERDATA,
|
||||
LBC_TYPE_VECTOR,
|
||||
|
||||
LBC_TYPE_ANY = 15,
|
||||
LBC_TYPE_OPTIONAL_BIT = 1 << 7,
|
||||
|
||||
LBC_TYPE_INVALID = 256,
|
||||
};
|
||||
|
||||
// Builtin function ids, used in LOP_FASTCALL
|
||||
enum LuauBuiltinFunction
|
||||
{
|
||||
LBF_NONE = 0,
|
||||
|
||||
// assert()
|
||||
LBF_ASSERT,
|
||||
|
||||
// math.
|
||||
LBF_MATH_ABS,
|
||||
LBF_MATH_ACOS,
|
||||
LBF_MATH_ASIN,
|
||||
LBF_MATH_ATAN2,
|
||||
LBF_MATH_ATAN,
|
||||
LBF_MATH_CEIL,
|
||||
LBF_MATH_COSH,
|
||||
LBF_MATH_COS,
|
||||
LBF_MATH_DEG,
|
||||
LBF_MATH_EXP,
|
||||
LBF_MATH_FLOOR,
|
||||
LBF_MATH_FMOD,
|
||||
LBF_MATH_FREXP,
|
||||
LBF_MATH_LDEXP,
|
||||
LBF_MATH_LOG10,
|
||||
LBF_MATH_LOG,
|
||||
LBF_MATH_MAX,
|
||||
LBF_MATH_MIN,
|
||||
LBF_MATH_MODF,
|
||||
LBF_MATH_POW,
|
||||
LBF_MATH_RAD,
|
||||
LBF_MATH_SINH,
|
||||
LBF_MATH_SIN,
|
||||
LBF_MATH_SQRT,
|
||||
LBF_MATH_TANH,
|
||||
LBF_MATH_TAN,
|
||||
|
||||
// bit32.
|
||||
LBF_BIT32_ARSHIFT,
|
||||
LBF_BIT32_BAND,
|
||||
LBF_BIT32_BNOT,
|
||||
LBF_BIT32_BOR,
|
||||
LBF_BIT32_BXOR,
|
||||
LBF_BIT32_BTEST,
|
||||
LBF_BIT32_EXTRACT,
|
||||
LBF_BIT32_LROTATE,
|
||||
LBF_BIT32_LSHIFT,
|
||||
LBF_BIT32_REPLACE,
|
||||
LBF_BIT32_RROTATE,
|
||||
LBF_BIT32_RSHIFT,
|
||||
|
||||
// type()
|
||||
LBF_TYPE,
|
||||
|
||||
// string.
|
||||
LBF_STRING_BYTE,
|
||||
LBF_STRING_CHAR,
|
||||
LBF_STRING_LEN,
|
||||
|
||||
// typeof()
|
||||
LBF_TYPEOF,
|
||||
|
||||
// string.
|
||||
LBF_STRING_SUB,
|
||||
|
||||
// math.
|
||||
LBF_MATH_CLAMP,
|
||||
LBF_MATH_SIGN,
|
||||
LBF_MATH_ROUND,
|
||||
|
||||
// raw*
|
||||
LBF_RAWSET,
|
||||
LBF_RAWGET,
|
||||
LBF_RAWEQUAL,
|
||||
|
||||
// table.
|
||||
LBF_TABLE_INSERT,
|
||||
LBF_TABLE_UNPACK,
|
||||
|
||||
// vector ctor
|
||||
LBF_VECTOR,
|
||||
|
||||
// bit32.count
|
||||
LBF_BIT32_COUNTLZ,
|
||||
LBF_BIT32_COUNTRZ,
|
||||
|
||||
// select(_, ...)
|
||||
LBF_SELECT_VARARG,
|
||||
|
||||
// rawlen
|
||||
LBF_RAWLEN,
|
||||
|
||||
// bit32.extract(_, k, k)
|
||||
LBF_BIT32_EXTRACTK,
|
||||
|
||||
// get/setmetatable
|
||||
LBF_GETMETATABLE,
|
||||
LBF_SETMETATABLE,
|
||||
|
||||
// tonumber/tostring
|
||||
LBF_TONUMBER,
|
||||
LBF_TOSTRING,
|
||||
};
|
||||
|
||||
// Capture type, used in LOP_CAPTURE
|
||||
enum LuauCaptureType
|
||||
{
|
||||
LCT_VAL = 0,
|
||||
LCT_REF,
|
||||
LCT_UPVAL,
|
||||
};
|
||||
|
||||
// Proto flag bitmask, stored in Proto::flags
|
||||
enum LuauProtoFlag
|
||||
{
|
||||
// used to tag main proto for modules with --!native
|
||||
LPF_NATIVE_MODULE = 1 << 0,
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Bytecode.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
inline int getOpLength(LuauOpcode op)
|
||||
{
|
||||
switch (op)
|
||||
{
|
||||
case LOP_GETGLOBAL:
|
||||
case LOP_SETGLOBAL:
|
||||
case LOP_GETIMPORT:
|
||||
case LOP_GETTABLEKS:
|
||||
case LOP_SETTABLEKS:
|
||||
case LOP_NAMECALL:
|
||||
case LOP_JUMPIFEQ:
|
||||
case LOP_JUMPIFLE:
|
||||
case LOP_JUMPIFLT:
|
||||
case LOP_JUMPIFNOTEQ:
|
||||
case LOP_JUMPIFNOTLE:
|
||||
case LOP_JUMPIFNOTLT:
|
||||
case LOP_NEWTABLE:
|
||||
case LOP_SETLIST:
|
||||
case LOP_FORGLOOP:
|
||||
case LOP_LOADKX:
|
||||
case LOP_FASTCALL2:
|
||||
case LOP_FASTCALL2K:
|
||||
case LOP_JUMPXEQKNIL:
|
||||
case LOP_JUMPXEQKB:
|
||||
case LOP_JUMPXEQKN:
|
||||
case LOP_JUMPXEQKS:
|
||||
return 2;
|
||||
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,24 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct FrontendCancellationToken
|
||||
{
|
||||
void cancel()
|
||||
{
|
||||
cancelled.store(true);
|
||||
}
|
||||
|
||||
bool requested()
|
||||
{
|
||||
return cancelled.load();
|
||||
}
|
||||
|
||||
std::atomic<bool> cancelled;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,31 @@
|
|||
// 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/TypeArena.h"
|
||||
#include "Luau/Type.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
// Only exposed so they can be unit tested.
|
||||
using SeenTypes = std::unordered_map<TypeId, TypeId>;
|
||||
using SeenTypePacks = std::unordered_map<TypePackId, TypePackId>;
|
||||
|
||||
struct CloneState
|
||||
{
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
|
||||
int recursionCount = 0;
|
||||
};
|
||||
|
||||
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState);
|
||||
TypeId clone(TypeId tp, TypeArena& dest, CloneState& cloneState);
|
||||
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState);
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,137 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
// Compiler codegen control macros
|
||||
#ifdef _MSC_VER
|
||||
#define LUAU_NORETURN __declspec(noreturn)
|
||||
#define LUAU_NOINLINE __declspec(noinline)
|
||||
#define LUAU_FORCEINLINE __forceinline
|
||||
#define LUAU_LIKELY(x) x
|
||||
#define LUAU_UNLIKELY(x) x
|
||||
#define LUAU_UNREACHABLE() __assume(false)
|
||||
#define LUAU_DEBUGBREAK() __debugbreak()
|
||||
#else
|
||||
#define LUAU_NORETURN __attribute__((__noreturn__))
|
||||
#define LUAU_NOINLINE __attribute__((noinline))
|
||||
#define LUAU_FORCEINLINE inline __attribute__((always_inline))
|
||||
#define LUAU_LIKELY(x) __builtin_expect(x, 1)
|
||||
#define LUAU_UNLIKELY(x) __builtin_expect(x, 0)
|
||||
#define LUAU_UNREACHABLE() __builtin_unreachable()
|
||||
#define LUAU_DEBUGBREAK() __builtin_trap()
|
||||
#endif
|
||||
|
||||
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
#define LUAU_BIG_ENDIAN
|
||||
#endif
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
using AssertHandler = int (*)(const char* expression, const char* file, int line, const char* function);
|
||||
|
||||
inline AssertHandler& assertHandler()
|
||||
{
|
||||
static AssertHandler handler = nullptr;
|
||||
return handler;
|
||||
}
|
||||
|
||||
// We want 'inline' to correctly link this function declared in the header
|
||||
// But we also want to prevent compiler from inlining this function when optimization and assertions are enabled together
|
||||
// Reason for that is that compilation times can increase significantly in such a configuration
|
||||
LUAU_NOINLINE inline int assertCallHandler(const char* expression, const char* file, int line, const char* function)
|
||||
{
|
||||
if (AssertHandler handler = assertHandler())
|
||||
return handler(expression, file, line, function);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
||||
#if !defined(NDEBUG) || defined(LUAU_ENABLE_ASSERT)
|
||||
#define LUAU_ASSERT(expr) ((void)(!!(expr) || (Luau::assertCallHandler(#expr, __FILE__, __LINE__, __FUNCTION__) && (LUAU_DEBUGBREAK(), 0))))
|
||||
#define LUAU_ASSERTENABLED
|
||||
#else
|
||||
#define LUAU_ASSERT(expr) (void)sizeof(!!(expr))
|
||||
#endif
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
struct FValue
|
||||
{
|
||||
static FValue* list;
|
||||
|
||||
T value;
|
||||
bool dynamic;
|
||||
const char* name;
|
||||
FValue* next;
|
||||
|
||||
FValue(const char* name, T def, bool dynamic)
|
||||
: value(def)
|
||||
, dynamic(dynamic)
|
||||
, name(name)
|
||||
, next(list)
|
||||
{
|
||||
list = this;
|
||||
}
|
||||
|
||||
operator T() const
|
||||
{
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
FValue<T>* FValue<T>::list = nullptr;
|
||||
|
||||
} // namespace Luau
|
||||
|
||||
#define LUAU_FASTFLAG(flag) \
|
||||
namespace FFlag \
|
||||
{ \
|
||||
extern Luau::FValue<bool> flag; \
|
||||
}
|
||||
#define LUAU_FASTFLAGVARIABLE(flag, def) \
|
||||
namespace FFlag \
|
||||
{ \
|
||||
Luau::FValue<bool> flag(#flag, def, false); \
|
||||
}
|
||||
#define LUAU_FASTINT(flag) \
|
||||
namespace FInt \
|
||||
{ \
|
||||
extern Luau::FValue<int> flag; \
|
||||
}
|
||||
#define LUAU_FASTINTVARIABLE(flag, def) \
|
||||
namespace FInt \
|
||||
{ \
|
||||
Luau::FValue<int> flag(#flag, def, false); \
|
||||
}
|
||||
|
||||
#define LUAU_DYNAMIC_FASTFLAG(flag) \
|
||||
namespace DFFlag \
|
||||
{ \
|
||||
extern Luau::FValue<bool> flag; \
|
||||
}
|
||||
#define LUAU_DYNAMIC_FASTFLAGVARIABLE(flag, def) \
|
||||
namespace DFFlag \
|
||||
{ \
|
||||
Luau::FValue<bool> flag(#flag, def, true); \
|
||||
}
|
||||
#define LUAU_DYNAMIC_FASTINT(flag) \
|
||||
namespace DFInt \
|
||||
{ \
|
||||
extern Luau::FValue<int> flag; \
|
||||
}
|
||||
#define LUAU_DYNAMIC_FASTINTVARIABLE(flag, def) \
|
||||
namespace DFInt \
|
||||
{ \
|
||||
Luau::FValue<int> flag(#flag, def, true); \
|
||||
}
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#define LUAU_PRINTF_ATTR(fmt, arg) __attribute__((format(printf, fmt, arg)))
|
||||
#else
|
||||
#define LUAU_PRINTF_ATTR(fmt, arg)
|
||||
#endif
|
|
@ -0,0 +1,55 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/LinterConfig.h"
|
||||
#include "Luau/ParseOptions.h"
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
using ModuleName = std::string;
|
||||
|
||||
constexpr const char* kConfigName = ".luaurc";
|
||||
|
||||
struct Config
|
||||
{
|
||||
Config();
|
||||
|
||||
Mode mode = Mode::Nonstrict;
|
||||
|
||||
ParseOptions parseOptions;
|
||||
|
||||
LintOptions enabledLint;
|
||||
LintOptions fatalLint;
|
||||
|
||||
bool lintErrors = false;
|
||||
bool typeErrors = true;
|
||||
|
||||
std::vector<std::string> globals;
|
||||
};
|
||||
|
||||
struct ConfigResolver
|
||||
{
|
||||
virtual ~ConfigResolver() {}
|
||||
|
||||
virtual const Config& getConfig(const ModuleName& name) const = 0;
|
||||
};
|
||||
|
||||
struct NullConfigResolver : ConfigResolver
|
||||
{
|
||||
Config defaultConfig;
|
||||
|
||||
virtual const Config& getConfig(const ModuleName& name) const override;
|
||||
};
|
||||
|
||||
std::optional<std::string> parseModeString(Mode& mode, const std::string& modeString, bool compat = false);
|
||||
std::optional<std::string> parseLintRuleString(
|
||||
LintOptions& enabledLints, LintOptions& fatalLints, const std::string& warningName, const std::string& value, bool compat = false);
|
||||
|
||||
std::optional<std::string> parseConfig(const std::string& contents, Config& config, bool compat = false);
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +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 <stdint.h>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
const char* findConfusable(uint32_t codepoint);
|
||||
}
|
|
@ -0,0 +1,298 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Ast.h" // Used for some of the enumerations
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct Scope;
|
||||
|
||||
struct Type;
|
||||
using TypeId = const Type*;
|
||||
|
||||
struct TypePackVar;
|
||||
using TypePackId = const TypePackVar*;
|
||||
|
||||
// subType <: superType
|
||||
struct SubtypeConstraint
|
||||
{
|
||||
TypeId subType;
|
||||
TypeId superType;
|
||||
};
|
||||
|
||||
// subPack <: superPack
|
||||
struct PackSubtypeConstraint
|
||||
{
|
||||
TypePackId subPack;
|
||||
TypePackId superPack;
|
||||
|
||||
// HACK!! TODO clip.
|
||||
// We need to know which of `PackSubtypeConstraint` are emitted from `AstStatReturn` vs any others.
|
||||
// Then we force these specific `PackSubtypeConstraint` to only dispatch in the order of the `return`s.
|
||||
bool returns = false;
|
||||
};
|
||||
|
||||
// generalizedType ~ gen sourceType
|
||||
struct GeneralizationConstraint
|
||||
{
|
||||
TypeId generalizedType;
|
||||
TypeId sourceType;
|
||||
};
|
||||
|
||||
// subType ~ inst superType
|
||||
struct InstantiationConstraint
|
||||
{
|
||||
TypeId subType;
|
||||
TypeId superType;
|
||||
};
|
||||
|
||||
struct UnaryConstraint
|
||||
{
|
||||
AstExprUnary::Op op;
|
||||
TypeId operandType;
|
||||
TypeId resultType;
|
||||
};
|
||||
|
||||
// let L : leftType
|
||||
// let R : rightType
|
||||
// in
|
||||
// L op R : resultType
|
||||
struct BinaryConstraint
|
||||
{
|
||||
AstExprBinary::Op op;
|
||||
TypeId leftType;
|
||||
TypeId rightType;
|
||||
TypeId resultType;
|
||||
|
||||
// When we dispatch this constraint, we update the key at this map to record
|
||||
// the overload that we selected.
|
||||
const AstNode* astFragment;
|
||||
DenseHashMap<const AstNode*, TypeId>* astOriginalCallTypes;
|
||||
DenseHashMap<const AstNode*, TypeId>* astOverloadResolvedTypes;
|
||||
};
|
||||
|
||||
// iteratee is iterable
|
||||
// iterators is the iteration types.
|
||||
struct IterableConstraint
|
||||
{
|
||||
TypePackId iterator;
|
||||
TypePackId variables;
|
||||
|
||||
const AstNode* nextAstFragment;
|
||||
DenseHashMap<const AstNode*, TypeId>* astForInNextTypes;
|
||||
};
|
||||
|
||||
// name(namedType) = name
|
||||
struct NameConstraint
|
||||
{
|
||||
TypeId namedType;
|
||||
std::string name;
|
||||
bool synthetic = false;
|
||||
std::vector<TypeId> typeParameters;
|
||||
std::vector<TypePackId> typePackParameters;
|
||||
};
|
||||
|
||||
// target ~ inst target
|
||||
struct TypeAliasExpansionConstraint
|
||||
{
|
||||
// Must be a PendingExpansionType.
|
||||
TypeId target;
|
||||
};
|
||||
|
||||
struct FunctionCallConstraint
|
||||
{
|
||||
TypeId fn;
|
||||
TypePackId argsPack;
|
||||
TypePackId result;
|
||||
class AstExprCall* callSite = nullptr;
|
||||
std::vector<std::optional<TypeId>> discriminantTypes;
|
||||
|
||||
// When we dispatch this constraint, we update the key at this map to record
|
||||
// the overload that we selected.
|
||||
DenseHashMap<const AstNode*, TypeId>* astOverloadResolvedTypes = nullptr;
|
||||
};
|
||||
|
||||
// result ~ prim ExpectedType SomeSingletonType MultitonType
|
||||
//
|
||||
// If ExpectedType is potentially a singleton (an actual singleton or a union
|
||||
// that contains a singleton), then result ~ SomeSingletonType
|
||||
//
|
||||
// else result ~ MultitonType
|
||||
struct PrimitiveTypeConstraint
|
||||
{
|
||||
TypeId resultType;
|
||||
TypeId expectedType;
|
||||
TypeId singletonType;
|
||||
TypeId multitonType;
|
||||
};
|
||||
|
||||
// result ~ hasProp type "prop_name"
|
||||
//
|
||||
// If the subject is a table, bind the result to the named prop. If the table
|
||||
// has an indexer, bind it to the index result type. If the subject is a union,
|
||||
// bind the result to the union of its constituents' properties.
|
||||
//
|
||||
// It would be nice to get rid of this constraint and someday replace it with
|
||||
//
|
||||
// T <: {p: X}
|
||||
//
|
||||
// Where {} describes an inexact shape type.
|
||||
struct HasPropConstraint
|
||||
{
|
||||
TypeId resultType;
|
||||
TypeId subjectType;
|
||||
std::string prop;
|
||||
|
||||
// HACK: We presently need types like true|false or string|"hello" when
|
||||
// deciding whether a particular literal expression should have a singleton
|
||||
// type. This boolean is set to true when extracting the property type of a
|
||||
// value that may be a union of tables.
|
||||
//
|
||||
// For example, in the following code fragment, we want the lookup of the
|
||||
// success property to yield true|false when extracting an expectedType in
|
||||
// this expression:
|
||||
//
|
||||
// type Result<T, E> = {success:true, result: T} | {success:false, error: E}
|
||||
//
|
||||
// local r: Result<number, string> = {success=true, result=9}
|
||||
//
|
||||
// If we naively simplify the expectedType to boolean, we will erroneously
|
||||
// compute the type boolean for the success property of the table literal.
|
||||
// This causes type checking to fail.
|
||||
bool suppressSimplification = false;
|
||||
};
|
||||
|
||||
// result ~ setProp subjectType ["prop", "prop2", ...] propType
|
||||
//
|
||||
// If the subject is a table or table-like thing that already has the named
|
||||
// property chain, we unify propType with that existing property type.
|
||||
//
|
||||
// If the subject is a free table, we augment it in place.
|
||||
//
|
||||
// If the subject is an unsealed table, result is an augmented table that
|
||||
// includes that new prop.
|
||||
struct SetPropConstraint
|
||||
{
|
||||
TypeId resultType;
|
||||
TypeId subjectType;
|
||||
std::vector<std::string> path;
|
||||
TypeId propType;
|
||||
};
|
||||
|
||||
// result ~ setIndexer subjectType indexType propType
|
||||
//
|
||||
// If the subject is a table or table-like thing that already has an indexer,
|
||||
// unify its indexType and propType with those from this constraint.
|
||||
//
|
||||
// If the table is a free or unsealed table, we augment it with a new indexer.
|
||||
struct SetIndexerConstraint
|
||||
{
|
||||
TypeId resultType;
|
||||
TypeId subjectType;
|
||||
TypeId indexType;
|
||||
TypeId propType;
|
||||
};
|
||||
|
||||
// if negation:
|
||||
// result ~ if isSingleton D then ~D else unknown where D = discriminantType
|
||||
// if not negation:
|
||||
// result ~ if isSingleton D then D else unknown where D = discriminantType
|
||||
struct SingletonOrTopTypeConstraint
|
||||
{
|
||||
TypeId resultType;
|
||||
TypeId discriminantType;
|
||||
bool negated;
|
||||
};
|
||||
|
||||
// resultType ~ unpack sourceTypePack
|
||||
//
|
||||
// Similar to PackSubtypeConstraint, but with one important difference: If the
|
||||
// sourcePack is blocked, this constraint blocks.
|
||||
struct UnpackConstraint
|
||||
{
|
||||
TypePackId resultPack;
|
||||
TypePackId sourcePack;
|
||||
};
|
||||
|
||||
// resultType ~ refine type mode discriminant
|
||||
//
|
||||
// Compute type & discriminant (or type | discriminant) as soon as possible (but
|
||||
// no sooner), simplify, and bind resultType to that type.
|
||||
struct RefineConstraint
|
||||
{
|
||||
enum
|
||||
{
|
||||
Intersection,
|
||||
Union
|
||||
} mode;
|
||||
|
||||
TypeId resultType;
|
||||
|
||||
TypeId type;
|
||||
TypeId discriminant;
|
||||
};
|
||||
|
||||
// ty ~ reduce ty
|
||||
//
|
||||
// Try to reduce ty, if it is a TypeFamilyInstanceType. Otherwise, do nothing.
|
||||
struct ReduceConstraint
|
||||
{
|
||||
TypeId ty;
|
||||
};
|
||||
|
||||
// tp ~ reduce tp
|
||||
//
|
||||
// Analogous to ReduceConstraint, but for type packs.
|
||||
struct ReducePackConstraint
|
||||
{
|
||||
TypePackId tp;
|
||||
};
|
||||
|
||||
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, UnaryConstraint,
|
||||
BinaryConstraint, IterableConstraint, NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, PrimitiveTypeConstraint,
|
||||
HasPropConstraint, SetPropConstraint, SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, RefineConstraint, ReduceConstraint,
|
||||
ReducePackConstraint>;
|
||||
|
||||
struct Constraint
|
||||
{
|
||||
Constraint(NotNull<Scope> scope, const Location& location, ConstraintV&& c);
|
||||
|
||||
Constraint(const Constraint&) = delete;
|
||||
Constraint& operator=(const Constraint&) = delete;
|
||||
|
||||
NotNull<Scope> scope;
|
||||
Location location;
|
||||
ConstraintV c;
|
||||
|
||||
std::vector<NotNull<Constraint>> dependencies;
|
||||
};
|
||||
|
||||
using ConstraintPtr = std::unique_ptr<Constraint>;
|
||||
|
||||
inline Constraint& asMutable(const Constraint& c)
|
||||
{
|
||||
return const_cast<Constraint&>(c);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* getMutable(Constraint& c)
|
||||
{
|
||||
return ::Luau::get_if<T>(&c.c);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const T* get(const Constraint& c)
|
||||
{
|
||||
return getMutable<T>(asMutable(c));
|
||||
}
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,309 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Constraint.h"
|
||||
#include "Luau/ControlFlow.h"
|
||||
#include "Luau/DataFlowGraph.h"
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/ModuleResolver.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/Refinement.h"
|
||||
#include "Luau/Symbol.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Variant.h"
|
||||
#include "Normalize.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct Scope;
|
||||
using ScopePtr = std::shared_ptr<Scope>;
|
||||
|
||||
struct DcrLogger;
|
||||
|
||||
struct Inference
|
||||
{
|
||||
TypeId ty = nullptr;
|
||||
RefinementId refinement = nullptr;
|
||||
|
||||
Inference() = default;
|
||||
|
||||
explicit Inference(TypeId ty, RefinementId refinement = nullptr)
|
||||
: ty(ty)
|
||||
, refinement(refinement)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct InferencePack
|
||||
{
|
||||
TypePackId tp = nullptr;
|
||||
std::vector<RefinementId> refinements;
|
||||
|
||||
InferencePack() = default;
|
||||
|
||||
explicit InferencePack(TypePackId tp, const std::vector<RefinementId>& refinements = {})
|
||||
: tp(tp)
|
||||
, refinements(refinements)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct ConstraintGraphBuilder
|
||||
{
|
||||
// A list of all the scopes in the module. This vector holds ownership of the
|
||||
// scope pointers; the scopes themselves borrow pointers to other scopes to
|
||||
// define the scope hierarchy.
|
||||
std::vector<std::pair<Location, ScopePtr>> scopes;
|
||||
|
||||
ModulePtr module;
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
const NotNull<TypeArena> arena;
|
||||
// The root scope of the module we're generating constraints for.
|
||||
// This is null when the CGB is initially constructed.
|
||||
Scope* rootScope;
|
||||
|
||||
// Constraints that go straight to the solver.
|
||||
std::vector<ConstraintPtr> constraints;
|
||||
|
||||
// Constraints that do not go to the solver right away. Other constraints
|
||||
// will enqueue them during solving.
|
||||
std::vector<ConstraintPtr> unqueuedConstraints;
|
||||
|
||||
// The private scope of type aliases for which the type parameters belong to.
|
||||
DenseHashMap<const AstStatTypeAlias*, ScopePtr> astTypeAliasDefiningScopes{nullptr};
|
||||
|
||||
NotNull<const DataFlowGraph> dfg;
|
||||
RefinementArena refinementArena;
|
||||
|
||||
int recursionCount = 0;
|
||||
|
||||
// It is pretty uncommon for constraint generation to itself produce errors, but it can happen.
|
||||
std::vector<TypeError> errors;
|
||||
|
||||
// Needed to be able to enable error-suppression preservation for immediate refinements.
|
||||
NotNull<Normalizer> normalizer;
|
||||
// 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;
|
||||
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope;
|
||||
std::vector<RequireCycle> requireCycles;
|
||||
|
||||
DcrLogger* logger;
|
||||
|
||||
ConstraintGraphBuilder(ModulePtr module, NotNull<Normalizer> normalizer, NotNull<ModuleResolver> moduleResolver, NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||
DcrLogger* logger, NotNull<DataFlowGraph> dfg, std::vector<RequireCycle> requireCycles);
|
||||
|
||||
/**
|
||||
* Fabricates a new free type belonging to a given scope.
|
||||
* @param scope the scope the free type belongs to.
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
ScopePtr childScope(AstNode* node, const ScopePtr& parent);
|
||||
|
||||
/**
|
||||
* Adds a new constraint with no dependencies to a given scope.
|
||||
* @param scope the scope to add the constraint to.
|
||||
* @param cv the constraint variant to add.
|
||||
* @return the pointer to the inserted constraint
|
||||
*/
|
||||
NotNull<Constraint> addConstraint(const ScopePtr& scope, const Location& location, ConstraintV cv);
|
||||
|
||||
/**
|
||||
* Adds a constraint to a given scope.
|
||||
* @param scope the scope to add the constraint to. Must not be null.
|
||||
* @param c the constraint to add.
|
||||
* @return the pointer to the inserted constraint
|
||||
*/
|
||||
NotNull<Constraint> addConstraint(const ScopePtr& scope, std::unique_ptr<Constraint> c);
|
||||
|
||||
void applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement);
|
||||
|
||||
/**
|
||||
* The entry point to the ConstraintGraphBuilder. This will construct a set
|
||||
* of scopes, constraints, and free types that can be solved later.
|
||||
* @param block the root block to generate constraints for.
|
||||
*/
|
||||
void visit(AstStatBlock* block);
|
||||
|
||||
ControlFlow visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block);
|
||||
|
||||
ControlFlow visit(const ScopePtr& scope, AstStat* stat);
|
||||
ControlFlow visit(const ScopePtr& scope, AstStatBlock* block);
|
||||
ControlFlow visit(const ScopePtr& scope, AstStatLocal* local);
|
||||
ControlFlow visit(const ScopePtr& scope, AstStatFor* for_);
|
||||
ControlFlow visit(const ScopePtr& scope, AstStatForIn* forIn);
|
||||
ControlFlow visit(const ScopePtr& scope, AstStatWhile* while_);
|
||||
ControlFlow visit(const ScopePtr& scope, AstStatRepeat* repeat);
|
||||
ControlFlow visit(const ScopePtr& scope, AstStatLocalFunction* function);
|
||||
ControlFlow visit(const ScopePtr& scope, AstStatFunction* function);
|
||||
ControlFlow visit(const ScopePtr& scope, AstStatReturn* ret);
|
||||
ControlFlow visit(const ScopePtr& scope, AstStatAssign* assign);
|
||||
ControlFlow visit(const ScopePtr& scope, AstStatCompoundAssign* assign);
|
||||
ControlFlow visit(const ScopePtr& scope, AstStatIf* ifStatement);
|
||||
ControlFlow visit(const ScopePtr& scope, AstStatTypeAlias* alias);
|
||||
ControlFlow visit(const ScopePtr& scope, AstStatDeclareGlobal* declareGlobal);
|
||||
ControlFlow visit(const ScopePtr& scope, AstStatDeclareClass* declareClass);
|
||||
ControlFlow visit(const ScopePtr& scope, AstStatDeclareFunction* declareFunction);
|
||||
ControlFlow visit(const ScopePtr& scope, AstStatError* error);
|
||||
|
||||
InferencePack checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs, const std::vector<std::optional<TypeId>>& expectedTypes = {});
|
||||
InferencePack checkPack(const ScopePtr& scope, AstExpr* expr, const std::vector<std::optional<TypeId>>& expectedTypes = {});
|
||||
|
||||
InferencePack checkPack(const ScopePtr& scope, AstExprCall* call);
|
||||
|
||||
/**
|
||||
* Checks an expression that is expected to evaluate to one type.
|
||||
* @param scope the scope the expression is contained within.
|
||||
* @param expr the expression to check.
|
||||
* @param expectedType the type of the expression that is expected from its
|
||||
* surrounding context. Used to implement bidirectional type checking.
|
||||
* @return the type of the expression.
|
||||
*/
|
||||
Inference check(const ScopePtr& scope, AstExpr* expr, ValueContext context = ValueContext::RValue, std::optional<TypeId> expectedType = {},
|
||||
bool forceSingleton = false);
|
||||
|
||||
Inference check(const ScopePtr& scope, AstExprConstantString* string, 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, ValueContext context);
|
||||
Inference check(const ScopePtr& scope, AstExprGlobal* global);
|
||||
Inference check(const ScopePtr& scope, AstExprIndexName* indexName);
|
||||
Inference check(const ScopePtr& scope, AstExprIndexExpr* indexExpr);
|
||||
Inference check(const ScopePtr& scope, AstExprFunction* func, std::optional<TypeId> expectedType);
|
||||
Inference check(const ScopePtr& scope, AstExprUnary* unary);
|
||||
Inference check(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType);
|
||||
Inference check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType);
|
||||
Inference check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert);
|
||||
Inference check(const ScopePtr& scope, AstExprInterpString* interpString);
|
||||
Inference check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType);
|
||||
std::tuple<TypeId, TypeId, RefinementId> checkBinary(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType);
|
||||
|
||||
std::vector<TypeId> checkLValues(const ScopePtr& scope, AstArray<AstExpr*> exprs);
|
||||
|
||||
TypeId checkLValue(const ScopePtr& scope, AstExpr* expr);
|
||||
|
||||
struct FunctionSignature
|
||||
{
|
||||
// The type of the function.
|
||||
TypeId signature;
|
||||
// The scope that encompasses the function's signature. May be nullptr
|
||||
// if there was no need for a signature scope (the function has no
|
||||
// generics).
|
||||
ScopePtr signatureScope;
|
||||
// The scope that encompasses the function's body. Is a child scope of
|
||||
// signatureScope, if present.
|
||||
ScopePtr bodyScope;
|
||||
};
|
||||
|
||||
FunctionSignature checkFunctionSignature(
|
||||
const ScopePtr& parent, AstExprFunction* fn, std::optional<TypeId> expectedType = {}, std::optional<Location> originalName = {});
|
||||
|
||||
/**
|
||||
* Checks the body of a function expression.
|
||||
* @param scope the interior scope of the body of the function.
|
||||
* @param fn the function expression to check.
|
||||
*/
|
||||
void checkFunctionBody(const ScopePtr& scope, AstExprFunction* fn);
|
||||
|
||||
/**
|
||||
* Resolves a type from its AST annotation.
|
||||
* @param scope the scope that the type annotation appears within.
|
||||
* @param ty the AST annotation to resolve.
|
||||
* @param inTypeArguments whether we are resolving a type that's contained within type arguments, `<...>`.
|
||||
* @return the type of the AST annotation.
|
||||
**/
|
||||
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.
|
||||
* @param tp the AST annotation to resolve.
|
||||
* @param inTypeArguments whether we are resolving a type that's contained within type arguments, `<...>`.
|
||||
* @return the type pack of the AST annotation.
|
||||
**/
|
||||
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.
|
||||
* @param list the AST annotation to resolve.
|
||||
* @param inTypeArguments whether we are resolving a type that's contained within type arguments, `<...>`.
|
||||
* @return the type pack of the AST annotation.
|
||||
**/
|
||||
TypePackId resolveTypePack(const ScopePtr& scope, const AstTypeList& list, bool inTypeArguments, bool replaceErrorWithFresh = false);
|
||||
|
||||
/**
|
||||
* Creates generic types given a list of AST definitions, resolving default
|
||||
* types as required.
|
||||
* @param scope the scope that the generics should belong to.
|
||||
* @param generics the AST generics to create types for.
|
||||
* @param useCache whether to use the generic type cache for the given
|
||||
* scope.
|
||||
* @param addTypes whether to add the types to the scope's
|
||||
* privateTypeBindings map.
|
||||
**/
|
||||
std::vector<std::pair<Name, GenericTypeDefinition>> createGenerics(
|
||||
const ScopePtr& scope, AstArray<AstGenericType> generics, bool useCache = false, bool addTypes = true);
|
||||
|
||||
/**
|
||||
* Creates generic type packs given a list of AST definitions, resolving
|
||||
* default type packs as required.
|
||||
* @param scope the scope that the generic packs should belong to.
|
||||
* @param generics the AST generics to create type packs for.
|
||||
* @param useCache whether to use the generic type pack cache for the given
|
||||
* scope.
|
||||
* @param addTypes whether to add the types to the scope's
|
||||
* privateTypePackBindings map.
|
||||
**/
|
||||
std::vector<std::pair<Name, GenericTypePackDefinition>> createGenericPacks(
|
||||
const ScopePtr& scope, AstArray<AstGenericTypePack> packs, bool useCache = false, bool addTypes = true);
|
||||
|
||||
Inference flattenPack(const ScopePtr& scope, Location location, InferencePack pack);
|
||||
|
||||
void reportError(Location location, TypeErrorData err);
|
||||
void reportCodeTooComplex(Location location);
|
||||
|
||||
/** Scan the program for global definitions.
|
||||
*
|
||||
* ConstraintGraphBuilder needs to differentiate between globals and accesses to undefined symbols. Doing this "for
|
||||
* real" in a general way is going to be pretty hard, so we are choosing not to tackle that yet. For now, we do an
|
||||
* initial scan of the AST and note what globals are defined.
|
||||
*/
|
||||
void prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program);
|
||||
|
||||
/** Given a function type annotation, return a vector describing the expected types of the calls to the function
|
||||
* For example, calling a function with annotation ((number) -> string & ((string) -> number))
|
||||
* yields a vector of size 1, with value: [number | string]
|
||||
*/
|
||||
std::vector<std::optional<TypeId>> getExpectedCallTypesForFunctionOverloads(const TypeId fnType);
|
||||
};
|
||||
|
||||
/** Borrow a vector of pointers from a vector of owning pointers to constraints.
|
||||
*/
|
||||
std::vector<NotNull<Constraint>> borrowConstraints(const std::vector<ConstraintPtr>& constraints);
|
||||
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,292 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Constraint.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeCheckLimits.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct DcrLogger;
|
||||
|
||||
// TypeId, TypePackId, or Constraint*. It is impossible to know which, but we
|
||||
// never dereference this pointer.
|
||||
using BlockedConstraintId = Variant<TypeId, TypePackId, const Constraint*>;
|
||||
|
||||
struct HashBlockedConstraintId
|
||||
{
|
||||
size_t operator()(const BlockedConstraintId& bci) const;
|
||||
};
|
||||
|
||||
struct ModuleResolver;
|
||||
|
||||
struct InstantiationSignature
|
||||
{
|
||||
TypeFun fn;
|
||||
std::vector<TypeId> arguments;
|
||||
std::vector<TypePackId> packArguments;
|
||||
|
||||
bool operator==(const InstantiationSignature& rhs) const;
|
||||
bool operator!=(const InstantiationSignature& rhs) const
|
||||
{
|
||||
return !((*this) == rhs);
|
||||
}
|
||||
};
|
||||
|
||||
struct HashInstantiationSignature
|
||||
{
|
||||
size_t operator()(const InstantiationSignature& signature) const;
|
||||
};
|
||||
|
||||
struct ConstraintSolver
|
||||
{
|
||||
NotNull<TypeArena> arena;
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
InternalErrorReporter iceReporter;
|
||||
NotNull<Normalizer> normalizer;
|
||||
// The entire set of constraints that the solver is trying to resolve.
|
||||
std::vector<NotNull<Constraint>> constraints;
|
||||
NotNull<Scope> rootScope;
|
||||
ModuleName currentModuleName;
|
||||
|
||||
// Constraints that the solver has generated, rather than sourcing from the
|
||||
// scope tree.
|
||||
std::vector<std::unique_ptr<Constraint>> solverConstraints;
|
||||
|
||||
// This includes every constraint that has not been fully solved.
|
||||
// A constraint can be both blocked and unsolved, for instance.
|
||||
std::vector<NotNull<const Constraint>> unsolvedConstraints;
|
||||
|
||||
// A mapping of constraint pointer to how many things the constraint is
|
||||
// blocked on. Can be empty or 0 for constraints that are not blocked on
|
||||
// anything.
|
||||
std::unordered_map<NotNull<const Constraint>, size_t> blockedConstraints;
|
||||
// A mapping of type/pack pointers to the constraints they block.
|
||||
std::unordered_map<BlockedConstraintId, std::vector<NotNull<const Constraint>>, HashBlockedConstraintId> blocked;
|
||||
// Memoized instantiations of type aliases.
|
||||
DenseHashMap<InstantiationSignature, TypeId, HashInstantiationSignature> instantiatedAliases{{}};
|
||||
|
||||
// Recorded errors that take place within the solver.
|
||||
ErrorVec errors;
|
||||
|
||||
NotNull<ModuleResolver> moduleResolver;
|
||||
std::vector<RequireCycle> requireCycles;
|
||||
|
||||
DcrLogger* logger;
|
||||
TypeCheckLimits limits;
|
||||
|
||||
explicit ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope> rootScope, std::vector<NotNull<Constraint>> constraints,
|
||||
ModuleName moduleName, NotNull<ModuleResolver> moduleResolver, std::vector<RequireCycle> requireCycles, DcrLogger* logger,
|
||||
TypeCheckLimits limits);
|
||||
|
||||
// Randomize the order in which to dispatch constraints
|
||||
void randomize(unsigned seed);
|
||||
|
||||
/**
|
||||
* Attempts to dispatch all pending constraints and reach a type solution
|
||||
* that satisfies all of the constraints.
|
||||
**/
|
||||
void run();
|
||||
|
||||
bool isDone();
|
||||
|
||||
void finalizeModule();
|
||||
|
||||
/** Attempt to dispatch a constraint. Returns true if it was successful. If
|
||||
* tryDispatch() returns false, the constraint remains in the unsolved set
|
||||
* and will be retried later.
|
||||
*/
|
||||
bool tryDispatch(NotNull<const Constraint> c, bool force);
|
||||
|
||||
bool tryDispatch(const SubtypeConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const PackSubtypeConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const GeneralizationConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const InstantiationConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const UnaryConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const BinaryConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint);
|
||||
bool tryDispatch(const TypeAliasExpansionConstraint& c, NotNull<const Constraint> constraint);
|
||||
bool tryDispatch(const FunctionCallConstraint& c, NotNull<const Constraint> constraint);
|
||||
bool tryDispatch(const PrimitiveTypeConstraint& c, NotNull<const Constraint> constraint);
|
||||
bool tryDispatch(const HasPropConstraint& c, NotNull<const Constraint> constraint);
|
||||
bool tryDispatch(const SetPropConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const SingletonOrTopTypeConstraint& c, NotNull<const Constraint> constraint);
|
||||
bool tryDispatch(const UnpackConstraint& c, NotNull<const Constraint> constraint);
|
||||
bool tryDispatch(const RefineConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
|
||||
// for a, ... in some_table do
|
||||
// also handles __iter metamethod
|
||||
bool tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
|
||||
// for a, ... in next_function, t, ... do
|
||||
bool tryDispatchIterableFunction(
|
||||
TypeId nextTy, TypeId tableTy, TypeId firstIndexTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
|
||||
std::pair<std::vector<TypeId>, std::optional<TypeId>> lookupTableProp(
|
||||
TypeId subjectType, const std::string& propName, bool suppressSimplification = false);
|
||||
std::pair<std::vector<TypeId>, std::optional<TypeId>> lookupTableProp(
|
||||
TypeId subjectType, const std::string& propName, bool suppressSimplification, std::unordered_set<TypeId>& seen);
|
||||
|
||||
void block(NotNull<const Constraint> target, NotNull<const Constraint> constraint);
|
||||
/**
|
||||
* Block a constraint on the resolution of a Type.
|
||||
* @returns false always. This is just to allow tryDispatch to return the result of block()
|
||||
*/
|
||||
bool block(TypeId target, NotNull<const Constraint> constraint);
|
||||
bool block(TypePackId target, NotNull<const Constraint> constraint);
|
||||
|
||||
// Block on every target
|
||||
template<typename T>
|
||||
bool block(const T& targets, NotNull<const Constraint> constraint)
|
||||
{
|
||||
for (TypeId target : targets)
|
||||
block(target, constraint);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* For all constraints that are blocked on one constraint, make them block
|
||||
* on a new constraint.
|
||||
* @param source the constraint to copy blocks from.
|
||||
* @param addition the constraint that other constraints should now block on.
|
||||
*/
|
||||
void inheritBlocks(NotNull<const Constraint> source, NotNull<const Constraint> addition);
|
||||
|
||||
// Traverse the type. If any pending types are found, block the constraint
|
||||
// on them.
|
||||
//
|
||||
// Returns false if a type blocks the constraint.
|
||||
//
|
||||
// FIXME: This use of a boolean for the return result is an appalling
|
||||
// interface.
|
||||
bool blockOnPendingTypes(TypeId target, NotNull<const Constraint> constraint);
|
||||
bool blockOnPendingTypes(TypePackId target, NotNull<const Constraint> constraint);
|
||||
|
||||
void unblock(NotNull<const Constraint> progressed);
|
||||
void unblock(TypeId progressed, Location location);
|
||||
void unblock(TypePackId progressed, Location location);
|
||||
void unblock(const std::vector<TypeId>& types, Location location);
|
||||
void unblock(const std::vector<TypePackId>& packs, Location location);
|
||||
|
||||
/**
|
||||
* @returns true if the TypeId is in a blocked state.
|
||||
*/
|
||||
bool isBlocked(TypeId ty);
|
||||
|
||||
/**
|
||||
* @returns true if the TypePackId is in a blocked state.
|
||||
*/
|
||||
bool isBlocked(TypePackId tp);
|
||||
|
||||
/**
|
||||
* Returns whether the constraint is blocked on anything.
|
||||
* @param constraint the constraint to check.
|
||||
*/
|
||||
bool isBlocked(NotNull<const Constraint> constraint);
|
||||
|
||||
/**
|
||||
* Creates a new Unifier and performs a single unification operation. Commits
|
||||
* the result.
|
||||
* @param subType the sub-type to unify.
|
||||
* @param superType the super-type to unify.
|
||||
*/
|
||||
ErrorVec unify(NotNull<Scope> scope, Location location, TypeId subType, TypeId superType);
|
||||
|
||||
/**
|
||||
* Creates a new Unifier and performs a single unification operation. Commits
|
||||
* the result.
|
||||
* @param subPack the sub-type pack to unify.
|
||||
* @param superPack the super-type pack to unify.
|
||||
*/
|
||||
ErrorVec unify(NotNull<Scope> scope, Location location, TypePackId subPack, TypePackId superPack);
|
||||
|
||||
/** Pushes a new solver constraint to the solver.
|
||||
* @param cv the body of the constraint.
|
||||
**/
|
||||
NotNull<Constraint> pushConstraint(NotNull<Scope> scope, const Location& location, ConstraintV cv);
|
||||
|
||||
/**
|
||||
* Attempts to resolve a module from its module information. Returns the
|
||||
* module-level return type of the module, or the error type if one cannot
|
||||
* be found. Reports errors to the solver if the module cannot be found or
|
||||
* the require is illegal.
|
||||
* @param module the module information to look up.
|
||||
* @param location the location where the require is taking place; used for
|
||||
* error locations.
|
||||
**/
|
||||
TypeId resolveModule(const ModuleInfo& module, const Location& location);
|
||||
|
||||
void reportError(TypeErrorData&& data, const Location& location);
|
||||
void reportError(TypeError e);
|
||||
|
||||
private:
|
||||
|
||||
/** Helper used by tryDispatch(SubtypeConstraint) and
|
||||
* tryDispatch(PackSubtypeConstraint)
|
||||
*
|
||||
* Attempts to unify subTy with superTy. If doing so would require unifying
|
||||
* BlockedTypes, fail and block the constraint on those BlockedTypes.
|
||||
*
|
||||
* If unification fails, replace all free types with errorType.
|
||||
*
|
||||
* If unification succeeds, unblock every type changed by the unification.
|
||||
*/
|
||||
template <typename TID>
|
||||
bool tryUnify(NotNull<const Constraint> constraint, TID subTy, TID superTy);
|
||||
|
||||
/**
|
||||
* Bind a BlockedType to another type while taking care not to bind it to
|
||||
* itself in the case that resultTy == blockedTy. This can happen if we
|
||||
* have a tautological constraint. When it does, we must instead bind
|
||||
* blockedTy to a fresh type belonging to an appropriate scope.
|
||||
*
|
||||
* To determine which scope is appropriate, we also accept rootTy, which is
|
||||
* to be the type that contains blockedTy.
|
||||
*/
|
||||
void bindBlockedType(TypeId blockedTy, TypeId resultTy, TypeId rootTy, Location location);
|
||||
|
||||
/**
|
||||
* Marks a constraint as being blocked on a type or type pack. The constraint
|
||||
* solver will not attempt to dispatch blocked constraints until their
|
||||
* dependencies have made progress.
|
||||
* @param target the type or type pack pointer that the constraint is blocked on.
|
||||
* @param constraint the constraint to block.
|
||||
**/
|
||||
void block_(BlockedConstraintId target, NotNull<const Constraint> constraint);
|
||||
|
||||
/**
|
||||
* Informs the solver that progress has been made on a type or type pack. The
|
||||
* solver will wake up all constraints that are blocked on the type or type pack,
|
||||
* and will resume attempting to dispatch them.
|
||||
* @param progressed the type or type pack pointer that has progressed.
|
||||
**/
|
||||
void unblock_(BlockedConstraintId progressed);
|
||||
|
||||
TypeId errorRecoveryType() const;
|
||||
TypePackId errorRecoveryTypePack() const;
|
||||
|
||||
TypePackId anyifyModuleReturnTypePackGenerics(TypePackId tp);
|
||||
|
||||
void throwTimeLimitError();
|
||||
void throwUserCancelError();
|
||||
|
||||
ToStringOptions opts;
|
||||
};
|
||||
|
||||
void dump(NotNull<Scope> rootScope, struct ToStringOptions& opts);
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,36 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct Scope;
|
||||
using ScopePtr = std::shared_ptr<Scope>;
|
||||
|
||||
enum class ControlFlow
|
||||
{
|
||||
None = 0b00001,
|
||||
Returns = 0b00010,
|
||||
Throws = 0b00100,
|
||||
Break = 0b01000, // Currently unused.
|
||||
Continue = 0b10000, // Currently unused.
|
||||
};
|
||||
|
||||
inline ControlFlow operator&(ControlFlow a, ControlFlow b)
|
||||
{
|
||||
return ControlFlow(int(a) & int(b));
|
||||
}
|
||||
|
||||
inline ControlFlow operator|(ControlFlow a, ControlFlow b)
|
||||
{
|
||||
return ControlFlow(int(a) | int(b));
|
||||
}
|
||||
|
||||
inline bool matches(ControlFlow a, ControlFlow b)
|
||||
{
|
||||
return (a & b) != ControlFlow(0);
|
||||
}
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,150 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
// Do not include LValue. It should never be used here.
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Breadcrumb.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Def.h"
|
||||
#include "Luau/Symbol.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct DataFlowGraph
|
||||
{
|
||||
DataFlowGraph(DataFlowGraph&&) = default;
|
||||
DataFlowGraph& operator=(DataFlowGraph&&) = default;
|
||||
|
||||
NullableBreadcrumbId getBreadcrumb(const AstExpr* expr) const;
|
||||
|
||||
BreadcrumbId getBreadcrumb(const AstLocal* local) const;
|
||||
BreadcrumbId getBreadcrumb(const AstExprLocal* local) const;
|
||||
BreadcrumbId getBreadcrumb(const AstExprGlobal* global) const;
|
||||
|
||||
BreadcrumbId getBreadcrumb(const AstStatDeclareGlobal* global) const;
|
||||
BreadcrumbId getBreadcrumb(const AstStatDeclareFunction* func) const;
|
||||
|
||||
private:
|
||||
DataFlowGraph() = default;
|
||||
|
||||
DataFlowGraph(const DataFlowGraph&) = delete;
|
||||
DataFlowGraph& operator=(const DataFlowGraph&) = delete;
|
||||
|
||||
DefArena defs;
|
||||
BreadcrumbArena breadcrumbs;
|
||||
|
||||
DenseHashMap<const AstExpr*, NullableBreadcrumbId> astBreadcrumbs{nullptr};
|
||||
|
||||
// Sometimes we don't have the AstExprLocal* but we have AstLocal*, and sometimes we need to extract that DefId.
|
||||
DenseHashMap<const AstLocal*, NullableBreadcrumbId> localBreadcrumbs{nullptr};
|
||||
|
||||
// There's no AstStatDeclaration, and it feels useless to introduce it just to enforce an invariant in one place.
|
||||
// All keys in this maps are really only statements that ambiently declares a symbol.
|
||||
DenseHashMap<const AstStat*, NullableBreadcrumbId> declaredBreadcrumbs{nullptr};
|
||||
|
||||
friend struct DataFlowGraphBuilder;
|
||||
};
|
||||
|
||||
struct DfgScope
|
||||
{
|
||||
DfgScope* parent;
|
||||
DenseHashMap<Symbol, NullableBreadcrumbId> bindings{Symbol{}};
|
||||
DenseHashMap<const Def*, std::unordered_map<std::string, NullableBreadcrumbId>> props{nullptr};
|
||||
|
||||
NullableBreadcrumbId lookup(Symbol symbol) const;
|
||||
NullableBreadcrumbId lookup(DefId def, const std::string& key) const;
|
||||
};
|
||||
|
||||
// Currently unsound. We do not presently track the control flow of the program.
|
||||
// Additionally, we do not presently track assignments.
|
||||
struct DataFlowGraphBuilder
|
||||
{
|
||||
static DataFlowGraph build(AstStatBlock* root, NotNull<struct InternalErrorReporter> handle);
|
||||
|
||||
private:
|
||||
DataFlowGraphBuilder() = default;
|
||||
|
||||
DataFlowGraphBuilder(const DataFlowGraphBuilder&) = delete;
|
||||
DataFlowGraphBuilder& operator=(const DataFlowGraphBuilder&) = delete;
|
||||
|
||||
DataFlowGraph graph;
|
||||
NotNull<DefArena> defs{&graph.defs};
|
||||
NotNull<BreadcrumbArena> breadcrumbs{&graph.breadcrumbs};
|
||||
|
||||
struct InternalErrorReporter* handle = nullptr;
|
||||
DfgScope* moduleScope = nullptr;
|
||||
|
||||
std::vector<std::unique_ptr<DfgScope>> scopes;
|
||||
|
||||
DfgScope* childScope(DfgScope* scope);
|
||||
|
||||
void visit(DfgScope* scope, AstStatBlock* b);
|
||||
void visitBlockWithoutChildScope(DfgScope* scope, AstStatBlock* b);
|
||||
|
||||
void visit(DfgScope* scope, AstStat* s);
|
||||
void visit(DfgScope* scope, AstStatIf* i);
|
||||
void visit(DfgScope* scope, AstStatWhile* w);
|
||||
void visit(DfgScope* scope, AstStatRepeat* r);
|
||||
void visit(DfgScope* scope, AstStatBreak* b);
|
||||
void visit(DfgScope* scope, AstStatContinue* c);
|
||||
void visit(DfgScope* scope, AstStatReturn* r);
|
||||
void visit(DfgScope* scope, AstStatExpr* e);
|
||||
void visit(DfgScope* scope, AstStatLocal* l);
|
||||
void visit(DfgScope* scope, AstStatFor* f);
|
||||
void visit(DfgScope* scope, AstStatForIn* f);
|
||||
void visit(DfgScope* scope, AstStatAssign* a);
|
||||
void visit(DfgScope* scope, AstStatCompoundAssign* c);
|
||||
void visit(DfgScope* scope, AstStatFunction* f);
|
||||
void visit(DfgScope* scope, AstStatLocalFunction* l);
|
||||
void visit(DfgScope* scope, AstStatTypeAlias* t);
|
||||
void visit(DfgScope* scope, AstStatDeclareGlobal* d);
|
||||
void visit(DfgScope* scope, AstStatDeclareFunction* d);
|
||||
void visit(DfgScope* scope, AstStatDeclareClass* d);
|
||||
void visit(DfgScope* scope, AstStatError* error);
|
||||
|
||||
BreadcrumbId visitExpr(DfgScope* scope, AstExpr* e);
|
||||
BreadcrumbId visitExpr(DfgScope* scope, AstExprLocal* l);
|
||||
BreadcrumbId visitExpr(DfgScope* scope, AstExprGlobal* g);
|
||||
BreadcrumbId visitExpr(DfgScope* scope, AstExprCall* c);
|
||||
BreadcrumbId visitExpr(DfgScope* scope, AstExprIndexName* i);
|
||||
BreadcrumbId visitExpr(DfgScope* scope, AstExprIndexExpr* i);
|
||||
BreadcrumbId visitExpr(DfgScope* scope, AstExprFunction* f);
|
||||
BreadcrumbId visitExpr(DfgScope* scope, AstExprTable* t);
|
||||
BreadcrumbId visitExpr(DfgScope* scope, AstExprUnary* u);
|
||||
BreadcrumbId visitExpr(DfgScope* scope, AstExprBinary* b);
|
||||
BreadcrumbId visitExpr(DfgScope* scope, AstExprTypeAssertion* t);
|
||||
BreadcrumbId visitExpr(DfgScope* scope, AstExprIfElse* i);
|
||||
BreadcrumbId visitExpr(DfgScope* scope, AstExprInterpString* i);
|
||||
BreadcrumbId visitExpr(DfgScope* scope, AstExprError* error);
|
||||
|
||||
void visitLValue(DfgScope* scope, AstExpr* e);
|
||||
void visitLValue(DfgScope* scope, AstExprLocal* l);
|
||||
void visitLValue(DfgScope* scope, AstExprGlobal* g);
|
||||
void visitLValue(DfgScope* scope, AstExprIndexName* i);
|
||||
void visitLValue(DfgScope* scope, AstExprIndexExpr* i);
|
||||
void visitLValue(DfgScope* scope, AstExprError* e);
|
||||
|
||||
void visitType(DfgScope* scope, AstType* t);
|
||||
void visitType(DfgScope* scope, AstTypeReference* r);
|
||||
void visitType(DfgScope* scope, AstTypeTable* t);
|
||||
void visitType(DfgScope* scope, AstTypeFunction* f);
|
||||
void visitType(DfgScope* scope, AstTypeTypeof* t);
|
||||
void visitType(DfgScope* scope, AstTypeUnion* u);
|
||||
void visitType(DfgScope* scope, AstTypeIntersection* i);
|
||||
void visitType(DfgScope* scope, AstTypeError* error);
|
||||
|
||||
void visitTypePack(DfgScope* scope, AstTypePack* p);
|
||||
void visitTypePack(DfgScope* scope, AstTypePackExplicit* e);
|
||||
void visitTypePack(DfgScope* scope, AstTypePackVariadic* v);
|
||||
void visitTypePack(DfgScope* scope, AstTypePackGeneric* g);
|
||||
|
||||
void visitTypeList(DfgScope* scope, AstTypeList l);
|
||||
|
||||
void visitGenerics(DfgScope* scope, AstArray<AstGenericType> g);
|
||||
void visitGenericPacks(DfgScope* scope, AstArray<AstGenericTypePack> g);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,147 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Constraint.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct ErrorSnapshot
|
||||
{
|
||||
std::string message;
|
||||
Location location;
|
||||
};
|
||||
|
||||
struct BindingSnapshot
|
||||
{
|
||||
std::string typeId;
|
||||
std::string typeString;
|
||||
Location location;
|
||||
};
|
||||
|
||||
struct TypeBindingSnapshot
|
||||
{
|
||||
std::string typeId;
|
||||
std::string typeString;
|
||||
};
|
||||
|
||||
struct ExprTypesAtLocation
|
||||
{
|
||||
Location location;
|
||||
TypeId ty;
|
||||
std::optional<TypeId> expectedTy;
|
||||
};
|
||||
|
||||
struct AnnotationTypesAtLocation
|
||||
{
|
||||
Location location;
|
||||
TypeId resolvedTy;
|
||||
};
|
||||
|
||||
struct ConstraintGenerationLog
|
||||
{
|
||||
std::string source;
|
||||
std::vector<ErrorSnapshot> errors;
|
||||
|
||||
std::vector<ExprTypesAtLocation> exprTypeLocations;
|
||||
std::vector<AnnotationTypesAtLocation> annotationTypeLocations;
|
||||
};
|
||||
|
||||
struct ScopeSnapshot
|
||||
{
|
||||
std::unordered_map<Name, BindingSnapshot> bindings;
|
||||
std::unordered_map<Name, TypeBindingSnapshot> typeBindings;
|
||||
std::unordered_map<Name, TypeBindingSnapshot> typePackBindings;
|
||||
std::vector<ScopeSnapshot> children;
|
||||
};
|
||||
|
||||
using ConstraintBlockTarget = Variant<TypeId, TypePackId, NotNull<const Constraint>>;
|
||||
|
||||
struct ConstraintBlock
|
||||
{
|
||||
ConstraintBlockTarget target;
|
||||
std::string stringification;
|
||||
};
|
||||
|
||||
struct ConstraintSnapshot
|
||||
{
|
||||
std::string stringification;
|
||||
Location location;
|
||||
std::vector<ConstraintBlock> blocks;
|
||||
};
|
||||
|
||||
struct BoundarySnapshot
|
||||
{
|
||||
DenseHashMap<const Constraint*, ConstraintSnapshot> unsolvedConstraints{nullptr};
|
||||
ScopeSnapshot rootScope;
|
||||
DenseHashMap<const void*, std::string> typeStrings{nullptr};
|
||||
};
|
||||
|
||||
struct StepSnapshot
|
||||
{
|
||||
const Constraint* currentConstraint;
|
||||
bool forced;
|
||||
DenseHashMap<const Constraint*, ConstraintSnapshot> unsolvedConstraints{nullptr};
|
||||
ScopeSnapshot rootScope;
|
||||
DenseHashMap<const void*, std::string> typeStrings{nullptr};
|
||||
};
|
||||
|
||||
struct TypeSolveLog
|
||||
{
|
||||
BoundarySnapshot initialState;
|
||||
std::vector<StepSnapshot> stepStates;
|
||||
BoundarySnapshot finalState;
|
||||
};
|
||||
|
||||
struct TypeCheckLog
|
||||
{
|
||||
std::vector<ErrorSnapshot> errors;
|
||||
};
|
||||
|
||||
struct DcrLogger
|
||||
{
|
||||
std::string compileOutput();
|
||||
|
||||
void captureSource(std::string source);
|
||||
void captureGenerationError(const TypeError& error);
|
||||
void captureConstraintLocation(NotNull<const Constraint> constraint, Location location);
|
||||
void captureGenerationModule(const ModulePtr& module);
|
||||
|
||||
void pushBlock(NotNull<const Constraint> constraint, TypeId block);
|
||||
void pushBlock(NotNull<const Constraint> constraint, TypePackId block);
|
||||
void pushBlock(NotNull<const Constraint> constraint, NotNull<const Constraint> block);
|
||||
void popBlock(TypeId block);
|
||||
void popBlock(TypePackId block);
|
||||
void popBlock(NotNull<const Constraint> block);
|
||||
|
||||
void captureInitialSolverState(const Scope* rootScope, const std::vector<NotNull<const Constraint>>& unsolvedConstraints);
|
||||
StepSnapshot prepareStepSnapshot(
|
||||
const Scope* rootScope, NotNull<const Constraint> current, bool force, const std::vector<NotNull<const Constraint>>& unsolvedConstraints);
|
||||
void commitStepSnapshot(StepSnapshot snapshot);
|
||||
void captureFinalSolverState(const Scope* rootScope, const std::vector<NotNull<const Constraint>>& unsolvedConstraints);
|
||||
|
||||
void captureTypeCheckError(const TypeError& error);
|
||||
|
||||
private:
|
||||
ConstraintGenerationLog generationLog;
|
||||
std::unordered_map<NotNull<const Constraint>, std::vector<ConstraintBlockTarget>> constraintBlocks;
|
||||
TypeSolveLog solveLog;
|
||||
TypeCheckLog checkLog;
|
||||
|
||||
ToStringOptions opts{true};
|
||||
|
||||
std::vector<ConstraintBlock> snapshotBlocks(NotNull<const Constraint> constraint);
|
||||
void captureBoundaryState(BoundarySnapshot& target, const Scope* rootScope, const std::vector<NotNull<const Constraint>>& unsolvedConstraints);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,83 @@
|
|||
// 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/TypedAllocator.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct Def;
|
||||
using DefId = NotNull<const Def>;
|
||||
|
||||
/**
|
||||
* A cell is a "single-object" value.
|
||||
*
|
||||
* Leaky implementation note: sometimes "multiple-object" values, but none of which were interesting enough to warrant creating a phi node instead.
|
||||
* That can happen because there's no point in creating a phi node that points to either resultant in `if math.random() > 0.5 then 5 else "hello"`.
|
||||
* This might become of utmost importance if we wanted to do some backward reasoning, e.g. if `5` is taken, then `cond` must be `truthy`.
|
||||
*/
|
||||
struct Cell
|
||||
{
|
||||
};
|
||||
|
||||
/**
|
||||
* A phi node is a union of cells.
|
||||
*
|
||||
* We need this because we're statically evaluating a program, and sometimes a place may be assigned with
|
||||
* different cells, and when that happens, we need a special data type that merges in all the cells
|
||||
* that will flow into that specific place. For example, consider this simple program:
|
||||
*
|
||||
* ```
|
||||
* x-1
|
||||
* if cond() then
|
||||
* x-2 = 5
|
||||
* else
|
||||
* x-3 = "hello"
|
||||
* end
|
||||
* x-4 : {x-2, x-3}
|
||||
* ```
|
||||
*
|
||||
* At x-4, we know for a fact statically that either `5` or `"hello"` can flow into the variable `x` after the branch, but
|
||||
* we cannot make any definitive decisions about which one, so we just take in both.
|
||||
*/
|
||||
struct Phi
|
||||
{
|
||||
std::vector<DefId> operands;
|
||||
};
|
||||
|
||||
/**
|
||||
* We statically approximate a value at runtime using a symbolic value, which we call a Def.
|
||||
*
|
||||
* DataFlowGraphBuilder will allocate these defs as a stand-in for some Luau values, and bind them to places that
|
||||
* can hold a Luau value, and then observes how those defs will commute as it statically evaluate the program.
|
||||
*
|
||||
* It must also be noted that defs are a cyclic graph, so it is not safe to recursively traverse into it expecting it to terminate.
|
||||
*/
|
||||
struct Def
|
||||
{
|
||||
using V = Variant<struct Cell, struct Phi>;
|
||||
|
||||
V v;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
const T* get(DefId def)
|
||||
{
|
||||
return get_if<T>(&def->v);
|
||||
}
|
||||
|
||||
struct DefArena
|
||||
{
|
||||
TypedAllocator<Def> allocator;
|
||||
|
||||
DefId freshCell();
|
||||
// TODO: implement once we have cases where we need to merge in definitions
|
||||
// DefId phi(const std::vector<DefId>& defs);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,641 @@
|
|||
// 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 <functional>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct DenseHashPointer
|
||||
{
|
||||
size_t operator()(const void* key) const
|
||||
{
|
||||
return (uintptr_t(key) >> 4) ^ (uintptr_t(key) >> 9);
|
||||
}
|
||||
};
|
||||
|
||||
// Internal implementation of DenseHashSet and DenseHashMap
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
using DenseHashDefault = std::conditional_t<std::is_pointer_v<T>, DenseHashPointer, std::hash<T>>;
|
||||
|
||||
template<typename Key, typename Item, typename MutableItem, typename ItemInterface, typename Hash, typename Eq>
|
||||
class DenseHashTable
|
||||
{
|
||||
public:
|
||||
class const_iterator;
|
||||
class iterator;
|
||||
|
||||
explicit DenseHashTable(const Key& empty_key, size_t buckets = 0)
|
||||
: data(nullptr)
|
||||
, capacity(0)
|
||||
, count(0)
|
||||
, empty_key(empty_key)
|
||||
{
|
||||
// validate that equality operator is at least somewhat functional
|
||||
LUAU_ASSERT(eq(empty_key, empty_key));
|
||||
// buckets has to be power-of-two or zero
|
||||
LUAU_ASSERT((buckets & (buckets - 1)) == 0);
|
||||
|
||||
if (buckets)
|
||||
{
|
||||
data = static_cast<Item*>(::operator new(sizeof(Item) * buckets));
|
||||
capacity = buckets;
|
||||
|
||||
ItemInterface::fill(data, buckets, empty_key);
|
||||
}
|
||||
}
|
||||
|
||||
~DenseHashTable()
|
||||
{
|
||||
if (data)
|
||||
destroy();
|
||||
}
|
||||
|
||||
DenseHashTable(const DenseHashTable& other)
|
||||
: data(nullptr)
|
||||
, capacity(0)
|
||||
, count(other.count)
|
||||
, empty_key(other.empty_key)
|
||||
{
|
||||
if (other.capacity)
|
||||
{
|
||||
data = static_cast<Item*>(::operator new(sizeof(Item) * other.capacity));
|
||||
|
||||
for (size_t i = 0; i < other.capacity; ++i)
|
||||
{
|
||||
new (&data[i]) Item(other.data[i]);
|
||||
capacity = i + 1; // if Item copy throws, capacity will note the number of initialized objects for destroy() to clean up
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DenseHashTable(DenseHashTable&& other)
|
||||
: data(other.data)
|
||||
, capacity(other.capacity)
|
||||
, count(other.count)
|
||||
, empty_key(other.empty_key)
|
||||
{
|
||||
other.data = nullptr;
|
||||
other.capacity = 0;
|
||||
other.count = 0;
|
||||
}
|
||||
|
||||
DenseHashTable& operator=(DenseHashTable&& other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
if (data)
|
||||
destroy();
|
||||
|
||||
data = other.data;
|
||||
capacity = other.capacity;
|
||||
count = other.count;
|
||||
empty_key = other.empty_key;
|
||||
|
||||
other.data = nullptr;
|
||||
other.capacity = 0;
|
||||
other.count = 0;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
DenseHashTable& operator=(const DenseHashTable& other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
DenseHashTable copy(other);
|
||||
*this = std::move(copy);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
if (capacity > 32)
|
||||
{
|
||||
destroy();
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemInterface::destroy(data, capacity);
|
||||
ItemInterface::fill(data, capacity, empty_key);
|
||||
}
|
||||
|
||||
count = 0;
|
||||
}
|
||||
|
||||
void destroy()
|
||||
{
|
||||
ItemInterface::destroy(data, capacity);
|
||||
|
||||
::operator delete(data);
|
||||
data = nullptr;
|
||||
|
||||
capacity = 0;
|
||||
}
|
||||
|
||||
Item* insert_unsafe(const Key& key)
|
||||
{
|
||||
// It is invalid to insert empty_key into the table since it acts as a "entry does not exist" marker
|
||||
LUAU_ASSERT(!eq(key, empty_key));
|
||||
|
||||
size_t hashmod = capacity - 1;
|
||||
size_t bucket = hasher(key) & hashmod;
|
||||
|
||||
for (size_t probe = 0; probe <= hashmod; ++probe)
|
||||
{
|
||||
Item& probe_item = data[bucket];
|
||||
|
||||
// Element does not exist, insert here
|
||||
if (eq(ItemInterface::getKey(probe_item), empty_key))
|
||||
{
|
||||
ItemInterface::setKey(probe_item, key);
|
||||
count++;
|
||||
return &probe_item;
|
||||
}
|
||||
|
||||
// Element already exists
|
||||
if (eq(ItemInterface::getKey(probe_item), key))
|
||||
{
|
||||
return &probe_item;
|
||||
}
|
||||
|
||||
// Hash collision, quadratic probing
|
||||
bucket = (bucket + probe + 1) & hashmod;
|
||||
}
|
||||
|
||||
// Hash table is full - this should not happen
|
||||
LUAU_ASSERT(false);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const Item* find(const Key& key) const
|
||||
{
|
||||
if (count == 0)
|
||||
return 0;
|
||||
if (eq(key, empty_key))
|
||||
return 0;
|
||||
|
||||
size_t hashmod = capacity - 1;
|
||||
size_t bucket = hasher(key) & hashmod;
|
||||
|
||||
for (size_t probe = 0; probe <= hashmod; ++probe)
|
||||
{
|
||||
const Item& probe_item = data[bucket];
|
||||
|
||||
// Element exists
|
||||
if (eq(ItemInterface::getKey(probe_item), key))
|
||||
return &probe_item;
|
||||
|
||||
// Element does not exist
|
||||
if (eq(ItemInterface::getKey(probe_item), empty_key))
|
||||
return NULL;
|
||||
|
||||
// Hash collision, quadratic probing
|
||||
bucket = (bucket + probe + 1) & hashmod;
|
||||
}
|
||||
|
||||
// Hash table is full - this should not happen
|
||||
LUAU_ASSERT(false);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void rehash()
|
||||
{
|
||||
size_t newsize = capacity == 0 ? 16 : capacity * 2;
|
||||
|
||||
DenseHashTable newtable(empty_key, newsize);
|
||||
|
||||
for (size_t i = 0; i < capacity; ++i)
|
||||
{
|
||||
const Key& key = ItemInterface::getKey(data[i]);
|
||||
|
||||
if (!eq(key, empty_key))
|
||||
{
|
||||
Item* item = newtable.insert_unsafe(key);
|
||||
*item = std::move(data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
LUAU_ASSERT(count == newtable.count);
|
||||
|
||||
std::swap(data, newtable.data);
|
||||
std::swap(capacity, newtable.capacity);
|
||||
}
|
||||
|
||||
void rehash_if_full(const Key& key)
|
||||
{
|
||||
if (count >= capacity * 3 / 4 && !find(key))
|
||||
{
|
||||
rehash();
|
||||
}
|
||||
}
|
||||
|
||||
const_iterator begin() const
|
||||
{
|
||||
size_t start = 0;
|
||||
|
||||
while (start < capacity && eq(ItemInterface::getKey(data[start]), empty_key))
|
||||
start++;
|
||||
|
||||
return const_iterator(this, start);
|
||||
}
|
||||
|
||||
const_iterator end() const
|
||||
{
|
||||
return const_iterator(this, capacity);
|
||||
}
|
||||
|
||||
iterator begin()
|
||||
{
|
||||
size_t start = 0;
|
||||
|
||||
while (start < capacity && eq(ItemInterface::getKey(data[start]), empty_key))
|
||||
start++;
|
||||
|
||||
return iterator(this, start);
|
||||
}
|
||||
|
||||
iterator end()
|
||||
{
|
||||
return iterator(this, capacity);
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return count;
|
||||
}
|
||||
|
||||
class const_iterator
|
||||
{
|
||||
public:
|
||||
using value_type = Item;
|
||||
using reference = Item&;
|
||||
using pointer = Item*;
|
||||
using iterator = pointer;
|
||||
using difference_type = size_t;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
const_iterator()
|
||||
: set(0)
|
||||
, index(0)
|
||||
{
|
||||
}
|
||||
|
||||
const_iterator(const DenseHashTable<Key, Item, MutableItem, ItemInterface, Hash, Eq>* set, size_t index)
|
||||
: set(set)
|
||||
, index(index)
|
||||
{
|
||||
}
|
||||
|
||||
const Item& operator*() const
|
||||
{
|
||||
return set->data[index];
|
||||
}
|
||||
|
||||
const Item* operator->() const
|
||||
{
|
||||
return &set->data[index];
|
||||
}
|
||||
|
||||
bool operator==(const const_iterator& other) const
|
||||
{
|
||||
return set == other.set && index == other.index;
|
||||
}
|
||||
|
||||
bool operator!=(const const_iterator& other) const
|
||||
{
|
||||
return set != other.set || index != other.index;
|
||||
}
|
||||
|
||||
const_iterator& operator++()
|
||||
{
|
||||
size_t size = set->capacity;
|
||||
|
||||
do
|
||||
{
|
||||
index++;
|
||||
} while (index < size && set->eq(ItemInterface::getKey(set->data[index]), set->empty_key));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
const_iterator operator++(int)
|
||||
{
|
||||
const_iterator res = *this;
|
||||
++*this;
|
||||
return res;
|
||||
}
|
||||
|
||||
private:
|
||||
const DenseHashTable<Key, Item, MutableItem, ItemInterface, Hash, Eq>* set;
|
||||
size_t index;
|
||||
};
|
||||
|
||||
class iterator
|
||||
{
|
||||
public:
|
||||
iterator()
|
||||
: set(0)
|
||||
, index(0)
|
||||
{
|
||||
}
|
||||
|
||||
iterator(DenseHashTable<Key, Item, MutableItem, ItemInterface, Hash, Eq>* set, size_t index)
|
||||
: set(set)
|
||||
, index(index)
|
||||
{
|
||||
}
|
||||
|
||||
MutableItem& operator*() const
|
||||
{
|
||||
return *reinterpret_cast<MutableItem*>(&set->data[index]);
|
||||
}
|
||||
|
||||
MutableItem* operator->() const
|
||||
{
|
||||
return reinterpret_cast<MutableItem*>(&set->data[index]);
|
||||
}
|
||||
|
||||
bool operator==(const iterator& other) const
|
||||
{
|
||||
return set == other.set && index == other.index;
|
||||
}
|
||||
|
||||
bool operator!=(const iterator& other) const
|
||||
{
|
||||
return set != other.set || index != other.index;
|
||||
}
|
||||
|
||||
iterator& operator++()
|
||||
{
|
||||
size_t size = set->capacity;
|
||||
|
||||
do
|
||||
{
|
||||
index++;
|
||||
} while (index < size && set->eq(ItemInterface::getKey(set->data[index]), set->empty_key));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator operator++(int)
|
||||
{
|
||||
iterator res = *this;
|
||||
++*this;
|
||||
return res;
|
||||
}
|
||||
|
||||
private:
|
||||
DenseHashTable<Key, Item, MutableItem, ItemInterface, Hash, Eq>* set;
|
||||
size_t index;
|
||||
};
|
||||
|
||||
private:
|
||||
Item* data;
|
||||
size_t capacity;
|
||||
size_t count;
|
||||
Key empty_key;
|
||||
Hash hasher;
|
||||
Eq eq;
|
||||
};
|
||||
|
||||
template<typename Key>
|
||||
struct ItemInterfaceSet
|
||||
{
|
||||
static const Key& getKey(const Key& item)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
|
||||
static void setKey(Key& item, const Key& key)
|
||||
{
|
||||
item = key;
|
||||
}
|
||||
|
||||
static void fill(Key* data, size_t count, const Key& key)
|
||||
{
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
new (&data[i]) Key(key);
|
||||
}
|
||||
|
||||
static void destroy(Key* data, size_t count)
|
||||
{
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
data[i].~Key();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Key, typename Value>
|
||||
struct ItemInterfaceMap
|
||||
{
|
||||
static const Key& getKey(const std::pair<Key, Value>& item)
|
||||
{
|
||||
return item.first;
|
||||
}
|
||||
|
||||
static void setKey(std::pair<Key, Value>& item, const Key& key)
|
||||
{
|
||||
item.first = key;
|
||||
}
|
||||
|
||||
static void fill(std::pair<Key, Value>* data, size_t count, const Key& key)
|
||||
{
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
new (&data[i].first) Key(key);
|
||||
new (&data[i].second) Value();
|
||||
}
|
||||
}
|
||||
|
||||
static void destroy(std::pair<Key, Value>* data, size_t count)
|
||||
{
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
data[i].first.~Key();
|
||||
data[i].second.~Value();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// This is a faster alternative of unordered_set, but it does not implement the same interface (i.e. it does not support erasing)
|
||||
template<typename Key, typename Hash = detail::DenseHashDefault<Key>, typename Eq = std::equal_to<Key>>
|
||||
class DenseHashSet
|
||||
{
|
||||
typedef detail::DenseHashTable<Key, Key, Key, detail::ItemInterfaceSet<Key>, Hash, Eq> Impl;
|
||||
Impl impl;
|
||||
|
||||
public:
|
||||
typedef typename Impl::const_iterator const_iterator;
|
||||
typedef typename Impl::iterator iterator;
|
||||
|
||||
explicit DenseHashSet(const Key& empty_key, size_t buckets = 0)
|
||||
: impl(empty_key, buckets)
|
||||
{
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
impl.clear();
|
||||
}
|
||||
|
||||
const Key& insert(const Key& key)
|
||||
{
|
||||
impl.rehash_if_full(key);
|
||||
return *impl.insert_unsafe(key);
|
||||
}
|
||||
|
||||
const Key* find(const Key& key) const
|
||||
{
|
||||
return impl.find(key);
|
||||
}
|
||||
|
||||
bool contains(const Key& key) const
|
||||
{
|
||||
return impl.find(key) != 0;
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return impl.size();
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return impl.size() == 0;
|
||||
}
|
||||
|
||||
const_iterator begin() const
|
||||
{
|
||||
return impl.begin();
|
||||
}
|
||||
|
||||
const_iterator end() const
|
||||
{
|
||||
return impl.end();
|
||||
}
|
||||
|
||||
iterator begin()
|
||||
{
|
||||
return impl.begin();
|
||||
}
|
||||
|
||||
iterator end()
|
||||
{
|
||||
return impl.end();
|
||||
}
|
||||
};
|
||||
|
||||
// This is a faster alternative of unordered_map, but it does not implement the same interface (i.e. it does not support erasing and has
|
||||
// contains() instead of find())
|
||||
template<typename Key, typename Value, typename Hash = detail::DenseHashDefault<Key>, typename Eq = std::equal_to<Key>>
|
||||
class DenseHashMap
|
||||
{
|
||||
typedef detail::DenseHashTable<Key, std::pair<Key, Value>, std::pair<const Key, Value>, detail::ItemInterfaceMap<Key, Value>, Hash, Eq> Impl;
|
||||
Impl impl;
|
||||
|
||||
public:
|
||||
typedef typename Impl::const_iterator const_iterator;
|
||||
typedef typename Impl::iterator iterator;
|
||||
|
||||
explicit DenseHashMap(const Key& empty_key, size_t buckets = 0)
|
||||
: impl(empty_key, buckets)
|
||||
{
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
impl.clear();
|
||||
}
|
||||
|
||||
// Note: this reference is invalidated by any insert operation (i.e. operator[])
|
||||
Value& operator[](const Key& key)
|
||||
{
|
||||
impl.rehash_if_full(key);
|
||||
return impl.insert_unsafe(key)->second;
|
||||
}
|
||||
|
||||
// Note: this pointer is invalidated by any insert operation (i.e. operator[])
|
||||
const Value* find(const Key& key) const
|
||||
{
|
||||
const std::pair<Key, Value>* result = impl.find(key);
|
||||
|
||||
return result ? &result->second : NULL;
|
||||
}
|
||||
|
||||
// Note: this pointer is invalidated by any insert operation (i.e. operator[])
|
||||
Value* find(const Key& key)
|
||||
{
|
||||
const std::pair<Key, Value>* result = impl.find(key);
|
||||
|
||||
return result ? const_cast<Value*>(&result->second) : NULL;
|
||||
}
|
||||
|
||||
bool contains(const Key& key) const
|
||||
{
|
||||
return impl.find(key) != 0;
|
||||
}
|
||||
|
||||
std::pair<Value&, bool> try_insert(const Key& key, const Value& value)
|
||||
{
|
||||
impl.rehash_if_full(key);
|
||||
|
||||
size_t before = impl.size();
|
||||
std::pair<Key, Value>* slot = impl.insert_unsafe(key);
|
||||
|
||||
// Value is fresh if container count has increased
|
||||
bool fresh = impl.size() > before;
|
||||
|
||||
if (fresh)
|
||||
slot->second = value;
|
||||
|
||||
return std::make_pair(std::ref(slot->second), fresh);
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return impl.size();
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return impl.size() == 0;
|
||||
}
|
||||
|
||||
const_iterator begin() const
|
||||
{
|
||||
return impl.begin();
|
||||
}
|
||||
|
||||
const_iterator end() const
|
||||
{
|
||||
return impl.end();
|
||||
}
|
||||
|
||||
iterator begin()
|
||||
{
|
||||
return impl.begin();
|
||||
}
|
||||
|
||||
iterator end()
|
||||
{
|
||||
return impl.end();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,198 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/UnifierSharedState.h"
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
struct DiffPathNode
|
||||
{
|
||||
// TODO: consider using Variants to simplify toString implementation
|
||||
enum Kind
|
||||
{
|
||||
TableProperty,
|
||||
FunctionArgument,
|
||||
FunctionReturn,
|
||||
Union,
|
||||
Intersection,
|
||||
Negation,
|
||||
};
|
||||
Kind kind;
|
||||
// non-null when TableProperty
|
||||
std::optional<Name> tableProperty;
|
||||
// non-null when FunctionArgument (unless variadic arg), FunctionReturn (unless variadic arg), Union, or Intersection (i.e. anonymous fields)
|
||||
std::optional<size_t> index;
|
||||
|
||||
/**
|
||||
* Do not use for leaf nodes
|
||||
*/
|
||||
DiffPathNode(Kind kind)
|
||||
: kind(kind)
|
||||
{
|
||||
}
|
||||
|
||||
DiffPathNode(Kind kind, std::optional<Name> tableProperty, std::optional<size_t> index)
|
||||
: kind(kind)
|
||||
, tableProperty(tableProperty)
|
||||
, index(index)
|
||||
{
|
||||
}
|
||||
|
||||
std::string toString() const;
|
||||
|
||||
static DiffPathNode constructWithTableProperty(Name tableProperty);
|
||||
|
||||
static DiffPathNode constructWithKindAndIndex(Kind kind, size_t index);
|
||||
|
||||
static DiffPathNode constructWithKind(Kind kind);
|
||||
};
|
||||
|
||||
struct DiffPathNodeLeaf
|
||||
{
|
||||
std::optional<TypeId> ty;
|
||||
std::optional<Name> tableProperty;
|
||||
std::optional<int> minLength;
|
||||
bool isVariadic;
|
||||
// TODO: Rename to anonymousIndex, for both union and Intersection
|
||||
std::optional<size_t> unionIndex;
|
||||
DiffPathNodeLeaf(
|
||||
std::optional<TypeId> ty, std::optional<Name> tableProperty, std::optional<int> minLength, bool isVariadic, std::optional<size_t> unionIndex)
|
||||
: ty(ty)
|
||||
, tableProperty(tableProperty)
|
||||
, minLength(minLength)
|
||||
, isVariadic(isVariadic)
|
||||
, unionIndex(unionIndex)
|
||||
{
|
||||
}
|
||||
|
||||
static DiffPathNodeLeaf detailsNormal(TypeId ty);
|
||||
|
||||
static DiffPathNodeLeaf detailsTableProperty(TypeId ty, Name tableProperty);
|
||||
|
||||
static DiffPathNodeLeaf detailsUnionIndex(TypeId ty, size_t index);
|
||||
|
||||
static DiffPathNodeLeaf detailsLength(int minLength, bool isVariadic);
|
||||
|
||||
static DiffPathNodeLeaf nullopts();
|
||||
};
|
||||
|
||||
struct DiffPath
|
||||
{
|
||||
std::vector<DiffPathNode> path;
|
||||
|
||||
std::string toString(bool prependDot) const;
|
||||
};
|
||||
struct DiffError
|
||||
{
|
||||
enum Kind
|
||||
{
|
||||
Normal,
|
||||
MissingTableProperty,
|
||||
MissingUnionMember,
|
||||
MissingIntersectionMember,
|
||||
IncompatibleGeneric,
|
||||
LengthMismatchInFnArgs,
|
||||
LengthMismatchInFnRets,
|
||||
};
|
||||
Kind kind;
|
||||
|
||||
DiffPath diffPath;
|
||||
DiffPathNodeLeaf left;
|
||||
DiffPathNodeLeaf right;
|
||||
|
||||
std::string leftRootName;
|
||||
std::string rightRootName;
|
||||
|
||||
DiffError(Kind kind, DiffPathNodeLeaf left, DiffPathNodeLeaf right, std::string leftRootName, std::string rightRootName)
|
||||
: kind(kind)
|
||||
, left(left)
|
||||
, right(right)
|
||||
, leftRootName(leftRootName)
|
||||
, rightRootName(rightRootName)
|
||||
{
|
||||
checkValidInitialization(left, right);
|
||||
}
|
||||
DiffError(Kind kind, DiffPath diffPath, DiffPathNodeLeaf left, DiffPathNodeLeaf right, std::string leftRootName, std::string rightRootName)
|
||||
: kind(kind)
|
||||
, diffPath(diffPath)
|
||||
, left(left)
|
||||
, right(right)
|
||||
, leftRootName(leftRootName)
|
||||
, rightRootName(rightRootName)
|
||||
{
|
||||
checkValidInitialization(left, right);
|
||||
}
|
||||
|
||||
std::string toString(bool multiLine = false) const;
|
||||
|
||||
private:
|
||||
std::string toStringALeaf(std::string rootName, const DiffPathNodeLeaf& leaf, const DiffPathNodeLeaf& otherLeaf, bool multiLine) const;
|
||||
void checkValidInitialization(const DiffPathNodeLeaf& left, const DiffPathNodeLeaf& right);
|
||||
void checkNonMissingPropertyLeavesHaveNulloptTableProperty() const;
|
||||
};
|
||||
|
||||
struct DifferResult
|
||||
{
|
||||
std::optional<DiffError> diffError;
|
||||
|
||||
DifferResult() {}
|
||||
DifferResult(DiffError diffError)
|
||||
: diffError(diffError)
|
||||
{
|
||||
}
|
||||
|
||||
void wrapDiffPath(DiffPathNode node);
|
||||
};
|
||||
struct DifferEnvironment
|
||||
{
|
||||
TypeId rootLeft;
|
||||
TypeId rootRight;
|
||||
std::optional<std::string> externalSymbolLeft;
|
||||
std::optional<std::string> externalSymbolRight;
|
||||
DenseHashMap<TypeId, TypeId> genericMatchedPairs;
|
||||
DenseHashMap<TypePackId, TypePackId> genericTpMatchedPairs;
|
||||
|
||||
DifferEnvironment(
|
||||
TypeId rootLeft, TypeId rootRight, std::optional<std::string> externalSymbolLeft, std::optional<std::string> externalSymbolRight)
|
||||
: rootLeft(rootLeft)
|
||||
, rootRight(rootRight)
|
||||
, externalSymbolLeft(externalSymbolLeft)
|
||||
, externalSymbolRight(externalSymbolRight)
|
||||
, genericMatchedPairs(nullptr)
|
||||
, genericTpMatchedPairs(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
bool isProvenEqual(TypeId left, TypeId right) const;
|
||||
bool isAssumedEqual(TypeId left, TypeId right) const;
|
||||
void recordProvenEqual(TypeId left, TypeId right);
|
||||
void pushVisiting(TypeId left, TypeId right);
|
||||
void popVisiting();
|
||||
std::vector<std::pair<TypeId, TypeId>>::const_reverse_iterator visitingBegin() const;
|
||||
std::vector<std::pair<TypeId, TypeId>>::const_reverse_iterator visitingEnd() const;
|
||||
std::string getDevFixFriendlyNameLeft() const;
|
||||
std::string getDevFixFriendlyNameRight() const;
|
||||
|
||||
private:
|
||||
// TODO: consider using DenseHashSet
|
||||
std::unordered_set<std::pair<TypeId, TypeId>, TypeIdPairHash> provenEqual;
|
||||
// Ancestors of current types
|
||||
std::unordered_set<std::pair<TypeId, TypeId>, TypeIdPairHash> visiting;
|
||||
std::vector<std::pair<TypeId, TypeId>> visitingStack;
|
||||
};
|
||||
DifferResult diff(TypeId ty1, TypeId ty2);
|
||||
DifferResult diffWithSymbols(TypeId ty1, TypeId ty2, std::optional<std::string> symbol1, std::optional<std::string> symbol2);
|
||||
|
||||
/**
|
||||
* True if ty is a "simple" type, i.e. cannot contain types.
|
||||
* string, number, boolean are simple types.
|
||||
* function and table are not simple types.
|
||||
*/
|
||||
bool isSimple(TypeId ty);
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,63 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct FunctionDocumentation;
|
||||
struct TableDocumentation;
|
||||
struct OverloadedFunctionDocumentation;
|
||||
struct BasicDocumentation;
|
||||
|
||||
using Documentation = Luau::Variant<BasicDocumentation, FunctionDocumentation, TableDocumentation, OverloadedFunctionDocumentation>;
|
||||
using DocumentationSymbol = std::string;
|
||||
|
||||
struct BasicDocumentation
|
||||
{
|
||||
std::string documentation;
|
||||
std::string learnMoreLink;
|
||||
std::string codeSample;
|
||||
};
|
||||
|
||||
struct FunctionParameterDocumentation
|
||||
{
|
||||
std::string name;
|
||||
DocumentationSymbol documentation;
|
||||
};
|
||||
|
||||
// Represents documentation for anything callable. This could be a method or a
|
||||
// callback or a free function.
|
||||
struct FunctionDocumentation
|
||||
{
|
||||
std::string documentation;
|
||||
std::vector<FunctionParameterDocumentation> parameters;
|
||||
std::vector<DocumentationSymbol> returns;
|
||||
std::string learnMoreLink;
|
||||
std::string codeSample;
|
||||
};
|
||||
|
||||
struct OverloadedFunctionDocumentation
|
||||
{
|
||||
// This is a map of function signature to overload symbol name.
|
||||
Luau::DenseHashMap<std::string, DocumentationSymbol> overloads;
|
||||
};
|
||||
|
||||
// Represents documentation for a table-like item, meaning "anything with keys".
|
||||
// This could be a table or a class.
|
||||
struct TableDocumentation
|
||||
{
|
||||
std::string documentation;
|
||||
Luau::DenseHashMap<std::string, DocumentationSymbol> keys;
|
||||
std::string learnMoreLink;
|
||||
std::string codeSample;
|
||||
};
|
||||
|
||||
using DocumentationDatabase = Luau::DenseHashMap<DocumentationSymbol, Documentation>;
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,473 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct FileResolver;
|
||||
struct TypeArena;
|
||||
struct TypeError;
|
||||
|
||||
struct TypeMismatch
|
||||
{
|
||||
enum Context
|
||||
{
|
||||
CovariantContext,
|
||||
InvariantContext
|
||||
};
|
||||
|
||||
TypeMismatch() = default;
|
||||
TypeMismatch(TypeId wantedType, TypeId givenType);
|
||||
TypeMismatch(TypeId wantedType, TypeId givenType, std::string reason);
|
||||
TypeMismatch(TypeId wantedType, TypeId givenType, std::string reason, std::optional<TypeError> error);
|
||||
|
||||
TypeMismatch(TypeId wantedType, TypeId givenType, Context context);
|
||||
TypeMismatch(TypeId wantedType, TypeId givenType, std::string reason, Context context);
|
||||
TypeMismatch(TypeId wantedType, TypeId givenType, std::string reason, std::optional<TypeError> error, Context context);
|
||||
|
||||
TypeId wantedType = nullptr;
|
||||
TypeId givenType = nullptr;
|
||||
Context context = CovariantContext;
|
||||
|
||||
std::string reason;
|
||||
std::shared_ptr<TypeError> error;
|
||||
|
||||
bool operator==(const TypeMismatch& rhs) const;
|
||||
};
|
||||
|
||||
struct UnknownSymbol
|
||||
{
|
||||
enum Context
|
||||
{
|
||||
Binding,
|
||||
Type,
|
||||
};
|
||||
Name name;
|
||||
Context context;
|
||||
|
||||
bool operator==(const UnknownSymbol& rhs) const;
|
||||
};
|
||||
|
||||
struct UnknownProperty
|
||||
{
|
||||
TypeId table;
|
||||
Name key;
|
||||
|
||||
bool operator==(const UnknownProperty& rhs) const;
|
||||
};
|
||||
|
||||
struct NotATable
|
||||
{
|
||||
TypeId ty;
|
||||
|
||||
bool operator==(const NotATable& rhs) const;
|
||||
};
|
||||
|
||||
struct CannotExtendTable
|
||||
{
|
||||
enum Context
|
||||
{
|
||||
Property,
|
||||
Indexer,
|
||||
Metatable
|
||||
};
|
||||
TypeId tableType;
|
||||
Context context;
|
||||
Name prop;
|
||||
|
||||
bool operator==(const CannotExtendTable& rhs) const;
|
||||
};
|
||||
|
||||
struct OnlyTablesCanHaveMethods
|
||||
{
|
||||
TypeId tableType;
|
||||
|
||||
bool operator==(const OnlyTablesCanHaveMethods& rhs) const;
|
||||
};
|
||||
|
||||
struct DuplicateTypeDefinition
|
||||
{
|
||||
Name name;
|
||||
std::optional<Location> previousLocation;
|
||||
|
||||
bool operator==(const DuplicateTypeDefinition& rhs) const;
|
||||
};
|
||||
|
||||
struct CountMismatch
|
||||
{
|
||||
enum Context
|
||||
{
|
||||
Arg,
|
||||
FunctionResult,
|
||||
ExprListResult,
|
||||
Return,
|
||||
};
|
||||
size_t expected;
|
||||
std::optional<size_t> maximum;
|
||||
size_t actual;
|
||||
Context context = Arg;
|
||||
bool isVariadic = false;
|
||||
std::string function;
|
||||
|
||||
bool operator==(const CountMismatch& rhs) const;
|
||||
};
|
||||
|
||||
struct FunctionDoesNotTakeSelf
|
||||
{
|
||||
bool operator==(const FunctionDoesNotTakeSelf& rhs) const;
|
||||
};
|
||||
|
||||
struct FunctionRequiresSelf
|
||||
{
|
||||
bool operator==(const FunctionRequiresSelf& rhs) const;
|
||||
};
|
||||
|
||||
struct OccursCheckFailed
|
||||
{
|
||||
bool operator==(const OccursCheckFailed& rhs) const;
|
||||
};
|
||||
|
||||
struct UnknownRequire
|
||||
{
|
||||
std::string modulePath;
|
||||
|
||||
bool operator==(const UnknownRequire& rhs) const;
|
||||
};
|
||||
|
||||
struct IncorrectGenericParameterCount
|
||||
{
|
||||
Name name;
|
||||
TypeFun typeFun;
|
||||
size_t actualParameters;
|
||||
size_t actualPackParameters;
|
||||
|
||||
bool operator==(const IncorrectGenericParameterCount& rhs) const;
|
||||
};
|
||||
|
||||
struct SyntaxError
|
||||
{
|
||||
std::string message;
|
||||
|
||||
bool operator==(const SyntaxError& rhs) const;
|
||||
};
|
||||
|
||||
struct CodeTooComplex
|
||||
{
|
||||
bool operator==(const CodeTooComplex&) const;
|
||||
};
|
||||
|
||||
struct UnificationTooComplex
|
||||
{
|
||||
bool operator==(const UnificationTooComplex&) const;
|
||||
};
|
||||
|
||||
// Could easily be folded into UnknownProperty with an extra field, std::set<Name> candidates.
|
||||
// But for telemetry purposes, we want to have this be a distinct variant.
|
||||
struct UnknownPropButFoundLikeProp
|
||||
{
|
||||
TypeId table;
|
||||
Name key;
|
||||
std::set<Name> candidates;
|
||||
|
||||
bool operator==(const UnknownPropButFoundLikeProp& rhs) const;
|
||||
};
|
||||
|
||||
struct GenericError
|
||||
{
|
||||
std::string message;
|
||||
|
||||
bool operator==(const GenericError& rhs) const;
|
||||
};
|
||||
|
||||
struct InternalError
|
||||
{
|
||||
std::string message;
|
||||
|
||||
bool operator==(const InternalError& rhs) const;
|
||||
};
|
||||
|
||||
struct CannotCallNonFunction
|
||||
{
|
||||
TypeId ty;
|
||||
|
||||
bool operator==(const CannotCallNonFunction& rhs) const;
|
||||
};
|
||||
|
||||
struct ExtraInformation
|
||||
{
|
||||
std::string message;
|
||||
bool operator==(const ExtraInformation& rhs) const;
|
||||
};
|
||||
|
||||
struct DeprecatedApiUsed
|
||||
{
|
||||
std::string symbol;
|
||||
std::string useInstead;
|
||||
bool operator==(const DeprecatedApiUsed& rhs) const;
|
||||
};
|
||||
|
||||
struct ModuleHasCyclicDependency
|
||||
{
|
||||
std::vector<ModuleName> cycle;
|
||||
bool operator==(const ModuleHasCyclicDependency& rhs) const;
|
||||
};
|
||||
|
||||
struct FunctionExitsWithoutReturning
|
||||
{
|
||||
TypePackId expectedReturnType;
|
||||
bool operator==(const FunctionExitsWithoutReturning& rhs) const;
|
||||
};
|
||||
|
||||
struct IllegalRequire
|
||||
{
|
||||
std::string moduleName;
|
||||
std::string reason;
|
||||
|
||||
bool operator==(const IllegalRequire& rhs) const;
|
||||
};
|
||||
|
||||
struct MissingProperties
|
||||
{
|
||||
enum Context
|
||||
{
|
||||
Missing,
|
||||
Extra
|
||||
};
|
||||
TypeId superType;
|
||||
TypeId subType;
|
||||
std::vector<Name> properties;
|
||||
Context context = Missing;
|
||||
|
||||
bool operator==(const MissingProperties& rhs) const;
|
||||
};
|
||||
|
||||
struct DuplicateGenericParameter
|
||||
{
|
||||
std::string parameterName;
|
||||
|
||||
bool operator==(const DuplicateGenericParameter& rhs) const;
|
||||
};
|
||||
|
||||
struct CannotInferBinaryOperation
|
||||
{
|
||||
enum OpKind
|
||||
{
|
||||
Operation,
|
||||
Comparison,
|
||||
};
|
||||
|
||||
AstExprBinary::Op op;
|
||||
std::optional<std::string> suggestedToAnnotate;
|
||||
OpKind kind;
|
||||
|
||||
bool operator==(const CannotInferBinaryOperation& rhs) const;
|
||||
};
|
||||
|
||||
struct SwappedGenericTypeParameter
|
||||
{
|
||||
enum Kind
|
||||
{
|
||||
Type,
|
||||
Pack,
|
||||
};
|
||||
|
||||
std::string name;
|
||||
// What was `name` being used as?
|
||||
Kind kind;
|
||||
|
||||
bool operator==(const SwappedGenericTypeParameter& rhs) const;
|
||||
};
|
||||
|
||||
struct OptionalValueAccess
|
||||
{
|
||||
TypeId optional;
|
||||
|
||||
bool operator==(const OptionalValueAccess& rhs) const;
|
||||
};
|
||||
|
||||
struct MissingUnionProperty
|
||||
{
|
||||
TypeId type;
|
||||
std::vector<TypeId> missing;
|
||||
Name key;
|
||||
|
||||
bool operator==(const MissingUnionProperty& rhs) const;
|
||||
};
|
||||
|
||||
struct TypesAreUnrelated
|
||||
{
|
||||
TypeId left;
|
||||
TypeId right;
|
||||
|
||||
bool operator==(const TypesAreUnrelated& rhs) const;
|
||||
};
|
||||
|
||||
struct NormalizationTooComplex
|
||||
{
|
||||
bool operator==(const NormalizationTooComplex&) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct TypePackMismatch
|
||||
{
|
||||
TypePackId wantedTp;
|
||||
TypePackId givenTp;
|
||||
|
||||
bool operator==(const TypePackMismatch& rhs) const;
|
||||
};
|
||||
|
||||
struct DynamicPropertyLookupOnClassesUnsafe
|
||||
{
|
||||
TypeId ty;
|
||||
|
||||
bool operator==(const DynamicPropertyLookupOnClassesUnsafe& rhs) const;
|
||||
};
|
||||
|
||||
struct UninhabitedTypeFamily
|
||||
{
|
||||
TypeId ty;
|
||||
|
||||
bool operator==(const UninhabitedTypeFamily& rhs) const;
|
||||
};
|
||||
|
||||
struct UninhabitedTypePackFamily
|
||||
{
|
||||
TypePackId tp;
|
||||
|
||||
bool operator==(const UninhabitedTypePackFamily& rhs) const;
|
||||
};
|
||||
|
||||
struct WhereClauseNeeded
|
||||
{
|
||||
TypeId ty;
|
||||
|
||||
bool operator==(const WhereClauseNeeded& rhs) const;
|
||||
};
|
||||
|
||||
struct PackWhereClauseNeeded
|
||||
{
|
||||
TypePackId tp;
|
||||
|
||||
bool operator==(const PackWhereClauseNeeded& rhs) const;
|
||||
};
|
||||
|
||||
using TypeErrorData = Variant<TypeMismatch, UnknownSymbol, UnknownProperty, NotATable, CannotExtendTable, OnlyTablesCanHaveMethods,
|
||||
DuplicateTypeDefinition, CountMismatch, FunctionDoesNotTakeSelf, FunctionRequiresSelf, OccursCheckFailed, UnknownRequire,
|
||||
IncorrectGenericParameterCount, SyntaxError, CodeTooComplex, UnificationTooComplex, UnknownPropButFoundLikeProp, GenericError, InternalError,
|
||||
CannotCallNonFunction, ExtraInformation, DeprecatedApiUsed, ModuleHasCyclicDependency, IllegalRequire, FunctionExitsWithoutReturning,
|
||||
DuplicateGenericParameter, CannotInferBinaryOperation, MissingProperties, SwappedGenericTypeParameter, OptionalValueAccess, MissingUnionProperty,
|
||||
TypesAreUnrelated, NormalizationTooComplex, TypePackMismatch, DynamicPropertyLookupOnClassesUnsafe, UninhabitedTypeFamily,
|
||||
UninhabitedTypePackFamily, WhereClauseNeeded, PackWhereClauseNeeded>;
|
||||
|
||||
struct TypeErrorSummary
|
||||
{
|
||||
Location location;
|
||||
ModuleName moduleName;
|
||||
int code;
|
||||
|
||||
TypeErrorSummary(const Location& location, const ModuleName& moduleName, int code)
|
||||
: location(location)
|
||||
, moduleName(moduleName)
|
||||
, code(code)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct TypeError
|
||||
{
|
||||
Location location;
|
||||
ModuleName moduleName;
|
||||
TypeErrorData data;
|
||||
|
||||
static int minCode();
|
||||
int code() const;
|
||||
|
||||
TypeError() = default;
|
||||
|
||||
TypeError(const Location& location, const ModuleName& moduleName, const TypeErrorData& data)
|
||||
: location(location)
|
||||
, moduleName(moduleName)
|
||||
, data(data)
|
||||
{
|
||||
}
|
||||
|
||||
TypeError(const Location& location, const TypeErrorData& data)
|
||||
: TypeError(location, {}, data)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const TypeError& rhs) const;
|
||||
|
||||
TypeErrorSummary summary() const;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
const T* get(const TypeError& e)
|
||||
{
|
||||
return get_if<T>(&e.data);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* get(TypeError& e)
|
||||
{
|
||||
return get_if<T>(&e.data);
|
||||
}
|
||||
|
||||
using ErrorVec = std::vector<TypeError>;
|
||||
|
||||
struct TypeErrorToStringOptions
|
||||
{
|
||||
FileResolver* fileResolver = nullptr;
|
||||
};
|
||||
|
||||
std::string toString(const TypeError& error);
|
||||
std::string toString(const TypeError& error, TypeErrorToStringOptions options);
|
||||
|
||||
bool containsParseErrorName(const TypeError& error);
|
||||
|
||||
// Copy any types named in the error into destArena.
|
||||
void copyErrors(ErrorVec& errors, struct TypeArena& destArena, NotNull<BuiltinTypes> builtinTypes);
|
||||
|
||||
// Internal Compiler Error
|
||||
struct InternalErrorReporter
|
||||
{
|
||||
std::function<void(const char*)> onInternalError;
|
||||
std::string moduleName;
|
||||
|
||||
[[noreturn]] void ice(const std::string& message, const Location& location) const;
|
||||
[[noreturn]] void ice(const std::string& message) const;
|
||||
};
|
||||
|
||||
class InternalCompilerError : public std::exception
|
||||
{
|
||||
public:
|
||||
explicit InternalCompilerError(const std::string& message)
|
||||
: message(message)
|
||||
{
|
||||
}
|
||||
explicit InternalCompilerError(const std::string& message, const std::string& moduleName)
|
||||
: message(message)
|
||||
, moduleName(moduleName)
|
||||
{
|
||||
}
|
||||
explicit InternalCompilerError(const std::string& message, const std::string& moduleName, const Location& location)
|
||||
: message(message)
|
||||
, moduleName(moduleName)
|
||||
, location(location)
|
||||
{
|
||||
}
|
||||
virtual const char* what() const throw();
|
||||
|
||||
const std::string message;
|
||||
const std::optional<std::string> moduleName;
|
||||
const std::optional<Location> location;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,27 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <string.h>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
inline bool isFlagExperimental(const char* flag)
|
||||
{
|
||||
// Flags in this list are disabled by default in various command-line tools. They may have behavior that is not fully final,
|
||||
// or critical bugs that are found after the code has been submitted.
|
||||
static const char* const kList[] = {
|
||||
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
|
||||
"LuauTinyControlFlowAnalysis", // waiting for updates to packages depended by internal builtin plugins
|
||||
// makes sure we always have at least one entry
|
||||
nullptr,
|
||||
};
|
||||
|
||||
for (const char* item : kList)
|
||||
if (item && strcmp(item, flag) == 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,64 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
class AstExpr;
|
||||
|
||||
using ModuleName = std::string;
|
||||
|
||||
struct SourceCode
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
None,
|
||||
Module,
|
||||
Script,
|
||||
Local
|
||||
};
|
||||
|
||||
std::string source;
|
||||
Type type;
|
||||
};
|
||||
|
||||
struct ModuleInfo
|
||||
{
|
||||
ModuleName name;
|
||||
bool optional = false;
|
||||
};
|
||||
|
||||
struct FileResolver
|
||||
{
|
||||
virtual ~FileResolver() {}
|
||||
|
||||
virtual std::optional<SourceCode> readSource(const ModuleName& name) = 0;
|
||||
|
||||
virtual std::optional<ModuleInfo> resolveModule(const ModuleInfo* context, AstExpr* expr)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
virtual std::string getHumanReadableModuleName(const ModuleName& name) const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
virtual std::optional<std::string> getEnvironmentForModule(const ModuleName& name) const
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
struct NullFileResolver : FileResolver
|
||||
{
|
||||
std::optional<SourceCode> readSource(const ModuleName& name) override
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,258 @@
|
|||
// 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 "Luau/Module.h"
|
||||
#include "Luau/ModuleResolver.h"
|
||||
#include "Luau/RequireTracer.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/TypeCheckLimits.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
class AstStat;
|
||||
class ParseError;
|
||||
struct Frontend;
|
||||
struct TypeError;
|
||||
struct LintWarning;
|
||||
struct GlobalTypes;
|
||||
struct TypeChecker;
|
||||
struct FileResolver;
|
||||
struct ModuleResolver;
|
||||
struct ParseResult;
|
||||
struct HotComment;
|
||||
struct BuildQueueItem;
|
||||
struct FrontendCancellationToken;
|
||||
|
||||
struct LoadDefinitionFileResult
|
||||
{
|
||||
bool success;
|
||||
ParseResult parseResult;
|
||||
SourceModule sourceModule;
|
||||
ModulePtr module;
|
||||
};
|
||||
|
||||
std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments);
|
||||
|
||||
std::vector<std::string_view> parsePathExpr(const AstExpr& pathExpr);
|
||||
|
||||
// Exported only for convenient testing.
|
||||
std::optional<ModuleName> pathExprToModuleName(const ModuleName& currentModuleName, const std::vector<std::string_view>& expr);
|
||||
|
||||
/** Try to convert an AST fragment into a ModuleName.
|
||||
* Returns std::nullopt if the expression cannot be resolved. This will most likely happen in cases where
|
||||
* the import path involves some dynamic computation that we cannot see into at typechecking time.
|
||||
*
|
||||
* Unintuitively, weirdly-formulated modules (like game.Parent.Parent.Parent.Foo) will successfully produce a ModuleName
|
||||
* as long as it falls within the permitted syntax. This is ok because we will fail to find the module and produce an
|
||||
* error when we try during typechecking.
|
||||
*/
|
||||
std::optional<ModuleName> pathExprToModuleName(const ModuleName& currentModuleName, const AstExpr& expr);
|
||||
|
||||
struct SourceNode
|
||||
{
|
||||
bool hasDirtySourceModule() const
|
||||
{
|
||||
return dirtySourceModule;
|
||||
}
|
||||
|
||||
bool hasDirtyModule(bool forAutocomplete) const
|
||||
{
|
||||
return forAutocomplete ? dirtyModuleForAutocomplete : dirtyModule;
|
||||
}
|
||||
|
||||
ModuleName name;
|
||||
std::string humanReadableName;
|
||||
std::unordered_set<ModuleName> requireSet;
|
||||
std::vector<std::pair<ModuleName, Location>> requireLocations;
|
||||
bool dirtySourceModule = true;
|
||||
bool dirtyModule = true;
|
||||
bool dirtyModuleForAutocomplete = true;
|
||||
double autocompleteLimitsMult = 1.0;
|
||||
};
|
||||
|
||||
struct FrontendOptions
|
||||
{
|
||||
// When true, we retain full type information about every term in the AST.
|
||||
// Setting this to false cuts back on RAM and is a good idea for batch
|
||||
// jobs where the type graph is not deeply inspected after typechecking
|
||||
// is complete.
|
||||
bool retainFullTypeGraphs = false;
|
||||
|
||||
// Run typechecking only in mode required for autocomplete (strict mode in
|
||||
// order to get more precise type information)
|
||||
bool forAutocomplete = false;
|
||||
|
||||
bool runLintChecks = false;
|
||||
|
||||
// If not empty, randomly shuffle the constraint set before attempting to
|
||||
// solve. Use this value to seed the random number generator.
|
||||
std::optional<unsigned> randomizeConstraintResolutionSeed;
|
||||
|
||||
std::optional<LintOptions> enabledLintWarnings;
|
||||
|
||||
std::shared_ptr<FrontendCancellationToken> cancellationToken;
|
||||
|
||||
// Time limit for typechecking a single module
|
||||
std::optional<double> moduleTimeLimitSec;
|
||||
|
||||
// When true, some internal complexity limits will be scaled down for modules that miss the limit set by moduleTimeLimitSec
|
||||
bool applyInternalLimitScaling = false;
|
||||
};
|
||||
|
||||
struct CheckResult
|
||||
{
|
||||
std::vector<TypeError> errors;
|
||||
|
||||
LintResult lintResult;
|
||||
|
||||
std::vector<ModuleName> timeoutHits;
|
||||
};
|
||||
|
||||
struct FrontendModuleResolver : ModuleResolver
|
||||
{
|
||||
FrontendModuleResolver(Frontend* frontend);
|
||||
|
||||
const ModulePtr getModule(const ModuleName& moduleName) const override;
|
||||
bool moduleExists(const ModuleName& moduleName) const override;
|
||||
std::optional<ModuleInfo> resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr) override;
|
||||
std::string getHumanReadableModuleName(const ModuleName& moduleName) const override;
|
||||
|
||||
void setModule(const ModuleName& moduleName, ModulePtr module);
|
||||
void clearModules();
|
||||
|
||||
private:
|
||||
Frontend* frontend;
|
||||
|
||||
mutable std::mutex moduleMutex;
|
||||
std::unordered_map<ModuleName, ModulePtr> modules;
|
||||
};
|
||||
|
||||
struct Frontend
|
||||
{
|
||||
struct Stats
|
||||
{
|
||||
size_t files = 0;
|
||||
size_t lines = 0;
|
||||
|
||||
size_t filesStrict = 0;
|
||||
size_t filesNonstrict = 0;
|
||||
|
||||
double timeRead = 0;
|
||||
double timeParse = 0;
|
||||
double timeCheck = 0;
|
||||
double timeLint = 0;
|
||||
};
|
||||
|
||||
Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, const FrontendOptions& options = {});
|
||||
|
||||
// Parse module graph and prepare SourceNode/SourceModule data, including required dependencies without running typechecking
|
||||
void parse(const ModuleName& name);
|
||||
|
||||
// Parse and typecheck module graph
|
||||
CheckResult check(const ModuleName& name, std::optional<FrontendOptions> optionOverride = {}); // new shininess
|
||||
|
||||
bool isDirty(const ModuleName& name, bool forAutocomplete = false) const;
|
||||
void markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty = nullptr);
|
||||
|
||||
/** Borrow a pointer into the SourceModule cache.
|
||||
*
|
||||
* Returns nullptr if we don't have it. This could mean that the script
|
||||
* doesn't exist, or simply that its contents have changed since the previous
|
||||
* check, in which case we do not have its AST.
|
||||
*
|
||||
* IMPORTANT: this pointer is only valid until the next call to markDirty. Do not retain it.
|
||||
*/
|
||||
SourceModule* getSourceModule(const ModuleName& name);
|
||||
const SourceModule* getSourceModule(const ModuleName& name) const;
|
||||
|
||||
void clearStats();
|
||||
void clear();
|
||||
|
||||
ScopePtr addEnvironment(const std::string& environmentName);
|
||||
ScopePtr getEnvironmentScope(const std::string& environmentName) const;
|
||||
|
||||
void registerBuiltinDefinition(const std::string& name, std::function<void(Frontend&, GlobalTypes&, ScopePtr)>);
|
||||
void applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName);
|
||||
|
||||
LoadDefinitionFileResult loadDefinitionFile(GlobalTypes& globals, ScopePtr targetScope, std::string_view source, const std::string& packageName,
|
||||
bool captureComments, bool typeCheckForAutocomplete = false);
|
||||
|
||||
// Batch module checking. Queue modules and check them together, retrieve results with 'getCheckResult'
|
||||
// If provided, 'executeTask' function is allowed to call the 'task' function on any thread and return without waiting for 'task' to complete
|
||||
void queueModuleCheck(const std::vector<ModuleName>& names);
|
||||
void queueModuleCheck(const ModuleName& name);
|
||||
std::vector<ModuleName> checkQueuedModules(std::optional<FrontendOptions> optionOverride = {},
|
||||
std::function<void(std::function<void()> task)> executeTask = {}, std::function<void(size_t done, size_t total)> progress = {});
|
||||
|
||||
std::optional<CheckResult> getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete = false);
|
||||
|
||||
private:
|
||||
ModulePtr check(const SourceModule& sourceModule, Mode mode, std::vector<RequireCycle> requireCycles, std::optional<ScopePtr> environmentScope,
|
||||
bool forAutocomplete, bool recordJsonLog, TypeCheckLimits typeCheckLimits);
|
||||
|
||||
std::pair<SourceNode*, SourceModule*> getSourceNode(const ModuleName& name);
|
||||
SourceModule parse(const ModuleName& name, std::string_view src, const ParseOptions& parseOptions);
|
||||
|
||||
bool parseGraph(
|
||||
std::vector<ModuleName>& buildQueue, const ModuleName& root, bool forAutocomplete, std::function<bool(const ModuleName&)> canSkip = {});
|
||||
|
||||
void addBuildQueueItems(std::vector<BuildQueueItem>& items, std::vector<ModuleName>& buildQueue, bool cycleDetected,
|
||||
std::unordered_set<Luau::ModuleName>& seen, const FrontendOptions& frontendOptions);
|
||||
void checkBuildQueueItem(BuildQueueItem& item);
|
||||
void checkBuildQueueItems(std::vector<BuildQueueItem>& items);
|
||||
void recordItemResult(const BuildQueueItem& item);
|
||||
|
||||
static LintResult classifyLints(const std::vector<LintWarning>& warnings, const Config& config);
|
||||
|
||||
ScopePtr getModuleEnvironment(const SourceModule& module, const Config& config, bool forAutocomplete) const;
|
||||
|
||||
std::unordered_map<std::string, ScopePtr> environments;
|
||||
std::unordered_map<std::string, std::function<void(Frontend&, GlobalTypes&, ScopePtr)>> builtinDefinitions;
|
||||
|
||||
BuiltinTypes builtinTypes_;
|
||||
|
||||
public:
|
||||
const NotNull<BuiltinTypes> builtinTypes;
|
||||
|
||||
FileResolver* fileResolver;
|
||||
|
||||
FrontendModuleResolver moduleResolver;
|
||||
FrontendModuleResolver moduleResolverForAutocomplete;
|
||||
|
||||
GlobalTypes globals;
|
||||
GlobalTypes globalsForAutocomplete;
|
||||
|
||||
ConfigResolver* configResolver;
|
||||
FrontendOptions options;
|
||||
InternalErrorReporter iceHandler;
|
||||
std::function<void(const ModuleName& name, const ScopePtr& scope, bool forAutocomplete)> prepareModuleScope;
|
||||
|
||||
std::unordered_map<ModuleName, std::shared_ptr<SourceNode>> sourceNodes;
|
||||
std::unordered_map<ModuleName, std::shared_ptr<SourceModule>> sourceModules;
|
||||
std::unordered_map<ModuleName, RequireTraceResult> requireTrace;
|
||||
|
||||
Stats stats = {};
|
||||
|
||||
std::vector<ModuleName> moduleQueue;
|
||||
};
|
||||
|
||||
ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
|
||||
const ScopePtr& globalScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, FrontendOptions options,
|
||||
TypeCheckLimits limits);
|
||||
|
||||
ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
|
||||
const ScopePtr& globalScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, FrontendOptions options,
|
||||
TypeCheckLimits limits, bool recordJsonLog);
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,134 @@
|
|||
// 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 <unordered_map>
|
||||
#include <vector>
|
||||
#include <type_traits>
|
||||
#include <iterator>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
template<typename K, typename V>
|
||||
struct InsertionOrderedMap
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<K>, "key must be trivially copyable");
|
||||
|
||||
private:
|
||||
using vec = std::vector<std::pair<K, V>>;
|
||||
|
||||
public:
|
||||
using iterator = typename vec::iterator;
|
||||
using const_iterator = typename vec::const_iterator;
|
||||
|
||||
void insert(K k, V v)
|
||||
{
|
||||
if (indices.count(k) != 0)
|
||||
return;
|
||||
|
||||
pairs.push_back(std::make_pair(k, std::move(v)));
|
||||
indices[k] = pairs.size() - 1;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
pairs.clear();
|
||||
indices.clear();
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
LUAU_ASSERT(pairs.size() == indices.size());
|
||||
return pairs.size();
|
||||
}
|
||||
|
||||
bool contains(const K& k) const
|
||||
{
|
||||
return indices.count(k) > 0;
|
||||
}
|
||||
|
||||
const V* get(const K& k) const
|
||||
{
|
||||
auto it = indices.find(k);
|
||||
if (it == indices.end())
|
||||
return nullptr;
|
||||
else
|
||||
return &pairs.at(it->second).second;
|
||||
}
|
||||
|
||||
V* get(const K& k)
|
||||
{
|
||||
auto it = indices.find(k);
|
||||
if (it == indices.end())
|
||||
return nullptr;
|
||||
else
|
||||
return &pairs.at(it->second).second;
|
||||
}
|
||||
|
||||
const_iterator begin() const
|
||||
{
|
||||
return pairs.begin();
|
||||
}
|
||||
|
||||
const_iterator end() const
|
||||
{
|
||||
return pairs.end();
|
||||
}
|
||||
|
||||
iterator begin()
|
||||
{
|
||||
return pairs.begin();
|
||||
}
|
||||
|
||||
iterator end()
|
||||
{
|
||||
return pairs.end();
|
||||
}
|
||||
|
||||
const_iterator find(K k) const
|
||||
{
|
||||
auto indicesIt = indices.find(k);
|
||||
if (indicesIt == indices.end())
|
||||
return end();
|
||||
else
|
||||
return begin() + indicesIt->second;
|
||||
}
|
||||
|
||||
iterator find(K k)
|
||||
{
|
||||
auto indicesIt = indices.find(k);
|
||||
if (indicesIt == indices.end())
|
||||
return end();
|
||||
else
|
||||
return begin() + indicesIt->second;
|
||||
}
|
||||
|
||||
void erase(iterator it)
|
||||
{
|
||||
if (it == pairs.end())
|
||||
return;
|
||||
|
||||
K k = it->first;
|
||||
auto indexIt = indices.find(k);
|
||||
if (indexIt == indices.end())
|
||||
return;
|
||||
|
||||
size_t removed = indexIt->second;
|
||||
indices.erase(indexIt);
|
||||
pairs.erase(it);
|
||||
|
||||
for (auto& [_, index] : indices)
|
||||
{
|
||||
if (index > removed)
|
||||
--index;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
vec pairs;
|
||||
std::unordered_map<K, size_t> indices;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,82 @@
|
|||
// 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/Substitution.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/Unifiable.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct BuiltinTypes;
|
||||
struct TxnLog;
|
||||
struct TypeArena;
|
||||
struct TypeCheckLimits;
|
||||
|
||||
// A substitution which replaces generic types in a given set by free types.
|
||||
struct ReplaceGenerics : Substitution
|
||||
{
|
||||
ReplaceGenerics(const TxnLog* log, TypeArena* arena, NotNull<BuiltinTypes> builtinTypes, TypeLevel level, Scope* scope, const std::vector<TypeId>& generics,
|
||||
const std::vector<TypePackId>& genericPacks)
|
||||
: Substitution(log, arena)
|
||||
, builtinTypes(builtinTypes)
|
||||
, level(level)
|
||||
, scope(scope)
|
||||
, generics(generics)
|
||||
, genericPacks(genericPacks)
|
||||
{
|
||||
}
|
||||
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
|
||||
TypeLevel level;
|
||||
Scope* scope;
|
||||
std::vector<TypeId> generics;
|
||||
std::vector<TypePackId> genericPacks;
|
||||
bool ignoreChildren(TypeId ty) override;
|
||||
bool isDirty(TypeId ty) override;
|
||||
bool isDirty(TypePackId tp) override;
|
||||
TypeId clean(TypeId ty) override;
|
||||
TypePackId clean(TypePackId tp) override;
|
||||
};
|
||||
|
||||
// A substitution which replaces generic functions by monomorphic functions
|
||||
struct Instantiation : Substitution
|
||||
{
|
||||
Instantiation(const TxnLog* log, TypeArena* arena, NotNull<BuiltinTypes> builtinTypes, TypeLevel level, Scope* scope)
|
||||
: Substitution(log, arena)
|
||||
, builtinTypes(builtinTypes)
|
||||
, level(level)
|
||||
, scope(scope)
|
||||
{
|
||||
}
|
||||
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
|
||||
TypeLevel level;
|
||||
Scope* scope;
|
||||
bool ignoreChildren(TypeId ty) override;
|
||||
bool isDirty(TypeId ty) override;
|
||||
bool isDirty(TypePackId tp) override;
|
||||
TypeId clean(TypeId ty) override;
|
||||
TypePackId clean(TypePackId tp) override;
|
||||
};
|
||||
|
||||
/** Attempt to instantiate a type. Only used under local type inference.
|
||||
*
|
||||
* When given a generic function type, instantiate() will return a copy with the
|
||||
* generics replaced by fresh types. Instantiation will return the same TypeId
|
||||
* back if the function does not have any generics.
|
||||
*
|
||||
* All higher order generics are left as-is. For example, instantiation of
|
||||
* <X>(<Y>(Y) -> (X, Y)) -> (X, Y) is (<Y>(Y) -> ('x, Y)) -> ('x, Y)
|
||||
*
|
||||
* We substitute the generic X for the free 'x, but leave the generic Y alone.
|
||||
*
|
||||
* Instantiation fails only when processing the type causes internal recursion
|
||||
* limits to be exceeded.
|
||||
*/
|
||||
std::optional<TypeId> instantiate(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, NotNull<TypeCheckLimits> limits, NotNull<Scope> scope, TypeId ty);
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,51 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/Ast.h"
|
||||
|
||||
#include <ostream>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
std::ostream& operator<<(std::ostream& lhs, const Position& position);
|
||||
std::ostream& operator<<(std::ostream& lhs, const Location& location);
|
||||
std::ostream& operator<<(std::ostream& lhs, const AstName& name);
|
||||
|
||||
std::ostream& operator<<(std::ostream& lhs, const TypeError& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const TypeMismatch& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const UnknownSymbol& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const UnknownProperty& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const NotATable& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const CannotExtendTable& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const OnlyTablesCanHaveMethods& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const DuplicateTypeDefinition& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const CountMismatch& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const FunctionDoesNotTakeSelf& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const FunctionRequiresSelf& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const OccursCheckFailed& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const UnknownRequire& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const UnknownPropButFoundLikeProp& e);
|
||||
std::ostream& operator<<(std::ostream& lhs, const GenericError& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const InternalError& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const FunctionExitsWithoutReturning& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const MissingProperties& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const IllegalRequire& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const ModuleHasCyclicDependency& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const DuplicateGenericParameter& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const CannotInferBinaryOperation& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const SwappedGenericTypeParameter& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const OptionalValueAccess& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const MissingUnionProperty& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const TypesAreUnrelated& error);
|
||||
|
||||
std::ostream& operator<<(std::ostream& lhs, const TableState& tv);
|
||||
std::ostream& operator<<(std::ostream& lhs, const Type& tv);
|
||||
std::ostream& operator<<(std::ostream& lhs, const TypePackVar& tv);
|
||||
|
||||
std::ostream& operator<<(std::ostream& lhs, const TypeErrorData& ted);
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,247 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "Luau/NotNull.h"
|
||||
|
||||
namespace Luau::Json
|
||||
{
|
||||
|
||||
struct JsonEmitter;
|
||||
|
||||
/// Writes a value to the JsonEmitter. Note that this can produce invalid JSON
|
||||
/// if you do not insert commas or appropriate object / array syntax.
|
||||
template<typename T>
|
||||
void write(JsonEmitter&, T) = delete;
|
||||
|
||||
/// Writes a boolean to a JsonEmitter.
|
||||
/// @param emitter the emitter to write to.
|
||||
/// @param b the boolean to write.
|
||||
void write(JsonEmitter& emitter, bool b);
|
||||
|
||||
/// Writes an integer to a JsonEmitter.
|
||||
/// @param emitter the emitter to write to.
|
||||
/// @param i the integer to write.
|
||||
void write(JsonEmitter& emitter, int i);
|
||||
|
||||
/// Writes an integer to a JsonEmitter.
|
||||
/// @param emitter the emitter to write to.
|
||||
/// @param i the integer to write.
|
||||
void write(JsonEmitter& emitter, long i);
|
||||
|
||||
/// Writes an integer to a JsonEmitter.
|
||||
/// @param emitter the emitter to write to.
|
||||
/// @param i the integer to write.
|
||||
void write(JsonEmitter& emitter, long long i);
|
||||
|
||||
/// Writes an integer to a JsonEmitter.
|
||||
/// @param emitter the emitter to write to.
|
||||
/// @param i the integer to write.
|
||||
void write(JsonEmitter& emitter, unsigned int i);
|
||||
|
||||
/// Writes an integer to a JsonEmitter.
|
||||
/// @param emitter the emitter to write to.
|
||||
/// @param i the integer to write.
|
||||
void write(JsonEmitter& emitter, unsigned long i);
|
||||
|
||||
/// Writes an integer to a JsonEmitter.
|
||||
/// @param emitter the emitter to write to.
|
||||
/// @param i the integer to write.
|
||||
void write(JsonEmitter& emitter, unsigned long long i);
|
||||
|
||||
/// Writes a double to a JsonEmitter.
|
||||
/// @param emitter the emitter to write to.
|
||||
/// @param d the double to write.
|
||||
void write(JsonEmitter& emitter, double d);
|
||||
|
||||
/// Writes a string to a JsonEmitter. The string will be escaped.
|
||||
/// @param emitter the emitter to write to.
|
||||
/// @param sv the string to write.
|
||||
void write(JsonEmitter& emitter, std::string_view sv);
|
||||
|
||||
/// Writes a character to a JsonEmitter as a single-character string. The
|
||||
/// character will be escaped.
|
||||
/// @param emitter the emitter to write to.
|
||||
/// @param c the string to write.
|
||||
void write(JsonEmitter& emitter, char c);
|
||||
|
||||
/// Writes a string to a JsonEmitter. The string will be escaped.
|
||||
/// @param emitter the emitter to write to.
|
||||
/// @param str the string to write.
|
||||
void write(JsonEmitter& emitter, const char* str);
|
||||
|
||||
/// Writes a string to a JsonEmitter. The string will be escaped.
|
||||
/// @param emitter the emitter to write to.
|
||||
/// @param str the string to write.
|
||||
void write(JsonEmitter& emitter, const std::string& str);
|
||||
|
||||
/// Writes null to a JsonEmitter.
|
||||
/// @param emitter the emitter to write to.
|
||||
void write(JsonEmitter& emitter, std::nullptr_t);
|
||||
|
||||
/// Writes null to a JsonEmitter.
|
||||
/// @param emitter the emitter to write to.
|
||||
void write(JsonEmitter& emitter, std::nullopt_t);
|
||||
|
||||
struct ObjectEmitter;
|
||||
struct ArrayEmitter;
|
||||
|
||||
struct JsonEmitter
|
||||
{
|
||||
JsonEmitter();
|
||||
|
||||
/// Converts the current contents of the JsonEmitter to a string value. This
|
||||
/// does not invalidate the emitter, but it does not clear it either.
|
||||
std::string str();
|
||||
|
||||
/// Returns the current comma state and resets it to false. Use popComma to
|
||||
/// restore the old state.
|
||||
/// @returns the previous comma state.
|
||||
bool pushComma();
|
||||
|
||||
/// Restores a previous comma state.
|
||||
/// @param c the comma state to restore.
|
||||
void popComma(bool c);
|
||||
|
||||
/// Writes a raw sequence of characters to the buffer, without escaping or
|
||||
/// other processing.
|
||||
/// @param sv the character sequence to write.
|
||||
void writeRaw(std::string_view sv);
|
||||
|
||||
/// Writes a character to the buffer, without escaping or other processing.
|
||||
/// @param c the character to write.
|
||||
void writeRaw(char c);
|
||||
|
||||
/// Writes a comma if this wasn't the first time writeComma has been
|
||||
/// invoked. Otherwise, sets the comma state to true.
|
||||
/// @see pushComma
|
||||
/// @see popComma
|
||||
void writeComma();
|
||||
|
||||
/// Begins writing an object to the emitter.
|
||||
/// @returns an ObjectEmitter that can be used to write key-value pairs.
|
||||
ObjectEmitter writeObject();
|
||||
|
||||
/// Begins writing an array to the emitter.
|
||||
/// @returns an ArrayEmitter that can be used to write values.
|
||||
ArrayEmitter writeArray();
|
||||
|
||||
private:
|
||||
bool comma = false;
|
||||
std::vector<std::string> chunks;
|
||||
|
||||
void newChunk();
|
||||
};
|
||||
|
||||
/// An interface for writing an object into a JsonEmitter instance.
|
||||
/// @see JsonEmitter::writeObject
|
||||
struct ObjectEmitter
|
||||
{
|
||||
ObjectEmitter(NotNull<JsonEmitter> emitter);
|
||||
~ObjectEmitter();
|
||||
|
||||
NotNull<JsonEmitter> emitter;
|
||||
bool comma;
|
||||
bool finished;
|
||||
|
||||
/// Writes a key-value pair to the associated JsonEmitter. Keys will be escaped.
|
||||
/// @param name the name of the key-value pair.
|
||||
/// @param value the value to write.
|
||||
template<typename T>
|
||||
void writePair(std::string_view name, T value)
|
||||
{
|
||||
if (finished)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
emitter->writeComma();
|
||||
write(*emitter, name);
|
||||
emitter->writeRaw(':');
|
||||
write(*emitter, value);
|
||||
}
|
||||
|
||||
/// Finishes writing the object, appending a closing `}` character and
|
||||
/// resetting the comma state of the associated emitter. This can only be
|
||||
/// called once, and once called will render the emitter unusable. This
|
||||
/// method is also called when the ObjectEmitter is destructed.
|
||||
void finish();
|
||||
};
|
||||
|
||||
/// An interface for writing an array into a JsonEmitter instance. Array values
|
||||
/// do not need to be the same type.
|
||||
/// @see JsonEmitter::writeArray
|
||||
struct ArrayEmitter
|
||||
{
|
||||
ArrayEmitter(NotNull<JsonEmitter> emitter);
|
||||
~ArrayEmitter();
|
||||
|
||||
NotNull<JsonEmitter> emitter;
|
||||
bool comma;
|
||||
bool finished;
|
||||
|
||||
/// Writes a value to the array.
|
||||
/// @param value the value to write.
|
||||
template<typename T>
|
||||
void writeValue(T value)
|
||||
{
|
||||
if (finished)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
emitter->writeComma();
|
||||
write(*emitter, value);
|
||||
}
|
||||
|
||||
/// Finishes writing the object, appending a closing `]` character and
|
||||
/// resetting the comma state of the associated emitter. This can only be
|
||||
/// called once, and once called will render the emitter unusable. This
|
||||
/// method is also called when the ArrayEmitter is destructed.
|
||||
void finish();
|
||||
};
|
||||
|
||||
/// Writes a vector as an array to a JsonEmitter.
|
||||
/// @param emitter the emitter to write to.
|
||||
/// @param vec the vector to write.
|
||||
template<typename T>
|
||||
void write(JsonEmitter& emitter, const std::vector<T>& vec)
|
||||
{
|
||||
ArrayEmitter a = emitter.writeArray();
|
||||
|
||||
for (const T& value : vec)
|
||||
a.writeValue(value);
|
||||
|
||||
a.finish();
|
||||
}
|
||||
|
||||
/// Writes an optional to a JsonEmitter. Will write the contained value, if
|
||||
/// present, or null, if no value is present.
|
||||
/// @param emitter the emitter to write to.
|
||||
/// @param v the value to write.
|
||||
template<typename T>
|
||||
void write(JsonEmitter& emitter, const std::optional<T>& v)
|
||||
{
|
||||
if (v.has_value())
|
||||
write(emitter, *v);
|
||||
else
|
||||
emitter.writeRaw("null");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void write(JsonEmitter& emitter, const std::unordered_map<std::string, T>& map)
|
||||
{
|
||||
ObjectEmitter o = emitter.writeObject();
|
||||
|
||||
for (const auto& [k, v] : map)
|
||||
o.writePair(k, v);
|
||||
|
||||
o.finish();
|
||||
}
|
||||
|
||||
} // namespace Luau::Json
|
|
@ -0,0 +1,53 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Variant.h"
|
||||
#include "Luau/Symbol.h"
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct Type;
|
||||
using TypeId = const Type*;
|
||||
|
||||
struct Field;
|
||||
|
||||
// Deprecated. Do not use in new work.
|
||||
using LValue = Variant<Symbol, Field>;
|
||||
|
||||
struct Field
|
||||
{
|
||||
std::shared_ptr<LValue> parent;
|
||||
std::string key;
|
||||
|
||||
bool operator==(const Field& rhs) const;
|
||||
bool operator!=(const Field& rhs) const;
|
||||
};
|
||||
|
||||
struct LValueHasher
|
||||
{
|
||||
size_t operator()(const LValue& lvalue) const;
|
||||
};
|
||||
|
||||
const LValue* baseof(const LValue& lvalue);
|
||||
|
||||
std::optional<LValue> tryGetLValue(const class AstExpr& expr);
|
||||
|
||||
// Utility function: breaks down an LValue to get at the Symbol
|
||||
Symbol getBaseSymbol(const LValue& lvalue);
|
||||
|
||||
template<typename T>
|
||||
const T* get(const LValue& lvalue)
|
||||
{
|
||||
return get_if<T>(&lvalue);
|
||||
}
|
||||
|
||||
using RefinementMap = std::unordered_map<LValue, TypeId, LValueHasher>;
|
||||
|
||||
void merge(RefinementMap& l, const RefinementMap& r, std::function<TypeId(TypeId, TypeId)> f);
|
||||
void addRefinement(RefinementMap& refis, const LValue& lvalue, TypeId ty);
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,268 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
class Allocator
|
||||
{
|
||||
public:
|
||||
Allocator();
|
||||
Allocator(Allocator&&);
|
||||
|
||||
Allocator& operator=(Allocator&&) = delete;
|
||||
|
||||
~Allocator();
|
||||
|
||||
void* allocate(size_t size);
|
||||
|
||||
template<typename T, typename... Args>
|
||||
T* alloc(Args&&... args)
|
||||
{
|
||||
static_assert(std::is_trivially_destructible<T>::value, "Objects allocated with this allocator will never have their destructors run!");
|
||||
|
||||
T* t = static_cast<T*>(allocate(sizeof(T)));
|
||||
new (t) T(std::forward<Args>(args)...);
|
||||
return t;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Page
|
||||
{
|
||||
Page* next;
|
||||
|
||||
char data[8192];
|
||||
};
|
||||
|
||||
Page* root;
|
||||
size_t offset;
|
||||
};
|
||||
|
||||
struct Lexeme
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
Eof = 0,
|
||||
|
||||
// 1..255 means actual character values
|
||||
Char_END = 256,
|
||||
|
||||
Equal,
|
||||
LessEqual,
|
||||
GreaterEqual,
|
||||
NotEqual,
|
||||
Dot2,
|
||||
Dot3,
|
||||
SkinnyArrow,
|
||||
DoubleColon,
|
||||
FloorDiv,
|
||||
|
||||
InterpStringBegin,
|
||||
InterpStringMid,
|
||||
InterpStringEnd,
|
||||
// An interpolated string with no expressions (like `x`)
|
||||
InterpStringSimple,
|
||||
|
||||
AddAssign,
|
||||
SubAssign,
|
||||
MulAssign,
|
||||
DivAssign,
|
||||
FloorDivAssign,
|
||||
ModAssign,
|
||||
PowAssign,
|
||||
ConcatAssign,
|
||||
|
||||
RawString,
|
||||
QuotedString,
|
||||
Number,
|
||||
Name,
|
||||
|
||||
Comment,
|
||||
BlockComment,
|
||||
|
||||
BrokenString,
|
||||
BrokenComment,
|
||||
BrokenUnicode,
|
||||
BrokenInterpDoubleBrace,
|
||||
|
||||
Error,
|
||||
|
||||
Reserved_BEGIN,
|
||||
ReservedAnd = Reserved_BEGIN,
|
||||
ReservedBreak,
|
||||
ReservedDo,
|
||||
ReservedElse,
|
||||
ReservedElseif,
|
||||
ReservedEnd,
|
||||
ReservedFalse,
|
||||
ReservedFor,
|
||||
ReservedFunction,
|
||||
ReservedIf,
|
||||
ReservedIn,
|
||||
ReservedLocal,
|
||||
ReservedNil,
|
||||
ReservedNot,
|
||||
ReservedOr,
|
||||
ReservedRepeat,
|
||||
ReservedReturn,
|
||||
ReservedThen,
|
||||
ReservedTrue,
|
||||
ReservedUntil,
|
||||
ReservedWhile,
|
||||
Reserved_END
|
||||
};
|
||||
|
||||
Type type;
|
||||
Location location;
|
||||
unsigned int length;
|
||||
|
||||
union
|
||||
{
|
||||
const char* data; // String, Number, Comment
|
||||
const char* name; // Name
|
||||
unsigned int codepoint; // BrokenUnicode
|
||||
};
|
||||
|
||||
Lexeme(const Location& location, Type type);
|
||||
Lexeme(const Location& location, char character);
|
||||
Lexeme(const Location& location, Type type, const char* data, size_t size);
|
||||
Lexeme(const Location& location, Type type, const char* name);
|
||||
|
||||
std::string toString() const;
|
||||
};
|
||||
|
||||
class AstNameTable
|
||||
{
|
||||
public:
|
||||
AstNameTable(Allocator& allocator);
|
||||
|
||||
AstName addStatic(const char* name, Lexeme::Type type = Lexeme::Name);
|
||||
|
||||
std::pair<AstName, Lexeme::Type> getOrAddWithType(const char* name, size_t length);
|
||||
std::pair<AstName, Lexeme::Type> getWithType(const char* name, size_t length) const;
|
||||
|
||||
AstName getOrAdd(const char* name);
|
||||
AstName get(const char* name) const;
|
||||
|
||||
private:
|
||||
struct Entry
|
||||
{
|
||||
AstName value;
|
||||
uint32_t length;
|
||||
Lexeme::Type type;
|
||||
|
||||
bool operator==(const Entry& other) const;
|
||||
};
|
||||
|
||||
struct EntryHash
|
||||
{
|
||||
size_t operator()(const Entry& e) const;
|
||||
};
|
||||
|
||||
DenseHashSet<Entry, EntryHash> data;
|
||||
|
||||
Allocator& allocator;
|
||||
};
|
||||
|
||||
class Lexer
|
||||
{
|
||||
public:
|
||||
Lexer(const char* buffer, std::size_t bufferSize, AstNameTable& names);
|
||||
|
||||
void setSkipComments(bool skip);
|
||||
void setReadNames(bool read);
|
||||
|
||||
const Location& previousLocation() const
|
||||
{
|
||||
return prevLocation;
|
||||
}
|
||||
|
||||
const Lexeme& next();
|
||||
const Lexeme& next(bool skipComments, bool updatePrevLocation);
|
||||
void nextline();
|
||||
|
||||
Lexeme lookahead();
|
||||
|
||||
const Lexeme& current() const
|
||||
{
|
||||
return lexeme;
|
||||
}
|
||||
|
||||
static bool isReserved(const std::string& word);
|
||||
|
||||
static bool fixupQuotedString(std::string& data);
|
||||
static void fixupMultilineString(std::string& data);
|
||||
|
||||
private:
|
||||
char peekch() const;
|
||||
char peekch(unsigned int lookahead) const;
|
||||
|
||||
Position position() const;
|
||||
|
||||
// consume() assumes current character is not a newline for performance; when that is not known, consumeAny() should be used instead.
|
||||
void consume();
|
||||
void consumeAny();
|
||||
|
||||
Lexeme readCommentBody();
|
||||
|
||||
// Given a sequence [===[ or ]===], returns:
|
||||
// 1. number of equal signs (or 0 if none present) between the brackets
|
||||
// 2. -1 if this is not a long comment/string separator
|
||||
// 3. -N if this is a malformed separator
|
||||
// Does *not* consume the closing brace.
|
||||
int skipLongSeparator();
|
||||
|
||||
Lexeme readLongString(const Position& start, int sep, Lexeme::Type ok, Lexeme::Type broken);
|
||||
Lexeme readQuotedString();
|
||||
|
||||
Lexeme readInterpolatedStringBegin();
|
||||
Lexeme readInterpolatedStringSection(Position start, Lexeme::Type formatType, Lexeme::Type endType);
|
||||
|
||||
void readBackslashInString();
|
||||
|
||||
std::pair<AstName, Lexeme::Type> readName();
|
||||
|
||||
Lexeme readNumber(const Position& start, unsigned int startOffset);
|
||||
|
||||
Lexeme readUtf8Error();
|
||||
Lexeme readNext();
|
||||
|
||||
const char* buffer;
|
||||
std::size_t bufferSize;
|
||||
|
||||
unsigned int offset;
|
||||
|
||||
unsigned int line;
|
||||
unsigned int lineOffset;
|
||||
|
||||
Lexeme lexeme;
|
||||
|
||||
Location prevLocation;
|
||||
|
||||
AstNameTable& names;
|
||||
|
||||
bool skipComments;
|
||||
bool readNames;
|
||||
|
||||
enum class BraceType
|
||||
{
|
||||
InterpolatedString,
|
||||
Normal
|
||||
};
|
||||
|
||||
std::vector<BraceType> braceStack;
|
||||
};
|
||||
|
||||
inline bool isSpace(char ch)
|
||||
{
|
||||
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '\v' || ch == '\f';
|
||||
}
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,33 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/LinterConfig.h"
|
||||
#include "Luau/Location.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct AstName;
|
||||
class AstStat;
|
||||
class AstNameTable;
|
||||
struct TypeChecker;
|
||||
struct Module;
|
||||
|
||||
using ScopePtr = std::shared_ptr<struct Scope>;
|
||||
|
||||
struct LintResult
|
||||
{
|
||||
std::vector<LintWarning> errors;
|
||||
std::vector<LintWarning> warnings;
|
||||
};
|
||||
|
||||
std::vector<LintWarning> lint(AstStat* root, const AstNameTable& names, const ScopePtr& env, const Module* module,
|
||||
const std::vector<HotComment>& hotcomments, const LintOptions& options);
|
||||
|
||||
std::vector<AstName> getDeprecatedGlobals(const AstNameTable& names);
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,124 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Location.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct HotComment;
|
||||
|
||||
struct LintWarning
|
||||
{
|
||||
// Make sure any new lint codes are documented here: https://luau-lang.org/lint
|
||||
// Note that in Studio, the active set of lint warnings is determined by FStringStudioLuauLints
|
||||
enum Code
|
||||
{
|
||||
Code_Unknown = 0,
|
||||
|
||||
Code_UnknownGlobal = 1, // superseded by type checker
|
||||
Code_DeprecatedGlobal = 2,
|
||||
Code_GlobalUsedAsLocal = 3,
|
||||
Code_LocalShadow = 4, // disabled in Studio
|
||||
Code_SameLineStatement = 5, // disabled in Studio
|
||||
Code_MultiLineStatement = 6,
|
||||
Code_LocalUnused = 7, // disabled in Studio
|
||||
Code_FunctionUnused = 8, // disabled in Studio
|
||||
Code_ImportUnused = 9, // disabled in Studio
|
||||
Code_BuiltinGlobalWrite = 10,
|
||||
Code_PlaceholderRead = 11,
|
||||
Code_UnreachableCode = 12,
|
||||
Code_UnknownType = 13,
|
||||
Code_ForRange = 14,
|
||||
Code_UnbalancedAssignment = 15,
|
||||
Code_ImplicitReturn = 16, // disabled in Studio, superseded by type checker in strict mode
|
||||
Code_DuplicateLocal = 17,
|
||||
Code_FormatString = 18,
|
||||
Code_TableLiteral = 19,
|
||||
Code_UninitializedLocal = 20,
|
||||
Code_DuplicateFunction = 21,
|
||||
Code_DeprecatedApi = 22,
|
||||
Code_TableOperations = 23,
|
||||
Code_DuplicateCondition = 24,
|
||||
Code_MisleadingAndOr = 25,
|
||||
Code_CommentDirective = 26,
|
||||
Code_IntegerParsing = 27,
|
||||
Code_ComparisonPrecedence = 28,
|
||||
|
||||
Code__Count
|
||||
};
|
||||
|
||||
Code code;
|
||||
Location location;
|
||||
std::string text;
|
||||
|
||||
static const char* getName(Code code);
|
||||
static Code parseName(const char* name);
|
||||
static uint64_t parseMask(const std::vector<HotComment>& hotcomments);
|
||||
};
|
||||
|
||||
struct LintOptions
|
||||
{
|
||||
uint64_t warningMask = 0;
|
||||
|
||||
void enableWarning(LintWarning::Code code)
|
||||
{
|
||||
warningMask |= 1ull << code;
|
||||
}
|
||||
void disableWarning(LintWarning::Code code)
|
||||
{
|
||||
warningMask &= ~(1ull << code);
|
||||
}
|
||||
|
||||
bool isEnabled(LintWarning::Code code) const
|
||||
{
|
||||
return 0 != (warningMask & (1ull << code));
|
||||
}
|
||||
|
||||
void setDefaults();
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
static const char* kWarningNames[] = {
|
||||
"Unknown",
|
||||
|
||||
"UnknownGlobal",
|
||||
"DeprecatedGlobal",
|
||||
"GlobalUsedAsLocal",
|
||||
"LocalShadow",
|
||||
"SameLineStatement",
|
||||
"MultiLineStatement",
|
||||
"LocalUnused",
|
||||
"FunctionUnused",
|
||||
"ImportUnused",
|
||||
"BuiltinGlobalWrite",
|
||||
"PlaceholderRead",
|
||||
"UnreachableCode",
|
||||
"UnknownType",
|
||||
"ForRange",
|
||||
"UnbalancedAssignment",
|
||||
"ImplicitReturn",
|
||||
"DuplicateLocal",
|
||||
"FormatString",
|
||||
"TableLiteral",
|
||||
"UninitializedLocal",
|
||||
"DuplicateFunction",
|
||||
"DeprecatedApi",
|
||||
"TableOperations",
|
||||
"DuplicateCondition",
|
||||
"MisleadingAndOr",
|
||||
"CommentDirective",
|
||||
"IntegerParsing",
|
||||
"ComparisonPrecedence",
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
static_assert(std::size(kWarningNames) == unsigned(LintWarning::Code__Count), "did you forget to add warning to the list?");
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,44 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct Position
|
||||
{
|
||||
unsigned int line, column;
|
||||
|
||||
Position(unsigned int line, unsigned int column);
|
||||
|
||||
bool operator==(const Position& rhs) const;
|
||||
bool operator!=(const Position& rhs) const;
|
||||
bool operator<(const Position& rhs) const;
|
||||
bool operator>(const Position& rhs) const;
|
||||
bool operator<=(const Position& rhs) const;
|
||||
bool operator>=(const Position& rhs) const;
|
||||
|
||||
void shift(const Position& start, const Position& oldEnd, const Position& newEnd);
|
||||
};
|
||||
|
||||
struct Location
|
||||
{
|
||||
Position begin, end;
|
||||
|
||||
Location();
|
||||
Location(const Position& begin, const Position& end);
|
||||
Location(const Position& begin, unsigned int length);
|
||||
Location(const Location& begin, const Location& end);
|
||||
|
||||
bool operator==(const Location& rhs) const;
|
||||
bool operator!=(const Location& rhs) const;
|
||||
|
||||
bool encloses(const Location& l) const;
|
||||
bool overlaps(const Location& l) const;
|
||||
bool contains(const Position& p) const;
|
||||
bool containsClosed(const Position& p) const;
|
||||
void extend(const Location& other);
|
||||
void shift(const Position& start, const Position& oldEnd, const Position& newEnd);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,33 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
static const std::unordered_map<AstExprBinary::Op, const char*> kBinaryOpMetamethods{
|
||||
{AstExprBinary::Op::CompareEq, "__eq"},
|
||||
{AstExprBinary::Op::CompareNe, "__eq"},
|
||||
{AstExprBinary::Op::CompareGe, "__lt"},
|
||||
{AstExprBinary::Op::CompareGt, "__le"},
|
||||
{AstExprBinary::Op::CompareLe, "__le"},
|
||||
{AstExprBinary::Op::CompareLt, "__lt"},
|
||||
{AstExprBinary::Op::Add, "__add"},
|
||||
{AstExprBinary::Op::Sub, "__sub"},
|
||||
{AstExprBinary::Op::Mul, "__mul"},
|
||||
{AstExprBinary::Op::Div, "__div"},
|
||||
{AstExprBinary::Op::FloorDiv, "__idiv"},
|
||||
{AstExprBinary::Op::Pow, "__pow"},
|
||||
{AstExprBinary::Op::Mod, "__mod"},
|
||||
{AstExprBinary::Op::Concat, "__concat"},
|
||||
};
|
||||
|
||||
static const std::unordered_map<AstExprUnary::Op, const char*> kUnaryOpMetamethods{
|
||||
{AstExprUnary::Op::Minus, "__unm"},
|
||||
{AstExprUnary::Op::Len, "__len"},
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,130 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Linter.h"
|
||||
#include "Luau/FileResolver.h"
|
||||
#include "Luau/ParseOptions.h"
|
||||
#include "Luau/ParseResult.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct Module;
|
||||
|
||||
using ScopePtr = std::shared_ptr<struct Scope>;
|
||||
using ModulePtr = std::shared_ptr<Module>;
|
||||
|
||||
class AstType;
|
||||
class AstTypePack;
|
||||
|
||||
/// Root of the AST of a parsed source file
|
||||
struct SourceModule
|
||||
{
|
||||
ModuleName name; // Module identifier or a filename
|
||||
std::string humanReadableName;
|
||||
|
||||
SourceCode::Type type = SourceCode::None;
|
||||
std::optional<std::string> environmentName;
|
||||
bool cyclic = false;
|
||||
|
||||
std::shared_ptr<Allocator> allocator;
|
||||
std::shared_ptr<AstNameTable> names;
|
||||
std::vector<ParseError> parseErrors;
|
||||
|
||||
AstStatBlock* root = nullptr;
|
||||
std::optional<Mode> mode;
|
||||
|
||||
std::vector<HotComment> hotcomments;
|
||||
std::vector<Comment> commentLocations;
|
||||
|
||||
SourceModule()
|
||||
: allocator(new Allocator)
|
||||
, names(new AstNameTable(*allocator))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
bool isWithinComment(const SourceModule& sourceModule, Position pos);
|
||||
bool isWithinComment(const ParseResult& result, Position pos);
|
||||
|
||||
struct RequireCycle
|
||||
{
|
||||
Location location;
|
||||
std::vector<ModuleName> path; // one of the paths for a require() to go all the way back to the originating module
|
||||
};
|
||||
|
||||
struct Module
|
||||
{
|
||||
~Module();
|
||||
|
||||
ModuleName name;
|
||||
std::string humanReadableName;
|
||||
|
||||
TypeArena interfaceTypes;
|
||||
TypeArena internalTypes;
|
||||
|
||||
// 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;
|
||||
|
||||
std::vector<std::pair<Location, ScopePtr>> scopes; // never empty
|
||||
|
||||
DenseHashMap<const AstExpr*, TypeId> astTypes{nullptr};
|
||||
DenseHashMap<const AstExpr*, TypePackId> astTypePacks{nullptr};
|
||||
DenseHashMap<const AstExpr*, TypeId> astExpectedTypes{nullptr};
|
||||
|
||||
// For AST nodes that are function calls, this map provides the
|
||||
// unspecialized type of the function that was called. If a function call
|
||||
// resolves to a __call metamethod application, this map will point at that
|
||||
// metamethod.
|
||||
//
|
||||
// This is useful for type checking and Signature Help.
|
||||
DenseHashMap<const AstNode*, TypeId> astOriginalCallTypes{nullptr};
|
||||
|
||||
// The specialization of a function that was selected. If the function is
|
||||
// generic, those generic type parameters will be replaced with the actual
|
||||
// types that were passed. If the function is an overload, this map will
|
||||
// point at the specific overloads that were selected.
|
||||
DenseHashMap<const AstNode*, TypeId> astOverloadResolvedTypes{nullptr};
|
||||
|
||||
// Only used with for...in loops. The computed type of the next() function
|
||||
// is kept here for type checking.
|
||||
DenseHashMap<const AstNode*, TypeId> astForInNextTypes{nullptr};
|
||||
|
||||
DenseHashMap<const AstType*, TypeId> astResolvedTypes{nullptr};
|
||||
DenseHashMap<const AstTypePack*, TypePackId> astResolvedTypePacks{nullptr};
|
||||
|
||||
// Map AST nodes to the scope they create. Cannot be NotNull<Scope> because
|
||||
// we need a sentinel value for the map.
|
||||
DenseHashMap<const AstNode*, Scope*> astScopes{nullptr};
|
||||
|
||||
std::unordered_map<Name, TypeId> declaredGlobals;
|
||||
ErrorVec errors;
|
||||
LintResult lintResult;
|
||||
Mode mode;
|
||||
SourceCode::Type type;
|
||||
double checkDurationSec = 0.0;
|
||||
bool timeout = false;
|
||||
bool cancelled = false;
|
||||
|
||||
TypePackId returnType = nullptr;
|
||||
std::unordered_map<Name, TypeFun> exportedTypeBindings;
|
||||
|
||||
bool hasModuleScope() const;
|
||||
ScopePtr getModuleScope() const;
|
||||
|
||||
// Once a module has been typechecked, we clone its public interface into a
|
||||
// separate arena. This helps us to force Type ownership into a DAG rather
|
||||
// than a DCG.
|
||||
void clonePublicInterface(NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,73 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/FileResolver.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
class AstExpr;
|
||||
struct Module;
|
||||
|
||||
using ModulePtr = std::shared_ptr<Module>;
|
||||
|
||||
struct ModuleResolver
|
||||
{
|
||||
virtual ~ModuleResolver() {}
|
||||
|
||||
/** Compute a ModuleName from an AST fragment. This AST fragment is generally the argument to the require() function.
|
||||
*
|
||||
* You probably want to implement this with some variation of pathExprToModuleName.
|
||||
*
|
||||
* @returns The ModuleInfo if the expression is a syntactically legal path.
|
||||
* @returns std::nullopt if we are unable to determine whether or not the expression is a valid path. Type inference will
|
||||
* silently assume that it could succeed in this case.
|
||||
*
|
||||
* FIXME: This is clearly not the right behaviour longterm. We'll want to adust this interface to be able to signal
|
||||
* a) success,
|
||||
* b) Definitive failure (this expression will absolutely cause require() to fail at runtime), and
|
||||
* c) uncertainty
|
||||
*/
|
||||
virtual std::optional<ModuleInfo> resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr) = 0;
|
||||
|
||||
/** Get a typechecked module from its name.
|
||||
*
|
||||
* This can return null under two circumstances: the module is unknown at compile time,
|
||||
* or there's a cycle, and we are still in the middle of typechecking the module.
|
||||
*/
|
||||
virtual const ModulePtr getModule(const ModuleName& moduleName) const = 0;
|
||||
|
||||
/** Is a module known at compile time?
|
||||
*
|
||||
* This function can be used to distinguish the above two cases.
|
||||
*/
|
||||
virtual bool moduleExists(const ModuleName& moduleName) const = 0;
|
||||
|
||||
virtual std::string getHumanReadableModuleName(const ModuleName& moduleName) const = 0;
|
||||
};
|
||||
|
||||
struct NullModuleResolver : ModuleResolver
|
||||
{
|
||||
std::optional<ModuleInfo> resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr) override
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
const ModulePtr getModule(const ModuleName& moduleName) const override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
bool moduleExists(const ModuleName& moduleName) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::string getHumanReadableModuleName(const ModuleName& moduleName) const override
|
||||
{
|
||||
return moduleName;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,391 @@
|
|||
// 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/Type.h"
|
||||
#include "Luau/UnifierSharedState.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct InternalErrorReporter;
|
||||
struct Module;
|
||||
struct Scope;
|
||||
struct BuiltinTypes;
|
||||
|
||||
using ModulePtr = std::shared_ptr<Module>;
|
||||
|
||||
bool isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice);
|
||||
bool isSubtype(TypePackId subTy, TypePackId superTy, NotNull<Scope> scope, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice);
|
||||
bool isConsistentSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice);
|
||||
bool isConsistentSubtype(TypePackId subTy, TypePackId superTy, NotNull<Scope> scope, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice);
|
||||
|
||||
class TypeIds
|
||||
{
|
||||
private:
|
||||
std::unordered_set<TypeId> types;
|
||||
std::vector<TypeId> order;
|
||||
std::size_t hash = 0;
|
||||
|
||||
public:
|
||||
using iterator = std::vector<TypeId>::iterator;
|
||||
using const_iterator = std::vector<TypeId>::const_iterator;
|
||||
|
||||
TypeIds(const TypeIds&) = default;
|
||||
TypeIds(TypeIds&&) = default;
|
||||
TypeIds() = default;
|
||||
~TypeIds() = default;
|
||||
TypeIds& operator=(TypeIds&&) = default;
|
||||
|
||||
void insert(TypeId ty);
|
||||
/// Erase every element that does not also occur in tys
|
||||
void retain(const TypeIds& tys);
|
||||
void clear();
|
||||
|
||||
TypeId front() const;
|
||||
iterator begin();
|
||||
iterator end();
|
||||
const_iterator begin() const;
|
||||
const_iterator end() const;
|
||||
iterator erase(const_iterator it);
|
||||
|
||||
size_t size() const;
|
||||
bool empty() const;
|
||||
size_t count(TypeId ty) const;
|
||||
|
||||
template<class Iterator>
|
||||
void insert(Iterator begin, Iterator end)
|
||||
{
|
||||
for (Iterator it = begin; it != end; ++it)
|
||||
insert(*it);
|
||||
}
|
||||
|
||||
bool operator==(const TypeIds& there) const;
|
||||
size_t getHash() const;
|
||||
bool isNever() const;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
||||
template<>
|
||||
struct std::hash<Luau::TypeIds>
|
||||
{
|
||||
std::size_t operator()(const Luau::TypeIds& tys) const
|
||||
{
|
||||
return tys.getHash();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct std::hash<const Luau::TypeIds*>
|
||||
{
|
||||
std::size_t operator()(const Luau::TypeIds* tys) const
|
||||
{
|
||||
return tys->getHash();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct std::equal_to<Luau::TypeIds>
|
||||
{
|
||||
bool operator()(const Luau::TypeIds& here, const Luau::TypeIds& there) const
|
||||
{
|
||||
return here == there;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct std::equal_to<const Luau::TypeIds*>
|
||||
{
|
||||
bool operator()(const Luau::TypeIds* here, const Luau::TypeIds* there) const
|
||||
{
|
||||
return *here == *there;
|
||||
}
|
||||
};
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
/** A normalized string type is either `string` (represented by `nullopt`) or a
|
||||
* union of string singletons.
|
||||
*
|
||||
* The representation is as follows:
|
||||
*
|
||||
* * A union of string singletons is finite and includes the singletons named by
|
||||
* the `singletons` field.
|
||||
* * An intersection of negated string singletons is cofinite and includes the
|
||||
* singletons excluded by the `singletons` field. It is implied that cofinite
|
||||
* values are exclusions from `string` itself.
|
||||
* * The `string` data type is a cofinite set minus zero elements.
|
||||
* * The `never` data type is a finite set plus zero elements.
|
||||
*/
|
||||
struct NormalizedStringType
|
||||
{
|
||||
// When false, this type represents a union of singleton string types.
|
||||
// eg "a" | "b" | "c"
|
||||
//
|
||||
// When true, this type represents string intersected with negated string
|
||||
// singleton types.
|
||||
// eg string & ~"a" & ~"b" & ...
|
||||
bool isCofinite = false;
|
||||
|
||||
std::map<std::string, TypeId> singletons;
|
||||
|
||||
void resetToString();
|
||||
void resetToNever();
|
||||
|
||||
bool isNever() const;
|
||||
bool isString() const;
|
||||
|
||||
/// Returns true if the string has finite domain.
|
||||
///
|
||||
/// Important subtlety: This method returns true for `never`. The empty set
|
||||
/// is indeed an empty set.
|
||||
bool isUnion() const;
|
||||
|
||||
/// Returns true if the string has infinite domain.
|
||||
bool isIntersection() const;
|
||||
|
||||
bool includes(const std::string& str) const;
|
||||
|
||||
static const NormalizedStringType never;
|
||||
|
||||
NormalizedStringType();
|
||||
NormalizedStringType(bool isCofinite, std::map<std::string, TypeId> singletons);
|
||||
};
|
||||
|
||||
bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& superStr);
|
||||
|
||||
struct NormalizedClassType
|
||||
{
|
||||
/** Has the following structure:
|
||||
*
|
||||
* (C1 & ~N11 & ... & ~Nn) | (C2 & ~N21 & ... & ~N2n) | ...
|
||||
*
|
||||
* C2 is either not a subtype of any other Cm, or it is and is also a
|
||||
* subtype of one of Nmn types within the same cluster.
|
||||
*
|
||||
* Each TypeId is a class type.
|
||||
*/
|
||||
std::unordered_map<TypeId, TypeIds> classes;
|
||||
|
||||
/**
|
||||
* In order to maintain a consistent insertion order, we use this vector to
|
||||
* keep track of it. An ordered std::map will sort by pointer identity,
|
||||
* which is undesirable.
|
||||
*/
|
||||
std::vector<TypeId> ordering;
|
||||
|
||||
void pushPair(TypeId ty, TypeIds negations);
|
||||
|
||||
void resetToNever();
|
||||
bool isNever() const;
|
||||
};
|
||||
|
||||
// A normalized function type can be `never`, the top function type `function`,
|
||||
// or an intersection of function types.
|
||||
//
|
||||
// NOTE: type normalization can fail on function types with generics (e.g.
|
||||
// because we do not support unions and intersections of generic type packs), so
|
||||
// this type may contain `error`.
|
||||
struct NormalizedFunctionType
|
||||
{
|
||||
bool isTop = false;
|
||||
TypeIds parts;
|
||||
|
||||
void resetToNever();
|
||||
void resetToTop();
|
||||
|
||||
bool isNever() const;
|
||||
};
|
||||
|
||||
// A normalized generic/free type is a union, where each option is of the form (X & T) where
|
||||
// * X is either a free type, a generic or a blocked type.
|
||||
// * T is a normalized type.
|
||||
struct NormalizedType;
|
||||
using NormalizedTyvars = std::unordered_map<TypeId, std::unique_ptr<NormalizedType>>;
|
||||
|
||||
// A normalized type is either any, unknown, or one of the form P | T | F | G where
|
||||
// * P is a union of primitive types (including singletons, classes and the error type)
|
||||
// * T is a union of table types
|
||||
// * F is a union of an intersection of function types
|
||||
// * G is a union of generic/free/blocked types, intersected with a normalized type
|
||||
struct NormalizedType
|
||||
{
|
||||
// The top part of the type.
|
||||
// This type is either never, unknown, or any.
|
||||
// If this type is not never, all the other fields are null.
|
||||
TypeId tops;
|
||||
|
||||
// The boolean part of the type.
|
||||
// This type is either never, boolean type, or a boolean singleton.
|
||||
TypeId booleans;
|
||||
|
||||
NormalizedClassType classes;
|
||||
|
||||
// The error part of the type.
|
||||
// This type is either never or the error type.
|
||||
TypeId errors;
|
||||
|
||||
// The nil part of the type.
|
||||
// This type is either never or nil.
|
||||
TypeId nils;
|
||||
|
||||
// The number part of the type.
|
||||
// This type is either never or number.
|
||||
TypeId numbers;
|
||||
|
||||
// The string part of the type.
|
||||
// This may be the `string` type, or a union of singletons.
|
||||
NormalizedStringType strings;
|
||||
|
||||
// The thread part of the type.
|
||||
// This type is either never or thread.
|
||||
TypeId threads;
|
||||
|
||||
// The (meta)table part of the type.
|
||||
// Each element of this set is a (meta)table type, or the top `table` type.
|
||||
// An empty set denotes never.
|
||||
TypeIds tables;
|
||||
|
||||
// The function part of the type.
|
||||
NormalizedFunctionType functions;
|
||||
|
||||
// The generic/free part of the type.
|
||||
NormalizedTyvars tyvars;
|
||||
|
||||
NormalizedType(NotNull<BuiltinTypes> builtinTypes);
|
||||
|
||||
NormalizedType() = delete;
|
||||
~NormalizedType() = default;
|
||||
|
||||
NormalizedType(const NormalizedType&) = delete;
|
||||
NormalizedType& operator=(const NormalizedType&) = delete;
|
||||
|
||||
NormalizedType(NormalizedType&&) = default;
|
||||
NormalizedType& operator=(NormalizedType&&) = default;
|
||||
|
||||
// IsType functions
|
||||
/// Returns true if the type is exactly a number. Behaves like Type::isNumber()
|
||||
bool isExactlyNumber() const;
|
||||
|
||||
/// Returns true if the type is a subtype of string(it could be a singleton). Behaves like Type::isString()
|
||||
bool isSubtypeOfString() const;
|
||||
|
||||
/// Returns true if this type should result in error suppressing behavior.
|
||||
bool shouldSuppressErrors() const;
|
||||
|
||||
/// Returns true if this type contains the primitve top table type, `table`.
|
||||
bool hasTopTable() const;
|
||||
|
||||
// Helpers that improve readability of the above (they just say if the component is present)
|
||||
bool hasTops() const;
|
||||
bool hasBooleans() const;
|
||||
bool hasClasses() const;
|
||||
bool hasErrors() const;
|
||||
bool hasNils() const;
|
||||
bool hasNumbers() const;
|
||||
bool hasStrings() const;
|
||||
bool hasThreads() const;
|
||||
bool hasTables() const;
|
||||
bool hasFunctions() const;
|
||||
bool hasTyvars() const;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class Normalizer
|
||||
{
|
||||
std::unordered_map<TypeId, std::unique_ptr<NormalizedType>> cachedNormals;
|
||||
std::unordered_map<const TypeIds*, TypeId> cachedIntersections;
|
||||
std::unordered_map<const TypeIds*, TypeId> cachedUnions;
|
||||
std::unordered_map<const TypeIds*, std::unique_ptr<TypeIds>> cachedTypeIds;
|
||||
|
||||
DenseHashMap<TypeId, bool> cachedIsInhabited{nullptr};
|
||||
DenseHashMap<std::pair<TypeId, TypeId>, bool, TypeIdPairHash> cachedIsInhabitedIntersection{{nullptr, nullptr}};
|
||||
|
||||
bool withinResourceLimits();
|
||||
|
||||
public:
|
||||
TypeArena* arena;
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
NotNull<UnifierSharedState> sharedState;
|
||||
bool cacheInhabitance = false;
|
||||
|
||||
Normalizer(TypeArena* arena, NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> sharedState, bool cacheInhabitance = false);
|
||||
Normalizer(const Normalizer&) = delete;
|
||||
Normalizer(Normalizer&&) = delete;
|
||||
Normalizer() = delete;
|
||||
~Normalizer() = default;
|
||||
Normalizer& operator=(Normalizer&&) = delete;
|
||||
Normalizer& operator=(Normalizer&) = delete;
|
||||
|
||||
// If this returns null, the typechecker should emit a "too complex" error
|
||||
const NormalizedType* normalize(TypeId ty);
|
||||
void clearNormal(NormalizedType& norm);
|
||||
|
||||
// ------- Cached TypeIds
|
||||
TypeId unionType(TypeId here, TypeId there);
|
||||
TypeId intersectionType(TypeId here, TypeId there);
|
||||
const TypeIds* cacheTypeIds(TypeIds tys);
|
||||
void clearCaches();
|
||||
|
||||
// ------- Normalizing unions
|
||||
void unionTysWithTy(TypeIds& here, TypeId there);
|
||||
TypeId unionOfTops(TypeId here, TypeId there);
|
||||
TypeId unionOfBools(TypeId here, TypeId there);
|
||||
void unionClassesWithClass(TypeIds& heres, TypeId there);
|
||||
void unionClasses(TypeIds& heres, const TypeIds& theres);
|
||||
void unionClassesWithClass(NormalizedClassType& heres, TypeId there);
|
||||
void unionClasses(NormalizedClassType& heres, const NormalizedClassType& theres);
|
||||
void unionStrings(NormalizedStringType& here, const NormalizedStringType& there);
|
||||
std::optional<TypePackId> unionOfTypePacks(TypePackId here, TypePackId there);
|
||||
std::optional<TypeId> unionOfFunctions(TypeId here, TypeId there);
|
||||
std::optional<TypeId> unionSaturatedFunctions(TypeId here, TypeId there);
|
||||
void unionFunctionsWithFunction(NormalizedFunctionType& heress, TypeId there);
|
||||
void unionFunctions(NormalizedFunctionType& heress, const NormalizedFunctionType& theress);
|
||||
void unionTablesWithTable(TypeIds& heres, TypeId there);
|
||||
void unionTables(TypeIds& heres, const TypeIds& theres);
|
||||
bool unionNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars = -1);
|
||||
bool unionNormalWithTy(NormalizedType& here, TypeId there, std::unordered_set<TypeId>& seenSetTypes, int ignoreSmallerTyvars = -1);
|
||||
|
||||
// ------- Negations
|
||||
std::optional<NormalizedType> negateNormal(const NormalizedType& here);
|
||||
TypeIds negateAll(const TypeIds& theres);
|
||||
TypeId negate(TypeId there);
|
||||
void subtractPrimitive(NormalizedType& here, TypeId ty);
|
||||
void subtractSingleton(NormalizedType& here, TypeId ty);
|
||||
|
||||
// ------- Normalizing intersections
|
||||
TypeId intersectionOfTops(TypeId here, TypeId there);
|
||||
TypeId intersectionOfBools(TypeId here, TypeId there);
|
||||
void intersectClasses(NormalizedClassType& heres, const NormalizedClassType& theres);
|
||||
void intersectClassesWithClass(NormalizedClassType& heres, TypeId there);
|
||||
void intersectStrings(NormalizedStringType& here, const NormalizedStringType& there);
|
||||
std::optional<TypePackId> intersectionOfTypePacks(TypePackId here, TypePackId there);
|
||||
std::optional<TypeId> intersectionOfTables(TypeId here, TypeId there);
|
||||
void intersectTablesWithTable(TypeIds& heres, TypeId there);
|
||||
void intersectTables(TypeIds& heres, const TypeIds& theres);
|
||||
std::optional<TypeId> intersectionOfFunctions(TypeId here, TypeId there);
|
||||
void intersectFunctionsWithFunction(NormalizedFunctionType& heress, TypeId there);
|
||||
void intersectFunctions(NormalizedFunctionType& heress, const NormalizedFunctionType& theress);
|
||||
bool intersectTyvarsWithTy(NormalizedTyvars& here, TypeId there, std::unordered_set<TypeId>& seenSetTypes);
|
||||
bool intersectNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars = -1);
|
||||
bool intersectNormalWithTy(NormalizedType& here, TypeId there, std::unordered_set<TypeId>& seenSetTypes);
|
||||
bool normalizeIntersections(const std::vector<TypeId>& intersections, NormalizedType& outType);
|
||||
|
||||
// Check for inhabitance
|
||||
bool isInhabited(TypeId ty);
|
||||
bool isInhabited(TypeId ty, std::unordered_set<TypeId> seen);
|
||||
bool isInhabited(const NormalizedType* norm, std::unordered_set<TypeId> seen = {});
|
||||
|
||||
// Check for intersections being inhabited
|
||||
bool isIntersectionInhabited(TypeId left, TypeId right);
|
||||
|
||||
// -------- Convert back from a normalized type to a type
|
||||
TypeId typeFromNormal(const NormalizedType& norm);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,104 @@
|
|||
// 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 <functional>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
/** A non-owning, non-null pointer to a T.
|
||||
*
|
||||
* A NotNull<T> is notionally identical to a T* with the added restriction that
|
||||
* it can never store nullptr.
|
||||
*
|
||||
* The sole conversion rule from T* to NotNull<T> is the single-argument
|
||||
* constructor, which is intentionally marked explicit. This constructor
|
||||
* performs a runtime test to verify that the passed pointer is never nullptr.
|
||||
*
|
||||
* Pointer arithmetic, increment, decrement, and array indexing are all
|
||||
* forbidden.
|
||||
*
|
||||
* An implicit coersion from NotNull<T> to T* is afforded, as are the pointer
|
||||
* indirection and member access operators. (*p and p->prop)
|
||||
*
|
||||
* The explicit delete statement is permitted (but not recommended) on a
|
||||
* NotNull<T> through this implicit conversion.
|
||||
*/
|
||||
template<typename T>
|
||||
struct NotNull
|
||||
{
|
||||
explicit NotNull(T* t)
|
||||
: ptr(t)
|
||||
{
|
||||
LUAU_ASSERT(t);
|
||||
}
|
||||
|
||||
explicit NotNull(std::nullptr_t) = delete;
|
||||
void operator=(std::nullptr_t) = delete;
|
||||
|
||||
template<typename U>
|
||||
NotNull(NotNull<U> other)
|
||||
: ptr(other.get())
|
||||
{
|
||||
}
|
||||
|
||||
operator T*() const noexcept
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
|
||||
T& operator*() const noexcept
|
||||
{
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
T* operator->() const noexcept
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
bool operator==(NotNull<U> other) const noexcept
|
||||
{
|
||||
return get() == other.get();
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
bool operator!=(NotNull<U> other) const noexcept
|
||||
{
|
||||
return get() != other.get();
|
||||
}
|
||||
|
||||
operator bool() const noexcept = delete;
|
||||
|
||||
T& operator[](int) = delete;
|
||||
|
||||
T& operator+(int) = delete;
|
||||
T& operator-(int) = delete;
|
||||
|
||||
T* get() const noexcept
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
|
||||
private:
|
||||
T* ptr;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
struct hash<Luau::NotNull<T>>
|
||||
{
|
||||
size_t operator()(const Luau::NotNull<T>& p) const
|
||||
{
|
||||
return std::hash<T*>()(p.get());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
|
@ -0,0 +1,21 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
enum class Mode
|
||||
{
|
||||
NoCheck, // Do not perform any inference
|
||||
Nonstrict, // Unannotated symbols are any
|
||||
Strict, // Unannotated symbols are inferred
|
||||
Definition, // Type definition module, has special parsing rules
|
||||
};
|
||||
|
||||
struct ParseOptions
|
||||
{
|
||||
bool allowDeclarationSyntax = false;
|
||||
bool captureComments = false;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,71 @@
|
|||
// 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/Location.h"
|
||||
#include "Luau/Lexer.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
class AstStatBlock;
|
||||
|
||||
class ParseError : public std::exception
|
||||
{
|
||||
public:
|
||||
ParseError(const Location& location, const std::string& message);
|
||||
|
||||
virtual const char* what() const throw();
|
||||
|
||||
const Location& getLocation() const;
|
||||
const std::string& getMessage() const;
|
||||
|
||||
static LUAU_NORETURN void raise(const Location& location, const char* format, ...) LUAU_PRINTF_ATTR(2, 3);
|
||||
|
||||
private:
|
||||
Location location;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
class ParseErrors : public std::exception
|
||||
{
|
||||
public:
|
||||
ParseErrors(std::vector<ParseError> errors);
|
||||
|
||||
virtual const char* what() const throw();
|
||||
|
||||
const std::vector<ParseError>& getErrors() const;
|
||||
|
||||
private:
|
||||
std::vector<ParseError> errors;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
struct HotComment
|
||||
{
|
||||
bool header;
|
||||
Location location;
|
||||
std::string content;
|
||||
};
|
||||
|
||||
struct Comment
|
||||
{
|
||||
Lexeme::Type type; // Comment, BlockComment, or BrokenComment
|
||||
Location location;
|
||||
};
|
||||
|
||||
struct ParseResult
|
||||
{
|
||||
AstStatBlock* root;
|
||||
size_t lines = 0;
|
||||
|
||||
std::vector<HotComment> hotcomments;
|
||||
std::vector<ParseError> errors;
|
||||
|
||||
std::vector<Comment> commentLocations;
|
||||
};
|
||||
|
||||
static constexpr const char* kParseNameError = "%error-id%";
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,415 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Lexer.h"
|
||||
#include "Luau/ParseOptions.h"
|
||||
#include "Luau/ParseResult.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <initializer_list>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
class TempVector
|
||||
{
|
||||
public:
|
||||
explicit TempVector(std::vector<T>& storage);
|
||||
|
||||
~TempVector();
|
||||
|
||||
const T& operator[](std::size_t index) const;
|
||||
|
||||
const T& front() const;
|
||||
|
||||
const T& back() const;
|
||||
|
||||
bool empty() const;
|
||||
|
||||
std::size_t size() const;
|
||||
|
||||
void push_back(const T& item);
|
||||
|
||||
typename std::vector<T>::const_iterator begin() const
|
||||
{
|
||||
return storage.begin() + offset;
|
||||
}
|
||||
typename std::vector<T>::const_iterator end() const
|
||||
{
|
||||
return storage.begin() + offset + size_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<T>& storage;
|
||||
size_t offset;
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
class Parser
|
||||
{
|
||||
public:
|
||||
static ParseResult parse(
|
||||
const char* buffer, std::size_t bufferSize, AstNameTable& names, Allocator& allocator, ParseOptions options = ParseOptions());
|
||||
|
||||
private:
|
||||
struct Name;
|
||||
struct Binding;
|
||||
|
||||
Parser(const char* buffer, std::size_t bufferSize, AstNameTable& names, Allocator& allocator, const ParseOptions& options);
|
||||
|
||||
bool blockFollow(const Lexeme& l);
|
||||
|
||||
AstStatBlock* parseChunk();
|
||||
|
||||
// chunk ::= {stat [`;']} [laststat [`;']]
|
||||
// block ::= chunk
|
||||
AstStatBlock* parseBlock();
|
||||
|
||||
AstStatBlock* parseBlockNoScope();
|
||||
|
||||
// stat ::=
|
||||
// varlist `=' explist |
|
||||
// functioncall |
|
||||
// do block end |
|
||||
// while exp do block end |
|
||||
// repeat block until exp |
|
||||
// if exp then block {elseif exp then block} [else block] end |
|
||||
// for Name `=' exp `,' exp [`,' exp] do block end |
|
||||
// for namelist in explist do block end |
|
||||
// function funcname funcbody |
|
||||
// local function Name funcbody |
|
||||
// local namelist [`=' explist]
|
||||
// laststat ::= return [explist] | break
|
||||
AstStat* parseStat();
|
||||
|
||||
// if exp then block {elseif exp then block} [else block] end
|
||||
AstStat* parseIf();
|
||||
|
||||
// while exp do block end
|
||||
AstStat* parseWhile();
|
||||
|
||||
// repeat block until exp
|
||||
AstStat* parseRepeat();
|
||||
|
||||
// do block end
|
||||
AstStat* parseDo();
|
||||
|
||||
// break
|
||||
AstStat* parseBreak();
|
||||
|
||||
// continue
|
||||
AstStat* parseContinue(const Location& start);
|
||||
|
||||
// for Name `=' exp `,' exp [`,' exp] do block end |
|
||||
// for namelist in explist do block end |
|
||||
AstStat* parseFor();
|
||||
|
||||
// funcname ::= Name {`.' Name} [`:' Name]
|
||||
AstExpr* parseFunctionName(Location start, bool& hasself, AstName& debugname);
|
||||
|
||||
// function funcname funcbody
|
||||
AstStat* parseFunctionStat();
|
||||
|
||||
// local function Name funcbody |
|
||||
// local namelist [`=' explist]
|
||||
AstStat* parseLocal();
|
||||
|
||||
// return [explist]
|
||||
AstStat* parseReturn();
|
||||
|
||||
// type Name `=' Type
|
||||
AstStat* parseTypeAlias(const Location& start, bool exported);
|
||||
|
||||
AstDeclaredClassProp parseDeclaredClassMethod();
|
||||
|
||||
// `declare global' Name: Type |
|
||||
// `declare function' Name`(' [parlist] `)' [`:` Type]
|
||||
AstStat* parseDeclaration(const Location& start);
|
||||
|
||||
// varlist `=' explist
|
||||
AstStat* parseAssignment(AstExpr* initial);
|
||||
|
||||
// var [`+=' | `-=' | `*=' | `/=' | `%=' | `^=' | `..='] exp
|
||||
AstStat* parseCompoundAssignment(AstExpr* initial, AstExprBinary::Op op);
|
||||
|
||||
std::pair<AstLocal*, AstArray<AstLocal*>> prepareFunctionArguments(const Location& start, bool hasself, const TempVector<Binding>& args);
|
||||
|
||||
// funcbodyhead ::= `(' [namelist [`,' `...'] | `...'] `)' [`:` Type]
|
||||
// funcbody ::= funcbodyhead block end
|
||||
std::pair<AstExprFunction*, AstLocal*> parseFunctionBody(
|
||||
bool hasself, const Lexeme& matchFunction, const AstName& debugname, const Name* localName);
|
||||
|
||||
// explist ::= {exp `,'} exp
|
||||
void parseExprList(TempVector<AstExpr*>& result);
|
||||
|
||||
// binding ::= Name [`:` Type]
|
||||
Binding parseBinding();
|
||||
|
||||
// bindinglist ::= (binding | `...') {`,' bindinglist}
|
||||
// Returns the location of the vararg ..., or std::nullopt if the function is not vararg.
|
||||
std::tuple<bool, Location, AstTypePack*> parseBindingList(TempVector<Binding>& result, bool allowDot3 = false);
|
||||
|
||||
AstType* parseOptionalType();
|
||||
|
||||
// TypeList ::= Type [`,' TypeList]
|
||||
// ReturnType ::= Type | `(' TypeList `)'
|
||||
// TableProp ::= Name `:' Type
|
||||
// TableIndexer ::= `[' Type `]' `:' Type
|
||||
// PropList ::= (TableProp | TableIndexer) [`,' PropList]
|
||||
// Type
|
||||
// ::= Name
|
||||
// | `nil`
|
||||
// | `{' [PropList] `}'
|
||||
// | `(' [TypeList] `)' `->` ReturnType
|
||||
|
||||
// Returns the variadic annotation, if it exists.
|
||||
AstTypePack* parseTypeList(TempVector<AstType*>& result, TempVector<std::optional<AstArgumentName>>& resultNames);
|
||||
|
||||
std::optional<AstTypeList> parseOptionalReturnType();
|
||||
std::pair<Location, AstTypeList> parseReturnType();
|
||||
|
||||
AstTableIndexer* parseTableIndexer();
|
||||
|
||||
AstTypeOrPack parseFunctionType(bool allowPack);
|
||||
AstType* parseFunctionTypeTail(const Lexeme& begin, AstArray<AstGenericType> generics, AstArray<AstGenericTypePack> genericPacks,
|
||||
AstArray<AstType*> params, AstArray<std::optional<AstArgumentName>> paramNames, AstTypePack* varargAnnotation);
|
||||
|
||||
AstType* parseTableType();
|
||||
AstTypeOrPack parseSimpleType(bool allowPack);
|
||||
|
||||
AstTypeOrPack parseTypeOrPack();
|
||||
AstType* parseType();
|
||||
|
||||
AstTypePack* parseTypePack();
|
||||
AstTypePack* parseVariadicArgumentTypePack();
|
||||
|
||||
AstType* parseTypeSuffix(AstType* type, const Location& begin);
|
||||
|
||||
static std::optional<AstExprUnary::Op> parseUnaryOp(const Lexeme& l);
|
||||
static std::optional<AstExprBinary::Op> parseBinaryOp(const Lexeme& l);
|
||||
static std::optional<AstExprBinary::Op> parseCompoundOp(const Lexeme& l);
|
||||
|
||||
struct BinaryOpPriority
|
||||
{
|
||||
unsigned char left, right;
|
||||
};
|
||||
|
||||
std::optional<AstExprUnary::Op> checkUnaryConfusables();
|
||||
std::optional<AstExprBinary::Op> checkBinaryConfusables(const BinaryOpPriority binaryPriority[], unsigned int limit);
|
||||
|
||||
// subexpr -> (asexp | unop subexpr) { binop subexpr }
|
||||
// where `binop' is any binary operator with a priority higher than `limit'
|
||||
AstExpr* parseExpr(unsigned int limit = 0);
|
||||
|
||||
// NAME
|
||||
AstExpr* parseNameExpr(const char* context = nullptr);
|
||||
|
||||
// prefixexp -> NAME | '(' expr ')'
|
||||
AstExpr* parsePrefixExpr();
|
||||
|
||||
// primaryexp -> prefixexp { `.' NAME | `[' exp `]' | `:' NAME funcargs | funcargs }
|
||||
AstExpr* parsePrimaryExpr(bool asStatement);
|
||||
|
||||
// asexp -> simpleexp [`::' Type]
|
||||
AstExpr* parseAssertionExpr();
|
||||
|
||||
// simpleexp -> NUMBER | STRING | NIL | true | false | ... | constructor | FUNCTION body | primaryexp
|
||||
AstExpr* parseSimpleExpr();
|
||||
|
||||
// args ::= `(' [explist] `)' | tableconstructor | String
|
||||
AstExpr* parseFunctionArgs(AstExpr* func, bool self);
|
||||
|
||||
// tableconstructor ::= `{' [fieldlist] `}'
|
||||
// fieldlist ::= field {fieldsep field} [fieldsep]
|
||||
// field ::= `[' exp `]' `=' exp | Name `=' exp | exp
|
||||
// fieldsep ::= `,' | `;'
|
||||
AstExpr* parseTableConstructor();
|
||||
|
||||
// TODO: Add grammar rules here?
|
||||
AstExpr* parseIfElseExpr();
|
||||
|
||||
// stringinterp ::= <INTERP_BEGIN> exp {<INTERP_MID> exp} <INTERP_END>
|
||||
AstExpr* parseInterpString();
|
||||
|
||||
// Name
|
||||
std::optional<Name> parseNameOpt(const char* context = nullptr);
|
||||
Name parseName(const char* context = nullptr);
|
||||
Name parseIndexName(const char* context, const Position& previous);
|
||||
|
||||
// `<' namelist `>'
|
||||
std::pair<AstArray<AstGenericType>, AstArray<AstGenericTypePack>> parseGenericTypeList(bool withDefaultValues);
|
||||
|
||||
// `<' Type[, ...] `>'
|
||||
AstArray<AstTypeOrPack> parseTypeParams();
|
||||
|
||||
std::optional<AstArray<char>> parseCharArray();
|
||||
AstExpr* parseString();
|
||||
AstExpr* parseNumber();
|
||||
|
||||
AstLocal* pushLocal(const Binding& binding);
|
||||
|
||||
unsigned int saveLocals();
|
||||
|
||||
void restoreLocals(unsigned int offset);
|
||||
|
||||
// check that parser is at lexeme/symbol, move to next lexeme/symbol on success, report failure and continue on failure
|
||||
bool expectAndConsume(char value, const char* context = nullptr);
|
||||
bool expectAndConsume(Lexeme::Type type, const char* context = nullptr);
|
||||
void expectAndConsumeFail(Lexeme::Type type, const char* context);
|
||||
|
||||
struct MatchLexeme
|
||||
{
|
||||
MatchLexeme(const Lexeme& l)
|
||||
: type(l.type)
|
||||
, position(l.location.begin)
|
||||
{
|
||||
}
|
||||
|
||||
Lexeme::Type type;
|
||||
Position position;
|
||||
};
|
||||
|
||||
bool expectMatchAndConsume(char value, const MatchLexeme& begin, bool searchForMissing = false);
|
||||
void expectMatchAndConsumeFail(Lexeme::Type type, const MatchLexeme& begin, const char* extra = nullptr);
|
||||
bool expectMatchAndConsumeRecover(char value, const MatchLexeme& begin, bool searchForMissing);
|
||||
|
||||
bool expectMatchEndAndConsume(Lexeme::Type type, const MatchLexeme& begin);
|
||||
void expectMatchEndAndConsumeFail(Lexeme::Type type, const MatchLexeme& begin);
|
||||
|
||||
template<typename T>
|
||||
AstArray<T> copy(const T* data, std::size_t size);
|
||||
|
||||
template<typename T>
|
||||
AstArray<T> copy(const TempVector<T>& data);
|
||||
|
||||
template<typename T>
|
||||
AstArray<T> copy(std::initializer_list<T> data);
|
||||
|
||||
AstArray<char> copy(const std::string& data);
|
||||
|
||||
void incrementRecursionCounter(const char* context);
|
||||
|
||||
void report(const Location& location, const char* format, va_list args);
|
||||
void report(const Location& location, const char* format, ...) LUAU_PRINTF_ATTR(3, 4);
|
||||
|
||||
void reportNameError(const char* context);
|
||||
|
||||
AstStatError* reportStatError(const Location& location, const AstArray<AstExpr*>& expressions, const AstArray<AstStat*>& statements,
|
||||
const char* format, ...) LUAU_PRINTF_ATTR(5, 6);
|
||||
AstExprError* reportExprError(const Location& location, const AstArray<AstExpr*>& expressions, const char* format, ...) LUAU_PRINTF_ATTR(4, 5);
|
||||
AstTypeError* reportTypeError(const Location& location, const AstArray<AstType*>& types, const char* format, ...) LUAU_PRINTF_ATTR(4, 5);
|
||||
// `parseErrorLocation` is associated with the parser error
|
||||
// `astErrorLocation` is associated with the AstTypeError created
|
||||
// It can be useful to have different error locations so that the parse error can include the next lexeme, while the AstTypeError can precisely
|
||||
// define the location (possibly of zero size) where a type annotation is expected.
|
||||
AstTypeError* reportMissingTypeError(const Location& parseErrorLocation, const Location& astErrorLocation, const char* format, ...)
|
||||
LUAU_PRINTF_ATTR(4, 5);
|
||||
|
||||
AstExpr* reportFunctionArgsError(AstExpr* func, bool self);
|
||||
void reportAmbiguousCallError();
|
||||
|
||||
void nextLexeme();
|
||||
|
||||
struct Function
|
||||
{
|
||||
bool vararg;
|
||||
unsigned int loopDepth;
|
||||
|
||||
Function()
|
||||
: vararg(false)
|
||||
, loopDepth(0)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct Local
|
||||
{
|
||||
AstLocal* local;
|
||||
unsigned int offset;
|
||||
|
||||
Local()
|
||||
: local(nullptr)
|
||||
, offset(0)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct Name
|
||||
{
|
||||
AstName name;
|
||||
Location location;
|
||||
|
||||
Name(const AstName& name, const Location& location)
|
||||
: name(name)
|
||||
, location(location)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct Binding
|
||||
{
|
||||
Name name;
|
||||
AstType* annotation;
|
||||
|
||||
explicit Binding(const Name& name, AstType* annotation = nullptr)
|
||||
: name(name)
|
||||
, annotation(annotation)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
ParseOptions options;
|
||||
|
||||
Lexer lexer;
|
||||
Allocator& allocator;
|
||||
|
||||
std::vector<Comment> commentLocations;
|
||||
std::vector<HotComment> hotcomments;
|
||||
|
||||
bool hotcommentHeader = true;
|
||||
|
||||
unsigned int recursionCounter;
|
||||
|
||||
AstName nameSelf;
|
||||
AstName nameNumber;
|
||||
AstName nameError;
|
||||
AstName nameNil;
|
||||
|
||||
MatchLexeme endMismatchSuspect;
|
||||
|
||||
std::vector<Function> functionStack;
|
||||
|
||||
DenseHashMap<AstName, AstLocal*> localMap;
|
||||
std::vector<AstLocal*> localStack;
|
||||
|
||||
std::vector<ParseError> parseErrors;
|
||||
|
||||
std::vector<unsigned int> matchRecoveryStopOnToken;
|
||||
|
||||
std::vector<AstStat*> scratchStat;
|
||||
std::vector<AstArray<char>> scratchString;
|
||||
std::vector<AstExpr*> scratchExpr;
|
||||
std::vector<AstExpr*> scratchExprAux;
|
||||
std::vector<AstName> scratchName;
|
||||
std::vector<AstName> scratchPackName;
|
||||
std::vector<Binding> scratchBinding;
|
||||
std::vector<AstLocal*> scratchLocal;
|
||||
std::vector<AstTableProp> scratchTableTypeProps;
|
||||
std::vector<AstType*> scratchType;
|
||||
std::vector<AstTypeOrPack> scratchTypeOrPack;
|
||||
std::vector<AstDeclaredClassProp> scratchDeclaredClassProps;
|
||||
std::vector<AstExprTable::Item> scratchItem;
|
||||
std::vector<AstArgumentName> scratchArgName;
|
||||
std::vector<AstGenericType> scratchGenericTypes;
|
||||
std::vector<AstGenericTypePack> scratchGenericTypePacks;
|
||||
std::vector<std::optional<AstArgumentName>> scratchOptArgName;
|
||||
std::string scratchData;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,95 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/LValue.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct Type;
|
||||
using TypeId = const Type*;
|
||||
|
||||
struct TruthyPredicate;
|
||||
struct IsAPredicate;
|
||||
struct TypeGuardPredicate;
|
||||
struct EqPredicate;
|
||||
struct AndPredicate;
|
||||
struct OrPredicate;
|
||||
struct NotPredicate;
|
||||
|
||||
using Predicate = Variant<TruthyPredicate, IsAPredicate, TypeGuardPredicate, EqPredicate, AndPredicate, OrPredicate, NotPredicate>;
|
||||
using PredicateVec = std::vector<Predicate>;
|
||||
|
||||
struct TruthyPredicate
|
||||
{
|
||||
LValue lvalue;
|
||||
Location location;
|
||||
};
|
||||
|
||||
struct IsAPredicate
|
||||
{
|
||||
LValue lvalue;
|
||||
Location location;
|
||||
TypeId ty;
|
||||
};
|
||||
|
||||
struct TypeGuardPredicate
|
||||
{
|
||||
LValue lvalue;
|
||||
Location location;
|
||||
std::string kind; // TODO: When singleton types arrive, replace this with `TypeId ty;`
|
||||
bool isTypeof;
|
||||
};
|
||||
|
||||
struct EqPredicate
|
||||
{
|
||||
LValue lvalue;
|
||||
TypeId type;
|
||||
Location location;
|
||||
};
|
||||
|
||||
struct AndPredicate
|
||||
{
|
||||
PredicateVec lhs;
|
||||
PredicateVec rhs;
|
||||
|
||||
AndPredicate(PredicateVec&& lhs, PredicateVec&& rhs);
|
||||
};
|
||||
|
||||
struct OrPredicate
|
||||
{
|
||||
PredicateVec lhs;
|
||||
PredicateVec rhs;
|
||||
|
||||
OrPredicate(PredicateVec&& lhs, PredicateVec&& rhs);
|
||||
};
|
||||
|
||||
struct NotPredicate
|
||||
{
|
||||
PredicateVec predicates;
|
||||
};
|
||||
|
||||
// Outside definition works around clang 15 issue where vector instantiation is triggered while Predicate is still incomplete
|
||||
inline AndPredicate::AndPredicate(PredicateVec&& lhs, PredicateVec&& rhs)
|
||||
: lhs(std::move(lhs))
|
||||
, rhs(std::move(rhs))
|
||||
{
|
||||
}
|
||||
|
||||
inline OrPredicate::OrPredicate(PredicateVec&& lhs, PredicateVec&& rhs)
|
||||
: lhs(std::move(lhs))
|
||||
, rhs(std::move(rhs))
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const T* get(const Predicate& predicate)
|
||||
{
|
||||
return get_if<T>(&predicate);
|
||||
}
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,41 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct TypeArena;
|
||||
struct Scope;
|
||||
|
||||
void quantify(TypeId ty, TypeLevel level);
|
||||
|
||||
// TODO: This is eerily similar to the pattern that NormalizedClassType
|
||||
// implements. We could, and perhaps should, merge them together.
|
||||
template<typename K, typename V>
|
||||
struct OrderedMap
|
||||
{
|
||||
std::vector<K> keys;
|
||||
DenseHashMap<K, V> pairings{nullptr};
|
||||
|
||||
void push(K k, V v)
|
||||
{
|
||||
keys.push_back(k);
|
||||
pairings[k] = v;
|
||||
}
|
||||
};
|
||||
|
||||
struct QuantifierResult
|
||||
{
|
||||
TypeId result;
|
||||
OrderedMap<TypeId, TypeId> insertedGenerics;
|
||||
OrderedMap<TypePackId, TypePackId> insertedGenericPacks;
|
||||
};
|
||||
|
||||
std::optional<QuantifierResult> quantify(TypeArena* arena, TypeId ty, Scope* scope);
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,51 @@
|
|||
// 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/Error.h"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <exception>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct RecursionLimitException : public InternalCompilerError
|
||||
{
|
||||
RecursionLimitException()
|
||||
: InternalCompilerError("Internal recursion counter limit exceeded")
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct RecursionCounter
|
||||
{
|
||||
RecursionCounter(int* count)
|
||||
: count(count)
|
||||
{
|
||||
++(*count);
|
||||
}
|
||||
|
||||
~RecursionCounter()
|
||||
{
|
||||
LUAU_ASSERT(*count > 0);
|
||||
--(*count);
|
||||
}
|
||||
|
||||
protected:
|
||||
int* count;
|
||||
};
|
||||
|
||||
struct RecursionLimiter : RecursionCounter
|
||||
{
|
||||
RecursionLimiter(int* count, int limit)
|
||||
: RecursionCounter(count)
|
||||
{
|
||||
if (limit > 0 && *count > limit)
|
||||
{
|
||||
throw RecursionLimitException();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,78 @@
|
|||
// 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/TypedAllocator.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
using BreadcrumbId = NotNull<const struct Breadcrumb>;
|
||||
|
||||
struct Type;
|
||||
using TypeId = const Type*;
|
||||
|
||||
struct Variadic;
|
||||
struct Negation;
|
||||
struct Conjunction;
|
||||
struct Disjunction;
|
||||
struct Equivalence;
|
||||
struct Proposition;
|
||||
using Refinement = Variant<Variadic, Negation, Conjunction, Disjunction, Equivalence, Proposition>;
|
||||
using RefinementId = Refinement*; // Can and most likely is nullptr.
|
||||
|
||||
struct Variadic
|
||||
{
|
||||
std::vector<RefinementId> refinements;
|
||||
};
|
||||
|
||||
struct Negation
|
||||
{
|
||||
RefinementId refinement;
|
||||
};
|
||||
|
||||
struct Conjunction
|
||||
{
|
||||
RefinementId lhs;
|
||||
RefinementId rhs;
|
||||
};
|
||||
|
||||
struct Disjunction
|
||||
{
|
||||
RefinementId lhs;
|
||||
RefinementId rhs;
|
||||
};
|
||||
|
||||
struct Equivalence
|
||||
{
|
||||
RefinementId lhs;
|
||||
RefinementId rhs;
|
||||
};
|
||||
|
||||
struct Proposition
|
||||
{
|
||||
BreadcrumbId breadcrumb;
|
||||
TypeId discriminantTy;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
const T* get(RefinementId refinement)
|
||||
{
|
||||
return get_if<T>(refinement);
|
||||
}
|
||||
|
||||
struct RefinementArena
|
||||
{
|
||||
RefinementId variadic(const std::vector<RefinementId>& refis);
|
||||
RefinementId negation(RefinementId refinement);
|
||||
RefinementId conjunction(RefinementId lhs, RefinementId rhs);
|
||||
RefinementId disjunction(RefinementId lhs, RefinementId rhs);
|
||||
RefinementId equivalence(RefinementId lhs, RefinementId rhs);
|
||||
RefinementId proposition(BreadcrumbId breadcrumb, TypeId discriminantTy);
|
||||
|
||||
private:
|
||||
TypedAllocator<Refinement> allocator;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,28 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/FileResolver.h"
|
||||
#include "Luau/Location.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
class AstStat;
|
||||
class AstExpr;
|
||||
class AstStatBlock;
|
||||
struct AstLocal;
|
||||
|
||||
struct RequireTraceResult
|
||||
{
|
||||
DenseHashMap<const AstExpr*, ModuleInfo> exprs{nullptr};
|
||||
|
||||
std::vector<std::pair<ModuleName, Location>> requireList;
|
||||
};
|
||||
|
||||
RequireTraceResult traceRequires(FileResolver* fileResolver, AstStatBlock* root, const ModuleName& currentModuleName);
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,88 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Def.h"
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/Type.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct Scope;
|
||||
|
||||
using ScopePtr = std::shared_ptr<Scope>;
|
||||
|
||||
struct Binding
|
||||
{
|
||||
TypeId typeId;
|
||||
Location location;
|
||||
bool deprecated = false;
|
||||
std::string deprecatedSuggestion;
|
||||
std::optional<std::string> documentationSymbol;
|
||||
};
|
||||
|
||||
struct Scope
|
||||
{
|
||||
explicit Scope(TypePackId returnType); // root scope
|
||||
explicit Scope(const ScopePtr& parent, int subLevel = 0); // child scope. Parent must not be nullptr.
|
||||
|
||||
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;
|
||||
std::optional<TypePackId> varargPack;
|
||||
|
||||
TypeLevel level;
|
||||
|
||||
std::unordered_map<Name, TypeFun> exportedTypeBindings;
|
||||
std::unordered_map<Name, TypeFun> privateTypeBindings;
|
||||
std::unordered_map<Name, Location> typeAliasLocations;
|
||||
std::unordered_map<Name, Location> typeAliasNameLocations;
|
||||
std::unordered_map<Name, ModuleName> importedModules; // Mapping from the name in the require statement to the internal moduleName.
|
||||
std::unordered_map<Name, std::unordered_map<Name, TypeFun>> importedTypeBindings;
|
||||
|
||||
DenseHashSet<Name> builtinTypeNames{""};
|
||||
void addBuiltinTypeBinding(const Name& name, const TypeFun& tyFun);
|
||||
|
||||
std::optional<TypeId> lookup(Symbol sym) const;
|
||||
std::optional<TypeId> lookup(DefId def) const;
|
||||
std::optional<std::pair<Binding*, Scope*>> lookupEx(Symbol sym);
|
||||
|
||||
std::optional<TypeFun> lookupType(const Name& name) const;
|
||||
std::optional<TypeFun> lookupImportedType(const Name& moduleAlias, const Name& name) const;
|
||||
|
||||
std::unordered_map<Name, TypePackId> privateTypePackBindings;
|
||||
std::optional<TypePackId> lookupPack(const Name& name) const;
|
||||
|
||||
// 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;
|
||||
|
||||
RefinementMap refinements;
|
||||
DenseHashMap<const Def*, TypeId> dcrRefinements{nullptr};
|
||||
void inheritRefinements(const ScopePtr& childScope);
|
||||
|
||||
// For mutually recursive type aliases, it's important that
|
||||
// they use the same types for the same names.
|
||||
// For instance, in `type Tree<T> { data: T, children: Forest<T> } type Forest<T> = {Tree<T>}`
|
||||
// we need that the generic type `T` in both cases is the same, so we use a cache.
|
||||
std::unordered_map<Name, TypeId> typeAliasTypeParameters;
|
||||
std::unordered_map<Name, TypePackId> typeAliasTypePackParameters;
|
||||
};
|
||||
|
||||
// Returns true iff the left scope encloses the right scope. A Scope* equal to
|
||||
// nullptr is considered to be the outermost-possible scope.
|
||||
bool subsumesStrict(Scope* left, Scope* right);
|
||||
|
||||
// Returns true if the left scope encloses the right scope, or if they are the
|
||||
// same scope. As in subsumesStrict(), nullptr is considered to be the
|
||||
// outermost-possible scope.
|
||||
bool subsumes(Scope* left, Scope* right);
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,36 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Type.h"
|
||||
|
||||
#include <set>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct TypeArena;
|
||||
struct BuiltinTypes;
|
||||
|
||||
struct SimplifyResult
|
||||
{
|
||||
TypeId result;
|
||||
|
||||
std::set<TypeId> blockedTypes;
|
||||
};
|
||||
|
||||
SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty, TypeId discriminant);
|
||||
SimplifyResult simplifyUnion(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty, TypeId discriminant);
|
||||
|
||||
enum class Relation
|
||||
{
|
||||
Disjoint, // No A is a B or vice versa
|
||||
Coincident, // Every A is in B and vice versa
|
||||
Intersects, // Some As are in B and some Bs are in A. ex (number | string) <-> (string | boolean)
|
||||
Subset, // Every A is in B
|
||||
Superset, // Every B is in A
|
||||
};
|
||||
|
||||
Relation relate(TypeId left, TypeId right);
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,36 @@
|
|||
// 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 <vector>
|
||||
#include <string>
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
std::string format(const char* fmt, ...) LUAU_PRINTF_ATTR(1, 2);
|
||||
std::string vformat(const char* fmt, va_list args);
|
||||
|
||||
void formatAppend(std::string& str, const char* fmt, ...) LUAU_PRINTF_ATTR(2, 3);
|
||||
void vformatAppend(std::string& ret, const char* fmt, va_list args);
|
||||
|
||||
std::string join(const std::vector<std::string_view>& segments, std::string_view delimiter);
|
||||
std::string join(const std::vector<std::string>& segments, std::string_view delimiter);
|
||||
|
||||
std::vector<std::string_view> split(std::string_view s, char delimiter);
|
||||
|
||||
// Computes the Damerau-Levenshtein distance of A and B.
|
||||
// https://en.wikipedia.org/wiki/Damerau-Levenshtein_distance#Distance_with_adjacent_transpositions
|
||||
size_t editDistance(std::string_view a, std::string_view b);
|
||||
|
||||
bool startsWith(std::string_view lhs, std::string_view rhs);
|
||||
bool equalsLower(std::string_view lhs, std::string_view rhs);
|
||||
|
||||
size_t hashRange(const char* data, size_t size);
|
||||
|
||||
std::string escape(std::string_view s, bool escapeForInterpString = false);
|
||||
bool isIdentifier(std::string_view s);
|
||||
} // namespace Luau
|
|
@ -0,0 +1,244 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
|
||||
// We provide an implementation of substitution on types,
|
||||
// which recursively replaces types by other types.
|
||||
// Examples include quantification (replacing free types by generics)
|
||||
// and instantiation (replacing generic types by free ones).
|
||||
//
|
||||
// To implement a substitution, implement a subclass of `Substitution`
|
||||
// and provide implementations of `isDirty` (which should be true for types that
|
||||
// should be replaced) and `clean` which replaces any dirty types.
|
||||
//
|
||||
// struct MySubst : Substitution
|
||||
// {
|
||||
// bool isDirty(TypeId ty) override { ... }
|
||||
// bool isDirty(TypePackId tp) override { ... }
|
||||
// TypeId clean(TypeId ty) override { ... }
|
||||
// TypePackId clean(TypePackId tp) override { ... }
|
||||
// bool ignoreChildren(TypeId ty) override { ... }
|
||||
// bool ignoreChildren(TypePackId tp) override { ... }
|
||||
// };
|
||||
//
|
||||
// For example, `Instantiation` in `TypeInfer.cpp` uses this.
|
||||
|
||||
// The implementation of substitution tries not to copy types
|
||||
// unnecessarily. It first finds all the types which can reach
|
||||
// a dirty type, and either cleans them (if they are dirty)
|
||||
// or clones them (if they are not). It then updates the children
|
||||
// of the newly created types. When considering reachability,
|
||||
// we do not consider the children of any type where ignoreChildren(ty) is true.
|
||||
|
||||
// There is a gotcha for cyclic types, which means we can't just use
|
||||
// a straightforward DFS. For example:
|
||||
//
|
||||
// type T = { f : () -> T, g: () -> number, h: X }
|
||||
//
|
||||
// If X is dirty, and is being replaced by X' then the result should be:
|
||||
//
|
||||
// type T' = { f : () -> T', g: () -> number, h: X' }
|
||||
//
|
||||
// that is the type of `f` is replaced, but the type of `g` is not.
|
||||
//
|
||||
// For this reason, we first use Tarjan's algorithm to find strongly
|
||||
// connected components. If any type in an SCC can reach a dirty type,
|
||||
// them the whole SCC can. For instance, in the above example,
|
||||
// `T`, and the type of `f` are in the same SCC, which is why `f` gets
|
||||
// replaced.
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct TxnLog;
|
||||
|
||||
enum class TarjanResult
|
||||
{
|
||||
TooManyChildren,
|
||||
Ok
|
||||
};
|
||||
|
||||
struct TarjanWorklistVertex
|
||||
{
|
||||
int index;
|
||||
int currEdge;
|
||||
int lastEdge;
|
||||
};
|
||||
|
||||
struct TarjanNode
|
||||
{
|
||||
TypeId ty;
|
||||
TypePackId tp;
|
||||
|
||||
bool onStack;
|
||||
bool dirty;
|
||||
|
||||
// Tarjan calculates the lowlink for each vertex,
|
||||
// which is the lowest ancestor index reachable from the vertex.
|
||||
int lowlink;
|
||||
};
|
||||
|
||||
// Tarjan's algorithm for finding the SCCs in a cyclic structure.
|
||||
// https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
|
||||
struct Tarjan
|
||||
{
|
||||
// Vertices (types and type packs) are indexed, using pre-order traversal.
|
||||
DenseHashMap<TypeId, int> typeToIndex{nullptr};
|
||||
DenseHashMap<TypePackId, int> packToIndex{nullptr};
|
||||
|
||||
std::vector<TarjanNode> nodes;
|
||||
|
||||
// Tarjan keeps a stack of vertices where we're still in the process
|
||||
// of finding their SCC.
|
||||
std::vector<int> stack;
|
||||
|
||||
int childCount = 0;
|
||||
int childLimit = 0;
|
||||
|
||||
// This should never be null; ensure you initialize it before calling
|
||||
// substitution methods.
|
||||
const TxnLog* log = nullptr;
|
||||
|
||||
std::vector<TypeId> edgesTy;
|
||||
std::vector<TypePackId> edgesTp;
|
||||
std::vector<TarjanWorklistVertex> worklist;
|
||||
|
||||
// This is hot code, so we optimize recursion to a stack.
|
||||
TarjanResult loop();
|
||||
|
||||
// Find or create the index for a vertex.
|
||||
// Return a boolean which is `true` if it's a freshly created index.
|
||||
std::pair<int, bool> indexify(TypeId ty);
|
||||
std::pair<int, bool> indexify(TypePackId tp);
|
||||
|
||||
// Recursively visit all the children of a vertex
|
||||
void visitChildren(TypeId ty, int index);
|
||||
void visitChildren(TypePackId tp, int index);
|
||||
|
||||
void visitChild(TypeId ty);
|
||||
void visitChild(TypePackId ty);
|
||||
|
||||
template<typename Ty>
|
||||
void visitChild(std::optional<Ty> ty)
|
||||
{
|
||||
if (ty)
|
||||
visitChild(*ty);
|
||||
}
|
||||
|
||||
// Visit the root vertex.
|
||||
TarjanResult visitRoot(TypeId ty);
|
||||
TarjanResult visitRoot(TypePackId ty);
|
||||
|
||||
void clearTarjan();
|
||||
|
||||
// Get/set the dirty bit for an index (grows the vector if needed)
|
||||
bool getDirty(int index);
|
||||
void setDirty(int index, bool d);
|
||||
|
||||
// Find all the dirty vertices reachable from `t`.
|
||||
TarjanResult findDirty(TypeId t);
|
||||
TarjanResult findDirty(TypePackId t);
|
||||
|
||||
// We find dirty vertices using Tarjan
|
||||
void visitEdge(int index, int parentIndex);
|
||||
void visitSCC(int index);
|
||||
|
||||
// Each subclass can decide to ignore some nodes.
|
||||
virtual bool ignoreChildren(TypeId ty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool ignoreChildren(TypePackId ty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Some subclasses might ignore children visit, but not other actions like replacing the children
|
||||
virtual bool ignoreChildrenVisit(TypeId ty)
|
||||
{
|
||||
return ignoreChildren(ty);
|
||||
}
|
||||
|
||||
virtual bool ignoreChildrenVisit(TypePackId ty)
|
||||
{
|
||||
return ignoreChildren(ty);
|
||||
}
|
||||
|
||||
// Subclasses should say which vertices are dirty,
|
||||
// and what to do with dirty vertices.
|
||||
virtual bool isDirty(TypeId ty) = 0;
|
||||
virtual bool isDirty(TypePackId tp) = 0;
|
||||
virtual void foundDirty(TypeId ty) = 0;
|
||||
virtual void foundDirty(TypePackId tp) = 0;
|
||||
};
|
||||
|
||||
// And finally substitution, which finds all the reachable dirty vertices
|
||||
// and replaces them with clean ones.
|
||||
struct Substitution : Tarjan
|
||||
{
|
||||
protected:
|
||||
Substitution(const TxnLog* log_, TypeArena* arena)
|
||||
: arena(arena)
|
||||
{
|
||||
log = log_;
|
||||
LUAU_ASSERT(log);
|
||||
LUAU_ASSERT(arena);
|
||||
}
|
||||
|
||||
public:
|
||||
TypeArena* arena;
|
||||
DenseHashMap<TypeId, TypeId> newTypes{nullptr};
|
||||
DenseHashMap<TypePackId, TypePackId> newPacks{nullptr};
|
||||
DenseHashSet<TypeId> replacedTypes{nullptr};
|
||||
DenseHashSet<TypePackId> replacedTypePacks{nullptr};
|
||||
|
||||
std::optional<TypeId> substitute(TypeId ty);
|
||||
std::optional<TypePackId> substitute(TypePackId tp);
|
||||
|
||||
TypeId replace(TypeId ty);
|
||||
TypePackId replace(TypePackId tp);
|
||||
|
||||
void replaceChildren(TypeId ty);
|
||||
void replaceChildren(TypePackId tp);
|
||||
|
||||
TypeId clone(TypeId ty);
|
||||
TypePackId clone(TypePackId tp);
|
||||
|
||||
// Substitutions use Tarjan to find dirty nodes and replace them
|
||||
void foundDirty(TypeId ty) override;
|
||||
void foundDirty(TypePackId tp) override;
|
||||
|
||||
// Implementing subclasses define how to clean a dirty type.
|
||||
virtual TypeId clean(TypeId ty) = 0;
|
||||
virtual TypePackId clean(TypePackId tp) = 0;
|
||||
|
||||
// Helper functions to create new types (used by subclasses)
|
||||
template<typename T>
|
||||
TypeId addType(const T& tv)
|
||||
{
|
||||
return arena->addType(tv);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
TypePackId addTypePack(const T& tp)
|
||||
{
|
||||
return arena->addTypePack(TypePackVar{tp});
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename Ty>
|
||||
std::optional<Ty> replace(std::optional<Ty> ty)
|
||||
{
|
||||
if (ty)
|
||||
return replace(*ty);
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,120 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/UnifierSharedState.h"
|
||||
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
template<typename A, typename B>
|
||||
struct TryPair;
|
||||
struct InternalErrorReporter;
|
||||
|
||||
class TypeIds;
|
||||
class Normalizer;
|
||||
struct NormalizedType;
|
||||
struct NormalizedClassType;
|
||||
|
||||
struct SubtypingResult
|
||||
{
|
||||
// Did the test succeed?
|
||||
bool isSubtype = false;
|
||||
bool isErrorSuppressing = false;
|
||||
bool normalizationTooComplex = false;
|
||||
|
||||
// If so, what constraints are implied by this relation?
|
||||
// If not, what happened?
|
||||
|
||||
void andAlso(const SubtypingResult& other);
|
||||
void orElse(const SubtypingResult& other);
|
||||
|
||||
// Only negates the `isSubtype`.
|
||||
static SubtypingResult negate(const SubtypingResult& result);
|
||||
static SubtypingResult all(const std::vector<SubtypingResult>& results);
|
||||
static SubtypingResult any(const std::vector<SubtypingResult>& results);
|
||||
};
|
||||
|
||||
struct Subtyping
|
||||
{
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
NotNull<TypeArena> arena;
|
||||
NotNull<Normalizer> normalizer;
|
||||
NotNull<InternalErrorReporter> iceReporter;
|
||||
|
||||
enum class Variance
|
||||
{
|
||||
Covariant,
|
||||
Contravariant
|
||||
};
|
||||
|
||||
Variance variance = Variance::Covariant;
|
||||
|
||||
struct GenericBounds
|
||||
{
|
||||
DenseHashSet<TypeId> lowerBound{nullptr};
|
||||
DenseHashSet<TypeId> upperBound{nullptr};
|
||||
};
|
||||
|
||||
/*
|
||||
* When we encounter a generic over the course of a subtyping test, we need
|
||||
* to tentatively map that generic onto a type on the other side.
|
||||
*/
|
||||
DenseHashMap<TypeId, GenericBounds> mappedGenerics{nullptr};
|
||||
DenseHashMap<TypePackId, TypePackId> mappedGenericPacks{nullptr};
|
||||
|
||||
using SeenSet = std::unordered_set<std::pair<TypeId, TypeId>, TypeIdPairHash>;
|
||||
|
||||
SeenSet seenTypes;
|
||||
|
||||
// TODO cache
|
||||
// TODO cyclic types
|
||||
// TODO recursion limits
|
||||
|
||||
SubtypingResult isSubtype(TypeId subTy, TypeId superTy);
|
||||
SubtypingResult isSubtype(TypePackId subTy, TypePackId superTy);
|
||||
|
||||
private:
|
||||
SubtypingResult isSubtype_(TypeId subTy, TypeId superTy);
|
||||
SubtypingResult isSubtype_(TypePackId subTy, TypePackId superTy);
|
||||
|
||||
template<typename SubTy, typename SuperTy>
|
||||
SubtypingResult isSubtype_(const TryPair<const SubTy*, const SuperTy*>& pair);
|
||||
|
||||
SubtypingResult isSubtype_(TypeId subTy, const UnionType* superUnion);
|
||||
SubtypingResult isSubtype_(const UnionType* subUnion, TypeId superTy);
|
||||
SubtypingResult isSubtype_(TypeId subTy, const IntersectionType* superIntersection);
|
||||
SubtypingResult isSubtype_(const IntersectionType* subIntersection, TypeId superTy);
|
||||
SubtypingResult isSubtype_(const PrimitiveType* subPrim, const PrimitiveType* superPrim);
|
||||
SubtypingResult isSubtype_(const SingletonType* subSingleton, const PrimitiveType* superPrim);
|
||||
SubtypingResult isSubtype_(const SingletonType* subSingleton, const SingletonType* superSingleton);
|
||||
SubtypingResult isSubtype_(const TableType* subTable, const TableType* superTable);
|
||||
SubtypingResult isSubtype_(const MetatableType* subMt, const MetatableType* superMt);
|
||||
SubtypingResult isSubtype_(const MetatableType* subMt, const TableType* superTable);
|
||||
SubtypingResult isSubtype_(const ClassType* subClass, const ClassType* superClass);
|
||||
SubtypingResult isSubtype_(const ClassType* subClass, const TableType* superTable); // Actually a class <: shape.
|
||||
SubtypingResult isSubtype_(const FunctionType* subFunction, const FunctionType* superFunction);
|
||||
SubtypingResult isSubtype_(const PrimitiveType* subPrim, const TableType* superTable);
|
||||
SubtypingResult isSubtype_(const SingletonType* subSingleton, const TableType* superTable);
|
||||
|
||||
SubtypingResult isSubtype_(const NormalizedType* subNorm, const NormalizedType* superNorm);
|
||||
SubtypingResult isSubtype_(const NormalizedClassType& subClass, const NormalizedClassType& superClass, const TypeIds& superTables);
|
||||
SubtypingResult isSubtype_(const TypeIds& subTypes, const TypeIds& superTypes);
|
||||
|
||||
SubtypingResult isSubtype_(const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic);
|
||||
|
||||
bool bindGeneric(TypeId subTp, TypeId superTp);
|
||||
bool bindGeneric(TypePackId subTp, TypePackId superTp);
|
||||
|
||||
template <typename T, typename Container>
|
||||
TypeId makeAggregateType(const Container& container, TypeId orElse);
|
||||
|
||||
[[noreturn]]
|
||||
void unexpected(TypePackId tp);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,95 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct Symbol
|
||||
{
|
||||
Symbol()
|
||||
: local(nullptr)
|
||||
, global()
|
||||
{
|
||||
}
|
||||
|
||||
Symbol(AstLocal* local)
|
||||
: local(local)
|
||||
, global()
|
||||
{
|
||||
}
|
||||
|
||||
Symbol(const AstName& global)
|
||||
: local(nullptr)
|
||||
, global(global)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Symbol(const T&) = delete;
|
||||
|
||||
AstLocal* local;
|
||||
AstName global;
|
||||
|
||||
explicit operator bool() const
|
||||
{
|
||||
return local != nullptr || global.value != nullptr;
|
||||
}
|
||||
|
||||
bool operator==(const Symbol& rhs) const;
|
||||
|
||||
bool operator!=(const Symbol& rhs) const
|
||||
{
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
bool operator<(const Symbol& rhs) const
|
||||
{
|
||||
if (local && rhs.local)
|
||||
return local < rhs.local;
|
||||
else if (global.value && rhs.global.value)
|
||||
return global < rhs.global;
|
||||
else if (local)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
AstName astName() const
|
||||
{
|
||||
if (local)
|
||||
return local->name;
|
||||
|
||||
LUAU_ASSERT(global.value);
|
||||
return global;
|
||||
}
|
||||
|
||||
const char* c_str() const
|
||||
{
|
||||
if (local)
|
||||
return local->name.value;
|
||||
|
||||
LUAU_ASSERT(global.value);
|
||||
return global.value;
|
||||
}
|
||||
};
|
||||
|
||||
std::string toString(const Symbol& name);
|
||||
|
||||
} // namespace Luau
|
||||
|
||||
namespace std
|
||||
{
|
||||
template<>
|
||||
struct hash<Luau::Symbol>
|
||||
{
|
||||
std::size_t operator()(const Luau::Symbol& s) const noexcept
|
||||
{
|
||||
return std::hash<const Luau::AstLocal*>()(s.local) ^ (s.global.value ? std::hash<std::string_view>()(s.global.value) : 0);
|
||||
}
|
||||
};
|
||||
} // namespace std
|
|
@ -0,0 +1,231 @@
|
|||
// 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 <vector>
|
||||
#include <memory>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauTimeTracing)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace TimeTrace
|
||||
{
|
||||
double getClock();
|
||||
uint32_t getClockMicroseconds();
|
||||
} // namespace TimeTrace
|
||||
} // namespace Luau
|
||||
|
||||
#if defined(LUAU_ENABLE_TIME_TRACE)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace TimeTrace
|
||||
{
|
||||
struct Token
|
||||
{
|
||||
const char* name;
|
||||
const char* category;
|
||||
};
|
||||
|
||||
enum class EventType : uint8_t
|
||||
{
|
||||
Enter,
|
||||
Leave,
|
||||
|
||||
ArgName,
|
||||
ArgValue,
|
||||
};
|
||||
|
||||
struct Event
|
||||
{
|
||||
EventType type;
|
||||
uint16_t token;
|
||||
|
||||
union
|
||||
{
|
||||
uint32_t microsec; // 1 hour trace limit
|
||||
uint32_t dataPos;
|
||||
} data;
|
||||
};
|
||||
|
||||
struct GlobalContext;
|
||||
struct ThreadContext;
|
||||
|
||||
std::shared_ptr<GlobalContext> getGlobalContext();
|
||||
|
||||
uint16_t createToken(GlobalContext& context, const char* name, const char* category);
|
||||
uint32_t createThread(GlobalContext& context, ThreadContext* threadContext);
|
||||
void releaseThread(GlobalContext& context, ThreadContext* threadContext);
|
||||
void flushEvents(GlobalContext& context, uint32_t threadId, const std::vector<Event>& events, const std::vector<char>& data);
|
||||
|
||||
struct ThreadContext
|
||||
{
|
||||
ThreadContext()
|
||||
: globalContext(getGlobalContext())
|
||||
{
|
||||
threadId = createThread(*globalContext, this);
|
||||
}
|
||||
|
||||
~ThreadContext()
|
||||
{
|
||||
if (!events.empty())
|
||||
flushEvents();
|
||||
|
||||
releaseThread(*globalContext, this);
|
||||
}
|
||||
|
||||
void flushEvents()
|
||||
{
|
||||
static uint16_t flushToken = createToken(*globalContext, "flushEvents", "TimeTrace");
|
||||
|
||||
events.push_back({EventType::Enter, flushToken, {getClockMicroseconds()}});
|
||||
|
||||
TimeTrace::flushEvents(*globalContext, threadId, events, data);
|
||||
|
||||
events.clear();
|
||||
data.clear();
|
||||
|
||||
events.push_back({EventType::Leave, 0, {getClockMicroseconds()}});
|
||||
}
|
||||
|
||||
void eventEnter(uint16_t token)
|
||||
{
|
||||
eventEnter(token, getClockMicroseconds());
|
||||
}
|
||||
|
||||
void eventEnter(uint16_t token, uint32_t microsec)
|
||||
{
|
||||
events.push_back({EventType::Enter, token, {microsec}});
|
||||
}
|
||||
|
||||
void eventLeave()
|
||||
{
|
||||
eventLeave(getClockMicroseconds());
|
||||
}
|
||||
|
||||
void eventLeave(uint32_t microsec)
|
||||
{
|
||||
events.push_back({EventType::Leave, 0, {microsec}});
|
||||
|
||||
if (events.size() > kEventFlushLimit)
|
||||
flushEvents();
|
||||
}
|
||||
|
||||
void eventArgument(const char* name, const char* value)
|
||||
{
|
||||
uint32_t pos = uint32_t(data.size());
|
||||
data.insert(data.end(), name, name + strlen(name) + 1);
|
||||
events.push_back({EventType::ArgName, 0, {pos}});
|
||||
|
||||
pos = uint32_t(data.size());
|
||||
data.insert(data.end(), value, value + strlen(value) + 1);
|
||||
events.push_back({EventType::ArgValue, 0, {pos}});
|
||||
}
|
||||
|
||||
std::shared_ptr<GlobalContext> globalContext;
|
||||
uint32_t threadId;
|
||||
std::vector<Event> events;
|
||||
std::vector<char> data;
|
||||
|
||||
static constexpr size_t kEventFlushLimit = 8192;
|
||||
};
|
||||
|
||||
ThreadContext& getThreadContext();
|
||||
|
||||
struct Scope
|
||||
{
|
||||
explicit Scope(uint16_t token)
|
||||
: context(getThreadContext())
|
||||
{
|
||||
if (!FFlag::DebugLuauTimeTracing)
|
||||
return;
|
||||
|
||||
context.eventEnter(token);
|
||||
}
|
||||
|
||||
~Scope()
|
||||
{
|
||||
if (!FFlag::DebugLuauTimeTracing)
|
||||
return;
|
||||
|
||||
context.eventLeave();
|
||||
}
|
||||
|
||||
ThreadContext& context;
|
||||
};
|
||||
|
||||
struct OptionalTailScope
|
||||
{
|
||||
explicit OptionalTailScope(uint16_t token, uint32_t threshold)
|
||||
: context(getThreadContext())
|
||||
, token(token)
|
||||
, threshold(threshold)
|
||||
{
|
||||
if (!FFlag::DebugLuauTimeTracing)
|
||||
return;
|
||||
|
||||
pos = uint32_t(context.events.size());
|
||||
microsec = getClockMicroseconds();
|
||||
}
|
||||
|
||||
~OptionalTailScope()
|
||||
{
|
||||
if (!FFlag::DebugLuauTimeTracing)
|
||||
return;
|
||||
|
||||
if (pos == context.events.size())
|
||||
{
|
||||
uint32_t curr = getClockMicroseconds();
|
||||
|
||||
if (curr - microsec > threshold)
|
||||
{
|
||||
context.eventEnter(token, microsec);
|
||||
context.eventLeave(curr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ThreadContext& context;
|
||||
uint16_t token;
|
||||
uint32_t threshold;
|
||||
uint32_t microsec;
|
||||
uint32_t pos;
|
||||
};
|
||||
|
||||
LUAU_NOINLINE uint16_t createScopeData(const char* name, const char* category);
|
||||
|
||||
} // namespace TimeTrace
|
||||
} // namespace Luau
|
||||
|
||||
// Regular scope
|
||||
#define LUAU_TIMETRACE_SCOPE(name, category) \
|
||||
static uint16_t lttScopeStatic = Luau::TimeTrace::createScopeData(name, category); \
|
||||
Luau::TimeTrace::Scope lttScope(lttScopeStatic)
|
||||
|
||||
// A scope without nested scopes that may be skipped if the time it took is less than the threshold
|
||||
#define LUAU_TIMETRACE_OPTIONAL_TAIL_SCOPE(name, category, microsec) \
|
||||
static uint16_t lttScopeStaticOptTail = Luau::TimeTrace::createScopeData(name, category); \
|
||||
Luau::TimeTrace::OptionalTailScope lttScope(lttScopeStaticOptTail, microsec)
|
||||
|
||||
// Extra key/value data can be added to regular scopes
|
||||
#define LUAU_TIMETRACE_ARGUMENT(name, value) \
|
||||
do \
|
||||
{ \
|
||||
if (FFlag::DebugLuauTimeTracing) \
|
||||
lttScope.context.eventArgument(name, value); \
|
||||
} while (false)
|
||||
|
||||
#else
|
||||
|
||||
#define LUAU_TIMETRACE_SCOPE(name, category)
|
||||
#define LUAU_TIMETRACE_OPTIONAL_TAIL_SCOPE(name, category, microsec)
|
||||
#define LUAU_TIMETRACE_ARGUMENT(name, value) \
|
||||
do \
|
||||
{ \
|
||||
} while (false)
|
||||
|
||||
#endif
|
|
@ -0,0 +1,31 @@
|
|||
// 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 <string>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
struct Type;
|
||||
using TypeId = const Type*;
|
||||
|
||||
struct TypePackVar;
|
||||
using TypePackId = const TypePackVar*;
|
||||
|
||||
struct ToDotOptions
|
||||
{
|
||||
bool showPointers = true; // Show pointer value in the node label
|
||||
bool duplicatePrimitives = true; // Display primitive types and 'any' as separate nodes
|
||||
};
|
||||
|
||||
std::string toDot(TypeId ty, const ToDotOptions& opts);
|
||||
std::string toDot(TypePackId tp, const ToDotOptions& opts);
|
||||
|
||||
std::string toDot(TypeId ty);
|
||||
std::string toDot(TypePackId tp);
|
||||
|
||||
void dumpDot(TypeId ty);
|
||||
void dumpDot(TypePackId tp);
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,144 @@
|
|||
// 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 <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
LUAU_FASTINT(LuauTableTypeMaximumStringifierLength)
|
||||
LUAU_FASTINT(LuauTypeMaximumStringifierLength)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
class AstExpr;
|
||||
|
||||
struct Scope;
|
||||
|
||||
struct Type;
|
||||
using TypeId = const Type*;
|
||||
|
||||
struct TypePackVar;
|
||||
using TypePackId = const TypePackVar*;
|
||||
|
||||
struct FunctionType;
|
||||
struct Constraint;
|
||||
|
||||
struct Position;
|
||||
struct Location;
|
||||
|
||||
struct ToStringNameMap
|
||||
{
|
||||
std::unordered_map<TypeId, std::string> types;
|
||||
std::unordered_map<TypePackId, std::string> typePacks;
|
||||
};
|
||||
|
||||
struct ToStringOptions
|
||||
{
|
||||
bool exhaustive = false; // If true, we produce complete output rather than comprehensible output
|
||||
bool useLineBreaks = false; // If true, we insert new lines to separate long results such as table entries/metatable.
|
||||
bool functionTypeArguments = false; // If true, output function type argument names when they are available
|
||||
bool hideTableKind = false; // If true, all tables will be surrounded with plain '{}'
|
||||
bool hideNamedFunctionTypeParameters = false; // If true, type parameters of functions will be hidden at top-level.
|
||||
bool hideFunctionSelfArgument = false; // If true, `self: X` will be omitted from the function signature if the function has self
|
||||
size_t maxTableLength = size_t(FInt::LuauTableTypeMaximumStringifierLength); // Only applied to TableTypes
|
||||
size_t maxTypeLength = size_t(FInt::LuauTypeMaximumStringifierLength);
|
||||
ToStringNameMap nameMap;
|
||||
std::shared_ptr<Scope> scope; // If present, module names will be added and types that are not available in scope will be marked as 'invalid'
|
||||
std::vector<std::string> namedFunctionOverrideArgNames; // If present, named function argument names will be overridden
|
||||
};
|
||||
|
||||
struct ToStringResult
|
||||
{
|
||||
std::string name;
|
||||
|
||||
bool invalid = false;
|
||||
bool error = false;
|
||||
bool cycle = false;
|
||||
bool truncated = false;
|
||||
};
|
||||
|
||||
ToStringResult toStringDetailed(TypeId ty, ToStringOptions& opts);
|
||||
ToStringResult toStringDetailed(TypePackId ty, ToStringOptions& opts);
|
||||
|
||||
std::string toString(TypeId ty, ToStringOptions& opts);
|
||||
std::string toString(TypePackId ty, ToStringOptions& opts);
|
||||
|
||||
// These overloads are selected when a temporary ToStringOptions is passed. (eg
|
||||
// via an initializer list)
|
||||
inline std::string toString(TypePackId ty, ToStringOptions&& opts)
|
||||
{
|
||||
// Delegate to the overload (TypePackId, ToStringOptions&)
|
||||
return toString(ty, opts);
|
||||
}
|
||||
inline std::string toString(TypeId ty, ToStringOptions&& opts)
|
||||
{
|
||||
// Delegate to the overload (TypeId, ToStringOptions&)
|
||||
return toString(ty, opts);
|
||||
}
|
||||
|
||||
// These are offered as overloads rather than a default parameter so that they can be easily invoked from within the MSVC debugger.
|
||||
// You can use them in watch expressions!
|
||||
inline std::string toString(TypeId ty)
|
||||
{
|
||||
return toString(ty, ToStringOptions{});
|
||||
}
|
||||
inline std::string toString(TypePackId ty)
|
||||
{
|
||||
return toString(ty, ToStringOptions{});
|
||||
}
|
||||
|
||||
std::string toString(const Constraint& c, ToStringOptions& opts);
|
||||
|
||||
inline std::string toString(const Constraint& c, ToStringOptions&& opts)
|
||||
{
|
||||
return toString(c, opts);
|
||||
}
|
||||
|
||||
std::string toString(const Constraint& c);
|
||||
|
||||
std::string toString(const Type& tv, ToStringOptions& opts);
|
||||
std::string toString(const TypePackVar& tp, ToStringOptions& opts);
|
||||
|
||||
inline std::string toString(const Type& tv)
|
||||
{
|
||||
ToStringOptions opts;
|
||||
return toString(tv, opts);
|
||||
}
|
||||
|
||||
inline std::string toString(const TypePackVar& tp)
|
||||
{
|
||||
ToStringOptions opts;
|
||||
return toString(tp, opts);
|
||||
}
|
||||
|
||||
std::string toStringNamedFunction(const std::string& funcName, const FunctionType& ftv, ToStringOptions& opts);
|
||||
|
||||
inline std::string toStringNamedFunction(const std::string& funcName, const FunctionType& ftv)
|
||||
{
|
||||
ToStringOptions opts;
|
||||
return toStringNamedFunction(funcName, ftv, opts);
|
||||
}
|
||||
|
||||
std::optional<std::string> getFunctionNameAsString(const AstExpr& expr);
|
||||
|
||||
// It could be useful to see the text representation of a type during a debugging session instead of exploring the content of the class
|
||||
// These functions will dump the type to stdout and can be evaluated in Watch/Immediate windows or as gdb/lldb expression
|
||||
std::string dump(TypeId ty);
|
||||
std::string dump(const std::optional<TypeId>& ty);
|
||||
std::string dump(TypePackId ty);
|
||||
std::string dump(const std::optional<TypePackId>& ty);
|
||||
std::string dump(const Constraint& c);
|
||||
|
||||
std::string dump(const std::shared_ptr<Scope>& scope, const char* name);
|
||||
|
||||
std::string generateName(size_t n);
|
||||
|
||||
std::string toString(const Position& position);
|
||||
std::string toString(const Location& location, int offset = 0, bool useBegin = true);
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,19 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
struct AstArray;
|
||||
|
||||
class AstStat;
|
||||
|
||||
bool containsFunctionCall(const AstStat& stat);
|
||||
bool containsFunctionCallOrReturn(const AstStat& stat);
|
||||
bool isFunction(const AstStat& stat);
|
||||
void toposort(std::vector<AstStat*>& stats);
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,31 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/ParseOptions.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
class AstNode;
|
||||
class AstStatBlock;
|
||||
|
||||
struct TranspileResult
|
||||
{
|
||||
std::string code;
|
||||
Location errorLocation;
|
||||
std::string parseError; // Nonempty if the transpile failed
|
||||
};
|
||||
|
||||
std::string toString(AstNode* node);
|
||||
void dump(AstNode* node);
|
||||
|
||||
// Never fails on a well-formed AST
|
||||
std::string transpile(AstStatBlock& ast);
|
||||
std::string transpileWithTypes(AstStatBlock& block);
|
||||
|
||||
// Only fails when parsing fails
|
||||
TranspileResult transpile(std::string_view source, ParseOptions options = ParseOptions{}, bool withTypes = false);
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,322 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypePack.h"
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
using TypeOrPackId = const void*;
|
||||
|
||||
// Pending state for a Type. Generated by a TxnLog and committed via
|
||||
// TxnLog::commit.
|
||||
struct PendingType
|
||||
{
|
||||
// The pending Type state.
|
||||
Type pending;
|
||||
|
||||
// On very rare occasions, we need to delete an entry from the TxnLog.
|
||||
// DenseHashMap does not afford that so we note its deadness here.
|
||||
bool dead = false;
|
||||
|
||||
explicit PendingType(Type state)
|
||||
: pending(std::move(state))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
std::string toString(PendingType* pending);
|
||||
std::string dump(PendingType* pending);
|
||||
|
||||
// Pending state for a TypePackVar. Generated by a TxnLog and committed via
|
||||
// TxnLog::commit.
|
||||
struct PendingTypePack
|
||||
{
|
||||
// The pending TypePackVar state.
|
||||
TypePackVar pending;
|
||||
|
||||
explicit PendingTypePack(TypePackVar state)
|
||||
: pending(std::move(state))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
std::string toString(PendingTypePack* pending);
|
||||
std::string dump(PendingTypePack* pending);
|
||||
|
||||
template<typename T>
|
||||
T* getMutable(PendingType* pending)
|
||||
{
|
||||
// We use getMutable here because this state is intended to be mutated freely.
|
||||
return getMutable<T>(&pending->pending);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* getMutable(PendingTypePack* pending)
|
||||
{
|
||||
// We use getMutable here because this state is intended to be mutated freely.
|
||||
return getMutable<T>(&pending->pending);
|
||||
}
|
||||
|
||||
// Log of what TypeIds we are rebinding, to be committed later.
|
||||
struct TxnLog
|
||||
{
|
||||
explicit TxnLog(bool useScopes = false)
|
||||
: typeVarChanges(nullptr)
|
||||
, typePackChanges(nullptr)
|
||||
, ownedSeen()
|
||||
, useScopes(useScopes)
|
||||
, sharedSeen(&ownedSeen)
|
||||
{
|
||||
}
|
||||
|
||||
explicit TxnLog(TxnLog* parent)
|
||||
: typeVarChanges(nullptr)
|
||||
, typePackChanges(nullptr)
|
||||
, parent(parent)
|
||||
{
|
||||
if (parent)
|
||||
{
|
||||
sharedSeen = parent->sharedSeen;
|
||||
}
|
||||
else
|
||||
{
|
||||
sharedSeen = &ownedSeen;
|
||||
}
|
||||
}
|
||||
|
||||
explicit TxnLog(std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen)
|
||||
: typeVarChanges(nullptr)
|
||||
, typePackChanges(nullptr)
|
||||
, sharedSeen(sharedSeen)
|
||||
{
|
||||
}
|
||||
|
||||
TxnLog(const TxnLog&) = delete;
|
||||
TxnLog& operator=(const TxnLog&) = delete;
|
||||
|
||||
TxnLog(TxnLog&&) = default;
|
||||
TxnLog& operator=(TxnLog&&) = default;
|
||||
|
||||
// Gets an empty TxnLog pointer. This is useful for constructs that
|
||||
// take a TxnLog, like TypePackIterator - use the empty log if you
|
||||
// don't have a TxnLog to give it.
|
||||
static const TxnLog* empty();
|
||||
|
||||
// Joins another TxnLog onto this one. You should use std::move to avoid
|
||||
// copying the rhs TxnLog.
|
||||
//
|
||||
// If both logs talk about the same type, pack, or table, the rhs takes
|
||||
// priority.
|
||||
void concat(TxnLog rhs);
|
||||
void concatAsIntersections(TxnLog rhs, NotNull<TypeArena> arena);
|
||||
void concatAsUnion(TxnLog rhs, NotNull<TypeArena> arena);
|
||||
|
||||
// Commits the TxnLog, rebinding all type pointers to their pending states.
|
||||
// Clears the TxnLog afterwards.
|
||||
void commit();
|
||||
|
||||
// Clears the TxnLog without committing any pending changes.
|
||||
void clear();
|
||||
|
||||
// Computes an inverse of this TxnLog at the current time.
|
||||
// This method should be called before commit is called in order to give an
|
||||
// accurate result. Committing the inverse of a TxnLog will undo the changes
|
||||
// made by commit, assuming the inverse log is accurate.
|
||||
TxnLog inverse();
|
||||
|
||||
bool haveSeen(TypeId lhs, TypeId rhs) const;
|
||||
void pushSeen(TypeId lhs, TypeId rhs);
|
||||
void popSeen(TypeId lhs, TypeId rhs);
|
||||
|
||||
bool haveSeen(TypePackId lhs, TypePackId rhs) const;
|
||||
void pushSeen(TypePackId lhs, TypePackId rhs);
|
||||
void popSeen(TypePackId lhs, TypePackId rhs);
|
||||
|
||||
// Queues a type for modification. The original type will not change until commit
|
||||
// is called. Use pending to get the pending state.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingType* queue(TypeId ty);
|
||||
|
||||
// Queues a type pack for modification. The original type pack will not change
|
||||
// until commit is called. Use pending to get the pending state.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingTypePack* queue(TypePackId tp);
|
||||
|
||||
// Returns the pending state of a type, or nullptr if there isn't any. It is important
|
||||
// to note that this pending state is not transitive: the pending state may reference
|
||||
// non-pending types freely, so you may need to call pending multiple times to view the
|
||||
// entire pending state of a type graph.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingType* pending(TypeId ty) const;
|
||||
|
||||
// Returns the pending state of a type pack, or nullptr if there isn't any. It is
|
||||
// important to note that this pending state is not transitive: the pending state may
|
||||
// reference non-pending types freely, so you may need to call pending multiple times
|
||||
// to view the entire pending state of a type graph.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingTypePack* pending(TypePackId tp) const;
|
||||
|
||||
// Queues a replacement of a type with another type.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingType* replace(TypeId ty, Type replacement);
|
||||
|
||||
// Queues a replacement of a type pack with another type pack.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingTypePack* replace(TypePackId tp, TypePackVar replacement);
|
||||
|
||||
// Queues a replacement of a table type with another table type that is bound
|
||||
// to a specific value.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingType* bindTable(TypeId ty, std::optional<TypeId> newBoundTo);
|
||||
|
||||
// Queues a replacement of a type with a level with a duplicate of that type
|
||||
// with a new type level.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingType* changeLevel(TypeId ty, TypeLevel newLevel);
|
||||
|
||||
// Queues a replacement of a type pack with a level with a duplicate of that
|
||||
// type pack with a new type level.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
PendingType* changeIndexer(TypeId ty, std::optional<TableIndexer> indexer);
|
||||
|
||||
// Returns the type level of the pending state of the type, or the level of that
|
||||
// type, if no pending state exists. If the type doesn't have a notion of a level,
|
||||
// returns nullopt. If the pending state doesn't have a notion of a level, but the
|
||||
// original state does, returns nullopt.
|
||||
std::optional<TypeLevel> getLevel(TypeId ty) const;
|
||||
|
||||
// Follows a type, accounting for pending type states. The returned type may have
|
||||
// pending state; you should use `pending` or `get` to find out.
|
||||
TypeId follow(TypeId ty) const;
|
||||
|
||||
// Follows a type pack, accounting for pending type states. The returned type pack
|
||||
// may have pending state; you should use `pending` or `get` to find out.
|
||||
TypePackId follow(TypePackId tp) const;
|
||||
|
||||
// Replaces a given type's state with a new variant. Returns the new pending state
|
||||
// of that type.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
template<typename T>
|
||||
PendingType* replace(TypeId ty, T replacement)
|
||||
{
|
||||
return replace(ty, Type(replacement));
|
||||
}
|
||||
|
||||
// Replaces a given type pack's state with a new variant. Returns the new
|
||||
// pending state of that type pack.
|
||||
//
|
||||
// The pointer returned lives until `commit` or `clear` is called.
|
||||
template<typename T>
|
||||
PendingTypePack* replace(TypePackId tp, T replacement)
|
||||
{
|
||||
return replace(tp, TypePackVar(replacement));
|
||||
}
|
||||
|
||||
// Returns T if a given type or type pack is this variant, respecting the
|
||||
// log's pending state.
|
||||
//
|
||||
// Do not retain this pointer; it has the potential to be invalidated when
|
||||
// commit or clear is called.
|
||||
template<typename T, typename TID>
|
||||
T* getMutable(TID ty) const
|
||||
{
|
||||
auto* pendingTy = pending(ty);
|
||||
if (pendingTy)
|
||||
return Luau::getMutable<T>(pendingTy);
|
||||
|
||||
return Luau::getMutable<T>(ty);
|
||||
}
|
||||
|
||||
template<typename T, typename TID>
|
||||
const T* get(TID ty) const
|
||||
{
|
||||
return this->getMutable<T>(ty);
|
||||
}
|
||||
|
||||
// Returns whether a given type or type pack is a given state, respecting the
|
||||
// log's pending state.
|
||||
//
|
||||
// This method will not assert if called on a BoundType or BoundTypePack.
|
||||
template<typename T, typename TID>
|
||||
bool is(TID ty) const
|
||||
{
|
||||
// We do not use getMutable here because this method can be called on
|
||||
// BoundTypes, which triggers an assertion.
|
||||
auto* pendingTy = pending(ty);
|
||||
if (pendingTy)
|
||||
return Luau::get_if<T>(&pendingTy->pending.ty) != nullptr;
|
||||
|
||||
return Luau::get_if<T>(&ty->ty) != nullptr;
|
||||
}
|
||||
|
||||
std::pair<std::vector<TypeId>, std::vector<TypePackId>> getChanges() const;
|
||||
|
||||
private:
|
||||
// unique_ptr is used to give us stable pointers across insertions into the
|
||||
// map. Otherwise, it would be really easy to accidentally invalidate the
|
||||
// pointers returned from queue/pending.
|
||||
DenseHashMap<TypeId, std::unique_ptr<PendingType>> typeVarChanges;
|
||||
DenseHashMap<TypePackId, std::unique_ptr<PendingTypePack>> typePackChanges;
|
||||
|
||||
TxnLog* parent = nullptr;
|
||||
|
||||
// Owned version of sharedSeen. This should not be accessed directly in
|
||||
// TxnLogs; use sharedSeen instead. This field exists because in the tree
|
||||
// of TxnLogs, the root must own its seen set. In all descendant TxnLogs,
|
||||
// this is an empty vector.
|
||||
std::vector<std::pair<TypeOrPackId, TypeOrPackId>> ownedSeen;
|
||||
|
||||
bool haveSeen(TypeOrPackId lhs, TypeOrPackId rhs) const;
|
||||
void pushSeen(TypeOrPackId lhs, TypeOrPackId rhs);
|
||||
void popSeen(TypeOrPackId lhs, TypeOrPackId rhs);
|
||||
|
||||
public:
|
||||
// There is one spot in the code where TxnLog has to reconcile collisions
|
||||
// between parallel logs. In that codepath, we have to work out which of two
|
||||
// FreeTypes subsumes the other. If useScopes is false, the TypeLevel is
|
||||
// used. Else we use the embedded Scope*.
|
||||
bool useScopes = false;
|
||||
|
||||
// It is sometimes the case under DCR that we speculatively rebind
|
||||
// GenericTypes to other types as though they were free. We mark logs that
|
||||
// contain these kinds of substitutions as radioactive so that we know that
|
||||
// we must never commit one.
|
||||
bool radioactive = false;
|
||||
|
||||
// Used to avoid infinite recursion when types are cyclic.
|
||||
// Shared with all the descendent TxnLogs.
|
||||
std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,56 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/TypedAllocator.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypePack.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
struct Module;
|
||||
|
||||
struct TypeArena
|
||||
{
|
||||
TypedAllocator<Type> types;
|
||||
TypedAllocator<TypePackVar> typePacks;
|
||||
|
||||
// Owning module, if any
|
||||
Module* owningModule = nullptr;
|
||||
|
||||
void clear();
|
||||
|
||||
template<typename T>
|
||||
TypeId addType(T tv)
|
||||
{
|
||||
if constexpr (std::is_same_v<T, UnionType>)
|
||||
LUAU_ASSERT(tv.options.size() >= 2);
|
||||
|
||||
return addTV(Type(std::move(tv)));
|
||||
}
|
||||
|
||||
TypeId addTV(Type&& tv);
|
||||
|
||||
TypeId freshType(TypeLevel level);
|
||||
TypeId freshType(Scope* scope);
|
||||
TypeId freshType(Scope* scope, TypeLevel level);
|
||||
|
||||
TypePackId freshTypePack(Scope* scope);
|
||||
|
||||
TypePackId addTypePack(std::initializer_list<TypeId> types);
|
||||
TypePackId addTypePack(std::vector<TypeId> types, std::optional<TypePackId> tail = {});
|
||||
TypePackId addTypePack(TypePack pack);
|
||||
TypePackId addTypePack(TypePackVar pack);
|
||||
|
||||
template<typename T>
|
||||
TypePackId addTypePack(T tp)
|
||||
{
|
||||
return addTypePack(TypePackVar(std::move(tp)));
|
||||
}
|
||||
};
|
||||
|
||||
void freeze(TypeArena& arena);
|
||||
void unfreeze(TypeArena& arena);
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,21 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Module.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct TypeRehydrationOptions
|
||||
{
|
||||
std::unordered_set<std::string> bannedNames;
|
||||
bool expandClassProps = false;
|
||||
};
|
||||
|
||||
void attachTypeData(SourceModule& source, Module& result);
|
||||
|
||||
AstType* rehydrateAnnotation(TypeId type, Allocator* allocator, const TypeRehydrationOptions& options = {});
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,41 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Cancellation.h"
|
||||
#include "Luau/Error.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
class TimeLimitError : public InternalCompilerError
|
||||
{
|
||||
public:
|
||||
explicit TimeLimitError(const std::string& moduleName)
|
||||
: InternalCompilerError("Typeinfer failed to complete in allotted time", moduleName)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class UserCancelError : public InternalCompilerError
|
||||
{
|
||||
public:
|
||||
explicit UserCancelError(const std::string& moduleName)
|
||||
: InternalCompilerError("Analysis has been cancelled by user", moduleName)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct TypeCheckLimits
|
||||
{
|
||||
std::optional<double> finishTime;
|
||||
std::optional<int> instantiationChildLimit;
|
||||
std::optional<int> unifierIterationLimit;
|
||||
|
||||
std::shared_ptr<FrontendCancellationToken> cancellationToken;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,20 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/NotNull.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct BuiltinTypes;
|
||||
struct DcrLogger;
|
||||
struct TypeCheckLimits;
|
||||
struct UnifierSharedState;
|
||||
|
||||
void check(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> sharedState, NotNull<TypeCheckLimits> limits, DcrLogger* logger, const SourceModule& sourceModule,
|
||||
Module* module);
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,125 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct Type;
|
||||
using TypeId = const Type*;
|
||||
|
||||
struct TypePackVar;
|
||||
using TypePackId = const TypePackVar*;
|
||||
|
||||
struct TypeArena;
|
||||
struct BuiltinTypes;
|
||||
struct TxnLog;
|
||||
class Normalizer;
|
||||
|
||||
/// Represents a reduction result, which may have successfully reduced the type,
|
||||
/// may have concretely failed to reduce the type, or may simply be stuck
|
||||
/// without more information.
|
||||
template<typename Ty>
|
||||
struct TypeFamilyReductionResult
|
||||
{
|
||||
/// The result of the reduction, if any. If this is nullopt, the family
|
||||
/// could not be reduced.
|
||||
std::optional<Ty> result;
|
||||
/// Whether the result is uninhabited: whether we know, unambiguously and
|
||||
/// permanently, whether this type family reduction results in an
|
||||
/// uninhabitable type. This will trigger an error to be reported.
|
||||
bool uninhabited;
|
||||
/// Any types that need to be progressed or mutated before the reduction may
|
||||
/// proceed.
|
||||
std::vector<TypeId> blockedTypes;
|
||||
/// Any type packs that need to be progressed or mutated before the
|
||||
/// reduction may proceed.
|
||||
std::vector<TypePackId> blockedPacks;
|
||||
};
|
||||
|
||||
/// Represents a type function that may be applied to map a series of types and
|
||||
/// type packs to a single output type.
|
||||
struct TypeFamily
|
||||
{
|
||||
/// The human-readable name of the type family. Used to stringify instance
|
||||
/// types.
|
||||
std::string name;
|
||||
|
||||
/// The reducer function for the type family.
|
||||
std::function<TypeFamilyReductionResult<TypeId>(std::vector<TypeId>, std::vector<TypePackId>, NotNull<TypeArena>, NotNull<BuiltinTypes>,
|
||||
NotNull<TxnLog>, NotNull<Scope>, NotNull<Normalizer>)>
|
||||
reducer;
|
||||
};
|
||||
|
||||
/// Represents a type function that may be applied to map a series of types and
|
||||
/// type packs to a single output type pack.
|
||||
struct TypePackFamily
|
||||
{
|
||||
/// The human-readable name of the type pack family. Used to stringify
|
||||
/// instance packs.
|
||||
std::string name;
|
||||
|
||||
/// The reducer function for the type pack family.
|
||||
std::function<TypeFamilyReductionResult<TypePackId>(std::vector<TypeId>, std::vector<TypePackId>, NotNull<TypeArena>, NotNull<BuiltinTypes>,
|
||||
NotNull<TxnLog>, NotNull<Scope>, NotNull<Normalizer>)>
|
||||
reducer;
|
||||
};
|
||||
|
||||
struct FamilyGraphReductionResult
|
||||
{
|
||||
ErrorVec errors;
|
||||
DenseHashSet<TypeId> blockedTypes{nullptr};
|
||||
DenseHashSet<TypePackId> blockedPacks{nullptr};
|
||||
DenseHashSet<TypeId> reducedTypes{nullptr};
|
||||
DenseHashSet<TypePackId> reducedPacks{nullptr};
|
||||
};
|
||||
|
||||
/**
|
||||
* Attempt to reduce all instances of any type or type pack family in the type
|
||||
* graph provided.
|
||||
*
|
||||
* @param entrypoint the entry point to the type graph.
|
||||
* @param location the location the reduction is occurring at; used to populate
|
||||
* type errors.
|
||||
* @param arena an arena to allocate types into.
|
||||
* @param builtins the built-in types.
|
||||
* @param log a TxnLog to use. If one is provided, substitution will take place
|
||||
* against the TxnLog, otherwise substitutions will directly mutate the type
|
||||
* graph. Do not provide the empty TxnLog, as a result.
|
||||
*/
|
||||
FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location location, NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtins,
|
||||
NotNull<Scope> scope, NotNull<Normalizer> normalizer, TxnLog* log = nullptr, bool force = false);
|
||||
|
||||
/**
|
||||
* Attempt to reduce all instances of any type or type pack family in the type
|
||||
* graph provided.
|
||||
*
|
||||
* @param entrypoint the entry point to the type graph.
|
||||
* @param location the location the reduction is occurring at; used to populate
|
||||
* type errors.
|
||||
* @param arena an arena to allocate types into.
|
||||
* @param builtins the built-in types.
|
||||
* @param log a TxnLog to use. If one is provided, substitution will take place
|
||||
* against the TxnLog, otherwise substitutions will directly mutate the type
|
||||
* graph. Do not provide the empty TxnLog, as a result.
|
||||
*/
|
||||
FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location location, NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtins,
|
||||
NotNull<Scope> scope, NotNull<Normalizer> normalizer, TxnLog* log = nullptr, bool force = false);
|
||||
|
||||
struct BuiltinTypeFamilies
|
||||
{
|
||||
BuiltinTypeFamilies();
|
||||
|
||||
TypeFamily addFamily;
|
||||
};
|
||||
|
||||
const BuiltinTypeFamilies kBuiltinTypeFamilies{};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,426 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Anyification.h"
|
||||
#include "Luau/ControlFlow.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/Predicate.h"
|
||||
#include "Luau/Substitution.h"
|
||||
#include "Luau/Symbol.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeCheckLimits.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Unifier.h"
|
||||
#include "Luau/UnifierSharedState.h"
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct Scope;
|
||||
struct TypeChecker;
|
||||
struct ModuleResolver;
|
||||
struct FrontendCancellationToken;
|
||||
|
||||
using Name = std::string;
|
||||
using ScopePtr = std::shared_ptr<Scope>;
|
||||
|
||||
struct OverloadErrorEntry
|
||||
{
|
||||
TxnLog log;
|
||||
ErrorVec errors;
|
||||
std::vector<TypeId> arguments;
|
||||
const FunctionType* fnTy;
|
||||
};
|
||||
|
||||
bool doesCallError(const AstExprCall* call);
|
||||
bool hasBreak(AstStat* node);
|
||||
const AstStat* getFallthrough(const AstStat* node);
|
||||
|
||||
struct UnifierOptions;
|
||||
struct Unifier;
|
||||
|
||||
struct GenericTypeDefinitions
|
||||
{
|
||||
std::vector<GenericTypeDefinition> genericTypes;
|
||||
std::vector<GenericTypePackDefinition> genericPacks;
|
||||
};
|
||||
|
||||
struct HashBoolNamePair
|
||||
{
|
||||
size_t operator()(const std::pair<bool, Name>& pair) const;
|
||||
};
|
||||
|
||||
struct GlobalTypes
|
||||
{
|
||||
GlobalTypes(NotNull<BuiltinTypes> builtinTypes);
|
||||
|
||||
NotNull<BuiltinTypes> builtinTypes; // Global types are based on builtin types
|
||||
|
||||
TypeArena globalTypes;
|
||||
SourceModule globalNames; // names for symbols entered into globalScope
|
||||
ScopePtr globalScope; // shared by all modules
|
||||
};
|
||||
|
||||
// All Types are retained via Environment::types. All TypeIds
|
||||
// within a program are borrowed pointers into this set.
|
||||
struct TypeChecker
|
||||
{
|
||||
explicit TypeChecker(
|
||||
const ScopePtr& globalScope, ModuleResolver* resolver, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter* iceHandler);
|
||||
TypeChecker(const TypeChecker&) = delete;
|
||||
TypeChecker& operator=(const TypeChecker&) = delete;
|
||||
|
||||
ModulePtr check(const SourceModule& module, Mode mode, std::optional<ScopePtr> environmentScope = std::nullopt);
|
||||
ModulePtr checkWithoutRecursionCheck(const SourceModule& module, Mode mode, std::optional<ScopePtr> environmentScope = std::nullopt);
|
||||
|
||||
std::vector<std::pair<Location, ScopePtr>> getScopes() const;
|
||||
|
||||
ControlFlow check(const ScopePtr& scope, const AstStat& statement);
|
||||
ControlFlow check(const ScopePtr& scope, const AstStatBlock& statement);
|
||||
ControlFlow check(const ScopePtr& scope, const AstStatIf& statement);
|
||||
ControlFlow check(const ScopePtr& scope, const AstStatWhile& statement);
|
||||
ControlFlow check(const ScopePtr& scope, const AstStatRepeat& statement);
|
||||
ControlFlow check(const ScopePtr& scope, const AstStatReturn& return_);
|
||||
ControlFlow check(const ScopePtr& scope, const AstStatAssign& assign);
|
||||
ControlFlow check(const ScopePtr& scope, const AstStatCompoundAssign& assign);
|
||||
ControlFlow check(const ScopePtr& scope, const AstStatLocal& local);
|
||||
ControlFlow check(const ScopePtr& scope, const AstStatFor& local);
|
||||
ControlFlow check(const ScopePtr& scope, const AstStatForIn& forin);
|
||||
ControlFlow check(const ScopePtr& scope, TypeId ty, const ScopePtr& funScope, const AstStatFunction& function);
|
||||
ControlFlow check(const ScopePtr& scope, TypeId ty, const ScopePtr& funScope, const AstStatLocalFunction& function);
|
||||
ControlFlow check(const ScopePtr& scope, const AstStatTypeAlias& typealias);
|
||||
ControlFlow check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass);
|
||||
ControlFlow check(const ScopePtr& scope, const AstStatDeclareFunction& declaredFunction);
|
||||
|
||||
void prototype(const ScopePtr& scope, const AstStatTypeAlias& typealias, int subLevel = 0);
|
||||
void prototype(const ScopePtr& scope, const AstStatDeclareClass& declaredClass);
|
||||
|
||||
ControlFlow checkBlock(const ScopePtr& scope, const AstStatBlock& statement);
|
||||
ControlFlow checkBlockWithoutRecursionCheck(const ScopePtr& scope, const AstStatBlock& statement);
|
||||
void checkBlockTypeAliases(const ScopePtr& scope, std::vector<AstStat*>& sorted);
|
||||
|
||||
WithPredicate<TypeId> checkExpr(
|
||||
const ScopePtr& scope, const AstExpr& expr, std::optional<TypeId> expectedType = std::nullopt, bool forceSingleton = false);
|
||||
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprLocal& expr);
|
||||
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprGlobal& expr);
|
||||
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprVarargs& expr);
|
||||
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprCall& expr);
|
||||
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprIndexName& expr);
|
||||
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprIndexExpr& expr);
|
||||
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprFunction& expr, std::optional<TypeId> expectedType = std::nullopt);
|
||||
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprTable& expr, std::optional<TypeId> expectedType = std::nullopt);
|
||||
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprUnary& expr);
|
||||
TypeId checkRelationalOperation(
|
||||
const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates = {});
|
||||
TypeId checkBinaryOperation(
|
||||
const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates = {});
|
||||
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprBinary& expr, std::optional<TypeId> expectedType = std::nullopt);
|
||||
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprTypeAssertion& expr);
|
||||
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprError& expr);
|
||||
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprIfElse& expr, std::optional<TypeId> expectedType = std::nullopt);
|
||||
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprInterpString& expr);
|
||||
|
||||
TypeId checkExprTable(const ScopePtr& scope, const AstExprTable& expr, const std::vector<std::pair<TypeId, TypeId>>& fieldTypes,
|
||||
std::optional<TypeId> expectedType);
|
||||
|
||||
// Returns the type of the lvalue.
|
||||
TypeId checkLValue(const ScopePtr& scope, const AstExpr& expr, ValueContext ctx);
|
||||
|
||||
// Returns the type of the lvalue.
|
||||
TypeId checkLValueBinding(const ScopePtr& scope, const AstExpr& expr, ValueContext ctx);
|
||||
TypeId checkLValueBinding(const ScopePtr& scope, const AstExprLocal& expr);
|
||||
TypeId checkLValueBinding(const ScopePtr& scope, const AstExprGlobal& expr);
|
||||
TypeId checkLValueBinding(const ScopePtr& scope, const AstExprIndexName& expr, ValueContext ctx);
|
||||
TypeId checkLValueBinding(const ScopePtr& scope, const AstExprIndexExpr& expr, ValueContext ctx);
|
||||
|
||||
TypeId checkFunctionName(const ScopePtr& scope, AstExpr& funName, TypeLevel level);
|
||||
std::pair<TypeId, ScopePtr> checkFunctionSignature(const ScopePtr& scope, int subLevel, const AstExprFunction& expr,
|
||||
std::optional<Location> originalNameLoc, std::optional<TypeId> selfType, std::optional<TypeId> expectedType);
|
||||
void checkFunctionBody(const ScopePtr& scope, TypeId type, const AstExprFunction& function);
|
||||
|
||||
void checkArgumentList(const ScopePtr& scope, const AstExpr& funName, Unifier& state, TypePackId paramPack, TypePackId argPack,
|
||||
const std::vector<Location>& argLocations);
|
||||
|
||||
WithPredicate<TypePackId> checkExprPack(const ScopePtr& scope, const AstExpr& expr);
|
||||
|
||||
WithPredicate<TypePackId> checkExprPackHelper(const ScopePtr& scope, const AstExpr& expr);
|
||||
WithPredicate<TypePackId> checkExprPackHelper(const ScopePtr& scope, const AstExprCall& expr);
|
||||
WithPredicate<TypePackId> checkExprPackHelper2(
|
||||
const ScopePtr& scope, const AstExprCall& expr, TypeId selfType, TypeId actualFunctionType, TypeId functionType, TypePackId retPack);
|
||||
|
||||
std::vector<std::optional<TypeId>> getExpectedTypesForCall(const std::vector<TypeId>& overloads, size_t argumentCount, bool selfCall);
|
||||
|
||||
std::unique_ptr<WithPredicate<TypePackId>> checkCallOverload(const ScopePtr& scope, const AstExprCall& expr, TypeId fn, TypePackId retPack,
|
||||
TypePackId argPack, TypePack* args, const std::vector<Location>* argLocations, const WithPredicate<TypePackId>& argListResult,
|
||||
std::vector<TypeId>& overloadsThatMatchArgCount, std::vector<TypeId>& overloadsThatDont, std::vector<OverloadErrorEntry>& errors);
|
||||
bool handleSelfCallMismatch(const ScopePtr& scope, const AstExprCall& expr, TypePack* args, const std::vector<Location>& argLocations,
|
||||
const std::vector<OverloadErrorEntry>& errors);
|
||||
void reportOverloadResolutionError(const ScopePtr& scope, const AstExprCall& expr, TypePackId retPack, TypePackId argPack,
|
||||
const std::vector<Location>& argLocations, const std::vector<TypeId>& overloads, const std::vector<TypeId>& overloadsThatMatchArgCount,
|
||||
std::vector<OverloadErrorEntry>& errors);
|
||||
|
||||
WithPredicate<TypePackId> checkExprList(const ScopePtr& scope, const Location& location, const AstArray<AstExpr*>& exprs,
|
||||
bool substituteFreeForNil = false, const std::vector<bool>& lhsAnnotations = {},
|
||||
const std::vector<std::optional<TypeId>>& expectedTypes = {});
|
||||
|
||||
static std::optional<AstExpr*> matchRequire(const AstExprCall& call);
|
||||
TypeId checkRequire(const ScopePtr& scope, const ModuleInfo& moduleInfo, const Location& location);
|
||||
|
||||
// Try to infer that the provided type is a table of some sort.
|
||||
// Reports an error if the type is already some kind of non-table.
|
||||
void tablify(TypeId type);
|
||||
|
||||
/** In nonstrict mode, many types need to be replaced by any.
|
||||
*/
|
||||
TypeId anyIfNonstrict(TypeId ty) const;
|
||||
|
||||
/** Attempt to unify the types.
|
||||
* Treat any failures as type errors in the final typecheck report.
|
||||
*/
|
||||
bool unify(TypeId subTy, TypeId superTy, const ScopePtr& scope, const Location& location);
|
||||
bool unify(TypeId subTy, TypeId superTy, const ScopePtr& scope, const Location& location, const UnifierOptions& options);
|
||||
bool unify(TypePackId subTy, TypePackId superTy, const ScopePtr& scope, const Location& location,
|
||||
CountMismatch::Context ctx = CountMismatch::Context::Arg);
|
||||
|
||||
/** Attempt to unify the types.
|
||||
* If this fails, and the subTy type can be instantiated, do so and try unification again.
|
||||
*/
|
||||
bool unifyWithInstantiationIfNeeded(TypeId subTy, TypeId superTy, const ScopePtr& scope, const Location& location);
|
||||
void unifyWithInstantiationIfNeeded(TypeId subTy, TypeId superTy, const ScopePtr& scope, Unifier& state);
|
||||
|
||||
/** Attempt to unify.
|
||||
* If there are errors, undo everything and return the errors.
|
||||
* If there are no errors, commit and return an empty error vector.
|
||||
*/
|
||||
template<typename Id>
|
||||
ErrorVec tryUnify_(Id subTy, Id superTy, const ScopePtr& scope, const Location& location);
|
||||
ErrorVec tryUnify(TypeId subTy, TypeId superTy, const ScopePtr& scope, const Location& location);
|
||||
ErrorVec tryUnify(TypePackId subTy, TypePackId superTy, const ScopePtr& scope, const Location& location);
|
||||
|
||||
// Test whether the two type vars unify. Never commits the result.
|
||||
template<typename Id>
|
||||
ErrorVec canUnify_(Id subTy, Id superTy, const ScopePtr& scope, const Location& location);
|
||||
ErrorVec canUnify(TypeId subTy, TypeId superTy, const ScopePtr& scope, const Location& location);
|
||||
ErrorVec canUnify(TypePackId subTy, TypePackId superTy, const ScopePtr& scope, const Location& location);
|
||||
|
||||
std::optional<TypeId> findMetatableEntry(TypeId type, std::string entry, const Location& location, bool addErrors);
|
||||
std::optional<TypeId> findTablePropertyRespectingMeta(TypeId lhsType, Name name, const Location& location, bool addErrors);
|
||||
|
||||
std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, TypeId type, const Name& name, const Location& location, bool addErrors);
|
||||
std::optional<TypeId> getIndexTypeFromTypeImpl(const ScopePtr& scope, TypeId type, const Name& name, const Location& location, bool addErrors);
|
||||
|
||||
std::optional<TypeId> tryStripUnionFromNil(TypeId ty);
|
||||
TypeId stripFromNilAndReport(TypeId ty, const Location& location);
|
||||
|
||||
public:
|
||||
/*
|
||||
* Convert monotype into a a polytype, by replacing any metavariables in descendant scopes
|
||||
* by bound generic type variables. This is used to infer that a function is generic.
|
||||
*/
|
||||
TypeId quantify(const ScopePtr& scope, TypeId ty, Location location);
|
||||
|
||||
/*
|
||||
* Convert a polytype into a monotype, by replacing any bound generic types by type metavariables.
|
||||
* This is used to typecheck particular calls to generic functions, and when generic functions
|
||||
* are passed as arguments.
|
||||
*
|
||||
* The "changed" boolean is used to permit us to return the same TypeId in the case that the instantiated type is unchanged.
|
||||
* This is important in certain cases, such as methods on objects, where a table contains a function whose first argument is the table.
|
||||
* Without this property, we can wind up in a situation where a new TypeId is allocated for the outer table. This can cause us to produce
|
||||
* unfortunate types like
|
||||
*
|
||||
* {method: ({method: (<CYCLE>) -> a}) -> a}
|
||||
*
|
||||
*/
|
||||
TypeId instantiate(const ScopePtr& scope, TypeId ty, Location location, const TxnLog* log = TxnLog::empty());
|
||||
|
||||
// Replace any free types or type packs by `any`.
|
||||
// This is used when exporting types from modules, to make sure free types don't leak.
|
||||
TypeId anyify(const ScopePtr& scope, TypeId ty, Location location);
|
||||
TypePackId anyify(const ScopePtr& scope, TypePackId ty, Location location);
|
||||
|
||||
TypePackId anyifyModuleReturnTypePackGenerics(TypePackId ty);
|
||||
|
||||
void reportError(const TypeError& error);
|
||||
void reportError(const Location& location, TypeErrorData error);
|
||||
void reportErrors(const ErrorVec& errors);
|
||||
|
||||
[[noreturn]] void ice(const std::string& message, const Location& location);
|
||||
[[noreturn]] void ice(const std::string& message);
|
||||
[[noreturn]] void throwTimeLimitError();
|
||||
[[noreturn]] void throwUserCancelError();
|
||||
|
||||
ScopePtr childFunctionScope(const ScopePtr& parent, const Location& location, int subLevel = 0);
|
||||
ScopePtr childScope(const ScopePtr& parent, const Location& location);
|
||||
|
||||
// Wrapper for merge(l, r, toUnion) but without the lambda junk.
|
||||
void merge(RefinementMap& l, const RefinementMap& r);
|
||||
|
||||
// Produce an "emergency backup type" for recovery from type errors.
|
||||
// This comes in two flavours, depening on whether or not we can make a good guess
|
||||
// for an error recovery type.
|
||||
TypeId errorRecoveryType(TypeId guess);
|
||||
TypePackId errorRecoveryTypePack(TypePackId guess);
|
||||
TypeId errorRecoveryType(const ScopePtr& scope);
|
||||
TypePackId errorRecoveryTypePack(const ScopePtr& scope);
|
||||
|
||||
private:
|
||||
void prepareErrorsForDisplay(ErrorVec& errVec);
|
||||
void diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData& data);
|
||||
void reportErrorCodeTooComplex(const Location& location);
|
||||
|
||||
private:
|
||||
Unifier mkUnifier(const ScopePtr& scope, const Location& location);
|
||||
|
||||
// These functions are only safe to call when we are in the process of typechecking a module.
|
||||
|
||||
// Produce a new free type var.
|
||||
TypeId freshType(const ScopePtr& scope);
|
||||
TypeId freshType(TypeLevel level);
|
||||
|
||||
// Produce a new singleton type var.
|
||||
TypeId singletonType(bool value);
|
||||
TypeId singletonType(std::string value);
|
||||
|
||||
TypeIdPredicate mkTruthyPredicate(bool sense, TypeId emptySetTy);
|
||||
|
||||
// TODO: Return TypeId only.
|
||||
std::optional<TypeId> filterMapImpl(TypeId type, TypeIdPredicate predicate);
|
||||
std::pair<std::optional<TypeId>, bool> filterMap(TypeId type, TypeIdPredicate predicate);
|
||||
|
||||
public:
|
||||
std::pair<std::optional<TypeId>, bool> pickTypesFromSense(TypeId type, bool sense, TypeId emptySetTy);
|
||||
|
||||
private:
|
||||
TypeId unionOfTypes(TypeId a, TypeId b, const ScopePtr& scope, const Location& location, bool unifyFreeTypes = true);
|
||||
|
||||
// ex
|
||||
// TypeId id = addType(FreeType());
|
||||
template<typename T>
|
||||
TypeId addType(const T& tv)
|
||||
{
|
||||
return addTV(Type(tv));
|
||||
}
|
||||
|
||||
TypeId addTV(Type&& tv);
|
||||
|
||||
TypePackId addTypePack(TypePackVar&& tp);
|
||||
TypePackId addTypePack(TypePack&& tp);
|
||||
|
||||
TypePackId addTypePack(const std::vector<TypeId>& ty);
|
||||
TypePackId addTypePack(const std::vector<TypeId>& ty, std::optional<TypePackId> tail);
|
||||
TypePackId addTypePack(std::initializer_list<TypeId>&& ty);
|
||||
TypePackId freshTypePack(const ScopePtr& scope);
|
||||
TypePackId freshTypePack(TypeLevel level);
|
||||
|
||||
TypeId resolveType(const ScopePtr& scope, const AstType& annotation);
|
||||
TypeId resolveTypeWorker(const ScopePtr& scope, const AstType& annotation);
|
||||
TypePackId resolveTypePack(const ScopePtr& scope, const AstTypeList& types);
|
||||
TypePackId resolveTypePack(const ScopePtr& scope, const AstTypePack& annotation);
|
||||
TypeId instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& typePackParams, const Location& location);
|
||||
|
||||
// Note: `scope` must be a fresh scope.
|
||||
GenericTypeDefinitions createGenericTypes(const ScopePtr& scope, std::optional<TypeLevel> levelOpt, const AstNode& node,
|
||||
const AstArray<AstGenericType>& genericNames, const AstArray<AstGenericTypePack>& genericPackNames, bool useCache = false);
|
||||
|
||||
public:
|
||||
void resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense);
|
||||
|
||||
private:
|
||||
void refineLValue(const LValue& lvalue, RefinementMap& refis, const ScopePtr& scope, TypeIdPredicate predicate);
|
||||
|
||||
std::optional<TypeId> resolveLValue(const ScopePtr& scope, const LValue& lvalue);
|
||||
std::optional<TypeId> resolveLValue(const RefinementMap& refis, const ScopePtr& scope, const LValue& lvalue);
|
||||
|
||||
void resolve(const PredicateVec& predicates, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr = false);
|
||||
void resolve(const Predicate& predicate, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr);
|
||||
void resolve(const TruthyPredicate& truthyP, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr);
|
||||
void resolve(const AndPredicate& andP, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||
void resolve(const OrPredicate& orP, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||
void resolve(const IsAPredicate& isaP, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||
void resolve(const TypeGuardPredicate& typeguardP, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||
void resolve(const EqPredicate& eqP, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||
|
||||
bool isNonstrictMode() const;
|
||||
bool useConstrainedIntersections() const;
|
||||
|
||||
public:
|
||||
/** Extract the types in a type pack, given the assumption that the pack must have some exact length.
|
||||
* TypePacks can have free tails, which means that inference has not yet determined the length of the pack.
|
||||
* Calling this function means submitting evidence that the pack must have the length provided.
|
||||
* If the pack is known not to have the correct length, an error will be reported.
|
||||
* The return vector is always of the exact requested length. In the event that the pack's length does
|
||||
* not match up, excess TypeIds will be ErrorTypes.
|
||||
*/
|
||||
std::vector<TypeId> unTypePack(const ScopePtr& scope, TypePackId pack, size_t expectedLength, const Location& location);
|
||||
|
||||
const ScopePtr& globalScope;
|
||||
|
||||
ModuleResolver* resolver;
|
||||
ModulePtr currentModule;
|
||||
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope;
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
InternalErrorReporter* iceHandler;
|
||||
|
||||
UnifierSharedState unifierState;
|
||||
Normalizer normalizer;
|
||||
|
||||
std::vector<RequireCycle> requireCycles;
|
||||
|
||||
// Type inference limits
|
||||
std::optional<double> finishTime;
|
||||
std::optional<int> instantiationChildLimit;
|
||||
std::optional<int> unifierIterationLimit;
|
||||
|
||||
std::shared_ptr<FrontendCancellationToken> cancellationToken;
|
||||
|
||||
public:
|
||||
const TypeId nilType;
|
||||
const TypeId numberType;
|
||||
const TypeId stringType;
|
||||
const TypeId booleanType;
|
||||
const TypeId threadType;
|
||||
const TypeId anyType;
|
||||
const TypeId unknownType;
|
||||
const TypeId neverType;
|
||||
|
||||
const TypePackId anyTypePack;
|
||||
const TypePackId neverTypePack;
|
||||
const TypePackId uninhabitableTypePack;
|
||||
|
||||
private:
|
||||
int checkRecursionCount = 0;
|
||||
int recursionCount = 0;
|
||||
|
||||
/**
|
||||
* We use this to avoid doing second-pass analysis of type aliases that are duplicates. We record a pair
|
||||
* (exported, name) to properly deal with the case where the two duplicates do not have the same export status.
|
||||
*/
|
||||
DenseHashSet<std::pair<bool, Name>, HashBoolNamePair> duplicateTypeAliases;
|
||||
|
||||
/**
|
||||
* A set of incorrect class definitions which is used to avoid a second-pass analysis.
|
||||
*/
|
||||
DenseHashSet<const AstStatDeclareClass*> incorrectClassDefinitions{nullptr};
|
||||
|
||||
std::vector<std::pair<TypeId, ScopePtr>> deferredQuantification;
|
||||
};
|
||||
|
||||
using PrintLineProc = void (*)(const std::string&);
|
||||
|
||||
extern PrintLineProc luauPrintLine;
|
||||
|
||||
// Unit test hook
|
||||
void setPrintLine(PrintLineProc pl);
|
||||
void resetPrintLine();
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,234 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/Unifiable.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <optional>
|
||||
#include <set>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct TypeArena;
|
||||
struct TypePackFamily;
|
||||
struct TxnLog;
|
||||
|
||||
struct TypePack;
|
||||
struct VariadicTypePack;
|
||||
struct BlockedTypePack;
|
||||
struct TypeFamilyInstanceTypePack;
|
||||
|
||||
struct TypePackVar;
|
||||
using TypePackId = const TypePackVar*;
|
||||
|
||||
struct FreeTypePack
|
||||
{
|
||||
explicit FreeTypePack(TypeLevel level);
|
||||
explicit FreeTypePack(Scope* scope);
|
||||
FreeTypePack(Scope* scope, TypeLevel level);
|
||||
|
||||
int index;
|
||||
TypeLevel level;
|
||||
Scope* scope = nullptr;
|
||||
};
|
||||
|
||||
struct GenericTypePack
|
||||
{
|
||||
// By default, generics are global, with a synthetic name
|
||||
GenericTypePack();
|
||||
explicit GenericTypePack(TypeLevel level);
|
||||
explicit GenericTypePack(const Name& name);
|
||||
explicit GenericTypePack(Scope* scope);
|
||||
GenericTypePack(TypeLevel level, const Name& name);
|
||||
GenericTypePack(Scope* scope, const Name& name);
|
||||
|
||||
int index;
|
||||
TypeLevel level;
|
||||
Scope* scope = nullptr;
|
||||
Name name;
|
||||
bool explicitName = false;
|
||||
};
|
||||
|
||||
using BoundTypePack = Unifiable::Bound<TypePackId>;
|
||||
using ErrorTypePack = Unifiable::Error;
|
||||
|
||||
using TypePackVariant =
|
||||
Unifiable::Variant<TypePackId, FreeTypePack, GenericTypePack, TypePack, VariadicTypePack, BlockedTypePack, TypeFamilyInstanceTypePack>;
|
||||
|
||||
/* A TypePack is a rope-like string of TypeIds. We use this structure to encode
|
||||
* notions like packs of unknown length and packs of any length, as well as more
|
||||
* nuanced compositions like "a pack which is a number prepended to this other pack,"
|
||||
* or "a pack that is 2 numbers followed by any number of any other types."
|
||||
*/
|
||||
struct TypePack
|
||||
{
|
||||
std::vector<TypeId> head;
|
||||
std::optional<TypePackId> tail;
|
||||
};
|
||||
|
||||
struct VariadicTypePack
|
||||
{
|
||||
TypeId ty;
|
||||
bool hidden = false; // if true, we don't display this when toString()ing a pack with this variadic as its tail.
|
||||
};
|
||||
|
||||
/**
|
||||
* Analogous to a BlockedType.
|
||||
*/
|
||||
struct BlockedTypePack
|
||||
{
|
||||
BlockedTypePack();
|
||||
size_t index;
|
||||
|
||||
static size_t nextIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* Analogous to a TypeFamilyInstanceType.
|
||||
*/
|
||||
struct TypeFamilyInstanceTypePack
|
||||
{
|
||||
NotNull<TypePackFamily> family;
|
||||
|
||||
std::vector<TypeId> typeArguments;
|
||||
std::vector<TypePackId> packArguments;
|
||||
};
|
||||
|
||||
struct TypePackVar
|
||||
{
|
||||
explicit TypePackVar(const TypePackVariant& ty);
|
||||
explicit TypePackVar(TypePackVariant&& ty);
|
||||
TypePackVar(TypePackVariant&& ty, bool persistent);
|
||||
|
||||
bool operator==(const TypePackVar& rhs) const;
|
||||
|
||||
TypePackVar& operator=(TypePackVariant&& tp);
|
||||
|
||||
TypePackVar& operator=(const TypePackVar& rhs);
|
||||
|
||||
// Re-assignes the content of the pack, but doesn't change the owning arena and can't make pack persistent.
|
||||
void reassign(const TypePackVar& rhs)
|
||||
{
|
||||
ty = rhs.ty;
|
||||
}
|
||||
|
||||
TypePackVariant ty;
|
||||
|
||||
bool persistent = false;
|
||||
|
||||
// Pointer to the type arena that allocated this pack.
|
||||
TypeArena* owningArena = nullptr;
|
||||
};
|
||||
|
||||
/* Walk the set of TypeIds in a TypePack.
|
||||
*
|
||||
* Like Types, individual TypePacks can be free, generic, or any.
|
||||
*
|
||||
* We afford the ability to work with these kinds of packs by giving the
|
||||
* iterator a .tail() property that yields the tail-most TypePack in the
|
||||
* rope.
|
||||
*
|
||||
* It is very commonplace to want to walk each type in a pack, then handle
|
||||
* the tail specially. eg when checking parameters, it might be the case
|
||||
* that the parameter pack ends with a VariadicTypePack. In this case, we
|
||||
* want to allow any number of extra arguments.
|
||||
*
|
||||
* The iterator obtained by calling end(tp) does not have a .tail(), but is
|
||||
* equivalent with end(tp2) for any two type packs.
|
||||
*/
|
||||
struct TypePackIterator
|
||||
{
|
||||
using value_type = Luau::TypeId;
|
||||
using pointer = value_type*;
|
||||
using reference = value_type&;
|
||||
using difference_type = size_t;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
TypePackIterator() = default;
|
||||
explicit TypePackIterator(TypePackId tp);
|
||||
TypePackIterator(TypePackId tp, const TxnLog* log);
|
||||
|
||||
TypePackIterator& operator++();
|
||||
TypePackIterator operator++(int);
|
||||
bool operator!=(const TypePackIterator& rhs);
|
||||
bool operator==(const TypePackIterator& rhs);
|
||||
|
||||
const TypeId& operator*();
|
||||
|
||||
/** Return the tail of a TypePack.
|
||||
* This may *only* be called on an iterator that has been incremented to the end.
|
||||
* Returns nullopt if the pack has fixed length.
|
||||
*/
|
||||
std::optional<TypePackId> tail();
|
||||
|
||||
friend TypePackIterator end(TypePackId tp);
|
||||
|
||||
private:
|
||||
TypePackId currentTypePack = nullptr;
|
||||
const TypePack* tp = nullptr;
|
||||
size_t currentIndex = 0;
|
||||
|
||||
const TxnLog* log;
|
||||
};
|
||||
|
||||
TypePackIterator begin(TypePackId tp);
|
||||
TypePackIterator begin(TypePackId tp, const TxnLog* log);
|
||||
TypePackIterator end(TypePackId tp);
|
||||
|
||||
using SeenSet = std::set<std::pair<const void*, const void*>>;
|
||||
|
||||
bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs);
|
||||
|
||||
TypePackId follow(TypePackId tp);
|
||||
TypePackId follow(TypePackId t, const void* context, TypePackId (*mapper)(const void*, TypePackId));
|
||||
|
||||
size_t size(TypePackId tp, TxnLog* log = nullptr);
|
||||
bool finite(TypePackId tp, TxnLog* log = nullptr);
|
||||
size_t size(const TypePack& tp, TxnLog* log = nullptr);
|
||||
std::optional<TypeId> first(TypePackId tp, bool ignoreHiddenVariadics = true);
|
||||
|
||||
TypePackVar* asMutable(TypePackId tp);
|
||||
TypePack* asMutable(const TypePack* tp);
|
||||
|
||||
template<typename T>
|
||||
const T* get(TypePackId tp)
|
||||
{
|
||||
LUAU_ASSERT(tp);
|
||||
|
||||
if constexpr (!std::is_same_v<T, BoundTypePack>)
|
||||
LUAU_ASSERT(get_if<BoundTypePack>(&tp->ty) == nullptr);
|
||||
|
||||
return get_if<T>(&(tp->ty));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* getMutable(TypePackId tp)
|
||||
{
|
||||
LUAU_ASSERT(tp);
|
||||
|
||||
if constexpr (!std::is_same_v<T, BoundTypePack>)
|
||||
LUAU_ASSERT(get_if<BoundTypePack>(&tp->ty) == nullptr);
|
||||
|
||||
return get_if<T>(&(asMutable(tp)->ty));
|
||||
}
|
||||
|
||||
/// Returns true if the type pack is known to be empty (no types in the head and no/an empty tail).
|
||||
bool isEmpty(TypePackId tp);
|
||||
|
||||
/// Flattens out a type pack. Also returns a valid TypePackId tail if the type pack's full size is not known
|
||||
std::pair<std::vector<TypeId>, std::optional<TypePackId>> flatten(TypePackId tp);
|
||||
std::pair<std::vector<TypeId>, std::optional<TypePackId>> flatten(TypePackId tp, const TxnLog& log);
|
||||
|
||||
/// Returs true if the type pack arose from a function that is declared to be variadic.
|
||||
/// Returns *false* for function argument packs that are inferred to be safe to oversaturate!
|
||||
bool isVariadic(TypePackId tp);
|
||||
bool isVariadic(TypePackId tp, const TxnLog& log);
|
||||
|
||||
// Returns true if the TypePack is Generic or Variadic. Does not walk TypePacks!!
|
||||
bool isVariadicTail(TypePackId tp, const TxnLog& log, bool includeHiddenVariadics = false);
|
||||
|
||||
bool containsNever(TypePackId tp);
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,146 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypePack.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct TxnLog;
|
||||
struct TypeArena;
|
||||
class Normalizer;
|
||||
|
||||
enum class ValueContext
|
||||
{
|
||||
LValue,
|
||||
RValue
|
||||
};
|
||||
|
||||
using ScopePtr = std::shared_ptr<struct Scope>;
|
||||
|
||||
std::optional<TypeId> findMetatableEntry(
|
||||
NotNull<BuiltinTypes> builtinTypes, ErrorVec& errors, TypeId type, const std::string& entry, Location location);
|
||||
std::optional<TypeId> findTablePropertyRespectingMeta(
|
||||
NotNull<BuiltinTypes> builtinTypes, ErrorVec& errors, TypeId ty, const std::string& name, Location location);
|
||||
|
||||
// Returns the minimum and maximum number of types the argument list can accept.
|
||||
std::pair<size_t, std::optional<size_t>> getParameterExtents(const TxnLog* log, TypePackId tp, bool includeHiddenVariadics = false);
|
||||
|
||||
// Extend the provided pack to at least `length` types.
|
||||
// Returns a temporary TypePack that contains those types plus a tail.
|
||||
TypePack extendTypePack(
|
||||
TypeArena& arena, NotNull<BuiltinTypes> builtinTypes, TypePackId pack, size_t length, std::vector<std::optional<TypeId>> overrides = {});
|
||||
|
||||
/**
|
||||
* Reduces a union by decomposing to the any/error type if it appears in the
|
||||
* type list, and by merging child unions. Also strips out duplicate (by pointer
|
||||
* identity) types.
|
||||
* @param types the input type list to reduce.
|
||||
* @returns the reduced type list.
|
||||
*/
|
||||
std::vector<TypeId> reduceUnion(const std::vector<TypeId>& types);
|
||||
|
||||
/**
|
||||
* Tries to remove nil from a union type, if there's another option. T | nil
|
||||
* reduces to T, but nil itself does not reduce.
|
||||
* @param builtinTypes the singleton types to use
|
||||
* @param arena the type arena to allocate the new type in, if necessary
|
||||
* @param ty the type to remove nil from
|
||||
* @returns a type with nil removed, or nil itself if that were the only option.
|
||||
*/
|
||||
TypeId stripNil(NotNull<BuiltinTypes> builtinTypes, TypeArena& arena, TypeId ty);
|
||||
|
||||
enum class ErrorSuppression
|
||||
{
|
||||
Suppress,
|
||||
DoNotSuppress,
|
||||
NormalizationFailed
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalizes the given type using the normalizer to determine if the type
|
||||
* should suppress any errors that would be reported involving it.
|
||||
* @param normalizer the normalizer to use
|
||||
* @param ty the type to check for error suppression
|
||||
* @returns an enum indicating whether or not to suppress the error or to signal a normalization failure
|
||||
*/
|
||||
ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypeId ty);
|
||||
|
||||
/**
|
||||
* Flattens and normalizes the given typepack using the normalizer to determine if the type
|
||||
* should suppress any errors that would be reported involving it.
|
||||
* @param normalizer the normalizer to use
|
||||
* @param tp the typepack to check for error suppression
|
||||
* @returns an enum indicating whether or not to suppress the error or to signal a normalization failure
|
||||
*/
|
||||
ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypePackId tp);
|
||||
|
||||
/**
|
||||
* Normalizes the two given type using the normalizer to determine if either type
|
||||
* should suppress any errors that would be reported involving it.
|
||||
* @param normalizer the normalizer to use
|
||||
* @param ty1 the first type to check for error suppression
|
||||
* @param ty2 the second type to check for error suppression
|
||||
* @returns an enum indicating whether or not to suppress the error or to signal a normalization failure
|
||||
*/
|
||||
ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypeId ty1, TypeId ty2);
|
||||
|
||||
/**
|
||||
* Flattens and normalizes the two given typepacks using the normalizer to determine if either type
|
||||
* should suppress any errors that would be reported involving it.
|
||||
* @param normalizer the normalizer to use
|
||||
* @param tp1 the first typepack to check for error suppression
|
||||
* @param tp2 the second typepack to check for error suppression
|
||||
* @returns an enum indicating whether or not to suppress the error or to signal a normalization failure
|
||||
*/
|
||||
ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypePackId tp1, TypePackId tp2);
|
||||
|
||||
// Similar to `std::optional<std::pair<A, B>>`, but whose `sizeof()` is the same as `std::pair<A, B>`
|
||||
// and cooperates with C++'s `if (auto p = ...)` syntax without the extra fatness of `std::optional`.
|
||||
template<typename A, typename B>
|
||||
struct TryPair {
|
||||
A first;
|
||||
B second;
|
||||
|
||||
explicit operator bool() const
|
||||
{
|
||||
return bool(first) && bool(second);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename A, typename B, typename Ty>
|
||||
TryPair<const A*, const B*> get2(Ty one, Ty two)
|
||||
{
|
||||
const A* a = get<A>(one);
|
||||
const B* b = get<B>(two);
|
||||
if (a && b)
|
||||
return {a, b};
|
||||
else
|
||||
return {nullptr, nullptr};
|
||||
}
|
||||
|
||||
template<typename T, typename Ty>
|
||||
const T* get(std::optional<Ty> ty)
|
||||
{
|
||||
if (ty)
|
||||
return get<T>(*ty);
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<typename Ty>
|
||||
std::optional<Ty> follow(std::optional<Ty> ty)
|
||||
{
|
||||
if (ty)
|
||||
return follow(*ty);
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,141 @@
|
|||
// 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 <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
void* pagedAllocate(size_t size);
|
||||
void pagedDeallocate(void* ptr, size_t size);
|
||||
void pagedFreeze(void* ptr, size_t size);
|
||||
void pagedUnfreeze(void* ptr, size_t size);
|
||||
|
||||
template<typename T>
|
||||
class TypedAllocator
|
||||
{
|
||||
public:
|
||||
TypedAllocator()
|
||||
{
|
||||
currentBlockSize = kBlockSize;
|
||||
}
|
||||
|
||||
TypedAllocator(const TypedAllocator&) = delete;
|
||||
TypedAllocator& operator=(const TypedAllocator&) = delete;
|
||||
|
||||
TypedAllocator(TypedAllocator&&) = default;
|
||||
TypedAllocator& operator=(TypedAllocator&&) = default;
|
||||
|
||||
~TypedAllocator()
|
||||
{
|
||||
if (frozen)
|
||||
unfreeze();
|
||||
free();
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
T* allocate(Args&&... args)
|
||||
{
|
||||
LUAU_ASSERT(!frozen);
|
||||
|
||||
if (currentBlockSize >= kBlockSize)
|
||||
{
|
||||
LUAU_ASSERT(currentBlockSize == kBlockSize);
|
||||
appendBlock();
|
||||
}
|
||||
|
||||
T* block = stuff.back();
|
||||
T* res = block + currentBlockSize;
|
||||
new (res) T(std::forward<Args&&...>(args...));
|
||||
++currentBlockSize;
|
||||
return res;
|
||||
}
|
||||
|
||||
bool contains(const T* ptr) const
|
||||
{
|
||||
for (T* block : stuff)
|
||||
if (ptr >= block && ptr < block + kBlockSize)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return stuff.empty();
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return stuff.empty() ? 0 : kBlockSize * (stuff.size() - 1) + currentBlockSize;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
if (frozen)
|
||||
unfreeze();
|
||||
free();
|
||||
|
||||
currentBlockSize = kBlockSize;
|
||||
}
|
||||
|
||||
void freeze()
|
||||
{
|
||||
for (T* block : stuff)
|
||||
pagedFreeze(block, kBlockSizeBytes);
|
||||
frozen = true;
|
||||
}
|
||||
|
||||
void unfreeze()
|
||||
{
|
||||
for (T* block : stuff)
|
||||
pagedUnfreeze(block, kBlockSizeBytes);
|
||||
frozen = false;
|
||||
}
|
||||
|
||||
bool isFrozen()
|
||||
{
|
||||
return frozen;
|
||||
}
|
||||
|
||||
private:
|
||||
void free()
|
||||
{
|
||||
LUAU_ASSERT(!frozen);
|
||||
|
||||
for (T* block : stuff)
|
||||
{
|
||||
size_t blockSize = (block == stuff.back()) ? currentBlockSize : kBlockSize;
|
||||
|
||||
for (size_t i = 0; i < blockSize; ++i)
|
||||
block[i].~T();
|
||||
|
||||
pagedDeallocate(block, kBlockSizeBytes);
|
||||
}
|
||||
|
||||
stuff.clear();
|
||||
currentBlockSize = 0;
|
||||
}
|
||||
|
||||
void appendBlock()
|
||||
{
|
||||
void* block = pagedAllocate(kBlockSizeBytes);
|
||||
if (!block)
|
||||
throw std::bad_alloc();
|
||||
|
||||
stuff.emplace_back(static_cast<T*>(block));
|
||||
currentBlockSize = 0;
|
||||
}
|
||||
|
||||
bool frozen = false;
|
||||
std::vector<T*> stuff;
|
||||
size_t currentBlockSize = 0;
|
||||
|
||||
static constexpr size_t kBlockSizeBytes = 32768;
|
||||
static constexpr size_t kBlockSize = kBlockSizeBytes / sizeof(T);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,112 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct Scope;
|
||||
|
||||
/**
|
||||
* The 'level' of a Type is an indirect way to talk about the scope that it 'belongs' too.
|
||||
* To start, read http://okmij.org/ftp/ML/generalization.html
|
||||
*
|
||||
* We extend the idea by adding a "sub-level" which helps us to differentiate sibling scopes
|
||||
* within a single larger scope.
|
||||
*
|
||||
* We need this because we try to prototype functions and add them to the type environment before
|
||||
* we check the function bodies. This allows us to properly typecheck many scenarios where there
|
||||
* is no single good order in which to typecheck a program.
|
||||
*/
|
||||
struct TypeLevel
|
||||
{
|
||||
int level = 0;
|
||||
int subLevel = 0;
|
||||
|
||||
// Returns true if the level of "this" belongs to an equal or larger scope than that of rhs
|
||||
bool subsumes(const TypeLevel& rhs) const
|
||||
{
|
||||
if (level < rhs.level)
|
||||
return true;
|
||||
if (level > rhs.level)
|
||||
return false;
|
||||
if (subLevel == rhs.subLevel)
|
||||
return true; // if level == rhs.level and subLevel == rhs.subLevel, then they are the exact same TypeLevel
|
||||
|
||||
// Sibling TypeLevels (that is, TypeLevels that share a level but have a different subLevel) are not considered to subsume one another
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns true if the level of "this" belongs to a larger (not equal) scope than that of rhs
|
||||
bool subsumesStrict(const TypeLevel& rhs) const
|
||||
{
|
||||
if (level == rhs.level && subLevel == rhs.subLevel)
|
||||
return false;
|
||||
else
|
||||
return subsumes(rhs);
|
||||
}
|
||||
|
||||
TypeLevel incr() const
|
||||
{
|
||||
TypeLevel result;
|
||||
result.level = level + 1;
|
||||
result.subLevel = 0;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
inline TypeLevel max(const TypeLevel& a, const TypeLevel& b)
|
||||
{
|
||||
if (a.subsumes(b))
|
||||
return b;
|
||||
else
|
||||
return a;
|
||||
}
|
||||
|
||||
inline TypeLevel min(const TypeLevel& a, const TypeLevel& b)
|
||||
{
|
||||
if (a.subsumes(b))
|
||||
return a;
|
||||
else
|
||||
return b;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
||||
namespace Luau::Unifiable
|
||||
{
|
||||
|
||||
using Name = std::string;
|
||||
|
||||
int freshIndex();
|
||||
|
||||
template<typename Id>
|
||||
struct Bound
|
||||
{
|
||||
explicit Bound(Id boundTo)
|
||||
: boundTo(boundTo)
|
||||
{
|
||||
}
|
||||
|
||||
Id boundTo;
|
||||
};
|
||||
|
||||
struct Error
|
||||
{
|
||||
// This constructor has to be public, since it's used in Type and TypePack,
|
||||
// but shouldn't be called directly. Please use errorRecoveryType() instead.
|
||||
Error();
|
||||
|
||||
int index;
|
||||
|
||||
private:
|
||||
static int nextIndex;
|
||||
};
|
||||
|
||||
template<typename Id, typename... Value>
|
||||
using Variant = Luau::Variant<Bound<Id>, Error, Value...>;
|
||||
|
||||
} // namespace Luau::Unifiable
|
|
@ -0,0 +1,191 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/ParseOptions.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/Substitution.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/UnifierSharedState.h"
|
||||
#include "Normalize.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
enum Variance
|
||||
{
|
||||
Covariant,
|
||||
Invariant
|
||||
};
|
||||
|
||||
// A substitution which replaces singleton types by their wider types
|
||||
struct Widen : Substitution
|
||||
{
|
||||
Widen(TypeArena* arena, NotNull<BuiltinTypes> builtinTypes)
|
||||
: Substitution(TxnLog::empty(), arena)
|
||||
, builtinTypes(builtinTypes)
|
||||
{
|
||||
}
|
||||
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
|
||||
bool isDirty(TypeId ty) override;
|
||||
bool isDirty(TypePackId ty) override;
|
||||
TypeId clean(TypeId ty) override;
|
||||
TypePackId clean(TypePackId ty) override;
|
||||
bool ignoreChildren(TypeId ty) override;
|
||||
|
||||
TypeId operator()(TypeId ty);
|
||||
TypePackId operator()(TypePackId ty);
|
||||
};
|
||||
|
||||
/**
|
||||
* Normally, when we unify table properties, we must do so invariantly, but we
|
||||
* can introduce a special exception: If the table property in the subtype
|
||||
* position arises from a literal expression, it is safe to instead perform a
|
||||
* covariant check.
|
||||
*
|
||||
* This is very useful for typechecking cases where table literals (and trees of
|
||||
* table literals) are passed directly to functions.
|
||||
*
|
||||
* In this case, we know that the property has no other name referring to it and
|
||||
* so it is perfectly safe for the function to mutate the table any way it
|
||||
* wishes.
|
||||
*/
|
||||
using LiteralProperties = DenseHashSet<Name>;
|
||||
|
||||
// TODO: Use this more widely.
|
||||
struct UnifierOptions
|
||||
{
|
||||
bool isFunctionCall = false;
|
||||
};
|
||||
|
||||
struct Unifier
|
||||
{
|
||||
TypeArena* const types;
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
NotNull<Normalizer> normalizer;
|
||||
|
||||
NotNull<Scope> scope; // const Scope maybe
|
||||
TxnLog log;
|
||||
bool failure = false;
|
||||
ErrorVec errors;
|
||||
Location location;
|
||||
Variance variance = Covariant;
|
||||
bool normalize = true; // Normalize unions and intersections if necessary
|
||||
bool checkInhabited = true; // Normalize types to check if they are inhabited
|
||||
CountMismatch::Context ctx = CountMismatch::Arg;
|
||||
|
||||
// If true, generics act as free types when unifying.
|
||||
bool hideousFixMeGenericsAreActuallyFree = false;
|
||||
|
||||
UnifierSharedState& sharedState;
|
||||
|
||||
// When the Unifier is forced to unify two blocked types (or packs), they
|
||||
// get added to these vectors. The ConstraintSolver can use this to know
|
||||
// when it is safe to reattempt dispatching a constraint.
|
||||
std::vector<TypeId> blockedTypes;
|
||||
std::vector<TypePackId> blockedTypePacks;
|
||||
|
||||
Unifier(NotNull<Normalizer> normalizer, NotNull<Scope> scope, const Location& location, Variance variance, TxnLog* parentLog = nullptr);
|
||||
|
||||
// Configure the Unifier to test for scope subsumption via embedded Scope
|
||||
// pointers rather than TypeLevels.
|
||||
void enableNewSolver();
|
||||
|
||||
// Test whether the two type vars unify. Never commits the result.
|
||||
ErrorVec canUnify(TypeId subTy, TypeId superTy);
|
||||
ErrorVec canUnify(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false);
|
||||
|
||||
/** Attempt to unify.
|
||||
* Populate the vector errors with any type errors that may arise.
|
||||
* Populate the transaction log with the set of TypeIds that need to be reset to undo the unification attempt.
|
||||
*/
|
||||
void tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr);
|
||||
|
||||
private:
|
||||
void tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr);
|
||||
void tryUnifyUnionWithType(TypeId subTy, const UnionType* uv, TypeId superTy);
|
||||
|
||||
// Traverse the two types provided and block on any BlockedTypes we find.
|
||||
// Returns true if any types were blocked on.
|
||||
bool DEPRECATED_blockOnBlockedTypes(TypeId subTy, TypeId superTy);
|
||||
|
||||
void tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionType* uv, bool cacheEnabled, bool isFunctionCall);
|
||||
void tryUnifyTypeWithIntersection(TypeId subTy, TypeId superTy, const IntersectionType* uv);
|
||||
void tryUnifyIntersectionWithType(TypeId subTy, const IntersectionType* uv, TypeId superTy, bool cacheEnabled, bool isFunctionCall);
|
||||
void tryUnifyNormalizedTypes(TypeId subTy, TypeId superTy, const NormalizedType& subNorm, const NormalizedType& superNorm, std::string reason,
|
||||
std::optional<TypeError> error = std::nullopt);
|
||||
void tryUnifyPrimitives(TypeId subTy, TypeId superTy);
|
||||
void tryUnifySingletons(TypeId subTy, TypeId superTy);
|
||||
void tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCall = false);
|
||||
void tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr);
|
||||
void tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed);
|
||||
void tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed);
|
||||
void tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed);
|
||||
void tryUnifyNegations(TypeId subTy, TypeId superTy);
|
||||
|
||||
TypePackId tryApplyOverloadedFunction(TypeId function, const NormalizedFunctionType& overloads, TypePackId args);
|
||||
|
||||
TypeId widen(TypeId ty);
|
||||
TypePackId widen(TypePackId tp);
|
||||
|
||||
TypeId deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> seen = {});
|
||||
|
||||
bool canCacheResult(TypeId subTy, TypeId superTy);
|
||||
void cacheResult(TypeId subTy, TypeId superTy, size_t prevErrorCount);
|
||||
|
||||
public:
|
||||
void tryUnify(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false);
|
||||
|
||||
private:
|
||||
void tryUnify_(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false);
|
||||
void tryUnifyVariadics(TypePackId subTy, TypePackId superTy, bool reversed, int subOffset = 0);
|
||||
|
||||
void tryUnifyWithAny(TypeId subTy, TypeId anyTy);
|
||||
void tryUnifyWithAny(TypePackId subTy, TypePackId anyTp);
|
||||
|
||||
std::optional<TypeId> findTablePropertyRespectingMeta(TypeId lhsType, Name name);
|
||||
|
||||
TxnLog combineLogsIntoIntersection(std::vector<TxnLog> logs);
|
||||
TxnLog combineLogsIntoUnion(std::vector<TxnLog> logs);
|
||||
|
||||
public:
|
||||
// Returns true if the type "needle" already occurs within "haystack" and reports an "infinite type error"
|
||||
bool occursCheck(TypeId needle, TypeId haystack, bool reversed);
|
||||
bool occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack);
|
||||
bool occursCheck(TypePackId needle, TypePackId haystack, bool reversed);
|
||||
bool occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack);
|
||||
|
||||
Unifier makeChildUnifier();
|
||||
|
||||
void reportError(TypeError err);
|
||||
LUAU_NOINLINE void reportError(Location location, TypeErrorData data);
|
||||
|
||||
private:
|
||||
TypeMismatch::Context mismatchContext();
|
||||
|
||||
void checkChildUnifierTypeMismatch(const ErrorVec& innerErrors, TypeId wantedType, TypeId givenType);
|
||||
void checkChildUnifierTypeMismatch(const ErrorVec& innerErrors, const std::string& prop, TypeId wantedType, TypeId givenType);
|
||||
|
||||
[[noreturn]] void ice(const std::string& message, const Location& location);
|
||||
[[noreturn]] void ice(const std::string& message);
|
||||
|
||||
// Available after regular type pack unification errors
|
||||
std::optional<int> firstPackErrorPos;
|
||||
|
||||
// If true, we do a bunch of small things differently to work better with
|
||||
// the new type inference engine. Most notably, we use the Scope hierarchy
|
||||
// directly rather than using TypeLevels.
|
||||
bool useNewSolver = false;
|
||||
};
|
||||
|
||||
void promoteTypeLevels(TxnLog& log, const TypeArena* arena, TypeLevel minLevel, Scope* outerScope, bool useScope, TypePackId tp);
|
||||
std::optional<TypeError> hasUnificationTooComplex(const ErrorVec& errors);
|
||||
std::optional<TypeError> hasCountMismatch(const ErrorVec& errors);
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,75 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/NotNull.h"
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
using TypeId = const struct Type*;
|
||||
using TypePackId = const struct TypePackVar*;
|
||||
|
||||
struct BuiltinTypes;
|
||||
struct InternalErrorReporter;
|
||||
struct Scope;
|
||||
struct TypeArena;
|
||||
|
||||
enum class OccursCheckResult
|
||||
{
|
||||
Pass,
|
||||
Fail
|
||||
};
|
||||
|
||||
struct Unifier2
|
||||
{
|
||||
NotNull<TypeArena> arena;
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
NotNull<InternalErrorReporter> ice;
|
||||
|
||||
int recursionCount = 0;
|
||||
int recursionLimit = 0;
|
||||
|
||||
Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> ice);
|
||||
|
||||
/** Attempt to commit the subtype relation subTy <: superTy to the type
|
||||
* graph.
|
||||
*
|
||||
* @returns true if successful.
|
||||
*
|
||||
* Note that incoherent types can and will successfully be unified. We stop
|
||||
* when we *cannot know* how to relate the provided types, not when doing so
|
||||
* would narrow something down to never or broaden it to unknown.
|
||||
*
|
||||
* Presently, the only way unification can fail is if we attempt to bind one
|
||||
* free TypePack to another and encounter an occurs check violation.
|
||||
*/
|
||||
bool unify(TypeId subTy, TypeId superTy);
|
||||
|
||||
// TODO think about this one carefully. We don't do unions or intersections of type packs
|
||||
bool unify(TypePackId subTp, TypePackId superTp);
|
||||
|
||||
std::optional<TypeId> generalize(NotNull<Scope> scope, TypeId ty);
|
||||
private:
|
||||
|
||||
/**
|
||||
* @returns simplify(left | right)
|
||||
*/
|
||||
TypeId mkUnion(TypeId left, TypeId right);
|
||||
|
||||
/**
|
||||
* @returns simplify(left & right)
|
||||
*/
|
||||
TypeId mkIntersection(TypeId left, TypeId right);
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypePack.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
struct InternalErrorReporter;
|
||||
|
||||
struct TypeIdPairHash
|
||||
{
|
||||
size_t hashOne(Luau::TypeId key) const
|
||||
{
|
||||
return (uintptr_t(key) >> 4) ^ (uintptr_t(key) >> 9);
|
||||
}
|
||||
|
||||
size_t operator()(const std::pair<Luau::TypeId, Luau::TypeId>& x) const
|
||||
{
|
||||
return hashOne(x.first) ^ (hashOne(x.second) << 1);
|
||||
}
|
||||
};
|
||||
|
||||
struct UnifierCounters
|
||||
{
|
||||
int recursionCount = 0;
|
||||
int recursionLimit = 0;
|
||||
int iterationCount = 0;
|
||||
int iterationLimit = 0;
|
||||
};
|
||||
|
||||
struct UnifierSharedState
|
||||
{
|
||||
UnifierSharedState(InternalErrorReporter* iceHandler)
|
||||
: iceHandler(iceHandler)
|
||||
{
|
||||
}
|
||||
|
||||
InternalErrorReporter* iceHandler;
|
||||
|
||||
DenseHashMap<TypeId, bool> skipCacheForType{nullptr};
|
||||
DenseHashSet<std::pair<TypeId, TypeId>, TypeIdPairHash> cachedUnify{{nullptr, nullptr}};
|
||||
DenseHashMap<std::pair<TypeId, TypeId>, TypeErrorData, TypeIdPairHash> cachedUnifyError{{nullptr, nullptr}};
|
||||
|
||||
DenseHashSet<TypeId> tempSeenTy{nullptr};
|
||||
DenseHashSet<TypePackId> tempSeenTp{nullptr};
|
||||
|
||||
UnifierCounters counters;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,290 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <initializer_list>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
template<typename... Ts>
|
||||
class Variant
|
||||
{
|
||||
static_assert(sizeof...(Ts) > 0, "variant must have at least 1 type (empty variants are ill-formed)");
|
||||
static_assert(std::disjunction_v<std::is_void<Ts>...> == false, "variant does not allow void as an alternative type");
|
||||
static_assert(std::disjunction_v<std::is_reference<Ts>...> == false, "variant does not allow references as an alternative type");
|
||||
static_assert(std::disjunction_v<std::is_array<Ts>...> == false, "variant does not allow arrays as an alternative type");
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
static constexpr int getTypeId()
|
||||
{
|
||||
using TT = std::decay_t<T>;
|
||||
|
||||
constexpr int N = sizeof...(Ts);
|
||||
constexpr bool is[N] = {std::is_same_v<TT, Ts>...};
|
||||
|
||||
for (int i = 0; i < N; ++i)
|
||||
if (is[i])
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
template<typename T, typename... Tail>
|
||||
struct First
|
||||
{
|
||||
using type = T;
|
||||
};
|
||||
|
||||
public:
|
||||
using first_alternative = typename First<Ts...>::type;
|
||||
|
||||
Variant()
|
||||
{
|
||||
static_assert(std::is_default_constructible_v<first_alternative>, "first alternative type must be default constructible");
|
||||
typeId = 0;
|
||||
new (&storage) first_alternative();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Variant(T&& value, std::enable_if_t<getTypeId<T>() >= 0>* = 0)
|
||||
{
|
||||
using TT = std::decay_t<T>;
|
||||
|
||||
constexpr int tid = getTypeId<T>();
|
||||
typeId = tid;
|
||||
new (&storage) TT(std::forward<T>(value));
|
||||
}
|
||||
|
||||
Variant(const Variant& other)
|
||||
{
|
||||
static constexpr FnCopy table[sizeof...(Ts)] = {&fnCopy<Ts>...};
|
||||
|
||||
typeId = other.typeId;
|
||||
table[typeId](&storage, &other.storage);
|
||||
}
|
||||
|
||||
Variant(Variant&& other)
|
||||
{
|
||||
typeId = other.typeId;
|
||||
tableMove[typeId](&storage, &other.storage);
|
||||
}
|
||||
|
||||
~Variant()
|
||||
{
|
||||
tableDtor[typeId](&storage);
|
||||
}
|
||||
|
||||
Variant& operator=(const Variant& other)
|
||||
{
|
||||
Variant copy(other);
|
||||
// static_cast<T&&> is equivalent to std::move() but faster in Debug
|
||||
return *this = static_cast<Variant&&>(copy);
|
||||
}
|
||||
|
||||
Variant& operator=(Variant&& other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
tableDtor[typeId](&storage);
|
||||
typeId = other.typeId;
|
||||
tableMove[typeId](&storage, &other.storage); // nothrow
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
T& emplace(Args&&... args)
|
||||
{
|
||||
using TT = std::decay_t<T>;
|
||||
constexpr int tid = getTypeId<T>();
|
||||
static_assert(tid >= 0, "unsupported T");
|
||||
|
||||
tableDtor[typeId](&storage);
|
||||
typeId = tid;
|
||||
new (&storage) TT{std::forward<Args>(args)...};
|
||||
|
||||
return *reinterpret_cast<T*>(&storage);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const T* get_if() const
|
||||
{
|
||||
constexpr int tid = getTypeId<T>();
|
||||
static_assert(tid >= 0, "unsupported T");
|
||||
|
||||
return tid == typeId ? reinterpret_cast<const T*>(&storage) : nullptr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* get_if()
|
||||
{
|
||||
constexpr int tid = getTypeId<T>();
|
||||
static_assert(tid >= 0, "unsupported T");
|
||||
|
||||
return tid == typeId ? reinterpret_cast<T*>(&storage) : nullptr;
|
||||
}
|
||||
|
||||
bool valueless_by_exception() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int index() const
|
||||
{
|
||||
return typeId;
|
||||
}
|
||||
|
||||
bool operator==(const Variant& other) const
|
||||
{
|
||||
static constexpr FnPred table[sizeof...(Ts)] = {&fnPredEq<Ts>...};
|
||||
|
||||
return typeId == other.typeId && table[typeId](&storage, &other.storage);
|
||||
}
|
||||
|
||||
bool operator!=(const Variant& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr size_t cmax(std::initializer_list<size_t> l)
|
||||
{
|
||||
size_t res = 0;
|
||||
for (size_t i : l)
|
||||
res = (res < i) ? i : res;
|
||||
return res;
|
||||
}
|
||||
|
||||
static constexpr size_t storageSize = cmax({sizeof(Ts)...});
|
||||
static constexpr size_t storageAlign = cmax({alignof(Ts)...});
|
||||
|
||||
using FnCopy = void (*)(void*, const void*);
|
||||
using FnMove = void (*)(void*, void*);
|
||||
using FnDtor = void (*)(void*);
|
||||
using FnPred = bool (*)(const void*, const void*);
|
||||
|
||||
template<typename T>
|
||||
static void fnCopy(void* dst, const void* src)
|
||||
{
|
||||
new (dst) T(*static_cast<const T*>(src));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void fnMove(void* dst, void* src)
|
||||
{
|
||||
// static_cast<T&&> is equivalent to std::move() but faster in Debug
|
||||
new (dst) T(static_cast<T&&>(*static_cast<T*>(src)));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void fnDtor(void* dst)
|
||||
{
|
||||
static_cast<T*>(dst)->~T();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static bool fnPredEq(const void* lhs, const void* rhs)
|
||||
{
|
||||
return *static_cast<const T*>(lhs) == *static_cast<const T*>(rhs);
|
||||
}
|
||||
|
||||
static constexpr FnMove tableMove[sizeof...(Ts)] = {&fnMove<Ts>...};
|
||||
static constexpr FnDtor tableDtor[sizeof...(Ts)] = {&fnDtor<Ts>...};
|
||||
|
||||
int typeId;
|
||||
alignas(storageAlign) char storage[storageSize];
|
||||
|
||||
template<class Visitor, typename... _Ts>
|
||||
friend auto visit(Visitor&& vis, const Variant<_Ts...>& var);
|
||||
template<class Visitor, typename... _Ts>
|
||||
friend auto visit(Visitor&& vis, Variant<_Ts...>& var);
|
||||
};
|
||||
|
||||
template<typename T, typename... Ts>
|
||||
const T* get_if(const Variant<Ts...>* var)
|
||||
{
|
||||
return var ? var->template get_if<T>() : nullptr;
|
||||
}
|
||||
|
||||
template<typename T, typename... Ts>
|
||||
T* get_if(Variant<Ts...>* var)
|
||||
{
|
||||
return var ? var->template get_if<T>() : nullptr;
|
||||
}
|
||||
|
||||
template<typename Visitor, typename Result, typename T>
|
||||
static void fnVisitR(Visitor& vis, Result& dst, std::conditional_t<std::is_const_v<T>, const void, void>* src)
|
||||
{
|
||||
dst = vis(*static_cast<T*>(src));
|
||||
}
|
||||
|
||||
template<typename Visitor, typename T>
|
||||
static void fnVisitV(Visitor& vis, std::conditional_t<std::is_const_v<T>, const void, void>* src)
|
||||
{
|
||||
vis(*static_cast<T*>(src));
|
||||
}
|
||||
|
||||
template<class Visitor, typename... Ts>
|
||||
auto visit(Visitor&& vis, const Variant<Ts...>& var)
|
||||
{
|
||||
static_assert(std::conjunction_v<std::is_invocable<Visitor, Ts>...>, "visitor must accept every alternative as an argument");
|
||||
|
||||
using Result = std::invoke_result_t<Visitor, typename Variant<Ts...>::first_alternative>;
|
||||
static_assert(std::conjunction_v<std::is_same<Result, std::invoke_result_t<Visitor, Ts>>...>,
|
||||
"visitor result type must be consistent between alternatives");
|
||||
|
||||
if constexpr (std::is_same_v<Result, void>)
|
||||
{
|
||||
using FnVisitV = void (*)(Visitor&, const void*);
|
||||
static const FnVisitV tableVisit[sizeof...(Ts)] = {&fnVisitV<Visitor, const Ts>...};
|
||||
|
||||
tableVisit[var.typeId](vis, &var.storage);
|
||||
}
|
||||
else
|
||||
{
|
||||
using FnVisitR = void (*)(Visitor&, Result&, const void*);
|
||||
static const FnVisitR tableVisit[sizeof...(Ts)] = {&fnVisitR<Visitor, Result, const Ts>...};
|
||||
|
||||
Result res;
|
||||
tableVisit[var.typeId](vis, res, &var.storage);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
template<class Visitor, typename... Ts>
|
||||
auto visit(Visitor&& vis, Variant<Ts...>& var)
|
||||
{
|
||||
static_assert(std::conjunction_v<std::is_invocable<Visitor, Ts&>...>, "visitor must accept every alternative as an argument");
|
||||
|
||||
using Result = std::invoke_result_t<Visitor, typename Variant<Ts...>::first_alternative&>;
|
||||
static_assert(std::conjunction_v<std::is_same<Result, std::invoke_result_t<Visitor, Ts&>>...>,
|
||||
"visitor result type must be consistent between alternatives");
|
||||
|
||||
if constexpr (std::is_same_v<Result, void>)
|
||||
{
|
||||
using FnVisitV = void (*)(Visitor&, void*);
|
||||
static const FnVisitV tableVisit[sizeof...(Ts)] = {&fnVisitV<Visitor, Ts>...};
|
||||
|
||||
tableVisit[var.typeId](vis, &var.storage);
|
||||
}
|
||||
else
|
||||
{
|
||||
using FnVisitR = void (*)(Visitor&, Result&, void*);
|
||||
static const FnVisitR tableVisit[sizeof...(Ts)] = {&fnVisitR<Visitor, Result, Ts>...};
|
||||
|
||||
Result res;
|
||||
tableVisit[var.typeId](vis, res, &var.storage);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
template<class>
|
||||
inline constexpr bool always_false_v = false;
|
||||
|
||||
} // namespace Luau
|
|
@ -0,0 +1,466 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/RecursionCounter.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/Type.h"
|
||||
|
||||
LUAU_FASTINT(LuauVisitRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauBoundLazyTypes2)
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
namespace visit_detail
|
||||
{
|
||||
/**
|
||||
* Apply f(tid, t, seen) if doing so would pass type checking, else apply f(tid, t)
|
||||
*
|
||||
* We do this to permit (but not require) Type visitors to accept the seen set as an argument.
|
||||
*/
|
||||
template<typename F, typename A, typename B, typename C>
|
||||
auto apply(A tid, const B& t, C& c, F& f) -> decltype(f(tid, t, c))
|
||||
{
|
||||
return f(tid, t, c);
|
||||
}
|
||||
|
||||
template<typename A, typename B, typename C, typename F>
|
||||
auto apply(A tid, const B& t, C&, F& f) -> decltype(f(tid, t))
|
||||
{
|
||||
return f(tid, t);
|
||||
}
|
||||
|
||||
inline bool hasSeen(std::unordered_set<void*>& seen, const void* tv)
|
||||
{
|
||||
void* ttv = const_cast<void*>(tv);
|
||||
return !seen.insert(ttv).second;
|
||||
}
|
||||
|
||||
inline bool hasSeen(DenseHashSet<void*>& seen, const void* tv)
|
||||
{
|
||||
void* ttv = const_cast<void*>(tv);
|
||||
|
||||
if (seen.contains(ttv))
|
||||
return true;
|
||||
|
||||
seen.insert(ttv);
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void unsee(std::unordered_set<void*>& seen, const void* tv)
|
||||
{
|
||||
void* ttv = const_cast<void*>(tv);
|
||||
seen.erase(ttv);
|
||||
}
|
||||
|
||||
inline void unsee(DenseHashSet<void*>& seen, const void* tv)
|
||||
{
|
||||
// When DenseHashSet is used for 'visitTypeOnce', where don't forget visited elements
|
||||
}
|
||||
|
||||
} // namespace visit_detail
|
||||
|
||||
template<typename S>
|
||||
struct GenericTypeVisitor
|
||||
{
|
||||
using Set = S;
|
||||
|
||||
Set seen;
|
||||
bool skipBoundTypes = false;
|
||||
int recursionCounter = 0;
|
||||
|
||||
GenericTypeVisitor() = default;
|
||||
|
||||
explicit GenericTypeVisitor(Set seen, bool skipBoundTypes = false)
|
||||
: seen(std::move(seen))
|
||||
, skipBoundTypes(skipBoundTypes)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void cycle(TypeId) {}
|
||||
virtual void cycle(TypePackId) {}
|
||||
|
||||
virtual bool visit(TypeId ty)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
virtual bool visit(TypeId ty, const BoundType& btv)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
virtual bool visit(TypeId ty, const FreeType& ftv)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
virtual bool visit(TypeId ty, const GenericType& gtv)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
virtual bool visit(TypeId ty, const ErrorType& etv)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
virtual bool visit(TypeId ty, const PrimitiveType& ptv)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
virtual bool visit(TypeId ty, const FunctionType& ftv)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
virtual bool visit(TypeId ty, const TableType& ttv)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
virtual bool visit(TypeId ty, const MetatableType& mtv)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
virtual bool visit(TypeId ty, const ClassType& ctv)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
virtual bool visit(TypeId ty, const AnyType& atv)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
virtual bool visit(TypeId ty, const UnknownType& utv)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
virtual bool visit(TypeId ty, const NeverType& ntv)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
virtual bool visit(TypeId ty, const UnionType& utv)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
virtual bool visit(TypeId ty, const IntersectionType& itv)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
virtual bool visit(TypeId ty, const BlockedType& btv)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
virtual bool visit(TypeId ty, const PendingExpansionType& petv)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
virtual bool visit(TypeId ty, const SingletonType& stv)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
virtual bool visit(TypeId ty, const NegationType& ntv)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
virtual bool visit(TypeId ty, const TypeFamilyInstanceType& tfit)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
|
||||
virtual bool visit(TypePackId tp)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
virtual bool visit(TypePackId tp, const BoundTypePack& btp)
|
||||
{
|
||||
return visit(tp);
|
||||
}
|
||||
virtual bool visit(TypePackId tp, const FreeTypePack& ftp)
|
||||
{
|
||||
return visit(tp);
|
||||
}
|
||||
virtual bool visit(TypePackId tp, const GenericTypePack& gtp)
|
||||
{
|
||||
return visit(tp);
|
||||
}
|
||||
virtual bool visit(TypePackId tp, const Unifiable::Error& etp)
|
||||
{
|
||||
return visit(tp);
|
||||
}
|
||||
virtual bool visit(TypePackId tp, const TypePack& pack)
|
||||
{
|
||||
return visit(tp);
|
||||
}
|
||||
virtual bool visit(TypePackId tp, const VariadicTypePack& vtp)
|
||||
{
|
||||
return visit(tp);
|
||||
}
|
||||
virtual bool visit(TypePackId tp, const BlockedTypePack& btp)
|
||||
{
|
||||
return visit(tp);
|
||||
}
|
||||
virtual bool visit(TypePackId tp, const TypeFamilyInstanceTypePack& tfitp)
|
||||
{
|
||||
return visit(tp);
|
||||
}
|
||||
|
||||
void traverse(TypeId ty)
|
||||
{
|
||||
RecursionLimiter limiter{&recursionCounter, FInt::LuauVisitRecursionLimit};
|
||||
|
||||
if (visit_detail::hasSeen(seen, ty))
|
||||
{
|
||||
cycle(ty);
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto btv = get<BoundType>(ty))
|
||||
{
|
||||
if (skipBoundTypes)
|
||||
traverse(btv->boundTo);
|
||||
else if (visit(ty, *btv))
|
||||
traverse(btv->boundTo);
|
||||
}
|
||||
else if (auto ftv = get<FreeType>(ty))
|
||||
visit(ty, *ftv);
|
||||
else if (auto gtv = get<GenericType>(ty))
|
||||
visit(ty, *gtv);
|
||||
else if (auto etv = get<ErrorType>(ty))
|
||||
visit(ty, *etv);
|
||||
else if (auto ptv = get<PrimitiveType>(ty))
|
||||
visit(ty, *ptv);
|
||||
else if (auto ftv = get<FunctionType>(ty))
|
||||
{
|
||||
if (visit(ty, *ftv))
|
||||
{
|
||||
traverse(ftv->argTypes);
|
||||
traverse(ftv->retTypes);
|
||||
}
|
||||
}
|
||||
else if (auto ttv = get<TableType>(ty))
|
||||
{
|
||||
// Some visitors want to see bound tables, that's why we traverse the original type
|
||||
if (skipBoundTypes && ttv->boundTo)
|
||||
{
|
||||
traverse(*ttv->boundTo);
|
||||
}
|
||||
else if (visit(ty, *ttv))
|
||||
{
|
||||
if (ttv->boundTo)
|
||||
{
|
||||
traverse(*ttv->boundTo);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto& [_name, prop] : ttv->props)
|
||||
{
|
||||
if (FFlag::DebugLuauReadWriteProperties)
|
||||
{
|
||||
if (auto ty = prop.readType())
|
||||
traverse(*ty);
|
||||
|
||||
if (auto ty = prop.writeType())
|
||||
traverse(*ty);
|
||||
}
|
||||
else
|
||||
traverse(prop.type());
|
||||
}
|
||||
|
||||
if (ttv->indexer)
|
||||
{
|
||||
traverse(ttv->indexer->indexType);
|
||||
traverse(ttv->indexer->indexResultType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (auto mtv = get<MetatableType>(ty))
|
||||
{
|
||||
if (visit(ty, *mtv))
|
||||
{
|
||||
traverse(mtv->table);
|
||||
traverse(mtv->metatable);
|
||||
}
|
||||
}
|
||||
else if (auto ctv = get<ClassType>(ty))
|
||||
{
|
||||
if (visit(ty, *ctv))
|
||||
{
|
||||
for (const auto& [name, prop] : ctv->props)
|
||||
{
|
||||
if (FFlag::DebugLuauReadWriteProperties)
|
||||
{
|
||||
if (auto ty = prop.readType())
|
||||
traverse(*ty);
|
||||
|
||||
if (auto ty = prop.writeType())
|
||||
traverse(*ty);
|
||||
}
|
||||
else
|
||||
traverse(prop.type());
|
||||
}
|
||||
|
||||
if (ctv->parent)
|
||||
traverse(*ctv->parent);
|
||||
|
||||
if (ctv->metatable)
|
||||
traverse(*ctv->metatable);
|
||||
|
||||
if (ctv->indexer)
|
||||
{
|
||||
traverse(ctv->indexer->indexType);
|
||||
traverse(ctv->indexer->indexResultType);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (auto atv = get<AnyType>(ty))
|
||||
visit(ty, *atv);
|
||||
else if (auto utv = get<UnionType>(ty))
|
||||
{
|
||||
if (visit(ty, *utv))
|
||||
{
|
||||
for (TypeId optTy : utv->options)
|
||||
traverse(optTy);
|
||||
}
|
||||
}
|
||||
else if (auto itv = get<IntersectionType>(ty))
|
||||
{
|
||||
if (visit(ty, *itv))
|
||||
{
|
||||
for (TypeId partTy : itv->parts)
|
||||
traverse(partTy);
|
||||
}
|
||||
}
|
||||
else if (auto ltv = get<LazyType>(ty))
|
||||
{
|
||||
if (TypeId unwrapped = ltv->unwrapped)
|
||||
traverse(unwrapped);
|
||||
|
||||
// Visiting into LazyType that hasn't been unwrapped may necessarily cause infinite expansion, so we don't do that on purpose.
|
||||
// Asserting also makes no sense, because the type _will_ happen here, most likely as a property of some ClassType
|
||||
// that doesn't need to be expanded.
|
||||
}
|
||||
else if (auto stv = get<SingletonType>(ty))
|
||||
visit(ty, *stv);
|
||||
else if (auto btv = get<BlockedType>(ty))
|
||||
visit(ty, *btv);
|
||||
else if (auto utv = get<UnknownType>(ty))
|
||||
visit(ty, *utv);
|
||||
else if (auto ntv = get<NeverType>(ty))
|
||||
visit(ty, *ntv);
|
||||
else if (auto petv = get<PendingExpansionType>(ty))
|
||||
{
|
||||
if (visit(ty, *petv))
|
||||
{
|
||||
for (TypeId a : petv->typeArguments)
|
||||
traverse(a);
|
||||
|
||||
for (TypePackId a : petv->packArguments)
|
||||
traverse(a);
|
||||
}
|
||||
}
|
||||
else if (auto ntv = get<NegationType>(ty))
|
||||
{
|
||||
if (visit(ty, *ntv))
|
||||
traverse(ntv->ty);
|
||||
}
|
||||
else if (auto tfit = get<TypeFamilyInstanceType>(ty))
|
||||
{
|
||||
if (visit(ty, *tfit))
|
||||
{
|
||||
for (TypeId p : tfit->typeArguments)
|
||||
traverse(p);
|
||||
|
||||
for (TypePackId p : tfit->packArguments)
|
||||
traverse(p);
|
||||
}
|
||||
}
|
||||
else
|
||||
LUAU_ASSERT(!"GenericTypeVisitor::traverse(TypeId) is not exhaustive!");
|
||||
|
||||
visit_detail::unsee(seen, ty);
|
||||
}
|
||||
|
||||
void traverse(TypePackId tp)
|
||||
{
|
||||
if (visit_detail::hasSeen(seen, tp))
|
||||
{
|
||||
cycle(tp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto btv = get<BoundTypePack>(tp))
|
||||
{
|
||||
if (visit(tp, *btv))
|
||||
traverse(btv->boundTo);
|
||||
}
|
||||
|
||||
else if (auto ftv = get<FreeTypePack>(tp))
|
||||
visit(tp, *ftv);
|
||||
|
||||
else if (auto gtv = get<GenericTypePack>(tp))
|
||||
visit(tp, *gtv);
|
||||
|
||||
else if (auto etv = get<Unifiable::Error>(tp))
|
||||
visit(tp, *etv);
|
||||
|
||||
else if (auto pack = get<TypePack>(tp))
|
||||
{
|
||||
bool res = visit(tp, *pack);
|
||||
if (res)
|
||||
{
|
||||
for (TypeId ty : pack->head)
|
||||
traverse(ty);
|
||||
|
||||
if (pack->tail)
|
||||
traverse(*pack->tail);
|
||||
}
|
||||
}
|
||||
else if (auto pack = get<VariadicTypePack>(tp))
|
||||
{
|
||||
bool res = visit(tp, *pack);
|
||||
if (res)
|
||||
traverse(pack->ty);
|
||||
}
|
||||
else if (auto btp = get<BlockedTypePack>(tp))
|
||||
visit(tp, *btp);
|
||||
else if (auto tfitp = get<TypeFamilyInstanceTypePack>(tp))
|
||||
{
|
||||
if (visit(tp, *tfitp))
|
||||
{
|
||||
for (TypeId t : tfitp->typeArguments)
|
||||
traverse(t);
|
||||
|
||||
for (TypePackId t : tfitp->packArguments)
|
||||
traverse(t);
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
LUAU_ASSERT(!"GenericTypeVisitor::traverse(TypePackId) is not exhaustive!");
|
||||
|
||||
visit_detail::unsee(seen, tp);
|
||||
}
|
||||
};
|
||||
|
||||
/** Visit each type under a given type. Skips over cycles and keeps recursion depth under control.
|
||||
*
|
||||
* The same type may be visited multiple times if there are multiple distinct paths to it. If this is undesirable, use
|
||||
* TypeOnceVisitor.
|
||||
*/
|
||||
struct TypeVisitor : GenericTypeVisitor<std::unordered_set<void*>>
|
||||
{
|
||||
explicit TypeVisitor(bool skipBoundTypes = false)
|
||||
: GenericTypeVisitor{{}, skipBoundTypes}
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/// Visit each type under a given type. Each type will only be checked once even if there are multiple paths to it.
|
||||
struct TypeOnceVisitor : GenericTypeVisitor<DenseHashSet<void*>>
|
||||
{
|
||||
explicit TypeOnceVisitor(bool skipBoundTypes = false)
|
||||
: GenericTypeVisitor{DenseHashSet<void*>{nullptr}, skipBoundTypes}
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Luau
|
Loading…
Reference in New Issue