mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-13 13:30:40 +00:00
Sync to upstream/release/501 (#20)
Co-authored-by: Rodactor <rodactor@roblox.com>
This commit is contained in:
parent
87445fc70f
commit
46110524ef
336 changed files with 118239 additions and 0 deletions
25
.clang-format
Normal file
25
.clang-format
Normal file
|
@ -0,0 +1,25 @@
|
|||
BasedOnStyle: LLVM
|
||||
|
||||
AccessModifierOffset: -4
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
AllowShortLambdasOnASingleLine: Empty
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
BreakBeforeBraces: Allman
|
||||
BreakConstructorInitializers: BeforeComma
|
||||
BreakInheritanceList: BeforeComma
|
||||
ColumnLimit: 150
|
||||
IndentCaseLabels: false
|
||||
SortIncludes: false
|
||||
IndentWidth: 4
|
||||
TabWidth: 4
|
||||
ObjCBlockIndentWidth: 4
|
||||
AlignAfterOpenBracket: DontAlign
|
||||
UseTab: Never
|
||||
PointerAlignment: Left
|
||||
SpaceAfterTemplateKeyword: false
|
||||
AlignEscapedNewlines: DontAlign
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
MaxEmptyLinesToKeep: 10
|
63
Analysis/include/Luau/AstQuery.h
Normal file
63
Analysis/include/Luau/AstQuery.h
Normal file
|
@ -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/Ast.h"
|
||||
#include "Luau/Documentation.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct Binding;
|
||||
struct SourceModule;
|
||||
struct Module;
|
||||
|
||||
struct TypeVar;
|
||||
using TypeId = const TypeVar*;
|
||||
|
||||
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>{});
|
||||
}
|
||||
|
||||
private:
|
||||
AstExpr* expr = nullptr;
|
||||
AstLocal* local = nullptr;
|
||||
};
|
||||
|
||||
std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos);
|
||||
AstNode* findNodeAtPosition(const SourceModule& source, 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
|
91
Analysis/include/Luau/Autocomplete.h
Normal file
91
Analysis/include/Luau/Autocomplete.h
Normal file
|
@ -0,0 +1,91 @@
|
|||
// 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/TypeVar.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 AutocompleteEntryKind
|
||||
{
|
||||
Property,
|
||||
Binding,
|
||||
Keyword,
|
||||
String,
|
||||
Type,
|
||||
Module,
|
||||
};
|
||||
|
||||
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 ClassTypeVar*> containingClass = std::nullopt;
|
||||
std::optional<const Property*> prop = std::nullopt;
|
||||
std::optional<std::string> documentationSymbol = std::nullopt;
|
||||
Tags tags;
|
||||
ParenthesesRecommendation parens = ParenthesesRecommendation::None;
|
||||
};
|
||||
|
||||
using AutocompleteEntryMap = std::unordered_map<std::string, AutocompleteEntry>;
|
||||
struct AutocompleteResult
|
||||
{
|
||||
AutocompleteEntryMap entryMap;
|
||||
std::vector<AstNode*> ancestry;
|
||||
|
||||
AutocompleteResult() = default;
|
||||
AutocompleteResult(AutocompleteEntryMap entryMap, std::vector<AstNode*> ancestry)
|
||||
: entryMap(std::move(entryMap))
|
||||
, ancestry(std::move(ancestry))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
using ModuleName = std::string;
|
||||
using StringCompletionCallback = std::function<std::optional<AutocompleteEntryMap>(std::string tag, std::optional<const ClassTypeVar*> ctx)>;
|
||||
|
||||
struct OwningAutocompleteResult
|
||||
{
|
||||
AutocompleteResult result;
|
||||
ModulePtr module;
|
||||
std::unique_ptr<SourceModule> sourceModule;
|
||||
};
|
||||
|
||||
AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback);
|
||||
OwningAutocompleteResult autocompleteSource(Frontend& frontend, std::string_view source, Position position, StringCompletionCallback callback);
|
||||
|
||||
} // namespace Luau
|
51
Analysis/include/Luau/BuiltinDefinitions.h
Normal file
51
Analysis/include/Luau/BuiltinDefinitions.h
Normal file
|
@ -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 "TypeInfer.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
void registerBuiltinTypes(TypeChecker& typeChecker);
|
||||
|
||||
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types);
|
||||
TypeId makeIntersection(TypeArena& arena, std::vector<TypeId>&& types);
|
||||
|
||||
/** Build an optional 't'
|
||||
*/
|
||||
TypeId makeOption(TypeChecker& typeChecker, 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 attachFunctionTag(TypeId ty, std::string constraint);
|
||||
|
||||
Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol = std::nullopt);
|
||||
void assignPropDocumentationSymbols(TableTypeVar::Props& props, const std::string& baseName);
|
||||
|
||||
std::string getBuiltinDefinitionSource();
|
||||
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const std::string& name, TypeId ty, const std::string& packageName);
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const std::string& name, Binding binding);
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const ScopePtr& scope, const std::string& name, TypeId ty, const std::string& packageName);
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const ScopePtr& scope, const std::string& name, Binding binding);
|
||||
std::optional<Binding> tryGetGlobalBinding(TypeChecker& typeChecker, const std::string& name);
|
||||
Binding* tryGetGlobalBindingRef(TypeChecker& typeChecker, const std::string& name);
|
||||
TypeId getGlobalBinding(TypeChecker& typeChecker, const std::string& name);
|
||||
|
||||
} // namespace Luau
|
58
Analysis/include/Luau/Config.h
Normal file
58
Analysis/include/Luau/Config.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Linter.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()
|
||||
{
|
||||
enabledLint.setDefaults();
|
||||
}
|
||||
|
||||
Mode mode = Mode::NoCheck;
|
||||
|
||||
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
|
50
Analysis/include/Luau/Documentation.h
Normal file
50
Analysis/include/Luau/Documentation.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct FunctionDocumentation;
|
||||
struct TableDocumentation;
|
||||
struct OverloadedFunctionDocumentation;
|
||||
|
||||
using Documentation = Luau::Variant<std::string, FunctionDocumentation, TableDocumentation, OverloadedFunctionDocumentation>;
|
||||
using DocumentationSymbol = std::string;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
using DocumentationDatabase = Luau::DenseHashMap<DocumentationSymbol, Documentation>;
|
||||
|
||||
} // namespace Luau
|
332
Analysis/include/Luau/Error.h
Normal file
332
Analysis/include/Luau/Error.h
Normal file
|
@ -0,0 +1,332 @@
|
|||
// 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 "Luau/Location.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct TypeMismatch
|
||||
{
|
||||
TypeId wantedType;
|
||||
TypeId givenType;
|
||||
|
||||
bool operator==(const TypeMismatch& rhs) const;
|
||||
};
|
||||
|
||||
struct UnknownSymbol
|
||||
{
|
||||
enum Context
|
||||
{
|
||||
Binding,
|
||||
Type,
|
||||
Generic
|
||||
};
|
||||
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;
|
||||
Location previousLocation;
|
||||
|
||||
bool operator==(const DuplicateTypeDefinition& rhs) const;
|
||||
};
|
||||
|
||||
struct CountMismatch
|
||||
{
|
||||
enum Context
|
||||
{
|
||||
Arg,
|
||||
Result,
|
||||
Return,
|
||||
};
|
||||
size_t expected;
|
||||
size_t actual;
|
||||
Context context = Arg;
|
||||
|
||||
bool operator==(const CountMismatch& rhs) const;
|
||||
};
|
||||
|
||||
struct FunctionDoesNotTakeSelf
|
||||
{
|
||||
bool operator==(const FunctionDoesNotTakeSelf& rhs) const;
|
||||
};
|
||||
|
||||
struct FunctionRequiresSelf
|
||||
{
|
||||
int requiredExtraNils = 0;
|
||||
|
||||
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;
|
||||
|
||||
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 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;
|
||||
};
|
||||
|
||||
using TypeErrorData = Variant<TypeMismatch, UnknownSymbol, UnknownProperty, NotATable, CannotExtendTable, OnlyTablesCanHaveMethods,
|
||||
DuplicateTypeDefinition, CountMismatch, FunctionDoesNotTakeSelf, FunctionRequiresSelf, OccursCheckFailed, UnknownRequire,
|
||||
IncorrectGenericParameterCount, SyntaxError, CodeTooComplex, UnificationTooComplex, UnknownPropButFoundLikeProp, GenericError,
|
||||
CannotCallNonFunction, ExtraInformation, DeprecatedApiUsed, ModuleHasCyclicDependency, IllegalRequire, FunctionExitsWithoutReturning,
|
||||
DuplicateGenericParameter, CannotInferBinaryOperation, MissingProperties, SwappedGenericTypeParameter, OptionalValueAccess, MissingUnionProperty>;
|
||||
|
||||
struct TypeError
|
||||
{
|
||||
Location location;
|
||||
ModuleName moduleName;
|
||||
TypeErrorData data;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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>;
|
||||
|
||||
std::string toString(const TypeError& error);
|
||||
|
||||
bool containsParseErrorName(const TypeError& error);
|
||||
|
||||
// Copy any types named in the error into destArena.
|
||||
void copyErrors(ErrorVec& errors, struct TypeArena& destArena);
|
||||
|
||||
// Internal Compiler Error
|
||||
struct InternalErrorReporter
|
||||
{
|
||||
std::function<void(const char*)> onInternalError;
|
||||
std::string moduleName;
|
||||
|
||||
[[noreturn]] void ice(const std::string& message, const Location& location);
|
||||
[[noreturn]] void ice(const std::string& message);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
103
Analysis/include/Luau/FileResolver.h
Normal file
103
Analysis/include/Luau/FileResolver.h
Normal file
|
@ -0,0 +1,103 @@
|
|||
// 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 FileResolver
|
||||
{
|
||||
virtual ~FileResolver() {}
|
||||
|
||||
/** Fetch the source code associated with the provided ModuleName.
|
||||
*
|
||||
* FIXME: This requires a string copy!
|
||||
*
|
||||
* @returns The actual Lua code on success.
|
||||
* @returns std::nullopt if no such file exists. When this occurs, type inference will report an UnknownRequire error.
|
||||
*/
|
||||
virtual std::optional<SourceCode> readSource(const ModuleName& name) = 0;
|
||||
|
||||
/** Does the module exist?
|
||||
*
|
||||
* Saves a string copy over reading the source and throwing it away.
|
||||
*/
|
||||
virtual bool moduleExists(const ModuleName& name) const = 0;
|
||||
|
||||
virtual std::optional<ModuleName> fromAstFragment(AstExpr* expr) const = 0;
|
||||
|
||||
/** Given a valid module name and a string of arbitrary data, figure out the concatenation.
|
||||
*/
|
||||
virtual ModuleName concat(const ModuleName& lhs, std::string_view rhs) const = 0;
|
||||
|
||||
/** Goes "up" a level in the hierarchy that the ModuleName represents.
|
||||
*
|
||||
* For instances, this is analogous to someInstance.Parent; for paths, this is equivalent to removing the last
|
||||
* element of the path. Other ModuleName representations may have other ways of doing this.
|
||||
*
|
||||
* @returns The parent ModuleName, if one exists.
|
||||
* @returns std::nullopt if there is no parent for this module name.
|
||||
*/
|
||||
virtual std::optional<ModuleName> getParentModuleName(const ModuleName& name) const = 0;
|
||||
|
||||
virtual std::optional<std::string> getHumanReadableModuleName_(const ModuleName& name) const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
virtual std::optional<std::string> getEnvironmentForModule(const ModuleName& name) const = 0;
|
||||
|
||||
/** LanguageService only:
|
||||
* std::optional<ModuleName> fromInstance(Instance* inst)
|
||||
*/
|
||||
};
|
||||
|
||||
struct NullFileResolver : FileResolver
|
||||
{
|
||||
std::optional<SourceCode> readSource(const ModuleName& name) override
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
bool moduleExists(const ModuleName& name) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::optional<ModuleName> fromAstFragment(AstExpr* expr) const override
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
ModuleName concat(const ModuleName& lhs, std::string_view rhs) const override
|
||||
{
|
||||
return lhs;
|
||||
}
|
||||
std::optional<ModuleName> getParentModuleName(const ModuleName& name) const override
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<std::string> getEnvironmentForModule(const ModuleName& name) const override
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Luau
|
179
Analysis/include/Luau/Frontend.h
Normal file
179
Analysis/include/Luau/Frontend.h
Normal file
|
@ -0,0 +1,179 @@
|
|||
// 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/TypeInfer.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
class AstStat;
|
||||
class ParseError;
|
||||
struct Frontend;
|
||||
struct TypeError;
|
||||
struct LintWarning;
|
||||
struct TypeChecker;
|
||||
struct FileResolver;
|
||||
struct ModuleResolver;
|
||||
struct ParseResult;
|
||||
|
||||
struct LoadDefinitionFileResult
|
||||
{
|
||||
bool success;
|
||||
ParseResult parseResult;
|
||||
ModulePtr module;
|
||||
};
|
||||
|
||||
LoadDefinitionFileResult loadDefinitionFile(
|
||||
TypeChecker& typeChecker, ScopePtr targetScope, std::string_view definition, const std::string& packageName);
|
||||
|
||||
std::optional<Mode> parseMode(const std::vector<std::string>& 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
|
||||
{
|
||||
ModuleName name;
|
||||
std::unordered_set<ModuleName> requires;
|
||||
std::vector<std::pair<ModuleName, Location>> requireLocations;
|
||||
bool dirty = true;
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
// When true, we run typechecking twice, one in the regular mode, ond once in strict mode
|
||||
// in order to get more precise type information (e.g. for autocomplete).
|
||||
bool typecheckTwice = false;
|
||||
};
|
||||
|
||||
struct CheckResult
|
||||
{
|
||||
std::vector<TypeError> errors;
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
Frontend* frontend;
|
||||
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 = {});
|
||||
|
||||
CheckResult check(const ModuleName& name); // new shininess
|
||||
LintResult lint(const ModuleName& name, std::optional<Luau::LintOptions> enabledLintWarnings = {});
|
||||
|
||||
/** Lint some code that has no associated DataModel object
|
||||
*
|
||||
* Since this source fragment has no name, we cannot cache its AST. Instead,
|
||||
* we return it to the caller to use as they wish.
|
||||
*/
|
||||
std::pair<SourceModule, LintResult> lintFragment(std::string_view source, std::optional<Luau::LintOptions> enabledLintWarnings = {});
|
||||
|
||||
CheckResult check(const SourceModule& module); // OLD. TODO KILL
|
||||
LintResult lint(const SourceModule& module, std::optional<Luau::LintOptions> enabledLintWarnings = {});
|
||||
|
||||
bool isDirty(const ModuleName& name) 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);
|
||||
|
||||
void registerBuiltinDefinition(const std::string& name, std::function<void(TypeChecker&, ScopePtr)>);
|
||||
void applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName);
|
||||
|
||||
private:
|
||||
std::pair<SourceNode*, SourceModule*> getSourceNode(CheckResult& checkResult, const ModuleName& name);
|
||||
SourceModule parse(const ModuleName& name, std::string_view src, const ParseOptions& parseOptions);
|
||||
|
||||
bool parseGraph(std::vector<ModuleName>& buildQueue, CheckResult& checkResult, const ModuleName& root);
|
||||
|
||||
static LintResult classifyLints(const std::vector<LintWarning>& warnings, const Config& config);
|
||||
|
||||
ScopePtr getModuleEnvironment(const SourceModule& module, const Config& config);
|
||||
|
||||
std::unordered_map<std::string, ScopePtr> environments;
|
||||
std::unordered_map<std::string, std::function<void(TypeChecker&, ScopePtr)>> builtinDefinitions;
|
||||
|
||||
public:
|
||||
FileResolver* fileResolver;
|
||||
FrontendModuleResolver moduleResolver;
|
||||
FrontendModuleResolver moduleResolverForAutocomplete;
|
||||
TypeChecker typeChecker;
|
||||
TypeChecker typeCheckerForAutocomplete;
|
||||
ConfigResolver* configResolver;
|
||||
FrontendOptions options;
|
||||
InternalErrorReporter iceHandler;
|
||||
TypeArena arenaForAutocomplete;
|
||||
|
||||
std::unordered_map<ModuleName, SourceNode> sourceNodes;
|
||||
std::unordered_map<ModuleName, SourceModule> sourceModules;
|
||||
std::unordered_map<ModuleName, RequireTraceResult> requires;
|
||||
|
||||
Stats stats = {};
|
||||
};
|
||||
|
||||
} // namespace Luau
|
46
Analysis/include/Luau/IostreamHelpers.h
Normal file
46
Analysis/include/Luau/IostreamHelpers.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
// 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/TypeVar.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 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 TableState& tv);
|
||||
std::ostream& operator<<(std::ostream& lhs, const TypeVar& tv);
|
||||
std::ostream& operator<<(std::ostream& lhs, const TypePackVar& tv);
|
||||
|
||||
std::ostream& operator<<(std::ostream& lhs, const TypeErrorData& ted);
|
||||
|
||||
} // namespace Luau
|
13
Analysis/include/Luau/JsonEncoder.h
Normal file
13
Analysis/include/Luau/JsonEncoder.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
// 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
|
||||
{
|
||||
|
||||
class AstNode;
|
||||
|
||||
std::string toJson(AstNode* node);
|
||||
|
||||
} // namespace Luau
|
96
Analysis/include/Luau/Linter.h
Normal file
96
Analysis/include/Luau/Linter.h
Normal file
|
@ -0,0 +1,96 @@
|
|||
// 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 <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct AstName;
|
||||
class AstStat;
|
||||
class AstNameTable;
|
||||
struct TypeChecker;
|
||||
struct Module;
|
||||
|
||||
using ScopePtr = std::shared_ptr<struct Scope>;
|
||||
|
||||
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__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<std::string>& hotcomments);
|
||||
};
|
||||
|
||||
struct LintResult
|
||||
{
|
||||
std::vector<LintWarning> errors;
|
||||
std::vector<LintWarning> warnings;
|
||||
};
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
std::vector<LintWarning> lint(AstStat* root, const AstNameTable& names, const ScopePtr& env, const Module* module, const LintOptions& options);
|
||||
|
||||
std::vector<AstName> getDeprecatedGlobals(const AstNameTable& names);
|
||||
|
||||
} // namespace Luau
|
111
Analysis/include/Luau/Module.h
Normal file
111
Analysis/include/Luau/Module.h
Normal file
|
@ -0,0 +1,111 @@
|
|||
// 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 "Luau/TypePack.h"
|
||||
#include "Luau/TypedAllocator.h"
|
||||
#include "Luau/ParseOptions.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Parser.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>;
|
||||
|
||||
/// Root of the AST of a parsed source file
|
||||
struct SourceModule
|
||||
{
|
||||
ModuleName name; // DataModel path if possible. Filename if not.
|
||||
SourceCode::Type type = SourceCode::None;
|
||||
std::optional<std::string> environmentName;
|
||||
bool cyclic = false;
|
||||
|
||||
std::unique_ptr<Allocator> allocator;
|
||||
std::unique_ptr<AstNameTable> names;
|
||||
std::vector<ParseError> parseErrors;
|
||||
|
||||
AstStatBlock* root = nullptr;
|
||||
std::optional<Mode> mode;
|
||||
uint64_t ignoreLints = 0;
|
||||
|
||||
std::vector<Comment> commentLocations;
|
||||
|
||||
SourceModule()
|
||||
: allocator(new Allocator)
|
||||
, names(new AstNameTable(*allocator))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
bool isWithinComment(const SourceModule& sourceModule, Position pos);
|
||||
|
||||
struct TypeArena
|
||||
{
|
||||
TypedAllocator<TypeVar> typeVars;
|
||||
TypedAllocator<TypePackVar> typePacks;
|
||||
|
||||
void clear();
|
||||
|
||||
template<typename T>
|
||||
TypeId addType(T tv)
|
||||
{
|
||||
return addTV(TypeVar(std::move(tv)));
|
||||
}
|
||||
|
||||
TypeId addTV(TypeVar&& tv);
|
||||
|
||||
TypeId freshType(TypeLevel level);
|
||||
|
||||
TypePackId addTypePack(std::initializer_list<TypeId> types);
|
||||
TypePackId addTypePack(std::vector<TypeId> types);
|
||||
TypePackId addTypePack(TypePack pack);
|
||||
TypePackId addTypePack(TypePackVar pack);
|
||||
};
|
||||
|
||||
void freeze(TypeArena& arena);
|
||||
void unfreeze(TypeArena& arena);
|
||||
|
||||
// Only exposed so they can be unit tested.
|
||||
using SeenTypes = std::unordered_map<TypeId, TypeId>;
|
||||
using SeenTypePacks = std::unordered_map<TypePackId, TypePackId>;
|
||||
|
||||
TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType = nullptr);
|
||||
TypeId clone(TypeId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType = nullptr);
|
||||
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType = nullptr);
|
||||
|
||||
struct Module
|
||||
{
|
||||
~Module();
|
||||
|
||||
TypeArena interfaceTypes;
|
||||
TypeArena internalTypes;
|
||||
|
||||
std::vector<std::pair<Location, ScopePtr>> scopes; // never empty
|
||||
std::unordered_map<const AstExpr*, TypeId> astTypes;
|
||||
std::unordered_map<const AstExpr*, TypeId> astExpectedTypes;
|
||||
std::unordered_map<const AstExpr*, TypeId> astOriginalCallTypes;
|
||||
std::unordered_map<const AstExpr*, TypeId> astOverloadResolvedTypes;
|
||||
std::unordered_map<Name, TypeId> declaredGlobals;
|
||||
ErrorVec errors;
|
||||
Mode mode;
|
||||
SourceCode::Type type;
|
||||
|
||||
ScopePtr getModuleScope() const;
|
||||
|
||||
// Once a module has been typechecked, we clone its public interface into a separate arena.
|
||||
// This helps us to force TypeVar ownership into a DAG rather than a DCG.
|
||||
// Returns true if there were any free types encountered in the public interface. This
|
||||
// indicates a bug in the type checker that we want to surface.
|
||||
bool clonePublicInterface();
|
||||
};
|
||||
|
||||
} // namespace Luau
|
79
Analysis/include/Luau/ModuleResolver.h
Normal file
79
Analysis/include/Luau/ModuleResolver.h
Normal file
|
@ -0,0 +1,79 @@
|
|||
// 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 ModuleInfo
|
||||
{
|
||||
ModuleName name;
|
||||
bool optional = false;
|
||||
};
|
||||
|
||||
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
|
120
Analysis/include/Luau/Predicate.h
Normal file
120
Analysis/include/Luau/Predicate.h
Normal file
|
@ -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/Variant.h"
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/Symbol.h"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct TypeVar;
|
||||
using TypeId = const TypeVar*;
|
||||
|
||||
struct Field;
|
||||
using LValue = Variant<Symbol, Field>;
|
||||
|
||||
struct Field
|
||||
{
|
||||
std::shared_ptr<LValue> parent; // TODO: Eventually use unique_ptr to enforce non-copyable trait.
|
||||
std::string key;
|
||||
};
|
||||
|
||||
std::optional<LValue> tryGetLValue(const class AstExpr& expr);
|
||||
|
||||
// Utility function: breaks down an LValue to get at the Symbol, and reverses the vector of keys.
|
||||
std::pair<Symbol, std::vector<std::string>> getFullName(const LValue& lvalue);
|
||||
|
||||
std::string toString(const LValue& lvalue);
|
||||
|
||||
template<typename T>
|
||||
const T* get(const LValue& lvalue)
|
||||
{
|
||||
return get_if<T>(&lvalue);
|
||||
}
|
||||
|
||||
// Key is a stringified encoding of an LValue.
|
||||
using RefinementMap = std::map<std::string, TypeId>;
|
||||
|
||||
void merge(RefinementMap& l, const RefinementMap& r, std::function<TypeId(TypeId, TypeId)> f);
|
||||
void addRefinement(RefinementMap& refis, const LValue& lvalue, TypeId ty);
|
||||
|
||||
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)
|
||||
: lhs(std::move(lhs))
|
||||
, rhs(std::move(rhs))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct OrPredicate
|
||||
{
|
||||
PredicateVec lhs;
|
||||
PredicateVec rhs;
|
||||
|
||||
OrPredicate(PredicateVec&& lhs, PredicateVec&& rhs)
|
||||
: lhs(std::move(lhs))
|
||||
, rhs(std::move(rhs))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct NotPredicate
|
||||
{
|
||||
PredicateVec predicates;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
const T* get(const Predicate& predicate)
|
||||
{
|
||||
return get_if<T>(&predicate);
|
||||
}
|
||||
|
||||
} // namespace Luau
|
39
Analysis/include/Luau/RecursionCounter.h
Normal file
39
Analysis/include/Luau/RecursionCounter.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
// 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 <stdexcept>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct RecursionCounter
|
||||
{
|
||||
RecursionCounter(int* count)
|
||||
: count(count)
|
||||
{
|
||||
++(*count);
|
||||
}
|
||||
|
||||
~RecursionCounter()
|
||||
{
|
||||
LUAU_ASSERT(*count > 0);
|
||||
--(*count);
|
||||
}
|
||||
|
||||
private:
|
||||
int* count;
|
||||
};
|
||||
|
||||
struct RecursionLimiter : RecursionCounter
|
||||
{
|
||||
RecursionLimiter(int* count, int limit)
|
||||
: RecursionCounter(count)
|
||||
{
|
||||
if (limit > 0 && *count > limit)
|
||||
throw std::runtime_error("Internal recursion counter limit exceeded");
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Luau
|
28
Analysis/include/Luau/RequireTracer.h
Normal file
28
Analysis/include/Luau/RequireTracer.h
Normal file
|
@ -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>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
class AstStat;
|
||||
class AstExpr;
|
||||
class AstStatBlock;
|
||||
struct AstLocal;
|
||||
|
||||
struct RequireTraceResult
|
||||
{
|
||||
DenseHashMap<const AstExpr*, ModuleName> exprs{0};
|
||||
DenseHashMap<const AstExpr*, bool> optional{0};
|
||||
|
||||
std::vector<std::pair<ModuleName, Location>> requires;
|
||||
};
|
||||
|
||||
RequireTraceResult traceRequires(FileResolver* fileResolver, AstStatBlock* root, ModuleName currentModuleName);
|
||||
|
||||
} // namespace Luau
|
208
Analysis/include/Luau/Substitution.h
Normal file
208
Analysis/include/Luau/Substitution.h
Normal file
|
@ -0,0 +1,208 @@
|
|||
// 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 "Luau/ModuleResolver.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeVar.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.
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauTrackOwningArena)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
enum class TarjanResult
|
||||
{
|
||||
TooManyChildren,
|
||||
Ok
|
||||
};
|
||||
|
||||
struct TarjanWorklistVertex
|
||||
{
|
||||
int index;
|
||||
int currEdge;
|
||||
int lastEdge;
|
||||
};
|
||||
|
||||
// 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<TypeId> indexToType;
|
||||
std::vector<TypePackId> indexToPack;
|
||||
|
||||
// Tarjan keeps a stack of vertices where we're still in the process
|
||||
// of finding their SCC.
|
||||
std::vector<int> stack;
|
||||
std::vector<bool> onStack;
|
||||
|
||||
// Tarjan calculates the lowlink for each vertex,
|
||||
// which is the lowest ancestor index reachable from the vertex.
|
||||
std::vector<int> lowlink;
|
||||
|
||||
int childCount = 0;
|
||||
|
||||
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();
|
||||
|
||||
// Clear the state
|
||||
void clear();
|
||||
|
||||
// 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);
|
||||
|
||||
// Visit the root vertex.
|
||||
TarjanResult visitRoot(TypeId ty);
|
||||
TarjanResult visitRoot(TypePackId ty);
|
||||
|
||||
// Each subclass gets called back once for each edge,
|
||||
// and once for each SCC.
|
||||
virtual void visitEdge(int index, int parentIndex) {}
|
||||
virtual 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;
|
||||
}
|
||||
};
|
||||
|
||||
// We use Tarjan to calculate dirty bits. We set `dirty[i]` true
|
||||
// if the vertex with index `i` can reach a dirty vertex.
|
||||
struct FindDirty : Tarjan
|
||||
{
|
||||
std::vector<bool> dirty;
|
||||
|
||||
// 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) override;
|
||||
void visitSCC(int index) override;
|
||||
|
||||
// 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 : FindDirty
|
||||
{
|
||||
ModulePtr currentModule;
|
||||
DenseHashMap<TypeId, TypeId> newTypes{nullptr};
|
||||
DenseHashMap<TypePackId, TypePackId> newPacks{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)
|
||||
{
|
||||
TypeId allocated = currentModule->internalTypes.typeVars.allocate(tv);
|
||||
if (FFlag::DebugLuauTrackOwningArena)
|
||||
asMutable(allocated)->owningArena = ¤tModule->internalTypes;
|
||||
|
||||
return allocated;
|
||||
}
|
||||
template<typename T>
|
||||
TypePackId addTypePack(const T& tp)
|
||||
{
|
||||
TypePackId allocated = currentModule->internalTypes.typePacks.allocate(tp);
|
||||
if (FFlag::DebugLuauTrackOwningArena)
|
||||
asMutable(allocated)->owningArena = ¤tModule->internalTypes;
|
||||
|
||||
return allocated;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Luau
|
95
Analysis/include/Luau/Symbol.h
Normal file
95
Analysis/include/Luau/Symbol.h
Normal file
|
@ -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
|
||||
{
|
||||
|
||||
// TODO Rename this to Name once the old type alias is gone.
|
||||
struct Symbol
|
||||
{
|
||||
Symbol()
|
||||
: local(nullptr)
|
||||
, global()
|
||||
{
|
||||
}
|
||||
|
||||
Symbol(AstLocal* local)
|
||||
: local(local)
|
||||
, global()
|
||||
{
|
||||
}
|
||||
|
||||
Symbol(const AstName& global)
|
||||
: local(nullptr)
|
||||
, global(global)
|
||||
{
|
||||
}
|
||||
|
||||
AstLocal* local;
|
||||
AstName global;
|
||||
|
||||
bool operator==(const Symbol& rhs) const
|
||||
{
|
||||
if (local)
|
||||
return local == rhs.local;
|
||||
if (global.value)
|
||||
return rhs.global.value && global == rhs.global.value; // Subtlety: AstName::operator==(const char*) uses strcmp, not pointer identity.
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
else
|
||||
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
|
72
Analysis/include/Luau/ToString.h
Normal file
72
Analysis/include/Luau/ToString.h
Normal file
|
@ -0,0 +1,72 @@
|
|||
// 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/TypeVar.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
LUAU_FASTINT(LuauTableTypeMaximumStringifierLength)
|
||||
LUAU_FASTINT(LuauTypeMaximumStringifierLength)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct ToStringNameMap
|
||||
{
|
||||
std::unordered_map<TypeId, std::string> typeVars;
|
||||
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 '{}'
|
||||
size_t maxTableLength = size_t(FInt::LuauTableTypeMaximumStringifierLength); // Only applied to TableTypeVars
|
||||
size_t maxTypeLength = size_t(FInt::LuauTypeMaximumStringifierLength);
|
||||
std::optional<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'
|
||||
};
|
||||
|
||||
struct ToStringResult
|
||||
{
|
||||
std::string name;
|
||||
ToStringNameMap nameMap;
|
||||
|
||||
bool invalid = false;
|
||||
bool error = false;
|
||||
bool cycle = false;
|
||||
bool truncated = false;
|
||||
};
|
||||
|
||||
ToStringResult toStringDetailed(TypeId ty, const ToStringOptions& opts = {});
|
||||
ToStringResult toStringDetailed(TypePackId ty, const ToStringOptions& opts = {});
|
||||
|
||||
std::string toString(TypeId ty, const ToStringOptions& opts);
|
||||
std::string toString(TypePackId ty, const ToStringOptions& 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 TypeVar& tv, const ToStringOptions& opts = {});
|
||||
std::string toString(const TypePackVar& tp, const ToStringOptions& opts = {});
|
||||
|
||||
// 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
|
||||
void dump(TypeId ty);
|
||||
void dump(TypePackId ty);
|
||||
|
||||
} // namespace Luau
|
18
Analysis/include/Luau/TopoSortStatements.h
Normal file
18
Analysis/include/Luau/TopoSortStatements.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
// 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 isFunction(const AstStat& stat);
|
||||
void toposort(std::vector<AstStat*>& stats);
|
||||
|
||||
} // namespace Luau
|
30
Analysis/include/Luau/Transpiler.h
Normal file
30
Analysis/include/Luau/Transpiler.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
// 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
|
||||
};
|
||||
|
||||
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{});
|
||||
|
||||
} // namespace Luau
|
46
Analysis/include/Luau/TxnLog.h
Normal file
46
Analysis/include/Luau/TxnLog.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/TypeVar.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
// Log of where what TypeIds we are rebinding and what they used to be
|
||||
struct TxnLog
|
||||
{
|
||||
TxnLog() = default;
|
||||
|
||||
explicit TxnLog(const std::vector<std::pair<TypeId, TypeId>>& seen)
|
||||
: seen(seen)
|
||||
{
|
||||
}
|
||||
|
||||
TxnLog(const TxnLog&) = delete;
|
||||
TxnLog& operator=(const TxnLog&) = delete;
|
||||
|
||||
TxnLog(TxnLog&&) = default;
|
||||
TxnLog& operator=(TxnLog&&) = default;
|
||||
|
||||
void operator()(TypeId a);
|
||||
void operator()(TypePackId a);
|
||||
void operator()(TableTypeVar* a);
|
||||
|
||||
void rollback();
|
||||
|
||||
void concat(TxnLog rhs);
|
||||
|
||||
bool haveSeen(TypeId lhs, TypeId rhs);
|
||||
void pushSeen(TypeId lhs, TypeId rhs);
|
||||
void popSeen(TypeId lhs, TypeId rhs);
|
||||
|
||||
private:
|
||||
std::vector<std::pair<TypeId, TypeVar>> typeVarChanges;
|
||||
std::vector<std::pair<TypePackId, TypePackVar>> typePackChanges;
|
||||
std::vector<std::pair<TableTypeVar*, std::optional<TypeId>>> tableChanges;
|
||||
|
||||
public:
|
||||
std::vector<std::pair<TypeId, TypeId>> seen; // used to avoid infinite recursion when types are cyclic
|
||||
};
|
||||
|
||||
} // namespace Luau
|
21
Analysis/include/Luau/TypeAttach.h
Normal file
21
Analysis/include/Luau/TypeAttach.h
Normal file
|
@ -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
|
453
Analysis/include/Luau/TypeInfer.h
Normal file
453
Analysis/include/Luau/TypeInfer.h
Normal file
|
@ -0,0 +1,453 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Predicate.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/Symbol.h"
|
||||
#include "Luau/Parser.h"
|
||||
#include "Luau/Substitution.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/Unifier.h"
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct Scope;
|
||||
struct TypeChecker;
|
||||
struct ModuleResolver;
|
||||
|
||||
using Name = std::string;
|
||||
using ScopePtr = std::shared_ptr<Scope>;
|
||||
using OverloadErrorEntry = std::tuple<std::vector<TypeError>, std::vector<TypeId>, const FunctionTypeVar*>;
|
||||
|
||||
bool doesCallError(const AstExprCall* call);
|
||||
bool hasBreak(AstStat* node);
|
||||
const AstStat* getFallthrough(const AstStat* node);
|
||||
|
||||
struct Unifier;
|
||||
|
||||
// A substitution which replaces generic types in a given set by free types.
|
||||
struct ReplaceGenerics : Substitution
|
||||
{
|
||||
TypeLevel level;
|
||||
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
|
||||
{
|
||||
TypeLevel level;
|
||||
ReplaceGenerics replaceGenerics;
|
||||
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 free types by generic types.
|
||||
struct Quantification : Substitution
|
||||
{
|
||||
TypeLevel level;
|
||||
std::vector<TypeId> generics;
|
||||
std::vector<TypePackId> genericPacks;
|
||||
bool isDirty(TypeId ty) override;
|
||||
bool isDirty(TypePackId tp) override;
|
||||
TypeId clean(TypeId ty) override;
|
||||
TypePackId clean(TypePackId tp) override;
|
||||
};
|
||||
|
||||
// A substitution which replaces free types by any
|
||||
struct Anyification : Substitution
|
||||
{
|
||||
TypeId anyType;
|
||||
TypePackId anyTypePack;
|
||||
bool isDirty(TypeId ty) override;
|
||||
bool isDirty(TypePackId tp) override;
|
||||
TypeId clean(TypeId ty) override;
|
||||
TypePackId clean(TypePackId tp) override;
|
||||
};
|
||||
|
||||
// A substitution which replaces the type parameters of a type function by arguments
|
||||
struct ApplyTypeFunction : Substitution
|
||||
{
|
||||
TypeLevel level;
|
||||
bool encounteredForwardedType;
|
||||
std::unordered_map<TypeId, TypeId> arguments;
|
||||
bool isDirty(TypeId ty) override;
|
||||
bool isDirty(TypePackId tp) override;
|
||||
TypeId clean(TypeId ty) override;
|
||||
TypePackId clean(TypePackId tp) override;
|
||||
};
|
||||
|
||||
// All TypeVars are retained via Environment::typeVars. All TypeIds
|
||||
// within a program are borrowed pointers into this set.
|
||||
struct TypeChecker
|
||||
{
|
||||
explicit TypeChecker(ModuleResolver* resolver, InternalErrorReporter* iceHandler);
|
||||
TypeChecker(const TypeChecker&) = delete;
|
||||
TypeChecker& operator=(const TypeChecker&) = delete;
|
||||
|
||||
ModulePtr check(const SourceModule& module, Mode mode, std::optional<ScopePtr> environmentScope = std::nullopt);
|
||||
|
||||
std::vector<std::pair<Location, ScopePtr>> getScopes() const;
|
||||
|
||||
void check(const ScopePtr& scope, const AstStat& statement);
|
||||
void check(const ScopePtr& scope, const AstStatBlock& statement);
|
||||
void check(const ScopePtr& scope, const AstStatIf& statement);
|
||||
void check(const ScopePtr& scope, const AstStatWhile& statement);
|
||||
void check(const ScopePtr& scope, const AstStatRepeat& statement);
|
||||
void check(const ScopePtr& scope, const AstStatReturn& return_);
|
||||
void check(const ScopePtr& scope, const AstStatAssign& assign);
|
||||
void check(const ScopePtr& scope, const AstStatCompoundAssign& assign);
|
||||
void check(const ScopePtr& scope, const AstStatLocal& local);
|
||||
void check(const ScopePtr& scope, const AstStatFor& local);
|
||||
void check(const ScopePtr& scope, const AstStatForIn& forin);
|
||||
void check(const ScopePtr& scope, TypeId ty, const ScopePtr& funScope, const AstStatFunction& function);
|
||||
void check(const ScopePtr& scope, TypeId ty, const ScopePtr& funScope, const AstStatLocalFunction& function);
|
||||
void check(const ScopePtr& scope, const AstStatTypeAlias& typealias, bool forwardDeclare = false);
|
||||
void check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass);
|
||||
void check(const ScopePtr& scope, const AstStatDeclareFunction& declaredFunction);
|
||||
|
||||
void checkBlock(const ScopePtr& scope, const AstStatBlock& statement);
|
||||
void checkBlockTypeAliases(const ScopePtr& scope, std::vector<AstStat*>& sorted);
|
||||
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExpr& expr, std::optional<TypeId> expectedType = std::nullopt);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprLocal& expr);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprGlobal& expr);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprVarargs& expr);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprCall& expr);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprIndexName& expr);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprIndexExpr& expr);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprFunction& expr, std::optional<TypeId> expectedType = std::nullopt);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprTable& expr, std::optional<TypeId> expectedType = std::nullopt);
|
||||
ExprResult<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 = {});
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprBinary& expr);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprTypeAssertion& expr);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprError& expr);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprIfElse& 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);
|
||||
|
||||
// Returns both the type of the lvalue and its binding (if the caller wants to mutate the binding).
|
||||
// Note: the binding may be null.
|
||||
std::pair<TypeId, TypeId*> checkLValueBinding(const ScopePtr& scope, const AstExpr& expr);
|
||||
std::pair<TypeId, TypeId*> checkLValueBinding(const ScopePtr& scope, const AstExprLocal& expr);
|
||||
std::pair<TypeId, TypeId*> checkLValueBinding(const ScopePtr& scope, const AstExprGlobal& expr);
|
||||
std::pair<TypeId, TypeId*> checkLValueBinding(const ScopePtr& scope, const AstExprIndexName& expr);
|
||||
std::pair<TypeId, TypeId*> checkLValueBinding(const ScopePtr& scope, const AstExprIndexExpr& expr);
|
||||
|
||||
TypeId checkFunctionName(const ScopePtr& scope, AstExpr& funName);
|
||||
std::pair<TypeId, ScopePtr> checkFunctionSignature(const ScopePtr& scope, int subLevel, const AstExprFunction& expr,
|
||||
std::optional<Location> originalNameLoc, std::optional<TypeId> expectedType);
|
||||
void checkFunctionBody(const ScopePtr& scope, TypeId type, const AstExprFunction& function);
|
||||
|
||||
void checkArgumentList(
|
||||
const ScopePtr& scope, Unifier& state, TypePackId paramPack, TypePackId argPack, const std::vector<Location>& argLocations);
|
||||
|
||||
ExprResult<TypePackId> checkExprPack(const ScopePtr& scope, const AstExpr& expr);
|
||||
ExprResult<TypePackId> checkExprPack(const ScopePtr& scope, const AstExprCall& expr);
|
||||
std::vector<std::optional<TypeId>> getExpectedTypesForCall(const std::vector<TypeId>& overloads, size_t argumentCount, bool selfCall);
|
||||
std::optional<ExprResult<TypePackId>> checkCallOverload(const ScopePtr& scope, const AstExprCall& expr, TypeId fn, TypePackId retPack,
|
||||
TypePackId argPack, TypePack* args, const std::vector<Location>& argLocations, const ExprResult<TypePackId>& argListResult,
|
||||
std::vector<TypeId>& overloadsThatMatchArgCount, std::vector<OverloadErrorEntry>& errors);
|
||||
bool handleSelfCallMismatch(const ScopePtr& scope, const AstExprCall& expr, TypePack* args, const std::vector<Location>& argLocations,
|
||||
const std::vector<OverloadErrorEntry>& errors);
|
||||
ExprResult<TypePackId> 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,
|
||||
const std::vector<OverloadErrorEntry>& errors);
|
||||
|
||||
ExprResult<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 typevars need to be replaced by any.
|
||||
*/
|
||||
TypeId anyIfNonstrict(TypeId ty) const;
|
||||
|
||||
/** Attempt to unify the types left and right. Treat any failures as type errors
|
||||
* in the final typecheck report.
|
||||
*/
|
||||
bool unify(TypeId left, TypeId right, const Location& location);
|
||||
bool unify(TypePackId left, TypePackId right, const Location& location, CountMismatch::Context ctx = CountMismatch::Context::Arg);
|
||||
|
||||
/** Attempt to unify the types left and right.
|
||||
* If this fails, and the right type can be instantiated, do so and try unification again.
|
||||
*/
|
||||
bool unifyWithInstantiationIfNeeded(const ScopePtr& scope, TypeId left, TypeId right, const Location& location);
|
||||
void unifyWithInstantiationIfNeeded(const ScopePtr& scope, TypeId left, TypeId right, Unifier& state);
|
||||
|
||||
/** Attempt to unify left with right.
|
||||
* If there are errors, undo everything and return the errors.
|
||||
* If there are no errors, commit and return an empty error vector.
|
||||
*/
|
||||
ErrorVec tryUnify(TypeId left, TypeId right, const Location& location);
|
||||
ErrorVec tryUnify(TypePackId left, TypePackId right, const Location& location);
|
||||
|
||||
// Test whether the two type vars unify. Never commits the result.
|
||||
ErrorVec canUnify(TypeId superTy, TypeId subTy, const Location& location);
|
||||
ErrorVec canUnify(TypePackId superTy, TypePackId subTy, const Location& location);
|
||||
|
||||
// Variant that takes a preexisting 'seen' set. We need this in certain cases to avoid infinitely recursing
|
||||
// into cyclic types.
|
||||
ErrorVec canUnify(const std::vector<std::pair<TypeId, TypeId>>& seen, TypeId left, TypeId right, const Location& location);
|
||||
|
||||
std::optional<TypeId> findMetatableEntry(TypeId type, std::string entry, const Location& location);
|
||||
std::optional<TypeId> findTablePropertyRespectingMeta(TypeId lhsType, Name name, const Location& location);
|
||||
|
||||
std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, TypeId type, const Name& name, const Location& location, bool addErrors);
|
||||
|
||||
// Reduces the union to its simplest possible shape.
|
||||
// (A | B) | B | C yields A | B | C
|
||||
std::vector<TypeId> reduceUnion(const std::vector<TypeId>& types);
|
||||
|
||||
std::optional<TypeId> tryStripUnionFromNil(TypeId ty);
|
||||
TypeId stripFromNilAndReport(TypeId ty, const Location& location);
|
||||
|
||||
template<typename Id>
|
||||
ErrorVec tryUnify_(Id left, Id right, const Location& location);
|
||||
|
||||
template<typename Id>
|
||||
ErrorVec canUnify_(Id left, Id right, 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);
|
||||
// Removed by FFlag::LuauRankNTypes
|
||||
TypePackId DEPRECATED_instantiate(const ScopePtr& scope, TypePackId ty, Location location);
|
||||
|
||||
// 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);
|
||||
|
||||
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);
|
||||
|
||||
ScopePtr childFunctionScope(const ScopePtr& parent, const Location& location, int subLevel = 0);
|
||||
ScopePtr childScope(const ScopePtr& parent, const Location& location, int subLevel = 0);
|
||||
|
||||
// Wrapper for merge(l, r, toUnion) but without the lambda junk.
|
||||
void merge(RefinementMap& l, const RefinementMap& r);
|
||||
|
||||
private:
|
||||
void prepareErrorsForDisplay(ErrorVec& errVec);
|
||||
void diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData& data);
|
||||
void reportErrorCodeTooComplex(const Location& location);
|
||||
|
||||
private:
|
||||
Unifier mkUnifier(const Location& location);
|
||||
Unifier mkUnifier(const std::vector<std::pair<TypeId, TypeId>>& seen, 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);
|
||||
TypeId DEPRECATED_freshType(const ScopePtr& scope, bool canBeGeneric = false);
|
||||
TypeId DEPRECATED_freshType(TypeLevel level, bool canBeGeneric = false);
|
||||
|
||||
// Returns nullopt if the predicate filters down the TypeId to 0 options.
|
||||
std::optional<TypeId> filterMap(TypeId type, TypeIdPredicate predicate);
|
||||
|
||||
TypeId unionOfTypes(TypeId a, TypeId b, const Location& location, bool unifyFreeTypes = true);
|
||||
|
||||
// ex
|
||||
// TypeId id = addType(FreeTypeVar());
|
||||
template<typename T>
|
||||
TypeId addType(const T& tv)
|
||||
{
|
||||
return addTV(TypeVar(tv));
|
||||
}
|
||||
|
||||
TypeId addType(const UnionTypeVar& utv);
|
||||
|
||||
TypeId addTV(TypeVar&& 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);
|
||||
TypePackId DEPRECATED_freshTypePack(const ScopePtr& scope, bool canBeGeneric = false);
|
||||
TypePackId DEPRECATED_freshTypePack(TypeLevel level, bool canBeGeneric = false);
|
||||
|
||||
TypeId resolveType(const ScopePtr& scope, const AstType& annotation, bool canBeGeneric = false);
|
||||
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 Location& location);
|
||||
|
||||
// Note: `scope` must be a fresh scope.
|
||||
std::pair<std::vector<TypeId>, std::vector<TypePackId>> createGenericTypes(
|
||||
const ScopePtr& scope, const AstNode& node, const AstArray<AstName>& genericNames, const AstArray<AstName>& genericPackNames);
|
||||
|
||||
public:
|
||||
ErrorVec resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense);
|
||||
|
||||
private:
|
||||
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, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr = false);
|
||||
void resolve(const Predicate& predicate, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr);
|
||||
void resolve(const TruthyPredicate& truthyP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr);
|
||||
void resolve(const AndPredicate& andP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||
void resolve(const OrPredicate& orP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||
void resolve(const IsAPredicate& isaP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||
void resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||
void DEPRECATED_resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||
void resolve(const EqPredicate& eqP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||
|
||||
bool isNonstrictMode() 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 ErrorTypeVars.
|
||||
*/
|
||||
std::vector<TypeId> unTypePack(const ScopePtr& scope, TypePackId pack, size_t expectedLength, const Location& location);
|
||||
|
||||
TypeArena globalTypes;
|
||||
|
||||
ModuleResolver* resolver;
|
||||
SourceModule globalNames; // names for symbols entered into globalScope
|
||||
ScopePtr globalScope; // shared by all modules
|
||||
ModulePtr currentModule;
|
||||
ModuleName currentModuleName;
|
||||
|
||||
Instantiation instantiation;
|
||||
Quantification quantification;
|
||||
Anyification anyification;
|
||||
ApplyTypeFunction applyTypeFunction;
|
||||
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope;
|
||||
InternalErrorReporter* iceHandler;
|
||||
|
||||
public:
|
||||
const TypeId nilType;
|
||||
const TypeId numberType;
|
||||
const TypeId stringType;
|
||||
const TypeId booleanType;
|
||||
const TypeId threadType;
|
||||
const TypeId anyType;
|
||||
|
||||
const TypeId errorType;
|
||||
const TypeId optionalNumberType;
|
||||
|
||||
const TypePackId anyTypePack;
|
||||
const TypePackId errorTypePack;
|
||||
|
||||
private:
|
||||
int checkRecursionCount = 0;
|
||||
int recursionCount = 0;
|
||||
};
|
||||
|
||||
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
|
||||
std::unordered_map<Symbol, Binding> bindings;
|
||||
TypePackId returnType;
|
||||
bool breakOk = false;
|
||||
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, std::unordered_map<Name, TypeFun>> importedTypeBindings;
|
||||
|
||||
std::optional<TypeId> lookup(const Symbol& name);
|
||||
|
||||
std::optional<TypeFun> lookupType(const Name& name);
|
||||
std::optional<TypeFun> lookupImportedType(const Name& moduleAlias, const Name& name);
|
||||
|
||||
std::unordered_map<Name, TypePackId> privateTypePackBindings;
|
||||
std::optional<TypePackId> lookupPack(const Name& name);
|
||||
|
||||
// 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);
|
||||
|
||||
RefinementMap refinements;
|
||||
|
||||
// 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> typeAliasParameters;
|
||||
};
|
||||
|
||||
// Unit test hook
|
||||
void setPrintLine(void (*pl)(const std::string& s));
|
||||
void resetPrintLine();
|
||||
|
||||
} // namespace Luau
|
161
Analysis/include/Luau/TypePack.h
Normal file
161
Analysis/include/Luau/TypePack.h
Normal file
|
@ -0,0 +1,161 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/Unifiable.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <optional>
|
||||
#include <set>
|
||||
|
||||
LUAU_FASTFLAG(LuauAddMissingFollow)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct TypeArena;
|
||||
|
||||
struct TypePack;
|
||||
struct VariadicTypePack;
|
||||
|
||||
struct TypePackVar;
|
||||
|
||||
using TypePackId = const TypePackVar*;
|
||||
using FreeTypePack = Unifiable::Free;
|
||||
using BoundTypePack = Unifiable::Bound<TypePackId>;
|
||||
using GenericTypePack = Unifiable::Generic;
|
||||
using TypePackVariant = Unifiable::Variant<TypePackId, TypePack, VariadicTypePack>;
|
||||
|
||||
/* 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;
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
TypePackVariant ty;
|
||||
bool persistent = false;
|
||||
|
||||
// Pointer to the type arena that allocated this type.
|
||||
// Do not depend on the value of this under any circumstances. This is for
|
||||
// debugging purposes only. This is only set in debug builds; it is nullptr
|
||||
// in all other environments.
|
||||
TypeArena* owningArena = nullptr;
|
||||
};
|
||||
|
||||
/* Walk the set of TypeIds in a TypePack.
|
||||
*
|
||||
* Like TypeVars, 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& 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;
|
||||
};
|
||||
|
||||
TypePackIterator begin(TypePackId tp);
|
||||
TypePackIterator end(TypePackId tp);
|
||||
|
||||
using SeenSet = std::set<std::pair<void*, void*>>;
|
||||
|
||||
bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs);
|
||||
|
||||
TypePackId follow(TypePackId tp);
|
||||
|
||||
size_t size(const TypePackId tp);
|
||||
size_t size(const TypePack& tp);
|
||||
std::optional<TypeId> first(TypePackId tp);
|
||||
|
||||
TypePackVar* asMutable(TypePackId tp);
|
||||
TypePack* asMutable(const TypePack* tp);
|
||||
|
||||
template<typename T>
|
||||
const T* get(TypePackId tp)
|
||||
{
|
||||
if (FFlag::LuauAddMissingFollow)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (FFlag::LuauAddMissingFollow)
|
||||
{
|
||||
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);
|
||||
|
||||
} // namespace Luau
|
19
Analysis/include/Luau/TypeUtils.h
Normal file
19
Analysis/include/Luau/TypeUtils.h
Normal file
|
@ -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 "Luau/Error.h"
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
using ScopePtr = std::shared_ptr<struct Scope>;
|
||||
|
||||
std::optional<TypeId> findMetatableEntry(ErrorVec& errors, const ScopePtr& globalScope, TypeId type, std::string entry, Location location);
|
||||
std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, const ScopePtr& globalScope, TypeId ty, Name name, Location location);
|
||||
|
||||
} // namespace Luau
|
531
Analysis/include/Luau/TypeVar.h
Normal file
531
Analysis/include/Luau/TypeVar.h
Normal file
|
@ -0,0 +1,531 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Predicate.h"
|
||||
#include "Luau/Unifiable.h"
|
||||
#include "Luau/Variant.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
LUAU_FASTINT(LuauTableTypeMaximumStringifierLength)
|
||||
LUAU_FASTINT(LuauTypeMaximumStringifierLength)
|
||||
LUAU_FASTFLAG(LuauAddMissingFollow)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct TypeArena;
|
||||
|
||||
/**
|
||||
* There are three kinds of type variables:
|
||||
* - `Free` variables are metavariables, which stand for unconstrained types.
|
||||
* - `Bound` variables are metavariables that have an equality constraint.
|
||||
* - `Generic` variables are type variables that are bound by generic functions.
|
||||
*
|
||||
* For example, consider the program:
|
||||
* ```
|
||||
* function(x, y) x.f = y end
|
||||
* ```
|
||||
* To typecheck this, we first introduce free metavariables for the types of `x` and `y`:
|
||||
* ```
|
||||
* function(x: X, y: Y) x.f = y end
|
||||
* ```
|
||||
* Type inference for the function body then produces the constraint:
|
||||
* ```
|
||||
* X = { f: Y }
|
||||
* ```
|
||||
* so `X` is now a bound metavariable. We can then quantify the metavariables,
|
||||
* which replaces any bound metavariables by their binding, and free metavariables
|
||||
* by bound generic variables:
|
||||
* ```
|
||||
* function<a>(x: { f: a }, y: a) x.f = y end
|
||||
* ```
|
||||
*/
|
||||
|
||||
// So... why `const T*` here rather than `T*`?
|
||||
// It's because we've had problems caused by the type graph being mutated
|
||||
// in ways it shouldn't be, for example mutating types from other modules.
|
||||
// To try to control this, we make the use of types immutable by default,
|
||||
// then provide explicit mutable access via getMutable and asMutable.
|
||||
// This means we can grep for all the places we're mutating the type graph,
|
||||
// and it makes it possible to provide other APIs (e.g. the txn log)
|
||||
// which control mutable access to the type graph.
|
||||
struct TypePackVar;
|
||||
using TypePackId = const TypePackVar*;
|
||||
|
||||
// TODO: rename to Type? CLI-39100
|
||||
struct TypeVar;
|
||||
|
||||
// Should never be null
|
||||
using TypeId = const TypeVar*;
|
||||
|
||||
using Name = std::string;
|
||||
|
||||
// A free type var is one whose exact shape has yet to be fully determined.
|
||||
using FreeTypeVar = Unifiable::Free;
|
||||
|
||||
// When a free type var is unified with any other, it is then "bound"
|
||||
// to that type var, indicating that the two types are actually the same type.
|
||||
using BoundTypeVar = Unifiable::Bound<TypeId>;
|
||||
|
||||
using GenericTypeVar = Unifiable::Generic;
|
||||
|
||||
using Tags = std::vector<std::string>;
|
||||
|
||||
using ModuleName = std::string;
|
||||
|
||||
struct PrimitiveTypeVar
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
NilType, // ObjC #defines Nil :(
|
||||
Boolean,
|
||||
Number,
|
||||
String,
|
||||
Thread,
|
||||
};
|
||||
|
||||
Type type;
|
||||
std::optional<TypeId> metatable; // string has a metatable
|
||||
|
||||
explicit PrimitiveTypeVar(Type type)
|
||||
: type(type)
|
||||
{
|
||||
}
|
||||
|
||||
explicit PrimitiveTypeVar(Type type, TypeId metatable)
|
||||
: type(type)
|
||||
, metatable(metatable)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct FunctionArgument
|
||||
{
|
||||
Name name;
|
||||
Location location;
|
||||
};
|
||||
|
||||
struct FunctionDefinition
|
||||
{
|
||||
std::optional<ModuleName> definitionModuleName;
|
||||
Location definitionLocation;
|
||||
std::optional<Location> varargLocation;
|
||||
Location originalNameLocation;
|
||||
};
|
||||
|
||||
// TODO: Come up with a better name.
|
||||
// TODO: Do we actually need this? We'll find out later if we can delete this.
|
||||
// Does not exactly belong in TypeVar.h, but this is the only way to appease the compiler.
|
||||
template<typename T>
|
||||
struct ExprResult
|
||||
{
|
||||
T type;
|
||||
PredicateVec predicates;
|
||||
};
|
||||
|
||||
using MagicFunction = std::function<std::optional<ExprResult<TypePackId>>(
|
||||
struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, ExprResult<TypePackId>)>;
|
||||
|
||||
struct FunctionTypeVar
|
||||
{
|
||||
// Global monomorphic function
|
||||
FunctionTypeVar(TypePackId argTypes, TypePackId retType, std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
||||
|
||||
// Global polymorphic function
|
||||
FunctionTypeVar(std::vector<TypeId> generics, std::vector<TypePackId> genericPacks, TypePackId argTypes, TypePackId retType,
|
||||
std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
||||
|
||||
// Local monomorphic function
|
||||
FunctionTypeVar(TypeLevel level, TypePackId argTypes, TypePackId retType, std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
||||
|
||||
// Local polymorphic function
|
||||
FunctionTypeVar(TypeLevel level, std::vector<TypeId> generics, std::vector<TypePackId> genericPacks, TypePackId argTypes, TypePackId retType,
|
||||
std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
||||
|
||||
TypeLevel level;
|
||||
/// These should all be generic
|
||||
std::vector<TypeId> generics;
|
||||
std::vector<TypePackId> genericPacks;
|
||||
TypePackId argTypes;
|
||||
std::vector<std::optional<FunctionArgument>> argNames;
|
||||
TypePackId retType;
|
||||
std::optional<FunctionDefinition> definition;
|
||||
MagicFunction magicFunction = nullptr; // Function pointer, can be nullptr.
|
||||
bool hasSelf;
|
||||
Tags tags;
|
||||
};
|
||||
|
||||
enum class TableState
|
||||
{
|
||||
// Sealed tables have an exact, known shape
|
||||
Sealed,
|
||||
|
||||
// An unsealed table can have extra properties added to it
|
||||
Unsealed,
|
||||
|
||||
// Tables which are not yet fully understood. We are still in the process of learning its shape.
|
||||
Free,
|
||||
|
||||
// A table which is a generic parameter to a function. We know that certain properties are required,
|
||||
// but we don't care about the full shape.
|
||||
Generic,
|
||||
};
|
||||
|
||||
struct TableIndexer
|
||||
{
|
||||
TableIndexer(TypeId indexType, TypeId indexResultType)
|
||||
: indexType(indexType)
|
||||
, indexResultType(indexResultType)
|
||||
{
|
||||
}
|
||||
|
||||
TypeId indexType;
|
||||
TypeId indexResultType;
|
||||
};
|
||||
|
||||
struct Property
|
||||
{
|
||||
TypeId type;
|
||||
bool deprecated = false;
|
||||
std::string deprecatedSuggestion;
|
||||
std::optional<Location> location = std::nullopt;
|
||||
Tags tags;
|
||||
std::optional<std::string> documentationSymbol;
|
||||
};
|
||||
|
||||
struct TableTypeVar
|
||||
{
|
||||
// We choose std::map over unordered_map here just because we have unit tests that compare
|
||||
// textual outputs. I don't want to spend the effort making them resilient in the case where
|
||||
// random events cause the iteration order of the map elements to change.
|
||||
// If this shows up in a profile, we can revisit it.
|
||||
using Props = std::map<Name, Property>;
|
||||
|
||||
TableTypeVar() = default;
|
||||
explicit TableTypeVar(TableState state, TypeLevel level);
|
||||
TableTypeVar(const Props& props, const std::optional<TableIndexer>& indexer, TypeLevel level, TableState state = TableState::Unsealed);
|
||||
|
||||
Props props;
|
||||
std::optional<TableIndexer> indexer;
|
||||
|
||||
TableState state = TableState::Unsealed;
|
||||
TypeLevel level;
|
||||
std::optional<std::string> name;
|
||||
|
||||
// Sometimes we throw a type on a name to make for nicer error messages, but without creating any entry in the type namespace
|
||||
// We need to know which is which when we stringify types.
|
||||
std::optional<std::string> syntheticName;
|
||||
|
||||
std::map<Name, Location> methodDefinitionLocations;
|
||||
std::vector<TypeId> instantiatedTypeParams;
|
||||
ModuleName definitionModuleName;
|
||||
|
||||
std::optional<TypeId> boundTo;
|
||||
Tags tags;
|
||||
};
|
||||
|
||||
// Represents a metatable attached to a table typevar. Somewhat analogous to a bound typevar.
|
||||
struct MetatableTypeVar
|
||||
{
|
||||
// Always points to a TableTypeVar.
|
||||
TypeId table;
|
||||
// Always points to either a TableTypeVar or a MetatableTypeVar.
|
||||
TypeId metatable;
|
||||
|
||||
std::optional<std::string> syntheticName;
|
||||
};
|
||||
|
||||
// Custom userdata of a class type
|
||||
struct ClassUserData
|
||||
{
|
||||
virtual ~ClassUserData() {}
|
||||
};
|
||||
|
||||
/** The type of a class.
|
||||
*
|
||||
* Classes behave like tables in many ways, but there are some important differences:
|
||||
*
|
||||
* The properties of a class are always exactly known.
|
||||
* Classes optionally have a parent class.
|
||||
* Two different classes that share the same properties are nevertheless distinct and mutually incompatible.
|
||||
*/
|
||||
struct ClassTypeVar
|
||||
{
|
||||
using Props = TableTypeVar::Props;
|
||||
|
||||
Name name;
|
||||
Props props;
|
||||
std::optional<TypeId> parent;
|
||||
std::optional<TypeId> metatable; // metaclass?
|
||||
Tags tags;
|
||||
std::shared_ptr<ClassUserData> userData;
|
||||
|
||||
ClassTypeVar(
|
||||
Name name, Props props, std::optional<TypeId> parent, std::optional<TypeId> metatable, Tags tags, std::shared_ptr<ClassUserData> userData)
|
||||
: name(name)
|
||||
, props(props)
|
||||
, parent(parent)
|
||||
, metatable(metatable)
|
||||
, tags(tags)
|
||||
, userData(userData)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct TypeFun
|
||||
{
|
||||
/// These should all be generic
|
||||
std::vector<TypeId> typeParams;
|
||||
|
||||
/** The underlying type.
|
||||
*
|
||||
* WARNING! This is not safe to use as a type if typeParams is not empty!!
|
||||
* You must first use TypeChecker::instantiateTypeFun to turn it into a real type.
|
||||
*/
|
||||
TypeId type;
|
||||
};
|
||||
|
||||
// Anything! All static checking is off.
|
||||
struct AnyTypeVar
|
||||
{
|
||||
};
|
||||
|
||||
struct UnionTypeVar
|
||||
{
|
||||
std::vector<TypeId> options;
|
||||
};
|
||||
|
||||
struct IntersectionTypeVar
|
||||
{
|
||||
std::vector<TypeId> parts;
|
||||
};
|
||||
|
||||
struct LazyTypeVar
|
||||
{
|
||||
std::function<TypeId()> thunk;
|
||||
};
|
||||
|
||||
using ErrorTypeVar = Unifiable::Error;
|
||||
|
||||
using TypeVariant = Unifiable::Variant<TypeId, PrimitiveTypeVar, FunctionTypeVar, TableTypeVar, MetatableTypeVar, ClassTypeVar, AnyTypeVar,
|
||||
UnionTypeVar, IntersectionTypeVar, LazyTypeVar>;
|
||||
|
||||
struct TypeVar final
|
||||
{
|
||||
explicit TypeVar(const TypeVariant& ty)
|
||||
: ty(ty)
|
||||
{
|
||||
}
|
||||
|
||||
explicit TypeVar(TypeVariant&& ty)
|
||||
: ty(std::move(ty))
|
||||
{
|
||||
}
|
||||
|
||||
TypeVar(const TypeVariant& ty, bool persistent)
|
||||
: ty(ty)
|
||||
, persistent(persistent)
|
||||
{
|
||||
}
|
||||
|
||||
TypeVariant ty;
|
||||
|
||||
// Kludge: A persistent TypeVar is one that belongs to the global scope.
|
||||
// Global type bindings are immutable but are reused many times.
|
||||
// Persistent TypeVars do not get cloned.
|
||||
bool persistent = false;
|
||||
|
||||
std::optional<std::string> documentationSymbol;
|
||||
|
||||
// Pointer to the type arena that allocated this type.
|
||||
// Do not depend on the value of this under any circumstances. This is for
|
||||
// debugging purposes only. This is only set in debug builds; it is nullptr
|
||||
// in all other environments.
|
||||
TypeArena* owningArena = nullptr;
|
||||
|
||||
bool operator==(const TypeVar& rhs) const;
|
||||
bool operator!=(const TypeVar& rhs) const;
|
||||
|
||||
TypeVar& operator=(const TypeVariant& rhs);
|
||||
TypeVar& operator=(TypeVariant&& rhs);
|
||||
};
|
||||
|
||||
using SeenSet = std::set<std::pair<void*, void*>>;
|
||||
bool areEqual(SeenSet& seen, const TypeVar& lhs, const TypeVar& rhs);
|
||||
|
||||
// Follow BoundTypeVars until we get to something real
|
||||
TypeId follow(TypeId t);
|
||||
|
||||
std::vector<TypeId> flattenIntersection(TypeId ty);
|
||||
|
||||
bool isPrim(TypeId ty, PrimitiveTypeVar::Type primType);
|
||||
bool isNil(TypeId ty);
|
||||
bool isBoolean(TypeId ty);
|
||||
bool isNumber(TypeId ty);
|
||||
bool isString(TypeId ty);
|
||||
bool isThread(TypeId ty);
|
||||
bool isOptional(TypeId ty);
|
||||
bool isTableIntersection(TypeId ty);
|
||||
bool isOverloadedFunction(TypeId ty);
|
||||
|
||||
std::optional<TypeId> getMetatable(TypeId type);
|
||||
TableTypeVar* getMutableTableType(TypeId type);
|
||||
const TableTypeVar* getTableType(TypeId type);
|
||||
|
||||
// If the type has a name, return that. Else if it has a synthetic name, return that.
|
||||
// Returns nullptr if the type has no name.
|
||||
const std::string* getName(TypeId type);
|
||||
|
||||
// Checks whether a union contains all types of another union.
|
||||
bool isSubset(const UnionTypeVar& super, const UnionTypeVar& sub);
|
||||
|
||||
// Checks if a type conains generic type binders
|
||||
bool isGeneric(const TypeId ty);
|
||||
|
||||
// Checks if a type may be instantiated to one containing generic type binders
|
||||
bool maybeGeneric(const TypeId ty);
|
||||
|
||||
struct SingletonTypes
|
||||
{
|
||||
const TypeId nilType = &nilType_;
|
||||
const TypeId numberType = &numberType_;
|
||||
const TypeId stringType = &stringType_;
|
||||
const TypeId booleanType = &booleanType_;
|
||||
const TypeId threadType = &threadType_;
|
||||
const TypeId anyType = &anyType_;
|
||||
const TypeId errorType = &errorType_;
|
||||
|
||||
SingletonTypes();
|
||||
SingletonTypes(const SingletonTypes&) = delete;
|
||||
void operator=(const SingletonTypes&) = delete;
|
||||
|
||||
private:
|
||||
std::unique_ptr<struct TypeArena> arena;
|
||||
TypeVar nilType_;
|
||||
TypeVar numberType_;
|
||||
TypeVar stringType_;
|
||||
TypeVar booleanType_;
|
||||
TypeVar threadType_;
|
||||
TypeVar anyType_;
|
||||
TypeVar errorType_;
|
||||
|
||||
TypeId makeStringMetatable();
|
||||
};
|
||||
|
||||
extern SingletonTypes singletonTypes;
|
||||
|
||||
void persist(TypeId ty);
|
||||
void persist(TypePackId tp);
|
||||
|
||||
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);
|
||||
|
||||
const TypeLevel* getLevel(TypeId ty);
|
||||
TypeLevel* getMutableLevel(TypeId ty);
|
||||
|
||||
const Property* lookupClassProp(const ClassTypeVar* cls, const Name& name);
|
||||
bool isSubclass(const ClassTypeVar* cls, const ClassTypeVar* parent);
|
||||
|
||||
bool hasGeneric(TypeId ty);
|
||||
bool hasGeneric(TypePackId tp);
|
||||
|
||||
TypeVar* asMutable(TypeId ty);
|
||||
|
||||
template<typename T>
|
||||
const T* get(TypeId tv)
|
||||
{
|
||||
if (FFlag::LuauAddMissingFollow)
|
||||
{
|
||||
LUAU_ASSERT(tv);
|
||||
|
||||
if constexpr (!std::is_same_v<T, BoundTypeVar>)
|
||||
LUAU_ASSERT(get_if<BoundTypeVar>(&tv->ty) == nullptr);
|
||||
}
|
||||
|
||||
return get_if<T>(&tv->ty);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* getMutable(TypeId tv)
|
||||
{
|
||||
if (FFlag::LuauAddMissingFollow)
|
||||
{
|
||||
LUAU_ASSERT(tv);
|
||||
|
||||
if constexpr (!std::is_same_v<T, BoundTypeVar>)
|
||||
LUAU_ASSERT(get_if<BoundTypeVar>(&tv->ty) == nullptr);
|
||||
}
|
||||
|
||||
return get_if<T>(&asMutable(tv)->ty);
|
||||
}
|
||||
|
||||
/* Traverses the UnionTypeVar yielding each TypeId.
|
||||
* If the iterator encounters a nested UnionTypeVar, it will instead yield each TypeId within.
|
||||
*
|
||||
* Beware: the iterator does not currently filter for unique TypeIds. This may change in the future.
|
||||
*/
|
||||
struct UnionTypeVarIterator
|
||||
{
|
||||
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;
|
||||
|
||||
explicit UnionTypeVarIterator(const UnionTypeVar* utv);
|
||||
|
||||
UnionTypeVarIterator& operator++();
|
||||
UnionTypeVarIterator operator++(int);
|
||||
bool operator!=(const UnionTypeVarIterator& rhs);
|
||||
bool operator==(const UnionTypeVarIterator& rhs);
|
||||
|
||||
const TypeId& operator*();
|
||||
|
||||
friend UnionTypeVarIterator end(const UnionTypeVar* utv);
|
||||
|
||||
private:
|
||||
UnionTypeVarIterator() = default;
|
||||
|
||||
// (UnionTypeVar* utv, size_t currentIndex)
|
||||
using SavedIterInfo = std::pair<const UnionTypeVar*, size_t>;
|
||||
|
||||
std::deque<SavedIterInfo> stack;
|
||||
std::unordered_set<const UnionTypeVar*> seen; // Only needed to protect the iterator from hanging the thread.
|
||||
|
||||
void advance();
|
||||
void descend();
|
||||
};
|
||||
|
||||
UnionTypeVarIterator begin(const UnionTypeVar* utv);
|
||||
UnionTypeVarIterator end(const UnionTypeVar* utv);
|
||||
|
||||
using TypeIdPredicate = std::function<std::optional<TypeId>(TypeId)>;
|
||||
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate);
|
||||
|
||||
// TEMP: Clip this prototype with FFlag::LuauStringMetatable
|
||||
std::optional<ExprResult<TypePackId>> magicFunctionFormat(
|
||||
struct TypeChecker& typechecker, const std::shared_ptr<struct Scope>& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult);
|
||||
|
||||
} // namespace Luau
|
134
Analysis/include/Luau/TypedAllocator.h
Normal file
134
Analysis/include/Luau/TypedAllocator.h
Normal file
|
@ -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 <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
void* pagedAllocate(size_t size);
|
||||
void pagedDeallocate(void* ptr);
|
||||
void pagedFreeze(void* ptr, size_t size);
|
||||
void pagedUnfreeze(void* ptr, size_t size);
|
||||
|
||||
template<typename T>
|
||||
class TypedAllocator
|
||||
{
|
||||
public:
|
||||
TypedAllocator()
|
||||
{
|
||||
appendBlock();
|
||||
}
|
||||
|
||||
~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.size() == 1 && currentBlockSize == 0;
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return kBlockSize * (stuff.size() - 1) + currentBlockSize;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
if (frozen)
|
||||
unfreeze();
|
||||
free();
|
||||
appendBlock();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
123
Analysis/include/Luau/Unifiable.h
Normal file
123
Analysis/include/Luau/Unifiable.h
Normal file
|
@ -0,0 +1,123 @@
|
|||
// 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
|
||||
{
|
||||
|
||||
/**
|
||||
* The 'level' of a TypeVar 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 typelevel "this" is "bigger" than 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;
|
||||
}
|
||||
|
||||
TypeLevel incr() const
|
||||
{
|
||||
TypeLevel result;
|
||||
result.level = level + 1;
|
||||
result.subLevel = 0;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
inline TypeLevel min(const TypeLevel& a, const TypeLevel& b)
|
||||
{
|
||||
if (a.subsumes(b))
|
||||
return a;
|
||||
else
|
||||
return b;
|
||||
}
|
||||
|
||||
namespace Unifiable
|
||||
{
|
||||
|
||||
using Name = std::string;
|
||||
|
||||
struct Free
|
||||
{
|
||||
explicit Free(TypeLevel level);
|
||||
Free(TypeLevel level, bool DEPRECATED_canBeGeneric);
|
||||
|
||||
int index;
|
||||
TypeLevel level;
|
||||
// Removed by FFlag::LuauRankNTypes
|
||||
bool DEPRECATED_canBeGeneric = false;
|
||||
// True if this free type variable is part of a mutually
|
||||
// recursive type alias whose definitions haven't been
|
||||
// resolved yet.
|
||||
bool forwardedTypeAlias = false;
|
||||
|
||||
private:
|
||||
static int nextIndex;
|
||||
};
|
||||
|
||||
template<typename Id>
|
||||
struct Bound
|
||||
{
|
||||
explicit Bound(Id boundTo)
|
||||
: boundTo(boundTo)
|
||||
{
|
||||
}
|
||||
|
||||
Id boundTo;
|
||||
};
|
||||
|
||||
struct Generic
|
||||
{
|
||||
// By default, generics are global, with a synthetic name
|
||||
Generic();
|
||||
explicit Generic(TypeLevel level);
|
||||
explicit Generic(const Name& name);
|
||||
Generic(TypeLevel level, const Name& name);
|
||||
|
||||
int index;
|
||||
TypeLevel level;
|
||||
Name name;
|
||||
bool explicitName;
|
||||
|
||||
private:
|
||||
static int nextIndex;
|
||||
};
|
||||
|
||||
struct Error
|
||||
{
|
||||
Error();
|
||||
|
||||
int index;
|
||||
|
||||
private:
|
||||
static int nextIndex;
|
||||
};
|
||||
|
||||
template<typename Id, typename... Value>
|
||||
using Variant = Variant<Free, Bound<Id>, Generic, Error, Value...>;
|
||||
|
||||
} // namespace Unifiable
|
||||
} // namespace Luau
|
98
Analysis/include/Luau/Unifier.h
Normal file
98
Analysis/include/Luau/Unifier.h
Normal file
|
@ -0,0 +1,98 @@
|
|||
// 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/TxnLog.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/Module.h" // FIXME: For TypeArena. It merits breaking out into its own header.
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
enum Variance
|
||||
{
|
||||
Covariant,
|
||||
Invariant
|
||||
};
|
||||
|
||||
struct UnifierCounters
|
||||
{
|
||||
int recursionCount = 0;
|
||||
int iterationCount = 0;
|
||||
};
|
||||
|
||||
struct Unifier
|
||||
{
|
||||
TypeArena* const types;
|
||||
Mode mode;
|
||||
ScopePtr globalScope; // sigh. Needed solely to get at string's metatable.
|
||||
|
||||
TxnLog log;
|
||||
ErrorVec errors;
|
||||
Location location;
|
||||
Variance variance = Covariant;
|
||||
CountMismatch::Context ctx = CountMismatch::Arg;
|
||||
|
||||
std::shared_ptr<UnifierCounters> counters;
|
||||
InternalErrorReporter* iceHandler;
|
||||
|
||||
Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const Location& location, Variance variance, InternalErrorReporter* iceHandler);
|
||||
Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const std::vector<std::pair<TypeId, TypeId>>& seen, const Location& location,
|
||||
Variance variance, InternalErrorReporter* iceHandler, const std::shared_ptr<UnifierCounters>& counters = nullptr);
|
||||
|
||||
// Test whether the two type vars unify. Never commits the result.
|
||||
ErrorVec canUnify(TypeId superTy, TypeId subTy);
|
||||
ErrorVec canUnify(TypePackId superTy, TypePackId subTy, bool isFunctionCall = false);
|
||||
|
||||
/** Attempt to unify left with right.
|
||||
* 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 superTy, TypeId subTy, bool isFunctionCall = false, bool isIntersection = false);
|
||||
|
||||
private:
|
||||
void tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall = false, bool isIntersection = false);
|
||||
void tryUnifyPrimitives(TypeId superTy, TypeId subTy);
|
||||
void tryUnifyFunctions(TypeId superTy, TypeId subTy, bool isFunctionCall = false);
|
||||
void tryUnifyTables(TypeId left, TypeId right, bool isIntersection = false);
|
||||
void tryUnifyFreeTable(TypeId free, TypeId other);
|
||||
void tryUnifySealedTables(TypeId left, TypeId right, bool isIntersection);
|
||||
void tryUnifyWithMetatable(TypeId metatable, TypeId other, bool reversed);
|
||||
void tryUnifyWithClass(TypeId superTy, TypeId subTy, bool reversed);
|
||||
void tryUnify(const TableIndexer& superIndexer, const TableIndexer& subIndexer);
|
||||
|
||||
public:
|
||||
void tryUnify(TypePackId superTy, TypePackId subTy, bool isFunctionCall = false);
|
||||
|
||||
private:
|
||||
void tryUnify_(TypePackId superTy, TypePackId subTy, bool isFunctionCall = false);
|
||||
void tryUnifyVariadics(TypePackId superTy, TypePackId subTy, bool reversed, int subOffset = 0);
|
||||
|
||||
void tryUnifyWithAny(TypeId any, TypeId ty);
|
||||
void tryUnifyWithAny(TypePackId any, TypePackId ty);
|
||||
|
||||
std::optional<TypeId> findTablePropertyRespectingMeta(TypeId lhsType, Name name);
|
||||
std::optional<TypeId> findMetatableEntry(TypeId type, std::string entry);
|
||||
|
||||
public:
|
||||
// Report an "infinite type error" if the type "needle" already occurs within "haystack"
|
||||
void occursCheck(TypeId needle, TypeId haystack);
|
||||
void occursCheck(std::unordered_set<TypeId>& seen, TypeId needle, TypeId haystack);
|
||||
void occursCheck(TypePackId needle, TypePackId haystack);
|
||||
void occursCheck(std::unordered_set<TypePackId>& seen, TypePackId needle, TypePackId haystack);
|
||||
|
||||
Unifier makeChildUnifier();
|
||||
|
||||
private:
|
||||
bool isNonstrictMode() const;
|
||||
|
||||
void checkChildUnifierTypeMismatch(const ErrorVec& innerErrors, TypeId wantedType, TypeId givenType);
|
||||
|
||||
[[noreturn]] void ice(const std::string& message, const Location& location);
|
||||
[[noreturn]] void ice(const std::string& message);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
302
Analysis/include/Luau/Variant.h
Normal file
302
Analysis/include/Luau/Variant.h
Normal file
|
@ -0,0 +1,302 @@
|
|||
// 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"
|
||||
|
||||
#ifndef LUAU_USE_STD_VARIANT
|
||||
#define LUAU_USE_STD_VARIANT 0
|
||||
#endif
|
||||
|
||||
#if LUAU_USE_STD_VARIANT
|
||||
#include <variant>
|
||||
#else
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
#include <initializer_list>
|
||||
#include <stddef.h>
|
||||
#endif
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
#if LUAU_USE_STD_VARIANT
|
||||
template<typename... Ts>
|
||||
using Variant = std::variant<Ts...>;
|
||||
|
||||
template<class Visitor, class Variant>
|
||||
auto visit(Visitor&& vis, Variant&& var)
|
||||
{
|
||||
// This change resolves the ABI issues with std::variant on libc++; std::visit normally throws bad_variant_access
|
||||
// but it requires an update to libc++.dylib which ships with macOS 10.14. To work around this, we assert on valueless
|
||||
// variants since we will never generate them and call into a libc++ function that doesn't throw.
|
||||
LUAU_ASSERT(!var.valueless_by_exception());
|
||||
|
||||
#ifdef __APPLE__
|
||||
// See https://stackoverflow.com/a/53868971/503215
|
||||
return std::__variant_detail::__visitation::__variant::__visit_value(vis, var);
|
||||
#else
|
||||
return std::visit(vis, var);
|
||||
#endif
|
||||
}
|
||||
|
||||
using std::get_if;
|
||||
#else
|
||||
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(value);
|
||||
}
|
||||
|
||||
Variant(const Variant& other)
|
||||
{
|
||||
typeId = other.typeId;
|
||||
tableCopy[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>
|
||||
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 FnCopy tableCopy[sizeof...(Ts)] = {&fnCopy<Ts>...};
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
template<class>
|
||||
inline constexpr bool always_false_v = false;
|
||||
|
||||
} // namespace Luau
|
200
Analysis/include/Luau/VisitTypeVar.h
Normal file
200
Analysis/include/Luau/VisitTypeVar.h
Normal file
|
@ -0,0 +1,200 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/TypePack.h"
|
||||
|
||||
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) TypeVar 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 void unsee(std::unordered_set<void*>& seen, const void* tv)
|
||||
{
|
||||
void* ttv = const_cast<void*>(tv);
|
||||
seen.erase(ttv);
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
void visit(TypePackId tp, F& f, std::unordered_set<void*>& seen);
|
||||
|
||||
template<typename F>
|
||||
void visit(TypeId ty, F& f, std::unordered_set<void*>& seen)
|
||||
{
|
||||
if (visit_detail::hasSeen(seen, ty))
|
||||
{
|
||||
f.cycle(ty);
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto btv = get<BoundTypeVar>(ty))
|
||||
{
|
||||
if (apply(ty, *btv, seen, f))
|
||||
visit(btv->boundTo, f, seen);
|
||||
}
|
||||
|
||||
else if (auto ftv = get<FreeTypeVar>(ty))
|
||||
apply(ty, *ftv, seen, f);
|
||||
|
||||
else if (auto gtv = get<GenericTypeVar>(ty))
|
||||
apply(ty, *gtv, seen, f);
|
||||
|
||||
else if (auto etv = get<ErrorTypeVar>(ty))
|
||||
apply(ty, *etv, seen, f);
|
||||
|
||||
else if (auto ptv = get<PrimitiveTypeVar>(ty))
|
||||
apply(ty, *ptv, seen, f);
|
||||
|
||||
else if (auto ftv = get<FunctionTypeVar>(ty))
|
||||
{
|
||||
if (apply(ty, *ftv, seen, f))
|
||||
{
|
||||
visit(ftv->argTypes, f, seen);
|
||||
visit(ftv->retType, f, seen);
|
||||
}
|
||||
}
|
||||
|
||||
else if (auto ttv = get<TableTypeVar>(ty))
|
||||
{
|
||||
if (apply(ty, *ttv, seen, f))
|
||||
{
|
||||
for (auto& [_name, prop] : ttv->props)
|
||||
visit(prop.type, f, seen);
|
||||
|
||||
if (ttv->indexer)
|
||||
{
|
||||
visit(ttv->indexer->indexType, f, seen);
|
||||
visit(ttv->indexer->indexResultType, f, seen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (auto mtv = get<MetatableTypeVar>(ty))
|
||||
{
|
||||
if (apply(ty, *mtv, seen, f))
|
||||
{
|
||||
visit(mtv->table, f, seen);
|
||||
visit(mtv->metatable, f, seen);
|
||||
}
|
||||
}
|
||||
|
||||
else if (auto ctv = get<ClassTypeVar>(ty))
|
||||
{
|
||||
if (apply(ty, *ctv, seen, f))
|
||||
{
|
||||
for (const auto& [name, prop] : ctv->props)
|
||||
visit(prop.type, f, seen);
|
||||
|
||||
if (ctv->parent)
|
||||
visit(*ctv->parent, f, seen);
|
||||
|
||||
if (ctv->metatable)
|
||||
visit(*ctv->metatable, f, seen);
|
||||
}
|
||||
}
|
||||
|
||||
else if (auto atv = get<AnyTypeVar>(ty))
|
||||
apply(ty, *atv, seen, f);
|
||||
|
||||
else if (auto utv = get<UnionTypeVar>(ty))
|
||||
{
|
||||
if (apply(ty, *utv, seen, f))
|
||||
{
|
||||
for (TypeId optTy : utv->options)
|
||||
visit(optTy, f, seen);
|
||||
}
|
||||
}
|
||||
|
||||
else if (auto itv = get<IntersectionTypeVar>(ty))
|
||||
{
|
||||
if (apply(ty, *itv, seen, f))
|
||||
{
|
||||
for (TypeId partTy : itv->parts)
|
||||
visit(partTy, f, seen);
|
||||
}
|
||||
}
|
||||
|
||||
visit_detail::unsee(seen, ty);
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
void visit(TypePackId tp, F& f, std::unordered_set<void*>& seen)
|
||||
{
|
||||
if (visit_detail::hasSeen(seen, tp))
|
||||
{
|
||||
f.cycle(tp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto btv = get<BoundTypePack>(tp))
|
||||
{
|
||||
if (apply(tp, *btv, seen, f))
|
||||
visit(btv->boundTo, f, seen);
|
||||
}
|
||||
|
||||
else if (auto ftv = get<Unifiable::Free>(tp))
|
||||
apply(tp, *ftv, seen, f);
|
||||
|
||||
else if (auto gtv = get<Unifiable::Generic>(tp))
|
||||
apply(tp, *gtv, seen, f);
|
||||
|
||||
else if (auto etv = get<Unifiable::Error>(tp))
|
||||
apply(tp, *etv, seen, f);
|
||||
|
||||
else if (auto pack = get<TypePack>(tp))
|
||||
{
|
||||
apply(tp, *pack, seen, f);
|
||||
|
||||
for (TypeId ty : pack->head)
|
||||
visit(ty, f, seen);
|
||||
|
||||
if (pack->tail)
|
||||
visit(*pack->tail, f, seen);
|
||||
}
|
||||
else if (auto pack = get<VariadicTypePack>(tp))
|
||||
{
|
||||
apply(tp, *pack, seen, f);
|
||||
visit(pack->ty, f, seen);
|
||||
}
|
||||
|
||||
visit_detail::unsee(seen, tp);
|
||||
}
|
||||
} // namespace visit_detail
|
||||
|
||||
template<typename TID, typename F>
|
||||
void visitTypeVar(TID ty, F& f, std::unordered_set<void*>& seen)
|
||||
{
|
||||
visit_detail::visit(ty, f, seen);
|
||||
}
|
||||
|
||||
template<typename TID, typename F>
|
||||
void visitTypeVar(TID ty, F& f)
|
||||
{
|
||||
std::unordered_set<void*> seen;
|
||||
visit_detail::visit(ty, f, seen);
|
||||
}
|
||||
|
||||
} // namespace Luau
|
411
Analysis/src/AstQuery.cpp
Normal file
411
Analysis/src/AstQuery.cpp
Normal file
|
@ -0,0 +1,411 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/AstQuery.h"
|
||||
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/ToString.h"
|
||||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct FindNode : public AstVisitor
|
||||
{
|
||||
const Position pos;
|
||||
const Position documentEnd;
|
||||
AstNode* best = nullptr;
|
||||
|
||||
explicit FindNode(Position pos, Position documentEnd)
|
||||
: pos(pos)
|
||||
, documentEnd(documentEnd)
|
||||
{
|
||||
}
|
||||
|
||||
bool visit(AstNode* node) override
|
||||
{
|
||||
if (node->location.contains(pos))
|
||||
{
|
||||
best = node;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Edge case: If we ask for the node at the position that is the very end of the document
|
||||
// return the innermost AST element that ends at that position.
|
||||
|
||||
if (node->location.end == documentEnd && pos >= documentEnd)
|
||||
{
|
||||
best = node;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstStatBlock* block) override
|
||||
{
|
||||
visit(static_cast<AstNode*>(block));
|
||||
|
||||
for (AstStat* stat : block->body)
|
||||
{
|
||||
if (stat->location.end < pos)
|
||||
continue;
|
||||
if (stat->location.begin > pos)
|
||||
break;
|
||||
|
||||
stat->visit(this);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct FindFullAncestry final : public AstVisitor
|
||||
{
|
||||
std::vector<AstNode*> nodes;
|
||||
Position pos;
|
||||
|
||||
explicit FindFullAncestry(Position pos)
|
||||
: pos(pos)
|
||||
{
|
||||
}
|
||||
|
||||
bool visit(AstNode* node)
|
||||
{
|
||||
if (node->location.contains(pos))
|
||||
{
|
||||
nodes.push_back(node);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos)
|
||||
{
|
||||
FindFullAncestry finder(pos);
|
||||
source.root->visit(&finder);
|
||||
return std::move(finder.nodes);
|
||||
}
|
||||
|
||||
AstNode* findNodeAtPosition(const SourceModule& source, Position pos)
|
||||
{
|
||||
const Position end = source.root->location.end;
|
||||
if (pos < source.root->location.begin)
|
||||
return source.root;
|
||||
|
||||
if (pos > end)
|
||||
pos = end;
|
||||
|
||||
FindNode findNode{pos, end};
|
||||
findNode.visit(source.root);
|
||||
return findNode.best;
|
||||
}
|
||||
|
||||
AstExpr* findExprAtPosition(const SourceModule& source, Position pos)
|
||||
{
|
||||
AstNode* node = findNodeAtPosition(source, pos);
|
||||
if (node)
|
||||
return node->asExpr();
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ScopePtr findScopeAtPosition(const Module& module, Position pos)
|
||||
{
|
||||
LUAU_ASSERT(!module.scopes.empty());
|
||||
|
||||
Location scopeLocation = module.scopes.front().first;
|
||||
ScopePtr scope = module.scopes.front().second;
|
||||
for (const auto& s : module.scopes)
|
||||
{
|
||||
if (s.first.contains(pos))
|
||||
{
|
||||
if (!scope || scopeLocation.encloses(s.first))
|
||||
{
|
||||
scopeLocation = s.first;
|
||||
scope = s.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
|
||||
std::optional<TypeId> findTypeAtPosition(const Module& module, const SourceModule& sourceModule, Position pos)
|
||||
{
|
||||
if (auto expr = findExprAtPosition(sourceModule, pos))
|
||||
{
|
||||
if (auto it = module.astTypes.find(expr); it != module.astTypes.end())
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<TypeId> findExpectedTypeAtPosition(const Module& module, const SourceModule& sourceModule, Position pos)
|
||||
{
|
||||
if (auto expr = findExprAtPosition(sourceModule, pos))
|
||||
{
|
||||
if (auto it = module.astExpectedTypes.find(expr); it != module.astExpectedTypes.end())
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static std::optional<AstStatLocal*> findBindingLocalStatement(const SourceModule& source, const Binding& binding)
|
||||
{
|
||||
std::vector<AstNode*> nodes = findAstAncestryOfPosition(source, binding.location.begin);
|
||||
auto iter = std::find_if(nodes.rbegin(), nodes.rend(), [](AstNode* node) {
|
||||
return node->is<AstStatLocal>();
|
||||
});
|
||||
return iter != nodes.rend() ? std::make_optional((*iter)->as<AstStatLocal>()) : std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<Binding> findBindingAtPosition(const Module& module, const SourceModule& source, Position pos)
|
||||
{
|
||||
AstExpr* expr = findExprAtPosition(source, pos);
|
||||
if (!expr)
|
||||
return std::nullopt;
|
||||
|
||||
Symbol name;
|
||||
if (auto g = expr->as<AstExprGlobal>())
|
||||
name = g->name;
|
||||
else if (auto l = expr->as<AstExprLocal>())
|
||||
name = l->local;
|
||||
else
|
||||
return std::nullopt;
|
||||
|
||||
ScopePtr currentScope = findScopeAtPosition(module, pos);
|
||||
LUAU_ASSERT(currentScope);
|
||||
|
||||
while (currentScope)
|
||||
{
|
||||
auto iter = currentScope->bindings.find(name);
|
||||
if (iter != currentScope->bindings.end() && iter->second.location.begin <= pos)
|
||||
{
|
||||
/* Ignore this binding if we're inside its definition. e.g. local abc = abc -- Will take the definition of abc from outer scope */
|
||||
std::optional<AstStatLocal*> bindingStatement = findBindingLocalStatement(source, iter->second);
|
||||
if (!bindingStatement || !(*bindingStatement)->location.contains(pos))
|
||||
return iter->second;
|
||||
}
|
||||
currentScope = currentScope->parent;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
struct FindExprOrLocal : public AstVisitor
|
||||
{
|
||||
const Position pos;
|
||||
ExprOrLocal result;
|
||||
|
||||
explicit FindExprOrLocal(Position pos)
|
||||
: pos(pos)
|
||||
{
|
||||
}
|
||||
|
||||
// We want to find the result with the smallest location range.
|
||||
bool isCloserMatch(Location newLocation)
|
||||
{
|
||||
auto current = result.getLocation();
|
||||
return newLocation.contains(pos) && (!current || current->encloses(newLocation));
|
||||
}
|
||||
|
||||
bool visit(AstStatBlock* block) override
|
||||
{
|
||||
for (AstStat* stat : block->body)
|
||||
{
|
||||
if (stat->location.end <= pos)
|
||||
continue;
|
||||
if (stat->location.begin > pos)
|
||||
break;
|
||||
|
||||
stat->visit(this);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExpr* expr) override
|
||||
{
|
||||
if (isCloserMatch(expr->location))
|
||||
{
|
||||
result.setExpr(expr);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visitLocal(AstLocal* local)
|
||||
{
|
||||
if (isCloserMatch(local->location))
|
||||
{
|
||||
result.setLocal(local);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstStatLocalFunction* function) override
|
||||
{
|
||||
visitLocal(function->name);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstStatLocal* al) override
|
||||
{
|
||||
for (size_t i = 0; i < al->vars.size; ++i)
|
||||
{
|
||||
visitLocal(al->vars.data[i]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool visit(AstExprFunction* fn) override
|
||||
{
|
||||
for (size_t i = 0; i < fn->args.size; ++i)
|
||||
{
|
||||
visitLocal(fn->args.data[i]);
|
||||
}
|
||||
return visit((class AstExpr*)fn);
|
||||
}
|
||||
|
||||
virtual bool visit(AstStatFor* forStat) override
|
||||
{
|
||||
visitLocal(forStat->var);
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool visit(AstStatForIn* forIn) override
|
||||
{
|
||||
for (AstLocal* var : forIn->vars)
|
||||
{
|
||||
visitLocal(var);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}; // namespace
|
||||
|
||||
ExprOrLocal findExprOrLocalAtPosition(const SourceModule& source, Position pos)
|
||||
{
|
||||
FindExprOrLocal findVisitor{pos};
|
||||
findVisitor.visit(source.root);
|
||||
return findVisitor.result;
|
||||
}
|
||||
|
||||
std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const SourceModule& source, const Module& module, Position position)
|
||||
{
|
||||
std::vector<AstNode*> ancestry = findAstAncestryOfPosition(source, position);
|
||||
|
||||
AstExpr* targetExpr = ancestry.size() >= 1 ? ancestry[ancestry.size() - 1]->asExpr() : nullptr;
|
||||
AstExpr* parentExpr = ancestry.size() >= 2 ? ancestry[ancestry.size() - 2]->asExpr() : nullptr;
|
||||
|
||||
if (std::optional<Binding> binding = findBindingAtPosition(module, source, position))
|
||||
{
|
||||
if (binding->documentationSymbol)
|
||||
{
|
||||
// This might be an overloaded function binding.
|
||||
if (get<IntersectionTypeVar>(follow(binding->typeId)))
|
||||
{
|
||||
TypeId matchingOverload = nullptr;
|
||||
if (parentExpr && parentExpr->is<AstExprCall>())
|
||||
{
|
||||
if (auto it = module.astOverloadResolvedTypes.find(parentExpr); it != module.astOverloadResolvedTypes.end())
|
||||
{
|
||||
matchingOverload = it->second;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchingOverload)
|
||||
{
|
||||
std::string overloadSymbol = *binding->documentationSymbol + "/overload/";
|
||||
// Default toString options are fine for this purpose.
|
||||
overloadSymbol += toString(matchingOverload);
|
||||
return overloadSymbol;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return binding->documentationSymbol;
|
||||
}
|
||||
|
||||
if (targetExpr)
|
||||
{
|
||||
if (AstExprIndexName* indexName = targetExpr->as<AstExprIndexName>())
|
||||
{
|
||||
if (auto it = module.astTypes.find(indexName->expr); it != module.astTypes.end())
|
||||
{
|
||||
TypeId parentTy = follow(it->second);
|
||||
if (const TableTypeVar* ttv = get<TableTypeVar>(parentTy))
|
||||
{
|
||||
if (auto propIt = ttv->props.find(indexName->index.value); propIt != ttv->props.end())
|
||||
{
|
||||
return propIt->second.documentationSymbol;
|
||||
}
|
||||
}
|
||||
else if (const ClassTypeVar* ctv = get<ClassTypeVar>(parentTy))
|
||||
{
|
||||
if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end())
|
||||
{
|
||||
return propIt->second.documentationSymbol;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (AstExprFunction* fn = targetExpr->as<AstExprFunction>())
|
||||
{
|
||||
// Handle event connection-like structures where we have
|
||||
// something:Connect(function(a, b, c) end)
|
||||
// In this case, we want to ascribe a documentation symbol to 'a'
|
||||
// based on the documentation symbol of Connect.
|
||||
if (parentExpr && parentExpr->is<AstExprCall>())
|
||||
{
|
||||
AstExprCall* call = parentExpr->as<AstExprCall>();
|
||||
if (std::optional<DocumentationSymbol> parentSymbol = getDocumentationSymbolAtPosition(source, module, call->func->location.begin))
|
||||
{
|
||||
for (size_t i = 0; i < call->args.size; ++i)
|
||||
{
|
||||
AstExpr* callArg = call->args.data[i];
|
||||
if (callArg == targetExpr)
|
||||
{
|
||||
std::string fnSymbol = *parentSymbol + "/param/" + std::to_string(i);
|
||||
for (size_t j = 0; j < fn->args.size; ++j)
|
||||
{
|
||||
AstLocal* fnArg = fn->args.data[j];
|
||||
|
||||
if (fnArg->location.contains(position))
|
||||
{
|
||||
return fnSymbol + "/param/" + std::to_string(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (std::optional<TypeId> ty = findTypeAtPosition(module, source, position))
|
||||
{
|
||||
if ((*ty)->documentationSymbol)
|
||||
{
|
||||
return (*ty)->documentationSymbol;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
1566
Analysis/src/Autocomplete.cpp
Normal file
1566
Analysis/src/Autocomplete.cpp
Normal file
File diff suppressed because it is too large
Load diff
805
Analysis/src/BuiltinDefinitions.cpp
Normal file
805
Analysis/src/BuiltinDefinitions.cpp
Normal file
|
@ -0,0 +1,805 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
|
||||
#include "Luau/Frontend.h"
|
||||
#include "Luau/Symbol.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/ToString.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(LuauParseGenericFunctions)
|
||||
LUAU_FASTFLAG(LuauGenericFunctions)
|
||||
LUAU_FASTFLAG(LuauRankNTypes)
|
||||
LUAU_FASTFLAG(LuauStringMetatable)
|
||||
|
||||
/** FIXME: Many of these type definitions are not quite completely accurate.
|
||||
*
|
||||
* Some of them require richer generics than we have. For instance, we do not yet have a way to talk
|
||||
* about a function that takes any number of values, but where each value must have some specific type.
|
||||
*/
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
static std::optional<ExprResult<TypePackId>> magicFunctionSelect(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult);
|
||||
static std::optional<ExprResult<TypePackId>> magicFunctionSetMetaTable(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult);
|
||||
static std::optional<ExprResult<TypePackId>> magicFunctionAssert(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult);
|
||||
static std::optional<ExprResult<TypePackId>> magicFunctionPack(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult);
|
||||
static std::optional<ExprResult<TypePackId>> magicFunctionRequire(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult);
|
||||
|
||||
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types)
|
||||
{
|
||||
return arena.addType(UnionTypeVar{std::move(types)});
|
||||
}
|
||||
|
||||
TypeId makeIntersection(TypeArena& arena, std::vector<TypeId>&& types)
|
||||
{
|
||||
return arena.addType(IntersectionTypeVar{std::move(types)});
|
||||
}
|
||||
|
||||
TypeId makeOption(TypeChecker& typeChecker, TypeArena& arena, TypeId t)
|
||||
{
|
||||
return makeUnion(arena, {typeChecker.nilType, t});
|
||||
}
|
||||
|
||||
TypeId makeFunction(
|
||||
TypeArena& arena, std::optional<TypeId> selfType, std::initializer_list<TypeId> paramTypes, std::initializer_list<TypeId> retTypes)
|
||||
{
|
||||
return makeFunction(arena, selfType, {}, {}, paramTypes, {}, retTypes);
|
||||
}
|
||||
|
||||
TypeId makeFunction(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)
|
||||
{
|
||||
return makeFunction(arena, selfType, generics, genericPacks, paramTypes, {}, retTypes);
|
||||
}
|
||||
|
||||
TypeId makeFunction(TypeArena& arena, std::optional<TypeId> selfType, std::initializer_list<TypeId> paramTypes,
|
||||
std::initializer_list<std::string> paramNames, std::initializer_list<TypeId> retTypes)
|
||||
{
|
||||
return makeFunction(arena, selfType, {}, {}, paramTypes, paramNames, retTypes);
|
||||
}
|
||||
|
||||
TypeId makeFunction(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)
|
||||
{
|
||||
std::vector<TypeId> params;
|
||||
if (selfType)
|
||||
params.push_back(*selfType);
|
||||
for (auto&& p : paramTypes)
|
||||
params.push_back(p);
|
||||
|
||||
TypePackId paramPack = arena.addTypePack(std::move(params));
|
||||
TypePackId retPack = arena.addTypePack(std::vector<TypeId>(retTypes));
|
||||
FunctionTypeVar ftv{generics, genericPacks, paramPack, retPack, {}, selfType.has_value()};
|
||||
|
||||
if (selfType)
|
||||
ftv.argNames.push_back(Luau::FunctionArgument{"self", {}});
|
||||
|
||||
if (paramNames.size() != 0)
|
||||
{
|
||||
for (auto&& p : paramNames)
|
||||
ftv.argNames.push_back(Luau::FunctionArgument{std::move(p), {}});
|
||||
}
|
||||
else if (selfType)
|
||||
{
|
||||
// If argument names were not provided, but we have already added a name for 'self' argument, we have to fill remaining slots as well
|
||||
for (size_t i = 0; i < paramTypes.size(); i++)
|
||||
ftv.argNames.push_back(std::nullopt);
|
||||
}
|
||||
|
||||
return arena.addType(std::move(ftv));
|
||||
}
|
||||
|
||||
void attachMagicFunction(TypeId ty, MagicFunction fn)
|
||||
{
|
||||
if (auto ftv = getMutable<FunctionTypeVar>(ty))
|
||||
ftv->magicFunction = fn;
|
||||
else
|
||||
LUAU_ASSERT(!"Got a non functional type");
|
||||
}
|
||||
|
||||
void attachFunctionTag(TypeId ty, std::string tag)
|
||||
{
|
||||
if (auto ftv = getMutable<FunctionTypeVar>(ty))
|
||||
{
|
||||
ftv->tags.emplace_back(std::move(tag));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(!"Got a non functional type");
|
||||
}
|
||||
}
|
||||
|
||||
Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol)
|
||||
{
|
||||
return {
|
||||
/* type */ ty,
|
||||
/* deprecated */ false,
|
||||
/* deprecatedSuggestion */ {},
|
||||
/* location */ std::nullopt,
|
||||
/* tags */ {},
|
||||
documentationSymbol,
|
||||
};
|
||||
}
|
||||
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const std::string& name, TypeId ty, const std::string& packageName)
|
||||
{
|
||||
addGlobalBinding(typeChecker, typeChecker.globalScope, name, ty, packageName);
|
||||
}
|
||||
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const std::string& name, Binding binding)
|
||||
{
|
||||
addGlobalBinding(typeChecker, typeChecker.globalScope, name, binding);
|
||||
}
|
||||
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const ScopePtr& scope, const std::string& name, TypeId ty, const std::string& packageName)
|
||||
{
|
||||
std::string documentationSymbol = packageName + "/global/" + name;
|
||||
addGlobalBinding(typeChecker, scope, name, Binding{ty, Location{}, {}, {}, documentationSymbol});
|
||||
}
|
||||
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const ScopePtr& scope, const std::string& name, Binding binding)
|
||||
{
|
||||
scope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = binding;
|
||||
}
|
||||
|
||||
TypeId getGlobalBinding(TypeChecker& typeChecker, const std::string& name)
|
||||
{
|
||||
auto t = tryGetGlobalBinding(typeChecker, name);
|
||||
LUAU_ASSERT(t.has_value());
|
||||
return t->typeId;
|
||||
}
|
||||
|
||||
std::optional<Binding> tryGetGlobalBinding(TypeChecker& typeChecker, const std::string& name)
|
||||
{
|
||||
AstName astName = typeChecker.globalNames.names->getOrAdd(name.c_str());
|
||||
auto it = typeChecker.globalScope->bindings.find(astName);
|
||||
if (it != typeChecker.globalScope->bindings.end())
|
||||
return it->second;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Binding* tryGetGlobalBindingRef(TypeChecker& typeChecker, const std::string& name)
|
||||
{
|
||||
AstName astName = typeChecker.globalNames.names->get(name.c_str());
|
||||
if (astName == AstName())
|
||||
return nullptr;
|
||||
|
||||
auto it = typeChecker.globalScope->bindings.find(astName);
|
||||
if (it != typeChecker.globalScope->bindings.end())
|
||||
return &it->second;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void assignPropDocumentationSymbols(TableTypeVar::Props& props, const std::string& baseName)
|
||||
{
|
||||
for (auto& [name, prop] : props)
|
||||
{
|
||||
prop.documentationSymbol = baseName + "." + name;
|
||||
}
|
||||
}
|
||||
|
||||
void registerBuiltinTypes(TypeChecker& typeChecker)
|
||||
{
|
||||
LUAU_ASSERT(!typeChecker.globalTypes.typeVars.isFrozen());
|
||||
LUAU_ASSERT(!typeChecker.globalTypes.typePacks.isFrozen());
|
||||
|
||||
TypeId numberType = typeChecker.numberType;
|
||||
TypeId booleanType = typeChecker.booleanType;
|
||||
TypeId nilType = typeChecker.nilType;
|
||||
TypeId stringType = typeChecker.stringType;
|
||||
TypeId threadType = typeChecker.threadType;
|
||||
TypeId anyType = typeChecker.anyType;
|
||||
|
||||
TypeArena& arena = typeChecker.globalTypes;
|
||||
|
||||
TypeId optionalNumber = makeOption(typeChecker, arena, numberType);
|
||||
TypeId optionalString = makeOption(typeChecker, arena, stringType);
|
||||
TypeId optionalBoolean = makeOption(typeChecker, arena, booleanType);
|
||||
|
||||
TypeId stringOrNumber = makeUnion(arena, {stringType, numberType});
|
||||
|
||||
TypePackId emptyPack = arena.addTypePack({});
|
||||
TypePackId oneNumberPack = arena.addTypePack({numberType});
|
||||
TypePackId oneStringPack = arena.addTypePack({stringType});
|
||||
TypePackId oneBooleanPack = arena.addTypePack({booleanType});
|
||||
TypePackId oneAnyPack = arena.addTypePack({anyType});
|
||||
|
||||
TypePackId anyTypePack = typeChecker.anyTypePack;
|
||||
|
||||
TypePackId numberVariadicList = arena.addTypePack(TypePackVar{VariadicTypePack{numberType}});
|
||||
TypePackId stringVariadicList = arena.addTypePack(TypePackVar{VariadicTypePack{stringType}});
|
||||
TypePackId listOfAtLeastOneNumber = arena.addTypePack(TypePack{{numberType}, numberVariadicList});
|
||||
|
||||
TypeId listOfAtLeastOneNumberToNumberType = arena.addType(FunctionTypeVar{
|
||||
listOfAtLeastOneNumber,
|
||||
oneNumberPack,
|
||||
});
|
||||
|
||||
TypeId listOfAtLeastZeroNumbersToNumberType = arena.addType(FunctionTypeVar{numberVariadicList, oneNumberPack});
|
||||
|
||||
TypeId stringToAnyMap = arena.addType(TableTypeVar{{}, TableIndexer(stringType, anyType), typeChecker.globalScope->level});
|
||||
|
||||
LoadDefinitionFileResult loadResult = Luau::loadDefinitionFile(typeChecker, typeChecker.globalScope, getBuiltinDefinitionSource(), "@luau");
|
||||
LUAU_ASSERT(loadResult.success);
|
||||
|
||||
TypeId mathLibType = getGlobalBinding(typeChecker, "math");
|
||||
if (TableTypeVar* ttv = getMutable<TableTypeVar>(mathLibType))
|
||||
{
|
||||
ttv->props["min"] = makeProperty(listOfAtLeastOneNumberToNumberType, "@luau/global/math.min");
|
||||
ttv->props["max"] = makeProperty(listOfAtLeastOneNumberToNumberType, "@luau/global/math.max");
|
||||
}
|
||||
|
||||
TypeId bit32LibType = getGlobalBinding(typeChecker, "bit32");
|
||||
if (TableTypeVar* ttv = getMutable<TableTypeVar>(bit32LibType))
|
||||
{
|
||||
ttv->props["band"] = makeProperty(listOfAtLeastZeroNumbersToNumberType, "@luau/global/bit32.band");
|
||||
ttv->props["bor"] = makeProperty(listOfAtLeastZeroNumbersToNumberType, "@luau/global/bit32.bor");
|
||||
ttv->props["bxor"] = makeProperty(listOfAtLeastZeroNumbersToNumberType, "@luau/global/bit32.bxor");
|
||||
ttv->props["btest"] = makeProperty(arena.addType(FunctionTypeVar{listOfAtLeastOneNumber, oneBooleanPack}), "@luau/global/bit32.btest");
|
||||
}
|
||||
|
||||
TypeId anyFunction = arena.addType(FunctionTypeVar{anyTypePack, anyTypePack});
|
||||
|
||||
TypeId genericK = arena.addType(GenericTypeVar{"K"});
|
||||
TypeId genericV = arena.addType(GenericTypeVar{"V"});
|
||||
TypeId mapOfKtoV = arena.addType(TableTypeVar{{}, TableIndexer(genericK, genericV), typeChecker.globalScope->level});
|
||||
|
||||
if (FFlag::LuauStringMetatable)
|
||||
{
|
||||
std::optional<TypeId> stringMetatableTy = getMetatable(singletonTypes.stringType);
|
||||
LUAU_ASSERT(stringMetatableTy);
|
||||
const TableTypeVar* stringMetatableTable = get<TableTypeVar>(follow(*stringMetatableTy));
|
||||
LUAU_ASSERT(stringMetatableTable);
|
||||
|
||||
auto it = stringMetatableTable->props.find("__index");
|
||||
LUAU_ASSERT(it != stringMetatableTable->props.end());
|
||||
|
||||
TypeId stringLib = it->second.type;
|
||||
addGlobalBinding(typeChecker, "string", stringLib, "@luau");
|
||||
}
|
||||
|
||||
if (FFlag::LuauParseGenericFunctions && FFlag::LuauGenericFunctions)
|
||||
{
|
||||
if (!FFlag::LuauStringMetatable)
|
||||
{
|
||||
TypeId stringLibTy = getGlobalBinding(typeChecker, "string");
|
||||
TableTypeVar* stringLib = getMutable<TableTypeVar>(stringLibTy);
|
||||
TypeId replArgType = makeUnion(
|
||||
arena, {stringType,
|
||||
arena.addType(TableTypeVar({}, TableIndexer(stringType, stringType), typeChecker.globalScope->level, TableState::Generic)),
|
||||
makeFunction(arena, std::nullopt, {stringType}, {stringType})});
|
||||
TypeId gsubFunc = makeFunction(arena, stringType, {stringType, replArgType, optionalNumber}, {stringType, numberType});
|
||||
|
||||
stringLib->props["gsub"] = makeProperty(gsubFunc, "@luau/global/string.gsub");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!FFlag::LuauStringMetatable)
|
||||
{
|
||||
TypeId stringToStringType = makeFunction(arena, std::nullopt, {stringType}, {stringType});
|
||||
|
||||
TypeId gmatchFunc = makeFunction(arena, stringType, {stringType}, {arena.addType(FunctionTypeVar{emptyPack, stringVariadicList})});
|
||||
|
||||
TypeId replArgType = makeUnion(
|
||||
arena, {stringType,
|
||||
arena.addType(TableTypeVar({}, TableIndexer(stringType, stringType), typeChecker.globalScope->level, TableState::Generic)),
|
||||
makeFunction(arena, std::nullopt, {stringType}, {stringType})});
|
||||
TypeId gsubFunc = makeFunction(arena, stringType, {stringType, replArgType, optionalNumber}, {stringType, numberType});
|
||||
|
||||
TypeId formatFn = arena.addType(FunctionTypeVar{arena.addTypePack(TypePack{{stringType}, anyTypePack}), oneStringPack});
|
||||
|
||||
TableTypeVar::Props stringLib = {
|
||||
// FIXME string.byte "can" return a pack of numbers, but only if 2nd or 3rd arguments were supplied
|
||||
{"byte", {makeFunction(arena, stringType, {optionalNumber, optionalNumber}, {optionalNumber})}},
|
||||
// FIXME char takes a variadic pack of numbers
|
||||
{"char", {makeFunction(arena, std::nullopt, {numberType, optionalNumber, optionalNumber, optionalNumber}, {stringType})}},
|
||||
{"find", {makeFunction(arena, stringType, {stringType, optionalNumber, optionalBoolean}, {optionalNumber, optionalNumber})}},
|
||||
{"format", {formatFn}}, // FIXME
|
||||
{"gmatch", {gmatchFunc}},
|
||||
{"gsub", {gsubFunc}},
|
||||
{"len", {makeFunction(arena, stringType, {}, {numberType})}},
|
||||
{"lower", {stringToStringType}},
|
||||
{"match", {makeFunction(arena, stringType, {stringType, optionalNumber}, {optionalString})}},
|
||||
{"rep", {makeFunction(arena, stringType, {numberType}, {stringType})}},
|
||||
{"reverse", {stringToStringType}},
|
||||
{"sub", {makeFunction(arena, stringType, {numberType, optionalNumber}, {stringType})}},
|
||||
{"upper", {stringToStringType}},
|
||||
{"split", {makeFunction(arena, stringType, {stringType, optionalString},
|
||||
{arena.addType(TableTypeVar{{}, TableIndexer{numberType, stringType}, typeChecker.globalScope->level})})}},
|
||||
{"pack", {arena.addType(FunctionTypeVar{
|
||||
arena.addTypePack(TypePack{{stringType}, anyTypePack}),
|
||||
oneStringPack,
|
||||
})}},
|
||||
{"packsize", {makeFunction(arena, stringType, {}, {numberType})}},
|
||||
{"unpack", {arena.addType(FunctionTypeVar{
|
||||
arena.addTypePack(TypePack{{stringType, stringType, optionalNumber}}),
|
||||
anyTypePack,
|
||||
})}},
|
||||
};
|
||||
|
||||
assignPropDocumentationSymbols(stringLib, "@luau/global/string");
|
||||
addGlobalBinding(typeChecker, "string",
|
||||
arena.addType(TableTypeVar{stringLib, std::nullopt, typeChecker.globalScope->level, TableState::Sealed}), "@luau");
|
||||
}
|
||||
|
||||
TableTypeVar::Props debugLib{
|
||||
{"info", {makeIntersection(arena,
|
||||
{
|
||||
arena.addType(FunctionTypeVar{arena.addTypePack({typeChecker.threadType, numberType, stringType}), anyTypePack}),
|
||||
arena.addType(FunctionTypeVar{arena.addTypePack({numberType, stringType}), anyTypePack}),
|
||||
arena.addType(FunctionTypeVar{arena.addTypePack({anyFunction, stringType}), anyTypePack}),
|
||||
})}},
|
||||
{"traceback", {makeIntersection(arena,
|
||||
{
|
||||
makeFunction(arena, std::nullopt, {optionalString, optionalNumber}, {stringType}),
|
||||
makeFunction(arena, std::nullopt, {typeChecker.threadType, optionalString, optionalNumber}, {stringType}),
|
||||
})}},
|
||||
};
|
||||
|
||||
assignPropDocumentationSymbols(debugLib, "@luau/global/debug");
|
||||
addGlobalBinding(typeChecker, "debug",
|
||||
arena.addType(TableTypeVar{debugLib, std::nullopt, typeChecker.globalScope->level, Luau::TableState::Sealed}), "@luau");
|
||||
|
||||
TableTypeVar::Props utf8Lib = {
|
||||
{"char", {arena.addType(FunctionTypeVar{listOfAtLeastOneNumber, oneStringPack})}}, // FIXME
|
||||
{"charpattern", {stringType}},
|
||||
{"codes", {makeFunction(arena, std::nullopt, {stringType},
|
||||
{makeFunction(arena, std::nullopt, {stringType, numberType}, {numberType, numberType}), stringType, numberType})}},
|
||||
{"codepoint",
|
||||
{arena.addType(FunctionTypeVar{arena.addTypePack({stringType, optionalNumber, optionalNumber}), listOfAtLeastOneNumber})}}, // FIXME
|
||||
{"len", {makeFunction(arena, std::nullopt, {stringType, optionalNumber, optionalNumber}, {optionalNumber, numberType})}},
|
||||
{"offset", {makeFunction(arena, std::nullopt, {stringType, optionalNumber, optionalNumber}, {numberType})}},
|
||||
{"nfdnormalize", {makeFunction(arena, std::nullopt, {stringType}, {stringType})}},
|
||||
{"graphemes", {makeFunction(arena, std::nullopt, {stringType, optionalNumber, optionalNumber},
|
||||
{makeFunction(arena, std::nullopt, {}, {numberType, numberType})})}},
|
||||
{"nfcnormalize", {makeFunction(arena, std::nullopt, {stringType}, {stringType})}},
|
||||
};
|
||||
|
||||
assignPropDocumentationSymbols(utf8Lib, "@luau/global/utf8");
|
||||
addGlobalBinding(
|
||||
typeChecker, "utf8", arena.addType(TableTypeVar{utf8Lib, std::nullopt, typeChecker.globalScope->level, TableState::Sealed}), "@luau");
|
||||
|
||||
TypeId optionalV = makeOption(typeChecker, arena, genericV);
|
||||
|
||||
TypeId arrayOfV = arena.addType(TableTypeVar{{}, TableIndexer(numberType, genericV), typeChecker.globalScope->level});
|
||||
|
||||
TypePackId unpackArgsPack = arena.addTypePack(TypePack{{arrayOfV, optionalNumber, optionalNumber}});
|
||||
TypePackId unpackReturnPack = arena.addTypePack(TypePack{{}, anyTypePack});
|
||||
TypeId unpackFunc = arena.addType(FunctionTypeVar{{genericV}, {}, unpackArgsPack, unpackReturnPack});
|
||||
|
||||
TypeId packResult = arena.addType(TableTypeVar{
|
||||
TableTypeVar::Props{{"n", {numberType}}}, TableIndexer{numberType, numberType}, typeChecker.globalScope->level, TableState::Sealed});
|
||||
TypePackId packArgsPack = arena.addTypePack(TypePack{{}, anyTypePack});
|
||||
TypePackId packReturnPack = arena.addTypePack(TypePack{{packResult}});
|
||||
|
||||
TypeId comparator = makeFunction(arena, std::nullopt, {genericV, genericV}, {booleanType});
|
||||
TypeId optionalComparator = makeOption(typeChecker, arena, comparator);
|
||||
|
||||
TypeId packFn = arena.addType(FunctionTypeVar(packArgsPack, packReturnPack));
|
||||
|
||||
TableTypeVar::Props tableLib = {
|
||||
{"concat", {makeFunction(arena, std::nullopt, {genericV}, {}, {arrayOfV, optionalString, optionalNumber, optionalNumber}, {stringType})}},
|
||||
{"insert", {makeIntersection(arena, {makeFunction(arena, std::nullopt, {genericV}, {}, {arrayOfV, genericV}, {}),
|
||||
makeFunction(arena, std::nullopt, {genericV}, {}, {arrayOfV, numberType, genericV}, {})})}},
|
||||
{"maxn", {makeFunction(arena, std::nullopt, {genericV}, {}, {arrayOfV}, {numberType})}},
|
||||
{"remove", {makeFunction(arena, std::nullopt, {genericV}, {}, {arrayOfV, optionalNumber}, {optionalV})}},
|
||||
{"sort", {makeFunction(arena, std::nullopt, {genericV}, {}, {arrayOfV, optionalComparator}, {})}},
|
||||
{"create", {makeFunction(arena, std::nullopt, {genericV}, {}, {numberType, optionalV}, {arrayOfV})}},
|
||||
{"find", {makeFunction(arena, std::nullopt, {genericV}, {}, {arrayOfV, genericV, optionalNumber}, {optionalNumber})}},
|
||||
|
||||
{"unpack", {unpackFunc}}, // FIXME
|
||||
{"pack", {packFn}},
|
||||
|
||||
// Lua 5.0 compat
|
||||
{"getn", {makeFunction(arena, std::nullopt, {genericV}, {}, {arrayOfV}, {numberType})}},
|
||||
{"foreach", {makeFunction(arena, std::nullopt, {genericK, genericV}, {},
|
||||
{mapOfKtoV, makeFunction(arena, std::nullopt, {genericK, genericV}, {})}, {})}},
|
||||
{"foreachi", {makeFunction(arena, std::nullopt, {genericV}, {}, {arrayOfV, makeFunction(arena, std::nullopt, {genericV}, {})}, {})}},
|
||||
|
||||
// backported from Lua 5.3
|
||||
{"move", {makeFunction(arena, std::nullopt, {genericV}, {}, {arrayOfV, numberType, numberType, numberType, arrayOfV}, {})}},
|
||||
|
||||
// added in Luau (borrowed from LuaJIT)
|
||||
{"clear", {makeFunction(arena, std::nullopt, {genericK, genericV}, {}, {mapOfKtoV}, {})}},
|
||||
|
||||
{"freeze", {makeFunction(arena, std::nullopt, {genericK, genericV}, {}, {mapOfKtoV}, {mapOfKtoV})}},
|
||||
{"isfrozen", {makeFunction(arena, std::nullopt, {genericK, genericV}, {}, {mapOfKtoV}, {booleanType})}},
|
||||
};
|
||||
|
||||
assignPropDocumentationSymbols(tableLib, "@luau/global/table");
|
||||
addGlobalBinding(
|
||||
typeChecker, "table", arena.addType(TableTypeVar{tableLib, std::nullopt, typeChecker.globalScope->level, TableState::Sealed}), "@luau");
|
||||
|
||||
TableTypeVar::Props coroutineLib = {
|
||||
{"create", {makeFunction(arena, std::nullopt, {anyFunction}, {threadType})}},
|
||||
{"resume", {arena.addType(FunctionTypeVar{arena.addTypePack(TypePack{{threadType}, anyTypePack}), anyTypePack})}},
|
||||
{"running", {makeFunction(arena, std::nullopt, {}, {threadType})}},
|
||||
{"status", {makeFunction(arena, std::nullopt, {threadType}, {stringType})}},
|
||||
{"wrap", {makeFunction(
|
||||
arena, std::nullopt, {anyFunction}, {anyType})}}, // FIXME this technically returns a function, but we can't represent this
|
||||
// atm since it can be called with different arg types at different times
|
||||
{"yield", {arena.addType(FunctionTypeVar{anyTypePack, anyTypePack})}},
|
||||
{"isyieldable", {makeFunction(arena, std::nullopt, {}, {booleanType})}},
|
||||
};
|
||||
|
||||
assignPropDocumentationSymbols(coroutineLib, "@luau/global/coroutine");
|
||||
addGlobalBinding(typeChecker, "coroutine",
|
||||
arena.addType(TableTypeVar{coroutineLib, std::nullopt, typeChecker.globalScope->level, TableState::Sealed}), "@luau");
|
||||
|
||||
TypeId genericT = arena.addType(GenericTypeVar{"T"});
|
||||
TypeId genericR = arena.addType(GenericTypeVar{"R"});
|
||||
|
||||
// assert returns all arguments
|
||||
TypePackId assertArgs = arena.addTypePack({genericT, optionalString});
|
||||
TypePackId assertRets = arena.addTypePack({genericT});
|
||||
addGlobalBinding(typeChecker, "assert", arena.addType(FunctionTypeVar{assertArgs, assertRets}), "@luau");
|
||||
|
||||
addGlobalBinding(typeChecker, "print", arena.addType(FunctionTypeVar{anyTypePack, emptyPack}), "@luau");
|
||||
|
||||
addGlobalBinding(typeChecker, "type", makeFunction(arena, std::nullopt, {genericT}, {}, {genericT}, {stringType}), "@luau");
|
||||
addGlobalBinding(typeChecker, "typeof", makeFunction(arena, std::nullopt, {genericT}, {}, {genericT}, {stringType}), "@luau");
|
||||
|
||||
addGlobalBinding(typeChecker, "error", makeFunction(arena, std::nullopt, {genericT}, {}, {genericT, optionalNumber}, {}), "@luau");
|
||||
|
||||
addGlobalBinding(typeChecker, "tostring", makeFunction(arena, std::nullopt, {genericT}, {}, {genericT}, {stringType}), "@luau");
|
||||
addGlobalBinding(
|
||||
typeChecker, "tonumber", makeFunction(arena, std::nullopt, {genericT}, {}, {genericT, optionalNumber}, {numberType}), "@luau");
|
||||
|
||||
addGlobalBinding(
|
||||
typeChecker, "rawequal", makeFunction(arena, std::nullopt, {genericT, genericR}, {}, {genericT, genericR}, {booleanType}), "@luau");
|
||||
addGlobalBinding(
|
||||
typeChecker, "rawget", makeFunction(arena, std::nullopt, {genericK, genericV}, {}, {mapOfKtoV, genericK}, {genericV}), "@luau");
|
||||
addGlobalBinding(typeChecker, "rawset",
|
||||
makeFunction(arena, std::nullopt, {genericK, genericV}, {}, {mapOfKtoV, genericK, genericV}, {mapOfKtoV}), "@luau");
|
||||
|
||||
TypePackId genericTPack = arena.addTypePack({genericT});
|
||||
TypePackId genericRPack = arena.addTypePack({genericR});
|
||||
TypeId genericArgsToReturnFunction = arena.addType(
|
||||
FunctionTypeVar{{genericT, genericR}, {}, arena.addTypePack(TypePack{{}, genericTPack}), arena.addTypePack(TypePack{{}, genericRPack})});
|
||||
|
||||
TypeId setfenvArgType = makeUnion(arena, {numberType, genericArgsToReturnFunction});
|
||||
TypeId setfenvReturnType = makeOption(typeChecker, arena, genericArgsToReturnFunction);
|
||||
addGlobalBinding(typeChecker, "setfenv", makeFunction(arena, std::nullopt, {setfenvArgType, stringToAnyMap}, {setfenvReturnType}), "@luau");
|
||||
|
||||
TypePackId ipairsArgsTypePack = arena.addTypePack({arrayOfV});
|
||||
|
||||
TypeId ipairsNextFunctionType = arena.addType(
|
||||
FunctionTypeVar{{genericK, genericV}, {}, arena.addTypePack({arrayOfV, numberType}), arena.addTypePack({numberType, genericV})});
|
||||
|
||||
// ipairs returns 'next, Array<V>, 0' so we would need type-level primitives and change to
|
||||
// again, we have a direct reference to 'next' because ipairs returns it
|
||||
// ipairs<V>(t: Array<V>) -> ((Array<V>) -> (number, V), Array<V>, 0)
|
||||
TypePackId ipairsReturnTypePack = arena.addTypePack(TypePack{{ipairsNextFunctionType, arrayOfV, numberType}});
|
||||
|
||||
// ipairs<V>(t: Array<V>) -> ((Array<V>) -> (number, V), Array<V>, number)
|
||||
addGlobalBinding(typeChecker, "ipairs", arena.addType(FunctionTypeVar{{genericV}, {}, ipairsArgsTypePack, ipairsReturnTypePack}), "@luau");
|
||||
|
||||
TypePackId pcallArg0FnArgs = arena.addTypePack(TypePackVar{GenericTypeVar{"A"}});
|
||||
TypePackId pcallArg0FnRet = arena.addTypePack(TypePackVar{GenericTypeVar{"R"}});
|
||||
TypeId pcallArg0 = arena.addType(FunctionTypeVar{pcallArg0FnArgs, pcallArg0FnRet});
|
||||
TypePackId pcallArgsTypePack = arena.addTypePack(TypePack{{pcallArg0}, pcallArg0FnArgs});
|
||||
|
||||
TypePackId pcallReturnTypePack = arena.addTypePack(TypePack{{booleanType}, pcallArg0FnRet});
|
||||
|
||||
// pcall<A..., R...>(f: (A...) -> R..., args: A...) -> boolean, R...
|
||||
addGlobalBinding(typeChecker, "pcall",
|
||||
arena.addType(FunctionTypeVar{{}, {pcallArg0FnArgs, pcallArg0FnRet}, pcallArgsTypePack, pcallReturnTypePack}), "@luau");
|
||||
|
||||
// errors thrown by the function 'f' are propagated onto the function 'err' that accepts it.
|
||||
// and either 'f' or 'err' are valid results of this xpcall
|
||||
// if 'err' did throw an error, then it returns: false, "error in error handling"
|
||||
// TODO: the above is not represented (nor representable) in the type annotation below.
|
||||
//
|
||||
// The real type of xpcall is as such: <E, A..., R1..., R2...>(f: (A...) -> R1..., err: (E) -> R2..., A...) -> (true, R1...) | (false,
|
||||
// R2...)
|
||||
TypePackId genericAPack = arena.addTypePack(TypePackVar{GenericTypeVar{"A"}});
|
||||
TypePackId genericR1Pack = arena.addTypePack(TypePackVar{GenericTypeVar{"R1"}});
|
||||
TypePackId genericR2Pack = arena.addTypePack(TypePackVar{GenericTypeVar{"R2"}});
|
||||
|
||||
TypeId genericE = arena.addType(GenericTypeVar{"E"});
|
||||
|
||||
TypeId xpcallFArg = arena.addType(FunctionTypeVar{genericAPack, genericR1Pack});
|
||||
TypeId xpcallErrArg = arena.addType(FunctionTypeVar{arena.addTypePack({genericE}), genericR2Pack});
|
||||
|
||||
TypePackId xpcallArgsPack = arena.addTypePack({{xpcallFArg, xpcallErrArg}, genericAPack});
|
||||
TypePackId xpcallRetPack = arena.addTypePack({{booleanType}, genericR1Pack}); // FIXME
|
||||
|
||||
addGlobalBinding(typeChecker, "xpcall",
|
||||
arena.addType(FunctionTypeVar{{genericE}, {genericAPack, genericR1Pack, genericR2Pack}, xpcallArgsPack, xpcallRetPack}), "@luau");
|
||||
|
||||
addGlobalBinding(typeChecker, "unpack", unpackFunc, "@luau");
|
||||
|
||||
TypePackId selectArgsTypePack = arena.addTypePack(TypePack{
|
||||
{stringOrNumber},
|
||||
anyTypePack // FIXME? select() is tricky.
|
||||
});
|
||||
|
||||
addGlobalBinding(typeChecker, "select", arena.addType(FunctionTypeVar{selectArgsTypePack, anyTypePack}), "@luau");
|
||||
|
||||
// TODO: not completely correct. loadstring's return type should be a function or (nil, string)
|
||||
TypeId loadstringFunc = arena.addType(FunctionTypeVar{anyTypePack, oneAnyPack});
|
||||
|
||||
addGlobalBinding(typeChecker, "loadstring",
|
||||
makeFunction(arena, std::nullopt, {stringType, optionalString},
|
||||
{
|
||||
makeOption(typeChecker, arena, loadstringFunc),
|
||||
makeOption(typeChecker, arena, stringType),
|
||||
}),
|
||||
"@luau");
|
||||
|
||||
// a userdata object is "roughly" the same as a sealed empty table
|
||||
// except `type(newproxy(false))` evaluates to "userdata" so we may need another special type here too.
|
||||
// another important thing to note: the value passed in conditionally creates an empty metatable, and you have to use getmetatable, NOT
|
||||
// setmetatable.
|
||||
// TODO: change this to something Luau can understand how to reject `setmetatable(newproxy(false or true), {})`.
|
||||
TypeId sealedTable = arena.addType(TableTypeVar(TableState::Sealed, typeChecker.globalScope->level));
|
||||
addGlobalBinding(typeChecker, "newproxy", makeFunction(arena, std::nullopt, {optionalBoolean}, {sealedTable}), "@luau");
|
||||
}
|
||||
|
||||
// next<K, V>(t: Table<K, V>, i: K | nil) -> (K, V)
|
||||
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(typeChecker, arena, genericK)}});
|
||||
addGlobalBinding(typeChecker, "next",
|
||||
arena.addType(FunctionTypeVar{{genericK, genericV}, {}, nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})}), "@luau");
|
||||
|
||||
TypePackId pairsArgsTypePack = arena.addTypePack({mapOfKtoV});
|
||||
|
||||
TypeId pairsNext = (FFlag::LuauRankNTypes ? arena.addType(FunctionTypeVar{nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})})
|
||||
: getGlobalBinding(typeChecker, "next"));
|
||||
TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, nilType}});
|
||||
|
||||
// NOTE we are missing 'i: K | nil' argument in the first return types' argument.
|
||||
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>) -> (K, V), Table<K, V>, nil)
|
||||
addGlobalBinding(typeChecker, "pairs", arena.addType(FunctionTypeVar{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
|
||||
|
||||
TypeId genericMT = arena.addType(GenericTypeVar{"MT"});
|
||||
|
||||
TableTypeVar tab{TableState::Generic, typeChecker.globalScope->level};
|
||||
TypeId tabTy = arena.addType(tab);
|
||||
|
||||
TypeId tableMetaMT = arena.addType(MetatableTypeVar{tabTy, genericMT});
|
||||
|
||||
addGlobalBinding(typeChecker, "getmetatable", makeFunction(arena, std::nullopt, {genericMT}, {}, {tableMetaMT}, {genericMT}), "@luau");
|
||||
|
||||
// setmetatable<MT>({ @metatable MT }, MT) -> { @metatable MT }
|
||||
// clang-format off
|
||||
addGlobalBinding(typeChecker, "setmetatable",
|
||||
arena.addType(
|
||||
FunctionTypeVar{
|
||||
{genericMT},
|
||||
{},
|
||||
arena.addTypePack(TypePack{{tableMetaMT, genericMT}}),
|
||||
arena.addTypePack(TypePack{{tableMetaMT}})
|
||||
}
|
||||
), "@luau"
|
||||
);
|
||||
// clang-format on
|
||||
|
||||
for (const auto& pair : typeChecker.globalScope->bindings)
|
||||
{
|
||||
persist(pair.second.typeId);
|
||||
|
||||
if (TableTypeVar* ttv = getMutable<TableTypeVar>(pair.second.typeId))
|
||||
ttv->name = toString(pair.first);
|
||||
}
|
||||
|
||||
attachMagicFunction(getGlobalBinding(typeChecker, "assert"), magicFunctionAssert);
|
||||
attachMagicFunction(getGlobalBinding(typeChecker, "setmetatable"), magicFunctionSetMetaTable);
|
||||
attachMagicFunction(getGlobalBinding(typeChecker, "select"), magicFunctionSelect);
|
||||
|
||||
auto tableLib = getMutable<TableTypeVar>(getGlobalBinding(typeChecker, "table"));
|
||||
attachMagicFunction(tableLib->props["pack"].type, magicFunctionPack);
|
||||
|
||||
auto stringLib = getMutable<TableTypeVar>(getGlobalBinding(typeChecker, "string"));
|
||||
attachMagicFunction(stringLib->props["format"].type, magicFunctionFormat);
|
||||
|
||||
attachMagicFunction(getGlobalBinding(typeChecker, "require"), magicFunctionRequire);
|
||||
}
|
||||
|
||||
static std::optional<ExprResult<TypePackId>> magicFunctionSelect(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult)
|
||||
{
|
||||
auto [paramPack, _predicates] = exprResult;
|
||||
|
||||
(void)scope;
|
||||
|
||||
if (expr.args.size <= 0)
|
||||
{
|
||||
typechecker.reportError(TypeError{expr.location, GenericError{"select should take 1 or more arguments"}});
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
AstExpr* arg1 = expr.args.data[0];
|
||||
if (AstExprConstantNumber* num = arg1->as<AstExprConstantNumber>())
|
||||
{
|
||||
const auto& [v, tail] = flatten(paramPack);
|
||||
|
||||
int offset = int(num->value);
|
||||
if (offset > 0)
|
||||
{
|
||||
if (size_t(offset) < v.size())
|
||||
{
|
||||
std::vector<TypeId> result(v.begin() + offset, v.end());
|
||||
return ExprResult<TypePackId>{typechecker.currentModule->internalTypes.addTypePack(TypePack{std::move(result), tail})};
|
||||
}
|
||||
else if (tail)
|
||||
return ExprResult<TypePackId>{*tail};
|
||||
}
|
||||
|
||||
typechecker.reportError(TypeError{arg1->location, GenericError{"bad argument #1 to select (index out of range)"}});
|
||||
}
|
||||
else if (AstExprConstantString* str = arg1->as<AstExprConstantString>())
|
||||
{
|
||||
if (str->value.size == 1 && str->value.data[0] == '#')
|
||||
return ExprResult<TypePackId>{typechecker.currentModule->internalTypes.addTypePack({typechecker.numberType})};
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static std::optional<ExprResult<TypePackId>> magicFunctionSetMetaTable(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult)
|
||||
{
|
||||
auto [paramPack, _predicates] = exprResult;
|
||||
|
||||
TypeArena& arena = typechecker.currentModule->internalTypes;
|
||||
|
||||
std::vector<TypeId> expectedArgs = typechecker.unTypePack(scope, paramPack, 2, expr.location);
|
||||
|
||||
TypeId target = follow(expectedArgs[0]);
|
||||
TypeId mt = follow(expectedArgs[1]);
|
||||
|
||||
if (const auto& tab = get<TableTypeVar>(target))
|
||||
{
|
||||
if (target->persistent)
|
||||
{
|
||||
typechecker.reportError(TypeError{expr.location, CannotExtendTable{target, CannotExtendTable::Metatable}});
|
||||
}
|
||||
else
|
||||
{
|
||||
typechecker.tablify(mt);
|
||||
|
||||
const TableTypeVar* mtTtv = get<TableTypeVar>(mt);
|
||||
MetatableTypeVar mtv{target, mt};
|
||||
if ((tab->name || tab->syntheticName) && (mtTtv && (mtTtv->name || mtTtv->syntheticName)))
|
||||
{
|
||||
std::string tableName = tab->name ? *tab->name : *tab->syntheticName;
|
||||
std::string metatableName = mtTtv->name ? *mtTtv->name : *mtTtv->syntheticName;
|
||||
|
||||
if (tableName == metatableName)
|
||||
mtv.syntheticName = tableName;
|
||||
else
|
||||
mtv.syntheticName = "{ @metatable: " + metatableName + ", " + tableName + " }";
|
||||
}
|
||||
|
||||
TypeId mtTy = arena.addType(mtv);
|
||||
|
||||
AstExpr* targetExpr = expr.args.data[0];
|
||||
if (AstExprLocal* targetLocal = targetExpr->as<AstExprLocal>())
|
||||
{
|
||||
const Name targetName(targetLocal->local->name.value);
|
||||
scope->bindings[targetLocal->local] = Binding{mtTy, expr.location};
|
||||
}
|
||||
|
||||
return ExprResult<TypePackId>{arena.addTypePack({mtTy})};
|
||||
}
|
||||
}
|
||||
else if (get<AnyTypeVar>(target) || get<ErrorTypeVar>(target) || isTableIntersection(target))
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
typechecker.reportError(TypeError{expr.location, GenericError{"setmetatable should take a table"}});
|
||||
}
|
||||
|
||||
return ExprResult<TypePackId>{arena.addTypePack({target})};
|
||||
}
|
||||
|
||||
static std::optional<ExprResult<TypePackId>> magicFunctionAssert(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult)
|
||||
{
|
||||
auto [paramPack, predicates] = exprResult;
|
||||
|
||||
if (expr.args.size < 1)
|
||||
return ExprResult<TypePackId>{paramPack};
|
||||
|
||||
typechecker.reportErrors(typechecker.resolve(predicates, scope, true));
|
||||
|
||||
return ExprResult<TypePackId>{paramPack};
|
||||
}
|
||||
|
||||
static std::optional<ExprResult<TypePackId>> magicFunctionPack(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult)
|
||||
{
|
||||
auto [paramPack, _predicates] = exprResult;
|
||||
|
||||
TypeArena& arena = typechecker.currentModule->internalTypes;
|
||||
|
||||
const auto& [paramTypes, paramTail] = flatten(paramPack);
|
||||
|
||||
std::vector<TypeId> options;
|
||||
options.reserve(paramTypes.size());
|
||||
for (auto type : paramTypes)
|
||||
options.push_back(type);
|
||||
|
||||
if (paramTail)
|
||||
{
|
||||
if (const VariadicTypePack* vtp = get<VariadicTypePack>(*paramTail))
|
||||
options.push_back(vtp->ty);
|
||||
}
|
||||
|
||||
options = typechecker.reduceUnion(options);
|
||||
|
||||
// table.pack() -> {| n: number, [number]: nil |}
|
||||
// table.pack(1) -> {| n: number, [number]: number |}
|
||||
// table.pack(1, "foo") -> {| n: number, [number]: number | string |}
|
||||
TypeId result = nullptr;
|
||||
if (options.empty())
|
||||
result = typechecker.nilType;
|
||||
else if (options.size() == 1)
|
||||
result = options[0];
|
||||
else
|
||||
result = arena.addType(UnionTypeVar{std::move(options)});
|
||||
|
||||
TypeId packedTable = arena.addType(
|
||||
TableTypeVar{{{"n", {typechecker.numberType}}}, TableIndexer(typechecker.numberType, result), scope->level, TableState::Sealed});
|
||||
|
||||
return ExprResult<TypePackId>{arena.addTypePack({packedTable})};
|
||||
}
|
||||
|
||||
static bool checkRequirePath(TypeChecker& typechecker, AstExpr* expr)
|
||||
{
|
||||
// require(foo.parent.bar) will technically work, but it depends on legacy goop that
|
||||
// Luau does not and could not support without a bunch of work. It's deprecated anyway, so
|
||||
// we'll warn here if we see it.
|
||||
bool good = true;
|
||||
AstExprIndexName* indexExpr = expr->as<AstExprIndexName>();
|
||||
|
||||
while (indexExpr)
|
||||
{
|
||||
if (indexExpr->index == "parent")
|
||||
{
|
||||
typechecker.reportError(indexExpr->indexLocation, DeprecatedApiUsed{"parent", "Parent"});
|
||||
good = false;
|
||||
}
|
||||
|
||||
indexExpr = indexExpr->expr->as<AstExprIndexName>();
|
||||
}
|
||||
|
||||
return good;
|
||||
}
|
||||
|
||||
static std::optional<ExprResult<TypePackId>> magicFunctionRequire(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult)
|
||||
{
|
||||
TypeArena& arena = typechecker.currentModule->internalTypes;
|
||||
|
||||
if (expr.args.size != 1)
|
||||
{
|
||||
typechecker.reportError(TypeError{expr.location, GenericError{"require takes 1 argument"}});
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
AstExpr* require = expr.args.data[0];
|
||||
|
||||
if (!checkRequirePath(typechecker, require))
|
||||
return std::nullopt;
|
||||
|
||||
if (auto moduleInfo = typechecker.resolver->resolveModuleInfo(typechecker.currentModuleName, *require))
|
||||
return ExprResult<TypePackId>{arena.addTypePack({typechecker.checkRequire(scope, *moduleInfo, expr.location)})};
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
278
Analysis/src/Config.cpp
Normal file
278
Analysis/src/Config.cpp
Normal file
|
@ -0,0 +1,278 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Config.h"
|
||||
|
||||
#include "Luau/Parser.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using Error = std::optional<std::string>;
|
||||
|
||||
}
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
static Error parseBoolean(bool& result, const std::string& value)
|
||||
{
|
||||
if (value == "true")
|
||||
result = true;
|
||||
else if (value == "false")
|
||||
result = false;
|
||||
else
|
||||
return Error{"Bad setting '" + value + "'. Valid options are true and false"};
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Error parseModeString(Mode& mode, const std::string& modeString, bool compat)
|
||||
{
|
||||
if (modeString == "nocheck")
|
||||
mode = Mode::NoCheck;
|
||||
else if (modeString == "strict")
|
||||
mode = Mode::Strict;
|
||||
else if (modeString == "nonstrict")
|
||||
mode = Mode::Nonstrict;
|
||||
else if (modeString == "noinfer" && compat)
|
||||
mode = Mode::NoCheck;
|
||||
else
|
||||
return Error{"Bad mode \"" + modeString + "\". Valid options are nocheck, nonstrict, and strict"};
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static Error parseLintRuleStringForCode(
|
||||
LintOptions& enabledLints, LintOptions& fatalLints, LintWarning::Code code, const std::string& value, bool compat)
|
||||
{
|
||||
if (value == "true")
|
||||
{
|
||||
enabledLints.enableWarning(code);
|
||||
}
|
||||
else if (value == "false")
|
||||
{
|
||||
enabledLints.disableWarning(code);
|
||||
}
|
||||
else if (compat)
|
||||
{
|
||||
if (value == "enabled")
|
||||
{
|
||||
enabledLints.enableWarning(code);
|
||||
fatalLints.disableWarning(code);
|
||||
}
|
||||
else if (value == "disabled")
|
||||
{
|
||||
enabledLints.disableWarning(code);
|
||||
fatalLints.disableWarning(code);
|
||||
}
|
||||
else if (value == "fatal")
|
||||
{
|
||||
enabledLints.enableWarning(code);
|
||||
fatalLints.enableWarning(code);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Error{"Bad setting '" + value + "'. Valid options are enabled, disabled, and fatal"};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return Error{"Bad setting '" + value + "'. Valid options are true and false"};
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Error parseLintRuleString(LintOptions& enabledLints, LintOptions& fatalLints, const std::string& warningName, const std::string& value, bool compat)
|
||||
{
|
||||
if (warningName == "*")
|
||||
{
|
||||
for (int code = LintWarning::Code_Unknown; code < LintWarning::Code__Count; ++code)
|
||||
{
|
||||
if (auto err = parseLintRuleStringForCode(enabledLints, fatalLints, LintWarning::Code(code), value, compat))
|
||||
return Error{"In key " + warningName + ": " + *err};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LintWarning::Code code = LintWarning::parseName(warningName.c_str());
|
||||
|
||||
if (code == LintWarning::Code_Unknown)
|
||||
return Error{"Unknown lint " + warningName};
|
||||
|
||||
if (auto err = parseLintRuleStringForCode(enabledLints, fatalLints, code, value, compat))
|
||||
return Error{"In key " + warningName + ": " + *err};
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static void next(Lexer& lexer)
|
||||
{
|
||||
lexer.next();
|
||||
|
||||
// skip C-style comments as Lexer only understands Lua-style comments atm
|
||||
while (lexer.current().type == '/')
|
||||
{
|
||||
Lexeme peek = lexer.lookahead();
|
||||
|
||||
if (peek.type != '/' || peek.location.begin != lexer.current().location.end)
|
||||
break;
|
||||
|
||||
lexer.nextline();
|
||||
}
|
||||
}
|
||||
|
||||
static Error fail(Lexer& lexer, const char* message)
|
||||
{
|
||||
Lexeme cur = lexer.current();
|
||||
|
||||
return format("Expected %s at line %d, got %s instead", message, cur.location.begin.line + 1, cur.toString().c_str());
|
||||
}
|
||||
|
||||
template<typename Action>
|
||||
static Error parseJson(const std::string& contents, Action action)
|
||||
{
|
||||
Allocator allocator;
|
||||
AstNameTable names(allocator);
|
||||
Lexer lexer(contents.data(), contents.size(), names);
|
||||
next(lexer);
|
||||
|
||||
std::vector<std::string> keys;
|
||||
bool arrayTop = false; // we don't support nested arrays
|
||||
|
||||
if (lexer.current().type != '{')
|
||||
return fail(lexer, "'{'");
|
||||
next(lexer);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (arrayTop)
|
||||
{
|
||||
if (lexer.current().type == ']')
|
||||
{
|
||||
next(lexer);
|
||||
arrayTop = false;
|
||||
|
||||
LUAU_ASSERT(!keys.empty());
|
||||
keys.pop_back();
|
||||
|
||||
if (lexer.current().type == ',')
|
||||
next(lexer);
|
||||
else if (lexer.current().type != '}')
|
||||
return fail(lexer, "',' or '}'");
|
||||
}
|
||||
else if (lexer.current().type == Lexeme::QuotedString)
|
||||
{
|
||||
std::string value(lexer.current().data, lexer.current().length);
|
||||
next(lexer);
|
||||
|
||||
if (Error err = action(keys, value))
|
||||
return err;
|
||||
|
||||
if (lexer.current().type == ',')
|
||||
next(lexer);
|
||||
else if (lexer.current().type != ']')
|
||||
return fail(lexer, "',' or ']'");
|
||||
}
|
||||
else
|
||||
return fail(lexer, "array element or ']'");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lexer.current().type == '}')
|
||||
{
|
||||
next(lexer);
|
||||
|
||||
if (keys.empty())
|
||||
{
|
||||
if (lexer.current().type != Lexeme::Eof)
|
||||
return fail(lexer, "end of file");
|
||||
|
||||
return {};
|
||||
}
|
||||
else
|
||||
keys.pop_back();
|
||||
|
||||
if (lexer.current().type == ',')
|
||||
next(lexer);
|
||||
else if (lexer.current().type != '}')
|
||||
return fail(lexer, "',' or '}'");
|
||||
}
|
||||
else if (lexer.current().type == Lexeme::QuotedString)
|
||||
{
|
||||
std::string key(lexer.current().data, lexer.current().length);
|
||||
next(lexer);
|
||||
|
||||
keys.push_back(key);
|
||||
|
||||
if (lexer.current().type != ':')
|
||||
return fail(lexer, "':'");
|
||||
next(lexer);
|
||||
|
||||
if (lexer.current().type == '{' || lexer.current().type == '[')
|
||||
{
|
||||
arrayTop = (lexer.current().type == '[');
|
||||
next(lexer);
|
||||
}
|
||||
else if (lexer.current().type == Lexeme::QuotedString || lexer.current().type == Lexeme::ReservedTrue ||
|
||||
lexer.current().type == Lexeme::ReservedFalse)
|
||||
{
|
||||
std::string value = lexer.current().type == Lexeme::QuotedString
|
||||
? std::string(lexer.current().data, lexer.current().length)
|
||||
: (lexer.current().type == Lexeme::ReservedTrue ? "true" : "false");
|
||||
next(lexer);
|
||||
|
||||
if (Error err = action(keys, value))
|
||||
return err;
|
||||
|
||||
keys.pop_back();
|
||||
|
||||
if (lexer.current().type == ',')
|
||||
next(lexer);
|
||||
else if (lexer.current().type != '}')
|
||||
return fail(lexer, "',' or '}'");
|
||||
}
|
||||
else
|
||||
return fail(lexer, "field value");
|
||||
}
|
||||
else
|
||||
return fail(lexer, "field key");
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Error parseConfig(const std::string& contents, Config& config, bool compat)
|
||||
{
|
||||
return parseJson(contents, [&](const std::vector<std::string>& keys, const std::string& value) -> Error {
|
||||
if (keys.size() == 1 && keys[0] == "languageMode")
|
||||
return parseModeString(config.mode, value, compat);
|
||||
else if (keys.size() == 2 && keys[0] == "lint")
|
||||
return parseLintRuleString(config.enabledLint, config.fatalLint, keys[1], value, compat);
|
||||
else if (keys.size() == 1 && keys[0] == "lintErrors")
|
||||
return parseBoolean(config.lintErrors, value);
|
||||
else if (keys.size() == 1 && keys[0] == "typeErrors")
|
||||
return parseBoolean(config.typeErrors, value);
|
||||
else if (keys.size() == 1 && keys[0] == "globals")
|
||||
{
|
||||
config.globals.push_back(value);
|
||||
return std::nullopt;
|
||||
}
|
||||
else if (compat && keys.size() == 2 && keys[0] == "language" && keys[1] == "mode")
|
||||
return parseModeString(config.mode, value, compat);
|
||||
else
|
||||
{
|
||||
std::vector<std::string_view> keysv(keys.begin(), keys.end());
|
||||
return "Unknown key " + join(keysv, "/");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const Config& NullConfigResolver::getConfig(const ModuleName& name) const
|
||||
{
|
||||
return defaultConfig;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
238
Analysis/src/EmbeddedBuiltinDefinitions.cpp
Normal file
238
Analysis/src/EmbeddedBuiltinDefinitions.cpp
Normal file
|
@ -0,0 +1,238 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauParseGenericFunctions)
|
||||
LUAU_FASTFLAG(LuauGenericFunctions)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
static const std::string kBuiltinDefinitionLuaSrc = R"BUILTIN_SRC(
|
||||
|
||||
declare bit32: {
|
||||
-- band, bor, bxor, and btest are declared in C++
|
||||
rrotate: (number, number) -> number,
|
||||
lrotate: (number, number) -> number,
|
||||
lshift: (number, number) -> number,
|
||||
arshift: (number, number) -> number,
|
||||
rshift: (number, number) -> number,
|
||||
bnot: (number) -> number,
|
||||
extract: (number, number, number?) -> number,
|
||||
replace: (number, number, number, number?) -> number,
|
||||
}
|
||||
|
||||
declare math: {
|
||||
frexp: (number) -> (number, number),
|
||||
ldexp: (number, number) -> number,
|
||||
fmod: (number, number) -> number,
|
||||
modf: (number) -> (number, number),
|
||||
pow: (number, number) -> number,
|
||||
exp: (number) -> number,
|
||||
|
||||
ceil: (number) -> number,
|
||||
floor: (number) -> number,
|
||||
abs: (number) -> number,
|
||||
sqrt: (number) -> number,
|
||||
|
||||
log: (number, number?) -> number,
|
||||
log10: (number) -> number,
|
||||
|
||||
rad: (number) -> number,
|
||||
deg: (number) -> number,
|
||||
|
||||
sin: (number) -> number,
|
||||
cos: (number) -> number,
|
||||
tan: (number) -> number,
|
||||
sinh: (number) -> number,
|
||||
cosh: (number) -> number,
|
||||
tanh: (number) -> number,
|
||||
atan: (number) -> number,
|
||||
acos: (number) -> number,
|
||||
asin: (number) -> number,
|
||||
atan2: (number, number) -> number,
|
||||
|
||||
-- min and max are declared in C++.
|
||||
|
||||
pi: number,
|
||||
huge: number,
|
||||
|
||||
randomseed: (number) -> (),
|
||||
random: (number?, number?) -> number,
|
||||
|
||||
sign: (number) -> number,
|
||||
clamp: (number, number, number) -> number,
|
||||
noise: (number, number?, number?) -> number,
|
||||
round: (number) -> number,
|
||||
}
|
||||
|
||||
type DateTypeArg = {
|
||||
year: number,
|
||||
month: number,
|
||||
day: number,
|
||||
hour: number?,
|
||||
min: number?,
|
||||
sec: number?,
|
||||
isdst: boolean?,
|
||||
}
|
||||
|
||||
type DateTypeResult = {
|
||||
year: number,
|
||||
month: number,
|
||||
wday: number,
|
||||
yday: number,
|
||||
day: number,
|
||||
hour: number,
|
||||
min: number,
|
||||
sec: number,
|
||||
isdst: boolean,
|
||||
}
|
||||
|
||||
declare os: {
|
||||
time: (DateTypeArg?) -> number,
|
||||
date: (string?, number?) -> DateTypeResult | string,
|
||||
difftime: (DateTypeResult | number, DateTypeResult | number) -> number,
|
||||
clock: () -> number,
|
||||
}
|
||||
|
||||
declare function require(target: any): any
|
||||
|
||||
declare function getfenv(target: any?): { [string]: any }
|
||||
|
||||
declare _G: any
|
||||
declare _VERSION: string
|
||||
|
||||
declare function gcinfo(): number
|
||||
|
||||
)BUILTIN_SRC";
|
||||
|
||||
std::string getBuiltinDefinitionSource()
|
||||
{
|
||||
std::string src = kBuiltinDefinitionLuaSrc;
|
||||
|
||||
if (FFlag::LuauParseGenericFunctions && FFlag::LuauGenericFunctions)
|
||||
{
|
||||
src += R"(
|
||||
declare function print<T...>(...: T...)
|
||||
|
||||
declare function type<T>(value: T): string
|
||||
declare function typeof<T>(value: T): string
|
||||
|
||||
-- `assert` has a magic function attached that will give more detailed type information
|
||||
declare function assert<T>(value: T, errorMessage: string?): T
|
||||
|
||||
declare function error<T>(message: T, level: number?)
|
||||
|
||||
declare function tostring<T>(value: T): string
|
||||
declare function tonumber<T>(value: T, radix: number?): number
|
||||
|
||||
declare function rawequal<T1, T2>(a: T1, b: T2): boolean
|
||||
declare function rawget<K, V>(tab: {[K]: V}, k: K): V
|
||||
declare function rawset<K, V>(tab: {[K]: V}, k: K, v: V): {[K]: V}
|
||||
|
||||
declare function setfenv<T..., R...>(target: number | (T...) -> R..., env: {[string]: any}): ((T...) -> R...)?
|
||||
|
||||
declare function ipairs<V>(tab: {V}): (({V}, number) -> (number, V), {V}, number)
|
||||
|
||||
declare function pcall<A..., R...>(f: (A...) -> R..., ...: A...): (boolean, R...)
|
||||
|
||||
-- FIXME: The actual type of `xpcall` is:
|
||||
-- <E, A..., R1..., R2...>(f: (A...) -> R1..., err: (E) -> R2..., A...) -> (true, R1...) | (false, R2...)
|
||||
-- Since we can't represent the return value, we use (boolean, R1...).
|
||||
declare function xpcall<E, A..., R1..., R2...>(f: (A...) -> R1..., err: (E) -> R2..., ...: A...): (boolean, R1...)
|
||||
|
||||
-- `select` has a magic function attached to provide more detailed type information
|
||||
declare function select<A...>(i: string | number, ...: A...): ...any
|
||||
|
||||
-- FIXME: This type is not entirely correct - `loadstring` returns a function or
|
||||
-- (nil, string).
|
||||
declare function loadstring<A...>(src: string, chunkname: string?): (((A...) -> any)?, string?)
|
||||
|
||||
-- a userdata object is "roughly" the same as a sealed empty table
|
||||
-- except `type(newproxy(false))` evaluates to "userdata" so we may need another special type here too.
|
||||
-- another important thing to note: the value passed in conditionally creates an empty metatable, and you have to use getmetatable, NOT
|
||||
-- setmetatable.
|
||||
-- FIXME: change this to something Luau can understand how to reject `setmetatable(newproxy(false or true), {})`.
|
||||
declare function newproxy(mt: boolean?): {}
|
||||
|
||||
declare coroutine: {
|
||||
create: <A..., R...>((A...) -> R...) -> thread,
|
||||
resume: <A..., R...>(thread, A...) -> (boolean, R...),
|
||||
running: () -> thread,
|
||||
status: (thread) -> string,
|
||||
-- FIXME: This technically returns a function, but we can't represent this yet.
|
||||
wrap: <A..., R...>((A...) -> R...) -> any,
|
||||
yield: <A..., R...>(A...) -> R...,
|
||||
isyieldable: () -> boolean,
|
||||
}
|
||||
|
||||
declare table: {
|
||||
concat: <V>({V}, string?, number?, number?) -> string,
|
||||
insert: (<V>({V}, V) -> ()) & (<V>({V}, number, V) -> ()),
|
||||
maxn: <V>({V}) -> number,
|
||||
remove: <V>({V}, number?) -> V?,
|
||||
sort: <V>({V}, ((V, V) -> boolean)?) -> (),
|
||||
create: <V>(number, V?) -> {V},
|
||||
find: <V>({V}, V, number?) -> number?,
|
||||
|
||||
unpack: <V>({V}, number?, number?) -> ...V,
|
||||
pack: <V>(...V) -> { n: number, [number]: V },
|
||||
|
||||
getn: <V>({V}) -> number,
|
||||
foreach: <K, V>({[K]: V}, (K, V) -> ()) -> (),
|
||||
foreachi: <V>({V}, (number, V) -> ()) -> (),
|
||||
|
||||
move: <V>({V}, number, number, number, {V}?) -> (),
|
||||
clear: <K, V>({[K]: V}) -> (),
|
||||
|
||||
freeze: <K, V>({[K]: V}) -> {[K]: V},
|
||||
isfrozen: <K, V>({[K]: V}) -> boolean,
|
||||
}
|
||||
|
||||
declare debug: {
|
||||
info: (<R...>(thread, number, string) -> R...) & (<R...>(number, string) -> R...) & (<A..., R1..., R2...>((A...) -> R1..., string) -> R2...),
|
||||
traceback: ((string?, number?) -> string) & ((thread, string?, number?) -> string),
|
||||
}
|
||||
|
||||
declare utf8: {
|
||||
char: (number, ...number) -> string,
|
||||
charpattern: string,
|
||||
codes: (string) -> ((string, number) -> (number, number), string, number),
|
||||
-- FIXME
|
||||
codepoint: (string, number?, number?) -> (number, ...number),
|
||||
len: (string, number?, number?) -> (number?, number?),
|
||||
offset: (string, number?, number?) -> number,
|
||||
nfdnormalize: (string) -> string,
|
||||
nfcnormalize: (string) -> string,
|
||||
graphemes: (string, number?, number?) -> (() -> (number, number)),
|
||||
}
|
||||
|
||||
declare string: {
|
||||
byte: (string, number?, number?) -> ...number,
|
||||
char: (number, ...number) -> string,
|
||||
find: (string, string, number?, boolean?) -> (number?, number?),
|
||||
-- `string.format` has a magic function attached that will provide more type information for literal format strings.
|
||||
format: <A...>(string, A...) -> string,
|
||||
gmatch: (string, string) -> () -> (...string),
|
||||
-- gsub is defined in C++ because we don't have syntax for describing a generic table.
|
||||
len: (string) -> number,
|
||||
lower: (string) -> string,
|
||||
match: (string, string, number?) -> string?,
|
||||
rep: (string, number) -> string,
|
||||
reverse: (string) -> string,
|
||||
sub: (string, number, number?) -> string,
|
||||
upper: (string) -> string,
|
||||
split: (string, string, string?) -> {string},
|
||||
pack: <A...>(string, A...) -> string,
|
||||
packsize: (string) -> number,
|
||||
unpack: <R...>(string, string, number?) -> R...,
|
||||
}
|
||||
|
||||
-- Cannot use `typeof` here because it will produce a polytype when we expect a monotype.
|
||||
declare function unpack<V>(tab: {V}, i: number?, j: number?): ...V
|
||||
)";
|
||||
}
|
||||
|
||||
return src;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
751
Analysis/src/Error.cpp
Normal file
751
Analysis/src/Error.cpp
Normal file
|
@ -0,0 +1,751 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Error.h"
|
||||
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/ToString.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAG(LuauFasterStringifier)
|
||||
|
||||
static std::string wrongNumberOfArgsString(size_t expectedCount, size_t actualCount, bool isTypeArgs = false)
|
||||
{
|
||||
std::string s = "expects " + std::to_string(expectedCount) + " ";
|
||||
|
||||
if (isTypeArgs)
|
||||
s += "type ";
|
||||
|
||||
s += "argument";
|
||||
if (expectedCount != 1)
|
||||
s += "s";
|
||||
|
||||
s += ", but ";
|
||||
|
||||
if (actualCount == 0)
|
||||
{
|
||||
s += "none";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (actualCount < expectedCount)
|
||||
s += "only ";
|
||||
|
||||
s += std::to_string(actualCount);
|
||||
}
|
||||
|
||||
s += (actualCount == 1) ? " is" : " are";
|
||||
|
||||
s += " specified";
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct ErrorConverter
|
||||
{
|
||||
std::string operator()(const Luau::TypeMismatch& tm) const
|
||||
{
|
||||
ToStringOptions opts;
|
||||
return "Type '" + Luau::toString(tm.givenType, opts) + "' could not be converted into '" + Luau::toString(tm.wantedType, opts) + "'";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::UnknownSymbol& e) const
|
||||
{
|
||||
switch (e.context)
|
||||
{
|
||||
case UnknownSymbol::Binding:
|
||||
return "Unknown global '" + e.name + "'";
|
||||
case UnknownSymbol::Type:
|
||||
return "Unknown type '" + e.name + "'";
|
||||
case UnknownSymbol::Generic:
|
||||
return "Unknown generic '" + e.name + "'";
|
||||
}
|
||||
|
||||
LUAU_ASSERT(!"Unexpected context for UnknownSymbol");
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::UnknownProperty& e) const
|
||||
{
|
||||
TypeId t = follow(e.table);
|
||||
if (get<TableTypeVar>(t))
|
||||
return "Key '" + e.key + "' not found in table '" + Luau::toString(t) + "'";
|
||||
else if (get<ClassTypeVar>(t))
|
||||
return "Key '" + e.key + "' not found in class '" + Luau::toString(t) + "'";
|
||||
else
|
||||
return "Type '" + Luau::toString(e.table) + "' does not have key '" + e.key + "'";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::NotATable& e) const
|
||||
{
|
||||
return "Expected type table, got '" + Luau::toString(e.ty) + "' instead";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::CannotExtendTable& e) const
|
||||
{
|
||||
switch (e.context)
|
||||
{
|
||||
case Luau::CannotExtendTable::Property:
|
||||
return "Cannot add property '" + e.prop + "' to table '" + Luau::toString(e.tableType) + "'";
|
||||
case Luau::CannotExtendTable::Metatable:
|
||||
return "Cannot add metatable to table '" + Luau::toString(e.tableType) + "'";
|
||||
case Luau::CannotExtendTable::Indexer:
|
||||
return "Cannot add indexer to table '" + Luau::toString(e.tableType) + "'";
|
||||
}
|
||||
|
||||
LUAU_ASSERT(!"Unknown context");
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::OnlyTablesCanHaveMethods& e) const
|
||||
{
|
||||
return "Cannot add method to non-table type '" + Luau::toString(e.tableType) + "'";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::DuplicateTypeDefinition& e) const
|
||||
{
|
||||
return "Redefinition of type '" + e.name + "', previously defined at line " + std::to_string(e.previousLocation.begin.line + 1);
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::CountMismatch& e) const
|
||||
{
|
||||
switch (e.context)
|
||||
{
|
||||
case CountMismatch::Return:
|
||||
{
|
||||
const std::string expectedS = e.expected == 1 ? "" : "s";
|
||||
const std::string actualS = e.actual == 1 ? "is" : "are";
|
||||
return "Expected to return " + std::to_string(e.expected) + " value" + expectedS + ", but " + std::to_string(e.actual) + " " + actualS +
|
||||
" returned here";
|
||||
}
|
||||
case CountMismatch::Result:
|
||||
if (e.expected > e.actual)
|
||||
return "Function returns " + std::to_string(e.expected) + " values but there are only " + std::to_string(e.expected) +
|
||||
" values to unpack them into.";
|
||||
else
|
||||
return "Function only returns " + std::to_string(e.expected) + " values. " + std::to_string(e.actual) + " are required here";
|
||||
case CountMismatch::Arg:
|
||||
return "Argument count mismatch. Function " + wrongNumberOfArgsString(e.expected, e.actual);
|
||||
}
|
||||
|
||||
LUAU_ASSERT(!"Unknown context");
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::FunctionDoesNotTakeSelf&) const
|
||||
{
|
||||
return std::string("This function does not take self. Did you mean to use a dot instead of a colon?");
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::FunctionRequiresSelf& e) const
|
||||
{
|
||||
if (e.requiredExtraNils)
|
||||
{
|
||||
const char* plural = e.requiredExtraNils == 1 ? "" : "s";
|
||||
return format("This function was declared to accept self, but you did not pass enough arguments. Use a colon instead of a dot or "
|
||||
"pass %i extra nil%s to suppress this warning",
|
||||
e.requiredExtraNils, plural);
|
||||
}
|
||||
else
|
||||
return "This function must be called with self. Did you mean to use a colon instead of a dot?";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::OccursCheckFailed&) const
|
||||
{
|
||||
return "Type contains a self-recursive construct that cannot be resolved";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::UnknownRequire& e) const
|
||||
{
|
||||
return "Unknown require: " + e.modulePath;
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::IncorrectGenericParameterCount& e) const
|
||||
{
|
||||
std::string name = e.name;
|
||||
if (!e.typeFun.typeParams.empty())
|
||||
{
|
||||
name += "<";
|
||||
bool first = true;
|
||||
for (TypeId t : e.typeFun.typeParams)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
name += ", ";
|
||||
|
||||
name += toString(t);
|
||||
}
|
||||
name += ">";
|
||||
}
|
||||
|
||||
return "Generic type '" + name + "' " + wrongNumberOfArgsString(e.typeFun.typeParams.size(), e.actualParameters, /*isTypeArgs*/ true);
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::SyntaxError& e) const
|
||||
{
|
||||
return "Syntax error: " + e.message;
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::CodeTooComplex&) const
|
||||
{
|
||||
return "Code is too complex to typecheck! Consider simplifying the code around this area";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::UnificationTooComplex&) const
|
||||
{
|
||||
return "Internal error: Code is too complex to typecheck! Consider adding type annotations around this area";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::UnknownPropButFoundLikeProp& e) const
|
||||
{
|
||||
std::string candidatesSuggestion = "Did you mean ";
|
||||
if (e.candidates.size() != 1)
|
||||
candidatesSuggestion += "one of ";
|
||||
|
||||
bool first = true;
|
||||
for (Name name : e.candidates)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
candidatesSuggestion += ", ";
|
||||
|
||||
candidatesSuggestion += "'" + name + "'";
|
||||
}
|
||||
|
||||
std::string s = "Key '" + e.key + "' not found in ";
|
||||
|
||||
TypeId t = follow(e.table);
|
||||
if (get<ClassTypeVar>(t))
|
||||
s += "class";
|
||||
else
|
||||
s += "table";
|
||||
|
||||
s += " '" + toString(e.table) + "'. " + candidatesSuggestion + "?";
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::GenericError& e) const
|
||||
{
|
||||
return e.message;
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::CannotCallNonFunction& e) const
|
||||
{
|
||||
return "Cannot call non-function " + toString(e.ty);
|
||||
}
|
||||
std::string operator()(const Luau::ExtraInformation& e) const
|
||||
{
|
||||
return e.message;
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::DeprecatedApiUsed& e) const
|
||||
{
|
||||
return "The property ." + e.symbol + " is deprecated. Use ." + e.useInstead + " instead.";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::ModuleHasCyclicDependency& e) const
|
||||
{
|
||||
if (e.cycle.empty())
|
||||
return "Cyclic module dependency detected";
|
||||
|
||||
std::string s = "Cyclic module dependency: ";
|
||||
|
||||
bool first = true;
|
||||
for (const ModuleName& name : e.cycle)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
s += " -> ";
|
||||
|
||||
s += name;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::FunctionExitsWithoutReturning& e) const
|
||||
{
|
||||
return "Not all codepaths in this function return '" + toString(e.expectedReturnType) + "'.";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::IllegalRequire& e) const
|
||||
{
|
||||
return "Cannot require module " + e.moduleName + ": " + e.reason;
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::MissingProperties& e) const
|
||||
{
|
||||
std::string s = "Table type '" + toString(e.subType) + "' not compatible with type '" + toString(e.superType) + "' because the former";
|
||||
|
||||
switch (e.context)
|
||||
{
|
||||
case MissingProperties::Missing:
|
||||
s += " is missing field";
|
||||
break;
|
||||
case MissingProperties::Extra:
|
||||
s += " has extra field";
|
||||
break;
|
||||
}
|
||||
|
||||
if (e.properties.size() > 1)
|
||||
s += "s";
|
||||
|
||||
s += " ";
|
||||
|
||||
for (size_t i = 0; i < e.properties.size(); ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
s += ", ";
|
||||
|
||||
if (i > 0 && i == e.properties.size() - 1)
|
||||
s += "and ";
|
||||
|
||||
s += "'" + e.properties[i] + "'";
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::DuplicateGenericParameter& e) const
|
||||
{
|
||||
return "Duplicate type parameter '" + e.parameterName + "'";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::CannotInferBinaryOperation& e) const
|
||||
{
|
||||
std::string ss = "Unknown type used in " + toString(e.op);
|
||||
|
||||
switch (e.kind)
|
||||
{
|
||||
case Luau::CannotInferBinaryOperation::Comparison:
|
||||
ss += " comparison";
|
||||
break;
|
||||
case Luau::CannotInferBinaryOperation::Operation:
|
||||
ss += " operation";
|
||||
}
|
||||
|
||||
if (e.suggestedToAnnotate)
|
||||
ss += "; consider adding a type annotation to '" + *e.suggestedToAnnotate + "'";
|
||||
|
||||
return ss;
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::SwappedGenericTypeParameter& e) const
|
||||
{
|
||||
switch (e.kind)
|
||||
{
|
||||
case Luau::SwappedGenericTypeParameter::Type:
|
||||
return "Variadic type parameter '" + e.name + "...' is used as a regular generic type; consider changing '" + e.name + "...' to '" +
|
||||
e.name + "' in the generic argument list";
|
||||
case Luau::SwappedGenericTypeParameter::Pack:
|
||||
return "Generic type '" + e.name + "' is used as a variadic type parameter; consider changing '" + e.name + "' to '" + e.name +
|
||||
"...' in the generic argument list";
|
||||
default:
|
||||
LUAU_ASSERT(!"Unknown kind");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::OptionalValueAccess& e) const
|
||||
{
|
||||
return "Value of type '" + toString(e.optional) + "' could be nil";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::MissingUnionProperty& e) const
|
||||
{
|
||||
std::string ss = "Key '" + e.key + "' is missing from ";
|
||||
|
||||
bool first = true;
|
||||
for (auto ty : e.missing)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
ss += ", ";
|
||||
|
||||
ss += "'" + toString(ty) + "'";
|
||||
}
|
||||
|
||||
return ss + " in the type '" + toString(e.type) + "'";
|
||||
}
|
||||
};
|
||||
|
||||
struct InvalidNameChecker
|
||||
{
|
||||
std::string invalidName = "%error-id%";
|
||||
|
||||
bool operator()(const Luau::UnknownProperty& e) const
|
||||
{
|
||||
return e.key == invalidName;
|
||||
}
|
||||
bool operator()(const Luau::CannotExtendTable& e) const
|
||||
{
|
||||
return e.prop == invalidName;
|
||||
}
|
||||
bool operator()(const Luau::DuplicateTypeDefinition& e) const
|
||||
{
|
||||
return e.name == invalidName;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool operator()(const T& other) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
bool TypeMismatch::operator==(const TypeMismatch& rhs) const
|
||||
{
|
||||
return *wantedType == *rhs.wantedType && *givenType == *rhs.givenType;
|
||||
}
|
||||
|
||||
bool UnknownSymbol::operator==(const UnknownSymbol& rhs) const
|
||||
{
|
||||
return name == rhs.name;
|
||||
}
|
||||
|
||||
bool UnknownProperty::operator==(const UnknownProperty& rhs) const
|
||||
{
|
||||
return *table == *rhs.table && key == rhs.key;
|
||||
}
|
||||
|
||||
bool NotATable::operator==(const NotATable& rhs) const
|
||||
{
|
||||
return ty == rhs.ty;
|
||||
}
|
||||
|
||||
bool CannotExtendTable::operator==(const CannotExtendTable& rhs) const
|
||||
{
|
||||
return *tableType == *rhs.tableType && prop == rhs.prop && context == rhs.context;
|
||||
}
|
||||
|
||||
bool OnlyTablesCanHaveMethods::operator==(const OnlyTablesCanHaveMethods& rhs) const
|
||||
{
|
||||
return *tableType == *rhs.tableType;
|
||||
}
|
||||
|
||||
bool DuplicateTypeDefinition::operator==(const DuplicateTypeDefinition& rhs) const
|
||||
{
|
||||
return name == rhs.name && previousLocation == rhs.previousLocation;
|
||||
}
|
||||
|
||||
bool CountMismatch::operator==(const CountMismatch& rhs) const
|
||||
{
|
||||
return expected == rhs.expected && actual == rhs.actual && context == rhs.context;
|
||||
}
|
||||
|
||||
bool FunctionDoesNotTakeSelf::operator==(const FunctionDoesNotTakeSelf&) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FunctionRequiresSelf::operator==(const FunctionRequiresSelf& e) const
|
||||
{
|
||||
return requiredExtraNils == e.requiredExtraNils;
|
||||
}
|
||||
|
||||
bool OccursCheckFailed::operator==(const OccursCheckFailed&) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnknownRequire::operator==(const UnknownRequire& rhs) const
|
||||
{
|
||||
return modulePath == rhs.modulePath;
|
||||
}
|
||||
|
||||
bool IncorrectGenericParameterCount::operator==(const IncorrectGenericParameterCount& rhs) const
|
||||
{
|
||||
if (name != rhs.name)
|
||||
return false;
|
||||
|
||||
if (typeFun.type != rhs.typeFun.type)
|
||||
return false;
|
||||
|
||||
if (typeFun.typeParams.size() != rhs.typeFun.typeParams.size())
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < typeFun.typeParams.size(); ++i)
|
||||
if (typeFun.typeParams[i] != rhs.typeFun.typeParams[i])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SyntaxError::operator==(const SyntaxError& rhs) const
|
||||
{
|
||||
return message == rhs.message;
|
||||
}
|
||||
|
||||
bool CodeTooComplex::operator==(const CodeTooComplex&) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnificationTooComplex::operator==(const UnificationTooComplex&) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnknownPropButFoundLikeProp::operator==(const UnknownPropButFoundLikeProp& rhs) const
|
||||
{
|
||||
return *table == *rhs.table && key == rhs.key && candidates.size() == rhs.candidates.size() &&
|
||||
std::equal(candidates.begin(), candidates.end(), rhs.candidates.begin());
|
||||
}
|
||||
|
||||
bool GenericError::operator==(const GenericError& rhs) const
|
||||
{
|
||||
return message == rhs.message;
|
||||
}
|
||||
|
||||
bool CannotCallNonFunction::operator==(const CannotCallNonFunction& rhs) const
|
||||
{
|
||||
return ty == rhs.ty;
|
||||
}
|
||||
|
||||
bool ExtraInformation::operator==(const ExtraInformation& rhs) const
|
||||
{
|
||||
return message == rhs.message;
|
||||
}
|
||||
|
||||
bool DeprecatedApiUsed::operator==(const DeprecatedApiUsed& rhs) const
|
||||
{
|
||||
return symbol == rhs.symbol && useInstead == rhs.useInstead;
|
||||
}
|
||||
|
||||
bool FunctionExitsWithoutReturning::operator==(const FunctionExitsWithoutReturning& rhs) const
|
||||
{
|
||||
return expectedReturnType == rhs.expectedReturnType;
|
||||
}
|
||||
|
||||
int TypeError::code() const
|
||||
{
|
||||
return 1000 + int(data.index());
|
||||
}
|
||||
|
||||
bool TypeError::operator==(const TypeError& rhs) const
|
||||
{
|
||||
return location == rhs.location && data == rhs.data;
|
||||
}
|
||||
|
||||
bool ModuleHasCyclicDependency::operator==(const ModuleHasCyclicDependency& rhs) const
|
||||
{
|
||||
return cycle.size() == rhs.cycle.size() && std::equal(cycle.begin(), cycle.end(), rhs.cycle.begin());
|
||||
}
|
||||
|
||||
bool IllegalRequire::operator==(const IllegalRequire& rhs) const
|
||||
{
|
||||
return moduleName == rhs.moduleName && reason == rhs.reason;
|
||||
}
|
||||
|
||||
bool MissingProperties::operator==(const MissingProperties& rhs) const
|
||||
{
|
||||
return *superType == *rhs.superType && *subType == *rhs.subType && properties.size() == rhs.properties.size() &&
|
||||
std::equal(properties.begin(), properties.end(), rhs.properties.begin()) && context == rhs.context;
|
||||
}
|
||||
|
||||
bool DuplicateGenericParameter::operator==(const DuplicateGenericParameter& rhs) const
|
||||
{
|
||||
return parameterName == rhs.parameterName;
|
||||
}
|
||||
|
||||
bool CannotInferBinaryOperation::operator==(const CannotInferBinaryOperation& rhs) const
|
||||
{
|
||||
return op == rhs.op && suggestedToAnnotate == rhs.suggestedToAnnotate && kind == rhs.kind;
|
||||
}
|
||||
|
||||
bool SwappedGenericTypeParameter::operator==(const SwappedGenericTypeParameter& rhs) const
|
||||
{
|
||||
return name == rhs.name && kind == rhs.kind;
|
||||
}
|
||||
|
||||
bool OptionalValueAccess::operator==(const OptionalValueAccess& rhs) const
|
||||
{
|
||||
return *optional == *rhs.optional;
|
||||
}
|
||||
|
||||
bool MissingUnionProperty::operator==(const MissingUnionProperty& rhs) const
|
||||
{
|
||||
if (missing.size() != rhs.missing.size())
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < missing.size(); ++i)
|
||||
{
|
||||
if (*missing[i] != *rhs.missing[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return *type == *rhs.type && key == rhs.key;
|
||||
}
|
||||
|
||||
std::string toString(const TypeError& error)
|
||||
{
|
||||
ErrorConverter converter;
|
||||
return Luau::visit(converter, error.data);
|
||||
}
|
||||
|
||||
bool containsParseErrorName(const TypeError& error)
|
||||
{
|
||||
return Luau::visit(InvalidNameChecker{}, error.data);
|
||||
}
|
||||
|
||||
void copyErrors(ErrorVec& errors, struct TypeArena& destArena)
|
||||
{
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
|
||||
auto clone = [&](auto&& ty) {
|
||||
return ::Luau::clone(ty, destArena, seenTypes, seenTypePacks);
|
||||
};
|
||||
|
||||
auto visitErrorData = [&](auto&& e) {
|
||||
using T = std::decay_t<decltype(e)>;
|
||||
|
||||
if constexpr (false)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, TypeMismatch>)
|
||||
{
|
||||
e.wantedType = clone(e.wantedType);
|
||||
e.givenType = clone(e.givenType);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, UnknownSymbol>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, UnknownProperty>)
|
||||
{
|
||||
e.table = clone(e.table);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, NotATable>)
|
||||
{
|
||||
e.ty = clone(e.ty);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, CannotExtendTable>)
|
||||
{
|
||||
e.tableType = clone(e.tableType);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, OnlyTablesCanHaveMethods>)
|
||||
{
|
||||
e.tableType = clone(e.tableType);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, DuplicateTypeDefinition>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, CountMismatch>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, FunctionDoesNotTakeSelf>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, FunctionRequiresSelf>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, OccursCheckFailed>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, UnknownRequire>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, IncorrectGenericParameterCount>)
|
||||
{
|
||||
e.typeFun = clone(e.typeFun);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, SyntaxError>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, CodeTooComplex>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, UnificationTooComplex>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, UnknownPropButFoundLikeProp>)
|
||||
{
|
||||
e.table = clone(e.table);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, GenericError>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, CannotCallNonFunction>)
|
||||
{
|
||||
e.ty = clone(e.ty);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, ExtraInformation>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, DeprecatedApiUsed>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, ModuleHasCyclicDependency>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, IllegalRequire>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, FunctionExitsWithoutReturning>)
|
||||
{
|
||||
e.expectedReturnType = clone(e.expectedReturnType);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, DuplicateGenericParameter>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, CannotInferBinaryOperation>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, MissingProperties>)
|
||||
{
|
||||
e.superType = clone(e.superType);
|
||||
e.subType = clone(e.subType);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, SwappedGenericTypeParameter>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, OptionalValueAccess>)
|
||||
{
|
||||
e.optional = clone(e.optional);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, MissingUnionProperty>)
|
||||
{
|
||||
e.type = clone(e.type);
|
||||
|
||||
for (auto& ty : e.missing)
|
||||
ty = clone(ty);
|
||||
}
|
||||
else
|
||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||
};
|
||||
|
||||
LUAU_ASSERT(!destArena.typeVars.isFrozen());
|
||||
LUAU_ASSERT(!destArena.typePacks.isFrozen());
|
||||
|
||||
for (TypeError& error : errors)
|
||||
visit(visitErrorData, error.data);
|
||||
}
|
||||
|
||||
void InternalErrorReporter::ice(const std::string& message, const Location& location)
|
||||
{
|
||||
std::runtime_error error("Internal error in " + moduleName + " at " + toString(location) + ": " + message);
|
||||
|
||||
if (onInternalError)
|
||||
onInternalError(error.what());
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
void InternalErrorReporter::ice(const std::string& message)
|
||||
{
|
||||
std::runtime_error error("Internal error in " + moduleName + ": " + message);
|
||||
|
||||
if (onInternalError)
|
||||
onInternalError(error.what());
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
967
Analysis/src/Frontend.cpp
Normal file
967
Analysis/src/Frontend.cpp
Normal file
|
@ -0,0 +1,967 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Frontend.h"
|
||||
|
||||
#include "Luau/Config.h"
|
||||
#include "Luau/FileResolver.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/Variant.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAG(LuauInferInNoCheckMode)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeCheckTwice, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSecondTypecheckKnowsTheDataModel, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauResolveModuleNameWithoutACurrentModule, false)
|
||||
LUAU_FASTFLAG(LuauTraceRequireLookupChild)
|
||||
LUAU_FASTFLAGVARIABLE(LuauPersistDefinitionFileTypes, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
std::optional<Mode> parseMode(const std::vector<std::string>& hotcomments)
|
||||
{
|
||||
for (const std::string& hc : hotcomments)
|
||||
{
|
||||
if (hc == "nocheck")
|
||||
return Mode::NoCheck;
|
||||
|
||||
if (hc == "nonstrict")
|
||||
return Mode::Nonstrict;
|
||||
|
||||
if (hc == "strict")
|
||||
return Mode::Strict;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static void generateDocumentationSymbols(TypeId ty, const std::string& rootName)
|
||||
{
|
||||
// TODO: What do we do in this situation? This means that the definition
|
||||
// file is exporting a type that is also a persistent type.
|
||||
if (ty->persistent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
asMutable(ty)->documentationSymbol = rootName;
|
||||
|
||||
if (TableTypeVar* ttv = getMutable<TableTypeVar>(ty))
|
||||
{
|
||||
for (auto& [name, prop] : ttv->props)
|
||||
{
|
||||
prop.documentationSymbol = rootName + "." + name;
|
||||
}
|
||||
}
|
||||
else if (ClassTypeVar* ctv = getMutable<ClassTypeVar>(ty))
|
||||
{
|
||||
for (auto& [name, prop] : ctv->props)
|
||||
{
|
||||
prop.documentationSymbol = rootName + "." + name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, ScopePtr targetScope, std::string_view source, const std::string& packageName)
|
||||
{
|
||||
Luau::Allocator allocator;
|
||||
Luau::AstNameTable names(allocator);
|
||||
|
||||
ParseOptions options;
|
||||
options.allowDeclarationSyntax = true;
|
||||
|
||||
Luau::ParseResult parseResult = Luau::Parser::parse(source.data(), source.size(), names, allocator, options);
|
||||
|
||||
if (parseResult.errors.size() > 0)
|
||||
return LoadDefinitionFileResult{false, parseResult, nullptr};
|
||||
|
||||
Luau::SourceModule module;
|
||||
module.root = parseResult.root;
|
||||
module.mode = Mode::Definition;
|
||||
|
||||
ModulePtr checkedModule = typeChecker.check(module, Mode::Definition);
|
||||
|
||||
if (checkedModule->errors.size() > 0)
|
||||
return LoadDefinitionFileResult{false, parseResult, checkedModule};
|
||||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->declaredGlobals)
|
||||
{
|
||||
TypeId globalTy = clone(ty, typeChecker.globalTypes, seenTypes, seenTypePacks);
|
||||
std::string documentationSymbol = packageName + "/global/" + name;
|
||||
generateDocumentationSymbols(globalTy, documentationSymbol);
|
||||
targetScope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
|
||||
|
||||
if (FFlag::LuauPersistDefinitionFileTypes)
|
||||
persist(globalTy);
|
||||
}
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->getModuleScope()->exportedTypeBindings)
|
||||
{
|
||||
TypeFun globalTy = clone(ty, typeChecker.globalTypes, seenTypes, seenTypePacks);
|
||||
std::string documentationSymbol = packageName + "/globaltype/" + name;
|
||||
generateDocumentationSymbols(globalTy.type, documentationSymbol);
|
||||
targetScope->exportedTypeBindings[name] = globalTy;
|
||||
|
||||
if (FFlag::LuauPersistDefinitionFileTypes)
|
||||
persist(globalTy.type);
|
||||
}
|
||||
|
||||
return LoadDefinitionFileResult{true, parseResult, checkedModule};
|
||||
}
|
||||
|
||||
std::vector<std::string_view> parsePathExpr(const AstExpr& pathExpr)
|
||||
{
|
||||
const AstExprIndexName* indexName = pathExpr.as<AstExprIndexName>();
|
||||
if (!indexName)
|
||||
return {};
|
||||
|
||||
std::vector<std::string_view> segments{indexName->index.value};
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (AstExprIndexName* in = indexName->expr->as<AstExprIndexName>())
|
||||
{
|
||||
segments.push_back(in->index.value);
|
||||
indexName = in;
|
||||
continue;
|
||||
}
|
||||
else if (AstExprGlobal* indexNameAsGlobal = indexName->expr->as<AstExprGlobal>())
|
||||
{
|
||||
segments.push_back(indexNameAsGlobal->name.value);
|
||||
break;
|
||||
}
|
||||
else if (AstExprLocal* indexNameAsLocal = indexName->expr->as<AstExprLocal>())
|
||||
{
|
||||
segments.push_back(indexNameAsLocal->local->name.value);
|
||||
break;
|
||||
}
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
std::reverse(segments.begin(), segments.end());
|
||||
return segments;
|
||||
}
|
||||
|
||||
std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const std::vector<std::string_view>& segments)
|
||||
{
|
||||
if (segments.empty())
|
||||
return std::nullopt;
|
||||
|
||||
std::vector<std::string_view> result;
|
||||
|
||||
auto it = segments.begin();
|
||||
|
||||
if (*it == "script" && !currentModuleName.empty())
|
||||
{
|
||||
result = split(currentModuleName, '/');
|
||||
++it;
|
||||
}
|
||||
|
||||
for (; it != segments.end(); ++it)
|
||||
{
|
||||
if (result.size() > 1 && *it == "Parent")
|
||||
result.pop_back();
|
||||
else
|
||||
result.push_back(*it);
|
||||
}
|
||||
|
||||
return join(result, "/");
|
||||
}
|
||||
|
||||
std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const AstExpr& pathExpr)
|
||||
{
|
||||
std::vector<std::string_view> segments = parsePathExpr(pathExpr);
|
||||
return pathExprToModuleName(currentModuleName, segments);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
ErrorVec accumulateErrors(
|
||||
const std::unordered_map<ModuleName, SourceNode>& sourceNodes, const std::unordered_map<ModuleName, ModulePtr>& modules, const ModuleName& name)
|
||||
{
|
||||
std::unordered_set<ModuleName> seen;
|
||||
std::vector<ModuleName> queue{name};
|
||||
|
||||
ErrorVec result;
|
||||
|
||||
while (!queue.empty())
|
||||
{
|
||||
ModuleName next = std::move(queue.back());
|
||||
queue.pop_back();
|
||||
|
||||
if (seen.count(next))
|
||||
continue;
|
||||
seen.insert(next);
|
||||
|
||||
auto it = sourceNodes.find(next);
|
||||
if (it == sourceNodes.end())
|
||||
continue;
|
||||
|
||||
const SourceNode& sourceNode = it->second;
|
||||
queue.insert(queue.end(), sourceNode.requires.begin(), sourceNode.requires.end());
|
||||
|
||||
// FIXME: If a module has a syntax error, we won't be able to re-report it here.
|
||||
// The solution is probably to move errors from Module to SourceNode
|
||||
|
||||
auto it2 = modules.find(next);
|
||||
if (it2 == modules.end())
|
||||
continue;
|
||||
|
||||
Module& module = *it2->second;
|
||||
|
||||
std::sort(module.errors.begin(), module.errors.end(), [](const TypeError& e1, const TypeError& e2) -> bool {
|
||||
return e1.location.begin > e2.location.begin;
|
||||
});
|
||||
|
||||
result.insert(result.end(), module.errors.begin(), module.errors.end());
|
||||
}
|
||||
|
||||
std::reverse(result.begin(), result.end());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
// Given a source node (start), find all requires that start a transitive dependency path that ends back at start
|
||||
// For each such path, record the full path and the location of the require in the starting module.
|
||||
// Note that this is O(V^2) for a fully connected graph and produces O(V) paths of length O(V)
|
||||
// However, when the graph is acyclic, this is O(V), as well as when only the first cycle is needed (stopAtFirst=true)
|
||||
std::vector<RequireCycle> getRequireCycles(
|
||||
const std::unordered_map<ModuleName, SourceNode>& sourceNodes, const SourceNode* start, bool stopAtFirst = false)
|
||||
{
|
||||
std::vector<RequireCycle> result;
|
||||
|
||||
DenseHashSet<const SourceNode*> seen(nullptr);
|
||||
std::vector<const SourceNode*> stack;
|
||||
std::vector<const SourceNode*> path;
|
||||
|
||||
for (const auto& [depName, depLocation] : start->requireLocations)
|
||||
{
|
||||
std::vector<ModuleName> cycle;
|
||||
|
||||
auto dit = sourceNodes.find(depName);
|
||||
if (dit == sourceNodes.end())
|
||||
continue;
|
||||
|
||||
stack.push_back(&dit->second);
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
const SourceNode* top = stack.back();
|
||||
stack.pop_back();
|
||||
|
||||
if (top == nullptr)
|
||||
{
|
||||
// special marker for post-order processing
|
||||
LUAU_ASSERT(!path.empty());
|
||||
top = path.back();
|
||||
path.pop_back();
|
||||
|
||||
// we reached the node! path must form a cycle now
|
||||
if (top == start)
|
||||
{
|
||||
for (const SourceNode* node : path)
|
||||
cycle.push_back(node->name);
|
||||
|
||||
cycle.push_back(top->name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (!seen.contains(top))
|
||||
{
|
||||
seen.insert(top);
|
||||
|
||||
// push marker for post-order processing
|
||||
path.push_back(top);
|
||||
stack.push_back(nullptr);
|
||||
|
||||
// note: we push require edges in the opposite order
|
||||
// because it's a stack, the last edge to be pushed gets processed first
|
||||
// this ensures that the cyclic path we report is the first one in DFS order
|
||||
for (size_t i = top->requireLocations.size(); i > 0; --i)
|
||||
{
|
||||
const ModuleName& reqName = top->requireLocations[i - 1].first;
|
||||
|
||||
auto rit = sourceNodes.find(reqName);
|
||||
if (rit != sourceNodes.end())
|
||||
stack.push_back(&rit->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
path.clear();
|
||||
stack.clear();
|
||||
|
||||
if (!cycle.empty())
|
||||
{
|
||||
result.push_back({depLocation, std::move(cycle)});
|
||||
|
||||
if (stopAtFirst)
|
||||
return result;
|
||||
|
||||
// note: if we didn't find a cycle, all nodes that we've seen don't depend [transitively] on start
|
||||
// so it's safe to *only* clear seen vector when we find a cycle
|
||||
// if we don't do it, we will not have correct reporting for some cycles
|
||||
seen.clear();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
double getTimestamp()
|
||||
{
|
||||
using namespace std::chrono;
|
||||
return double(duration_cast<nanoseconds>(high_resolution_clock::now().time_since_epoch()).count()) / 1e9;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Frontend::Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, const FrontendOptions& options)
|
||||
: fileResolver(fileResolver)
|
||||
, moduleResolver(this)
|
||||
, moduleResolverForAutocomplete(this)
|
||||
, typeChecker(&moduleResolver, &iceHandler)
|
||||
, typeCheckerForAutocomplete(&moduleResolverForAutocomplete, &iceHandler)
|
||||
, configResolver(configResolver)
|
||||
, options(options)
|
||||
{
|
||||
}
|
||||
|
||||
FrontendModuleResolver::FrontendModuleResolver(Frontend* frontend)
|
||||
: frontend(frontend)
|
||||
{
|
||||
}
|
||||
|
||||
CheckResult Frontend::check(const ModuleName& name)
|
||||
{
|
||||
CheckResult checkResult;
|
||||
|
||||
auto it = sourceNodes.find(name);
|
||||
if (it != sourceNodes.end() && !it->second.dirty)
|
||||
{
|
||||
// No recheck required.
|
||||
auto it2 = moduleResolver.modules.find(name);
|
||||
if (it2 == moduleResolver.modules.end() || it2->second == nullptr)
|
||||
throw std::runtime_error("Frontend::modules does not have data for " + name);
|
||||
|
||||
return CheckResult{accumulateErrors(sourceNodes, moduleResolver.modules, name)};
|
||||
}
|
||||
|
||||
std::vector<ModuleName> buildQueue;
|
||||
bool cycleDetected = parseGraph(buildQueue, checkResult, name);
|
||||
|
||||
// Keep track of which AST nodes we've reported cycles in
|
||||
std::unordered_set<AstNode*> reportedCycles;
|
||||
|
||||
for (const ModuleName& moduleName : buildQueue)
|
||||
{
|
||||
LUAU_ASSERT(sourceNodes.count(moduleName));
|
||||
SourceNode& sourceNode = sourceNodes[moduleName];
|
||||
|
||||
if (!sourceNode.dirty)
|
||||
continue;
|
||||
|
||||
LUAU_ASSERT(sourceModules.count(moduleName));
|
||||
SourceModule& sourceModule = sourceModules[moduleName];
|
||||
|
||||
const Config& config = configResolver->getConfig(moduleName);
|
||||
|
||||
Mode mode = sourceModule.mode.value_or(config.mode);
|
||||
|
||||
ScopePtr environmentScope = getModuleEnvironment(sourceModule, config);
|
||||
|
||||
double timestamp = getTimestamp();
|
||||
|
||||
std::vector<RequireCycle> requireCycles;
|
||||
|
||||
// in NoCheck mode we only need to compute the value of .cyclic for typeck
|
||||
// in the future we could replace toposort with an algorithm that can flag cyclic nodes by itself
|
||||
// however, for now getRequireCycles isn't expensive in practice on the cases we care about, and long term
|
||||
// all correct programs must be acyclic so this code triggers rarely
|
||||
if (cycleDetected)
|
||||
requireCycles = getRequireCycles(sourceNodes, &sourceNode, mode == Mode::NoCheck);
|
||||
|
||||
// This is used by the type checker to replace the resulting type of cyclic modules with any
|
||||
sourceModule.cyclic = !requireCycles.empty();
|
||||
|
||||
ModulePtr module = typeChecker.check(sourceModule, mode, environmentScope);
|
||||
|
||||
// If we're typechecking twice, we do so.
|
||||
// The second typecheck is always in strict mode with DM awareness
|
||||
// to provide better typen information for IDE features.
|
||||
if (options.typecheckTwice && FFlag::LuauSecondTypecheckKnowsTheDataModel)
|
||||
{
|
||||
ModulePtr moduleForAutocomplete = typeCheckerForAutocomplete.check(sourceModule, Mode::Strict);
|
||||
moduleResolverForAutocomplete.modules[moduleName] = moduleForAutocomplete;
|
||||
}
|
||||
else if (options.retainFullTypeGraphs && options.typecheckTwice && mode != Mode::Strict)
|
||||
{
|
||||
ModulePtr strictModule = typeChecker.check(sourceModule, Mode::Strict, environmentScope);
|
||||
module->astTypes.clear();
|
||||
module->astOriginalCallTypes.clear();
|
||||
module->astExpectedTypes.clear();
|
||||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
|
||||
for (const auto& [expr, strictTy] : strictModule->astTypes)
|
||||
module->astTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks);
|
||||
|
||||
for (const auto& [expr, strictTy] : strictModule->astOriginalCallTypes)
|
||||
module->astOriginalCallTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks);
|
||||
|
||||
for (const auto& [expr, strictTy] : strictModule->astExpectedTypes)
|
||||
module->astExpectedTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks);
|
||||
}
|
||||
|
||||
stats.timeCheck += getTimestamp() - timestamp;
|
||||
stats.filesStrict += mode == Mode::Strict;
|
||||
stats.filesNonstrict += mode == Mode::Nonstrict;
|
||||
|
||||
if (module == nullptr)
|
||||
throw std::runtime_error("Frontend::check produced a nullptr module for " + moduleName);
|
||||
|
||||
if (!options.retainFullTypeGraphs)
|
||||
{
|
||||
// copyErrors needs to allocate into interfaceTypes as it copies
|
||||
// types out of internalTypes, so we unfreeze it here.
|
||||
unfreeze(module->interfaceTypes);
|
||||
copyErrors(module->errors, module->interfaceTypes);
|
||||
freeze(module->interfaceTypes);
|
||||
|
||||
module->internalTypes.clear();
|
||||
module->astTypes.clear();
|
||||
module->astExpectedTypes.clear();
|
||||
module->astOriginalCallTypes.clear();
|
||||
}
|
||||
|
||||
if (mode != Mode::NoCheck)
|
||||
{
|
||||
for (const RequireCycle& cyc : requireCycles)
|
||||
{
|
||||
TypeError te{cyc.location, moduleName, ModuleHasCyclicDependency{cyc.path}};
|
||||
|
||||
module->errors.push_back(te);
|
||||
}
|
||||
}
|
||||
|
||||
ErrorVec parseErrors;
|
||||
|
||||
for (const ParseError& pe : sourceModule.parseErrors)
|
||||
parseErrors.push_back(TypeError{pe.getLocation(), moduleName, SyntaxError{pe.what()}});
|
||||
|
||||
module->errors.insert(module->errors.begin(), parseErrors.begin(), parseErrors.end());
|
||||
|
||||
checkResult.errors.insert(checkResult.errors.end(), module->errors.begin(), module->errors.end());
|
||||
|
||||
moduleResolver.modules[moduleName] = std::move(module);
|
||||
sourceNode.dirty = false;
|
||||
}
|
||||
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
bool Frontend::parseGraph(std::vector<ModuleName>& buildQueue, CheckResult& checkResult, const ModuleName& root)
|
||||
{
|
||||
// https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search
|
||||
enum Mark
|
||||
{
|
||||
None,
|
||||
Temporary,
|
||||
Permanent
|
||||
};
|
||||
|
||||
DenseHashMap<SourceNode*, Mark> seen(nullptr);
|
||||
std::vector<SourceNode*> stack;
|
||||
std::vector<SourceNode*> path;
|
||||
bool cyclic = false;
|
||||
|
||||
{
|
||||
auto [sourceNode, _] = getSourceNode(checkResult, root);
|
||||
if (sourceNode)
|
||||
stack.push_back(sourceNode);
|
||||
}
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
SourceNode* top = stack.back();
|
||||
stack.pop_back();
|
||||
|
||||
if (top == nullptr)
|
||||
{
|
||||
// special marker for post-order processing
|
||||
LUAU_ASSERT(!path.empty());
|
||||
|
||||
top = path.back();
|
||||
path.pop_back();
|
||||
|
||||
// note: topseen ref gets invalidated in any seen[] access, beware - only one seen[] access per iteration!
|
||||
Mark& topseen = seen[top];
|
||||
LUAU_ASSERT(topseen == Temporary);
|
||||
topseen = Permanent;
|
||||
|
||||
buildQueue.push_back(top->name);
|
||||
}
|
||||
else
|
||||
{
|
||||
// note: topseen ref gets invalidated in any seen[] access, beware - only one seen[] access per iteration!
|
||||
Mark& topseen = seen[top];
|
||||
|
||||
if (topseen != None)
|
||||
{
|
||||
cyclic |= topseen == Temporary;
|
||||
continue;
|
||||
}
|
||||
|
||||
topseen = Temporary;
|
||||
|
||||
// push marker for post-order processing
|
||||
stack.push_back(nullptr);
|
||||
path.push_back(top);
|
||||
|
||||
// push children
|
||||
for (const ModuleName& dep : top->requires)
|
||||
{
|
||||
auto it = sourceNodes.find(dep);
|
||||
if (it != sourceNodes.end())
|
||||
{
|
||||
// this is a critical optimization: we do *not* traverse non-dirty subtrees.
|
||||
// this relies on the fact that markDirty marks reverse-dependencies dirty as well
|
||||
// thus if a node is not dirty, all its transitive deps aren't dirty, which means that they won't ever need
|
||||
// to be built, *and* can't form a cycle with any nodes we did process.
|
||||
if (!it->second.dirty)
|
||||
continue;
|
||||
|
||||
// note: this check is technically redundant *except* that getSourceNode has somewhat broken memoization
|
||||
// calling getSourceNode twice in succession will reparse the file, since getSourceNode leaves dirty flag set
|
||||
if (seen.contains(&it->second))
|
||||
{
|
||||
stack.push_back(&it->second);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
auto [sourceNode, _] = getSourceNode(checkResult, dep);
|
||||
if (sourceNode)
|
||||
{
|
||||
stack.push_back(sourceNode);
|
||||
|
||||
// note: this assignment is paired with .contains() check above and effectively deduplicates getSourceNode()
|
||||
seen[sourceNode] = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cyclic;
|
||||
}
|
||||
|
||||
ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config& config)
|
||||
{
|
||||
ScopePtr result = typeChecker.globalScope;
|
||||
|
||||
if (module.environmentName)
|
||||
result = getEnvironmentScope(*module.environmentName);
|
||||
|
||||
if (!config.globals.empty())
|
||||
{
|
||||
result = std::make_shared<Scope>(result);
|
||||
|
||||
for (const std::string& global : config.globals)
|
||||
{
|
||||
AstName name = module.names->get(global.c_str());
|
||||
|
||||
if (name.value)
|
||||
result->bindings[name].typeId = typeChecker.anyType;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
LintResult Frontend::lint(const ModuleName& name, std::optional<Luau::LintOptions> enabledLintWarnings)
|
||||
{
|
||||
CheckResult checkResult;
|
||||
auto [_sourceNode, sourceModule] = getSourceNode(checkResult, name);
|
||||
|
||||
if (!sourceModule)
|
||||
return LintResult{}; // FIXME: We really should do something a bit more obvious when a file is too broken to lint.
|
||||
|
||||
return lint(*sourceModule, enabledLintWarnings);
|
||||
}
|
||||
|
||||
std::pair<SourceModule, LintResult> Frontend::lintFragment(std::string_view source, std::optional<Luau::LintOptions> enabledLintWarnings)
|
||||
{
|
||||
const Config& config = configResolver->getConfig("");
|
||||
|
||||
SourceModule sourceModule = parse(ModuleName{}, source, config.parseOptions);
|
||||
|
||||
Luau::LintOptions lintOptions = enabledLintWarnings.value_or(config.enabledLint);
|
||||
lintOptions.warningMask &= sourceModule.ignoreLints;
|
||||
|
||||
double timestamp = getTimestamp();
|
||||
|
||||
std::vector<LintWarning> warnings =
|
||||
Luau::lint(sourceModule.root, *sourceModule.names.get(), typeChecker.globalScope, nullptr, enabledLintWarnings.value_or(config.enabledLint));
|
||||
|
||||
stats.timeLint += getTimestamp() - timestamp;
|
||||
|
||||
return {std::move(sourceModule), classifyLints(warnings, config)};
|
||||
}
|
||||
|
||||
CheckResult Frontend::check(const SourceModule& module)
|
||||
{
|
||||
const Config& config = configResolver->getConfig(module.name);
|
||||
|
||||
Mode mode = module.mode.value_or(config.mode);
|
||||
|
||||
double timestamp = getTimestamp();
|
||||
|
||||
ModulePtr checkedModule = typeChecker.check(module, mode);
|
||||
|
||||
stats.timeCheck += getTimestamp() - timestamp;
|
||||
stats.filesStrict += mode == Mode::Strict;
|
||||
stats.filesNonstrict += mode == Mode::Nonstrict;
|
||||
|
||||
if (checkedModule == nullptr)
|
||||
throw std::runtime_error("Frontend::check produced a nullptr module for module " + module.name);
|
||||
moduleResolver.modules[module.name] = checkedModule;
|
||||
|
||||
return CheckResult{checkedModule->errors};
|
||||
}
|
||||
|
||||
LintResult Frontend::lint(const SourceModule& module, std::optional<Luau::LintOptions> enabledLintWarnings)
|
||||
{
|
||||
const Config& config = configResolver->getConfig(module.name);
|
||||
|
||||
LintOptions options = enabledLintWarnings.value_or(config.enabledLint);
|
||||
options.warningMask &= ~module.ignoreLints;
|
||||
|
||||
Mode mode = module.mode.value_or(config.mode);
|
||||
if (mode != Mode::NoCheck)
|
||||
{
|
||||
options.disableWarning(Luau::LintWarning::Code_UnknownGlobal);
|
||||
}
|
||||
|
||||
if (mode == Mode::Strict)
|
||||
{
|
||||
options.disableWarning(Luau::LintWarning::Code_ImplicitReturn);
|
||||
}
|
||||
|
||||
ScopePtr environmentScope = getModuleEnvironment(module, config);
|
||||
|
||||
ModulePtr modulePtr = moduleResolver.getModule(module.name);
|
||||
|
||||
double timestamp = getTimestamp();
|
||||
|
||||
std::vector<LintWarning> warnings = Luau::lint(module.root, *module.names, environmentScope, modulePtr.get(), options);
|
||||
|
||||
stats.timeLint += getTimestamp() - timestamp;
|
||||
|
||||
return classifyLints(warnings, config);
|
||||
}
|
||||
|
||||
bool Frontend::isDirty(const ModuleName& name) const
|
||||
{
|
||||
auto it = sourceNodes.find(name);
|
||||
return it == sourceNodes.end() || it->second.dirty;
|
||||
}
|
||||
|
||||
/*
|
||||
* Mark a file as requiring rechecking before its type information can be safely used again.
|
||||
*
|
||||
* I am not particularly pleased with the way each dirty() operation involves a BFS on reverse dependencies.
|
||||
* It would be nice for this function to be O(1)
|
||||
*/
|
||||
void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty)
|
||||
{
|
||||
if (!moduleResolver.modules.count(name))
|
||||
return;
|
||||
|
||||
std::unordered_map<ModuleName, std::vector<ModuleName>> reverseDeps;
|
||||
for (const auto& module : sourceNodes)
|
||||
{
|
||||
for (const auto& dep : module.second.requires)
|
||||
reverseDeps[dep].push_back(module.first);
|
||||
}
|
||||
|
||||
std::vector<ModuleName> queue{name};
|
||||
|
||||
while (!queue.empty())
|
||||
{
|
||||
ModuleName next = std::move(queue.back());
|
||||
queue.pop_back();
|
||||
|
||||
LUAU_ASSERT(sourceNodes.count(next) > 0);
|
||||
SourceNode& sourceNode = sourceNodes[next];
|
||||
|
||||
if (markedDirty)
|
||||
markedDirty->push_back(next);
|
||||
|
||||
if (sourceNode.dirty)
|
||||
continue;
|
||||
|
||||
sourceNode.dirty = true;
|
||||
|
||||
if (0 == reverseDeps.count(name))
|
||||
continue;
|
||||
|
||||
sourceModules.erase(name);
|
||||
|
||||
const std::vector<ModuleName>& dependents = reverseDeps[name];
|
||||
queue.insert(queue.end(), dependents.begin(), dependents.end());
|
||||
}
|
||||
}
|
||||
|
||||
SourceModule* Frontend::getSourceModule(const ModuleName& moduleName)
|
||||
{
|
||||
auto it = sourceModules.find(moduleName);
|
||||
if (it != sourceModules.end())
|
||||
return &it->second;
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) const
|
||||
{
|
||||
return const_cast<Frontend*>(this)->getSourceModule(moduleName);
|
||||
}
|
||||
|
||||
// Read AST into sourceModules if necessary. Trace require()s. Report parse errors.
|
||||
std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(CheckResult& checkResult, const ModuleName& name)
|
||||
{
|
||||
auto it = sourceNodes.find(name);
|
||||
if (it != sourceNodes.end() && !it->second.dirty)
|
||||
{
|
||||
auto moduleIt = sourceModules.find(name);
|
||||
if (moduleIt != sourceModules.end())
|
||||
return {&it->second, &moduleIt->second};
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(!"Everything in sourceNodes should also be in sourceModules");
|
||||
return {&it->second, nullptr};
|
||||
}
|
||||
}
|
||||
|
||||
double timestamp = getTimestamp();
|
||||
|
||||
std::optional<SourceCode> source = fileResolver->readSource(name);
|
||||
std::optional<std::string> environmentName = fileResolver->getEnvironmentForModule(name);
|
||||
|
||||
stats.timeRead += getTimestamp() - timestamp;
|
||||
|
||||
if (!source)
|
||||
{
|
||||
sourceModules.erase(name);
|
||||
return {nullptr, nullptr};
|
||||
}
|
||||
|
||||
const Config& config = configResolver->getConfig(name);
|
||||
ParseOptions opts = config.parseOptions;
|
||||
opts.captureComments = true;
|
||||
SourceModule result = parse(name, source->source, opts);
|
||||
result.type = source->type;
|
||||
|
||||
RequireTraceResult& requireTrace = requires[name];
|
||||
requireTrace = traceRequires(fileResolver, result.root, name);
|
||||
|
||||
SourceNode& sourceNode = sourceNodes[name];
|
||||
SourceModule& sourceModule = sourceModules[name];
|
||||
|
||||
sourceModule = std::move(result);
|
||||
sourceModule.environmentName = environmentName;
|
||||
|
||||
sourceNode.name = name;
|
||||
sourceNode.requires.clear();
|
||||
sourceNode.requireLocations.clear();
|
||||
sourceNode.dirty = true;
|
||||
|
||||
for (const auto& [moduleName, location] : requireTrace.requires)
|
||||
sourceNode.requires.insert(moduleName);
|
||||
|
||||
sourceNode.requireLocations = requireTrace.requires;
|
||||
|
||||
return {&sourceNode, &sourceModule};
|
||||
}
|
||||
|
||||
/** Try to parse a source file into a SourceModule.
|
||||
*
|
||||
* The logic here is a little bit more complicated than we'd like it to be.
|
||||
*
|
||||
* If a file does not exist, we return none to prevent the Frontend from creating knowledge that this module exists.
|
||||
* If the Frontend thinks that the file exists, it will not produce an "Unknown require" error.
|
||||
*
|
||||
* If the file has syntax errors, we report them and synthesize an empty AST if it's not available.
|
||||
* This suppresses the Unknown require error and allows us to make a best effort to typecheck code that require()s
|
||||
* something that has broken syntax.
|
||||
* We also translate Luau::ParseError into a Luau::TypeError so that we can use a vector<TypeError> to describe the
|
||||
* result of the check()
|
||||
*/
|
||||
SourceModule Frontend::parse(const ModuleName& name, std::string_view src, const ParseOptions& parseOptions)
|
||||
{
|
||||
SourceModule sourceModule;
|
||||
|
||||
double timestamp = getTimestamp();
|
||||
|
||||
auto parseResult = Luau::Parser::parse(src.data(), src.size(), *sourceModule.names, *sourceModule.allocator, parseOptions);
|
||||
|
||||
stats.timeParse += getTimestamp() - timestamp;
|
||||
stats.files++;
|
||||
stats.lines += std::count(src.begin(), src.end(), '\n') + (src.size() && src.back() != '\n');
|
||||
|
||||
if (!parseResult.errors.empty())
|
||||
sourceModule.parseErrors.insert(sourceModule.parseErrors.end(), parseResult.errors.begin(), parseResult.errors.end());
|
||||
|
||||
if (parseResult.errors.empty() || parseResult.root)
|
||||
{
|
||||
sourceModule.root = parseResult.root;
|
||||
sourceModule.mode = parseMode(parseResult.hotcomments);
|
||||
sourceModule.ignoreLints = LintWarning::parseMask(parseResult.hotcomments);
|
||||
}
|
||||
else
|
||||
{
|
||||
sourceModule.root = sourceModule.allocator->alloc<AstStatBlock>(Location{}, AstArray<AstStat*>{nullptr, 0});
|
||||
sourceModule.mode = Mode::NoCheck;
|
||||
}
|
||||
|
||||
sourceModule.name = name;
|
||||
if (parseOptions.captureComments)
|
||||
sourceModule.commentLocations = std::move(parseResult.commentLocations);
|
||||
return sourceModule;
|
||||
}
|
||||
|
||||
std::optional<ModuleInfo> FrontendModuleResolver::resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr)
|
||||
{
|
||||
// FIXME I think this can be pushed into the FileResolver.
|
||||
auto it = frontend->requires.find(currentModuleName);
|
||||
if (it == frontend->requires.end())
|
||||
{
|
||||
// CLI-43699
|
||||
// If we can't find the current module name, that's because we bypassed the frontend's initializer
|
||||
// and called typeChecker.check directly. (This is done by autocompleteSource, for example).
|
||||
// In that case, requires will always fail.
|
||||
if (FFlag::LuauResolveModuleNameWithoutACurrentModule)
|
||||
return std::nullopt;
|
||||
else
|
||||
throw std::runtime_error("Frontend::resolveModuleName: Unknown currentModuleName '" + currentModuleName + "'");
|
||||
}
|
||||
|
||||
const auto& exprs = it->second.exprs;
|
||||
|
||||
const ModuleName* relativeName = exprs.find(&pathExpr);
|
||||
if (!relativeName || relativeName->empty())
|
||||
return std::nullopt;
|
||||
|
||||
if (FFlag::LuauTraceRequireLookupChild)
|
||||
{
|
||||
const bool* optional = it->second.optional.find(&pathExpr);
|
||||
|
||||
return {{*relativeName, optional ? *optional : false}};
|
||||
}
|
||||
else
|
||||
{
|
||||
return {{*relativeName, false}};
|
||||
}
|
||||
}
|
||||
|
||||
const ModulePtr FrontendModuleResolver::getModule(const ModuleName& moduleName) const
|
||||
{
|
||||
auto it = modules.find(moduleName);
|
||||
if (it != modules.end())
|
||||
return it->second;
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool FrontendModuleResolver::moduleExists(const ModuleName& moduleName) const
|
||||
{
|
||||
return frontend->fileResolver->moduleExists(moduleName);
|
||||
}
|
||||
|
||||
std::string FrontendModuleResolver::getHumanReadableModuleName(const ModuleName& moduleName) const
|
||||
{
|
||||
return frontend->fileResolver->getHumanReadableModuleName_(moduleName).value_or(moduleName);
|
||||
}
|
||||
|
||||
ScopePtr Frontend::addEnvironment(const std::string& environmentName)
|
||||
{
|
||||
LUAU_ASSERT(environments.count(environmentName) == 0);
|
||||
|
||||
if (environments.count(environmentName) == 0)
|
||||
{
|
||||
ScopePtr scope = std::make_shared<Scope>(typeChecker.globalScope);
|
||||
environments[environmentName] = scope;
|
||||
return scope;
|
||||
}
|
||||
else
|
||||
return environments[environmentName];
|
||||
}
|
||||
|
||||
ScopePtr Frontend::getEnvironmentScope(const std::string& environmentName)
|
||||
{
|
||||
LUAU_ASSERT(environments.count(environmentName) > 0);
|
||||
|
||||
return environments[environmentName];
|
||||
}
|
||||
|
||||
void Frontend::registerBuiltinDefinition(const std::string& name, std::function<void(TypeChecker&, ScopePtr)> applicator)
|
||||
{
|
||||
LUAU_ASSERT(builtinDefinitions.count(name) == 0);
|
||||
|
||||
if (builtinDefinitions.count(name) == 0)
|
||||
builtinDefinitions[name] = applicator;
|
||||
}
|
||||
|
||||
void Frontend::applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName)
|
||||
{
|
||||
LUAU_ASSERT(builtinDefinitions.count(definitionName) > 0);
|
||||
|
||||
if (builtinDefinitions.count(definitionName) > 0)
|
||||
builtinDefinitions[definitionName](typeChecker, getEnvironmentScope(environmentName));
|
||||
}
|
||||
|
||||
LintResult Frontend::classifyLints(const std::vector<LintWarning>& warnings, const Config& config)
|
||||
{
|
||||
LintResult result;
|
||||
for (const auto& w : warnings)
|
||||
{
|
||||
if (config.lintErrors || config.fatalLint.isEnabled(w.code))
|
||||
result.errors.push_back(w);
|
||||
else
|
||||
result.warnings.push_back(w);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void Frontend::clearStats()
|
||||
{
|
||||
stats = {};
|
||||
}
|
||||
|
||||
void Frontend::clear()
|
||||
{
|
||||
sourceNodes.clear();
|
||||
sourceModules.clear();
|
||||
moduleResolver.modules.clear();
|
||||
moduleResolverForAutocomplete.modules.clear();
|
||||
requires.clear();
|
||||
}
|
||||
|
||||
} // namespace Luau
|
280
Analysis/src/IostreamHelpers.cpp
Normal file
280
Analysis/src/IostreamHelpers.cpp
Normal file
|
@ -0,0 +1,280 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/IostreamHelpers.h"
|
||||
#include "Luau/ToString.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const Position& position)
|
||||
{
|
||||
return stream << "{ line = " << position.line << ", col = " << position.column << " }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const Location& location)
|
||||
{
|
||||
return stream << "Location { " << location.begin << ", " << location.end << " }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const AstName& name)
|
||||
{
|
||||
if (name.value)
|
||||
return stream << name.value;
|
||||
else
|
||||
return stream << "<empty>";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const TypeMismatch& tm)
|
||||
{
|
||||
return stream << "TypeMismatch { " << toString(tm.wantedType) << ", " << toString(tm.givenType) << " }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const TypeError& error)
|
||||
{
|
||||
return stream << "TypeError { \"" << error.moduleName << "\", " << error.location << ", " << error.data << " }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const UnknownSymbol& error)
|
||||
{
|
||||
return stream << "UnknownSymbol { " << error.name << " , context " << error.context << " }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const UnknownProperty& error)
|
||||
{
|
||||
return stream << "UnknownProperty { " << toString(error.table) << ", key = " << error.key << " }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const NotATable& ge)
|
||||
{
|
||||
return stream << "NotATable { " << toString(ge.ty) << " }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const CannotExtendTable& error)
|
||||
{
|
||||
return stream << "CannotExtendTable { " << toString(error.tableType) << ", context " << error.context << ", prop \"" << error.prop << "\" }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const OnlyTablesCanHaveMethods& error)
|
||||
{
|
||||
return stream << "OnlyTablesCanHaveMethods { " << toString(error.tableType) << " }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const DuplicateTypeDefinition& error)
|
||||
{
|
||||
return stream << "DuplicateTypeDefinition { " << error.name << " }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const CountMismatch& error)
|
||||
{
|
||||
return stream << "CountMismatch { expected " << error.expected << ", got " << error.actual << ", context " << error.context << " }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const FunctionDoesNotTakeSelf&)
|
||||
{
|
||||
return stream << "FunctionDoesNotTakeSelf { }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const FunctionRequiresSelf& error)
|
||||
{
|
||||
return stream << "FunctionRequiresSelf { extraNils " << error.requiredExtraNils << " }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const OccursCheckFailed&)
|
||||
{
|
||||
return stream << "OccursCheckFailed { }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const UnknownRequire& error)
|
||||
{
|
||||
return stream << "UnknownRequire { " << error.modulePath << " }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const IncorrectGenericParameterCount& error)
|
||||
{
|
||||
stream << "IncorrectGenericParameterCount { name = " << error.name;
|
||||
|
||||
if (!error.typeFun.typeParams.empty())
|
||||
{
|
||||
stream << "<";
|
||||
bool first = true;
|
||||
for (TypeId t : error.typeFun.typeParams)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
stream << ", ";
|
||||
|
||||
stream << toString(t);
|
||||
}
|
||||
stream << ">";
|
||||
}
|
||||
|
||||
stream << ", typeFun = " << toString(error.typeFun.type) << ", actualCount = " << error.actualParameters << " }";
|
||||
return stream;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const SyntaxError& ge)
|
||||
{
|
||||
return stream << "SyntaxError { " << ge.message << " }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const CodeTooComplex&)
|
||||
{
|
||||
return stream << "CodeTooComplex {}";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const UnificationTooComplex&)
|
||||
{
|
||||
return stream << "UnificationTooComplex {}";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const UnknownPropButFoundLikeProp& e)
|
||||
{
|
||||
stream << "UnknownPropButFoundLikeProp { key = '" << e.key << "', suggested = { ";
|
||||
|
||||
bool first = true;
|
||||
for (Name name : e.candidates)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
stream << ", ";
|
||||
|
||||
stream << "'" << name << "'";
|
||||
}
|
||||
|
||||
return stream << " }, table = " << toString(e.table) << " } ";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const GenericError& ge)
|
||||
{
|
||||
return stream << "GenericError { " << ge.message << " }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const CannotCallNonFunction& e)
|
||||
{
|
||||
return stream << "CannotCallNonFunction { " << toString(e.ty) << " }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const FunctionExitsWithoutReturning& error)
|
||||
{
|
||||
return stream << "FunctionExitsWithoutReturning {" << toString(error.expectedReturnType) << "}";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const ExtraInformation& e)
|
||||
{
|
||||
return stream << "ExtraInformation { " << e.message << " }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const DeprecatedApiUsed& e)
|
||||
{
|
||||
return stream << "DeprecatedApiUsed { " << e.symbol << ", useInstead = " << e.useInstead << " }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const ModuleHasCyclicDependency& e)
|
||||
{
|
||||
stream << "ModuleHasCyclicDependency {";
|
||||
|
||||
bool first = true;
|
||||
for (const ModuleName& name : e.cycle)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
stream << ", ";
|
||||
|
||||
stream << name;
|
||||
}
|
||||
|
||||
return stream << "}";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const IllegalRequire& e)
|
||||
{
|
||||
return stream << "IllegalRequire { " << e.moduleName << ", reason = " << e.reason << " }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const MissingProperties& e)
|
||||
{
|
||||
stream << "MissingProperties { superType = '" << toString(e.superType) << "', subType = '" << toString(e.subType) << "', properties = { ";
|
||||
|
||||
bool first = true;
|
||||
for (Name name : e.properties)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
stream << ", ";
|
||||
|
||||
stream << "'" << name << "'";
|
||||
}
|
||||
|
||||
return stream << " }, context " << e.context << " } ";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const DuplicateGenericParameter& error)
|
||||
{
|
||||
return stream << "DuplicateGenericParameter { " + error.parameterName + " }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const CannotInferBinaryOperation& error)
|
||||
{
|
||||
return stream << "CannotInferBinaryOperation { op = " + toString(error.op) + ", suggested = '" +
|
||||
(error.suggestedToAnnotate ? *error.suggestedToAnnotate : "") + "', kind "
|
||||
<< error.kind << "}";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const SwappedGenericTypeParameter& error)
|
||||
{
|
||||
return stream << "SwappedGenericTypeParameter { name = '" + error.name + "', kind = " + std::to_string(error.kind) + " }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const OptionalValueAccess& error)
|
||||
{
|
||||
return stream << "OptionalValueAccess { optional = '" + toString(error.optional) + "' }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const MissingUnionProperty& error)
|
||||
{
|
||||
stream << "MissingUnionProperty { type = '" + toString(error.type) + "', missing = { ";
|
||||
|
||||
bool first = true;
|
||||
for (auto ty : error.missing)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
stream << ", ";
|
||||
|
||||
stream << "'" << toString(ty) << "'";
|
||||
}
|
||||
|
||||
return stream << " }, key = '" + error.key + "' }";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const TableState& tv)
|
||||
{
|
||||
return stream << static_cast<std::underlying_type<TableState>::type>(tv);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const TypeVar& tv)
|
||||
{
|
||||
return stream << toString(tv);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const TypePackVar& tv)
|
||||
{
|
||||
return stream << toString(tv);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& lhs, const TypeErrorData& ted)
|
||||
{
|
||||
Luau::visit(
|
||||
[&](const auto& a) {
|
||||
lhs << a;
|
||||
},
|
||||
ted);
|
||||
|
||||
return lhs;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
1041
Analysis/src/JsonEncoder.cpp
Normal file
1041
Analysis/src/JsonEncoder.cpp
Normal file
File diff suppressed because it is too large
Load diff
2568
Analysis/src/Linter.cpp
Normal file
2568
Analysis/src/Linter.cpp
Normal file
File diff suppressed because it is too large
Load diff
521
Analysis/src/Module.cpp
Normal file
521
Analysis/src/Module.cpp
Normal file
|
@ -0,0 +1,521 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Module.h"
|
||||
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/VisitTypeVar.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false)
|
||||
LUAU_FASTFLAG(LuauSecondTypecheckKnowsTheDataModel)
|
||||
LUAU_FASTFLAG(LuauCaptureBrokenCommentSpans)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
static bool contains(Position pos, Comment comment)
|
||||
{
|
||||
if (comment.location.contains(pos))
|
||||
return true;
|
||||
else if (FFlag::LuauCaptureBrokenCommentSpans && comment.type == Lexeme::BrokenComment &&
|
||||
comment.location.begin <= pos) // Broken comments are broken specifically because they don't have an end
|
||||
return true;
|
||||
else if (comment.type == Lexeme::Comment && comment.location.end == pos)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isWithinComment(const SourceModule& sourceModule, Position pos)
|
||||
{
|
||||
auto iter = std::lower_bound(sourceModule.commentLocations.begin(), sourceModule.commentLocations.end(),
|
||||
Comment{Lexeme::Comment, Location{pos, pos}}, [](const Comment& a, const Comment& b) {
|
||||
return a.location.end < b.location.end;
|
||||
});
|
||||
|
||||
if (iter == sourceModule.commentLocations.end())
|
||||
return false;
|
||||
|
||||
if (contains(pos, *iter))
|
||||
return true;
|
||||
|
||||
// Due to the nature of std::lower_bound, it is possible that iter points at a comment that ends
|
||||
// at pos. We'll try the next comment, if it exists.
|
||||
++iter;
|
||||
if (iter == sourceModule.commentLocations.end())
|
||||
return false;
|
||||
|
||||
return contains(pos, *iter);
|
||||
}
|
||||
|
||||
void TypeArena::clear()
|
||||
{
|
||||
typeVars.clear();
|
||||
typePacks.clear();
|
||||
}
|
||||
|
||||
TypeId TypeArena::addTV(TypeVar&& tv)
|
||||
{
|
||||
TypeId allocated = typeVars.allocate(std::move(tv));
|
||||
|
||||
if (FFlag::DebugLuauTrackOwningArena)
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
}
|
||||
|
||||
TypeId TypeArena::freshType(TypeLevel level)
|
||||
{
|
||||
TypeId allocated = typeVars.allocate(FreeTypeVar{level});
|
||||
|
||||
if (FFlag::DebugLuauTrackOwningArena)
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
}
|
||||
|
||||
TypePackId TypeArena::addTypePack(std::initializer_list<TypeId> types)
|
||||
{
|
||||
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
|
||||
|
||||
if (FFlag::DebugLuauTrackOwningArena)
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
}
|
||||
|
||||
TypePackId TypeArena::addTypePack(std::vector<TypeId> types)
|
||||
{
|
||||
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
|
||||
|
||||
if (FFlag::DebugLuauTrackOwningArena)
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
}
|
||||
|
||||
TypePackId TypeArena::addTypePack(TypePack tp)
|
||||
{
|
||||
TypePackId allocated = typePacks.allocate(std::move(tp));
|
||||
|
||||
if (FFlag::DebugLuauTrackOwningArena)
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
}
|
||||
|
||||
TypePackId TypeArena::addTypePack(TypePackVar tp)
|
||||
{
|
||||
TypePackId allocated = typePacks.allocate(std::move(tp));
|
||||
|
||||
if (FFlag::DebugLuauTrackOwningArena)
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
}
|
||||
|
||||
using SeenTypes = std::unordered_map<TypeId, TypeId>;
|
||||
using SeenTypePacks = std::unordered_map<TypePackId, TypePackId>;
|
||||
|
||||
TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType);
|
||||
TypeId clone(TypeId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType);
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct TypePackCloner;
|
||||
|
||||
/*
|
||||
* Both TypeCloner and TypePackCloner work by depositing the requested type variable into the appropriate 'seen' set.
|
||||
* They do not return anything because their sole consumer (the deepClone function) already has a pointer into this storage.
|
||||
*/
|
||||
|
||||
struct TypeCloner
|
||||
{
|
||||
TypeCloner(TypeArena& dest, TypeId typeId, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks)
|
||||
: dest(dest)
|
||||
, typeId(typeId)
|
||||
, seenTypes(seenTypes)
|
||||
, seenTypePacks(seenTypePacks)
|
||||
{
|
||||
}
|
||||
|
||||
TypeArena& dest;
|
||||
TypeId typeId;
|
||||
SeenTypes& seenTypes;
|
||||
SeenTypePacks& seenTypePacks;
|
||||
|
||||
bool* encounteredFreeType = nullptr;
|
||||
|
||||
template<typename T>
|
||||
void defaultClone(const T& t);
|
||||
|
||||
void operator()(const Unifiable::Free& t);
|
||||
void operator()(const Unifiable::Generic& t);
|
||||
void operator()(const Unifiable::Bound<TypeId>& t);
|
||||
void operator()(const Unifiable::Error& t);
|
||||
void operator()(const PrimitiveTypeVar& t);
|
||||
void operator()(const FunctionTypeVar& t);
|
||||
void operator()(const TableTypeVar& t);
|
||||
void operator()(const MetatableTypeVar& t);
|
||||
void operator()(const ClassTypeVar& t);
|
||||
void operator()(const AnyTypeVar& t);
|
||||
void operator()(const UnionTypeVar& t);
|
||||
void operator()(const IntersectionTypeVar& t);
|
||||
void operator()(const LazyTypeVar& t);
|
||||
};
|
||||
|
||||
struct TypePackCloner
|
||||
{
|
||||
TypeArena& dest;
|
||||
TypePackId typePackId;
|
||||
SeenTypes& seenTypes;
|
||||
SeenTypePacks& seenTypePacks;
|
||||
bool* encounteredFreeType = nullptr;
|
||||
|
||||
TypePackCloner(TypeArena& dest, TypePackId typePackId, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks)
|
||||
: dest(dest)
|
||||
, typePackId(typePackId)
|
||||
, seenTypes(seenTypes)
|
||||
, seenTypePacks(seenTypePacks)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void defaultClone(const T& t)
|
||||
{
|
||||
TypePackId cloned = dest.typePacks.allocate(t);
|
||||
seenTypePacks[typePackId] = cloned;
|
||||
}
|
||||
|
||||
void operator()(const Unifiable::Free& t)
|
||||
{
|
||||
if (encounteredFreeType)
|
||||
*encounteredFreeType = true;
|
||||
|
||||
seenTypePacks[typePackId] = dest.typePacks.allocate(TypePackVar{Unifiable::Error{}});
|
||||
}
|
||||
|
||||
void operator()(const Unifiable::Generic& t)
|
||||
{
|
||||
defaultClone(t);
|
||||
}
|
||||
void operator()(const Unifiable::Error& t)
|
||||
{
|
||||
defaultClone(t);
|
||||
}
|
||||
|
||||
// While we are a-cloning, we can flatten out bound TypeVars and make things a bit tighter.
|
||||
// We just need to be sure that we rewrite pointers both to the binder and the bindee to the same pointer.
|
||||
void operator()(const Unifiable::Bound<TypePackId>& t)
|
||||
{
|
||||
TypePackId cloned = clone(t.boundTo, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
seenTypePacks[typePackId] = cloned;
|
||||
}
|
||||
|
||||
void operator()(const VariadicTypePack& t)
|
||||
{
|
||||
TypePackId cloned = dest.typePacks.allocate(VariadicTypePack{clone(t.ty, dest, seenTypes, seenTypePacks, encounteredFreeType)});
|
||||
seenTypePacks[typePackId] = cloned;
|
||||
}
|
||||
|
||||
void operator()(const TypePack& t)
|
||||
{
|
||||
TypePackId cloned = dest.typePacks.allocate(TypePack{});
|
||||
TypePack* destTp = getMutable<TypePack>(cloned);
|
||||
LUAU_ASSERT(destTp != nullptr);
|
||||
seenTypePacks[typePackId] = cloned;
|
||||
|
||||
for (TypeId ty : t.head)
|
||||
destTp->head.push_back(clone(ty, dest, seenTypes, seenTypePacks, encounteredFreeType));
|
||||
|
||||
if (t.tail)
|
||||
destTp->tail = clone(*t.tail, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
void TypeCloner::defaultClone(const T& t)
|
||||
{
|
||||
TypeId cloned = dest.typeVars.allocate(t);
|
||||
seenTypes[typeId] = cloned;
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const Unifiable::Free& t)
|
||||
{
|
||||
if (encounteredFreeType)
|
||||
*encounteredFreeType = true;
|
||||
|
||||
seenTypes[typeId] = dest.typeVars.allocate(ErrorTypeVar{});
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const Unifiable::Generic& t)
|
||||
{
|
||||
defaultClone(t);
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const Unifiable::Bound<TypeId>& t)
|
||||
{
|
||||
TypeId boundTo = clone(t.boundTo, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
seenTypes[typeId] = boundTo;
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const Unifiable::Error& t)
|
||||
{
|
||||
defaultClone(t);
|
||||
}
|
||||
void TypeCloner::operator()(const PrimitiveTypeVar& t)
|
||||
{
|
||||
defaultClone(t);
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const FunctionTypeVar& t)
|
||||
{
|
||||
TypeId result = dest.typeVars.allocate(FunctionTypeVar{TypeLevel{0, 0}, {}, {}, nullptr, nullptr, t.definition, t.hasSelf});
|
||||
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(result);
|
||||
LUAU_ASSERT(ftv != nullptr);
|
||||
|
||||
seenTypes[typeId] = result;
|
||||
|
||||
for (TypeId generic : t.generics)
|
||||
ftv->generics.push_back(clone(generic, dest, seenTypes, seenTypePacks, encounteredFreeType));
|
||||
|
||||
for (TypePackId genericPack : t.genericPacks)
|
||||
ftv->genericPacks.push_back(clone(genericPack, dest, seenTypes, seenTypePacks, encounteredFreeType));
|
||||
|
||||
if (FFlag::LuauSecondTypecheckKnowsTheDataModel)
|
||||
ftv->tags = t.tags;
|
||||
|
||||
ftv->argTypes = clone(t.argTypes, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
ftv->argNames = t.argNames;
|
||||
ftv->retType = clone(t.retType, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const TableTypeVar& t)
|
||||
{
|
||||
TypeId result = dest.typeVars.allocate(TableTypeVar{});
|
||||
TableTypeVar* ttv = getMutable<TableTypeVar>(result);
|
||||
LUAU_ASSERT(ttv != nullptr);
|
||||
|
||||
*ttv = t;
|
||||
|
||||
seenTypes[typeId] = result;
|
||||
|
||||
ttv->level = TypeLevel{0, 0};
|
||||
|
||||
for (const auto& [name, prop] : t.props)
|
||||
{
|
||||
if (FFlag::LuauSecondTypecheckKnowsTheDataModel)
|
||||
ttv->props[name] = {clone(prop.type, dest, seenTypes, seenTypePacks, encounteredFreeType), prop.deprecated, {}, prop.location, prop.tags};
|
||||
else
|
||||
ttv->props[name] = {clone(prop.type, dest, seenTypes, seenTypePacks, encounteredFreeType), prop.deprecated, {}, prop.location};
|
||||
}
|
||||
|
||||
if (t.indexer)
|
||||
ttv->indexer = TableIndexer{clone(t.indexer->indexType, dest, seenTypes, seenTypePacks, encounteredFreeType),
|
||||
clone(t.indexer->indexResultType, dest, seenTypes, seenTypePacks, encounteredFreeType)};
|
||||
|
||||
if (t.boundTo)
|
||||
ttv->boundTo = clone(*t.boundTo, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
|
||||
for (TypeId& arg : ttv->instantiatedTypeParams)
|
||||
arg = (clone(arg, dest, seenTypes, seenTypePacks, encounteredFreeType));
|
||||
|
||||
if (ttv->state == TableState::Free)
|
||||
{
|
||||
if (!t.boundTo)
|
||||
{
|
||||
if (encounteredFreeType)
|
||||
*encounteredFreeType = true;
|
||||
}
|
||||
|
||||
ttv->state = TableState::Sealed;
|
||||
}
|
||||
|
||||
ttv->definitionModuleName = t.definitionModuleName;
|
||||
ttv->methodDefinitionLocations = t.methodDefinitionLocations;
|
||||
ttv->tags = t.tags;
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const MetatableTypeVar& t)
|
||||
{
|
||||
TypeId result = dest.typeVars.allocate(MetatableTypeVar{});
|
||||
MetatableTypeVar* mtv = getMutable<MetatableTypeVar>(result);
|
||||
seenTypes[typeId] = result;
|
||||
|
||||
mtv->table = clone(t.table, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
mtv->metatable = clone(t.metatable, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const ClassTypeVar& t)
|
||||
{
|
||||
TypeId result = dest.typeVars.allocate(ClassTypeVar{t.name, {}, std::nullopt, std::nullopt, t.tags, t.userData});
|
||||
ClassTypeVar* ctv = getMutable<ClassTypeVar>(result);
|
||||
|
||||
seenTypes[typeId] = result;
|
||||
|
||||
for (const auto& [name, prop] : t.props)
|
||||
if (FFlag::LuauSecondTypecheckKnowsTheDataModel)
|
||||
ctv->props[name] = {clone(prop.type, dest, seenTypes, seenTypePacks, encounteredFreeType), prop.deprecated, {}, prop.location, prop.tags};
|
||||
else
|
||||
ctv->props[name] = {clone(prop.type, dest, seenTypes, seenTypePacks, encounteredFreeType), prop.deprecated, {}, prop.location};
|
||||
|
||||
if (t.parent)
|
||||
ctv->parent = clone(*t.parent, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
|
||||
if (t.metatable)
|
||||
ctv->metatable = clone(*t.metatable, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const AnyTypeVar& t)
|
||||
{
|
||||
defaultClone(t);
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const UnionTypeVar& t)
|
||||
{
|
||||
TypeId result = dest.typeVars.allocate(UnionTypeVar{});
|
||||
seenTypes[typeId] = result;
|
||||
|
||||
UnionTypeVar* option = getMutable<UnionTypeVar>(result);
|
||||
LUAU_ASSERT(option != nullptr);
|
||||
|
||||
for (TypeId ty : t.options)
|
||||
option->options.push_back(clone(ty, dest, seenTypes, seenTypePacks, encounteredFreeType));
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const IntersectionTypeVar& t)
|
||||
{
|
||||
TypeId result = dest.typeVars.allocate(IntersectionTypeVar{});
|
||||
seenTypes[typeId] = result;
|
||||
|
||||
IntersectionTypeVar* option = getMutable<IntersectionTypeVar>(result);
|
||||
LUAU_ASSERT(option != nullptr);
|
||||
|
||||
for (TypeId ty : t.parts)
|
||||
option->parts.push_back(clone(ty, dest, seenTypes, seenTypePacks, encounteredFreeType));
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const LazyTypeVar& t)
|
||||
{
|
||||
defaultClone(t);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType)
|
||||
{
|
||||
if (tp->persistent)
|
||||
return tp;
|
||||
|
||||
TypePackId& res = seenTypePacks[tp];
|
||||
|
||||
if (res == nullptr)
|
||||
{
|
||||
TypePackCloner cloner{dest, tp, seenTypes, seenTypePacks};
|
||||
cloner.encounteredFreeType = encounteredFreeType;
|
||||
Luau::visit(cloner, tp->ty); // Mutates the storage that 'res' points into.
|
||||
}
|
||||
|
||||
if (FFlag::DebugLuauTrackOwningArena)
|
||||
asMutable(res)->owningArena = &dest;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
TypeId clone(TypeId typeId, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType)
|
||||
{
|
||||
if (typeId->persistent)
|
||||
return typeId;
|
||||
|
||||
TypeId& res = seenTypes[typeId];
|
||||
|
||||
if (res == nullptr)
|
||||
{
|
||||
TypeCloner cloner{dest, typeId, seenTypes, seenTypePacks};
|
||||
cloner.encounteredFreeType = encounteredFreeType;
|
||||
Luau::visit(cloner, typeId->ty); // Mutates the storage that 'res' points into.
|
||||
asMutable(res)->documentationSymbol = typeId->documentationSymbol;
|
||||
}
|
||||
|
||||
if (FFlag::DebugLuauTrackOwningArena)
|
||||
asMutable(res)->owningArena = &dest;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType)
|
||||
{
|
||||
TypeFun result;
|
||||
for (TypeId param : typeFun.typeParams)
|
||||
result.typeParams.push_back(clone(param, dest, seenTypes, seenTypePacks, encounteredFreeType));
|
||||
|
||||
result.type = clone(typeFun.type, dest, seenTypes, seenTypePacks, encounteredFreeType);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ScopePtr Module::getModuleScope() const
|
||||
{
|
||||
LUAU_ASSERT(!scopes.empty());
|
||||
return scopes.front().second;
|
||||
}
|
||||
|
||||
void freeze(TypeArena& arena)
|
||||
{
|
||||
if (!FFlag::DebugLuauFreezeArena)
|
||||
return;
|
||||
|
||||
arena.typeVars.freeze();
|
||||
arena.typePacks.freeze();
|
||||
}
|
||||
|
||||
void unfreeze(TypeArena& arena)
|
||||
{
|
||||
if (!FFlag::DebugLuauFreezeArena)
|
||||
return;
|
||||
|
||||
arena.typeVars.unfreeze();
|
||||
arena.typePacks.unfreeze();
|
||||
}
|
||||
|
||||
Module::~Module()
|
||||
{
|
||||
unfreeze(interfaceTypes);
|
||||
unfreeze(internalTypes);
|
||||
}
|
||||
|
||||
bool Module::clonePublicInterface()
|
||||
{
|
||||
LUAU_ASSERT(interfaceTypes.typeVars.empty());
|
||||
LUAU_ASSERT(interfaceTypes.typePacks.empty());
|
||||
|
||||
bool encounteredFreeType = false;
|
||||
|
||||
SeenTypePacks seenTypePacks;
|
||||
SeenTypes seenTypes;
|
||||
|
||||
ScopePtr moduleScope = getModuleScope();
|
||||
|
||||
moduleScope->returnType = clone(moduleScope->returnType, interfaceTypes, seenTypes, seenTypePacks, &encounteredFreeType);
|
||||
if (moduleScope->varargPack)
|
||||
moduleScope->varargPack = clone(*moduleScope->varargPack, interfaceTypes, seenTypes, seenTypePacks, &encounteredFreeType);
|
||||
|
||||
for (auto& pair : moduleScope->exportedTypeBindings)
|
||||
pair.second = clone(pair.second, interfaceTypes, seenTypes, seenTypePacks, &encounteredFreeType);
|
||||
|
||||
for (TypeId ty : moduleScope->returnType)
|
||||
if (get<GenericTypeVar>(follow(ty)))
|
||||
*asMutable(ty) = AnyTypeVar{};
|
||||
|
||||
freeze(internalTypes);
|
||||
freeze(interfaceTypes);
|
||||
|
||||
return encounteredFreeType;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
93
Analysis/src/Predicate.cpp
Normal file
93
Analysis/src/Predicate.cpp
Normal file
|
@ -0,0 +1,93 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Predicate.h"
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauOrPredicate)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
std::optional<LValue> tryGetLValue(const AstExpr& node)
|
||||
{
|
||||
const AstExpr* expr = &node;
|
||||
while (auto e = expr->as<AstExprGroup>())
|
||||
expr = e->expr;
|
||||
|
||||
if (auto local = expr->as<AstExprLocal>())
|
||||
return Symbol{local->local};
|
||||
else if (auto global = expr->as<AstExprGlobal>())
|
||||
return Symbol{global->name};
|
||||
else if (auto indexname = expr->as<AstExprIndexName>())
|
||||
{
|
||||
if (auto lvalue = tryGetLValue(*indexname->expr))
|
||||
return Field{std::make_shared<LValue>(*lvalue), indexname->index.value};
|
||||
}
|
||||
else if (auto indexexpr = expr->as<AstExprIndexExpr>())
|
||||
{
|
||||
if (auto lvalue = tryGetLValue(*indexexpr->expr))
|
||||
if (auto string = indexexpr->expr->as<AstExprConstantString>())
|
||||
return Field{std::make_shared<LValue>(*lvalue), std::string(string->value.data, string->value.size)};
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::pair<Symbol, std::vector<std::string>> getFullName(const LValue& lvalue)
|
||||
{
|
||||
const LValue* current = &lvalue;
|
||||
std::vector<std::string> keys;
|
||||
while (auto field = get<Field>(*current))
|
||||
{
|
||||
keys.push_back(field->key);
|
||||
current = field->parent.get();
|
||||
if (!current)
|
||||
LUAU_ASSERT(!"LValue root is a Field?");
|
||||
}
|
||||
|
||||
const Symbol* symbol = get<Symbol>(*current);
|
||||
return {*symbol, std::vector<std::string>(keys.rbegin(), keys.rend())};
|
||||
}
|
||||
|
||||
std::string toString(const LValue& lvalue)
|
||||
{
|
||||
auto [symbol, keys] = getFullName(lvalue);
|
||||
std::string s = toString(symbol);
|
||||
for (std::string key : keys)
|
||||
s += "." + key;
|
||||
return s;
|
||||
}
|
||||
|
||||
void merge(RefinementMap& l, const RefinementMap& r, std::function<TypeId(TypeId, TypeId)> f)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauOrPredicate);
|
||||
|
||||
auto itL = l.begin();
|
||||
auto itR = r.begin();
|
||||
while (itL != l.end() && itR != r.end())
|
||||
{
|
||||
const auto& [k, a] = *itR;
|
||||
if (itL->first == k)
|
||||
{
|
||||
l[k] = f(itL->second, a);
|
||||
++itL;
|
||||
++itR;
|
||||
}
|
||||
else if (itL->first > k)
|
||||
{
|
||||
l[k] = a;
|
||||
++itR;
|
||||
}
|
||||
else
|
||||
++itL;
|
||||
}
|
||||
|
||||
l.insert(itR, r.end());
|
||||
}
|
||||
|
||||
void addRefinement(RefinementMap& refis, const LValue& lvalue, TypeId ty)
|
||||
{
|
||||
refis[toString(lvalue)] = ty;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
190
Analysis/src/RequireTracer.cpp
Normal file
190
Analysis/src/RequireTracer.cpp
Normal file
|
@ -0,0 +1,190 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/RequireTracer.h"
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Module.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauTraceRequireLookupChild, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct RequireTracer : AstVisitor
|
||||
{
|
||||
explicit RequireTracer(FileResolver* fileResolver, ModuleName currentModuleName)
|
||||
: fileResolver(fileResolver)
|
||||
, currentModuleName(std::move(currentModuleName))
|
||||
{
|
||||
}
|
||||
|
||||
FileResolver* const fileResolver;
|
||||
ModuleName currentModuleName;
|
||||
DenseHashMap<AstLocal*, ModuleName> locals{0};
|
||||
RequireTraceResult result;
|
||||
|
||||
std::optional<ModuleName> fromAstFragment(AstExpr* expr)
|
||||
{
|
||||
if (auto g = expr->as<AstExprGlobal>(); g && g->name == "script")
|
||||
return currentModuleName;
|
||||
|
||||
return fileResolver->fromAstFragment(expr);
|
||||
}
|
||||
|
||||
bool visit(AstStatLocal* stat) override
|
||||
{
|
||||
for (size_t i = 0; i < stat->vars.size; ++i)
|
||||
{
|
||||
AstLocal* local = stat->vars.data[i];
|
||||
|
||||
if (local->annotation)
|
||||
{
|
||||
if (AstTypeTypeof* ann = local->annotation->as<AstTypeTypeof>())
|
||||
ann->expr->visit(this);
|
||||
}
|
||||
|
||||
if (i < stat->values.size)
|
||||
{
|
||||
AstExpr* expr = stat->values.data[i];
|
||||
expr->visit(this);
|
||||
|
||||
const ModuleName* name = result.exprs.find(expr);
|
||||
if (name)
|
||||
locals[local] = *name;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprGlobal* global) override
|
||||
{
|
||||
std::optional<ModuleName> name = fromAstFragment(global);
|
||||
if (name)
|
||||
result.exprs[global] = *name;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprLocal* local) override
|
||||
{
|
||||
const ModuleName* name = locals.find(local->local);
|
||||
if (name)
|
||||
result.exprs[local] = *name;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprIndexName* indexName) override
|
||||
{
|
||||
indexName->expr->visit(this);
|
||||
|
||||
const ModuleName* name = result.exprs.find(indexName->expr);
|
||||
if (name)
|
||||
{
|
||||
if (indexName->index == "parent" || indexName->index == "Parent")
|
||||
{
|
||||
if (auto parent = fileResolver->getParentModuleName(*name))
|
||||
result.exprs[indexName] = *parent;
|
||||
}
|
||||
else
|
||||
result.exprs[indexName] = fileResolver->concat(*name, indexName->index.value);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprIndexExpr* indexExpr) override
|
||||
{
|
||||
indexExpr->expr->visit(this);
|
||||
|
||||
const ModuleName* name = result.exprs.find(indexExpr->expr);
|
||||
const AstExprConstantString* str = indexExpr->index->as<AstExprConstantString>();
|
||||
if (name && str)
|
||||
{
|
||||
result.exprs[indexExpr] = fileResolver->concat(*name, std::string_view(str->value.data, str->value.size));
|
||||
}
|
||||
|
||||
indexExpr->index->visit(this);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprTypeAssertion* expr) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we see game:GetService("StringLiteral") or Game:GetService("StringLiteral"), then rewrite to game.StringLiteral.
|
||||
// Else traverse arguments and trace requires to them.
|
||||
bool visit(AstExprCall* call) override
|
||||
{
|
||||
for (AstExpr* arg : call->args)
|
||||
arg->visit(this);
|
||||
|
||||
call->func->visit(this);
|
||||
|
||||
AstExprGlobal* globalName = call->func->as<AstExprGlobal>();
|
||||
if (globalName && globalName->name == "require" && call->args.size >= 1)
|
||||
{
|
||||
if (const ModuleName* moduleName = result.exprs.find(call->args.data[0]))
|
||||
result.requires.push_back({*moduleName, call->location});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
AstExprIndexName* indexName = call->func->as<AstExprIndexName>();
|
||||
if (!indexName)
|
||||
return false;
|
||||
|
||||
std::optional<ModuleName> rootName = fromAstFragment(indexName->expr);
|
||||
|
||||
if (FFlag::LuauTraceRequireLookupChild && !rootName)
|
||||
{
|
||||
if (const ModuleName* moduleName = result.exprs.find(indexName->expr))
|
||||
rootName = *moduleName;
|
||||
}
|
||||
|
||||
if (!rootName)
|
||||
return false;
|
||||
|
||||
bool supportedLookup = indexName->index == "GetService" ||
|
||||
(FFlag::LuauTraceRequireLookupChild && (indexName->index == "FindFirstChild" || indexName->index == "WaitForChild"));
|
||||
|
||||
if (!supportedLookup)
|
||||
return false;
|
||||
|
||||
if (call->args.size != 1)
|
||||
return false;
|
||||
|
||||
AstExprConstantString* name = call->args.data[0]->as<AstExprConstantString>();
|
||||
if (!name)
|
||||
return false;
|
||||
|
||||
std::string_view v{name->value.data, name->value.size};
|
||||
if (v.end() != std::find(v.begin(), v.end(), '/'))
|
||||
return false;
|
||||
|
||||
result.exprs[call] = fileResolver->concat(*rootName, v);
|
||||
|
||||
// 'WaitForChild' can be used on modules that are not awailable at the typecheck time, but will be awailable at runtime
|
||||
// If we fail to find such module, we will not report an UnknownRequire error
|
||||
if (FFlag::LuauTraceRequireLookupChild && indexName->index == "WaitForChild")
|
||||
result.optional[call] = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
RequireTraceResult traceRequires(FileResolver* fileResolver, AstStatBlock* root, ModuleName currentModuleName)
|
||||
{
|
||||
RequireTracer tracer{fileResolver, std::move(currentModuleName)};
|
||||
root->visit(&tracer);
|
||||
return tracer.result;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
530
Analysis/src/Substitution.cpp
Normal file
530
Analysis/src/Substitution.cpp
Normal file
|
@ -0,0 +1,530 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Substitution.h"
|
||||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 0)
|
||||
LUAU_FASTFLAG(LuauSecondTypecheckKnowsTheDataModel)
|
||||
LUAU_FASTFLAG(LuauRankNTypes)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
void Tarjan::visitChildren(TypeId ty, int index)
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
||||
if (FFlag::LuauRankNTypes && ignoreChildren(ty))
|
||||
return;
|
||||
|
||||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
|
||||
{
|
||||
visitChild(ftv->argTypes);
|
||||
visitChild(ftv->retType);
|
||||
}
|
||||
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
|
||||
{
|
||||
LUAU_ASSERT(!ttv->boundTo);
|
||||
for (const auto& [name, prop] : ttv->props)
|
||||
visitChild(prop.type);
|
||||
if (ttv->indexer)
|
||||
{
|
||||
visitChild(ttv->indexer->indexType);
|
||||
visitChild(ttv->indexer->indexResultType);
|
||||
}
|
||||
for (TypeId itp : ttv->instantiatedTypeParams)
|
||||
visitChild(itp);
|
||||
}
|
||||
else if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(ty))
|
||||
{
|
||||
visitChild(mtv->table);
|
||||
visitChild(mtv->metatable);
|
||||
}
|
||||
else if (const UnionTypeVar* utv = get<UnionTypeVar>(ty))
|
||||
{
|
||||
for (TypeId opt : utv->options)
|
||||
visitChild(opt);
|
||||
}
|
||||
else if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(ty))
|
||||
{
|
||||
for (TypeId part : itv->parts)
|
||||
visitChild(part);
|
||||
}
|
||||
}
|
||||
|
||||
void Tarjan::visitChildren(TypePackId tp, int index)
|
||||
{
|
||||
tp = follow(tp);
|
||||
|
||||
if (FFlag::LuauRankNTypes && ignoreChildren(tp))
|
||||
return;
|
||||
|
||||
if (const TypePack* tpp = get<TypePack>(tp))
|
||||
{
|
||||
for (TypeId tv : tpp->head)
|
||||
visitChild(tv);
|
||||
if (tpp->tail)
|
||||
visitChild(*tpp->tail);
|
||||
}
|
||||
else if (const VariadicTypePack* vtp = get<VariadicTypePack>(tp))
|
||||
{
|
||||
visitChild(vtp->ty);
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<int, bool> Tarjan::indexify(TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
||||
bool fresh = !typeToIndex.contains(ty);
|
||||
int& index = typeToIndex[ty];
|
||||
|
||||
if (fresh)
|
||||
{
|
||||
index = int(indexToType.size());
|
||||
indexToType.push_back(ty);
|
||||
indexToPack.push_back(nullptr);
|
||||
onStack.push_back(false);
|
||||
lowlink.push_back(index);
|
||||
}
|
||||
return {index, fresh};
|
||||
}
|
||||
|
||||
std::pair<int, bool> Tarjan::indexify(TypePackId tp)
|
||||
{
|
||||
tp = follow(tp);
|
||||
|
||||
bool fresh = !packToIndex.contains(tp);
|
||||
int& index = packToIndex[tp];
|
||||
|
||||
if (fresh)
|
||||
{
|
||||
index = int(indexToPack.size());
|
||||
indexToType.push_back(nullptr);
|
||||
indexToPack.push_back(tp);
|
||||
onStack.push_back(false);
|
||||
lowlink.push_back(index);
|
||||
}
|
||||
return {index, fresh};
|
||||
}
|
||||
|
||||
void Tarjan::visitChild(TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
||||
edgesTy.push_back(ty);
|
||||
edgesTp.push_back(nullptr);
|
||||
}
|
||||
|
||||
void Tarjan::visitChild(TypePackId tp)
|
||||
{
|
||||
tp = follow(tp);
|
||||
|
||||
edgesTy.push_back(nullptr);
|
||||
edgesTp.push_back(tp);
|
||||
}
|
||||
|
||||
TarjanResult Tarjan::loop()
|
||||
{
|
||||
// Normally Tarjan is presented recursively, but this is a hot loop, so worth optimizing
|
||||
while (!worklist.empty())
|
||||
{
|
||||
auto [index, currEdge, lastEdge] = worklist.back();
|
||||
|
||||
// First visit
|
||||
if (currEdge == -1)
|
||||
{
|
||||
++childCount;
|
||||
if (FInt::LuauTarjanChildLimit > 0 && FInt::LuauTarjanChildLimit < childCount)
|
||||
return TarjanResult::TooManyChildren;
|
||||
|
||||
stack.push_back(index);
|
||||
onStack[index] = true;
|
||||
|
||||
currEdge = int(edgesTy.size());
|
||||
|
||||
// Fill in edge list of this vertex
|
||||
if (TypeId ty = indexToType[index])
|
||||
visitChildren(ty, index);
|
||||
else if (TypePackId tp = indexToPack[index])
|
||||
visitChildren(tp, index);
|
||||
|
||||
lastEdge = int(edgesTy.size());
|
||||
}
|
||||
|
||||
// Visit children
|
||||
bool foundFresh = false;
|
||||
|
||||
for (; currEdge < lastEdge; currEdge++)
|
||||
{
|
||||
int childIndex = -1;
|
||||
bool fresh = false;
|
||||
|
||||
if (auto ty = edgesTy[currEdge])
|
||||
std::tie(childIndex, fresh) = indexify(ty);
|
||||
else if (auto tp = edgesTp[currEdge])
|
||||
std::tie(childIndex, fresh) = indexify(tp);
|
||||
else
|
||||
LUAU_ASSERT(false);
|
||||
|
||||
if (fresh)
|
||||
{
|
||||
// Original recursion point, update the parent continuation point and start the new element
|
||||
worklist.back() = {index, currEdge + 1, lastEdge};
|
||||
worklist.push_back({childIndex, -1, -1});
|
||||
|
||||
// We need to continue the top-level loop from the start with the new worklist element
|
||||
foundFresh = true;
|
||||
break;
|
||||
}
|
||||
else if (onStack[childIndex])
|
||||
{
|
||||
lowlink[index] = std::min(lowlink[index], childIndex);
|
||||
}
|
||||
|
||||
visitEdge(childIndex, index);
|
||||
}
|
||||
|
||||
if (foundFresh)
|
||||
continue;
|
||||
|
||||
if (lowlink[index] == index)
|
||||
{
|
||||
visitSCC(index);
|
||||
while (!stack.empty())
|
||||
{
|
||||
int popped = stack.back();
|
||||
stack.pop_back();
|
||||
onStack[popped] = false;
|
||||
if (popped == index)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
worklist.pop_back();
|
||||
|
||||
// Original return from recursion into a child
|
||||
if (!worklist.empty())
|
||||
{
|
||||
auto [parentIndex, _, parentEndEdge] = worklist.back();
|
||||
|
||||
// No need to keep child edges around
|
||||
edgesTy.resize(parentEndEdge);
|
||||
edgesTp.resize(parentEndEdge);
|
||||
|
||||
lowlink[parentIndex] = std::min(lowlink[parentIndex], lowlink[index]);
|
||||
visitEdge(index, parentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return TarjanResult::Ok;
|
||||
}
|
||||
|
||||
void Tarjan::clear()
|
||||
{
|
||||
typeToIndex.clear();
|
||||
indexToType.clear();
|
||||
packToIndex.clear();
|
||||
indexToPack.clear();
|
||||
lowlink.clear();
|
||||
stack.clear();
|
||||
onStack.clear();
|
||||
|
||||
edgesTy.clear();
|
||||
edgesTp.clear();
|
||||
worklist.clear();
|
||||
}
|
||||
|
||||
TarjanResult Tarjan::visitRoot(TypeId ty)
|
||||
{
|
||||
childCount = 0;
|
||||
ty = follow(ty);
|
||||
|
||||
clear();
|
||||
auto [index, fresh] = indexify(ty);
|
||||
worklist.push_back({index, -1, -1});
|
||||
return loop();
|
||||
}
|
||||
|
||||
TarjanResult Tarjan::visitRoot(TypePackId tp)
|
||||
{
|
||||
childCount = 0;
|
||||
tp = follow(tp);
|
||||
|
||||
clear();
|
||||
auto [index, fresh] = indexify(tp);
|
||||
worklist.push_back({index, -1, -1});
|
||||
return loop();
|
||||
}
|
||||
|
||||
bool FindDirty::getDirty(int index)
|
||||
{
|
||||
if (dirty.size() <= size_t(index))
|
||||
dirty.resize(index + 1, false);
|
||||
return dirty[index];
|
||||
}
|
||||
|
||||
void FindDirty::setDirty(int index, bool d)
|
||||
{
|
||||
if (dirty.size() <= size_t(index))
|
||||
dirty.resize(index + 1, false);
|
||||
dirty[index] = d;
|
||||
}
|
||||
|
||||
void FindDirty::visitEdge(int index, int parentIndex)
|
||||
{
|
||||
if (getDirty(index))
|
||||
setDirty(parentIndex, true);
|
||||
}
|
||||
|
||||
void FindDirty::visitSCC(int index)
|
||||
{
|
||||
bool d = getDirty(index);
|
||||
|
||||
for (auto it = stack.rbegin(); !d && it != stack.rend(); it++)
|
||||
{
|
||||
if (TypeId ty = indexToType[*it])
|
||||
d = isDirty(ty);
|
||||
else if (TypePackId tp = indexToPack[*it])
|
||||
d = isDirty(tp);
|
||||
if (*it == index)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!d)
|
||||
return;
|
||||
|
||||
for (auto it = stack.rbegin(); it != stack.rend(); it++)
|
||||
{
|
||||
setDirty(*it, true);
|
||||
if (TypeId ty = indexToType[*it])
|
||||
foundDirty(ty);
|
||||
else if (TypePackId tp = indexToPack[*it])
|
||||
foundDirty(tp);
|
||||
if (*it == index)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
TarjanResult FindDirty::findDirty(TypeId ty)
|
||||
{
|
||||
dirty.clear();
|
||||
return visitRoot(ty);
|
||||
}
|
||||
|
||||
TarjanResult FindDirty::findDirty(TypePackId tp)
|
||||
{
|
||||
dirty.clear();
|
||||
return visitRoot(tp);
|
||||
}
|
||||
|
||||
std::optional<TypeId> Substitution::substitute(TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
newTypes.clear();
|
||||
newPacks.clear();
|
||||
|
||||
auto result = findDirty(ty);
|
||||
if (result != TarjanResult::Ok)
|
||||
return std::nullopt;
|
||||
|
||||
for (auto [oldTy, newTy] : newTypes)
|
||||
replaceChildren(newTy);
|
||||
for (auto [oldTp, newTp] : newPacks)
|
||||
replaceChildren(newTp);
|
||||
TypeId newTy = replace(ty);
|
||||
return newTy;
|
||||
}
|
||||
|
||||
std::optional<TypePackId> Substitution::substitute(TypePackId tp)
|
||||
{
|
||||
tp = follow(tp);
|
||||
newTypes.clear();
|
||||
newPacks.clear();
|
||||
|
||||
auto result = findDirty(tp);
|
||||
if (result != TarjanResult::Ok)
|
||||
return std::nullopt;
|
||||
|
||||
for (auto [oldTy, newTy] : newTypes)
|
||||
replaceChildren(newTy);
|
||||
for (auto [oldTp, newTp] : newPacks)
|
||||
replaceChildren(newTp);
|
||||
TypePackId newTp = replace(tp);
|
||||
return newTp;
|
||||
}
|
||||
|
||||
TypeId Substitution::clone(TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
||||
TypeId result = ty;
|
||||
|
||||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
|
||||
{
|
||||
FunctionTypeVar clone = FunctionTypeVar{ftv->level, ftv->argTypes, ftv->retType, ftv->definition, ftv->hasSelf};
|
||||
clone.generics = ftv->generics;
|
||||
clone.genericPacks = ftv->genericPacks;
|
||||
clone.magicFunction = ftv->magicFunction;
|
||||
clone.tags = ftv->tags;
|
||||
clone.argNames = ftv->argNames;
|
||||
result = addType(std::move(clone));
|
||||
}
|
||||
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
|
||||
{
|
||||
LUAU_ASSERT(!ttv->boundTo);
|
||||
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state};
|
||||
clone.methodDefinitionLocations = ttv->methodDefinitionLocations;
|
||||
clone.definitionModuleName = ttv->definitionModuleName;
|
||||
clone.name = ttv->name;
|
||||
clone.syntheticName = ttv->syntheticName;
|
||||
clone.instantiatedTypeParams = ttv->instantiatedTypeParams;
|
||||
if (FFlag::LuauSecondTypecheckKnowsTheDataModel)
|
||||
clone.tags = ttv->tags;
|
||||
result = addType(std::move(clone));
|
||||
}
|
||||
else if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(ty))
|
||||
{
|
||||
MetatableTypeVar clone = MetatableTypeVar{mtv->table, mtv->metatable};
|
||||
clone.syntheticName = mtv->syntheticName;
|
||||
result = addType(std::move(clone));
|
||||
}
|
||||
else if (const UnionTypeVar* utv = get<UnionTypeVar>(ty))
|
||||
{
|
||||
UnionTypeVar clone;
|
||||
clone.options = utv->options;
|
||||
result = addType(std::move(clone));
|
||||
}
|
||||
else if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(ty))
|
||||
{
|
||||
IntersectionTypeVar clone;
|
||||
clone.parts = itv->parts;
|
||||
result = addType(std::move(clone));
|
||||
}
|
||||
|
||||
asMutable(result)->documentationSymbol = ty->documentationSymbol;
|
||||
return result;
|
||||
}
|
||||
|
||||
TypePackId Substitution::clone(TypePackId tp)
|
||||
{
|
||||
tp = follow(tp);
|
||||
if (const TypePack* tpp = get<TypePack>(tp))
|
||||
{
|
||||
TypePack clone;
|
||||
clone.head = tpp->head;
|
||||
clone.tail = tpp->tail;
|
||||
return addTypePack(std::move(clone));
|
||||
}
|
||||
else if (const VariadicTypePack* vtp = get<VariadicTypePack>(tp))
|
||||
{
|
||||
VariadicTypePack clone;
|
||||
clone.ty = vtp->ty;
|
||||
return addTypePack(std::move(clone));
|
||||
}
|
||||
else
|
||||
return tp;
|
||||
}
|
||||
|
||||
void Substitution::foundDirty(TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
if (isDirty(ty))
|
||||
newTypes[ty] = clean(ty);
|
||||
else
|
||||
newTypes[ty] = clone(ty);
|
||||
}
|
||||
|
||||
void Substitution::foundDirty(TypePackId tp)
|
||||
{
|
||||
tp = follow(tp);
|
||||
if (isDirty(tp))
|
||||
newPacks[tp] = clean(tp);
|
||||
else
|
||||
newPacks[tp] = clone(tp);
|
||||
}
|
||||
|
||||
TypeId Substitution::replace(TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
if (TypeId* prevTy = newTypes.find(ty))
|
||||
return *prevTy;
|
||||
else
|
||||
return ty;
|
||||
}
|
||||
|
||||
TypePackId Substitution::replace(TypePackId tp)
|
||||
{
|
||||
tp = follow(tp);
|
||||
if (TypePackId* prevTp = newPacks.find(tp))
|
||||
return *prevTp;
|
||||
else
|
||||
return tp;
|
||||
}
|
||||
|
||||
void Substitution::replaceChildren(TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
||||
if (FFlag::LuauRankNTypes && ignoreChildren(ty))
|
||||
return;
|
||||
|
||||
if (FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty))
|
||||
{
|
||||
ftv->argTypes = replace(ftv->argTypes);
|
||||
ftv->retType = replace(ftv->retType);
|
||||
}
|
||||
else if (TableTypeVar* ttv = getMutable<TableTypeVar>(ty))
|
||||
{
|
||||
LUAU_ASSERT(!ttv->boundTo);
|
||||
for (auto& [name, prop] : ttv->props)
|
||||
prop.type = replace(prop.type);
|
||||
if (ttv->indexer)
|
||||
{
|
||||
ttv->indexer->indexType = replace(ttv->indexer->indexType);
|
||||
ttv->indexer->indexResultType = replace(ttv->indexer->indexResultType);
|
||||
}
|
||||
for (TypeId& itp : ttv->instantiatedTypeParams)
|
||||
itp = replace(itp);
|
||||
}
|
||||
else if (MetatableTypeVar* mtv = getMutable<MetatableTypeVar>(ty))
|
||||
{
|
||||
mtv->table = replace(mtv->table);
|
||||
mtv->metatable = replace(mtv->metatable);
|
||||
}
|
||||
else if (UnionTypeVar* utv = getMutable<UnionTypeVar>(ty))
|
||||
{
|
||||
for (TypeId& opt : utv->options)
|
||||
opt = replace(opt);
|
||||
}
|
||||
else if (IntersectionTypeVar* itv = getMutable<IntersectionTypeVar>(ty))
|
||||
{
|
||||
for (TypeId& part : itv->parts)
|
||||
part = replace(part);
|
||||
}
|
||||
}
|
||||
|
||||
void Substitution::replaceChildren(TypePackId tp)
|
||||
{
|
||||
tp = follow(tp);
|
||||
|
||||
if (FFlag::LuauRankNTypes && ignoreChildren(tp))
|
||||
return;
|
||||
|
||||
if (TypePack* tpp = getMutable<TypePack>(tp))
|
||||
{
|
||||
for (TypeId& tv : tpp->head)
|
||||
tv = replace(tv);
|
||||
if (tpp->tail)
|
||||
tpp->tail = replace(*tpp->tail);
|
||||
}
|
||||
else if (VariadicTypePack* vtp = getMutable<VariadicTypePack>(tp))
|
||||
{
|
||||
vtp->ty = replace(vtp->ty);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Luau
|
18
Analysis/src/Symbol.cpp
Normal file
18
Analysis/src/Symbol.cpp
Normal file
|
@ -0,0 +1,18 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Symbol.h"
|
||||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
std::string toString(const Symbol& name)
|
||||
{
|
||||
if (name.local)
|
||||
return name.local->name.value;
|
||||
|
||||
LUAU_ASSERT(name.global.value);
|
||||
return name.global.value;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
1142
Analysis/src/ToString.cpp
Normal file
1142
Analysis/src/ToString.cpp
Normal file
File diff suppressed because it is too large
Load diff
552
Analysis/src/TopoSortStatements.cpp
Normal file
552
Analysis/src/TopoSortStatements.cpp
Normal file
|
@ -0,0 +1,552 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/TopoSortStatements.h"
|
||||
|
||||
/* Decide the order in which we typecheck Lua statements in a block.
|
||||
*
|
||||
* Algorithm:
|
||||
*
|
||||
* 1. Build up a dependency graph.
|
||||
* i. An AstStat is said to depend on another AstStat if it refers to it in any child node.
|
||||
* A dependency is the relationship between the declaration of a symbol and its uses.
|
||||
* ii. Additionally, statements that do not define functions have a dependency on the previous non-function statement. We do this
|
||||
* to prevent the algorithm from checking imperative statements out-of-order.
|
||||
* 2. Walk each node in the graph in lexical order. For each node:
|
||||
* i. Select the next thing `t`
|
||||
* ii. If `t` has no dependencies at all and is not a function definition, check it now
|
||||
* iii. If `t` is a function definition or an expression that does not include a function call, add it to a queue `Q`.
|
||||
* iv. Else, toposort `Q` and check things until it is possible to check `t`
|
||||
* * If this fails, we expect the Lua runtime to also fail, as the code is trying to use a symbol before it has been defined.
|
||||
* 3. Toposort whatever remains in `Q` and check it all.
|
||||
*
|
||||
* The end result that we want satisfies a few qualities:
|
||||
*
|
||||
* 1. Things are generally checked in lexical order.
|
||||
* 2. If a function F calls another function G that is declared out-of-order, but in a way that will work when the code is actually run, we want
|
||||
* to check G before F.
|
||||
* 3. Cyclic dependencies can be resolved by picking an arbitrary statement to check first.
|
||||
*/
|
||||
|
||||
#include "Luau/Parser.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
// For some reason, natvis interacts really poorly with anonymous data types
|
||||
namespace detail
|
||||
{
|
||||
|
||||
struct Identifier
|
||||
{
|
||||
std::string name; // A nice textual name
|
||||
const AstLocal* ctx; // Only used to disambiguate potentially shadowed names
|
||||
};
|
||||
|
||||
bool operator==(const Identifier& lhs, const Identifier& rhs)
|
||||
{
|
||||
return lhs.name == rhs.name && lhs.ctx == rhs.ctx;
|
||||
}
|
||||
|
||||
struct IdentifierHash
|
||||
{
|
||||
size_t operator()(const Identifier& ident) const
|
||||
{
|
||||
return std::hash<std::string>()(ident.name) ^ std::hash<const void*>()(ident.ctx);
|
||||
}
|
||||
};
|
||||
|
||||
struct Node;
|
||||
|
||||
struct Arcs
|
||||
{
|
||||
std::set<Node*> provides;
|
||||
std::set<Node*> depends;
|
||||
};
|
||||
|
||||
struct Node : Arcs
|
||||
{
|
||||
std::optional<Identifier> name;
|
||||
AstStat* element;
|
||||
|
||||
Node(const std::optional<Identifier>& name, AstStat* el)
|
||||
: name(name)
|
||||
, element(el)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
using NodeQueue = std::deque<std::unique_ptr<Node>>;
|
||||
using NodeList = std::list<std::unique_ptr<Node>>;
|
||||
|
||||
std::optional<Identifier> mkName(const AstExpr& expr);
|
||||
|
||||
Identifier mkName(const AstLocal& local)
|
||||
{
|
||||
return {local.name.value, &local};
|
||||
}
|
||||
|
||||
Identifier mkName(const AstExprLocal& local)
|
||||
{
|
||||
return mkName(*local.local);
|
||||
}
|
||||
|
||||
Identifier mkName(const AstExprGlobal& global)
|
||||
{
|
||||
return {global.name.value, nullptr};
|
||||
}
|
||||
|
||||
Identifier mkName(const AstName& name)
|
||||
{
|
||||
return {name.value, nullptr};
|
||||
}
|
||||
|
||||
std::optional<Identifier> mkName(const AstExprIndexName& expr)
|
||||
{
|
||||
auto lhs = mkName(*expr.expr);
|
||||
if (lhs)
|
||||
{
|
||||
std::string s = std::move(lhs->name);
|
||||
s += ".";
|
||||
s += expr.index.value;
|
||||
return Identifier{std::move(s), lhs->ctx};
|
||||
}
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Identifier mkName(const AstExprError& expr)
|
||||
{
|
||||
return {format("error#%d", expr.messageIndex), nullptr};
|
||||
}
|
||||
|
||||
std::optional<Identifier> mkName(const AstExpr& expr)
|
||||
{
|
||||
if (auto l = expr.as<AstExprLocal>())
|
||||
return mkName(*l);
|
||||
else if (auto g = expr.as<AstExprGlobal>())
|
||||
return mkName(*g);
|
||||
else if (auto i = expr.as<AstExprIndexName>())
|
||||
return mkName(*i);
|
||||
else if (auto e = expr.as<AstExprError>())
|
||||
return mkName(*e);
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Identifier mkName(const AstStatFunction& function)
|
||||
{
|
||||
auto name = mkName(*function.name);
|
||||
LUAU_ASSERT(bool(name));
|
||||
if (!name)
|
||||
throw std::runtime_error("Internal error: Function declaration has a bad name");
|
||||
|
||||
return *name;
|
||||
}
|
||||
|
||||
Identifier mkName(const AstStatLocalFunction& function)
|
||||
{
|
||||
return mkName(*function.name);
|
||||
}
|
||||
|
||||
std::optional<Identifier> mkName(const AstStatAssign& assign)
|
||||
{
|
||||
if (assign.vars.size != 1)
|
||||
return std::nullopt;
|
||||
|
||||
return mkName(*assign.vars.data[0]);
|
||||
}
|
||||
|
||||
std::optional<Identifier> mkName(const AstStatLocal& local)
|
||||
{
|
||||
if (local.vars.size != 1)
|
||||
return std::nullopt;
|
||||
|
||||
return mkName(*local.vars.data[0]);
|
||||
}
|
||||
|
||||
Identifier mkName(const AstStatTypeAlias& typealias)
|
||||
{
|
||||
return mkName(typealias.name);
|
||||
}
|
||||
|
||||
std::optional<Identifier> mkName(AstStat* const el)
|
||||
{
|
||||
if (auto function = el->as<AstStatFunction>())
|
||||
return mkName(*function);
|
||||
else if (auto function = el->as<AstStatLocalFunction>())
|
||||
return mkName(*function);
|
||||
else if (auto assign = el->as<AstStatAssign>())
|
||||
return mkName(*assign);
|
||||
else if (auto local = el->as<AstStatLocal>())
|
||||
return mkName(*local);
|
||||
else if (auto typealias = el->as<AstStatTypeAlias>())
|
||||
return mkName(*typealias);
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
struct ArcCollector : public AstVisitor
|
||||
{
|
||||
NodeQueue& queue;
|
||||
DenseHashMap<Identifier, Node*, IdentifierHash> map;
|
||||
|
||||
Node* currentArc;
|
||||
|
||||
ArcCollector(NodeQueue& queue)
|
||||
: queue(queue)
|
||||
, map(Identifier{std::string{}, 0})
|
||||
, currentArc(nullptr)
|
||||
{
|
||||
for (const auto& node : queue)
|
||||
{
|
||||
if (node->name && !map.contains(*node->name))
|
||||
map[*node->name] = node.get();
|
||||
}
|
||||
}
|
||||
|
||||
void add(const Identifier& name)
|
||||
{
|
||||
Node** it = map.find(name);
|
||||
if (it == nullptr)
|
||||
return;
|
||||
|
||||
Node* n = *it;
|
||||
|
||||
if (n == currentArc)
|
||||
return;
|
||||
|
||||
n->provides.insert(currentArc);
|
||||
currentArc->depends.insert(n);
|
||||
}
|
||||
|
||||
bool visit(AstExprGlobal* node) override
|
||||
{
|
||||
add(mkName(*node));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstExprLocal* node) override
|
||||
{
|
||||
add(mkName(*node));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstExprIndexName* node) override
|
||||
{
|
||||
auto name = mkName(*node);
|
||||
if (name)
|
||||
add(*name);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstStatFunction* node) override
|
||||
{
|
||||
auto name = mkName(*node->name);
|
||||
if (!name)
|
||||
throw std::runtime_error("Internal error: AstStatFunction has a bad name");
|
||||
|
||||
add(*name);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstStatLocalFunction* node) override
|
||||
{
|
||||
add(mkName(*node->name));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstStatAssign* node) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstStatTypeAlias* node) override
|
||||
{
|
||||
add(mkName(*node));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstType* node) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstTypeReference* node) override
|
||||
{
|
||||
add(mkName(node->name));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstTypeTypeof* node) override
|
||||
{
|
||||
std::optional<Identifier> name = mkName(*node->expr);
|
||||
if (name)
|
||||
add(*name);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct ContainsFunctionCall : public AstVisitor
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
bool visit(AstExpr*) override
|
||||
{
|
||||
return !result; // short circuit if result is true
|
||||
}
|
||||
|
||||
bool visit(AstExprCall*) override
|
||||
{
|
||||
result = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstStatForIn*) override
|
||||
{
|
||||
// for in loops perform an implicit function call as part of the iterator protocol
|
||||
result = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExprFunction*) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool visit(AstStatFunction*) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool visit(AstStatLocalFunction*) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstType* ta) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
bool isToposortableNode(const AstStat& stat)
|
||||
{
|
||||
return isFunction(stat) || stat.is<AstStatTypeAlias>();
|
||||
}
|
||||
|
||||
bool containsToposortableNode(const std::vector<AstStat*>& block)
|
||||
{
|
||||
for (AstStat* stat : block)
|
||||
if (isToposortableNode(*stat))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isBlockTerminator(const AstStat& stat)
|
||||
{
|
||||
return stat.is<AstStatReturn>() || stat.is<AstStatBreak>() || stat.is<AstStatContinue>();
|
||||
}
|
||||
|
||||
// Clip arcs to and from the node
|
||||
void prune(Node* next)
|
||||
{
|
||||
for (const auto& node : next->provides)
|
||||
{
|
||||
auto it = node->depends.find(next);
|
||||
LUAU_ASSERT(it != node->depends.end());
|
||||
node->depends.erase(it);
|
||||
}
|
||||
|
||||
for (const auto& node : next->depends)
|
||||
{
|
||||
auto it = node->provides.find(next);
|
||||
LUAU_ASSERT(it != node->provides.end());
|
||||
node->provides.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
// Drain Q until the target's depends arcs are satisfied. target is always added to the result.
|
||||
void drain(NodeList& Q, std::vector<AstStat*>& result, Node* target)
|
||||
{
|
||||
// Trying to toposort a subgraph is a pretty big hassle. :(
|
||||
// Some of the nodes in .depends and .provides aren't present in our subgraph
|
||||
|
||||
std::map<Node*, Arcs> allArcs;
|
||||
|
||||
for (auto& node : Q)
|
||||
{
|
||||
// Copy the connectivity information but filter out any provides or depends arcs that are not in Q
|
||||
Arcs& arcs = allArcs[node.get()];
|
||||
|
||||
DenseHashSet<Node*> elements{nullptr};
|
||||
for (const auto& q : Q)
|
||||
elements.insert(q.get());
|
||||
|
||||
for (Node* node : node->depends)
|
||||
{
|
||||
if (elements.contains(node))
|
||||
arcs.depends.insert(node);
|
||||
}
|
||||
for (Node* node : node->provides)
|
||||
{
|
||||
if (elements.contains(node))
|
||||
arcs.provides.insert(node);
|
||||
}
|
||||
}
|
||||
|
||||
while (!Q.empty())
|
||||
{
|
||||
if (target && target->depends.empty())
|
||||
{
|
||||
prune(target);
|
||||
result.push_back(target->element);
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<Node> nextNode;
|
||||
|
||||
for (auto iter = Q.begin(); iter != Q.end(); ++iter)
|
||||
{
|
||||
if (isBlockTerminator(*iter->get()->element))
|
||||
continue;
|
||||
|
||||
LUAU_ASSERT(allArcs.end() != allArcs.find(iter->get()));
|
||||
const Arcs& arcs = allArcs[iter->get()];
|
||||
|
||||
if (arcs.depends.empty())
|
||||
{
|
||||
nextNode = std::move(*iter);
|
||||
Q.erase(iter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!nextNode)
|
||||
{
|
||||
// We've hit a cycle or a terminator. Pick an arbitrary node.
|
||||
nextNode = std::move(Q.front());
|
||||
Q.pop_front();
|
||||
}
|
||||
|
||||
for (const auto& node : nextNode->provides)
|
||||
{
|
||||
auto it = allArcs.find(node);
|
||||
if (allArcs.end() != it)
|
||||
{
|
||||
auto i2 = it->second.depends.find(nextNode.get());
|
||||
LUAU_ASSERT(i2 != it->second.depends.end());
|
||||
it->second.depends.erase(i2);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& node : nextNode->depends)
|
||||
{
|
||||
auto it = allArcs.find(node);
|
||||
if (allArcs.end() != it)
|
||||
{
|
||||
auto i2 = it->second.provides.find(nextNode.get());
|
||||
LUAU_ASSERT(i2 != it->second.provides.end());
|
||||
it->second.provides.erase(i2);
|
||||
}
|
||||
}
|
||||
|
||||
prune(nextNode.get());
|
||||
result.push_back(nextNode->element);
|
||||
}
|
||||
|
||||
if (target)
|
||||
{
|
||||
prune(target);
|
||||
result.push_back(target->element);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
bool containsFunctionCall(const AstStat& stat)
|
||||
{
|
||||
detail::ContainsFunctionCall cfc;
|
||||
const_cast<AstStat&>(stat).visit(&cfc);
|
||||
return cfc.result;
|
||||
}
|
||||
|
||||
bool isFunction(const AstStat& stat)
|
||||
{
|
||||
return stat.is<AstStatFunction>() || stat.is<AstStatLocalFunction>();
|
||||
}
|
||||
|
||||
void toposort(std::vector<AstStat*>& stats)
|
||||
{
|
||||
using namespace detail;
|
||||
|
||||
if (stats.empty())
|
||||
return;
|
||||
|
||||
if (!containsToposortableNode(stats))
|
||||
return;
|
||||
|
||||
std::vector<AstStat*> result;
|
||||
result.reserve(stats.size());
|
||||
|
||||
NodeQueue nodes;
|
||||
NodeList Q;
|
||||
|
||||
for (AstStat* stat : stats)
|
||||
nodes.push_back(std::unique_ptr<Node>(new Node(mkName(stat), stat)));
|
||||
|
||||
ArcCollector collector{nodes};
|
||||
|
||||
for (const auto& node : nodes)
|
||||
{
|
||||
collector.currentArc = node.get();
|
||||
node->element->visit(&collector);
|
||||
}
|
||||
|
||||
{
|
||||
auto it = nodes.begin();
|
||||
auto prev = it;
|
||||
|
||||
while (it != nodes.end())
|
||||
{
|
||||
if (it != prev && !isToposortableNode(*(*it)->element))
|
||||
{
|
||||
(*it)->depends.insert(prev->get());
|
||||
(*prev)->provides.insert(it->get());
|
||||
prev = it;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
while (!nodes.empty())
|
||||
{
|
||||
Node* next = nodes.front().get();
|
||||
|
||||
if (next->depends.empty() && !isBlockTerminator(*next->element))
|
||||
{
|
||||
prune(next);
|
||||
result.push_back(next->element);
|
||||
}
|
||||
else if (!containsFunctionCall(*next->element))
|
||||
Q.push_back(std::move(nodes.front()));
|
||||
else
|
||||
drain(Q, result, next);
|
||||
|
||||
nodes.pop_front();
|
||||
}
|
||||
|
||||
drain(Q, result, nullptr);
|
||||
|
||||
std::swap(stats, result);
|
||||
}
|
||||
|
||||
} // namespace Luau
|
1156
Analysis/src/Transpiler.cpp
Normal file
1156
Analysis/src/Transpiler.cpp
Normal file
File diff suppressed because it is too large
Load diff
72
Analysis/src/TxnLog.cpp
Normal file
72
Analysis/src/TxnLog.cpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/TxnLog.h"
|
||||
|
||||
#include "Luau/TypePack.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
void TxnLog::operator()(TypeId a)
|
||||
{
|
||||
typeVarChanges.emplace_back(a, *a);
|
||||
}
|
||||
|
||||
void TxnLog::operator()(TypePackId a)
|
||||
{
|
||||
typePackChanges.emplace_back(a, *a);
|
||||
}
|
||||
|
||||
void TxnLog::operator()(TableTypeVar* a)
|
||||
{
|
||||
tableChanges.emplace_back(a, a->boundTo);
|
||||
}
|
||||
|
||||
void TxnLog::rollback()
|
||||
{
|
||||
for (auto it = typeVarChanges.rbegin(); it != typeVarChanges.rend(); ++it)
|
||||
std::swap(*asMutable(it->first), it->second);
|
||||
|
||||
for (auto it = typePackChanges.rbegin(); it != typePackChanges.rend(); ++it)
|
||||
std::swap(*asMutable(it->first), it->second);
|
||||
|
||||
for (auto it = tableChanges.rbegin(); it != tableChanges.rend(); ++it)
|
||||
std::swap(it->first->boundTo, it->second);
|
||||
}
|
||||
|
||||
void TxnLog::concat(TxnLog rhs)
|
||||
{
|
||||
typeVarChanges.insert(typeVarChanges.end(), rhs.typeVarChanges.begin(), rhs.typeVarChanges.end());
|
||||
rhs.typeVarChanges.clear();
|
||||
|
||||
typePackChanges.insert(typePackChanges.end(), rhs.typePackChanges.begin(), rhs.typePackChanges.end());
|
||||
rhs.typePackChanges.clear();
|
||||
|
||||
tableChanges.insert(tableChanges.end(), rhs.tableChanges.begin(), rhs.tableChanges.end());
|
||||
rhs.tableChanges.clear();
|
||||
|
||||
seen.swap(rhs.seen);
|
||||
rhs.seen.clear();
|
||||
}
|
||||
|
||||
bool TxnLog::haveSeen(TypeId lhs, TypeId rhs)
|
||||
{
|
||||
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
||||
return (seen.end() != std::find(seen.begin(), seen.end(), sortedPair));
|
||||
}
|
||||
|
||||
void TxnLog::pushSeen(TypeId lhs, TypeId rhs)
|
||||
{
|
||||
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
||||
seen.push_back(sortedPair);
|
||||
}
|
||||
|
||||
void TxnLog::popSeen(TypeId lhs, TypeId rhs)
|
||||
{
|
||||
const std::pair<TypeId, TypeId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
||||
LUAU_ASSERT(sortedPair == seen.back());
|
||||
seen.pop_back();
|
||||
}
|
||||
|
||||
} // namespace Luau
|
437
Analysis/src/TypeAttach.cpp
Normal file
437
Analysis/src/TypeAttach.cpp
Normal file
|
@ -0,0 +1,437 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/TypeAttach.h"
|
||||
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/Parser.h"
|
||||
#include "Luau/RecursionCounter.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
LUAU_FASTFLAG(LuauGenericFunctions)
|
||||
|
||||
static char* allocateString(Luau::Allocator& allocator, std::string_view contents)
|
||||
{
|
||||
char* result = (char*)allocator.allocate(contents.size() + 1);
|
||||
memcpy(result, contents.data(), contents.size());
|
||||
result[contents.size()] = '\0';
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename... Data>
|
||||
static char* allocateString(Luau::Allocator& allocator, const char* format, Data... data)
|
||||
{
|
||||
int len = snprintf(nullptr, 0, format, data...);
|
||||
char* result = (char*)allocator.allocate(len + 1);
|
||||
snprintf(result, len + 1, format, data...);
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
class TypeRehydrationVisitor
|
||||
{
|
||||
mutable std::map<void*, int> seen;
|
||||
mutable int count = 0;
|
||||
|
||||
bool hasSeen(const void* tv) const
|
||||
{
|
||||
void* ttv = const_cast<void*>(tv);
|
||||
auto it = seen.find(ttv);
|
||||
if (it != seen.end() && it->second < count)
|
||||
return true;
|
||||
|
||||
seen[ttv] = count;
|
||||
return false;
|
||||
}
|
||||
|
||||
public:
|
||||
TypeRehydrationVisitor(Allocator* alloc, const TypeRehydrationOptions& options = TypeRehydrationOptions())
|
||||
: allocator(alloc)
|
||||
, options(options)
|
||||
{
|
||||
}
|
||||
|
||||
AstType* operator()(const PrimitiveTypeVar& ptv) const
|
||||
{
|
||||
switch (ptv.type)
|
||||
{
|
||||
case PrimitiveTypeVar::NilType:
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("nil"));
|
||||
case PrimitiveTypeVar::Boolean:
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("boolean"));
|
||||
case PrimitiveTypeVar::Number:
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("number"));
|
||||
case PrimitiveTypeVar::String:
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("string"));
|
||||
case PrimitiveTypeVar::Thread:
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("thread"));
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
AstType* operator()(const AnyTypeVar&) const
|
||||
{
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("any"));
|
||||
}
|
||||
AstType* operator()(const TableTypeVar& ttv) const
|
||||
{
|
||||
RecursionCounter counter(&count);
|
||||
|
||||
if (ttv.name && options.bannedNames.find(*ttv.name) == options.bannedNames.end())
|
||||
{
|
||||
AstArray<AstType*> generics;
|
||||
generics.size = ttv.instantiatedTypeParams.size();
|
||||
generics.data = static_cast<AstType**>(allocator->allocate(sizeof(AstType*) * generics.size));
|
||||
|
||||
for (size_t i = 0; i < ttv.instantiatedTypeParams.size(); ++i)
|
||||
{
|
||||
generics.data[i] = Luau::visit(*this, ttv.instantiatedTypeParams[i]->ty);
|
||||
}
|
||||
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName(ttv.name->c_str()), generics);
|
||||
}
|
||||
|
||||
if (hasSeen(&ttv))
|
||||
{
|
||||
if (ttv.name)
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName(ttv.name->c_str()));
|
||||
else
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("<Cycle>"));
|
||||
}
|
||||
|
||||
AstArray<AstTableProp> props;
|
||||
props.size = ttv.props.size();
|
||||
props.data = static_cast<AstTableProp*>(allocator->allocate(sizeof(AstTableProp) * props.size));
|
||||
int idx = 0;
|
||||
for (const auto& [propName, prop] : ttv.props)
|
||||
{
|
||||
RecursionCounter counter(&count);
|
||||
|
||||
char* name = allocateString(*allocator, propName);
|
||||
|
||||
props.data[idx].name = AstName(name);
|
||||
props.data[idx].type = Luau::visit(*this, prop.type->ty);
|
||||
props.data[idx].location = Location();
|
||||
idx++;
|
||||
}
|
||||
|
||||
AstTableIndexer* indexer = nullptr;
|
||||
if (ttv.indexer)
|
||||
{
|
||||
RecursionCounter counter(&count);
|
||||
|
||||
indexer = allocator->alloc<AstTableIndexer>();
|
||||
indexer->indexType = Luau::visit(*this, ttv.indexer->indexType->ty);
|
||||
indexer->resultType = Luau::visit(*this, ttv.indexer->indexResultType->ty);
|
||||
}
|
||||
return allocator->alloc<AstTypeTable>(Location(), props, indexer);
|
||||
}
|
||||
|
||||
AstType* operator()(const MetatableTypeVar& mtv) const
|
||||
{
|
||||
return Luau::visit(*this, mtv.table->ty);
|
||||
}
|
||||
|
||||
AstType* operator()(const ClassTypeVar& ctv) const
|
||||
{
|
||||
RecursionCounter counter(&count);
|
||||
|
||||
char* name = allocateString(*allocator, ctv.name);
|
||||
|
||||
if (!options.expandClassProps || hasSeen(&ctv) || count > 1)
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName{name});
|
||||
|
||||
AstArray<AstTableProp> props;
|
||||
props.size = ctv.props.size();
|
||||
props.data = static_cast<AstTableProp*>(allocator->allocate(sizeof(AstTableProp) * props.size));
|
||||
|
||||
int idx = 0;
|
||||
for (const auto& [propName, prop] : ctv.props)
|
||||
{
|
||||
char* name = allocateString(*allocator, propName);
|
||||
|
||||
props.data[idx].name = AstName{name};
|
||||
props.data[idx].type = Luau::visit(*this, prop.type->ty);
|
||||
props.data[idx].location = Location();
|
||||
idx++;
|
||||
}
|
||||
|
||||
return allocator->alloc<AstTypeTable>(Location(), props);
|
||||
}
|
||||
|
||||
AstType* operator()(const FunctionTypeVar& ftv) const
|
||||
{
|
||||
RecursionCounter counter(&count);
|
||||
|
||||
if (hasSeen(&ftv))
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("<Cycle>"));
|
||||
|
||||
AstArray<AstName> generics;
|
||||
if (FFlag::LuauGenericFunctions)
|
||||
{
|
||||
generics.size = ftv.generics.size();
|
||||
generics.data = static_cast<AstName*>(allocator->allocate(sizeof(AstName) * generics.size));
|
||||
size_t i = 0;
|
||||
for (auto it = ftv.generics.begin(); it != ftv.generics.end(); ++it)
|
||||
{
|
||||
if (auto gtv = get<GenericTypeVar>(*it))
|
||||
generics.data[i++] = AstName(gtv->name.c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
generics.size = 0;
|
||||
generics.data = nullptr;
|
||||
}
|
||||
|
||||
AstArray<AstName> genericPacks;
|
||||
if (FFlag::LuauGenericFunctions)
|
||||
{
|
||||
genericPacks.size = ftv.genericPacks.size();
|
||||
genericPacks.data = static_cast<AstName*>(allocator->allocate(sizeof(AstName) * genericPacks.size));
|
||||
size_t i = 0;
|
||||
for (auto it = ftv.genericPacks.begin(); it != ftv.genericPacks.end(); ++it)
|
||||
{
|
||||
if (auto gtv = get<GenericTypeVar>(*it))
|
||||
genericPacks.data[i++] = AstName(gtv->name.c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
generics.size = 0;
|
||||
generics.data = nullptr;
|
||||
}
|
||||
|
||||
AstArray<AstType*> argTypes;
|
||||
const auto& [argVector, argTail] = flatten(ftv.argTypes);
|
||||
argTypes.size = argVector.size();
|
||||
argTypes.data = static_cast<AstType**>(allocator->allocate(sizeof(AstType*) * argTypes.size));
|
||||
for (size_t i = 0; i < argTypes.size; ++i)
|
||||
{
|
||||
RecursionCounter counter(&count);
|
||||
|
||||
argTypes.data[i] = Luau::visit(*this, (argVector[i])->ty);
|
||||
}
|
||||
|
||||
AstTypePack* argTailAnnotation = nullptr;
|
||||
if (argTail)
|
||||
{
|
||||
TypePackId tail = *argTail;
|
||||
if (const VariadicTypePack* vtp = get<VariadicTypePack>(tail))
|
||||
{
|
||||
argTailAnnotation = allocator->alloc<AstTypePackVariadic>(Location(), Luau::visit(*this, vtp->ty->ty));
|
||||
}
|
||||
}
|
||||
|
||||
AstArray<std::optional<AstArgumentName>> argNames;
|
||||
argNames.size = ftv.argNames.size();
|
||||
argNames.data = static_cast<std::optional<AstArgumentName>*>(allocator->allocate(sizeof(std::optional<AstArgumentName>) * argNames.size));
|
||||
size_t i = 0;
|
||||
for (const auto& el : ftv.argNames)
|
||||
{
|
||||
if (el)
|
||||
argNames.data[i++] = {AstName(el->name.c_str()), el->location};
|
||||
else
|
||||
argNames.data[i++] = {};
|
||||
}
|
||||
|
||||
AstArray<AstType*> returnTypes;
|
||||
const auto& [retVector, retTail] = flatten(ftv.retType);
|
||||
returnTypes.size = retVector.size();
|
||||
returnTypes.data = static_cast<AstType**>(allocator->allocate(sizeof(AstType*) * returnTypes.size));
|
||||
for (size_t i = 0; i < returnTypes.size; ++i)
|
||||
{
|
||||
RecursionCounter counter(&count);
|
||||
|
||||
returnTypes.data[i] = Luau::visit(*this, (retVector[i])->ty);
|
||||
}
|
||||
|
||||
AstTypePack* retTailAnnotation = nullptr;
|
||||
if (retTail)
|
||||
{
|
||||
TypePackId tail = *retTail;
|
||||
if (const VariadicTypePack* vtp = get<VariadicTypePack>(tail))
|
||||
{
|
||||
retTailAnnotation = allocator->alloc<AstTypePackVariadic>(Location(), Luau::visit(*this, vtp->ty->ty));
|
||||
}
|
||||
}
|
||||
|
||||
return allocator->alloc<AstTypeFunction>(
|
||||
Location(), generics, genericPacks, AstTypeList{argTypes, argTailAnnotation}, argNames, AstTypeList{returnTypes, retTailAnnotation});
|
||||
}
|
||||
AstType* operator()(const Unifiable::Error&) const
|
||||
{
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("Unifiable<Error>"));
|
||||
}
|
||||
AstType* operator()(const GenericTypeVar& gtv) const
|
||||
{
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName(gtv.name.c_str()));
|
||||
}
|
||||
AstType* operator()(const Unifiable::Bound<TypeId>& bound) const
|
||||
{
|
||||
return Luau::visit(*this, bound.boundTo->ty);
|
||||
}
|
||||
AstType* operator()(Unifiable::Free ftv) const
|
||||
{
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("free"));
|
||||
}
|
||||
AstType* operator()(const UnionTypeVar& uv) const
|
||||
{
|
||||
AstArray<AstType*> unionTypes;
|
||||
unionTypes.size = uv.options.size();
|
||||
unionTypes.data = static_cast<AstType**>(allocator->allocate(sizeof(AstType*) * unionTypes.size));
|
||||
for (size_t i = 0; i < unionTypes.size; ++i)
|
||||
{
|
||||
unionTypes.data[i] = Luau::visit(*this, uv.options[i]->ty);
|
||||
}
|
||||
return allocator->alloc<AstTypeUnion>(Location(), unionTypes);
|
||||
}
|
||||
AstType* operator()(const IntersectionTypeVar& uv) const
|
||||
{
|
||||
AstArray<AstType*> intersectionTypes;
|
||||
intersectionTypes.size = uv.parts.size();
|
||||
intersectionTypes.data = static_cast<AstType**>(allocator->allocate(sizeof(AstType*) * intersectionTypes.size));
|
||||
for (size_t i = 0; i < intersectionTypes.size; ++i)
|
||||
{
|
||||
intersectionTypes.data[i] = Luau::visit(*this, uv.parts[i]->ty);
|
||||
}
|
||||
return allocator->alloc<AstTypeIntersection>(Location(), intersectionTypes);
|
||||
}
|
||||
AstType* operator()(const LazyTypeVar& ltv) const
|
||||
{
|
||||
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("<Lazy?>"));
|
||||
}
|
||||
|
||||
private:
|
||||
Allocator* allocator;
|
||||
const TypeRehydrationOptions& options;
|
||||
};
|
||||
|
||||
class TypeAttacher : public AstVisitor
|
||||
{
|
||||
public:
|
||||
TypeAttacher(Module& checker, Luau::Allocator* alloc)
|
||||
: module(checker)
|
||||
, allocator(alloc)
|
||||
{
|
||||
}
|
||||
ScopePtr getScope(const Location& loc)
|
||||
{
|
||||
Location scopeLocation;
|
||||
ScopePtr scope = nullptr;
|
||||
for (const auto& s : module.scopes)
|
||||
{
|
||||
if (s.first.encloses(loc))
|
||||
{
|
||||
if (!scope || scopeLocation.encloses(s.first))
|
||||
{
|
||||
scopeLocation = s.first;
|
||||
scope = s.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return scope;
|
||||
}
|
||||
|
||||
AstType* typeAst(std::optional<TypeId> type)
|
||||
{
|
||||
if (!type)
|
||||
return nullptr;
|
||||
return Luau::visit(TypeRehydrationVisitor(allocator), (*type)->ty);
|
||||
}
|
||||
|
||||
AstArray<Luau::AstType*> typeAstPack(TypePackId type)
|
||||
{
|
||||
const auto& [v, tail] = flatten(type);
|
||||
|
||||
AstArray<AstType*> result;
|
||||
result.size = v.size();
|
||||
result.data = static_cast<AstType**>(allocator->allocate(sizeof(AstType*) * v.size()));
|
||||
for (size_t i = 0; i < v.size(); ++i)
|
||||
{
|
||||
result.data[i] = Luau::visit(TypeRehydrationVisitor(allocator), v[i]->ty);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
virtual bool visit(AstStatLocal* al) override
|
||||
{
|
||||
for (size_t i = 0; i < al->vars.size; ++i)
|
||||
{
|
||||
visitLocal(al->vars.data[i]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool visitLocal(AstLocal* local)
|
||||
{
|
||||
AstType* annotation = local->annotation;
|
||||
if (!annotation)
|
||||
{
|
||||
if (auto result = getScope(local->location)->lookup(local))
|
||||
local->annotation = typeAst(*result);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool visit(AstExprLocal* al) override
|
||||
{
|
||||
return visitLocal(al->local);
|
||||
}
|
||||
virtual bool visit(AstExprFunction* fn) override
|
||||
{
|
||||
// TODO: add generics if the inferred type of the function is generic CLI-39908
|
||||
for (size_t i = 0; i < fn->args.size; ++i)
|
||||
{
|
||||
AstLocal* arg = fn->args.data[i];
|
||||
visitLocal(arg);
|
||||
}
|
||||
|
||||
if (!fn->hasReturnAnnotation)
|
||||
{
|
||||
if (auto result = getScope(fn->body->location))
|
||||
{
|
||||
TypePackId ret = result->returnType;
|
||||
fn->hasReturnAnnotation = true;
|
||||
|
||||
AstTypePack* variadicAnnotation = nullptr;
|
||||
const auto& [v, tail] = flatten(ret);
|
||||
|
||||
if (tail)
|
||||
{
|
||||
TypePackId tailPack = *tail;
|
||||
if (const VariadicTypePack* vtp = get<VariadicTypePack>(tailPack))
|
||||
variadicAnnotation = allocator->alloc<AstTypePackVariadic>(Location(), typeAst(vtp->ty));
|
||||
}
|
||||
|
||||
fn->returnAnnotation = AstTypeList{typeAstPack(ret), variadicAnnotation};
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
Module& module;
|
||||
Allocator* allocator;
|
||||
};
|
||||
|
||||
void attachTypeData(SourceModule& source, Module& result)
|
||||
{
|
||||
TypeAttacher ta(result, source.allocator.get());
|
||||
source.root->visit(&ta);
|
||||
}
|
||||
|
||||
AstType* rehydrateAnnotation(TypeId type, Allocator* allocator, const TypeRehydrationOptions& options)
|
||||
{
|
||||
return Luau::visit(TypeRehydrationVisitor(allocator, options), type->ty);
|
||||
}
|
||||
|
||||
} // namespace Luau
|
5497
Analysis/src/TypeInfer.cpp
Normal file
5497
Analysis/src/TypeInfer.cpp
Normal file
File diff suppressed because it is too large
Load diff
277
Analysis/src/TypePack.cpp
Normal file
277
Analysis/src/TypePack.cpp
Normal file
|
@ -0,0 +1,277 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/TypePack.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
TypePackVar::TypePackVar(const TypePackVariant& tp)
|
||||
: ty(tp)
|
||||
{
|
||||
}
|
||||
|
||||
TypePackVar::TypePackVar(TypePackVariant&& tp)
|
||||
: ty(std::move(tp))
|
||||
{
|
||||
}
|
||||
|
||||
TypePackVar::TypePackVar(TypePackVariant&& tp, bool persistent)
|
||||
: ty(std::move(tp))
|
||||
, persistent(persistent)
|
||||
{
|
||||
}
|
||||
|
||||
bool TypePackVar::operator==(const TypePackVar& rhs) const
|
||||
{
|
||||
SeenSet seen;
|
||||
return areEqual(seen, *this, rhs);
|
||||
}
|
||||
|
||||
TypePackVar& TypePackVar::operator=(TypePackVariant&& tp)
|
||||
{
|
||||
ty = std::move(tp);
|
||||
return *this;
|
||||
}
|
||||
|
||||
TypePackIterator::TypePackIterator(TypePackId typePack)
|
||||
: currentTypePack(follow(typePack))
|
||||
, tp(get<TypePack>(currentTypePack))
|
||||
, currentIndex(0)
|
||||
{
|
||||
while (tp && tp->head.empty())
|
||||
{
|
||||
currentTypePack = tp->tail ? follow(*tp->tail) : nullptr;
|
||||
tp = currentTypePack ? get<TypePack>(currentTypePack) : nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
TypePackIterator& TypePackIterator::operator++()
|
||||
{
|
||||
LUAU_ASSERT(tp);
|
||||
|
||||
++currentIndex;
|
||||
while (tp && currentIndex >= tp->head.size())
|
||||
{
|
||||
currentTypePack = tp->tail ? follow(*tp->tail) : nullptr;
|
||||
tp = currentTypePack ? get<TypePack>(currentTypePack) : nullptr;
|
||||
currentIndex = 0;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
TypePackIterator TypePackIterator::operator++(int)
|
||||
{
|
||||
TypePackIterator copy = *this;
|
||||
++*this;
|
||||
return copy;
|
||||
}
|
||||
|
||||
bool TypePackIterator::operator!=(const TypePackIterator& rhs)
|
||||
{
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
bool TypePackIterator::operator==(const TypePackIterator& rhs)
|
||||
{
|
||||
return tp == rhs.tp && currentIndex == rhs.currentIndex;
|
||||
}
|
||||
|
||||
const TypeId& TypePackIterator::operator*()
|
||||
{
|
||||
LUAU_ASSERT(tp);
|
||||
return tp->head[currentIndex];
|
||||
}
|
||||
|
||||
std::optional<TypePackId> TypePackIterator::tail()
|
||||
{
|
||||
LUAU_ASSERT(!tp);
|
||||
return currentTypePack ? std::optional<TypePackId>{currentTypePack} : std::nullopt;
|
||||
}
|
||||
|
||||
TypePackIterator begin(TypePackId tp)
|
||||
{
|
||||
return TypePackIterator{tp};
|
||||
}
|
||||
|
||||
TypePackIterator end(TypePackId tp)
|
||||
{
|
||||
return FFlag::LuauAddMissingFollow ? TypePackIterator{} : TypePackIterator{nullptr};
|
||||
}
|
||||
|
||||
bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs)
|
||||
{
|
||||
TypePackId lhsId = const_cast<TypePackId>(&lhs);
|
||||
TypePackId rhsId = const_cast<TypePackId>(&rhs);
|
||||
TypePackIterator lhsIter = begin(lhsId);
|
||||
TypePackIterator rhsIter = begin(rhsId);
|
||||
TypePackIterator lhsEnd = end(lhsId);
|
||||
TypePackIterator rhsEnd = end(rhsId);
|
||||
while (lhsIter != lhsEnd && rhsIter != rhsEnd)
|
||||
{
|
||||
if (!areEqual(seen, **lhsIter, **rhsIter))
|
||||
return false;
|
||||
++lhsIter;
|
||||
++rhsIter;
|
||||
}
|
||||
|
||||
if (lhsIter != lhsEnd || rhsIter != rhsEnd)
|
||||
return false;
|
||||
|
||||
if (!lhsIter.tail() && !rhsIter.tail())
|
||||
return true;
|
||||
if (!lhsIter.tail() || !rhsIter.tail())
|
||||
return false;
|
||||
|
||||
TypePackId lhsTail = *lhsIter.tail();
|
||||
TypePackId rhsTail = *rhsIter.tail();
|
||||
|
||||
{
|
||||
const Unifiable::Free* lf = get_if<Unifiable::Free>(&lhsTail->ty);
|
||||
const Unifiable::Free* rf = get_if<Unifiable::Free>(&rhsTail->ty);
|
||||
if (lf && rf)
|
||||
return lf->index == rf->index;
|
||||
}
|
||||
|
||||
{
|
||||
const Unifiable::Bound<TypePackId>* lb = get_if<Unifiable::Bound<TypePackId>>(&lhsTail->ty);
|
||||
const Unifiable::Bound<TypePackId>* rb = get_if<Unifiable::Bound<TypePackId>>(&rhsTail->ty);
|
||||
if (lb && rb)
|
||||
return areEqual(seen, *lb->boundTo, *rb->boundTo);
|
||||
}
|
||||
|
||||
{
|
||||
const Unifiable::Generic* lg = get_if<Unifiable::Generic>(&lhsTail->ty);
|
||||
const Unifiable::Generic* rg = get_if<Unifiable::Generic>(&rhsTail->ty);
|
||||
if (lg && rg)
|
||||
return lg->index == rg->index;
|
||||
}
|
||||
|
||||
{
|
||||
const VariadicTypePack* lv = get_if<VariadicTypePack>(&lhsTail->ty);
|
||||
const VariadicTypePack* rv = get_if<VariadicTypePack>(&rhsTail->ty);
|
||||
if (lv && rv)
|
||||
return areEqual(seen, *lv->ty, *rv->ty);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
TypePackId follow(TypePackId tp)
|
||||
{
|
||||
auto advance = [](TypePackId ty) -> std::optional<TypePackId> {
|
||||
if (const Unifiable::Bound<TypePackId>* btv = get<Unifiable::Bound<TypePackId>>(ty))
|
||||
return btv->boundTo;
|
||||
else
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
TypePackId cycleTester = tp; // Null once we've determined that there is no cycle
|
||||
if (auto a = advance(cycleTester))
|
||||
cycleTester = *a;
|
||||
else
|
||||
return tp;
|
||||
|
||||
while (true)
|
||||
{
|
||||
auto a1 = advance(tp);
|
||||
if (a1)
|
||||
tp = *a1;
|
||||
else
|
||||
return tp;
|
||||
|
||||
if (nullptr != cycleTester)
|
||||
{
|
||||
auto a2 = advance(cycleTester);
|
||||
if (a2)
|
||||
{
|
||||
auto a3 = advance(*a2);
|
||||
if (a3)
|
||||
cycleTester = *a3;
|
||||
else
|
||||
cycleTester = nullptr;
|
||||
}
|
||||
else
|
||||
cycleTester = nullptr;
|
||||
|
||||
if (tp == cycleTester)
|
||||
throw std::runtime_error("Luau::follow detected a TypeVar cycle!!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t size(TypePackId tp)
|
||||
{
|
||||
if (auto pack = get<TypePack>(FFlag::LuauAddMissingFollow ? follow(tp) : tp))
|
||||
return size(*pack);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t size(const TypePack& tp)
|
||||
{
|
||||
size_t result = tp.head.size();
|
||||
if (tp.tail)
|
||||
{
|
||||
const TypePack* tail = get<TypePack>(FFlag::LuauAddMissingFollow ? follow(*tp.tail) : *tp.tail);
|
||||
if (tail)
|
||||
result += size(*tail);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<TypeId> first(TypePackId tp)
|
||||
{
|
||||
auto it = begin(tp);
|
||||
auto endIter = end(tp);
|
||||
|
||||
if (it != endIter)
|
||||
return *it;
|
||||
|
||||
if (auto tail = it.tail())
|
||||
{
|
||||
if (auto vtp = get<VariadicTypePack>(*tail))
|
||||
return vtp->ty;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool isEmpty(TypePackId tp)
|
||||
{
|
||||
tp = follow(tp);
|
||||
if (auto tpp = get<TypePack>(tp))
|
||||
{
|
||||
return tpp->head.empty() && (!tpp->tail || isEmpty(*tpp->tail));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::pair<std::vector<TypeId>, std::optional<TypePackId>> flatten(TypePackId tp)
|
||||
{
|
||||
std::vector<TypeId> res;
|
||||
|
||||
auto iter = begin(tp);
|
||||
auto endIter = end(tp);
|
||||
while (iter != endIter)
|
||||
{
|
||||
res.push_back(*iter);
|
||||
++iter;
|
||||
}
|
||||
|
||||
return {res, iter.tail()};
|
||||
}
|
||||
|
||||
TypePackVar* asMutable(TypePackId tp)
|
||||
{
|
||||
return const_cast<TypePackVar*>(tp);
|
||||
}
|
||||
|
||||
TypePack* asMutable(const TypePack* tp)
|
||||
{
|
||||
return const_cast<TypePack*>(tp);
|
||||
}
|
||||
|
||||
} // namespace Luau
|
95
Analysis/src/TypeUtils.cpp
Normal file
95
Analysis/src/TypeUtils.cpp
Normal file
|
@ -0,0 +1,95 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/TypeUtils.h"
|
||||
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauStringMetatable)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
std::optional<TypeId> findMetatableEntry(ErrorVec& errors, const ScopePtr& globalScope, TypeId type, std::string entry, Location location)
|
||||
{
|
||||
type = follow(type);
|
||||
|
||||
if (!FFlag::LuauStringMetatable)
|
||||
{
|
||||
if (const PrimitiveTypeVar* primType = get<PrimitiveTypeVar>(type))
|
||||
{
|
||||
if (primType->type != PrimitiveTypeVar::String || "__index" != entry)
|
||||
return std::nullopt;
|
||||
|
||||
auto it = globalScope->bindings.find(AstName{"string"});
|
||||
if (it != globalScope->bindings.end())
|
||||
return it->second.typeId;
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<TypeId> metatable = getMetatable(type);
|
||||
if (!metatable)
|
||||
return std::nullopt;
|
||||
|
||||
TypeId unwrapped = follow(*metatable);
|
||||
|
||||
if (get<AnyTypeVar>(unwrapped))
|
||||
return singletonTypes.anyType;
|
||||
|
||||
const TableTypeVar* mtt = getTableType(unwrapped);
|
||||
if (!mtt)
|
||||
{
|
||||
errors.push_back(TypeError{location, GenericError{"Metatable was not a table."}});
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto it = mtt->props.find(entry);
|
||||
if (it != mtt->props.end())
|
||||
return it->second.type;
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, const ScopePtr& globalScope, TypeId ty, Name name, Location location)
|
||||
{
|
||||
if (get<AnyTypeVar>(ty))
|
||||
return ty;
|
||||
|
||||
if (const TableTypeVar* tableType = getTableType(ty))
|
||||
{
|
||||
const auto& it = tableType->props.find(name);
|
||||
if (it != tableType->props.end())
|
||||
return it->second.type;
|
||||
}
|
||||
|
||||
std::optional<TypeId> mtIndex = findMetatableEntry(errors, globalScope, ty, "__index", location);
|
||||
while (mtIndex)
|
||||
{
|
||||
TypeId index = follow(*mtIndex);
|
||||
if (const auto& itt = getTableType(index))
|
||||
{
|
||||
const auto& fit = itt->props.find(name);
|
||||
if (fit != itt->props.end())
|
||||
return fit->second.type;
|
||||
}
|
||||
else if (const auto& itf = get<FunctionTypeVar>(index))
|
||||
{
|
||||
std::optional<TypeId> r = first(follow(itf->retType));
|
||||
if (!r)
|
||||
return singletonTypes.nilType;
|
||||
else
|
||||
return *r;
|
||||
}
|
||||
else if (get<AnyTypeVar>(index))
|
||||
return singletonTypes.anyType;
|
||||
else
|
||||
errors.push_back(TypeError{location, GenericError{"__index should either be a function or table. Got " + toString(index)}});
|
||||
|
||||
mtIndex = findMetatableEntry(errors, globalScope, *mtIndex, "__index", location);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
1505
Analysis/src/TypeVar.cpp
Normal file
1505
Analysis/src/TypeVar.cpp
Normal file
File diff suppressed because it is too large
Load diff
99
Analysis/src/TypedAllocator.cpp
Normal file
99
Analysis/src/TypedAllocator.cpp
Normal file
|
@ -0,0 +1,99 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/TypedAllocator.h"
|
||||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <Windows.h>
|
||||
|
||||
const size_t kPageSize = 4096;
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
const size_t kPageSize = sysconf(_SC_PAGESIZE);
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauFreezeArena)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
static void* systemAllocateAligned(size_t size, size_t align)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return _aligned_malloc(size, align);
|
||||
#elif defined(__ANDROID__) // for Android 4.1
|
||||
return memalign(align, size);
|
||||
#else
|
||||
void* ptr;
|
||||
return posix_memalign(&ptr, align, size) == 0 ? ptr : 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void systemDeallocateAligned(void* ptr)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
_aligned_free(ptr);
|
||||
#else
|
||||
free(ptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
static size_t pageAlign(size_t size)
|
||||
{
|
||||
return (size + kPageSize - 1) & ~(kPageSize - 1);
|
||||
}
|
||||
|
||||
void* pagedAllocate(size_t size)
|
||||
{
|
||||
if (FFlag::DebugLuauFreezeArena)
|
||||
return systemAllocateAligned(pageAlign(size), kPageSize);
|
||||
else
|
||||
return ::operator new(size, std::nothrow);
|
||||
}
|
||||
|
||||
void pagedDeallocate(void* ptr)
|
||||
{
|
||||
if (FFlag::DebugLuauFreezeArena)
|
||||
systemDeallocateAligned(ptr);
|
||||
else
|
||||
::operator delete(ptr);
|
||||
}
|
||||
|
||||
void pagedFreeze(void* ptr, size_t size)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::DebugLuauFreezeArena);
|
||||
LUAU_ASSERT(uintptr_t(ptr) % kPageSize == 0);
|
||||
|
||||
#ifdef _WIN32
|
||||
DWORD oldProtect;
|
||||
BOOL rc = VirtualProtect(ptr, pageAlign(size), PAGE_READONLY, &oldProtect);
|
||||
LUAU_ASSERT(rc);
|
||||
#else
|
||||
int rc = mprotect(ptr, pageAlign(size), PROT_READ);
|
||||
LUAU_ASSERT(rc == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
void pagedUnfreeze(void* ptr, size_t size)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::DebugLuauFreezeArena);
|
||||
LUAU_ASSERT(uintptr_t(ptr) % kPageSize == 0);
|
||||
|
||||
#ifdef _WIN32
|
||||
DWORD oldProtect;
|
||||
BOOL rc = VirtualProtect(ptr, pageAlign(size), PAGE_READWRITE, &oldProtect);
|
||||
LUAU_ASSERT(rc);
|
||||
#else
|
||||
int rc = mprotect(ptr, pageAlign(size), PROT_READ | PROT_WRITE);
|
||||
LUAU_ASSERT(rc == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Luau
|
67
Analysis/src/Unifiable.cpp
Normal file
67
Analysis/src/Unifiable.cpp
Normal file
|
@ -0,0 +1,67 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Unifiable.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauRankNTypes)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace Unifiable
|
||||
{
|
||||
|
||||
Free::Free(TypeLevel level)
|
||||
: index(++nextIndex)
|
||||
, level(level)
|
||||
{
|
||||
}
|
||||
|
||||
Free::Free(TypeLevel level, bool DEPRECATED_canBeGeneric)
|
||||
: index(++nextIndex)
|
||||
, level(level)
|
||||
, DEPRECATED_canBeGeneric(DEPRECATED_canBeGeneric)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauRankNTypes);
|
||||
}
|
||||
|
||||
int Free::nextIndex = 0;
|
||||
|
||||
Generic::Generic()
|
||||
: index(++nextIndex)
|
||||
, name("g" + std::to_string(index))
|
||||
, explicitName(false)
|
||||
{
|
||||
}
|
||||
|
||||
Generic::Generic(TypeLevel level)
|
||||
: index(++nextIndex)
|
||||
, level(level)
|
||||
, name("g" + std::to_string(index))
|
||||
, explicitName(false)
|
||||
{
|
||||
}
|
||||
|
||||
Generic::Generic(const Name& name)
|
||||
: index(++nextIndex)
|
||||
, name(name)
|
||||
, explicitName(true)
|
||||
{
|
||||
}
|
||||
|
||||
Generic::Generic(TypeLevel level, const Name& name)
|
||||
: index(++nextIndex)
|
||||
, level(level)
|
||||
, name(name)
|
||||
, explicitName(true)
|
||||
{
|
||||
}
|
||||
|
||||
int Generic::nextIndex = 0;
|
||||
|
||||
Error::Error()
|
||||
: index(++nextIndex)
|
||||
{
|
||||
}
|
||||
|
||||
int Error::nextIndex = 0;
|
||||
|
||||
} // namespace Unifiable
|
||||
} // namespace Luau
|
1575
Analysis/src/Unifier.cpp
Normal file
1575
Analysis/src/Unifier.cpp
Normal file
File diff suppressed because it is too large
Load diff
1198
Ast/include/Luau/Ast.h
Normal file
1198
Ast/include/Luau/Ast.h
Normal file
File diff suppressed because it is too large
Load diff
133
Ast/include/Luau/Common.h
Normal file
133
Ast/include/Luau/Common.h
Normal file
|
@ -0,0 +1,133 @@
|
|||
// 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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
using AssertHandler = int (*)(const char* expression, const char* file, int line);
|
||||
|
||||
inline AssertHandler& assertHandler()
|
||||
{
|
||||
static AssertHandler handler = nullptr;
|
||||
return handler;
|
||||
}
|
||||
|
||||
inline int assertCallHandler(const char* expression, const char* file, int line)
|
||||
{
|
||||
if (AssertHandler handler = assertHandler())
|
||||
return handler(expression, file, line);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
||||
#if !defined(NDEBUG) || defined(LUAU_ENABLE_ASSERT)
|
||||
#define LUAU_ASSERT(expr) ((void)(!!(expr) || (Luau::assertCallHandler(#expr, __FILE__, __LINE__) && (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, void (*reg)(const char*, T*, bool) = nullptr)
|
||||
: value(def)
|
||||
, dynamic(dynamic)
|
||||
, name(name)
|
||||
, next(list)
|
||||
{
|
||||
list = this;
|
||||
|
||||
if (reg)
|
||||
reg(name, &value, dynamic);
|
||||
}
|
||||
|
||||
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, nullptr); \
|
||||
}
|
||||
#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, nullptr); \
|
||||
}
|
||||
|
||||
#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, nullptr); \
|
||||
}
|
||||
#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, nullptr); \
|
||||
}
|
9
Ast/include/Luau/Confusables.h
Normal file
9
Ast/include/Luau/Confusables.h
Normal file
|
@ -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);
|
||||
}
|
407
Ast/include/Luau/DenseHash.h
Normal file
407
Ast/include/Luau/DenseHash.h
Normal file
|
@ -0,0 +1,407 @@
|
|||
// 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 <vector>
|
||||
#include <type_traits>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
// Internal implementation of DenseHashSet and DenseHashMap
|
||||
namespace detail
|
||||
{
|
||||
|
||||
struct DenseHashPointer
|
||||
{
|
||||
size_t operator()(const void* key) const
|
||||
{
|
||||
return (uintptr_t(key) >> 4) ^ (uintptr_t(key) >> 9);
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
DenseHashTable(const Key& empty_key, size_t buckets = 0)
|
||||
: count(0)
|
||||
, empty_key(empty_key)
|
||||
{
|
||||
// buckets has to be power-of-two or zero
|
||||
LUAU_ASSERT((buckets & (buckets - 1)) == 0);
|
||||
|
||||
// don't move this to initializer list! this works around an MSVC codegen issue on AMD CPUs:
|
||||
// https://developercommunity.visualstudio.com/t/stdvector-constructor-from-size-t-is-25-times-slow/1546547
|
||||
if (buckets)
|
||||
data.resize(buckets, ItemInterface::create(empty_key));
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
data.clear();
|
||||
count = 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 = data.size() - 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 (data.empty())
|
||||
return 0;
|
||||
if (eq(key, empty_key))
|
||||
return 0;
|
||||
|
||||
size_t hashmod = data.size() - 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 = data.empty() ? 16 : data.size() * 2;
|
||||
|
||||
if (data.empty() && data.capacity() >= newsize)
|
||||
{
|
||||
LUAU_ASSERT(count == 0);
|
||||
data.resize(newsize, ItemInterface::create(empty_key));
|
||||
return;
|
||||
}
|
||||
|
||||
DenseHashTable newtable(empty_key, newsize);
|
||||
|
||||
for (size_t i = 0; i < data.size(); ++i)
|
||||
{
|
||||
const Key& key = ItemInterface::getKey(data[i]);
|
||||
|
||||
if (!eq(key, empty_key))
|
||||
*newtable.insert_unsafe(key) = data[i];
|
||||
}
|
||||
|
||||
LUAU_ASSERT(count == newtable.count);
|
||||
data.swap(newtable.data);
|
||||
}
|
||||
|
||||
void rehash_if_full()
|
||||
{
|
||||
if (count >= data.size() * 3 / 4)
|
||||
{
|
||||
rehash();
|
||||
}
|
||||
}
|
||||
|
||||
const_iterator begin() const
|
||||
{
|
||||
size_t start = 0;
|
||||
|
||||
while (start < data.size() && eq(ItemInterface::getKey(data[start]), empty_key))
|
||||
start++;
|
||||
|
||||
return const_iterator(this, start);
|
||||
}
|
||||
|
||||
const_iterator end() const
|
||||
{
|
||||
return const_iterator(this, data.size());
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return count;
|
||||
}
|
||||
|
||||
class const_iterator
|
||||
{
|
||||
public:
|
||||
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->data.size();
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
private:
|
||||
std::vector<Item> data;
|
||||
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 Key create(const Key& key)
|
||||
{
|
||||
return 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 std::pair<Key, Value> create(const Key& key)
|
||||
{
|
||||
return std::pair<Key, Value>(key, 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;
|
||||
|
||||
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();
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
// 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;
|
||||
|
||||
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();
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Luau
|
236
Ast/include/Luau/Lexer.h
Normal file
236
Ast/include/Luau/Lexer.h
Normal file
|
@ -0,0 +1,236 @@
|
|||
// 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"
|
||||
|
||||
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,
|
||||
|
||||
AddAssign,
|
||||
SubAssign,
|
||||
MulAssign,
|
||||
DivAssign,
|
||||
ModAssign,
|
||||
PowAssign,
|
||||
ConcatAssign,
|
||||
|
||||
RawString,
|
||||
QuotedString,
|
||||
Number,
|
||||
Name,
|
||||
|
||||
Comment,
|
||||
BlockComment,
|
||||
|
||||
BrokenString,
|
||||
BrokenComment,
|
||||
BrokenUnicode,
|
||||
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);
|
||||
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;
|
||||
|
||||
void consume();
|
||||
|
||||
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();
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
109
Ast/include/Luau/Location.h
Normal file
109
Ast/include/Luau/Location.h
Normal file
|
@ -0,0 +1,109 @@
|
|||
// 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)
|
||||
: line(line)
|
||||
, column(column)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const Position& rhs) const
|
||||
{
|
||||
return this->column == rhs.column && this->line == rhs.line;
|
||||
}
|
||||
bool operator!=(const Position& rhs) const
|
||||
{
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
bool operator<(const Position& rhs) const
|
||||
{
|
||||
if (line == rhs.line)
|
||||
return column < rhs.column;
|
||||
else
|
||||
return line < rhs.line;
|
||||
}
|
||||
|
||||
bool operator>(const Position& rhs) const
|
||||
{
|
||||
if (line == rhs.line)
|
||||
return column > rhs.column;
|
||||
else
|
||||
return line > rhs.line;
|
||||
}
|
||||
|
||||
bool operator<=(const Position& rhs) const
|
||||
{
|
||||
return *this == rhs || *this < rhs;
|
||||
}
|
||||
|
||||
bool operator>=(const Position& rhs) const
|
||||
{
|
||||
return *this == rhs || *this > rhs;
|
||||
}
|
||||
};
|
||||
|
||||
struct Location
|
||||
{
|
||||
Position begin, end;
|
||||
|
||||
Location()
|
||||
: begin(0, 0)
|
||||
, end(0, 0)
|
||||
{
|
||||
}
|
||||
|
||||
Location(const Position& begin, const Position& end)
|
||||
: begin(begin)
|
||||
, end(end)
|
||||
{
|
||||
}
|
||||
|
||||
Location(const Position& begin, unsigned int length)
|
||||
: begin(begin)
|
||||
, end(begin.line, begin.column + length)
|
||||
{
|
||||
}
|
||||
|
||||
Location(const Location& begin, const Location& end)
|
||||
: begin(begin.begin)
|
||||
, end(end.end)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const Location& rhs) const
|
||||
{
|
||||
return this->begin == rhs.begin && this->end == rhs.end;
|
||||
}
|
||||
bool operator!=(const Location& rhs) const
|
||||
{
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
bool encloses(const Location& l) const
|
||||
{
|
||||
return begin <= l.begin && end >= l.end;
|
||||
}
|
||||
bool contains(const Position& p) const
|
||||
{
|
||||
return begin <= p && p < end;
|
||||
}
|
||||
bool containsClosed(const Position& p) const
|
||||
{
|
||||
return begin <= p && p <= end;
|
||||
}
|
||||
};
|
||||
|
||||
std::string toString(const Position& position);
|
||||
std::string toString(const Location& location);
|
||||
|
||||
} // namespace Luau
|
23
Ast/include/Luau/ParseOptions.h
Normal file
23
Ast/include/Luau/ParseOptions.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
// 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 allowTypeAnnotations = true;
|
||||
bool supportContinueStatement = true;
|
||||
bool allowDeclarationSyntax = false;
|
||||
bool captureComments = false;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
423
Ast/include/Luau/Parser.h
Normal file
423
Ast/include/Luau/Parser.h
Normal file
|
@ -0,0 +1,423 @@
|
|||
// 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/StringUtils.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <initializer_list>
|
||||
#include <optional>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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_;
|
||||
};
|
||||
|
||||
struct Comment
|
||||
{
|
||||
Lexeme::Type type; // Comment, BlockComment, or BrokenComment
|
||||
Location location;
|
||||
};
|
||||
|
||||
struct ParseResult
|
||||
{
|
||||
AstStatBlock* root;
|
||||
std::vector<std::string> hotcomments;
|
||||
std::vector<ParseError> errors;
|
||||
|
||||
std::vector<Comment> commentLocations;
|
||||
};
|
||||
|
||||
class Parser
|
||||
{
|
||||
public:
|
||||
static ParseResult parse(
|
||||
const char* buffer, std::size_t bufferSize, AstNameTable& names, Allocator& allocator, ParseOptions options = ParseOptions());
|
||||
|
||||
static constexpr const char* errorName = "%error-id%";
|
||||
|
||||
private:
|
||||
struct Name;
|
||||
struct Binding;
|
||||
|
||||
Parser(const char* buffer, std::size_t bufferSize, AstNameTable& names, Allocator& allocator);
|
||||
|
||||
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();
|
||||
|
||||
// function funcname funcbody |
|
||||
// funcname ::= Name {`.' Name} [`:' Name]
|
||||
AstStat* parseFunctionStat();
|
||||
|
||||
// local function Name funcbody |
|
||||
// local namelist [`=' explist]
|
||||
AstStat* parseLocal();
|
||||
|
||||
// return [explist]
|
||||
AstStat* parseReturn();
|
||||
|
||||
// type Name `=' typeannotation
|
||||
AstStat* parseTypeAlias(const Location& start, bool exported);
|
||||
|
||||
AstDeclaredClassProp parseDeclaredClassMethod();
|
||||
|
||||
// `declare global' Name: typeannotation |
|
||||
// `declare function' Name`(' [parlist] `)' [`:` TypeAnnotation]
|
||||
AstStat* parseDeclaration(const Location& start);
|
||||
|
||||
// varlist `=' explist
|
||||
AstStat* parseAssignment(AstExpr* initial);
|
||||
|
||||
// var [`+=' | `-=' | `*=' | `/=' | `%=' | `^=' | `..='] exp
|
||||
AstStat* parseCompoundAssignment(AstExpr* initial, AstExprBinary::Op op);
|
||||
|
||||
// funcbody ::= `(' [parlist] `)' block end
|
||||
// parlist ::= namelist [`,' `...'] | `...'
|
||||
std::pair<AstExprFunction*, AstLocal*> parseFunctionBody(
|
||||
bool hasself, const Lexeme& matchFunction, const AstName& debugname, std::optional<Name> localName);
|
||||
|
||||
// explist ::= {exp `,'} exp
|
||||
void parseExprList(TempVector<AstExpr*>& result);
|
||||
|
||||
// binding ::= Name [`:` TypeAnnotation]
|
||||
Binding parseBinding();
|
||||
|
||||
// bindinglist ::= (binding | `...') {`,' bindinglist}
|
||||
// Returns the location of the vararg ..., or std::nullopt if the function is not vararg.
|
||||
std::pair<std::optional<Location>, AstTypePack*> parseBindingList(TempVector<Binding>& result, bool allowDot3 = false);
|
||||
|
||||
AstType* parseOptionalTypeAnnotation();
|
||||
|
||||
// TypeList ::= TypeAnnotation [`,' TypeList]
|
||||
// ReturnType ::= TypeAnnotation | `(' TypeList `)'
|
||||
// TableProp ::= Name `:' TypeAnnotation
|
||||
// TableIndexer ::= `[' TypeAnnotation `]' `:' TypeAnnotation
|
||||
// PropList ::= (TableProp | TableIndexer) [`,' PropList]
|
||||
// TypeAnnotation
|
||||
// ::= 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> parseOptionalReturnTypeAnnotation();
|
||||
std::pair<Location, AstTypeList> parseReturnTypeAnnotation();
|
||||
|
||||
AstTableIndexer* parseTableIndexerAnnotation();
|
||||
|
||||
AstType* parseFunctionTypeAnnotation();
|
||||
AstType* parseFunctionTypeAnnotationTail(const Lexeme& begin, AstArray<AstName> generics, AstArray<AstName> genericPacks,
|
||||
AstArray<AstType*>& params, AstArray<std::optional<AstArgumentName>>& paramNames, AstTypePack* varargAnnotation);
|
||||
|
||||
AstType* parseTableTypeAnnotation();
|
||||
AstType* parseSimpleTypeAnnotation();
|
||||
|
||||
AstType* parseTypeAnnotation(TempVector<AstType*>& parts, const Location& begin);
|
||||
AstType* parseTypeAnnotation();
|
||||
|
||||
AstTypePack* parseTypePackAnnotation();
|
||||
AstTypePack* parseVariadicArgumentAnnotation();
|
||||
|
||||
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 [`::' typeAnnotation]
|
||||
AstExpr* parseAssertionExpr();
|
||||
|
||||
// simpleexp -> NUMBER | STRING | NIL | true | false | ... | constructor | FUNCTION body | primaryexp
|
||||
AstExpr* parseSimpleExpr();
|
||||
|
||||
// args ::= `(' [explist] `)' | tableconstructor | String
|
||||
AstExpr* parseFunctionArgs(AstExpr* func, bool self, const Location& selfLocation);
|
||||
|
||||
// tableconstructor ::= `{' [fieldlist] `}'
|
||||
// fieldlist ::= field {fieldsep field} [fieldsep]
|
||||
// field ::= `[' exp `]' `=' exp | Name `=' exp | exp
|
||||
// fieldsep ::= `,' | `;'
|
||||
AstExpr* parseTableConstructor();
|
||||
|
||||
// TODO: Add grammar rules here?
|
||||
AstExpr* parseIfElseExpr();
|
||||
|
||||
// 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<AstName>, AstArray<AstName>> parseGenericTypeList();
|
||||
std::pair<AstArray<AstName>, AstArray<AstName>> parseGenericTypeListIfFFlagParseGenericFunctions();
|
||||
|
||||
// `<' typeAnnotation[, ...] `>'
|
||||
AstArray<AstType*> parseTypeParams();
|
||||
|
||||
AstExpr* parseString();
|
||||
|
||||
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);
|
||||
|
||||
bool expectMatchAndConsume(char value, const Lexeme& begin, bool searchForMissing = false);
|
||||
void expectMatchAndConsumeFail(Lexeme::Type type, const Lexeme& begin, const char* extra = nullptr);
|
||||
|
||||
bool expectMatchEndAndConsume(Lexeme::Type type, const Lexeme& begin);
|
||||
void expectMatchEndAndConsumeFail(Lexeme::Type type, const Lexeme& 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* reportTypeAnnotationError(const Location& location, const AstArray<AstType*>& types, bool isMissing, const char* format, ...)
|
||||
LUAU_PRINTF_ATTR(5, 6);
|
||||
|
||||
const Lexeme& 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;
|
||||
|
||||
unsigned int recursionCounter;
|
||||
|
||||
AstName nameSelf;
|
||||
AstName nameNumber;
|
||||
AstName nameError;
|
||||
AstName nameNil;
|
||||
|
||||
Lexeme 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<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*> scratchAnnotation;
|
||||
std::vector<AstDeclaredClassProp> scratchDeclaredClassProps;
|
||||
std::vector<AstExprTable::Item> scratchItem;
|
||||
std::vector<AstArgumentName> scratchArgName;
|
||||
std::vector<std::optional<AstArgumentName>> scratchOptArgName;
|
||||
std::string scratchData;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
37
Ast/include/Luau/StringUtils.h
Normal file
37
Ast/include/Luau/StringUtils.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#define LUAU_PRINTF_ATTR(fmt, arg) __attribute__((format(printf, fmt, arg)))
|
||||
#else
|
||||
#define LUAU_PRINTF_ATTR(fmt, arg)
|
||||
#endif
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
} // namespace Luau
|
886
Ast/src/Ast.cpp
Normal file
886
Ast/src/Ast.cpp
Normal file
|
@ -0,0 +1,886 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Ast.h"
|
||||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
static void visitTypeList(AstVisitor* visitor, const AstTypeList& list)
|
||||
{
|
||||
for (AstType* ty : list.types)
|
||||
ty->visit(visitor);
|
||||
|
||||
if (list.tailType)
|
||||
list.tailType->visit(visitor);
|
||||
}
|
||||
|
||||
int gAstRttiIndex = 0;
|
||||
|
||||
AstExprGroup::AstExprGroup(const Location& location, AstExpr* expr)
|
||||
: AstExpr(ClassIndex(), location)
|
||||
, expr(expr)
|
||||
{
|
||||
}
|
||||
|
||||
void AstExprGroup::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
expr->visit(visitor);
|
||||
}
|
||||
|
||||
AstExprConstantNil::AstExprConstantNil(const Location& location)
|
||||
: AstExpr(ClassIndex(), location)
|
||||
{
|
||||
}
|
||||
|
||||
void AstExprConstantNil::visit(AstVisitor* visitor)
|
||||
{
|
||||
visitor->visit(this);
|
||||
}
|
||||
|
||||
AstExprConstantBool::AstExprConstantBool(const Location& location, bool value)
|
||||
: AstExpr(ClassIndex(), location)
|
||||
, value(value)
|
||||
{
|
||||
}
|
||||
|
||||
void AstExprConstantBool::visit(AstVisitor* visitor)
|
||||
{
|
||||
visitor->visit(this);
|
||||
}
|
||||
|
||||
AstExprConstantNumber::AstExprConstantNumber(const Location& location, double value)
|
||||
: AstExpr(ClassIndex(), location)
|
||||
, value(value)
|
||||
{
|
||||
}
|
||||
|
||||
void AstExprConstantNumber::visit(AstVisitor* visitor)
|
||||
{
|
||||
visitor->visit(this);
|
||||
}
|
||||
|
||||
AstExprConstantString::AstExprConstantString(const Location& location, const AstArray<char>& value)
|
||||
: AstExpr(ClassIndex(), location)
|
||||
, value(value)
|
||||
{
|
||||
}
|
||||
|
||||
void AstExprConstantString::visit(AstVisitor* visitor)
|
||||
{
|
||||
visitor->visit(this);
|
||||
}
|
||||
|
||||
AstExprLocal::AstExprLocal(const Location& location, AstLocal* local, bool upvalue)
|
||||
: AstExpr(ClassIndex(), location)
|
||||
, local(local)
|
||||
, upvalue(upvalue)
|
||||
{
|
||||
}
|
||||
|
||||
void AstExprLocal::visit(AstVisitor* visitor)
|
||||
{
|
||||
visitor->visit(this);
|
||||
}
|
||||
|
||||
AstExprGlobal::AstExprGlobal(const Location& location, const AstName& name)
|
||||
: AstExpr(ClassIndex(), location)
|
||||
, name(name)
|
||||
{
|
||||
}
|
||||
|
||||
void AstExprGlobal::visit(AstVisitor* visitor)
|
||||
{
|
||||
visitor->visit(this);
|
||||
}
|
||||
|
||||
AstExprVarargs::AstExprVarargs(const Location& location)
|
||||
: AstExpr(ClassIndex(), location)
|
||||
{
|
||||
}
|
||||
|
||||
void AstExprVarargs::visit(AstVisitor* visitor)
|
||||
{
|
||||
visitor->visit(this);
|
||||
}
|
||||
|
||||
AstExprCall::AstExprCall(const Location& location, AstExpr* func, const AstArray<AstExpr*>& args, bool self, const Location& argLocation)
|
||||
: AstExpr(ClassIndex(), location)
|
||||
, func(func)
|
||||
, args(args)
|
||||
, self(self)
|
||||
, argLocation(argLocation)
|
||||
{
|
||||
}
|
||||
|
||||
void AstExprCall::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
func->visit(visitor);
|
||||
|
||||
for (AstExpr* arg : args)
|
||||
arg->visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
AstExprIndexName::AstExprIndexName(
|
||||
const Location& location, AstExpr* expr, const AstName& index, const Location& indexLocation, const Position& opPosition, char op)
|
||||
: AstExpr(ClassIndex(), location)
|
||||
, expr(expr)
|
||||
, index(index)
|
||||
, indexLocation(indexLocation)
|
||||
, opPosition(opPosition)
|
||||
, op(op)
|
||||
{
|
||||
}
|
||||
|
||||
void AstExprIndexName::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
expr->visit(visitor);
|
||||
}
|
||||
|
||||
AstExprIndexExpr::AstExprIndexExpr(const Location& location, AstExpr* expr, AstExpr* index)
|
||||
: AstExpr(ClassIndex(), location)
|
||||
, expr(expr)
|
||||
, index(index)
|
||||
{
|
||||
}
|
||||
|
||||
void AstExprIndexExpr::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
expr->visit(visitor);
|
||||
index->visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
AstExprFunction::AstExprFunction(const Location& location, const AstArray<AstName>& generics, const AstArray<AstName>& genericPacks, AstLocal* self,
|
||||
const AstArray<AstLocal*>& args, std::optional<Location> vararg, AstStatBlock* body, size_t functionDepth, const AstName& debugname,
|
||||
std::optional<AstTypeList> returnAnnotation, AstTypePack* varargAnnotation, bool hasEnd, std::optional<Location> argLocation)
|
||||
: AstExpr(ClassIndex(), location)
|
||||
, generics(generics)
|
||||
, genericPacks(genericPacks)
|
||||
, self(self)
|
||||
, args(args)
|
||||
, hasReturnAnnotation(returnAnnotation.has_value())
|
||||
, returnAnnotation()
|
||||
, vararg(vararg.has_value())
|
||||
, varargLocation(vararg.value_or(Location()))
|
||||
, varargAnnotation(varargAnnotation)
|
||||
, body(body)
|
||||
, functionDepth(functionDepth)
|
||||
, debugname(debugname)
|
||||
, hasEnd(hasEnd)
|
||||
, argLocation(argLocation)
|
||||
{
|
||||
if (returnAnnotation.has_value())
|
||||
this->returnAnnotation = *returnAnnotation;
|
||||
}
|
||||
|
||||
void AstExprFunction::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
for (AstLocal* arg : args)
|
||||
{
|
||||
if (arg->annotation)
|
||||
arg->annotation->visit(visitor);
|
||||
}
|
||||
|
||||
if (varargAnnotation)
|
||||
varargAnnotation->visit(visitor);
|
||||
|
||||
if (hasReturnAnnotation)
|
||||
visitTypeList(visitor, returnAnnotation);
|
||||
|
||||
body->visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
AstExprTable::AstExprTable(const Location& location, const AstArray<Item>& items)
|
||||
: AstExpr(ClassIndex(), location)
|
||||
, items(items)
|
||||
{
|
||||
}
|
||||
|
||||
void AstExprTable::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
for (const Item& item : items)
|
||||
{
|
||||
if (item.key)
|
||||
item.key->visit(visitor);
|
||||
|
||||
item.value->visit(visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AstExprUnary::AstExprUnary(const Location& location, Op op, AstExpr* expr)
|
||||
: AstExpr(ClassIndex(), location)
|
||||
, op(op)
|
||||
, expr(expr)
|
||||
{
|
||||
}
|
||||
|
||||
void AstExprUnary::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
expr->visit(visitor);
|
||||
}
|
||||
|
||||
std::string toString(AstExprUnary::Op op)
|
||||
{
|
||||
switch (op)
|
||||
{
|
||||
case AstExprUnary::Minus:
|
||||
return "-";
|
||||
case AstExprUnary::Not:
|
||||
return "not";
|
||||
case AstExprUnary::Len:
|
||||
return "#";
|
||||
default:
|
||||
LUAU_ASSERT(false);
|
||||
return ""; // MSVC requires this even though the switch/case is exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
AstExprBinary::AstExprBinary(const Location& location, Op op, AstExpr* left, AstExpr* right)
|
||||
: AstExpr(ClassIndex(), location)
|
||||
, op(op)
|
||||
, left(left)
|
||||
, right(right)
|
||||
{
|
||||
}
|
||||
|
||||
void AstExprBinary::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
left->visit(visitor);
|
||||
right->visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
std::string toString(AstExprBinary::Op op)
|
||||
{
|
||||
switch (op)
|
||||
{
|
||||
case AstExprBinary::Add:
|
||||
return "+";
|
||||
case AstExprBinary::Sub:
|
||||
return "-";
|
||||
case AstExprBinary::Mul:
|
||||
return "*";
|
||||
case AstExprBinary::Div:
|
||||
return "/";
|
||||
case AstExprBinary::Mod:
|
||||
return "%";
|
||||
case AstExprBinary::Pow:
|
||||
return "^";
|
||||
case AstExprBinary::Concat:
|
||||
return "..";
|
||||
case AstExprBinary::CompareNe:
|
||||
return "~=";
|
||||
case AstExprBinary::CompareEq:
|
||||
return "==";
|
||||
case AstExprBinary::CompareLt:
|
||||
return "<";
|
||||
case AstExprBinary::CompareLe:
|
||||
return "<=";
|
||||
case AstExprBinary::CompareGt:
|
||||
return ">";
|
||||
case AstExprBinary::CompareGe:
|
||||
return ">=";
|
||||
case AstExprBinary::And:
|
||||
return "and";
|
||||
case AstExprBinary::Or:
|
||||
return "or";
|
||||
default:
|
||||
LUAU_ASSERT(false);
|
||||
return ""; // MSVC requires this even though the switch/case is exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
AstExprTypeAssertion::AstExprTypeAssertion(const Location& location, AstExpr* expr, AstType* annotation)
|
||||
: AstExpr(ClassIndex(), location)
|
||||
, expr(expr)
|
||||
, annotation(annotation)
|
||||
{
|
||||
}
|
||||
|
||||
void AstExprTypeAssertion::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
expr->visit(visitor);
|
||||
annotation->visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
AstExprIfElse::AstExprIfElse(const Location& location, AstExpr* condition, bool hasThen, AstExpr* trueExpr, bool hasElse, AstExpr* falseExpr)
|
||||
: AstExpr(ClassIndex(), location)
|
||||
, condition(condition)
|
||||
, hasThen(hasThen)
|
||||
, trueExpr(trueExpr)
|
||||
, hasElse(hasElse)
|
||||
, falseExpr(falseExpr)
|
||||
{
|
||||
}
|
||||
|
||||
void AstExprIfElse::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
condition->visit(visitor);
|
||||
trueExpr->visit(visitor);
|
||||
falseExpr->visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
AstExprError::AstExprError(const Location& location, const AstArray<AstExpr*>& expressions, unsigned messageIndex)
|
||||
: AstExpr(ClassIndex(), location)
|
||||
, expressions(expressions)
|
||||
, messageIndex(messageIndex)
|
||||
{
|
||||
}
|
||||
|
||||
void AstExprError::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
for (AstExpr* expression : expressions)
|
||||
expression->visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
AstStatBlock::AstStatBlock(const Location& location, const AstArray<AstStat*>& body)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, body(body)
|
||||
{
|
||||
}
|
||||
|
||||
void AstStatBlock::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
for (AstStat* stat : body)
|
||||
stat->visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
AstStatIf::AstStatIf(const Location& location, AstExpr* condition, AstStatBlock* thenbody, AstStat* elsebody, bool hasThen,
|
||||
const Location& thenLocation, const std::optional<Location>& elseLocation, bool hasEnd)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, condition(condition)
|
||||
, thenbody(thenbody)
|
||||
, elsebody(elsebody)
|
||||
, hasThen(hasThen)
|
||||
, thenLocation(thenLocation)
|
||||
, hasEnd(hasEnd)
|
||||
{
|
||||
if (bool(elseLocation))
|
||||
{
|
||||
hasElse = true;
|
||||
this->elseLocation = *elseLocation;
|
||||
}
|
||||
}
|
||||
|
||||
void AstStatIf::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
condition->visit(visitor);
|
||||
thenbody->visit(visitor);
|
||||
|
||||
if (elsebody)
|
||||
elsebody->visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
AstStatWhile::AstStatWhile(const Location& location, AstExpr* condition, AstStatBlock* body, bool hasDo, const Location& doLocation, bool hasEnd)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, condition(condition)
|
||||
, body(body)
|
||||
, hasDo(hasDo)
|
||||
, doLocation(doLocation)
|
||||
, hasEnd(hasEnd)
|
||||
{
|
||||
}
|
||||
|
||||
void AstStatWhile::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
condition->visit(visitor);
|
||||
body->visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
AstStatRepeat::AstStatRepeat(const Location& location, AstExpr* condition, AstStatBlock* body, bool hasUntil)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, condition(condition)
|
||||
, body(body)
|
||||
, hasUntil(hasUntil)
|
||||
{
|
||||
}
|
||||
|
||||
void AstStatRepeat::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
body->visit(visitor);
|
||||
condition->visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
AstStatBreak::AstStatBreak(const Location& location)
|
||||
: AstStat(ClassIndex(), location)
|
||||
{
|
||||
}
|
||||
|
||||
void AstStatBreak::visit(AstVisitor* visitor)
|
||||
{
|
||||
visitor->visit(this);
|
||||
}
|
||||
|
||||
AstStatContinue::AstStatContinue(const Location& location)
|
||||
: AstStat(ClassIndex(), location)
|
||||
{
|
||||
}
|
||||
|
||||
void AstStatContinue::visit(AstVisitor* visitor)
|
||||
{
|
||||
visitor->visit(this);
|
||||
}
|
||||
|
||||
AstStatReturn::AstStatReturn(const Location& location, const AstArray<AstExpr*>& list)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, list(list)
|
||||
{
|
||||
}
|
||||
|
||||
void AstStatReturn::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
for (AstExpr* expr : list)
|
||||
expr->visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
AstStatExpr::AstStatExpr(const Location& location, AstExpr* expr)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, expr(expr)
|
||||
{
|
||||
}
|
||||
|
||||
void AstStatExpr::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
expr->visit(visitor);
|
||||
}
|
||||
|
||||
AstStatLocal::AstStatLocal(
|
||||
const Location& location, const AstArray<AstLocal*>& vars, const AstArray<AstExpr*>& values, const std::optional<Location>& equalsSignLocation)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, vars(vars)
|
||||
, values(values)
|
||||
{
|
||||
if (bool(equalsSignLocation))
|
||||
{
|
||||
hasEqualsSign = true;
|
||||
this->equalsSignLocation = *equalsSignLocation;
|
||||
}
|
||||
}
|
||||
|
||||
void AstStatLocal::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
for (AstLocal* var : vars)
|
||||
{
|
||||
if (var->annotation)
|
||||
var->annotation->visit(visitor);
|
||||
}
|
||||
|
||||
for (AstExpr* expr : values)
|
||||
expr->visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
AstStatFor::AstStatFor(const Location& location, AstLocal* var, AstExpr* from, AstExpr* to, AstExpr* step, AstStatBlock* body, bool hasDo,
|
||||
const Location& doLocation, bool hasEnd)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, var(var)
|
||||
, from(from)
|
||||
, to(to)
|
||||
, step(step)
|
||||
, body(body)
|
||||
, hasDo(hasDo)
|
||||
, doLocation(doLocation)
|
||||
, hasEnd(hasEnd)
|
||||
{
|
||||
}
|
||||
|
||||
void AstStatFor::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
if (var->annotation)
|
||||
var->annotation->visit(visitor);
|
||||
|
||||
from->visit(visitor);
|
||||
to->visit(visitor);
|
||||
|
||||
if (step)
|
||||
step->visit(visitor);
|
||||
|
||||
body->visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
AstStatForIn::AstStatForIn(const Location& location, const AstArray<AstLocal*>& vars, const AstArray<AstExpr*>& values, AstStatBlock* body,
|
||||
bool hasIn, const Location& inLocation, bool hasDo, const Location& doLocation, bool hasEnd)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, vars(vars)
|
||||
, values(values)
|
||||
, body(body)
|
||||
, hasIn(hasIn)
|
||||
, inLocation(inLocation)
|
||||
, hasDo(hasDo)
|
||||
, doLocation(doLocation)
|
||||
, hasEnd(hasEnd)
|
||||
{
|
||||
}
|
||||
|
||||
void AstStatForIn::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
for (AstLocal* var : vars)
|
||||
{
|
||||
if (var->annotation)
|
||||
var->annotation->visit(visitor);
|
||||
}
|
||||
|
||||
for (AstExpr* expr : values)
|
||||
expr->visit(visitor);
|
||||
|
||||
body->visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
AstStatAssign::AstStatAssign(const Location& location, const AstArray<AstExpr*>& vars, const AstArray<AstExpr*>& values)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, vars(vars)
|
||||
, values(values)
|
||||
{
|
||||
}
|
||||
|
||||
void AstStatAssign::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
for (AstExpr* lvalue : vars)
|
||||
lvalue->visit(visitor);
|
||||
|
||||
for (AstExpr* expr : values)
|
||||
expr->visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
AstStatCompoundAssign::AstStatCompoundAssign(const Location& location, AstExprBinary::Op op, AstExpr* var, AstExpr* value)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, op(op)
|
||||
, var(var)
|
||||
, value(value)
|
||||
{
|
||||
}
|
||||
|
||||
void AstStatCompoundAssign::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
var->visit(visitor);
|
||||
value->visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
AstStatFunction::AstStatFunction(const Location& location, AstExpr* name, AstExprFunction* func)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, name(name)
|
||||
, func(func)
|
||||
{
|
||||
}
|
||||
|
||||
void AstStatFunction::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
name->visit(visitor);
|
||||
func->visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
AstStatLocalFunction::AstStatLocalFunction(const Location& location, AstLocal* name, AstExprFunction* func)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, name(name)
|
||||
, func(func)
|
||||
{
|
||||
}
|
||||
|
||||
void AstStatLocalFunction::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
func->visit(visitor);
|
||||
}
|
||||
|
||||
AstStatTypeAlias::AstStatTypeAlias(const Location& location, const AstName& name, const AstArray<AstName>& generics, AstType* type, bool exported)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, name(name)
|
||||
, generics(generics)
|
||||
, type(type)
|
||||
, exported(exported)
|
||||
{
|
||||
}
|
||||
|
||||
void AstStatTypeAlias::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
type->visit(visitor);
|
||||
}
|
||||
|
||||
AstStatDeclareGlobal::AstStatDeclareGlobal(const Location& location, const AstName& name, AstType* type)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, name(name)
|
||||
, type(type)
|
||||
{
|
||||
}
|
||||
|
||||
void AstStatDeclareGlobal::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
type->visit(visitor);
|
||||
}
|
||||
|
||||
AstStatDeclareFunction::AstStatDeclareFunction(const Location& location, const AstName& name, const AstArray<AstName>& generics,
|
||||
const AstArray<AstName>& genericPacks, const AstTypeList& params, const AstArray<AstArgumentName>& paramNames, const AstTypeList& retTypes)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, name(name)
|
||||
, generics(generics)
|
||||
, genericPacks(genericPacks)
|
||||
, params(params)
|
||||
, paramNames(paramNames)
|
||||
, retTypes(retTypes)
|
||||
{
|
||||
}
|
||||
|
||||
void AstStatDeclareFunction::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
visitTypeList(visitor, params);
|
||||
visitTypeList(visitor, retTypes);
|
||||
}
|
||||
}
|
||||
|
||||
AstStatDeclareClass::AstStatDeclareClass(
|
||||
const Location& location, const AstName& name, std::optional<AstName> superName, const AstArray<AstDeclaredClassProp>& props)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, name(name)
|
||||
, superName(superName)
|
||||
, props(props)
|
||||
{
|
||||
}
|
||||
|
||||
void AstStatDeclareClass::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
for (const AstDeclaredClassProp& prop : props)
|
||||
prop.ty->visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
AstStatError::AstStatError(
|
||||
const Location& location, const AstArray<AstExpr*>& expressions, const AstArray<AstStat*>& statements, unsigned messageIndex)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, expressions(expressions)
|
||||
, statements(statements)
|
||||
, messageIndex(messageIndex)
|
||||
{
|
||||
}
|
||||
|
||||
void AstStatError::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
for (AstNode* expression : expressions)
|
||||
expression->visit(visitor);
|
||||
|
||||
for (AstNode* statement : statements)
|
||||
statement->visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
AstTypeReference::AstTypeReference(const Location& location, std::optional<AstName> prefix, AstName name, const AstArray<AstType*>& generics)
|
||||
: AstType(ClassIndex(), location)
|
||||
, hasPrefix(bool(prefix))
|
||||
, prefix(prefix ? *prefix : AstName())
|
||||
, name(name)
|
||||
, generics(generics)
|
||||
{
|
||||
}
|
||||
|
||||
void AstTypeReference::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
for (AstType* generic : generics)
|
||||
generic->visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
AstTypeTable::AstTypeTable(const Location& location, const AstArray<AstTableProp>& props, AstTableIndexer* indexer)
|
||||
: AstType(ClassIndex(), location)
|
||||
, props(props)
|
||||
, indexer(indexer)
|
||||
{
|
||||
}
|
||||
|
||||
void AstTypeTable::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
for (const AstTableProp& prop : props)
|
||||
prop.type->visit(visitor);
|
||||
|
||||
if (indexer)
|
||||
{
|
||||
indexer->indexType->visit(visitor);
|
||||
indexer->resultType->visit(visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AstTypeFunction::AstTypeFunction(const Location& location, const AstArray<AstName>& generics, const AstArray<AstName>& genericPacks,
|
||||
const AstTypeList& argTypes, const AstArray<std::optional<AstArgumentName>>& argNames, const AstTypeList& returnTypes)
|
||||
: AstType(ClassIndex(), location)
|
||||
, generics(generics)
|
||||
, genericPacks(genericPacks)
|
||||
, argTypes(argTypes)
|
||||
, argNames(argNames)
|
||||
, returnTypes(returnTypes)
|
||||
{
|
||||
LUAU_ASSERT(argNames.size == 0 || argNames.size == argTypes.types.size);
|
||||
}
|
||||
|
||||
void AstTypeFunction::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
visitTypeList(visitor, argTypes);
|
||||
visitTypeList(visitor, returnTypes);
|
||||
}
|
||||
}
|
||||
|
||||
AstTypeTypeof::AstTypeTypeof(const Location& location, AstExpr* expr)
|
||||
: AstType(ClassIndex(), location)
|
||||
, expr(expr)
|
||||
{
|
||||
}
|
||||
|
||||
void AstTypeTypeof::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
expr->visit(visitor);
|
||||
}
|
||||
|
||||
AstTypeUnion::AstTypeUnion(const Location& location, const AstArray<AstType*>& types)
|
||||
: AstType(ClassIndex(), location)
|
||||
, types(types)
|
||||
{
|
||||
}
|
||||
|
||||
void AstTypeUnion::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
for (AstType* type : types)
|
||||
type->visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
AstTypeIntersection::AstTypeIntersection(const Location& location, const AstArray<AstType*>& types)
|
||||
: AstType(ClassIndex(), location)
|
||||
, types(types)
|
||||
{
|
||||
}
|
||||
|
||||
void AstTypeIntersection::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
for (AstType* type : types)
|
||||
type->visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
AstTypeError::AstTypeError(const Location& location, const AstArray<AstType*>& types, bool isMissing, unsigned messageIndex)
|
||||
: AstType(ClassIndex(), location)
|
||||
, types(types)
|
||||
, isMissing(isMissing)
|
||||
, messageIndex(messageIndex)
|
||||
{
|
||||
}
|
||||
|
||||
void AstTypeError::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
{
|
||||
for (AstType* type : types)
|
||||
type->visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
AstTypePackVariadic::AstTypePackVariadic(const Location& location, AstType* variadicType)
|
||||
: AstTypePack(ClassIndex(), location)
|
||||
, variadicType(variadicType)
|
||||
{
|
||||
}
|
||||
|
||||
void AstTypePackVariadic::visit(AstVisitor* visitor)
|
||||
{
|
||||
if (visitor->visit(this))
|
||||
variadicType->visit(visitor);
|
||||
}
|
||||
|
||||
AstTypePackGeneric::AstTypePackGeneric(const Location& location, AstName name)
|
||||
: AstTypePack(ClassIndex(), location)
|
||||
, genericName(name)
|
||||
{
|
||||
}
|
||||
|
||||
void AstTypePackGeneric::visit(AstVisitor* visitor)
|
||||
{
|
||||
visitor->visit(this);
|
||||
}
|
||||
|
||||
AstName getIdentifier(AstExpr* node)
|
||||
{
|
||||
if (AstExprGlobal* expr = node->as<AstExprGlobal>())
|
||||
return expr->name;
|
||||
|
||||
if (AstExprLocal* expr = node->as<AstExprLocal>())
|
||||
return expr->local->name;
|
||||
|
||||
return AstName();
|
||||
}
|
||||
|
||||
} // namespace Luau
|
1818
Ast/src/Confusables.cpp
Normal file
1818
Ast/src/Confusables.cpp
Normal file
File diff suppressed because it is too large
Load diff
1149
Ast/src/Lexer.cpp
Normal file
1149
Ast/src/Lexer.cpp
Normal file
File diff suppressed because it is too large
Load diff
17
Ast/src/Location.cpp
Normal file
17
Ast/src/Location.cpp
Normal file
|
@ -0,0 +1,17 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Location.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
std::string toString(const Position& position)
|
||||
{
|
||||
return "{ line = " + std::to_string(position.line) + ", col = " + std::to_string(position.column) + " }";
|
||||
}
|
||||
|
||||
std::string toString(const Location& location)
|
||||
{
|
||||
return "Location { " + toString(location.begin) + ", " + toString(location.end) + " }";
|
||||
}
|
||||
|
||||
} // namespace Luau
|
2693
Ast/src/Parser.cpp
Normal file
2693
Ast/src/Parser.cpp
Normal file
File diff suppressed because it is too large
Load diff
228
Ast/src/StringUtils.cpp
Normal file
228
Ast/src/StringUtils.cpp
Normal file
|
@ -0,0 +1,228 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/StringUtils.h"
|
||||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
static void vformatAppend(std::string& ret, const char* fmt, va_list args)
|
||||
{
|
||||
va_list argscopy;
|
||||
va_copy(argscopy, args);
|
||||
#ifdef _MSC_VER
|
||||
int actualSize = _vscprintf(fmt, argscopy);
|
||||
#else
|
||||
int actualSize = vsnprintf(NULL, 0, fmt, argscopy);
|
||||
#endif
|
||||
va_end(argscopy);
|
||||
|
||||
if (actualSize <= 0)
|
||||
return;
|
||||
|
||||
size_t sz = ret.size();
|
||||
ret.resize(sz + actualSize);
|
||||
vsnprintf(&ret[0] + sz, actualSize + 1, fmt, args);
|
||||
}
|
||||
|
||||
std::string format(const char* fmt, ...)
|
||||
{
|
||||
std::string result;
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vformatAppend(result, fmt, args);
|
||||
va_end(args);
|
||||
return result;
|
||||
}
|
||||
|
||||
void formatAppend(std::string& str, const char* fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vformatAppend(str, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
std::string vformat(const char* fmt, va_list args)
|
||||
{
|
||||
std::string ret;
|
||||
vformatAppend(ret, fmt, args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename String>
|
||||
static std::string joinImpl(const std::vector<String>& segments, std::string_view delimiter)
|
||||
{
|
||||
if (segments.empty())
|
||||
return "";
|
||||
|
||||
size_t len = (segments.size() - 1) * delimiter.size();
|
||||
for (const auto& sv : segments)
|
||||
len += sv.size();
|
||||
|
||||
std::string result;
|
||||
result.resize(len);
|
||||
char* dest = const_cast<char*>(result.data()); // This const_cast is only necessary until C++17
|
||||
|
||||
auto it = segments.begin();
|
||||
memcpy(dest, it->data(), it->size());
|
||||
dest += it->size();
|
||||
++it;
|
||||
for (; it != segments.end(); ++it)
|
||||
{
|
||||
memcpy(dest, delimiter.data(), delimiter.size());
|
||||
dest += delimiter.size();
|
||||
memcpy(dest, it->data(), it->size());
|
||||
dest += it->size();
|
||||
}
|
||||
|
||||
LUAU_ASSERT(dest == result.data() + len);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string join(const std::vector<std::string_view>& segments, std::string_view delimiter)
|
||||
{
|
||||
return joinImpl(segments, delimiter);
|
||||
}
|
||||
|
||||
std::string join(const std::vector<std::string>& segments, std::string_view delimiter)
|
||||
{
|
||||
return joinImpl(segments, delimiter);
|
||||
}
|
||||
|
||||
std::vector<std::string_view> split(std::string_view s, char delimiter)
|
||||
{
|
||||
std::vector<std::string_view> result;
|
||||
|
||||
while (!s.empty())
|
||||
{
|
||||
auto index = s.find(delimiter);
|
||||
if (index == std::string::npos)
|
||||
{
|
||||
result.push_back(s);
|
||||
break;
|
||||
}
|
||||
result.push_back(s.substr(0, index));
|
||||
s.remove_prefix(index + 1);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t editDistance(std::string_view a, std::string_view b)
|
||||
{
|
||||
// When there are matching prefix and suffix, they end up computing as zero cost, effectively making it no-op. We drop these characters.
|
||||
while (!a.empty() && !b.empty() && a.front() == b.front())
|
||||
{
|
||||
a.remove_prefix(1);
|
||||
b.remove_prefix(1);
|
||||
}
|
||||
|
||||
while (!a.empty() && !b.empty() && a.back() == b.back())
|
||||
{
|
||||
a.remove_suffix(1);
|
||||
b.remove_suffix(1);
|
||||
}
|
||||
|
||||
// Since we know the edit distance is the difference of the length of A and B discounting the matching prefixes and suffixes,
|
||||
// it is therefore pointless to run the rest of this function to find that out. We immediately infer this size and return it.
|
||||
if (a.empty())
|
||||
return b.size();
|
||||
if (b.empty())
|
||||
return a.size();
|
||||
|
||||
size_t maxDistance = a.size() + b.size();
|
||||
|
||||
std::vector<size_t> distances((a.size() + 2) * (b.size() + 2), 0);
|
||||
auto getPos = [b](size_t x, size_t y) -> size_t {
|
||||
return (x * (b.size() + 2)) + y;
|
||||
};
|
||||
|
||||
distances[0] = maxDistance;
|
||||
|
||||
for (size_t x = 0; x <= a.size(); ++x)
|
||||
{
|
||||
distances[getPos(x + 1, 0)] = maxDistance;
|
||||
distances[getPos(x + 1, 1)] = x;
|
||||
}
|
||||
|
||||
for (size_t y = 0; y <= b.size(); ++y)
|
||||
{
|
||||
distances[getPos(0, y + 1)] = maxDistance;
|
||||
distances[getPos(1, y + 1)] = y;
|
||||
}
|
||||
|
||||
std::array<size_t, 256> seenCharToRow;
|
||||
seenCharToRow.fill(0);
|
||||
|
||||
for (size_t x = 1; x <= a.size(); ++x)
|
||||
{
|
||||
size_t lastMatchedY = 0;
|
||||
|
||||
for (size_t y = 1; y <= b.size(); ++y)
|
||||
{
|
||||
size_t x1 = seenCharToRow[b[y - 1]];
|
||||
size_t y1 = lastMatchedY;
|
||||
|
||||
size_t cost = 1;
|
||||
if (a[x - 1] == b[y - 1])
|
||||
{
|
||||
cost = 0;
|
||||
lastMatchedY = y;
|
||||
}
|
||||
|
||||
size_t transposition = distances[getPos(x1, y1)] + (x - x1 - 1) + 1 + (y - y1 - 1);
|
||||
size_t substitution = distances[getPos(x, y)] + cost;
|
||||
size_t insertion = distances[getPos(x, y + 1)] + 1;
|
||||
size_t deletion = distances[getPos(x + 1, y)] + 1;
|
||||
|
||||
// It's more performant to use std::min(size_t, size_t) rather than the initializer_list overload.
|
||||
// Until proven otherwise, please do not change this.
|
||||
distances[getPos(x + 1, y + 1)] = std::min(std::min(insertion, deletion), std::min(substitution, transposition));
|
||||
}
|
||||
|
||||
seenCharToRow[a[x - 1]] = x;
|
||||
}
|
||||
|
||||
return distances[getPos(a.size() + 1, b.size() + 1)];
|
||||
}
|
||||
|
||||
bool startsWith(std::string_view haystack, std::string_view needle)
|
||||
{
|
||||
// ::starts_with is C++20
|
||||
return haystack.size() >= needle.size() && haystack.substr(0, needle.size()) == needle;
|
||||
}
|
||||
|
||||
bool equalsLower(std::string_view lhs, std::string_view rhs)
|
||||
{
|
||||
if (lhs.size() != rhs.size())
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < lhs.size(); ++i)
|
||||
if (tolower(uint8_t(lhs[i])) != tolower(uint8_t(rhs[i])))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t hashRange(const char* data, size_t size)
|
||||
{
|
||||
// FNV-1a
|
||||
uint32_t hash = 2166136261;
|
||||
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
{
|
||||
hash ^= uint8_t(data[i]);
|
||||
hash *= 16777619;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
252
CLI/Analyze.cpp
Normal file
252
CLI/Analyze.cpp
Normal file
|
@ -0,0 +1,252 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/ModuleResolver.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
#include "Luau/Frontend.h"
|
||||
#include "Luau/TypeAttach.h"
|
||||
#include "Luau/Transpiler.h"
|
||||
|
||||
#include "FileUtils.h"
|
||||
|
||||
enum class ReportFormat
|
||||
{
|
||||
Default,
|
||||
Luacheck
|
||||
};
|
||||
|
||||
static void report(ReportFormat format, const char* name, const Luau::Location& location, const char* type, const char* message)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case ReportFormat::Default:
|
||||
fprintf(stderr, "%s(%d,%d): %s: %s\n", name, location.begin.line + 1, location.begin.column + 1, type, message);
|
||||
break;
|
||||
|
||||
case ReportFormat::Luacheck:
|
||||
{
|
||||
// Note: luacheck's end column is inclusive but our end column is exclusive
|
||||
// In addition, luacheck doesn't support multi-line messages, so if the error is multiline we'll fake end column as 100 and hope for the best
|
||||
int columnEnd = (location.begin.line == location.end.line) ? location.end.column : 100;
|
||||
|
||||
fprintf(stdout, "%s:%d:%d-%d: (W0) %s: %s\n", name, location.begin.line + 1, location.begin.column + 1, columnEnd, type, message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void reportError(ReportFormat format, const char* name, const Luau::TypeError& error)
|
||||
{
|
||||
if (const Luau::SyntaxError* syntaxError = Luau::get_if<Luau::SyntaxError>(&error.data))
|
||||
report(format, name, error.location, "SyntaxError", syntaxError->message.c_str());
|
||||
else
|
||||
report(format, name, error.location, "TypeError", Luau::toString(error).c_str());
|
||||
}
|
||||
|
||||
static void reportWarning(ReportFormat format, const char* name, const Luau::LintWarning& warning)
|
||||
{
|
||||
report(format, name, warning.location, Luau::LintWarning::getName(warning.code), warning.text.c_str());
|
||||
}
|
||||
|
||||
static bool analyzeFile(Luau::Frontend& frontend, const char* name, ReportFormat format, bool annotate)
|
||||
{
|
||||
Luau::CheckResult cr = frontend.check(name);
|
||||
|
||||
if (!frontend.getSourceModule(name))
|
||||
{
|
||||
fprintf(stderr, "Error opening %s\n", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto& error : cr.errors)
|
||||
reportError(format, name, error);
|
||||
|
||||
Luau::LintResult lr = frontend.lint(name);
|
||||
|
||||
for (auto& error : lr.errors)
|
||||
reportWarning(format, name, error);
|
||||
for (auto& warning : lr.warnings)
|
||||
reportWarning(format, name, warning);
|
||||
|
||||
if (annotate)
|
||||
{
|
||||
Luau::SourceModule* sm = frontend.getSourceModule(name);
|
||||
Luau::ModulePtr m = frontend.moduleResolver.getModule(name);
|
||||
|
||||
Luau::attachTypeData(*sm, *m);
|
||||
|
||||
std::string annotated = Luau::transpileWithTypes(*sm->root);
|
||||
|
||||
printf("%s", annotated.c_str());
|
||||
}
|
||||
|
||||
return cr.errors.empty() && lr.errors.empty();
|
||||
}
|
||||
|
||||
static void displayHelp(const char* argv0)
|
||||
{
|
||||
printf("Usage: %s [--mode] [options] [file list]\n", argv0);
|
||||
printf("\n");
|
||||
printf("Available modes:\n");
|
||||
printf(" omitted: typecheck and lint input files\n");
|
||||
printf(" --annotate: typecheck input files and output source with type annotations\n");
|
||||
printf("\n");
|
||||
printf("Available options:\n");
|
||||
printf(" --formatter=plain: report analysis errors in Luacheck-compatible format\n");
|
||||
}
|
||||
|
||||
static int assertionHandler(const char* expr, const char* file, int line)
|
||||
{
|
||||
printf("%s(%d): ASSERTION FAILED: %s\n", file, line, expr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct CliFileResolver : Luau::FileResolver
|
||||
{
|
||||
std::optional<Luau::SourceCode> readSource(const Luau::ModuleName& name) override
|
||||
{
|
||||
std::optional<std::string> source = readFile(name);
|
||||
if (!source)
|
||||
return std::nullopt;
|
||||
|
||||
return Luau::SourceCode{*source, Luau::SourceCode::Module};
|
||||
}
|
||||
|
||||
bool moduleExists(const Luau::ModuleName& name) const override
|
||||
{
|
||||
return !!readFile(name);
|
||||
}
|
||||
|
||||
std::optional<Luau::ModuleName> fromAstFragment(Luau::AstExpr* expr) const override
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Luau::ModuleName concat(const Luau::ModuleName& lhs, std::string_view rhs) const override
|
||||
{
|
||||
return lhs + "/" + std::string(rhs);
|
||||
}
|
||||
|
||||
std::optional<Luau::ModuleName> getParentModuleName(const Luau::ModuleName& name) const override
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::string> getEnvironmentForModule(const Luau::ModuleName& name) const override
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
struct CliConfigResolver : Luau::ConfigResolver
|
||||
{
|
||||
Luau::Config defaultConfig;
|
||||
|
||||
mutable std::unordered_map<std::string, Luau::Config> configCache;
|
||||
mutable std::vector<std::pair<std::string, std::string>> configErrors;
|
||||
|
||||
CliConfigResolver()
|
||||
{
|
||||
defaultConfig.mode = Luau::Mode::Nonstrict;
|
||||
}
|
||||
|
||||
const Luau::Config& getConfig(const Luau::ModuleName& name) const override
|
||||
{
|
||||
std::optional<std::string> path = getParentPath(name);
|
||||
if (!path)
|
||||
return defaultConfig;
|
||||
|
||||
return readConfigRec(*path);
|
||||
}
|
||||
|
||||
const Luau::Config& readConfigRec(const std::string& path) const
|
||||
{
|
||||
auto it = configCache.find(path);
|
||||
if (it != configCache.end())
|
||||
return it->second;
|
||||
|
||||
std::optional<std::string> parent = getParentPath(path);
|
||||
Luau::Config result = parent ? readConfigRec(*parent) : defaultConfig;
|
||||
|
||||
std::string configPath = joinPaths(path, Luau::kConfigName);
|
||||
|
||||
if (std::optional<std::string> contents = readFile(configPath))
|
||||
{
|
||||
std::optional<std::string> error = Luau::parseConfig(*contents, result);
|
||||
if (error)
|
||||
configErrors.push_back({configPath, *error});
|
||||
}
|
||||
|
||||
return configCache[path] = result;
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
Luau::assertHandler() = assertionHandler;
|
||||
|
||||
for (Luau::FValue<bool>* flag = Luau::FValue<bool>::list; flag; flag = flag->next)
|
||||
if (strncmp(flag->name, "Luau", 4) == 0)
|
||||
flag->value = true;
|
||||
|
||||
if (argc >= 2 && strcmp(argv[1], "--help") == 0)
|
||||
{
|
||||
displayHelp(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ReportFormat format = ReportFormat::Default;
|
||||
bool annotate = false;
|
||||
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
if (argv[i][0] != '-')
|
||||
continue;
|
||||
|
||||
if (strcmp(argv[i], "--formatter=plain") == 0)
|
||||
format = ReportFormat::Luacheck;
|
||||
else if (strcmp(argv[i], "--annotate") == 0)
|
||||
annotate = true;
|
||||
}
|
||||
|
||||
Luau::FrontendOptions frontendOptions;
|
||||
frontendOptions.retainFullTypeGraphs = annotate;
|
||||
|
||||
CliFileResolver fileResolver;
|
||||
CliConfigResolver configResolver;
|
||||
Luau::Frontend frontend(&fileResolver, &configResolver, frontendOptions);
|
||||
|
||||
Luau::registerBuiltinTypes(frontend.typeChecker);
|
||||
Luau::freeze(frontend.typeChecker.globalTypes);
|
||||
|
||||
int failed = 0;
|
||||
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
if (argv[i][0] == '-')
|
||||
continue;
|
||||
|
||||
if (isDirectory(argv[i]))
|
||||
{
|
||||
traverseDirectory(argv[i], [&](const std::string& name) {
|
||||
if (name.length() > 4 && name.rfind(".lua") == name.length() - 4)
|
||||
failed += !analyzeFile(frontend, name.c_str(), format, annotate);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
failed += !analyzeFile(frontend, argv[i], format, annotate);
|
||||
}
|
||||
}
|
||||
|
||||
if (!configResolver.configErrors.empty())
|
||||
{
|
||||
failed += int(configResolver.configErrors.size());
|
||||
|
||||
for (const auto& pair : configResolver.configErrors)
|
||||
fprintf(stderr, "%s: %s\n", pair.first.c_str(), pair.second.c_str());
|
||||
}
|
||||
|
||||
return (format == ReportFormat::Luacheck) ? 0 : failed;
|
||||
}
|
||||
|
||||
|
224
CLI/FileUtils.cpp
Normal file
224
CLI/FileUtils.cpp
Normal file
|
@ -0,0 +1,224 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "FileUtils.h"
|
||||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
static std::wstring fromUtf8(const std::string& path)
|
||||
{
|
||||
size_t result = MultiByteToWideChar(CP_UTF8, 0, path.data(), int(path.size()), nullptr, 0);
|
||||
LUAU_ASSERT(result);
|
||||
|
||||
std::wstring buf(result, L'\0');
|
||||
MultiByteToWideChar(CP_UTF8, 0, path.data(), int(path.size()), &buf[0], int(buf.size()));
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static std::string toUtf8(const std::wstring& path)
|
||||
{
|
||||
size_t result = WideCharToMultiByte(CP_UTF8, 0, path.data(), int(path.size()), nullptr, 0, nullptr, nullptr);
|
||||
LUAU_ASSERT(result);
|
||||
|
||||
std::string buf(result, '\0');
|
||||
WideCharToMultiByte(CP_UTF8, 0, path.data(), int(path.size()), &buf[0], int(buf.size()), nullptr, nullptr);
|
||||
|
||||
return buf;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::optional<std::string> readFile(const std::string& name)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
FILE* file = _wfopen(fromUtf8(name).c_str(), L"rb");
|
||||
#else
|
||||
FILE* file = fopen(name.c_str(), "rb");
|
||||
#endif
|
||||
|
||||
if (!file)
|
||||
return std::nullopt;
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
long length = ftell(file);
|
||||
if (length < 0)
|
||||
{
|
||||
fclose(file);
|
||||
return std::nullopt;
|
||||
}
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
std::string result(length, 0);
|
||||
|
||||
size_t read = fread(result.data(), 1, length, file);
|
||||
fclose(file);
|
||||
|
||||
if (read != size_t(length))
|
||||
return std::nullopt;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename Ch>
|
||||
static void joinPaths(std::basic_string<Ch>& str, const Ch* lhs, const Ch* rhs)
|
||||
{
|
||||
str = lhs;
|
||||
if (!str.empty() && str.back() != '/' && str.back() != '\\' && *rhs != '/' && *rhs != '\\')
|
||||
str += '/';
|
||||
str += rhs;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
static bool traverseDirectoryRec(const std::wstring& path, const std::function<void(const std::string& name)>& callback)
|
||||
{
|
||||
std::wstring query = path + std::wstring(L"/*");
|
||||
|
||||
WIN32_FIND_DATAW data;
|
||||
HANDLE h = FindFirstFileW(query.c_str(), &data);
|
||||
|
||||
if (h == INVALID_HANDLE_VALUE)
|
||||
return false;
|
||||
|
||||
std::wstring buf;
|
||||
|
||||
do
|
||||
{
|
||||
if (wcscmp(data.cFileName, L".") != 0 && wcscmp(data.cFileName, L"..") != 0)
|
||||
{
|
||||
joinPaths(buf, path.c_str(), data.cFileName);
|
||||
|
||||
if (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
|
||||
{
|
||||
// Skip reparse points to avoid handling cycles
|
||||
}
|
||||
else if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
{
|
||||
traverseDirectoryRec(buf, callback);
|
||||
}
|
||||
else
|
||||
{
|
||||
callback(toUtf8(buf));
|
||||
}
|
||||
}
|
||||
} while (FindNextFileW(h, &data));
|
||||
|
||||
FindClose(h);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool traverseDirectory(const std::string& path, const std::function<void(const std::string& name)>& callback)
|
||||
{
|
||||
return traverseDirectoryRec(fromUtf8(path), callback);
|
||||
}
|
||||
#else
|
||||
static bool traverseDirectoryRec(const std::string& path, const std::function<void(const std::string& name)>& callback)
|
||||
{
|
||||
int fd = open(path.c_str(), O_DIRECTORY);
|
||||
DIR* dir = fdopendir(fd);
|
||||
|
||||
if (!dir)
|
||||
return false;
|
||||
|
||||
std::string buf;
|
||||
|
||||
while (dirent* entry = readdir(dir))
|
||||
{
|
||||
const dirent& data = *entry;
|
||||
|
||||
if (strcmp(data.d_name, ".") != 0 && strcmp(data.d_name, "..") != 0)
|
||||
{
|
||||
joinPaths(buf, path.c_str(), data.d_name);
|
||||
|
||||
int type = data.d_type;
|
||||
|
||||
// we need to stat DT_UNKNOWN to be able to tell the type
|
||||
if (type == DT_UNKNOWN)
|
||||
{
|
||||
struct stat st = {};
|
||||
#ifdef _ATFILE_SOURCE
|
||||
fstatat(fd, data.d_name, &st, 0);
|
||||
#else
|
||||
lstat(buf.c_str(), &st);
|
||||
#endif
|
||||
|
||||
type = IFTODT(st.st_mode);
|
||||
}
|
||||
|
||||
if (type == DT_DIR)
|
||||
{
|
||||
traverseDirectoryRec(buf, callback);
|
||||
}
|
||||
else if (type == DT_REG)
|
||||
{
|
||||
callback(buf);
|
||||
}
|
||||
else if (type == DT_LNK)
|
||||
{
|
||||
// Skip symbolic links to avoid handling cycles
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool traverseDirectory(const std::string& path, const std::function<void(const std::string& name)>& callback)
|
||||
{
|
||||
return traverseDirectoryRec(path, callback);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool isDirectory(const std::string& path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return (GetFileAttributesW(fromUtf8(path).c_str()) & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
||||
#else
|
||||
struct stat st = {};
|
||||
lstat(path.c_str(), &st);
|
||||
return (st.st_mode & S_IFMT) == S_IFDIR;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string joinPaths(const std::string& lhs, const std::string& rhs)
|
||||
{
|
||||
std::string result = lhs;
|
||||
if (!result.empty() && result.back() != '/' && result.back() != '\\')
|
||||
result += '/';
|
||||
result += rhs;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<std::string> getParentPath(const std::string& path)
|
||||
{
|
||||
if (path == "" || path == "." || path == "/")
|
||||
return std::nullopt;
|
||||
|
||||
#ifdef _WIN32
|
||||
if (path.size() == 2 && path.back() == ':')
|
||||
return std::nullopt;
|
||||
#endif
|
||||
|
||||
std::string::size_type slash = path.find_last_of("\\/", path.size() - 1);
|
||||
|
||||
if (slash == 0)
|
||||
return "/";
|
||||
|
||||
if (slash != std::string::npos)
|
||||
return path.substr(0, slash);
|
||||
|
||||
return "";
|
||||
}
|
14
CLI/FileUtils.h
Normal file
14
CLI/FileUtils.h
Normal file
|
@ -0,0 +1,14 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
std::optional<std::string> readFile(const std::string& name);
|
||||
|
||||
bool isDirectory(const std::string& path);
|
||||
bool traverseDirectory(const std::string& path, const std::function<void(const std::string& name)>& callback);
|
||||
|
||||
std::string joinPaths(const std::string& lhs, const std::string& rhs);
|
||||
std::optional<std::string> getParentPath(const std::string& path);
|
155
CLI/Profiler.cpp
Normal file
155
CLI/Profiler.cpp
Normal file
|
@ -0,0 +1,155 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "lua.h"
|
||||
|
||||
#include "Luau/DenseHash.h"
|
||||
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
|
||||
struct Profiler
|
||||
{
|
||||
// static state
|
||||
lua_Callbacks* callbacks = nullptr;
|
||||
int frequency = 1000;
|
||||
std::thread thread;
|
||||
|
||||
// variables for communication between loop and trigger
|
||||
std::atomic<bool> exit = false;
|
||||
std::atomic<uint64_t> ticks = 0;
|
||||
std::atomic<uint64_t> samples = 0;
|
||||
|
||||
// private state for trigger
|
||||
uint64_t currentTicks = 0;
|
||||
std::string stackScratch;
|
||||
|
||||
// statistics, updated by trigger
|
||||
Luau::DenseHashMap<std::string, uint64_t> data{""};
|
||||
uint64_t gc[16] = {};
|
||||
} gProfiler;
|
||||
|
||||
static void profilerTrigger(lua_State* L, int gc)
|
||||
{
|
||||
uint64_t currentTicks = gProfiler.ticks.load();
|
||||
uint64_t elapsedTicks = currentTicks - gProfiler.currentTicks;
|
||||
|
||||
if (elapsedTicks)
|
||||
{
|
||||
std::string& stack = gProfiler.stackScratch;
|
||||
|
||||
stack.clear();
|
||||
|
||||
if (gc > 0)
|
||||
stack += "GC,GC,";
|
||||
|
||||
lua_Debug ar;
|
||||
for (int level = 0; lua_getinfo(L, level, "sn", &ar); ++level)
|
||||
{
|
||||
if (!stack.empty())
|
||||
stack += ';';
|
||||
|
||||
stack += ar.short_src;
|
||||
stack += ',';
|
||||
if (ar.name)
|
||||
stack += ar.name;
|
||||
stack += ',';
|
||||
if (ar.linedefined > 0)
|
||||
stack += std::to_string(ar.linedefined);
|
||||
}
|
||||
|
||||
if (!stack.empty())
|
||||
{
|
||||
gProfiler.data[stack] += elapsedTicks;
|
||||
}
|
||||
|
||||
if (gc > 0)
|
||||
{
|
||||
gProfiler.gc[gc] += elapsedTicks;
|
||||
}
|
||||
}
|
||||
|
||||
gProfiler.currentTicks = currentTicks;
|
||||
gProfiler.callbacks->interrupt = nullptr;
|
||||
}
|
||||
|
||||
static void profilerLoop()
|
||||
{
|
||||
double last = lua_clock();
|
||||
|
||||
while (!gProfiler.exit)
|
||||
{
|
||||
double now = lua_clock();
|
||||
|
||||
if (now - last >= 1.0 / double(gProfiler.frequency))
|
||||
{
|
||||
gProfiler.ticks += uint64_t((now - last) * 1e6);
|
||||
gProfiler.samples++;
|
||||
gProfiler.callbacks->interrupt = profilerTrigger;
|
||||
|
||||
last = now;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void profilerStart(lua_State* L, int frequency)
|
||||
{
|
||||
gProfiler.frequency = frequency;
|
||||
gProfiler.callbacks = lua_callbacks(L);
|
||||
|
||||
gProfiler.exit = false;
|
||||
gProfiler.thread = std::thread(profilerLoop);
|
||||
}
|
||||
|
||||
void profilerStop()
|
||||
{
|
||||
gProfiler.exit = true;
|
||||
gProfiler.thread.join();
|
||||
}
|
||||
|
||||
void profilerDump(const char* name)
|
||||
{
|
||||
FILE* f = fopen(name, "wb");
|
||||
if (!f)
|
||||
{
|
||||
fprintf(stderr, "Error opening profile %s\n", name);
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t total = 0;
|
||||
|
||||
for (auto& p : gProfiler.data)
|
||||
{
|
||||
fprintf(f, "%lld %s\n", static_cast<long long>(p.second), p.first.c_str());
|
||||
total += p.second;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
printf("Profiler dump written to %s (total runtime %.3f seconds, %lld samples, %lld stacks)\n", name, double(total) / 1e6,
|
||||
static_cast<long long>(gProfiler.samples.load()), static_cast<long long>(gProfiler.data.size()));
|
||||
|
||||
uint64_t totalgc = 0;
|
||||
for (uint64_t p : gProfiler.gc)
|
||||
totalgc += p;
|
||||
|
||||
if (totalgc)
|
||||
{
|
||||
printf("GC: %.3f seconds (%.2f%%)", double(totalgc) / 1e6, double(totalgc) / double(total) * 100);
|
||||
|
||||
for (size_t i = 0; i < std::size(gProfiler.gc); ++i)
|
||||
{
|
||||
extern const char* luaC_statename(int state);
|
||||
|
||||
uint64_t p = gProfiler.gc[i];
|
||||
|
||||
if (p)
|
||||
printf(", %s %.2f%%", luaC_statename(int(i)), double(p) / double(totalgc) * 100);
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
}
|
8
CLI/Profiler.h
Normal file
8
CLI/Profiler.h
Normal file
|
@ -0,0 +1,8 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
struct lua_State;
|
||||
|
||||
void profilerStart(lua_State* L, int frequency);
|
||||
void profilerStop();
|
||||
void profilerDump(const char* name);
|
495
CLI/Repl.cpp
Normal file
495
CLI/Repl.cpp
Normal file
|
@ -0,0 +1,495 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "lua.h"
|
||||
#include "lualib.h"
|
||||
|
||||
#include "Luau/Compiler.h"
|
||||
#include "Luau/BytecodeBuilder.h"
|
||||
#include "Luau/Parser.h"
|
||||
|
||||
#include "FileUtils.h"
|
||||
#include "Profiler.h"
|
||||
|
||||
#include "linenoise.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
static int lua_loadstring(lua_State* L)
|
||||
{
|
||||
size_t l = 0;
|
||||
const char* s = luaL_checklstring(L, 1, &l);
|
||||
const char* chunkname = luaL_optstring(L, 2, s);
|
||||
|
||||
lua_setsafeenv(L, LUA_ENVIRONINDEX, false);
|
||||
|
||||
std::string bytecode = Luau::compile(std::string(s, l));
|
||||
if (luau_load(L, chunkname, bytecode.data(), bytecode.size()) == 0)
|
||||
return 1;
|
||||
|
||||
lua_pushnil(L);
|
||||
lua_insert(L, -2); /* put before error message */
|
||||
return 2; /* return nil plus error message */
|
||||
}
|
||||
|
||||
static int finishrequire(lua_State* L)
|
||||
{
|
||||
if (lua_isstring(L, -1))
|
||||
lua_error(L);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int lua_require(lua_State* L)
|
||||
{
|
||||
std::string name = luaL_checkstring(L, 1);
|
||||
std::string chunkname = "=" + name;
|
||||
|
||||
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
|
||||
|
||||
// return the module from the cache
|
||||
lua_getfield(L, -1, name.c_str());
|
||||
if (!lua_isnil(L, -1))
|
||||
return finishrequire(L);
|
||||
lua_pop(L, 1);
|
||||
|
||||
std::optional<std::string> source = readFile(name + ".lua");
|
||||
if (!source)
|
||||
luaL_argerrorL(L, 1, ("error loading " + name).c_str());
|
||||
|
||||
// module needs to run in a new thread, isolated from the rest
|
||||
lua_State* GL = lua_mainthread(L);
|
||||
lua_State* ML = lua_newthread(GL);
|
||||
lua_xmove(GL, L, 1);
|
||||
|
||||
// new thread needs to have the globals sandboxed
|
||||
luaL_sandboxthread(ML);
|
||||
|
||||
// now we can compile & run module on the new thread
|
||||
std::string bytecode = Luau::compile(*source);
|
||||
if (luau_load(ML, chunkname.c_str(), bytecode.data(), bytecode.size()) == 0)
|
||||
{
|
||||
int status = lua_resume(ML, L, 0);
|
||||
|
||||
if (status == 0)
|
||||
{
|
||||
if (lua_gettop(ML) == 0)
|
||||
lua_pushstring(ML, "module must return a value");
|
||||
else if (!lua_istable(ML, -1) && !lua_isfunction(ML, -1))
|
||||
lua_pushstring(ML, "module must return a table or function");
|
||||
}
|
||||
else if (status == LUA_YIELD)
|
||||
{
|
||||
lua_pushstring(ML, "module can not yield");
|
||||
}
|
||||
else if (!lua_isstring(ML, -1))
|
||||
{
|
||||
lua_pushstring(ML, "unknown error while running module");
|
||||
}
|
||||
}
|
||||
|
||||
// there's now a return value on top of ML; stack of L is MODULES thread
|
||||
lua_xmove(ML, L, 1);
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setfield(L, -4, name.c_str());
|
||||
|
||||
return finishrequire(L);
|
||||
}
|
||||
|
||||
static int lua_collectgarbage(lua_State* L)
|
||||
{
|
||||
const char* option = luaL_optstring(L, 1, "collect");
|
||||
|
||||
if (strcmp(option, "collect") == 0)
|
||||
{
|
||||
lua_gc(L, LUA_GCCOLLECT, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(option, "count") == 0)
|
||||
{
|
||||
int c = lua_gc(L, LUA_GCCOUNT, 0);
|
||||
lua_pushnumber(L, c);
|
||||
return 1;
|
||||
}
|
||||
|
||||
luaL_error(L, "collectgarbage must be called with 'count' or 'collect'");
|
||||
}
|
||||
|
||||
static void setupState(lua_State* L)
|
||||
{
|
||||
luaL_openlibs(L);
|
||||
|
||||
static const luaL_Reg funcs[] = {
|
||||
{"loadstring", lua_loadstring},
|
||||
{"require", lua_require},
|
||||
{"collectgarbage", lua_collectgarbage},
|
||||
{NULL, NULL},
|
||||
};
|
||||
|
||||
lua_pushvalue(L, LUA_GLOBALSINDEX);
|
||||
luaL_register(L, NULL, funcs);
|
||||
lua_pop(L, 1);
|
||||
|
||||
luaL_sandbox(L);
|
||||
}
|
||||
|
||||
static std::string runCode(lua_State* L, const std::string& source)
|
||||
{
|
||||
std::string bytecode = Luau::compile(source);
|
||||
|
||||
if (luau_load(L, "=stdin", bytecode.data(), bytecode.size()) != 0)
|
||||
{
|
||||
size_t len;
|
||||
const char* msg = lua_tolstring(L, -1, &len);
|
||||
|
||||
std::string error(msg, len);
|
||||
lua_pop(L, 1);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
lua_State* T = lua_newthread(L);
|
||||
|
||||
lua_pushvalue(L, -2);
|
||||
lua_remove(L, -3);
|
||||
lua_xmove(L, T, 1);
|
||||
|
||||
int status = lua_resume(T, NULL, 0);
|
||||
|
||||
if (status == 0)
|
||||
{
|
||||
int n = lua_gettop(T);
|
||||
|
||||
if (n)
|
||||
{
|
||||
luaL_checkstack(T, LUA_MINSTACK, "too many results to print");
|
||||
lua_getglobal(T, "print");
|
||||
lua_insert(T, 1);
|
||||
lua_pcall(T, n, 0, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string error = (status == LUA_YIELD) ? "thread yielded unexpectedly" : lua_tostring(T, -1);
|
||||
error += "\nstack backtrace:\n";
|
||||
error += lua_debugtrace(T);
|
||||
|
||||
fprintf(stdout, "%s", error.c_str());
|
||||
}
|
||||
|
||||
lua_pop(L, 1);
|
||||
return std::string();
|
||||
}
|
||||
|
||||
static void completeIndexer(lua_State* L, const char* editBuffer, size_t start, std::vector<std::string>& completions)
|
||||
{
|
||||
std::string_view lookup = editBuffer + start;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
size_t dot = lookup.find('.');
|
||||
std::string_view prefix = lookup.substr(0, dot);
|
||||
|
||||
if (dot == std::string_view::npos)
|
||||
{
|
||||
// table, key
|
||||
lua_pushnil(L);
|
||||
|
||||
while (lua_next(L, -2) != 0)
|
||||
{
|
||||
// table, key, value
|
||||
std::string_view key = lua_tostring(L, -2);
|
||||
|
||||
if (!key.empty() && Luau::startsWith(key, prefix))
|
||||
completions.push_back(editBuffer + std::string(key.substr(prefix.size())));
|
||||
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// find the key in the table
|
||||
lua_pushlstring(L, prefix.data(), prefix.size());
|
||||
lua_rawget(L, -2);
|
||||
lua_remove(L, -2);
|
||||
|
||||
if (lua_isnil(L, -1))
|
||||
break;
|
||||
|
||||
lookup.remove_prefix(dot + 1);
|
||||
}
|
||||
}
|
||||
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
static void completeRepl(lua_State* L, const char* editBuffer, std::vector<std::string>& completions)
|
||||
{
|
||||
size_t start = strlen(editBuffer);
|
||||
while (start > 0 && (isalnum(editBuffer[start - 1]) || editBuffer[start - 1] == '.'))
|
||||
start--;
|
||||
|
||||
// look the value up in current global table first
|
||||
lua_pushvalue(L, LUA_GLOBALSINDEX);
|
||||
completeIndexer(L, editBuffer, start, completions);
|
||||
|
||||
// and in actual global table after that
|
||||
lua_getglobal(L, "_G");
|
||||
completeIndexer(L, editBuffer, start, completions);
|
||||
}
|
||||
|
||||
static void runRepl()
|
||||
{
|
||||
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
|
||||
lua_State* L = globalState.get();
|
||||
|
||||
setupState(L);
|
||||
|
||||
luaL_sandboxthread(L);
|
||||
|
||||
linenoise::SetCompletionCallback([L](const char* editBuffer, std::vector<std::string>& completions) {
|
||||
completeRepl(L, editBuffer, completions);
|
||||
});
|
||||
|
||||
std::string buffer;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
bool quit = false;
|
||||
std::string line = linenoise::Readline(buffer.empty() ? "> " : ">> ", quit);
|
||||
if (quit)
|
||||
break;
|
||||
|
||||
if (buffer.empty() && runCode(L, std::string("return ") + line) == std::string())
|
||||
{
|
||||
linenoise::AddHistory(line.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
buffer += line;
|
||||
buffer += " "; // linenoise doesn't work very well with multiline history entries
|
||||
|
||||
std::string error = runCode(L, buffer);
|
||||
|
||||
if (error.length() >= 5 && error.compare(error.length() - 5, 5, "<eof>") == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (error.length())
|
||||
{
|
||||
fprintf(stdout, "%s\n", error.c_str());
|
||||
}
|
||||
|
||||
linenoise::AddHistory(buffer.c_str());
|
||||
buffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
static bool runFile(const char* name, lua_State* GL)
|
||||
{
|
||||
std::optional<std::string> source = readFile(name);
|
||||
if (!source)
|
||||
{
|
||||
fprintf(stderr, "Error opening %s\n", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
// module needs to run in a new thread, isolated from the rest
|
||||
lua_State* L = lua_newthread(GL);
|
||||
|
||||
// new thread needs to have the globals sandboxed
|
||||
luaL_sandboxthread(L);
|
||||
|
||||
std::string chunkname = "=" + std::string(name);
|
||||
|
||||
std::string bytecode = Luau::compile(*source);
|
||||
int status = 0;
|
||||
|
||||
if (luau_load(L, chunkname.c_str(), bytecode.data(), bytecode.size()) == 0)
|
||||
{
|
||||
status = lua_resume(L, NULL, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
status = LUA_ERRSYNTAX;
|
||||
}
|
||||
|
||||
if (status == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string error = (status == LUA_YIELD) ? "thread yielded unexpectedly" : lua_tostring(L, -1);
|
||||
error += "\nstacktrace:\n";
|
||||
error += lua_debugtrace(L);
|
||||
|
||||
fprintf(stderr, "%s", error.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void report(const char* name, const Luau::Location& location, const char* type, const char* message)
|
||||
{
|
||||
fprintf(stderr, "%s(%d,%d): %s: %s\n", name, location.begin.line + 1, location.begin.column + 1, type, message);
|
||||
}
|
||||
|
||||
static void reportError(const char* name, const Luau::ParseError& error)
|
||||
{
|
||||
report(name, error.getLocation(), "SyntaxError", error.what());
|
||||
}
|
||||
|
||||
static void reportError(const char* name, const Luau::CompileError& error)
|
||||
{
|
||||
report(name, error.getLocation(), "CompileError", error.what());
|
||||
}
|
||||
|
||||
static bool compileFile(const char* name)
|
||||
{
|
||||
std::optional<std::string> source = readFile(name);
|
||||
if (!source)
|
||||
{
|
||||
fprintf(stderr, "Error opening %s\n", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Luau::BytecodeBuilder bcb;
|
||||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Source);
|
||||
bcb.setDumpSource(*source);
|
||||
|
||||
Luau::compileOrThrow(bcb, *source);
|
||||
|
||||
printf("%s", bcb.dumpEverything().c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Luau::ParseErrors& e)
|
||||
{
|
||||
for (auto& error : e.getErrors())
|
||||
reportError(name, error);
|
||||
return false;
|
||||
}
|
||||
catch (Luau::CompileError& e)
|
||||
{
|
||||
reportError(name, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void displayHelp(const char* argv0)
|
||||
{
|
||||
printf("Usage: %s [--mode] [options] [file list]\n", argv0);
|
||||
printf("\n");
|
||||
printf("When mode and file list are omitted, an interactive REPL is started instead.\n");
|
||||
printf("\n");
|
||||
printf("Available modes:\n");
|
||||
printf(" omitted: compile and run input files one by one\n");
|
||||
printf(" --compile: compile input files and output resulting bytecode\n");
|
||||
printf("\n");
|
||||
printf("Available options:\n");
|
||||
printf(" --profile[=N]: profile the code using N Hz sampling (default 10000) and output results to profile.out\n");
|
||||
}
|
||||
|
||||
static int assertionHandler(const char* expr, const char* file, int line)
|
||||
{
|
||||
printf("%s(%d): ASSERTION FAILED: %s\n", file, line, expr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
Luau::assertHandler() = assertionHandler;
|
||||
|
||||
for (Luau::FValue<bool>* flag = Luau::FValue<bool>::list; flag; flag = flag->next)
|
||||
if (strncmp(flag->name, "Luau", 4) == 0)
|
||||
flag->value = true;
|
||||
|
||||
if (argc == 1)
|
||||
{
|
||||
runRepl();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (argc >= 2 && strcmp(argv[1], "--help") == 0)
|
||||
{
|
||||
displayHelp(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (argc >= 2 && strcmp(argv[1], "--compile") == 0)
|
||||
{
|
||||
int failed = 0;
|
||||
|
||||
for (int i = 2; i < argc; ++i)
|
||||
{
|
||||
if (argv[i][0] == '-')
|
||||
continue;
|
||||
|
||||
if (isDirectory(argv[i]))
|
||||
{
|
||||
traverseDirectory(argv[i], [&](const std::string& name) {
|
||||
if (name.length() > 4 && name.rfind(".lua") == name.length() - 4)
|
||||
failed += !compileFile(name.c_str());
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
failed += !compileFile(argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return failed;
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
|
||||
lua_State* L = globalState.get();
|
||||
|
||||
setupState(L);
|
||||
|
||||
int profile = 0;
|
||||
|
||||
for (int i = 1; i < argc; ++i)
|
||||
if (strcmp(argv[i], "--profile") == 0)
|
||||
profile = 10000; // default to 10 KHz
|
||||
else if (strncmp(argv[i], "--profile=", 10) == 0)
|
||||
profile = atoi(argv[i] + 10);
|
||||
|
||||
if (profile)
|
||||
profilerStart(L, profile);
|
||||
|
||||
int failed = 0;
|
||||
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
if (argv[i][0] == '-')
|
||||
continue;
|
||||
|
||||
if (isDirectory(argv[i]))
|
||||
{
|
||||
traverseDirectory(argv[i], [&](const std::string& name) {
|
||||
if (name.length() > 4 && name.rfind(".lua") == name.length() - 4)
|
||||
failed += !runFile(name.c_str(), L);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
failed += !runFile(argv[i], L);
|
||||
}
|
||||
}
|
||||
|
||||
if (profile)
|
||||
{
|
||||
profilerStop();
|
||||
profilerDump("profile.out");
|
||||
}
|
||||
|
||||
return failed;
|
||||
}
|
||||
}
|
||||
|
||||
|
88
CMakeLists.txt
Normal file
88
CMakeLists.txt
Normal file
|
@ -0,0 +1,88 @@
|
|||
# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
if(EXT_PLATFORM_STRING)
|
||||
include(EXTLuau.cmake)
|
||||
return()
|
||||
endif()
|
||||
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(Luau LANGUAGES CXX)
|
||||
|
||||
option(LUAU_BUILD_CLI "Build CLI" ON)
|
||||
option(LUAU_BUILD_TESTS "Build tests" ON)
|
||||
|
||||
add_library(Luau.Ast STATIC)
|
||||
add_library(Luau.Compiler STATIC)
|
||||
add_library(Luau.Analysis STATIC)
|
||||
add_library(Luau.VM STATIC)
|
||||
|
||||
if(LUAU_BUILD_CLI)
|
||||
add_executable(Luau.Repl.CLI)
|
||||
add_executable(Luau.Analyze.CLI)
|
||||
|
||||
# This also adds target `name` on Linux/macOS and `name.exe` on Windows
|
||||
set_target_properties(Luau.Repl.CLI PROPERTIES OUTPUT_NAME luau)
|
||||
set_target_properties(Luau.Analyze.CLI PROPERTIES OUTPUT_NAME luau-analyze)
|
||||
endif()
|
||||
|
||||
if(LUAU_BUILD_TESTS)
|
||||
add_executable(Luau.UnitTest)
|
||||
add_executable(Luau.Conformance)
|
||||
endif()
|
||||
include(Sources.cmake)
|
||||
|
||||
target_compile_features(Luau.Ast PUBLIC cxx_std_17)
|
||||
target_include_directories(Luau.Ast PUBLIC Ast/include)
|
||||
|
||||
target_compile_features(Luau.Compiler PUBLIC cxx_std_17)
|
||||
target_include_directories(Luau.Compiler PUBLIC Compiler/include)
|
||||
target_link_libraries(Luau.Compiler PUBLIC Luau.Ast)
|
||||
|
||||
target_compile_features(Luau.Analysis PUBLIC cxx_std_17)
|
||||
target_include_directories(Luau.Analysis PUBLIC Analysis/include)
|
||||
target_link_libraries(Luau.Analysis PUBLIC Luau.Ast)
|
||||
|
||||
target_compile_features(Luau.VM PRIVATE cxx_std_11)
|
||||
target_include_directories(Luau.VM PUBLIC VM/include)
|
||||
|
||||
set(LUAU_OPTIONS)
|
||||
|
||||
if(MSVC)
|
||||
list(APPEND LUAU_OPTIONS /D_CRT_SECURE_NO_WARNINGS) # We need to use the portable CRT functions.
|
||||
list(APPEND LUAU_OPTIONS /WX) # Warnings are errors
|
||||
list(APPEND LUAU_OPTIONS /MP) # Distribute single project compilation across multiple cores
|
||||
else()
|
||||
list(APPEND LUAU_OPTIONS -Wall) # All warnings
|
||||
list(APPEND LUAU_OPTIONS -Werror) # Warnings are errors
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
list(APPEND LUAU_OPTIONS -Wno-unused) # GCC considers variables declared/checked in if() as unused
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_compile_options(Luau.Ast PRIVATE ${LUAU_OPTIONS})
|
||||
target_compile_options(Luau.Analysis PRIVATE ${LUAU_OPTIONS})
|
||||
target_compile_options(Luau.VM PRIVATE ${LUAU_OPTIONS})
|
||||
|
||||
if(LUAU_BUILD_CLI)
|
||||
target_compile_options(Luau.Repl.CLI PRIVATE ${LUAU_OPTIONS})
|
||||
target_compile_options(Luau.Analyze.CLI PRIVATE ${LUAU_OPTIONS})
|
||||
|
||||
target_include_directories(Luau.Repl.CLI PRIVATE extern)
|
||||
target_link_libraries(Luau.Repl.CLI PRIVATE Luau.Compiler Luau.VM)
|
||||
|
||||
if(UNIX)
|
||||
target_link_libraries(Luau.Repl.CLI PRIVATE pthread)
|
||||
endif()
|
||||
|
||||
target_link_libraries(Luau.Analyze.CLI PRIVATE Luau.Analysis)
|
||||
endif()
|
||||
|
||||
if(LUAU_BUILD_TESTS)
|
||||
target_compile_options(Luau.UnitTest PRIVATE ${LUAU_OPTIONS})
|
||||
target_include_directories(Luau.UnitTest PRIVATE extern)
|
||||
target_link_libraries(Luau.UnitTest PRIVATE Luau.Analysis Luau.Compiler)
|
||||
|
||||
target_compile_options(Luau.Conformance PRIVATE ${LUAU_OPTIONS})
|
||||
target_include_directories(Luau.Conformance PRIVATE extern)
|
||||
target_link_libraries(Luau.Conformance PRIVATE Luau.Analysis Luau.Compiler Luau.VM)
|
||||
endif()
|
478
Compiler/include/Luau/Bytecode.h
Normal file
478
Compiler/include/Luau/Bytecode.h
Normal file
|
@ -0,0 +1,478 @@
|
|||
// 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-254. 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.
|
||||
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 (0..255)
|
||||
LOP_GETUPVAL,
|
||||
|
||||
// SETUPVAL: store value into the upvalue table for the current function
|
||||
// A: target register
|
||||
// B: upvalue index (0..255)
|
||||
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; 0 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 truthful) 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 truthful) 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)
|
||||
// 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/FORGLOOP_INEXT: FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_inext
|
||||
// FORGPREP_INEXT prepares the index variable and jumps to FORGLOOP_INEXT
|
||||
// FORGLOOP_INEXT has identical encoding and semantics to FORGLOOP (except for AUX encoding)
|
||||
LOP_FORGPREP_INEXT,
|
||||
LOP_FORGLOOP_INEXT,
|
||||
|
||||
// FORGPREP_NEXT/FORGLOOP_NEXT: FORGLOOP with 2 output variables (no AUX encoding), assuming generator is luaB_next
|
||||
// FORGPREP_NEXT prepares the index variable and jumps to FORGLOOP_NEXT
|
||||
// FORGLOOP_NEXT has identical encoding and semantics to FORGLOOP (except for AUX encoding)
|
||||
LOP_FORGPREP_NEXT,
|
||||
LOP_FORGLOOP_NEXT,
|
||||
|
||||
// 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,
|
||||
|
||||
// JUMPIFEQK, JUMPIFNOTEQK: jumps to target offset if the comparison with constant is true (or false, for NOT variants)
|
||||
// A: source register 1
|
||||
// D: jump offset (-32768..32767; 0 means "next instruction" aka "don't jump")
|
||||
// AUX: constant table index
|
||||
LOP_JUMPIFEQK,
|
||||
LOP_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,
|
||||
|
||||
// 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
|
||||
LBC_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,
|
||||
};
|
||||
|
||||
// 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,
|
||||
};
|
||||
|
||||
// Capture type, used in LOP_CAPTURE
|
||||
enum LuauCaptureType
|
||||
{
|
||||
LCT_VAL = 0,
|
||||
LCT_REF,
|
||||
LCT_UPVAL,
|
||||
};
|
250
Compiler/include/Luau/BytecodeBuilder.h
Normal file
250
Compiler/include/Luau/BytecodeBuilder.h
Normal file
|
@ -0,0 +1,250 @@
|
|||
// 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"
|
||||
#include "Luau/DenseHash.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
class BytecodeEncoder
|
||||
{
|
||||
public:
|
||||
virtual ~BytecodeEncoder() {}
|
||||
|
||||
virtual uint8_t encodeOp(uint8_t op) = 0;
|
||||
};
|
||||
|
||||
class BytecodeBuilder
|
||||
{
|
||||
public:
|
||||
// BytecodeBuilder does *not* copy the data passed via StringRef; instead, it keeps the ref around until finalize()
|
||||
// Please be careful with the lifetime of the data that's being passed because of this.
|
||||
// The safe and correct pattern is to only build StringRefs out of pieces of AST (AstName or AstArray<>) that are backed by AstAllocator.
|
||||
// Note that you must finalize() the builder before the Allocator backing the Ast is destroyed.
|
||||
struct StringRef
|
||||
{
|
||||
// To construct a StringRef, use sref() from Compiler.cpp.
|
||||
const char* data = nullptr;
|
||||
size_t length = 0;
|
||||
|
||||
bool operator==(const StringRef& other) const;
|
||||
};
|
||||
|
||||
struct TableShape
|
||||
{
|
||||
static const unsigned int kMaxLength = 32;
|
||||
|
||||
int32_t keys[kMaxLength];
|
||||
unsigned int length = 0;
|
||||
|
||||
bool operator==(const TableShape& other) const;
|
||||
};
|
||||
|
||||
BytecodeBuilder(BytecodeEncoder* encoder = 0);
|
||||
|
||||
uint32_t beginFunction(uint8_t numparams, bool isvararg = false);
|
||||
void endFunction(uint8_t maxstacksize, uint8_t numupvalues);
|
||||
|
||||
void setMainFunction(uint32_t fid);
|
||||
|
||||
int32_t addConstantNil();
|
||||
int32_t addConstantBoolean(bool value);
|
||||
int32_t addConstantNumber(double value);
|
||||
int32_t addConstantString(StringRef value);
|
||||
int32_t addImport(uint32_t iid);
|
||||
int32_t addConstantTable(const TableShape& shape);
|
||||
int32_t addConstantClosure(uint32_t fid);
|
||||
|
||||
int16_t addChildFunction(uint32_t fid);
|
||||
|
||||
void emitABC(LuauOpcode op, uint8_t a, uint8_t b, uint8_t c);
|
||||
void emitAD(LuauOpcode op, uint8_t a, int16_t d);
|
||||
void emitE(LuauOpcode op, int32_t e);
|
||||
void emitAux(uint32_t aux);
|
||||
|
||||
size_t emitLabel();
|
||||
|
||||
[[nodiscard]] bool patchJumpD(size_t jumpLabel, size_t targetLabel);
|
||||
[[nodiscard]] bool patchSkipC(size_t jumpLabel, size_t targetLabel);
|
||||
|
||||
void foldJumps();
|
||||
void expandJumps();
|
||||
|
||||
void setDebugFunctionName(StringRef name);
|
||||
void setDebugLine(int line);
|
||||
void pushDebugLocal(StringRef name, uint8_t reg, uint32_t startpc, uint32_t endpc);
|
||||
void pushDebugUpval(StringRef name);
|
||||
uint32_t getDebugPC() const;
|
||||
|
||||
void finalize();
|
||||
|
||||
enum DumpFlags
|
||||
{
|
||||
Dump_Code = 1 << 0,
|
||||
Dump_Lines = 1 << 1,
|
||||
Dump_Source = 1 << 2,
|
||||
Dump_Locals = 1 << 3,
|
||||
};
|
||||
|
||||
void setDumpFlags(uint32_t flags)
|
||||
{
|
||||
dumpFlags = flags;
|
||||
dumpFunctionPtr = &BytecodeBuilder::dumpCurrentFunction;
|
||||
}
|
||||
|
||||
void setDumpSource(const std::string& source);
|
||||
|
||||
const std::string& getBytecode() const
|
||||
{
|
||||
LUAU_ASSERT(!bytecode.empty()); // did you forget to call finalize?
|
||||
return bytecode;
|
||||
}
|
||||
|
||||
std::string dumpFunction(uint32_t id) const;
|
||||
std::string dumpEverything() const;
|
||||
|
||||
static uint32_t getImportId(int32_t id0);
|
||||
static uint32_t getImportId(int32_t id0, int32_t id1);
|
||||
static uint32_t getImportId(int32_t id0, int32_t id1, int32_t id2);
|
||||
|
||||
static uint32_t getStringHash(StringRef key);
|
||||
|
||||
static std::string getError(const std::string& message);
|
||||
|
||||
private:
|
||||
struct Constant
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
Type_Nil,
|
||||
Type_Boolean,
|
||||
Type_Number,
|
||||
Type_String,
|
||||
Type_Import,
|
||||
Type_Table,
|
||||
Type_Closure,
|
||||
};
|
||||
|
||||
Type type;
|
||||
union
|
||||
{
|
||||
bool valueBoolean;
|
||||
double valueNumber;
|
||||
unsigned int valueString; // index into string table
|
||||
uint32_t valueImport; // 10-10-10-2 encoded import id
|
||||
uint32_t valueTable; // index into tableShapes[]
|
||||
uint32_t valueClosure; // index of function in global list
|
||||
};
|
||||
};
|
||||
|
||||
struct ConstantKey
|
||||
{
|
||||
Constant::Type type;
|
||||
// Note: this stores value* from Constant; when type is Number_Double, this stores the same bits as double does but in uint64_t.
|
||||
uint64_t value;
|
||||
|
||||
bool operator==(const ConstantKey& key) const
|
||||
{
|
||||
return type == key.type && value == key.value;
|
||||
}
|
||||
};
|
||||
|
||||
struct Function
|
||||
{
|
||||
std::string data;
|
||||
|
||||
uint8_t maxstacksize = 0;
|
||||
uint8_t numparams = 0;
|
||||
uint8_t numupvalues = 0;
|
||||
bool isvararg = false;
|
||||
|
||||
unsigned int debugname = 0;
|
||||
|
||||
std::string dump;
|
||||
std::string dumpname;
|
||||
};
|
||||
|
||||
struct DebugLocal
|
||||
{
|
||||
unsigned int name;
|
||||
|
||||
uint8_t reg;
|
||||
uint32_t startpc;
|
||||
uint32_t endpc;
|
||||
};
|
||||
|
||||
struct DebugUpval
|
||||
{
|
||||
unsigned int name;
|
||||
};
|
||||
|
||||
struct Jump
|
||||
{
|
||||
uint32_t source;
|
||||
uint32_t target;
|
||||
};
|
||||
|
||||
struct StringRefHash
|
||||
{
|
||||
size_t operator()(const StringRef& v) const;
|
||||
};
|
||||
|
||||
struct ConstantKeyHash
|
||||
{
|
||||
size_t operator()(const ConstantKey& key) const;
|
||||
};
|
||||
|
||||
struct TableShapeHash
|
||||
{
|
||||
size_t operator()(const TableShape& v) const;
|
||||
};
|
||||
|
||||
std::vector<Function> functions;
|
||||
uint32_t currentFunction = ~0u;
|
||||
uint32_t mainFunction = ~0u;
|
||||
|
||||
std::vector<uint32_t> insns;
|
||||
std::vector<int> lines;
|
||||
std::vector<Constant> constants;
|
||||
std::vector<uint32_t> protos;
|
||||
std::vector<Jump> jumps;
|
||||
|
||||
std::vector<TableShape> tableShapes;
|
||||
|
||||
bool hasLongJumps = false;
|
||||
|
||||
DenseHashMap<ConstantKey, int32_t, ConstantKeyHash> constantMap;
|
||||
DenseHashMap<TableShape, int32_t, TableShapeHash> tableShapeMap;
|
||||
|
||||
int debugLine = 0;
|
||||
|
||||
std::vector<DebugLocal> debugLocals;
|
||||
std::vector<DebugUpval> debugUpvals;
|
||||
|
||||
DenseHashMap<StringRef, unsigned int, StringRefHash> stringTable;
|
||||
|
||||
BytecodeEncoder* encoder = nullptr;
|
||||
std::string bytecode;
|
||||
|
||||
uint32_t dumpFlags = 0;
|
||||
std::vector<std::string> dumpSource;
|
||||
|
||||
std::string (BytecodeBuilder::*dumpFunctionPtr)() const = nullptr;
|
||||
|
||||
void validate() const;
|
||||
|
||||
std::string dumpCurrentFunction() const;
|
||||
const uint32_t* dumpInstruction(const uint32_t* opcode, std::string& output) const;
|
||||
|
||||
void writeFunction(std::string& ss, uint32_t id) const;
|
||||
void writeLineInfo(std::string& ss) const;
|
||||
void writeStringTable(std::string& ss) const;
|
||||
|
||||
int32_t addConstant(const ConstantKey& key, const Constant& value);
|
||||
unsigned int addStringTableEntry(StringRef value);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
67
Compiler/include/Luau/Compiler.h
Normal file
67
Compiler/include/Luau/Compiler.h
Normal file
|
@ -0,0 +1,67 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/ParseOptions.h"
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
class AstStatBlock;
|
||||
class AstNameTable;
|
||||
class BytecodeBuilder;
|
||||
class BytecodeEncoder;
|
||||
|
||||
struct CompileOptions
|
||||
{
|
||||
// default bytecode version target; can be used to compile code for older clients
|
||||
int bytecodeVersion = 1;
|
||||
|
||||
// 0 - no optimization
|
||||
// 1 - baseline optimization level that doesn't prevent debuggability
|
||||
// 2 - includes optimizations that harm debuggability such as inlining
|
||||
int optimizationLevel = 1;
|
||||
|
||||
// 0 - no debugging support
|
||||
// 1 - line info & function names only; sufficient for backtraces
|
||||
// 2 - full debug info with local & upvalue names; necessary for debugger
|
||||
int debugLevel = 1;
|
||||
|
||||
// 0 - no code coverage support
|
||||
// 1 - statement coverage
|
||||
// 2 - statement and expression coverage (verbose)
|
||||
int coverageLevel = 0;
|
||||
|
||||
// global builtin to construct vectors; disabled by default
|
||||
const char* vectorLib = nullptr;
|
||||
const char* vectorCtor = nullptr;
|
||||
};
|
||||
|
||||
class CompileError : public std::exception
|
||||
{
|
||||
public:
|
||||
CompileError(const Location& location, const std::string& message);
|
||||
|
||||
virtual ~CompileError() throw();
|
||||
|
||||
virtual const char* what() const throw();
|
||||
|
||||
const Location& getLocation() const;
|
||||
|
||||
static LUAU_NORETURN void raise(const Location& location, const char* format, ...) LUAU_PRINTF_ATTR(2, 3);
|
||||
|
||||
private:
|
||||
Location location;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
// compiles bytecode into bytecode builder using either a pre-parsed AST or parsing it from source; throws on errors
|
||||
void compileOrThrow(BytecodeBuilder& bytecode, AstStatBlock* root, const AstNameTable& names, const CompileOptions& options = {});
|
||||
void compileOrThrow(BytecodeBuilder& bytecode, const std::string& source, const CompileOptions& options = {}, const ParseOptions& parseOptions = {});
|
||||
|
||||
// compiles bytecode into a bytecode blob, that either contains the valid bytecode or an encoded error that luau_load can decode
|
||||
std::string compile(
|
||||
const std::string& source, const CompileOptions& options = {}, const ParseOptions& parseOptions = {}, BytecodeEncoder* encoder = nullptr);
|
||||
|
||||
} // namespace Luau
|
1726
Compiler/src/BytecodeBuilder.cpp
Normal file
1726
Compiler/src/BytecodeBuilder.cpp
Normal file
File diff suppressed because it is too large
Load diff
3778
Compiler/src/Compiler.cpp
Normal file
3778
Compiler/src/Compiler.cpp
Normal file
File diff suppressed because it is too large
Load diff
21
LICENSE.txt
Normal file
21
LICENSE.txt
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019-2021 Roblox Corporation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
169
Makefile
Normal file
169
Makefile
Normal file
|
@ -0,0 +1,169 @@
|
|||
# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
MAKEFLAGS+=-r -j8
|
||||
COMMA=,
|
||||
|
||||
config=debug
|
||||
|
||||
BUILD=build/$(config)
|
||||
|
||||
AST_SOURCES=$(wildcard Ast/src/*.cpp)
|
||||
AST_OBJECTS=$(AST_SOURCES:%=$(BUILD)/%.o)
|
||||
AST_TARGET=$(BUILD)/libluauast.a
|
||||
|
||||
COMPILER_SOURCES=$(wildcard Compiler/src/*.cpp)
|
||||
COMPILER_OBJECTS=$(COMPILER_SOURCES:%=$(BUILD)/%.o)
|
||||
COMPILER_TARGET=$(BUILD)/libluaucompiler.a
|
||||
|
||||
ANALYSIS_SOURCES=$(wildcard Analysis/src/*.cpp)
|
||||
ANALYSIS_OBJECTS=$(ANALYSIS_SOURCES:%=$(BUILD)/%.o)
|
||||
ANALYSIS_TARGET=$(BUILD)/libluauanalysis.a
|
||||
|
||||
VM_SOURCES=$(wildcard VM/src/*.cpp)
|
||||
VM_OBJECTS=$(VM_SOURCES:%=$(BUILD)/%.o)
|
||||
VM_TARGET=$(BUILD)/libluauvm.a
|
||||
|
||||
TESTS_SOURCES=$(wildcard tests/*.cpp)
|
||||
TESTS_OBJECTS=$(TESTS_SOURCES:%=$(BUILD)/%.o)
|
||||
TESTS_TARGET=$(BUILD)/luau-tests
|
||||
|
||||
REPL_CLI_SOURCES=CLI/FileUtils.cpp CLI/Profiler.cpp CLI/Repl.cpp
|
||||
REPL_CLI_OBJECTS=$(REPL_CLI_SOURCES:%=$(BUILD)/%.o)
|
||||
REPL_CLI_TARGET=$(BUILD)/luau
|
||||
|
||||
ANALYZE_CLI_SOURCES=CLI/FileUtils.cpp CLI/Analyze.cpp
|
||||
ANALYZE_CLI_OBJECTS=$(ANALYZE_CLI_SOURCES:%=$(BUILD)/%.o)
|
||||
ANALYZE_CLI_TARGET=$(BUILD)/luau-analyze
|
||||
|
||||
FUZZ_SOURCES=$(wildcard fuzz/*.cpp)
|
||||
FUZZ_OBJECTS=$(FUZZ_SOURCES:%=$(BUILD)/%.o)
|
||||
|
||||
TESTS_ARGS=
|
||||
ifneq ($(flags),)
|
||||
TESTS_ARGS+=--fflags=$(flags)
|
||||
endif
|
||||
|
||||
OBJECTS=$(AST_OBJECTS) $(COMPILER_OBJECTS) $(ANALYSIS_OBJECTS) $(VM_OBJECTS) $(TESTS_OBJECTS) $(CLI_OBJECTS) $(FUZZ_OBJECTS)
|
||||
|
||||
# common flags
|
||||
CXXFLAGS=-g -Wall -Werror
|
||||
LDFLAGS=
|
||||
|
||||
CXXFLAGS+=-Wno-unused # temporary, for older gcc versions
|
||||
|
||||
# configuration-specific flags
|
||||
ifeq ($(config),release)
|
||||
CXXFLAGS+=-O2 -DNDEBUG
|
||||
endif
|
||||
|
||||
ifeq ($(config),coverage)
|
||||
CXXFLAGS+=-fprofile-instr-generate -fcoverage-mapping
|
||||
LDFLAGS+=-fprofile-instr-generate
|
||||
endif
|
||||
|
||||
ifeq ($(config),sanitize)
|
||||
CXXFLAGS+=-fsanitize=address -O1
|
||||
LDFLAGS+=-fsanitize=address
|
||||
endif
|
||||
|
||||
ifeq ($(config),analyze)
|
||||
CXXFLAGS+=--analyze
|
||||
endif
|
||||
|
||||
ifeq ($(config),fuzz)
|
||||
CXX=clang++ # our fuzzing infra relies on llvm fuzzer
|
||||
CXXFLAGS+=-fsanitize=address,fuzzer -Ibuild/libprotobuf-mutator -Ibuild/libprotobuf-mutator/external.protobuf/include -O2
|
||||
LDFLAGS+=-fsanitize=address,fuzzer
|
||||
endif
|
||||
|
||||
# target-specific flags
|
||||
$(AST_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include
|
||||
$(COMPILER_OBJECTS): CXXFLAGS+=-std=c++17 -ICompiler/include -IAst/include
|
||||
$(ANALYSIS_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include -IAnalysis/include
|
||||
$(VM_OBJECTS): CXXFLAGS+=-std=c++11 -IVM/include
|
||||
$(TESTS_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include -ICompiler/include -IAnalysis/include -IVM/include -Iextern
|
||||
$(REPL_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include -ICompiler/include -IVM/include -Iextern
|
||||
$(ANALYZE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include -IAnalysis/include -Iextern
|
||||
$(FUZZ_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include -ICompiler/include -IAnalysis/include -IVM/include
|
||||
|
||||
$(REPL_CLI_TARGET): LDFLAGS+=-lpthread
|
||||
fuzz-proto fuzz-prototest: LDFLAGS+=build/libprotobuf-mutator/src/libfuzzer/libprotobuf-mutator-libfuzzer.a build/libprotobuf-mutator/src/libprotobuf-mutator.a build/libprotobuf-mutator/external.protobuf/lib/libprotobuf.a
|
||||
|
||||
# pseudo targets
|
||||
.PHONY: all test clean coverage format luau-size
|
||||
|
||||
all: $(REPL_CLI_TARGET) $(ANALYZE_CLI_TARGET) $(TESTS_TARGET)
|
||||
|
||||
test: $(TESTS_TARGET)
|
||||
$(TESTS_TARGET) $(TESTS_ARGS)
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD)
|
||||
|
||||
coverage: $(TESTS_TARGET)
|
||||
$(TESTS_TARGET) --fflags=true
|
||||
mv default.profraw default-flags.profraw
|
||||
$(TESTS_TARGET)
|
||||
llvm-profdata merge default.profraw default-flags.profraw -o default.profdata
|
||||
rm default.profraw default-flags.profraw
|
||||
llvm-cov show -format=html -show-instantiations=false -show-line-counts=true -show-region-summary=false -ignore-filename-regex=\(tests\|extern\)/.* -output-dir=coverage --instr-profile default.profdata build/coverage/luau-tests
|
||||
llvm-cov report -ignore-filename-regex=\(tests\|extern\)/.* -show-region-summary=false --instr-profile default.profdata build/coverage/luau-tests
|
||||
|
||||
format:
|
||||
find . -name '*.h' -or -name '*.cpp' | xargs clang-format -i
|
||||
|
||||
luau-size: luau
|
||||
nm --print-size --demangle luau | grep ' t void luau_execute<false>' | awk -F ' ' '{sum += strtonum("0x" $$2)} END {print sum " interpreter" }'
|
||||
nm --print-size --demangle luau | grep ' t luauF_' | awk -F ' ' '{sum += strtonum("0x" $$2)} END {print sum " builtins" }'
|
||||
|
||||
# executable target aliases
|
||||
luau: $(REPL_CLI_TARGET)
|
||||
cp $^ $@
|
||||
|
||||
luau-analyze: $(ANALYZE_CLI_TARGET)
|
||||
cp $^ $@
|
||||
|
||||
# executable targets
|
||||
$(TESTS_TARGET): $(TESTS_OBJECTS) $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(VM_TARGET)
|
||||
$(REPL_CLI_TARGET): $(REPL_CLI_OBJECTS) $(COMPILER_TARGET) $(AST_TARGET) $(VM_TARGET)
|
||||
$(ANALYZE_CLI_TARGET): $(ANALYZE_CLI_OBJECTS) $(ANALYSIS_TARGET) $(AST_TARGET)
|
||||
|
||||
$(TESTS_TARGET) $(REPL_CLI_TARGET) $(ANALYZE_CLI_TARGET):
|
||||
$(CXX) $^ $(LDFLAGS) -o $@
|
||||
|
||||
# executable targets for fuzzing
|
||||
fuzz-%: $(BUILD)/fuzz/%.cpp.o $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(VM_TARGET)
|
||||
fuzz-proto: $(BUILD)/fuzz/proto.cpp.o $(BUILD)/fuzz/protoprint.cpp.o $(BUILD)/fuzz/luau.pb.cpp.o $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(VM_TARGET) | build/libprotobuf-mutator
|
||||
fuzz-prototest: $(BUILD)/fuzz/prototest.cpp.o $(BUILD)/fuzz/protoprint.cpp.o $(BUILD)/fuzz/luau.pb.cpp.o $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(VM_TARGET) | build/libprotobuf-mutator
|
||||
|
||||
fuzz-%:
|
||||
$(CXX) $^ $(LDFLAGS) -o $@
|
||||
|
||||
# static library targets
|
||||
$(AST_TARGET): $(AST_OBJECTS)
|
||||
$(COMPILER_TARGET): $(COMPILER_OBJECTS)
|
||||
$(ANALYSIS_TARGET): $(ANALYSIS_OBJECTS)
|
||||
$(VM_TARGET): $(VM_OBJECTS)
|
||||
|
||||
$(AST_TARGET) $(COMPILER_TARGET) $(ANALYSIS_TARGET) $(VM_TARGET):
|
||||
ar rcs $@ $^
|
||||
|
||||
# object file targets
|
||||
$(BUILD)/%.cpp.o: %.cpp
|
||||
@mkdir -p $(dir $@)
|
||||
$(CXX) $< $(CXXFLAGS) -c -MMD -MP -o $@
|
||||
|
||||
# protobuf fuzzer setup
|
||||
fuzz/luau.pb.cpp: fuzz/luau.proto build/libprotobuf-mutator
|
||||
cd fuzz && ../build/libprotobuf-mutator/external.protobuf/bin/protoc luau.proto --cpp_out=.
|
||||
mv fuzz/luau.pb.cc fuzz/luau.pb.cpp
|
||||
|
||||
$(BUILD)/fuzz/proto.cpp.o: build/libprotobuf-mutator
|
||||
$(BUILD)/fuzz/protoprint.cpp.o: build/libprotobuf-mutator
|
||||
|
||||
build/libprotobuf-mutator:
|
||||
git clone https://github.com/google/libprotobuf-mutator build/libprotobuf-mutator
|
||||
CXX= cmake -S build/libprotobuf-mutator -B build/libprotobuf-mutator -D CMAKE_BUILD_TYPE=Release -D LIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON -D LIB_PROTO_MUTATOR_TESTING=OFF
|
||||
make -C build/libprotobuf-mutator -j8
|
||||
|
||||
# picks up include dependencies for all object files
|
||||
-include $(OBJECTS:.o=.d)
|
215
Sources.cmake
Normal file
215
Sources.cmake
Normal file
|
@ -0,0 +1,215 @@
|
|||
# Luau.Ast Sources
|
||||
target_sources(Luau.Ast PRIVATE
|
||||
Ast/include/Luau/Ast.h
|
||||
Ast/include/Luau/Common.h
|
||||
Ast/include/Luau/Confusables.h
|
||||
Ast/include/Luau/DenseHash.h
|
||||
Ast/include/Luau/Lexer.h
|
||||
Ast/include/Luau/Location.h
|
||||
Ast/include/Luau/ParseOptions.h
|
||||
Ast/include/Luau/Parser.h
|
||||
Ast/include/Luau/StringUtils.h
|
||||
|
||||
Ast/src/Ast.cpp
|
||||
Ast/src/Confusables.cpp
|
||||
Ast/src/Lexer.cpp
|
||||
Ast/src/Location.cpp
|
||||
Ast/src/Parser.cpp
|
||||
Ast/src/StringUtils.cpp
|
||||
)
|
||||
|
||||
# Luau.Compiler Sources
|
||||
target_sources(Luau.Compiler PRIVATE
|
||||
Compiler/include/Luau/Bytecode.h
|
||||
Compiler/include/Luau/BytecodeBuilder.h
|
||||
Compiler/include/Luau/Compiler.h
|
||||
|
||||
Compiler/src/BytecodeBuilder.cpp
|
||||
Compiler/src/Compiler.cpp
|
||||
)
|
||||
|
||||
# Luau.Analysis Sources
|
||||
target_sources(Luau.Analysis PRIVATE
|
||||
Analysis/include/Luau/AstQuery.h
|
||||
Analysis/include/Luau/Autocomplete.h
|
||||
Analysis/include/Luau/BuiltinDefinitions.h
|
||||
Analysis/include/Luau/Config.h
|
||||
Analysis/include/Luau/Documentation.h
|
||||
Analysis/include/Luau/Error.h
|
||||
Analysis/include/Luau/FileResolver.h
|
||||
Analysis/include/Luau/Frontend.h
|
||||
Analysis/include/Luau/IostreamHelpers.h
|
||||
Analysis/include/Luau/JsonEncoder.h
|
||||
Analysis/include/Luau/Linter.h
|
||||
Analysis/include/Luau/Module.h
|
||||
Analysis/include/Luau/ModuleResolver.h
|
||||
Analysis/include/Luau/Predicate.h
|
||||
Analysis/include/Luau/RecursionCounter.h
|
||||
Analysis/include/Luau/RequireTracer.h
|
||||
Analysis/include/Luau/Substitution.h
|
||||
Analysis/include/Luau/Symbol.h
|
||||
Analysis/include/Luau/TopoSortStatements.h
|
||||
Analysis/include/Luau/ToString.h
|
||||
Analysis/include/Luau/Transpiler.h
|
||||
Analysis/include/Luau/TxnLog.h
|
||||
Analysis/include/Luau/TypeAttach.h
|
||||
Analysis/include/Luau/TypedAllocator.h
|
||||
Analysis/include/Luau/TypeInfer.h
|
||||
Analysis/include/Luau/TypePack.h
|
||||
Analysis/include/Luau/TypeUtils.h
|
||||
Analysis/include/Luau/TypeVar.h
|
||||
Analysis/include/Luau/Unifiable.h
|
||||
Analysis/include/Luau/Unifier.h
|
||||
Analysis/include/Luau/Variant.h
|
||||
Analysis/include/Luau/VisitTypeVar.h
|
||||
|
||||
Analysis/src/AstQuery.cpp
|
||||
Analysis/src/Autocomplete.cpp
|
||||
Analysis/src/BuiltinDefinitions.cpp
|
||||
Analysis/src/Config.cpp
|
||||
Analysis/src/Error.cpp
|
||||
Analysis/src/Frontend.cpp
|
||||
Analysis/src/IostreamHelpers.cpp
|
||||
Analysis/src/JsonEncoder.cpp
|
||||
Analysis/src/Linter.cpp
|
||||
Analysis/src/Module.cpp
|
||||
Analysis/src/Predicate.cpp
|
||||
Analysis/src/RequireTracer.cpp
|
||||
Analysis/src/Substitution.cpp
|
||||
Analysis/src/Symbol.cpp
|
||||
Analysis/src/TopoSortStatements.cpp
|
||||
Analysis/src/ToString.cpp
|
||||
Analysis/src/Transpiler.cpp
|
||||
Analysis/src/TxnLog.cpp
|
||||
Analysis/src/TypeAttach.cpp
|
||||
Analysis/src/TypedAllocator.cpp
|
||||
Analysis/src/TypeInfer.cpp
|
||||
Analysis/src/TypePack.cpp
|
||||
Analysis/src/TypeUtils.cpp
|
||||
Analysis/src/TypeVar.cpp
|
||||
Analysis/src/Unifiable.cpp
|
||||
Analysis/src/Unifier.cpp
|
||||
Analysis/src/EmbeddedBuiltinDefinitions.cpp
|
||||
)
|
||||
|
||||
# Luau.VM Sources
|
||||
target_sources(Luau.VM PRIVATE
|
||||
VM/include/lua.h
|
||||
VM/include/luaconf.h
|
||||
VM/include/lualib.h
|
||||
|
||||
VM/src/lapi.cpp
|
||||
VM/src/laux.cpp
|
||||
VM/src/lbaselib.cpp
|
||||
VM/src/lbitlib.cpp
|
||||
VM/src/lbuiltins.cpp
|
||||
VM/src/lcorolib.cpp
|
||||
VM/src/ldblib.cpp
|
||||
VM/src/ldebug.cpp
|
||||
VM/src/ldo.cpp
|
||||
VM/src/lfunc.cpp
|
||||
VM/src/lgc.cpp
|
||||
VM/src/linit.cpp
|
||||
VM/src/lmathlib.cpp
|
||||
VM/src/lmem.cpp
|
||||
VM/src/lobject.cpp
|
||||
VM/src/loslib.cpp
|
||||
VM/src/lperf.cpp
|
||||
VM/src/lstate.cpp
|
||||
VM/src/lstring.cpp
|
||||
VM/src/lstrlib.cpp
|
||||
VM/src/ltable.cpp
|
||||
VM/src/ltablib.cpp
|
||||
VM/src/ltm.cpp
|
||||
VM/src/lutf8lib.cpp
|
||||
VM/src/lvmexecute.cpp
|
||||
VM/src/lvmload.cpp
|
||||
VM/src/lvmutils.cpp
|
||||
VM/src/lapi.h
|
||||
VM/src/lbuiltins.h
|
||||
VM/src/lbytecode.h
|
||||
VM/src/lcommon.h
|
||||
VM/src/ldebug.h
|
||||
VM/src/ldo.h
|
||||
VM/src/lfunc.h
|
||||
VM/src/lgc.h
|
||||
VM/src/lmem.h
|
||||
VM/src/lnumutils.h
|
||||
VM/src/lobject.h
|
||||
VM/src/lstate.h
|
||||
VM/src/lstring.h
|
||||
VM/src/ltable.h
|
||||
VM/src/ltm.h
|
||||
VM/src/lvm.h
|
||||
)
|
||||
|
||||
if(TARGET Luau.Repl.CLI)
|
||||
# Luau.Repl.CLI Sources
|
||||
target_sources(Luau.Repl.CLI PRIVATE
|
||||
CLI/FileUtils.h
|
||||
CLI/FileUtils.cpp
|
||||
CLI/Profiler.h
|
||||
CLI/Profiler.cpp
|
||||
CLI/Repl.cpp)
|
||||
endif()
|
||||
|
||||
if(TARGET Luau.Analyze.CLI)
|
||||
# Luau.Analyze.CLI Sources
|
||||
target_sources(Luau.Analyze.CLI PRIVATE
|
||||
CLI/FileUtils.h
|
||||
CLI/FileUtils.cpp
|
||||
CLI/Analyze.cpp)
|
||||
endif()
|
||||
|
||||
if(TARGET Luau.UnitTest)
|
||||
# Luau.UnitTest Sources
|
||||
target_sources(Luau.UnitTest PRIVATE
|
||||
tests/Fixture.h
|
||||
tests/IostreamOptional.h
|
||||
tests/ScopedFlags.h
|
||||
tests/Fixture.cpp
|
||||
tests/AstQuery.test.cpp
|
||||
tests/AstVisitor.test.cpp
|
||||
tests/Autocomplete.test.cpp
|
||||
tests/BuiltinDefinitions.test.cpp
|
||||
tests/Compiler.test.cpp
|
||||
tests/Config.test.cpp
|
||||
tests/Error.test.cpp
|
||||
tests/Frontend.test.cpp
|
||||
tests/JsonEncoder.test.cpp
|
||||
tests/Linter.test.cpp
|
||||
tests/Module.test.cpp
|
||||
tests/NonstrictMode.test.cpp
|
||||
tests/Parser.test.cpp
|
||||
tests/Predicate.test.cpp
|
||||
tests/RequireTracer.test.cpp
|
||||
tests/StringUtils.test.cpp
|
||||
tests/Symbol.test.cpp
|
||||
tests/TopoSort.test.cpp
|
||||
tests/ToString.test.cpp
|
||||
tests/Transpiler.test.cpp
|
||||
tests/TypeInfer.annotations.test.cpp
|
||||
tests/TypeInfer.builtins.test.cpp
|
||||
tests/TypeInfer.classes.test.cpp
|
||||
tests/TypeInfer.definitions.test.cpp
|
||||
tests/TypeInfer.generics.test.cpp
|
||||
tests/TypeInfer.intersectionTypes.test.cpp
|
||||
tests/TypeInfer.provisional.test.cpp
|
||||
tests/TypeInfer.refinements.test.cpp
|
||||
tests/TypeInfer.tables.test.cpp
|
||||
tests/TypeInfer.test.cpp
|
||||
tests/TypeInfer.tryUnify.test.cpp
|
||||
tests/TypeInfer.typePacks.cpp
|
||||
tests/TypeInfer.unionTypes.test.cpp
|
||||
tests/TypePack.test.cpp
|
||||
tests/TypeVar.test.cpp
|
||||
tests/Variant.test.cpp
|
||||
tests/main.cpp)
|
||||
endif()
|
||||
|
||||
if(TARGET Luau.Conformance)
|
||||
# Luau.Conformance Sources
|
||||
target_sources(Luau.Conformance PRIVATE
|
||||
tests/Conformance.test.cpp
|
||||
tests/main.cpp)
|
||||
endif()
|
385
VM/include/lua.h
Normal file
385
VM/include/lua.h
Normal file
|
@ -0,0 +1,385 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "luaconf.h"
|
||||
|
||||
|
||||
|
||||
|
||||
/* option for multiple returns in `lua_pcall' and `lua_call' */
|
||||
#define LUA_MULTRET (-1)
|
||||
|
||||
/*
|
||||
** pseudo-indices
|
||||
*/
|
||||
#define LUA_REGISTRYINDEX (-10000)
|
||||
#define LUA_ENVIRONINDEX (-10001)
|
||||
#define LUA_GLOBALSINDEX (-10002)
|
||||
#define lua_upvalueindex(i) (LUA_GLOBALSINDEX - (i))
|
||||
|
||||
/* thread status; 0 is OK */
|
||||
enum lua_Status
|
||||
{
|
||||
LUA_OK = 0,
|
||||
LUA_YIELD,
|
||||
LUA_ERRRUN,
|
||||
LUA_ERRSYNTAX,
|
||||
LUA_ERRMEM,
|
||||
LUA_ERRERR,
|
||||
LUA_BREAK, /* yielded for a debug breakpoint */
|
||||
};
|
||||
|
||||
typedef struct lua_State lua_State;
|
||||
|
||||
typedef int (*lua_CFunction)(lua_State* L);
|
||||
typedef int (*lua_Continuation)(lua_State* L, int status);
|
||||
|
||||
/*
|
||||
** prototype for memory-allocation functions
|
||||
*/
|
||||
|
||||
typedef void* (*lua_Alloc)(lua_State* L, void* ud, void* ptr, size_t osize, size_t nsize);
|
||||
|
||||
/* non-return type */
|
||||
#define l_noret void LUA_NORETURN
|
||||
|
||||
/*
|
||||
** basic types
|
||||
*/
|
||||
#define LUA_TNONE (-1)
|
||||
|
||||
/*
|
||||
* WARNING: if you change the order of this enumeration,
|
||||
* grep "ORDER TYPE"
|
||||
*/
|
||||
// clang-format off
|
||||
enum lua_Type
|
||||
{
|
||||
LUA_TNIL = 0, /* must be 0 due to lua_isnoneornil */
|
||||
LUA_TBOOLEAN = 1, /* must be 1 due to l_isfalse */
|
||||
|
||||
|
||||
LUA_TLIGHTUSERDATA,
|
||||
LUA_TNUMBER,
|
||||
LUA_TVECTOR,
|
||||
|
||||
LUA_TSTRING, /* all types above this must be value types, all types below this must be GC types - see iscollectable */
|
||||
|
||||
|
||||
LUA_TTABLE,
|
||||
LUA_TFUNCTION,
|
||||
LUA_TUSERDATA,
|
||||
LUA_TTHREAD,
|
||||
|
||||
/* values below this line are used in GCObject tags but may never show up in TValue type tags */
|
||||
LUA_TPROTO,
|
||||
LUA_TUPVAL,
|
||||
LUA_TDEADKEY,
|
||||
|
||||
/* the count of TValue type tags */
|
||||
LUA_T_COUNT = LUA_TPROTO
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
/* type of numbers in Luau */
|
||||
typedef double lua_Number;
|
||||
|
||||
/* type for integer functions */
|
||||
typedef int lua_Integer;
|
||||
|
||||
/* unsigned integer type */
|
||||
typedef unsigned lua_Unsigned;
|
||||
|
||||
/*
|
||||
** state manipulation
|
||||
*/
|
||||
LUA_API lua_State* lua_newstate(lua_Alloc f, void* ud);
|
||||
LUA_API void lua_close(lua_State* L);
|
||||
LUA_API lua_State* lua_newthread(lua_State* L);
|
||||
LUA_API lua_State* lua_mainthread(lua_State* L);
|
||||
|
||||
/*
|
||||
** basic stack manipulation
|
||||
*/
|
||||
LUA_API int lua_gettop(lua_State* L);
|
||||
LUA_API void lua_settop(lua_State* L, int idx);
|
||||
LUA_API void lua_pushvalue(lua_State* L, int idx);
|
||||
LUA_API void lua_remove(lua_State* L, int idx);
|
||||
LUA_API void lua_insert(lua_State* L, int idx);
|
||||
LUA_API void lua_replace(lua_State* L, int idx);
|
||||
LUA_API int lua_checkstack(lua_State* L, int sz);
|
||||
LUA_API void lua_rawcheckstack(lua_State* L, int sz); /* allows for unlimited stack frames */
|
||||
|
||||
LUA_API void lua_xmove(lua_State* from, lua_State* to, int n);
|
||||
LUA_API void lua_xpush(lua_State* from, lua_State* to, int idx);
|
||||
|
||||
/*
|
||||
** access functions (stack -> C)
|
||||
*/
|
||||
|
||||
LUA_API int lua_isnumber(lua_State* L, int idx);
|
||||
LUA_API int lua_isstring(lua_State* L, int idx);
|
||||
LUA_API int lua_iscfunction(lua_State* L, int idx);
|
||||
LUA_API int lua_isLfunction(lua_State* L, int idx);
|
||||
LUA_API int lua_isuserdata(lua_State* L, int idx);
|
||||
LUA_API int lua_type(lua_State* L, int idx);
|
||||
LUA_API const char* lua_typename(lua_State* L, int tp);
|
||||
|
||||
LUA_API int lua_equal(lua_State* L, int idx1, int idx2);
|
||||
LUA_API int lua_rawequal(lua_State* L, int idx1, int idx2);
|
||||
LUA_API int lua_lessthan(lua_State* L, int idx1, int idx2);
|
||||
|
||||
LUA_API double lua_tonumberx(lua_State* L, int idx, int* isnum);
|
||||
LUA_API int lua_tointegerx(lua_State* L, int idx, int* isnum);
|
||||
LUA_API unsigned lua_tounsignedx(lua_State* L, int idx, int* isnum);
|
||||
LUA_API const float* lua_tovector(lua_State* L, int idx);
|
||||
LUA_API int lua_toboolean(lua_State* L, int idx);
|
||||
LUA_API const char* lua_tolstring(lua_State* L, int idx, size_t* len);
|
||||
LUA_API const char* lua_tostringatom(lua_State* L, int idx, int* atom);
|
||||
LUA_API const char* lua_namecallatom(lua_State* L, int* atom);
|
||||
LUA_API int lua_objlen(lua_State* L, int idx);
|
||||
LUA_API lua_CFunction lua_tocfunction(lua_State* L, int idx);
|
||||
LUA_API void* lua_touserdata(lua_State* L, int idx);
|
||||
LUA_API void* lua_touserdatatagged(lua_State* L, int idx, int tag);
|
||||
LUA_API int lua_userdatatag(lua_State* L, int idx);
|
||||
LUA_API lua_State* lua_tothread(lua_State* L, int idx);
|
||||
LUA_API const void* lua_topointer(lua_State* L, int idx);
|
||||
|
||||
/*
|
||||
** push functions (C -> stack)
|
||||
*/
|
||||
LUA_API void lua_pushnil(lua_State* L);
|
||||
LUA_API void lua_pushnumber(lua_State* L, double n);
|
||||
LUA_API void lua_pushinteger(lua_State* L, int n);
|
||||
LUA_API void lua_pushunsigned(lua_State* L, unsigned n);
|
||||
LUA_API void lua_pushvector(lua_State* L, float x, float y, float z);
|
||||
LUA_API void lua_pushlstring(lua_State* L, const char* s, size_t l);
|
||||
LUA_API void lua_pushstring(lua_State* L, const char* s);
|
||||
LUA_API const char* lua_pushvfstring(lua_State* L, const char* fmt, va_list argp);
|
||||
LUA_API LUA_PRINTF_ATTR(2, 3) const char* lua_pushfstringL(lua_State* L, const char* fmt, ...);
|
||||
LUA_API void lua_pushcfunction(
|
||||
lua_State* L, lua_CFunction fn, const char* debugname = NULL, int nup = 0, lua_Continuation cont = NULL);
|
||||
LUA_API void lua_pushboolean(lua_State* L, int b);
|
||||
LUA_API void lua_pushlightuserdata(lua_State* L, void* p);
|
||||
LUA_API int lua_pushthread(lua_State* L);
|
||||
|
||||
/*
|
||||
** get functions (Lua -> stack)
|
||||
*/
|
||||
LUA_API void lua_gettable(lua_State* L, int idx);
|
||||
LUA_API void lua_getfield(lua_State* L, int idx, const char* k);
|
||||
LUA_API void lua_rawgetfield(lua_State* L, int idx, const char* k);
|
||||
LUA_API void lua_rawget(lua_State* L, int idx);
|
||||
LUA_API void lua_rawgeti(lua_State* L, int idx, int n);
|
||||
LUA_API void lua_createtable(lua_State* L, int narr, int nrec);
|
||||
|
||||
LUA_API void lua_setreadonly(lua_State* L, int idx, bool value);
|
||||
LUA_API int lua_getreadonly(lua_State* L, int idx);
|
||||
LUA_API void lua_setsafeenv(lua_State* L, int idx, bool value);
|
||||
|
||||
LUA_API void* lua_newuserdata(lua_State* L, size_t sz, int tag);
|
||||
LUA_API void* lua_newuserdatadtor(lua_State* L, size_t sz, void (*dtor)(void*));
|
||||
LUA_API int lua_getmetatable(lua_State* L, int objindex);
|
||||
LUA_API void lua_getfenv(lua_State* L, int idx);
|
||||
|
||||
/*
|
||||
** set functions (stack -> Lua)
|
||||
*/
|
||||
LUA_API void lua_settable(lua_State* L, int idx);
|
||||
LUA_API void lua_setfield(lua_State* L, int idx, const char* k);
|
||||
LUA_API void lua_rawset(lua_State* L, int idx);
|
||||
LUA_API void lua_rawseti(lua_State* L, int idx, int n);
|
||||
LUA_API int lua_setmetatable(lua_State* L, int objindex);
|
||||
LUA_API int lua_setfenv(lua_State* L, int idx);
|
||||
|
||||
/*
|
||||
** `load' and `call' functions (load and run Luau bytecode)
|
||||
*/
|
||||
LUA_API int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size, int env = 0);
|
||||
LUA_API void lua_call(lua_State* L, int nargs, int nresults);
|
||||
LUA_API int lua_pcall(lua_State* L, int nargs, int nresults, int errfunc);
|
||||
|
||||
/*
|
||||
** coroutine functions
|
||||
*/
|
||||
LUA_API int lua_yield(lua_State* L, int nresults);
|
||||
LUA_API int lua_break(lua_State* L);
|
||||
LUA_API int lua_resume(lua_State* L, lua_State* from, int narg);
|
||||
LUA_API int lua_resumeerror(lua_State* L, lua_State* from);
|
||||
LUA_API int lua_status(lua_State* L);
|
||||
LUA_API int lua_isyieldable(lua_State* L);
|
||||
|
||||
/*
|
||||
** garbage-collection function and options
|
||||
*/
|
||||
|
||||
enum lua_GCOp
|
||||
{
|
||||
LUA_GCSTOP,
|
||||
LUA_GCRESTART,
|
||||
LUA_GCCOLLECT,
|
||||
LUA_GCCOUNT,
|
||||
LUA_GCISRUNNING,
|
||||
|
||||
// garbage collection is handled by 'assists' that perform some amount of GC work matching pace of allocation
|
||||
// explicit GC steps allow to perform some amount of work at custom points to offset the need for GC assists
|
||||
// note that GC might also be paused for some duration (until bytes allocated meet the threshold)
|
||||
// if an explicit step is performed during this pause, it will trigger the start of the next collection cycle
|
||||
LUA_GCSTEP,
|
||||
|
||||
LUA_GCSETGOAL,
|
||||
LUA_GCSETSTEPMUL,
|
||||
LUA_GCSETSTEPSIZE,
|
||||
};
|
||||
|
||||
LUA_API int lua_gc(lua_State* L, int what, int data);
|
||||
|
||||
/*
|
||||
** miscellaneous functions
|
||||
*/
|
||||
|
||||
LUA_API l_noret lua_error(lua_State* L);
|
||||
|
||||
LUA_API int lua_next(lua_State* L, int idx);
|
||||
|
||||
LUA_API void lua_concat(lua_State* L, int n);
|
||||
|
||||
LUA_API uintptr_t lua_encodepointer(lua_State* L, uintptr_t p);
|
||||
|
||||
LUA_API double lua_clock();
|
||||
|
||||
LUA_API void lua_setuserdatadtor(lua_State* L, int tag, void (*dtor)(void*));
|
||||
|
||||
/*
|
||||
** reference system, can be used to pin objects
|
||||
*/
|
||||
#define LUA_NOREF -1
|
||||
#define LUA_REFNIL 0
|
||||
|
||||
LUA_API int lua_ref(lua_State* L, int idx);
|
||||
LUA_API void lua_unref(lua_State* L, int ref);
|
||||
|
||||
#define lua_getref(L, ref) lua_rawgeti(L, LUA_REGISTRYINDEX, (ref))
|
||||
|
||||
/*
|
||||
** ===============================================================
|
||||
** some useful macros
|
||||
** ===============================================================
|
||||
*/
|
||||
#define lua_tonumber(L, i) lua_tonumberx(L, i, NULL)
|
||||
#define lua_tointeger(L, i) lua_tointegerx(L, i, NULL)
|
||||
#define lua_tounsigned(L, i) lua_tounsignedx(L, i, NULL)
|
||||
|
||||
#define lua_pop(L, n) lua_settop(L, -(n)-1)
|
||||
|
||||
#define lua_newtable(L) lua_createtable(L, 0, 0)
|
||||
|
||||
#define lua_strlen(L, i) lua_objlen(L, (i))
|
||||
|
||||
#define lua_isfunction(L, n) (lua_type(L, (n)) == LUA_TFUNCTION)
|
||||
#define lua_istable(L, n) (lua_type(L, (n)) == LUA_TTABLE)
|
||||
#define lua_islightuserdata(L, n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA)
|
||||
#define lua_isnil(L, n) (lua_type(L, (n)) == LUA_TNIL)
|
||||
#define lua_isboolean(L, n) (lua_type(L, (n)) == LUA_TBOOLEAN)
|
||||
#define lua_isthread(L, n) (lua_type(L, (n)) == LUA_TTHREAD)
|
||||
#define lua_isnone(L, n) (lua_type(L, (n)) == LUA_TNONE)
|
||||
#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= LUA_TNIL)
|
||||
|
||||
#define lua_pushliteral(L, s) lua_pushlstring(L, "" s, (sizeof(s) / sizeof(char)) - 1)
|
||||
|
||||
#define lua_setglobal(L, s) lua_setfield(L, LUA_GLOBALSINDEX, (s))
|
||||
#define lua_getglobal(L, s) lua_getfield(L, LUA_GLOBALSINDEX, (s))
|
||||
|
||||
#define lua_tostring(L, i) lua_tolstring(L, (i), NULL)
|
||||
|
||||
#define lua_pushfstring(L, fmt, ...) lua_pushfstringL(L, fmt, ##__VA_ARGS__)
|
||||
|
||||
/*
|
||||
** {======================================================================
|
||||
** Debug API
|
||||
** =======================================================================
|
||||
*/
|
||||
|
||||
typedef struct lua_Debug lua_Debug; /* activation record */
|
||||
|
||||
/* Functions to be called by the debugger in specific events */
|
||||
typedef void (*lua_Hook)(lua_State* L, lua_Debug* ar);
|
||||
|
||||
LUA_API int lua_getinfo(lua_State* L, int level, const char* what, lua_Debug* ar);
|
||||
LUA_API int lua_getargument(lua_State* L, int level, int n);
|
||||
LUA_API const char* lua_getlocal(lua_State* L, int level, int n);
|
||||
LUA_API const char* lua_setlocal(lua_State* L, int level, int n);
|
||||
LUA_API const char* lua_getupvalue(lua_State* L, int funcindex, int n);
|
||||
LUA_API const char* lua_setupvalue(lua_State* L, int funcindex, int n);
|
||||
|
||||
LUA_API void lua_singlestep(lua_State* L, bool singlestep);
|
||||
LUA_API void lua_breakpoint(lua_State* L, int funcindex, int line, bool enable);
|
||||
|
||||
/* Warning: this function is not thread-safe since it stores the result in a shared global array! Only use for debugging. */
|
||||
LUA_API const char* lua_debugtrace(lua_State* L);
|
||||
|
||||
struct lua_Debug
|
||||
{
|
||||
const char* name; /* (n) */
|
||||
const char* what; /* (s) `Lua', `C', `main', `tail' */
|
||||
const char* source; /* (s) */
|
||||
int linedefined; /* (s) */
|
||||
int currentline; /* (l) */
|
||||
unsigned char nupvals; /* (u) number of upvalues */
|
||||
unsigned char nparams; /* (a) number of parameters */
|
||||
char isvararg; /* (a) */
|
||||
char short_src[LUA_IDSIZE]; /* (s) */
|
||||
void* userdata; /* only valid in luau_callhook */
|
||||
};
|
||||
|
||||
/* }====================================================================== */
|
||||
|
||||
/* Callbacks that can be used to reconfigure behavior of the VM dynamically.
|
||||
* These are shared between all coroutines.
|
||||
*
|
||||
* Note: interrupt is safe to set from an arbitrary thread but all other callbacks
|
||||
* can only be changed when the VM is not running any code */
|
||||
struct lua_Callbacks
|
||||
{
|
||||
void (*interrupt)(lua_State* L, int gc); /* gets called at safepoints (loop back edges, call/ret, gc) if set */
|
||||
void (*panic)(lua_State* L, int errcode); /* gets called when an unprotected error is raised (if longjmp is used) */
|
||||
|
||||
void (*userthread)(lua_State* LP, lua_State* L); /* gets called when L is created (LP == parent) or destroyed (LP == NULL) */
|
||||
int16_t (*useratom)(const char* s, size_t l); /* gets called when a string is created; returned atom can be retrieved via tostringatom */
|
||||
|
||||
void (*debugbreak)(lua_State* L, lua_Debug* ar); /* gets called when BREAK instruction is encountered */
|
||||
void (*debugstep)(lua_State* L, lua_Debug* ar); /* gets called after each instruction in single step mode */
|
||||
void (*debuginterrupt)(lua_State* L, lua_Debug* ar); /* gets called when thread execution is interrupted by break in another thread */
|
||||
void (*debugprotectederror)(lua_State* L); /* gets called when protected call results in an error */
|
||||
};
|
||||
|
||||
LUA_API lua_Callbacks* lua_callbacks(lua_State* L);
|
||||
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2019-2021 Roblox Corporation
|
||||
* Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
******************************************************************************/
|
124
VM/include/luaconf.h
Normal file
124
VM/include/luaconf.h
Normal file
|
@ -0,0 +1,124 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
// When debugging complex issues, consider enabling one of these:
|
||||
// This will reallocate the stack very aggressively at every opportunity; use this with asan to catch stale stack pointers
|
||||
// #define HARDSTACKTESTS 1
|
||||
// This will call GC validation very aggressively at every incremental GC step; use this with caution as it's SLOW
|
||||
// #define HARDMEMTESTS 1
|
||||
// This will call GC validation very aggressively at every GC opportunity; use this with caution as it's VERY SLOW
|
||||
// #define HARDMEMTESTS 2
|
||||
|
||||
// To force MSVC2017+ to generate SSE2 code for some stdlib functions we need to locally enable /fp:fast
|
||||
// Note that /fp:fast changes the semantics of floating point comparisons so this is only safe to do for functions without ones
|
||||
#if defined(_MSC_VER) && !defined(__clang__)
|
||||
#define LUAU_FASTMATH_BEGIN __pragma(float_control(precise, off, push))
|
||||
#define LUAU_FASTMATH_END __pragma(float_control(pop))
|
||||
#else
|
||||
#define LUAU_FASTMATH_BEGIN
|
||||
#define LUAU_FASTMATH_END
|
||||
#endif
|
||||
|
||||
// Used on functions that have a printf-like interface to validate them statically
|
||||
#if defined(__GNUC__)
|
||||
#define LUA_PRINTF_ATTR(fmt, arg) __attribute__((format(printf, fmt, arg)))
|
||||
#else
|
||||
#define LUA_PRINTF_ATTR(fmt, arg)
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define LUA_NORETURN __declspec(noreturn)
|
||||
#else
|
||||
#define LUA_NORETURN __attribute__((__noreturn__))
|
||||
#endif
|
||||
|
||||
/* Can be used to reconfigure visibility/exports for public APIs */
|
||||
#define LUA_API extern
|
||||
#define LUALIB_API LUA_API
|
||||
|
||||
/* Can be used to reconfigure visibility for internal APIs */
|
||||
#if defined(__GNUC__)
|
||||
#define LUAI_FUNC __attribute__((visibility("hidden"))) extern
|
||||
#define LUAI_DATA LUAI_FUNC
|
||||
#else
|
||||
#define LUAI_FUNC extern
|
||||
#define LUAI_DATA extern
|
||||
#endif
|
||||
|
||||
/* Can be used to reconfigure internal error handling to use longjmp instead of C++ EH */
|
||||
#define LUA_USE_LONGJMP 0
|
||||
|
||||
/* LUA_IDSIZE gives the maximum size for the description of the source */
|
||||
#define LUA_IDSIZE 256
|
||||
|
||||
/*
|
||||
@@ LUAI_GCGOAL defines the desired top heap size in relation to the live heap
|
||||
@* size at the end of the GC cycle
|
||||
** CHANGE it if you want the GC to run faster or slower (higher values
|
||||
** mean larger GC pauses which mean slower collection.) You can also change
|
||||
** this value dynamically.
|
||||
*/
|
||||
#define LUAI_GCGOAL 200 /* 200% (allow heap to double compared to live heap size) */
|
||||
|
||||
/*
|
||||
@@ LUAI_GCSTEPMUL / LUAI_GCSTEPSIZE define the default speed of garbage collection
|
||||
@* relative to memory allocation.
|
||||
** Every LUAI_GCSTEPSIZE KB allocated, incremental collector collects LUAI_GCSTEPSIZE
|
||||
** times LUAI_GCSTEPMUL% bytes.
|
||||
** CHANGE it if you want to change the granularity of the garbage
|
||||
** collection.
|
||||
*/
|
||||
#define LUAI_GCSTEPMUL 200 /* GC runs 'twice the speed' of memory allocation */
|
||||
#define LUAI_GCSTEPSIZE 1 /* GC runs every KB of memory allocation */
|
||||
|
||||
/* LUA_MINSTACK is the guaranteed number of Lua stack slots available to a C function */
|
||||
#define LUA_MINSTACK 20
|
||||
|
||||
/* LUAI_MAXCSTACK limits the number of Lua stack slots that a C function can use */
|
||||
#define LUAI_MAXCSTACK 8000
|
||||
|
||||
/* LUAI_MAXCALLS limits the number of nested calls */
|
||||
#define LUAI_MAXCALLS 20000
|
||||
|
||||
/* LUAI_MAXCCALLS is the maximum depth for nested C calls; this limit depends on native stack size */
|
||||
#define LUAI_MAXCCALLS 200
|
||||
|
||||
/* buffer size used for on-stack string operations; this limit depends on native stack size */
|
||||
#define LUA_BUFFERSIZE 512
|
||||
|
||||
/* number of valid Lua userdata tags */
|
||||
#define LUA_UTAG_LIMIT 128
|
||||
|
||||
/* upper bound for number of size classes used by page allocator */
|
||||
#define LUA_SIZECLASSES 32
|
||||
|
||||
/* available number of separate memory categories */
|
||||
#define LUA_MEMORY_CATEGORIES 256
|
||||
|
||||
/* minimum size for the string table (must be power of 2) */
|
||||
#define LUA_MINSTRTABSIZE 32
|
||||
|
||||
/* maximum number of captures supported by pattern matching */
|
||||
#define LUA_MAXCAPTURES 32
|
||||
|
||||
/* }================================================================== */
|
||||
|
||||
/* Default number printing format and the string length limit */
|
||||
#define LUA_NUMBER_FMT "%.14g"
|
||||
#define LUAI_MAXNUMBER2STR 32 /* 16 digits, sign, point, and \0 */
|
||||
|
||||
/*
|
||||
@@ LUAI_USER_ALIGNMENT_T is a type that requires maximum alignment.
|
||||
** CHANGE it if your system requires alignments larger than double. (For
|
||||
** instance, if your system supports long doubles and they must be
|
||||
** aligned in 16-byte boundaries, then you should add long double in the
|
||||
** union.) Probably you do not need to change this.
|
||||
*/
|
||||
#define LUAI_USER_ALIGNMENT_T \
|
||||
union \
|
||||
{ \
|
||||
double u; \
|
||||
void* s; \
|
||||
long l; \
|
||||
}
|
129
VM/include/lualib.h
Normal file
129
VM/include/lualib.h
Normal file
|
@ -0,0 +1,129 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
#define luaL_error(L, fmt, ...) luaL_errorL(L, fmt, ##__VA_ARGS__)
|
||||
#define luaL_typeerror(L, narg, tname) luaL_typeerrorL(L, narg, tname)
|
||||
#define luaL_argerror(L, narg, extramsg) luaL_argerrorL(L, narg, extramsg)
|
||||
|
||||
typedef struct luaL_Reg
|
||||
{
|
||||
const char* name;
|
||||
lua_CFunction func;
|
||||
} luaL_Reg;
|
||||
|
||||
LUALIB_API void luaL_register(lua_State* L, const char* libname, const luaL_Reg* l);
|
||||
LUALIB_API int luaL_getmetafield(lua_State* L, int obj, const char* e);
|
||||
LUALIB_API int luaL_callmeta(lua_State* L, int obj, const char* e);
|
||||
LUALIB_API l_noret luaL_typeerrorL(lua_State* L, int narg, const char* tname);
|
||||
LUALIB_API l_noret luaL_argerrorL(lua_State* L, int narg, const char* extramsg);
|
||||
LUALIB_API const char* luaL_checklstring(lua_State* L, int numArg, size_t* l);
|
||||
LUALIB_API const char* luaL_optlstring(lua_State* L, int numArg, const char* def, size_t* l);
|
||||
LUALIB_API double luaL_checknumber(lua_State* L, int numArg);
|
||||
LUALIB_API double luaL_optnumber(lua_State* L, int nArg, double def);
|
||||
|
||||
LUALIB_API int luaL_checkinteger(lua_State* L, int numArg);
|
||||
LUALIB_API int luaL_optinteger(lua_State* L, int nArg, int def);
|
||||
LUALIB_API unsigned luaL_checkunsigned(lua_State* L, int numArg);
|
||||
LUALIB_API unsigned luaL_optunsigned(lua_State* L, int numArg, unsigned def);
|
||||
|
||||
LUALIB_API void luaL_checkstack(lua_State* L, int sz, const char* msg);
|
||||
LUALIB_API void luaL_checktype(lua_State* L, int narg, int t);
|
||||
LUALIB_API void luaL_checkany(lua_State* L, int narg);
|
||||
|
||||
LUALIB_API int luaL_newmetatable(lua_State* L, const char* tname);
|
||||
LUALIB_API void* luaL_checkudata(lua_State* L, int ud, const char* tname);
|
||||
|
||||
LUALIB_API void luaL_where(lua_State* L, int lvl);
|
||||
LUALIB_API LUA_PRINTF_ATTR(2, 3) l_noret luaL_errorL(lua_State* L, const char* fmt, ...);
|
||||
|
||||
LUALIB_API int luaL_checkoption(lua_State* L, int narg, const char* def, const char* const lst[]);
|
||||
|
||||
LUALIB_API const char* luaL_tolstring(lua_State* L, int idx, size_t* len);
|
||||
|
||||
LUALIB_API lua_State* luaL_newstate(void);
|
||||
|
||||
LUALIB_API const char* luaL_findtable(lua_State* L, int idx, const char* fname, int szhint);
|
||||
|
||||
/*
|
||||
** ===============================================================
|
||||
** some useful macros
|
||||
** ===============================================================
|
||||
*/
|
||||
|
||||
#define luaL_argcheck(L, cond, arg, extramsg) ((void)((cond) ? (void)0 : luaL_argerror(L, arg, extramsg)))
|
||||
#define luaL_argexpected(L, cond, arg, tname) ((void)((cond) ? (void)0 : luaL_typeerror(L, arg, tname)))
|
||||
|
||||
#define luaL_checkstring(L, n) (luaL_checklstring(L, (n), NULL))
|
||||
#define luaL_optstring(L, n, d) (luaL_optlstring(L, (n), (d), NULL))
|
||||
|
||||
#define luaL_typename(L, i) lua_typename(L, lua_type(L, (i)))
|
||||
|
||||
#define luaL_getmetatable(L, n) (lua_getfield(L, LUA_REGISTRYINDEX, (n)))
|
||||
|
||||
#define luaL_opt(L, f, n, d) (lua_isnoneornil(L, (n)) ? (d) : f(L, (n)))
|
||||
|
||||
/* generic buffer manipulation */
|
||||
|
||||
struct luaL_Buffer
|
||||
{
|
||||
char* p; // current position in buffer
|
||||
char* end; // end of the current buffer
|
||||
lua_State* L;
|
||||
struct TString* storage;
|
||||
char buffer[LUA_BUFFERSIZE];
|
||||
};
|
||||
|
||||
// when internal buffer storage is exhaused, a mutable string value 'storage' will be placed on the stack
|
||||
// in general, functions expect the mutable string buffer to be placed on top of the stack (top-1)
|
||||
// with the exception of luaL_addvalue that expects the value at the top and string buffer further away (top-2)
|
||||
// functions that accept a 'boxloc' support string buffer placement at any location in the stack
|
||||
// all the buffer users we have in Luau match this pattern, but it's something to keep in mind for new uses of buffers
|
||||
|
||||
#define luaL_addchar(B, c) ((void)((B)->p < (B)->end || luaL_extendbuffer(B, 1, -1)), (*(B)->p++ = (char)(c)))
|
||||
#define luaL_addstring(B, s) luaL_addlstring(B, s, strlen(s))
|
||||
|
||||
LUALIB_API void luaL_buffinit(lua_State* L, luaL_Buffer* B);
|
||||
LUALIB_API char* luaL_buffinitsize(lua_State* L, luaL_Buffer* B, size_t size);
|
||||
LUALIB_API char* luaL_extendbuffer(luaL_Buffer* B, size_t additionalsize, int boxloc);
|
||||
LUALIB_API void luaL_reservebuffer(luaL_Buffer* B, size_t size, int boxloc);
|
||||
LUALIB_API void luaL_addlstring(luaL_Buffer* B, const char* s, size_t l);
|
||||
LUALIB_API void luaL_addvalue(luaL_Buffer* B);
|
||||
LUALIB_API void luaL_pushresult(luaL_Buffer* B);
|
||||
LUALIB_API void luaL_pushresultsize(luaL_Buffer* B, size_t size);
|
||||
|
||||
/* builtin libraries */
|
||||
LUALIB_API int luaopen_base(lua_State* L);
|
||||
|
||||
#define LUA_COLIBNAME "coroutine"
|
||||
LUALIB_API int luaopen_coroutine(lua_State* L);
|
||||
|
||||
#define LUA_TABLIBNAME "table"
|
||||
LUALIB_API int luaopen_table(lua_State* L);
|
||||
|
||||
#define LUA_OSLIBNAME "os"
|
||||
LUALIB_API int luaopen_os(lua_State* L);
|
||||
|
||||
#define LUA_STRLIBNAME "string"
|
||||
LUALIB_API int luaopen_string(lua_State* L);
|
||||
|
||||
#define LUA_BITLIBNAME "bit32"
|
||||
LUALIB_API int luaopen_bit32(lua_State* L);
|
||||
|
||||
#define LUA_UTF8LIBNAME "utf8"
|
||||
LUALIB_API int luaopen_utf8(lua_State* L);
|
||||
|
||||
#define LUA_MATHLIBNAME "math"
|
||||
LUALIB_API int luaopen_math(lua_State* L);
|
||||
|
||||
#define LUA_DBLIBNAME "debug"
|
||||
LUALIB_API int luaopen_debug(lua_State* L);
|
||||
|
||||
/* open all builtin libraries */
|
||||
LUALIB_API void luaL_openlibs(lua_State* L);
|
||||
|
||||
/* sandbox libraries and globals */
|
||||
LUALIB_API void luaL_sandbox(lua_State* L);
|
||||
LUALIB_API void luaL_sandboxthread(lua_State* L);
|
1273
VM/src/lapi.cpp
Normal file
1273
VM/src/lapi.cpp
Normal file
File diff suppressed because it is too large
Load diff
8
VM/src/lapi.h
Normal file
8
VM/src/lapi.h
Normal file
|
@ -0,0 +1,8 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "lobject.h"
|
||||
|
||||
LUAI_FUNC const TValue* luaA_toobject(lua_State* L, int idx);
|
||||
LUAI_FUNC void luaA_pushobject(lua_State* L, const TValue* o);
|
477
VM/src/laux.cpp
Normal file
477
VM/src/laux.cpp
Normal file
|
@ -0,0 +1,477 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||
#include "lualib.h"
|
||||
|
||||
#include "lobject.h"
|
||||
#include "lstate.h"
|
||||
#include "lstring.h"
|
||||
#include "lapi.h"
|
||||
#include "lgc.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* convert a stack index to positive */
|
||||
#define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : lua_gettop(L) + (i) + 1)
|
||||
|
||||
/*
|
||||
** {======================================================
|
||||
** Error-report functions
|
||||
** =======================================================
|
||||
*/
|
||||
|
||||
static const char* currfuncname(lua_State* L)
|
||||
{
|
||||
Closure* cl = L->ci > L->base_ci ? curr_func(L) : NULL;
|
||||
const char* debugname = cl && cl->isC ? cl->c.debugname + 0 : NULL;
|
||||
|
||||
if (debugname && strcmp(debugname, "__namecall") == 0)
|
||||
return L->namecall ? getstr(L->namecall) : NULL;
|
||||
else
|
||||
return debugname;
|
||||
}
|
||||
|
||||
LUALIB_API l_noret luaL_argerrorL(lua_State* L, int narg, const char* extramsg)
|
||||
{
|
||||
const char* fname = currfuncname(L);
|
||||
|
||||
if (fname)
|
||||
luaL_error(L, "invalid argument #%d to '%s' (%s)", narg, fname, extramsg);
|
||||
else
|
||||
luaL_error(L, "invalid argument #%d (%s)", narg, extramsg);
|
||||
}
|
||||
|
||||
LUALIB_API l_noret luaL_typeerrorL(lua_State* L, int narg, const char* tname)
|
||||
{
|
||||
const char* fname = currfuncname(L);
|
||||
const TValue* obj = luaA_toobject(L, narg);
|
||||
|
||||
if (obj)
|
||||
{
|
||||
if (fname)
|
||||
luaL_error(L, "invalid argument #%d to '%s' (%s expected, got %s)", narg, fname, tname, luaT_objtypename(L, obj));
|
||||
else
|
||||
luaL_error(L, "invalid argument #%d (%s expected, got %s)", narg, tname, luaT_objtypename(L, obj));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (fname)
|
||||
luaL_error(L, "missing argument #%d to '%s' (%s expected)", narg, fname, tname);
|
||||
else
|
||||
luaL_error(L, "missing argument #%d (%s expected)", narg, tname);
|
||||
}
|
||||
}
|
||||
|
||||
static l_noret tag_error(lua_State* L, int narg, int tag)
|
||||
{
|
||||
luaL_typeerrorL(L, narg, lua_typename(L, tag));
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_where(lua_State* L, int level)
|
||||
{
|
||||
lua_Debug ar;
|
||||
if (lua_getinfo(L, level, "sl", &ar) && ar.currentline > 0)
|
||||
{
|
||||
lua_pushfstring(L, "%s:%d: ", ar.short_src, ar.currentline);
|
||||
return;
|
||||
}
|
||||
lua_pushliteral(L, ""); /* else, no information available... */
|
||||
}
|
||||
|
||||
LUALIB_API l_noret luaL_errorL(lua_State* L, const char* fmt, ...)
|
||||
{
|
||||
va_list argp;
|
||||
va_start(argp, fmt);
|
||||
luaL_where(L, 1);
|
||||
lua_pushvfstring(L, fmt, argp);
|
||||
va_end(argp);
|
||||
lua_concat(L, 2);
|
||||
lua_error(L);
|
||||
}
|
||||
|
||||
/* }====================================================== */
|
||||
|
||||
LUALIB_API int luaL_checkoption(lua_State* L, int narg, const char* def, const char* const lst[])
|
||||
{
|
||||
const char* name = (def) ? luaL_optstring(L, narg, def) : luaL_checkstring(L, narg);
|
||||
int i;
|
||||
for (i = 0; lst[i]; i++)
|
||||
if (strcmp(lst[i], name) == 0)
|
||||
return i;
|
||||
const char* msg = lua_pushfstring(L, "invalid option '%s'", name);
|
||||
luaL_argerrorL(L, narg, msg);
|
||||
}
|
||||
|
||||
LUALIB_API int luaL_newmetatable(lua_State* L, const char* tname)
|
||||
{
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get registry.name */
|
||||
if (!lua_isnil(L, -1)) /* name already in use? */
|
||||
return 0; /* leave previous value on top, but return 0 */
|
||||
lua_pop(L, 1);
|
||||
lua_newtable(L); /* create metatable */
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setfield(L, LUA_REGISTRYINDEX, tname); /* registry.name = metatable */
|
||||
return 1;
|
||||
}
|
||||
|
||||
LUALIB_API void* luaL_checkudata(lua_State* L, int ud, const char* tname)
|
||||
{
|
||||
void* p = lua_touserdata(L, ud);
|
||||
if (p != NULL)
|
||||
{ /* value is a userdata? */
|
||||
if (lua_getmetatable(L, ud))
|
||||
{ /* does it have a metatable? */
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get correct metatable */
|
||||
if (lua_rawequal(L, -1, -2))
|
||||
{ /* does it have the correct mt? */
|
||||
lua_pop(L, 2); /* remove both metatables */
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
luaL_typeerrorL(L, ud, tname); /* else error */
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_checkstack(lua_State* L, int space, const char* mes)
|
||||
{
|
||||
if (!lua_checkstack(L, space))
|
||||
luaL_error(L, "stack overflow (%s)", mes);
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_checktype(lua_State* L, int narg, int t)
|
||||
{
|
||||
if (lua_type(L, narg) != t)
|
||||
tag_error(L, narg, t);
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_checkany(lua_State* L, int narg)
|
||||
{
|
||||
if (lua_type(L, narg) == LUA_TNONE)
|
||||
luaL_error(L, "missing argument #%d", narg);
|
||||
}
|
||||
|
||||
LUALIB_API const char* luaL_checklstring(lua_State* L, int narg, size_t* len)
|
||||
{
|
||||
const char* s = lua_tolstring(L, narg, len);
|
||||
if (!s)
|
||||
tag_error(L, narg, LUA_TSTRING);
|
||||
return s;
|
||||
}
|
||||
|
||||
LUALIB_API const char* luaL_optlstring(lua_State* L, int narg, const char* def, size_t* len)
|
||||
{
|
||||
if (lua_isnoneornil(L, narg))
|
||||
{
|
||||
if (len)
|
||||
*len = (def ? strlen(def) : 0);
|
||||
return def;
|
||||
}
|
||||
else
|
||||
return luaL_checklstring(L, narg, len);
|
||||
}
|
||||
|
||||
LUALIB_API double luaL_checknumber(lua_State* L, int narg)
|
||||
{
|
||||
int isnum;
|
||||
double d = lua_tonumberx(L, narg, &isnum);
|
||||
if (!isnum)
|
||||
tag_error(L, narg, LUA_TNUMBER);
|
||||
return d;
|
||||
}
|
||||
|
||||
LUALIB_API double luaL_optnumber(lua_State* L, int narg, double def)
|
||||
{
|
||||
return luaL_opt(L, luaL_checknumber, narg, def);
|
||||
}
|
||||
|
||||
LUALIB_API int luaL_checkinteger(lua_State* L, int narg)
|
||||
{
|
||||
int isnum;
|
||||
int d = lua_tointegerx(L, narg, &isnum);
|
||||
if (!isnum)
|
||||
tag_error(L, narg, LUA_TNUMBER);
|
||||
return d;
|
||||
}
|
||||
|
||||
LUALIB_API int luaL_optinteger(lua_State* L, int narg, int def)
|
||||
{
|
||||
return luaL_opt(L, luaL_checkinteger, narg, def);
|
||||
}
|
||||
|
||||
LUALIB_API unsigned luaL_checkunsigned(lua_State* L, int narg)
|
||||
{
|
||||
int isnum;
|
||||
unsigned d = lua_tounsignedx(L, narg, &isnum);
|
||||
if (!isnum)
|
||||
tag_error(L, narg, LUA_TNUMBER);
|
||||
return d;
|
||||
}
|
||||
|
||||
LUALIB_API unsigned luaL_optunsigned(lua_State* L, int narg, unsigned def)
|
||||
{
|
||||
return luaL_opt(L, luaL_checkunsigned, narg, def);
|
||||
}
|
||||
|
||||
LUALIB_API int luaL_getmetafield(lua_State* L, int obj, const char* event)
|
||||
{
|
||||
if (!lua_getmetatable(L, obj)) /* no metatable? */
|
||||
return 0;
|
||||
lua_pushstring(L, event);
|
||||
lua_rawget(L, -2);
|
||||
if (lua_isnil(L, -1))
|
||||
{
|
||||
lua_pop(L, 2); /* remove metatable and metafield */
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
lua_remove(L, -2); /* remove only metatable */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
LUALIB_API int luaL_callmeta(lua_State* L, int obj, const char* event)
|
||||
{
|
||||
obj = abs_index(L, obj);
|
||||
if (!luaL_getmetafield(L, obj, event)) /* no metafield? */
|
||||
return 0;
|
||||
lua_pushvalue(L, obj);
|
||||
lua_call(L, 1, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int libsize(const luaL_Reg* l)
|
||||
{
|
||||
int size = 0;
|
||||
for (; l->name; l++)
|
||||
size++;
|
||||
return size;
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_register(lua_State* L, const char* libname, const luaL_Reg* l)
|
||||
{
|
||||
if (libname)
|
||||
{
|
||||
int size = libsize(l);
|
||||
/* check whether lib already exists */
|
||||
luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 1);
|
||||
lua_getfield(L, -1, libname); /* get _LOADED[libname] */
|
||||
if (!lua_istable(L, -1))
|
||||
{ /* not found? */
|
||||
lua_pop(L, 1); /* remove previous result */
|
||||
/* try global variable (and create one if it does not exist) */
|
||||
if (luaL_findtable(L, LUA_GLOBALSINDEX, libname, size) != NULL)
|
||||
luaL_error(L, "name conflict for module '%s'", libname);
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setfield(L, -3, libname); /* _LOADED[libname] = new table */
|
||||
}
|
||||
lua_remove(L, -2); /* remove _LOADED table */
|
||||
}
|
||||
for (; l->name; l++)
|
||||
{
|
||||
lua_pushcfunction(L, l->func, l->name);
|
||||
lua_setfield(L, -2, l->name);
|
||||
}
|
||||
}
|
||||
|
||||
LUALIB_API const char* luaL_findtable(lua_State* L, int idx, const char* fname, int szhint)
|
||||
{
|
||||
const char* e;
|
||||
lua_pushvalue(L, idx);
|
||||
do
|
||||
{
|
||||
e = strchr(fname, '.');
|
||||
if (e == NULL)
|
||||
e = fname + strlen(fname);
|
||||
lua_pushlstring(L, fname, e - fname);
|
||||
lua_rawget(L, -2);
|
||||
if (lua_isnil(L, -1))
|
||||
{ /* no such field? */
|
||||
lua_pop(L, 1); /* remove this nil */
|
||||
lua_createtable(L, 0, (*e == '.' ? 1 : szhint)); /* new table for field */
|
||||
lua_pushlstring(L, fname, e - fname);
|
||||
lua_pushvalue(L, -2);
|
||||
lua_settable(L, -4); /* set new table into field */
|
||||
}
|
||||
else if (!lua_istable(L, -1))
|
||||
{ /* field has a non-table value? */
|
||||
lua_pop(L, 2); /* remove table and value */
|
||||
return fname; /* return problematic part of the name */
|
||||
}
|
||||
lua_remove(L, -2); /* remove previous table */
|
||||
fname = e + 1;
|
||||
} while (*e == '.');
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
** {======================================================
|
||||
** Generic Buffer manipulation
|
||||
** =======================================================
|
||||
*/
|
||||
|
||||
static size_t getnextbuffersize(lua_State* L, size_t currentsize, size_t desiredsize)
|
||||
{
|
||||
size_t newsize = currentsize + currentsize / 2;
|
||||
|
||||
// check for size oveflow
|
||||
if (SIZE_MAX - desiredsize < currentsize)
|
||||
luaL_error(L, "buffer too large");
|
||||
|
||||
// growth factor might not be enough to satisfy the desired size
|
||||
if (newsize < desiredsize)
|
||||
newsize = desiredsize;
|
||||
|
||||
return newsize;
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_buffinit(lua_State* L, luaL_Buffer* B)
|
||||
{
|
||||
// start with an internal buffer
|
||||
B->p = B->buffer;
|
||||
B->end = B->p + LUA_BUFFERSIZE;
|
||||
|
||||
B->L = L;
|
||||
B->storage = nullptr;
|
||||
}
|
||||
|
||||
LUALIB_API char* luaL_buffinitsize(lua_State* L, luaL_Buffer* B, size_t size)
|
||||
{
|
||||
luaL_buffinit(L, B);
|
||||
luaL_reservebuffer(B, size, -1);
|
||||
return B->p;
|
||||
}
|
||||
|
||||
LUALIB_API char* luaL_extendbuffer(luaL_Buffer* B, size_t additionalsize, int boxloc)
|
||||
{
|
||||
lua_State* L = B->L;
|
||||
|
||||
if (B->storage)
|
||||
LUAU_ASSERT(B->storage == tsvalue(L->top + boxloc));
|
||||
|
||||
char* base = B->storage ? B->storage->data : B->buffer;
|
||||
|
||||
size_t capacity = B->end - base;
|
||||
size_t nextsize = getnextbuffersize(B->L, capacity, capacity + additionalsize);
|
||||
|
||||
TString* newStorage = luaS_bufstart(L, nextsize);
|
||||
|
||||
memcpy(newStorage->data, base, B->p - base);
|
||||
|
||||
// place the string storage at the expected position in the stack
|
||||
if (base == B->buffer)
|
||||
{
|
||||
lua_pushnil(L);
|
||||
lua_insert(L, boxloc);
|
||||
}
|
||||
|
||||
setsvalue2s(L, L->top + boxloc, newStorage);
|
||||
B->p = newStorage->data + (B->p - base);
|
||||
B->end = newStorage->data + nextsize;
|
||||
B->storage = newStorage;
|
||||
|
||||
return B->p;
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_reservebuffer(luaL_Buffer* B, size_t size, int boxloc)
|
||||
{
|
||||
if (size_t(B->end - B->p) < size)
|
||||
luaL_extendbuffer(B, size - (B->end - B->p), boxloc);
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_addlstring(luaL_Buffer* B, const char* s, size_t len)
|
||||
{
|
||||
if (size_t(B->end - B->p) < len)
|
||||
luaL_extendbuffer(B, len - (B->end - B->p), -1);
|
||||
|
||||
memcpy(B->p, s, len);
|
||||
B->p += len;
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_addvalue(luaL_Buffer* B)
|
||||
{
|
||||
lua_State* L = B->L;
|
||||
|
||||
size_t vl;
|
||||
if (const char* s = lua_tolstring(L, -1, &vl))
|
||||
{
|
||||
if (size_t(B->end - B->p) < vl)
|
||||
luaL_extendbuffer(B, vl - (B->end - B->p), -2);
|
||||
|
||||
memcpy(B->p, s, vl);
|
||||
B->p += vl;
|
||||
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_pushresult(luaL_Buffer* B)
|
||||
{
|
||||
lua_State* L = B->L;
|
||||
|
||||
if (TString* storage = B->storage)
|
||||
{
|
||||
luaC_checkGC(L);
|
||||
|
||||
// if we finished just at the end of the string buffer, we can convert it to a mutable stirng without a copy
|
||||
if (B->p == B->end)
|
||||
{
|
||||
setsvalue2s(L, L->top - 1, luaS_buffinish(L, storage));
|
||||
}
|
||||
else
|
||||
{
|
||||
setsvalue2s(L, L->top - 1, luaS_newlstr(L, storage->data, B->p - storage->data));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lua_pushlstring(L, B->buffer, B->p - B->buffer);
|
||||
}
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_pushresultsize(luaL_Buffer* B, size_t size)
|
||||
{
|
||||
B->p += size;
|
||||
luaL_pushresult(B);
|
||||
}
|
||||
|
||||
/* }====================================================== */
|
||||
|
||||
LUALIB_API const char* luaL_tolstring(lua_State* L, int idx, size_t* len)
|
||||
{
|
||||
if (luaL_callmeta(L, idx, "__tostring")) /* is there a metafield? */
|
||||
{
|
||||
if (!lua_isstring(L, -1))
|
||||
luaL_error(L, "'__tostring' must return a string");
|
||||
return lua_tolstring(L, -1, len);
|
||||
}
|
||||
|
||||
switch (lua_type(L, idx))
|
||||
{
|
||||
case LUA_TNUMBER:
|
||||
lua_pushstring(L, lua_tostring(L, idx));
|
||||
break;
|
||||
case LUA_TSTRING:
|
||||
lua_pushvalue(L, idx);
|
||||
break;
|
||||
case LUA_TBOOLEAN:
|
||||
lua_pushstring(L, (lua_toboolean(L, idx) ? "true" : "false"));
|
||||
break;
|
||||
case LUA_TNIL:
|
||||
lua_pushliteral(L, "nil");
|
||||
break;
|
||||
case LUA_TVECTOR:
|
||||
{
|
||||
const float* v = lua_tovector(L, idx);
|
||||
lua_pushfstring(L, LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT, v[0], v[1], v[2]);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
const void* ptr = lua_topointer(L, idx);
|
||||
unsigned long long enc = lua_encodepointer(L, uintptr_t(ptr));
|
||||
lua_pushfstring(L, "%s: 0x%016llx", luaL_typename(L, idx), enc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return lua_tolstring(L, -1, len);
|
||||
}
|
466
VM/src/lbaselib.cpp
Normal file
466
VM/src/lbaselib.cpp
Normal file
|
@ -0,0 +1,466 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||
#include "lualib.h"
|
||||
|
||||
#include "lstate.h"
|
||||
#include "lapi.h"
|
||||
#include "ldo.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static void writestring(const char* s, size_t l)
|
||||
{
|
||||
fwrite(s, 1, l, stdout);
|
||||
}
|
||||
|
||||
static int luaB_print(lua_State* L)
|
||||
{
|
||||
int n = lua_gettop(L); /* number of arguments */
|
||||
for (int i = 1; i <= n; i++)
|
||||
{
|
||||
size_t l;
|
||||
const char* s = luaL_tolstring(L, i, &l); /* convert to string using __tostring et al */
|
||||
if (i > 1)
|
||||
writestring("\t", 1);
|
||||
writestring(s, l);
|
||||
lua_pop(L, 1); /* pop result */
|
||||
}
|
||||
writestring("\n", 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int luaB_tonumber(lua_State* L)
|
||||
{
|
||||
int base = luaL_optinteger(L, 2, 10);
|
||||
if (base == 10)
|
||||
{ /* standard conversion */
|
||||
luaL_checkany(L, 1);
|
||||
if (lua_isnumber(L, 1))
|
||||
{
|
||||
lua_pushnumber(L, lua_tonumber(L, 1));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* s1 = luaL_checkstring(L, 1);
|
||||
luaL_argcheck(L, 2 <= base && base <= 36, 2, "base out of range");
|
||||
char* s2;
|
||||
unsigned long long n;
|
||||
n = strtoull(s1, &s2, base);
|
||||
if (s1 != s2)
|
||||
{ /* at least one valid digit? */
|
||||
while (isspace((unsigned char)(*s2)))
|
||||
s2++; /* skip trailing spaces */
|
||||
if (*s2 == '\0')
|
||||
{ /* no invalid trailing characters? */
|
||||
lua_pushnumber(L, (double)n);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
lua_pushnil(L); /* else not a number */
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int luaB_error(lua_State* L)
|
||||
{
|
||||
int level = luaL_optinteger(L, 2, 1);
|
||||
lua_settop(L, 1);
|
||||
if (lua_isstring(L, 1) && level > 0)
|
||||
{ /* add extra information? */
|
||||
luaL_where(L, level);
|
||||
lua_pushvalue(L, 1);
|
||||
lua_concat(L, 2);
|
||||
}
|
||||
lua_error(L);
|
||||
}
|
||||
|
||||
static int luaB_getmetatable(lua_State* L)
|
||||
{
|
||||
luaL_checkany(L, 1);
|
||||
if (!lua_getmetatable(L, 1))
|
||||
{
|
||||
lua_pushnil(L);
|
||||
return 1; /* no metatable */
|
||||
}
|
||||
luaL_getmetafield(L, 1, "__metatable");
|
||||
return 1; /* returns either __metatable field (if present) or metatable */
|
||||
}
|
||||
|
||||
static int luaB_setmetatable(lua_State* L)
|
||||
{
|
||||
int t = lua_type(L, 2);
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
luaL_argexpected(L, t == LUA_TNIL || t == LUA_TTABLE, 2, "nil or table");
|
||||
if (luaL_getmetafield(L, 1, "__metatable"))
|
||||
luaL_error(L, "cannot change a protected metatable");
|
||||
lua_settop(L, 2);
|
||||
lua_setmetatable(L, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void getfunc(lua_State* L, int opt)
|
||||
{
|
||||
if (lua_isfunction(L, 1))
|
||||
lua_pushvalue(L, 1);
|
||||
else
|
||||
{
|
||||
lua_Debug ar;
|
||||
int level = opt ? luaL_optinteger(L, 1, 1) : luaL_checkinteger(L, 1);
|
||||
luaL_argcheck(L, level >= 0, 1, "level must be non-negative");
|
||||
if (lua_getinfo(L, level, "f", &ar) == 0)
|
||||
luaL_argerror(L, 1, "invalid level");
|
||||
if (lua_isnil(L, -1))
|
||||
luaL_error(L, "no function environment for tail call at level %d", level);
|
||||
}
|
||||
}
|
||||
|
||||
static int luaB_getfenv(lua_State* L)
|
||||
{
|
||||
getfunc(L, 1);
|
||||
if (lua_iscfunction(L, -1)) /* is a C function? */
|
||||
lua_pushvalue(L, LUA_GLOBALSINDEX); /* return the thread's global env. */
|
||||
else
|
||||
lua_getfenv(L, -1);
|
||||
lua_setsafeenv(L, -1, false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int luaB_setfenv(lua_State* L)
|
||||
{
|
||||
luaL_checktype(L, 2, LUA_TTABLE);
|
||||
getfunc(L, 0);
|
||||
lua_pushvalue(L, 2);
|
||||
lua_setsafeenv(L, -1, false);
|
||||
if (lua_isnumber(L, 1) && lua_tonumber(L, 1) == 0)
|
||||
{
|
||||
/* change environment of current thread */
|
||||
lua_pushthread(L);
|
||||
lua_insert(L, -2);
|
||||
lua_setfenv(L, -2);
|
||||
return 0;
|
||||
}
|
||||
else if (lua_iscfunction(L, -2) || lua_setfenv(L, -2) == 0)
|
||||
luaL_error(L, "'setfenv' cannot change environment of given object");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int luaB_rawequal(lua_State* L)
|
||||
{
|
||||
luaL_checkany(L, 1);
|
||||
luaL_checkany(L, 2);
|
||||
lua_pushboolean(L, lua_rawequal(L, 1, 2));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int luaB_rawget(lua_State* L)
|
||||
{
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
luaL_checkany(L, 2);
|
||||
lua_settop(L, 2);
|
||||
lua_rawget(L, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int luaB_rawset(lua_State* L)
|
||||
{
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
luaL_checkany(L, 2);
|
||||
luaL_checkany(L, 3);
|
||||
lua_settop(L, 3);
|
||||
lua_rawset(L, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int luaB_gcinfo(lua_State* L)
|
||||
{
|
||||
lua_pushinteger(L, lua_gc(L, LUA_GCCOUNT, 0));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int luaB_type(lua_State* L)
|
||||
{
|
||||
luaL_checkany(L, 1);
|
||||
lua_pushstring(L, luaL_typename(L, 1));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int luaB_typeof(lua_State* L)
|
||||
{
|
||||
luaL_checkany(L, 1);
|
||||
const TValue* obj = luaA_toobject(L, 1);
|
||||
lua_pushstring(L, luaT_objtypename(L, obj));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int luaB_next(lua_State* L)
|
||||
{
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
lua_settop(L, 2); /* create a 2nd argument if there isn't one */
|
||||
if (lua_next(L, 1))
|
||||
return 2;
|
||||
else
|
||||
{
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int luaB_pairs(lua_State* L)
|
||||
{
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
lua_pushvalue(L, lua_upvalueindex(1)); /* return generator, */
|
||||
lua_pushvalue(L, 1); /* state, */
|
||||
lua_pushnil(L); /* and initial value */
|
||||
return 3;
|
||||
}
|
||||
|
||||
int luaB_inext(lua_State* L)
|
||||
{
|
||||
int i = luaL_checkinteger(L, 2);
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
i++; /* next value */
|
||||
lua_pushinteger(L, i);
|
||||
lua_rawgeti(L, 1, i);
|
||||
return (lua_isnil(L, -1)) ? 0 : 2;
|
||||
}
|
||||
|
||||
static int luaB_ipairs(lua_State* L)
|
||||
{
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
lua_pushvalue(L, lua_upvalueindex(1)); /* return generator, */
|
||||
lua_pushvalue(L, 1); /* state, */
|
||||
lua_pushinteger(L, 0); /* and initial value */
|
||||
return 3;
|
||||
}
|
||||
|
||||
static int luaB_assert(lua_State* L)
|
||||
{
|
||||
luaL_checkany(L, 1);
|
||||
if (!lua_toboolean(L, 1))
|
||||
luaL_error(L, "%s", luaL_optstring(L, 2, "assertion failed!"));
|
||||
return lua_gettop(L);
|
||||
}
|
||||
|
||||
static int luaB_select(lua_State* L)
|
||||
{
|
||||
int n = lua_gettop(L);
|
||||
if (lua_type(L, 1) == LUA_TSTRING && *lua_tostring(L, 1) == '#')
|
||||
{
|
||||
lua_pushinteger(L, n - 1);
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
int i = luaL_checkinteger(L, 1);
|
||||
if (i < 0)
|
||||
i = n + i;
|
||||
else if (i > n)
|
||||
i = n;
|
||||
luaL_argcheck(L, 1 <= i, 1, "index out of range");
|
||||
return n - i;
|
||||
}
|
||||
}
|
||||
|
||||
static void luaB_pcallrun(lua_State* L, void* ud)
|
||||
{
|
||||
StkId func = (StkId)ud;
|
||||
|
||||
luaD_call(L, func, LUA_MULTRET);
|
||||
}
|
||||
|
||||
static int luaB_pcally(lua_State* L)
|
||||
{
|
||||
luaL_checkany(L, 1);
|
||||
|
||||
StkId func = L->base;
|
||||
|
||||
// any errors from this point on are handled by continuation
|
||||
L->ci->flags |= LUA_CALLINFO_HANDLE;
|
||||
|
||||
// maintain yieldable invariant (baseCcalls <= nCcalls)
|
||||
L->baseCcalls++;
|
||||
int status = luaD_pcall(L, luaB_pcallrun, func, savestack(L, func), 0);
|
||||
L->baseCcalls--;
|
||||
|
||||
// necessary to accomodate functions that return lots of values
|
||||
expandstacklimit(L, L->top);
|
||||
|
||||
// yielding means we need to propagate yield; resume will call continuation function later
|
||||
if (status == 0 && (L->status == LUA_YIELD || L->status == LUA_BREAK))
|
||||
return -1; // -1 is a marker for yielding from C
|
||||
|
||||
// immediate return (error or success)
|
||||
lua_rawcheckstack(L, 1);
|
||||
lua_pushboolean(L, status == 0);
|
||||
lua_insert(L, 1);
|
||||
return lua_gettop(L); // return status + all results
|
||||
}
|
||||
|
||||
static int luaB_pcallcont(lua_State* L, int status)
|
||||
{
|
||||
if (status == 0)
|
||||
{
|
||||
lua_rawcheckstack(L, 1);
|
||||
lua_pushboolean(L, true);
|
||||
lua_insert(L, 1); // insert status before all results
|
||||
return lua_gettop(L);
|
||||
}
|
||||
else
|
||||
{
|
||||
lua_rawcheckstack(L, 1);
|
||||
lua_pushboolean(L, false);
|
||||
lua_insert(L, -2); // insert status before error object
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
static int luaB_xpcally(lua_State* L)
|
||||
{
|
||||
luaL_checktype(L, 2, LUA_TFUNCTION);
|
||||
|
||||
/* swap function & error function */
|
||||
lua_pushvalue(L, 1);
|
||||
lua_pushvalue(L, 2);
|
||||
lua_replace(L, 1);
|
||||
lua_replace(L, 2);
|
||||
/* at this point the stack looks like err, f, args */
|
||||
|
||||
// any errors from this point on are handled by continuation
|
||||
L->ci->flags |= LUA_CALLINFO_HANDLE;
|
||||
|
||||
StkId errf = L->base;
|
||||
StkId func = L->base + 1;
|
||||
|
||||
// maintain yieldable invariant (baseCcalls <= nCcalls)
|
||||
L->baseCcalls++;
|
||||
int status = luaD_pcall(L, luaB_pcallrun, func, savestack(L, func), savestack(L, errf));
|
||||
L->baseCcalls--;
|
||||
|
||||
// necessary to accomodate functions that return lots of values
|
||||
expandstacklimit(L, L->top);
|
||||
|
||||
// yielding means we need to propagate yield; resume will call continuation function later
|
||||
if (status == 0 && (L->status == LUA_YIELD || L->status == LUA_BREAK))
|
||||
return -1; // -1 is a marker for yielding from C
|
||||
|
||||
// immediate return (error or success)
|
||||
lua_rawcheckstack(L, 1);
|
||||
lua_pushboolean(L, status == 0);
|
||||
lua_replace(L, 1); // replace error function with status
|
||||
return lua_gettop(L); // return status + all results
|
||||
}
|
||||
|
||||
static void luaB_xpcallerr(lua_State* L, void* ud)
|
||||
{
|
||||
StkId func = (StkId)ud;
|
||||
|
||||
luaD_call(L, func, 1);
|
||||
}
|
||||
|
||||
static int luaB_xpcallcont(lua_State* L, int status)
|
||||
{
|
||||
if (status == 0)
|
||||
{
|
||||
lua_rawcheckstack(L, 1);
|
||||
lua_pushboolean(L, true);
|
||||
lua_replace(L, 1); // replace error function with status
|
||||
return lua_gettop(L); /* return status + all results */
|
||||
}
|
||||
else
|
||||
{
|
||||
lua_rawcheckstack(L, 3);
|
||||
lua_pushboolean(L, false);
|
||||
lua_pushvalue(L, 1); // push error function on top of the stack
|
||||
lua_pushvalue(L, -3); // push error object (that was on top of the stack before)
|
||||
|
||||
StkId res = L->top - 3;
|
||||
StkId errf = L->top - 2;
|
||||
|
||||
// note: we pass res as errfunc as a short cut; if errf generates an error, we'll try to execute res (boolean) and fail
|
||||
luaD_pcall(L, luaB_xpcallerr, errf, savestack(L, errf), savestack(L, res));
|
||||
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
static int luaB_tostring(lua_State* L)
|
||||
{
|
||||
luaL_checkany(L, 1);
|
||||
luaL_tolstring(L, 1, NULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int luaB_newproxy(lua_State* L)
|
||||
{
|
||||
int t = lua_type(L, 1);
|
||||
luaL_argexpected(L, t == LUA_TNONE || t == LUA_TNIL || t == LUA_TBOOLEAN, 1, "nil or boolean");
|
||||
|
||||
bool needsmt = lua_toboolean(L, 1);
|
||||
|
||||
lua_newuserdata(L, 0, 0);
|
||||
|
||||
if (needsmt)
|
||||
{
|
||||
lua_newtable(L);
|
||||
lua_setmetatable(L, -2);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const luaL_Reg base_funcs[] = {
|
||||
{"assert", luaB_assert},
|
||||
{"error", luaB_error},
|
||||
{"gcinfo", luaB_gcinfo},
|
||||
{"getfenv", luaB_getfenv},
|
||||
{"getmetatable", luaB_getmetatable},
|
||||
{"next", luaB_next},
|
||||
{"newproxy", luaB_newproxy},
|
||||
{"print", luaB_print},
|
||||
{"rawequal", luaB_rawequal},
|
||||
{"rawget", luaB_rawget},
|
||||
{"rawset", luaB_rawset},
|
||||
{"select", luaB_select},
|
||||
{"setfenv", luaB_setfenv},
|
||||
{"setmetatable", luaB_setmetatable},
|
||||
{"tonumber", luaB_tonumber},
|
||||
{"tostring", luaB_tostring},
|
||||
{"type", luaB_type},
|
||||
{"typeof", luaB_typeof},
|
||||
{NULL, NULL},
|
||||
};
|
||||
|
||||
static void auxopen(lua_State* L, const char* name, lua_CFunction f, lua_CFunction u)
|
||||
{
|
||||
lua_pushcfunction(L, u);
|
||||
lua_pushcfunction(L, f, name, 1);
|
||||
lua_setfield(L, -2, name);
|
||||
}
|
||||
|
||||
LUALIB_API int luaopen_base(lua_State* L)
|
||||
{
|
||||
/* set global _G */
|
||||
lua_pushvalue(L, LUA_GLOBALSINDEX);
|
||||
lua_setglobal(L, "_G");
|
||||
|
||||
/* open lib into global table */
|
||||
luaL_register(L, "_G", base_funcs);
|
||||
lua_pushliteral(L, "Luau");
|
||||
lua_setglobal(L, "_VERSION"); /* set global _VERSION */
|
||||
|
||||
/* `ipairs' and `pairs' need auxiliary functions as upvalues */
|
||||
auxopen(L, "ipairs", luaB_ipairs, luaB_inext);
|
||||
auxopen(L, "pairs", luaB_pairs, luaB_next);
|
||||
|
||||
lua_pushcfunction(L, luaB_pcally, "pcall", 0, luaB_pcallcont);
|
||||
lua_setfield(L, -2, "pcall");
|
||||
|
||||
lua_pushcfunction(L, luaB_xpcally, "xpcall", 0, luaB_xpcallcont);
|
||||
lua_setfield(L, -2, "xpcall");
|
||||
|
||||
return 1;
|
||||
}
|
201
VM/src/lbitlib.cpp
Normal file
201
VM/src/lbitlib.cpp
Normal file
|
@ -0,0 +1,201 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||
#include "lualib.h"
|
||||
|
||||
#include "lnumutils.h"
|
||||
|
||||
#define ALLONES ~0u
|
||||
#define NBITS int(8 * sizeof(unsigned))
|
||||
|
||||
/* macro to trim extra bits */
|
||||
#define trim(x) ((x)&ALLONES)
|
||||
|
||||
/* builds a number with 'n' ones (1 <= n <= NBITS) */
|
||||
#define mask(n) (~((ALLONES << 1) << ((n)-1)))
|
||||
|
||||
typedef unsigned b_uint;
|
||||
|
||||
static b_uint andaux(lua_State* L)
|
||||
{
|
||||
int i, n = lua_gettop(L);
|
||||
b_uint r = ~(b_uint)0;
|
||||
for (i = 1; i <= n; i++)
|
||||
r &= luaL_checkunsigned(L, i);
|
||||
return trim(r);
|
||||
}
|
||||
|
||||
static int b_and(lua_State* L)
|
||||
{
|
||||
b_uint r = andaux(L);
|
||||
lua_pushunsigned(L, r);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int b_test(lua_State* L)
|
||||
{
|
||||
b_uint r = andaux(L);
|
||||
lua_pushboolean(L, r != 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int b_or(lua_State* L)
|
||||
{
|
||||
int i, n = lua_gettop(L);
|
||||
b_uint r = 0;
|
||||
for (i = 1; i <= n; i++)
|
||||
r |= luaL_checkunsigned(L, i);
|
||||
lua_pushunsigned(L, trim(r));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int b_xor(lua_State* L)
|
||||
{
|
||||
int i, n = lua_gettop(L);
|
||||
b_uint r = 0;
|
||||
for (i = 1; i <= n; i++)
|
||||
r ^= luaL_checkunsigned(L, i);
|
||||
lua_pushunsigned(L, trim(r));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int b_not(lua_State* L)
|
||||
{
|
||||
b_uint r = ~luaL_checkunsigned(L, 1);
|
||||
lua_pushunsigned(L, trim(r));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int b_shift(lua_State* L, b_uint r, int i)
|
||||
{
|
||||
if (i < 0)
|
||||
{ /* shift right? */
|
||||
i = -i;
|
||||
r = trim(r);
|
||||
if (i >= NBITS)
|
||||
r = 0;
|
||||
else
|
||||
r >>= i;
|
||||
}
|
||||
else
|
||||
{ /* shift left */
|
||||
if (i >= NBITS)
|
||||
r = 0;
|
||||
else
|
||||
r <<= i;
|
||||
r = trim(r);
|
||||
}
|
||||
lua_pushunsigned(L, r);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int b_lshift(lua_State* L)
|
||||
{
|
||||
return b_shift(L, luaL_checkunsigned(L, 1), luaL_checkinteger(L, 2));
|
||||
}
|
||||
|
||||
static int b_rshift(lua_State* L)
|
||||
{
|
||||
return b_shift(L, luaL_checkunsigned(L, 1), -luaL_checkinteger(L, 2));
|
||||
}
|
||||
|
||||
static int b_arshift(lua_State* L)
|
||||
{
|
||||
b_uint r = luaL_checkunsigned(L, 1);
|
||||
int i = luaL_checkinteger(L, 2);
|
||||
if (i < 0 || !(r & ((b_uint)1 << (NBITS - 1))))
|
||||
return b_shift(L, r, -i);
|
||||
else
|
||||
{ /* arithmetic shift for 'negative' number */
|
||||
if (i >= NBITS)
|
||||
r = ALLONES;
|
||||
else
|
||||
r = trim((r >> i) | ~(~(b_uint)0 >> i)); /* add signal bit */
|
||||
lua_pushunsigned(L, r);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int b_rot(lua_State* L, int i)
|
||||
{
|
||||
b_uint r = luaL_checkunsigned(L, 1);
|
||||
i &= (NBITS - 1); /* i = i % NBITS */
|
||||
r = trim(r);
|
||||
if (i != 0) /* avoid undefined shift of NBITS when i == 0 */
|
||||
r = (r << i) | (r >> (NBITS - i));
|
||||
lua_pushunsigned(L, trim(r));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int b_lrot(lua_State* L)
|
||||
{
|
||||
return b_rot(L, luaL_checkinteger(L, 2));
|
||||
}
|
||||
|
||||
static int b_rrot(lua_State* L)
|
||||
{
|
||||
return b_rot(L, -luaL_checkinteger(L, 2));
|
||||
}
|
||||
|
||||
/*
|
||||
** get field and width arguments for field-manipulation functions,
|
||||
** checking whether they are valid.
|
||||
** ('luaL_error' called without 'return' to avoid later warnings about
|
||||
** 'width' being used uninitialized.)
|
||||
*/
|
||||
static int fieldargs(lua_State* L, int farg, int* width)
|
||||
{
|
||||
int f = luaL_checkinteger(L, farg);
|
||||
int w = luaL_optinteger(L, farg + 1, 1);
|
||||
luaL_argcheck(L, 0 <= f, farg, "field cannot be negative");
|
||||
luaL_argcheck(L, 0 < w, farg + 1, "width must be positive");
|
||||
if (f + w > NBITS)
|
||||
luaL_error(L, "trying to access non-existent bits");
|
||||
*width = w;
|
||||
return f;
|
||||
}
|
||||
|
||||
static int b_extract(lua_State* L)
|
||||
{
|
||||
int w;
|
||||
b_uint r = luaL_checkunsigned(L, 1);
|
||||
int f = fieldargs(L, 2, &w);
|
||||
r = (r >> f) & mask(w);
|
||||
lua_pushunsigned(L, r);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int b_replace(lua_State* L)
|
||||
{
|
||||
int w;
|
||||
b_uint r = luaL_checkunsigned(L, 1);
|
||||
b_uint v = luaL_checkunsigned(L, 2);
|
||||
int f = fieldargs(L, 3, &w);
|
||||
int m = mask(w);
|
||||
v &= m; /* erase bits outside given width */
|
||||
r = (r & ~(m << f)) | (v << f);
|
||||
lua_pushunsigned(L, r);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const luaL_Reg bitlib[] = {
|
||||
{"arshift", b_arshift},
|
||||
{"band", b_and},
|
||||
{"bnot", b_not},
|
||||
{"bor", b_or},
|
||||
{"bxor", b_xor},
|
||||
{"btest", b_test},
|
||||
{"extract", b_extract},
|
||||
{"lrotate", b_lrot},
|
||||
{"lshift", b_lshift},
|
||||
{"replace", b_replace},
|
||||
{"rrotate", b_rrot},
|
||||
{"rshift", b_rshift},
|
||||
{NULL, NULL},
|
||||
};
|
||||
|
||||
LUALIB_API int luaopen_bit32(lua_State* L)
|
||||
{
|
||||
luaL_register(L, LUA_BITLIBNAME, bitlib);
|
||||
|
||||
return 1;
|
||||
}
|
1099
VM/src/lbuiltins.cpp
Normal file
1099
VM/src/lbuiltins.cpp
Normal file
File diff suppressed because it is too large
Load diff
9
VM/src/lbuiltins.h
Normal file
9
VM/src/lbuiltins.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "lobject.h"
|
||||
|
||||
typedef int (*luau_FastFunction)(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams);
|
||||
|
||||
extern luau_FastFunction luauF_table[256];
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue