mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
Sync to upstream/release/501 (#20)
Co-authored-by: Rodactor <rodactor@roblox.com>
This commit is contained in:
parent
12b2838de0
commit
d01addc625
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 New Issue
Block a user