Sync to upstream/release/501 (#20)
Co-authored-by: Rodactor <rodactor@roblox.com>
This commit is contained in:
parent
12b2838de0
commit
d01addc625
336 changed files with 118239 additions and 0 deletions
25
.clang-format
Normal file
25
.clang-format
Normal file
|
@ -0,0 +1,25 @@
|
|||
BasedOnStyle: LLVM
|
||||
|
||||
AccessModifierOffset: -4
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
AllowShortLambdasOnASingleLine: Empty
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
BreakBeforeBraces: Allman
|
||||
BreakConstructorInitializers: BeforeComma
|
||||
BreakInheritanceList: BeforeComma
|
||||
ColumnLimit: 150
|
||||
IndentCaseLabels: false
|
||||
SortIncludes: false
|
||||
IndentWidth: 4
|
||||
TabWidth: 4
|
||||
ObjCBlockIndentWidth: 4
|
||||
AlignAfterOpenBracket: DontAlign
|
||||
UseTab: Never
|
||||
PointerAlignment: Left
|
||||
SpaceAfterTemplateKeyword: false
|
||||
AlignEscapedNewlines: DontAlign
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
MaxEmptyLinesToKeep: 10
|
63
Analysis/include/Luau/AstQuery.h
Normal file
63
Analysis/include/Luau/AstQuery.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Documentation.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct Binding;
|
||||
struct SourceModule;
|
||||
struct Module;
|
||||
|
||||
struct TypeVar;
|
||||
using TypeId = const TypeVar*;
|
||||
|
||||
using ScopePtr = std::shared_ptr<struct Scope>;
|
||||
|
||||
struct ExprOrLocal
|
||||
{
|
||||
AstExpr* getExpr()
|
||||
{
|
||||
return expr;
|
||||
}
|
||||
AstLocal* getLocal()
|
||||
{
|
||||
return local;
|
||||
}
|
||||
void setExpr(AstExpr* newExpr)
|
||||
{
|
||||
expr = newExpr;
|
||||
local = nullptr;
|
||||
}
|
||||
void setLocal(AstLocal* newLocal)
|
||||
{
|
||||
local = newLocal;
|
||||
expr = nullptr;
|
||||
}
|
||||
std::optional<Location> getLocation()
|
||||
{
|
||||
return expr ? expr->location : (local ? local->location : std::optional<Location>{});
|
||||
}
|
||||
|
||||
private:
|
||||
AstExpr* expr = nullptr;
|
||||
AstLocal* local = nullptr;
|
||||
};
|
||||
|
||||
std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos);
|
||||
AstNode* findNodeAtPosition(const SourceModule& source, Position pos);
|
||||
AstExpr* findExprAtPosition(const SourceModule& source, Position pos);
|
||||
ScopePtr findScopeAtPosition(const Module& module, Position pos);
|
||||
std::optional<Binding> findBindingAtPosition(const Module& module, const SourceModule& source, Position pos);
|
||||
ExprOrLocal findExprOrLocalAtPosition(const SourceModule& source, Position pos);
|
||||
|
||||
std::optional<TypeId> findTypeAtPosition(const Module& module, const SourceModule& sourceModule, Position pos);
|
||||
std::optional<TypeId> findExpectedTypeAtPosition(const Module& module, const SourceModule& sourceModule, Position pos);
|
||||
|
||||
std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const SourceModule& source, const Module& module, Position position);
|
||||
|
||||
} // namespace Luau
|
91
Analysis/include/Luau/Autocomplete.h
Normal file
91
Analysis/include/Luau/Autocomplete.h
Normal file
|
@ -0,0 +1,91 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct Frontend;
|
||||
struct SourceModule;
|
||||
struct Module;
|
||||
struct TypeChecker;
|
||||
|
||||
using ModulePtr = std::shared_ptr<Module>;
|
||||
|
||||
enum class AutocompleteEntryKind
|
||||
{
|
||||
Property,
|
||||
Binding,
|
||||
Keyword,
|
||||
String,
|
||||
Type,
|
||||
Module,
|
||||
};
|
||||
|
||||
enum class ParenthesesRecommendation
|
||||
{
|
||||
None,
|
||||
CursorAfter,
|
||||
CursorInside,
|
||||
};
|
||||
|
||||
enum class TypeCorrectKind
|
||||
{
|
||||
None,
|
||||
Correct,
|
||||
CorrectFunctionResult,
|
||||
};
|
||||
|
||||
struct AutocompleteEntry
|
||||
{
|
||||
AutocompleteEntryKind kind = AutocompleteEntryKind::Property;
|
||||
// Nullopt if kind is Keyword
|
||||
std::optional<TypeId> type = std::nullopt;
|
||||
bool deprecated = false;
|
||||
// Only meaningful if kind is Property.
|
||||
bool wrongIndexType = false;
|
||||
// Set if this suggestion matches the type expected in the context
|
||||
TypeCorrectKind typeCorrect = TypeCorrectKind::None;
|
||||
|
||||
std::optional<const ClassTypeVar*> containingClass = std::nullopt;
|
||||
std::optional<const Property*> prop = std::nullopt;
|
||||
std::optional<std::string> documentationSymbol = std::nullopt;
|
||||
Tags tags;
|
||||
ParenthesesRecommendation parens = ParenthesesRecommendation::None;
|
||||
};
|
||||
|
||||
using AutocompleteEntryMap = std::unordered_map<std::string, AutocompleteEntry>;
|
||||
struct AutocompleteResult
|
||||
{
|
||||
AutocompleteEntryMap entryMap;
|
||||
std::vector<AstNode*> ancestry;
|
||||
|
||||
AutocompleteResult() = default;
|
||||
AutocompleteResult(AutocompleteEntryMap entryMap, std::vector<AstNode*> ancestry)
|
||||
: entryMap(std::move(entryMap))
|
||||
, ancestry(std::move(ancestry))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
using ModuleName = std::string;
|
||||
using StringCompletionCallback = std::function<std::optional<AutocompleteEntryMap>(std::string tag, std::optional<const ClassTypeVar*> ctx)>;
|
||||
|
||||
struct OwningAutocompleteResult
|
||||
{
|
||||
AutocompleteResult result;
|
||||
ModulePtr module;
|
||||
std::unique_ptr<SourceModule> sourceModule;
|
||||
};
|
||||
|
||||
AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback);
|
||||
OwningAutocompleteResult autocompleteSource(Frontend& frontend, std::string_view source, Position position, StringCompletionCallback callback);
|
||||
|
||||
} // namespace Luau
|
51
Analysis/include/Luau/BuiltinDefinitions.h
Normal file
51
Analysis/include/Luau/BuiltinDefinitions.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "TypeInfer.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
void registerBuiltinTypes(TypeChecker& typeChecker);
|
||||
|
||||
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types);
|
||||
TypeId makeIntersection(TypeArena& arena, std::vector<TypeId>&& types);
|
||||
|
||||
/** Build an optional 't'
|
||||
*/
|
||||
TypeId makeOption(TypeChecker& typeChecker, TypeArena& arena, TypeId t);
|
||||
|
||||
/** Small utility function for building up type definitions from C++.
|
||||
*/
|
||||
TypeId makeFunction( // Monomorphic
|
||||
TypeArena& arena, std::optional<TypeId> selfType, std::initializer_list<TypeId> paramTypes, std::initializer_list<TypeId> retTypes);
|
||||
|
||||
TypeId makeFunction( // Polymorphic
|
||||
TypeArena& arena, std::optional<TypeId> selfType, std::initializer_list<TypeId> generics, std::initializer_list<TypePackId> genericPacks,
|
||||
std::initializer_list<TypeId> paramTypes, std::initializer_list<TypeId> retTypes);
|
||||
|
||||
TypeId makeFunction( // Monomorphic
|
||||
TypeArena& arena, std::optional<TypeId> selfType, std::initializer_list<TypeId> paramTypes, std::initializer_list<std::string> paramNames,
|
||||
std::initializer_list<TypeId> retTypes);
|
||||
|
||||
TypeId makeFunction( // Polymorphic
|
||||
TypeArena& arena, std::optional<TypeId> selfType, std::initializer_list<TypeId> generics, std::initializer_list<TypePackId> genericPacks,
|
||||
std::initializer_list<TypeId> paramTypes, std::initializer_list<std::string> paramNames, std::initializer_list<TypeId> retTypes);
|
||||
|
||||
void attachMagicFunction(TypeId ty, MagicFunction fn);
|
||||
void attachFunctionTag(TypeId ty, std::string constraint);
|
||||
|
||||
Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol = std::nullopt);
|
||||
void assignPropDocumentationSymbols(TableTypeVar::Props& props, const std::string& baseName);
|
||||
|
||||
std::string getBuiltinDefinitionSource();
|
||||
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const std::string& name, TypeId ty, const std::string& packageName);
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const std::string& name, Binding binding);
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const ScopePtr& scope, const std::string& name, TypeId ty, const std::string& packageName);
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const ScopePtr& scope, const std::string& name, Binding binding);
|
||||
std::optional<Binding> tryGetGlobalBinding(TypeChecker& typeChecker, const std::string& name);
|
||||
Binding* tryGetGlobalBindingRef(TypeChecker& typeChecker, const std::string& name);
|
||||
TypeId getGlobalBinding(TypeChecker& typeChecker, const std::string& name);
|
||||
|
||||
} // namespace Luau
|
58
Analysis/include/Luau/Config.h
Normal file
58
Analysis/include/Luau/Config.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Linter.h"
|
||||
#include "Luau/ParseOptions.h"
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
using ModuleName = std::string;
|
||||
|
||||
constexpr const char* kConfigName = ".luaurc";
|
||||
|
||||
struct Config
|
||||
{
|
||||
Config()
|
||||
{
|
||||
enabledLint.setDefaults();
|
||||
}
|
||||
|
||||
Mode mode = Mode::NoCheck;
|
||||
|
||||
ParseOptions parseOptions;
|
||||
|
||||
LintOptions enabledLint;
|
||||
LintOptions fatalLint;
|
||||
|
||||
bool lintErrors = false;
|
||||
bool typeErrors = true;
|
||||
|
||||
std::vector<std::string> globals;
|
||||
};
|
||||
|
||||
struct ConfigResolver
|
||||
{
|
||||
virtual ~ConfigResolver() {}
|
||||
|
||||
virtual const Config& getConfig(const ModuleName& name) const = 0;
|
||||
};
|
||||
|
||||
struct NullConfigResolver : ConfigResolver
|
||||
{
|
||||
Config defaultConfig;
|
||||
|
||||
virtual const Config& getConfig(const ModuleName& name) const override;
|
||||
};
|
||||
|
||||
std::optional<std::string> parseModeString(Mode& mode, const std::string& modeString, bool compat = false);
|
||||
std::optional<std::string> parseLintRuleString(
|
||||
LintOptions& enabledLints, LintOptions& fatalLints, const std::string& warningName, const std::string& value, bool compat = false);
|
||||
|
||||
std::optional<std::string> parseConfig(const std::string& contents, Config& config, bool compat = false);
|
||||
|
||||
} // namespace Luau
|
50
Analysis/include/Luau/Documentation.h
Normal file
50
Analysis/include/Luau/Documentation.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct FunctionDocumentation;
|
||||
struct TableDocumentation;
|
||||
struct OverloadedFunctionDocumentation;
|
||||
|
||||
using Documentation = Luau::Variant<std::string, FunctionDocumentation, TableDocumentation, OverloadedFunctionDocumentation>;
|
||||
using DocumentationSymbol = std::string;
|
||||
|
||||
struct FunctionParameterDocumentation
|
||||
{
|
||||
std::string name;
|
||||
DocumentationSymbol documentation;
|
||||
};
|
||||
|
||||
// Represents documentation for anything callable. This could be a method or a
|
||||
// callback or a free function.
|
||||
struct FunctionDocumentation
|
||||
{
|
||||
std::string documentation;
|
||||
std::vector<FunctionParameterDocumentation> parameters;
|
||||
std::vector<DocumentationSymbol> returns;
|
||||
};
|
||||
|
||||
struct OverloadedFunctionDocumentation
|
||||
{
|
||||
// This is a map of function signature to overload symbol name.
|
||||
Luau::DenseHashMap<std::string, DocumentationSymbol> overloads;
|
||||
};
|
||||
|
||||
// Represents documentation for a table-like item, meaning "anything with keys".
|
||||
// This could be a table or a class.
|
||||
struct TableDocumentation
|
||||
{
|
||||
std::string documentation;
|
||||
Luau::DenseHashMap<std::string, DocumentationSymbol> keys;
|
||||
};
|
||||
|
||||
using DocumentationDatabase = Luau::DenseHashMap<DocumentationSymbol, Documentation>;
|
||||
|
||||
} // namespace Luau
|
332
Analysis/include/Luau/Error.h
Normal file
332
Analysis/include/Luau/Error.h
Normal file
|
@ -0,0 +1,332 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/FileResolver.h"
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct TypeMismatch
|
||||
{
|
||||
TypeId wantedType;
|
||||
TypeId givenType;
|
||||
|
||||
bool operator==(const TypeMismatch& rhs) const;
|
||||
};
|
||||
|
||||
struct UnknownSymbol
|
||||
{
|
||||
enum Context
|
||||
{
|
||||
Binding,
|
||||
Type,
|
||||
Generic
|
||||
};
|
||||
Name name;
|
||||
Context context;
|
||||
|
||||
bool operator==(const UnknownSymbol& rhs) const;
|
||||
};
|
||||
|
||||
struct UnknownProperty
|
||||
{
|
||||
TypeId table;
|
||||
Name key;
|
||||
|
||||
bool operator==(const UnknownProperty& rhs) const;
|
||||
};
|
||||
|
||||
struct NotATable
|
||||
{
|
||||
TypeId ty;
|
||||
|
||||
bool operator==(const NotATable& rhs) const;
|
||||
};
|
||||
|
||||
struct CannotExtendTable
|
||||
{
|
||||
enum Context
|
||||
{
|
||||
Property,
|
||||
Indexer,
|
||||
Metatable
|
||||
};
|
||||
TypeId tableType;
|
||||
Context context;
|
||||
Name prop;
|
||||
|
||||
bool operator==(const CannotExtendTable& rhs) const;
|
||||
};
|
||||
|
||||
struct OnlyTablesCanHaveMethods
|
||||
{
|
||||
TypeId tableType;
|
||||
|
||||
bool operator==(const OnlyTablesCanHaveMethods& rhs) const;
|
||||
};
|
||||
|
||||
struct DuplicateTypeDefinition
|
||||
{
|
||||
Name name;
|
||||
Location previousLocation;
|
||||
|
||||
bool operator==(const DuplicateTypeDefinition& rhs) const;
|
||||
};
|
||||
|
||||
struct CountMismatch
|
||||
{
|
||||
enum Context
|
||||
{
|
||||
Arg,
|
||||
Result,
|
||||
Return,
|
||||
};
|
||||
size_t expected;
|
||||
size_t actual;
|
||||
Context context = Arg;
|
||||
|
||||
bool operator==(const CountMismatch& rhs) const;
|
||||
};
|
||||
|
||||
struct FunctionDoesNotTakeSelf
|
||||
{
|
||||
bool operator==(const FunctionDoesNotTakeSelf& rhs) const;
|
||||
};
|
||||
|
||||
struct FunctionRequiresSelf
|
||||
{
|
||||
int requiredExtraNils = 0;
|
||||
|
||||
bool operator==(const FunctionRequiresSelf& rhs) const;
|
||||
};
|
||||
|
||||
struct OccursCheckFailed
|
||||
{
|
||||
bool operator==(const OccursCheckFailed& rhs) const;
|
||||
};
|
||||
|
||||
struct UnknownRequire
|
||||
{
|
||||
std::string modulePath;
|
||||
|
||||
bool operator==(const UnknownRequire& rhs) const;
|
||||
};
|
||||
|
||||
struct IncorrectGenericParameterCount
|
||||
{
|
||||
Name name;
|
||||
TypeFun typeFun;
|
||||
size_t actualParameters;
|
||||
|
||||
bool operator==(const IncorrectGenericParameterCount& rhs) const;
|
||||
};
|
||||
|
||||
struct SyntaxError
|
||||
{
|
||||
std::string message;
|
||||
|
||||
bool operator==(const SyntaxError& rhs) const;
|
||||
};
|
||||
|
||||
struct CodeTooComplex
|
||||
{
|
||||
bool operator==(const CodeTooComplex&) const;
|
||||
};
|
||||
|
||||
struct UnificationTooComplex
|
||||
{
|
||||
bool operator==(const UnificationTooComplex&) const;
|
||||
};
|
||||
|
||||
// Could easily be folded into UnknownProperty with an extra field, std::set<Name> candidates.
|
||||
// But for telemetry purposes, we want to have this be a distinct variant.
|
||||
struct UnknownPropButFoundLikeProp
|
||||
{
|
||||
TypeId table;
|
||||
Name key;
|
||||
std::set<Name> candidates;
|
||||
|
||||
bool operator==(const UnknownPropButFoundLikeProp& rhs) const;
|
||||
};
|
||||
|
||||
struct GenericError
|
||||
{
|
||||
std::string message;
|
||||
|
||||
bool operator==(const GenericError& rhs) const;
|
||||
};
|
||||
|
||||
struct CannotCallNonFunction
|
||||
{
|
||||
TypeId ty;
|
||||
|
||||
bool operator==(const CannotCallNonFunction& rhs) const;
|
||||
};
|
||||
|
||||
struct ExtraInformation
|
||||
{
|
||||
std::string message;
|
||||
bool operator==(const ExtraInformation& rhs) const;
|
||||
};
|
||||
|
||||
struct DeprecatedApiUsed
|
||||
{
|
||||
std::string symbol;
|
||||
std::string useInstead;
|
||||
bool operator==(const DeprecatedApiUsed& rhs) const;
|
||||
};
|
||||
|
||||
struct ModuleHasCyclicDependency
|
||||
{
|
||||
std::vector<ModuleName> cycle;
|
||||
bool operator==(const ModuleHasCyclicDependency& rhs) const;
|
||||
};
|
||||
|
||||
struct FunctionExitsWithoutReturning
|
||||
{
|
||||
TypePackId expectedReturnType;
|
||||
bool operator==(const FunctionExitsWithoutReturning& rhs) const;
|
||||
};
|
||||
|
||||
struct IllegalRequire
|
||||
{
|
||||
std::string moduleName;
|
||||
std::string reason;
|
||||
|
||||
bool operator==(const IllegalRequire& rhs) const;
|
||||
};
|
||||
|
||||
struct MissingProperties
|
||||
{
|
||||
enum Context
|
||||
{
|
||||
Missing,
|
||||
Extra
|
||||
};
|
||||
TypeId superType;
|
||||
TypeId subType;
|
||||
std::vector<Name> properties;
|
||||
Context context = Missing;
|
||||
|
||||
bool operator==(const MissingProperties& rhs) const;
|
||||
};
|
||||
|
||||
struct DuplicateGenericParameter
|
||||
{
|
||||
std::string parameterName;
|
||||
|
||||
bool operator==(const DuplicateGenericParameter& rhs) const;
|
||||
};
|
||||
|
||||
struct CannotInferBinaryOperation
|
||||
{
|
||||
enum OpKind
|
||||
{
|
||||
Operation,
|
||||
Comparison,
|
||||
};
|
||||
|
||||
AstExprBinary::Op op;
|
||||
std::optional<std::string> suggestedToAnnotate;
|
||||
OpKind kind;
|
||||
|
||||
bool operator==(const CannotInferBinaryOperation& rhs) const;
|
||||
};
|
||||
|
||||
struct SwappedGenericTypeParameter
|
||||
{
|
||||
enum Kind
|
||||
{
|
||||
Type,
|
||||
Pack,
|
||||
};
|
||||
|
||||
std::string name;
|
||||
// What was `name` being used as?
|
||||
Kind kind;
|
||||
|
||||
bool operator==(const SwappedGenericTypeParameter& rhs) const;
|
||||
};
|
||||
|
||||
struct OptionalValueAccess
|
||||
{
|
||||
TypeId optional;
|
||||
|
||||
bool operator==(const OptionalValueAccess& rhs) const;
|
||||
};
|
||||
|
||||
struct MissingUnionProperty
|
||||
{
|
||||
TypeId type;
|
||||
std::vector<TypeId> missing;
|
||||
Name key;
|
||||
|
||||
bool operator==(const MissingUnionProperty& rhs) const;
|
||||
};
|
||||
|
||||
using TypeErrorData = Variant<TypeMismatch, UnknownSymbol, UnknownProperty, NotATable, CannotExtendTable, OnlyTablesCanHaveMethods,
|
||||
DuplicateTypeDefinition, CountMismatch, FunctionDoesNotTakeSelf, FunctionRequiresSelf, OccursCheckFailed, UnknownRequire,
|
||||
IncorrectGenericParameterCount, SyntaxError, CodeTooComplex, UnificationTooComplex, UnknownPropButFoundLikeProp, GenericError,
|
||||
CannotCallNonFunction, ExtraInformation, DeprecatedApiUsed, ModuleHasCyclicDependency, IllegalRequire, FunctionExitsWithoutReturning,
|
||||
DuplicateGenericParameter, CannotInferBinaryOperation, MissingProperties, SwappedGenericTypeParameter, OptionalValueAccess, MissingUnionProperty>;
|
||||
|
||||
struct TypeError
|
||||
{
|
||||
Location location;
|
||||
ModuleName moduleName;
|
||||
TypeErrorData data;
|
||||
|
||||
int code() const;
|
||||
|
||||
TypeError() = default;
|
||||
|
||||
TypeError(const Location& location, const ModuleName& moduleName, const TypeErrorData& data)
|
||||
: location(location)
|
||||
, moduleName(moduleName)
|
||||
, data(data)
|
||||
{
|
||||
}
|
||||
|
||||
TypeError(const Location& location, const TypeErrorData& data)
|
||||
: TypeError(location, {}, data)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const TypeError& rhs) const;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
const T* get(const TypeError& e)
|
||||
{
|
||||
return get_if<T>(&e.data);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* get(TypeError& e)
|
||||
{
|
||||
return get_if<T>(&e.data);
|
||||
}
|
||||
|
||||
using ErrorVec = std::vector<TypeError>;
|
||||
|
||||
std::string toString(const TypeError& error);
|
||||
|
||||
bool containsParseErrorName(const TypeError& error);
|
||||
|
||||
// Copy any types named in the error into destArena.
|
||||
void copyErrors(ErrorVec& errors, struct TypeArena& destArena);
|
||||
|
||||
// Internal Compiler Error
|
||||
struct InternalErrorReporter
|
||||
{
|
||||
std::function<void(const char*)> onInternalError;
|
||||
std::string moduleName;
|
||||
|
||||
[[noreturn]] void ice(const std::string& message, const Location& location);
|
||||
[[noreturn]] void ice(const std::string& message);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
103
Analysis/include/Luau/FileResolver.h
Normal file
103
Analysis/include/Luau/FileResolver.h
Normal file
|
@ -0,0 +1,103 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
class AstExpr;
|
||||
|
||||
using ModuleName = std::string;
|
||||
|
||||
struct SourceCode
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
None,
|
||||
Module,
|
||||
Script,
|
||||
Local
|
||||
};
|
||||
|
||||
std::string source;
|
||||
Type type;
|
||||
};
|
||||
|
||||
struct FileResolver
|
||||
{
|
||||
virtual ~FileResolver() {}
|
||||
|
||||
/** Fetch the source code associated with the provided ModuleName.
|
||||
*
|
||||
* FIXME: This requires a string copy!
|
||||
*
|
||||
* @returns The actual Lua code on success.
|
||||
* @returns std::nullopt if no such file exists. When this occurs, type inference will report an UnknownRequire error.
|
||||
*/
|
||||
virtual std::optional<SourceCode> readSource(const ModuleName& name) = 0;
|
||||
|
||||
/** Does the module exist?
|
||||
*
|
||||
* Saves a string copy over reading the source and throwing it away.
|
||||
*/
|
||||
virtual bool moduleExists(const ModuleName& name) const = 0;
|
||||
|
||||
virtual std::optional<ModuleName> fromAstFragment(AstExpr* expr) const = 0;
|
||||
|
||||
/** Given a valid module name and a string of arbitrary data, figure out the concatenation.
|
||||
*/
|
||||
virtual ModuleName concat(const ModuleName& lhs, std::string_view rhs) const = 0;
|
||||
|
||||
/** Goes "up" a level in the hierarchy that the ModuleName represents.
|
||||
*
|
||||
* For instances, this is analogous to someInstance.Parent; for paths, this is equivalent to removing the last
|
||||
* element of the path. Other ModuleName representations may have other ways of doing this.
|
||||
*
|
||||
* @returns The parent ModuleName, if one exists.
|
||||
* @returns std::nullopt if there is no parent for this module name.
|
||||
*/
|
||||
virtual std::optional<ModuleName> getParentModuleName(const ModuleName& name) const = 0;
|
||||
|
||||
virtual std::optional<std::string> getHumanReadableModuleName_(const ModuleName& name) const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
virtual std::optional<std::string> getEnvironmentForModule(const ModuleName& name) const = 0;
|
||||
|
||||
/** LanguageService only:
|
||||
* std::optional<ModuleName> fromInstance(Instance* inst)
|
||||
*/
|
||||
};
|
||||
|
||||
struct NullFileResolver : FileResolver
|
||||
{
|
||||
std::optional<SourceCode> readSource(const ModuleName& name) override
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
bool moduleExists(const ModuleName& name) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::optional<ModuleName> fromAstFragment(AstExpr* expr) const override
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
ModuleName concat(const ModuleName& lhs, std::string_view rhs) const override
|
||||
{
|
||||
return lhs;
|
||||
}
|
||||
std::optional<ModuleName> getParentModuleName(const ModuleName& name) const override
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<std::string> getEnvironmentForModule(const ModuleName& name) const override
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Luau
|
179
Analysis/include/Luau/Frontend.h
Normal file
179
Analysis/include/Luau/Frontend.h
Normal file
|
@ -0,0 +1,179 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Config.h"
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/ModuleResolver.h"
|
||||
#include "Luau/RequireTracer.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
class AstStat;
|
||||
class ParseError;
|
||||
struct Frontend;
|
||||
struct TypeError;
|
||||
struct LintWarning;
|
||||
struct TypeChecker;
|
||||
struct FileResolver;
|
||||
struct ModuleResolver;
|
||||
struct ParseResult;
|
||||
|
||||
struct LoadDefinitionFileResult
|
||||
{
|
||||
bool success;
|
||||
ParseResult parseResult;
|
||||
ModulePtr module;
|
||||
};
|
||||
|
||||
LoadDefinitionFileResult loadDefinitionFile(
|
||||
TypeChecker& typeChecker, ScopePtr targetScope, std::string_view definition, const std::string& packageName);
|
||||
|
||||
std::optional<Mode> parseMode(const std::vector<std::string>& hotcomments);
|
||||
|
||||
std::vector<std::string_view> parsePathExpr(const AstExpr& pathExpr);
|
||||
|
||||
// Exported only for convenient testing.
|
||||
std::optional<ModuleName> pathExprToModuleName(const ModuleName& currentModuleName, const std::vector<std::string_view>& expr);
|
||||
|
||||
/** Try to convert an AST fragment into a ModuleName.
|
||||
* Returns std::nullopt if the expression cannot be resolved. This will most likely happen in cases where
|
||||
* the import path involves some dynamic computation that we cannot see into at typechecking time.
|
||||
*
|
||||
* Unintuitively, weirdly-formulated modules (like game.Parent.Parent.Parent.Foo) will successfully produce a ModuleName
|
||||
* as long as it falls within the permitted syntax. This is ok because we will fail to find the module and produce an
|
||||
* error when we try during typechecking.
|
||||
*/
|
||||
std::optional<ModuleName> pathExprToModuleName(const ModuleName& currentModuleName, const AstExpr& expr);
|
||||
|
||||
struct SourceNode
|
||||
{
|
||||
ModuleName name;
|
||||
std::unordered_set<ModuleName> requires;
|
||||
std::vector<std::pair<ModuleName, Location>> requireLocations;
|
||||
bool dirty = true;
|
||||
};
|
||||
|
||||
struct FrontendOptions
|
||||
{
|
||||
// When true, we retain full type information about every term in the AST.
|
||||
// Setting this to false cuts back on RAM and is a good idea for batch
|
||||
// jobs where the type graph is not deeply inspected after typechecking
|
||||
// is complete.
|
||||
bool retainFullTypeGraphs = false;
|
||||
|
||||
// When true, we run typechecking twice, one in the regular mode, ond once in strict mode
|
||||
// in order to get more precise type information (e.g. for autocomplete).
|
||||
bool typecheckTwice = false;
|
||||
};
|
||||
|
||||
struct CheckResult
|
||||
{
|
||||
std::vector<TypeError> errors;
|
||||
};
|
||||
|
||||
struct FrontendModuleResolver : ModuleResolver
|
||||
{
|
||||
FrontendModuleResolver(Frontend* frontend);
|
||||
|
||||
const ModulePtr getModule(const ModuleName& moduleName) const override;
|
||||
bool moduleExists(const ModuleName& moduleName) const override;
|
||||
std::optional<ModuleInfo> resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr) override;
|
||||
std::string getHumanReadableModuleName(const ModuleName& moduleName) const override;
|
||||
|
||||
Frontend* frontend;
|
||||
std::unordered_map<ModuleName, ModulePtr> modules;
|
||||
};
|
||||
|
||||
struct Frontend
|
||||
{
|
||||
struct Stats
|
||||
{
|
||||
size_t files = 0;
|
||||
size_t lines = 0;
|
||||
|
||||
size_t filesStrict = 0;
|
||||
size_t filesNonstrict = 0;
|
||||
|
||||
double timeRead = 0;
|
||||
double timeParse = 0;
|
||||
double timeCheck = 0;
|
||||
double timeLint = 0;
|
||||
};
|
||||
|
||||
Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, const FrontendOptions& options = {});
|
||||
|
||||
CheckResult check(const ModuleName& name); // new shininess
|
||||
LintResult lint(const ModuleName& name, std::optional<Luau::LintOptions> enabledLintWarnings = {});
|
||||
|
||||
/** Lint some code that has no associated DataModel object
|
||||
*
|
||||
* Since this source fragment has no name, we cannot cache its AST. Instead,
|
||||
* we return it to the caller to use as they wish.
|
||||
*/
|
||||
std::pair<SourceModule, LintResult> lintFragment(std::string_view source, std::optional<Luau::LintOptions> enabledLintWarnings = {});
|
||||
|
||||
CheckResult check(const SourceModule& module); // OLD. TODO KILL
|
||||
LintResult lint(const SourceModule& module, std::optional<Luau::LintOptions> enabledLintWarnings = {});
|
||||
|
||||
bool isDirty(const ModuleName& name) const;
|
||||
void markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty = nullptr);
|
||||
|
||||
/** Borrow a pointer into the SourceModule cache.
|
||||
*
|
||||
* Returns nullptr if we don't have it. This could mean that the script
|
||||
* doesn't exist, or simply that its contents have changed since the previous
|
||||
* check, in which case we do not have its AST.
|
||||
*
|
||||
* IMPORTANT: this pointer is only valid until the next call to markDirty. Do not retain it.
|
||||
*/
|
||||
SourceModule* getSourceModule(const ModuleName& name);
|
||||
const SourceModule* getSourceModule(const ModuleName& name) const;
|
||||
|
||||
void clearStats();
|
||||
void clear();
|
||||
|
||||
ScopePtr addEnvironment(const std::string& environmentName);
|
||||
ScopePtr getEnvironmentScope(const std::string& environmentName);
|
||||
|
||||
void registerBuiltinDefinition(const std::string& name, std::function<void(TypeChecker&, ScopePtr)>);
|
||||
void applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName);
|
||||
|
||||
private:
|
||||
std::pair<SourceNode*, SourceModule*> getSourceNode(CheckResult& checkResult, const ModuleName& name);
|
||||
SourceModule parse(const ModuleName& name, std::string_view src, const ParseOptions& parseOptions);
|
||||
|
||||
bool parseGraph(std::vector<ModuleName>& buildQueue, CheckResult& checkResult, const ModuleName& root);
|
||||
|
||||
static LintResult classifyLints(const std::vector<LintWarning>& warnings, const Config& config);
|
||||
|
||||
ScopePtr getModuleEnvironment(const SourceModule& module, const Config& config);
|
||||
|
||||
std::unordered_map<std::string, ScopePtr> environments;
|
||||
std::unordered_map<std::string, std::function<void(TypeChecker&, ScopePtr)>> builtinDefinitions;
|
||||
|
||||
public:
|
||||
FileResolver* fileResolver;
|
||||
FrontendModuleResolver moduleResolver;
|
||||
FrontendModuleResolver moduleResolverForAutocomplete;
|
||||
TypeChecker typeChecker;
|
||||
TypeChecker typeCheckerForAutocomplete;
|
||||
ConfigResolver* configResolver;
|
||||
FrontendOptions options;
|
||||
InternalErrorReporter iceHandler;
|
||||
TypeArena arenaForAutocomplete;
|
||||
|
||||
std::unordered_map<ModuleName, SourceNode> sourceNodes;
|
||||
std::unordered_map<ModuleName, SourceModule> sourceModules;
|
||||
std::unordered_map<ModuleName, RequireTraceResult> requires;
|
||||
|
||||
Stats stats = {};
|
||||
};
|
||||
|
||||
} // namespace Luau
|
46
Analysis/include/Luau/IostreamHelpers.h
Normal file
46
Analysis/include/Luau/IostreamHelpers.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/Ast.h"
|
||||
|
||||
#include <ostream>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
std::ostream& operator<<(std::ostream& lhs, const Position& position);
|
||||
std::ostream& operator<<(std::ostream& lhs, const Location& location);
|
||||
std::ostream& operator<<(std::ostream& lhs, const AstName& name);
|
||||
|
||||
std::ostream& operator<<(std::ostream& lhs, const TypeError& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const TypeMismatch& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const UnknownSymbol& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const UnknownProperty& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const NotATable& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const CannotExtendTable& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const OnlyTablesCanHaveMethods& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const DuplicateTypeDefinition& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const CountMismatch& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const FunctionDoesNotTakeSelf& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const FunctionRequiresSelf& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const OccursCheckFailed& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const UnknownRequire& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const UnknownPropButFoundLikeProp& e);
|
||||
std::ostream& operator<<(std::ostream& lhs, const GenericError& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const FunctionExitsWithoutReturning& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const MissingProperties& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const IllegalRequire& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const ModuleHasCyclicDependency& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const DuplicateGenericParameter& error);
|
||||
std::ostream& operator<<(std::ostream& lhs, const CannotInferBinaryOperation& error);
|
||||
|
||||
std::ostream& operator<<(std::ostream& lhs, const TableState& tv);
|
||||
std::ostream& operator<<(std::ostream& lhs, const TypeVar& tv);
|
||||
std::ostream& operator<<(std::ostream& lhs, const TypePackVar& tv);
|
||||
|
||||
std::ostream& operator<<(std::ostream& lhs, const TypeErrorData& ted);
|
||||
|
||||
} // namespace Luau
|
13
Analysis/include/Luau/JsonEncoder.h
Normal file
13
Analysis/include/Luau/JsonEncoder.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
class AstNode;
|
||||
|
||||
std::string toJson(AstNode* node);
|
||||
|
||||
} // namespace Luau
|
96
Analysis/include/Luau/Linter.h
Normal file
96
Analysis/include/Luau/Linter.h
Normal file
|
@ -0,0 +1,96 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Location.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct AstName;
|
||||
class AstStat;
|
||||
class AstNameTable;
|
||||
struct TypeChecker;
|
||||
struct Module;
|
||||
|
||||
using ScopePtr = std::shared_ptr<struct Scope>;
|
||||
|
||||
struct LintWarning
|
||||
{
|
||||
// Make sure any new lint codes are documented here: https://luau-lang.org/lint
|
||||
// Note that in Studio, the active set of lint warnings is determined by FStringStudioLuauLints
|
||||
enum Code
|
||||
{
|
||||
Code_Unknown = 0,
|
||||
|
||||
Code_UnknownGlobal = 1, // superseded by type checker
|
||||
Code_DeprecatedGlobal = 2,
|
||||
Code_GlobalUsedAsLocal = 3,
|
||||
Code_LocalShadow = 4, // disabled in Studio
|
||||
Code_SameLineStatement = 5, // disabled in Studio
|
||||
Code_MultiLineStatement = 6,
|
||||
Code_LocalUnused = 7, // disabled in Studio
|
||||
Code_FunctionUnused = 8, // disabled in Studio
|
||||
Code_ImportUnused = 9, // disabled in Studio
|
||||
Code_BuiltinGlobalWrite = 10,
|
||||
Code_PlaceholderRead = 11,
|
||||
Code_UnreachableCode = 12,
|
||||
Code_UnknownType = 13,
|
||||
Code_ForRange = 14,
|
||||
Code_UnbalancedAssignment = 15,
|
||||
Code_ImplicitReturn = 16, // disabled in Studio, superseded by type checker in strict mode
|
||||
Code_DuplicateLocal = 17,
|
||||
Code_FormatString = 18,
|
||||
Code_TableLiteral = 19,
|
||||
Code_UninitializedLocal = 20,
|
||||
Code_DuplicateFunction = 21,
|
||||
Code_DeprecatedApi = 22,
|
||||
Code_TableOperations = 23,
|
||||
Code_DuplicateCondition = 24,
|
||||
|
||||
Code__Count
|
||||
};
|
||||
|
||||
Code code;
|
||||
Location location;
|
||||
std::string text;
|
||||
|
||||
static const char* getName(Code code);
|
||||
static Code parseName(const char* name);
|
||||
static uint64_t parseMask(const std::vector<std::string>& hotcomments);
|
||||
};
|
||||
|
||||
struct LintResult
|
||||
{
|
||||
std::vector<LintWarning> errors;
|
||||
std::vector<LintWarning> warnings;
|
||||
};
|
||||
|
||||
struct LintOptions
|
||||
{
|
||||
uint64_t warningMask = 0;
|
||||
|
||||
void enableWarning(LintWarning::Code code)
|
||||
{
|
||||
warningMask |= 1ull << code;
|
||||
}
|
||||
void disableWarning(LintWarning::Code code)
|
||||
{
|
||||
warningMask &= ~(1ull << code);
|
||||
}
|
||||
|
||||
bool isEnabled(LintWarning::Code code) const
|
||||
{
|
||||
return 0 != (warningMask & (1ull << code));
|
||||
}
|
||||
|
||||
void setDefaults();
|
||||
};
|
||||
|
||||
std::vector<LintWarning> lint(AstStat* root, const AstNameTable& names, const ScopePtr& env, const Module* module, const LintOptions& options);
|
||||
|
||||
std::vector<AstName> getDeprecatedGlobals(const AstNameTable& names);
|
||||
|
||||
} // namespace Luau
|
111
Analysis/include/Luau/Module.h
Normal file
111
Analysis/include/Luau/Module.h
Normal file
|
@ -0,0 +1,111 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/FileResolver.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypedAllocator.h"
|
||||
#include "Luau/ParseOptions.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Parser.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct Module;
|
||||
|
||||
using ScopePtr = std::shared_ptr<struct Scope>;
|
||||
using ModulePtr = std::shared_ptr<Module>;
|
||||
|
||||
/// Root of the AST of a parsed source file
|
||||
struct SourceModule
|
||||
{
|
||||
ModuleName name; // DataModel path if possible. Filename if not.
|
||||
SourceCode::Type type = SourceCode::None;
|
||||
std::optional<std::string> environmentName;
|
||||
bool cyclic = false;
|
||||
|
||||
std::unique_ptr<Allocator> allocator;
|
||||
std::unique_ptr<AstNameTable> names;
|
||||
std::vector<ParseError> parseErrors;
|
||||
|
||||
AstStatBlock* root = nullptr;
|
||||
std::optional<Mode> mode;
|
||||
uint64_t ignoreLints = 0;
|
||||
|
||||
std::vector<Comment> commentLocations;
|
||||
|
||||
SourceModule()
|
||||
: allocator(new Allocator)
|
||||
, names(new AstNameTable(*allocator))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
bool isWithinComment(const SourceModule& sourceModule, Position pos);
|
||||
|
||||
struct TypeArena
|
||||
{
|
||||
TypedAllocator<TypeVar> typeVars;
|
||||
TypedAllocator<TypePackVar> typePacks;
|
||||
|
||||
void clear();
|
||||
|
||||
template<typename T>
|
||||
TypeId addType(T tv)
|
||||
{
|
||||
return addTV(TypeVar(std::move(tv)));
|
||||
}
|
||||
|
||||
TypeId addTV(TypeVar&& tv);
|
||||
|
||||
TypeId freshType(TypeLevel level);
|
||||
|
||||
TypePackId addTypePack(std::initializer_list<TypeId> types);
|
||||
TypePackId addTypePack(std::vector<TypeId> types);
|
||||
TypePackId addTypePack(TypePack pack);
|
||||
TypePackId addTypePack(TypePackVar pack);
|
||||
};
|
||||
|
||||
void freeze(TypeArena& arena);
|
||||
void unfreeze(TypeArena& arena);
|
||||
|
||||
// Only exposed so they can be unit tested.
|
||||
using SeenTypes = std::unordered_map<TypeId, TypeId>;
|
||||
using SeenTypePacks = std::unordered_map<TypePackId, TypePackId>;
|
||||
|
||||
TypePackId clone(TypePackId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType = nullptr);
|
||||
TypeId clone(TypeId tp, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType = nullptr);
|
||||
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks& seenTypePacks, bool* encounteredFreeType = nullptr);
|
||||
|
||||
struct Module
|
||||
{
|
||||
~Module();
|
||||
|
||||
TypeArena interfaceTypes;
|
||||
TypeArena internalTypes;
|
||||
|
||||
std::vector<std::pair<Location, ScopePtr>> scopes; // never empty
|
||||
std::unordered_map<const AstExpr*, TypeId> astTypes;
|
||||
std::unordered_map<const AstExpr*, TypeId> astExpectedTypes;
|
||||
std::unordered_map<const AstExpr*, TypeId> astOriginalCallTypes;
|
||||
std::unordered_map<const AstExpr*, TypeId> astOverloadResolvedTypes;
|
||||
std::unordered_map<Name, TypeId> declaredGlobals;
|
||||
ErrorVec errors;
|
||||
Mode mode;
|
||||
SourceCode::Type type;
|
||||
|
||||
ScopePtr getModuleScope() const;
|
||||
|
||||
// Once a module has been typechecked, we clone its public interface into a separate arena.
|
||||
// This helps us to force TypeVar ownership into a DAG rather than a DCG.
|
||||
// Returns true if there were any free types encountered in the public interface. This
|
||||
// indicates a bug in the type checker that we want to surface.
|
||||
bool clonePublicInterface();
|
||||
};
|
||||
|
||||
} // namespace Luau
|
79
Analysis/include/Luau/ModuleResolver.h
Normal file
79
Analysis/include/Luau/ModuleResolver.h
Normal file
|
@ -0,0 +1,79 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/FileResolver.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
class AstExpr;
|
||||
struct Module;
|
||||
|
||||
using ModulePtr = std::shared_ptr<Module>;
|
||||
|
||||
struct ModuleInfo
|
||||
{
|
||||
ModuleName name;
|
||||
bool optional = false;
|
||||
};
|
||||
|
||||
struct ModuleResolver
|
||||
{
|
||||
virtual ~ModuleResolver() {}
|
||||
|
||||
/** Compute a ModuleName from an AST fragment. This AST fragment is generally the argument to the require() function.
|
||||
*
|
||||
* You probably want to implement this with some variation of pathExprToModuleName.
|
||||
*
|
||||
* @returns The ModuleInfo if the expression is a syntactically legal path.
|
||||
* @returns std::nullopt if we are unable to determine whether or not the expression is a valid path. Type inference will
|
||||
* silently assume that it could succeed in this case.
|
||||
*
|
||||
* FIXME: This is clearly not the right behaviour longterm. We'll want to adust this interface to be able to signal
|
||||
* a) success,
|
||||
* b) Definitive failure (this expression will absolutely cause require() to fail at runtime), and
|
||||
* c) uncertainty
|
||||
*/
|
||||
virtual std::optional<ModuleInfo> resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr) = 0;
|
||||
|
||||
/** Get a typechecked module from its name.
|
||||
*
|
||||
* This can return null under two circumstances: the module is unknown at compile time,
|
||||
* or there's a cycle, and we are still in the middle of typechecking the module.
|
||||
*/
|
||||
virtual const ModulePtr getModule(const ModuleName& moduleName) const = 0;
|
||||
|
||||
/** Is a module known at compile time?
|
||||
*
|
||||
* This function can be used to distinguish the above two cases.
|
||||
*/
|
||||
virtual bool moduleExists(const ModuleName& moduleName) const = 0;
|
||||
|
||||
virtual std::string getHumanReadableModuleName(const ModuleName& moduleName) const = 0;
|
||||
};
|
||||
|
||||
struct NullModuleResolver : ModuleResolver
|
||||
{
|
||||
std::optional<ModuleInfo> resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr) override
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
const ModulePtr getModule(const ModuleName& moduleName) const override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
bool moduleExists(const ModuleName& moduleName) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::string getHumanReadableModuleName(const ModuleName& moduleName) const override
|
||||
{
|
||||
return moduleName;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Luau
|
120
Analysis/include/Luau/Predicate.h
Normal file
120
Analysis/include/Luau/Predicate.h
Normal file
|
@ -0,0 +1,120 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Variant.h"
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/Symbol.h"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct TypeVar;
|
||||
using TypeId = const TypeVar*;
|
||||
|
||||
struct Field;
|
||||
using LValue = Variant<Symbol, Field>;
|
||||
|
||||
struct Field
|
||||
{
|
||||
std::shared_ptr<LValue> parent; // TODO: Eventually use unique_ptr to enforce non-copyable trait.
|
||||
std::string key;
|
||||
};
|
||||
|
||||
std::optional<LValue> tryGetLValue(const class AstExpr& expr);
|
||||
|
||||
// Utility function: breaks down an LValue to get at the Symbol, and reverses the vector of keys.
|
||||
std::pair<Symbol, std::vector<std::string>> getFullName(const LValue& lvalue);
|
||||
|
||||
std::string toString(const LValue& lvalue);
|
||||
|
||||
template<typename T>
|
||||
const T* get(const LValue& lvalue)
|
||||
{
|
||||
return get_if<T>(&lvalue);
|
||||
}
|
||||
|
||||
// Key is a stringified encoding of an LValue.
|
||||
using RefinementMap = std::map<std::string, TypeId>;
|
||||
|
||||
void merge(RefinementMap& l, const RefinementMap& r, std::function<TypeId(TypeId, TypeId)> f);
|
||||
void addRefinement(RefinementMap& refis, const LValue& lvalue, TypeId ty);
|
||||
|
||||
struct TruthyPredicate;
|
||||
struct IsAPredicate;
|
||||
struct TypeGuardPredicate;
|
||||
struct EqPredicate;
|
||||
struct AndPredicate;
|
||||
struct OrPredicate;
|
||||
struct NotPredicate;
|
||||
|
||||
using Predicate = Variant<TruthyPredicate, IsAPredicate, TypeGuardPredicate, EqPredicate, AndPredicate, OrPredicate, NotPredicate>;
|
||||
using PredicateVec = std::vector<Predicate>;
|
||||
|
||||
struct TruthyPredicate
|
||||
{
|
||||
LValue lvalue;
|
||||
Location location;
|
||||
};
|
||||
|
||||
struct IsAPredicate
|
||||
{
|
||||
LValue lvalue;
|
||||
Location location;
|
||||
TypeId ty;
|
||||
};
|
||||
|
||||
struct TypeGuardPredicate
|
||||
{
|
||||
LValue lvalue;
|
||||
Location location;
|
||||
std::string kind; // TODO: When singleton types arrive, replace this with `TypeId ty;`
|
||||
bool isTypeof;
|
||||
};
|
||||
|
||||
struct EqPredicate
|
||||
{
|
||||
LValue lvalue;
|
||||
TypeId type;
|
||||
Location location;
|
||||
};
|
||||
|
||||
struct AndPredicate
|
||||
{
|
||||
PredicateVec lhs;
|
||||
PredicateVec rhs;
|
||||
|
||||
AndPredicate(PredicateVec&& lhs, PredicateVec&& rhs)
|
||||
: lhs(std::move(lhs))
|
||||
, rhs(std::move(rhs))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct OrPredicate
|
||||
{
|
||||
PredicateVec lhs;
|
||||
PredicateVec rhs;
|
||||
|
||||
OrPredicate(PredicateVec&& lhs, PredicateVec&& rhs)
|
||||
: lhs(std::move(lhs))
|
||||
, rhs(std::move(rhs))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct NotPredicate
|
||||
{
|
||||
PredicateVec predicates;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
const T* get(const Predicate& predicate)
|
||||
{
|
||||
return get_if<T>(&predicate);
|
||||
}
|
||||
|
||||
} // namespace Luau
|
39
Analysis/include/Luau/RecursionCounter.h
Normal file
39
Analysis/include/Luau/RecursionCounter.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct RecursionCounter
|
||||
{
|
||||
RecursionCounter(int* count)
|
||||
: count(count)
|
||||
{
|
||||
++(*count);
|
||||
}
|
||||
|
||||
~RecursionCounter()
|
||||
{
|
||||
LUAU_ASSERT(*count > 0);
|
||||
--(*count);
|
||||
}
|
||||
|
||||
private:
|
||||
int* count;
|
||||
};
|
||||
|
||||
struct RecursionLimiter : RecursionCounter
|
||||
{
|
||||
RecursionLimiter(int* count, int limit)
|
||||
: RecursionCounter(count)
|
||||
{
|
||||
if (limit > 0 && *count > limit)
|
||||
throw std::runtime_error("Internal recursion counter limit exceeded");
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Luau
|
28
Analysis/include/Luau/RequireTracer.h
Normal file
28
Analysis/include/Luau/RequireTracer.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/FileResolver.h"
|
||||
#include "Luau/Location.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
class AstStat;
|
||||
class AstExpr;
|
||||
class AstStatBlock;
|
||||
struct AstLocal;
|
||||
|
||||
struct RequireTraceResult
|
||||
{
|
||||
DenseHashMap<const AstExpr*, ModuleName> exprs{0};
|
||||
DenseHashMap<const AstExpr*, bool> optional{0};
|
||||
|
||||
std::vector<std::pair<ModuleName, Location>> requires;
|
||||
};
|
||||
|
||||
RequireTraceResult traceRequires(FileResolver* fileResolver, AstStatBlock* root, ModuleName currentModuleName);
|
||||
|
||||
} // namespace Luau
|
208
Analysis/include/Luau/Substitution.h
Normal file
208
Analysis/include/Luau/Substitution.h
Normal file
|
@ -0,0 +1,208 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/ModuleResolver.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
|
||||
// We provide an implementation of substitution on types,
|
||||
// which recursively replaces types by other types.
|
||||
// Examples include quantification (replacing free types by generics)
|
||||
// and instantiation (replacing generic types by free ones).
|
||||
//
|
||||
// To implement a substitution, implement a subclass of `Substitution`
|
||||
// and provide implementations of `isDirty` (which should be true for types that
|
||||
// should be replaced) and `clean` which replaces any dirty types.
|
||||
//
|
||||
// struct MySubst : Substitution
|
||||
// {
|
||||
// bool isDirty(TypeId ty) override { ... }
|
||||
// bool isDirty(TypePackId tp) override { ... }
|
||||
// TypeId clean(TypeId ty) override { ... }
|
||||
// TypePackId clean(TypePackId tp) override { ... }
|
||||
// bool ignoreChildren(TypeId ty) override { ... }
|
||||
// bool ignoreChildren(TypePackId tp) override { ... }
|
||||
// };
|
||||
//
|
||||
// For example, `Instantiation` in `TypeInfer.cpp` uses this.
|
||||
|
||||
// The implementation of substitution tries not to copy types
|
||||
// unnecessarily. It first finds all the types which can reach
|
||||
// a dirty type, and either cleans them (if they are dirty)
|
||||
// or clones them (if they are not). It then updates the children
|
||||
// of the newly created types. When considering reachability,
|
||||
// we do not consider the children of any type where ignoreChildren(ty) is true.
|
||||
|
||||
// There is a gotcha for cyclic types, which means we can't just use
|
||||
// a straightforward DFS. For example:
|
||||
//
|
||||
// type T = { f : () -> T, g: () -> number, h: X }
|
||||
//
|
||||
// If X is dirty, and is being replaced by X' then the result should be:
|
||||
//
|
||||
// type T' = { f : () -> T', g: () -> number, h: X' }
|
||||
//
|
||||
// that is the type of `f` is replaced, but the type of `g` is not.
|
||||
//
|
||||
// For this reason, we first use Tarjan's algorithm to find strongly
|
||||
// connected components. If any type in an SCC can reach a dirty type,
|
||||
// them the whole SCC can. For instance, in the above example,
|
||||
// `T`, and the type of `f` are in the same SCC, which is why `f` gets
|
||||
// replaced.
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauTrackOwningArena)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
enum class TarjanResult
|
||||
{
|
||||
TooManyChildren,
|
||||
Ok
|
||||
};
|
||||
|
||||
struct TarjanWorklistVertex
|
||||
{
|
||||
int index;
|
||||
int currEdge;
|
||||
int lastEdge;
|
||||
};
|
||||
|
||||
// Tarjan's algorithm for finding the SCCs in a cyclic structure.
|
||||
// https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
|
||||
struct Tarjan
|
||||
{
|
||||
// Vertices (types and type packs) are indexed, using pre-order traversal.
|
||||
DenseHashMap<TypeId, int> typeToIndex{nullptr};
|
||||
DenseHashMap<TypePackId, int> packToIndex{nullptr};
|
||||
std::vector<TypeId> indexToType;
|
||||
std::vector<TypePackId> indexToPack;
|
||||
|
||||
// Tarjan keeps a stack of vertices where we're still in the process
|
||||
// of finding their SCC.
|
||||
std::vector<int> stack;
|
||||
std::vector<bool> onStack;
|
||||
|
||||
// Tarjan calculates the lowlink for each vertex,
|
||||
// which is the lowest ancestor index reachable from the vertex.
|
||||
std::vector<int> lowlink;
|
||||
|
||||
int childCount = 0;
|
||||
|
||||
std::vector<TypeId> edgesTy;
|
||||
std::vector<TypePackId> edgesTp;
|
||||
std::vector<TarjanWorklistVertex> worklist;
|
||||
// This is hot code, so we optimize recursion to a stack.
|
||||
TarjanResult loop();
|
||||
|
||||
// Clear the state
|
||||
void clear();
|
||||
|
||||
// Find or create the index for a vertex.
|
||||
// Return a boolean which is `true` if it's a freshly created index.
|
||||
std::pair<int, bool> indexify(TypeId ty);
|
||||
std::pair<int, bool> indexify(TypePackId tp);
|
||||
|
||||
// Recursively visit all the children of a vertex
|
||||
void visitChildren(TypeId ty, int index);
|
||||
void visitChildren(TypePackId tp, int index);
|
||||
|
||||
void visitChild(TypeId ty);
|
||||
void visitChild(TypePackId ty);
|
||||
|
||||
// Visit the root vertex.
|
||||
TarjanResult visitRoot(TypeId ty);
|
||||
TarjanResult visitRoot(TypePackId ty);
|
||||
|
||||
// Each subclass gets called back once for each edge,
|
||||
// and once for each SCC.
|
||||
virtual void visitEdge(int index, int parentIndex) {}
|
||||
virtual void visitSCC(int index) {}
|
||||
|
||||
// Each subclass can decide to ignore some nodes.
|
||||
virtual bool ignoreChildren(TypeId ty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual bool ignoreChildren(TypePackId ty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// We use Tarjan to calculate dirty bits. We set `dirty[i]` true
|
||||
// if the vertex with index `i` can reach a dirty vertex.
|
||||
struct FindDirty : Tarjan
|
||||
{
|
||||
std::vector<bool> dirty;
|
||||
|
||||
// Get/set the dirty bit for an index (grows the vector if needed)
|
||||
bool getDirty(int index);
|
||||
void setDirty(int index, bool d);
|
||||
|
||||
// Find all the dirty vertices reachable from `t`.
|
||||
TarjanResult findDirty(TypeId t);
|
||||
TarjanResult findDirty(TypePackId t);
|
||||
|
||||
// We find dirty vertices using Tarjan
|
||||
void visitEdge(int index, int parentIndex) override;
|
||||
void visitSCC(int index) override;
|
||||
|
||||
// Subclasses should say which vertices are dirty,
|
||||
// and what to do with dirty vertices.
|
||||
virtual bool isDirty(TypeId ty) = 0;
|
||||
virtual bool isDirty(TypePackId tp) = 0;
|
||||
virtual void foundDirty(TypeId ty) = 0;
|
||||
virtual void foundDirty(TypePackId tp) = 0;
|
||||
};
|
||||
|
||||
// And finally substitution, which finds all the reachable dirty vertices
|
||||
// and replaces them with clean ones.
|
||||
struct Substitution : FindDirty
|
||||
{
|
||||
ModulePtr currentModule;
|
||||
DenseHashMap<TypeId, TypeId> newTypes{nullptr};
|
||||
DenseHashMap<TypePackId, TypePackId> newPacks{nullptr};
|
||||
|
||||
std::optional<TypeId> substitute(TypeId ty);
|
||||
std::optional<TypePackId> substitute(TypePackId tp);
|
||||
|
||||
TypeId replace(TypeId ty);
|
||||
TypePackId replace(TypePackId tp);
|
||||
void replaceChildren(TypeId ty);
|
||||
void replaceChildren(TypePackId tp);
|
||||
TypeId clone(TypeId ty);
|
||||
TypePackId clone(TypePackId tp);
|
||||
|
||||
// Substitutions use Tarjan to find dirty nodes and replace them
|
||||
void foundDirty(TypeId ty) override;
|
||||
void foundDirty(TypePackId tp) override;
|
||||
|
||||
// Implementing subclasses define how to clean a dirty type.
|
||||
virtual TypeId clean(TypeId ty) = 0;
|
||||
virtual TypePackId clean(TypePackId tp) = 0;
|
||||
|
||||
// Helper functions to create new types (used by subclasses)
|
||||
template<typename T>
|
||||
TypeId addType(const T& tv)
|
||||
{
|
||||
TypeId allocated = currentModule->internalTypes.typeVars.allocate(tv);
|
||||
if (FFlag::DebugLuauTrackOwningArena)
|
||||
asMutable(allocated)->owningArena = ¤tModule->internalTypes;
|
||||
|
||||
return allocated;
|
||||
}
|
||||
template<typename T>
|
||||
TypePackId addTypePack(const T& tp)
|
||||
{
|
||||
TypePackId allocated = currentModule->internalTypes.typePacks.allocate(tp);
|
||||
if (FFlag::DebugLuauTrackOwningArena)
|
||||
asMutable(allocated)->owningArena = ¤tModule->internalTypes;
|
||||
|
||||
return allocated;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Luau
|
95
Analysis/include/Luau/Symbol.h
Normal file
95
Analysis/include/Luau/Symbol.h
Normal file
|
@ -0,0 +1,95 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
// TODO Rename this to Name once the old type alias is gone.
|
||||
struct Symbol
|
||||
{
|
||||
Symbol()
|
||||
: local(nullptr)
|
||||
, global()
|
||||
{
|
||||
}
|
||||
|
||||
Symbol(AstLocal* local)
|
||||
: local(local)
|
||||
, global()
|
||||
{
|
||||
}
|
||||
|
||||
Symbol(const AstName& global)
|
||||
: local(nullptr)
|
||||
, global(global)
|
||||
{
|
||||
}
|
||||
|
||||
AstLocal* local;
|
||||
AstName global;
|
||||
|
||||
bool operator==(const Symbol& rhs) const
|
||||
{
|
||||
if (local)
|
||||
return local == rhs.local;
|
||||
if (global.value)
|
||||
return rhs.global.value && global == rhs.global.value; // Subtlety: AstName::operator==(const char*) uses strcmp, not pointer identity.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator!=(const Symbol& rhs) const
|
||||
{
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
bool operator<(const Symbol& rhs) const
|
||||
{
|
||||
if (local && rhs.local)
|
||||
return local < rhs.local;
|
||||
else if (global.value && rhs.global.value)
|
||||
return global < rhs.global;
|
||||
else if (local)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
AstName astName() const
|
||||
{
|
||||
if (local)
|
||||
return local->name;
|
||||
|
||||
LUAU_ASSERT(global.value);
|
||||
return global;
|
||||
}
|
||||
|
||||
const char* c_str() const
|
||||
{
|
||||
if (local)
|
||||
return local->name.value;
|
||||
|
||||
LUAU_ASSERT(global.value);
|
||||
return global.value;
|
||||
}
|
||||
};
|
||||
|
||||
std::string toString(const Symbol& name);
|
||||
|
||||
} // namespace Luau
|
||||
|
||||
namespace std
|
||||
{
|
||||
template<>
|
||||
struct hash<Luau::Symbol>
|
||||
{
|
||||
std::size_t operator()(const Luau::Symbol& s) const noexcept
|
||||
{
|
||||
return std::hash<const Luau::AstLocal*>()(s.local) ^ (s.global.value ? std::hash<std::string_view>()(s.global.value) : 0);
|
||||
}
|
||||
};
|
||||
} // namespace std
|
72
Analysis/include/Luau/ToString.h
Normal file
72
Analysis/include/Luau/ToString.h
Normal file
|
@ -0,0 +1,72 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
LUAU_FASTINT(LuauTableTypeMaximumStringifierLength)
|
||||
LUAU_FASTINT(LuauTypeMaximumStringifierLength)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct ToStringNameMap
|
||||
{
|
||||
std::unordered_map<TypeId, std::string> typeVars;
|
||||
std::unordered_map<TypePackId, std::string> typePacks;
|
||||
};
|
||||
|
||||
struct ToStringOptions
|
||||
{
|
||||
bool exhaustive = false; // If true, we produce complete output rather than comprehensible output
|
||||
bool useLineBreaks = false; // If true, we insert new lines to separate long results such as table entries/metatable.
|
||||
bool functionTypeArguments = false; // If true, output function type argument names when they are available
|
||||
bool hideTableKind = false; // If true, all tables will be surrounded with plain '{}'
|
||||
size_t maxTableLength = size_t(FInt::LuauTableTypeMaximumStringifierLength); // Only applied to TableTypeVars
|
||||
size_t maxTypeLength = size_t(FInt::LuauTypeMaximumStringifierLength);
|
||||
std::optional<ToStringNameMap> nameMap;
|
||||
std::shared_ptr<Scope> scope; // If present, module names will be added and types that are not available in scope will be marked as 'invalid'
|
||||
};
|
||||
|
||||
struct ToStringResult
|
||||
{
|
||||
std::string name;
|
||||
ToStringNameMap nameMap;
|
||||
|
||||
bool invalid = false;
|
||||
bool error = false;
|
||||
bool cycle = false;
|
||||
bool truncated = false;
|
||||
};
|
||||
|
||||
ToStringResult toStringDetailed(TypeId ty, const ToStringOptions& opts = {});
|
||||
ToStringResult toStringDetailed(TypePackId ty, const ToStringOptions& opts = {});
|
||||
|
||||
std::string toString(TypeId ty, const ToStringOptions& opts);
|
||||
std::string toString(TypePackId ty, const ToStringOptions& opts);
|
||||
|
||||
// These are offered as overloads rather than a default parameter so that they can be easily invoked from within the MSVC debugger.
|
||||
// You can use them in watch expressions!
|
||||
inline std::string toString(TypeId ty)
|
||||
{
|
||||
return toString(ty, ToStringOptions{});
|
||||
}
|
||||
inline std::string toString(TypePackId ty)
|
||||
{
|
||||
return toString(ty, ToStringOptions{});
|
||||
}
|
||||
|
||||
std::string toString(const TypeVar& tv, const ToStringOptions& opts = {});
|
||||
std::string toString(const TypePackVar& tp, const ToStringOptions& opts = {});
|
||||
|
||||
// It could be useful to see the text representation of a type during a debugging session instead of exploring the content of the class
|
||||
// These functions will dump the type to stdout and can be evaluated in Watch/Immediate windows or as gdb/lldb expression
|
||||
void dump(TypeId ty);
|
||||
void dump(TypePackId ty);
|
||||
|
||||
} // namespace Luau
|
18
Analysis/include/Luau/TopoSortStatements.h
Normal file
18
Analysis/include/Luau/TopoSortStatements.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
struct AstArray;
|
||||
|
||||
class AstStat;
|
||||
|
||||
bool containsFunctionCall(const AstStat& stat);
|
||||
bool isFunction(const AstStat& stat);
|
||||
void toposort(std::vector<AstStat*>& stats);
|
||||
|
||||
} // namespace Luau
|
30
Analysis/include/Luau/Transpiler.h
Normal file
30
Analysis/include/Luau/Transpiler.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/ParseOptions.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
class AstNode;
|
||||
class AstStatBlock;
|
||||
|
||||
struct TranspileResult
|
||||
{
|
||||
std::string code;
|
||||
Location errorLocation;
|
||||
std::string parseError; // Nonempty if the transpile failed
|
||||
};
|
||||
|
||||
void dump(AstNode* node);
|
||||
|
||||
// Never fails on a well-formed AST
|
||||
std::string transpile(AstStatBlock& ast);
|
||||
std::string transpileWithTypes(AstStatBlock& block);
|
||||
|
||||
// Only fails when parsing fails
|
||||
TranspileResult transpile(std::string_view source, ParseOptions options = ParseOptions{});
|
||||
|
||||
} // namespace Luau
|
46
Analysis/include/Luau/TxnLog.h
Normal file
46
Analysis/include/Luau/TxnLog.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/TypeVar.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
// Log of where what TypeIds we are rebinding and what they used to be
|
||||
struct TxnLog
|
||||
{
|
||||
TxnLog() = default;
|
||||
|
||||
explicit TxnLog(const std::vector<std::pair<TypeId, TypeId>>& seen)
|
||||
: seen(seen)
|
||||
{
|
||||
}
|
||||
|
||||
TxnLog(const TxnLog&) = delete;
|
||||
TxnLog& operator=(const TxnLog&) = delete;
|
||||
|
||||
TxnLog(TxnLog&&) = default;
|
||||
TxnLog& operator=(TxnLog&&) = default;
|
||||
|
||||
void operator()(TypeId a);
|
||||
void operator()(TypePackId a);
|
||||
void operator()(TableTypeVar* a);
|
||||
|
||||
void rollback();
|
||||
|
||||
void concat(TxnLog rhs);
|
||||
|
||||
bool haveSeen(TypeId lhs, TypeId rhs);
|
||||
void pushSeen(TypeId lhs, TypeId rhs);
|
||||
void popSeen(TypeId lhs, TypeId rhs);
|
||||
|
||||
private:
|
||||
std::vector<std::pair<TypeId, TypeVar>> typeVarChanges;
|
||||
std::vector<std::pair<TypePackId, TypePackVar>> typePackChanges;
|
||||
std::vector<std::pair<TableTypeVar*, std::optional<TypeId>>> tableChanges;
|
||||
|
||||
public:
|
||||
std::vector<std::pair<TypeId, TypeId>> seen; // used to avoid infinite recursion when types are cyclic
|
||||
};
|
||||
|
||||
} // namespace Luau
|
21
Analysis/include/Luau/TypeAttach.h
Normal file
21
Analysis/include/Luau/TypeAttach.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Module.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct TypeRehydrationOptions
|
||||
{
|
||||
std::unordered_set<std::string> bannedNames;
|
||||
bool expandClassProps = false;
|
||||
};
|
||||
|
||||
void attachTypeData(SourceModule& source, Module& result);
|
||||
|
||||
AstType* rehydrateAnnotation(TypeId type, Allocator* allocator, const TypeRehydrationOptions& options = {});
|
||||
|
||||
} // namespace Luau
|
453
Analysis/include/Luau/TypeInfer.h
Normal file
453
Analysis/include/Luau/TypeInfer.h
Normal file
|
@ -0,0 +1,453 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Predicate.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/Symbol.h"
|
||||
#include "Luau/Parser.h"
|
||||
#include "Luau/Substitution.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/Unifier.h"
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct Scope;
|
||||
struct TypeChecker;
|
||||
struct ModuleResolver;
|
||||
|
||||
using Name = std::string;
|
||||
using ScopePtr = std::shared_ptr<Scope>;
|
||||
using OverloadErrorEntry = std::tuple<std::vector<TypeError>, std::vector<TypeId>, const FunctionTypeVar*>;
|
||||
|
||||
bool doesCallError(const AstExprCall* call);
|
||||
bool hasBreak(AstStat* node);
|
||||
const AstStat* getFallthrough(const AstStat* node);
|
||||
|
||||
struct Unifier;
|
||||
|
||||
// A substitution which replaces generic types in a given set by free types.
|
||||
struct ReplaceGenerics : Substitution
|
||||
{
|
||||
TypeLevel level;
|
||||
std::vector<TypeId> generics;
|
||||
std::vector<TypePackId> genericPacks;
|
||||
bool ignoreChildren(TypeId ty) override;
|
||||
bool isDirty(TypeId ty) override;
|
||||
bool isDirty(TypePackId tp) override;
|
||||
TypeId clean(TypeId ty) override;
|
||||
TypePackId clean(TypePackId tp) override;
|
||||
};
|
||||
|
||||
// A substitution which replaces generic functions by monomorphic functions
|
||||
struct Instantiation : Substitution
|
||||
{
|
||||
TypeLevel level;
|
||||
ReplaceGenerics replaceGenerics;
|
||||
bool ignoreChildren(TypeId ty) override;
|
||||
bool isDirty(TypeId ty) override;
|
||||
bool isDirty(TypePackId tp) override;
|
||||
TypeId clean(TypeId ty) override;
|
||||
TypePackId clean(TypePackId tp) override;
|
||||
};
|
||||
|
||||
// A substitution which replaces free types by generic types.
|
||||
struct Quantification : Substitution
|
||||
{
|
||||
TypeLevel level;
|
||||
std::vector<TypeId> generics;
|
||||
std::vector<TypePackId> genericPacks;
|
||||
bool isDirty(TypeId ty) override;
|
||||
bool isDirty(TypePackId tp) override;
|
||||
TypeId clean(TypeId ty) override;
|
||||
TypePackId clean(TypePackId tp) override;
|
||||
};
|
||||
|
||||
// A substitution which replaces free types by any
|
||||
struct Anyification : Substitution
|
||||
{
|
||||
TypeId anyType;
|
||||
TypePackId anyTypePack;
|
||||
bool isDirty(TypeId ty) override;
|
||||
bool isDirty(TypePackId tp) override;
|
||||
TypeId clean(TypeId ty) override;
|
||||
TypePackId clean(TypePackId tp) override;
|
||||
};
|
||||
|
||||
// A substitution which replaces the type parameters of a type function by arguments
|
||||
struct ApplyTypeFunction : Substitution
|
||||
{
|
||||
TypeLevel level;
|
||||
bool encounteredForwardedType;
|
||||
std::unordered_map<TypeId, TypeId> arguments;
|
||||
bool isDirty(TypeId ty) override;
|
||||
bool isDirty(TypePackId tp) override;
|
||||
TypeId clean(TypeId ty) override;
|
||||
TypePackId clean(TypePackId tp) override;
|
||||
};
|
||||
|
||||
// All TypeVars are retained via Environment::typeVars. All TypeIds
|
||||
// within a program are borrowed pointers into this set.
|
||||
struct TypeChecker
|
||||
{
|
||||
explicit TypeChecker(ModuleResolver* resolver, InternalErrorReporter* iceHandler);
|
||||
TypeChecker(const TypeChecker&) = delete;
|
||||
TypeChecker& operator=(const TypeChecker&) = delete;
|
||||
|
||||
ModulePtr check(const SourceModule& module, Mode mode, std::optional<ScopePtr> environmentScope = std::nullopt);
|
||||
|
||||
std::vector<std::pair<Location, ScopePtr>> getScopes() const;
|
||||
|
||||
void check(const ScopePtr& scope, const AstStat& statement);
|
||||
void check(const ScopePtr& scope, const AstStatBlock& statement);
|
||||
void check(const ScopePtr& scope, const AstStatIf& statement);
|
||||
void check(const ScopePtr& scope, const AstStatWhile& statement);
|
||||
void check(const ScopePtr& scope, const AstStatRepeat& statement);
|
||||
void check(const ScopePtr& scope, const AstStatReturn& return_);
|
||||
void check(const ScopePtr& scope, const AstStatAssign& assign);
|
||||
void check(const ScopePtr& scope, const AstStatCompoundAssign& assign);
|
||||
void check(const ScopePtr& scope, const AstStatLocal& local);
|
||||
void check(const ScopePtr& scope, const AstStatFor& local);
|
||||
void check(const ScopePtr& scope, const AstStatForIn& forin);
|
||||
void check(const ScopePtr& scope, TypeId ty, const ScopePtr& funScope, const AstStatFunction& function);
|
||||
void check(const ScopePtr& scope, TypeId ty, const ScopePtr& funScope, const AstStatLocalFunction& function);
|
||||
void check(const ScopePtr& scope, const AstStatTypeAlias& typealias, bool forwardDeclare = false);
|
||||
void check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass);
|
||||
void check(const ScopePtr& scope, const AstStatDeclareFunction& declaredFunction);
|
||||
|
||||
void checkBlock(const ScopePtr& scope, const AstStatBlock& statement);
|
||||
void checkBlockTypeAliases(const ScopePtr& scope, std::vector<AstStat*>& sorted);
|
||||
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExpr& expr, std::optional<TypeId> expectedType = std::nullopt);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprLocal& expr);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprGlobal& expr);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprVarargs& expr);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprCall& expr);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprIndexName& expr);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprIndexExpr& expr);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprFunction& expr, std::optional<TypeId> expectedType = std::nullopt);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprTable& expr, std::optional<TypeId> expectedType = std::nullopt);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprUnary& expr);
|
||||
TypeId checkRelationalOperation(
|
||||
const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates = {});
|
||||
TypeId checkBinaryOperation(
|
||||
const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates = {});
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprBinary& expr);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprTypeAssertion& expr);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprError& expr);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprIfElse& expr);
|
||||
|
||||
TypeId checkExprTable(const ScopePtr& scope, const AstExprTable& expr, const std::vector<std::pair<TypeId, TypeId>>& fieldTypes,
|
||||
std::optional<TypeId> expectedType);
|
||||
|
||||
// Returns the type of the lvalue.
|
||||
TypeId checkLValue(const ScopePtr& scope, const AstExpr& expr);
|
||||
|
||||
// Returns both the type of the lvalue and its binding (if the caller wants to mutate the binding).
|
||||
// Note: the binding may be null.
|
||||
std::pair<TypeId, TypeId*> checkLValueBinding(const ScopePtr& scope, const AstExpr& expr);
|
||||
std::pair<TypeId, TypeId*> checkLValueBinding(const ScopePtr& scope, const AstExprLocal& expr);
|
||||
std::pair<TypeId, TypeId*> checkLValueBinding(const ScopePtr& scope, const AstExprGlobal& expr);
|
||||
std::pair<TypeId, TypeId*> checkLValueBinding(const ScopePtr& scope, const AstExprIndexName& expr);
|
||||
std::pair<TypeId, TypeId*> checkLValueBinding(const ScopePtr& scope, const AstExprIndexExpr& expr);
|
||||
|
||||
TypeId checkFunctionName(const ScopePtr& scope, AstExpr& funName);
|
||||
std::pair<TypeId, ScopePtr> checkFunctionSignature(const ScopePtr& scope, int subLevel, const AstExprFunction& expr,
|
||||
std::optional<Location> originalNameLoc, std::optional<TypeId> expectedType);
|
||||
void checkFunctionBody(const ScopePtr& scope, TypeId type, const AstExprFunction& function);
|
||||
|
||||
void checkArgumentList(
|
||||
const ScopePtr& scope, Unifier& state, TypePackId paramPack, TypePackId argPack, const std::vector<Location>& argLocations);
|
||||
|
||||
ExprResult<TypePackId> checkExprPack(const ScopePtr& scope, const AstExpr& expr);
|
||||
ExprResult<TypePackId> checkExprPack(const ScopePtr& scope, const AstExprCall& expr);
|
||||
std::vector<std::optional<TypeId>> getExpectedTypesForCall(const std::vector<TypeId>& overloads, size_t argumentCount, bool selfCall);
|
||||
std::optional<ExprResult<TypePackId>> checkCallOverload(const ScopePtr& scope, const AstExprCall& expr, TypeId fn, TypePackId retPack,
|
||||
TypePackId argPack, TypePack* args, const std::vector<Location>& argLocations, const ExprResult<TypePackId>& argListResult,
|
||||
std::vector<TypeId>& overloadsThatMatchArgCount, std::vector<OverloadErrorEntry>& errors);
|
||||
bool handleSelfCallMismatch(const ScopePtr& scope, const AstExprCall& expr, TypePack* args, const std::vector<Location>& argLocations,
|
||||
const std::vector<OverloadErrorEntry>& errors);
|
||||
ExprResult<TypePackId> reportOverloadResolutionError(const ScopePtr& scope, const AstExprCall& expr, TypePackId retPack, TypePackId argPack,
|
||||
const std::vector<Location>& argLocations, const std::vector<TypeId>& overloads, const std::vector<TypeId>& overloadsThatMatchArgCount,
|
||||
const std::vector<OverloadErrorEntry>& errors);
|
||||
|
||||
ExprResult<TypePackId> checkExprList(const ScopePtr& scope, const Location& location, const AstArray<AstExpr*>& exprs,
|
||||
bool substituteFreeForNil = false, const std::vector<bool>& lhsAnnotations = {},
|
||||
const std::vector<std::optional<TypeId>>& expectedTypes = {});
|
||||
|
||||
static std::optional<AstExpr*> matchRequire(const AstExprCall& call);
|
||||
TypeId checkRequire(const ScopePtr& scope, const ModuleInfo& moduleInfo, const Location& location);
|
||||
|
||||
// Try to infer that the provided type is a table of some sort.
|
||||
// Reports an error if the type is already some kind of non-table.
|
||||
void tablify(TypeId type);
|
||||
|
||||
/** In nonstrict mode, many typevars need to be replaced by any.
|
||||
*/
|
||||
TypeId anyIfNonstrict(TypeId ty) const;
|
||||
|
||||
/** Attempt to unify the types left and right. Treat any failures as type errors
|
||||
* in the final typecheck report.
|
||||
*/
|
||||
bool unify(TypeId left, TypeId right, const Location& location);
|
||||
bool unify(TypePackId left, TypePackId right, const Location& location, CountMismatch::Context ctx = CountMismatch::Context::Arg);
|
||||
|
||||
/** Attempt to unify the types left and right.
|
||||
* If this fails, and the right type can be instantiated, do so and try unification again.
|
||||
*/
|
||||
bool unifyWithInstantiationIfNeeded(const ScopePtr& scope, TypeId left, TypeId right, const Location& location);
|
||||
void unifyWithInstantiationIfNeeded(const ScopePtr& scope, TypeId left, TypeId right, Unifier& state);
|
||||
|
||||
/** Attempt to unify left with right.
|
||||
* If there are errors, undo everything and return the errors.
|
||||
* If there are no errors, commit and return an empty error vector.
|
||||
*/
|
||||
ErrorVec tryUnify(TypeId left, TypeId right, const Location& location);
|
||||
ErrorVec tryUnify(TypePackId left, TypePackId right, const Location& location);
|
||||
|
||||
// Test whether the two type vars unify. Never commits the result.
|
||||
ErrorVec canUnify(TypeId superTy, TypeId subTy, const Location& location);
|
||||
ErrorVec canUnify(TypePackId superTy, TypePackId subTy, const Location& location);
|
||||
|
||||
// Variant that takes a preexisting 'seen' set. We need this in certain cases to avoid infinitely recursing
|
||||
// into cyclic types.
|
||||
ErrorVec canUnify(const std::vector<std::pair<TypeId, TypeId>>& seen, TypeId left, TypeId right, const Location& location);
|
||||
|
||||
std::optional<TypeId> findMetatableEntry(TypeId type, std::string entry, const Location& location);
|
||||
std::optional<TypeId> findTablePropertyRespectingMeta(TypeId lhsType, Name name, const Location& location);
|
||||
|
||||
std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, TypeId type, const Name& name, const Location& location, bool addErrors);
|
||||
|
||||
// Reduces the union to its simplest possible shape.
|
||||
// (A | B) | B | C yields A | B | C
|
||||
std::vector<TypeId> reduceUnion(const std::vector<TypeId>& types);
|
||||
|
||||
std::optional<TypeId> tryStripUnionFromNil(TypeId ty);
|
||||
TypeId stripFromNilAndReport(TypeId ty, const Location& location);
|
||||
|
||||
template<typename Id>
|
||||
ErrorVec tryUnify_(Id left, Id right, const Location& location);
|
||||
|
||||
template<typename Id>
|
||||
ErrorVec canUnify_(Id left, Id right, const Location& location);
|
||||
|
||||
public:
|
||||
/*
|
||||
* Convert monotype into a a polytype, by replacing any metavariables in descendant scopes
|
||||
* by bound generic type variables. This is used to infer that a function is generic.
|
||||
*/
|
||||
TypeId quantify(const ScopePtr& scope, TypeId ty, Location location);
|
||||
|
||||
/*
|
||||
* Convert a polytype into a monotype, by replacing any bound generic types by type metavariables.
|
||||
* This is used to typecheck particular calls to generic functions, and when generic functions
|
||||
* are passed as arguments.
|
||||
*
|
||||
* The "changed" boolean is used to permit us to return the same TypeId in the case that the instantiated type is unchanged.
|
||||
* This is important in certain cases, such as methods on objects, where a table contains a function whose first argument is the table.
|
||||
* Without this property, we can wind up in a situation where a new TypeId is allocated for the outer table. This can cause us to produce
|
||||
* unfortunate types like
|
||||
*
|
||||
* {method: ({method: (<CYCLE>) -> a}) -> a}
|
||||
*
|
||||
*/
|
||||
TypeId instantiate(const ScopePtr& scope, TypeId ty, Location location);
|
||||
// Removed by FFlag::LuauRankNTypes
|
||||
TypePackId DEPRECATED_instantiate(const ScopePtr& scope, TypePackId ty, Location location);
|
||||
|
||||
// Replace any free types or type packs by `any`.
|
||||
// This is used when exporting types from modules, to make sure free types don't leak.
|
||||
TypeId anyify(const ScopePtr& scope, TypeId ty, Location location);
|
||||
TypePackId anyify(const ScopePtr& scope, TypePackId ty, Location location);
|
||||
|
||||
void reportError(const TypeError& error);
|
||||
void reportError(const Location& location, TypeErrorData error);
|
||||
void reportErrors(const ErrorVec& errors);
|
||||
|
||||
[[noreturn]] void ice(const std::string& message, const Location& location);
|
||||
[[noreturn]] void ice(const std::string& message);
|
||||
|
||||
ScopePtr childFunctionScope(const ScopePtr& parent, const Location& location, int subLevel = 0);
|
||||
ScopePtr childScope(const ScopePtr& parent, const Location& location, int subLevel = 0);
|
||||
|
||||
// Wrapper for merge(l, r, toUnion) but without the lambda junk.
|
||||
void merge(RefinementMap& l, const RefinementMap& r);
|
||||
|
||||
private:
|
||||
void prepareErrorsForDisplay(ErrorVec& errVec);
|
||||
void diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData& data);
|
||||
void reportErrorCodeTooComplex(const Location& location);
|
||||
|
||||
private:
|
||||
Unifier mkUnifier(const Location& location);
|
||||
Unifier mkUnifier(const std::vector<std::pair<TypeId, TypeId>>& seen, const Location& location);
|
||||
|
||||
// These functions are only safe to call when we are in the process of typechecking a module.
|
||||
|
||||
// Produce a new free type var.
|
||||
TypeId freshType(const ScopePtr& scope);
|
||||
TypeId freshType(TypeLevel level);
|
||||
TypeId DEPRECATED_freshType(const ScopePtr& scope, bool canBeGeneric = false);
|
||||
TypeId DEPRECATED_freshType(TypeLevel level, bool canBeGeneric = false);
|
||||
|
||||
// Returns nullopt if the predicate filters down the TypeId to 0 options.
|
||||
std::optional<TypeId> filterMap(TypeId type, TypeIdPredicate predicate);
|
||||
|
||||
TypeId unionOfTypes(TypeId a, TypeId b, const Location& location, bool unifyFreeTypes = true);
|
||||
|
||||
// ex
|
||||
// TypeId id = addType(FreeTypeVar());
|
||||
template<typename T>
|
||||
TypeId addType(const T& tv)
|
||||
{
|
||||
return addTV(TypeVar(tv));
|
||||
}
|
||||
|
||||
TypeId addType(const UnionTypeVar& utv);
|
||||
|
||||
TypeId addTV(TypeVar&& tv);
|
||||
|
||||
TypePackId addTypePack(TypePackVar&& tp);
|
||||
TypePackId addTypePack(TypePack&& tp);
|
||||
|
||||
TypePackId addTypePack(const std::vector<TypeId>& ty);
|
||||
TypePackId addTypePack(const std::vector<TypeId>& ty, std::optional<TypePackId> tail);
|
||||
TypePackId addTypePack(std::initializer_list<TypeId>&& ty);
|
||||
TypePackId freshTypePack(const ScopePtr& scope);
|
||||
TypePackId freshTypePack(TypeLevel level);
|
||||
TypePackId DEPRECATED_freshTypePack(const ScopePtr& scope, bool canBeGeneric = false);
|
||||
TypePackId DEPRECATED_freshTypePack(TypeLevel level, bool canBeGeneric = false);
|
||||
|
||||
TypeId resolveType(const ScopePtr& scope, const AstType& annotation, bool canBeGeneric = false);
|
||||
TypePackId resolveTypePack(const ScopePtr& scope, const AstTypeList& types);
|
||||
TypePackId resolveTypePack(const ScopePtr& scope, const AstTypePack& annotation);
|
||||
TypeId instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf, const std::vector<TypeId>& typeParams, const Location& location);
|
||||
|
||||
// Note: `scope` must be a fresh scope.
|
||||
std::pair<std::vector<TypeId>, std::vector<TypePackId>> createGenericTypes(
|
||||
const ScopePtr& scope, const AstNode& node, const AstArray<AstName>& genericNames, const AstArray<AstName>& genericPackNames);
|
||||
|
||||
public:
|
||||
ErrorVec resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense);
|
||||
|
||||
private:
|
||||
std::optional<TypeId> resolveLValue(const ScopePtr& scope, const LValue& lvalue);
|
||||
std::optional<TypeId> resolveLValue(const RefinementMap& refis, const ScopePtr& scope, const LValue& lvalue);
|
||||
|
||||
void resolve(const PredicateVec& predicates, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr = false);
|
||||
void resolve(const Predicate& predicate, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr);
|
||||
void resolve(const TruthyPredicate& truthyP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr);
|
||||
void resolve(const AndPredicate& andP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||
void resolve(const OrPredicate& orP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||
void resolve(const IsAPredicate& isaP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||
void resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||
void DEPRECATED_resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||
void resolve(const EqPredicate& eqP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||
|
||||
bool isNonstrictMode() const;
|
||||
|
||||
public:
|
||||
/** Extract the types in a type pack, given the assumption that the pack must have some exact length.
|
||||
* TypePacks can have free tails, which means that inference has not yet determined the length of the pack.
|
||||
* Calling this function means submitting evidence that the pack must have the length provided.
|
||||
* If the pack is known not to have the correct length, an error will be reported.
|
||||
* The return vector is always of the exact requested length. In the event that the pack's length does
|
||||
* not match up, excess TypeIds will be ErrorTypeVars.
|
||||
*/
|
||||
std::vector<TypeId> unTypePack(const ScopePtr& scope, TypePackId pack, size_t expectedLength, const Location& location);
|
||||
|
||||
TypeArena globalTypes;
|
||||
|
||||
ModuleResolver* resolver;
|
||||
SourceModule globalNames; // names for symbols entered into globalScope
|
||||
ScopePtr globalScope; // shared by all modules
|
||||
ModulePtr currentModule;
|
||||
ModuleName currentModuleName;
|
||||
|
||||
Instantiation instantiation;
|
||||
Quantification quantification;
|
||||
Anyification anyification;
|
||||
ApplyTypeFunction applyTypeFunction;
|
||||
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope;
|
||||
InternalErrorReporter* iceHandler;
|
||||
|
||||
public:
|
||||
const TypeId nilType;
|
||||
const TypeId numberType;
|
||||
const TypeId stringType;
|
||||
const TypeId booleanType;
|
||||
const TypeId threadType;
|
||||
const TypeId anyType;
|
||||
|
||||
const TypeId errorType;
|
||||
const TypeId optionalNumberType;
|
||||
|
||||
const TypePackId anyTypePack;
|
||||
const TypePackId errorTypePack;
|
||||
|
||||
private:
|
||||
int checkRecursionCount = 0;
|
||||
int recursionCount = 0;
|
||||
};
|
||||
|
||||
struct Binding
|
||||
{
|
||||
TypeId typeId;
|
||||
Location location;
|
||||
bool deprecated = false;
|
||||
std::string deprecatedSuggestion;
|
||||
std::optional<std::string> documentationSymbol;
|
||||
};
|
||||
|
||||
struct Scope
|
||||
{
|
||||
explicit Scope(TypePackId returnType); // root scope
|
||||
explicit Scope(const ScopePtr& parent, int subLevel = 0); // child scope. Parent must not be nullptr.
|
||||
|
||||
const ScopePtr parent; // null for the root
|
||||
std::unordered_map<Symbol, Binding> bindings;
|
||||
TypePackId returnType;
|
||||
bool breakOk = false;
|
||||
std::optional<TypePackId> varargPack;
|
||||
|
||||
TypeLevel level;
|
||||
|
||||
std::unordered_map<Name, TypeFun> exportedTypeBindings;
|
||||
std::unordered_map<Name, TypeFun> privateTypeBindings;
|
||||
std::unordered_map<Name, Location> typeAliasLocations;
|
||||
|
||||
std::unordered_map<Name, std::unordered_map<Name, TypeFun>> importedTypeBindings;
|
||||
|
||||
std::optional<TypeId> lookup(const Symbol& name);
|
||||
|
||||
std::optional<TypeFun> lookupType(const Name& name);
|
||||
std::optional<TypeFun> lookupImportedType(const Name& moduleAlias, const Name& name);
|
||||
|
||||
std::unordered_map<Name, TypePackId> privateTypePackBindings;
|
||||
std::optional<TypePackId> lookupPack(const Name& name);
|
||||
|
||||
// WARNING: This function linearly scans for a string key of equal value! It is thus O(n**2)
|
||||
std::optional<Binding> linearSearchForBinding(const std::string& name, bool traverseScopeChain = true);
|
||||
|
||||
RefinementMap refinements;
|
||||
|
||||
// For mutually recursive type aliases, it's important that
|
||||
// they use the same types for the same names.
|
||||
// For instance, in `type Tree<T> { data: T, children: Forest<T> } type Forest<T> = {Tree<T>}`
|
||||
// we need that the generic type `T` in both cases is the same, so we use a cache.
|
||||
std::unordered_map<Name, TypeId> typeAliasParameters;
|
||||
};
|
||||
|
||||
// Unit test hook
|
||||
void setPrintLine(void (*pl)(const std::string& s));
|
||||
void resetPrintLine();
|
||||
|
||||
} // namespace Luau
|
161
Analysis/include/Luau/TypePack.h
Normal file
161
Analysis/include/Luau/TypePack.h
Normal file
|
@ -0,0 +1,161 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/Unifiable.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <optional>
|
||||
#include <set>
|
||||
|
||||
LUAU_FASTFLAG(LuauAddMissingFollow)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct TypeArena;
|
||||
|
||||
struct TypePack;
|
||||
struct VariadicTypePack;
|
||||
|
||||
struct TypePackVar;
|
||||
|
||||
using TypePackId = const TypePackVar*;
|
||||
using FreeTypePack = Unifiable::Free;
|
||||
using BoundTypePack = Unifiable::Bound<TypePackId>;
|
||||
using GenericTypePack = Unifiable::Generic;
|
||||
using TypePackVariant = Unifiable::Variant<TypePackId, TypePack, VariadicTypePack>;
|
||||
|
||||
/* A TypePack is a rope-like string of TypeIds. We use this structure to encode
|
||||
* notions like packs of unknown length and packs of any length, as well as more
|
||||
* nuanced compositions like "a pack which is a number prepended to this other pack,"
|
||||
* or "a pack that is 2 numbers followed by any number of any other types."
|
||||
*/
|
||||
struct TypePack
|
||||
{
|
||||
std::vector<TypeId> head;
|
||||
std::optional<TypePackId> tail;
|
||||
};
|
||||
|
||||
struct VariadicTypePack
|
||||
{
|
||||
TypeId ty;
|
||||
};
|
||||
|
||||
struct TypePackVar
|
||||
{
|
||||
explicit TypePackVar(const TypePackVariant& ty);
|
||||
explicit TypePackVar(TypePackVariant&& ty);
|
||||
TypePackVar(TypePackVariant&& ty, bool persistent);
|
||||
bool operator==(const TypePackVar& rhs) const;
|
||||
TypePackVar& operator=(TypePackVariant&& tp);
|
||||
|
||||
TypePackVariant ty;
|
||||
bool persistent = false;
|
||||
|
||||
// Pointer to the type arena that allocated this type.
|
||||
// Do not depend on the value of this under any circumstances. This is for
|
||||
// debugging purposes only. This is only set in debug builds; it is nullptr
|
||||
// in all other environments.
|
||||
TypeArena* owningArena = nullptr;
|
||||
};
|
||||
|
||||
/* Walk the set of TypeIds in a TypePack.
|
||||
*
|
||||
* Like TypeVars, individual TypePacks can be free, generic, or any.
|
||||
*
|
||||
* We afford the ability to work with these kinds of packs by giving the
|
||||
* iterator a .tail() property that yields the tail-most TypePack in the
|
||||
* rope.
|
||||
*
|
||||
* It is very commonplace to want to walk each type in a pack, then handle
|
||||
* the tail specially. eg when checking parameters, it might be the case
|
||||
* that the parameter pack ends with a VariadicTypePack. In this case, we
|
||||
* want to allow any number of extra arguments.
|
||||
*
|
||||
* The iterator obtained by calling end(tp) does not have a .tail(), but is
|
||||
* equivalent with end(tp2) for any two type packs.
|
||||
*/
|
||||
struct TypePackIterator
|
||||
{
|
||||
using value_type = Luau::TypeId;
|
||||
using pointer = value_type*;
|
||||
using reference = value_type&;
|
||||
using difference_type = size_t;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
TypePackIterator() = default;
|
||||
explicit TypePackIterator(TypePackId tp);
|
||||
|
||||
TypePackIterator& operator++();
|
||||
TypePackIterator operator++(int);
|
||||
bool operator!=(const TypePackIterator& rhs);
|
||||
bool operator==(const TypePackIterator& rhs);
|
||||
|
||||
const TypeId& operator*();
|
||||
|
||||
/** Return the tail of a TypePack.
|
||||
* This may *only* be called on an iterator that has been incremented to the end.
|
||||
* Returns nullopt if the pack has fixed length.
|
||||
*/
|
||||
std::optional<TypePackId> tail();
|
||||
|
||||
friend TypePackIterator end(TypePackId tp);
|
||||
|
||||
private:
|
||||
TypePackId currentTypePack = nullptr;
|
||||
const TypePack* tp = nullptr;
|
||||
size_t currentIndex = 0;
|
||||
};
|
||||
|
||||
TypePackIterator begin(TypePackId tp);
|
||||
TypePackIterator end(TypePackId tp);
|
||||
|
||||
using SeenSet = std::set<std::pair<void*, void*>>;
|
||||
|
||||
bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs);
|
||||
|
||||
TypePackId follow(TypePackId tp);
|
||||
|
||||
size_t size(const TypePackId tp);
|
||||
size_t size(const TypePack& tp);
|
||||
std::optional<TypeId> first(TypePackId tp);
|
||||
|
||||
TypePackVar* asMutable(TypePackId tp);
|
||||
TypePack* asMutable(const TypePack* tp);
|
||||
|
||||
template<typename T>
|
||||
const T* get(TypePackId tp)
|
||||
{
|
||||
if (FFlag::LuauAddMissingFollow)
|
||||
{
|
||||
LUAU_ASSERT(tp);
|
||||
|
||||
if constexpr (!std::is_same_v<T, BoundTypePack>)
|
||||
LUAU_ASSERT(get_if<BoundTypePack>(&tp->ty) == nullptr);
|
||||
}
|
||||
|
||||
return get_if<T>(&(tp->ty));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* getMutable(TypePackId tp)
|
||||
{
|
||||
if (FFlag::LuauAddMissingFollow)
|
||||
{
|
||||
LUAU_ASSERT(tp);
|
||||
|
||||
if constexpr (!std::is_same_v<T, BoundTypePack>)
|
||||
LUAU_ASSERT(get_if<BoundTypePack>(&tp->ty) == nullptr);
|
||||
}
|
||||
|
||||
return get_if<T>(&(asMutable(tp)->ty));
|
||||
}
|
||||
|
||||
/// Returns true if the type pack is known to be empty (no types in the head and no/an empty tail).
|
||||
bool isEmpty(TypePackId tp);
|
||||
|
||||
/// Flattens out a type pack. Also returns a valid TypePackId tail if the type pack's full size is not known
|
||||
std::pair<std::vector<TypeId>, std::optional<TypePackId>> flatten(TypePackId tp);
|
||||
|
||||
} // namespace Luau
|
19
Analysis/include/Luau/TypeUtils.h
Normal file
19
Analysis/include/Luau/TypeUtils.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
using ScopePtr = std::shared_ptr<struct Scope>;
|
||||
|
||||
std::optional<TypeId> findMetatableEntry(ErrorVec& errors, const ScopePtr& globalScope, TypeId type, std::string entry, Location location);
|
||||
std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, const ScopePtr& globalScope, TypeId ty, Name name, Location location);
|
||||
|
||||
} // namespace Luau
|
531
Analysis/include/Luau/TypeVar.h
Normal file
531
Analysis/include/Luau/TypeVar.h
Normal file
|
@ -0,0 +1,531 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Predicate.h"
|
||||
#include "Luau/Unifiable.h"
|
||||
#include "Luau/Variant.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
LUAU_FASTINT(LuauTableTypeMaximumStringifierLength)
|
||||
LUAU_FASTINT(LuauTypeMaximumStringifierLength)
|
||||
LUAU_FASTFLAG(LuauAddMissingFollow)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct TypeArena;
|
||||
|
||||
/**
|
||||
* There are three kinds of type variables:
|
||||
* - `Free` variables are metavariables, which stand for unconstrained types.
|
||||
* - `Bound` variables are metavariables that have an equality constraint.
|
||||
* - `Generic` variables are type variables that are bound by generic functions.
|
||||
*
|
||||
* For example, consider the program:
|
||||
* ```
|
||||
* function(x, y) x.f = y end
|
||||
* ```
|
||||
* To typecheck this, we first introduce free metavariables for the types of `x` and `y`:
|
||||
* ```
|
||||
* function(x: X, y: Y) x.f = y end
|
||||
* ```
|
||||
* Type inference for the function body then produces the constraint:
|
||||
* ```
|
||||
* X = { f: Y }
|
||||
* ```
|
||||
* so `X` is now a bound metavariable. We can then quantify the metavariables,
|
||||
* which replaces any bound metavariables by their binding, and free metavariables
|
||||
* by bound generic variables:
|
||||
* ```
|
||||
* function<a>(x: { f: a }, y: a) x.f = y end
|
||||
* ```
|
||||
*/
|
||||
|
||||
// So... why `const T*` here rather than `T*`?
|
||||
// It's because we've had problems caused by the type graph being mutated
|
||||
// in ways it shouldn't be, for example mutating types from other modules.
|
||||
// To try to control this, we make the use of types immutable by default,
|
||||
// then provide explicit mutable access via getMutable and asMutable.
|
||||
// This means we can grep for all the places we're mutating the type graph,
|
||||
// and it makes it possible to provide other APIs (e.g. the txn log)
|
||||
// which control mutable access to the type graph.
|
||||
struct TypePackVar;
|
||||
using TypePackId = const TypePackVar*;
|
||||
|
||||
// TODO: rename to Type? CLI-39100
|
||||
struct TypeVar;
|
||||
|
||||
// Should never be null
|
||||
using TypeId = const TypeVar*;
|
||||
|
||||
using Name = std::string;
|
||||
|
||||
// A free type var is one whose exact shape has yet to be fully determined.
|
||||
using FreeTypeVar = Unifiable::Free;
|
||||
|
||||
// When a free type var is unified with any other, it is then "bound"
|
||||
// to that type var, indicating that the two types are actually the same type.
|
||||
using BoundTypeVar = Unifiable::Bound<TypeId>;
|
||||
|
||||
using GenericTypeVar = Unifiable::Generic;
|
||||
|
||||
using Tags = std::vector<std::string>;
|
||||
|
||||
using ModuleName = std::string;
|
||||
|
||||
struct PrimitiveTypeVar
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
NilType, // ObjC #defines Nil :(
|
||||
Boolean,
|
||||
Number,
|
||||
String,
|
||||
Thread,
|
||||
};
|
||||
|
||||
Type type;
|
||||
std::optional<TypeId> metatable; // string has a metatable
|
||||
|
||||
explicit PrimitiveTypeVar(Type type)
|
||||
: type(type)
|
||||
{
|
||||
}
|
||||
|
||||
explicit PrimitiveTypeVar(Type type, TypeId metatable)
|
||||
: type(type)
|
||||
, metatable(metatable)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct FunctionArgument
|
||||
{
|
||||
Name name;
|
||||
Location location;
|
||||
};
|
||||
|
||||
struct FunctionDefinition
|
||||
{
|
||||
std::optional<ModuleName> definitionModuleName;
|
||||
Location definitionLocation;
|
||||
std::optional<Location> varargLocation;
|
||||
Location originalNameLocation;
|
||||
};
|
||||
|
||||
// TODO: Come up with a better name.
|
||||
// TODO: Do we actually need this? We'll find out later if we can delete this.
|
||||
// Does not exactly belong in TypeVar.h, but this is the only way to appease the compiler.
|
||||
template<typename T>
|
||||
struct ExprResult
|
||||
{
|
||||
T type;
|
||||
PredicateVec predicates;
|
||||
};
|
||||
|
||||
using MagicFunction = std::function<std::optional<ExprResult<TypePackId>>(
|
||||
struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, ExprResult<TypePackId>)>;
|
||||
|
||||
struct FunctionTypeVar
|
||||
{
|
||||
// Global monomorphic function
|
||||
FunctionTypeVar(TypePackId argTypes, TypePackId retType, std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
||||
|
||||
// Global polymorphic function
|
||||
FunctionTypeVar(std::vector<TypeId> generics, std::vector<TypePackId> genericPacks, TypePackId argTypes, TypePackId retType,
|
||||
std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
||||
|
||||
// Local monomorphic function
|
||||
FunctionTypeVar(TypeLevel level, TypePackId argTypes, TypePackId retType, std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
||||
|
||||
// Local polymorphic function
|
||||
FunctionTypeVar(TypeLevel level, std::vector<TypeId> generics, std::vector<TypePackId> genericPacks, TypePackId argTypes, TypePackId retType,
|
||||
std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
||||
|
||||
TypeLevel level;
|
||||
/// These should all be generic
|
||||
std::vector<TypeId> generics;
|
||||
std::vector<TypePackId> genericPacks;
|
||||
TypePackId argTypes;
|
||||
std::vector<std::optional<FunctionArgument>> argNames;
|
||||
TypePackId retType;
|
||||
std::optional<FunctionDefinition> definition;
|
||||
MagicFunction magicFunction = nullptr; // Function pointer, can be nullptr.
|
||||
bool hasSelf;
|
||||
Tags tags;
|
||||
};
|
||||
|
||||
enum class TableState
|
||||
{
|
||||
// Sealed tables have an exact, known shape
|
||||
Sealed,
|
||||
|
||||
// An unsealed table can have extra properties added to it
|
||||
Unsealed,
|
||||
|
||||
// Tables which are not yet fully understood. We are still in the process of learning its shape.
|
||||
Free,
|
||||
|
||||
// A table which is a generic parameter to a function. We know that certain properties are required,
|
||||
// but we don't care about the full shape.
|
||||
Generic,
|
||||
};
|
||||
|
||||
struct TableIndexer
|
||||
{
|
||||
TableIndexer(TypeId indexType, TypeId indexResultType)
|
||||
: indexType(indexType)
|
||||
, indexResultType(indexResultType)
|
||||
{
|
||||
}
|
||||
|
||||
TypeId indexType;
|
||||
TypeId indexResultType;
|
||||
};
|
||||
|
||||
struct Property
|
||||
{
|
||||
TypeId type;
|
||||
bool deprecated = false;
|
||||
std::string deprecatedSuggestion;
|
||||
std::optional<Location> location = std::nullopt;
|
||||
Tags tags;
|
||||
std::optional<std::string> documentationSymbol;
|
||||
};
|
||||
|
||||
struct TableTypeVar
|
||||
{
|
||||
// We choose std::map over unordered_map here just because we have unit tests that compare
|
||||
// textual outputs. I don't want to spend the effort making them resilient in the case where
|
||||
// random events cause the iteration order of the map elements to change.
|
||||
// If this shows up in a profile, we can revisit it.
|
||||
using Props = std::map<Name, Property>;
|
||||
|
||||
TableTypeVar() = default;
|
||||
explicit TableTypeVar(TableState state, TypeLevel level);
|
||||
TableTypeVar(const Props& props, const std::optional<TableIndexer>& indexer, TypeLevel level, TableState state = TableState::Unsealed);
|
||||
|
||||
Props props;
|
||||
std::optional<TableIndexer> indexer;
|
||||
|
||||
TableState state = TableState::Unsealed;
|
||||
TypeLevel level;
|
||||
std::optional<std::string> name;
|
||||
|
||||
// Sometimes we throw a type on a name to make for nicer error messages, but without creating any entry in the type namespace
|
||||
// We need to know which is which when we stringify types.
|
||||
std::optional<std::string> syntheticName;
|
||||
|
||||
std::map<Name, Location> methodDefinitionLocations;
|
||||
std::vector<TypeId> instantiatedTypeParams;
|
||||
ModuleName definitionModuleName;
|
||||
|
||||
std::optional<TypeId> boundTo;
|
||||
Tags tags;
|
||||
};
|
||||
|
||||
// Represents a metatable attached to a table typevar. Somewhat analogous to a bound typevar.
|
||||
struct MetatableTypeVar
|
||||
{
|
||||
// Always points to a TableTypeVar.
|
||||
TypeId table;
|
||||
// Always points to either a TableTypeVar or a MetatableTypeVar.
|
||||
TypeId metatable;
|
||||
|
||||
std::optional<std::string> syntheticName;
|
||||
};
|
||||
|
||||
// Custom userdata of a class type
|
||||
struct ClassUserData
|
||||
{
|
||||
virtual ~ClassUserData() {}
|
||||
};
|
||||
|
||||
/** The type of a class.
|
||||
*
|
||||
* Classes behave like tables in many ways, but there are some important differences:
|
||||
*
|
||||
* The properties of a class are always exactly known.
|
||||
* Classes optionally have a parent class.
|
||||
* Two different classes that share the same properties are nevertheless distinct and mutually incompatible.
|
||||
*/
|
||||
struct ClassTypeVar
|
||||
{
|
||||
using Props = TableTypeVar::Props;
|
||||
|
||||
Name name;
|
||||
Props props;
|
||||
std::optional<TypeId> parent;
|
||||
std::optional<TypeId> metatable; // metaclass?
|
||||
Tags tags;
|
||||
std::shared_ptr<ClassUserData> userData;
|
||||
|
||||
ClassTypeVar(
|
||||
Name name, Props props, std::optional<TypeId> parent, std::optional<TypeId> metatable, Tags tags, std::shared_ptr<ClassUserData> userData)
|
||||
: name(name)
|
||||
, props(props)
|
||||
, parent(parent)
|
||||
, metatable(metatable)
|
||||
, tags(tags)
|
||||
, userData(userData)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct TypeFun
|
||||
{
|
||||
/// These should all be generic
|
||||
std::vector<TypeId> typeParams;
|
||||
|
||||
/** The underlying type.
|
||||
*
|
||||
* WARNING! This is not safe to use as a type if typeParams is not empty!!
|
||||
* You must first use TypeChecker::instantiateTypeFun to turn it into a real type.
|
||||
*/
|
||||
TypeId type;
|
||||
};
|
||||
|
||||
// Anything! All static checking is off.
|
||||
struct AnyTypeVar
|
||||
{
|
||||
};
|
||||
|
||||
struct UnionTypeVar
|
||||
{
|
||||
std::vector<TypeId> options;
|
||||
};
|
||||
|
||||
struct IntersectionTypeVar
|
||||
{
|
||||
std::vector<TypeId> parts;
|
||||
};
|
||||
|
||||
struct LazyTypeVar
|
||||
{
|
||||
std::function<TypeId()> thunk;
|
||||
};
|
||||
|
||||
using ErrorTypeVar = Unifiable::Error;
|
||||
|
||||
using TypeVariant = Unifiable::Variant<TypeId, PrimitiveTypeVar, FunctionTypeVar, TableTypeVar, MetatableTypeVar, ClassTypeVar, AnyTypeVar,
|
||||
UnionTypeVar, IntersectionTypeVar, LazyTypeVar>;
|
||||
|
||||
struct TypeVar final
|
||||
{
|
||||
explicit TypeVar(const TypeVariant& ty)
|
||||
: ty(ty)
|
||||
{
|
||||
}
|
||||
|
||||
explicit TypeVar(TypeVariant&& ty)
|
||||
: ty(std::move(ty))
|
||||
{
|
||||
}
|
||||
|
||||
TypeVar(const TypeVariant& ty, bool persistent)
|
||||
: ty(ty)
|
||||
, persistent(persistent)
|
||||
{
|
||||
}
|
||||
|
||||
TypeVariant ty;
|
||||
|
||||
// Kludge: A persistent TypeVar is one that belongs to the global scope.
|
||||
// Global type bindings are immutable but are reused many times.
|
||||
// Persistent TypeVars do not get cloned.
|
||||
bool persistent = false;
|
||||
|
||||
std::optional<std::string> documentationSymbol;
|
||||
|
||||
// Pointer to the type arena that allocated this type.
|
||||
// Do not depend on the value of this under any circumstances. This is for
|
||||
// debugging purposes only. This is only set in debug builds; it is nullptr
|
||||
// in all other environments.
|
||||
TypeArena* owningArena = nullptr;
|
||||
|
||||
bool operator==(const TypeVar& rhs) const;
|
||||
bool operator!=(const TypeVar& rhs) const;
|
||||
|
||||
TypeVar& operator=(const TypeVariant& rhs);
|
||||
TypeVar& operator=(TypeVariant&& rhs);
|
||||
};
|
||||
|
||||
using SeenSet = std::set<std::pair<void*, void*>>;
|
||||
bool areEqual(SeenSet& seen, const TypeVar& lhs, const TypeVar& rhs);
|
||||
|
||||
// Follow BoundTypeVars until we get to something real
|
||||
TypeId follow(TypeId t);
|
||||
|
||||
std::vector<TypeId> flattenIntersection(TypeId ty);
|
||||
|
||||
bool isPrim(TypeId ty, PrimitiveTypeVar::Type primType);
|
||||
bool isNil(TypeId ty);
|
||||
bool isBoolean(TypeId ty);
|
||||
bool isNumber(TypeId ty);
|
||||
bool isString(TypeId ty);
|
||||
bool isThread(TypeId ty);
|
||||
bool isOptional(TypeId ty);
|
||||
bool isTableIntersection(TypeId ty);
|
||||
bool isOverloadedFunction(TypeId ty);
|
||||
|
||||
std::optional<TypeId> getMetatable(TypeId type);
|
||||
TableTypeVar* getMutableTableType(TypeId type);
|
||||
const TableTypeVar* getTableType(TypeId type);
|
||||
|
||||
// If the type has a name, return that. Else if it has a synthetic name, return that.
|
||||
// Returns nullptr if the type has no name.
|
||||
const std::string* getName(TypeId type);
|
||||
|
||||
// Checks whether a union contains all types of another union.
|
||||
bool isSubset(const UnionTypeVar& super, const UnionTypeVar& sub);
|
||||
|
||||
// Checks if a type conains generic type binders
|
||||
bool isGeneric(const TypeId ty);
|
||||
|
||||
// Checks if a type may be instantiated to one containing generic type binders
|
||||
bool maybeGeneric(const TypeId ty);
|
||||
|
||||
struct SingletonTypes
|
||||
{
|
||||
const TypeId nilType = &nilType_;
|
||||
const TypeId numberType = &numberType_;
|
||||
const TypeId stringType = &stringType_;
|
||||
const TypeId booleanType = &booleanType_;
|
||||
const TypeId threadType = &threadType_;
|
||||
const TypeId anyType = &anyType_;
|
||||
const TypeId errorType = &errorType_;
|
||||
|
||||
SingletonTypes();
|
||||
SingletonTypes(const SingletonTypes&) = delete;
|
||||
void operator=(const SingletonTypes&) = delete;
|
||||
|
||||
private:
|
||||
std::unique_ptr<struct TypeArena> arena;
|
||||
TypeVar nilType_;
|
||||
TypeVar numberType_;
|
||||
TypeVar stringType_;
|
||||
TypeVar booleanType_;
|
||||
TypeVar threadType_;
|
||||
TypeVar anyType_;
|
||||
TypeVar errorType_;
|
||||
|
||||
TypeId makeStringMetatable();
|
||||
};
|
||||
|
||||
extern SingletonTypes singletonTypes;
|
||||
|
||||
void persist(TypeId ty);
|
||||
void persist(TypePackId tp);
|
||||
|
||||
struct ToDotOptions
|
||||
{
|
||||
bool showPointers = true; // Show pointer value in the node label
|
||||
bool duplicatePrimitives = true; // Display primitive types and 'any' as separate nodes
|
||||
};
|
||||
|
||||
std::string toDot(TypeId ty, const ToDotOptions& opts);
|
||||
std::string toDot(TypePackId tp, const ToDotOptions& opts);
|
||||
|
||||
std::string toDot(TypeId ty);
|
||||
std::string toDot(TypePackId tp);
|
||||
|
||||
void dumpDot(TypeId ty);
|
||||
void dumpDot(TypePackId tp);
|
||||
|
||||
const TypeLevel* getLevel(TypeId ty);
|
||||
TypeLevel* getMutableLevel(TypeId ty);
|
||||
|
||||
const Property* lookupClassProp(const ClassTypeVar* cls, const Name& name);
|
||||
bool isSubclass(const ClassTypeVar* cls, const ClassTypeVar* parent);
|
||||
|
||||
bool hasGeneric(TypeId ty);
|
||||
bool hasGeneric(TypePackId tp);
|
||||
|
||||
TypeVar* asMutable(TypeId ty);
|
||||
|
||||
template<typename T>
|
||||
const T* get(TypeId tv)
|
||||
{
|
||||
if (FFlag::LuauAddMissingFollow)
|
||||
{
|
||||
LUAU_ASSERT(tv);
|
||||
|
||||
if constexpr (!std::is_same_v<T, BoundTypeVar>)
|
||||
LUAU_ASSERT(get_if<BoundTypeVar>(&tv->ty) == nullptr);
|
||||
}
|
||||
|
||||
return get_if<T>(&tv->ty);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* getMutable(TypeId tv)
|
||||
{
|
||||
if (FFlag::LuauAddMissingFollow)
|
||||
{
|
||||
LUAU_ASSERT(tv);
|
||||
|
||||
if constexpr (!std::is_same_v<T, BoundTypeVar>)
|
||||
LUAU_ASSERT(get_if<BoundTypeVar>(&tv->ty) == nullptr);
|
||||
}
|
||||
|
||||
return get_if<T>(&asMutable(tv)->ty);
|
||||
}
|
||||
|
||||
/* Traverses the UnionTypeVar yielding each TypeId.
|
||||
* If the iterator encounters a nested UnionTypeVar, it will instead yield each TypeId within.
|
||||
*
|
||||
* Beware: the iterator does not currently filter for unique TypeIds. This may change in the future.
|
||||
*/
|
||||
struct UnionTypeVarIterator
|
||||
{
|
||||
using value_type = Luau::TypeId;
|
||||
using pointer = value_type*;
|
||||
using reference = value_type&;
|
||||
using difference_type = size_t;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
explicit UnionTypeVarIterator(const UnionTypeVar* utv);
|
||||
|
||||
UnionTypeVarIterator& operator++();
|
||||
UnionTypeVarIterator operator++(int);
|
||||
bool operator!=(const UnionTypeVarIterator& rhs);
|
||||
bool operator==(const UnionTypeVarIterator& rhs);
|
||||
|
||||
const TypeId& operator*();
|
||||
|
||||
friend UnionTypeVarIterator end(const UnionTypeVar* utv);
|
||||
|
||||
private:
|
||||
UnionTypeVarIterator() = default;
|
||||
|
||||
// (UnionTypeVar* utv, size_t currentIndex)
|
||||
using SavedIterInfo = std::pair<const UnionTypeVar*, size_t>;
|
||||
|
||||
std::deque<SavedIterInfo> stack;
|
||||
std::unordered_set<const UnionTypeVar*> seen; // Only needed to protect the iterator from hanging the thread.
|
||||
|
||||
void advance();
|
||||
void descend();
|
||||
};
|
||||
|
||||
UnionTypeVarIterator begin(const UnionTypeVar* utv);
|
||||
UnionTypeVarIterator end(const UnionTypeVar* utv);
|
||||
|
||||
using TypeIdPredicate = std::function<std::optional<TypeId>(TypeId)>;
|
||||
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate);
|
||||
|
||||
// TEMP: Clip this prototype with FFlag::LuauStringMetatable
|
||||
std::optional<ExprResult<TypePackId>> magicFunctionFormat(
|
||||
struct TypeChecker& typechecker, const std::shared_ptr<struct Scope>& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult);
|
||||
|
||||
} // namespace Luau
|
134
Analysis/include/Luau/TypedAllocator.h
Normal file
134
Analysis/include/Luau/TypedAllocator.h
Normal file
|
@ -0,0 +1,134 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
void* pagedAllocate(size_t size);
|
||||
void pagedDeallocate(void* ptr);
|
||||
void pagedFreeze(void* ptr, size_t size);
|
||||
void pagedUnfreeze(void* ptr, size_t size);
|
||||
|
||||
template<typename T>
|
||||
class TypedAllocator
|
||||
{
|
||||
public:
|
||||
TypedAllocator()
|
||||
{
|
||||
appendBlock();
|
||||
}
|
||||
|
||||
~TypedAllocator()
|
||||
{
|
||||
if (frozen)
|
||||
unfreeze();
|
||||
free();
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
T* allocate(Args&&... args)
|
||||
{
|
||||
LUAU_ASSERT(!frozen);
|
||||
|
||||
if (currentBlockSize >= kBlockSize)
|
||||
{
|
||||
LUAU_ASSERT(currentBlockSize == kBlockSize);
|
||||
appendBlock();
|
||||
}
|
||||
|
||||
T* block = stuff.back();
|
||||
T* res = block + currentBlockSize;
|
||||
new (res) T(std::forward<Args&&...>(args...));
|
||||
++currentBlockSize;
|
||||
return res;
|
||||
}
|
||||
|
||||
bool contains(const T* ptr) const
|
||||
{
|
||||
for (T* block : stuff)
|
||||
if (ptr >= block && ptr < block + kBlockSize)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return stuff.size() == 1 && currentBlockSize == 0;
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return kBlockSize * (stuff.size() - 1) + currentBlockSize;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
if (frozen)
|
||||
unfreeze();
|
||||
free();
|
||||
appendBlock();
|
||||
}
|
||||
|
||||
void freeze()
|
||||
{
|
||||
for (T* block : stuff)
|
||||
pagedFreeze(block, kBlockSizeBytes);
|
||||
frozen = true;
|
||||
}
|
||||
|
||||
void unfreeze()
|
||||
{
|
||||
for (T* block : stuff)
|
||||
pagedUnfreeze(block, kBlockSizeBytes);
|
||||
frozen = false;
|
||||
}
|
||||
|
||||
bool isFrozen()
|
||||
{
|
||||
return frozen;
|
||||
}
|
||||
|
||||
private:
|
||||
void free()
|
||||
{
|
||||
LUAU_ASSERT(!frozen);
|
||||
|
||||
for (T* block : stuff)
|
||||
{
|
||||
size_t blockSize = (block == stuff.back()) ? currentBlockSize : kBlockSize;
|
||||
|
||||
for (size_t i = 0; i < blockSize; ++i)
|
||||
block[i].~T();
|
||||
|
||||
pagedDeallocate(block);
|
||||
}
|
||||
|
||||
stuff.clear();
|
||||
currentBlockSize = 0;
|
||||
}
|
||||
|
||||
void appendBlock()
|
||||
{
|
||||
void* block = pagedAllocate(kBlockSizeBytes);
|
||||
if (!block)
|
||||
throw std::bad_alloc();
|
||||
|
||||
stuff.emplace_back(static_cast<T*>(block));
|
||||
currentBlockSize = 0;
|
||||
}
|
||||
|
||||
bool frozen = false;
|
||||
std::vector<T*> stuff;
|
||||
size_t currentBlockSize = 0;
|
||||
|
||||
static constexpr size_t kBlockSizeBytes = 32768;
|
||||
static constexpr size_t kBlockSize = kBlockSizeBytes / sizeof(T);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
123
Analysis/include/Luau/Unifiable.h
Normal file
123
Analysis/include/Luau/Unifiable.h
Normal file
|
@ -0,0 +1,123 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
/**
|
||||
* The 'level' of a TypeVar is an indirect way to talk about the scope that it 'belongs' too.
|
||||
* To start, read http://okmij.org/ftp/ML/generalization.html
|
||||
*
|
||||
* We extend the idea by adding a "sub-level" which helps us to differentiate sibling scopes
|
||||
* within a single larger scope.
|
||||
*
|
||||
* We need this because we try to prototype functions and add them to the type environment before
|
||||
* we check the function bodies. This allows us to properly typecheck many scenarios where there
|
||||
* is no single good order in which to typecheck a program.
|
||||
*/
|
||||
struct TypeLevel
|
||||
{
|
||||
int level = 0;
|
||||
int subLevel = 0;
|
||||
|
||||
// Returns true if the typelevel "this" is "bigger" than rhs
|
||||
bool subsumes(const TypeLevel& rhs) const
|
||||
{
|
||||
if (level < rhs.level)
|
||||
return true;
|
||||
if (level > rhs.level)
|
||||
return false;
|
||||
if (subLevel == rhs.subLevel)
|
||||
return true; // if level == rhs.level and subLevel == rhs.subLevel, then they are the exact same TypeLevel
|
||||
|
||||
// Sibling TypeLevels (that is, TypeLevels that share a level but have a different subLevel) are not considered to subsume one another
|
||||
return false;
|
||||
}
|
||||
|
||||
TypeLevel incr() const
|
||||
{
|
||||
TypeLevel result;
|
||||
result.level = level + 1;
|
||||
result.subLevel = 0;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
inline TypeLevel min(const TypeLevel& a, const TypeLevel& b)
|
||||
{
|
||||
if (a.subsumes(b))
|
||||
return a;
|
||||
else
|
||||
return b;
|
||||
}
|
||||
|
||||
namespace Unifiable
|
||||
{
|
||||
|
||||
using Name = std::string;
|
||||
|
||||
struct Free
|
||||
{
|
||||
explicit Free(TypeLevel level);
|
||||
Free(TypeLevel level, bool DEPRECATED_canBeGeneric);
|
||||
|
||||
int index;
|
||||
TypeLevel level;
|
||||
// Removed by FFlag::LuauRankNTypes
|
||||
bool DEPRECATED_canBeGeneric = false;
|
||||
// True if this free type variable is part of a mutually
|
||||
// recursive type alias whose definitions haven't been
|
||||
// resolved yet.
|
||||
bool forwardedTypeAlias = false;
|
||||
|
||||
private:
|
||||
static int nextIndex;
|
||||
};
|
||||
|
||||
template<typename Id>
|
||||
struct Bound
|
||||
{
|
||||
explicit Bound(Id boundTo)
|
||||
: boundTo(boundTo)
|
||||
{
|
||||
}
|
||||
|
||||
Id boundTo;
|
||||
};
|
||||
|
||||
struct Generic
|
||||
{
|
||||
// By default, generics are global, with a synthetic name
|
||||
Generic();
|
||||
explicit Generic(TypeLevel level);
|
||||
explicit Generic(const Name& name);
|
||||
Generic(TypeLevel level, const Name& name);
|
||||
|
||||
int index;
|
||||
TypeLevel level;
|
||||
Name name;
|
||||
bool explicitName;
|
||||
|
||||
private:
|
||||
static int nextIndex;
|
||||
};
|
||||
|
||||
struct Error
|
||||
{
|
||||
Error();
|
||||
|
||||
int index;
|
||||
|
||||
private:
|
||||
static int nextIndex;
|
||||
};
|
||||
|
||||
template<typename Id, typename... Value>
|
||||
using Variant = Variant<Free, Bound<Id>, Generic, Error, Value...>;
|
||||
|
||||
} // namespace Unifiable
|
||||
} // namespace Luau
|
98
Analysis/include/Luau/Unifier.h
Normal file
98
Analysis/include/Luau/Unifier.h
Normal file
|
@ -0,0 +1,98 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/Module.h" // FIXME: For TypeArena. It merits breaking out into its own header.
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
enum Variance
|
||||
{
|
||||
Covariant,
|
||||
Invariant
|
||||
};
|
||||
|
||||
struct UnifierCounters
|
||||
{
|
||||
int recursionCount = 0;
|
||||
int iterationCount = 0;
|
||||
};
|
||||
|
||||
struct Unifier
|
||||
{
|
||||
TypeArena* const types;
|
||||
Mode mode;
|
||||
ScopePtr globalScope; // sigh. Needed solely to get at string's metatable.
|
||||
|
||||
TxnLog log;
|
||||
ErrorVec errors;
|
||||
Location location;
|
||||
Variance variance = Covariant;
|
||||
CountMismatch::Context ctx = CountMismatch::Arg;
|
||||
|
||||
std::shared_ptr<UnifierCounters> counters;
|
||||
InternalErrorReporter* iceHandler;
|
||||
|
||||
Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const Location& location, Variance variance, InternalErrorReporter* iceHandler);
|
||||
Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const std::vector<std::pair<TypeId, TypeId>>& seen, const Location& location,
|
||||
Variance variance, InternalErrorReporter* iceHandler, const std::shared_ptr<UnifierCounters>& counters = nullptr);
|
||||
|
||||
// Test whether the two type vars unify. Never commits the result.
|
||||
ErrorVec canUnify(TypeId superTy, TypeId subTy);
|
||||
ErrorVec canUnify(TypePackId superTy, TypePackId subTy, bool isFunctionCall = false);
|
||||
|
||||
/** Attempt to unify left with right.
|
||||
* Populate the vector errors with any type errors that may arise.
|
||||
* Populate the transaction log with the set of TypeIds that need to be reset to undo the unification attempt.
|
||||
*/
|
||||
void tryUnify(TypeId superTy, TypeId subTy, bool isFunctionCall = false, bool isIntersection = false);
|
||||
|
||||
private:
|
||||
void tryUnify_(TypeId superTy, TypeId subTy, bool isFunctionCall = false, bool isIntersection = false);
|
||||
void tryUnifyPrimitives(TypeId superTy, TypeId subTy);
|
||||
void tryUnifyFunctions(TypeId superTy, TypeId subTy, bool isFunctionCall = false);
|
||||
void tryUnifyTables(TypeId left, TypeId right, bool isIntersection = false);
|
||||
void tryUnifyFreeTable(TypeId free, TypeId other);
|
||||
void tryUnifySealedTables(TypeId left, TypeId right, bool isIntersection);
|
||||
void tryUnifyWithMetatable(TypeId metatable, TypeId other, bool reversed);
|
||||
void tryUnifyWithClass(TypeId superTy, TypeId subTy, bool reversed);
|
||||
void tryUnify(const TableIndexer& superIndexer, const TableIndexer& subIndexer);
|
||||
|
||||
public:
|
||||
void tryUnify(TypePackId superTy, TypePackId subTy, bool isFunctionCall = false);
|
||||
|
||||
private:
|
||||
void tryUnify_(TypePackId superTy, TypePackId subTy, bool isFunctionCall = false);
|
||||
void tryUnifyVariadics(TypePackId superTy, TypePackId subTy, bool reversed, int subOffset = 0);
|
||||
|
||||
void tryUnifyWithAny(TypeId any, TypeId ty);
|
||||
void tryUnifyWithAny(TypePackId any, TypePackId ty);
|
||||
|
||||
std::optional<TypeId> findTablePropertyRespectingMeta(TypeId lhsType, Name name);
|
||||
std::optional<TypeId> findMetatableEntry(TypeId type, std::string entry);
|
||||
|
||||
public:
|
||||
// Report an "infinite type error" if the type "needle" already occurs within "haystack"
|
||||
void occursCheck(TypeId needle, TypeId haystack);
|
||||
void occursCheck(std::unordered_set<TypeId>& seen, TypeId needle, TypeId haystack);
|
||||
void occursCheck(TypePackId needle, TypePackId haystack);
|
||||
void occursCheck(std::unordered_set<TypePackId>& seen, TypePackId needle, TypePackId haystack);
|
||||
|
||||
Unifier makeChildUnifier();
|
||||
|
||||
private:
|
||||
bool isNonstrictMode() const;
|
||||
|
||||
void checkChildUnifierTypeMismatch(const ErrorVec& innerErrors, TypeId wantedType, TypeId givenType);
|
||||
|
||||
[[noreturn]] void ice(const std::string& message, const Location& location);
|
||||
[[noreturn]] void ice(const std::string& message);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
302
Analysis/include/Luau/Variant.h
Normal file
302
Analysis/include/Luau/Variant.h
Normal file
|
@ -0,0 +1,302 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#ifndef LUAU_USE_STD_VARIANT
|
||||
#define LUAU_USE_STD_VARIANT 0
|
||||
#endif
|
||||
|
||||
#if LUAU_USE_STD_VARIANT
|
||||
#include <variant>
|
||||
#else
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
#include <initializer_list>
|
||||
#include <stddef.h>
|
||||
#endif
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
#if LUAU_USE_STD_VARIANT
|
||||
template<typename... Ts>
|
||||
using Variant = std::variant<Ts...>;
|
||||
|
||||
template<class Visitor, class Variant>
|
||||
auto visit(Visitor&& vis, Variant&& var)
|
||||
{
|
||||
// This change resolves the ABI issues with std::variant on libc++; std::visit normally throws bad_variant_access
|
||||
// but it requires an update to libc++.dylib which ships with macOS 10.14. To work around this, we assert on valueless
|
||||
// variants since we will never generate them and call into a libc++ function that doesn't throw.
|
||||
LUAU_ASSERT(!var.valueless_by_exception());
|
||||
|
||||
#ifdef __APPLE__
|
||||
// See https://stackoverflow.com/a/53868971/503215
|
||||
return std::__variant_detail::__visitation::__variant::__visit_value(vis, var);
|
||||
#else
|
||||
return std::visit(vis, var);
|
||||
#endif
|
||||
}
|
||||
|
||||
using std::get_if;
|
||||
#else
|
||||
template<typename... Ts>
|
||||
class Variant
|
||||
{
|
||||
static_assert(sizeof...(Ts) > 0, "variant must have at least 1 type (empty variants are ill-formed)");
|
||||
static_assert(std::disjunction_v<std::is_void<Ts>...> == false, "variant does not allow void as an alternative type");
|
||||
static_assert(std::disjunction_v<std::is_reference<Ts>...> == false, "variant does not allow references as an alternative type");
|
||||
static_assert(std::disjunction_v<std::is_array<Ts>...> == false, "variant does not allow arrays as an alternative type");
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
static constexpr int getTypeId()
|
||||
{
|
||||
using TT = std::decay_t<T>;
|
||||
|
||||
constexpr int N = sizeof...(Ts);
|
||||
constexpr bool is[N] = {std::is_same_v<TT, Ts>...};
|
||||
|
||||
for (int i = 0; i < N; ++i)
|
||||
if (is[i])
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
template<typename T, typename... Tail>
|
||||
struct First
|
||||
{
|
||||
using type = T;
|
||||
};
|
||||
|
||||
public:
|
||||
using first_alternative = typename First<Ts...>::type;
|
||||
|
||||
Variant()
|
||||
{
|
||||
static_assert(std::is_default_constructible_v<first_alternative>, "first alternative type must be default constructible");
|
||||
typeId = 0;
|
||||
new (&storage) first_alternative();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Variant(T&& value, std::enable_if_t<getTypeId<T>() >= 0>* = 0)
|
||||
{
|
||||
using TT = std::decay_t<T>;
|
||||
|
||||
constexpr int tid = getTypeId<T>();
|
||||
typeId = tid;
|
||||
new (&storage) TT(value);
|
||||
}
|
||||
|
||||
Variant(const Variant& other)
|
||||
{
|
||||
typeId = other.typeId;
|
||||
tableCopy[typeId](&storage, &other.storage);
|
||||
}
|
||||
|
||||
Variant(Variant&& other)
|
||||
{
|
||||
typeId = other.typeId;
|
||||
tableMove[typeId](&storage, &other.storage);
|
||||
}
|
||||
|
||||
~Variant()
|
||||
{
|
||||
tableDtor[typeId](&storage);
|
||||
}
|
||||
|
||||
Variant& operator=(const Variant& other)
|
||||
{
|
||||
Variant copy(other);
|
||||
// static_cast<T&&> is equivalent to std::move() but faster in Debug
|
||||
return *this = static_cast<Variant&&>(copy);
|
||||
}
|
||||
|
||||
Variant& operator=(Variant&& other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
tableDtor[typeId](&storage);
|
||||
typeId = other.typeId;
|
||||
tableMove[typeId](&storage, &other.storage); // nothrow
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const T* get_if() const
|
||||
{
|
||||
constexpr int tid = getTypeId<T>();
|
||||
static_assert(tid >= 0, "unsupported T");
|
||||
|
||||
return tid == typeId ? reinterpret_cast<const T*>(&storage) : nullptr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* get_if()
|
||||
{
|
||||
constexpr int tid = getTypeId<T>();
|
||||
static_assert(tid >= 0, "unsupported T");
|
||||
|
||||
return tid == typeId ? reinterpret_cast<T*>(&storage) : nullptr;
|
||||
}
|
||||
|
||||
bool valueless_by_exception() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int index() const
|
||||
{
|
||||
return typeId;
|
||||
}
|
||||
|
||||
bool operator==(const Variant& other) const
|
||||
{
|
||||
static constexpr FnPred table[sizeof...(Ts)] = {&fnPredEq<Ts>...};
|
||||
|
||||
return typeId == other.typeId && table[typeId](&storage, &other.storage);
|
||||
}
|
||||
|
||||
bool operator!=(const Variant& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr size_t cmax(std::initializer_list<size_t> l)
|
||||
{
|
||||
size_t res = 0;
|
||||
for (size_t i : l)
|
||||
res = (res < i) ? i : res;
|
||||
return res;
|
||||
}
|
||||
|
||||
static constexpr size_t storageSize = cmax({sizeof(Ts)...});
|
||||
static constexpr size_t storageAlign = cmax({alignof(Ts)...});
|
||||
|
||||
using FnCopy = void (*)(void*, const void*);
|
||||
using FnMove = void (*)(void*, void*);
|
||||
using FnDtor = void (*)(void*);
|
||||
using FnPred = bool (*)(const void*, const void*);
|
||||
|
||||
template<typename T>
|
||||
static void fnCopy(void* dst, const void* src)
|
||||
{
|
||||
new (dst) T(*static_cast<const T*>(src));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void fnMove(void* dst, void* src)
|
||||
{
|
||||
// static_cast<T&&> is equivalent to std::move() but faster in Debug
|
||||
new (dst) T(static_cast<T&&>(*static_cast<T*>(src)));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void fnDtor(void* dst)
|
||||
{
|
||||
static_cast<T*>(dst)->~T();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static bool fnPredEq(const void* lhs, const void* rhs)
|
||||
{
|
||||
return *static_cast<const T*>(lhs) == *static_cast<const T*>(rhs);
|
||||
}
|
||||
|
||||
static constexpr FnCopy tableCopy[sizeof...(Ts)] = {&fnCopy<Ts>...};
|
||||
static constexpr FnMove tableMove[sizeof...(Ts)] = {&fnMove<Ts>...};
|
||||
static constexpr FnDtor tableDtor[sizeof...(Ts)] = {&fnDtor<Ts>...};
|
||||
|
||||
int typeId;
|
||||
alignas(storageAlign) char storage[storageSize];
|
||||
|
||||
template<class Visitor, typename... _Ts>
|
||||
friend auto visit(Visitor&& vis, const Variant<_Ts...>& var);
|
||||
template<class Visitor, typename... _Ts>
|
||||
friend auto visit(Visitor&& vis, Variant<_Ts...>& var);
|
||||
};
|
||||
|
||||
template<typename T, typename... Ts>
|
||||
const T* get_if(const Variant<Ts...>* var)
|
||||
{
|
||||
return var ? var->template get_if<T>() : nullptr;
|
||||
}
|
||||
|
||||
template<typename T, typename... Ts>
|
||||
T* get_if(Variant<Ts...>* var)
|
||||
{
|
||||
return var ? var->template get_if<T>() : nullptr;
|
||||
}
|
||||
|
||||
template<typename Visitor, typename Result, typename T>
|
||||
static void fnVisitR(Visitor& vis, Result& dst, std::conditional_t<std::is_const_v<T>, const void, void>* src)
|
||||
{
|
||||
dst = vis(*static_cast<T*>(src));
|
||||
}
|
||||
|
||||
template<typename Visitor, typename T>
|
||||
static void fnVisitV(Visitor& vis, std::conditional_t<std::is_const_v<T>, const void, void>* src)
|
||||
{
|
||||
vis(*static_cast<T*>(src));
|
||||
}
|
||||
|
||||
template<class Visitor, typename... Ts>
|
||||
auto visit(Visitor&& vis, const Variant<Ts...>& var)
|
||||
{
|
||||
using Result = std::invoke_result_t<Visitor, typename Variant<Ts...>::first_alternative>;
|
||||
static_assert(std::conjunction_v<std::is_same<Result, std::invoke_result_t<Visitor, Ts>>...>,
|
||||
"visitor result type must be consistent between alternatives");
|
||||
|
||||
if constexpr (std::is_same_v<Result, void>)
|
||||
{
|
||||
using FnVisitV = void (*)(Visitor&, const void*);
|
||||
static const FnVisitV tableVisit[sizeof...(Ts)] = {&fnVisitV<Visitor, const Ts>...};
|
||||
|
||||
tableVisit[var.typeId](vis, &var.storage);
|
||||
}
|
||||
else
|
||||
{
|
||||
using FnVisitR = void (*)(Visitor&, Result&, const void*);
|
||||
static const FnVisitR tableVisit[sizeof...(Ts)] = {&fnVisitR<Visitor, Result, const Ts>...};
|
||||
|
||||
Result res;
|
||||
tableVisit[var.typeId](vis, res, &var.storage);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
template<class Visitor, typename... Ts>
|
||||
auto visit(Visitor&& vis, Variant<Ts...>& var)
|
||||
{
|
||||
using Result = std::invoke_result_t<Visitor, typename Variant<Ts...>::first_alternative&>;
|
||||
static_assert(std::conjunction_v<std::is_same<Result, std::invoke_result_t<Visitor, Ts&>>...>,
|
||||
"visitor result type must be consistent between alternatives");
|
||||
|
||||
if constexpr (std::is_same_v<Result, void>)
|
||||
{
|
||||
using FnVisitV = void (*)(Visitor&, void*);
|
||||
static const FnVisitV tableVisit[sizeof...(Ts)] = {&fnVisitV<Visitor, Ts>...};
|
||||
|
||||
tableVisit[var.typeId](vis, &var.storage);
|
||||
}
|
||||
else
|
||||
{
|
||||
using FnVisitR = void (*)(Visitor&, Result&, void*);
|
||||
static const FnVisitR tableVisit[sizeof...(Ts)] = {&fnVisitR<Visitor, Result, Ts>...};
|
||||
|
||||
Result res;
|
||||
tableVisit[var.typeId](vis, res, &var.storage);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
template<class>
|
||||
inline constexpr bool always_false_v = false;
|
||||
|
||||
} // namespace Luau
|
200
Analysis/include/Luau/VisitTypeVar.h
Normal file
200
Analysis/include/Luau/VisitTypeVar.h
Normal file
|
@ -0,0 +1,200 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/TypePack.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
namespace visit_detail
|
||||
{
|
||||
/**
|
||||
* Apply f(tid, t, seen) if doing so would pass type checking, else apply f(tid, t)
|
||||
*
|
||||
* We do this to permit (but not require) TypeVar visitors to accept the seen set as an argument.
|
||||
*/
|
||||
template<typename F, typename A, typename B, typename C>
|
||||
auto apply(A tid, const B& t, C& c, F& f) -> decltype(f(tid, t, c))
|
||||
{
|
||||
return f(tid, t, c);
|
||||
}
|
||||
|
||||
template<typename A, typename B, typename C, typename F>
|
||||
auto apply(A tid, const B& t, C&, F& f) -> decltype(f(tid, t))
|
||||
{
|
||||
return f(tid, t);
|
||||
}
|
||||
|
||||
inline bool hasSeen(std::unordered_set<void*>& seen, const void* tv)
|
||||
{
|
||||
void* ttv = const_cast<void*>(tv);
|
||||
return !seen.insert(ttv).second;
|
||||
}
|
||||
|
||||
inline void unsee(std::unordered_set<void*>& seen, const void* tv)
|
||||
{
|
||||
void* ttv = const_cast<void*>(tv);
|
||||
seen.erase(ttv);
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
void visit(TypePackId tp, F& f, std::unordered_set<void*>& seen);
|
||||
|
||||
template<typename F>
|
||||
void visit(TypeId ty, F& f, std::unordered_set<void*>& seen)
|
||||
{
|
||||
if (visit_detail::hasSeen(seen, ty))
|
||||
{
|
||||
f.cycle(ty);
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto btv = get<BoundTypeVar>(ty))
|
||||
{
|
||||
if (apply(ty, *btv, seen, f))
|
||||
visit(btv->boundTo, f, seen);
|
||||
}
|
||||
|
||||
else if (auto ftv = get<FreeTypeVar>(ty))
|
||||
apply(ty, *ftv, seen, f);
|
||||
|
||||
else if (auto gtv = get<GenericTypeVar>(ty))
|
||||
apply(ty, *gtv, seen, f);
|
||||
|
||||
else if (auto etv = get<ErrorTypeVar>(ty))
|
||||
apply(ty, *etv, seen, f);
|
||||
|
||||
else if (auto ptv = get<PrimitiveTypeVar>(ty))
|
||||
apply(ty, *ptv, seen, f);
|
||||
|
||||
else if (auto ftv = get<FunctionTypeVar>(ty))
|
||||
{
|
||||
if (apply(ty, *ftv, seen, f))
|
||||
{
|
||||
visit(ftv->argTypes, f, seen);
|
||||
visit(ftv->retType, f, seen);
|
||||
}
|
||||
}
|
||||
|
||||
else if (auto ttv = get<TableTypeVar>(ty))
|
||||
{
|
||||
if (apply(ty, *ttv, seen, f))
|
||||
{
|
||||
for (auto& [_name, prop] : ttv->props)
|
||||
visit(prop.type, f, seen);
|
||||
|
||||
if (ttv->indexer)
|
||||
{
|
||||
visit(ttv->indexer->indexType, f, seen);
|
||||
visit(ttv->indexer->indexResultType, f, seen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (auto mtv = get<MetatableTypeVar>(ty))
|
||||
{
|
||||
if (apply(ty, *mtv, seen, f))
|
||||
{
|
||||
visit(mtv->table, f, seen);
|
||||
visit(mtv->metatable, f, seen);
|
||||
}
|
||||
}
|
||||
|
||||
else if (auto ctv = get<ClassTypeVar>(ty))
|
||||
{
|
||||
if (apply(ty, *ctv, seen, f))
|
||||
{
|
||||
for (const auto& [name, prop] : ctv->props)
|
||||
visit(prop.type, f, seen);
|
||||
|
||||
if (ctv->parent)
|
||||
visit(*ctv->parent, f, seen);
|
||||
|
||||
if (ctv->metatable)
|
||||
visit(*ctv->metatable, f, seen);
|
||||
}
|
||||
}
|
||||
|
||||
else if (auto atv = get<AnyTypeVar>(ty))
|
||||
apply(ty, *atv, seen, f);
|
||||
|
||||
else if (auto utv = get<UnionTypeVar>(ty))
|
||||
{
|
||||
if (apply(ty, *utv, seen, f))
|
||||
{
|
||||
for (TypeId optTy : utv->options)
|
||||
visit(optTy, f, seen);
|
||||
}
|
||||
}
|
||||
|
||||
else if (auto itv = get<IntersectionTypeVar>(ty))
|
||||
{
|
||||
if (apply(ty, *itv, seen, f))
|
||||
{
|
||||
for (TypeId partTy : itv->parts)
|
||||
visit(partTy, f, seen);
|
||||
}
|
||||
}
|
||||
|
||||
visit_detail::unsee(seen, ty);
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
void visit(TypePackId tp, F& f, std::unordered_set<void*>& seen)
|
||||
{
|
||||
if (visit_detail::hasSeen(seen, tp))
|
||||
{
|
||||
f.cycle(tp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto btv = get<BoundTypePack>(tp))
|
||||
{
|
||||
if (apply(tp, *btv, seen, f))
|
||||
visit(btv->boundTo, f, seen);
|
||||
}
|
||||
|
||||
else if (auto ftv = get<Unifiable::Free>(tp))
|
||||
apply(tp, *ftv, seen, f);
|
||||
|
||||
else if (auto gtv = get<Unifiable::Generic>(tp))
|
||||
apply(tp, *gtv, seen, f);
|
||||
|
||||
else if (auto etv = get<Unifiable::Error>(tp))
|
||||
apply(tp, *etv, seen, f);
|
||||
|
||||
else if (auto pack = get<TypePack>(tp))
|
||||
{
|
||||
apply(tp, *pack, seen, f);
|
||||
|
||||
for (TypeId ty : pack->head)
|
||||
visit(ty, f, seen);
|
||||
|
||||
if (pack->tail)
|
||||
visit(*pack->tail, f, seen);
|
||||
}
|
||||
else if (auto pack = get<VariadicTypePack>(tp))
|
||||
{
|
||||
apply(tp, *pack, seen, f);
|
||||
visit(pack->ty, f, seen);
|
||||
}
|
||||
|
||||
visit_detail::unsee(seen, tp);
|
||||
}
|
||||
} // namespace visit_detail
|
||||
|
||||
template<typename TID, typename F>
|
||||
void visitTypeVar(TID ty, F& f, std::unordered_set<void*>& seen)
|
||||
{
|
||||
visit_detail::visit(ty, f, seen);
|
||||
}
|
||||
|
||||
template<typename TID, typename F>
|
||||
void visitTypeVar(TID ty, F& f)
|
||||
{
|
||||
std::unordered_set<void*> seen;
|
||||
visit_detail::visit(ty, f, seen);
|
||||
}
|
||||
|
||||
} // namespace Luau
|
411
Analysis/src/AstQuery.cpp
Normal file
411
Analysis/src/AstQuery.cpp
Normal file
|
@ -0,0 +1,411 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/AstQuery.h"
|
||||
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/ToString.h"
|
||||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct FindNode : public AstVisitor
|
||||
{
|
||||
const Position pos;
|
||||
const Position documentEnd;
|
||||
AstNode* best = nullptr;
|
||||
|
||||
explicit FindNode(Position pos, Position documentEnd)
|
||||
: pos(pos)
|
||||
, documentEnd(documentEnd)
|
||||
{
|
||||
}
|
||||
|
||||
bool visit(AstNode* node) override
|
||||
{
|
||||
if (node->location.contains(pos))
|
||||
{
|
||||
best = node;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Edge case: If we ask for the node at the position that is the very end of the document
|
||||
// return the innermost AST element that ends at that position.
|
||||
|
||||
if (node->location.end == documentEnd && pos >= documentEnd)
|
||||
{
|
||||
best = node;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstStatBlock* block) override
|
||||
{
|
||||
visit(static_cast<AstNode*>(block));
|
||||
|
||||
for (AstStat* stat : block->body)
|
||||
{
|
||||
if (stat->location.end < pos)
|
||||
continue;
|
||||
if (stat->location.begin > pos)
|
||||
break;
|
||||
|
||||
stat->visit(this);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct FindFullAncestry final : public AstVisitor
|
||||
{
|
||||
std::vector<AstNode*> nodes;
|
||||
Position pos;
|
||||
|
||||
explicit FindFullAncestry(Position pos)
|
||||
: pos(pos)
|
||||
{
|
||||
}
|
||||
|
||||
bool visit(AstNode* node)
|
||||
{
|
||||
if (node->location.contains(pos))
|
||||
{
|
||||
nodes.push_back(node);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos)
|
||||
{
|
||||
FindFullAncestry finder(pos);
|
||||
source.root->visit(&finder);
|
||||
return std::move(finder.nodes);
|
||||
}
|
||||
|
||||
AstNode* findNodeAtPosition(const SourceModule& source, Position pos)
|
||||
{
|
||||
const Position end = source.root->location.end;
|
||||
if (pos < source.root->location.begin)
|
||||
return source.root;
|
||||
|
||||
if (pos > end)
|
||||
pos = end;
|
||||
|
||||
FindNode findNode{pos, end};
|
||||
findNode.visit(source.root);
|
||||
return findNode.best;
|
||||
}
|
||||
|
||||
AstExpr* findExprAtPosition(const SourceModule& source, Position pos)
|
||||
{
|
||||
AstNode* node = findNodeAtPosition(source, pos);
|
||||
if (node)
|
||||
return node->asExpr();
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ScopePtr findScopeAtPosition(const Module& module, Position pos)
|
||||
{
|
||||
LUAU_ASSERT(!module.scopes.empty());
|
||||
|
||||
Location scopeLocation = module.scopes.front().first;
|
||||
ScopePtr scope = module.scopes.front().second;
|
||||
for (const auto& s : module.scopes)
|
||||
{
|
||||
if (s.first.contains(pos))
|
||||
{
|
||||
if (!scope || scopeLocation.encloses(s.first))
|
||||
{
|
||||
scopeLocation = s.first;
|
||||
scope = s.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
|
||||
std::optional<TypeId> findTypeAtPosition(const Module& module, const SourceModule& sourceModule, Position pos)
|
||||
{
|
||||
if (auto expr = findExprAtPosition(sourceModule, pos))
|
||||
{
|
||||
if (auto it = module.astTypes.find(expr); it != module.astTypes.end())
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<TypeId> findExpectedTypeAtPosition(const Module& module, const SourceModule& sourceModule, Position pos)
|
||||
{
|
||||
if (auto expr = findExprAtPosition(sourceModule, pos))
|
||||
{
|
||||
if (auto it = module.astExpectedTypes.find(expr); it != module.astExpectedTypes.end())
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static std::optional<AstStatLocal*> findBindingLocalStatement(const SourceModule& source, const Binding& binding)
|
||||
{
|
||||
std::vector<AstNode*> nodes = findAstAncestryOfPosition(source, binding.location.begin);
|
||||
auto iter = std::find_if(nodes.rbegin(), nodes.rend(), [](AstNode* node) {
|
||||
return node->is<AstStatLocal>();
|
||||
});
|
||||
return iter != nodes.rend() ? std::make_optional((*iter)->as<AstStatLocal>()) : std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<Binding> findBindingAtPosition(const Module& module, const SourceModule& source, Position pos)
|
||||
{
|
||||
AstExpr* expr = findExprAtPosition(source, pos);
|
||||
if (!expr)
|
||||
return std::nullopt;
|
||||
|
||||
Symbol name;
|
||||
if (auto g = expr->as<AstExprGlobal>())
|
||||
name = g->name;
|
||||
else if (auto l = expr->as<AstExprLocal>())
|
||||
name = l->local;
|
||||
else
|
||||
return std::nullopt;
|
||||
|
||||
ScopePtr currentScope = findScopeAtPosition(module, pos);
|
||||
LUAU_ASSERT(currentScope);
|
||||
|
||||
while (currentScope)
|
||||
{
|
||||
auto iter = currentScope->bindings.find(name);
|
||||
if (iter != currentScope->bindings.end() && iter->second.location.begin <= pos)
|
||||
{
|
||||
/* Ignore this binding if we're inside its definition. e.g. local abc = abc -- Will take the definition of abc from outer scope */
|
||||
std::optional<AstStatLocal*> bindingStatement = findBindingLocalStatement(source, iter->second);
|
||||
if (!bindingStatement || !(*bindingStatement)->location.contains(pos))
|
||||
return iter->second;
|
||||
}
|
||||
currentScope = currentScope->parent;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
struct FindExprOrLocal : public AstVisitor
|
||||
{
|
||||
const Position pos;
|
||||
ExprOrLocal result;
|
||||
|
||||
explicit FindExprOrLocal(Position pos)
|
||||
: pos(pos)
|
||||
{
|
||||
}
|
||||
|
||||
// We want to find the result with the smallest location range.
|
||||
bool isCloserMatch(Location newLocation)
|
||||
{
|
||||
auto current = result.getLocation();
|
||||
return newLocation.contains(pos) && (!current || current->encloses(newLocation));
|
||||
}
|
||||
|
||||
bool visit(AstStatBlock* block) override
|
||||
{
|
||||
for (AstStat* stat : block->body)
|
||||
{
|
||||
if (stat->location.end <= pos)
|
||||
continue;
|
||||
if (stat->location.begin > pos)
|
||||
break;
|
||||
|
||||
stat->visit(this);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstExpr* expr) override
|
||||
{
|
||||
if (isCloserMatch(expr->location))
|
||||
{
|
||||
result.setExpr(expr);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visitLocal(AstLocal* local)
|
||||
{
|
||||
if (isCloserMatch(local->location))
|
||||
{
|
||||
result.setLocal(local);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstStatLocalFunction* function) override
|
||||
{
|
||||
visitLocal(function->name);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstStatLocal* al) override
|
||||
{
|
||||
for (size_t i = 0; i < al->vars.size; ++i)
|
||||
{
|
||||
visitLocal(al->vars.data[i]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool visit(AstExprFunction* fn) override
|
||||
{
|
||||
for (size_t i = 0; i < fn->args.size; ++i)
|
||||
{
|
||||
visitLocal(fn->args.data[i]);
|
||||
}
|
||||
return visit((class AstExpr*)fn);
|
||||
}
|
||||
|
||||
virtual bool visit(AstStatFor* forStat) override
|
||||
{
|
||||
visitLocal(forStat->var);
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool visit(AstStatForIn* forIn) override
|
||||
{
|
||||
for (AstLocal* var : forIn->vars)
|
||||
{
|
||||
visitLocal(var);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}; // namespace
|
||||
|
||||
ExprOrLocal findExprOrLocalAtPosition(const SourceModule& source, Position pos)
|
||||
{
|
||||
FindExprOrLocal findVisitor{pos};
|
||||
findVisitor.visit(source.root);
|
||||
return findVisitor.result;
|
||||
}
|
||||
|
||||
std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const SourceModule& source, const Module& module, Position position)
|
||||
{
|
||||
std::vector<AstNode*> ancestry = findAstAncestryOfPosition(source, position);
|
||||
|
||||
AstExpr* targetExpr = ancestry.size() >= 1 ? ancestry[ancestry.size() - 1]->asExpr() : nullptr;
|
||||
AstExpr* parentExpr = ancestry.size() >= 2 ? ancestry[ancestry.size() - 2]->asExpr() : nullptr;
|
||||
|
||||
if (std::optional<Binding> binding = findBindingAtPosition(module, source, position))
|
||||
{
|
||||
if (binding->documentationSymbol)
|
||||
{
|
||||
// This might be an overloaded function binding.
|
||||
if (get<IntersectionTypeVar>(follow(binding->typeId)))
|
||||
{
|
||||
TypeId matchingOverload = nullptr;
|
||||
if (parentExpr && parentExpr->is<AstExprCall>())
|
||||
{
|
||||
if (auto it = module.astOverloadResolvedTypes.find(parentExpr); it != module.astOverloadResolvedTypes.end())
|
||||
{
|
||||
matchingOverload = it->second;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchingOverload)
|
||||
{
|
||||
std::string overloadSymbol = *binding->documentationSymbol + "/overload/";
|
||||
// Default toString options are fine for this purpose.
|
||||
overloadSymbol += toString(matchingOverload);
|
||||
return overloadSymbol;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return binding->documentationSymbol;
|
||||
}
|
||||
|
||||
if (targetExpr)
|
||||
{
|
||||
if (AstExprIndexName* indexName = targetExpr->as<AstExprIndexName>())
|
||||
{
|
||||
if (auto it = module.astTypes.find(indexName->expr); it != module.astTypes.end())
|
||||
{
|
||||
TypeId parentTy = follow(it->second);
|
||||
if (const TableTypeVar* ttv = get<TableTypeVar>(parentTy))
|
||||
{
|
||||
if (auto propIt = ttv->props.find(indexName->index.value); propIt != ttv->props.end())
|
||||
{
|
||||
return propIt->second.documentationSymbol;
|
||||
}
|
||||
}
|
||||
else if (const ClassTypeVar* ctv = get<ClassTypeVar>(parentTy))
|
||||
{
|
||||
if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end())
|
||||
{
|
||||
return propIt->second.documentationSymbol;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (AstExprFunction* fn = targetExpr->as<AstExprFunction>())
|
||||
{
|
||||
// Handle event connection-like structures where we have
|
||||
// something:Connect(function(a, b, c) end)
|
||||
// In this case, we want to ascribe a documentation symbol to 'a'
|
||||
// based on the documentation symbol of Connect.
|
||||
if (parentExpr && parentExpr->is<AstExprCall>())
|
||||
{
|
||||
AstExprCall* call = parentExpr->as<AstExprCall>();
|
||||
if (std::optional<DocumentationSymbol> parentSymbol = getDocumentationSymbolAtPosition(source, module, call->func->location.begin))
|
||||
{
|
||||
for (size_t i = 0; i < call->args.size; ++i)
|
||||
{
|
||||
AstExpr* callArg = call->args.data[i];
|
||||
if (callArg == targetExpr)
|
||||
{
|
||||
std::string fnSymbol = *parentSymbol + "/param/" + std::to_string(i);
|
||||
for (size_t j = 0; j < fn->args.size; ++j)
|
||||
{
|
||||
AstLocal* fnArg = fn->args.data[j];
|
||||
|
||||
if (fnArg->location.contains(position))
|
||||
{
|
||||
return fnSymbol + "/param/" + std::to_string(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (std::optional<TypeId> ty = findTypeAtPosition(module, source, position))
|
||||
{
|
||||
if ((*ty)->documentationSymbol)
|
||||
{
|
||||
return (*ty)->documentationSymbol;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
1566
Analysis/src/Autocomplete.cpp
Normal file
1566
Analysis/src/Autocomplete.cpp
Normal file
File diff suppressed because it is too large
Load diff
805
Analysis/src/BuiltinDefinitions.cpp
Normal file
805
Analysis/src/BuiltinDefinitions.cpp
Normal file
|
@ -0,0 +1,805 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
|
||||
#include "Luau/Frontend.h"
|
||||
#include "Luau/Symbol.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/ToString.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(LuauParseGenericFunctions)
|
||||
LUAU_FASTFLAG(LuauGenericFunctions)
|
||||
LUAU_FASTFLAG(LuauRankNTypes)
|
||||
LUAU_FASTFLAG(LuauStringMetatable)
|
||||
|
||||
/** FIXME: Many of these type definitions are not quite completely accurate.
|
||||
*
|
||||
* Some of them require richer generics than we have. For instance, we do not yet have a way to talk
|
||||
* about a function that takes any number of values, but where each value must have some specific type.
|
||||
*/
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
static std::optional<ExprResult<TypePackId>> magicFunctionSelect(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult);
|
||||
static std::optional<ExprResult<TypePackId>> magicFunctionSetMetaTable(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult);
|
||||
static std::optional<ExprResult<TypePackId>> magicFunctionAssert(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult);
|
||||
static std::optional<ExprResult<TypePackId>> magicFunctionPack(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult);
|
||||
static std::optional<ExprResult<TypePackId>> magicFunctionRequire(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult);
|
||||
|
||||
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types)
|
||||
{
|
||||
return arena.addType(UnionTypeVar{std::move(types)});
|
||||
}
|
||||
|
||||
TypeId makeIntersection(TypeArena& arena, std::vector<TypeId>&& types)
|
||||
{
|
||||
return arena.addType(IntersectionTypeVar{std::move(types)});
|
||||
}
|
||||
|
||||
TypeId makeOption(TypeChecker& typeChecker, TypeArena& arena, TypeId t)
|
||||
{
|
||||
return makeUnion(arena, {typeChecker.nilType, t});
|
||||
}
|
||||
|
||||
TypeId makeFunction(
|
||||
TypeArena& arena, std::optional<TypeId> selfType, std::initializer_list<TypeId> paramTypes, std::initializer_list<TypeId> retTypes)
|
||||
{
|
||||
return makeFunction(arena, selfType, {}, {}, paramTypes, {}, retTypes);
|
||||
}
|
||||
|
||||
TypeId makeFunction(TypeArena& arena, std::optional<TypeId> selfType, std::initializer_list<TypeId> generics,
|
||||
std::initializer_list<TypePackId> genericPacks, std::initializer_list<TypeId> paramTypes, std::initializer_list<TypeId> retTypes)
|
||||
{
|
||||
return makeFunction(arena, selfType, generics, genericPacks, paramTypes, {}, retTypes);
|
||||
}
|
||||
|
||||
TypeId makeFunction(TypeArena& arena, std::optional<TypeId> selfType, std::initializer_list<TypeId> paramTypes,
|
||||
std::initializer_list<std::string> paramNames, std::initializer_list<TypeId> retTypes)
|
||||
{
|
||||
return makeFunction(arena, selfType, {}, {}, paramTypes, paramNames, retTypes);
|
||||
}
|
||||
|
||||
TypeId makeFunction(TypeArena& arena, std::optional<TypeId> selfType, std::initializer_list<TypeId> generics,
|
||||
std::initializer_list<TypePackId> genericPacks, std::initializer_list<TypeId> paramTypes, std::initializer_list<std::string> paramNames,
|
||||
std::initializer_list<TypeId> retTypes)
|
||||
{
|
||||
std::vector<TypeId> params;
|
||||
if (selfType)
|
||||
params.push_back(*selfType);
|
||||
for (auto&& p : paramTypes)
|
||||
params.push_back(p);
|
||||
|
||||
TypePackId paramPack = arena.addTypePack(std::move(params));
|
||||
TypePackId retPack = arena.addTypePack(std::vector<TypeId>(retTypes));
|
||||
FunctionTypeVar ftv{generics, genericPacks, paramPack, retPack, {}, selfType.has_value()};
|
||||
|
||||
if (selfType)
|
||||
ftv.argNames.push_back(Luau::FunctionArgument{"self", {}});
|
||||
|
||||
if (paramNames.size() != 0)
|
||||
{
|
||||
for (auto&& p : paramNames)
|
||||
ftv.argNames.push_back(Luau::FunctionArgument{std::move(p), {}});
|
||||
}
|
||||
else if (selfType)
|
||||
{
|
||||
// If argument names were not provided, but we have already added a name for 'self' argument, we have to fill remaining slots as well
|
||||
for (size_t i = 0; i < paramTypes.size(); i++)
|
||||
ftv.argNames.push_back(std::nullopt);
|
||||
}
|
||||
|
||||
return arena.addType(std::move(ftv));
|
||||
}
|
||||
|
||||
void attachMagicFunction(TypeId ty, MagicFunction fn)
|
||||
{
|
||||
if (auto ftv = getMutable<FunctionTypeVar>(ty))
|
||||
ftv->magicFunction = fn;
|
||||
else
|
||||
LUAU_ASSERT(!"Got a non functional type");
|
||||
}
|
||||
|
||||
void attachFunctionTag(TypeId ty, std::string tag)
|
||||
{
|
||||
if (auto ftv = getMutable<FunctionTypeVar>(ty))
|
||||
{
|
||||
ftv->tags.emplace_back(std::move(tag));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(!"Got a non functional type");
|
||||
}
|
||||
}
|
||||
|
||||
Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol)
|
||||
{
|
||||
return {
|
||||
/* type */ ty,
|
||||
/* deprecated */ false,
|
||||
/* deprecatedSuggestion */ {},
|
||||
/* location */ std::nullopt,
|
||||
/* tags */ {},
|
||||
documentationSymbol,
|
||||
};
|
||||
}
|
||||
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const std::string& name, TypeId ty, const std::string& packageName)
|
||||
{
|
||||
addGlobalBinding(typeChecker, typeChecker.globalScope, name, ty, packageName);
|
||||
}
|
||||
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const std::string& name, Binding binding)
|
||||
{
|
||||
addGlobalBinding(typeChecker, typeChecker.globalScope, name, binding);
|
||||
}
|
||||
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const ScopePtr& scope, const std::string& name, TypeId ty, const std::string& packageName)
|
||||
{
|
||||
std::string documentationSymbol = packageName + "/global/" + name;
|
||||
addGlobalBinding(typeChecker, scope, name, Binding{ty, Location{}, {}, {}, documentationSymbol});
|
||||
}
|
||||
|
||||
void addGlobalBinding(TypeChecker& typeChecker, const ScopePtr& scope, const std::string& name, Binding binding)
|
||||
{
|
||||
scope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = binding;
|
||||
}
|
||||
|
||||
TypeId getGlobalBinding(TypeChecker& typeChecker, const std::string& name)
|
||||
{
|
||||
auto t = tryGetGlobalBinding(typeChecker, name);
|
||||
LUAU_ASSERT(t.has_value());
|
||||
return t->typeId;
|
||||
}
|
||||
|
||||
std::optional<Binding> tryGetGlobalBinding(TypeChecker& typeChecker, const std::string& name)
|
||||
{
|
||||
AstName astName = typeChecker.globalNames.names->getOrAdd(name.c_str());
|
||||
auto it = typeChecker.globalScope->bindings.find(astName);
|
||||
if (it != typeChecker.globalScope->bindings.end())
|
||||
return it->second;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Binding* tryGetGlobalBindingRef(TypeChecker& typeChecker, const std::string& name)
|
||||
{
|
||||
AstName astName = typeChecker.globalNames.names->get(name.c_str());
|
||||
if (astName == AstName())
|
||||
return nullptr;
|
||||
|
||||
auto it = typeChecker.globalScope->bindings.find(astName);
|
||||
if (it != typeChecker.globalScope->bindings.end())
|
||||
return &it->second;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void assignPropDocumentationSymbols(TableTypeVar::Props& props, const std::string& baseName)
|
||||
{
|
||||
for (auto& [name, prop] : props)
|
||||
{
|
||||
prop.documentationSymbol = baseName + "." + name;
|
||||
}
|
||||
}
|
||||
|
||||
void registerBuiltinTypes(TypeChecker& typeChecker)
|
||||
{
|
||||
LUAU_ASSERT(!typeChecker.globalTypes.typeVars.isFrozen());
|
||||
LUAU_ASSERT(!typeChecker.globalTypes.typePacks.isFrozen());
|
||||
|
||||
TypeId numberType = typeChecker.numberType;
|
||||
TypeId booleanType = typeChecker.booleanType;
|
||||
TypeId nilType = typeChecker.nilType;
|
||||
TypeId stringType = typeChecker.stringType;
|
||||
TypeId threadType = typeChecker.threadType;
|
||||
TypeId anyType = typeChecker.anyType;
|
||||
|
||||
TypeArena& arena = typeChecker.globalTypes;
|
||||
|
||||
TypeId optionalNumber = makeOption(typeChecker, arena, numberType);
|
||||
TypeId optionalString = makeOption(typeChecker, arena, stringType);
|
||||
TypeId optionalBoolean = makeOption(typeChecker, arena, booleanType);
|
||||
|
||||
TypeId stringOrNumber = makeUnion(arena, {stringType, numberType});
|
||||
|
||||
TypePackId emptyPack = arena.addTypePack({});
|
||||
TypePackId oneNumberPack = arena.addTypePack({numberType});
|
||||
TypePackId oneStringPack = arena.addTypePack({stringType});
|
||||
TypePackId oneBooleanPack = arena.addTypePack({booleanType});
|
||||
TypePackId oneAnyPack = arena.addTypePack({anyType});
|
||||
|
||||
TypePackId anyTypePack = typeChecker.anyTypePack;
|
||||
|
||||
TypePackId numberVariadicList = arena.addTypePack(TypePackVar{VariadicTypePack{numberType}});
|
||||
TypePackId stringVariadicList = arena.addTypePack(TypePackVar{VariadicTypePack{stringType}});
|
||||
TypePackId listOfAtLeastOneNumber = arena.addTypePack(TypePack{{numberType}, numberVariadicList});
|
||||
|
||||
TypeId listOfAtLeastOneNumberToNumberType = arena.addType(FunctionTypeVar{
|
||||
listOfAtLeastOneNumber,
|
||||
oneNumberPack,
|
||||
});
|
||||
|
||||
TypeId listOfAtLeastZeroNumbersToNumberType = arena.addType(FunctionTypeVar{numberVariadicList, oneNumberPack});
|
||||
|
||||
TypeId stringToAnyMap = arena.addType(TableTypeVar{{}, TableIndexer(stringType, anyType), typeChecker.globalScope->level});
|
||||
|
||||
LoadDefinitionFileResult loadResult = Luau::loadDefinitionFile(typeChecker, typeChecker.globalScope, getBuiltinDefinitionSource(), "@luau");
|
||||
LUAU_ASSERT(loadResult.success);
|
||||
|
||||
TypeId mathLibType = getGlobalBinding(typeChecker, "math");
|
||||
if (TableTypeVar* ttv = getMutable<TableTypeVar>(mathLibType))
|
||||
{
|
||||
ttv->props["min"] = makeProperty(listOfAtLeastOneNumberToNumberType, "@luau/global/math.min");
|
||||
ttv->props["max"] = makeProperty(listOfAtLeastOneNumberToNumberType, "@luau/global/math.max");
|
||||
}
|
||||
|
||||
TypeId bit32LibType = getGlobalBinding(typeChecker, "bit32");
|
||||
if (TableTypeVar* ttv = getMutable<TableTypeVar>(bit32LibType))
|
||||
{
|
||||
ttv->props["band"] = makeProperty(listOfAtLeastZeroNumbersToNumberType, "@luau/global/bit32.band");
|
||||
ttv->props["bor"] = makeProperty(listOfAtLeastZeroNumbersToNumberType, "@luau/global/bit32.bor");
|
||||
ttv->props["bxor"] = makeProperty(listOfAtLeastZeroNumbersToNumberType, "@luau/global/bit32.bxor");
|
||||
ttv->props["btest"] = makeProperty(arena.addType(FunctionTypeVar{listOfAtLeastOneNumber, oneBooleanPack}), "@luau/global/bit32.btest");
|
||||
}
|
||||
|
||||
TypeId anyFunction = arena.addType(FunctionTypeVar{anyTypePack, anyTypePack});
|
||||
|
||||
TypeId genericK = arena.addType(GenericTypeVar{"K"});
|
||||
TypeId genericV = arena.addType(GenericTypeVar{"V"});
|
||||
TypeId mapOfKtoV = arena.addType(TableTypeVar{{}, TableIndexer(genericK, genericV), typeChecker.globalScope->level});
|
||||
|
||||
if (FFlag::LuauStringMetatable)
|
||||
{
|
||||
std::optional<TypeId> stringMetatableTy = getMetatable(singletonTypes.stringType);
|
||||
LUAU_ASSERT(stringMetatableTy);
|
||||
const TableTypeVar* stringMetatableTable = get<TableTypeVar>(follow(*stringMetatableTy));
|
||||
LUAU_ASSERT(stringMetatableTable);
|
||||
|
||||
auto it = stringMetatableTable->props.find("__index");
|
||||
LUAU_ASSERT(it != stringMetatableTable->props.end());
|
||||
|
||||
TypeId stringLib = it->second.type;
|
||||
addGlobalBinding(typeChecker, "string", stringLib, "@luau");
|
||||
}
|
||||
|
||||
if (FFlag::LuauParseGenericFunctions && FFlag::LuauGenericFunctions)
|
||||
{
|
||||
if (!FFlag::LuauStringMetatable)
|
||||
{
|
||||
TypeId stringLibTy = getGlobalBinding(typeChecker, "string");
|
||||
TableTypeVar* stringLib = getMutable<TableTypeVar>(stringLibTy);
|
||||
TypeId replArgType = makeUnion(
|
||||
arena, {stringType,
|
||||
arena.addType(TableTypeVar({}, TableIndexer(stringType, stringType), typeChecker.globalScope->level, TableState::Generic)),
|
||||
makeFunction(arena, std::nullopt, {stringType}, {stringType})});
|
||||
TypeId gsubFunc = makeFunction(arena, stringType, {stringType, replArgType, optionalNumber}, {stringType, numberType});
|
||||
|
||||
stringLib->props["gsub"] = makeProperty(gsubFunc, "@luau/global/string.gsub");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!FFlag::LuauStringMetatable)
|
||||
{
|
||||
TypeId stringToStringType = makeFunction(arena, std::nullopt, {stringType}, {stringType});
|
||||
|
||||
TypeId gmatchFunc = makeFunction(arena, stringType, {stringType}, {arena.addType(FunctionTypeVar{emptyPack, stringVariadicList})});
|
||||
|
||||
TypeId replArgType = makeUnion(
|
||||
arena, {stringType,
|
||||
arena.addType(TableTypeVar({}, TableIndexer(stringType, stringType), typeChecker.globalScope->level, TableState::Generic)),
|
||||
makeFunction(arena, std::nullopt, {stringType}, {stringType})});
|
||||
TypeId gsubFunc = makeFunction(arena, stringType, {stringType, replArgType, optionalNumber}, {stringType, numberType});
|
||||
|
||||
TypeId formatFn = arena.addType(FunctionTypeVar{arena.addTypePack(TypePack{{stringType}, anyTypePack}), oneStringPack});
|
||||
|
||||
TableTypeVar::Props stringLib = {
|
||||
// FIXME string.byte "can" return a pack of numbers, but only if 2nd or 3rd arguments were supplied
|
||||
{"byte", {makeFunction(arena, stringType, {optionalNumber, optionalNumber}, {optionalNumber})}},
|
||||
// FIXME char takes a variadic pack of numbers
|
||||
{"char", {makeFunction(arena, std::nullopt, {numberType, optionalNumber, optionalNumber, optionalNumber}, {stringType})}},
|
||||
{"find", {makeFunction(arena, stringType, {stringType, optionalNumber, optionalBoolean}, {optionalNumber, optionalNumber})}},
|
||||
{"format", {formatFn}}, // FIXME
|
||||
{"gmatch", {gmatchFunc}},
|
||||
{"gsub", {gsubFunc}},
|
||||
{"len", {makeFunction(arena, stringType, {}, {numberType})}},
|
||||
{"lower", {stringToStringType}},
|
||||
{"match", {makeFunction(arena, stringType, {stringType, optionalNumber}, {optionalString})}},
|
||||
{"rep", {makeFunction(arena, stringType, {numberType}, {stringType})}},
|
||||
{"reverse", {stringToStringType}},
|
||||
{"sub", {makeFunction(arena, stringType, {numberType, optionalNumber}, {stringType})}},
|
||||
{"upper", {stringToStringType}},
|
||||
{"split", {makeFunction(arena, stringType, {stringType, optionalString},
|
||||
{arena.addType(TableTypeVar{{}, TableIndexer{numberType, stringType}, typeChecker.globalScope->level})})}},
|
||||
{"pack", {arena.addType(FunctionTypeVar{
|
||||
arena.addTypePack(TypePack{{stringType}, anyTypePack}),
|
||||
oneStringPack,
|
||||
})}},
|
||||
{"packsize", {makeFunction(arena, stringType, {}, {numberType})}},
|
||||
{"unpack", {arena.addType(FunctionTypeVar{
|
||||
arena.addTypePack(TypePack{{stringType, stringType, optionalNumber}}),
|
||||
anyTypePack,
|
||||
})}},
|
||||
};
|
||||
|
||||
assignPropDocumentationSymbols(stringLib, "@luau/global/string");
|
||||
addGlobalBinding(typeChecker, "string",
|
||||
arena.addType(TableTypeVar{stringLib, std::nullopt, typeChecker.globalScope->level, TableState::Sealed}), "@luau");
|
||||
}
|
||||
|
||||
TableTypeVar::Props debugLib{
|
||||
{"info", {makeIntersection(arena,
|
||||
{
|
||||
arena.addType(FunctionTypeVar{arena.addTypePack({typeChecker.threadType, numberType, stringType}), anyTypePack}),
|
||||
arena.addType(FunctionTypeVar{arena.addTypePack({numberType, stringType}), anyTypePack}),
|
||||
arena.addType(FunctionTypeVar{arena.addTypePack({anyFunction, stringType}), anyTypePack}),
|
||||
})}},
|
||||
{"traceback", {makeIntersection(arena,
|
||||
{
|
||||
makeFunction(arena, std::nullopt, {optionalString, optionalNumber}, {stringType}),
|
||||
makeFunction(arena, std::nullopt, {typeChecker.threadType, optionalString, optionalNumber}, {stringType}),
|
||||
})}},
|
||||
};
|
||||
|
||||
assignPropDocumentationSymbols(debugLib, "@luau/global/debug");
|
||||
addGlobalBinding(typeChecker, "debug",
|
||||
arena.addType(TableTypeVar{debugLib, std::nullopt, typeChecker.globalScope->level, Luau::TableState::Sealed}), "@luau");
|
||||
|
||||
TableTypeVar::Props utf8Lib = {
|
||||
{"char", {arena.addType(FunctionTypeVar{listOfAtLeastOneNumber, oneStringPack})}}, // FIXME
|
||||
{"charpattern", {stringType}},
|
||||
{"codes", {makeFunction(arena, std::nullopt, {stringType},
|
||||
{makeFunction(arena, std::nullopt, {stringType, numberType}, {numberType, numberType}), stringType, numberType})}},
|
||||
{"codepoint",
|
||||
{arena.addType(FunctionTypeVar{arena.addTypePack({stringType, optionalNumber, optionalNumber}), listOfAtLeastOneNumber})}}, // FIXME
|
||||
{"len", {makeFunction(arena, std::nullopt, {stringType, optionalNumber, optionalNumber}, {optionalNumber, numberType})}},
|
||||
{"offset", {makeFunction(arena, std::nullopt, {stringType, optionalNumber, optionalNumber}, {numberType})}},
|
||||
{"nfdnormalize", {makeFunction(arena, std::nullopt, {stringType}, {stringType})}},
|
||||
{"graphemes", {makeFunction(arena, std::nullopt, {stringType, optionalNumber, optionalNumber},
|
||||
{makeFunction(arena, std::nullopt, {}, {numberType, numberType})})}},
|
||||
{"nfcnormalize", {makeFunction(arena, std::nullopt, {stringType}, {stringType})}},
|
||||
};
|
||||
|
||||
assignPropDocumentationSymbols(utf8Lib, "@luau/global/utf8");
|
||||
addGlobalBinding(
|
||||
typeChecker, "utf8", arena.addType(TableTypeVar{utf8Lib, std::nullopt, typeChecker.globalScope->level, TableState::Sealed}), "@luau");
|
||||
|
||||
TypeId optionalV = makeOption(typeChecker, arena, genericV);
|
||||
|
||||
TypeId arrayOfV = arena.addType(TableTypeVar{{}, TableIndexer(numberType, genericV), typeChecker.globalScope->level});
|
||||
|
||||
TypePackId unpackArgsPack = arena.addTypePack(TypePack{{arrayOfV, optionalNumber, optionalNumber}});
|
||||
TypePackId unpackReturnPack = arena.addTypePack(TypePack{{}, anyTypePack});
|
||||
TypeId unpackFunc = arena.addType(FunctionTypeVar{{genericV}, {}, unpackArgsPack, unpackReturnPack});
|
||||
|
||||
TypeId packResult = arena.addType(TableTypeVar{
|
||||
TableTypeVar::Props{{"n", {numberType}}}, TableIndexer{numberType, numberType}, typeChecker.globalScope->level, TableState::Sealed});
|
||||
TypePackId packArgsPack = arena.addTypePack(TypePack{{}, anyTypePack});
|
||||
TypePackId packReturnPack = arena.addTypePack(TypePack{{packResult}});
|
||||
|
||||
TypeId comparator = makeFunction(arena, std::nullopt, {genericV, genericV}, {booleanType});
|
||||
TypeId optionalComparator = makeOption(typeChecker, arena, comparator);
|
||||
|
||||
TypeId packFn = arena.addType(FunctionTypeVar(packArgsPack, packReturnPack));
|
||||
|
||||
TableTypeVar::Props tableLib = {
|
||||
{"concat", {makeFunction(arena, std::nullopt, {genericV}, {}, {arrayOfV, optionalString, optionalNumber, optionalNumber}, {stringType})}},
|
||||
{"insert", {makeIntersection(arena, {makeFunction(arena, std::nullopt, {genericV}, {}, {arrayOfV, genericV}, {}),
|
||||
makeFunction(arena, std::nullopt, {genericV}, {}, {arrayOfV, numberType, genericV}, {})})}},
|
||||
{"maxn", {makeFunction(arena, std::nullopt, {genericV}, {}, {arrayOfV}, {numberType})}},
|
||||
{"remove", {makeFunction(arena, std::nullopt, {genericV}, {}, {arrayOfV, optionalNumber}, {optionalV})}},
|
||||
{"sort", {makeFunction(arena, std::nullopt, {genericV}, {}, {arrayOfV, optionalComparator}, {})}},
|
||||
{"create", {makeFunction(arena, std::nullopt, {genericV}, {}, {numberType, optionalV}, {arrayOfV})}},
|
||||
{"find", {makeFunction(arena, std::nullopt, {genericV}, {}, {arrayOfV, genericV, optionalNumber}, {optionalNumber})}},
|
||||
|
||||
{"unpack", {unpackFunc}}, // FIXME
|
||||
{"pack", {packFn}},
|
||||
|
||||
// Lua 5.0 compat
|
||||
{"getn", {makeFunction(arena, std::nullopt, {genericV}, {}, {arrayOfV}, {numberType})}},
|
||||
{"foreach", {makeFunction(arena, std::nullopt, {genericK, genericV}, {},
|
||||
{mapOfKtoV, makeFunction(arena, std::nullopt, {genericK, genericV}, {})}, {})}},
|
||||
{"foreachi", {makeFunction(arena, std::nullopt, {genericV}, {}, {arrayOfV, makeFunction(arena, std::nullopt, {genericV}, {})}, {})}},
|
||||
|
||||
// backported from Lua 5.3
|
||||
{"move", {makeFunction(arena, std::nullopt, {genericV}, {}, {arrayOfV, numberType, numberType, numberType, arrayOfV}, {})}},
|
||||
|
||||
// added in Luau (borrowed from LuaJIT)
|
||||
{"clear", {makeFunction(arena, std::nullopt, {genericK, genericV}, {}, {mapOfKtoV}, {})}},
|
||||
|
||||
{"freeze", {makeFunction(arena, std::nullopt, {genericK, genericV}, {}, {mapOfKtoV}, {mapOfKtoV})}},
|
||||
{"isfrozen", {makeFunction(arena, std::nullopt, {genericK, genericV}, {}, {mapOfKtoV}, {booleanType})}},
|
||||
};
|
||||
|
||||
assignPropDocumentationSymbols(tableLib, "@luau/global/table");
|
||||
addGlobalBinding(
|
||||
typeChecker, "table", arena.addType(TableTypeVar{tableLib, std::nullopt, typeChecker.globalScope->level, TableState::Sealed}), "@luau");
|
||||
|
||||
TableTypeVar::Props coroutineLib = {
|
||||
{"create", {makeFunction(arena, std::nullopt, {anyFunction}, {threadType})}},
|
||||
{"resume", {arena.addType(FunctionTypeVar{arena.addTypePack(TypePack{{threadType}, anyTypePack}), anyTypePack})}},
|
||||
{"running", {makeFunction(arena, std::nullopt, {}, {threadType})}},
|
||||
{"status", {makeFunction(arena, std::nullopt, {threadType}, {stringType})}},
|
||||
{"wrap", {makeFunction(
|
||||
arena, std::nullopt, {anyFunction}, {anyType})}}, // FIXME this technically returns a function, but we can't represent this
|
||||
// atm since it can be called with different arg types at different times
|
||||
{"yield", {arena.addType(FunctionTypeVar{anyTypePack, anyTypePack})}},
|
||||
{"isyieldable", {makeFunction(arena, std::nullopt, {}, {booleanType})}},
|
||||
};
|
||||
|
||||
assignPropDocumentationSymbols(coroutineLib, "@luau/global/coroutine");
|
||||
addGlobalBinding(typeChecker, "coroutine",
|
||||
arena.addType(TableTypeVar{coroutineLib, std::nullopt, typeChecker.globalScope->level, TableState::Sealed}), "@luau");
|
||||
|
||||
TypeId genericT = arena.addType(GenericTypeVar{"T"});
|
||||
TypeId genericR = arena.addType(GenericTypeVar{"R"});
|
||||
|
||||
// assert returns all arguments
|
||||
TypePackId assertArgs = arena.addTypePack({genericT, optionalString});
|
||||
TypePackId assertRets = arena.addTypePack({genericT});
|
||||
addGlobalBinding(typeChecker, "assert", arena.addType(FunctionTypeVar{assertArgs, assertRets}), "@luau");
|
||||
|
||||
addGlobalBinding(typeChecker, "print", arena.addType(FunctionTypeVar{anyTypePack, emptyPack}), "@luau");
|
||||
|
||||
addGlobalBinding(typeChecker, "type", makeFunction(arena, std::nullopt, {genericT}, {}, {genericT}, {stringType}), "@luau");
|
||||
addGlobalBinding(typeChecker, "typeof", makeFunction(arena, std::nullopt, {genericT}, {}, {genericT}, {stringType}), "@luau");
|
||||
|
||||
addGlobalBinding(typeChecker, "error", makeFunction(arena, std::nullopt, {genericT}, {}, {genericT, optionalNumber}, {}), "@luau");
|
||||
|
||||
addGlobalBinding(typeChecker, "tostring", makeFunction(arena, std::nullopt, {genericT}, {}, {genericT}, {stringType}), "@luau");
|
||||
addGlobalBinding(
|
||||
typeChecker, "tonumber", makeFunction(arena, std::nullopt, {genericT}, {}, {genericT, optionalNumber}, {numberType}), "@luau");
|
||||
|
||||
addGlobalBinding(
|
||||
typeChecker, "rawequal", makeFunction(arena, std::nullopt, {genericT, genericR}, {}, {genericT, genericR}, {booleanType}), "@luau");
|
||||
addGlobalBinding(
|
||||
typeChecker, "rawget", makeFunction(arena, std::nullopt, {genericK, genericV}, {}, {mapOfKtoV, genericK}, {genericV}), "@luau");
|
||||
addGlobalBinding(typeChecker, "rawset",
|
||||
makeFunction(arena, std::nullopt, {genericK, genericV}, {}, {mapOfKtoV, genericK, genericV}, {mapOfKtoV}), "@luau");
|
||||
|
||||
TypePackId genericTPack = arena.addTypePack({genericT});
|
||||
TypePackId genericRPack = arena.addTypePack({genericR});
|
||||
TypeId genericArgsToReturnFunction = arena.addType(
|
||||
FunctionTypeVar{{genericT, genericR}, {}, arena.addTypePack(TypePack{{}, genericTPack}), arena.addTypePack(TypePack{{}, genericRPack})});
|
||||
|
||||
TypeId setfenvArgType = makeUnion(arena, {numberType, genericArgsToReturnFunction});
|
||||
TypeId setfenvReturnType = makeOption(typeChecker, arena, genericArgsToReturnFunction);
|
||||
addGlobalBinding(typeChecker, "setfenv", makeFunction(arena, std::nullopt, {setfenvArgType, stringToAnyMap}, {setfenvReturnType}), "@luau");
|
||||
|
||||
TypePackId ipairsArgsTypePack = arena.addTypePack({arrayOfV});
|
||||
|
||||
TypeId ipairsNextFunctionType = arena.addType(
|
||||
FunctionTypeVar{{genericK, genericV}, {}, arena.addTypePack({arrayOfV, numberType}), arena.addTypePack({numberType, genericV})});
|
||||
|
||||
// ipairs returns 'next, Array<V>, 0' so we would need type-level primitives and change to
|
||||
// again, we have a direct reference to 'next' because ipairs returns it
|
||||
// ipairs<V>(t: Array<V>) -> ((Array<V>) -> (number, V), Array<V>, 0)
|
||||
TypePackId ipairsReturnTypePack = arena.addTypePack(TypePack{{ipairsNextFunctionType, arrayOfV, numberType}});
|
||||
|
||||
// ipairs<V>(t: Array<V>) -> ((Array<V>) -> (number, V), Array<V>, number)
|
||||
addGlobalBinding(typeChecker, "ipairs", arena.addType(FunctionTypeVar{{genericV}, {}, ipairsArgsTypePack, ipairsReturnTypePack}), "@luau");
|
||||
|
||||
TypePackId pcallArg0FnArgs = arena.addTypePack(TypePackVar{GenericTypeVar{"A"}});
|
||||
TypePackId pcallArg0FnRet = arena.addTypePack(TypePackVar{GenericTypeVar{"R"}});
|
||||
TypeId pcallArg0 = arena.addType(FunctionTypeVar{pcallArg0FnArgs, pcallArg0FnRet});
|
||||
TypePackId pcallArgsTypePack = arena.addTypePack(TypePack{{pcallArg0}, pcallArg0FnArgs});
|
||||
|
||||
TypePackId pcallReturnTypePack = arena.addTypePack(TypePack{{booleanType}, pcallArg0FnRet});
|
||||
|
||||
// pcall<A..., R...>(f: (A...) -> R..., args: A...) -> boolean, R...
|
||||
addGlobalBinding(typeChecker, "pcall",
|
||||
arena.addType(FunctionTypeVar{{}, {pcallArg0FnArgs, pcallArg0FnRet}, pcallArgsTypePack, pcallReturnTypePack}), "@luau");
|
||||
|
||||
// errors thrown by the function 'f' are propagated onto the function 'err' that accepts it.
|
||||
// and either 'f' or 'err' are valid results of this xpcall
|
||||
// if 'err' did throw an error, then it returns: false, "error in error handling"
|
||||
// TODO: the above is not represented (nor representable) in the type annotation below.
|
||||
//
|
||||
// The real type of xpcall is as such: <E, A..., R1..., R2...>(f: (A...) -> R1..., err: (E) -> R2..., A...) -> (true, R1...) | (false,
|
||||
// R2...)
|
||||
TypePackId genericAPack = arena.addTypePack(TypePackVar{GenericTypeVar{"A"}});
|
||||
TypePackId genericR1Pack = arena.addTypePack(TypePackVar{GenericTypeVar{"R1"}});
|
||||
TypePackId genericR2Pack = arena.addTypePack(TypePackVar{GenericTypeVar{"R2"}});
|
||||
|
||||
TypeId genericE = arena.addType(GenericTypeVar{"E"});
|
||||
|
||||
TypeId xpcallFArg = arena.addType(FunctionTypeVar{genericAPack, genericR1Pack});
|
||||
TypeId xpcallErrArg = arena.addType(FunctionTypeVar{arena.addTypePack({genericE}), genericR2Pack});
|
||||
|
||||
TypePackId xpcallArgsPack = arena.addTypePack({{xpcallFArg, xpcallErrArg}, genericAPack});
|
||||
TypePackId xpcallRetPack = arena.addTypePack({{booleanType}, genericR1Pack}); // FIXME
|
||||
|
||||
addGlobalBinding(typeChecker, "xpcall",
|
||||
arena.addType(FunctionTypeVar{{genericE}, {genericAPack, genericR1Pack, genericR2Pack}, xpcallArgsPack, xpcallRetPack}), "@luau");
|
||||
|
||||
addGlobalBinding(typeChecker, "unpack", unpackFunc, "@luau");
|
||||
|
||||
TypePackId selectArgsTypePack = arena.addTypePack(TypePack{
|
||||
{stringOrNumber},
|
||||
anyTypePack // FIXME? select() is tricky.
|
||||
});
|
||||
|
||||
addGlobalBinding(typeChecker, "select", arena.addType(FunctionTypeVar{selectArgsTypePack, anyTypePack}), "@luau");
|
||||
|
||||
// TODO: not completely correct. loadstring's return type should be a function or (nil, string)
|
||||
TypeId loadstringFunc = arena.addType(FunctionTypeVar{anyTypePack, oneAnyPack});
|
||||
|
||||
addGlobalBinding(typeChecker, "loadstring",
|
||||
makeFunction(arena, std::nullopt, {stringType, optionalString},
|
||||
{
|
||||
makeOption(typeChecker, arena, loadstringFunc),
|
||||
makeOption(typeChecker, arena, stringType),
|
||||
}),
|
||||
"@luau");
|
||||
|
||||
// a userdata object is "roughly" the same as a sealed empty table
|
||||
// except `type(newproxy(false))` evaluates to "userdata" so we may need another special type here too.
|
||||
// another important thing to note: the value passed in conditionally creates an empty metatable, and you have to use getmetatable, NOT
|
||||
// setmetatable.
|
||||
// TODO: change this to something Luau can understand how to reject `setmetatable(newproxy(false or true), {})`.
|
||||
TypeId sealedTable = arena.addType(TableTypeVar(TableState::Sealed, typeChecker.globalScope->level));
|
||||
addGlobalBinding(typeChecker, "newproxy", makeFunction(arena, std::nullopt, {optionalBoolean}, {sealedTable}), "@luau");
|
||||
}
|
||||
|
||||
// next<K, V>(t: Table<K, V>, i: K | nil) -> (K, V)
|
||||
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(typeChecker, arena, genericK)}});
|
||||
addGlobalBinding(typeChecker, "next",
|
||||
arena.addType(FunctionTypeVar{{genericK, genericV}, {}, nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})}), "@luau");
|
||||
|
||||
TypePackId pairsArgsTypePack = arena.addTypePack({mapOfKtoV});
|
||||
|
||||
TypeId pairsNext = (FFlag::LuauRankNTypes ? arena.addType(FunctionTypeVar{nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})})
|
||||
: getGlobalBinding(typeChecker, "next"));
|
||||
TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, nilType}});
|
||||
|
||||
// NOTE we are missing 'i: K | nil' argument in the first return types' argument.
|
||||
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>) -> (K, V), Table<K, V>, nil)
|
||||
addGlobalBinding(typeChecker, "pairs", arena.addType(FunctionTypeVar{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
|
||||
|
||||
TypeId genericMT = arena.addType(GenericTypeVar{"MT"});
|
||||
|
||||
TableTypeVar tab{TableState::Generic, typeChecker.globalScope->level};
|
||||
TypeId tabTy = arena.addType(tab);
|
||||
|
||||
TypeId tableMetaMT = arena.addType(MetatableTypeVar{tabTy, genericMT});
|
||||
|
||||
addGlobalBinding(typeChecker, "getmetatable", makeFunction(arena, std::nullopt, {genericMT}, {}, {tableMetaMT}, {genericMT}), "@luau");
|
||||
|
||||
// setmetatable<MT>({ @metatable MT }, MT) -> { @metatable MT }
|
||||
// clang-format off
|
||||
addGlobalBinding(typeChecker, "setmetatable",
|
||||
arena.addType(
|
||||
FunctionTypeVar{
|
||||
{genericMT},
|
||||
{},
|
||||
arena.addTypePack(TypePack{{tableMetaMT, genericMT}}),
|
||||
arena.addTypePack(TypePack{{tableMetaMT}})
|
||||
}
|
||||
), "@luau"
|
||||
);
|
||||
// clang-format on
|
||||
|
||||
for (const auto& pair : typeChecker.globalScope->bindings)
|
||||
{
|
||||
persist(pair.second.typeId);
|
||||
|
||||
if (TableTypeVar* ttv = getMutable<TableTypeVar>(pair.second.typeId))
|
||||
ttv->name = toString(pair.first);
|
||||
}
|
||||
|
||||
attachMagicFunction(getGlobalBinding(typeChecker, "assert"), magicFunctionAssert);
|
||||
attachMagicFunction(getGlobalBinding(typeChecker, "setmetatable"), magicFunctionSetMetaTable);
|
||||
attachMagicFunction(getGlobalBinding(typeChecker, "select"), magicFunctionSelect);
|
||||
|
||||
auto tableLib = getMutable<TableTypeVar>(getGlobalBinding(typeChecker, "table"));
|
||||
attachMagicFunction(tableLib->props["pack"].type, magicFunctionPack);
|
||||
|
||||
auto stringLib = getMutable<TableTypeVar>(getGlobalBinding(typeChecker, "string"));
|
||||
attachMagicFunction(stringLib->props["format"].type, magicFunctionFormat);
|
||||
|
||||
attachMagicFunction(getGlobalBinding(typeChecker, "require"), magicFunctionRequire);
|
||||
}
|
||||
|
||||
static std::optional<ExprResult<TypePackId>> magicFunctionSelect(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult)
|
||||
{
|
||||
auto [paramPack, _predicates] = exprResult;
|
||||
|
||||
(void)scope;
|
||||
|
||||
if (expr.args.size <= 0)
|
||||
{
|
||||
typechecker.reportError(TypeError{expr.location, GenericError{"select should take 1 or more arguments"}});
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
AstExpr* arg1 = expr.args.data[0];
|
||||
if (AstExprConstantNumber* num = arg1->as<AstExprConstantNumber>())
|
||||
{
|
||||
const auto& [v, tail] = flatten(paramPack);
|
||||
|
||||
int offset = int(num->value);
|
||||
if (offset > 0)
|
||||
{
|
||||
if (size_t(offset) < v.size())
|
||||
{
|
||||
std::vector<TypeId> result(v.begin() + offset, v.end());
|
||||
return ExprResult<TypePackId>{typechecker.currentModule->internalTypes.addTypePack(TypePack{std::move(result), tail})};
|
||||
}
|
||||
else if (tail)
|
||||
return ExprResult<TypePackId>{*tail};
|
||||
}
|
||||
|
||||
typechecker.reportError(TypeError{arg1->location, GenericError{"bad argument #1 to select (index out of range)"}});
|
||||
}
|
||||
else if (AstExprConstantString* str = arg1->as<AstExprConstantString>())
|
||||
{
|
||||
if (str->value.size == 1 && str->value.data[0] == '#')
|
||||
return ExprResult<TypePackId>{typechecker.currentModule->internalTypes.addTypePack({typechecker.numberType})};
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static std::optional<ExprResult<TypePackId>> magicFunctionSetMetaTable(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult)
|
||||
{
|
||||
auto [paramPack, _predicates] = exprResult;
|
||||
|
||||
TypeArena& arena = typechecker.currentModule->internalTypes;
|
||||
|
||||
std::vector<TypeId> expectedArgs = typechecker.unTypePack(scope, paramPack, 2, expr.location);
|
||||
|
||||
TypeId target = follow(expectedArgs[0]);
|
||||
TypeId mt = follow(expectedArgs[1]);
|
||||
|
||||
if (const auto& tab = get<TableTypeVar>(target))
|
||||
{
|
||||
if (target->persistent)
|
||||
{
|
||||
typechecker.reportError(TypeError{expr.location, CannotExtendTable{target, CannotExtendTable::Metatable}});
|
||||
}
|
||||
else
|
||||
{
|
||||
typechecker.tablify(mt);
|
||||
|
||||
const TableTypeVar* mtTtv = get<TableTypeVar>(mt);
|
||||
MetatableTypeVar mtv{target, mt};
|
||||
if ((tab->name || tab->syntheticName) && (mtTtv && (mtTtv->name || mtTtv->syntheticName)))
|
||||
{
|
||||
std::string tableName = tab->name ? *tab->name : *tab->syntheticName;
|
||||
std::string metatableName = mtTtv->name ? *mtTtv->name : *mtTtv->syntheticName;
|
||||
|
||||
if (tableName == metatableName)
|
||||
mtv.syntheticName = tableName;
|
||||
else
|
||||
mtv.syntheticName = "{ @metatable: " + metatableName + ", " + tableName + " }";
|
||||
}
|
||||
|
||||
TypeId mtTy = arena.addType(mtv);
|
||||
|
||||
AstExpr* targetExpr = expr.args.data[0];
|
||||
if (AstExprLocal* targetLocal = targetExpr->as<AstExprLocal>())
|
||||
{
|
||||
const Name targetName(targetLocal->local->name.value);
|
||||
scope->bindings[targetLocal->local] = Binding{mtTy, expr.location};
|
||||
}
|
||||
|
||||
return ExprResult<TypePackId>{arena.addTypePack({mtTy})};
|
||||
}
|
||||
}
|
||||
else if (get<AnyTypeVar>(target) || get<ErrorTypeVar>(target) || isTableIntersection(target))
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
typechecker.reportError(TypeError{expr.location, GenericError{"setmetatable should take a table"}});
|
||||
}
|
||||
|
||||
return ExprResult<TypePackId>{arena.addTypePack({target})};
|
||||
}
|
||||
|
||||
static std::optional<ExprResult<TypePackId>> magicFunctionAssert(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult)
|
||||
{
|
||||
auto [paramPack, predicates] = exprResult;
|
||||
|
||||
if (expr.args.size < 1)
|
||||
return ExprResult<TypePackId>{paramPack};
|
||||
|
||||
typechecker.reportErrors(typechecker.resolve(predicates, scope, true));
|
||||
|
||||
return ExprResult<TypePackId>{paramPack};
|
||||
}
|
||||
|
||||
static std::optional<ExprResult<TypePackId>> magicFunctionPack(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult)
|
||||
{
|
||||
auto [paramPack, _predicates] = exprResult;
|
||||
|
||||
TypeArena& arena = typechecker.currentModule->internalTypes;
|
||||
|
||||
const auto& [paramTypes, paramTail] = flatten(paramPack);
|
||||
|
||||
std::vector<TypeId> options;
|
||||
options.reserve(paramTypes.size());
|
||||
for (auto type : paramTypes)
|
||||
options.push_back(type);
|
||||
|
||||
if (paramTail)
|
||||
{
|
||||
if (const VariadicTypePack* vtp = get<VariadicTypePack>(*paramTail))
|
||||
options.push_back(vtp->ty);
|
||||
}
|
||||
|
||||
options = typechecker.reduceUnion(options);
|
||||
|
||||
// table.pack() -> {| n: number, [number]: nil |}
|
||||
// table.pack(1) -> {| n: number, [number]: number |}
|
||||
// table.pack(1, "foo") -> {| n: number, [number]: number | string |}
|
||||
TypeId result = nullptr;
|
||||
if (options.empty())
|
||||
result = typechecker.nilType;
|
||||
else if (options.size() == 1)
|
||||
result = options[0];
|
||||
else
|
||||
result = arena.addType(UnionTypeVar{std::move(options)});
|
||||
|
||||
TypeId packedTable = arena.addType(
|
||||
TableTypeVar{{{"n", {typechecker.numberType}}}, TableIndexer(typechecker.numberType, result), scope->level, TableState::Sealed});
|
||||
|
||||
return ExprResult<TypePackId>{arena.addTypePack({packedTable})};
|
||||
}
|
||||
|
||||
static bool checkRequirePath(TypeChecker& typechecker, AstExpr* expr)
|
||||
{
|
||||
// require(foo.parent.bar) will technically work, but it depends on legacy goop that
|
||||
// Luau does not and could not support without a bunch of work. It's deprecated anyway, so
|
||||
// we'll warn here if we see it.
|
||||
bool good = true;
|
||||
AstExprIndexName* indexExpr = expr->as<AstExprIndexName>();
|
||||
|
||||
while (indexExpr)
|
||||
{
|
||||
if (indexExpr->index == "parent")
|
||||
{
|
||||
typechecker.reportError(indexExpr->indexLocation, DeprecatedApiUsed{"parent", "Parent"});
|
||||
good = false;
|
||||
}
|
||||
|
||||
indexExpr = indexExpr->expr->as<AstExprIndexName>();
|
||||
}
|
||||
|
||||
return good;
|
||||
}
|
||||
|
||||
static std::optional<ExprResult<TypePackId>> magicFunctionRequire(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult)
|
||||
{
|
||||
TypeArena& arena = typechecker.currentModule->internalTypes;
|
||||
|
||||
if (expr.args.size != 1)
|
||||
{
|
||||
typechecker.reportError(TypeError{expr.location, GenericError{"require takes 1 argument"}});
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
AstExpr* require = expr.args.data[0];
|
||||
|
||||
if (!checkRequirePath(typechecker, require))
|
||||
return std::nullopt;
|
||||
|
||||
if (auto moduleInfo = typechecker.resolver->resolveModuleInfo(typechecker.currentModuleName, *require))
|
||||
return ExprResult<TypePackId>{arena.addTypePack({typechecker.checkRequire(scope, *moduleInfo, expr.location)})};
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
278
Analysis/src/Config.cpp
Normal file
278
Analysis/src/Config.cpp
Normal file
|
@ -0,0 +1,278 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Config.h"
|
||||
|
||||
#include "Luau/Parser.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using Error = std::optional<std::string>;
|
||||
|
||||
}
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
static Error parseBoolean(bool& result, const std::string& value)
|
||||
{
|
||||
if (value == "true")
|
||||
result = true;
|
||||
else if (value == "false")
|
||||
result = false;
|
||||
else
|
||||
return Error{"Bad setting '" + value + "'. Valid options are true and false"};
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Error parseModeString(Mode& mode, const std::string& modeString, bool compat)
|
||||
{
|
||||
if (modeString == "nocheck")
|
||||
mode = Mode::NoCheck;
|
||||
else if (modeString == "strict")
|
||||
mode = Mode::Strict;
|
||||
else if (modeString == "nonstrict")
|
||||
mode = Mode::Nonstrict;
|
||||
else if (modeString == "noinfer" && compat)
|
||||
mode = Mode::NoCheck;
|
||||
else
|
||||
return Error{"Bad mode \"" + modeString + "\". Valid options are nocheck, nonstrict, and strict"};
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static Error parseLintRuleStringForCode(
|
||||
LintOptions& enabledLints, LintOptions& fatalLints, LintWarning::Code code, const std::string& value, bool compat)
|
||||
{
|
||||
if (value == "true")
|
||||
{
|
||||
enabledLints.enableWarning(code);
|
||||
}
|
||||
else if (value == "false")
|
||||
{
|
||||
enabledLints.disableWarning(code);
|
||||
}
|
||||
else if (compat)
|
||||
{
|
||||
if (value == "enabled")
|
||||
{
|
||||
enabledLints.enableWarning(code);
|
||||
fatalLints.disableWarning(code);
|
||||
}
|
||||
else if (value == "disabled")
|
||||
{
|
||||
enabledLints.disableWarning(code);
|
||||
fatalLints.disableWarning(code);
|
||||
}
|
||||
else if (value == "fatal")
|
||||
{
|
||||
enabledLints.enableWarning(code);
|
||||
fatalLints.enableWarning(code);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Error{"Bad setting '" + value + "'. Valid options are enabled, disabled, and fatal"};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return Error{"Bad setting '" + value + "'. Valid options are true and false"};
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Error parseLintRuleString(LintOptions& enabledLints, LintOptions& fatalLints, const std::string& warningName, const std::string& value, bool compat)
|
||||
{
|
||||
if (warningName == "*")
|
||||
{
|
||||
for (int code = LintWarning::Code_Unknown; code < LintWarning::Code__Count; ++code)
|
||||
{
|
||||
if (auto err = parseLintRuleStringForCode(enabledLints, fatalLints, LintWarning::Code(code), value, compat))
|
||||
return Error{"In key " + warningName + ": " + *err};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LintWarning::Code code = LintWarning::parseName(warningName.c_str());
|
||||
|
||||
if (code == LintWarning::Code_Unknown)
|
||||
return Error{"Unknown lint " + warningName};
|
||||
|
||||
if (auto err = parseLintRuleStringForCode(enabledLints, fatalLints, code, value, compat))
|
||||
return Error{"In key " + warningName + ": " + *err};
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static void next(Lexer& lexer)
|
||||
{
|
||||
lexer.next();
|
||||
|
||||
// skip C-style comments as Lexer only understands Lua-style comments atm
|
||||
while (lexer.current().type == '/')
|
||||
{
|
||||
Lexeme peek = lexer.lookahead();
|
||||
|
||||
if (peek.type != '/' || peek.location.begin != lexer.current().location.end)
|
||||
break;
|
||||
|
||||
lexer.nextline();
|
||||
}
|
||||
}
|
||||
|
||||
static Error fail(Lexer& lexer, const char* message)
|
||||
{
|
||||
Lexeme cur = lexer.current();
|
||||
|
||||
return format("Expected %s at line %d, got %s instead", message, cur.location.begin.line + 1, cur.toString().c_str());
|
||||
}
|
||||
|
||||
template<typename Action>
|
||||
static Error parseJson(const std::string& contents, Action action)
|
||||
{
|
||||
Allocator allocator;
|
||||
AstNameTable names(allocator);
|
||||
Lexer lexer(contents.data(), contents.size(), names);
|
||||
next(lexer);
|
||||
|
||||
std::vector<std::string> keys;
|
||||
bool arrayTop = false; // we don't support nested arrays
|
||||
|
||||
if (lexer.current().type != '{')
|
||||
return fail(lexer, "'{'");
|
||||
next(lexer);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (arrayTop)
|
||||
{
|
||||
if (lexer.current().type == ']')
|
||||
{
|
||||
next(lexer);
|
||||
arrayTop = false;
|
||||
|
||||
LUAU_ASSERT(!keys.empty());
|
||||
keys.pop_back();
|
||||
|
||||
if (lexer.current().type == ',')
|
||||
next(lexer);
|
||||
else if (lexer.current().type != '}')
|
||||
return fail(lexer, "',' or '}'");
|
||||
}
|
||||
else if (lexer.current().type == Lexeme::QuotedString)
|
||||
{
|
||||
std::string value(lexer.current().data, lexer.current().length);
|
||||
next(lexer);
|
||||
|
||||
if (Error err = action(keys, value))
|
||||
return err;
|
||||
|
||||
if (lexer.current().type == ',')
|
||||
next(lexer);
|
||||
else if (lexer.current().type != ']')
|
||||
return fail(lexer, "',' or ']'");
|
||||
}
|
||||
else
|
||||
return fail(lexer, "array element or ']'");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lexer.current().type == '}')
|
||||
{
|
||||
next(lexer);
|
||||
|
||||
if (keys.empty())
|
||||
{
|
||||
if (lexer.current().type != Lexeme::Eof)
|
||||
return fail(lexer, "end of file");
|
||||
|
||||
return {};
|
||||
}
|
||||
else
|
||||
keys.pop_back();
|
||||
|
||||
if (lexer.current().type == ',')
|
||||
next(lexer);
|
||||
else if (lexer.current().type != '}')
|
||||
return fail(lexer, "',' or '}'");
|
||||
}
|
||||
else if (lexer.current().type == Lexeme::QuotedString)
|
||||
{
|
||||
std::string key(lexer.current().data, lexer.current().length);
|
||||
next(lexer);
|
||||
|
||||
keys.push_back(key);
|
||||
|
||||
if (lexer.current().type != ':')
|
||||
return fail(lexer, "':'");
|
||||
next(lexer);
|
||||
|
||||
if (lexer.current().type == '{' || lexer.current().type == '[')
|
||||
{
|
||||
arrayTop = (lexer.current().type == '[');
|
||||
next(lexer);
|
||||
}
|
||||
else if (lexer.current().type == Lexeme::QuotedString || lexer.current().type == Lexeme::ReservedTrue ||
|
||||
lexer.current().type == Lexeme::ReservedFalse)
|
||||
{
|
||||
std::string value = lexer.current().type == Lexeme::QuotedString
|
||||
? std::string(lexer.current().data, lexer.current().length)
|
||||
: (lexer.current().type == Lexeme::ReservedTrue ? "true" : "false");
|
||||
next(lexer);
|
||||
|
||||
if (Error err = action(keys, value))
|
||||
return err;
|
||||
|
||||
keys.pop_back();
|
||||
|
||||
if (lexer.current().type == ',')
|
||||
next(lexer);
|
||||
else if (lexer.current().type != '}')
|
||||
return fail(lexer, "',' or '}'");
|
||||
}
|
||||
else
|
||||
return fail(lexer, "field value");
|
||||
}
|
||||
else
|
||||
return fail(lexer, "field key");
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Error parseConfig(const std::string& contents, Config& config, bool compat)
|
||||
{
|
||||
return parseJson(contents, [&](const std::vector<std::string>& keys, const std::string& value) -> Error {
|
||||
if (keys.size() == 1 && keys[0] == "languageMode")
|
||||
return parseModeString(config.mode, value, compat);
|
||||
else if (keys.size() == 2 && keys[0] == "lint")
|
||||
return parseLintRuleString(config.enabledLint, config.fatalLint, keys[1], value, compat);
|
||||
else if (keys.size() == 1 && keys[0] == "lintErrors")
|
||||
return parseBoolean(config.lintErrors, value);
|
||||
else if (keys.size() == 1 && keys[0] == "typeErrors")
|
||||
return parseBoolean(config.typeErrors, value);
|
||||
else if (keys.size() == 1 && keys[0] == "globals")
|
||||
{
|
||||
config.globals.push_back(value);
|
||||
return std::nullopt;
|
||||
}
|
||||
else if (compat && keys.size() == 2 && keys[0] == "language" && keys[1] == "mode")
|
||||
return parseModeString(config.mode, value, compat);
|
||||
else
|
||||
{
|
||||
std::vector<std::string_view> keysv(keys.begin(), keys.end());
|
||||
return "Unknown key " + join(keysv, "/");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const Config& NullConfigResolver::getConfig(const ModuleName& name) const
|
||||
{
|
||||
return defaultConfig;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
238
Analysis/src/EmbeddedBuiltinDefinitions.cpp
Normal file
238
Analysis/src/EmbeddedBuiltinDefinitions.cpp
Normal file
|
@ -0,0 +1,238 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauParseGenericFunctions)
|
||||
LUAU_FASTFLAG(LuauGenericFunctions)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
static const std::string kBuiltinDefinitionLuaSrc = R"BUILTIN_SRC(
|
||||
|
||||
declare bit32: {
|
||||
-- band, bor, bxor, and btest are declared in C++
|
||||
rrotate: (number, number) -> number,
|
||||
lrotate: (number, number) -> number,
|
||||
lshift: (number, number) -> number,
|
||||
arshift: (number, number) -> number,
|
||||
rshift: (number, number) -> number,
|
||||
bnot: (number) -> number,
|
||||
extract: (number, number, number?) -> number,
|
||||
replace: (number, number, number, number?) -> number,
|
||||
}
|
||||
|
||||
declare math: {
|
||||
frexp: (number) -> (number, number),
|
||||
ldexp: (number, number) -> number,
|
||||
fmod: (number, number) -> number,
|
||||
modf: (number) -> (number, number),
|
||||
pow: (number, number) -> number,
|
||||
exp: (number) -> number,
|
||||
|
||||
ceil: (number) -> number,
|
||||
floor: (number) -> number,
|
||||
abs: (number) -> number,
|
||||
sqrt: (number) -> number,
|
||||
|
||||
log: (number, number?) -> number,
|
||||
log10: (number) -> number,
|
||||
|
||||
rad: (number) -> number,
|
||||
deg: (number) -> number,
|
||||
|
||||
sin: (number) -> number,
|
||||
cos: (number) -> number,
|
||||
tan: (number) -> number,
|
||||
sinh: (number) -> number,
|
||||
cosh: (number) -> number,
|
||||
tanh: (number) -> number,
|
||||
atan: (number) -> number,
|
||||
acos: (number) -> number,
|
||||
asin: (number) -> number,
|
||||
atan2: (number, number) -> number,
|
||||
|
||||
-- min and max are declared in C++.
|
||||
|
||||
pi: number,
|
||||
huge: number,
|
||||
|
||||
randomseed: (number) -> (),
|
||||
random: (number?, number?) -> number,
|
||||
|
||||
sign: (number) -> number,
|
||||
clamp: (number, number, number) -> number,
|
||||
noise: (number, number?, number?) -> number,
|
||||
round: (number) -> number,
|
||||
}
|
||||
|
||||
type DateTypeArg = {
|
||||
year: number,
|
||||
month: number,
|
||||
day: number,
|
||||
hour: number?,
|
||||
min: number?,
|
||||
sec: number?,
|
||||
isdst: boolean?,
|
||||
}
|
||||
|
||||
type DateTypeResult = {
|
||||
year: number,
|
||||
month: number,
|
||||
wday: number,
|
||||
yday: number,
|
||||
day: number,
|
||||
hour: number,
|
||||
min: number,
|
||||
sec: number,
|
||||
isdst: boolean,
|
||||
}
|
||||
|
||||
declare os: {
|
||||
time: (DateTypeArg?) -> number,
|
||||
date: (string?, number?) -> DateTypeResult | string,
|
||||
difftime: (DateTypeResult | number, DateTypeResult | number) -> number,
|
||||
clock: () -> number,
|
||||
}
|
||||
|
||||
declare function require(target: any): any
|
||||
|
||||
declare function getfenv(target: any?): { [string]: any }
|
||||
|
||||
declare _G: any
|
||||
declare _VERSION: string
|
||||
|
||||
declare function gcinfo(): number
|
||||
|
||||
)BUILTIN_SRC";
|
||||
|
||||
std::string getBuiltinDefinitionSource()
|
||||
{
|
||||
std::string src = kBuiltinDefinitionLuaSrc;
|
||||
|
||||
if (FFlag::LuauParseGenericFunctions && FFlag::LuauGenericFunctions)
|
||||
{
|
||||
src += R"(
|
||||
declare function print<T...>(...: T...)
|
||||
|
||||
declare function type<T>(value: T): string
|
||||
declare function typeof<T>(value: T): string
|
||||
|
||||
-- `assert` has a magic function attached that will give more detailed type information
|
||||
declare function assert<T>(value: T, errorMessage: string?): T
|
||||
|
||||
declare function error<T>(message: T, level: number?)
|
||||
|
||||
declare function tostring<T>(value: T): string
|
||||
declare function tonumber<T>(value: T, radix: number?): number
|
||||
|
||||
declare function rawequal<T1, T2>(a: T1, b: T2): boolean
|
||||
declare function rawget<K, V>(tab: {[K]: V}, k: K): V
|
||||
declare function rawset<K, V>(tab: {[K]: V}, k: K, v: V): {[K]: V}
|
||||
|
||||
declare function setfenv<T..., R...>(target: number | (T...) -> R..., env: {[string]: any}): ((T...) -> R...)?
|
||||
|
||||
declare function ipairs<V>(tab: {V}): (({V}, number) -> (number, V), {V}, number)
|
||||
|
||||
declare function pcall<A..., R...>(f: (A...) -> R..., ...: A...): (boolean, R...)
|
||||
|
||||
-- FIXME: The actual type of `xpcall` is:
|
||||
-- <E, A..., R1..., R2...>(f: (A...) -> R1..., err: (E) -> R2..., A...) -> (true, R1...) | (false, R2...)
|
||||
-- Since we can't represent the return value, we use (boolean, R1...).
|
||||
declare function xpcall<E, A..., R1..., R2...>(f: (A...) -> R1..., err: (E) -> R2..., ...: A...): (boolean, R1...)
|
||||
|
||||
-- `select` has a magic function attached to provide more detailed type information
|
||||
declare function select<A...>(i: string | number, ...: A...): ...any
|
||||
|
||||
-- FIXME: This type is not entirely correct - `loadstring` returns a function or
|
||||
-- (nil, string).
|
||||
declare function loadstring<A...>(src: string, chunkname: string?): (((A...) -> any)?, string?)
|
||||
|
||||
-- a userdata object is "roughly" the same as a sealed empty table
|
||||
-- except `type(newproxy(false))` evaluates to "userdata" so we may need another special type here too.
|
||||
-- another important thing to note: the value passed in conditionally creates an empty metatable, and you have to use getmetatable, NOT
|
||||
-- setmetatable.
|
||||
-- FIXME: change this to something Luau can understand how to reject `setmetatable(newproxy(false or true), {})`.
|
||||
declare function newproxy(mt: boolean?): {}
|
||||
|
||||
declare coroutine: {
|
||||
create: <A..., R...>((A...) -> R...) -> thread,
|
||||
resume: <A..., R...>(thread, A...) -> (boolean, R...),
|
||||
running: () -> thread,
|
||||
status: (thread) -> string,
|
||||
-- FIXME: This technically returns a function, but we can't represent this yet.
|
||||
wrap: <A..., R...>((A...) -> R...) -> any,
|
||||
yield: <A..., R...>(A...) -> R...,
|
||||
isyieldable: () -> boolean,
|
||||
}
|
||||
|
||||
declare table: {
|
||||
concat: <V>({V}, string?, number?, number?) -> string,
|
||||
insert: (<V>({V}, V) -> ()) & (<V>({V}, number, V) -> ()),
|
||||
maxn: <V>({V}) -> number,
|
||||
remove: <V>({V}, number?) -> V?,
|
||||
sort: <V>({V}, ((V, V) -> boolean)?) -> (),
|
||||
create: <V>(number, V?) -> {V},
|
||||
find: <V>({V}, V, number?) -> number?,
|
||||
|
||||
unpack: <V>({V}, number?, number?) -> ...V,
|
||||
pack: <V>(...V) -> { n: number, [number]: V },
|
||||
|
||||
getn: <V>({V}) -> number,
|
||||
foreach: <K, V>({[K]: V}, (K, V) -> ()) -> (),
|
||||
foreachi: <V>({V}, (number, V) -> ()) -> (),
|
||||
|
||||
move: <V>({V}, number, number, number, {V}?) -> (),
|
||||
clear: <K, V>({[K]: V}) -> (),
|
||||
|
||||
freeze: <K, V>({[K]: V}) -> {[K]: V},
|
||||
isfrozen: <K, V>({[K]: V}) -> boolean,
|
||||
}
|
||||
|
||||
declare debug: {
|
||||
info: (<R...>(thread, number, string) -> R...) & (<R...>(number, string) -> R...) & (<A..., R1..., R2...>((A...) -> R1..., string) -> R2...),
|
||||
traceback: ((string?, number?) -> string) & ((thread, string?, number?) -> string),
|
||||
}
|
||||
|
||||
declare utf8: {
|
||||
char: (number, ...number) -> string,
|
||||
charpattern: string,
|
||||
codes: (string) -> ((string, number) -> (number, number), string, number),
|
||||
-- FIXME
|
||||
codepoint: (string, number?, number?) -> (number, ...number),
|
||||
len: (string, number?, number?) -> (number?, number?),
|
||||
offset: (string, number?, number?) -> number,
|
||||
nfdnormalize: (string) -> string,
|
||||
nfcnormalize: (string) -> string,
|
||||
graphemes: (string, number?, number?) -> (() -> (number, number)),
|
||||
}
|
||||
|
||||
declare string: {
|
||||
byte: (string, number?, number?) -> ...number,
|
||||
char: (number, ...number) -> string,
|
||||
find: (string, string, number?, boolean?) -> (number?, number?),
|
||||
-- `string.format` has a magic function attached that will provide more type information for literal format strings.
|
||||
format: <A...>(string, A...) -> string,
|
||||
gmatch: (string, string) -> () -> (...string),
|
||||
-- gsub is defined in C++ because we don't have syntax for describing a generic table.
|
||||
len: (string) -> number,
|
||||
lower: (string) -> string,
|
||||
match: (string, string, number?) -> string?,
|
||||
rep: (string, number) -> string,
|
||||
reverse: (string) -> string,
|
||||
sub: (string, number, number?) -> string,
|
||||
upper: (string) -> string,
|
||||
split: (string, string, string?) -> {string},
|
||||
pack: <A...>(string, A...) -> string,
|
||||
packsize: (string) -> number,
|
||||
unpack: <R...>(string, string, number?) -> R...,
|
||||
}
|
||||
|
||||
-- Cannot use `typeof` here because it will produce a polytype when we expect a monotype.
|
||||
declare function unpack<V>(tab: {V}, i: number?, j: number?): ...V
|
||||
)";
|
||||
}
|
||||
|
||||
return src;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
751
Analysis/src/Error.cpp
Normal file
751
Analysis/src/Error.cpp
Normal file
|
@ -0,0 +1,751 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Error.h"
|
||||
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/ToString.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAG(LuauFasterStringifier)
|
||||
|
||||
static std::string wrongNumberOfArgsString(size_t expectedCount, size_t actualCount, bool isTypeArgs = false)
|
||||
{
|
||||
std::string s = "expects " + std::to_string(expectedCount) + " ";
|
||||
|
||||
if (isTypeArgs)
|
||||
s += "type ";
|
||||
|
||||
s += "argument";
|
||||
if (expectedCount != 1)
|
||||
s += "s";
|
||||
|
||||
s += ", but ";
|
||||
|
||||
if (actualCount == 0)
|
||||
{
|
||||
s += "none";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (actualCount < expectedCount)
|
||||
s += "only ";
|
||||
|
||||
s += std::to_string(actualCount);
|
||||
}
|
||||
|
||||
s += (actualCount == 1) ? " is" : " are";
|
||||
|
||||
s += " specified";
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct ErrorConverter
|
||||
{
|
||||
std::string operator()(const Luau::TypeMismatch& tm) const
|
||||
{
|
||||
ToStringOptions opts;
|
||||
return "Type '" + Luau::toString(tm.givenType, opts) + "' could not be converted into '" + Luau::toString(tm.wantedType, opts) + "'";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::UnknownSymbol& e) const
|
||||
{
|
||||
switch (e.context)
|
||||
{
|
||||
case UnknownSymbol::Binding:
|
||||
return "Unknown global '" + e.name + "'";
|
||||
case UnknownSymbol::Type:
|
||||
return "Unknown type '" + e.name + "'";
|
||||
case UnknownSymbol::Generic:
|
||||
return "Unknown generic '" + e.name + "'";
|
||||
}
|
||||
|
||||
LUAU_ASSERT(!"Unexpected context for UnknownSymbol");
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::UnknownProperty& e) const
|
||||
{
|
||||
TypeId t = follow(e.table);
|
||||
if (get<TableTypeVar>(t))
|
||||
return "Key '" + e.key + "' not found in table '" + Luau::toString(t) + "'";
|
||||
else if (get<ClassTypeVar>(t))
|
||||
return "Key '" + e.key + "' not found in class '" + Luau::toString(t) + "'";
|
||||
else
|
||||
return "Type '" + Luau::toString(e.table) + "' does not have key '" + e.key + "'";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::NotATable& e) const
|
||||
{
|
||||
return "Expected type table, got '" + Luau::toString(e.ty) + "' instead";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::CannotExtendTable& e) const
|
||||
{
|
||||
switch (e.context)
|
||||
{
|
||||
case Luau::CannotExtendTable::Property:
|
||||
return "Cannot add property '" + e.prop + "' to table '" + Luau::toString(e.tableType) + "'";
|
||||
case Luau::CannotExtendTable::Metatable:
|
||||
return "Cannot add metatable to table '" + Luau::toString(e.tableType) + "'";
|
||||
case Luau::CannotExtendTable::Indexer:
|
||||
return "Cannot add indexer to table '" + Luau::toString(e.tableType) + "'";
|
||||
}
|
||||
|
||||
LUAU_ASSERT(!"Unknown context");
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::OnlyTablesCanHaveMethods& e) const
|
||||
{
|
||||
return "Cannot add method to non-table type '" + Luau::toString(e.tableType) + "'";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::DuplicateTypeDefinition& e) const
|
||||
{
|
||||
return "Redefinition of type '" + e.name + "', previously defined at line " + std::to_string(e.previousLocation.begin.line + 1);
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::CountMismatch& e) const
|
||||
{
|
||||
switch (e.context)
|
||||
{
|
||||
case CountMismatch::Return:
|
||||
{
|
||||
const std::string expectedS = e.expected == 1 ? "" : "s";
|
||||
const std::string actualS = e.actual == 1 ? "is" : "are";
|
||||
return "Expected to return " + std::to_string(e.expected) + " value" + expectedS + ", but " + std::to_string(e.actual) + " " + actualS +
|
||||
" returned here";
|
||||
}
|
||||
case CountMismatch::Result:
|
||||
if (e.expected > e.actual)
|
||||
return "Function returns " + std::to_string(e.expected) + " values but there are only " + std::to_string(e.expected) +
|
||||
" values to unpack them into.";
|
||||
else
|
||||
return "Function only returns " + std::to_string(e.expected) + " values. " + std::to_string(e.actual) + " are required here";
|
||||
case CountMismatch::Arg:
|
||||
return "Argument count mismatch. Function " + wrongNumberOfArgsString(e.expected, e.actual);
|
||||
}
|
||||
|
||||
LUAU_ASSERT(!"Unknown context");
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::FunctionDoesNotTakeSelf&) const
|
||||
{
|
||||
return std::string("This function does not take self. Did you mean to use a dot instead of a colon?");
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::FunctionRequiresSelf& e) const
|
||||
{
|
||||
if (e.requiredExtraNils)
|
||||
{
|
||||
const char* plural = e.requiredExtraNils == 1 ? "" : "s";
|
||||
return format("This function was declared to accept self, but you did not pass enough arguments. Use a colon instead of a dot or "
|
||||
"pass %i extra nil%s to suppress this warning",
|
||||
e.requiredExtraNils, plural);
|
||||
}
|
||||
else
|
||||
return "This function must be called with self. Did you mean to use a colon instead of a dot?";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::OccursCheckFailed&) const
|
||||
{
|
||||
return "Type contains a self-recursive construct that cannot be resolved";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::UnknownRequire& e) const
|
||||
{
|
||||
return "Unknown require: " + e.modulePath;
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::IncorrectGenericParameterCount& e) const
|
||||
{
|
||||
std::string name = e.name;
|
||||
if (!e.typeFun.typeParams.empty())
|
||||
{
|
||||
name += "<";
|
||||
bool first = true;
|
||||
for (TypeId t : e.typeFun.typeParams)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
name += ", ";
|
||||
|
||||
name += toString(t);
|
||||
}
|
||||
name += ">";
|
||||
}
|
||||
|
||||
return "Generic type '" + name + "' " + wrongNumberOfArgsString(e.typeFun.typeParams.size(), e.actualParameters, /*isTypeArgs*/ true);
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::SyntaxError& e) const
|
||||
{
|
||||
return "Syntax error: " + e.message;
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::CodeTooComplex&) const
|
||||
{
|
||||
return "Code is too complex to typecheck! Consider simplifying the code around this area";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::UnificationTooComplex&) const
|
||||
{
|
||||
return "Internal error: Code is too complex to typecheck! Consider adding type annotations around this area";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::UnknownPropButFoundLikeProp& e) const
|
||||
{
|
||||
std::string candidatesSuggestion = "Did you mean ";
|
||||
if (e.candidates.size() != 1)
|
||||
candidatesSuggestion += "one of ";
|
||||
|
||||
bool first = true;
|
||||
for (Name name : e.candidates)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
candidatesSuggestion += ", ";
|
||||
|
||||
candidatesSuggestion += "'" + name + "'";
|
||||
}
|
||||
|
||||
std::string s = "Key '" + e.key + "' not found in ";
|
||||
|
||||
TypeId t = follow(e.table);
|
||||
if (get<ClassTypeVar>(t))
|
||||
s += "class";
|
||||
else
|
||||
s += "table";
|
||||
|
||||
s += " '" + toString(e.table) + "'. " + candidatesSuggestion + "?";
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::GenericError& e) const
|
||||
{
|
||||
return e.message;
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::CannotCallNonFunction& e) const
|
||||
{
|
||||
return "Cannot call non-function " + toString(e.ty);
|
||||
}
|
||||
std::string operator()(const Luau::ExtraInformation& e) const
|
||||
{
|
||||
return e.message;
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::DeprecatedApiUsed& e) const
|
||||
{
|
||||
return "The property ." + e.symbol + " is deprecated. Use ." + e.useInstead + " instead.";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::ModuleHasCyclicDependency& e) const
|
||||
{
|
||||
if (e.cycle.empty())
|
||||
return "Cyclic module dependency detected";
|
||||
|
||||
std::string s = "Cyclic module dependency: ";
|
||||
|
||||
bool first = true;
|
||||
for (const ModuleName& name : e.cycle)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
s += " -> ";
|
||||
|
||||
s += name;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::FunctionExitsWithoutReturning& e) const
|
||||
{
|
||||
return "Not all codepaths in this function return '" + toString(e.expectedReturnType) + "'.";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::IllegalRequire& e) const
|
||||
{
|
||||
return "Cannot require module " + e.moduleName + ": " + e.reason;
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::MissingProperties& e) const
|
||||
{
|
||||
std::string s = "Table type '" + toString(e.subType) + "' not compatible with type '" + toString(e.superType) + "' because the former";
|
||||
|
||||
switch (e.context)
|
||||
{
|
||||
case MissingProperties::Missing:
|
||||
s += " is missing field";
|
||||
break;
|
||||
case MissingProperties::Extra:
|
||||
s += " has extra field";
|
||||
break;
|
||||
}
|
||||
|
||||
if (e.properties.size() > 1)
|
||||
s += "s";
|
||||
|
||||
s += " ";
|
||||
|
||||
for (size_t i = 0; i < e.properties.size(); ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
s += ", ";
|
||||
|
||||
if (i > 0 && i == e.properties.size() - 1)
|
||||
s += "and ";
|
||||
|
||||
s += "'" + e.properties[i] + "'";
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::DuplicateGenericParameter& e) const
|
||||
{
|
||||
return "Duplicate type parameter '" + e.parameterName + "'";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::CannotInferBinaryOperation& e) const
|
||||
{
|
||||
std::string ss = "Unknown type used in " + toString(e.op);
|
||||
|
||||
switch (e.kind)
|
||||
{
|
||||
case Luau::CannotInferBinaryOperation::Comparison:
|
||||
ss += " comparison";
|
||||
break;
|
||||
case Luau::CannotInferBinaryOperation::Operation:
|
||||
ss += " operation";
|
||||
}
|
||||
|
||||
if (e.suggestedToAnnotate)
|
||||
ss += "; consider adding a type annotation to '" + *e.suggestedToAnnotate + "'";
|
||||
|
||||
return ss;
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::SwappedGenericTypeParameter& e) const
|
||||
{
|
||||
switch (e.kind)
|
||||
{
|
||||
case Luau::SwappedGenericTypeParameter::Type:
|
||||
return "Variadic type parameter '" + e.name + "...' is used as a regular generic type; consider changing '" + e.name + "...' to '" +
|
||||
e.name + "' in the generic argument list";
|
||||
case Luau::SwappedGenericTypeParameter::Pack:
|
||||
return "Generic type '" + e.name + "' is used as a variadic type parameter; consider changing '" + e.name + "' to '" + e.name +
|
||||
"...' in the generic argument list";
|
||||
default:
|
||||
LUAU_ASSERT(!"Unknown kind");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::OptionalValueAccess& e) const
|
||||
{
|
||||
return "Value of type '" + toString(e.optional) + "' could be nil";
|
||||
}
|
||||
|
||||
std::string operator()(const Luau::MissingUnionProperty& e) const
|
||||
{
|
||||
std::string ss = "Key '" + e.key + "' is missing from ";
|
||||
|
||||
bool first = true;
|
||||
for (auto ty : e.missing)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
ss += ", ";
|
||||
|
||||
ss += "'" + toString(ty) + "'";
|
||||
}
|
||||
|
||||
return ss + " in the type '" + toString(e.type) + "'";
|
||||
}
|
||||
};
|
||||
|
||||
struct InvalidNameChecker
|
||||
{
|
||||
std::string invalidName = "%error-id%";
|
||||
|
||||
bool operator()(const Luau::UnknownProperty& e) const
|
||||
{
|
||||
return e.key == invalidName;
|
||||
}
|
||||
bool operator()(const Luau::CannotExtendTable& e) const
|
||||
{
|
||||
return e.prop == invalidName;
|
||||
}
|
||||
bool operator()(const Luau::DuplicateTypeDefinition& e) const
|
||||
{
|
||||
return e.name == invalidName;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool operator()(const T& other) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
bool TypeMismatch::operator==(const TypeMismatch& rhs) const
|
||||
{
|
||||
return *wantedType == *rhs.wantedType && *givenType == *rhs.givenType;
|
||||
}
|
||||
|
||||
bool UnknownSymbol::operator==(const UnknownSymbol& rhs) const
|
||||
{
|
||||
return name == rhs.name;
|
||||
}
|
||||
|
||||
bool UnknownProperty::operator==(const UnknownProperty& rhs) const
|
||||
{
|
||||
return *table == *rhs.table && key == rhs.key;
|
||||
}
|
||||
|
||||
bool NotATable::operator==(const NotATable& rhs) const
|
||||
{
|
||||
return ty == rhs.ty;
|
||||
}
|
||||
|
||||
bool CannotExtendTable::operator==(const CannotExtendTable& rhs) const
|
||||
{
|
||||
return *tableType == *rhs.tableType && prop == rhs.prop && context == rhs.context;
|
||||
}
|
||||
|
||||
bool OnlyTablesCanHaveMethods::operator==(const OnlyTablesCanHaveMethods& rhs) const
|
||||
{
|
||||
return *tableType == *rhs.tableType;
|
||||
}
|
||||
|
||||
bool DuplicateTypeDefinition::operator==(const DuplicateTypeDefinition& rhs) const
|
||||
{
|
||||
return name == rhs.name && previousLocation == rhs.previousLocation;
|
||||
}
|
||||
|
||||
bool CountMismatch::operator==(const CountMismatch& rhs) const
|
||||
{
|
||||
return expected == rhs.expected && actual == rhs.actual && context == rhs.context;
|
||||
}
|
||||
|
||||
bool FunctionDoesNotTakeSelf::operator==(const FunctionDoesNotTakeSelf&) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FunctionRequiresSelf::operator==(const FunctionRequiresSelf& e) const
|
||||
{
|
||||
return requiredExtraNils == e.requiredExtraNils;
|
||||
}
|
||||
|
||||
bool OccursCheckFailed::operator==(const OccursCheckFailed&) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnknownRequire::operator==(const UnknownRequire& rhs) const
|
||||
{
|
||||
return modulePath == rhs.modulePath;
|
||||
}
|
||||
|
||||
bool IncorrectGenericParameterCount::operator==(const IncorrectGenericParameterCount& rhs) const
|
||||
{
|
||||
if (name != rhs.name)
|
||||
return false;
|
||||
|
||||
if (typeFun.type != rhs.typeFun.type)
|
||||
return false;
|
||||
|
||||
if (typeFun.typeParams.size() != rhs.typeFun.typeParams.size())
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < typeFun.typeParams.size(); ++i)
|
||||
if (typeFun.typeParams[i] != rhs.typeFun.typeParams[i])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SyntaxError::operator==(const SyntaxError& rhs) const
|
||||
{
|
||||
return message == rhs.message;
|
||||
}
|
||||
|
||||
bool CodeTooComplex::operator==(const CodeTooComplex&) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnificationTooComplex::operator==(const UnificationTooComplex&) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnknownPropButFoundLikeProp::operator==(const UnknownPropButFoundLikeProp& rhs) const
|
||||
{
|
||||
return *table == *rhs.table && key == rhs.key && candidates.size() == rhs.candidates.size() &&
|
||||
std::equal(candidates.begin(), candidates.end(), rhs.candidates.begin());
|
||||
}
|
||||
|
||||
bool GenericError::operator==(const GenericError& rhs) const
|
||||
{
|
||||
return message == rhs.message;
|
||||
}
|
||||
|
||||
bool CannotCallNonFunction::operator==(const CannotCallNonFunction& rhs) const
|
||||
{
|
||||
return ty == rhs.ty;
|
||||
}
|
||||
|
||||
bool ExtraInformation::operator==(const ExtraInformation& rhs) const
|
||||
{
|
||||
return message == rhs.message;
|
||||
}
|
||||
|
||||
bool DeprecatedApiUsed::operator==(const DeprecatedApiUsed& rhs) const
|
||||
{
|
||||
return symbol == rhs.symbol && useInstead == rhs.useInstead;
|
||||
}
|
||||
|
||||
bool FunctionExitsWithoutReturning::operator==(const FunctionExitsWithoutReturning& rhs) const
|
||||
{
|
||||
return expectedReturnType == rhs.expectedReturnType;
|
||||
}
|
||||
|
||||
int TypeError::code() const
|
||||
{
|
||||
return 1000 + int(data.index());
|
||||
}
|
||||
|
||||
bool TypeError::operator==(const TypeError& rhs) const
|
||||
{
|
||||
return location == rhs.location && data == rhs.data;
|
||||
}
|
||||
|
||||
bool ModuleHasCyclicDependency::operator==(const ModuleHasCyclicDependency& rhs) const
|
||||
{
|
||||
return cycle.size() == rhs.cycle.size() && std::equal(cycle.begin(), cycle.end(), rhs.cycle.begin());
|
||||
}
|
||||
|
||||
bool IllegalRequire::operator==(const IllegalRequire& rhs) const
|
||||
{
|
||||
return moduleName == rhs.moduleName && reason == rhs.reason;
|
||||
}
|
||||
|
||||
bool MissingProperties::operator==(const MissingProperties& rhs) const
|
||||
{
|
||||
return *superType == *rhs.superType && *subType == *rhs.subType && properties.size() == rhs.properties.size() &&
|
||||
std::equal(properties.begin(), properties.end(), rhs.properties.begin()) && context == rhs.context;
|
||||
}
|
||||
|
||||
bool DuplicateGenericParameter::operator==(const DuplicateGenericParameter& rhs) const
|
||||
{
|
||||
return parameterName == rhs.parameterName;
|
||||
}
|
||||
|
||||
bool CannotInferBinaryOperation::operator==(const CannotInferBinaryOperation& rhs) const
|
||||
{
|
||||
return op == rhs.op && suggestedToAnnotate == rhs.suggestedToAnnotate && kind == rhs.kind;
|
||||
}
|
||||
|
||||
bool SwappedGenericTypeParameter::operator==(const SwappedGenericTypeParameter& rhs) const
|
||||
{
|
||||
return name == rhs.name && kind == rhs.kind;
|
||||
}
|
||||
|
||||
bool OptionalValueAccess::operator==(const OptionalValueAccess& rhs) const
|
||||
{
|
||||
return *optional == *rhs.optional;
|
||||
}
|
||||
|
||||
bool MissingUnionProperty::operator==(const MissingUnionProperty& rhs) const
|
||||
{
|
||||
if (missing.size() != rhs.missing.size())
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < missing.size(); ++i)
|
||||
{
|
||||
if (*missing[i] != *rhs.missing[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return *type == *rhs.type && key == rhs.key;
|
||||
}
|
||||
|
||||
std::string toString(const TypeError& error)
|
||||
{
|
||||
ErrorConverter converter;
|
||||
return Luau::visit(converter, error.data);
|
||||
}
|
||||
|
||||
bool containsParseErrorName(const TypeError& error)
|
||||
{
|
||||
return Luau::visit(InvalidNameChecker{}, error.data);
|
||||
}
|
||||
|
||||
void copyErrors(ErrorVec& errors, struct TypeArena& destArena)
|
||||
{
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
|
||||
auto clone = [&](auto&& ty) {
|
||||
return ::Luau::clone(ty, destArena, seenTypes, seenTypePacks);
|
||||
};
|
||||
|
||||
auto visitErrorData = [&](auto&& e) {
|
||||
using T = std::decay_t<decltype(e)>;
|
||||
|
||||
if constexpr (false)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, TypeMismatch>)
|
||||
{
|
||||
e.wantedType = clone(e.wantedType);
|
||||
e.givenType = clone(e.givenType);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, UnknownSymbol>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, UnknownProperty>)
|
||||
{
|
||||
e.table = clone(e.table);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, NotATable>)
|
||||
{
|
||||
e.ty = clone(e.ty);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, CannotExtendTable>)
|
||||
{
|
||||
e.tableType = clone(e.tableType);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, OnlyTablesCanHaveMethods>)
|
||||
{
|
||||
e.tableType = clone(e.tableType);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, DuplicateTypeDefinition>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, CountMismatch>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, FunctionDoesNotTakeSelf>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, FunctionRequiresSelf>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, OccursCheckFailed>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, UnknownRequire>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, IncorrectGenericParameterCount>)
|
||||
{
|
||||
e.typeFun = clone(e.typeFun);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, SyntaxError>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, CodeTooComplex>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, UnificationTooComplex>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, UnknownPropButFoundLikeProp>)
|
||||
{
|
||||
e.table = clone(e.table);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, GenericError>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, CannotCallNonFunction>)
|
||||
{
|
||||
e.ty = clone(e.ty);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, ExtraInformation>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, DeprecatedApiUsed>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, ModuleHasCyclicDependency>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, IllegalRequire>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, FunctionExitsWithoutReturning>)
|
||||
{
|
||||
e.expectedReturnType = clone(e.expectedReturnType);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, DuplicateGenericParameter>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, CannotInferBinaryOperation>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, MissingProperties>)
|
||||
{
|
||||
e.superType = clone(e.superType);
|
||||
e.subType = clone(e.subType);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, SwappedGenericTypeParameter>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, OptionalValueAccess>)
|
||||
{
|
||||
e.optional = clone(e.optional);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, MissingUnionProperty>)
|
||||
{
|
||||
e.type = clone(e.type);
|
||||
|
||||
for (auto& ty : e.missing)
|
||||
ty = clone(ty);
|
||||
}
|
||||
else
|
||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||
};
|
||||
|
||||
LUAU_ASSERT(!destArena.typeVars.isFrozen());
|
||||
LUAU_ASSERT(!destArena.typePacks.isFrozen());
|
||||
|
||||
for (TypeError& error : errors)
|
||||
visit(visitErrorData, error.data);
|
||||
}
|
||||
|
||||
void InternalErrorReporter::ice(const std::string& message, const Location& location)
|
||||
{
|
||||
std::runtime_error error("Internal error in " + moduleName + " at " + toString(location) + ": " + message);
|
||||
|
||||
if (onInternalError)
|
||||
onInternalError(error.what());
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
void InternalErrorReporter::ice(const std::string& message)
|
||||
{
|
||||
std::runtime_error error("Internal error in " + moduleName + ": " + message);
|
||||
|
||||
if (onInternalError)
|
||||
onInternalError(error.what());
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
967
Analysis/src/Frontend.cpp
Normal file
967
Analysis/src/Frontend.cpp
Normal file
|
@ -0,0 +1,967 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Frontend.h"
|
||||
|
||||
#include "Luau/Config.h"
|
||||
#include "Luau/FileResolver.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/Variant.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAG(LuauInferInNoCheckMode)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeCheckTwice, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSecondTypecheckKnowsTheDataModel, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauResolveModuleNameWithoutACurrentModule, false)
|
||||
LUAU_FASTFLAG(LuauTraceRequireLookupChild)
|
||||
LUAU_FASTFLAGVARIABLE(LuauPersistDefinitionFileTypes, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
std::optional<Mode> parseMode(const std::vector<std::string>& hotcomments)
|
||||
{
|
||||
for (const std::string& hc : hotcomments)
|
||||
{
|
||||
if (hc == "nocheck")
|
||||
return Mode::NoCheck;
|
||||
|
||||
if (hc == "nonstrict")
|
||||
return Mode::Nonstrict;
|
||||
|
||||
if (hc == "strict")
|
||||
return Mode::Strict;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static void generateDocumentationSymbols(TypeId ty, const std::string& rootName)
|
||||
{
|
||||
// TODO: What do we do in this situation? This means that the definition
|
||||
// file is exporting a type that is also a persistent type.
|
||||
if (ty->persistent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
asMutable(ty)->documentationSymbol = rootName;
|
||||
|
||||
if (TableTypeVar* ttv = getMutable<TableTypeVar>(ty))
|
||||
{
|
||||
for (auto& [name, prop] : ttv->props)
|
||||
{
|
||||
prop.documentationSymbol = rootName + "." + name;
|
||||
}
|
||||
}
|
||||
else if (ClassTypeVar* ctv = getMutable<ClassTypeVar>(ty))
|
||||
{
|
||||
for (auto& [name, prop] : ctv->props)
|
||||
{
|
||||
prop.documentationSymbol = rootName + "." + name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, ScopePtr targetScope, std::string_view source, const std::string& packageName)
|
||||
{
|
||||
Luau::Allocator allocator;
|
||||
Luau::AstNameTable names(allocator);
|
||||
|
||||
ParseOptions options;
|
||||
options.allowDeclarationSyntax = true;
|
||||
|
||||
Luau::ParseResult parseResult = Luau::Parser::parse(source.data(), source.size(), names, allocator, options);
|
||||
|
||||
if (parseResult.errors.size() > 0)
|
||||
return LoadDefinitionFileResult{false, parseResult, nullptr};
|
||||
|
||||
Luau::SourceModule module;
|
||||
module.root = parseResult.root;
|
||||
module.mode = Mode::Definition;
|
||||
|
||||
ModulePtr checkedModule = typeChecker.check(module, Mode::Definition);
|
||||
|
||||
if (checkedModule->errors.size() > 0)
|
||||
return LoadDefinitionFileResult{false, parseResult, checkedModule};
|
||||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->declaredGlobals)
|
||||
{
|
||||
TypeId globalTy = clone(ty, typeChecker.globalTypes, seenTypes, seenTypePacks);
|
||||
std::string documentationSymbol = packageName + "/global/" + name;
|
||||
generateDocumentationSymbols(globalTy, documentationSymbol);
|
||||
targetScope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
|
||||
|
||||
if (FFlag::LuauPersistDefinitionFileTypes)
|
||||
persist(globalTy);
|
||||
}
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->getModuleScope()->exportedTypeBindings)
|
||||
{
|
||||
TypeFun globalTy = clone(ty, typeChecker.globalTypes, seenTypes, seenTypePacks);
|
||||
std::string documentationSymbol = packageName + "/globaltype/" + name;
|
||||
generateDocumentationSymbols(globalTy.type, documentationSymbol);
|
||||
targetScope->exportedTypeBindings[name] = globalTy;
|
||||
|
||||
if (FFlag::LuauPersistDefinitionFileTypes)
|
||||
persist(globalTy.type);
|
||||
}
|
||||
|
||||
return LoadDefinitionFileResult{true, parseResult, checkedModule};
|
||||
}
|
||||
|
||||
std::vector<std::string_view> parsePathExpr(const AstExpr& pathExpr)
|
||||
{
|
||||
const AstExprIndexName* indexName = pathExpr.as<AstExprIndexName>();
|
||||
if (!indexName)
|
||||
return {};
|
||||
|
||||
std::vector<std::string_view> segments{indexName->index.value};
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (AstExprIndexName* in = indexName->expr->as<AstExprIndexName>())
|
||||
{
|
||||
segments.push_back(in->index.value);
|
||||
indexName = in;
|
||||
continue;
|
||||
}
|
||||
else if (AstExprGlobal* indexNameAsGlobal = indexName->expr->as<AstExprGlobal>())
|
||||
{
|
||||
segments.push_back(indexNameAsGlobal->name.value);
|
||||
break;
|
||||
}
|
||||
else if (AstExprLocal* indexNameAsLocal = indexName->expr->as<AstExprLocal>())
|
||||
{
|
||||
segments.push_back(indexNameAsLocal->local->name.value);
|
||||
break;
|
||||
}
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
std::reverse(segments.begin(), segments.end());
|
||||
return segments;
|
||||
}
|
||||
|
||||
std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const std::vector<std::string_view>& segments)
|
||||
{
|
||||
if (segments.empty())
|
||||
return std::nullopt;
|
||||
|
||||
std::vector<std::string_view> result;
|
||||
|
||||
auto it = segments.begin();
|
||||
|
||||
if (*it == "script" && !currentModuleName.empty())
|
||||
{
|
||||
result = split(currentModuleName, '/');
|
||||
++it;
|
||||
}
|
||||
|
||||
for (; it != segments.end(); ++it)
|
||||
{
|
||||
if (result.size() > 1 && *it == "Parent")
|
||||
result.pop_back();
|
||||
else
|
||||
result.push_back(*it);
|
||||
}
|
||||
|
||||
return join(result, "/");
|
||||
}
|
||||
|
||||
std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const AstExpr& pathExpr)
|
||||
{
|
||||
std::vector<std::string_view> segments = parsePathExpr(pathExpr);
|
||||
return pathExprToModuleName(currentModuleName, segments);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
ErrorVec accumulateErrors(
|
||||
const std::unordered_map<ModuleName, SourceNode>& sourceNodes, const std::unordered_map<ModuleName, ModulePtr>& modules, const ModuleName& name)
|
||||
{
|
||||
std::unordered_set<ModuleName> seen;
|
||||
std::vector<ModuleName> queue{name};
|
||||
|
||||
ErrorVec result;
|
||||
|
||||
while (!queue.empty())
|
||||
{
|
||||
ModuleName next = std::move(queue.back());
|
||||
queue.pop_back();
|
||||
|
||||
if (seen.count(next))
|
||||
continue;
|
||||
seen.insert(next);
|
||||
|
||||
auto it = sourceNodes.find(next);
|
||||
if (it == sourceNodes.end())
|
||||
continue;
|
||||
|
||||
const SourceNode& sourceNode = it->second;
|
||||
queue.insert(queue.end(), sourceNode.requires.begin(), sourceNode.requires.end());
|
||||
|
||||
// FIXME: If a module has a syntax error, we won't be able to re-report it here.
|
||||
// The solution is probably to move errors from Module to SourceNode
|
||||
|
||||
auto it2 = modules.find(next);
|
||||
if (it2 == modules.end())
|
||||
continue;
|
||||
|
||||
Module& module = *it2->second;
|
||||
|
||||
std::sort(module.errors.begin(), module.errors.end(), [](const TypeError& e1, const TypeError& e2) -> bool {
|
||||
return e1.location.begin > e2.location.begin;
|
||||
});
|
||||
|
||||
result.insert(result.end(), module.errors.begin(), module.errors.end());
|
||||
}
|
||||
|
||||
std::reverse(result.begin(), result.end());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
struct RequireCycle
|
||||
{
|
||||
Location location;
|
||||
std::vector<ModuleName> path; // one of the paths for a require() to go all the way back to the originating module
|
||||
};
|
||||
|
||||
// Given a source node (start), find all requires that start a transitive dependency path that ends back at start
|
||||
// For each such path, record the full path and the location of the require in the starting module.
|
||||
// Note that this is O(V^2) for a fully connected graph and produces O(V) paths of length O(V)
|
||||
// However, when the graph is acyclic, this is O(V), as well as when only the first cycle is needed (stopAtFirst=true)
|
||||
std::vector<RequireCycle> getRequireCycles(
|
||||
const std::unordered_map<ModuleName, SourceNode>& sourceNodes, const SourceNode* start, bool stopAtFirst = false)
|
||||
{
|
||||
std::vector<RequireCycle> result;
|
||||
|
||||
DenseHashSet<const SourceNode*> seen(nullptr);
|
||||
std::vector<const SourceNode*> stack;
|
||||
std::vector<const SourceNode*> path;
|
||||
|
||||
for (const auto& [depName, depLocation] : start->requireLocations)
|
||||
{
|
||||
std::vector<ModuleName> cycle;
|
||||
|
||||
auto dit = sourceNodes.find(depName);
|
||||
if (dit == sourceNodes.end())
|
||||
continue;
|
||||
|
||||
stack.push_back(&dit->second);
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
const SourceNode* top = stack.back();
|
||||
stack.pop_back();
|
||||
|
||||
if (top == nullptr)
|
||||
{
|
||||
// special marker for post-order processing
|
||||
LUAU_ASSERT(!path.empty());
|
||||
top = path.back();
|
||||
path.pop_back();
|
||||
|
||||
// we reached the node! path must form a cycle now
|
||||
if (top == start)
|
||||
{
|
||||
for (const SourceNode* node : path)
|
||||
cycle.push_back(node->name);
|
||||
|
||||
cycle.push_back(top->name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (!seen.contains(top))
|
||||
{
|
||||
seen.insert(top);
|
||||
|
||||
// push marker for post-order processing
|
||||
path.push_back(top);
|
||||
stack.push_back(nullptr);
|
||||
|
||||
// note: we push require edges in the opposite order
|
||||
// because it's a stack, the last edge to be pushed gets processed first
|
||||
// this ensures that the cyclic path we report is the first one in DFS order
|
||||
for (size_t i = top->requireLocations.size(); i > 0; --i)
|
||||
{
|
||||
const ModuleName& reqName = top->requireLocations[i - 1].first;
|
||||
|
||||
auto rit = sourceNodes.find(reqName);
|
||||
if (rit != sourceNodes.end())
|
||||
stack.push_back(&rit->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
path.clear();
|
||||
stack.clear();
|
||||
|
||||
if (!cycle.empty())
|
||||
{
|
||||
result.push_back({depLocation, std::move(cycle)});
|
||||
|
||||
if (stopAtFirst)
|
||||
return result;
|
||||
|
||||
// note: if we didn't find a cycle, all nodes that we've seen don't depend [transitively] on start
|
||||
// so it's safe to *only* clear seen vector when we find a cycle
|
||||
// if we don't do it, we will not have correct reporting for some cycles
|
||||
seen.clear();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
double getTimestamp()
|
||||
{
|
||||
using namespace std::chrono;
|
||||
return double(duration_cast<nanoseconds>(high_resolution_clock::now().time_since_epoch()).count()) / 1e9;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Frontend::Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, const FrontendOptions& options)
|
||||
: fileResolver(fileResolver)
|
||||
, moduleResolver(this)
|
||||
, moduleResolverForAutocomplete(this)
|
||||
, typeChecker(&moduleResolver, &iceHandler)
|
||||
, typeCheckerForAutocomplete(&moduleResolverForAutocomplete, &iceHandler)
|
||||
, configResolver(configResolver)
|
||||
, options(options)
|
||||
{
|
||||
}
|
||||
|
||||
FrontendModuleResolver::FrontendModuleResolver(Frontend* frontend)
|
||||
: frontend(frontend)
|
||||
{
|
||||
}
|
||||
|
||||
CheckResult Frontend::check(const ModuleName& name)
|
||||
{
|
||||
CheckResult checkResult;
|
||||
|
||||
auto it = sourceNodes.find(name);
|
||||
if (it != sourceNodes.end() && !it->second.dirty)
|
||||
{
|
||||
// No recheck required.
|
||||
auto it2 = moduleResolver.modules.find(name);
|
||||
if (it2 == moduleResolver.modules.end() || it2->second == nullptr)
|
||||
throw std::runtime_error("Frontend::modules does not have data for " + name);
|
||||
|
||||
return CheckResult{accumulateErrors(sourceNodes, moduleResolver.modules, name)};
|
||||
}
|
||||
|
||||
std::vector<ModuleName> buildQueue;
|
||||
bool cycleDetected = parseGraph(buildQueue, checkResult, name);
|
||||
|
||||
// Keep track of which AST nodes we've reported cycles in
|
||||
std::unordered_set<AstNode*> reportedCycles;
|
||||
|
||||
for (const ModuleName& moduleName : buildQueue)
|
||||
{
|
||||
LUAU_ASSERT(sourceNodes.count(moduleName));
|
||||
SourceNode& sourceNode = sourceNodes[moduleName];
|
||||
|
||||
if (!sourceNode.dirty)
|
||||
continue;
|
||||
|
||||
LUAU_ASSERT(sourceModules.count(moduleName));
|
||||
SourceModule& sourceModule = sourceModules[moduleName];
|
||||
|
||||
const Config& config = configResolver->getConfig(moduleName);
|
||||
|
||||
Mode mode = sourceModule.mode.value_or(config.mode);
|
||||
|
||||
ScopePtr environmentScope = getModuleEnvironment(sourceModule, config);
|
||||
|
||||
double timestamp = getTimestamp();
|
||||
|
||||
std::vector<RequireCycle> requireCycles;
|
||||
|
||||
// in NoCheck mode we only need to compute the value of .cyclic for typeck
|
||||
// in the future we could replace toposort with an algorithm that can flag cyclic nodes by itself
|
||||
// however, for now getRequireCycles isn't expensive in practice on the cases we care about, and long term
|
||||
// all correct programs must be acyclic so this code triggers rarely
|
||||
if (cycleDetected)
|
||||
requireCycles = getRequireCycles(sourceNodes, &sourceNode, mode == Mode::NoCheck);
|
||||
|
||||
// This is used by the type checker to replace the resulting type of cyclic modules with any
|
||||
sourceModule.cyclic = !requireCycles.empty();
|
||||
|
||||
ModulePtr module = typeChecker.check(sourceModule, mode, environmentScope);
|
||||
|
||||
// If we're typechecking twice, we do so.
|
||||
// The second typecheck is always in strict mode with DM awareness
|
||||
// to provide better typen information for IDE features.
|
||||
if (options.typecheckTwice && FFlag::LuauSecondTypecheckKnowsTheDataModel)
|
||||
{
|
||||
ModulePtr moduleForAutocomplete = typeCheckerForAutocomplete.check(sourceModule, Mode::Strict);
|
||||
moduleResolverForAutocomplete.modules[moduleName] = moduleForAutocomplete;
|
||||
}
|
||||
else if (options.retainFullTypeGraphs && options.typecheckTwice && mode != Mode::Strict)
|
||||
{
|
||||
ModulePtr strictModule = typeChecker.check(sourceModule, Mode::Strict, environmentScope);
|
||||
module->astTypes.clear();
|
||||
module->astOriginalCallTypes.clear();
|
||||
module->astExpectedTypes.clear();
|
||||
|
||||
SeenTypes seenTypes;
|
||||
SeenTypePacks seenTypePacks;
|
||||
|
||||
for (const auto& [expr, strictTy] : strictModule->astTypes)
|
||||
module->astTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks);
|
||||
|
||||
for (const auto& [expr, strictTy] : strictModule->astOriginalCallTypes)
|
||||
module->astOriginalCallTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks);
|
||||
|
||||
for (const auto& [expr, strictTy] : strictModule->astExpectedTypes)
|
||||
module->astExpectedTypes[expr] = clone(strictTy, module->interfaceTypes, seenTypes, seenTypePacks);
|
||||
}
|
||||
|
||||
stats.timeCheck += getTimestamp() - timestamp;
|
||||
stats.filesStrict += mode == Mode::Strict;
|
||||
stats.filesNonstrict += mode == Mode::Nonstrict;
|
||||
|
||||
if (module == nullptr)
|
||||
throw std::runtime_error("Frontend::check produced a nullptr module for " + moduleName);
|
||||
|
||||
if (!options.retainFullTypeGraphs)
|
||||
{
|
||||
// copyErrors needs to allocate into interfaceTypes as it copies
|
||||
// types out of internalTypes, so we unfreeze it here.
|
||||
unfreeze(module->interfaceTypes);
|
||||
copyErrors(module->errors, module->interfaceTypes);
|
||||
freeze(module->interfaceTypes);
|
||||
|
||||
module->internalTypes.clear();
|
||||
module->astTypes.clear();
|
||||
module->astExpectedTypes.clear();
|
||||
module->astOriginalCallTypes.clear();
|
||||
}
|
||||
|
||||
if (mode != Mode::NoCheck)
|
||||
{
|
||||
for (const RequireCycle& cyc : requireCycles)
|
||||
{
|
||||
TypeError te{cyc.location, moduleName, ModuleHasCyclicDependency{cyc.path}};
|
||||
|
||||
module->errors.push_back(te);
|
||||
}
|
||||
}
|
||||
|
||||
ErrorVec parseErrors;
|
||||
|
||||
for (const ParseError& pe : sourceModule.parseErrors)
|
||||
parseErrors.push_back(TypeError{pe.getLocation(), moduleName, SyntaxError{pe.what()}});
|
||||
|
||||
module->errors.insert(module->errors.begin(), parseErrors.begin(), parseErrors.end());
|
||||
|
||||
checkResult.errors.insert(checkResult.errors.end(), module->errors.begin(), module->errors.end());
|
||||
|
||||
moduleResolver.modules[moduleName] = std::move(module);
|
||||
sourceNode.dirty = false;
|
||||
}
|
||||
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
bool Frontend::parseGraph(std::vector<ModuleName>& buildQueue, CheckResult& checkResult, const ModuleName& root)
|
||||
{
|
||||
// https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search
|
||||
enum Mark
|
||||
{
|
||||
None,
|
||||
Temporary,
|
||||
Permanent
|
||||
};
|
||||
|
||||
DenseHashMap<SourceNode*, Mark> seen(nullptr);
|
||||
std::vector<SourceNode*> stack;
|
||||
std::vector<SourceNode*> path;
|
||||
bool cyclic = false;
|
||||
|
||||
{
|
||||
auto [sourceNode, _] = getSourceNode(checkResult, root);
|
||||
if (sourceNode)
|
||||
stack.push_back(sourceNode);
|
||||
}
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
SourceNode* top = stack.back();
|
||||
stack.pop_back();
|
||||
|
||||
if (top == nullptr)
|
||||
{
|
||||
// special marker for post-order processing
|
||||
LUAU_ASSERT(!path.empty());
|
||||
|
||||
top = path.back();
|
||||
path.pop_back();
|
||||
|
||||
// note: topseen ref gets invalidated in any seen[] access, beware - only one seen[] access per iteration!
|
||||
Mark& topseen = seen[top];
|
||||
LUAU_ASSERT(topseen == Temporary);
|
||||
topseen = Permanent;
|
||||
|
||||
buildQueue.push_back(top->name);
|
||||
}
|
||||
else
|
||||
{
|
||||
// note: topseen ref gets invalidated in any seen[] access, beware - only one seen[] access per iteration!
|
||||
Mark& topseen = seen[top];
|
||||
|
||||
if (topseen != None)
|
||||
{
|
||||
cyclic |= topseen == Temporary;
|
||||
continue;
|
||||
}
|
||||
|
||||
topseen = Temporary;
|
||||
|
||||
// push marker for post-order processing
|
||||
stack.push_back(nullptr);
|
||||
path.push_back(top);
|
||||
|
||||
// push children
|
||||
for (const ModuleName& dep : top->requires)
|
||||
{
|
||||
auto it = sourceNodes.find(dep);
|
||||
if (it != sourceNodes.end())
|
||||
{
|
||||
// this is a critical optimization: we do *not* traverse non-dirty subtrees.
|
||||
// this relies on the fact that markDirty marks reverse-dependencies dirty as well
|
||||
// thus if a node is not dirty, all its transitive deps aren't dirty, which means that they won't ever need
|
||||
// to be built, *and* can't form a cycle with any nodes we did process.
|
||||
if (!it->second.dirty)
|
||||
continue;
|
||||
|
||||
// note: this check is technically redundant *except* that getSourceNode has somewhat broken memoization
|
||||
// calling getSourceNode twice in succession will reparse the file, since getSourceNode leaves dirty flag set
|
||||
if (seen.contains(&it->second))
|
||||
{
|
||||
stack.push_back(&it->second);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
auto [sourceNode, _] = getSourceNode(checkResult, dep);
|
||||
if (sourceNode)
|
||||
{
|
||||
stack.push_back(sourceNode);
|
||||
|
||||
// note: this assignment is paired with .contains() check above and effectively deduplicates getSourceNode()
|
||||
seen[sourceNode] = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cyclic;
|
||||
}
|
||||
|
||||
ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config& config)
|
||||
{
|
||||
ScopePtr result = typeChecker.globalScope;
|
||||
|
||||
if (module.environmentName)
|
||||
result = getEnvironmentScope(*module.environmentName);
|
||||
|
||||
if (!config.globals.empty())
|
||||
{
|
||||
result = std::make_shared<Scope>(result);
|
||||
|
||||
for (const std::string& global : config.globals)
|
||||
{
|
||||
AstName name = module.names->get(global.c_str());
|
||||
|
||||
if (name.value)
|
||||
result->bindings[name].typeId = typeChecker.anyType;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
LintResult Frontend::lint(const ModuleName& name, std::optional<Luau::LintOptions> enabledLintWarnings)
|
||||
{
|
||||
CheckResult checkResult;
|
||||
auto [_sourceNode, sourceModule] = getSourceNode(checkResult, name);
|
||||
|
||||
if (!sourceModule)
|
||||
return LintResult{}; // FIXME: We really should do something a bit more obvious when a file is too broken to lint.
|
||||
|
||||
return lint(*sourceModule, enabledLintWarnings);
|
||||
}
|
||||
|
||||
std::pair<SourceModule, LintResult> Frontend::lintFragment(std::string_view source, std::optional<Luau::LintOptions> enabledLintWarnings)
|
||||
{
|
||||
const Config& config = configResolver->getConfig("");
|
||||
|
||||
SourceModule sourceModule = parse(ModuleName{}, source, config.parseOptions);
|
||||
|
||||
Luau::LintOptions lintOptions = enabledLintWarnings.value_or(config.enabledLint);
|
||||
lintOptions.warningMask &= sourceModule.ignoreLints;
|
||||
|
||||
double timestamp = getTimestamp();
|
||||
|
||||
std::vector<LintWarning> warnings =
|
||||
Luau::lint(sourceModule.root, *sourceModule.names.get(), typeChecker.globalScope, nullptr, enabledLintWarnings.value_or(config.enabledLint));
|
||||
|
||||
stats.timeLint += getTimestamp() - timestamp;
|
||||
|
||||
return {std::move(sourceModule), classifyLints(warnings, config)};
|
||||
}
|
||||
|
||||
CheckResult Frontend::check(const SourceModule& module)
|
||||
{
|
||||
const Config& config = configResolver->getConfig(module.name);
|
||||
|
||||
Mode mode = module.mode.value_or(config.mode);
|
||||
|
||||
double timestamp = getTimestamp();
|
||||
|
||||
ModulePtr checkedModule = typeChecker.check(module, mode);
|
||||
|
||||
stats.timeCheck += getTimestamp() - timestamp;
|
||||
stats.filesStrict += mode == Mode::Strict;
|
||||
stats.filesNonstrict += mode == Mode::Nonstrict;
|
||||
|
||||
if (checkedModule == nullptr)
|
||||
throw std::runtime_error("Frontend::check produced a nullptr module for module " + module.name);
|
||||
moduleResolver.modules[module.name] = checkedModule;
|
||||
|
||||
return CheckResult{checkedModule->errors};
|
||||
}
|
||||
|
||||
LintResult Frontend::lint(const SourceModule& module, std::optional<Luau::LintOptions> enabledLintWarnings)
|
||||
{
|
||||
const Config& config = configResolver->getConfig(module.name);
|
||||
|
||||
LintOptions options = enabledLintWarnings.value_or(config.enabledLint);
|
||||
options.warningMask &= ~module.ignoreLints;
|
||||
|
||||
Mode mode = module.mode.value_or(config.mode);
|
||||
if (mode != Mode::NoCheck)
|
||||
{
|
||||
options.disableWarning(Luau::LintWarning::Code_UnknownGlobal);
|
||||
}
|
||||
|
||||
if (mode == Mode::Strict)
|
||||
{
|
||||
options.disableWarning(Luau::LintWarning::Code_ImplicitReturn);
|
||||
}
|
||||
|
||||
ScopePtr environmentScope = getModuleEnvironment(module, config);
|
||||
|
||||
ModulePtr modulePtr = moduleResolver.getModule(module.name);
|
||||
|
||||
double timestamp = getTimestamp();
|
||||
|
||||
std::vector<LintWarning> warnings = Luau::lint(module.root, *module.names, environmentScope, modulePtr.get(), options);
|
||||
|
||||
stats.timeLint += getTimestamp() - timestamp;
|
||||
|
||||
return classifyLints(warnings, config);
|
||||
}
|
||||
|
||||
bool Frontend::isDirty(const ModuleName& name) const
|
||||
{
|
||||
auto it = sourceNodes.find(name);
|
||||
return it == sourceNodes.end() || it->second.dirty;
|
||||
}
|
||||
|
||||
/*
|
||||
* Mark a file as requiring rechecking before its type information can be safely used again.
|
||||
*
|
||||
* I am not particularly pleased with the way each dirty() operation involves a BFS on reverse dependencies.
|
||||
* It would be nice for this function to be O(1)
|
||||
*/
|
||||
void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty)
|
||||
{
|
||||
if (!moduleResolver.modules.count(name))
|
||||
return;
|
||||
|
||||
std::unordered_map<ModuleName, std::vector<ModuleName>> reverseDeps;
|
||||
for (const auto& module : sourceNodes)
|
||||
{
|
||||
for (const auto& dep : module.second.requires)
|
||||
reverseDeps[dep].push_back(module.first);
|
||||
}
|
||||
|
||||
std::vector<ModuleName> queue{name};
|
||||
|
||||
while (!queue.empty())
|
||||
{
|
||||
ModuleName next = std::move(queue.back());
|
||||
queue.pop_back();
|
||||
|
||||
LUAU_ASSERT(sourceNodes.count(next) > 0);
|
||||
SourceNode& sourceNode = sourceNodes[next];
|
||||
|
||||
if (markedDirty)
|
||||
markedDirty->push_back(next);
|
||||
|
||||
if (sourceNode.dirty)
|
||||
continue;
|
||||
|
||||
sourceNode.dirty = true;
|
||||
|
||||
if (0 == reverseDeps.count(name))
|
||||
continue;
|
||||
|
||||
sourceModules.erase(name);
|
||||
|
||||
const std::vector<ModuleName>& dependents = reverseDeps[name];
|
||||
queue.insert(queue.end(), dependents.begin(), dependents.end());
|
||||
}
|
||||
}
|
||||
|
||||
SourceModule* Frontend::getSourceModule(const ModuleName& moduleName)
|
||||
{
|
||||
auto it = sourceModules.find(moduleName);
|
||||
if (it != sourceModules.end())
|
||||
return &it->second;
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) const
|
||||
{
|
||||
return const_cast<Frontend*>(this)->getSourceModule(moduleName);
|
||||
}
|
||||
|
||||
// Read AST into sourceModules if necessary. Trace require()s. Report parse errors.
|
||||
std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(CheckResult& checkResult, const ModuleName& name)
|
||||
{
|
||||
auto it = sourceNodes.find(name);
|
||||
if (it != sourceNodes.end() && !it->second.dirty)
|
||||
{
|
||||
auto moduleIt = sourceModules.find(name);
|
||||
if (moduleIt != sourceModules.end())
|
||||
return {&it->second, &moduleIt->second};
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(!"Everything in sourceNodes should also be in sourceModules");
|
||||
return {&it->second, nullptr};
|
||||
}
|
||||
}
|
||||
|
||||
double timestamp = getTimestamp();
|
||||
|
||||
std::optional<SourceCode> source = fileResolver->readSource(name);
|
||||
std::optional<std::string> environmentName = fileResolver->getEnvironmentForModule(name);
|
||||
|
||||
stats.timeRead += getTimestamp() - timestamp;
|
||||
|
||||
if (!source)
|
||||
{
|
||||
sourceModules.erase(name);
|
||||
return {nullptr, nullptr};
|
||||
}
|
||||
|
||||
const Config& config = configResolver->getConfig(name);
|
||||
ParseOptions opts = config.parseOptions;
|
||||
opts.captureComments = true;
|
||||
SourceModule result = parse(name, source->source, opts);
|
||||
result.type = source->type;
|
||||
|
||||
RequireTraceResult& requireTrace = requires[name];
|
||||
requireTrace = traceRequires(fileResolver, result.root, name);
|
||||
|
||||
SourceNode& sourceNode = sourceNodes[name];
|
||||
SourceModule& sourceModule = sourceModules[name];
|
||||
|
||||
sourceModule = std::move(result);
|
||||
sourceModule.environmentName = environmentName;
|
||||
|
||||
sourceNode.name = name;
|
||||
sourceNode.requires.clear();
|
||||
sourceNode.requireLocations.clear();
|
||||
sourceNode.dirty = true;
|
||||
|
||||
for (const auto& [moduleName, location] : requireTrace.requires)
|
||||
sourceNode.requires.insert(moduleName);
|
||||
|
||||
sourceNode.requireLocations = requireTrace.requires;
|
||||
|
||||
return {&sourceNode, &sourceModule};
|
||||
}
|
||||
|
||||
/** Try to parse a source file into a SourceModule.
|
||||
*
|
||||
* The logic here is a little bit more complicated than we'd like it to be.
|
||||
*
|
||||
* If a file does not exist, we return none to prevent the Frontend from creating knowledge that this module exists.
|
||||
* If the Frontend thinks that the file exists, it will not produce an "Unknown require" error.
|
||||
*
|
||||
* If the file has syntax errors, we report them and synthesize an empty AST if it's not available.
|
||||
* This suppresses the Unknown require error and allows us to make a best effort to typecheck code that require()s
|
||||
* something that has broken syntax.
|
||||
* We also translate Luau::ParseError into a Luau::TypeError so that we can use a vector<TypeError> to describe the
|
||||
* result of the check()
|
||||
*/
|
||||
SourceModule Frontend::parse(const ModuleName& name, std::string_view src, const ParseOptions& parseOptions)
|
||||
{
|
||||
SourceModule sourceModule;
|
||||
|
||||
double timestamp = getTimestamp();
|
||||
|
||||
auto parseResult = Luau::Parser::parse(src.data(), src.size(), *sourceModule.names, *sourceModule.allocator, parseOptions);
|
||||
|
||||
stats.timeParse += getTimestamp() - timestamp;
|
||||