Add files via upload

This commit is contained in:
Babyhamsta 2022-07-30 22:29:23 -05:00 committed by GitHub
parent 05d9edddb6
commit 752d09c8da
Signed by: DevComp
GPG key ID: 4AEE18F83AFDEB23
86 changed files with 8028 additions and 3170 deletions

View file

@ -0,0 +1,78 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Ast.h"
#include "lluz/Documentation.h"
#include <memory>
namespace lluz
{
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>{});
}
std::optional<AstName> getName()
{
if (expr)
{
if (AstName name = getIdentifier(expr); name.value)
{
return name;
}
}
else if (local)
{
return local->name;
}
return std::nullopt;
}
private:
AstExpr* expr = nullptr;
AstLocal* local = nullptr;
};
std::vector<AstNode*> 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 lluz

View file

@ -0,0 +1,93 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Location.h"
#include "lluz/TypeVar.h"
#include <unordered_map>
#include <string>
#include <memory>
#include <optional>
namespace lluz
{
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);
// Deprecated, do not use in new work.
OwningAutocompleteResult autocompleteSource(Frontend& frontend, std::string_view source, Position position, StringCompletionCallback callback);
} // namespace lluz

View file

@ -0,0 +1,51 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Scope.h"
#include "lluz/TypeInfer.h"
namespace lluz
{
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);
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 lluz

View file

@ -0,0 +1,30 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/TypeArena.h"
#include "lluz/TypeVar.h"
#include <unordered_map>
namespace lluz
{
// Only exposed so they can be unit tested.
using SeenTypes = std::unordered_map<TypeId, TypeId>;
using SeenTypePacks = std::unordered_map<TypePackId, TypePackId>;
struct CloneState
{
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
int recursionCount = 0;
};
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState);
TypeId clone(TypeId tp, TypeArena& dest, CloneState& cloneState);
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState);
TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log);
} // namespace lluz

View file

@ -0,0 +1,58 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Linter.h"
#include "lluz/ParseOptions.h"
#include <string>
#include <optional>
#include <vector>
namespace lluz
{
using ModuleName = std::string;
constexpr const char* kConfigName = ".lluzrc";
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 lluz

View file

@ -0,0 +1,105 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Ast.h" // Used for some of the enumerations
#include "lluz/NotNull.h"
#include "lluz/Variant.h"
#include <string>
#include <memory>
#include <vector>
namespace lluz
{
struct Scope2;
struct TypeVar;
using TypeId = const TypeVar*;
struct TypePackVar;
using TypePackId = const TypePackVar*;
// subType <: superType
struct SubtypeConstraint
{
TypeId subType;
TypeId superType;
};
// subPack <: superPack
struct PackSubtypeConstraint
{
TypePackId subPack;
TypePackId superPack;
};
// subType ~ gen superType
struct GeneralizationConstraint
{
TypeId generalizedType;
TypeId sourceType;
Scope2* scope;
};
// subType ~ inst superType
struct InstantiationConstraint
{
TypeId subType;
TypeId superType;
};
struct UnaryConstraint
{
AstExprUnary::Op op;
TypeId operandType;
TypeId resultType;
};
struct BinaryConstraint
{
AstExprBinary::Op op;
TypeId leftType;
TypeId rightType;
TypeId resultType;
};
// name(namedType) = name
struct NameConstraint
{
TypeId namedType;
std::string name;
};
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, UnaryConstraint,
BinaryConstraint, NameConstraint>;
using ConstraintPtr = std::unique_ptr<struct Constraint>;
struct Constraint
{
explicit Constraint(ConstraintV&& c);
Constraint(const Constraint&) = delete;
Constraint& operator=(const Constraint&) = delete;
ConstraintV c;
std::vector<NotNull<Constraint>> dependencies;
};
inline Constraint& asMutable(const Constraint& c)
{
return const_cast<Constraint&>(c);
}
template<typename T>
T* getMutable(Constraint& c)
{
return ::lluz::get_if<T>(&c.c);
}
template<typename T>
const T* get(const Constraint& c)
{
return getMutable<T>(asMutable(c));
}
} // namespace lluz

View file

@ -0,0 +1,197 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include <memory>
#include <vector>
#include <unordered_map>
#include "lluz/Ast.h"
#include "lluz/Constraint.h"
#include "lluz/Module.h"
#include "lluz/NotNull.h"
#include "lluz/Symbol.h"
#include "lluz/TypeVar.h"
#include "lluz/Variant.h"
namespace lluz
{
struct Scope2;
struct ConstraintGraphBuilder
{
// A list of all the scopes in the module. This vector holds ownership of the
// scope pointers; the scopes themselves borrow pointers to other scopes to
// define the scope hierarchy.
std::vector<std::pair<Location, std::unique_ptr<Scope2>>> scopes;
ModuleName moduleName;
SingletonTypes& singletonTypes;
const NotNull<TypeArena> arena;
// The root scope of the module we're generating constraints for.
// This is null when the CGB is initially constructed.
Scope2* rootScope;
// A mapping of AST node to TypeId.
DenseHashMap<const AstExpr*, TypeId> astTypes{nullptr};
// A mapping of AST node to TypePackId.
DenseHashMap<const AstExpr*, TypePackId> astTypePacks{nullptr};
DenseHashMap<const AstExpr*, TypeId> astOriginalCallTypes{nullptr};
// Types resolved from type annotations. Analogous to astTypes.
DenseHashMap<const AstType*, TypeId> astResolvedTypes{nullptr};
// Type packs resolved from type annotations. Analogous to astTypePacks.
DenseHashMap<const AstTypePack*, TypePackId> astResolvedTypePacks{nullptr};
int recursionCount = 0;
// It is pretty uncommon for constraint generation to itself produce errors, but it can happen.
std::vector<TypeError> errors;
// Occasionally constraint generation needs to produce an ICE.
const NotNull<InternalErrorReporter> ice;
NotNull<Scope2> globalScope;
ConstraintGraphBuilder(const ModuleName& moduleName, TypeArena* arena, NotNull<InternalErrorReporter> ice, NotNull<Scope2> globalScope);
/**
* Fabricates a new free type belonging to a given scope.
* @param scope the scope the free type belongs to.
*/
TypeId freshType(NotNull<Scope2> scope);
/**
* Fabricates a new free type pack belonging to a given scope.
* @param scope the scope the free type pack belongs to.
*/
TypePackId freshTypePack(NotNull<Scope2> scope);
/**
* Fabricates a scope that is a child of another scope.
* @param location the lexical extent of the scope in the source code.
* @param parent the parent scope of the new scope. Must not be null.
*/
NotNull<Scope2> childScope(Location location, NotNull<Scope2> parent);
/**
* Adds a new constraint with no dependencies to a given scope.
* @param scope the scope to add the constraint to.
* @param cv the constraint variant to add.
*/
void addConstraint(NotNull<Scope2> scope, ConstraintV cv);
/**
* Adds a constraint to a given scope.
* @param scope the scope to add the constraint to. Must not be null.
* @param c the constraint to add.
*/
void addConstraint(NotNull<Scope2> scope, std::unique_ptr<Constraint> c);
/**
* The entry point to the ConstraintGraphBuilder. This will construct a set
* of scopes, constraints, and free types that can be solved later.
* @param block the root block to generate constraints for.
*/
void visit(AstStatBlock* block);
void visitBlockWithoutChildScope(NotNull<Scope2> scope, AstStatBlock* block);
void visit(NotNull<Scope2> scope, AstStat* stat);
void visit(NotNull<Scope2> scope, AstStatBlock* block);
void visit(NotNull<Scope2> scope, AstStatLocal* local);
void visit(NotNull<Scope2> scope, AstStatLocalFunction* function);
void visit(NotNull<Scope2> scope, AstStatFunction* function);
void visit(NotNull<Scope2> scope, AstStatReturn* ret);
void visit(NotNull<Scope2> scope, AstStatAssign* assign);
void visit(NotNull<Scope2> scope, AstStatIf* ifStatement);
void visit(NotNull<Scope2> scope, AstStatTypeAlias* alias);
TypePackId checkExprList(NotNull<Scope2> scope, const AstArray<AstExpr*>& exprs);
TypePackId checkPack(NotNull<Scope2> scope, AstArray<AstExpr*> exprs);
TypePackId checkPack(NotNull<Scope2> scope, AstExpr* expr);
/**
* Checks an expression that is expected to evaluate to one type.
* @param scope the scope the expression is contained within.
* @param expr the expression to check.
* @return the type of the expression.
*/
TypeId check(NotNull<Scope2> scope, AstExpr* expr);
TypeId checkExprTable(NotNull<Scope2> scope, AstExprTable* expr);
TypeId check(NotNull<Scope2> scope, AstExprIndexName* indexName);
TypeId check(NotNull<Scope2> scope, AstExprIndexExpr* indexExpr);
TypeId check(NotNull<Scope2> scope, AstExprUnary* unary);
TypeId check(NotNull<Scope2> scope, AstExprBinary* binary);
struct FunctionSignature
{
// The type of the function.
TypeId signature;
// The scope that encompasses the function's signature. May be nullptr
// if there was no need for a signature scope (the function has no
// generics).
Scope2* signatureScope;
// The scope that encompasses the function's body. Is a child scope of
// signatureScope, if present.
NotNull<Scope2> bodyScope;
};
FunctionSignature checkFunctionSignature(NotNull<Scope2> parent, AstExprFunction* fn);
/**
* Checks the body of a function expression.
* @param scope the interior scope of the body of the function.
* @param fn the function expression to check.
*/
void checkFunctionBody(NotNull<Scope2> scope, AstExprFunction* fn);
/**
* Resolves a type from its AST annotation.
* @param scope the scope that the type annotation appears within.
* @param ty the AST annotation to resolve.
* @return the type of the AST annotation.
**/
TypeId resolveType(NotNull<Scope2> scope, AstType* ty);
/**
* Resolves a type pack from its AST annotation.
* @param scope the scope that the type annotation appears within.
* @param tp the AST annotation to resolve.
* @return the type pack of the AST annotation.
**/
TypePackId resolveTypePack(NotNull<Scope2> scope, AstTypePack* tp);
TypePackId resolveTypePack(NotNull<Scope2> scope, const AstTypeList& list);
std::vector<std::pair<Name, GenericTypeDefinition>> createGenerics(NotNull<Scope2> scope, AstArray<AstGenericType> generics);
std::vector<std::pair<Name, GenericTypePackDefinition>> createGenericPacks(NotNull<Scope2> scope, AstArray<AstGenericTypePack> packs);
TypeId flattenPack(NotNull<Scope2> scope, Location location, TypePackId tp);
void reportError(Location location, TypeErrorData err);
void reportCodeTooComplex(Location location);
/** Scan the program for global definitions.
*
* ConstraintGraphBuilder needs to differentiate between globals and accesses to undefined symbols. Doing this "for
* real" in a general way is going to be pretty hard, so we are choosing not to tackle that yet. For now, we do an
* initial scan of the AST and note what globals are defined.
*/
void prepopulateGlobalScope(NotNull<Scope2> globalScope, AstStatBlock* program);
};
/**
* Collects a vector of borrowed constraints from the scope and all its child
* scopes. It is important to only call this function when you're done adding
* constraints to the scope or its descendants, lest the borrowed pointers
* become invalid due to a container reallocation.
* @param rootScope the root scope of the scope graph to collect constraints
* from.
* @return a list of pointers to constraints contained within the scope graph.
* None of these pointers should be null.
*/
std::vector<NotNull<Constraint>> collectConstraints(NotNull<Scope2> rootScope);
} // namespace lluz

View file

@ -0,0 +1,126 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Error.h"
#include "lluz/Variant.h"
#include "lluz/Constraint.h"
#include "lluz/ConstraintSolverLogger.h"
#include "lluz/TypeVar.h"
#include <vector>
namespace lluz
{
// TypeId, TypePackId, or Constraint*. It is impossible to know which, but we
// never dereference this pointer.
using BlockedConstraintId = const void*;
struct ConstraintSolver
{
TypeArena* arena;
InternalErrorReporter iceReporter;
// The entire set of constraints that the solver is trying to resolve. It
// is important to not add elements to this vector, lest the underlying
// storage that we retain pointers to be mutated underneath us.
const std::vector<NotNull<Constraint>> constraints;
NotNull<Scope2> rootScope;
// This includes every constraint that has not been fully solved.
// A constraint can be both blocked and unsolved, for instance.
std::vector<NotNull<const Constraint>> unsolvedConstraints;
// A mapping of constraint pointer to how many things the constraint is
// blocked on. Can be empty or 0 for constraints that are not blocked on
// anything.
std::unordered_map<NotNull<const Constraint>, size_t> blockedConstraints;
// A mapping of type/pack pointers to the constraints they block.
std::unordered_map<BlockedConstraintId, std::vector<NotNull<const Constraint>>> blocked;
ConstraintSolverLogger logger;
explicit ConstraintSolver(TypeArena* arena, NotNull<Scope2> rootScope);
/**
* Attempts to dispatch all pending constraints and reach a type solution
* that satisfies all of the constraints.
**/
void run();
bool done();
/** Attempt to dispatch a constraint. Returns true if it was successful.
* If tryDispatch() returns false, the constraint remains in the unsolved set and will be retried later.
*/
bool tryDispatch(NotNull<const Constraint> c, bool force);
bool tryDispatch(const SubtypeConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const PackSubtypeConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const GeneralizationConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const InstantiationConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const UnaryConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const BinaryConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint);
void block(NotNull<const Constraint> target, NotNull<const Constraint> constraint);
/**
* Block a constraint on the resolution of a TypeVar.
* @returns false always. This is just to allow tryDispatch to return the result of block()
*/
bool block(TypeId target, NotNull<const Constraint> constraint);
bool block(TypePackId target, NotNull<const Constraint> constraint);
void unblock(NotNull<const Constraint> progressed);
void unblock(TypeId progressed);
void unblock(TypePackId progressed);
/**
* @returns true if the TypeId is in a blocked state.
*/
bool isBlocked(TypeId ty);
/**
* Returns whether the constraint is blocked on anything.
* @param constraint the constraint to check.
*/
bool isBlocked(NotNull<const Constraint> constraint);
/**
* Creates a new Unifier and performs a single unification operation. Commits
* the result.
* @param subType the sub-type to unify.
* @param superType the super-type to unify.
*/
void unify(TypeId subType, TypeId superType);
/**
* Creates a new Unifier and performs a single unification operation. Commits
* the result.
* @param subPack the sub-type pack to unify.
* @param superPack the super-type pack to unify.
*/
void unify(TypePackId subPack, TypePackId superPack);
private:
/**
* Marks a constraint as being blocked on a type or type pack. The constraint
* solver will not attempt to dispatch blocked constraints until their
* dependencies have made progress.
* @param target the type or type pack pointer that the constraint is blocked on.
* @param constraint the constraint to block.
**/
void block_(BlockedConstraintId target, NotNull<const Constraint> constraint);
/**
* Informs the solver that progress has been made on a type or type pack. The
* solver will wake up all constraints that are blocked on the type or type pack,
* and will resume attempting to dispatch them.
* @param progressed the type or type pack pointer that has progressed.
**/
void unblock_(BlockedConstraintId progressed);
};
void dump(NotNull<Scope2> rootScope, struct ToStringOptions& opts);
} // namespace lluz

View file

@ -0,0 +1,28 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "lluz/Constraint.h"
#include "lluz/NotNull.h"
#include "lluz/Scope.h"
#include "lluz/ToString.h"
#include <optional>
#include <string>
#include <vector>
namespace lluz
{
struct ConstraintSolverLogger
{
std::string compileOutput();
void captureBoundarySnapshot(const Scope2* rootScope, std::vector<NotNull<const Constraint>>& unsolvedConstraints);
void prepareStepSnapshot(const Scope2* rootScope, NotNull<const Constraint> current, std::vector<NotNull<const Constraint>>& unsolvedConstraints);
void commitPreparedStepSnapshot();
private:
std::vector<std::string> snapshots;
std::optional<std::string> preparedSnapshot;
ToStringOptions opts;
};
} // namespace lluz

View file

@ -0,0 +1,62 @@
#pragma once
#include "lluz/DenseHash.h"
#include "lluz/Variant.h"
#include <string>
#include <vector>
namespace lluz
{
struct FunctionDocumentation;
struct TableDocumentation;
struct OverloadedFunctionDocumentation;
struct BasicDocumentation;
using Documentation = lluz::Variant<BasicDocumentation, FunctionDocumentation, TableDocumentation, OverloadedFunctionDocumentation>;
using DocumentationSymbol = std::string;
struct BasicDocumentation
{
std::string documentation;
std::string learnMoreLink;
std::string codeSample;
};
struct FunctionParameterDocumentation
{
std::string name;
DocumentationSymbol documentation;
};
// Represents documentation for anything callable. This could be a method or a
// callback or a free function.
struct FunctionDocumentation
{
std::string documentation;
std::vector<FunctionParameterDocumentation> parameters;
std::vector<DocumentationSymbol> returns;
std::string learnMoreLink;
std::string codeSample;
};
struct OverloadedFunctionDocumentation
{
// This is a map of function signature to overload symbol name.
lluz::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;
lluz::DenseHashMap<std::string, DocumentationSymbol> keys;
std::string learnMoreLink;
std::string codeSample;
};
using DocumentationDatabase = lluz::DenseHashMap<DocumentationSymbol, Documentation>;
} // namespace lluz

View file

@ -0,0 +1,393 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/FileResolver.h"
#include "lluz/Location.h"
#include "lluz/TypeVar.h"
#include "lluz/Variant.h"
#include "lluz/TypeArena.h"
namespace lluz
{
struct TypeError;
struct TypeMismatch
{
TypeMismatch() = default;
TypeMismatch(TypeId wantedType, TypeId givenType);
TypeMismatch(TypeId wantedType, TypeId givenType, std::string reason);
TypeMismatch(TypeId wantedType, TypeId givenType, std::string reason, TypeError error);
TypeId wantedType = nullptr;
TypeId givenType = nullptr;
std::string reason;
std::shared_ptr<TypeError> error;
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 isVariadic = false;
bool operator==(const CountMismatch& rhs) const;
};
struct FunctionDoesNotTakeSelf
{
bool operator==(const FunctionDoesNotTakeSelf& rhs) const;
};
struct FunctionRequiresSelf
{
bool operator==(const FunctionRequiresSelf& rhs) const;
};
struct OccursCheckFailed
{
bool operator==(const OccursCheckFailed& rhs) const;
};
struct UnknownRequire
{
std::string modulePath;
bool operator==(const UnknownRequire& rhs) const;
};
struct IncorrectGenericParameterCount
{
Name name;
TypeFun typeFun;
size_t actualParameters;
size_t actualPackParameters;
bool operator==(const IncorrectGenericParameterCount& rhs) const;
};
struct SyntaxError
{
std::string message;
bool operator==(const SyntaxError& rhs) const;
};
struct CodeTooComplex
{
bool operator==(const CodeTooComplex&) const;
};
struct UnificationTooComplex
{
bool operator==(const UnificationTooComplex&) const;
};
// Could easily be folded into UnknownProperty with an extra field, std::set<Name> candidates.
// But for telemetry purposes, we want to have this be a distinct variant.
struct UnknownPropButFoundLikeProp
{
TypeId table;
Name key;
std::set<Name> candidates;
bool operator==(const UnknownPropButFoundLikeProp& rhs) const;
};
struct GenericError
{
std::string message;
bool operator==(const GenericError& rhs) const;
};
struct InternalError
{
std::string message;
bool operator==(const InternalError& rhs) const;
};
struct CannotCallNonFunction
{
TypeId ty;
bool operator==(const CannotCallNonFunction& rhs) const;
};
struct ExtraInformation
{
std::string message;
bool operator==(const ExtraInformation& rhs) const;
};
struct DeprecatedApiUsed
{
std::string symbol;
std::string useInstead;
bool operator==(const DeprecatedApiUsed& rhs) const;
};
struct ModuleHasCyclicDependency
{
std::vector<ModuleName> cycle;
bool operator==(const ModuleHasCyclicDependency& rhs) const;
};
struct FunctionExitsWithoutReturning
{
TypePackId expectedReturnType;
bool operator==(const FunctionExitsWithoutReturning& rhs) const;
};
struct IllegalRequire
{
std::string moduleName;
std::string reason;
bool operator==(const IllegalRequire& rhs) const;
};
struct MissingProperties
{
enum Context
{
Missing,
Extra
};
TypeId superType;
TypeId subType;
std::vector<Name> properties;
Context context = Missing;
bool operator==(const MissingProperties& rhs) const;
};
struct DuplicateGenericParameter
{
std::string parameterName;
bool operator==(const DuplicateGenericParameter& rhs) const;
};
struct CannotInferBinaryOperation
{
enum OpKind
{
Operation,
Comparison,
};
AstExprBinary::Op op;
std::optional<std::string> suggestedToAnnotate;
OpKind kind;
bool operator==(const CannotInferBinaryOperation& rhs) const;
};
struct SwappedGenericTypeParameter
{
enum Kind
{
Type,
Pack,
};
std::string name;
// What was `name` being used as?
Kind kind;
bool operator==(const SwappedGenericTypeParameter& rhs) const;
};
struct OptionalValueAccess
{
TypeId optional;
bool operator==(const OptionalValueAccess& rhs) const;
};
struct MissingUnionProperty
{
TypeId type;
std::vector<TypeId> missing;
Name key;
bool operator==(const MissingUnionProperty& rhs) const;
};
struct TypesAreUnrelated
{
TypeId left;
TypeId right;
bool operator==(const TypesAreUnrelated& rhs) const;
};
struct NormalizationTooComplex
{
bool operator==(const NormalizationTooComplex&) const
{
return true;
}
};
using TypeErrorData = Variant<TypeMismatch, UnknownSymbol, UnknownProperty, NotATable, CannotExtendTable, OnlyTablesCanHaveMethods,
DuplicateTypeDefinition, CountMismatch, FunctionDoesNotTakeSelf, FunctionRequiresSelf, OccursCheckFailed, UnknownRequire,
IncorrectGenericParameterCount, SyntaxError, CodeTooComplex, UnificationTooComplex, UnknownPropButFoundLikeProp, GenericError, InternalError,
CannotCallNonFunction, ExtraInformation, DeprecatedApiUsed, ModuleHasCyclicDependency, IllegalRequire, FunctionExitsWithoutReturning,
DuplicateGenericParameter, CannotInferBinaryOperation, MissingProperties, SwappedGenericTypeParameter, OptionalValueAccess, MissingUnionProperty,
TypesAreUnrelated, NormalizationTooComplex>;
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>;
struct TypeErrorToStringOptions
{
FileResolver* fileResolver = nullptr;
};
std::string toString(const TypeError& error);
std::string toString(const TypeError& error, TypeErrorToStringOptions options);
bool containsParseErrorName(const TypeError& error);
// Copy any types named in the error into destArena.
void copyErrors(ErrorVec& errors, struct TypeArena& destArena);
// 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);
};
class InternalCompilerError : public std::exception
{
public:
explicit InternalCompilerError(const std::string& message, const std::string& moduleName)
: message(message)
, moduleName(moduleName)
{
}
explicit InternalCompilerError(const std::string& message, const std::string& moduleName, const Location& location)
: message(message)
, moduleName(moduleName)
, location(location)
{
}
virtual const char* what() const throw();
const std::string message;
const std::string moduleName;
const std::optional<Location> location;
};
} // namespace lluz

View file

@ -0,0 +1,64 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include <string>
#include <optional>
namespace lluz
{
class AstExpr;
using ModuleName = std::string;
struct SourceCode
{
enum Type
{
None,
Module,
Script,
Local
};
std::string source;
Type type;
};
struct ModuleInfo
{
ModuleName name;
bool optional = false;
};
struct FileResolver
{
virtual ~FileResolver() {}
virtual std::optional<SourceCode> readSource(const ModuleName& name) = 0;
virtual std::optional<ModuleInfo> resolveModule(const ModuleInfo* context, AstExpr* expr)
{
return std::nullopt;
}
virtual std::string getHumanReadableModuleName(const ModuleName& name) const
{
return name;
}
virtual std::optional<std::string> getEnvironmentForModule(const ModuleName& name) const
{
return std::nullopt;
}
};
struct NullFileResolver : FileResolver
{
std::optional<SourceCode> readSource(const ModuleName& name) override
{
return std::nullopt;
}
};
} // namespace lluz

View file

@ -0,0 +1,199 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Config.h"
#include "lluz/Module.h"
#include "lluz/ModuleResolver.h"
#include "lluz/RequireTracer.h"
#include "lluz/Scope.h"
#include "lluz/TypeInfer.h"
#include "lluz/Variant.h"
#include <string>
#include <vector>
#include <optional>
namespace lluz
{
class AstStat;
class ParseError;
struct Frontend;
struct TypeError;
struct LintWarning;
struct TypeChecker;
struct FileResolver;
struct ModuleResolver;
struct ParseResult;
struct HotComment;
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<HotComment>& hotcomments);
std::vector<std::string_view> parsePathExpr(const AstExpr& pathExpr);
// Exported only for convenient testing.
std::optional<ModuleName> pathExprToModuleName(const ModuleName& currentModuleName, const std::vector<std::string_view>& expr);
/** Try to convert an AST fragment into a ModuleName.
* Returns std::nullopt if the expression cannot be resolved. This will most likely happen in cases where
* the import path involves some dynamic computation that we cannot see into at typechecking time.
*
* Unintuitively, weirdly-formulated modules (like game.Parent.Parent.Parent.Foo) will successfully produce a ModuleName
* as long as it falls within the permitted syntax. This is ok because we will fail to find the module and produce an
* error when we try during typechecking.
*/
std::optional<ModuleName> pathExprToModuleName(const ModuleName& currentModuleName, const AstExpr& expr);
struct SourceNode
{
bool hasDirtySourceModule() const
{
return dirtySourceModule;
}
bool hasDirtyModule(bool forAutocomplete) const
{
return forAutocomplete ? dirtyModuleForAutocomplete : dirtyModule;
}
ModuleName name;
std::unordered_set<ModuleName> requireSet;
std::vector<std::pair<ModuleName, Location>> requireLocations;
bool dirtySourceModule = true;
bool dirtyModule = true;
bool dirtyModuleForAutocomplete = true;
double autocompleteLimitsMult = 1.0;
};
struct FrontendOptions
{
// When true, we retain full type information about every term in the AST.
// Setting this to false cuts back on RAM and is a good idea for batch
// jobs where the type graph is not deeply inspected after typechecking
// is complete.
bool retainFullTypeGraphs = false;
// Run typechecking only in mode required for autocomplete (strict mode in order to get more precise type information)
bool forAutocomplete = false;
};
struct CheckResult
{
std::vector<TypeError> errors;
std::vector<ModuleName> timeoutHits;
};
struct FrontendModuleResolver : ModuleResolver
{
FrontendModuleResolver(Frontend* frontend);
const ModulePtr getModule(const ModuleName& moduleName) const override;
bool moduleExists(const ModuleName& moduleName) const override;
std::optional<ModuleInfo> resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr) override;
std::string getHumanReadableModuleName(const ModuleName& moduleName) const override;
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, std::optional<FrontendOptions> optionOverride = {}); // new shininess
LintResult lint(const ModuleName& name, std::optional<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<LintOptions> enabledLintWarnings = {});
LintResult lint(const SourceModule& module, std::optional<LintOptions> enabledLintWarnings = {});
bool isDirty(const ModuleName& name, bool forAutocomplete = false) const;
void markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty = nullptr);
/** Borrow a pointer into the SourceModule cache.
*
* Returns nullptr if we don't have it. This could mean that the script
* doesn't exist, or simply that its contents have changed since the previous
* check, in which case we do not have its AST.
*
* IMPORTANT: this pointer is only valid until the next call to markDirty. Do not retain it.
*/
SourceModule* getSourceModule(const ModuleName& name);
const SourceModule* getSourceModule(const ModuleName& name) const;
void clearStats();
void clear();
ScopePtr addEnvironment(const std::string& environmentName);
ScopePtr getEnvironmentScope(const std::string& environmentName);
void registerBuiltinDefinition(const std::string& name, std::function<void(TypeChecker&, ScopePtr)>);
void applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName);
NotNull<Scope2> getGlobalScope2();
private:
ModulePtr check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope);
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, bool forAutocomplete);
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;
std::unique_ptr<Scope2> globalScope2;
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> requireTrace;
Stats stats = {};
};
} // namespace lluz

View file

@ -0,0 +1,53 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Substitution.h"
#include "lluz/TypeVar.h"
#include "lluz/Unifiable.h"
namespace lluz
{
struct TypeArena;
struct TxnLog;
// A substitution which replaces generic types in a given set by free types.
struct ReplaceGenerics : Substitution
{
ReplaceGenerics(
const TxnLog* log, TypeArena* arena, TypeLevel level, const std::vector<TypeId>& generics, const std::vector<TypePackId>& genericPacks)
: Substitution(log, arena)
, level(level)
, generics(generics)
, genericPacks(genericPacks)
{
}
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
{
Instantiation(const TxnLog* log, TypeArena* arena, TypeLevel level)
: Substitution(log, arena)
, level(level)
{
}
TypeLevel level;
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;
};
} // namespace lluz

View file

@ -0,0 +1,51 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Error.h"
#include "lluz/Location.h"
#include "lluz/TypeVar.h"
#include "lluz/Ast.h"
#include <ostream>
namespace lluz
{
std::ostream& operator<<(std::ostream& lhs, const Position& position);
std::ostream& operator<<(std::ostream& lhs, const Location& location);
std::ostream& operator<<(std::ostream& lhs, const AstName& name);
std::ostream& operator<<(std::ostream& lhs, const TypeError& error);
std::ostream& operator<<(std::ostream& lhs, const TypeMismatch& error);
std::ostream& operator<<(std::ostream& lhs, const UnknownSymbol& error);
std::ostream& operator<<(std::ostream& lhs, const UnknownProperty& error);
std::ostream& operator<<(std::ostream& lhs, const NotATable& error);
std::ostream& operator<<(std::ostream& lhs, const CannotExtendTable& error);
std::ostream& operator<<(std::ostream& lhs, const OnlyTablesCanHaveMethods& error);
std::ostream& operator<<(std::ostream& lhs, const DuplicateTypeDefinition& error);
std::ostream& operator<<(std::ostream& lhs, const CountMismatch& error);
std::ostream& operator<<(std::ostream& lhs, const FunctionDoesNotTakeSelf& error);
std::ostream& operator<<(std::ostream& lhs, const FunctionRequiresSelf& error);
std::ostream& operator<<(std::ostream& lhs, const OccursCheckFailed& error);
std::ostream& operator<<(std::ostream& lhs, const UnknownRequire& error);
std::ostream& operator<<(std::ostream& lhs, const UnknownPropButFoundLikeProp& e);
std::ostream& operator<<(std::ostream& lhs, const GenericError& error);
std::ostream& operator<<(std::ostream& lhs, const InternalError& error);
std::ostream& operator<<(std::ostream& lhs, const FunctionExitsWithoutReturning& error);
std::ostream& operator<<(std::ostream& lhs, const MissingProperties& error);
std::ostream& operator<<(std::ostream& lhs, const IllegalRequire& error);
std::ostream& operator<<(std::ostream& lhs, const ModuleHasCyclicDependency& error);
std::ostream& operator<<(std::ostream& lhs, const DuplicateGenericParameter& error);
std::ostream& operator<<(std::ostream& lhs, const CannotInferBinaryOperation& error);
std::ostream& operator<<(std::ostream& lhs, const SwappedGenericTypeParameter& error);
std::ostream& operator<<(std::ostream& lhs, const OptionalValueAccess& error);
std::ostream& operator<<(std::ostream& lhs, const MissingUnionProperty& error);
std::ostream& operator<<(std::ostream& lhs, const TypesAreUnrelated& error);
std::ostream& operator<<(std::ostream& lhs, const TableState& tv);
std::ostream& operator<<(std::ostream& lhs, const TypeVar& tv);
std::ostream& operator<<(std::ostream& lhs, const TypePackVar& tv);
std::ostream& operator<<(std::ostream& lhs, const TypeErrorData& ted);
} // namespace lluz

View file

@ -0,0 +1,13 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include <string>
namespace lluz
{
class AstNode;
std::string toJson(AstNode* node);
} // namespace lluz

View file

@ -0,0 +1,51 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Variant.h"
#include "lluz/Symbol.h"
#include <memory>
#include <unordered_map>
namespace lluz
{
struct TypeVar;
using TypeId = const TypeVar*;
struct Field;
using LValue = Variant<Symbol, Field>;
struct Field
{
std::shared_ptr<LValue> parent;
std::string key;
bool operator==(const Field& rhs) const;
bool operator!=(const Field& rhs) const;
};
struct LValueHasher
{
size_t operator()(const LValue& lvalue) const;
};
const LValue* baseof(const LValue& lvalue);
std::optional<LValue> tryGetLValue(const class AstExpr& expr);
// Utility function: breaks down an LValue to get at the Symbol
Symbol getBaseSymbol(const LValue& lvalue);
template<typename T>
const T* get(const LValue& lvalue)
{
return get_if<T>(&lvalue);
}
using RefinementMap = std::unordered_map<LValue, TypeId, LValueHasher>;
void merge(RefinementMap& l, const RefinementMap& r, std::function<TypeId(TypeId, TypeId)> f);
void addRefinement(RefinementMap& refis, const LValue& lvalue, TypeId ty);
} // namespace lluz

View file

@ -0,0 +1,100 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Location.h"
#include <memory>
#include <vector>
namespace lluz
{
struct AstName;
class AstStat;
class AstNameTable;
struct TypeChecker;
struct Module;
struct HotComment;
using ScopePtr = std::shared_ptr<struct Scope>;
struct LintWarning
{
// Make sure any new lint codes are documented here: https://lluz-lang.org/lint
// Note that in Studio, the active set of lint warnings is determined by FStringStudiolluzLints
enum Code
{
Code_Unknown = 0,
Code_UnknownGlobal = 1, // superseded by type checker
Code_DeprecatedGlobal = 2,
Code_GlobalUsedAsLocal = 3,
Code_LocalShadow = 4, // disabled in Studio
Code_SameLineStatement = 5, // disabled in Studio
Code_MultiLineStatement = 6,
Code_LocalUnused = 7, // disabled in Studio
Code_FunctionUnused = 8, // disabled in Studio
Code_ImportUnused = 9, // disabled in Studio
Code_BuiltinGlobalWrite = 10,
Code_PlaceholderRead = 11,
Code_UnreachableCode = 12,
Code_UnknownType = 13,
Code_ForRange = 14,
Code_UnbalancedAssignment = 15,
Code_ImplicitReturn = 16, // disabled in Studio, superseded by type checker in strict mode
Code_DuplicateLocal = 17,
Code_FormatString = 18,
Code_TableLiteral = 19,
Code_UninitializedLocal = 20,
Code_DuplicateFunction = 21,
Code_DeprecatedApi = 22,
Code_TableOperations = 23,
Code_DuplicateCondition = 24,
Code_MisleadingAndOr = 25,
Code_CommentDirective = 26,
Code__Count
};
Code code;
Location location;
std::string text;
static const char* getName(Code code);
static Code parseName(const char* name);
static uint64_t parseMask(const std::vector<HotComment>& hotcomments);
};
struct 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 std::vector<HotComment>& hotcomments, const LintOptions& options);
std::vector<AstName> getDeprecatedGlobals(const AstNameTable& names);
} // namespace lluz

View file

@ -0,0 +1,96 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Error.h"
#include "lluz/FileResolver.h"
#include "lluz/ParseOptions.h"
#include "lluz/ParseResult.h"
#include "lluz/Scope.h"
#include "lluz/TypeArena.h"
#include <memory>
#include <vector>
#include <unordered_map>
#include <optional>
namespace lluz
{
struct Module;
using ScopePtr = std::shared_ptr<struct Scope>;
using ModulePtr = std::shared_ptr<Module>;
class AstType;
class AstTypePack;
/// Root of the AST of a parsed source file
struct SourceModule
{
ModuleName name; // DataModel path if possible. Filename if not.
SourceCode::Type type = SourceCode::None;
std::optional<std::string> environmentName;
bool cyclic = false;
std::shared_ptr<Allocator> allocator;
std::shared_ptr<AstNameTable> names;
std::vector<ParseError> parseErrors;
AstStatBlock* root = nullptr;
std::optional<Mode> mode;
std::vector<HotComment> hotcomments;
std::vector<Comment> commentLocations;
SourceModule()
: allocator(new Allocator)
, names(new AstNameTable(*allocator))
{
}
};
bool isWithinComment(const SourceModule& sourceModule, Position pos);
struct RequireCycle
{
Location location;
std::vector<ModuleName> path; // one of the paths for a require() to go all the way back to the originating module
};
struct Module
{
~Module();
TypeArena interfaceTypes;
TypeArena internalTypes;
// Scopes and AST types refer to parse data, so we need to keep that alive
std::shared_ptr<Allocator> allocator;
std::shared_ptr<AstNameTable> names;
std::vector<std::pair<Location, ScopePtr>> scopes; // never empty
std::vector<std::pair<Location, std::unique_ptr<Scope2>>> scope2s; // never empty
DenseHashMap<const AstExpr*, TypeId> astTypes{nullptr};
DenseHashMap<const AstExpr*, TypePackId> astTypePacks{nullptr};
DenseHashMap<const AstExpr*, TypeId> astExpectedTypes{nullptr};
DenseHashMap<const AstExpr*, TypeId> astOriginalCallTypes{nullptr};
DenseHashMap<const AstExpr*, TypeId> astOverloadResolvedTypes{nullptr};
DenseHashMap<const AstType*, TypeId> astResolvedTypes{nullptr};
DenseHashMap<const AstTypePack*, TypePackId> astResolvedTypePacks{nullptr};
std::unordered_map<Name, TypeId> declaredGlobals;
ErrorVec errors;
Mode mode;
SourceCode::Type type;
bool timeout = false;
ScopePtr getModuleScope() const;
Scope2* getModuleScope2() 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.
void clonePublicInterface(InternalErrorReporter& ice);
};
} // namespace lluz

View file

@ -0,0 +1,73 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/FileResolver.h"
#include <memory>
#include <optional>
#include <string>
namespace lluz
{
class AstExpr;
struct Module;
using ModulePtr = std::shared_ptr<Module>;
struct ModuleResolver
{
virtual ~ModuleResolver() {}
/** Compute a ModuleName from an AST fragment. This AST fragment is generally the argument to the require() function.
*
* You probably want to implement this with some variation of pathExprToModuleName.
*
* @returns The ModuleInfo if the expression is a syntactically legal path.
* @returns std::nullopt if we are unable to determine whether or not the expression is a valid path. Type inference will
* silently assume that it could succeed in this case.
*
* FIXME: This is clearly not the right behaviour longterm. We'll want to adust this interface to be able to signal
* a) success,
* b) Definitive failure (this expression will absolutely cause require() to fail at runtime), and
* c) uncertainty
*/
virtual std::optional<ModuleInfo> resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr) = 0;
/** Get a typechecked module from its name.
*
* This can return null under two circumstances: the module is unknown at compile time,
* or there's a cycle, and we are still in the middle of typechecking the module.
*/
virtual const ModulePtr getModule(const ModuleName& moduleName) const = 0;
/** Is a module known at compile time?
*
* This function can be used to distinguish the above two cases.
*/
virtual bool moduleExists(const ModuleName& moduleName) const = 0;
virtual std::string getHumanReadableModuleName(const ModuleName& moduleName) const = 0;
};
struct NullModuleResolver : ModuleResolver
{
std::optional<ModuleInfo> resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr) override
{
return std::nullopt;
}
const ModulePtr getModule(const ModuleName& moduleName) const override
{
return nullptr;
}
bool moduleExists(const ModuleName& moduleName) const override
{
return false;
}
std::string getHumanReadableModuleName(const ModuleName& moduleName) const override
{
return moduleName;
}
};
} // namespace lluz

View file

@ -0,0 +1,20 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "lluz/Substitution.h"
#include "lluz/TypeVar.h"
#include "lluz/Module.h"
namespace lluz
{
struct InternalErrorReporter;
bool isSubtype(TypeId subTy, TypeId superTy, InternalErrorReporter& ice);
bool isSubtype(TypePackId subTy, TypePackId superTy, InternalErrorReporter& ice);
std::pair<TypeId, bool> normalize(TypeId ty, TypeArena& arena, InternalErrorReporter& ice);
std::pair<TypeId, bool> normalize(TypeId ty, const ModulePtr& module, InternalErrorReporter& ice);
std::pair<TypePackId, bool> normalize(TypePackId ty, TypeArena& arena, InternalErrorReporter& ice);
std::pair<TypePackId, bool> normalize(TypePackId ty, const ModulePtr& module, InternalErrorReporter& ice);
} // namespace lluz

View file

@ -0,0 +1,90 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Common.h"
#include <functional>
namespace lluz
{
/** A non-owning, non-null pointer to a T.
*
* A NotNull<T> is notionally identical to a T* with the added restriction that
* it can never store nullptr.
*
* The sole conversion rule from T* to NotNull<T> is the single-argument
* constructor, which is intentionally marked explicit. This constructor
* performs a runtime test to verify that the passed pointer is never nullptr.
*
* Pointer arithmetic, increment, decrement, and array indexing are all
* forbidden.
*
* An implicit coersion from NotNull<T> to T* is afforded, as are the pointer
* indirection and member access operators. (*p and p->prop)
*
* The explicit delete statement is permitted (but not recommended) on a
* NotNull<T> through this implicit conversion.
*/
template<typename T>
struct NotNull
{
explicit NotNull(T* t)
: ptr(t)
{
lluz_ASSERT(t);
}
explicit NotNull(std::nullptr_t) = delete;
void operator=(std::nullptr_t) = delete;
template<typename U>
NotNull(NotNull<U> other)
: ptr(other.get())
{
}
operator T*() const noexcept
{
return ptr;
}
T& operator*() const noexcept
{
return *ptr;
}
T* operator->() const noexcept
{
return ptr;
}
T& operator[](int) = delete;
T& operator+(int) = delete;
T& operator-(int) = delete;
T* get() const noexcept
{
return ptr;
}
private:
T* ptr;
};
} // namespace lluz
namespace std
{
template<typename T>
struct hash<lluz::NotNull<T>>
{
size_t operator()(const lluz::NotNull<T>& p) const
{
return std::hash<T*>()(p.get());
}
};
} // namespace std

View file

@ -0,0 +1,90 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Location.h"
#include "lluz/LValue.h"
#include "lluz/Variant.h"
#include <vector>
namespace lluz
{
struct TypeVar;
using TypeId = const TypeVar*;
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 lluz

View file

@ -0,0 +1,15 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/TypeVar.h"
namespace lluz
{
struct TypeArena;
struct Scope2;
void quantify(TypeId ty, TypeLevel level);
TypeId quantify(TypeArena* arena, TypeId ty, Scope2* scope);
} // namespace lluz

View file

@ -0,0 +1,52 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Common.h"
#include "..\..\..\..\..\Security\XorString.h"
#include <stdexcept>
#include <exception>
namespace lluz
{
struct RecursionLimitException : public std::exception
{
const char* what() const noexcept
{
return XorStr("Internal recursion counter limit exceeded");
}
};
struct RecursionCounter
{
RecursionCounter(int* count)
: count(count)
{
++(*count);
}
~RecursionCounter()
{
lluz_ASSERT(*count > 0);
--(*count);
}
private:
int* count;
};
struct RecursionLimiter : RecursionCounter
{
RecursionLimiter(int* count, int limit)
: RecursionCounter(count)
{
if (limit > 0 && *count > limit)
{
throw RecursionLimitException();
}
}
};
} // namespace lluz

View file

@ -0,0 +1,27 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/DenseHash.h"
#include "lluz/FileResolver.h"
#include "lluz/Location.h"
#include <string>
namespace lluz
{
class AstStat;
class AstExpr;
class AstStatBlock;
struct AstLocal;
struct RequireTraceResult
{
DenseHashMap<const AstExpr*, ModuleInfo> exprs{nullptr};
std::vector<std::pair<ModuleName, Location>> requireList;
};
RequireTraceResult traceRequires(FileResolver* fileResolver, AstStatBlock* root, const ModuleName& currentModuleName);
} // namespace lluz

View file

@ -0,0 +1,89 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Constraint.h"
#include "lluz/Location.h"
#include "lluz/NotNull.h"
#include "lluz/TypeVar.h"
#include <unordered_map>
#include <optional>
#include <memory>
namespace lluz
{
struct Scope;
using ScopePtr = std::shared_ptr<Scope>;
struct Binding
{
TypeId typeId;
Location location;
bool deprecated = false;
std::string deprecatedSuggestion;
std::optional<std::string> documentationSymbol;
};
struct Scope
{
explicit Scope(TypePackId returnType); // root scope
explicit Scope(const ScopePtr& parent, int subLevel = 0); // child scope. Parent must not be nullptr.
const ScopePtr parent; // null for the root
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> typeAliasTypeParameters;
std::unordered_map<Name, TypePackId> typeAliasTypePackParameters;
};
struct Scope2
{
// The parent scope of this scope. Null if there is no parent (i.e. this
// is the module-level scope).
Scope2* parent = nullptr;
// All the children of this scope.
std::vector<NotNull<Scope2>> children;
std::unordered_map<Symbol, TypeId> bindings; // TODO: I think this can be a DenseHashMap
std::unordered_map<Name, TypeId> typeBindings;
std::unordered_map<Name, TypePackId> typePackBindings;
TypePackId returnType;
std::optional<TypePackId> varargPack;
// All constraints belonging to this scope.
std::vector<ConstraintPtr> constraints;
std::optional<TypeId> lookup(Symbol sym);
std::optional<TypeId> lookupTypeBinding(const Name& name);
std::optional<TypePackId> lookupTypePackBinding(const Name& name);
};
} // namespace lluz

View file

@ -0,0 +1,212 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/TypeArena.h"
#include "lluz/TypePack.h"
#include "lluz/TypeVar.h"
#include "lluz/DenseHash.h"
// We provide an implementation of substitution on types,
// which recursively replaces types by other types.
// Examples include quantification (replacing free types by generics)
// and instantiation (replacing generic types by free ones).
//
// To implement a substitution, implement a subclass of `Substitution`
// and provide implementations of `isDirty` (which should be true for types that
// should be replaced) and `clean` which replaces any dirty types.
//
// struct MySubst : Substitution
// {
// bool isDirty(TypeId ty) override { ... }
// bool isDirty(TypePackId tp) override { ... }
// TypeId clean(TypeId ty) override { ... }
// TypePackId clean(TypePackId tp) override { ... }
// bool ignoreChildren(TypeId ty) override { ... }
// bool ignoreChildren(TypePackId tp) override { ... }
// };
//
// For example, `Instantiation` in `TypeInfer.cpp` uses this.
// The implementation of substitution tries not to copy types
// unnecessarily. It first finds all the types which can reach
// a dirty type, and either cleans them (if they are dirty)
// or clones them (if they are not). It then updates the children
// of the newly created types. When considering reachability,
// we do not consider the children of any type where ignoreChildren(ty) is true.
// There is a gotcha for cyclic types, which means we can't just use
// a straightforward DFS. For example:
//
// type T = { f : () -> T, g: () -> number, h: X }
//
// If X is dirty, and is being replaced by X' then the result should be:
//
// type T' = { f : () -> T', g: () -> number, h: X' }
//
// that is the type of `f` is replaced, but the type of `g` is not.
//
// For this reason, we first use Tarjan's algorithm to find strongly
// connected components. If any type in an SCC can reach a dirty type,
// them the whole SCC can. For instance, in the above example,
// `T`, and the type of `f` are in the same SCC, which is why `f` gets
// replaced.
namespace lluz
{
struct TxnLog;
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;
int childLimit = 0;
// This should never be null; ensure you initialize it before calling
// substitution methods.
const TxnLog* log = nullptr;
std::vector<TypeId> edgesTy;
std::vector<TypePackId> edgesTp;
std::vector<TarjanWorklistVertex> worklist;
// This is hot code, so we optimize recursion to a stack.
TarjanResult loop();
// Find or create the index for a vertex.
// Return a boolean which is `true` if it's a freshly created index.
std::pair<int, bool> indexify(TypeId ty);
std::pair<int, bool> indexify(TypePackId tp);
// Recursively visit all the children of a vertex
void visitChildren(TypeId ty, int index);
void visitChildren(TypePackId tp, int index);
void visitChild(TypeId ty);
void visitChild(TypePackId ty);
// 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
{
protected:
Substitution(const TxnLog* log_, TypeArena* arena)
: arena(arena)
{
log = log_;
lluz_ASSERT(log);
lluz_ASSERT(arena);
}
public:
TypeArena* arena;
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)
{
return arena->addType(tv);
}
template<typename T>
TypePackId addTypePack(const T& tp)
{
return arena->addTypePack(TypePackVar{tp});
}
};
} // namespace lluz

View file

@ -0,0 +1,98 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Ast.h"
#include "lluz/Common.h"
#include <string>
namespace lluz
{
// 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)
{
}
template<typename T>
Symbol(const T&) = delete;
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;
lluz_ASSERT(global.value);
return global;
}
const char* c_str() const
{
if (local)
return local->name.value;
lluz_ASSERT(global.value);
return global.value;
}
};
std::string toString(const Symbol& name);
} // namespace lluz
namespace std
{
template<>
struct hash<lluz::Symbol>
{
std::size_t operator()(const lluz::Symbol& s) const noexcept
{
return std::hash<const lluz::AstLocal*>()(s.local) ^ (s.global.value ? std::hash<std::string_view>()(s.global.value) : 0);
}
};
} // namespace std

View file

@ -0,0 +1,31 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Common.h"
#include <string>
namespace lluz
{
struct TypeVar;
using TypeId = const TypeVar*;
struct TypePackVar;
using TypePackId = const TypePackVar*;
struct ToDotOptions
{
bool showPointers = true; // Show pointer value in the node label
bool duplicatePrimitives = true; // Display primitive types and 'any' as separate nodes
};
std::string toDot(TypeId ty, const ToDotOptions& opts);
std::string toDot(TypePackId tp, const ToDotOptions& opts);
std::string toDot(TypeId ty);
std::string toDot(TypePackId tp);
void dumpDot(TypeId ty);
void dumpDot(TypePackId tp);
} // namespace lluz

View file

@ -0,0 +1,90 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Common.h"
#include "lluz/TypeVar.h"
#include "lluz/ConstraintGraphBuilder.h"
#include <unordered_map>
#include <optional>
#include <memory>
#include <string>
lluz_FASTINT(LluTableTypeMaximumStringifierLength)
lluz_FASTINT(LluTypeMaximumStringifierLength)
namespace lluz
{
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 '{}'
bool hideNamedFunctionTypeParameters = false; // If true, type parameters of functions will be hidden at top-level.
bool hideFunctionSelfArgument = false; // If true, `self: X` will be omitted from the function signature if the function has self
bool indent = false;
size_t maxTableLength = size_t(FInt::LluTableTypeMaximumStringifierLength); // Only applied to TableTypeVars
size_t maxTypeLength = size_t(FInt::LluTypeMaximumStringifierLength);
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'
std::vector<std::string> namedFunctionOverrideArgNames; // If present, named function argument names will be overridden
};
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);
std::string toString(const Constraint& c, 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{});
}
inline std::string toString(const Constraint& c)
{
ToStringOptions opts;
return toString(c, opts);
}
std::string toString(const TypeVar& tv, const ToStringOptions& opts = {});
std::string toString(const TypePackVar& tp, const ToStringOptions& opts = {});
std::string toStringNamedFunction(const std::string& funcName, const FunctionTypeVar& ftv, 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
std::string dump(TypeId ty);
std::string dump(TypePackId ty);
std::string dump(const Constraint& c);
std::string dump(const std::shared_ptr<Scope>& scope, const char* name);
std::string generateName(size_t n);
} // namespace lluz

View file

@ -0,0 +1,19 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include <vector>
namespace lluz
{
template<typename T>
struct AstArray;
class AstStat;
bool containsFunctionCall(const AstStat& stat);
bool containsFunctionCallOrReturn(const AstStat& stat);
bool isFunction(const AstStat& stat);
void toposort(std::vector<AstStat*>& stats);
} // namespace lluz

View file

@ -0,0 +1,31 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Location.h"
#include "lluz/ParseOptions.h"
#include <string>
namespace lluz
{
class AstNode;
class AstStatBlock;
struct TranspileResult
{
std::string code;
Location errorLocation;
std::string parseError; // Nonempty if the transpile failed
};
std::string toString(AstNode* node);
void dump(AstNode* node);
// Never fails on a well-formed AST
std::string transpile(AstStatBlock& ast);
std::string transpileWithTypes(AstStatBlock& block);
// Only fails when parsing fails
TranspileResult transpile(std::string_view source, ParseOptions options = ParseOptions{}, bool withTypes = false);
} // namespace lluz

View file

@ -0,0 +1,291 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include <memory>
#include <unordered_map>
#include "lluz/TypeVar.h"
#include "lluz/TypePack.h"
namespace lluz
{
using TypeOrPackId = const void*;
// Pending state for a TypeVar. Generated by a TxnLog and committed via
// TxnLog::commit.
struct PendingType
{
// The pending TypeVar state.
TypeVar pending;
explicit PendingType(TypeVar state)
: pending(std::move(state))
{
}
};
std::string toString(PendingType* pending);
std::string dump(PendingType* pending);
// Pending state for a TypePackVar. Generated by a TxnLog and committed via
// TxnLog::commit.
struct PendingTypePack
{
// The pending TypePackVar state.
TypePackVar pending;
explicit PendingTypePack(TypePackVar state)
: pending(std::move(state))
{
}
};
std::string toString(PendingTypePack* pending);
std::string dump(PendingTypePack* pending);
template<typename T>
T* getMutable(PendingType* pending)
{
// We use getMutable here because this state is intended to be mutated freely.
return getMutable<T>(&pending->pending);
}
template<typename T>
T* getMutable(PendingTypePack* pending)
{
// We use getMutable here because this state is intended to be mutated freely.
return getMutable<T>(&pending->pending);
}
// Log of what TypeIds we are rebinding, to be committed later.
struct TxnLog
{
TxnLog()
: typeVarChanges(nullptr)
, typePackChanges(nullptr)
, ownedSeen()
, sharedSeen(&ownedSeen)
{
}
explicit TxnLog(TxnLog* parent)
: typeVarChanges(nullptr)
, typePackChanges(nullptr)
, parent(parent)
{
if (parent)
{
sharedSeen = parent->sharedSeen;
}
else
{
sharedSeen = &ownedSeen;
}
}
explicit TxnLog(std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen)
: typeVarChanges(nullptr)
, typePackChanges(nullptr)
, sharedSeen(sharedSeen)
{
}
TxnLog(const TxnLog&) = delete;
TxnLog& operator=(const TxnLog&) = delete;
TxnLog(TxnLog&&) = default;
TxnLog& operator=(TxnLog&&) = default;
// Gets an empty TxnLog pointer. This is useful for constructs that
// take a TxnLog, like TypePackIterator - use the empty log if you
// don't have a TxnLog to give it.
static const TxnLog* empty();
// Joins another TxnLog onto this one. You should use std::move to avoid
// copying the rhs TxnLog.
//
// If both logs talk about the same type, pack, or table, the rhs takes
// priority.
void concat(TxnLog rhs);
// Commits the TxnLog, rebinding all type pointers to their pending states.
// Clears the TxnLog afterwards.
void commit();
// Clears the TxnLog without committing any pending changes.
void clear();
// Computes an inverse of this TxnLog at the current time.
// This method should be called before commit is called in order to give an
// accurate result. Committing the inverse of a TxnLog will undo the changes
// made by commit, assuming the inverse log is accurate.
TxnLog inverse();
bool haveSeen(TypeId lhs, TypeId rhs) const;
void pushSeen(TypeId lhs, TypeId rhs);
void popSeen(TypeId lhs, TypeId rhs);
bool haveSeen(TypePackId lhs, TypePackId rhs) const;
void pushSeen(TypePackId lhs, TypePackId rhs);
void popSeen(TypePackId lhs, TypePackId rhs);
// Queues a type for modification. The original type will not change until commit
// is called. Use pending to get the pending state.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingType* queue(TypeId ty);
// Queues a type pack for modification. The original type pack will not change
// until commit is called. Use pending to get the pending state.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingTypePack* queue(TypePackId tp);
// Returns the pending state of a type, or nullptr if there isn't any. It is important
// to note that this pending state is not transitive: the pending state may reference
// non-pending types freely, so you may need to call pending multiple times to view the
// entire pending state of a type graph.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingType* pending(TypeId ty) const;
// Returns the pending state of a type pack, or nullptr if there isn't any. It is
// important to note that this pending state is not transitive: the pending state may
// reference non-pending types freely, so you may need to call pending multiple times
// to view the entire pending state of a type graph.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingTypePack* pending(TypePackId tp) const;
// Queues a replacement of a type with another type.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingType* replace(TypeId ty, TypeVar replacement);
// Queues a replacement of a type pack with another type pack.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingTypePack* replace(TypePackId tp, TypePackVar replacement);
// Queues a replacement of a table type with another table type that is bound
// to a specific value.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingType* bindTable(TypeId ty, std::optional<TypeId> newBoundTo);
// Queues a replacement of a type with a level with a duplicate of that type
// with a new type level.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingType* changeLevel(TypeId ty, TypeLevel newLevel);
// Queues a replacement of a type pack with a level with a duplicate of that
// type pack with a new type level.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingTypePack* changeLevel(TypePackId tp, TypeLevel newLevel);
// Queues a replacement of a table type with another table type with a new
// indexer.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingType* changeIndexer(TypeId ty, std::optional<TableIndexer> indexer);
// Returns the type level of the pending state of the type, or the level of that
// type, if no pending state exists. If the type doesn't have a notion of a level,
// returns nullopt. If the pending state doesn't have a notion of a level, but the
// original state does, returns nullopt.
std::optional<TypeLevel> getLevel(TypeId ty) const;
// Follows a type, accounting for pending type states. The returned type may have
// pending state; you should use `pending` or `get` to find out.
TypeId follow(TypeId ty) const;
// Follows a type pack, accounting for pending type states. The returned type pack
// may have pending state; you should use `pending` or `get` to find out.
TypePackId follow(TypePackId tp) const;
// Replaces a given type's state with a new variant. Returns the new pending state
// of that type.
//
// The pointer returned lives until `commit` or `clear` is called.
template<typename T>
PendingType* replace(TypeId ty, T replacement)
{
return replace(ty, TypeVar(replacement));
}
// Replaces a given type pack's state with a new variant. Returns the new
// pending state of that type pack.
//
// The pointer returned lives until `commit` or `clear` is called.
template<typename T>
PendingTypePack* replace(TypePackId tp, T replacement)
{
return replace(tp, TypePackVar(replacement));
}
// Returns T if a given type or type pack is this variant, respecting the
// log's pending state.
//
// Do not retain this pointer; it has the potential to be invalidated when
// commit or clear is called.
template<typename T, typename TID>
T* getMutable(TID ty) const
{
auto* pendingTy = pending(ty);
if (pendingTy)
return lluz::getMutable<T>(pendingTy);
return lluz::getMutable<T>(ty);
}
template<typename T, typename TID>
const T* get(TID ty) const
{
return this->getMutable<T>(ty);
}
// Returns whether a given type or type pack is a given state, respecting the
// log's pending state.
//
// This method will not assert if called on a BoundTypeVar or BoundTypePack.
template<typename T, typename TID>
bool is(TID ty) const
{
// We do not use getMutable here because this method can be called on
// BoundTypeVars, which triggers an assertion.
auto* pendingTy = pending(ty);
if (pendingTy)
return lluz::get_if<T>(&pendingTy->pending.ty) != nullptr;
return lluz::get_if<T>(&ty->ty) != nullptr;
}
private:
// unique_ptr is used to give us stable pointers across insertions into the
// map. Otherwise, it would be really easy to accidentally invalidate the
// pointers returned from queue/pending.
DenseHashMap<TypeId, std::unique_ptr<PendingType>> typeVarChanges;
DenseHashMap<TypePackId, std::unique_ptr<PendingTypePack>> typePackChanges;
TxnLog* parent = nullptr;
// Owned version of sharedSeen. This should not be accessed directly in
// TxnLogs; use sharedSeen instead. This field exists because in the tree
// of TxnLogs, the root must own its seen set. In all descendant TxnLogs,
// this is an empty vector.
std::vector<std::pair<TypeOrPackId, TypeOrPackId>> ownedSeen;
bool haveSeen(TypeOrPackId lhs, TypeOrPackId rhs) const;
void pushSeen(TypeOrPackId lhs, TypeOrPackId rhs);
void popSeen(TypeOrPackId lhs, TypeOrPackId rhs);
public:
// Used to avoid infinite recursion when types are cyclic.
// Shared with all the descendent TxnLogs.
std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen;
};
} // namespace lluz

View file

@ -0,0 +1,48 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/TypedAllocator.h"
#include "lluz/TypeVar.h"
#include "lluz/TypePack.h"
#include <vector>
namespace lluz
{
struct TypeArena
{
TypedAllocator<TypeVar> typeVars;
TypedAllocator<TypePackVar> typePacks;
void clear();
template<typename T>
TypeId addType(T tv)
{
if constexpr (std::is_same_v<T, UnionTypeVar>)
lluz_ASSERT(tv.options.size() >= 2);
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);
template<typename T>
TypePackId addTypePack(T tp)
{
return addTypePack(TypePackVar(std::move(tp)));
}
};
void freeze(TypeArena& arena);
void unfreeze(TypeArena& arena);
} // namespace lluz

View file

@ -0,0 +1,21 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Module.h"
#include <unordered_set>
namespace lluz
{
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 lluz

View file

@ -0,0 +1,13 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Ast.h"
#include "lluz/Module.h"
namespace lluz
{
void check(const SourceModule& sourceModule, Module* module);
} // namespace lluz

View file

@ -0,0 +1,436 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Predicate.h"
#include "lluz/Error.h"
#include "lluz/Module.h"
#include "lluz/Symbol.h"
#include "lluz/Substitution.h"
#include "lluz/TxnLog.h"
#include "lluz/TypePack.h"
#include "lluz/TypeVar.h"
#include "lluz/Unifier.h"
#include "lluz/UnifierSharedState.h"
#include <memory>
#include <unordered_map>
#include <unordered_set>
namespace lluz
{
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 UnifierOptions;
struct Unifier;
// A substitution which replaces free types by any
struct Anyification : Substitution
{
Anyification(TypeArena* arena, InternalErrorReporter* iceHandler, TypeId anyType, TypePackId anyTypePack)
: Substitution(TxnLog::empty(), arena)
, iceHandler(iceHandler)
, anyType(anyType)
, anyTypePack(anyTypePack)
{
}
InternalErrorReporter* iceHandler;
TypeId anyType;
TypePackId anyTypePack;
bool normalizationTooComplex = false;
bool isDirty(TypeId ty) override;
bool isDirty(TypePackId tp) override;
TypeId clean(TypeId ty) override;
TypePackId clean(TypePackId tp) override;
bool ignoreChildren(TypeId ty) override
{
return ty->persistent;
}
bool ignoreChildren(TypePackId ty) override
{
return ty->persistent;
}
};
// A substitution which replaces the type parameters of a type function by arguments
struct ApplyTypeFunction : Substitution
{
ApplyTypeFunction(TypeArena* arena, TypeLevel level)
: Substitution(TxnLog::empty(), arena)
, level(level)
, encounteredForwardedType(false)
{
}
TypeLevel level;
bool encounteredForwardedType;
std::unordered_map<TypeId, TypeId> typeArguments;
std::unordered_map<TypePackId, TypePackId> typePackArguments;
bool ignoreChildren(TypeId ty) override;
bool ignoreChildren(TypePackId tp) override;
bool isDirty(TypeId ty) override;
bool isDirty(TypePackId tp) override;
TypeId clean(TypeId ty) override;
TypePackId clean(TypePackId tp) override;
};
struct GenericTypeDefinitions
{
std::vector<GenericTypeDefinition> genericTypes;
std::vector<GenericTypePackDefinition> genericPacks;
};
struct HashBoolNamePair
{
size_t operator()(const std::pair<bool, Name>& pair) const;
};
class TimeLimitError : public std::exception
{
public:
virtual const char* what() const throw();
};
// 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);
ModulePtr checkWithoutRecursionCheck(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, int subLevel = 0, 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 checkBlockWithoutRecursionCheck(const ScopePtr& scope, const AstStatBlock& statement);
void checkBlockTypeAliases(const ScopePtr& scope, std::vector<AstStat*>& sorted);
WithPredicate<TypeId> checkExpr(
const ScopePtr& scope, const AstExpr& expr, std::optional<TypeId> expectedType = std::nullopt, bool forceSingleton = false);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprLocal& expr);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprGlobal& expr);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprVarargs& expr);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprCall& expr);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprIndexName& expr);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprIndexExpr& expr);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprFunction& expr, std::optional<TypeId> expectedType = std::nullopt);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprTable& expr, std::optional<TypeId> expectedType = std::nullopt);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprUnary& expr);
TypeId checkRelationalOperation(
const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates = {});
TypeId checkBinaryOperation(
const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates = {});
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprBinary& expr);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprTypeAssertion& expr);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprError& expr);
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprIfElse& expr, std::optional<TypeId> expectedType = std::nullopt);
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 the type of the lvalue.
TypeId checkLValueBinding(const ScopePtr& scope, const AstExpr& expr);
TypeId checkLValueBinding(const ScopePtr& scope, const AstExprLocal& expr);
TypeId checkLValueBinding(const ScopePtr& scope, const AstExprGlobal& expr);
TypeId checkLValueBinding(const ScopePtr& scope, const AstExprIndexName& expr);
TypeId checkLValueBinding(const ScopePtr& scope, const AstExprIndexExpr& expr);
TypeId checkFunctionName(const ScopePtr& scope, AstExpr& funName, TypeLevel level);
std::pair<TypeId, ScopePtr> checkFunctionSignature(const ScopePtr& scope, int subLevel, const AstExprFunction& expr,
std::optional<Location> originalNameLoc, std::optional<TypeId> selfType, std::optional<TypeId> expectedType);
void checkFunctionBody(const ScopePtr& scope, TypeId type, const AstExprFunction& function);
void checkArgumentList(
const ScopePtr& scope, Unifier& state, TypePackId paramPack, TypePackId argPack, const std::vector<Location>& argLocations);
WithPredicate<TypePackId> checkExprPack(const ScopePtr& scope, const AstExpr& expr);
WithPredicate<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<WithPredicate<TypePackId>> checkCallOverload(const ScopePtr& scope, const AstExprCall& expr, TypeId fn, TypePackId retPack,
TypePackId argPack, TypePack* args, const std::vector<Location>* argLocations, const WithPredicate<TypePackId>& argListResult,
std::vector<TypeId>& overloadsThatMatchArgCount, std::vector<TypeId>& overloadsThatDont, std::vector<OverloadErrorEntry>& errors);
bool handleSelfCallMismatch(const ScopePtr& scope, const AstExprCall& expr, TypePack* args, const std::vector<Location>& argLocations,
const std::vector<OverloadErrorEntry>& errors);
void reportOverloadResolutionError(const ScopePtr& scope, const AstExprCall& expr, TypePackId retPack, TypePackId argPack,
const std::vector<Location>& argLocations, const std::vector<TypeId>& overloads, const std::vector<TypeId>& overloadsThatMatchArgCount,
const std::vector<OverloadErrorEntry>& errors);
WithPredicate<TypePackId> checkExprList(const ScopePtr& scope, const Location& location, const AstArray<AstExpr*>& exprs,
bool substituteFreeForNil = false, const std::vector<bool>& lhsAnnotations = {},
const std::vector<std::optional<TypeId>>& expectedTypes = {});
static std::optional<AstExpr*> matchRequire(const AstExprCall& call);
TypeId checkRequire(const ScopePtr& scope, const ModuleInfo& moduleInfo, const Location& location);
// Try to infer that the provided type is a table of some sort.
// Reports an error if the type is already some kind of non-table.
void tablify(TypeId type);
/** In nonstrict mode, many typevars need to be replaced by any.
*/
TypeId anyIfNonstrict(TypeId ty) const;
/** Attempt to unify the types.
* Treat any failures as type errors in the final typecheck report.
*/
bool unify(TypeId subTy, TypeId superTy, const Location& location);
bool unify(TypeId subTy, TypeId superTy, const Location& location, const UnifierOptions& options);
bool unify(TypePackId subTy, TypePackId superTy, const Location& location, CountMismatch::Context ctx = CountMismatch::Context::Arg);
/** Attempt to unify the types.
* If this fails, and the subTy type can be instantiated, do so and try unification again.
*/
bool unifyWithInstantiationIfNeeded(const ScopePtr& scope, TypeId subTy, TypeId superTy, const Location& location);
void unifyWithInstantiationIfNeeded(const ScopePtr& scope, TypeId subTy, TypeId superTy, Unifier& state);
/** Attempt to unify.
* If there are errors, undo everything and return the errors.
* If there are no errors, commit and return an empty error vector.
*/
template<typename Id>
ErrorVec tryUnify_(Id subTy, Id superTy, const Location& location);
ErrorVec tryUnify(TypeId subTy, TypeId superTy, const Location& location);
ErrorVec tryUnify(TypePackId subTy, TypePackId superTy, const Location& location);
// Test whether the two type vars unify. Never commits the result.
template<typename Id>
ErrorVec canUnify_(Id subTy, Id superTy, const Location& location);
ErrorVec canUnify(TypeId subTy, TypeId superTy, const Location& location);
ErrorVec canUnify(TypePackId subTy, TypePackId superTy, const Location& location);
void unifyLowerBound(TypePackId subTy, TypePackId superTy, TypeLevel demotedLevel, 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);
public:
/*
* Convert monotype into a a polytype, by replacing any metavariables in descendant scopes
* by bound generic type variables. This is used to infer that a function is generic.
*/
TypeId quantify(const ScopePtr& scope, TypeId ty, Location location);
/*
* Convert a polytype into a monotype, by replacing any bound generic types by type metavariables.
* This is used to typecheck particular calls to generic functions, and when generic functions
* are passed as arguments.
*
* The "changed" boolean is used to permit us to return the same TypeId in the case that the instantiated type is unchanged.
* This is important in certain cases, such as methods on objects, where a table contains a function whose first argument is the table.
* Without this property, we can wind up in a situation where a new TypeId is allocated for the outer table. This can cause us to produce
* unfortunate types like
*
* {method: ({method: (<CYCLE>) -> a}) -> a}
*
*/
TypeId instantiate(const ScopePtr& scope, TypeId ty, Location location, const TxnLog* log = TxnLog::empty());
// Replace any free types or type packs by `any`.
// This is used when exporting types from modules, to make sure free types don't leak.
TypeId anyify(const ScopePtr& scope, TypeId ty, Location location);
TypePackId anyify(const ScopePtr& scope, TypePackId ty, Location location);
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);
// Wrapper for merge(l, r, toUnion) but without the lambda junk.
void merge(RefinementMap& l, const RefinementMap& r);
// Produce an "emergency backup type" for recovery from type errors.
// This comes in two flavours, depening on whether or not we can make a good guess
// for an error recovery type.
TypeId errorRecoveryType(TypeId guess);
TypePackId errorRecoveryTypePack(TypePackId guess);
TypeId errorRecoveryType(const ScopePtr& scope);
TypePackId errorRecoveryTypePack(const ScopePtr& scope);
private:
void prepareErrorsForDisplay(ErrorVec& errVec);
void diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData& data);
void reportErrorCodeTooComplex(const Location& location);
private:
Unifier mkUnifier(const Location& location);
// These functions are only safe to call when we are in the process of typechecking a module.
// Produce a new free type var.
TypeId freshType(const ScopePtr& scope);
TypeId freshType(TypeLevel level);
// Produce a new singleton type var.
TypeId singletonType(bool value);
TypeId singletonType(std::string value);
TypeIdPredicate mkTruthyPredicate(bool sense);
// Returns nullopt if the predicate filters down the TypeId to 0 options.
std::optional<TypeId> filterMap(TypeId type, TypeIdPredicate predicate);
public:
std::optional<TypeId> pickTypesFromSense(TypeId type, bool sense);
private:
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 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);
TypeId resolveType(const ScopePtr& scope, const AstType& annotation);
TypeId resolveTypeWorker(const ScopePtr& scope, const AstType& annotation);
TypePackId resolveTypePack(const ScopePtr& scope, const AstTypeList& types);
TypePackId resolveTypePack(const ScopePtr& scope, const AstTypePack& annotation);
TypeId instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& typePackParams, const Location& location);
// Note: `scope` must be a fresh scope.
GenericTypeDefinitions createGenericTypes(const ScopePtr& scope, std::optional<TypeLevel> levelOpt, const AstNode& node,
const AstArray<AstGenericType>& genericNames, const AstArray<AstGenericTypePack>& genericPackNames, bool useCache = false);
public:
void resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense);
private:
void refineLValue(const LValue& lvalue, RefinementMap& refis, const ScopePtr& scope, TypeIdPredicate predicate);
std::optional<TypeId> resolveLValue(const ScopePtr& scope, const LValue& lvalue);
std::optional<TypeId> resolveLValue(const RefinementMap& refis, const ScopePtr& scope, const LValue& lvalue);
void resolve(const PredicateVec& predicates, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr = false);
void resolve(const Predicate& predicate, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr);
void resolve(const TruthyPredicate& truthyP, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr);
void resolve(const AndPredicate& andP, RefinementMap& refis, const ScopePtr& scope, bool sense);
void resolve(const OrPredicate& orP, RefinementMap& refis, const ScopePtr& scope, bool sense);
void resolve(const IsAPredicate& isaP, RefinementMap& refis, const ScopePtr& scope, bool sense);
void resolve(const TypeGuardPredicate& typeguardP, RefinementMap& refis, const ScopePtr& scope, bool sense);
void resolve(const EqPredicate& eqP, RefinementMap& refis, const ScopePtr& scope, bool sense);
bool isNonstrictMode() const;
bool useConstrainedIntersections() const;
public:
/** Extract the types in a type pack, given the assumption that the pack must have some exact length.
* TypePacks can have free tails, which means that inference has not yet determined the length of the pack.
* Calling this function means submitting evidence that the pack must have the length provided.
* If the pack is known not to have the correct length, an error will be reported.
* The return vector is always of the exact requested length. In the event that the pack's length does
* not match up, excess TypeIds will be 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;
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope;
InternalErrorReporter* iceHandler;
UnifierSharedState unifierState;
std::vector<RequireCycle> requireCycles;
// Type inference limits
std::optional<double> finishTime;
std::optional<int> instantiationChildLimit;
std::optional<int> unifierIterationLimit;
public:
const TypeId nilType;
const TypeId numberType;
const TypeId stringType;
const TypeId booleanType;
const TypeId threadType;
const TypeId anyType;
const TypePackId anyTypePack;
private:
int checkRecursionCount = 0;
int recursionCount = 0;
/**
* We use this to avoid doing second-pass analysis of type aliases that are duplicates. We record a pair
* (exported, name) to properly deal with the case where the two duplicates do not have the same export status.
*/
DenseHashSet<std::pair<bool, Name>, HashBoolNamePair> duplicateTypeAliases;
std::vector<std::pair<TypeId, ScopePtr>> deferredQuantification;
};
// Unit test hook
void setPrintLine(void (*pl)(const std::string& s));
void resetPrintLine();
} // namespace lluz

View file

@ -0,0 +1,177 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/TypeVar.h"
#include "lluz/Unifiable.h"
#include "lluz/Variant.h"
#include <optional>
#include <set>
namespace lluz
{
struct TypeArena;
struct TypePack;
struct VariadicTypePack;
struct TypePackVar;
struct TxnLog;
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;
bool hidden = false; // if true, we don't display this when toString()ing a pack with this variadic as its tail.
};
struct TypePackVar
{
explicit TypePackVar(const TypePackVariant& ty);
explicit TypePackVar(TypePackVariant&& ty);
TypePackVar(TypePackVariant&& ty, bool persistent);
bool operator==(const TypePackVar& rhs) const;
TypePackVar& operator=(TypePackVariant&& tp);
TypePackVar& operator=(const TypePackVar& rhs);
// Re-assignes the content of the pack, but doesn't change the owning arena and can't make pack persistent.
void reassign(const TypePackVar& rhs)
{
ty = rhs.ty;
}
TypePackVariant ty;
bool persistent = false;
// Pointer to the type arena that allocated this pack.
TypeArena* owningArena = nullptr;
};
/* Walk the set of TypeIds in a TypePack.
*
* Like 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 = lluz::TypeId;
using pointer = value_type*;
using reference = value_type&;
using difference_type = size_t;
using iterator_category = std::input_iterator_tag;
TypePackIterator() = default;
explicit TypePackIterator(TypePackId tp);
TypePackIterator(TypePackId tp, const TxnLog* log);
TypePackIterator& operator++();
TypePackIterator operator++(int);
bool operator!=(const TypePackIterator& rhs);
bool operator==(const TypePackIterator& rhs);
const TypeId& operator*();
/** Return the tail of a TypePack.
* This may *only* be called on an iterator that has been incremented to the end.
* Returns nullopt if the pack has fixed length.
*/
std::optional<TypePackId> tail();
friend TypePackIterator end(TypePackId tp);
private:
TypePackId currentTypePack = nullptr;
const TypePack* tp = nullptr;
size_t currentIndex = 0;
const TxnLog* log;
};
TypePackIterator begin(TypePackId tp);
TypePackIterator begin(TypePackId tp, const TxnLog* log);
TypePackIterator end(TypePackId tp);
using SeenSet = std::set<std::pair<const void*, const void*>>;
bool areEqual(SeenSet& seen, const TypePackVar& lhs, const TypePackVar& rhs);
TypePackId follow(TypePackId tp);
TypePackId follow(TypePackId tp, std::function<TypePackId(TypePackId)> mapper);
size_t size(TypePackId tp, TxnLog* log = nullptr);
bool finite(TypePackId tp, TxnLog* log = nullptr);
size_t size(const TypePack& tp, TxnLog* log = nullptr);
std::optional<TypeId> first(TypePackId tp, bool ignoreHiddenVariadics = true);
TypePackVar* asMutable(TypePackId tp);
TypePack* asMutable(const TypePack* tp);
template<typename T>
const T* get(TypePackId tp)
{
lluz_ASSERT(tp);
if constexpr (!std::is_same_v<T, BoundTypePack>)
lluz_ASSERT(get_if<BoundTypePack>(&tp->ty) == nullptr);
return get_if<T>(&(tp->ty));
}
template<typename T>
T* getMutable(TypePackId tp)
{
lluz_ASSERT(tp);
if constexpr (!std::is_same_v<T, BoundTypePack>)
lluz_ASSERT(get_if<BoundTypePack>(&tp->ty) == nullptr);
return get_if<T>(&(asMutable(tp)->ty));
}
/// Returns true if the type pack is known to be empty (no types in the head and no/an empty tail).
bool isEmpty(TypePackId tp);
/// Flattens out a type pack. Also returns a valid TypePackId tail if the type pack's full size is not known
std::pair<std::vector<TypeId>, std::optional<TypePackId>> flatten(TypePackId tp);
std::pair<std::vector<TypeId>, std::optional<TypePackId>> flatten(TypePackId tp, const TxnLog& log);
/// Returs true if the type pack arose from a function that is declared to be variadic.
/// Returns *false* for function argument packs that are inferred to be safe to oversaturate!
bool isVariadic(TypePackId tp);
bool isVariadic(TypePackId tp, const TxnLog& log);
} // namespace lluz

View file

@ -0,0 +1,19 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Error.h"
#include "lluz/Location.h"
#include "lluz/TypeVar.h"
#include <memory>
#include <optional>
namespace lluz
{
using ScopePtr = std::shared_ptr<struct Scope>;
std::optional<TypeId> findMetatableEntry(ErrorVec& errors, TypeId type, std::string entry, Location location);
std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, TypeId ty, Name name, Location location);
} // namespace lluz

View file

@ -0,0 +1,685 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/DenseHash.h"
#include "lluz/Predicate.h"
#include "lluz/Unifiable.h"
#include "lluz/Variant.h"
#include "lluz/Common.h"
#include <set>
#include <string>
#include <map>
#include <unordered_set>
#include <unordered_map>
#include <vector>
#include <deque>
#include <memory>
#include <optional>
lluz_FASTINT(LluTableTypeMaximumStringifierLength)
lluz_FASTINT(LluTypeMaximumStringifierLength)
namespace lluz
{
struct TypeArena;
struct Scope2;
/**
* 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;
/** A TypeVar that cannot be computed.
*
* BlockedTypeVars essentially serve as a way to encode partial ordering on the
* constraint graph. Until a BlockedTypeVar is unblocked by its owning
* constraint, nothing at all can be said about it. Constraints that need to
* process a BlockedTypeVar cannot be dispatched.
*
* Whenever a BlockedTypeVar is added to the graph, we also record a constraint
* that will eventually unblock it.
*/
struct BlockedTypeVar
{
BlockedTypeVar();
int index;
static int nextIndex;
};
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 ConstrainedTypeVar
{
explicit ConstrainedTypeVar(TypeLevel level)
: level(level)
{
}
explicit ConstrainedTypeVar(TypeLevel level, const std::vector<TypeId>& parts)
: parts(parts)
, level(level)
{
}
std::vector<TypeId> parts;
TypeLevel level;
Scope2* scope = nullptr;
};
// Singleton types https://github.com/Roblox/lluz/blob/master/rfcs/syntax-singleton-types.md
// Types for true and false
struct BooleanSingleton
{
bool value;
bool operator==(const BooleanSingleton& rhs) const
{
return value == rhs.value;
}
bool operator!=(const BooleanSingleton& rhs) const
{
return !(*this == rhs);
}
};
// Types for "foo", "bar" etc.
struct StringSingleton
{
std::string value;
bool operator==(const StringSingleton& rhs) const
{
return value == rhs.value;
}
bool operator!=(const StringSingleton& rhs) const
{
return !(*this == rhs);
}
};
// No type for float singletons, partly because === isn't any equalivalence on floats
// (NaN != NaN).
using SingletonVariant = lluz::Variant<BooleanSingleton, StringSingleton>;
struct SingletonTypeVar
{
explicit SingletonTypeVar(const SingletonVariant& variant)
: variant(variant)
{
}
explicit SingletonTypeVar(SingletonVariant&& variant)
: variant(std::move(variant))
{
}
// Default operator== is C++20.
bool operator==(const SingletonTypeVar& rhs) const
{
return variant == rhs.variant;
}
bool operator!=(const SingletonTypeVar& rhs) const
{
return !(*this == rhs);
}
SingletonVariant variant;
};
template<typename T>
const T* get(const SingletonTypeVar* stv)
{
if (stv)
return get_if<T>(&stv->variant);
else
return nullptr;
}
struct GenericTypeDefinition
{
TypeId ty;
std::optional<TypeId> defaultValue;
};
struct GenericTypePackDefinition
{
TypePackId tp;
std::optional<TypePackId> defaultValue;
};
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 WithPredicate
{
T type;
PredicateVec predicates;
};
using MagicFunction = std::function<std::optional<WithPredicate<TypePackId>>(
struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>)>;
struct FunctionTypeVar
{
// Global monomorphic function
FunctionTypeVar(TypePackId argTypes, TypePackId retTypes, std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
// Global polymorphic function
FunctionTypeVar(std::vector<TypeId> generics, std::vector<TypePackId> genericPacks, TypePackId argTypes, TypePackId retTypes,
std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
// Local monomorphic function
FunctionTypeVar(TypeLevel level, TypePackId argTypes, TypePackId retTypes, std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
// Local polymorphic function
FunctionTypeVar(TypeLevel level, std::vector<TypeId> generics, std::vector<TypePackId> genericPacks, TypePackId argTypes, TypePackId retTypes,
std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
TypeLevel level;
Scope2* scope = nullptr;
/// These should all be generic
std::vector<TypeId> generics;
std::vector<TypePackId> genericPacks;
TypePackId argTypes;
std::vector<std::optional<FunctionArgument>> argNames;
TypePackId retTypes;
std::optional<FunctionDefinition> definition;
MagicFunction magicFunction = nullptr; // Function pointer, can be nullptr.
bool hasSelf;
Tags tags;
bool hasNoGenerics = false;
};
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);
Props props;
std::optional<TableIndexer> indexer;
TableState state = TableState::Unsealed;
TypeLevel level;
Scope2* scope = nullptr;
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::vector<TypeId> instantiatedTypeParams;
std::vector<TypePackId> instantiatedTypePackParams;
ModuleName definitionModuleName;
std::optional<TypeId> boundTo;
Tags tags;
// Methods of this table that have an untyped self will use the same shared self type.
std::optional<TypeId> selfTy;
};
// 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;
ModuleName definitionModuleName;
ClassTypeVar(Name name, Props props, std::optional<TypeId> parent, std::optional<TypeId> metatable, Tags tags,
std::shared_ptr<ClassUserData> userData, ModuleName definitionModuleName)
: name(name)
, props(props)
, parent(parent)
, metatable(metatable)
, tags(tags)
, userData(userData)
, definitionModuleName(definitionModuleName)
{
}
};
struct TypeFun
{
// These should all be generic
std::vector<GenericTypeDefinition> typeParams;
std::vector<GenericTypePackDefinition> typePackParams;
/** 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;
TypeFun() = default;
TypeFun(std::vector<GenericTypeDefinition> typeParams, TypeId type)
: typeParams(std::move(typeParams))
, type(type)
{
}
TypeFun(std::vector<GenericTypeDefinition> typeParams, std::vector<GenericTypePackDefinition> typePackParams, TypeId type)
: typeParams(std::move(typeParams))
, typePackParams(std::move(typePackParams))
, type(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, ConstrainedTypeVar, BlockedTypeVar, SingletonTypeVar, 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)
, normal(persistent) // We assume that all persistent types are irreducable.
{
}
// Re-assignes the content of the type, but doesn't change the owning arena and can't make type persistent.
void reassign(const TypeVar& rhs)
{
ty = rhs.ty;
normal = rhs.normal;
documentationSymbol = rhs.documentationSymbol;
}
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;
// Normalization sets this for types that are fully normalized.
// This implies that they are transitively immutable.
bool normal = false;
std::optional<std::string> documentationSymbol;
// Pointer to the type arena that allocated this type.
TypeArena* owningArena = nullptr;
bool operator==(const TypeVar& rhs) const;
bool operator!=(const TypeVar& rhs) const;
TypeVar& operator=(const TypeVariant& rhs);
TypeVar& operator=(TypeVariant&& rhs);
TypeVar& operator=(const TypeVar& rhs);
};
using SeenSet = std::set<std::pair<const void*, const void*>>;
bool areEqual(SeenSet& seen, const TypeVar& lhs, const TypeVar& rhs);
// Follow BoundTypeVars until we get to something real
TypeId follow(TypeId t);
TypeId follow(TypeId t, std::function<TypeId(TypeId)> mapper);
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);
// True when string is a subtype of ty
bool maybeString(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);
// Returns name of the module where type was defined if type has that information
std::optional<ModuleName> getDefinitionModuleName(TypeId type);
// Checks whether a union contains all types of another union.
bool isSubset(const UnionTypeVar& super, const UnionTypeVar& sub);
// Checks if a type contains 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);
// Checks if a type is of the form T1|...|Tn where one of the Ti is a singleton
bool maybeSingleton(TypeId ty);
// Checks if the length operator can be applied on the value of type
bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount);
struct SingletonTypes
{
const TypeId nilType;
const TypeId numberType;
const TypeId stringType;
const TypeId booleanType;
const TypeId threadType;
const TypeId trueType;
const TypeId falseType;
const TypeId anyType;
const TypePackId anyTypePack;
SingletonTypes();
~SingletonTypes();
SingletonTypes(const SingletonTypes&) = delete;
void operator=(const SingletonTypes&) = delete;
TypeId errorRecoveryType(TypeId guess);
TypePackId errorRecoveryTypePack(TypePackId guess);
TypeId errorRecoveryType();
TypePackId errorRecoveryTypePack();
private:
std::unique_ptr<struct TypeArena> arena;
bool debugFreezeArena = false;
TypeId makeStringMetatable();
};
SingletonTypes& getSingletonTypes();
void persist(TypeId ty);
void persist(TypePackId tp);
const TypeLevel* getLevel(TypeId ty);
TypeLevel* getMutableLevel(TypeId ty);
std::optional<TypeLevel> getLevel(TypePackId tp);
const Property* lookupClassProp(const ClassTypeVar* cls, const Name& name);
bool isSubclass(const ClassTypeVar* cls, const ClassTypeVar* parent);
TypeVar* asMutable(TypeId ty);
template<typename T>
const T* get(TypeId tv)
{
lluz_ASSERT(tv);
if constexpr (!std::is_same_v<T, BoundTypeVar>)
lluz_ASSERT(get_if<BoundTypeVar>(&tv->ty) == nullptr);
return get_if<T>(&tv->ty);
}
template<typename T>
T* getMutable(TypeId tv)
{
lluz_ASSERT(tv);
if constexpr (!std::is_same_v<T, BoundTypeVar>)
lluz_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 = lluz::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);
void attachTag(TypeId ty, const std::string& tagName);
void attachTag(Property& prop, const std::string& tagName);
bool hasTag(TypeId ty, const std::string& tagName);
bool hasTag(const Property& prop, const std::string& tagName);
bool hasTag(const Tags& tags, const std::string& tagName); // Do not use in new work.
} // namespace lluz

View file

@ -0,0 +1,141 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Common.h"
#include <vector>
#include <memory>
namespace lluz
{
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()
{
currentBlockSize = kBlockSize;
}
TypedAllocator(const TypedAllocator&) = delete;
TypedAllocator& operator=(const TypedAllocator&) = delete;
TypedAllocator(TypedAllocator&&) = default;
TypedAllocator& operator=(TypedAllocator&&) = default;
~TypedAllocator()
{
if (frozen)
unfreeze();
free();
}
template<typename... Args>
T* allocate(Args&&... args)
{
lluz_ASSERT(!frozen);
if (currentBlockSize >= kBlockSize)
{
lluz_ASSERT(currentBlockSize == kBlockSize);
appendBlock();
}
T* block = stuff.back();
T* res = block + currentBlockSize;
new (res) T(std::forward<Args&&...>(args...));
++currentBlockSize;
return res;
}
bool contains(const T* ptr) const
{
for (T* block : stuff)
if (ptr >= block && ptr < block + kBlockSize)
return true;
return false;
}
bool empty() const
{
return stuff.empty();
}
size_t size() const
{
return stuff.empty() ? 0 : kBlockSize * (stuff.size() - 1) + currentBlockSize;
}
void clear()
{
if (frozen)
unfreeze();
free();
currentBlockSize = kBlockSize;
}
void freeze()
{
for (T* block : stuff)
pagedFreeze(block, kBlockSizeBytes);
frozen = true;
}
void unfreeze()
{
for (T* block : stuff)
pagedUnfreeze(block, kBlockSizeBytes);
frozen = false;
}
bool isFrozen()
{
return frozen;
}
private:
void free()
{
lluz_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 lluz

View file

@ -0,0 +1,147 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Variant.h"
#include <string>
namespace lluz
{
struct Scope2;
/**
* 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 level of "this" belongs to an equal or larger scope than that of rhs
bool subsumes(const TypeLevel& rhs) const
{
if (level < rhs.level)
return true;
if (level > rhs.level)
return false;
if (subLevel == rhs.subLevel)
return true; // if level == rhs.level and subLevel == rhs.subLevel, then they are the exact same TypeLevel
// Sibling TypeLevels (that is, TypeLevels that share a level but have a different subLevel) are not considered to subsume one another
return false;
}
// Returns true if the level of "this" belongs to a larger (not equal) scope than that of rhs
bool subsumesStrict(const TypeLevel& rhs) const
{
if (level == rhs.level && subLevel == rhs.subLevel)
return false;
else
return subsumes(rhs);
}
TypeLevel incr() const
{
TypeLevel result;
result.level = level + 1;
result.subLevel = 0;
return result;
}
};
inline TypeLevel max(const TypeLevel& a, const TypeLevel& b)
{
if (a.subsumes(b))
return b;
else
return a;
}
inline TypeLevel min(const TypeLevel& a, const TypeLevel& b)
{
if (a.subsumes(b))
return a;
else
return b;
}
} // namespace lluz
namespace lluz::Unifiable
{
using Name = std::string;
struct Free
{
explicit Free(TypeLevel level);
explicit Free(Scope2* scope);
int index;
TypeLevel level;
Scope2* scope = nullptr;
// 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);
explicit Generic(Scope2* scope);
Generic(TypeLevel level, const Name& name);
Generic(Scope2* scope, const Name& name);
int index;
TypeLevel level;
Scope2* scope = nullptr;
Name name;
bool explicitName = false;
private:
static int nextIndex;
};
struct Error
{
// This constructor has to be public, since it's used in TypeVar and TypePack,
// but shouldn't be called directly. Please use errorRecoveryType() instead.
Error();
int index;
private:
static int nextIndex;
};
template<typename Id, typename... Value>
using Variant = lluz::Variant<Free, Bound<Id>, Generic, Error, Value...>;
} // namespace lluz::Unifiable

View file

@ -0,0 +1,136 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Error.h"
#include "lluz/Location.h"
#include "lluz/TxnLog.h"
#include "lluz/TypeInfer.h"
#include "lluz/TypeArena.h"
#include "lluz/UnifierSharedState.h"
#include <unordered_set>
namespace lluz
{
enum Variance
{
Covariant,
Invariant
};
// A substitution which replaces singleton types by their wider types
struct Widen : Substitution
{
Widen(TypeArena* arena)
: Substitution(TxnLog::empty(), arena)
{
}
bool isDirty(TypeId ty) override;
bool isDirty(TypePackId ty) override;
TypeId clean(TypeId ty) override;
TypePackId clean(TypePackId ty) override;
bool ignoreChildren(TypeId ty) override;
TypeId operator()(TypeId ty);
TypePackId operator()(TypePackId ty);
};
// TODO: Use this more widely.
struct UnifierOptions
{
bool isFunctionCall = false;
};
struct Unifier
{
TypeArena* const types;
Mode mode;
TxnLog log;
ErrorVec errors;
Location location;
Variance variance = Covariant;
bool anyIsTop = false; // If true, we consider any to be a top type. If false, it is a familiar but weird mix of top and bottom all at once.
CountMismatch::Context ctx = CountMismatch::Arg;
UnifierSharedState& sharedState;
Unifier(TypeArena* types, Mode mode, const Location& location, Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog = nullptr);
// Test whether the two type vars unify. Never commits the result.
ErrorVec canUnify(TypeId subTy, TypeId superTy);
ErrorVec canUnify(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false);
/** Attempt to unify.
* Populate the vector errors with any type errors that may arise.
* Populate the transaction log with the set of TypeIds that need to be reset to undo the unification attempt.
*/
void tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false);
private:
void tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false);
void tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId superTy);
void tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTypeVar* uv, bool cacheEnabled, bool isFunctionCall);
void tryUnifyTypeWithIntersection(TypeId subTy, TypeId superTy, const IntersectionTypeVar* uv);
void tryUnifyIntersectionWithType(TypeId subTy, const IntersectionTypeVar* uv, TypeId superTy, bool cacheEnabled, bool isFunctionCall);
void tryUnifyPrimitives(TypeId subTy, TypeId superTy);
void tryUnifySingletons(TypeId subTy, TypeId superTy);
void tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCall = false);
void tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection = false);
void tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed);
void tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed);
TypeId widen(TypeId ty);
TypePackId widen(TypePackId tp);
TypeId deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> seen = {});
bool canCacheResult(TypeId subTy, TypeId superTy);
void cacheResult(TypeId subTy, TypeId superTy, size_t prevErrorCount);
public:
void tryUnify(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false);
private:
void tryUnify_(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false);
void tryUnifyVariadics(TypePackId subTy, TypePackId superTy, bool reversed, int subOffset = 0);
void tryUnifyWithAny(TypeId subTy, TypeId anyTy);
void tryUnifyWithAny(TypePackId subTy, TypePackId anyTp);
std::optional<TypeId> findTablePropertyRespectingMeta(TypeId lhsType, Name name);
void tryUnifyWithConstrainedSubTypeVar(TypeId subTy, TypeId superTy);
void tryUnifyWithConstrainedSuperTypeVar(TypeId subTy, TypeId superTy);
public:
void unifyLowerBound(TypePackId subTy, TypePackId superTy, TypeLevel demotedLevel);
// Report an "infinite type error" if the type "needle" already occurs within "haystack"
void occursCheck(TypeId needle, TypeId haystack);
void occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack);
void occursCheck(TypePackId needle, TypePackId haystack);
void occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack);
Unifier makeChildUnifier();
void reportError(TypeError err);
private:
bool isNonstrictMode() const;
void checkChildUnifierTypeMismatch(const ErrorVec& innerErrors, TypeId wantedType, TypeId givenType);
void checkChildUnifierTypeMismatch(const ErrorVec& innerErrors, const std::string& prop, TypeId wantedType, TypeId givenType);
[[noreturn]] void ice(const std::string& message, const Location& location);
[[noreturn]] void ice(const std::string& message);
// Available after regular type pack unification errors
std::optional<int> firstPackErrorPos;
};
void promoteTypeLevels(TxnLog& log, const TypeArena* arena, TypeLevel minLevel, TypePackId tp);
} // namespace lluz

View file

@ -0,0 +1,55 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/DenseHash.h"
#include "lluz/Error.h"
#include "lluz/TypeVar.h"
#include "lluz/TypePack.h"
#include <utility>
namespace lluz
{
struct InternalErrorReporter;
struct TypeIdPairHash
{
size_t hashOne(lluz::TypeId key) const
{
return (uintptr_t(key) >> 4) ^ (uintptr_t(key) >> 9);
}
size_t operator()(const std::pair<lluz::TypeId, lluz::TypeId>& x) const
{
return hashOne(x.first) ^ (hashOne(x.second) << 1);
}
};
struct UnifierCounters
{
int recursionCount = 0;
int recursionLimit = 0;
int iterationCount = 0;
int iterationLimit = 0;
};
struct UnifierSharedState
{
UnifierSharedState(InternalErrorReporter* iceHandler)
: iceHandler(iceHandler)
{
}
InternalErrorReporter* iceHandler;
DenseHashMap<TypeId, bool> skipCacheForType{nullptr};
DenseHashSet<std::pair<TypeId, TypeId>, TypeIdPairHash> cachedUnify{{nullptr, nullptr}};
DenseHashMap<std::pair<TypeId, TypeId>, TypeErrorData, TypeIdPairHash> cachedUnifyError{{nullptr, nullptr}};
DenseHashSet<TypeId> tempSeenTy{nullptr};
DenseHashSet<TypePackId> tempSeenTp{nullptr};
UnifierCounters counters;
};
} // namespace lluz

View file

@ -0,0 +1,289 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lluz/Common.h"
#include <new>
#include <type_traits>
#include <initializer_list>
#include <stddef.h>
#include <utility>
namespace lluz
{
template<typename... Ts>
class Variant
{
static_assert(sizeof...(Ts) > 0, XorStr("variant must have at least 1 type (empty variants are ill-formed)"));
static_assert(std::disjunction_v<std::is_void<Ts>...> == false, XorStr("variant does not allow void as an alternative type"));
static_assert(std::disjunction_v<std::is_reference<Ts>...> == false, XorStr("variant does not allow references as an alternative type"));
static_assert(std::disjunction_v<std::is_array<Ts>...> == false, XorStr("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>, XorStr("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, typename... Args>
T& emplace(Args&&... args)
{
using TT = std::decay_t<T>;
constexpr int tid = getTypeId<T>();
static_assert(tid >= 0, XorStr("unsupported T"));
tableDtor[typeId](&storage);
typeId = tid;
new (&storage) TT(std::forward<Args>(args)...);
return *reinterpret_cast<T*>(&storage);
}
template<typename T>
const T* get_if() const
{
constexpr int tid = getTypeId<T>();
static_assert(tid >= 0, XorStr("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, XorStr("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)
{
static_assert(std::conjunction_v<std::is_invocable<Visitor, Ts>...>, XorStr("visitor must accept every alternative as an argument"));
using Result = std::invoke_result_t<Visitor, typename Variant<Ts...>::first_alternative>;
static_assert(std::conjunction_v<std::is_same<Result, std::invoke_result_t<Visitor, Ts>>...>,
XorStr("visitor result type must be consistent between alternatives"));
if constexpr (std::is_same_v<Result, void>)
{
using FnVisitV = void (*)(Visitor&, const void*);
static const FnVisitV tableVisit[sizeof...(Ts)] = {&fnVisitV<Visitor, const Ts>...};
tableVisit[var.typeId](vis, &var.storage);
}
else
{
using FnVisitR = void (*)(Visitor&, Result&, const void*);
static const FnVisitR tableVisit[sizeof...(Ts)] = {&fnVisitR<Visitor, Result, const Ts>...};
Result res;
tableVisit[var.typeId](vis, res, &var.storage);
return res;
}
}
template<class Visitor, typename... Ts>
auto visit(Visitor&& vis, Variant<Ts...>& var)
{
static_assert(std::conjunction_v<std::is_invocable<Visitor, Ts&>...>, XorStr("visitor must accept every alternative as an argument"));
using Result = std::invoke_result_t<Visitor, typename Variant<Ts...>::first_alternative&>;
static_assert(std::conjunction_v<std::is_same<Result, std::invoke_result_t<Visitor, Ts&>>...>,
XorStr("visitor result type must be consistent between alternatives"));
if constexpr (std::is_same_v<Result, void>)
{
using FnVisitV = void (*)(Visitor&, void*);
static const FnVisitV tableVisit[sizeof...(Ts)] = {&fnVisitV<Visitor, Ts>...};
tableVisit[var.typeId](vis, &var.storage);
}
else
{
using FnVisitR = void (*)(Visitor&, Result&, void*);
static const FnVisitR tableVisit[sizeof...(Ts)] = {&fnVisitR<Visitor, Result, Ts>...};
Result res;
tableVisit[var.typeId](vis, res, &var.storage);
return res;
}
}
template<class>
inline constexpr bool always_false_v = false;
} // namespace lluz

View file

@ -0,0 +1,353 @@
// This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include <unordered_set>
#include "lluz/DenseHash.h"
#include "lluz/RecursionCounter.h"
#include "lluz/TypePack.h"
#include "lluz/TypeVar.h"
lluz_FASTINT(LluVisitRecursionLimit)
lluz_FASTFLAG(LluNormalizeFlagIsConservative)
namespace lluz
{
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 bool hasSeen(DenseHashSet<void*>& seen, const void* tv)
{
void* ttv = const_cast<void*>(tv);
if (seen.contains(ttv))
return true;
seen.insert(ttv);
return false;
}
inline void unsee(std::unordered_set<void*>& seen, const void* tv)
{
void* ttv = const_cast<void*>(tv);
seen.erase(ttv);
}
inline void unsee(DenseHashSet<void*>& seen, const void* tv)
{
// When DenseHashSet is used for 'visitTypeVarOnce', where don't forget visited elements
}
} // namespace visit_detail
template<typename S>
struct GenericTypeVarVisitor
{
using Set = S;
Set seen;
int recursionCounter = 0;
GenericTypeVarVisitor() = default;
explicit GenericTypeVarVisitor(Set seen)
: seen(std::move(seen))
{
}
virtual void cycle(TypeId) {}
virtual void cycle(TypePackId) {}
virtual bool visit(TypeId ty)
{
return true;
}
virtual bool visit(TypeId ty, const BoundTypeVar& btv)
{
return visit(ty);
}
virtual bool visit(TypeId ty, const FreeTypeVar& ftv)
{
return visit(ty);
}
virtual bool visit(TypeId ty, const GenericTypeVar& gtv)
{
return visit(ty);
}
virtual bool visit(TypeId ty, const ErrorTypeVar& etv)
{
return visit(ty);
}
virtual bool visit(TypeId ty, const ConstrainedTypeVar& ctv)
{
return visit(ty);
}
virtual bool visit(TypeId ty, const PrimitiveTypeVar& ptv)
{
return visit(ty);
}
virtual bool visit(TypeId ty, const FunctionTypeVar& ftv)
{
return visit(ty);
}
virtual bool visit(TypeId ty, const TableTypeVar& ttv)
{
return visit(ty);
}
virtual bool visit(TypeId ty, const MetatableTypeVar& mtv)
{
return visit(ty);
}
virtual bool visit(TypeId ty, const ClassTypeVar& ctv)
{
return visit(ty);
}
virtual bool visit(TypeId ty, const AnyTypeVar& atv)
{
return visit(ty);
}
virtual bool visit(TypeId ty, const UnionTypeVar& utv)
{
return visit(ty);
}
virtual bool visit(TypeId ty, const IntersectionTypeVar& itv)
{
return visit(ty);
}
virtual bool visit(TypePackId tp)
{
return true;
}
virtual bool visit(TypePackId tp, const BoundTypePack& btp)
{
return visit(tp);
}
virtual bool visit(TypePackId tp, const FreeTypePack& ftp)
{
return visit(tp);
}
virtual bool visit(TypePackId tp, const GenericTypePack& gtp)
{
return visit(tp);
}
virtual bool visit(TypePackId tp, const Unifiable::Error& etp)
{
return visit(tp);
}
virtual bool visit(TypePackId tp, const TypePack& pack)
{
return visit(tp);
}
virtual bool visit(TypePackId tp, const VariadicTypePack& vtp)
{
return visit(tp);
}
void traverse(TypeId ty)
{
RecursionLimiter limiter{&recursionCounter, FInt::LluVisitRecursionLimit};
if (visit_detail::hasSeen(seen, ty))
{
cycle(ty);
return;
}
if (auto btv = get<BoundTypeVar>(ty))
{
if (visit(ty, *btv))
traverse(btv->boundTo);
}
else if (auto ftv = get<FreeTypeVar>(ty))
visit(ty, *ftv);
else if (auto gtv = get<GenericTypeVar>(ty))
visit(ty, *gtv);
else if (auto etv = get<ErrorTypeVar>(ty))
visit(ty, *etv);
else if (auto ctv = get<ConstrainedTypeVar>(ty))
{
if (visit(ty, *ctv))
{
for (TypeId part : ctv->parts)
traverse(part);
}
}
else if (auto ptv = get<PrimitiveTypeVar>(ty))
visit(ty, *ptv);
else if (auto ftv = get<FunctionTypeVar>(ty))
{
if (visit(ty, *ftv))
{
traverse(ftv->argTypes);
traverse(ftv->retTypes);
}
}
else if (auto ttv = get<TableTypeVar>(ty))
{
// Some visitors want to see bound tables, that's why we traverse the original type
if (visit(ty, *ttv))
{
if (ttv->boundTo)
{
traverse(*ttv->boundTo);
}
else
{
for (auto& [_name, prop] : ttv->props)
traverse(prop.type);
if (ttv->indexer)
{
traverse(ttv->indexer->indexType);
traverse(ttv->indexer->indexResultType);
}
}
}
}
else if (auto mtv = get<MetatableTypeVar>(ty))
{
if (visit(ty, *mtv))
{
traverse(mtv->table);
traverse(mtv->metatable);
}
}
else if (auto ctv = get<ClassTypeVar>(ty))
{
if (visit(ty, *ctv))
{
for (const auto& [name, prop] : ctv->props)
traverse(prop.type);
if (ctv->parent)
traverse(*ctv->parent);
if (ctv->metatable)
traverse(*ctv->metatable);
}
}
else if (auto atv = get<AnyTypeVar>(ty))
visit(ty, *atv);
else if (auto utv = get<UnionTypeVar>(ty))
{
if (visit(ty, *utv))
{
for (TypeId optTy : utv->options)
traverse(optTy);
}
}
else if (auto itv = get<IntersectionTypeVar>(ty))
{
if (visit(ty, *itv))
{
for (TypeId partTy : itv->parts)
traverse(partTy);
}
}
visit_detail::unsee(seen, ty);
}
void traverse(TypePackId tp)
{
if (visit_detail::hasSeen(seen, tp))
{
cycle(tp);
return;
}
if (auto btv = get<BoundTypePack>(tp))
{
if (visit(tp, *btv))
traverse(btv->boundTo);
}
else if (auto ftv = get<Unifiable::Free>(tp))
visit(tp, *ftv);
else if (auto gtv = get<Unifiable::Generic>(tp))
visit(tp, *gtv);
else if (auto etv = get<Unifiable::Error>(tp))
visit(tp, *etv);
else if (auto pack = get<TypePack>(tp))
{
bool res = visit(tp, *pack);
if (!FFlag::LluNormalizeFlagIsConservative || res)
{
for (TypeId ty : pack->head)
traverse(ty);
if (pack->tail)
traverse(*pack->tail);
}
}
else if (auto pack = get<VariadicTypePack>(tp))
{
bool res = visit(tp, *pack);
if (!FFlag::LluNormalizeFlagIsConservative || res)
traverse(pack->ty);
}
else
lluz_ASSERT(!XorStr("GenericTypeVarVisitor::traverse(TypePackId) is not exhaustive!"));
visit_detail::unsee(seen, tp);
}
};
/** Visit each type under a given type. Skips over cycles and keeps recursion depth under control.
*
* The same type may be visited multiple times if there are multiple distinct paths to it. If this is undesirable, use
* TypeVarOnceVisitor.
*/
struct TypeVarVisitor : GenericTypeVarVisitor<std::unordered_set<void*>>
{
};
/// Visit each type under a given type. Each type will only be checked once even if there are multiple paths to it.
struct TypeVarOnceVisitor : GenericTypeVarVisitor<DenseHashSet<void*>>
{
TypeVarOnceVisitor()
: GenericTypeVarVisitor{DenseHashSet<void*>{nullptr}}
{
}
};
} // namespace lluz

View file

@ -1,120 +1,22 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/AstQuery.h" #include "lluz/AstQuery.h"
#include "Luau/Module.h" #include "lluz/Module.h"
#include "Luau/Scope.h" #include "lluz/Scope.h"
#include "Luau/TypeInfer.h" #include "lluz/TypeInfer.h"
#include "Luau/TypeVar.h" #include "lluz/TypeVar.h"
#include "Luau/ToString.h" #include "lluz/ToString.h"
#include "Luau/Common.h" #include "lluz/Common.h"
#include <algorithm> #include <algorithm>
namespace Luau namespace lluz
{ {
namespace namespace
{ {
struct AutocompleteNodeFinder : public AstVisitor
{
const Position pos;
std::vector<AstNode*> ancestry;
explicit AutocompleteNodeFinder(Position pos, AstNode* root)
: pos(pos)
{
}
bool visit(AstExpr* expr) override
{
if (expr->location.begin < pos && pos <= expr->location.end)
{
ancestry.push_back(expr);
return true;
}
return false;
}
bool visit(AstStat* stat) override
{
if (stat->location.begin < pos && pos <= stat->location.end)
{
ancestry.push_back(stat);
return true;
}
return false;
}
bool visit(AstType* type) override
{
if (type->location.begin < pos && pos <= type->location.end)
{
ancestry.push_back(type);
return true;
}
return false;
}
bool visit(AstTypeError* type) override
{
// For a missing type, match the whole range including the start position
if (type->isMissing && type->location.containsClosed(pos))
{
ancestry.push_back(type);
return true;
}
return false;
}
bool visit(class AstTypePack* typePack) override
{
return true;
}
bool visit(AstStatBlock* block) override
{
// If ancestry is empty, we are inspecting the root of the AST. Its extent is considered to be infinite.
if (ancestry.empty())
{
ancestry.push_back(block);
return true;
}
// AstExprIndexName nodes are nested outside-in, so we want the outermost node in the case of nested nodes.
// ex foo.bar.baz is represented in the AST as IndexName{ IndexName {foo, bar}, baz}
if (!ancestry.empty() && ancestry.back()->is<AstExprIndexName>())
return false;
// Type annotation error might intersect the block statement when the function header is being written,
// annotation takes priority
if (!ancestry.empty() && ancestry.back()->is<AstTypeError>())
return false;
// If the cursor is at the end of an expression or type and simultaneously at the beginning of a block,
// the expression or type wins out.
// The exception to this is if we are in a block under an AstExprFunction. In this case, we consider the position to
// be within the block.
if (block->location.begin == pos && !ancestry.empty())
{
if (ancestry.back()->asExpr() && !ancestry.back()->is<AstExprFunction>())
return false;
if (ancestry.back()->asType())
return false;
}
if (block->location.begin <= pos && pos <= block->location.end)
{
ancestry.push_back(block);
return true;
}
return false;
}
};
struct FindNode : public AstVisitor struct FindNode : public AstVisitor
{ {
const Position pos; const Position pos;
@ -200,13 +102,6 @@ struct FindFullAncestry final : public AstVisitor
} // namespace } // namespace
std::vector<AstNode*> findAncestryAtPositionForAutocomplete(const SourceModule& source, Position pos)
{
AutocompleteNodeFinder finder{pos, source.root};
source.root->visit(&finder);
return finder.ancestry;
}
std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos) std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos)
{ {
const Position end = source.root->location.end; const Position end = source.root->location.end;
@ -215,7 +110,7 @@ std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Posi
FindFullAncestry finder(pos, end); FindFullAncestry finder(pos, end);
source.root->visit(&finder); source.root->visit(&finder);
return finder.nodes; return std::move(finder.nodes);
} }
AstNode* findNodeAtPosition(const SourceModule& source, Position pos) AstNode* findNodeAtPosition(const SourceModule& source, Position pos)
@ -243,7 +138,7 @@ AstExpr* findExprAtPosition(const SourceModule& source, Position pos)
ScopePtr findScopeAtPosition(const Module& module, Position pos) ScopePtr findScopeAtPosition(const Module& module, Position pos)
{ {
LUAU_ASSERT(!module.scopes.empty()); lluz_ASSERT(!module.scopes.empty());
Location scopeLocation = module.scopes.front().first; Location scopeLocation = module.scopes.front().first;
ScopePtr scope = module.scopes.front().second; ScopePtr scope = module.scopes.front().second;
@ -307,7 +202,7 @@ std::optional<Binding> findBindingAtPosition(const Module& module, const SourceM
return std::nullopt; return std::nullopt;
ScopePtr currentScope = findScopeAtPosition(module, pos); ScopePtr currentScope = findScopeAtPosition(module, pos);
LUAU_ASSERT(currentScope); lluz_ASSERT(currentScope);
while (currentScope) while (currentScope)
{ {
@ -530,4 +425,4 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
return std::nullopt; return std::nullopt;
} }
} // namespace Luau } // namespace lluz

View file

@ -1,25 +1,124 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Autocomplete.h" #include "lluz/Autocomplete.h"
#include "Luau/AstQuery.h" #include "lluz/AstQuery.h"
#include "Luau/BuiltinDefinitions.h" #include "lluz/BuiltinDefinitions.h"
#include "Luau/Frontend.h" #include "lluz/Frontend.h"
#include "Luau/ToString.h" #include "lluz/ToString.h"
#include "Luau/TypeInfer.h" #include "lluz/TypeInfer.h"
#include "Luau/TypePack.h" #include "lluz/TypePack.h"
#include "lluz/Parser.h" // TODO: only needed for autocompleteSource which is deprecated
#include "..\..\..\..\Security\XorString.h"
#include <algorithm> #include <algorithm>
#include <unordered_set> #include <unordered_set>
#include <utility> #include <utility>
LUAU_FASTFLAG(LuauSelfCallAutocompleteFix3) lluz_FASTFLAG(LluSelfCallAutocompleteFix2)
static const std::unordered_set<std::string> kStatementStartingKeywords = { static const std::unordered_set<std::string> kStatementStartingKeywords = {
"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; XorStr("while"), XorStr("if"), XorStr("local"), XorStr("repeat"), XorStr("function"), XorStr("do"), XorStr("for"), XorStr("return"), XorStr("break"), XorStr("continue"), XorStr("type"), XorStr("export")};
namespace Luau namespace lluz
{ {
struct NodeFinder : public AstVisitor
{
const Position pos;
std::vector<AstNode*> ancestry;
explicit NodeFinder(Position pos, AstNode* root)
: pos(pos)
{
}
bool visit(AstExpr* expr) override
{
if (expr->location.begin < pos && pos <= expr->location.end)
{
ancestry.push_back(expr);
return true;
}
return false;
}
bool visit(AstStat* stat) override
{
if (stat->location.begin < pos && pos <= stat->location.end)
{
ancestry.push_back(stat);
return true;
}
return false;
}
bool visit(AstType* type) override
{
if (type->location.begin < pos && pos <= type->location.end)
{
ancestry.push_back(type);
return true;
}
return false;
}
bool visit(AstTypeError* type) override
{
// For a missing type, match the whole range including the start position
if (type->isMissing && type->location.containsClosed(pos))
{
ancestry.push_back(type);
return true;
}
return false;
}
bool visit(class AstTypePack* typePack) override
{
return true;
}
bool visit(AstStatBlock* block) override
{
// If ancestry is empty, we are inspecting the root of the AST. Its extent is considered to be infinite.
if (ancestry.empty())
{
ancestry.push_back(block);
return true;
}
// AstExprIndexName nodes are nested outside-in, so we want the outermost node in the case of nested nodes.
// ex foo.bar.baz is represented in the AST as IndexName{ IndexName {foo, bar}, baz}
if (!ancestry.empty() && ancestry.back()->is<AstExprIndexName>())
return false;
// Type annotation error might intersect the block statement when the function header is being written,
// annotation takes priority
if (!ancestry.empty() && ancestry.back()->is<AstTypeError>())
return false;
// If the cursor is at the end of an expression or type and simultaneously at the beginning of a block,
// the expression or type wins out.
// The exception to this is if we are in a block under an AstExprFunction. In this case, we consider the position to
// be within the block.
if (block->location.begin == pos && !ancestry.empty())
{
if (ancestry.back()->asExpr() && !ancestry.back()->is<AstExprFunction>())
return false;
if (ancestry.back()->asType())
return false;
}
if (block->location.begin <= pos && pos <= block->location.end)
{
ancestry.push_back(block);
return true;
}
return false;
}
};
static bool alreadyHasParens(const std::vector<AstNode*>& nodes) static bool alreadyHasParens(const std::vector<AstNode*>& nodes)
{ {
@ -52,7 +151,7 @@ static ParenthesesRecommendation getParenRecommendationForFunc(const FunctionTyp
auto idxExpr = nodes.back()->as<AstExprIndexName>(); auto idxExpr = nodes.back()->as<AstExprIndexName>();
bool hasImplicitSelf = idxExpr && idxExpr->op == ':'; bool hasImplicitSelf = idxExpr && idxExpr->op == ':';
auto [argTypes, argVariadicPack] = Luau::flatten(func->argTypes); auto [argTypes, argVariadicPack] = lluz::flatten(func->argTypes);
if (argVariadicPack.has_value() && isVariadic(*argVariadicPack)) if (argVariadicPack.has_value() && isVariadic(*argVariadicPack))
return ParenthesesRecommendation::CursorInside; return ParenthesesRecommendation::CursorInside;
@ -64,9 +163,9 @@ static ParenthesesRecommendation getParenRecommendationForFunc(const FunctionTyp
static ParenthesesRecommendation getParenRecommendationForIntersect(const IntersectionTypeVar* intersect, const std::vector<AstNode*>& nodes) static ParenthesesRecommendation getParenRecommendationForIntersect(const IntersectionTypeVar* intersect, const std::vector<AstNode*>& nodes)
{ {
ParenthesesRecommendation rec = ParenthesesRecommendation::None; ParenthesesRecommendation rec = ParenthesesRecommendation::None;
for (Luau::TypeId partId : intersect->parts) for (lluz::TypeId partId : intersect->parts)
{ {
if (auto partFunc = Luau::get<FunctionTypeVar>(partId)) if (auto partFunc = lluz::get<FunctionTypeVar>(partId))
{ {
rec = std::max(rec, getParenRecommendationForFunc(partFunc, nodes)); rec = std::max(rec, getParenRecommendationForFunc(partFunc, nodes));
} }
@ -84,7 +183,7 @@ static ParenthesesRecommendation getParenRecommendation(TypeId id, const std::ve
if (typeCorrect == TypeCorrectKind::Correct) if (typeCorrect == TypeCorrectKind::Correct)
return ParenthesesRecommendation::None; return ParenthesesRecommendation::None;
id = Luau::follow(id); id = lluz::follow(id);
if (auto func = get<FunctionTypeVar>(id)) if (auto func = get<FunctionTypeVar>(id))
{ {
return getParenRecommendationForFunc(func, nodes); return getParenRecommendationForFunc(func, nodes);
@ -149,7 +248,7 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
ty = follow(ty); ty = follow(ty);
auto canUnify = [&typeArena](TypeId subTy, TypeId superTy) { auto canUnify = [&typeArena](TypeId subTy, TypeId superTy) {
LUAU_ASSERT(!FFlag::LuauSelfCallAutocompleteFix3); lluz_ASSERT(!FFlag::LluSelfCallAutocompleteFix2);
InternalErrorReporter iceReporter; InternalErrorReporter iceReporter;
UnifierSharedState unifierState(&iceReporter); UnifierSharedState unifierState(&iceReporter);
@ -168,7 +267,7 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
TypeId expectedType = follow(*typeAtPosition); TypeId expectedType = follow(*typeAtPosition);
auto checkFunctionType = [typeArena, &canUnify, &expectedType](const FunctionTypeVar* ftv) { auto checkFunctionType = [typeArena, &canUnify, &expectedType](const FunctionTypeVar* ftv) {
if (FFlag::LuauSelfCallAutocompleteFix3) if (FFlag::LluSelfCallAutocompleteFix2)
{ {
if (std::optional<TypeId> firstRetTy = first(ftv->retTypes)) if (std::optional<TypeId> firstRetTy = first(ftv->retTypes))
return checkTypeMatch(typeArena, *firstRetTy, expectedType); return checkTypeMatch(typeArena, *firstRetTy, expectedType);
@ -209,7 +308,7 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
} }
} }
if (FFlag::LuauSelfCallAutocompleteFix3) if (FFlag::LluSelfCallAutocompleteFix2)
return checkTypeMatch(typeArena, ty, expectedType) ? TypeCorrectKind::Correct : TypeCorrectKind::None; return checkTypeMatch(typeArena, ty, expectedType) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
else else
return canUnify(ty, expectedType) ? TypeCorrectKind::Correct : TypeCorrectKind::None; return canUnify(ty, expectedType) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
@ -226,7 +325,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
const std::vector<AstNode*>& nodes, AutocompleteEntryMap& result, std::unordered_set<TypeId>& seen, const std::vector<AstNode*>& nodes, AutocompleteEntryMap& result, std::unordered_set<TypeId>& seen,
std::optional<const ClassTypeVar*> containingClass = std::nullopt) std::optional<const ClassTypeVar*> containingClass = std::nullopt)
{ {
if (FFlag::LuauSelfCallAutocompleteFix3) if (FFlag::LluSelfCallAutocompleteFix2)
rootTy = follow(rootTy); rootTy = follow(rootTy);
ty = follow(ty); ty = follow(ty);
@ -235,8 +334,8 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
return; return;
seen.insert(ty); seen.insert(ty);
auto isWrongIndexer_DEPRECATED = [indexType, useStrictFunctionIndexers = !!get<ClassTypeVar>(ty)](Luau::TypeId type) { auto isWrongIndexer_DEPRECATED = [indexType, useStrictFunctionIndexers = !!get<ClassTypeVar>(ty)](lluz::TypeId type) {
LUAU_ASSERT(!FFlag::LuauSelfCallAutocompleteFix3); lluz_ASSERT(!FFlag::LluSelfCallAutocompleteFix2);
if (indexType == PropIndexType::Key) if (indexType == PropIndexType::Key)
return false; return false;
@ -252,7 +351,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
bool allHaveSelf = true; bool allHaveSelf = true;
for (auto subType : itv->parts) for (auto subType : itv->parts)
{ {
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(Luau::follow(subType))) if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(lluz::follow(subType)))
{ {
allHaveSelf &= ftv->hasSelf; allHaveSelf &= ftv->hasSelf;
} }
@ -268,8 +367,8 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
return colonIndex; return colonIndex;
} }
}; };
auto isWrongIndexer = [typeArena, rootTy, indexType](Luau::TypeId type) { auto isWrongIndexer = [typeArena, rootTy, indexType](lluz::TypeId type) {
LUAU_ASSERT(FFlag::LuauSelfCallAutocompleteFix3); lluz_ASSERT(FFlag::LluSelfCallAutocompleteFix2);
if (indexType == PropIndexType::Key) if (indexType == PropIndexType::Key)
return false; return false;
@ -277,20 +376,21 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
bool calledWithSelf = indexType == PropIndexType::Colon; bool calledWithSelf = indexType == PropIndexType::Colon;
auto isCompatibleCall = [typeArena, rootTy, calledWithSelf](const FunctionTypeVar* ftv) { auto isCompatibleCall = [typeArena, rootTy, calledWithSelf](const FunctionTypeVar* ftv) {
// Strong match with definition is a success
if (calledWithSelf == ftv->hasSelf)
return true;
// Calls on classes require strict match between how function is declared and how it's called
if (get<ClassTypeVar>(rootTy)) if (get<ClassTypeVar>(rootTy))
return false;
// When called with ':', but declared without 'self', it is invalid if a function has incompatible first argument or no arguments at all
// When called with '.', but declared with 'self', it is considered invalid if first argument is compatible
if (std::optional<TypeId> firstArgTy = first(ftv->argTypes))
{ {
if (checkTypeMatch(typeArena, rootTy, *firstArgTy)) // Calls on classes require strict match between how function is declared and how it's called
return calledWithSelf; return calledWithSelf == ftv->hasSelf;
}
// If a call is made with ':', it is invalid if a function has incompatible first argument or no arguments at all
// If a call is made with '.', but it was declared with 'self', it is considered invalid if first argument is compatible
if (calledWithSelf || ftv->hasSelf)
{
if (std::optional<TypeId> firstArgTy = first(ftv->argTypes))
{
if (checkTypeMatch(typeArena, rootTy, *firstArgTy))
return calledWithSelf;
}
} }
return !calledWithSelf; return !calledWithSelf;
@ -304,7 +404,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
{ {
for (auto subType : itv->parts) for (auto subType : itv->parts)
{ {
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(Luau::follow(subType))) if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(lluz::follow(subType)))
{ {
if (isCompatibleCall(ftv)) if (isCompatibleCall(ftv))
return false; return false;
@ -322,7 +422,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
// already populated, it takes precedence over the property we found just now. // already populated, it takes precedence over the property we found just now.
if (result.count(name) == 0 && name != kParseNameError) if (result.count(name) == 0 && name != kParseNameError)
{ {
Luau::TypeId type = Luau::follow(prop.type); lluz::TypeId type = lluz::follow(prop.type);
TypeCorrectKind typeCorrect = indexType == PropIndexType::Key ? TypeCorrectKind::Correct TypeCorrectKind typeCorrect = indexType == PropIndexType::Key ? TypeCorrectKind::Correct
: checkTypeCorrectKind(module, typeArena, nodes.back(), {{}, {}}, type); : checkTypeCorrectKind(module, typeArena, nodes.back(), {{}, {}}, type);
ParenthesesRecommendation parens = ParenthesesRecommendation parens =
@ -332,7 +432,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
AutocompleteEntryKind::Property, AutocompleteEntryKind::Property,
type, type,
prop.deprecated, prop.deprecated,
FFlag::LuauSelfCallAutocompleteFix3 ? isWrongIndexer(type) : isWrongIndexer_DEPRECATED(type), FFlag::LluSelfCallAutocompleteFix2 ? isWrongIndexer(type) : isWrongIndexer_DEPRECATED(type),
typeCorrect, typeCorrect,
containingClass, containingClass,
&prop, &prop,
@ -345,7 +445,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
}; };
auto fillMetatableProps = [&](const TableTypeVar* mtable) { auto fillMetatableProps = [&](const TableTypeVar* mtable) {
auto indexIt = mtable->props.find("__index"); auto indexIt = mtable->props.find(XorStr("__index"));
if (indexIt != mtable->props.end()) if (indexIt != mtable->props.end())
{ {
TypeId followed = follow(indexIt->second.type); TypeId followed = follow(indexIt->second.type);
@ -375,7 +475,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
{ {
autocompleteProps(module, typeArena, rootTy, mt->table, indexType, nodes, result, seen); autocompleteProps(module, typeArena, rootTy, mt->table, indexType, nodes, result, seen);
if (FFlag::LuauSelfCallAutocompleteFix3) if (FFlag::LluSelfCallAutocompleteFix2)
{ {
if (auto mtable = get<TableTypeVar>(mt->metatable)) if (auto mtable = get<TableTypeVar>(mt->metatable))
fillMetatableProps(mtable); fillMetatableProps(mtable);
@ -386,7 +486,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
if (!mtable) if (!mtable)
return; return;
auto indexIt = mtable->props.find("__index"); auto indexIt = mtable->props.find(XorStr("__index"));
if (indexIt != mtable->props.end()) if (indexIt != mtable->props.end())
{ {
TypeId followed = follow(indexIt->second.type); TypeId followed = follow(indexIt->second.type);
@ -441,7 +541,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
AutocompleteEntryMap inner; AutocompleteEntryMap inner;
std::unordered_set<TypeId> innerSeen; std::unordered_set<TypeId> innerSeen;
if (!FFlag::LuauSelfCallAutocompleteFix3) if (!FFlag::LluSelfCallAutocompleteFix2)
innerSeen = seen; innerSeen = seen;
if (isNil(*iter)) if (isNil(*iter))
@ -467,7 +567,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
++iter; ++iter;
} }
} }
else if (auto pt = get<PrimitiveTypeVar>(ty); pt && FFlag::LuauSelfCallAutocompleteFix3) else if (auto pt = get<PrimitiveTypeVar>(ty); pt && FFlag::LluSelfCallAutocompleteFix2)
{ {
if (pt->metatable) if (pt->metatable)
{ {
@ -475,7 +575,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
fillMetatableProps(mtable); fillMetatableProps(mtable);
} }
} }
else if (FFlag::LuauSelfCallAutocompleteFix3 && get<StringSingleton>(get<SingletonTypeVar>(ty))) else if (FFlag::LluSelfCallAutocompleteFix2 && get<StringSingleton>(get<SingletonTypeVar>(ty)))
{ {
autocompleteProps(module, typeArena, rootTy, getSingletonTypes().stringType, indexType, nodes, result, seen); autocompleteProps(module, typeArena, rootTy, getSingletonTypes().stringType, indexType, nodes, result, seen);
} }
@ -484,7 +584,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
static void autocompleteKeywords( static void autocompleteKeywords(
const SourceModule& sourceModule, const std::vector<AstNode*>& ancestry, Position position, AutocompleteEntryMap& result) const SourceModule& sourceModule, const std::vector<AstNode*>& ancestry, Position position, AutocompleteEntryMap& result)
{ {
LUAU_ASSERT(!ancestry.empty()); lluz_ASSERT(!ancestry.empty());
AstNode* node = ancestry.back(); AstNode* node = ancestry.back();
@ -496,9 +596,9 @@ static void autocompleteKeywords(
// complex, however; this is good enough for now. // complex, however; this is good enough for now.
// These are not context-sensitive keywords, so we can unconditionally assign. // These are not context-sensitive keywords, so we can unconditionally assign.
result["and"] = {AutocompleteEntryKind::Keyword}; result[XorStr("and")] = {AutocompleteEntryKind::Keyword};
result["or"] = {AutocompleteEntryKind::Keyword}; result[XorStr("or")] = {AutocompleteEntryKind::Keyword};
result["not"] = {AutocompleteEntryKind::Keyword}; result[XorStr("not")] = {AutocompleteEntryKind::Keyword};
} }
} }
@ -539,7 +639,7 @@ static void autocompleteStringSingleton(TypeId ty, bool addQuotes, AutocompleteE
{ {
auto formatKey = [addQuotes](const std::string& key) { auto formatKey = [addQuotes](const std::string& key) {
if (addQuotes) if (addQuotes)
return "\"" + escape(key) + "\""; return XorStr("\")" + escape(key) + "\"";
return escape(key); return escape(key);
}; };
@ -764,7 +864,7 @@ static std::optional<bool> functionIsExpectedAt(const Module& module, AstNode* n
if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(expectedType)) if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(expectedType))
{ {
return std::all_of(begin(itv->parts), end(itv->parts), [](auto&& ty) { return std::all_of(begin(itv->parts), end(itv->parts), [](auto&& ty) {
return get<FunctionTypeVar>(Luau::follow(ty)) != nullptr; return get<FunctionTypeVar>(lluz::follow(ty)) != nullptr;
}); });
} }
@ -807,7 +907,7 @@ AutocompleteEntryMap autocompleteTypeNames(const Module& module, Position positi
} }
AstNode* parent = nullptr; AstNode* parent = nullptr;
AstType* topType = nullptr; // TODO: rename? AstType* topType = nullptr;
for (auto it = ancestry.rbegin(), e = ancestry.rend(); it != e; ++it) for (auto it = ancestry.rbegin(), e = ancestry.rend(); it != e; ++it)
{ {
@ -1115,15 +1215,15 @@ static AutocompleteEntryMap autocompleteStatement(
for (auto it = ancestry.rbegin(); it != ancestry.rend(); ++it) for (auto it = ancestry.rbegin(); it != ancestry.rend(); ++it)
{ {
if (AstStatForIn* statForIn = (*it)->as<AstStatForIn>(); statForIn && !statForIn->hasEnd) if (AstStatForIn* statForIn = (*it)->as<AstStatForIn>(); statForIn && !statForIn->hasEnd)
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); result.emplace(XorStr("end"), AutocompleteEntry{AutocompleteEntryKind::Keyword});
if (AstStatFor* statFor = (*it)->as<AstStatFor>(); statFor && !statFor->hasEnd) if (AstStatFor* statFor = (*it)->as<AstStatFor>(); statFor && !statFor->hasEnd)
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); result.emplace(XorStr("end"), AutocompleteEntry{AutocompleteEntryKind::Keyword});
if (AstStatIf* statIf = (*it)->as<AstStatIf>(); statIf && !statIf->hasEnd) if (AstStatIf* statIf = (*it)->as<AstStatIf>(); statIf && !statIf->hasEnd)
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); result.emplace(XorStr("end"), AutocompleteEntry{AutocompleteEntryKind::Keyword});
if (AstStatWhile* statWhile = (*it)->as<AstStatWhile>(); statWhile && !statWhile->hasEnd) if (AstStatWhile* statWhile = (*it)->as<AstStatWhile>(); statWhile && !statWhile->hasEnd)
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); result.emplace(XorStr("end"), AutocompleteEntry{AutocompleteEntryKind::Keyword});
if (AstExprFunction* exprFunction = (*it)->as<AstExprFunction>(); exprFunction && !exprFunction->hasEnd) if (AstExprFunction* exprFunction = (*it)->as<AstExprFunction>(); exprFunction && !exprFunction->hasEnd)
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword}); result.emplace(XorStr("end"), AutocompleteEntry{AutocompleteEntryKind::Keyword});
} }
if (ancestry.size() >= 2) if (ancestry.size() >= 2)
@ -1133,13 +1233,13 @@ static AutocompleteEntryMap autocompleteStatement(
{ {
if (!statIf->elsebody || (statIf->elseLocation && statIf->elseLocation->containsClosed(position))) if (!statIf->elsebody || (statIf->elseLocation && statIf->elseLocation->containsClosed(position)))
{ {
result.emplace("else", AutocompleteEntry{AutocompleteEntryKind::Keyword}); result.emplace(XorStr("else"), AutocompleteEntry{AutocompleteEntryKind::Keyword});
result.emplace("elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}); result.emplace(XorStr("elseif"), AutocompleteEntry{AutocompleteEntryKind::Keyword});
} }
} }
if (AstStatRepeat* statRepeat = parent->as<AstStatRepeat>(); statRepeat && !statRepeat->hasUntil) if (AstStatRepeat* statRepeat = parent->as<AstStatRepeat>(); statRepeat && !statRepeat->hasUntil)
result.emplace("until", AutocompleteEntry{AutocompleteEntryKind::Keyword}); result.emplace(XorStr("until"), AutocompleteEntry{AutocompleteEntryKind::Keyword});
} }
if (ancestry.size() >= 4) if (ancestry.size() >= 4)
@ -1148,13 +1248,13 @@ static AutocompleteEntryMap autocompleteStatement(
if (AstStatIf* statIf = iter[3]->as<AstStatIf>(); if (AstStatIf* statIf = iter[3]->as<AstStatIf>();
statIf != nullptr && !statIf->elsebody && iter[2]->is<AstStatBlock>() && iter[1]->is<AstStatError>() && isIdentifier(iter[0])) statIf != nullptr && !statIf->elsebody && iter[2]->is<AstStatBlock>() && iter[1]->is<AstStatError>() && isIdentifier(iter[0]))
{ {
result.emplace("else", AutocompleteEntry{AutocompleteEntryKind::Keyword}); result.emplace(XorStr("else"), AutocompleteEntry{AutocompleteEntryKind::Keyword});
result.emplace("elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}); result.emplace(XorStr("elseif"), AutocompleteEntry{AutocompleteEntryKind::Keyword});
} }
} }
if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat && !statRepeat->hasUntil) if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat && !statRepeat->hasUntil)
result.emplace("until", AutocompleteEntry{AutocompleteEntryKind::Keyword}); result.emplace(XorStr("until"), AutocompleteEntry{AutocompleteEntryKind::Keyword});
return result; return result;
} }
@ -1181,7 +1281,7 @@ static bool autocompleteIfElseExpression(
} }
else if (!ifElseExpr->hasThen) else if (!ifElseExpr->hasThen)
{ {
outResult["then"] = {AutocompleteEntryKind::Keyword}; outResult[XorStr("then")] = {AutocompleteEntryKind::Keyword};
return true; return true;
} }
else if (ifElseExpr->trueExpr->location.containsClosed(position)) else if (ifElseExpr->trueExpr->location.containsClosed(position))
@ -1190,8 +1290,8 @@ static bool autocompleteIfElseExpression(
} }
else if (!ifElseExpr->hasElse) else if (!ifElseExpr->hasElse)
{ {
outResult["else"] = {AutocompleteEntryKind::Keyword}; outResult[XorStr("else")] = {AutocompleteEntryKind::Keyword};
outResult["elseif"] = {AutocompleteEntryKind::Keyword}; outResult[XorStr("elseif")] = {AutocompleteEntryKind::Keyword};
return true; return true;
} }
else else
@ -1203,7 +1303,7 @@ static bool autocompleteIfElseExpression(
static void autocompleteExpression(const SourceModule& sourceModule, const Module& module, const TypeChecker& typeChecker, TypeArena* typeArena, static void autocompleteExpression(const SourceModule& sourceModule, const Module& module, const TypeChecker& typeChecker, TypeArena* typeArena,
const std::vector<AstNode*>& ancestry, Position position, AutocompleteEntryMap& result) const std::vector<AstNode*>& ancestry, Position position, AutocompleteEntryMap& result)
{ {
LUAU_ASSERT(!ancestry.empty()); lluz_ASSERT(!ancestry.empty());
AstNode* node = ancestry.rbegin()[0]; AstNode* node = ancestry.rbegin()[0];
@ -1250,12 +1350,12 @@ static void autocompleteExpression(const SourceModule& sourceModule, const Modul
TypeCorrectKind correctForFunction = TypeCorrectKind correctForFunction =
functionIsExpectedAt(module, node, position).value_or(false) ? TypeCorrectKind::Correct : TypeCorrectKind::None; functionIsExpectedAt(module, node, position).value_or(false) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
result["if"] = {AutocompleteEntryKind::Keyword, std::nullopt, false, false}; result[XorStr("if")] = {AutocompleteEntryKind::Keyword, std::nullopt, false, false};
result["true"] = {AutocompleteEntryKind::Keyword, typeChecker.booleanType, false, false, correctForTrue}; result[XorStr("true")] = {AutocompleteEntryKind::Keyword, typeChecker.booleanType, false, false, correctForTrue};
result["false"] = {AutocompleteEntryKind::Keyword, typeChecker.booleanType, false, false, correctForFalse}; result[XorStr("false")] = {AutocompleteEntryKind::Keyword, typeChecker.booleanType, false, false, correctForFalse};
result["nil"] = {AutocompleteEntryKind::Keyword, typeChecker.nilType, false, false, correctForNil}; result[XorStr("nil")] = {AutocompleteEntryKind::Keyword, typeChecker.nilType, false, false, correctForNil};
result["not"] = {AutocompleteEntryKind::Keyword}; result[XorStr("not")] = {AutocompleteEntryKind::Keyword};
result["function"] = {AutocompleteEntryKind::Keyword, std::nullopt, false, false, correctForFunction}; result[XorStr("function")] = {AutocompleteEntryKind::Keyword, std::nullopt, false, false, correctForFunction};
if (auto ty = findExpectedTypeAt(module, node, position)) if (auto ty = findExpectedTypeAt(module, node, position))
autocompleteStringSingleton(*ty, true, result); autocompleteStringSingleton(*ty, true, result);
@ -1292,14 +1392,14 @@ static std::optional<const ClassTypeVar*> getMethodContainingClass(const ModuleP
return std::nullopt; return std::nullopt;
} }
Luau::TypeId parentType = Luau::follow(*parentIt); lluz::TypeId parentType = lluz::follow(*parentIt);
if (auto parentClass = Luau::get<ClassTypeVar>(parentType)) if (auto parentClass = lluz::get<ClassTypeVar>(parentType))
{ {
return parentClass; return parentClass;
} }
if (auto parentUnion = Luau::get<UnionTypeVar>(parentType)) if (auto parentUnion = lluz::get<UnionTypeVar>(parentType))
{ {
return returnFirstNonnullOptionOfType<ClassTypeVar>(parentUnion); return returnFirstNonnullOptionOfType<ClassTypeVar>(parentUnion);
} }
@ -1350,17 +1450,17 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(const Source
return std::nullopt; return std::nullopt;
}; };
auto followedId = Luau::follow(*it); auto followedId = lluz::follow(*it);
if (auto functionType = Luau::get<FunctionTypeVar>(followedId)) if (auto functionType = lluz::get<FunctionTypeVar>(followedId))
{ {
return performCallback(functionType); return performCallback(functionType);
} }
if (auto intersect = Luau::get<IntersectionTypeVar>(followedId)) if (auto intersect = lluz::get<IntersectionTypeVar>(followedId))
{ {
for (TypeId part : intersect->parts) for (TypeId part : intersect->parts)
{ {
if (auto candidateFunctionType = Luau::get<FunctionTypeVar>(part)) if (auto candidateFunctionType = lluz::get<FunctionTypeVar>(part))
{ {
if (std::optional<AutocompleteEntryMap> ret = performCallback(candidateFunctionType)) if (std::optional<AutocompleteEntryMap> ret = performCallback(candidateFunctionType))
{ {
@ -1379,20 +1479,21 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
if (isWithinComment(sourceModule, position)) if (isWithinComment(sourceModule, position))
return {}; return {};
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(sourceModule, position); NodeFinder finder{position, sourceModule.root};
LUAU_ASSERT(!ancestry.empty()); sourceModule.root->visit(&finder);
AstNode* node = ancestry.back(); lluz_ASSERT(!finder.ancestry.empty());
AstNode* node = finder.ancestry.back();
AstExprConstantNil dummy{Location{}}; AstExprConstantNil dummy{Location{}};
AstNode* parent = ancestry.size() >= 2 ? ancestry.rbegin()[1] : &dummy; AstNode* parent = finder.ancestry.size() >= 2 ? finder.ancestry.rbegin()[1] : &dummy;
// If we are inside a body of a function that doesn't have a completed argument list, ignore the body node // If we are inside a body of a function that doesn't have a completed argument list, ignore the body node
if (auto exprFunction = parent->as<AstExprFunction>(); exprFunction && !exprFunction->argLocation && node == exprFunction->body) if (auto exprFunction = parent->as<AstExprFunction>(); exprFunction && !exprFunction->argLocation && node == exprFunction->body)
{ {
ancestry.pop_back(); finder.ancestry.pop_back();
node = ancestry.back(); node = finder.ancestry.back();
parent = ancestry.size() >= 2 ? ancestry.rbegin()[1] : &dummy; parent = finder.ancestry.size() >= 2 ? finder.ancestry.rbegin()[1] : &dummy;
} }
if (auto indexName = node->as<AstExprIndexName>()) if (auto indexName = node->as<AstExprIndexName>())
@ -1404,48 +1505,50 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
TypeId ty = follow(*it); TypeId ty = follow(*it);
PropIndexType indexType = indexName->op == ':' ? PropIndexType::Colon : PropIndexType::Point; PropIndexType indexType = indexName->op == ':' ? PropIndexType::Colon : PropIndexType::Point;
if (!FFlag::LuauSelfCallAutocompleteFix3 && isString(ty)) if (!FFlag::LluSelfCallAutocompleteFix2 && isString(ty))
return { return {autocompleteProps(*module, typeArena, typeChecker.globalScope->bindings[AstName{XorStr("string")}].typeId, indexType, finder.ancestry),
autocompleteProps(*module, typeArena, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, ancestry), ancestry}; finder.ancestry};
else else
return {autocompleteProps(*module, typeArena, ty, indexType, ancestry), ancestry}; return {autocompleteProps(*module, typeArena, ty, indexType, finder.ancestry), finder.ancestry};
} }
else if (auto typeReference = node->as<AstTypeReference>()) else if (auto typeReference = node->as<AstTypeReference>())
{ {
if (typeReference->prefix) if (typeReference->prefix)
return {autocompleteModuleTypes(*module, position, typeReference->prefix->value), ancestry}; return {autocompleteModuleTypes(*module, position, typeReference->prefix->value), finder.ancestry};
else else
return {autocompleteTypeNames(*module, position, ancestry), ancestry}; return {autocompleteTypeNames(*module, position, finder.ancestry), finder.ancestry};
} }
else if (node->is<AstTypeError>()) else if (node->is<AstTypeError>())
{ {
return {autocompleteTypeNames(*module, position, ancestry), ancestry}; return {autocompleteTypeNames(*module, position, finder.ancestry), finder.ancestry};
} }
else if (AstStatLocal* statLocal = node->as<AstStatLocal>()) else if (AstStatLocal* statLocal = node->as<AstStatLocal>())
{ {
if (statLocal->vars.size == 1 && (!statLocal->equalsSignLocation || position < statLocal->equalsSignLocation->begin)) if (statLocal->vars.size == 1 && (!statLocal->equalsSignLocation || position < statLocal->equalsSignLocation->begin))
return {{{"function", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry}; return {{{XorStr("function"), AutocompleteEntry{
AutocompleteEntryKind::Keyword}}}, finder.ancestry};
else if (statLocal->equalsSignLocation && position >= statLocal->equalsSignLocation->end) else if (statLocal->equalsSignLocation && position >= statLocal->equalsSignLocation->end)
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position), ancestry}; return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, finder.ancestry, position), finder.ancestry};
else else
return {}; return {};
} }
else if (AstStatFor* statFor = extractStat<AstStatFor>(ancestry)) else if (AstStatFor* statFor = extractStat<AstStatFor>(finder.ancestry))
{ {
if (!statFor->hasDo || position < statFor->doLocation.begin) if (!statFor->hasDo || position < statFor->doLocation.begin)
{ {
if (!statFor->from->is<AstExprError>() && !statFor->to->is<AstExprError>() && (!statFor->step || !statFor->step->is<AstExprError>())) if (!statFor->from->is<AstExprError>() && !statFor->to->is<AstExprError>() && (!statFor->step || !statFor->step->is<AstExprError>()))
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry}; return {{{XorStr("do"), AutocompleteEntry{
AutocompleteEntryKind::Keyword}}}, finder.ancestry};
if (statFor->from->location.containsClosed(position) || statFor->to->location.containsClosed(position) || if (statFor->from->location.containsClosed(position) || statFor->to->location.containsClosed(position) ||
(statFor->step && statFor->step->location.containsClosed(position))) (statFor->step && statFor->step->location.containsClosed(position)))
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position), ancestry}; return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, finder.ancestry, position), finder.ancestry};
return {}; return {};
} }
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry}; return {autocompleteStatement(sourceModule, *module, finder.ancestry, position), finder.ancestry};
} }
else if (AstStatForIn* statForIn = parent->as<AstStatForIn>(); statForIn && (node->is<AstStatBlock>() || isIdentifier(node))) else if (AstStatForIn* statForIn = parent->as<AstStatForIn>(); statForIn && (node->is<AstStatBlock>() || isIdentifier(node)))
@ -1461,67 +1564,76 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
return {}; return {};
} }
return {{{"in", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry}; return {{{XorStr("in"), AutocompleteEntry{
AutocompleteEntryKind::Keyword}}}, finder.ancestry};
} }
if (!statForIn->hasDo || position <= statForIn->doLocation.begin) if (!statForIn->hasDo || position <= statForIn->doLocation.begin)
{ {
LUAU_ASSERT(statForIn->values.size > 0); lluz_ASSERT(statForIn->values.size > 0);
AstExpr* lastExpr = statForIn->values.data[statForIn->values.size - 1]; AstExpr* lastExpr = statForIn->values.data[statForIn->values.size - 1];
if (lastExpr->location.containsClosed(position)) if (lastExpr->location.containsClosed(position))
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position), ancestry}; return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, finder.ancestry, position), finder.ancestry};
if (position > lastExpr->location.end) if (position > lastExpr->location.end)
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry}; return {{{XorStr("do"), AutocompleteEntry{
AutocompleteEntryKind::Keyword}}}, finder.ancestry};
return {}; // Not sure what this means return {}; // Not sure what this means
} }
} }
else if (AstStatForIn* statForIn = extractStat<AstStatForIn>(ancestry)) else if (AstStatForIn* statForIn = extractStat<AstStatForIn>(finder.ancestry))
{ {
// The AST looks a bit differently if the cursor is at a position where only the "do" keyword is allowed. // The AST looks a bit differently if the cursor is at a position where only the "do" keyword is allowed.
// ex "for f in f do" // ex "for f in f do"
if (!statForIn->hasDo) if (!statForIn->hasDo)
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry}; return {{{XorStr("do"), AutocompleteEntry{
AutocompleteEntryKind::Keyword}}}, finder.ancestry};
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry}; return {autocompleteStatement(sourceModule, *module, finder.ancestry, position), finder.ancestry};
} }
else if (AstStatWhile* statWhile = parent->as<AstStatWhile>(); node->is<AstStatBlock>() && statWhile) else if (AstStatWhile* statWhile = parent->as<AstStatWhile>(); node->is<AstStatBlock>() && statWhile)
{ {
if (!statWhile->hasDo && !statWhile->condition->is<AstStatError>() && position > statWhile->condition->location.end) if (!statWhile->hasDo && !statWhile->condition->is<AstStatError>() && position > statWhile->condition->location.end)
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry}; return {{{XorStr("do"), AutocompleteEntry{
AutocompleteEntryKind::Keyword}}}, finder.ancestry};
if (!statWhile->hasDo || position < statWhile->doLocation.begin) if (!statWhile->hasDo || position < statWhile->doLocation.begin)
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position), ancestry}; return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, finder.ancestry, position), finder.ancestry};
if (statWhile->hasDo && position > statWhile->doLocation.end) if (statWhile->hasDo && position > statWhile->doLocation.end)
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry}; return {autocompleteStatement(sourceModule, *module, finder.ancestry, position), finder.ancestry};
} }
else if (AstStatWhile* statWhile = extractStat<AstStatWhile>(ancestry); statWhile && !statWhile->hasDo) else if (AstStatWhile* statWhile = extractStat<AstStatWhile>(finder.ancestry); statWhile && !statWhile->hasDo)
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry}; return {{{XorStr("do"), AutocompleteEntry{
AutocompleteEntryKind::Keyword}}}, finder.ancestry};
else if (AstStatIf* statIf = node->as<AstStatIf>(); statIf && !statIf->elseLocation.has_value()) else if (AstStatIf* statIf = node->as<AstStatIf>(); statIf && !statIf->elseLocation.has_value())
{ {
return { return {{{XorStr("else"), AutocompleteEntry{
{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry}; AutocompleteEntryKind::Keyword}}, {XorStr("elseif"), AutocompleteEntry{
AutocompleteEntryKind::Keyword}}},
finder.ancestry};
} }
else if (AstStatIf* statIf = parent->as<AstStatIf>(); statIf && node->is<AstStatBlock>()) else if (AstStatIf* statIf = parent->as<AstStatIf>(); statIf && node->is<AstStatBlock>())
{ {
if (statIf->condition->is<AstExprError>()) if (statIf->condition->is<AstExprError>())
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position), ancestry}; return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, finder.ancestry, position), finder.ancestry};
else if (!statIf->thenLocation || statIf->thenLocation->containsClosed(position)) else if (!statIf->thenLocation || statIf->thenLocation->containsClosed(position))
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry}; return {{{XorStr("then"), AutocompleteEntry{
AutocompleteEntryKind::Keyword}}}, finder.ancestry};
} }
else if (AstStatIf* statIf = extractStat<AstStatIf>(ancestry); else if (AstStatIf* statIf = extractStat<AstStatIf>(finder.ancestry);
statIf && (!statIf->thenLocation || statIf->thenLocation->containsClosed(position))) statIf && (!statIf->thenLocation || statIf->thenLocation->containsClosed(position)))
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry}; return {{{XorStr("then"), AutocompleteEntry{
AutocompleteEntryKind::Keyword}}}, finder.ancestry};
else if (AstStatRepeat* statRepeat = node->as<AstStatRepeat>(); statRepeat && statRepeat->condition->is<AstExprError>()) else if (AstStatRepeat* statRepeat = node->as<AstStatRepeat>(); statRepeat && statRepeat->condition->is<AstExprError>())
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position), ancestry}; return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, finder.ancestry, position), finder.ancestry};
else if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat) else if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(finder.ancestry); statRepeat)
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry}; return {autocompleteStatement(sourceModule, *module, finder.ancestry, position), finder.ancestry};
else if (AstExprTable* exprTable = parent->as<AstExprTable>(); exprTable && (node->is<AstExprGlobal>() || node->is<AstExprConstantString>())) else if (AstExprTable* exprTable = parent->as<AstExprTable>(); exprTable && (node->is<AstExprGlobal>() || node->is<AstExprConstantString>()))
{ {
for (const auto& [kind, key, value] : exprTable->items) for (const auto& [kind, key, value] : exprTable->items)
@ -1531,7 +1643,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
{ {
if (auto it = module->astExpectedTypes.find(exprTable)) if (auto it = module->astExpectedTypes.find(exprTable))
{ {
auto result = autocompleteProps(*module, typeArena, *it, PropIndexType::Key, ancestry); auto result = autocompleteProps(*module, typeArena, *it, PropIndexType::Key, finder.ancestry);
// Remove keys that are already completed // Remove keys that are already completed
for (const auto& item : exprTable->items) for (const auto& item : exprTable->items)
@ -1545,9 +1657,9 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
// If we know for sure that a key is being written, do not offer general expression suggestions // If we know for sure that a key is being written, do not offer general expression suggestions
if (!key) if (!key)
autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position, result); autocompleteExpression(sourceModule, *module, typeChecker, typeArena, finder.ancestry, position, result);
return {result, ancestry}; return {result, finder.ancestry};
} }
break; break;
@ -1555,11 +1667,11 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
} }
} }
else if (isIdentifier(node) && (parent->is<AstStatExpr>() || parent->is<AstStatError>())) else if (isIdentifier(node) && (parent->is<AstStatExpr>() || parent->is<AstStatError>()))
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry}; return {autocompleteStatement(sourceModule, *module, finder.ancestry, position), finder.ancestry};
if (std::optional<AutocompleteEntryMap> ret = autocompleteStringParams(sourceModule, module, ancestry, position, callback)) if (std::optional<AutocompleteEntryMap> ret = autocompleteStringParams(sourceModule, module, finder.ancestry, position, callback))
{ {
return {*ret, ancestry}; return {*ret, finder.ancestry};
} }
else if (node->is<AstExprConstantString>()) else if (node->is<AstExprConstantString>())
{ {
@ -1568,14 +1680,14 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
if (auto it = module->astExpectedTypes.find(node->asExpr())) if (auto it = module->astExpectedTypes.find(node->asExpr()))
autocompleteStringSingleton(*it, false, result); autocompleteStringSingleton(*it, false, result);
if (ancestry.size() >= 2) if (finder.ancestry.size() >= 2)
{ {
if (auto idxExpr = ancestry.at(ancestry.size() - 2)->as<AstExprIndexExpr>()) if (auto idxExpr = finder.ancestry.at(finder.ancestry.size() - 2)->as<AstExprIndexExpr>())
{ {
if (auto it = module->astTypes.find(idxExpr->expr)) if (auto it = module->astTypes.find(idxExpr->expr))
autocompleteProps(*module, typeArena, follow(*it), PropIndexType::Point, ancestry, result); autocompleteProps(*module, typeArena, follow(*it), PropIndexType::Point, finder.ancestry, result);
} }
else if (auto binExpr = ancestry.at(ancestry.size() - 2)->as<AstExprBinary>()) else if (auto binExpr = finder.ancestry.at(finder.ancestry.size() - 2)->as<AstExprBinary>())
{ {
if (binExpr->op == AstExprBinary::CompareEq || binExpr->op == AstExprBinary::CompareNe) if (binExpr->op == AstExprBinary::CompareEq || binExpr->op == AstExprBinary::CompareNe)
{ {
@ -1585,7 +1697,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
} }
} }
return {result, ancestry}; return {result, finder.ancestry};
} }
if (node->is<AstExprConstantNumber>()) if (node->is<AstExprConstantNumber>())
@ -1594,9 +1706,9 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
} }
if (node->asExpr()) if (node->asExpr())
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position), ancestry}; return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, finder.ancestry, position), finder.ancestry};
else if (node->asStat()) else if (node->asStat())
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry}; return {autocompleteStatement(sourceModule, *module, finder.ancestry, position), finder.ancestry};
return {}; return {};
} }
@ -1626,4 +1738,32 @@ AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName
return autocompleteResult; return autocompleteResult;
} }
} // namespace Luau OwningAutocompleteResult autocompleteSource(Frontend& frontend, std::string_view source, Position position, StringCompletionCallback callback)
{
// TODO: Remove #include "lluz/Parser.h" with this function
auto sourceModule = std::make_unique<SourceModule>();
ParseOptions parseOptions;
parseOptions.captureComments = true;
ParseResult result = Parser::parse(source.data(), source.size(), *sourceModule->names, *sourceModule->allocator, parseOptions);
if (!result.root)
return {AutocompleteResult{}, {}, nullptr};
sourceModule->name = XorStr("FRAGMENT_SCRIPT");
sourceModule->root = result.root;
sourceModule->mode = Mode::Strict;
sourceModule->commentLocations = std::move(result.commentLocations);
TypeChecker& typeChecker = frontend.typeCheckerForAutocomplete;
ModulePtr module = typeChecker.check(*sourceModule, Mode::Strict);
OwningAutocompleteResult autocompleteResult = {
autocomplete(*sourceModule, module, typeChecker, &frontend.arenaForAutocomplete, position, callback), std::move(module),
std::move(sourceModule)};
frontend.arenaForAutocomplete.clear();
return autocompleteResult;
}
} // namespace lluz

View file

@ -1,15 +1,14 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h" #include "lluz/BuiltinDefinitions.h"
#include "Luau/Frontend.h" #include "lluz/Frontend.h"
#include "Luau/Symbol.h" #include "lluz/Symbol.h"
#include "Luau/Common.h" #include "lluz/Common.h"
#include "Luau/ToString.h" #include "lluz/ToString.h"
#include <algorithm> #include <algorithm>
LUAU_FASTFLAGVARIABLE(LuauSetMetaTableArgsCheck, false) lluz_FASTFLAGVARIABLE(LluSetMetaTableArgsCheck, false)
LUAU_FASTFLAG(LuauUnknownAndNeverType)
/** FIXME: Many of these type definitions are not quite completely accurate. /** FIXME: Many of these type definitions are not quite completely accurate.
* *
@ -17,7 +16,7 @@ LUAU_FASTFLAG(LuauUnknownAndNeverType)
* about a function that takes any number of values, but where each value must have some specific type. * about a function that takes any number of values, but where each value must have some specific type.
*/ */
namespace Luau namespace lluz
{ {
static std::optional<WithPredicate<TypePackId>> magicFunctionSelect( static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
@ -79,12 +78,12 @@ TypeId makeFunction(TypeArena& arena, std::optional<TypeId> selfType, std::initi
FunctionTypeVar ftv{generics, genericPacks, paramPack, retPack, {}, selfType.has_value()}; FunctionTypeVar ftv{generics, genericPacks, paramPack, retPack, {}, selfType.has_value()};
if (selfType) if (selfType)
ftv.argNames.push_back(Luau::FunctionArgument{"self", {}}); ftv.argNames.push_back(lluz::FunctionArgument{"self", {}});
if (paramNames.size() != 0) if (paramNames.size() != 0)
{ {
for (auto&& p : paramNames) for (auto&& p : paramNames)
ftv.argNames.push_back(Luau::FunctionArgument{std::move(p), {}}); ftv.argNames.push_back(lluz::FunctionArgument{std::move(p), {}});
} }
else if (selfType) else if (selfType)
{ {
@ -101,7 +100,7 @@ void attachMagicFunction(TypeId ty, MagicFunction fn)
if (auto ftv = getMutable<FunctionTypeVar>(ty)) if (auto ftv = getMutable<FunctionTypeVar>(ty))
ftv->magicFunction = fn; ftv->magicFunction = fn;
else else
LUAU_ASSERT(!"Got a non functional type"); lluz_ASSERT(!XorStr("Got a non functional type"));
} }
Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol) Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol)
@ -140,7 +139,7 @@ void addGlobalBinding(TypeChecker& typeChecker, const ScopePtr& scope, const std
TypeId getGlobalBinding(TypeChecker& typeChecker, const std::string& name) TypeId getGlobalBinding(TypeChecker& typeChecker, const std::string& name)
{ {
auto t = tryGetGlobalBinding(typeChecker, name); auto t = tryGetGlobalBinding(typeChecker, name);
LUAU_ASSERT(t.has_value()); lluz_ASSERT(t.has_value());
return t->typeId; return t->typeId;
} }
@ -177,34 +176,34 @@ void assignPropDocumentationSymbols(TableTypeVar::Props& props, const std::strin
void registerBuiltinTypes(TypeChecker& typeChecker) void registerBuiltinTypes(TypeChecker& typeChecker)
{ {
LUAU_ASSERT(!typeChecker.globalTypes.typeVars.isFrozen()); lluz_ASSERT(!typeChecker.globalTypes.typeVars.isFrozen());
LUAU_ASSERT(!typeChecker.globalTypes.typePacks.isFrozen()); lluz_ASSERT(!typeChecker.globalTypes.typePacks.isFrozen());
TypeId nilType = typeChecker.nilType; TypeId nilType = typeChecker.nilType;
TypeArena& arena = typeChecker.globalTypes; TypeArena& arena = typeChecker.globalTypes;
LoadDefinitionFileResult loadResult = Luau::loadDefinitionFile(typeChecker, typeChecker.globalScope, getBuiltinDefinitionSource(), "@luau"); LoadDefinitionFileResult loadResult = lluz::loadDefinitionFile(typeChecker, typeChecker.globalScope, getBuiltinDefinitionSource(), XorStr("@lluz"));
LUAU_ASSERT(loadResult.success); lluz_ASSERT(loadResult.success);
TypeId genericK = arena.addType(GenericTypeVar{"K"}); TypeId genericK = arena.addType(GenericTypeVar{"K"});
TypeId genericV = arena.addType(GenericTypeVar{"V"}); TypeId genericV = arena.addType(GenericTypeVar{"V"});
TypeId mapOfKtoV = arena.addType(TableTypeVar{{}, TableIndexer(genericK, genericV), typeChecker.globalScope->level, TableState::Generic}); TypeId mapOfKtoV = arena.addType(TableTypeVar{{}, TableIndexer(genericK, genericV), typeChecker.globalScope->level, TableState::Generic});
std::optional<TypeId> stringMetatableTy = getMetatable(getSingletonTypes().stringType); std::optional<TypeId> stringMetatableTy = getMetatable(getSingletonTypes().stringType);
LUAU_ASSERT(stringMetatableTy); lluz_ASSERT(stringMetatableTy);
const TableTypeVar* stringMetatableTable = get<TableTypeVar>(follow(*stringMetatableTy)); const TableTypeVar* stringMetatableTable = get<TableTypeVar>(follow(*stringMetatableTy));
LUAU_ASSERT(stringMetatableTable); lluz_ASSERT(stringMetatableTable);
auto it = stringMetatableTable->props.find("__index"); auto it = stringMetatableTable->props.find(XorStr("__index"));
LUAU_ASSERT(it != stringMetatableTable->props.end()); lluz_ASSERT(it != stringMetatableTable->props.end());
addGlobalBinding(typeChecker, "string", it->second.type, "@luau"); addGlobalBinding(typeChecker, XorStr("string"), it->second.type, XorStr("@lluz"));
// next<K, V>(t: Table<K, V>, i: K?) -> (K, V) // next<K, V>(t: Table<K, V>, i: K?) -> (K, V)
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(typeChecker, arena, genericK)}}); TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(typeChecker, arena, genericK)}});
addGlobalBinding(typeChecker, "next", addGlobalBinding(typeChecker, "next",
arena.addType(FunctionTypeVar{{genericK, genericV}, {}, nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})}), "@luau"); arena.addType(FunctionTypeVar{{genericK, genericV}, {}, nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})}), XorStr("@lluz"));
TypePackId pairsArgsTypePack = arena.addTypePack({mapOfKtoV}); TypePackId pairsArgsTypePack = arena.addTypePack({mapOfKtoV});
@ -212,7 +211,7 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, nilType}}); TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, nilType}});
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil) // pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil)
addGlobalBinding(typeChecker, "pairs", arena.addType(FunctionTypeVar{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau"); addGlobalBinding(typeChecker, XorStr("pairs"), arena.addType(FunctionTypeVar{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), XorStr("@lluz"));
TypeId genericMT = arena.addType(GenericTypeVar{"MT"}); TypeId genericMT = arena.addType(GenericTypeVar{"MT"});
@ -221,19 +220,19 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
TypeId tableMetaMT = arena.addType(MetatableTypeVar{tabTy, genericMT}); TypeId tableMetaMT = arena.addType(MetatableTypeVar{tabTy, genericMT});
addGlobalBinding(typeChecker, "getmetatable", makeFunction(arena, std::nullopt, {genericMT}, {}, {tableMetaMT}, {genericMT}), "@luau"); addGlobalBinding(typeChecker, XorStr("getmetatable"), makeFunction(arena, std::nullopt, {genericMT}, {}, {tableMetaMT}, {genericMT}), "@lluz");
// setmetatable<MT>({ @metatable MT }, MT) -> { @metatable MT }
// clang-format off // clang-format off
// setmetatable<T: {}, MT>(T, MT) -> { @metatable MT, T } addGlobalBinding(typeChecker, XorStr("setmetatable"),
addGlobalBinding(typeChecker, "setmetatable",
arena.addType( arena.addType(
FunctionTypeVar{ FunctionTypeVar{
{genericMT}, {genericMT},
{}, {},
arena.addTypePack(TypePack{{FFlag::LuauUnknownAndNeverType ? tabTy : tableMetaMT, genericMT}}), arena.addTypePack(TypePack{{tableMetaMT, genericMT}}),
arena.addTypePack(TypePack{{tableMetaMT}}) arena.addTypePack(TypePack{{tableMetaMT}})
} }
), "@luau" ), XorStr("@lluz")
); );
// clang-format on // clang-format on
@ -248,20 +247,20 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
} }
} }
attachMagicFunction(getGlobalBinding(typeChecker, "assert"), magicFunctionAssert); attachMagicFunction(getGlobalBinding(typeChecker, XorStr("assert")), magicFunctionAssert);
attachMagicFunction(getGlobalBinding(typeChecker, "setmetatable"), magicFunctionSetMetaTable); attachMagicFunction(getGlobalBinding(typeChecker, XorStr("setmetatable")), magicFunctionSetMetaTable);
attachMagicFunction(getGlobalBinding(typeChecker, "select"), magicFunctionSelect); attachMagicFunction(getGlobalBinding(typeChecker, XorStr("select")), magicFunctionSelect);
if (TableTypeVar* ttv = getMutable<TableTypeVar>(getGlobalBinding(typeChecker, "table"))) if (TableTypeVar* ttv = getMutable<TableTypeVar>(getGlobalBinding(typeChecker, XorStr("table"))))
{ {
// tabTy is a generic table type which we can't express via declaration syntax yet // tabTy is a generic table type which we can't express via declaration syntax yet
ttv->props["freeze"] = makeProperty(makeFunction(arena, std::nullopt, {tabTy}, {tabTy}), "@luau/global/table.freeze"); ttv->props[XorStr("freeze")] = makeProperty(makeFunction(arena, std::nullopt, {tabTy}, {tabTy}), XorStr("@lluz/global/table.freeze"));
ttv->props["clone"] = makeProperty(makeFunction(arena, std::nullopt, {tabTy}, {tabTy}), "@luau/global/table.clone"); ttv->props[XorStr("clone")] = makeProperty(makeFunction(arena, std::nullopt, {tabTy}, {tabTy}), XorStr("@lluz/global/table.clone"));
attachMagicFunction(ttv->props["pack"].type, magicFunctionPack); attachMagicFunction(ttv->props[XorStr("pack")].type, magicFunctionPack);
} }
attachMagicFunction(getGlobalBinding(typeChecker, "require"), magicFunctionRequire); attachMagicFunction(getGlobalBinding(typeChecker, XorStr("require")), magicFunctionRequire);
} }
static std::optional<WithPredicate<TypePackId>> magicFunctionSelect( static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
@ -273,7 +272,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
if (expr.args.size <= 0) if (expr.args.size <= 0)
{ {
typechecker.reportError(TypeError{expr.location, GenericError{"select should take 1 or more arguments"}}); typechecker.reportError(TypeError{expr.location, GenericError{XorStr("select should take 1 or more arguments")}});
return std::nullopt; return std::nullopt;
} }
@ -294,7 +293,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
return WithPredicate<TypePackId>{*tail}; return WithPredicate<TypePackId>{*tail};
} }
typechecker.reportError(TypeError{arg1->location, GenericError{"bad argument #1 to select (index out of range)"}}); typechecker.reportError(TypeError{arg1->location, GenericError{XorStr("bad argument #1 to select (index out of range)")}});
} }
else if (AstExprConstantString* str = arg1->as<AstExprConstantString>()) else if (AstExprConstantString* str = arg1->as<AstExprConstantString>())
{ {
@ -310,12 +309,6 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
{ {
auto [paramPack, _predicates] = withPredicate; auto [paramPack, _predicates] = withPredicate;
if (FFlag::LuauUnknownAndNeverType)
{
if (size(paramPack) < 2 && finite(paramPack))
return std::nullopt;
}
TypeArena& arena = typechecker.currentModule->internalTypes; TypeArena& arena = typechecker.currentModule->internalTypes;
std::vector<TypeId> expectedArgs = typechecker.unTypePack(scope, paramPack, 2, expr.location); std::vector<TypeId> expectedArgs = typechecker.unTypePack(scope, paramPack, 2, expr.location);
@ -323,12 +316,6 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
TypeId target = follow(expectedArgs[0]); TypeId target = follow(expectedArgs[0]);
TypeId mt = follow(expectedArgs[1]); TypeId mt = follow(expectedArgs[1]);
if (FFlag::LuauUnknownAndNeverType)
{
typechecker.tablify(target);
typechecker.tablify(mt);
}
if (const auto& tab = get<TableTypeVar>(target)) if (const auto& tab = get<TableTypeVar>(target))
{ {
if (target->persistent) if (target->persistent)
@ -337,8 +324,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
} }
else else
{ {
if (!FFlag::LuauUnknownAndNeverType) typechecker.tablify(mt);
typechecker.tablify(mt);
const TableTypeVar* mtTtv = get<TableTypeVar>(mt); const TableTypeVar* mtTtv = get<TableTypeVar>(mt);
MetatableTypeVar mtv{target, mt}; MetatableTypeVar mtv{target, mt};
@ -350,20 +336,17 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
if (tableName == metatableName) if (tableName == metatableName)
mtv.syntheticName = tableName; mtv.syntheticName = tableName;
else else
mtv.syntheticName = "{ @metatable: " + metatableName + ", " + tableName + " }"; mtv.syntheticName = XorStr("{ @metatable: ") + metatableName + ", " + tableName + " }";
} }
TypeId mtTy = arena.addType(mtv); TypeId mtTy = arena.addType(mtv);
if (FFlag::LuauSetMetaTableArgsCheck && expr.args.size < 1) if (FFlag::LluSetMetaTableArgsCheck && expr.args.size < 1)
{ {
if (FFlag::LuauUnknownAndNeverType) return WithPredicate<TypePackId>{};
return std::nullopt;
else
return WithPredicate<TypePackId>{};
} }
if (!FFlag::LuauSetMetaTableArgsCheck || !expr.self) if (!FFlag::LluSetMetaTableArgsCheck || !expr.self)
{ {
AstExpr* targetExpr = expr.args.data[0]; AstExpr* targetExpr = expr.args.data[0];
if (AstExprLocal* targetLocal = targetExpr->as<AstExprLocal>()) if (AstExprLocal* targetLocal = targetExpr->as<AstExprLocal>())
@ -381,7 +364,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
} }
else else
{ {
typechecker.reportError(TypeError{expr.location, GenericError{"setmetatable should take a table"}}); typechecker.reportError(TypeError{expr.location, GenericError{XorStr("setmetatable should take a table")}});
} }
return WithPredicate<TypePackId>{arena.addTypePack({target})}; return WithPredicate<TypePackId>{arena.addTypePack({target})};
@ -407,21 +390,11 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionAssert(
if (head.size() > 0) if (head.size() > 0)
{ {
auto [ty, ok] = typechecker.pickTypesFromSense(head[0], true); std::optional<TypeId> newhead = typechecker.pickTypesFromSense(head[0], true);
if (FFlag::LuauUnknownAndNeverType) if (!newhead)
{ head = {typechecker.nilType};
if (get<NeverTypeVar>(*ty))
head = {*ty};
else
head[0] = *ty;
}
else else
{ head[0] = *newhead;
if (!ty)
head = {typechecker.nilType};
else
head[0] = *ty;
}
} }
return WithPredicate<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})}; return WithPredicate<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})};
@ -469,16 +442,16 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionPack(
static bool checkRequirePath(TypeChecker& typechecker, AstExpr* expr) static bool checkRequirePath(TypeChecker& typechecker, AstExpr* expr)
{ {
// require(foo.parent.bar) will technically work, but it depends on legacy goop that // 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 // lluz does not and could not support without a bunch of work. It's deprecated anyway, so
// we'll warn here if we see it. // we'll warn here if we see it.
bool good = true; bool good = true;
AstExprIndexName* indexExpr = expr->as<AstExprIndexName>(); AstExprIndexName* indexExpr = expr->as<AstExprIndexName>();
while (indexExpr) while (indexExpr)
{ {
if (indexExpr->index == "parent") if (indexExpr->index == XorStr("parent"))
{ {
typechecker.reportError(indexExpr->indexLocation, DeprecatedApiUsed{"parent", "Parent"}); typechecker.reportError(indexExpr->indexLocation, DeprecatedApiUsed{XorStr("parent"), XorStr("Parent")});
good = false; good = false;
} }
@ -495,7 +468,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionRequire(
if (expr.args.size != 1) if (expr.args.size != 1)
{ {
typechecker.reportError(TypeError{expr.location, GenericError{"require takes 1 argument"}}); typechecker.reportError(TypeError{expr.location, GenericError{XorStr("require takes 1 argument")}});
return std::nullopt; return std::nullopt;
} }
@ -508,4 +481,4 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionRequire(
return std::nullopt; return std::nullopt;
} }
} // namespace Luau } // namespace lluz

View file

@ -1,16 +1,16 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Clone.h" #include "lluz/Clone.h"
#include "Luau/RecursionCounter.h" #include "lluz/RecursionCounter.h"
#include "Luau/TxnLog.h" #include "lluz/TxnLog.h"
#include "Luau/TypePack.h" #include "lluz/TypePack.h"
#include "Luau/Unifiable.h" #include "lluz/Unifiable.h"
LUAU_FASTFLAG(DebugLuauCopyBeforeNormalizing) lluz_FASTFLAG(DebugLluCopyBeforeNormalizing)
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300) lluz_FASTINTVARIABLE(LluTypeCloneRecursionLimit, 300)
namespace Luau namespace lluz
{ {
namespace namespace
@ -59,8 +59,6 @@ struct TypeCloner
void operator()(const UnionTypeVar& t); void operator()(const UnionTypeVar& t);
void operator()(const IntersectionTypeVar& t); void operator()(const IntersectionTypeVar& t);
void operator()(const LazyTypeVar& t); void operator()(const LazyTypeVar& t);
void operator()(const UnknownTypeVar& t);
void operator()(const NeverTypeVar& t);
}; };
struct TypePackCloner struct TypePackCloner
@ -105,7 +103,7 @@ struct TypePackCloner
void operator()(const Unifiable::Bound<TypePackId>& t) void operator()(const Unifiable::Bound<TypePackId>& t)
{ {
TypePackId cloned = clone(t.boundTo, dest, cloneState); TypePackId cloned = clone(t.boundTo, dest, cloneState);
if (FFlag::DebugLuauCopyBeforeNormalizing) if (FFlag::DebugLluCopyBeforeNormalizing)
cloned = dest.addTypePack(TypePackVar{BoundTypePack{cloned}}); cloned = dest.addTypePack(TypePackVar{BoundTypePack{cloned}});
seenTypePacks[typePackId] = cloned; seenTypePacks[typePackId] = cloned;
} }
@ -120,7 +118,7 @@ struct TypePackCloner
{ {
TypePackId cloned = dest.addTypePack(TypePack{}); TypePackId cloned = dest.addTypePack(TypePack{});
TypePack* destTp = getMutable<TypePack>(cloned); TypePack* destTp = getMutable<TypePack>(cloned);
LUAU_ASSERT(destTp != nullptr); lluz_ASSERT(destTp != nullptr);
seenTypePacks[typePackId] = cloned; seenTypePacks[typePackId] = cloned;
for (TypeId ty : t.head) for (TypeId ty : t.head)
@ -151,7 +149,7 @@ void TypeCloner::operator()(const Unifiable::Generic& t)
void TypeCloner::operator()(const Unifiable::Bound<TypeId>& t) void TypeCloner::operator()(const Unifiable::Bound<TypeId>& t)
{ {
TypeId boundTo = clone(t.boundTo, dest, cloneState); TypeId boundTo = clone(t.boundTo, dest, cloneState);
if (FFlag::DebugLuauCopyBeforeNormalizing) if (FFlag::DebugLluCopyBeforeNormalizing)
boundTo = dest.addType(BoundTypeVar{boundTo}); boundTo = dest.addType(BoundTypeVar{boundTo});
seenTypes[typeId] = boundTo; seenTypes[typeId] = boundTo;
} }
@ -175,7 +173,7 @@ void TypeCloner::operator()(const ConstrainedTypeVar& t)
{ {
TypeId res = dest.addType(ConstrainedTypeVar{t.level}); TypeId res = dest.addType(ConstrainedTypeVar{t.level});
ConstrainedTypeVar* ctv = getMutable<ConstrainedTypeVar>(res); ConstrainedTypeVar* ctv = getMutable<ConstrainedTypeVar>(res);
LUAU_ASSERT(ctv); lluz_ASSERT(ctv);
seenTypes[typeId] = res; seenTypes[typeId] = res;
@ -195,7 +193,7 @@ void TypeCloner::operator()(const FunctionTypeVar& t)
{ {
TypeId result = dest.addType(FunctionTypeVar{TypeLevel{0, 0}, {}, {}, nullptr, nullptr, t.definition, t.hasSelf}); TypeId result = dest.addType(FunctionTypeVar{TypeLevel{0, 0}, {}, {}, nullptr, nullptr, t.definition, t.hasSelf});
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(result); FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(result);
LUAU_ASSERT(ftv != nullptr); lluz_ASSERT(ftv != nullptr);
seenTypes[typeId] = result; seenTypes[typeId] = result;
@ -215,7 +213,7 @@ void TypeCloner::operator()(const FunctionTypeVar& t)
void TypeCloner::operator()(const TableTypeVar& t) void TypeCloner::operator()(const TableTypeVar& t)
{ {
// If table is now bound to another one, we ignore the content of the original // If table is now bound to another one, we ignore the content of the original
if (!FFlag::DebugLuauCopyBeforeNormalizing && t.boundTo) if (!FFlag::DebugLluCopyBeforeNormalizing && t.boundTo)
{ {
TypeId boundTo = clone(*t.boundTo, dest, cloneState); TypeId boundTo = clone(*t.boundTo, dest, cloneState);
seenTypes[typeId] = boundTo; seenTypes[typeId] = boundTo;
@ -224,7 +222,7 @@ void TypeCloner::operator()(const TableTypeVar& t)
TypeId result = dest.addType(TableTypeVar{}); TypeId result = dest.addType(TableTypeVar{});
TableTypeVar* ttv = getMutable<TableTypeVar>(result); TableTypeVar* ttv = getMutable<TableTypeVar>(result);
LUAU_ASSERT(ttv != nullptr); lluz_ASSERT(ttv != nullptr);
*ttv = t; *ttv = t;
@ -232,7 +230,7 @@ void TypeCloner::operator()(const TableTypeVar& t)
ttv->level = TypeLevel{0, 0}; ttv->level = TypeLevel{0, 0};
if (FFlag::DebugLuauCopyBeforeNormalizing && t.boundTo) if (FFlag::DebugLluCopyBeforeNormalizing && t.boundTo)
ttv->boundTo = clone(*t.boundTo, dest, cloneState); ttv->boundTo = clone(*t.boundTo, dest, cloneState);
for (const auto& [name, prop] : t.props) for (const auto& [name, prop] : t.props)
@ -301,7 +299,7 @@ void TypeCloner::operator()(const IntersectionTypeVar& t)
seenTypes[typeId] = result; seenTypes[typeId] = result;
IntersectionTypeVar* option = getMutable<IntersectionTypeVar>(result); IntersectionTypeVar* option = getMutable<IntersectionTypeVar>(result);
LUAU_ASSERT(option != nullptr); lluz_ASSERT(option != nullptr);
for (TypeId ty : t.parts) for (TypeId ty : t.parts)
option->parts.push_back(clone(ty, dest, cloneState)); option->parts.push_back(clone(ty, dest, cloneState));
@ -312,16 +310,6 @@ void TypeCloner::operator()(const LazyTypeVar& t)
defaultClone(t); defaultClone(t);
} }
void TypeCloner::operator()(const UnknownTypeVar& t)
{
defaultClone(t);
}
void TypeCloner::operator()(const NeverTypeVar& t)
{
defaultClone(t);
}
} // anonymous namespace } // anonymous namespace
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState) TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState)
@ -329,14 +317,14 @@ TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState)
if (tp->persistent) if (tp->persistent)
return tp; return tp;
RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit); RecursionLimiter _ra(&cloneState.recursionCount, FInt::LluTypeCloneRecursionLimit);
TypePackId& res = cloneState.seenTypePacks[tp]; TypePackId& res = cloneState.seenTypePacks[tp];
if (res == nullptr) if (res == nullptr)
{ {
TypePackCloner cloner{dest, tp, cloneState}; TypePackCloner cloner{dest, tp, cloneState};
Luau::visit(cloner, tp->ty); // Mutates the storage that 'res' points into. lluz::visit(cloner, tp->ty); // Mutates the storage that 'res' points into.
} }
return res; return res;
@ -347,14 +335,14 @@ TypeId clone(TypeId typeId, TypeArena& dest, CloneState& cloneState)
if (typeId->persistent) if (typeId->persistent)
return typeId; return typeId;
RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit); RecursionLimiter _ra(&cloneState.recursionCount, FInt::LluTypeCloneRecursionLimit);
TypeId& res = cloneState.seenTypes[typeId]; TypeId& res = cloneState.seenTypes[typeId];
if (res == nullptr) if (res == nullptr)
{ {
TypeCloner cloner{dest, typeId, cloneState}; TypeCloner cloner{dest, typeId, cloneState};
Luau::visit(cloner, typeId->ty); // Mutates the storage that 'res' points into. lluz::visit(cloner, typeId->ty); // Mutates the storage that 'res' points into.
// Persistent types are not being cloned and we get the original type back which might be read-only // Persistent types are not being cloned and we get the original type back which might be read-only
if (!res->persistent) if (!res->persistent)
@ -419,7 +407,7 @@ TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log)
} }
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty)) else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
{ {
LUAU_ASSERT(!ttv->boundTo); lluz_ASSERT(!ttv->boundTo);
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state}; TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state};
clone.definitionModuleName = ttv->definitionModuleName; clone.definitionModuleName = ttv->definitionModuleName;
clone.name = ttv->name; clone.name = ttv->name;
@ -459,4 +447,4 @@ TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log)
return result; return result;
} }
} // namespace Luau } // namespace lluz

View file

@ -1,8 +1,10 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Config.h" #include "lluz/Config.h"
#include "Luau/Lexer.h" #include "lluz/Lexer.h"
#include "Luau/StringUtils.h" #include "lluz/StringUtils.h"
#include "..\..\..\..\Security\XorString.h"
namespace namespace
{ {
@ -11,14 +13,14 @@ using Error = std::optional<std::string>;
} }
namespace Luau namespace lluz
{ {
static Error parseBoolean(bool& result, const std::string& value) static Error parseBoolean(bool& result, const std::string& value)
{ {
if (value == "true") if (value == XorStr("true"))
result = true; result = true;
else if (value == "false") else if (value == XorStr("false"))
result = false; result = false;
else else
return Error{"Bad setting '" + value + "'. Valid options are true and false"}; return Error{"Bad setting '" + value + "'. Valid options are true and false"};
@ -28,13 +30,13 @@ static Error parseBoolean(bool& result, const std::string& value)
Error parseModeString(Mode& mode, const std::string& modeString, bool compat) Error parseModeString(Mode& mode, const std::string& modeString, bool compat)
{ {
if (modeString == "nocheck") if (modeString == XorStr("nocheck"))
mode = Mode::NoCheck; mode = Mode::NoCheck;
else if (modeString == "strict") else if (modeString == XorStr("strict"))
mode = Mode::Strict; mode = Mode::Strict;
else if (modeString == "nonstrict") else if (modeString == XorStr("nonstrict"))
mode = Mode::Nonstrict; mode = Mode::Nonstrict;
else if (modeString == "noinfer" && compat) else if (modeString == XorStr("noinfer") && compat)
mode = Mode::NoCheck; mode = Mode::NoCheck;
else else
return Error{"Bad mode \"" + modeString + "\". Valid options are nocheck, nonstrict, and strict"}; return Error{"Bad mode \"" + modeString + "\". Valid options are nocheck, nonstrict, and strict"};
@ -45,27 +47,27 @@ Error parseModeString(Mode& mode, const std::string& modeString, bool compat)
static Error parseLintRuleStringForCode( static Error parseLintRuleStringForCode(
LintOptions& enabledLints, LintOptions& fatalLints, LintWarning::Code code, const std::string& value, bool compat) LintOptions& enabledLints, LintOptions& fatalLints, LintWarning::Code code, const std::string& value, bool compat)
{ {
if (value == "true") if (value == XorStr("true"))
{ {
enabledLints.enableWarning(code); enabledLints.enableWarning(code);
} }
else if (value == "false") else if (value == XorStr("false"))
{ {
enabledLints.disableWarning(code); enabledLints.disableWarning(code);
} }
else if (compat) else if (compat)
{ {
if (value == "enabled") if (value == XorStr("enabled"))
{ {
enabledLints.enableWarning(code); enabledLints.enableWarning(code);
fatalLints.disableWarning(code); fatalLints.disableWarning(code);
} }
else if (value == "disabled") else if (value == XorStr("disabled"))
{ {
enabledLints.disableWarning(code); enabledLints.disableWarning(code);
fatalLints.disableWarning(code); fatalLints.disableWarning(code);
} }
else if (value == "fatal") else if (value == XorStr("fatal"))
{ {
enabledLints.enableWarning(code); enabledLints.enableWarning(code);
fatalLints.enableWarning(code); fatalLints.enableWarning(code);
@ -85,7 +87,7 @@ static Error parseLintRuleStringForCode(
Error parseLintRuleString(LintOptions& enabledLints, LintOptions& fatalLints, const std::string& warningName, const std::string& value, bool compat) Error parseLintRuleString(LintOptions& enabledLints, LintOptions& fatalLints, const std::string& warningName, const std::string& value, bool compat)
{ {
if (warningName == "*") if (warningName == XorStr("*"))
{ {
for (int code = LintWarning::Code_Unknown; code < LintWarning::Code__Count; ++code) for (int code = LintWarning::Code_Unknown; code < LintWarning::Code__Count; ++code)
{ {
@ -127,7 +129,7 @@ static Error fail(Lexer& lexer, const char* message)
{ {
Lexeme cur = lexer.current(); Lexeme cur = lexer.current();
return format("Expected %s at line %d, got %s instead", message, cur.location.begin.line + 1, cur.toString().c_str()); return format(XorStr("Expected %s at line %d, got %s instead"), message, cur.location.begin.line + 1, cur.toString().c_str());
} }
template<typename Action> template<typename Action>
@ -142,7 +144,7 @@ static Error parseJson(const std::string& contents, Action action)
bool arrayTop = false; // we don't support nested arrays bool arrayTop = false; // we don't support nested arrays
if (lexer.current().type != '{') if (lexer.current().type != '{')
return fail(lexer, "'{'"); return fail(lexer, XorStr("'{'"));
next(lexer); next(lexer);
for (;;) for (;;)
@ -154,13 +156,13 @@ static Error parseJson(const std::string& contents, Action action)
next(lexer); next(lexer);
arrayTop = false; arrayTop = false;
LUAU_ASSERT(!keys.empty()); lluz_ASSERT(!keys.empty());
keys.pop_back(); keys.pop_back();
if (lexer.current().type == ',') if (lexer.current().type == ',')
next(lexer); next(lexer);
else if (lexer.current().type != '}') else if (lexer.current().type != '}')
return fail(lexer, "',' or '}'"); return fail(lexer, XorStr("',' or '}'"));
} }
else if (lexer.current().type == Lexeme::QuotedString) else if (lexer.current().type == Lexeme::QuotedString)
{ {
@ -173,10 +175,10 @@ static Error parseJson(const std::string& contents, Action action)
if (lexer.current().type == ',') if (lexer.current().type == ',')
next(lexer); next(lexer);
else if (lexer.current().type != ']') else if (lexer.current().type != ']')
return fail(lexer, "',' or ']'"); return fail(lexer, XorStr("',' or ']'"));
} }
else else
return fail(lexer, "array element or ']'"); return fail(lexer, XorStr("array element or ']'"));
} }
else else
{ {
@ -187,7 +189,7 @@ static Error parseJson(const std::string& contents, Action action)
if (keys.empty()) if (keys.empty())
{ {
if (lexer.current().type != Lexeme::Eof) if (lexer.current().type != Lexeme::Eof)
return fail(lexer, "end of file"); return fail(lexer, XorStr("end of file"));
return {}; return {};
} }
@ -197,7 +199,7 @@ static Error parseJson(const std::string& contents, Action action)
if (lexer.current().type == ',') if (lexer.current().type == ',')
next(lexer); next(lexer);
else if (lexer.current().type != '}') else if (lexer.current().type != '}')
return fail(lexer, "',' or '}'"); return fail(lexer, XorStr("',' or '}'"));
} }
else if (lexer.current().type == Lexeme::QuotedString) else if (lexer.current().type == Lexeme::QuotedString)
{ {
@ -207,7 +209,7 @@ static Error parseJson(const std::string& contents, Action action)
keys.push_back(key); keys.push_back(key);
if (lexer.current().type != ':') if (lexer.current().type != ':')
return fail(lexer, "':'"); return fail(lexer, XorStr("':'"));
next(lexer); next(lexer);
if (lexer.current().type == '{' || lexer.current().type == '[') if (lexer.current().type == '{' || lexer.current().type == '[')
@ -220,7 +222,7 @@ static Error parseJson(const std::string& contents, Action action)
{ {
std::string value = lexer.current().type == Lexeme::QuotedString std::string value = lexer.current().type == Lexeme::QuotedString
? std::string(lexer.current().data, lexer.current().length) ? std::string(lexer.current().data, lexer.current().length)
: (lexer.current().type == Lexeme::ReservedTrue ? "true" : "false"); : (lexer.current().type == Lexeme::ReservedTrue ? XorStr("true" : "false"));
next(lexer); next(lexer);
if (Error err = action(keys, value)) if (Error err = action(keys, value))
@ -231,13 +233,13 @@ static Error parseJson(const std::string& contents, Action action)
if (lexer.current().type == ',') if (lexer.current().type == ',')
next(lexer); next(lexer);
else if (lexer.current().type != '}') else if (lexer.current().type != '}')
return fail(lexer, "',' or '}'"); return fail(lexer, XorStr("',' or '}'"));
} }
else else
return fail(lexer, "field value"); return fail(lexer, XorStr("field value"));
} }
else else
return fail(lexer, "field key"); return fail(lexer, XorStr("field key"));
} }
} }
@ -247,25 +249,25 @@ static Error parseJson(const std::string& contents, Action action)
Error parseConfig(const std::string& contents, Config& config, bool compat) Error parseConfig(const std::string& contents, Config& config, bool compat)
{ {
return parseJson(contents, [&](const std::vector<std::string>& keys, const std::string& value) -> Error { return parseJson(contents, [&](const std::vector<std::string>& keys, const std::string& value) -> Error {
if (keys.size() == 1 && keys[0] == "languageMode") if (keys.size() == 1 && keys[0] == XorStr("languageMode"))
return parseModeString(config.mode, value, compat); return parseModeString(config.mode, value, compat);
else if (keys.size() == 2 && keys[0] == "lint") else if (keys.size() == 2 && keys[0] == XorStr("lint"))
return parseLintRuleString(config.enabledLint, config.fatalLint, keys[1], value, compat); return parseLintRuleString(config.enabledLint, config.fatalLint, keys[1], value, compat);
else if (keys.size() == 1 && keys[0] == "lintErrors") else if (keys.size() == 1 && keys[0] == XorStr("lintErrors"))
return parseBoolean(config.lintErrors, value); return parseBoolean(config.lintErrors, value);
else if (keys.size() == 1 && keys[0] == "typeErrors") else if (keys.size() == 1 && keys[0] == XorStr("typeErrors"))
return parseBoolean(config.typeErrors, value); return parseBoolean(config.typeErrors, value);
else if (keys.size() == 1 && keys[0] == "globals") else if (keys.size() == 1 && keys[0] == XorStr("globals"))
{ {
config.globals.push_back(value); config.globals.push_back(value);
return std::nullopt; return std::nullopt;
} }
else if (compat && keys.size() == 2 && keys[0] == "language" && keys[1] == "mode") else if (compat && keys.size() == 2 && keys[0] == XorStr("language") && keys[1] == XorStr("mode"))
return parseModeString(config.mode, value, compat); return parseModeString(config.mode, value, compat);
else else
{ {
std::vector<std::string_view> keysv(keys.begin(), keys.end()); std::vector<std::string_view> keysv(keys.begin(), keys.end());
return "Unknown key " + join(keysv, "/"); return XorStr("Unknown key " + join(keysv, "/"));
} }
}); });
} }
@ -275,4 +277,4 @@ const Config& NullConfigResolver::getConfig(const ModuleName& name) const
return defaultConfig; return defaultConfig;
} }
} // namespace Luau } // namespace lluz

View file

@ -1,8 +1,8 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Constraint.h" #include "lluz/Constraint.h"
namespace Luau namespace lluz
{ {
Constraint::Constraint(ConstraintV&& c) Constraint::Constraint(ConstraintV&& c)
@ -10,4 +10,4 @@ Constraint::Constraint(ConstraintV&& c)
{ {
} }
} // namespace Luau } // namespace lluz

View file

@ -1,20 +1,20 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/ConstraintGraphBuilder.h" #include "lluz/ConstraintGraphBuilder.h"
#include "Luau/RecursionCounter.h" #include "lluz/RecursionCounter.h"
#include "Luau/ToString.h" #include "lluz/ToString.h"
LUAU_FASTINT(LuauCheckRecursionLimit); lluz_FASTINT(LluCheckRecursionLimit);
#include "Luau/Scope.h" #include "lluz/Scope.h"
namespace Luau namespace lluz
{ {
const AstStat* getFallthrough(const AstStat* node); // TypeInfer.cpp const AstStat* getFallthrough(const AstStat* node); // TypeInfer.cpp
ConstraintGraphBuilder::ConstraintGraphBuilder( ConstraintGraphBuilder::ConstraintGraphBuilder(
const ModuleName& moduleName, TypeArena* arena, NotNull<InternalErrorReporter> ice, NotNull<Scope> globalScope) const ModuleName& moduleName, TypeArena* arena, NotNull<InternalErrorReporter> ice, NotNull<Scope2> globalScope)
: moduleName(moduleName) : moduleName(moduleName)
, singletonTypes(getSingletonTypes()) , singletonTypes(getSingletonTypes())
, arena(arena) , arena(arena)
@ -22,52 +22,54 @@ ConstraintGraphBuilder::ConstraintGraphBuilder(
, ice(ice) , ice(ice)
, globalScope(globalScope) , globalScope(globalScope)
{ {
LUAU_ASSERT(arena); lluz_ASSERT(arena);
} }
TypeId ConstraintGraphBuilder::freshType(const ScopePtr& scope) TypeId ConstraintGraphBuilder::freshType(NotNull<Scope2> scope)
{ {
return arena->addType(FreeTypeVar{scope.get()}); return arena->addType(FreeTypeVar{scope});
} }
TypePackId ConstraintGraphBuilder::freshTypePack(const ScopePtr& scope) TypePackId ConstraintGraphBuilder::freshTypePack(NotNull<Scope2> scope)
{ {
FreeTypePack f{scope.get()}; FreeTypePack f{scope};
return arena->addTypePack(TypePackVar{std::move(f)}); return arena->addTypePack(TypePackVar{std::move(f)});
} }
ScopePtr ConstraintGraphBuilder::childScope(Location location, const ScopePtr& parent) NotNull<Scope2> ConstraintGraphBuilder::childScope(Location location, NotNull<Scope2> parent)
{ {
auto scope = std::make_shared<Scope>(parent); auto scope = std::make_unique<Scope2>();
scopes.emplace_back(location, scope); NotNull<Scope2> borrow = NotNull(scope.get());
scopes.emplace_back(location, std::move(scope));
scope->returnType = parent->returnType; borrow->parent = parent;
parent->children.push_back(NotNull(scope.get())); borrow->returnType = parent->returnType;
parent->children.push_back(borrow);
return scope; return borrow;
} }
void ConstraintGraphBuilder::addConstraint(const ScopePtr& scope, ConstraintV cv) void ConstraintGraphBuilder::addConstraint(NotNull<Scope2> scope, ConstraintV cv)
{ {
scope->constraints.emplace_back(new Constraint{std::move(cv)}); scope->constraints.emplace_back(new Constraint{std::move(cv)});
} }
void ConstraintGraphBuilder::addConstraint(const ScopePtr& scope, std::unique_ptr<Constraint> c) void ConstraintGraphBuilder::addConstraint(NotNull<Scope2> scope, std::unique_ptr<Constraint> c)
{ {
scope->constraints.emplace_back(std::move(c)); scope->constraints.emplace_back(std::move(c));
} }
void ConstraintGraphBuilder::visit(AstStatBlock* block) void ConstraintGraphBuilder::visit(AstStatBlock* block)
{ {
LUAU_ASSERT(scopes.empty()); lluz_ASSERT(scopes.empty());
LUAU_ASSERT(rootScope == nullptr); lluz_ASSERT(rootScope == nullptr);
ScopePtr scope = std::make_shared<Scope>(singletonTypes.anyTypePack); scopes.emplace_back(block->location, std::make_unique<Scope2>());
rootScope = scope.get(); rootScope = scopes.back().second.get();
scopes.emplace_back(block->location, scope); NotNull<Scope2> borrow = NotNull(rootScope);
rootScope->returnType = freshTypePack(scope); rootScope->returnType = freshTypePack(borrow);
prepopulateGlobalScope(scope, block); prepopulateGlobalScope(borrow, block);
// TODO: We should share the global scope. // TODO: We should share the global scope.
rootScope->typeBindings["nil"] = singletonTypes.nilType; rootScope->typeBindings["nil"] = singletonTypes.nilType;
@ -76,14 +78,14 @@ void ConstraintGraphBuilder::visit(AstStatBlock* block)
rootScope->typeBindings["boolean"] = singletonTypes.booleanType; rootScope->typeBindings["boolean"] = singletonTypes.booleanType;
rootScope->typeBindings["thread"] = singletonTypes.threadType; rootScope->typeBindings["thread"] = singletonTypes.threadType;
visitBlockWithoutChildScope(scope, block); visitBlockWithoutChildScope(borrow, block);
} }
void ConstraintGraphBuilder::visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block) void ConstraintGraphBuilder::visitBlockWithoutChildScope(NotNull<Scope2> scope, AstStatBlock* block)
{ {
RecursionCounter counter{&recursionCount}; RecursionCounter counter{&recursionCount};
if (recursionCount >= FInt::LuauCheckRecursionLimit) if (recursionCount >= FInt::LluCheckRecursionLimit)
{ {
reportCodeTooComplex(block->location); reportCodeTooComplex(block->location);
return; return;
@ -93,16 +95,14 @@ void ConstraintGraphBuilder::visitBlockWithoutChildScope(const ScopePtr& scope,
visit(scope, stat); visit(scope, stat);
} }
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStat* stat) void ConstraintGraphBuilder::visit(NotNull<Scope2> scope, AstStat* stat)
{ {
RecursionLimiter limiter{&recursionCount, FInt::LuauCheckRecursionLimit}; RecursionLimiter limiter{&recursionCount, FInt::LluCheckRecursionLimit};
if (auto s = stat->as<AstStatBlock>()) if (auto s = stat->as<AstStatBlock>())
visit(scope, s); visit(scope, s);
else if (auto s = stat->as<AstStatLocal>()) else if (auto s = stat->as<AstStatLocal>())
visit(scope, s); visit(scope, s);
else if (auto s = stat->as<AstStatFor>())
visit(scope, s);
else if (auto f = stat->as<AstStatFunction>()) else if (auto f = stat->as<AstStatFunction>())
visit(scope, f); visit(scope, f);
else if (auto f = stat->as<AstStatLocalFunction>()) else if (auto f = stat->as<AstStatLocalFunction>())
@ -118,27 +118,25 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStat* stat)
else if (auto a = stat->as<AstStatTypeAlias>()) else if (auto a = stat->as<AstStatTypeAlias>())
visit(scope, a); visit(scope, a);
else else
LUAU_ASSERT(0); lluz_ASSERT(0);
} }
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local) void ConstraintGraphBuilder::visit(NotNull<Scope2> scope, AstStatLocal* local)
{ {
std::vector<TypeId> varTypes; std::vector<TypeId> varTypes;
for (AstLocal* local : local->vars) for (AstLocal* local : local->vars)
{ {
TypeId ty = freshType(scope); TypeId ty = freshType(scope);
Location location = local->location;
if (local->annotation) if (local->annotation)
{ {
location = local->annotation->location;
TypeId annotation = resolveType(scope, local->annotation); TypeId annotation = resolveType(scope, local->annotation);
addConstraint(scope, SubtypeConstraint{ty, annotation}); addConstraint(scope, SubtypeConstraint{ty, annotation});
} }
varTypes.push_back(ty); varTypes.push_back(ty);
scope->bindings[local] = Binding{ty, location}; scope->bindings[local] = ty;
} }
for (size_t i = 0; i < local->values.size; ++i) for (size_t i = 0; i < local->values.size; ++i)
@ -169,39 +167,18 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
} }
} }
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for_) void addConstraints(Constraint* constraint, NotNull<Scope2> scope)
{
auto checkNumber = [&](AstExpr* expr)
{
if (!expr)
return;
TypeId t = check(scope, expr);
addConstraint(scope, SubtypeConstraint{t, singletonTypes.numberType});
};
checkNumber(for_->from);
checkNumber(for_->to);
checkNumber(for_->step);
ScopePtr forScope = childScope(for_->location, scope);
forScope->bindings[for_->var] = Binding{singletonTypes.numberType, for_->var->location};
visit(forScope, for_->body);
}
void addConstraints(Constraint* constraint, NotNull<Scope> scope)
{ {
scope->constraints.reserve(scope->constraints.size() + scope->constraints.size()); scope->constraints.reserve(scope->constraints.size() + scope->constraints.size());
for (const auto& c : scope->constraints) for (const auto& c : scope->constraints)
constraint->dependencies.push_back(NotNull{c.get()}); constraint->dependencies.push_back(NotNull{c.get()});
for (NotNull<Scope> childScope : scope->children) for (NotNull<Scope2> childScope : scope->children)
addConstraints(constraint, childScope); addConstraints(constraint, childScope);
} }
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocalFunction* function) void ConstraintGraphBuilder::visit(NotNull<Scope2> scope, AstStatLocalFunction* function)
{ {
// Local // Local
// Global // Global
@ -210,24 +187,24 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocalFunction*
TypeId functionType = nullptr; TypeId functionType = nullptr;
auto ty = scope->lookup(function->name); auto ty = scope->lookup(function->name);
LUAU_ASSERT(!ty.has_value()); // The parser ensures that every local function has a distinct Symbol for its name. lluz_ASSERT(!ty.has_value()); // The parser ensures that every local function has a distinct Symbol for its name.
functionType = arena->addType(BlockedTypeVar{}); functionType = arena->addType(BlockedTypeVar{});
scope->bindings[function->name] = Binding{functionType, function->name->location}; scope->bindings[function->name] = functionType;
FunctionSignature sig = checkFunctionSignature(scope, function->func); FunctionSignature sig = checkFunctionSignature(scope, function->func);
sig.bodyScope->bindings[function->name] = Binding{sig.signature, function->func->location}; sig.bodyScope->bindings[function->name] = sig.signature;
checkFunctionBody(sig.bodyScope, function->func); checkFunctionBody(sig.bodyScope, function->func);
std::unique_ptr<Constraint> c{ std::unique_ptr<Constraint> c{
new Constraint{GeneralizationConstraint{functionType, sig.signature, sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()}}}; new Constraint{GeneralizationConstraint{functionType, sig.signature, sig.signatureScope ? sig.signatureScope : sig.bodyScope}}};
addConstraints(c.get(), NotNull(sig.bodyScope.get())); addConstraints(c.get(), sig.bodyScope);
addConstraint(scope, std::move(c)); addConstraint(scope, std::move(c));
} }
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* function) void ConstraintGraphBuilder::visit(NotNull<Scope2> scope, AstStatFunction* function)
{ {
// Name could be AstStatLocal, AstStatGlobal, AstStatIndexName. // Name could be AstStatLocal, AstStatGlobal, AstStatIndexName.
// With or without self // With or without self
@ -247,9 +224,9 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct
else else
{ {
functionType = arena->addType(BlockedTypeVar{}); functionType = arena->addType(BlockedTypeVar{});
scope->bindings[localName->local] = Binding{functionType, localName->location}; scope->bindings[localName->local] = functionType;
} }
sig.bodyScope->bindings[localName->local] = Binding{sig.signature, localName->location}; sig.bodyScope->bindings[localName->local] = sig.signature;
} }
else if (AstExprGlobal* globalName = function->name->as<AstExprGlobal>()) else if (AstExprGlobal* globalName = function->name->as<AstExprGlobal>())
{ {
@ -262,9 +239,9 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct
else else
{ {
functionType = arena->addType(BlockedTypeVar{}); functionType = arena->addType(BlockedTypeVar{});
rootScope->bindings[globalName->name] = Binding{functionType, globalName->location}; rootScope->bindings[globalName->name] = functionType;
} }
sig.bodyScope->bindings[globalName->name] = Binding{sig.signature, globalName->location}; sig.bodyScope->bindings[globalName->name] = sig.signature;
} }
else if (AstExprIndexName* indexName = function->name->as<AstExprIndexName>()) else if (AstExprIndexName* indexName = function->name->as<AstExprIndexName>())
{ {
@ -286,26 +263,26 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct
functionType = singletonTypes.errorRecoveryType(); functionType = singletonTypes.errorRecoveryType();
} }
LUAU_ASSERT(functionType != nullptr); lluz_ASSERT(functionType != nullptr);
checkFunctionBody(sig.bodyScope, function->func); checkFunctionBody(sig.bodyScope, function->func);
std::unique_ptr<Constraint> c{ std::unique_ptr<Constraint> c{
new Constraint{GeneralizationConstraint{functionType, sig.signature, sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()}}}; new Constraint{GeneralizationConstraint{functionType, sig.signature, sig.signatureScope ? sig.signatureScope : sig.bodyScope}}};
addConstraints(c.get(), NotNull(sig.bodyScope.get())); addConstraints(c.get(), sig.bodyScope);
addConstraint(scope, std::move(c)); addConstraint(scope, std::move(c));
} }
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatReturn* ret) void ConstraintGraphBuilder::visit(NotNull<Scope2> scope, AstStatReturn* ret)
{ {
TypePackId exprTypes = checkPack(scope, ret->list); TypePackId exprTypes = checkPack(scope, ret->list);
addConstraint(scope, PackSubtypeConstraint{exprTypes, scope->returnType}); addConstraint(scope, PackSubtypeConstraint{exprTypes, scope->returnType});
} }
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatBlock* block) void ConstraintGraphBuilder::visit(NotNull<Scope2> scope, AstStatBlock* block)
{ {
ScopePtr innerScope = childScope(block->location, scope); NotNull<Scope2> innerScope = childScope(block->location, scope);
// In order to enable mutually-recursive type aliases, we need to // In order to enable mutually-recursive type aliases, we need to
// populate the type bindings before we actually check any of the // populate the type bindings before we actually check any of the
@ -323,7 +300,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatBlock* block)
visitBlockWithoutChildScope(innerScope, block); visitBlockWithoutChildScope(innerScope, block);
} }
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatAssign* assign) void ConstraintGraphBuilder::visit(NotNull<Scope2> scope, AstStatAssign* assign)
{ {
TypePackId varPackId = checkExprList(scope, assign->vars); TypePackId varPackId = checkExprList(scope, assign->vars);
TypePackId valuePack = checkPack(scope, assign->values); TypePackId valuePack = checkPack(scope, assign->values);
@ -331,21 +308,21 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatAssign* assign)
addConstraint(scope, PackSubtypeConstraint{valuePack, varPackId}); addConstraint(scope, PackSubtypeConstraint{valuePack, varPackId});
} }
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatIf* ifStatement) void ConstraintGraphBuilder::visit(NotNull<Scope2> scope, AstStatIf* ifStatement)
{ {
check(scope, ifStatement->condition); check(scope, ifStatement->condition);
ScopePtr thenScope = childScope(ifStatement->thenbody->location, scope); NotNull<Scope2> thenScope = childScope(ifStatement->thenbody->location, scope);
visit(thenScope, ifStatement->thenbody); visit(thenScope, ifStatement->thenbody);
if (ifStatement->elsebody) if (ifStatement->elsebody)
{ {
ScopePtr elseScope = childScope(ifStatement->elsebody->location, scope); NotNull<Scope2> elseScope = childScope(ifStatement->elsebody->location, scope);
visit(elseScope, ifStatement->elsebody); visit(elseScope, ifStatement->elsebody);
} }
} }
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlias* alias) void ConstraintGraphBuilder::visit(NotNull<Scope2> scope, AstStatTypeAlias* alias)
{ {
// TODO: Exported type aliases // TODO: Exported type aliases
// TODO: Generic type aliases // TODO: Generic type aliases
@ -354,7 +331,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlias* alia
// This should always be here since we do a separate pass over the // This should always be here since we do a separate pass over the
// AST to set up typeBindings. If it's not, we've somehow skipped // AST to set up typeBindings. If it's not, we've somehow skipped
// this alias in that first pass. // this alias in that first pass.
LUAU_ASSERT(it != scope->typeBindings.end()); lluz_ASSERT(it != scope->typeBindings.end());
if (it == scope->typeBindings.end()) if (it == scope->typeBindings.end())
{ {
ice->ice("Type alias does not have a pre-populated binding", alias->location); ice->ice("Type alias does not have a pre-populated binding", alias->location);
@ -371,7 +348,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlias* alia
addConstraint(scope, NameConstraint{ty, alias->name.value}); addConstraint(scope, NameConstraint{ty, alias->name.value});
} }
TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs) TypePackId ConstraintGraphBuilder::checkPack(NotNull<Scope2> scope, AstArray<AstExpr*> exprs)
{ {
if (exprs.size == 0) if (exprs.size == 0)
return arena->addTypePack({}); return arena->addTypePack({});
@ -387,16 +364,16 @@ TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray<Ast
last = checkPack(scope, exprs.data[i]); last = checkPack(scope, exprs.data[i]);
} }
LUAU_ASSERT(last != nullptr); lluz_ASSERT(last != nullptr);
return arena->addTypePack(TypePack{std::move(types), last}); return arena->addTypePack(TypePack{std::move(types), last});
} }
TypePackId ConstraintGraphBuilder::checkExprList(const ScopePtr& scope, const AstArray<AstExpr*>& exprs) TypePackId ConstraintGraphBuilder::checkExprList(NotNull<Scope2> scope, const AstArray<AstExpr*>& exprs)
{ {
TypePackId result = arena->addTypePack({}); TypePackId result = arena->addTypePack({});
TypePack* resultPack = getMutable<TypePack>(result); TypePack* resultPack = getMutable<TypePack>(result);
LUAU_ASSERT(resultPack); lluz_ASSERT(resultPack);
for (size_t i = 0; i < exprs.size; ++i) for (size_t i = 0; i < exprs.size; ++i)
{ {
@ -413,11 +390,11 @@ TypePackId ConstraintGraphBuilder::checkExprList(const ScopePtr& scope, const As
return result; return result;
} }
TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* expr) TypePackId ConstraintGraphBuilder::checkPack(NotNull<Scope2> scope, AstExpr* expr)
{ {
RecursionCounter counter{&recursionCount}; RecursionCounter counter{&recursionCount};
if (recursionCount >= FInt::LuauCheckRecursionLimit) if (recursionCount >= FInt::LluCheckRecursionLimit)
{ {
reportCodeTooComplex(expr->location); reportCodeTooComplex(expr->location);
return singletonTypes.errorRecoveryTypePack(); return singletonTypes.errorRecoveryTypePack();
@ -463,16 +440,16 @@ TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* exp
result = arena->addTypePack({t}); result = arena->addTypePack({t});
} }
LUAU_ASSERT(result); lluz_ASSERT(result);
astTypePacks[expr] = result; astTypePacks[expr] = result;
return result; return result;
} }
TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr) TypeId ConstraintGraphBuilder::check(NotNull<Scope2> scope, AstExpr* expr)
{ {
RecursionCounter counter{&recursionCount}; RecursionCounter counter{&recursionCount};
if (recursionCount >= FInt::LuauCheckRecursionLimit) if (recursionCount >= FInt::LluCheckRecursionLimit)
{ {
reportCodeTooComplex(expr->location); reportCodeTooComplex(expr->location);
return singletonTypes.errorRecoveryType(); return singletonTypes.errorRecoveryType();
@ -539,16 +516,16 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr)
} }
else else
{ {
LUAU_ASSERT(0); lluz_ASSERT(0);
result = freshType(scope); result = freshType(scope);
} }
LUAU_ASSERT(result); lluz_ASSERT(result);
astTypes[expr] = result; astTypes[expr] = result;
return result; return result;
} }
TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName* indexName) TypeId ConstraintGraphBuilder::check(NotNull<Scope2> scope, AstExprIndexName* indexName)
{ {
TypeId obj = check(scope, indexName->expr); TypeId obj = check(scope, indexName->expr);
TypeId result = freshType(scope); TypeId result = freshType(scope);
@ -564,7 +541,7 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName* in
return result; return result;
} }
TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr* indexExpr) TypeId ConstraintGraphBuilder::check(NotNull<Scope2> scope, AstExprIndexExpr* indexExpr)
{ {
TypeId obj = check(scope, indexExpr->expr); TypeId obj = check(scope, indexExpr->expr);
TypeId indexType = check(scope, indexExpr->index); TypeId indexType = check(scope, indexExpr->index);
@ -579,7 +556,7 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr* in
return result; return result;
} }
TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprUnary* unary) TypeId ConstraintGraphBuilder::check(NotNull<Scope2> scope, AstExprUnary* unary)
{ {
TypeId operandType = check(scope, unary->expr); TypeId operandType = check(scope, unary->expr);
@ -592,14 +569,14 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprUnary* unary)
return resultType; return resultType;
} }
default: default:
LUAU_ASSERT(0); lluz_ASSERT(0);
} }
LUAU_UNREACHABLE(); lluz_UNREACHABLE();
return singletonTypes.errorRecoveryType(); return singletonTypes.errorRecoveryType();
} }
TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* binary) TypeId ConstraintGraphBuilder::check(NotNull<Scope2> scope, AstExprBinary* binary)
{ {
TypeId leftType = check(scope, binary->left); TypeId leftType = check(scope, binary->left);
TypeId rightType = check(scope, binary->right); TypeId rightType = check(scope, binary->right);
@ -617,18 +594,18 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* binar
return resultType; return resultType;
} }
default: default:
LUAU_ASSERT(0); lluz_ASSERT(0);
} }
LUAU_ASSERT(0); lluz_ASSERT(0);
return nullptr; return nullptr;
} }
TypeId ConstraintGraphBuilder::checkExprTable(const ScopePtr& scope, AstExprTable* expr) TypeId ConstraintGraphBuilder::checkExprTable(NotNull<Scope2> scope, AstExprTable* expr)
{ {
TypeId ty = arena->addType(TableTypeVar{}); TypeId ty = arena->addType(TableTypeVar{});
TableTypeVar* ttv = getMutable<TableTypeVar>(ty); TableTypeVar* ttv = getMutable<TableTypeVar>(ty);
LUAU_ASSERT(ttv); lluz_ASSERT(ttv);
auto createIndexer = [this, scope, ttv](TypeId currentIndexType, TypeId currentResultType) { auto createIndexer = [this, scope, ttv](TypeId currentIndexType, TypeId currentResultType) {
if (!ttv->indexer) if (!ttv->indexer)
@ -674,10 +651,10 @@ TypeId ConstraintGraphBuilder::checkExprTable(const ScopePtr& scope, AstExprTabl
return ty; return ty;
} }
ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionSignature(const ScopePtr& parent, AstExprFunction* fn) ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionSignature(NotNull<Scope2> parent, AstExprFunction* fn)
{ {
ScopePtr signatureScope = nullptr; Scope2* signatureScope = nullptr;
ScopePtr bodyScope = nullptr; Scope2* bodyScope = nullptr;
TypePackId returnType = nullptr; TypePackId returnType = nullptr;
std::vector<TypeId> genericTypes; std::vector<TypeId> genericTypes;
@ -690,17 +667,18 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
// generics properly. // generics properly.
if (hasGenerics) if (hasGenerics)
{ {
signatureScope = childScope(fn->location, parent); NotNull signatureBorrow = childScope(fn->location, parent);
signatureScope = signatureBorrow.get();
// We need to assign returnType before creating bodyScope so that the // We need to assign returnType before creating bodyScope so that the
// return type gets propogated to bodyScope. // return type gets propogated to bodyScope.
returnType = freshTypePack(signatureScope); returnType = freshTypePack(signatureBorrow);
signatureScope->returnType = returnType; signatureScope->returnType = returnType;
bodyScope = childScope(fn->body->location, signatureScope); bodyScope = childScope(fn->body->location, signatureBorrow).get();
std::vector<std::pair<Name, GenericTypeDefinition>> genericDefinitions = createGenerics(signatureScope, fn->generics); std::vector<std::pair<Name, GenericTypeDefinition>> genericDefinitions = createGenerics(signatureBorrow, fn->generics);
std::vector<std::pair<Name, GenericTypePackDefinition>> genericPackDefinitions = createGenericPacks(signatureScope, fn->genericPacks); std::vector<std::pair<Name, GenericTypePackDefinition>> genericPackDefinitions = createGenericPacks(signatureBorrow, fn->genericPacks);
// We do not support default values on function generics, so we only // We do not support default values on function generics, so we only
// care about the types involved. // care about the types involved.
@ -718,10 +696,11 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
} }
else else
{ {
bodyScope = childScope(fn->body->location, parent); NotNull bodyBorrow = childScope(fn->body->location, parent);
bodyScope = bodyBorrow.get();
returnType = freshTypePack(bodyScope); returnType = freshTypePack(bodyBorrow);
bodyScope->returnType = returnType; bodyBorrow->returnType = returnType;
// To eliminate the need to branch on hasGenerics below, we say that the // To eliminate the need to branch on hasGenerics below, we say that the
// signature scope is the body scope when there is no real signature // signature scope is the body scope when there is no real signature
@ -729,24 +708,27 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
signatureScope = bodyScope; signatureScope = bodyScope;
} }
NotNull bodyBorrow = NotNull(bodyScope);
NotNull signatureBorrow = NotNull(signatureScope);
if (fn->returnAnnotation) if (fn->returnAnnotation)
{ {
TypePackId annotatedRetType = resolveTypePack(signatureScope, *fn->returnAnnotation); TypePackId annotatedRetType = resolveTypePack(signatureBorrow, *fn->returnAnnotation);
addConstraint(signatureScope, PackSubtypeConstraint{returnType, annotatedRetType}); addConstraint(signatureBorrow, PackSubtypeConstraint{returnType, annotatedRetType});
} }
std::vector<TypeId> argTypes; std::vector<TypeId> argTypes;
for (AstLocal* local : fn->args) for (AstLocal* local : fn->args)
{ {
TypeId t = freshType(signatureScope); TypeId t = freshType(signatureBorrow);
argTypes.push_back(t); argTypes.push_back(t);
signatureScope->bindings[local] = Binding{t, local->location}; signatureScope->bindings[local] = t;
if (local->annotation) if (local->annotation)
{ {
TypeId argAnnotation = resolveType(signatureScope, local->annotation); TypeId argAnnotation = resolveType(signatureBorrow, local->annotation);
addConstraint(signatureScope, SubtypeConstraint{t, argAnnotation}); addConstraint(signatureBorrow, SubtypeConstraint{t, argAnnotation});
} }
} }
@ -759,7 +741,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
actualFunction.genericPacks = std::move(genericTypePacks); actualFunction.genericPacks = std::move(genericTypePacks);
TypeId actualFunctionType = arena->addType(std::move(actualFunction)); TypeId actualFunctionType = arena->addType(std::move(actualFunction));
LUAU_ASSERT(actualFunctionType); lluz_ASSERT(actualFunctionType);
astTypes[fn] = actualFunctionType; astTypes[fn] = actualFunctionType;
return { return {
@ -767,11 +749,11 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
// Undo the workaround we made above: if there's no signature scope, // Undo the workaround we made above: if there's no signature scope,
// don't report it. // don't report it.
/* signatureScope */ hasGenerics ? signatureScope : nullptr, /* signatureScope */ hasGenerics ? signatureScope : nullptr,
/* bodyScope */ bodyScope, /* bodyScope */ bodyBorrow,
}; };
} }
void ConstraintGraphBuilder::checkFunctionBody(const ScopePtr& scope, AstExprFunction* fn) void ConstraintGraphBuilder::checkFunctionBody(NotNull<Scope2> scope, AstExprFunction* fn)
{ {
visitBlockWithoutChildScope(scope, fn->body); visitBlockWithoutChildScope(scope, fn->body);
@ -784,7 +766,7 @@ void ConstraintGraphBuilder::checkFunctionBody(const ScopePtr& scope, AstExprFun
} }
} }
TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty) TypeId ConstraintGraphBuilder::resolveType(NotNull<Scope2> scope, AstType* ty)
{ {
TypeId result = nullptr; TypeId result = nullptr;
@ -792,8 +774,8 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty)
{ {
// TODO: Support imported types w/ require tracing. // TODO: Support imported types w/ require tracing.
// TODO: Support generic type references. // TODO: Support generic type references.
LUAU_ASSERT(!ref->prefix); lluz_ASSERT(!ref->prefix);
LUAU_ASSERT(!ref->hasParameterList); lluz_ASSERT(!ref->hasParameterList);
// TODO: If it doesn't exist, should we introduce a free binding? // TODO: If it doesn't exist, should we introduce a free binding?
// This is probably important for handling type aliases. // This is probably important for handling type aliases.
@ -829,7 +811,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty)
{ {
// TODO: Recursion limit. // TODO: Recursion limit.
bool hasGenerics = fn->generics.size > 0 || fn->genericPacks.size > 0; bool hasGenerics = fn->generics.size > 0 || fn->genericPacks.size > 0;
ScopePtr signatureScope = nullptr; Scope2* signatureScope = nullptr;
std::vector<TypeId> genericTypes; std::vector<TypeId> genericTypes;
std::vector<TypePackId> genericTypePacks; std::vector<TypePackId> genericTypePacks;
@ -838,21 +820,22 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty)
// for the generic bindings to live on. // for the generic bindings to live on.
if (hasGenerics) if (hasGenerics)
{ {
signatureScope = childScope(fn->location, scope); NotNull<Scope2> signatureBorrow = childScope(fn->location, scope);
signatureScope = signatureBorrow.get();
std::vector<std::pair<Name, GenericTypeDefinition>> genericDefinitions = createGenerics(signatureScope, fn->generics); std::vector<std::pair<Name, GenericTypeDefinition>> genericDefinitions = createGenerics(signatureBorrow, fn->generics);
std::vector<std::pair<Name, GenericTypePackDefinition>> genericPackDefinitions = createGenericPacks(signatureScope, fn->genericPacks); std::vector<std::pair<Name, GenericTypePackDefinition>> genericPackDefinitions = createGenericPacks(signatureBorrow, fn->genericPacks);
for (const auto& [name, g] : genericDefinitions) for (const auto& [name, g] : genericDefinitions)
{ {
genericTypes.push_back(g.ty); genericTypes.push_back(g.ty);
signatureScope->typeBindings[name] = g.ty; signatureBorrow->typeBindings[name] = g.ty;
} }
for (const auto& [name, g] : genericPackDefinitions) for (const auto& [name, g] : genericPackDefinitions)
{ {
genericTypePacks.push_back(g.tp); genericTypePacks.push_back(g.tp);
signatureScope->typePackBindings[name] = g.tp; signatureBorrow->typePackBindings[name] = g.tp;
} }
} }
else else
@ -860,11 +843,13 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty)
// To eliminate the need to branch on hasGenerics below, we say that // To eliminate the need to branch on hasGenerics below, we say that
// the signature scope is the parent scope if we don't have // the signature scope is the parent scope if we don't have
// generics. // generics.
signatureScope = scope; signatureScope = scope.get();
} }
TypePackId argTypes = resolveTypePack(signatureScope, fn->argTypes); NotNull<Scope2> signatureBorrow(signatureScope);
TypePackId returnTypes = resolveTypePack(signatureScope, fn->returnTypes);
TypePackId argTypes = resolveTypePack(signatureBorrow, fn->argTypes);
TypePackId returnTypes = resolveTypePack(signatureBorrow, fn->returnTypes);
// TODO: FunctionTypeVar needs a pointer to the scope so that we know // TODO: FunctionTypeVar needs a pointer to the scope so that we know
// how to quantify/instantiate it. // how to quantify/instantiate it.
@ -934,7 +919,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty)
} }
else else
{ {
LUAU_ASSERT(0); lluz_ASSERT(0);
result = singletonTypes.errorRecoveryType(); result = singletonTypes.errorRecoveryType();
} }
@ -942,7 +927,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty)
return result; return result;
} }
TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, AstTypePack* tp) TypePackId ConstraintGraphBuilder::resolveTypePack(NotNull<Scope2> scope, AstTypePack* tp)
{ {
TypePackId result; TypePackId result;
if (auto expl = tp->as<AstTypePackExplicit>()) if (auto expl = tp->as<AstTypePackExplicit>())
@ -956,11 +941,11 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, AstTyp
} }
else if (auto gen = tp->as<AstTypePackGeneric>()) else if (auto gen = tp->as<AstTypePackGeneric>())
{ {
result = arena->addTypePack(TypePackVar{GenericTypePack{scope.get(), gen->genericName.value}}); result = arena->addTypePack(TypePackVar{GenericTypePack{scope, gen->genericName.value}});
} }
else else
{ {
LUAU_ASSERT(0); lluz_ASSERT(0);
result = singletonTypes.errorRecoveryTypePack(); result = singletonTypes.errorRecoveryTypePack();
} }
@ -968,7 +953,7 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, AstTyp
return result; return result;
} }
TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, const AstTypeList& list) TypePackId ConstraintGraphBuilder::resolveTypePack(NotNull<Scope2> scope, const AstTypeList& list)
{ {
std::vector<TypeId> head; std::vector<TypeId> head;
@ -986,12 +971,12 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, const
return arena->addTypePack(TypePack{head, tail}); return arena->addTypePack(TypePack{head, tail});
} }
std::vector<std::pair<Name, GenericTypeDefinition>> ConstraintGraphBuilder::createGenerics(const ScopePtr& scope, AstArray<AstGenericType> generics) std::vector<std::pair<Name, GenericTypeDefinition>> ConstraintGraphBuilder::createGenerics(NotNull<Scope2> scope, AstArray<AstGenericType> generics)
{ {
std::vector<std::pair<Name, GenericTypeDefinition>> result; std::vector<std::pair<Name, GenericTypeDefinition>> result;
for (const auto& generic : generics) for (const auto& generic : generics)
{ {
TypeId genericTy = arena->addType(GenericTypeVar{scope.get(), generic.name.value}); TypeId genericTy = arena->addType(GenericTypeVar{scope, generic.name.value});
std::optional<TypeId> defaultTy = std::nullopt; std::optional<TypeId> defaultTy = std::nullopt;
if (generic.defaultValue) if (generic.defaultValue)
@ -1007,12 +992,12 @@ std::vector<std::pair<Name, GenericTypeDefinition>> ConstraintGraphBuilder::crea
} }
std::vector<std::pair<Name, GenericTypePackDefinition>> ConstraintGraphBuilder::createGenericPacks( std::vector<std::pair<Name, GenericTypePackDefinition>> ConstraintGraphBuilder::createGenericPacks(
const ScopePtr& scope, AstArray<AstGenericTypePack> generics) NotNull<Scope2> scope, AstArray<AstGenericTypePack> generics)
{ {
std::vector<std::pair<Name, GenericTypePackDefinition>> result; std::vector<std::pair<Name, GenericTypePackDefinition>> result;
for (const auto& generic : generics) for (const auto& generic : generics)
{ {
TypePackId genericTy = arena->addTypePack(TypePackVar{GenericTypePack{scope.get(), generic.name.value}}); TypePackId genericTy = arena->addTypePack(TypePackVar{GenericTypePack{scope, generic.name.value}});
std::optional<TypePackId> defaultTy = std::nullopt; std::optional<TypePackId> defaultTy = std::nullopt;
if (generic.defaultValue) if (generic.defaultValue)
@ -1027,7 +1012,7 @@ std::vector<std::pair<Name, GenericTypePackDefinition>> ConstraintGraphBuilder::
return result; return result;
} }
TypeId ConstraintGraphBuilder::flattenPack(const ScopePtr& scope, Location location, TypePackId tp) TypeId ConstraintGraphBuilder::flattenPack(NotNull<Scope2> scope, Location location, TypePackId tp)
{ {
if (auto f = first(tp)) if (auto f = first(tp))
return *f; return *f;
@ -1053,10 +1038,10 @@ void ConstraintGraphBuilder::reportCodeTooComplex(Location location)
struct GlobalPrepopulator : AstVisitor struct GlobalPrepopulator : AstVisitor
{ {
const NotNull<Scope> globalScope; const NotNull<Scope2> globalScope;
const NotNull<TypeArena> arena; const NotNull<TypeArena> arena;
GlobalPrepopulator(NotNull<Scope> globalScope, NotNull<TypeArena> arena) GlobalPrepopulator(NotNull<Scope2> globalScope, NotNull<TypeArena> arena)
: globalScope(globalScope) : globalScope(globalScope)
, arena(arena) , arena(arena)
{ {
@ -1065,33 +1050,33 @@ struct GlobalPrepopulator : AstVisitor
bool visit(AstStatFunction* function) override bool visit(AstStatFunction* function) override
{ {
if (AstExprGlobal* g = function->name->as<AstExprGlobal>()) if (AstExprGlobal* g = function->name->as<AstExprGlobal>())
globalScope->bindings[g->name] = Binding{arena->addType(BlockedTypeVar{})}; globalScope->bindings[g->name] = arena->addType(BlockedTypeVar{});
return true; return true;
} }
}; };
void ConstraintGraphBuilder::prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program) void ConstraintGraphBuilder::prepopulateGlobalScope(NotNull<Scope2> globalScope, AstStatBlock* program)
{ {
GlobalPrepopulator gp{NotNull{globalScope.get()}, arena}; GlobalPrepopulator gp{NotNull{globalScope}, arena};
program->visit(&gp); program->visit(&gp);
} }
void collectConstraints(std::vector<NotNull<Constraint>>& result, NotNull<Scope> scope) void collectConstraints(std::vector<NotNull<Constraint>>& result, NotNull<Scope2> scope)
{ {
for (const auto& c : scope->constraints) for (const auto& c : scope->constraints)
result.push_back(NotNull{c.get()}); result.push_back(NotNull{c.get()});
for (NotNull<Scope> child : scope->children) for (NotNull<Scope2> child : scope->children)
collectConstraints(result, child); collectConstraints(result, child);
} }
std::vector<NotNull<Constraint>> collectConstraints(NotNull<Scope> rootScope) std::vector<NotNull<Constraint>> collectConstraints(NotNull<Scope2> rootScope)
{ {
std::vector<NotNull<Constraint>> result; std::vector<NotNull<Constraint>> result;
collectConstraints(result, rootScope); collectConstraints(result, rootScope);
return result; return result;
} }
} // namespace Luau } // namespace lluz

View file

@ -1,51 +1,51 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/ConstraintSolver.h" #include "lluz/ConstraintSolver.h"
#include "Luau/Instantiation.h" #include "lluz/Instantiation.h"
#include "Luau/Location.h" #include "lluz/Location.h"
#include "Luau/Quantify.h" #include "lluz/Quantify.h"
#include "Luau/ToString.h" #include "lluz/ToString.h"
#include "Luau/Unifier.h" #include "lluz/Unifier.h"
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false); lluz_FASTFLAGVARIABLE(DebugLluLogSolver, false);
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false); lluz_FASTFLAGVARIABLE(DebugLluLogSolverToJson, false);
namespace Luau namespace lluz
{ {
[[maybe_unused]] static void dumpBindings(NotNull<Scope> scope, ToStringOptions& opts) [[maybe_unused]] static void dumpBindings(NotNull<Scope2> scope, ToStringOptions& opts)
{ {
for (const auto& [k, v] : scope->bindings) for (const auto& [k, v] : scope->bindings)
{ {
auto d = toStringDetailed(v.typeId, opts); auto d = toStringDetailed(v, opts);
opts.nameMap = d.nameMap; opts.nameMap = d.nameMap;
printf("\t%s : %s\n", k.c_str(), d.name.c_str()); printf("\t%s : %s\n", k.c_str(), d.name.c_str());
} }
for (NotNull<Scope> child : scope->children) for (NotNull<Scope2> child : scope->children)
dumpBindings(child, opts); dumpBindings(child, opts);
} }
static void dumpConstraints(NotNull<Scope> scope, ToStringOptions& opts) static void dumpConstraints(NotNull<Scope2> scope, ToStringOptions& opts)
{ {
for (const ConstraintPtr& c : scope->constraints) for (const ConstraintPtr& c : scope->constraints)
{ {
printf("\t%s\n", toString(*c, opts).c_str()); printf("\t%s\n", toString(*c, opts).c_str());
} }
for (NotNull<Scope> child : scope->children) for (NotNull<Scope2> child : scope->children)
dumpConstraints(child, opts); dumpConstraints(child, opts);
} }
void dump(NotNull<Scope> rootScope, ToStringOptions& opts) void dump(NotNull<Scope2> rootScope, ToStringOptions& opts)
{ {
printf("constraints:\n"); printf(XorStr("constraints:\n"));
dumpConstraints(rootScope, opts); dumpConstraints(rootScope, opts);
} }
void dump(ConstraintSolver* cs, ToStringOptions& opts) void dump(ConstraintSolver* cs, ToStringOptions& opts)
{ {
printf("constraints:\n"); printf(XorStr("constraints:\n"));
for (const Constraint* c : cs->unsolvedConstraints) for (const Constraint* c : cs->unsolvedConstraints)
{ {
printf("\t%s\n", toString(*c, opts).c_str()); printf("\t%s\n", toString(*c, opts).c_str());
@ -55,7 +55,7 @@ void dump(ConstraintSolver* cs, ToStringOptions& opts)
} }
} }
ConstraintSolver::ConstraintSolver(TypeArena* arena, NotNull<Scope> rootScope) ConstraintSolver::ConstraintSolver(TypeArena* arena, NotNull<Scope2> rootScope)
: arena(arena) : arena(arena)
, constraints(collectConstraints(rootScope)) , constraints(collectConstraints(rootScope))
, rootScope(rootScope) , rootScope(rootScope)
@ -78,13 +78,13 @@ void ConstraintSolver::run()
ToStringOptions opts; ToStringOptions opts;
if (FFlag::DebugLuauLogSolver) if (FFlag::DebugLluLogSolver)
{ {
printf("Starting solver\n"); printf(XorStr("Starting solver\n"));
dump(this, opts); dump(this, opts);
} }
if (FFlag::DebugLuauLogSolverToJson) if (FFlag::DebugLluLogSolverToJson)
{ {
logger.captureBoundarySnapshot(rootScope, unsolvedConstraints); logger.captureBoundarySnapshot(rootScope, unsolvedConstraints);
} }
@ -102,9 +102,9 @@ void ConstraintSolver::run()
continue; continue;
} }
std::string saveMe = FFlag::DebugLuauLogSolver ? toString(*c, opts) : std::string{}; std::string saveMe = FFlag::DebugLluLogSolver ? toString(*c, opts) : std::string{};
if (FFlag::DebugLuauLogSolverToJson) if (FFlag::DebugLluLogSolverToJson)
{ {
logger.prepareStepSnapshot(rootScope, c, unsolvedConstraints); logger.prepareStepSnapshot(rootScope, c, unsolvedConstraints);
} }
@ -117,15 +117,15 @@ void ConstraintSolver::run()
{ {
unsolvedConstraints.erase(unsolvedConstraints.begin() + i); unsolvedConstraints.erase(unsolvedConstraints.begin() + i);
if (FFlag::DebugLuauLogSolverToJson) if (FFlag::DebugLluLogSolverToJson)
{ {
logger.commitPreparedStepSnapshot(); logger.commitPreparedStepSnapshot();
} }
if (FFlag::DebugLuauLogSolver) if (FFlag::DebugLluLogSolver)
{ {
if (force) if (force)
printf("Force "); printf(XorStr("Force "));
printf("Dispatched\n\t%s\n", saveMe.c_str()); printf("Dispatched\n\t%s\n", saveMe.c_str());
dump(this, opts); dump(this, opts);
} }
@ -148,12 +148,12 @@ void ConstraintSolver::run()
progress |= runSolverPass(true); progress |= runSolverPass(true);
} while (progress); } while (progress);
if (FFlag::DebugLuauLogSolver) if (FFlag::DebugLluLogSolver)
{ {
dumpBindings(rootScope, opts); dumpBindings(rootScope, opts);
} }
if (FFlag::DebugLuauLogSolverToJson) if (FFlag::DebugLluLogSolverToJson)
{ {
logger.captureBoundarySnapshot(rootScope, unsolvedConstraints); logger.captureBoundarySnapshot(rootScope, unsolvedConstraints);
printf("Logger output:\n%s\n", logger.compileOutput().c_str()); printf("Logger output:\n%s\n", logger.compileOutput().c_str());
@ -187,7 +187,7 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
else if (auto nc = get<NameConstraint>(*constraint)) else if (auto nc = get<NameConstraint>(*constraint))
success = tryDispatch(*nc, constraint); success = tryDispatch(*nc, constraint);
else else
LUAU_ASSERT(0); lluz_ASSERT(0);
if (success) if (success)
{ {
@ -248,7 +248,7 @@ bool ConstraintSolver::tryDispatch(const InstantiationConstraint& c, NotNull<con
Instantiation inst(TxnLog::empty(), arena, TypeLevel{}); Instantiation inst(TxnLog::empty(), arena, TypeLevel{});
std::optional<TypeId> instantiated = inst.substitute(c.superType); std::optional<TypeId> instantiated = inst.substitute(c.superType);
LUAU_ASSERT(instantiated); // TODO FIXME HANDLE THIS lluz_ASSERT(instantiated); // TODO FIXME HANDLE THIS
if (isBlocked(c.subType)) if (isBlocked(c.subType))
asMutable(c.subType)->ty.emplace<BoundTypeVar>(*instantiated); asMutable(c.subType)->ty.emplace<BoundTypeVar>(*instantiated);
@ -270,7 +270,7 @@ bool ConstraintSolver::tryDispatch(const UnaryConstraint& c, NotNull<const Const
if (get<FreeTypeVar>(operandType)) if (get<FreeTypeVar>(operandType))
return block(operandType, constraint); return block(operandType, constraint);
LUAU_ASSERT(get<BlockedTypeVar>(c.resultType)); lluz_ASSERT(get<BlockedTypeVar>(c.resultType));
if (isNumber(operandType) || get<AnyTypeVar>(operandType) || get<ErrorTypeVar>(operandType)) if (isNumber(operandType) || get<AnyTypeVar>(operandType) || get<ErrorTypeVar>(operandType))
{ {
@ -278,7 +278,7 @@ bool ConstraintSolver::tryDispatch(const UnaryConstraint& c, NotNull<const Const
return true; return true;
} }
LUAU_ASSERT(0); // TODO metatable handling lluz_ASSERT(0); // TODO metatable handling
return false; return false;
} }
@ -364,7 +364,7 @@ void ConstraintSolver::unblock_(BlockedConstraintId progressed)
// `blockedConstraints` desynchronized at some point. This is problematic // `blockedConstraints` desynchronized at some point. This is problematic
// because we rely on this count being correct to skip over blocked // because we rely on this count being correct to skip over blocked
// constraints. // constraints.
LUAU_ASSERT(count > 0); lluz_ASSERT(count > 0);
count -= 1; count -= 1;
} }
@ -415,4 +415,4 @@ void ConstraintSolver::unify(TypePackId subPack, TypePackId superPack)
u.log.commit(); u.log.commit();
} }
} // namespace Luau } // namespace lluz

View file

@ -1,16 +1,16 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/ConstraintSolverLogger.h" #include "lluz/ConstraintSolverLogger.h"
namespace Luau namespace lluz
{ {
static std::string dumpScopeAndChildren(const Scope* scope, ToStringOptions& opts) static std::string dumpScopeAndChildren(const Scope2* scope, ToStringOptions& opts)
{ {
std::string output = "{\"bindings\":{"; std::string output = "{\"bindings\":{";
bool comma = false; bool comma = false;
for (const auto& [name, binding] : scope->bindings) for (const auto& [name, type] : scope->bindings)
{ {
if (comma) if (comma)
output += ","; output += ",";
@ -19,7 +19,7 @@ static std::string dumpScopeAndChildren(const Scope* scope, ToStringOptions& opt
output += name.c_str(); output += name.c_str();
output += "\": \""; output += "\": \"";
ToStringResult result = toStringDetailed(binding.typeId, opts); ToStringResult result = toStringDetailed(type, opts);
opts.nameMap = std::move(result.nameMap); opts.nameMap = std::move(result.nameMap);
output += result.name; output += result.name;
output += "\""; output += "\"";
@ -30,7 +30,7 @@ static std::string dumpScopeAndChildren(const Scope* scope, ToStringOptions& opt
output += "},\"children\":["; output += "},\"children\":[";
comma = false; comma = false;
for (const Scope* child : scope->children) for (const Scope2* child : scope->children)
{ {
if (comma) if (comma)
output += ","; output += ",";
@ -96,7 +96,7 @@ std::string ConstraintSolverLogger::compileOutput()
return output; return output;
} }
void ConstraintSolverLogger::captureBoundarySnapshot(const Scope* rootScope, std::vector<NotNull<const Constraint>>& unsolvedConstraints) void ConstraintSolverLogger::captureBoundarySnapshot(const Scope2* rootScope, std::vector<NotNull<const Constraint>>& unsolvedConstraints)
{ {
std::string snapshot = "{\"type\":\"boundary\",\"rootScope\":"; std::string snapshot = "{\"type\":\"boundary\",\"rootScope\":";
@ -109,9 +109,9 @@ void ConstraintSolverLogger::captureBoundarySnapshot(const Scope* rootScope, std
} }
void ConstraintSolverLogger::prepareStepSnapshot( void ConstraintSolverLogger::prepareStepSnapshot(
const Scope* rootScope, NotNull<const Constraint> current, std::vector<NotNull<const Constraint>>& unsolvedConstraints) const Scope2* rootScope, NotNull<const Constraint> current, std::vector<NotNull<const Constraint>>& unsolvedConstraints)
{ {
// LUAU_ASSERT(!preparedSnapshot); // lluz_ASSERT(!preparedSnapshot);
std::string snapshot = "{\"type\":\"step\",\"rootScope\":"; std::string snapshot = "{\"type\":\"step\",\"rootScope\":";
@ -136,4 +136,4 @@ void ConstraintSolverLogger::commitPreparedStepSnapshot()
} }
} }
} // namespace Luau } // namespace lluz

View file

@ -1,12 +1,14 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h" #include "lluz/BuiltinDefinitions.h"
LUAU_FASTFLAG(LuauUnknownAndNeverType) #include "..\..\..\..\Security\XorString.h"
namespace Luau lluz_FASTFLAG(LluCheckLenMT)
namespace lluz
{ {
static const std::string kBuiltinDefinitionLuaSrc = R"BUILTIN_SRC( static const std::string kBuiltinDefinitionLuaSrc = XorStr(R"BUILTIN_SRC(
declare bit32: { declare bit32: {
band: (...number) -> number, band: (...number) -> number,
@ -116,13 +118,14 @@ declare function typeof<T>(value: T): string
-- `assert` has a magic function attached that will give more detailed type information -- `assert` has a magic function attached that will give more detailed type information
declare function assert<T>(value: T, errorMessage: string?): T 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 tostring<T>(value: T): string
declare function tonumber<T>(value: T, radix: number?): number? declare function tonumber<T>(value: T, radix: number?): number?
declare function rawequal<T1, T2>(a: T1, b: T2): boolean declare function rawequal<T1, T2>(a: T1, b: T2): boolean
declare function rawget<K, V>(tab: {[K]: V}, k: K): V 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 rawset<K, V>(tab: {[K]: V}, k: K, v: V): {[K]: V}
declare function rawlen<K, V>(obj: {[K]: V} | string): number
declare function setfenv<T..., R...>(target: number | (T...) -> R..., env: {[string]: any}): ((T...) -> R...)? declare function setfenv<T..., R...>(target: number | (T...) -> R..., env: {[string]: any}): ((T...) -> R...)?
@ -199,19 +202,17 @@ declare utf8: {
-- Cannot use `typeof` here because it will produce a polytype when we expect a monotype. -- 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 declare function unpack<V>(tab: {V}, i: number?, j: number?): ...V
)BUILTIN_SRC"; )BUILTIN_SRC");
std::string getBuiltinDefinitionSource() std::string getBuiltinDefinitionSource()
{ {
std::string result = kBuiltinDefinitionLuaSrc; std::string result = kBuiltinDefinitionLuaSrc;
if (FFlag::LuauUnknownAndNeverType) // TODO: move this into kBuiltinDefinitionLuaSrc
result += "declare function error<T>(message: T, level: number?): never\n"; if (FFlag::LluCheckLenMT)
else result += XorStr("declare function rawlen<K, V>(obj: {[K]: V} | string): number\n");
result += "declare function error<T>(message: T, level: number?)\n";
return result; return result;
} }
} // namespace Luau } // namespace lluz

View file

@ -1,14 +1,14 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Error.h" #include "lluz/Error.h"
#include "Luau/Clone.h" #include "lluz/Clone.h"
#include "Luau/StringUtils.h" #include "lluz/StringUtils.h"
#include "Luau/ToString.h" #include "lluz/ToString.h"
#include <stdexcept> #include <stdexcept>
LUAU_FASTFLAGVARIABLE(LuauTypeMismatchModuleNameResolution, false) lluz_FASTFLAGVARIABLE(LluTypeMismatchModuleNameResolution, false)
LUAU_FASTFLAGVARIABLE(LuauUseInternalCompilerErrorException, false) lluz_FASTFLAGVARIABLE(LluUseInternalCompilerErrorException, false)
static std::string wrongNumberOfArgsString(size_t expectedCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false) static std::string wrongNumberOfArgsString(size_t expectedCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false)
{ {
@ -47,17 +47,17 @@ static std::string wrongNumberOfArgsString(size_t expectedCount, size_t actualCo
return s; return s;
} }
namespace Luau namespace lluz
{ {
struct ErrorConverter struct ErrorConverter
{ {
FileResolver* fileResolver = nullptr; FileResolver* fileResolver = nullptr;
std::string operator()(const Luau::TypeMismatch& tm) const std::string operator()(const lluz::TypeMismatch& tm) const
{ {
std::string givenTypeName = Luau::toString(tm.givenType); std::string givenTypeName = lluz::toString(tm.givenType);
std::string wantedTypeName = Luau::toString(tm.wantedType); std::string wantedTypeName = lluz::toString(tm.wantedType);
std::string result; std::string result;
@ -67,7 +67,7 @@ struct ErrorConverter
{ {
if (auto wantedDefinitionModule = getDefinitionModuleName(tm.wantedType)) if (auto wantedDefinitionModule = getDefinitionModuleName(tm.wantedType))
{ {
if (FFlag::LuauTypeMismatchModuleNameResolution && fileResolver != nullptr) if (FFlag::LluTypeMismatchModuleNameResolution && fileResolver != nullptr)
{ {
std::string givenModuleName = fileResolver->getHumanReadableModuleName(*givenDefinitionModule); std::string givenModuleName = fileResolver->getHumanReadableModuleName(*givenDefinitionModule);
std::string wantedModuleName = fileResolver->getHumanReadableModuleName(*wantedDefinitionModule); std::string wantedModuleName = fileResolver->getHumanReadableModuleName(*wantedDefinitionModule);
@ -93,13 +93,13 @@ struct ErrorConverter
if (!tm.reason.empty()) if (!tm.reason.empty())
result += tm.reason + " "; result += tm.reason + " ";
if (FFlag::LuauTypeMismatchModuleNameResolution) if (FFlag::LluTypeMismatchModuleNameResolution)
{ {
result += Luau::toString(*tm.error, TypeErrorToStringOptions{fileResolver}); result += lluz::toString(*tm.error, TypeErrorToStringOptions{fileResolver});
} }
else else
{ {
result += Luau::toString(*tm.error); result += lluz::toString(*tm.error);
} }
} }
else if (!tm.reason.empty()) else if (!tm.reason.empty())
@ -110,65 +110,65 @@ struct ErrorConverter
return result; return result;
} }
std::string operator()(const Luau::UnknownSymbol& e) const std::string operator()(const lluz::UnknownSymbol& e) const
{ {
switch (e.context) switch (e.context)
{ {
case UnknownSymbol::Binding: case UnknownSymbol::Binding:
return "Unknown global '" + e.name + "'"; return XorStr("Unknown global '") + e.name + "'";
case UnknownSymbol::Type: case UnknownSymbol::Type:
return "Unknown type '" + e.name + "'"; return XorStr("Unknown type '") + e.name + "'";
case UnknownSymbol::Generic: case UnknownSymbol::Generic:
return "Unknown generic '" + e.name + "'"; return XorStr("Unknown generic '") + e.name + "'";
} }
LUAU_ASSERT(!"Unexpected context for UnknownSymbol"); lluz_ASSERT(!XorStr("Unexpected context for UnknownSymbol"));
return ""; return XorStr("");
} }
std::string operator()(const Luau::UnknownProperty& e) const std::string operator()(const lluz::UnknownProperty& e) const
{ {
TypeId t = follow(e.table); TypeId t = follow(e.table);
if (get<TableTypeVar>(t)) if (get<TableTypeVar>(t))
return "Key '" + e.key + "' not found in table '" + Luau::toString(t) + "'"; return XorStr("Key '") + e.key + "' not found in table '" + lluz::toString(t) + "'";
else if (get<ClassTypeVar>(t)) else if (get<ClassTypeVar>(t))
return "Key '" + e.key + "' not found in class '" + Luau::toString(t) + "'"; return XorStr("Key '") + e.key + "' not found in class '" + lluz::toString(t) + "'";
else else
return "Type '" + Luau::toString(e.table) + "' does not have key '" + e.key + "'"; return XorStr("Type '") + lluz::toString(e.table) + "' does not have key '" + e.key + "'";
} }
std::string operator()(const Luau::NotATable& e) const std::string operator()(const lluz::NotATable& e) const
{ {
return "Expected type table, got '" + Luau::toString(e.ty) + "' instead"; return XorStr("Expected type table, got '") + lluz::toString(e.ty) + "' instead";
} }
std::string operator()(const Luau::CannotExtendTable& e) const std::string operator()(const lluz::CannotExtendTable& e) const
{ {
switch (e.context) switch (e.context)
{ {
case Luau::CannotExtendTable::Property: case lluz::CannotExtendTable::Property:
return "Cannot add property '" + e.prop + "' to table '" + Luau::toString(e.tableType) + "'"; return XorStr("Cannot add property '") + e.prop + "' to table '" + lluz::toString(e.tableType) + "'";
case Luau::CannotExtendTable::Metatable: case lluz::CannotExtendTable::Metatable:
return "Cannot add metatable to table '" + Luau::toString(e.tableType) + "'"; return XorStr("Cannot add metatable to table '") + lluz::toString(e.tableType) + "'";
case Luau::CannotExtendTable::Indexer: case lluz::CannotExtendTable::Indexer:
return "Cannot add indexer to table '" + Luau::toString(e.tableType) + "'"; return XorStr("Cannot add indexer to table '") + lluz::toString(e.tableType) + "'";
} }
LUAU_ASSERT(!"Unknown context"); lluz_ASSERT(!XorStr("Unknown context"));
return ""; return XorStr("");
} }
std::string operator()(const Luau::OnlyTablesCanHaveMethods& e) const std::string operator()(const lluz::OnlyTablesCanHaveMethods& e) const
{ {
return "Cannot add method to non-table type '" + Luau::toString(e.tableType) + "'"; return XorStr("Cannot add method to non-table type '") + lluz::toString(e.tableType) + "'";
} }
std::string operator()(const Luau::DuplicateTypeDefinition& e) const std::string operator()(const lluz::DuplicateTypeDefinition& e) const
{ {
return "Redefinition of type '" + e.name + "', previously defined at line " + std::to_string(e.previousLocation.begin.line + 1); return XorStr("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 std::string operator()(const lluz::CountMismatch& e) const
{ {
const std::string expectedS = e.expected == 1 ? "" : "s"; const std::string expectedS = e.expected == 1 ? "" : "s";
const std::string actualS = e.actual == 1 ? "" : "s"; const std::string actualS = e.actual == 1 ? "" : "s";
@ -177,45 +177,45 @@ struct ErrorConverter
switch (e.context) switch (e.context)
{ {
case CountMismatch::Return: case CountMismatch::Return:
return "Expected to return " + std::to_string(e.expected) + " value" + expectedS + ", but " + std::to_string(e.actual) + " " + return XorStr("Expected to return ") + std::to_string(e.expected) + " value" + expectedS + ", but " + std::to_string(e.actual) + " " +
actualVerb + " returned here"; actualVerb + " returned here";
case CountMismatch::Result: case CountMismatch::Result:
// It is alright if right hand side produces more values than the // It is alright if right hand side produces more values than the
// left hand side accepts. In this context consider only the opposite case. // left hand side accepts. In this context consider only the opposite case.
return "Function only returns " + std::to_string(e.expected) + " value" + expectedS + ". " + std::to_string(e.actual) + return XorStr("Function only returns ") + std::to_string(e.expected) + " value" + expectedS + ". " + std::to_string(e.actual) +
" are required here"; " are required here";
case CountMismatch::Arg: case CountMismatch::Arg:
return "Argument count mismatch. Function " + wrongNumberOfArgsString(e.expected, e.actual, /*argPrefix*/ nullptr, e.isVariadic); return XorStr("Argument count mismatch. Function ") + wrongNumberOfArgsString(e.expected, e.actual, /*argPrefix*/ nullptr, e.isVariadic);
} }
LUAU_ASSERT(!"Unknown context"); lluz_ASSERT(!XorStr("Unknown context"));
return ""; return XorStr("");
} }
std::string operator()(const Luau::FunctionDoesNotTakeSelf&) const std::string operator()(const lluz::FunctionDoesNotTakeSelf&) const
{ {
return std::string("This function does not take self. Did you mean to use a dot instead of a colon?"); return std::string(XorStr("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 std::string operator()(const lluz::FunctionRequiresSelf& e) const
{ {
return "This function must be called with self. Did you mean to use a colon instead of a dot?"; return XorStr("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 std::string operator()(const lluz::OccursCheckFailed&) const
{ {
return "Type contains a self-recursive construct that cannot be resolved"; return XorStr("Type contains a self-recursive construct that cannot be resolved");
} }
std::string operator()(const Luau::UnknownRequire& e) const std::string operator()(const lluz::UnknownRequire& e) const
{ {
if (e.modulePath.empty()) if (e.modulePath.empty())
return "Unknown require: unsupported path"; return XorStr("Unknown require: unsupported path");
else else
return "Unknown require: " + e.modulePath; return XorStr("Unknown require: ") + e.modulePath;
} }
std::string operator()(const Luau::IncorrectGenericParameterCount& e) const std::string operator()(const lluz::IncorrectGenericParameterCount& e) const
{ {
std::string name = e.name; std::string name = e.name;
if (!e.typeFun.typeParams.empty() || !e.typeFun.typePackParams.empty()) if (!e.typeFun.typeParams.empty() || !e.typeFun.typePackParams.empty())
@ -246,29 +246,29 @@ struct ErrorConverter
} }
if (e.typeFun.typeParams.size() != e.actualParameters) if (e.typeFun.typeParams.size() != e.actualParameters)
return "Generic type '" + name + "' " + return XorStr("Generic type '") + name + "' " +
wrongNumberOfArgsString(e.typeFun.typeParams.size(), e.actualParameters, "type", !e.typeFun.typePackParams.empty()); wrongNumberOfArgsString(e.typeFun.typeParams.size(), e.actualParameters, "type", !e.typeFun.typePackParams.empty());
return "Generic type '" + name + "' " + return XorStr("Generic type '") + name + "' " +
wrongNumberOfArgsString(e.typeFun.typePackParams.size(), e.actualPackParameters, "type pack", /*isVariadic*/ false); wrongNumberOfArgsString(e.typeFun.typePackParams.size(), e.actualPackParameters, "type pack", /*isVariadic*/ false);
} }
std::string operator()(const Luau::SyntaxError& e) const std::string operator()(const lluz::SyntaxError& e) const
{ {
return e.message; return e.message;
} }
std::string operator()(const Luau::CodeTooComplex&) const std::string operator()(const lluz::CodeTooComplex&) const
{ {
return "Code is too complex to typecheck! Consider simplifying the code around this area"; return XorStr("Code is too complex to typecheck! Consider simplifying the code around this area");
} }
std::string operator()(const Luau::UnificationTooComplex&) const std::string operator()(const lluz::UnificationTooComplex&) const
{ {
return "Internal error: Code is too complex to typecheck! Consider adding type annotations around this area"; return XorStr("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 operator()(const lluz::UnknownPropButFoundLikeProp& e) const
{ {
std::string candidatesSuggestion = "Did you mean "; std::string candidatesSuggestion = "Did you mean ";
if (e.candidates.size() != 1) if (e.candidates.size() != 1)
@ -297,34 +297,34 @@ struct ErrorConverter
return s; return s;
} }
std::string operator()(const Luau::GenericError& e) const std::string operator()(const lluz::GenericError& e) const
{ {
return e.message; return e.message;
} }
std::string operator()(const Luau::InternalError& e) const std::string operator()(const lluz::InternalError& e) const
{ {
return e.message; return e.message;
} }
std::string operator()(const Luau::CannotCallNonFunction& e) const std::string operator()(const lluz::CannotCallNonFunction& e) const
{ {
return "Cannot call non-function " + toString(e.ty); return XorStr("Cannot call non-function ") + toString(e.ty);
} }
std::string operator()(const Luau::ExtraInformation& e) const std::string operator()(const lluz::ExtraInformation& e) const
{ {
return e.message; return e.message;
} }
std::string operator()(const Luau::DeprecatedApiUsed& e) const std::string operator()(const lluz::DeprecatedApiUsed& e) const
{ {
return "The property ." + e.symbol + " is deprecated. Use ." + e.useInstead + " instead."; return XorStr("The property .") + e.symbol + " is deprecated. Use ." + e.useInstead + " instead.";
} }
std::string operator()(const Luau::ModuleHasCyclicDependency& e) const std::string operator()(const lluz::ModuleHasCyclicDependency& e) const
{ {
if (e.cycle.empty()) if (e.cycle.empty())
return "Cyclic module dependency detected"; return XorStr("Cyclic module dependency detected");
std::string s = "Cyclic module dependency: "; std::string s = "Cyclic module dependency: ";
@ -342,17 +342,17 @@ struct ErrorConverter
return s; return s;
} }
std::string operator()(const Luau::FunctionExitsWithoutReturning& e) const std::string operator()(const lluz::FunctionExitsWithoutReturning& e) const
{ {
return "Not all codepaths in this function return '" + toString(e.expectedReturnType) + "'."; return XorStr("Not all codepaths in this function return '") + toString(e.expectedReturnType) + "'.";
} }
std::string operator()(const Luau::IllegalRequire& e) const std::string operator()(const lluz::IllegalRequire& e) const
{ {
return "Cannot require module " + e.moduleName + ": " + e.reason; return XorStr("Cannot require module ") + e.moduleName + ": " + e.reason;
} }
std::string operator()(const Luau::MissingProperties& e) const std::string operator()(const lluz::MissingProperties& e) const
{ {
std::string s = "Table type '" + toString(e.subType) + "' not compatible with type '" + toString(e.superType) + "' because the former"; std::string s = "Table type '" + toString(e.subType) + "' not compatible with type '" + toString(e.superType) + "' because the former";
@ -385,21 +385,21 @@ struct ErrorConverter
return s; return s;
} }
std::string operator()(const Luau::DuplicateGenericParameter& e) const std::string operator()(const lluz::DuplicateGenericParameter& e) const
{ {
return "Duplicate type parameter '" + e.parameterName + "'"; return XorStr("Duplicate type parameter '") + e.parameterName + "'";
} }
std::string operator()(const Luau::CannotInferBinaryOperation& e) const std::string operator()(const lluz::CannotInferBinaryOperation& e) const
{ {
std::string ss = "Unknown type used in " + toString(e.op); std::string ss = "Unknown type used in " + toString(e.op);
switch (e.kind) switch (e.kind)
{ {
case Luau::CannotInferBinaryOperation::Comparison: case lluz::CannotInferBinaryOperation::Comparison:
ss += " comparison"; ss += " comparison";
break; break;
case Luau::CannotInferBinaryOperation::Operation: case lluz::CannotInferBinaryOperation::Operation:
ss += " operation"; ss += " operation";
} }
@ -409,28 +409,28 @@ struct ErrorConverter
return ss; return ss;
} }
std::string operator()(const Luau::SwappedGenericTypeParameter& e) const std::string operator()(const lluz::SwappedGenericTypeParameter& e) const
{ {
switch (e.kind) switch (e.kind)
{ {
case Luau::SwappedGenericTypeParameter::Type: case lluz::SwappedGenericTypeParameter::Type:
return "Variadic type parameter '" + e.name + "...' is used as a regular generic type; consider changing '" + e.name + "...' to '" + return XorStr("Variadic type parameter '") + e.name + "...' is used as a regular generic type; consider changing '" + e.name + "...' to '" +
e.name + "' in the generic argument list"; e.name + "' in the generic argument list";
case Luau::SwappedGenericTypeParameter::Pack: case lluz::SwappedGenericTypeParameter::Pack:
return "Generic type '" + e.name + "' is used as a variadic type parameter; consider changing '" + e.name + "' to '" + e.name + return XorStr("Generic type '") + e.name + "' is used as a variadic type parameter; consider changing '" + e.name + "' to '" + e.name +
"...' in the generic argument list"; "...' in the generic argument list";
default: default:
LUAU_ASSERT(!"Unknown kind"); lluz_ASSERT(!XorStr("Unknown kind"));
return ""; return XorStr("");
} }
} }
std::string operator()(const Luau::OptionalValueAccess& e) const std::string operator()(const lluz::OptionalValueAccess& e) const
{ {
return "Value of type '" + toString(e.optional) + "' could be nil"; return XorStr("Value of type '") + toString(e.optional) + "' could be nil";
} }
std::string operator()(const Luau::MissingUnionProperty& e) const std::string operator()(const lluz::MissingUnionProperty& e) const
{ {
std::string ss = "Key '" + e.key + "' is missing from "; std::string ss = "Key '" + e.key + "' is missing from ";
@ -450,12 +450,12 @@ struct ErrorConverter
std::string operator()(const TypesAreUnrelated& e) const std::string operator()(const TypesAreUnrelated& e) const
{ {
return "Cannot cast '" + toString(e.left) + "' into '" + toString(e.right) + "' because the types are unrelated"; return XorStr("Cannot cast '") + toString(e.left) + "' into '" + toString(e.right) + "' because the types are unrelated";
} }
std::string operator()(const NormalizationTooComplex&) const std::string operator()(const NormalizationTooComplex&) const
{ {
return "Code is too complex to typecheck! Consider simplifying the code around this area"; return XorStr("Code is too complex to typecheck! Consider simplifying the code around this area");
} }
}; };
@ -463,15 +463,15 @@ struct InvalidNameChecker
{ {
std::string invalidName = "%error-id%"; std::string invalidName = "%error-id%";
bool operator()(const Luau::UnknownProperty& e) const bool operator()(const lluz::UnknownProperty& e) const
{ {
return e.key == invalidName; return e.key == invalidName;
} }
bool operator()(const Luau::CannotExtendTable& e) const bool operator()(const lluz::CannotExtendTable& e) const
{ {
return e.prop == invalidName; return e.prop == invalidName;
} }
bool operator()(const Luau::DuplicateTypeDefinition& e) const bool operator()(const lluz::DuplicateTypeDefinition& e) const
{ {
return e.name == invalidName; return e.name == invalidName;
} }
@ -723,19 +723,19 @@ std::string toString(const TypeError& error)
std::string toString(const TypeError& error, TypeErrorToStringOptions options) std::string toString(const TypeError& error, TypeErrorToStringOptions options)
{ {
ErrorConverter converter{options.fileResolver}; ErrorConverter converter{options.fileResolver};
return Luau::visit(converter, error.data); return lluz::visit(converter, error.data);
} }
bool containsParseErrorName(const TypeError& error) bool containsParseErrorName(const TypeError& error)
{ {
return Luau::visit(InvalidNameChecker{}, error.data); return lluz::visit(InvalidNameChecker{}, error.data);
} }
template<typename T> template<typename T>
void copyError(T& e, TypeArena& destArena, CloneState cloneState) void copyError(T& e, TypeArena& destArena, CloneState cloneState)
{ {
auto clone = [&](auto&& ty) { auto clone = [&](auto&& ty) {
return ::Luau::clone(ty, destArena, cloneState); return ::lluz::clone(ty, destArena, cloneState);
}; };
auto visitErrorData = [&](auto&& e) { auto visitErrorData = [&](auto&& e) {
@ -867,7 +867,7 @@ void copyError(T& e, TypeArena& destArena, CloneState cloneState)
{ {
} }
else else
static_assert(always_false_v<T>, "Non-exhaustive type switch"); static_assert(always_false_v<T>, XorStr("Non-exhaustive type switch"));
} }
void copyErrors(ErrorVec& errors, TypeArena& destArena) void copyErrors(ErrorVec& errors, TypeArena& destArena)
@ -878,8 +878,8 @@ void copyErrors(ErrorVec& errors, TypeArena& destArena)
copyError(e, destArena, cloneState); copyError(e, destArena, cloneState);
}; };
LUAU_ASSERT(!destArena.typeVars.isFrozen()); lluz_ASSERT(!destArena.typeVars.isFrozen());
LUAU_ASSERT(!destArena.typePacks.isFrozen()); lluz_ASSERT(!destArena.typePacks.isFrozen());
for (TypeError& error : errors) for (TypeError& error : errors)
visit(visitErrorData, error.data); visit(visitErrorData, error.data);
@ -887,7 +887,7 @@ void copyErrors(ErrorVec& errors, TypeArena& destArena)
void InternalErrorReporter::ice(const std::string& message, const Location& location) void InternalErrorReporter::ice(const std::string& message, const Location& location)
{ {
if (FFlag::LuauUseInternalCompilerErrorException) if (FFlag::LluUseInternalCompilerErrorException)
{ {
InternalCompilerError error(message, moduleName, location); InternalCompilerError error(message, moduleName, location);
@ -909,7 +909,7 @@ void InternalErrorReporter::ice(const std::string& message, const Location& loca
void InternalErrorReporter::ice(const std::string& message) void InternalErrorReporter::ice(const std::string& message)
{ {
if (FFlag::LuauUseInternalCompilerErrorException) if (FFlag::LluUseInternalCompilerErrorException)
{ {
InternalCompilerError error(message, moduleName); InternalCompilerError error(message, moduleName);
@ -934,4 +934,4 @@ const char* InternalCompilerError::what() const throw()
return this->message.data(); return this->message.data();
} }
} // namespace Luau } // namespace lluz

View file

@ -1,33 +1,33 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Frontend.h" #include "lluz/Frontend.h"
#include "Luau/Clone.h" #include "lluz/Clone.h"
#include "Luau/Common.h" #include "lluz/Common.h"
#include "Luau/Config.h" #include "lluz/Config.h"
#include "Luau/ConstraintGraphBuilder.h" #include "lluz/ConstraintGraphBuilder.h"
#include "Luau/ConstraintSolver.h" #include "lluz/ConstraintSolver.h"
#include "Luau/FileResolver.h" #include "lluz/FileResolver.h"
#include "Luau/Parser.h" #include "lluz/Parser.h"
#include "Luau/Scope.h" #include "lluz/Scope.h"
#include "Luau/StringUtils.h" #include "lluz/StringUtils.h"
#include "Luau/TimeTrace.h" #include "lluz/TimeTrace.h"
#include "Luau/TypeChecker2.h" #include "lluz/TypeChecker2.h"
#include "Luau/TypeInfer.h" #include "lluz/TypeInfer.h"
#include "Luau/Variant.h" #include "lluz/Variant.h"
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
#include <stdexcept> #include <stdexcept>
LUAU_FASTINT(LuauTypeInferIterationLimit) lluz_FASTINT(LluTypeInferIterationLimit)
LUAU_FASTINT(LuauTarjanChildLimit) lluz_FASTINT(LluTarjanChildLimit)
LUAU_FASTFLAG(LuauInferInNoCheckMode) lluz_FASTFLAG(LluInferInNoCheckMode)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false) lluz_FASTFLAGVARIABLE(LluKnowsTheDataModel3, false)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteDynamicLimits, false) lluz_FASTFLAGVARIABLE(LlluztocompleteDynamicLimits, false)
LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100) lluz_FASTINTVARIABLE(LlluztocompleteCheckTimeoutMs, 100)
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false) lluz_FASTFLAGVARIABLE(DebugLluDeferredConstraintResolution, false)
namespace Luau namespace lluz
{ {
std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments) std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments)
@ -37,13 +37,13 @@ std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments)
if (!hc.header) if (!hc.header)
continue; continue;
if (hc.content == "nocheck") if (hc.content == XorStr("nocheck"))
return Mode::NoCheck; return Mode::NoCheck;
if (hc.content == "nonstrict") if (hc.content == XorStr("nonstrict"))
return Mode::Nonstrict; return Mode::Nonstrict;
if (hc.content == "strict") if (hc.content == XorStr("strict"))
return Mode::Strict; return Mode::Strict;
} }
@ -79,20 +79,20 @@ static void generateDocumentationSymbols(TypeId ty, const std::string& rootName)
LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, ScopePtr targetScope, std::string_view source, const std::string& packageName) LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, ScopePtr targetScope, std::string_view source, const std::string& packageName)
{ {
LUAU_TIMETRACE_SCOPE("loadDefinitionFile", "Frontend"); lluz_TIMETRACE_SCOPE(XorStr("loadDefinitionFile", "Frontend"));
Luau::Allocator allocator; lluz::Allocator allocator;
Luau::AstNameTable names(allocator); lluz::AstNameTable names(allocator);
ParseOptions options; ParseOptions options;
options.allowDeclarationSyntax = true; options.allowDeclarationSyntax = true;
Luau::ParseResult parseResult = Luau::Parser::parse(source.data(), source.size(), names, allocator, options); lluz::ParseResult parseResult = lluz::Parser::parse(source.data(), source.size(), names, allocator, options);
if (parseResult.errors.size() > 0) if (parseResult.errors.size() > 0)
return LoadDefinitionFileResult{false, parseResult, nullptr}; return LoadDefinitionFileResult{false, parseResult, nullptr};
Luau::SourceModule module; lluz::SourceModule module;
module.root = parseResult.root; module.root = parseResult.root;
module.mode = Mode::Definition; module.mode = Mode::Definition;
@ -169,7 +169,7 @@ std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleN
auto it = segments.begin(); auto it = segments.begin();
if (*it == "script" && !currentModuleName.empty()) if (*it == XorStr("script") && !currentModuleName.empty())
{ {
result = split(currentModuleName, '/'); result = split(currentModuleName, '/');
++it; ++it;
@ -177,13 +177,13 @@ std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleN
for (; it != segments.end(); ++it) for (; it != segments.end(); ++it)
{ {
if (result.size() > 1 && *it == "Parent") if (result.size() > 1 && *it == XorStr("Parent"))
result.pop_back(); result.pop_back();
else else
result.push_back(*it); result.push_back(*it);
} }
return join(result, "/"); return join(result, XorStr("/"));
} }
std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const AstExpr& pathExpr) std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const AstExpr& pathExpr)
@ -271,7 +271,7 @@ std::vector<RequireCycle> getRequireCycles(
if (top == nullptr) if (top == nullptr)
{ {
// special marker for post-order processing // special marker for post-order processing
LUAU_ASSERT(!path.empty()); lluz_ASSERT(!path.empty());
top = path.back(); top = path.back();
path.pop_back(); path.pop_back();
@ -353,8 +353,8 @@ FrontendModuleResolver::FrontendModuleResolver(Frontend* frontend)
CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOptions> optionOverride) CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOptions> optionOverride)
{ {
LUAU_TIMETRACE_SCOPE("Frontend::check", "Frontend"); lluz_TIMETRACE_SCOPE(XorStr("Frontend::check", "Frontend"));
LUAU_TIMETRACE_ARGUMENT("name", name.c_str()); lluz_TIMETRACE_ARGUMENT("name", name.c_str());
FrontendOptions frontendOptions = optionOverride.value_or(options); FrontendOptions frontendOptions = optionOverride.value_or(options);
CheckResult checkResult; CheckResult checkResult;
@ -386,17 +386,17 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
// Keep track of which AST nodes we've reported cycles in // Keep track of which AST nodes we've reported cycles in
std::unordered_set<AstNode*> reportedCycles; std::unordered_set<AstNode*> reportedCycles;
double autocompleteTimeLimit = FInt::LuauAutocompleteCheckTimeoutMs / 1000.0; double autocompleteTimeLimit = FInt::LlluztocompleteCheckTimeoutMs / 1000.0;
for (const ModuleName& moduleName : buildQueue) for (const ModuleName& moduleName : buildQueue)
{ {
LUAU_ASSERT(sourceNodes.count(moduleName)); lluz_ASSERT(sourceNodes.count(moduleName));
SourceNode& sourceNode = sourceNodes[moduleName]; SourceNode& sourceNode = sourceNodes[moduleName];
if (!sourceNode.hasDirtyModule(frontendOptions.forAutocomplete)) if (!sourceNode.hasDirtyModule(frontendOptions.forAutocomplete))
continue; continue;
LUAU_ASSERT(sourceModules.count(moduleName)); lluz_ASSERT(sourceModules.count(moduleName));
SourceModule& sourceModule = sourceModules[moduleName]; SourceModule& sourceModule = sourceModules[moduleName];
const Config& config = configResolver->getConfig(moduleName); const Config& config = configResolver->getConfig(moduleName);
@ -430,20 +430,20 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
else else
typeCheckerForAutocomplete.finishTime = std::nullopt; typeCheckerForAutocomplete.finishTime = std::nullopt;
if (FFlag::LuauAutocompleteDynamicLimits) if (FFlag::LlluztocompleteDynamicLimits)
{ {
// TODO: This is a dirty ad hoc solution for autocomplete timeouts // TODO: This is a dirty ad hoc solution for autocomplete timeouts
// We are trying to dynamically adjust our existing limits to lower total typechecking time under the limit // We are trying to dynamically adjust our existing limits to lower total typechecking time under the limit
// so that we'll have type information for the whole file at lower quality instead of a full abort in the middle // so that we'll have type information for the whole file at lower quality instead of a full abort in the middle
if (FInt::LuauTarjanChildLimit > 0) if (FInt::LluTarjanChildLimit > 0)
typeCheckerForAutocomplete.instantiationChildLimit = typeCheckerForAutocomplete.instantiationChildLimit =
std::max(1, int(FInt::LuauTarjanChildLimit * sourceNode.autocompleteLimitsMult)); std::max(1, int(FInt::LluTarjanChildLimit * sourceNode.autocompleteLimitsMult));
else else
typeCheckerForAutocomplete.instantiationChildLimit = std::nullopt; typeCheckerForAutocomplete.instantiationChildLimit = std::nullopt;
if (FInt::LuauTypeInferIterationLimit > 0) if (FInt::LluTypeInferIterationLimit > 0)
typeCheckerForAutocomplete.unifierIterationLimit = typeCheckerForAutocomplete.unifierIterationLimit =
std::max(1, int(FInt::LuauTypeInferIterationLimit * sourceNode.autocompleteLimitsMult)); std::max(1, int(FInt::LluTypeInferIterationLimit * sourceNode.autocompleteLimitsMult));
else else
typeCheckerForAutocomplete.unifierIterationLimit = std::nullopt; typeCheckerForAutocomplete.unifierIterationLimit = std::nullopt;
} }
@ -457,10 +457,10 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
{ {
checkResult.timeoutHits.push_back(moduleName); checkResult.timeoutHits.push_back(moduleName);
if (FFlag::LuauAutocompleteDynamicLimits) if (FFlag::LlluztocompleteDynamicLimits)
sourceNode.autocompleteLimitsMult = sourceNode.autocompleteLimitsMult / 2.0; sourceNode.autocompleteLimitsMult = sourceNode.autocompleteLimitsMult / 2.0;
} }
else if (FFlag::LuauAutocompleteDynamicLimits && duration < autocompleteTimeLimit / 2.0) else if (FFlag::LlluztocompleteDynamicLimits && duration < autocompleteTimeLimit / 2.0)
{ {
sourceNode.autocompleteLimitsMult = std::min(sourceNode.autocompleteLimitsMult * 2.0, 1.0); sourceNode.autocompleteLimitsMult = std::min(sourceNode.autocompleteLimitsMult * 2.0, 1.0);
} }
@ -474,7 +474,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
typeChecker.requireCycles = requireCycles; typeChecker.requireCycles = requireCycles;
ModulePtr module = FFlag::DebugLuauDeferredConstraintResolution ? check(sourceModule, mode, environmentScope) ModulePtr module = FFlag::DebugLluDeferredConstraintResolution ? check(sourceModule, mode, environmentScope)
: typeChecker.check(sourceModule, mode, environmentScope); : typeChecker.check(sourceModule, mode, environmentScope);
stats.timeCheck += getTimestamp() - timestamp; stats.timeCheck += getTimestamp() - timestamp;
@ -529,8 +529,8 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
bool Frontend::parseGraph(std::vector<ModuleName>& buildQueue, CheckResult& checkResult, const ModuleName& root, bool forAutocomplete) bool Frontend::parseGraph(std::vector<ModuleName>& buildQueue, CheckResult& checkResult, const ModuleName& root, bool forAutocomplete)
{ {
LUAU_TIMETRACE_SCOPE("Frontend::parseGraph", "Frontend"); lluz_TIMETRACE_SCOPE(XorStr("Frontend::parseGraph", "Frontend"));
LUAU_TIMETRACE_ARGUMENT("root", root.c_str()); lluz_TIMETRACE_ARGUMENT("root", root.c_str());
// https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search // https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search
enum Mark enum Mark
@ -559,14 +559,14 @@ bool Frontend::parseGraph(std::vector<ModuleName>& buildQueue, CheckResult& chec
if (top == nullptr) if (top == nullptr)
{ {
// special marker for post-order processing // special marker for post-order processing
LUAU_ASSERT(!path.empty()); lluz_ASSERT(!path.empty());
top = path.back(); top = path.back();
path.pop_back(); path.pop_back();
// note: topseen ref gets invalidated in any seen[] access, beware - only one seen[] access per iteration! // note: topseen ref gets invalidated in any seen[] access, beware - only one seen[] access per iteration!
Mark& topseen = seen[top]; Mark& topseen = seen[top];
LUAU_ASSERT(topseen == Temporary); lluz_ASSERT(topseen == Temporary);
topseen = Permanent; topseen = Permanent;
buildQueue.push_back(top->name); buildQueue.push_back(top->name);
@ -648,10 +648,10 @@ ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config
return result; return result;
} }
LintResult Frontend::lint(const ModuleName& name, std::optional<Luau::LintOptions> enabledLintWarnings) LintResult Frontend::lint(const ModuleName& name, std::optional<lluz::LintOptions> enabledLintWarnings)
{ {
LUAU_TIMETRACE_SCOPE("Frontend::lint", "Frontend"); lluz_TIMETRACE_SCOPE(XorStr("Frontend::lint", "Frontend"));
LUAU_TIMETRACE_ARGUMENT("name", name.c_str()); lluz_TIMETRACE_ARGUMENT("name", name.c_str());
CheckResult checkResult; CheckResult checkResult;
auto [_sourceNode, sourceModule] = getSourceNode(checkResult, name); auto [_sourceNode, sourceModule] = getSourceNode(checkResult, name);
@ -662,10 +662,33 @@ LintResult Frontend::lint(const ModuleName& name, std::optional<Luau::LintOption
return lint(*sourceModule, enabledLintWarnings); return lint(*sourceModule, enabledLintWarnings);
} }
LintResult Frontend::lint(const SourceModule& module, std::optional<Luau::LintOptions> enabledLintWarnings) std::pair<SourceModule, LintResult> Frontend::lintFragment(std::string_view source, std::optional<lluz::LintOptions> enabledLintWarnings)
{ {
LUAU_TIMETRACE_SCOPE("Frontend::lint", "Frontend"); lluz_TIMETRACE_SCOPE(XorStr("Frontend::lintFragment", "Frontend"));
LUAU_TIMETRACE_ARGUMENT("module", module.name.c_str());
const Config& config = configResolver->getConfig(XorStr(""));
SourceModule sourceModule = parse(ModuleName{}, source, config.parseOptions);
uint64_t ignoreLints = LintWarning::parseMask(sourceModule.hotcomments);
lluz::LintOptions lintOptions = enabledLintWarnings.value_or(config.enabledLint);
lintOptions.warningMask &= ~ignoreLints;
double timestamp = getTimestamp();
std::vector<LintWarning> warnings = lluz::lint(sourceModule.root, *sourceModule.names.get(), typeChecker.globalScope, nullptr,
sourceModule.hotcomments, enabledLintWarnings.value_or(config.enabledLint));
stats.timeLint += getTimestamp() - timestamp;
return {std::move(sourceModule), classifyLints(warnings, config)};
}
LintResult Frontend::lint(const SourceModule& module, std::optional<lluz::LintOptions> enabledLintWarnings)
{
lluz_TIMETRACE_SCOPE(XorStr("Frontend::lint", "Frontend"));
lluz_TIMETRACE_ARGUMENT("module", module.name.c_str());
const Config& config = configResolver->getConfig(module.name); const Config& config = configResolver->getConfig(module.name);
@ -677,12 +700,12 @@ LintResult Frontend::lint(const SourceModule& module, std::optional<Luau::LintOp
Mode mode = module.mode.value_or(config.mode); Mode mode = module.mode.value_or(config.mode);
if (mode != Mode::NoCheck) if (mode != Mode::NoCheck)
{ {
options.disableWarning(Luau::LintWarning::Code_UnknownGlobal); options.disableWarning(lluz::LintWarning::Code_UnknownGlobal);
} }
if (mode == Mode::Strict) if (mode == Mode::Strict)
{ {
options.disableWarning(Luau::LintWarning::Code_ImplicitReturn); options.disableWarning(lluz::LintWarning::Code_ImplicitReturn);
} }
ScopePtr environmentScope = getModuleEnvironment(module, config); ScopePtr environmentScope = getModuleEnvironment(module, config);
@ -691,7 +714,7 @@ LintResult Frontend::lint(const SourceModule& module, std::optional<Luau::LintOp
double timestamp = getTimestamp(); double timestamp = getTimestamp();
std::vector<LintWarning> warnings = Luau::lint(module.root, *module.names, environmentScope, modulePtr.get(), module.hotcomments, options); std::vector<LintWarning> warnings = lluz::lint(module.root, *module.names, environmentScope, modulePtr.get(), module.hotcomments, options);
stats.timeLint += getTimestamp() - timestamp; stats.timeLint += getTimestamp() - timestamp;
@ -729,7 +752,7 @@ void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* marked
ModuleName next = std::move(queue.back()); ModuleName next = std::move(queue.back());
queue.pop_back(); queue.pop_back();
LUAU_ASSERT(sourceNodes.count(next) > 0); lluz_ASSERT(sourceNodes.count(next) > 0);
SourceNode& sourceNode = sourceNodes[next]; SourceNode& sourceNode = sourceNodes[next];
if (markedDirty) if (markedDirty)
@ -766,35 +789,35 @@ const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) cons
return const_cast<Frontend*>(this)->getSourceModule(moduleName); return const_cast<Frontend*>(this)->getSourceModule(moduleName);
} }
NotNull<Scope> Frontend::getGlobalScope() NotNull<Scope2> Frontend::getGlobalScope2()
{ {
if (!globalScope) if (!globalScope2)
{ {
const SingletonTypes& singletonTypes = getSingletonTypes(); const SingletonTypes& singletonTypes = getSingletonTypes();
globalScope = std::make_unique<Scope>(singletonTypes.anyTypePack); globalScope2 = std::make_unique<Scope2>();
globalScope->typeBindings["nil"] = singletonTypes.nilType; globalScope2->typeBindings["nil"] = singletonTypes.nilType;
globalScope->typeBindings["number"] = singletonTypes.numberType; globalScope2->typeBindings["number"] = singletonTypes.numberType;
globalScope->typeBindings["string"] = singletonTypes.stringType; globalScope2->typeBindings["string"] = singletonTypes.stringType;
globalScope->typeBindings["boolean"] = singletonTypes.booleanType; globalScope2->typeBindings["boolean"] = singletonTypes.booleanType;
globalScope->typeBindings["thread"] = singletonTypes.threadType; globalScope2->typeBindings["thread"] = singletonTypes.threadType;
} }
return NotNull(globalScope.get()); return NotNull(globalScope2.get());
} }
ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope) ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope)
{ {
ModulePtr result = std::make_shared<Module>(); ModulePtr result = std::make_shared<Module>();
ConstraintGraphBuilder cgb{sourceModule.name, &result->internalTypes, NotNull(&iceHandler), getGlobalScope()}; ConstraintGraphBuilder cgb{sourceModule.name, &result->internalTypes, NotNull(&iceHandler), getGlobalScope2()};
cgb.visit(sourceModule.root); cgb.visit(sourceModule.root);
result->errors = std::move(cgb.errors); result->errors = std::move(cgb.errors);
ConstraintSolver cs{&result->internalTypes, NotNull(cgb.rootScope)}; ConstraintSolver cs{&result->internalTypes, NotNull(cgb.rootScope)};
cs.run(); cs.run();
result->scopes = std::move(cgb.scopes); result->scope2s = std::move(cgb.scopes);
result->astTypes = std::move(cgb.astTypes); result->astTypes = std::move(cgb.astTypes);
result->astTypePacks = std::move(cgb.astTypePacks); result->astTypePacks = std::move(cgb.astTypePacks);
result->astOriginalCallTypes = std::move(cgb.astOriginalCallTypes); result->astOriginalCallTypes = std::move(cgb.astOriginalCallTypes);
@ -803,7 +826,7 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, const Sco
result->clonePublicInterface(iceHandler); result->clonePublicInterface(iceHandler);
Luau::check(sourceModule, result.get()); lluz::check(sourceModule, result.get());
return result; return result;
} }
@ -811,8 +834,8 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, const Sco
// Read AST into sourceModules if necessary. Trace require()s. Report parse errors. // Read AST into sourceModules if necessary. Trace require()s. Report parse errors.
std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(CheckResult& checkResult, const ModuleName& name) std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(CheckResult& checkResult, const ModuleName& name)
{ {
LUAU_TIMETRACE_SCOPE("Frontend::getSourceNode", "Frontend"); lluz_TIMETRACE_SCOPE(XorStr("Frontend::getSourceNode", "Frontend"));
LUAU_TIMETRACE_ARGUMENT("name", name.c_str()); lluz_TIMETRACE_ARGUMENT("name", name.c_str());
auto it = sourceNodes.find(name); auto it = sourceNodes.find(name);
if (it != sourceNodes.end() && !it->second.hasDirtySourceModule()) if (it != sourceNodes.end() && !it->second.hasDirtySourceModule())
@ -822,7 +845,7 @@ std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(CheckResult& check
return {&it->second, &moduleIt->second}; return {&it->second, &moduleIt->second};
else else
{ {
LUAU_ASSERT(!"Everything in sourceNodes should also be in sourceModules"); lluz_ASSERT(!XorStr("Everything in sourceNodes should also be in sourceModules"));
return {&it->second, nullptr}; return {&it->second, nullptr};
} }
} }
@ -884,19 +907,19 @@ std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(CheckResult& check
* If the file has syntax errors, we report them and synthesize an empty AST if it's not available. * 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 * 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. * 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 * We also translate lluz::ParseError into a lluz::TypeError so that we can use a vector<TypeError> to describe the
* result of the check() * result of the check()
*/ */
SourceModule Frontend::parse(const ModuleName& name, std::string_view src, const ParseOptions& parseOptions) SourceModule Frontend::parse(const ModuleName& name, std::string_view src, const ParseOptions& parseOptions)
{ {
LUAU_TIMETRACE_SCOPE("Frontend::parse", "Frontend"); lluz_TIMETRACE_SCOPE(XorStr("Frontend::parse", "Frontend"));
LUAU_TIMETRACE_ARGUMENT("name", name.c_str()); lluz_TIMETRACE_ARGUMENT("name", name.c_str());
SourceModule sourceModule; SourceModule sourceModule;
double timestamp = getTimestamp(); double timestamp = getTimestamp();
auto parseResult = Luau::Parser::parse(src.data(), src.size(), *sourceModule.names, *sourceModule.allocator, parseOptions); auto parseResult = lluz::Parser::parse(src.data(), src.size(), *sourceModule.names, *sourceModule.allocator, parseOptions);
stats.timeParse += getTimestamp() - timestamp; stats.timeParse += getTimestamp() - timestamp;
stats.files++; stats.files++;
@ -935,7 +958,7 @@ std::optional<ModuleInfo> FrontendModuleResolver::resolveModuleInfo(const Module
{ {
// CLI-43699 // CLI-43699
// If we can't find the current module name, that's because we bypassed the frontend's initializer // If we can't find the current module name, that's because we bypassed the frontend's initializer
// and called typeChecker.check directly. // and called typeChecker.check directly. (This is done by autocompleteSource, for example).
// In that case, requires will always fail. // In that case, requires will always fail.
return std::nullopt; return std::nullopt;
} }
@ -970,7 +993,7 @@ std::string FrontendModuleResolver::getHumanReadableModuleName(const ModuleName&
ScopePtr Frontend::addEnvironment(const std::string& environmentName) ScopePtr Frontend::addEnvironment(const std::string& environmentName)
{ {
LUAU_ASSERT(environments.count(environmentName) == 0); lluz_ASSERT(environments.count(environmentName) == 0);
if (environments.count(environmentName) == 0) if (environments.count(environmentName) == 0)
{ {
@ -984,14 +1007,14 @@ ScopePtr Frontend::addEnvironment(const std::string& environmentName)
ScopePtr Frontend::getEnvironmentScope(const std::string& environmentName) ScopePtr Frontend::getEnvironmentScope(const std::string& environmentName)
{ {
LUAU_ASSERT(environments.count(environmentName) > 0); lluz_ASSERT(environments.count(environmentName) > 0);
return environments[environmentName]; return environments[environmentName];
} }
void Frontend::registerBuiltinDefinition(const std::string& name, std::function<void(TypeChecker&, ScopePtr)> applicator) void Frontend::registerBuiltinDefinition(const std::string& name, std::function<void(TypeChecker&, ScopePtr)> applicator)
{ {
LUAU_ASSERT(builtinDefinitions.count(name) == 0); lluz_ASSERT(builtinDefinitions.count(name) == 0);
if (builtinDefinitions.count(name) == 0) if (builtinDefinitions.count(name) == 0)
builtinDefinitions[name] = applicator; builtinDefinitions[name] = applicator;
@ -999,7 +1022,7 @@ void Frontend::registerBuiltinDefinition(const std::string& name, std::function<
void Frontend::applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName) void Frontend::applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName)
{ {
LUAU_ASSERT(builtinDefinitions.count(definitionName) > 0); lluz_ASSERT(builtinDefinitions.count(definitionName) > 0);
if (builtinDefinitions.count(definitionName) > 0) if (builtinDefinitions.count(definitionName) > 0)
builtinDefinitions[definitionName](typeChecker, getEnvironmentScope(environmentName)); builtinDefinitions[definitionName](typeChecker, getEnvironmentScope(environmentName));
@ -1033,4 +1056,4 @@ void Frontend::clear()
requireTrace.clear(); requireTrace.clear();
} }
} // namespace Luau } // namespace lluz

View file

@ -1,10 +1,10 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Common.h" #include "lluz/Common.h"
#include "Luau/Instantiation.h" #include "lluz/Instantiation.h"
#include "Luau/TxnLog.h" #include "lluz/TxnLog.h"
#include "Luau/TypeArena.h" #include "lluz/TypeArena.h"
namespace Luau namespace lluz
{ {
bool Instantiation::isDirty(TypeId ty) bool Instantiation::isDirty(TypeId ty)
@ -38,7 +38,7 @@ bool Instantiation::ignoreChildren(TypeId ty)
TypeId Instantiation::clean(TypeId ty) TypeId Instantiation::clean(TypeId ty)
{ {
const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty); const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty);
LUAU_ASSERT(ftv); lluz_ASSERT(ftv);
FunctionTypeVar clone = FunctionTypeVar{level, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf}; FunctionTypeVar clone = FunctionTypeVar{level, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf};
clone.magicFunction = ftv->magicFunction; clone.magicFunction = ftv->magicFunction;
@ -60,7 +60,7 @@ TypeId Instantiation::clean(TypeId ty)
TypePackId Instantiation::clean(TypePackId tp) TypePackId Instantiation::clean(TypePackId tp)
{ {
LUAU_ASSERT(false); lluz_ASSERT(false);
return tp; return tp;
} }
@ -104,7 +104,7 @@ bool ReplaceGenerics::isDirty(TypePackId tp)
TypeId ReplaceGenerics::clean(TypeId ty) TypeId ReplaceGenerics::clean(TypeId ty)
{ {
LUAU_ASSERT(isDirty(ty)); lluz_ASSERT(isDirty(ty));
if (const TableTypeVar* ttv = log->getMutable<TableTypeVar>(ty)) if (const TableTypeVar* ttv = log->getMutable<TableTypeVar>(ty))
{ {
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, level, TableState::Free}; TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, level, TableState::Free};
@ -117,8 +117,8 @@ TypeId ReplaceGenerics::clean(TypeId ty)
TypePackId ReplaceGenerics::clean(TypePackId tp) TypePackId ReplaceGenerics::clean(TypePackId tp)
{ {
LUAU_ASSERT(isDirty(tp)); lluz_ASSERT(isDirty(tp));
return addTypePack(TypePackVar(FreeTypePack{level})); return addTypePack(TypePackVar(FreeTypePack{level}));
} }
} // namespace Luau } // namespace lluz

View file

@ -1,8 +1,8 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/IostreamHelpers.h" #include "lluz/IostreamHelpers.h"
#include "Luau/ToString.h" #include "lluz/ToString.h"
namespace Luau namespace lluz
{ {
std::ostream& operator<<(std::ostream& stream, const Position& position) std::ostream& operator<<(std::ostream& stream, const Position& position)
@ -189,7 +189,7 @@ static void errorToString(std::ostream& stream, const T& err)
else if constexpr (std::is_same_v<T, NormalizationTooComplex>) else if constexpr (std::is_same_v<T, NormalizationTooComplex>)
stream << "NormalizationTooComplex { }"; stream << "NormalizationTooComplex { }";
else else
static_assert(always_false_v<T>, "Non-exhaustive type switch"); static_assert(always_false_v<T>, XorStr("Non-exhaustive type switch"));
} }
std::ostream& operator<<(std::ostream& stream, const TypeErrorData& data) std::ostream& operator<<(std::ostream& stream, const TypeErrorData& data)
@ -221,4 +221,4 @@ std::ostream& operator<<(std::ostream& stream, const TypePackVar& tv)
return stream << toString(tv); return stream << toString(tv);
} }
} // namespace Luau } // namespace lluz

View file

@ -1,12 +1,11 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/JsonEncoder.h" #include "lluz/JsonEncoder.h"
#include "Luau/Ast.h" #include "lluz/Ast.h"
#include "Luau/ParseResult.h" #include "lluz/StringUtils.h"
#include "Luau/StringUtils.h" #include "lluz/Common.h"
#include "Luau/Common.h"
namespace Luau namespace lluz
{ {
struct AstJsonEncoder : public AstVisitor struct AstJsonEncoder : public AstVisitor
@ -22,7 +21,7 @@ struct AstJsonEncoder : public AstVisitor
std::string str() std::string str()
{ {
return join(chunks, ""); return join(chunks, XorStr(""));
} }
bool pushComma() bool pushComma()
@ -76,58 +75,49 @@ struct AstJsonEncoder : public AstVisitor
writeRaw(std::string_view{&c, 1}); writeRaw(std::string_view{&c, 1});
} }
void writeType(std::string_view propValue)
{
write("type", propValue);
}
template<typename T> template<typename T>
void write(std::string_view propName, const T& value) void write(std::string_view propName, const T& value)
{ {
if (comma) if (comma)
writeRaw(","); writeRaw(XorStr(","));
comma = true; comma = true;
writeRaw("\""); writeRaw(XorStr("\""));
writeRaw(propName); writeRaw(propName);
writeRaw("\":"); writeRaw(XorStr("\":"));
write(value); write(value);
} }
void write(bool b) void write(bool b)
{ {
if (b) if (b)
writeRaw("true"); writeRaw(XorStr("true"));
else else
writeRaw("false"); writeRaw(XorStr("false"));
} }
void write(double d) void write(double d)
{ {
char b[32]; char b[256];
snprintf(b, sizeof(b), "%.17g", d); sprintf(b, "%g", d);
writeRaw(b); writeRaw(b);
} }
void writeString(std::string_view sv) void writeString(std::string_view sv)
{ {
// TODO escape more accurately? // TODO escape more accurately?
writeRaw("\""); writeRaw(XorStr("\""));
for (char c : sv) for (char c : sv)
{ {
if (c == '"') if (c == '"')
writeRaw("\\\""); writeRaw(XorStr("\\\""));
else if (c == '\\') else if (c == '\0')
writeRaw("\\\\"); writeRaw(XorStr("\\\0"));
else if (c < ' ')
writeRaw(format("\\u%04x", c));
else if (c == '\n')
writeRaw("\\n");
else else
writeRaw(c); writeRaw(c);
} }
writeRaw("\""); writeRaw(XorStr("\""));
} }
void write(char c) void write(char c)
@ -160,7 +150,7 @@ struct AstJsonEncoder : public AstVisitor
} }
void write(std::nullptr_t) void write(std::nullptr_t)
{ {
writeRaw("null"); writeRaw(XorStr("null"));
} }
void write(std::string_view str) void write(std::string_view str)
{ {
@ -171,42 +161,41 @@ struct AstJsonEncoder : public AstVisitor
if (name) if (name)
write(*name); write(*name);
else else
writeRaw("null"); writeRaw(XorStr("null"));
} }
void write(AstName name) void write(AstName name)
{ {
writeString(name.value ? name.value : ""); writeString(name.value ? name.value : XorStr(""));
} }
void write(const Position& position) void write(const Position& position)
{ {
write(position.line); write(position.line);
writeRaw(","); writeRaw(XorStr(","));
write(position.column); write(position.column);
} }
void write(const Location& location) void write(const Location& location)
{ {
writeRaw("\""); writeRaw(XorStr("\""));
write(location.begin); write(location.begin);
writeRaw(" - "); writeRaw(XorStr(" - "));
write(location.end); write(location.end);
writeRaw("\""); writeRaw(XorStr("\""));
} }
void write(AstLocal* local) void write(AstLocal* local)
{ {
writeRaw("{"); writeRaw(XorStr("{"));
bool c = pushComma(); bool c = pushComma();
if (local->annotation != nullptr) if (local->annotation != nullptr)
write("luauType", local->annotation); write("type", local->annotation);
else else
write("luauType", nullptr); write("type", nullptr);
write("name", local->name); write("name", local->name);
writeType("AstLocal");
write("location", local->location); write("location", local->location);
popComma(c); popComma(c);
writeRaw("}"); writeRaw(XorStr("}"));
} }
void writeNode(AstNode* node) void writeNode(AstNode* node)
@ -217,13 +206,13 @@ struct AstJsonEncoder : public AstVisitor
template<typename F> template<typename F>
void writeNode(AstNode* node, std::string_view name, F&& f) void writeNode(AstNode* node, std::string_view name, F&& f)
{ {
writeRaw("{"); writeRaw(XorStr("{"));
bool c = pushComma(); bool c = pushComma();
writeType(name); write("type", name);
writeNode(node); writeNode(node);
f(); f();
popComma(c); popComma(c);
writeRaw("}"); writeRaw(XorStr("}"));
} }
void write(AstNode* node) void write(AstNode* node)
@ -286,18 +275,18 @@ struct AstJsonEncoder : public AstVisitor
template<typename T> template<typename T>
void write(AstArray<T> arr) void write(AstArray<T> arr)
{ {
writeRaw("["); writeRaw(XorStr("["));
bool comma = false; bool comma = false;
for (const auto& a : arr) for (const auto& a : arr)
{ {
if (comma) if (comma)
writeRaw(","); writeRaw(XorStr(","));
else else
comma = true; comma = true;
write(a); write(a);
} }
writeRaw("]"); writeRaw(XorStr("]"));
} }
void write(AstArray<char> arr) void write(AstArray<char> arr)
@ -362,43 +351,40 @@ struct AstJsonEncoder : public AstVisitor
if (typeList) if (typeList)
write(*typeList); write(*typeList);
else else
writeRaw("null"); writeRaw(XorStr("null"));
} }
void write(const AstTypeList& typeList) void write(const AstTypeList& typeList)
{ {
writeRaw("{"); writeRaw(XorStr("{"));
bool c = pushComma(); bool c = pushComma();
writeType("AstTypeList");
write("types", typeList.types); write("types", typeList.types);
if (typeList.tailType) if (typeList.tailType)
write("tailType", typeList.tailType); write("tailType", typeList.tailType);
popComma(c); popComma(c);
writeRaw("}"); writeRaw(XorStr("}"));
} }
void write(const AstGenericType& genericType) void write(const AstGenericType& genericType)
{ {
writeRaw("{"); writeRaw(XorStr("{"));
bool c = pushComma(); bool c = pushComma();
writeType("AstGenericType");
write("name", genericType.name); write("name", genericType.name);
if (genericType.defaultValue) if (genericType.defaultValue)
write("luauType", genericType.defaultValue); write("type", genericType.defaultValue);
popComma(c); popComma(c);
writeRaw("}"); writeRaw(XorStr("}"));
} }
void write(const AstGenericTypePack& genericTypePack) void write(const AstGenericTypePack& genericTypePack)
{ {
writeRaw("{"); writeRaw(XorStr("{"));
bool c = pushComma(); bool c = pushComma();
writeType("AstGenericTypePack");
write("name", genericTypePack.name); write("name", genericTypePack.name);
if (genericTypePack.defaultValue) if (genericTypePack.defaultValue)
write("luauType", genericTypePack.defaultValue); write("type", genericTypePack.defaultValue);
popComma(c); popComma(c);
writeRaw("}"); writeRaw(XorStr("}"));
} }
void write(AstExprTable::Item::Kind kind) void write(AstExprTable::Item::Kind kind)
@ -406,19 +392,18 @@ struct AstJsonEncoder : public AstVisitor
switch (kind) switch (kind)
{ {
case AstExprTable::Item::List: case AstExprTable::Item::List:
return writeString("item"); return writeString(XorStr("item"));
case AstExprTable::Item::Record: case AstExprTable::Item::Record:
return writeString("record"); return writeString(XorStr("record"));
case AstExprTable::Item::General: case AstExprTable::Item::General:
return writeString("general"); return writeString(XorStr("general"));
} }
} }
void write(const AstExprTable::Item& item) void write(const AstExprTable::Item& item)
{ {
writeRaw("{"); writeRaw(XorStr("{"));
bool c = pushComma(); bool c = pushComma();
writeType("AstExprTableItem");
write("kind", item.kind); write("kind", item.kind);
switch (item.kind) switch (item.kind)
{ {
@ -431,18 +416,7 @@ struct AstJsonEncoder : public AstVisitor
break; break;
} }
popComma(c); popComma(c);
writeRaw("}"); writeRaw(XorStr("}"));
}
void write(class AstExprIfElse* node)
{
writeNode(node, "AstExprIfElse", [&]() {
PROP(condition);
PROP(hasThen);
PROP(trueExpr);
PROP(hasElse);
PROP(falseExpr);
});
} }
void write(class AstExprTable* node) void write(class AstExprTable* node)
@ -457,11 +431,11 @@ struct AstJsonEncoder : public AstVisitor
switch (op) switch (op)
{ {
case AstExprUnary::Not: case AstExprUnary::Not:
return writeString("Not"); return writeString(XorStr("not"));
case AstExprUnary::Minus: case AstExprUnary::Minus:
return writeString("Minus"); return writeString(XorStr("minus"));
case AstExprUnary::Len: case AstExprUnary::Len:
return writeString("Len"); return writeString(XorStr("len"));
} }
} }
@ -478,35 +452,35 @@ struct AstJsonEncoder : public AstVisitor
switch (op) switch (op)
{ {
case AstExprBinary::Add: case AstExprBinary::Add:
return writeString("Add"); return writeString(XorStr("Add"));
case AstExprBinary::Sub: case AstExprBinary::Sub:
return writeString("Sub"); return writeString(XorStr("Sub"));
case AstExprBinary::Mul: case AstExprBinary::Mul:
return writeString("Mul"); return writeString(XorStr("Mul"));
case AstExprBinary::Div: case AstExprBinary::Div:
return writeString("Div"); return writeString(XorStr("Div"));
case AstExprBinary::Mod: case AstExprBinary::Mod:
return writeString("Mod"); return writeString(XorStr("Mod"));
case AstExprBinary::Pow: case AstExprBinary::Pow:
return writeString("Pow"); return writeString(XorStr("Pow"));
case AstExprBinary::Concat: case AstExprBinary::Concat:
return writeString("Concat"); return writeString(XorStr("Concat"));
case AstExprBinary::CompareNe: case AstExprBinary::CompareNe:
return writeString("CompareNe"); return writeString(XorStr("CompareNe"));
case AstExprBinary::CompareEq: case AstExprBinary::CompareEq:
return writeString("CompareEq"); return writeString(XorStr("CompareEq"));
case AstExprBinary::CompareLt: case AstExprBinary::CompareLt:
return writeString("CompareLt"); return writeString(XorStr("CompareLt"));
case AstExprBinary::CompareLe: case AstExprBinary::CompareLe:
return writeString("CompareLe"); return writeString(XorStr("CompareLe"));
case AstExprBinary::CompareGt: case AstExprBinary::CompareGt:
return writeString("CompareGt"); return writeString(XorStr("CompareGt"));
case AstExprBinary::CompareGe: case AstExprBinary::CompareGe:
return writeString("CompareGe"); return writeString(XorStr("CompareGe"));
case AstExprBinary::And: case AstExprBinary::And:
return writeString("And"); return writeString(XorStr("And"));
case AstExprBinary::Or: case AstExprBinary::Or:
return writeString("Or"); return writeString(XorStr("Or"));
} }
} }
@ -538,18 +512,18 @@ struct AstJsonEncoder : public AstVisitor
void write(class AstStatBlock* node) void write(class AstStatBlock* node)
{ {
writeNode(node, "AstStatBlock", [&]() { writeNode(node, "AstStatBlock", [&]() {
writeRaw(",\"body\":["); writeRaw(XorStr(",\"body\":["));
bool comma = false; bool comma = false;
for (AstStat* stat : node->body) for (AstStat* stat : node->body)
{ {
if (comma) if (comma)
writeRaw(","); writeRaw(XorStr(","));
else else
comma = true; comma = true;
write(stat); write(stat);
} }
writeRaw("]"); writeRaw(XorStr("]"));
}); });
} }
@ -567,7 +541,7 @@ struct AstJsonEncoder : public AstVisitor
void write(class AstStatWhile* node) void write(class AstStatWhile* node)
{ {
writeNode(node, "AstStatWhile", [&]() { writeNode(node, "AtStatWhile", [&]() {
PROP(condition); PROP(condition);
PROP(body); PROP(body);
PROP(hasDo); PROP(hasDo);
@ -707,13 +681,12 @@ struct AstJsonEncoder : public AstVisitor
void write(const AstDeclaredClassProp& prop) void write(const AstDeclaredClassProp& prop)
{ {
writeRaw("{"); writeRaw(XorStr("{"));
bool c = pushComma(); bool c = pushComma();
write("name", prop.name); write("name", prop.name);
writeType("AstDeclaredClassProp"); write("type", prop.ty);
write("luauType", prop.ty);
popComma(c); popComma(c);
writeRaw("}"); writeRaw(XorStr("}"));
} }
void write(class AstStatDeclareClass* node) void write(class AstStatDeclareClass* node)
@ -754,16 +727,15 @@ struct AstJsonEncoder : public AstVisitor
void write(const AstTableProp& prop) void write(const AstTableProp& prop)
{ {
writeRaw("{"); writeRaw(XorStr("{"));
bool c = pushComma(); bool c = pushComma();
write("name", prop.name); write("name", prop.name);
writeType("AstTableProp");
write("location", prop.location); write("location", prop.location);
write("propType", prop.type); write("type", prop.type);
popComma(c); popComma(c);
writeRaw("}"); writeRaw(XorStr("}"));
} }
void write(class AstTypeTable* node) void write(class AstTypeTable* node)
@ -774,24 +746,6 @@ struct AstJsonEncoder : public AstVisitor
}); });
} }
void write(struct AstTableIndexer* indexer)
{
if (indexer)
{
writeRaw("{");
bool c = pushComma();
write("location", indexer->location);
write("indexType", indexer->indexType);
write("resultType", indexer->resultType);
popComma(c);
writeRaw("}");
}
else
{
writeRaw("null");
}
}
void write(class AstTypeFunction* node) void write(class AstTypeFunction* node)
{ {
writeNode(node, "AstTypeFunction", [&]() { writeNode(node, "AstTypeFunction", [&]() {
@ -882,12 +836,6 @@ struct AstJsonEncoder : public AstVisitor
return false; return false;
} }
bool visit(class AstExprIfElse* node) override
{
write(node);
return false;
}
bool visit(class AstExprLocal* node) override bool visit(class AstExprLocal* node) override
{ {
write(node); write(node);
@ -1145,42 +1093,6 @@ struct AstJsonEncoder : public AstVisitor
write(node); write(node);
return false; return false;
} }
void writeComments(std::vector<Comment> commentLocations)
{
bool commentComma = false;
for (Comment comment : commentLocations)
{
if (commentComma)
{
writeRaw(",");
}
else
{
commentComma = true;
}
writeRaw("{");
bool c = pushComma();
switch (comment.type)
{
case Lexeme::Comment:
writeType("Comment");
break;
case Lexeme::BlockComment:
writeType("BlockComment");
break;
case Lexeme::BrokenComment:
writeType("BrokenComment");
break;
default:
break;
}
write("location", comment.location);
popComma(c);
writeRaw("}");
}
}
}; };
std::string toJson(AstNode* node) std::string toJson(AstNode* node)
@ -1190,15 +1102,4 @@ std::string toJson(AstNode* node)
return encoder.str(); return encoder.str();
} }
std::string toJson(AstNode* node, const std::vector<Comment>& commentLocations) } // namespace lluz
{
AstJsonEncoder encoder;
encoder.writeRaw(R"({"root":)");
node->visit(&encoder);
encoder.writeRaw(R"(,"commentLocations":[)");
encoder.writeComments(commentLocations);
encoder.writeRaw("]}");
return encoder.str();
}
} // namespace Luau

View file

@ -1,16 +1,16 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/LValue.h" #include "lluz/LValue.h"
#include "Luau/Ast.h" #include "lluz/Ast.h"
#include <vector> #include <vector>
namespace Luau namespace lluz
{ {
bool Field::operator==(const Field& rhs) const bool Field::operator==(const Field& rhs) const
{ {
LUAU_ASSERT(parent && rhs.parent); lluz_ASSERT(parent && rhs.parent);
return key == rhs.key && (parent == rhs.parent || *parent == *rhs.parent); return key == rhs.key && (parent == rhs.parent || *parent == *rhs.parent);
} }
@ -34,7 +34,7 @@ size_t LValueHasher::operator()(const LValue& lvalue) const
else if (auto symbol = get<Symbol>(*current)) else if (auto symbol = get<Symbol>(*current))
acc ^= std::hash<Symbol>{}(*symbol) << 1; acc ^= std::hash<Symbol>{}(*symbol) << 1;
else else
LUAU_ASSERT(!"Hash not accumulated for this new LValue alternative."); lluz_ASSERT(!XorStr("Hash not accumulated for this new LValue alternative."));
current = baseof(*current); current = baseof(*current);
} }
@ -48,7 +48,7 @@ const LValue* baseof(const LValue& lvalue)
return field->parent.get(); return field->parent.get();
auto symbol = get<Symbol>(lvalue); auto symbol = get<Symbol>(lvalue);
LUAU_ASSERT(symbol); lluz_ASSERT(symbol);
return nullptr; // Base of root is null. return nullptr; // Base of root is null.
} }
@ -84,7 +84,7 @@ Symbol getBaseSymbol(const LValue& lvalue)
current = baseof(*current); current = baseof(*current);
const Symbol* symbol = get<Symbol>(*current); const Symbol* symbol = get<Symbol>(*current);
LUAU_ASSERT(symbol); lluz_ASSERT(symbol);
return *symbol; return *symbol;
} }
@ -104,4 +104,4 @@ void addRefinement(RefinementMap& refis, const LValue& lvalue, TypeId ty)
refis[lvalue] = ty; refis[lvalue] = ty;
} }
} // namespace Luau } // namespace lluz

File diff suppressed because it is too large Load diff

View file

@ -1,25 +1,24 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Module.h" #include "lluz/Module.h"
#include "Luau/Clone.h" #include "lluz/Clone.h"
#include "Luau/Common.h" #include "lluz/Common.h"
#include "Luau/ConstraintGraphBuilder.h" #include "lluz/ConstraintGraphBuilder.h"
#include "Luau/Normalize.h" #include "lluz/Normalize.h"
#include "Luau/RecursionCounter.h" #include "lluz/RecursionCounter.h"
#include "Luau/Scope.h" #include "lluz/Scope.h"
#include "Luau/TypeInfer.h" #include "lluz/TypeInfer.h"
#include "Luau/TypePack.h" #include "lluz/TypePack.h"
#include "Luau/TypeVar.h" #include "lluz/TypeVar.h"
#include "Luau/VisitTypeVar.h" #include "lluz/VisitTypeVar.h"
#include <algorithm> #include <algorithm>
LUAU_FASTFLAG(LuauLowerBoundsCalculation); lluz_FASTFLAG(LluLowerBoundsCalculation);
LUAU_FASTFLAG(LuauNormalizeFlagIsConservative); lluz_FASTFLAG(LluNormalizeFlagIsConservative);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); lluz_FASTFLAG(DebugLluDeferredConstraintResolution);
LUAU_FASTFLAGVARIABLE(LuauForceExportSurfacesToBeNormal, false);
namespace Luau namespace lluz
{ {
static bool contains(Position pos, Comment comment) static bool contains(Position pos, Comment comment)
@ -95,66 +94,59 @@ Module::~Module()
void Module::clonePublicInterface(InternalErrorReporter& ice) void Module::clonePublicInterface(InternalErrorReporter& ice)
{ {
LUAU_ASSERT(interfaceTypes.typeVars.empty()); lluz_ASSERT(interfaceTypes.typeVars.empty());
LUAU_ASSERT(interfaceTypes.typePacks.empty()); lluz_ASSERT(interfaceTypes.typePacks.empty());
CloneState cloneState; CloneState cloneState;
ScopePtr moduleScope = getModuleScope(); ScopePtr moduleScope = FFlag::DebugLluDeferredConstraintResolution ? nullptr : getModuleScope();
Scope2* moduleScope2 = FFlag::DebugLluDeferredConstraintResolution ? getModuleScope2() : nullptr;
TypePackId returnType = moduleScope->returnType; TypePackId returnType = FFlag::DebugLluDeferredConstraintResolution ? moduleScope2->returnType : moduleScope->returnType;
std::optional<TypePackId> varargPack = FFlag::DebugLuauDeferredConstraintResolution ? std::nullopt : moduleScope->varargPack; std::optional<TypePackId> varargPack = FFlag::DebugLluDeferredConstraintResolution ? std::nullopt : moduleScope->varargPack;
std::unordered_map<Name, TypeFun>* exportedTypeBindings = std::unordered_map<Name, TypeFun>* exportedTypeBindings =
FFlag::DebugLuauDeferredConstraintResolution ? nullptr : &moduleScope->exportedTypeBindings; FFlag::DebugLluDeferredConstraintResolution ? nullptr : &moduleScope->exportedTypeBindings;
returnType = clone(returnType, interfaceTypes, cloneState); returnType = clone(returnType, interfaceTypes, cloneState);
moduleScope->returnType = returnType; if (moduleScope)
if (varargPack)
{ {
varargPack = clone(*varargPack, interfaceTypes, cloneState); moduleScope->returnType = returnType;
moduleScope->varargPack = varargPack; if (varargPack)
{
varargPack = clone(*varargPack, interfaceTypes, cloneState);
moduleScope->varargPack = varargPack;
}
}
else
{
lluz_ASSERT(moduleScope2);
moduleScope2->returnType = returnType; // TODO varargPack
}
if (FFlag::LluLowerBoundsCalculation)
{
normalize(returnType, interfaceTypes, ice);
if (varargPack)
normalize(*varargPack, interfaceTypes, ice);
} }
ForceNormal forceNormal{&interfaceTypes}; ForceNormal forceNormal{&interfaceTypes};
if (FFlag::LuauLowerBoundsCalculation)
{
normalize(returnType, interfaceTypes, ice);
if (FFlag::LuauForceExportSurfacesToBeNormal)
forceNormal.traverse(returnType);
if (varargPack)
{
normalize(*varargPack, interfaceTypes, ice);
if (FFlag::LuauForceExportSurfacesToBeNormal)
forceNormal.traverse(*varargPack);
}
}
if (exportedTypeBindings) if (exportedTypeBindings)
{ {
for (auto& [name, tf] : *exportedTypeBindings) for (auto& [name, tf] : *exportedTypeBindings)
{ {
tf = clone(tf, interfaceTypes, cloneState); tf = clone(tf, interfaceTypes, cloneState);
if (FFlag::LuauLowerBoundsCalculation) if (FFlag::LluLowerBoundsCalculation)
{ {
normalize(tf.type, interfaceTypes, ice); normalize(tf.type, interfaceTypes, ice);
if (FFlag::LuauNormalizeFlagIsConservative) if (FFlag::LluNormalizeFlagIsConservative)
{ {
// We're about to freeze the memory. We know that the flag is conservative by design. Cyclic tables // We're about to freeze the memory. We know that the flag is conservative by design. Cyclic tables
// won't be marked normal. If the types aren't normal by now, they never will be. // won't be marked normal. If the types aren't normal by now, they never will be.
forceNormal.traverse(tf.type); forceNormal.traverse(tf.type);
for (GenericTypeDefinition param : tf.typeParams)
{
forceNormal.traverse(param.ty);
if (param.defaultValue)
{
normalize(*param.defaultValue, interfaceTypes, ice);
forceNormal.traverse(*param.defaultValue);
}
}
} }
} }
} }
@ -173,13 +165,8 @@ void Module::clonePublicInterface(InternalErrorReporter& ice)
for (auto& [name, ty] : declaredGlobals) for (auto& [name, ty] : declaredGlobals)
{ {
ty = clone(ty, interfaceTypes, cloneState); ty = clone(ty, interfaceTypes, cloneState);
if (FFlag::LuauLowerBoundsCalculation) if (FFlag::LluLowerBoundsCalculation)
{
normalize(ty, interfaceTypes, ice); normalize(ty, interfaceTypes, ice);
if (FFlag::LuauForceExportSurfacesToBeNormal)
forceNormal.traverse(ty);
}
} }
freeze(internalTypes); freeze(internalTypes);
@ -188,8 +175,14 @@ void Module::clonePublicInterface(InternalErrorReporter& ice)
ScopePtr Module::getModuleScope() const ScopePtr Module::getModuleScope() const
{ {
LUAU_ASSERT(!scopes.empty()); lluz_ASSERT(!scopes.empty());
return scopes.front().second; return scopes.front().second;
} }
} // namespace Luau Scope2* Module::getModuleScope2() const
{
lluz_ASSERT(!scope2s.empty());
return scope2s.front().second.get();
}
} // namespace lluz

View file

@ -1,24 +1,23 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Normalize.h" #include "lluz/Normalize.h"
#include <algorithm> #include <algorithm>
#include "Luau/Clone.h" #include "lluz/Clone.h"
#include "Luau/Unifier.h" #include "lluz/Unifier.h"
#include "Luau/VisitTypeVar.h" #include "lluz/VisitTypeVar.h"
LUAU_FASTFLAGVARIABLE(DebugLuauCopyBeforeNormalizing, false) lluz_FASTFLAGVARIABLE(DebugLluCopyBeforeNormalizing, false)
// This could theoretically be 2000 on amd64, but x86 requires this. // This could theoretically be 2000 on amd64, but x86 requires this.
LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200); lluz_FASTINTVARIABLE(LluNormalizeIterationLimit, 1200);
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineTableFix, false); lluz_FASTFLAGVARIABLE(LluNormalizeCombineTableFix, false);
LUAU_FASTFLAGVARIABLE(LuauNormalizeFlagIsConservative, false); lluz_FASTFLAGVARIABLE(LluNormalizeFlagIsConservative, false);
LUAU_FASTFLAGVARIABLE(LuauFixNormalizationOfCyclicUnions, false); lluz_FASTFLAGVARIABLE(LluNormalizeCombineEqFix, false);
LUAU_FASTFLAG(LuauUnknownAndNeverType) lluz_FASTFLAG(LluQuantifyConstrained)
LUAU_FASTFLAG(LuauQuantifyConstrained)
namespace Luau namespace lluz
{ {
namespace namespace
@ -86,10 +85,10 @@ static bool areNormal_(const T& t, const std::unordered_set<void*>& seen, Intern
int count = 0; int count = 0;
auto isNormal = [&](TypeId ty) { auto isNormal = [&](TypeId ty) {
++count; ++count;
if (count >= FInt::LuauNormalizeIterationLimit) if (count >= FInt::LluNormalizeIterationLimit)
ice.ice("Luau::areNormal hit iteration limit"); ice.ice(XorStr("lluz::areNormal hit iteration limit"));
if (FFlag::LuauNormalizeFlagIsConservative) if (FFlag::LluNormalizeFlagIsConservative)
return ty->normal; return ty->normal;
else else
{ {
@ -129,7 +128,7 @@ static bool areNormal(TypePackId tp, const std::unordered_set<void*>& seen, Inte
#define CHECK_ITERATION_LIMIT(...) \ #define CHECK_ITERATION_LIMIT(...) \
do \ do \
{ \ { \
if (iterationLimit > FInt::LuauNormalizeIterationLimit) \ if (iterationLimit > FInt::LluNormalizeIterationLimit) \
{ \ { \
limitExceeded = true; \ limitExceeded = true; \
return __VA_ARGS__; \ return __VA_ARGS__; \
@ -155,7 +154,7 @@ struct Normalize final : TypeVarVisitor
bool visit(TypeId ty, const FreeTypeVar&) override bool visit(TypeId ty, const FreeTypeVar&) override
{ {
LUAU_ASSERT(!ty->normal); lluz_ASSERT(!ty->normal);
return false; return false;
} }
@ -167,7 +166,7 @@ struct Normalize final : TypeVarVisitor
return false; return false;
// It should never be the case that this TypeVar is normal, but is bound to a non-normal type, except in nontrivial cases. // It should never be the case that this TypeVar is normal, but is bound to a non-normal type, except in nontrivial cases.
LUAU_ASSERT(!ty->normal || ty->normal == btv.boundTo->normal); lluz_ASSERT(!ty->normal || ty->normal == btv.boundTo->normal);
asMutable(ty)->normal = btv.boundTo->normal; asMutable(ty)->normal = btv.boundTo->normal;
return !ty->normal; return !ty->normal;
@ -175,7 +174,7 @@ struct Normalize final : TypeVarVisitor
bool visit(TypeId ty, const PrimitiveTypeVar&) override bool visit(TypeId ty, const PrimitiveTypeVar&) override
{ {
LUAU_ASSERT(ty->normal); lluz_ASSERT(ty->normal);
return false; return false;
} }
@ -183,6 +182,7 @@ struct Normalize final : TypeVarVisitor
{ {
if (!ty->normal) if (!ty->normal)
asMutable(ty)->normal = true; asMutable(ty)->normal = true;
return false; return false;
} }
@ -193,24 +193,10 @@ struct Normalize final : TypeVarVisitor
return false; return false;
} }
bool visit(TypeId ty, const UnknownTypeVar&) override
{
if (!ty->normal)
asMutable(ty)->normal = true;
return false;
}
bool visit(TypeId ty, const NeverTypeVar&) override
{
if (!ty->normal)
asMutable(ty)->normal = true;
return false;
}
bool visit(TypeId ty, const ConstrainedTypeVar& ctvRef) override bool visit(TypeId ty, const ConstrainedTypeVar& ctvRef) override
{ {
CHECK_ITERATION_LIMIT(false); CHECK_ITERATION_LIMIT(false);
LUAU_ASSERT(!ty->normal); lluz_ASSERT(!ty->normal);
ConstrainedTypeVar* ctv = const_cast<ConstrainedTypeVar*>(&ctvRef); ConstrainedTypeVar* ctv = const_cast<ConstrainedTypeVar*>(&ctvRef);
@ -222,7 +208,7 @@ struct Normalize final : TypeVarVisitor
std::vector<TypeId> newParts = normalizeUnion(parts); std::vector<TypeId> newParts = normalizeUnion(parts);
if (FFlag::LuauQuantifyConstrained) if (FFlag::LluQuantifyConstrained)
{ {
ctv->parts = std::move(newParts); ctv->parts = std::move(newParts);
} }
@ -294,7 +280,7 @@ struct Normalize final : TypeVarVisitor
} }
// An unsealed table can never be normal, ditto for free tables iff the type it is bound to is also not normal. // An unsealed table can never be normal, ditto for free tables iff the type it is bound to is also not normal.
if (FFlag::LuauQuantifyConstrained) if (FFlag::LluQuantifyConstrained)
{ {
if (ttv.state == TableState::Generic || ttv.state == TableState::Sealed || (ttv.state == TableState::Free && follow(ty)->normal)) if (ttv.state == TableState::Generic || ttv.state == TableState::Sealed || (ttv.state == TableState::Free && follow(ty)->normal))
asMutable(ty)->normal = normal; asMutable(ty)->normal = normal;
@ -329,7 +315,7 @@ struct Normalize final : TypeVarVisitor
bool visit(TypeId ty, const AnyTypeVar&) override bool visit(TypeId ty, const AnyTypeVar&) override
{ {
LUAU_ASSERT(ty->normal); lluz_ASSERT(ty->normal);
return false; return false;
} }
@ -341,23 +327,17 @@ struct Normalize final : TypeVarVisitor
return false; return false;
UnionTypeVar* utv = &const_cast<UnionTypeVar&>(utvRef); UnionTypeVar* utv = &const_cast<UnionTypeVar&>(utvRef);
std::vector<TypeId> options = std::move(utv->options);
// TODO: Clip tempOptions and optionsRef when clipping FFlag::LuauFixNormalizationOfCyclicUnions
std::vector<TypeId> tempOptions;
if (!FFlag::LuauFixNormalizationOfCyclicUnions)
tempOptions = std::move(utv->options);
std::vector<TypeId>& optionsRef = FFlag::LuauFixNormalizationOfCyclicUnions ? utv->options : tempOptions;
// We might transmute, so it's not safe to rely on the builtin traversal logic of visitTypeVar // We might transmute, so it's not safe to rely on the builtin traversal logic of visitTypeVar
for (TypeId option : optionsRef) for (TypeId option : options)
traverse(option); traverse(option);
std::vector<TypeId> newOptions = normalizeUnion(optionsRef); std::vector<TypeId> newOptions = normalizeUnion(options);
const bool normal = areNormal(newOptions, seen, ice); const bool normal = areNormal(newOptions, seen, ice);
LUAU_ASSERT(!newOptions.empty()); lluz_ASSERT(!newOptions.empty());
if (newOptions.size() == 1) if (newOptions.size() == 1)
*asMutable(ty) = BoundTypeVar{newOptions[0]}; *asMutable(ty) = BoundTypeVar{newOptions[0]};
@ -378,106 +358,51 @@ struct Normalize final : TypeVarVisitor
IntersectionTypeVar* itv = &const_cast<IntersectionTypeVar&>(itvRef); IntersectionTypeVar* itv = &const_cast<IntersectionTypeVar&>(itvRef);
if (FFlag::LuauFixNormalizationOfCyclicUnions) std::vector<TypeId> oldParts = std::move(itv->parts);
for (TypeId part : oldParts)
traverse(part);
std::vector<TypeId> tables;
for (TypeId part : oldParts)
{ {
std::vector<TypeId> oldParts = itv->parts; part = follow(part);
IntersectionTypeVar newIntersection; if (get<TableTypeVar>(part))
tables.push_back(part);
for (TypeId part : oldParts) else
traverse(part);
std::vector<TypeId> tables;
for (TypeId part : oldParts)
{ {
part = follow(part); Replacer replacer{&arena, nullptr, nullptr}; // FIXME this is super super WEIRD
if (get<TableTypeVar>(part)) combineIntoIntersection(replacer, itv, part);
tables.push_back(part);
else
{
Replacer replacer{&arena, nullptr, nullptr}; // FIXME this is super super WEIRD
combineIntoIntersection(replacer, &newIntersection, part);
}
}
// Don't allocate a new table if there's just one in the intersection.
if (tables.size() == 1)
newIntersection.parts.push_back(tables[0]);
else if (!tables.empty())
{
const TableTypeVar* first = get<TableTypeVar>(tables[0]);
LUAU_ASSERT(first);
TypeId newTable = arena.addType(TableTypeVar{first->state, first->level});
TableTypeVar* ttv = getMutable<TableTypeVar>(newTable);
for (TypeId part : tables)
{
// Intuition: If combineIntoTable() needs to clone a table, any references to 'part' are cyclic and need
// to be rewritten to point at 'newTable' in the clone.
Replacer replacer{&arena, part, newTable};
combineIntoTable(replacer, ttv, part);
}
newIntersection.parts.push_back(newTable);
}
itv->parts = std::move(newIntersection.parts);
asMutable(ty)->normal = areNormal(itv->parts, seen, ice);
if (itv->parts.size() == 1)
{
TypeId part = itv->parts[0];
*asMutable(ty) = BoundTypeVar{part};
} }
} }
else
// Don't allocate a new table if there's just one in the intersection.
if (tables.size() == 1)
itv->parts.push_back(tables[0]);
else if (!tables.empty())
{ {
std::vector<TypeId> oldParts = std::move(itv->parts); const TableTypeVar* first = get<TableTypeVar>(tables[0]);
lluz_ASSERT(first);
for (TypeId part : oldParts) TypeId newTable = arena.addType(TableTypeVar{first->state, first->level});
traverse(part); TableTypeVar* ttv = getMutable<TableTypeVar>(newTable);
for (TypeId part : tables)
std::vector<TypeId> tables;
for (TypeId part : oldParts)
{ {
part = follow(part); // Intuition: If combineIntoTable() needs to clone a table, any references to 'part' are cyclic and need
if (get<TableTypeVar>(part)) // to be rewritten to point at 'newTable' in the clone.
tables.push_back(part); Replacer replacer{&arena, part, newTable};
else combineIntoTable(replacer, ttv, part);
{
Replacer replacer{&arena, nullptr, nullptr}; // FIXME this is super super WEIRD
combineIntoIntersection(replacer, itv, part);
}
} }
// Don't allocate a new table if there's just one in the intersection. itv->parts.push_back(newTable);
if (tables.size() == 1) }
itv->parts.push_back(tables[0]);
else if (!tables.empty())
{
const TableTypeVar* first = get<TableTypeVar>(tables[0]);
LUAU_ASSERT(first);
TypeId newTable = arena.addType(TableTypeVar{first->state, first->level}); asMutable(ty)->normal = areNormal(itv->parts, seen, ice);
TableTypeVar* ttv = getMutable<TableTypeVar>(newTable);
for (TypeId part : tables)
{
// Intuition: If combineIntoTable() needs to clone a table, any references to 'part' are cyclic and need
// to be rewritten to point at 'newTable' in the clone.
Replacer replacer{&arena, part, newTable};
combineIntoTable(replacer, ttv, part);
}
itv->parts.push_back(newTable); if (itv->parts.size() == 1)
} {
TypeId part = itv->parts[0];
asMutable(ty)->normal = areNormal(itv->parts, seen, ice); *asMutable(ty) = BoundTypeVar{part};
if (itv->parts.size() == 1)
{
TypeId part = itv->parts[0];
*asMutable(ty) = BoundTypeVar{part};
}
} }
return false; return false;
@ -491,13 +416,7 @@ struct Normalize final : TypeVarVisitor
std::vector<TypeId> result; std::vector<TypeId> result;
for (TypeId part : options) for (TypeId part : options)
{
// AnyTypeVar always win the battle no matter what we do, so we're done.
if (FFlag::LuauUnknownAndNeverType && get<AnyTypeVar>(follow(part)))
return {part};
combineIntoUnion(result, part); combineIntoUnion(result, part);
}
return result; return result;
} }
@ -508,17 +427,7 @@ struct Normalize final : TypeVarVisitor
if (auto utv = get<UnionTypeVar>(ty)) if (auto utv = get<UnionTypeVar>(ty))
{ {
for (TypeId t : utv) for (TypeId t : utv)
{
// AnyTypeVar always win the battle no matter what we do, so we're done.
if (FFlag::LuauUnknownAndNeverType && get<AnyTypeVar>(t))
{
result = {t};
return;
}
combineIntoUnion(result, t); combineIntoUnion(result, t);
}
return; return;
} }
@ -623,12 +532,12 @@ struct Normalize final : TypeVarVisitor
// so if you increase the size of a stack frame, you'll need to decrease the limit. // so if you increase the size of a stack frame, you'll need to decrease the limit.
CHECK_ITERATION_LIMIT(); CHECK_ITERATION_LIMIT();
LUAU_ASSERT(table); lluz_ASSERT(table);
ty = follow(ty); ty = follow(ty);
TableTypeVar* tyTable = getMutable<TableTypeVar>(ty); TableTypeVar* tyTable = getMutable<TableTypeVar>(ty);
LUAU_ASSERT(tyTable); lluz_ASSERT(tyTable);
for (const auto& [propName, prop] : tyTable->props) for (const auto& [propName, prop] : tyTable->props)
{ {
@ -652,24 +561,6 @@ struct Normalize final : TypeVarVisitor
table->props.insert({propName, prop}); table->props.insert({propName, prop});
} }
if (FFlag::LuauFixNormalizationOfCyclicUnions)
{
if (tyTable->indexer)
{
if (table->indexer)
{
table->indexer->indexType = combine(replacer, replacer.smartClone(tyTable->indexer->indexType), table->indexer->indexType);
table->indexer->indexResultType =
combine(replacer, replacer.smartClone(tyTable->indexer->indexResultType), table->indexer->indexResultType);
}
else
{
table->indexer =
TableIndexer{replacer.smartClone(tyTable->indexer->indexType), replacer.smartClone(tyTable->indexer->indexResultType)};
}
}
}
table->state = combineTableStates(table->state, tyTable->state); table->state = combineTableStates(table->state, tyTable->state);
table->level = max(table->level, tyTable->level); table->level = max(table->level, tyTable->level);
} }
@ -680,14 +571,15 @@ struct Normalize final : TypeVarVisitor
*/ */
TypeId combine(Replacer& replacer, TypeId a, TypeId b) TypeId combine(Replacer& replacer, TypeId a, TypeId b)
{ {
b = follow(b); if (FFlag::LluNormalizeCombineEqFix)
b = follow(b);
if (FFlag::LuauNormalizeCombineTableFix && a == b) if (FFlag::LluNormalizeCombineTableFix && a == b)
return a; return a;
if (!get<IntersectionTypeVar>(a) && !get<TableTypeVar>(a)) if (!get<IntersectionTypeVar>(a) && !get<TableTypeVar>(a))
{ {
if (!FFlag::LuauNormalizeCombineTableFix && a == b) if (!FFlag::LluNormalizeCombineTableFix && a == b)
return a; return a;
else else
return arena.addType(IntersectionTypeVar{{a, b}}); return arena.addType(IntersectionTypeVar{{a, b}});
@ -700,14 +592,14 @@ struct Normalize final : TypeVarVisitor
} }
else if (auto ttv = getMutable<TableTypeVar>(a)) else if (auto ttv = getMutable<TableTypeVar>(a))
{ {
if (FFlag::LuauNormalizeCombineTableFix && !get<TableTypeVar>(b)) if (FFlag::LluNormalizeCombineTableFix && !get<TableTypeVar>(FFlag::LluNormalizeCombineEqFix ? b : follow(b)))
return arena.addType(IntersectionTypeVar{{a, b}}); return arena.addType(IntersectionTypeVar{{a, b}});
combineIntoTable(replacer, ttv, b); combineIntoTable(replacer, ttv, b);
return a; return a;
} }
LUAU_ASSERT(!"Impossible"); lluz_ASSERT(!XorStr("Impossible"));
LUAU_UNREACHABLE(); lluz_UNREACHABLE();
} }
}; };
@ -719,7 +611,7 @@ struct Normalize final : TypeVarVisitor
std::pair<TypeId, bool> normalize(TypeId ty, TypeArena& arena, InternalErrorReporter& ice) std::pair<TypeId, bool> normalize(TypeId ty, TypeArena& arena, InternalErrorReporter& ice)
{ {
CloneState state; CloneState state;
if (FFlag::DebugLuauCopyBeforeNormalizing) if (FFlag::DebugLluCopyBeforeNormalizing)
(void)clone(ty, arena, state); (void)clone(ty, arena, state);
Normalize n{arena, ice}; Normalize n{arena, ice};
@ -743,7 +635,7 @@ std::pair<TypeId, bool> normalize(TypeId ty, const ModulePtr& module, InternalEr
std::pair<TypePackId, bool> normalize(TypePackId tp, TypeArena& arena, InternalErrorReporter& ice) std::pair<TypePackId, bool> normalize(TypePackId tp, TypeArena& arena, InternalErrorReporter& ice)
{ {
CloneState state; CloneState state;
if (FFlag::DebugLuauCopyBeforeNormalizing) if (FFlag::DebugLluCopyBeforeNormalizing)
(void)clone(tp, arena, state); (void)clone(tp, arena, state);
Normalize n{arena, ice}; Normalize n{arena, ice};
@ -757,4 +649,4 @@ std::pair<TypePackId, bool> normalize(TypePackId tp, const ModulePtr& module, In
return normalize(tp, module->internalTypes, ice); return normalize(tp, module->internalTypes, ice);
} }
} // namespace Luau } // namespace lluz

View file

@ -1,28 +1,28 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Quantify.h" #include "lluz/Quantify.h"
#include "Luau/Scope.h" #include "lluz/Scope.h"
#include "Luau/Substitution.h" #include "lluz/Substitution.h"
#include "Luau/TxnLog.h" #include "lluz/TxnLog.h"
#include "Luau/VisitTypeVar.h" #include "lluz/VisitTypeVar.h"
LUAU_FASTFLAG(LuauAlwaysQuantify); lluz_FASTFLAG(LluAlwaysQuantify);
LUAU_FASTFLAG(DebugLuauSharedSelf) lluz_FASTFLAG(DebugLluSharedSelf)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); lluz_FASTFLAG(DebugLluDeferredConstraintResolution);
LUAU_FASTFLAGVARIABLE(LuauQuantifyConstrained, false) lluz_FASTFLAGVARIABLE(LluQuantifyConstrained, false)
namespace Luau namespace lluz
{ {
/// @return true if outer encloses inner /// @return true if outer encloses inner
static bool subsumes(Scope* outer, Scope* inner) static bool subsumes(Scope2* outer, Scope2* inner)
{ {
while (inner) while (inner)
{ {
if (inner == outer) if (inner == outer)
return true; return true;
inner = inner->parent.get(); inner = inner->parent;
} }
return false; return false;
@ -33,30 +33,30 @@ struct Quantifier final : TypeVarOnceVisitor
TypeLevel level; TypeLevel level;
std::vector<TypeId> generics; std::vector<TypeId> generics;
std::vector<TypePackId> genericPacks; std::vector<TypePackId> genericPacks;
Scope* scope = nullptr; Scope2* scope = nullptr;
bool seenGenericType = false; bool seenGenericType = false;
bool seenMutableType = false; bool seenMutableType = false;
explicit Quantifier(TypeLevel level) explicit Quantifier(TypeLevel level)
: level(level) : level(level)
{ {
LUAU_ASSERT(!FFlag::DebugLuauDeferredConstraintResolution); lluz_ASSERT(!FFlag::DebugLluDeferredConstraintResolution);
} }
explicit Quantifier(Scope* scope) explicit Quantifier(Scope2* scope)
: scope(scope) : scope(scope)
{ {
LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution); lluz_ASSERT(FFlag::DebugLluDeferredConstraintResolution);
} }
/// @return true if outer encloses inner /// @return true if outer encloses inner
bool subsumes(Scope* outer, Scope* inner) bool subsumes(Scope2* outer, Scope2* inner)
{ {
while (inner) while (inner)
{ {
if (inner == outer) if (inner == outer)
return true; return true;
inner = inner->parent.get(); inner = inner->parent;
} }
return false; return false;
@ -66,10 +66,10 @@ struct Quantifier final : TypeVarOnceVisitor
{ {
seenMutableType = true; seenMutableType = true;
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ftv.scope) : !level.subsumes(ftv.level)) if (FFlag::DebugLluDeferredConstraintResolution ? !subsumes(scope, ftv.scope) : !level.subsumes(ftv.level))
return false; return false;
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLluDeferredConstraintResolution)
*asMutable(ty) = GenericTypeVar{scope}; *asMutable(ty) = GenericTypeVar{scope};
else else
*asMutable(ty) = GenericTypeVar{level}; *asMutable(ty) = GenericTypeVar{level};
@ -81,13 +81,13 @@ struct Quantifier final : TypeVarOnceVisitor
bool visit(TypeId ty, const ConstrainedTypeVar&) override bool visit(TypeId ty, const ConstrainedTypeVar&) override
{ {
if (FFlag::LuauQuantifyConstrained) if (FFlag::LluQuantifyConstrained)
{ {
ConstrainedTypeVar* ctv = getMutable<ConstrainedTypeVar>(ty); ConstrainedTypeVar* ctv = getMutable<ConstrainedTypeVar>(ty);
seenMutableType = true; seenMutableType = true;
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ctv->scope) : !level.subsumes(ctv->level)) if (FFlag::DebugLluDeferredConstraintResolution ? !subsumes(scope, ctv->scope) : !level.subsumes(ctv->level))
return false; return false;
std::vector<TypeId> opts = std::move(ctv->parts); std::vector<TypeId> opts = std::move(ctv->parts);
@ -109,7 +109,7 @@ struct Quantifier final : TypeVarOnceVisitor
bool visit(TypeId ty, const TableTypeVar&) override bool visit(TypeId ty, const TableTypeVar&) override
{ {
LUAU_ASSERT(getMutable<TableTypeVar>(ty)); lluz_ASSERT(getMutable<TableTypeVar>(ty));
TableTypeVar& ttv = *getMutable<TableTypeVar>(ty); TableTypeVar& ttv = *getMutable<TableTypeVar>(ty);
if (ttv.state == TableState::Generic) if (ttv.state == TableState::Generic)
@ -118,13 +118,13 @@ struct Quantifier final : TypeVarOnceVisitor
if (ttv.state == TableState::Free) if (ttv.state == TableState::Free)
seenMutableType = true; seenMutableType = true;
if (!FFlag::LuauQuantifyConstrained) if (!FFlag::LluQuantifyConstrained)
{ {
if (ttv.state == TableState::Sealed || ttv.state == TableState::Generic) if (ttv.state == TableState::Sealed || ttv.state == TableState::Generic)
return false; return false;
} }
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ttv.scope) : !level.subsumes(ttv.level)) if (FFlag::DebugLluDeferredConstraintResolution ? !subsumes(scope, ttv.scope) : !level.subsumes(ttv.level))
{ {
if (ttv.state == TableState::Unsealed) if (ttv.state == TableState::Unsealed)
seenMutableType = true; seenMutableType = true;
@ -148,7 +148,7 @@ struct Quantifier final : TypeVarOnceVisitor
{ {
seenMutableType = true; seenMutableType = true;
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ftp.scope) : !level.subsumes(ftp.level)) if (FFlag::DebugLluDeferredConstraintResolution ? !subsumes(scope, ftp.scope) : !level.subsumes(ftp.level))
return false; return false;
*asMutable(tp) = GenericTypePack{level}; *asMutable(tp) = GenericTypePack{level};
@ -159,7 +159,7 @@ struct Quantifier final : TypeVarOnceVisitor
void quantify(TypeId ty, TypeLevel level) void quantify(TypeId ty, TypeLevel level)
{ {
if (FFlag::DebugLuauSharedSelf) if (FFlag::DebugLluSharedSelf)
{ {
ty = follow(ty); ty = follow(ty);
@ -177,7 +177,7 @@ void quantify(TypeId ty, TypeLevel level)
if (!ftv || !ftv->hasSelf) if (!ftv || !ftv->hasSelf)
continue; continue;
if (Luau::first(ftv->argTypes) == ttv->selfTy) if (lluz::first(ftv->argTypes) == ttv->selfTy)
{ {
ftv->generics.insert(ftv->generics.end(), selfQ.generics.begin(), selfQ.generics.end()); ftv->generics.insert(ftv->generics.end(), selfQ.generics.begin(), selfQ.generics.end());
ftv->genericPacks.insert(ftv->genericPacks.end(), selfQ.genericPacks.begin(), selfQ.genericPacks.end()); ftv->genericPacks.insert(ftv->genericPacks.end(), selfQ.genericPacks.begin(), selfQ.genericPacks.end());
@ -202,8 +202,8 @@ void quantify(TypeId ty, TypeLevel level)
q.traverse(ty); q.traverse(ty);
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty); FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty);
LUAU_ASSERT(ftv); lluz_ASSERT(ftv);
if (FFlag::LuauAlwaysQuantify) if (FFlag::LluAlwaysQuantify)
{ {
ftv->generics.insert(ftv->generics.end(), q.generics.begin(), q.generics.end()); ftv->generics.insert(ftv->generics.end(), q.generics.begin(), q.generics.end());
ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end()); ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end());
@ -216,14 +216,14 @@ void quantify(TypeId ty, TypeLevel level)
} }
} }
void quantify(TypeId ty, Scope* scope) void quantify(TypeId ty, Scope2* scope)
{ {
Quantifier q{scope}; Quantifier q{scope};
q.traverse(ty); q.traverse(ty);
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty); FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty);
LUAU_ASSERT(ftv); lluz_ASSERT(ftv);
if (FFlag::LuauAlwaysQuantify) if (FFlag::LluAlwaysQuantify)
{ {
ftv->generics.insert(ftv->generics.end(), q.generics.begin(), q.generics.end()); ftv->generics.insert(ftv->generics.end(), q.generics.begin(), q.generics.end());
ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end()); ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end());
@ -240,11 +240,11 @@ void quantify(TypeId ty, Scope* scope)
struct PureQuantifier : Substitution struct PureQuantifier : Substitution
{ {
Scope* scope; Scope2* scope;
std::vector<TypeId> insertedGenerics; std::vector<TypeId> insertedGenerics;
std::vector<TypePackId> insertedGenericPacks; std::vector<TypePackId> insertedGenericPacks;
PureQuantifier(TypeArena* arena, Scope* scope) PureQuantifier(TypeArena* arena, Scope2* scope)
: Substitution(TxnLog::empty(), arena) : Substitution(TxnLog::empty(), arena)
, scope(scope) , scope(scope)
{ {
@ -252,7 +252,7 @@ struct PureQuantifier : Substitution
bool isDirty(TypeId ty) override bool isDirty(TypeId ty) override
{ {
LUAU_ASSERT(ty == follow(ty)); lluz_ASSERT(ty == follow(ty));
if (auto ftv = get<FreeTypeVar>(ty)) if (auto ftv = get<FreeTypeVar>(ty))
{ {
@ -288,7 +288,7 @@ struct PureQuantifier : Substitution
{ {
TypeId result = arena->addType(TableTypeVar{}); TypeId result = arena->addType(TableTypeVar{});
TableTypeVar* resultTable = getMutable<TableTypeVar>(result); TableTypeVar* resultTable = getMutable<TableTypeVar>(result);
LUAU_ASSERT(resultTable); lluz_ASSERT(resultTable);
*resultTable = *ttv; *resultTable = *ttv;
resultTable->scope = nullptr; resultTable->scope = nullptr;
@ -322,14 +322,14 @@ struct PureQuantifier : Substitution
} }
}; };
TypeId quantify(TypeArena* arena, TypeId ty, Scope* scope) TypeId quantify(TypeArena* arena, TypeId ty, Scope2* scope)
{ {
PureQuantifier quantifier{arena, scope}; PureQuantifier quantifier{arena, scope};
std::optional<TypeId> result = quantifier.substitute(ty); std::optional<TypeId> result = quantifier.substitute(ty);
LUAU_ASSERT(result); lluz_ASSERT(result);
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(*result); FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(*result);
LUAU_ASSERT(ftv); lluz_ASSERT(ftv);
ftv->generics.insert(ftv->generics.end(), quantifier.insertedGenerics.begin(), quantifier.insertedGenerics.end()); ftv->generics.insert(ftv->generics.end(), quantifier.insertedGenerics.begin(), quantifier.insertedGenerics.end());
ftv->genericPacks.insert(ftv->genericPacks.end(), quantifier.insertedGenericPacks.begin(), quantifier.insertedGenericPacks.end()); ftv->genericPacks.insert(ftv->genericPacks.end(), quantifier.insertedGenericPacks.begin(), quantifier.insertedGenericPacks.end());
ftv->hasNoGenerics = ftv->generics.empty() && ftv->genericPacks.empty(); ftv->hasNoGenerics = ftv->generics.empty() && ftv->genericPacks.empty();
@ -337,4 +337,4 @@ TypeId quantify(TypeArena* arena, TypeId ty, Scope* scope)
return *result; return *result;
} }
} // namespace Luau } // namespace lluz

View file

@ -1,10 +1,10 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/RequireTracer.h" #include "lluz/RequireTracer.h"
#include "Luau/Ast.h" #include "lluz/Ast.h"
#include "Luau/Module.h" #include "lluz/Module.h"
namespace Luau namespace lluz
{ {
struct RequireTracer : AstVisitor struct RequireTracer : AstVisitor
@ -27,7 +27,7 @@ struct RequireTracer : AstVisitor
{ {
AstExprGlobal* global = expr->func->as<AstExprGlobal>(); AstExprGlobal* global = expr->func->as<AstExprGlobal>();
if (global && global->name == "require" && expr->args.size >= 1) if (global && global->name == XorStr("require") && expr->args.size >= 1)
requireCalls.push_back(expr); requireCalls.push_back(expr);
return true; return true;
@ -163,4 +163,4 @@ RequireTraceResult traceRequires(FileResolver* fileResolver, AstStatBlock* root,
return result; return result;
} }
} // namespace Luau } // namespace lluz

View file

@ -1,8 +1,8 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Scope.h" #include "lluz/Scope.h"
namespace Luau namespace lluz
{ {
Scope::Scope(TypePackId returnType) Scope::Scope(TypePackId returnType)
@ -21,6 +21,22 @@ Scope::Scope(const ScopePtr& parent, int subLevel)
level.subLevel = subLevel; level.subLevel = subLevel;
} }
std::optional<TypeId> Scope::lookup(const Symbol& name)
{
Scope* scope = this;
while (scope)
{
auto it = scope->bindings.find(name);
if (it != scope->bindings.end())
return it->second.typeId;
scope = scope->parent.get();
}
return std::nullopt;
}
std::optional<TypeFun> Scope::lookupType(const Name& name) std::optional<TypeFun> Scope::lookupType(const Name& name)
{ {
const Scope* scope = this; const Scope* scope = this;
@ -105,51 +121,51 @@ std::optional<Binding> Scope::linearSearchForBinding(const std::string& name, bo
return std::nullopt; return std::nullopt;
} }
std::optional<TypeId> Scope::lookup(Symbol sym) std::optional<TypeId> Scope2::lookup(Symbol sym)
{ {
Scope* s = this; Scope2* s = this;
while (true) while (true)
{ {
auto it = s->bindings.find(sym); auto it = s->bindings.find(sym);
if (it != s->bindings.end()) if (it != s->bindings.end())
return it->second.typeId; return it->second;
if (s->parent) if (s->parent)
s = s->parent.get(); s = s->parent;
else else
return std::nullopt; return std::nullopt;
} }
} }
std::optional<TypeId> Scope::lookupTypeBinding(const Name& name) std::optional<TypeId> Scope2::lookupTypeBinding(const Name& name)
{ {
Scope* s = this; Scope2* s = this;
while (s) while (s)
{ {
auto it = s->typeBindings.find(name); auto it = s->typeBindings.find(name);
if (it != s->typeBindings.end()) if (it != s->typeBindings.end())
return it->second; return it->second;
s = s->parent.get(); s = s->parent;
} }
return std::nullopt; return std::nullopt;
} }
std::optional<TypePackId> Scope::lookupTypePackBinding(const Name& name) std::optional<TypePackId> Scope2::lookupTypePackBinding(const Name& name)
{ {
Scope* s = this; Scope2* s = this;
while (s) while (s)
{ {
auto it = s->typePackBindings.find(name); auto it = s->typePackBindings.find(name);
if (it != s->typePackBindings.end()) if (it != s->typePackBindings.end())
return it->second; return it->second;
s = s->parent.get(); s = s->parent;
} }
return std::nullopt; return std::nullopt;
} }
} // namespace Luau } // namespace lluz

View file

@ -1,24 +1,22 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Substitution.h" #include "lluz/Substitution.h"
#include "Luau/Common.h" #include "lluz/Common.h"
#include "Luau/Clone.h" #include "lluz/Clone.h"
#include "Luau/TxnLog.h" #include "lluz/TxnLog.h"
#include <algorithm> #include <algorithm>
#include <stdexcept> #include <stdexcept>
LUAU_FASTFLAGVARIABLE(LuauAnyificationMustClone, false) lluz_FASTFLAG(LluLowerBoundsCalculation)
LUAU_FASTFLAG(LuauLowerBoundsCalculation) lluz_FASTINTVARIABLE(LluTarjanChildLimit, 10000)
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
LUAU_FASTFLAG(LuauUnknownAndNeverType)
namespace Luau namespace lluz
{ {
void Tarjan::visitChildren(TypeId ty, int index) void Tarjan::visitChildren(TypeId ty, int index)
{ {
LUAU_ASSERT(ty == log->follow(ty)); lluz_ASSERT(ty == log->follow(ty));
if (ignoreChildren(ty)) if (ignoreChildren(ty))
return; return;
@ -33,7 +31,7 @@ void Tarjan::visitChildren(TypeId ty, int index)
} }
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty)) else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
{ {
LUAU_ASSERT(!ttv->boundTo); lluz_ASSERT(!ttv->boundTo);
for (const auto& [name, prop] : ttv->props) for (const auto& [name, prop] : ttv->props)
visitChild(prop.type); visitChild(prop.type);
if (ttv->indexer) if (ttv->indexer)
@ -72,7 +70,7 @@ void Tarjan::visitChildren(TypeId ty, int index)
void Tarjan::visitChildren(TypePackId tp, int index) void Tarjan::visitChildren(TypePackId tp, int index)
{ {
LUAU_ASSERT(tp == log->follow(tp)); lluz_ASSERT(tp == log->follow(tp));
if (ignoreChildren(tp)) if (ignoreChildren(tp))
return; return;
@ -156,7 +154,7 @@ TarjanResult Tarjan::loop()
if (currEdge == -1) if (currEdge == -1)
{ {
++childCount; ++childCount;
if (childLimit > 0 && (FFlag::LuauUnknownAndNeverType ? childLimit <= childCount : childLimit < childCount)) if (childLimit > 0 && childLimit < childCount)
return TarjanResult::TooManyChildren; return TarjanResult::TooManyChildren;
stack.push_back(index); stack.push_back(index);
@ -186,7 +184,7 @@ TarjanResult Tarjan::loop()
else if (auto tp = edgesTp[currEdge]) else if (auto tp = edgesTp[currEdge])
std::tie(childIndex, fresh) = indexify(tp); std::tie(childIndex, fresh) = indexify(tp);
else else
LUAU_ASSERT(false); lluz_ASSERT(false);
if (fresh) if (fresh)
{ {
@ -245,7 +243,7 @@ TarjanResult Tarjan::visitRoot(TypeId ty)
{ {
childCount = 0; childCount = 0;
if (childLimit == 0) if (childLimit == 0)
childLimit = FInt::LuauTarjanChildLimit; childLimit = FInt::LluTarjanChildLimit;
ty = log->follow(ty); ty = log->follow(ty);
@ -258,7 +256,7 @@ TarjanResult Tarjan::visitRoot(TypePackId tp)
{ {
childCount = 0; childCount = 0;
if (childLimit == 0) if (childLimit == 0)
childLimit = FInt::LuauTarjanChildLimit; childLimit = FInt::LluTarjanChildLimit;
tp = log->follow(tp); tp = log->follow(tp);
@ -433,17 +431,14 @@ TypePackId Substitution::replace(TypePackId tp)
void Substitution::replaceChildren(TypeId ty) void Substitution::replaceChildren(TypeId ty)
{ {
if (BoundTypeVar* btv = log->getMutable<BoundTypeVar>(ty); FFlag::LuauLowerBoundsCalculation && btv) if (BoundTypeVar* btv = log->getMutable<BoundTypeVar>(ty); FFlag::LluLowerBoundsCalculation && btv)
btv->boundTo = replace(btv->boundTo); btv->boundTo = replace(btv->boundTo);
LUAU_ASSERT(ty == log->follow(ty)); lluz_ASSERT(ty == log->follow(ty));
if (ignoreChildren(ty)) if (ignoreChildren(ty))
return; return;
if (FFlag::LuauAnyificationMustClone && ty->owningArena != arena)
return;
if (FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty)) if (FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty))
{ {
ftv->argTypes = replace(ftv->argTypes); ftv->argTypes = replace(ftv->argTypes);
@ -451,7 +446,7 @@ void Substitution::replaceChildren(TypeId ty)
} }
else if (TableTypeVar* ttv = getMutable<TableTypeVar>(ty)) else if (TableTypeVar* ttv = getMutable<TableTypeVar>(ty))
{ {
LUAU_ASSERT(!ttv->boundTo); lluz_ASSERT(!ttv->boundTo);
for (auto& [name, prop] : ttv->props) for (auto& [name, prop] : ttv->props)
prop.type = replace(prop.type); prop.type = replace(prop.type);
if (ttv->indexer) if (ttv->indexer)
@ -490,14 +485,11 @@ void Substitution::replaceChildren(TypeId ty)
void Substitution::replaceChildren(TypePackId tp) void Substitution::replaceChildren(TypePackId tp)
{ {
LUAU_ASSERT(tp == log->follow(tp)); lluz_ASSERT(tp == log->follow(tp));
if (ignoreChildren(tp)) if (ignoreChildren(tp))
return; return;
if (FFlag::LuauAnyificationMustClone && tp->owningArena != arena)
return;
if (TypePack* tpp = getMutable<TypePack>(tp)) if (TypePack* tpp = getMutable<TypePack>(tp))
{ {
for (TypeId& tv : tpp->head) for (TypeId& tv : tpp->head)
@ -511,4 +503,4 @@ void Substitution::replaceChildren(TypePackId tp)
} }
} }
} // namespace Luau } // namespace lluz

View file

@ -1,9 +1,9 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Symbol.h" #include "lluz/Symbol.h"
#include "Luau/Common.h" #include "lluz/Common.h"
namespace Luau namespace lluz
{ {
std::string toString(const Symbol& name) std::string toString(const Symbol& name)
@ -11,8 +11,8 @@ std::string toString(const Symbol& name)
if (name.local) if (name.local)
return name.local->name.value; return name.local->name.value;
LUAU_ASSERT(name.global.value); lluz_ASSERT(name.global.value);
return name.global.value; return name.global.value;
} }
} // namespace Luau } // namespace lluz

View file

@ -1,15 +1,15 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/ToDot.h" #include "lluz/ToDot.h"
#include "Luau/ToString.h" #include "lluz/ToString.h"
#include "Luau/TypePack.h" #include "lluz/TypePack.h"
#include "Luau/TypeVar.h" #include "lluz/TypeVar.h"
#include "Luau/StringUtils.h" #include "lluz/StringUtils.h"
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
namespace Luau namespace lluz
{ {
namespace namespace
@ -106,12 +106,12 @@ void StateDot::startNode(int index)
void StateDot::finishNode() void StateDot::finishNode()
{ {
formatAppend(result, "];\n"); formatAppend(result, XorStr("];\n"));
} }
void StateDot::startNodeLabel() void StateDot::startNodeLabel()
{ {
formatAppend(result, "label=\""); formatAppend(result, XorStr("label=\""));
} }
void StateDot::finishNodeLabel(TypeId ty) void StateDot::finishNodeLabel(TypeId ty)
@ -153,8 +153,8 @@ void StateDot::visitChildren(TypeId ty, int index)
finishNodeLabel(ty); finishNodeLabel(ty);
finishNode(); finishNode();
visitChild(ftv->argTypes, index, "arg"); visitChild(ftv->argTypes, index, XorStr("arg"));
visitChild(ftv->retTypes, index, "ret"); visitChild(ftv->retTypes, index, XorStr("ret"));
} }
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty)) else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
{ {
@ -168,20 +168,20 @@ void StateDot::visitChildren(TypeId ty, int index)
finishNode(); finishNode();
if (ttv->boundTo) if (ttv->boundTo)
return visitChild(*ttv->boundTo, index, "boundTo"); return visitChild(*ttv->boundTo, index, XorStr("boundTo"));
for (const auto& [name, prop] : ttv->props) for (const auto& [name, prop] : ttv->props)
visitChild(prop.type, index, name.c_str()); visitChild(prop.type, index, name.c_str());
if (ttv->indexer) if (ttv->indexer)
{ {
visitChild(ttv->indexer->indexType, index, "[index]"); visitChild(ttv->indexer->indexType, index, XorStr("[index]"));
visitChild(ttv->indexer->indexResultType, index, "[value]"); visitChild(ttv->indexer->indexResultType, index, XorStr("[value]"));
} }
for (TypeId itp : ttv->instantiatedTypeParams) for (TypeId itp : ttv->instantiatedTypeParams)
visitChild(itp, index, "typeParam"); visitChild(itp, index, XorStr("typeParam"));
for (TypePackId itp : ttv->instantiatedTypePackParams) for (TypePackId itp : ttv->instantiatedTypePackParams)
visitChild(itp, index, "typePackParam"); visitChild(itp, index, XorStr("typePackParam"));
} }
else if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(ty)) else if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(ty))
{ {
@ -189,8 +189,8 @@ void StateDot::visitChildren(TypeId ty, int index)
finishNodeLabel(ty); finishNodeLabel(ty);
finishNode(); finishNode();
visitChild(mtv->table, index, "table"); visitChild(mtv->table, index, XorStr("table"));
visitChild(mtv->metatable, index, "metatable"); visitChild(mtv->metatable, index, XorStr("metatable"));
} }
else if (const UnionTypeVar* utv = get<UnionTypeVar>(ty)) else if (const UnionTypeVar* utv = get<UnionTypeVar>(ty))
{ {
@ -262,10 +262,10 @@ void StateDot::visitChildren(TypeId ty, int index)
visitChild(prop.type, index, name.c_str()); visitChild(prop.type, index, name.c_str());
if (ctv->parent) if (ctv->parent)
visitChild(*ctv->parent, index, "[parent]"); visitChild(*ctv->parent, index, XorStr("[parent]"));
if (ctv->metatable) if (ctv->metatable)
visitChild(*ctv->metatable, index, "[metatable]"); visitChild(*ctv->metatable, index, XorStr("[metatable]"));
} }
else if (const SingletonTypeVar* stv = get<SingletonTypeVar>(ty)) else if (const SingletonTypeVar* stv = get<SingletonTypeVar>(ty))
{ {
@ -283,7 +283,7 @@ void StateDot::visitChildren(TypeId ty, int index)
res += bs->value ? "true" : "false"; res += bs->value ? "true" : "false";
} }
else else
LUAU_ASSERT(!"unknown singleton type"); lluz_ASSERT(!XorStr("unknown singleton type"));
formatAppend(result, "SingletonTypeVar %s", res.c_str()); formatAppend(result, "SingletonTypeVar %s", res.c_str());
finishNodeLabel(ty); finishNodeLabel(ty);
@ -291,7 +291,7 @@ void StateDot::visitChildren(TypeId ty, int index)
} }
else else
{ {
LUAU_ASSERT(!"unknown type kind"); lluz_ASSERT(!XorStr("unknown type kind"));
finishNodeLabel(ty); finishNodeLabel(ty);
finishNode(); finishNode();
} }
@ -323,7 +323,7 @@ void StateDot::visitChildren(TypePackId tp, int index)
for (TypeId tv : tpp->head) for (TypeId tv : tpp->head)
visitChild(tv, index); visitChild(tv, index);
if (tpp->tail) if (tpp->tail)
visitChild(*tpp->tail, index, "tail"); visitChild(*tpp->tail, index, XorStr("tail"));
} }
else if (const VariadicTypePack* vtp = get<VariadicTypePack>(tp)) else if (const VariadicTypePack* vtp = get<VariadicTypePack>(tp))
{ {
@ -356,7 +356,7 @@ void StateDot::visitChildren(TypePackId tp, int index)
} }
else else
{ {
LUAU_ASSERT(!"unknown type pack kind"); lluz_ASSERT(!XorStr("unknown type pack kind"));
finishNodeLabel(tp); finishNodeLabel(tp);
finishNode(); finishNode();
} }
@ -406,4 +406,4 @@ void dumpDot(TypePackId tp)
printf("%s\n", toDot(tp).c_str()); printf("%s\n", toDot(tp).c_str());
} }
} // namespace Luau } // namespace lluz

View file

@ -1,27 +1,26 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/ToString.h" #include "lluz/ToString.h"
#include "Luau/Scope.h" #include "lluz/Scope.h"
#include "Luau/TypeInfer.h" #include "lluz/TypeInfer.h"
#include "Luau/TypePack.h" #include "lluz/TypePack.h"
#include "Luau/TypeVar.h" #include "lluz/TypeVar.h"
#include "Luau/VisitTypeVar.h" #include "lluz/VisitTypeVar.h"
#include <algorithm> #include <algorithm>
#include <stdexcept> #include <stdexcept>
LUAU_FASTFLAG(LuauLowerBoundsCalculation) lluz_FASTFLAG(LluLowerBoundsCalculation)
LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAGVARIABLE(LuauSpecialTypesAsterisked, false)
/* /*
* Prefix generic typenames with gen- * Prefix generic typenames with gen-
* Additionally, free types will be prefixed with free- and suffixed with their level. eg free-a-4 * Additionally, free types will be prefixed with free- and suffixed with their level. eg free-a-4
* Fair warning: Setting this will break a lot of Luau unit tests. * Fair warning: Setting this will break a lot of lluz unit tests.
*/ */
LUAU_FASTFLAGVARIABLE(DebugLuauVerboseTypeNames, false) lluz_FASTFLAGVARIABLE(DebugLluVerboseTypeNames, false)
lluz_FASTFLAGVARIABLE(LluToStringTableBracesNewlines, false)
namespace Luau namespace lluz
{ {
namespace namespace
@ -97,7 +96,7 @@ void findCyclicTypes(std::set<TypeId>& cycles, std::set<TypePackId>& cycleTPs, T
} // namespace } // namespace
static std::pair<bool, std::optional<Luau::Name>> canUseTypeNameInScope(ScopePtr scope, const std::string& name) static std::pair<bool, std::optional<lluz::Name>> canUseTypeNameInScope(ScopePtr scope, const std::string& name)
{ {
for (ScopePtr curr = scope; curr; curr = curr->parent) for (ScopePtr curr = scope; curr; curr = curr->parent)
{ {
@ -215,7 +214,7 @@ struct StringifierState
void emit(TypeLevel level) void emit(TypeLevel level)
{ {
emit(std::to_string(level.level)); emit(std::to_string(level.level));
emit("-"); emit(XorStr("-"));
emit(std::to_string(level.subLevel)); emit(std::to_string(level.subLevel));
} }
@ -245,9 +244,9 @@ struct StringifierState
void newline() void newline()
{ {
if (!opts.useLineBreaks) if (!opts.useLineBreaks)
return emit(" "); return emit(XorStr(" "));
emit("\n"); emit(XorStr("\n"));
emitIndentation(); emitIndentation();
} }
@ -278,10 +277,7 @@ struct TypeVarStringifier
if (tv->ty.valueless_by_exception()) if (tv->ty.valueless_by_exception())
{ {
state.result.error = true; state.result.error = true;
if (FFlag::LuauSpecialTypesAsterisked) state.emit(XorStr("< VALUELESS BY EXCEPTION >"));
state.emit("* VALUELESS BY EXCEPTION *");
else
state.emit("< VALUELESS BY EXCEPTION >");
return; return;
} }
@ -292,7 +288,7 @@ struct TypeVarStringifier
return; return;
} }
Luau::visit( lluz::visit(
[this, tv](auto&& t) [this, tv](auto&& t)
{ {
return (*this)(tv, t); return (*this)(tv, t);
@ -309,14 +305,14 @@ struct TypeVarStringifier
return; return;
if (types.size() || typePacks.size()) if (types.size() || typePacks.size())
state.emit("<"); state.emit(XorStr("<"));
bool first = true; bool first = true;
for (TypeId ty : types) for (TypeId ty : types)
{ {
if (!first) if (!first)
state.emit(", "); state.emit(XorStr(", "));
first = false; first = false;
stringify(ty); stringify(ty);
@ -330,35 +326,35 @@ struct TypeVarStringifier
continue; continue;
if (!first) if (!first)
state.emit(", "); state.emit(XorStr(", "));
else else
first = false; first = false;
bool wrap = !singleTp && get<TypePack>(follow(tp)); bool wrap = !singleTp && get<TypePack>(follow(tp));
if (wrap) if (wrap)
state.emit("("); state.emit(XorStr("("));
stringify(tp); stringify(tp);
if (wrap) if (wrap)
state.emit(")"); state.emit(XorStr(")"));
} }
if (types.size() || typePacks.size()) if (types.size() || typePacks.size())
state.emit(">"); state.emit(XorStr(">"));
} }
void operator()(TypeId ty, const Unifiable::Free& ftv) void operator()(TypeId ty, const Unifiable::Free& ftv)
{ {
state.result.invalid = true; state.result.invalid = true;
if (FFlag::DebugLuauVerboseTypeNames) if (FFlag::DebugLluVerboseTypeNames)
state.emit("free-"); state.emit(XorStr("free-"));
state.emit(state.getName(ty)); state.emit(state.getName(ty));
if (FFlag::DebugLuauVerboseTypeNames) if (FFlag::DebugLluVerboseTypeNames)
{ {
state.emit("-"); state.emit(XorStr("-"));
state.emit(ftv.level); state.emit(ftv.level);
} }
} }
@ -384,10 +380,10 @@ struct TypeVarStringifier
{ {
state.result.invalid = true; state.result.invalid = true;
state.emit("["); state.emit(XorStr("["));
if (FFlag::DebugLuauVerboseTypeNames) if (FFlag::DebugLluVerboseTypeNames)
state.emit(ctv.level); state.emit(ctv.level);
state.emit("["); state.emit(XorStr("["));
bool first = true; bool first = true;
for (TypeId ty : ctv.parts) for (TypeId ty : ctv.parts)
@ -395,19 +391,19 @@ struct TypeVarStringifier
if (first) if (first)
first = false; first = false;
else else
state.emit("|"); state.emit(XorStr("|"));
stringify(ty); stringify(ty);
} }
state.emit("]]"); state.emit(XorStr("]]"));
} }
void operator()(TypeId, const BlockedTypeVar& btv) void operator()(TypeId, const BlockedTypeVar& btv)
{ {
state.emit("*blocked-"); state.emit(XorStr("*blocked-"));
state.emit(btv.index); state.emit(btv.index);
state.emit("*"); state.emit(XorStr("*"));
} }
void operator()(TypeId, const PrimitiveTypeVar& ptv) void operator()(TypeId, const PrimitiveTypeVar& ptv)
@ -415,40 +411,40 @@ struct TypeVarStringifier
switch (ptv.type) switch (ptv.type)
{ {
case PrimitiveTypeVar::NilType: case PrimitiveTypeVar::NilType:
state.emit("nil"); state.emit(XorStr("nil"));
return; return;
case PrimitiveTypeVar::Boolean: case PrimitiveTypeVar::Boolean:
state.emit("boolean"); state.emit(XorStr("boolean"));
return; return;
case PrimitiveTypeVar::Number: case PrimitiveTypeVar::Number:
state.emit("number"); state.emit(XorStr("number"));
return; return;
case PrimitiveTypeVar::String: case PrimitiveTypeVar::String:
state.emit("string"); state.emit(XorStr("string"));
return; return;
case PrimitiveTypeVar::Thread: case PrimitiveTypeVar::Thread:
state.emit("thread"); state.emit(XorStr("thread"));
return; return;
default: default:
LUAU_ASSERT(!"Unknown primitive type"); lluz_ASSERT(!XorStr("Unknown primitive type"));
throw std::runtime_error("Unknown primitive type " + std::to_string(ptv.type)); throw std::runtime_error("Unknown primitive type " + std::to_string(ptv.type));
} }
} }
void operator()(TypeId, const SingletonTypeVar& stv) void operator()(TypeId, const SingletonTypeVar& stv)
{ {
if (const BooleanSingleton* bs = Luau::get<BooleanSingleton>(&stv)) if (const BooleanSingleton* bs = lluz::get<BooleanSingleton>(&stv))
state.emit(bs->value ? "true" : "false"); state.emit(bs->value ? XorStr("true" : "false"));
else if (const StringSingleton* ss = Luau::get<StringSingleton>(&stv)) else if (const StringSingleton* ss = lluz::get<StringSingleton>(&stv))
{ {
state.emit("\""); state.emit(XorStr("\""));
state.emit(escape(ss->value)); state.emit(escape(ss->value));
state.emit("\""); state.emit(XorStr("\""));
} }
else else
{ {
LUAU_ASSERT(!"Unknown singleton type"); lluz_ASSERT(!XorStr("Unknown singleton type"));
throw std::runtime_error("Unknown singleton type"); throw std::runtime_error(XorStr("Unknown singleton type"));
} }
} }
@ -457,47 +453,44 @@ struct TypeVarStringifier
if (state.hasSeen(&ftv)) if (state.hasSeen(&ftv))
{ {
state.result.cycle = true; state.result.cycle = true;
if (FFlag::LuauSpecialTypesAsterisked) state.emit(XorStr("<CYCLE>"));
state.emit("*CYCLE*");
else
state.emit("<CYCLE>");
return; return;
} }
// We should not be respecting opts.hideNamedFunctionTypeParameters here. // We should not be respecting opts.hideNamedFunctionTypeParameters here.
if (ftv.generics.size() > 0 || ftv.genericPacks.size() > 0) if (ftv.generics.size() > 0 || ftv.genericPacks.size() > 0)
{ {
state.emit("<"); state.emit(XorStr("<"));
bool comma = false; bool comma = false;
for (auto it = ftv.generics.begin(); it != ftv.generics.end(); ++it) for (auto it = ftv.generics.begin(); it != ftv.generics.end(); ++it)
{ {
if (comma) if (comma)
state.emit(", "); state.emit(XorStr(", "));
comma = true; comma = true;
stringify(*it); stringify(*it);
} }
for (auto it = ftv.genericPacks.begin(); it != ftv.genericPacks.end(); ++it) for (auto it = ftv.genericPacks.begin(); it != ftv.genericPacks.end(); ++it)
{ {
if (comma) if (comma)
state.emit(", "); state.emit(XorStr(", "));
comma = true; comma = true;
stringify(*it); stringify(*it);
} }
state.emit(">"); state.emit(XorStr(">"));
} }
state.emit("("); state.emit(XorStr("("));
if (state.opts.functionTypeArguments) if (state.opts.functionTypeArguments)
stringify(ftv.argTypes, ftv.argNames); stringify(ftv.argTypes, ftv.argNames);
else else
stringify(ftv.argTypes); stringify(ftv.argTypes);
state.emit(") -> "); state.emit(XorStr(") -> "));
bool plural = true; bool plural = true;
if (FFlag::LuauLowerBoundsCalculation) if (FFlag::LluLowerBoundsCalculation)
{ {
auto retBegin = begin(ftv.retTypes); auto retBegin = begin(ftv.retTypes);
auto retEnd = end(ftv.retTypes); auto retEnd = end(ftv.retTypes);
@ -518,12 +511,12 @@ struct TypeVarStringifier
} }
if (plural) if (plural)
state.emit("("); state.emit(XorStr("("));
stringify(ftv.retTypes); stringify(ftv.retTypes);
if (plural) if (plural)
state.emit(")"); state.emit(XorStr(")"));
state.unsee(&ftv); state.unsee(&ftv);
} }
@ -548,7 +541,7 @@ struct TypeVarStringifier
if (moduleName) if (moduleName)
{ {
state.emit(*moduleName); state.emit(*moduleName);
state.emit("."); state.emit(XorStr("."));
} }
} }
@ -568,10 +561,7 @@ struct TypeVarStringifier
if (state.hasSeen(&ttv)) if (state.hasSeen(&ttv))
{ {
state.result.cycle = true; state.result.cycle = true;
if (FFlag::LuauSpecialTypesAsterisked) state.emit(XorStr("<CYCLE>"));
state.emit("*CYCLE*");
else
state.emit("<CYCLE>");
return; return;
} }
@ -581,31 +571,63 @@ struct TypeVarStringifier
{ {
case TableState::Sealed: case TableState::Sealed:
state.result.invalid = true; state.result.invalid = true;
openbrace = "{|"; if (FFlag::LluToStringTableBracesNewlines)
closedbrace = "|}"; {
openbrace = "{|";
closedbrace = "|}";
}
else
{
openbrace = "{| ";
closedbrace = " |}";
}
break; break;
case TableState::Unsealed: case TableState::Unsealed:
openbrace = "{"; if (FFlag::LluToStringTableBracesNewlines)
closedbrace = "}"; {
openbrace = "{";
closedbrace = "}";
}
else
{
openbrace = "{ ";
closedbrace = " }";
}
break; break;
case TableState::Free: case TableState::Free:
state.result.invalid = true; state.result.invalid = true;
openbrace = "{-"; if (FFlag::LluToStringTableBracesNewlines)
closedbrace = "-}"; {
openbrace = "{-";
closedbrace = "-}";
}
else
{
openbrace = "{- ";
closedbrace = " -}";
}
break; break;
case TableState::Generic: case TableState::Generic:
state.result.invalid = true; state.result.invalid = true;
openbrace = "{+"; if (FFlag::LluToStringTableBracesNewlines)
closedbrace = "+}"; {
openbrace = "{+";
closedbrace = "+}";
}
else
{
openbrace = "{+ ";
closedbrace = " +}";
}
break; break;
} }
// If this appears to be an array, we want to stringify it using the {T} syntax. // If this appears to be an array, we want to stringify it using the {T} syntax.
if (ttv.indexer && ttv.props.empty() && isNumber(ttv.indexer->indexType)) if (ttv.indexer && ttv.props.empty() && isNumber(ttv.indexer->indexType))
{ {
state.emit("{"); state.emit(XorStr("{"));
stringify(ttv.indexer->indexResultType); stringify(ttv.indexer->indexResultType);
state.emit("}"); state.emit(XorStr("}"));
return; return;
} }
@ -615,10 +637,11 @@ struct TypeVarStringifier
bool comma = false; bool comma = false;
if (ttv.indexer) if (ttv.indexer)
{ {
state.newline(); if (FFlag::LluToStringTableBracesNewlines)
state.emit("["); state.newline();
state.emit(XorStr("["));
stringify(ttv.indexer->indexType); stringify(ttv.indexer->indexType);
state.emit("]: "); state.emit(XorStr("]: "));
stringify(ttv.indexer->indexResultType); stringify(ttv.indexer->indexResultType);
comma = true; comma = true;
} }
@ -629,19 +652,21 @@ struct TypeVarStringifier
{ {
if (comma) if (comma)
{ {
state.emit(","); state.emit(XorStr(","));
state.newline(); state.newline();
} }
else else if (FFlag::LluToStringTableBracesNewlines)
{
state.newline(); state.newline();
}
size_t length = state.result.name.length() - oldLength; size_t length = state.result.name.length() - oldLength;
if (state.opts.maxTableLength > 0 && (length - 2 * index) >= state.opts.maxTableLength) if (state.opts.maxTableLength > 0 && (length - 2 * index) >= state.opts.maxTableLength)
{ {
state.emit("... "); state.emit(XorStr("... "));
state.emit(std::to_string(ttv.props.size() - index)); state.emit(std::to_string(ttv.props.size() - index));
state.emit(" more ..."); state.emit(XorStr(" more ..."));
break; break;
} }
@ -649,21 +674,24 @@ struct TypeVarStringifier
state.emit(name); state.emit(name);
else else
{ {
state.emit("[\""); state.emit(XorStr("[\""));
state.emit(escape(name)); state.emit(escape(name));
state.emit("\"]"); state.emit(XorStr("\"]"));
} }
state.emit(": "); state.emit(XorStr(": "));
stringify(prop.type); stringify(prop.type);
comma = true; comma = true;
++index; ++index;
} }
state.dedent(); state.dedent();
if (comma) if (FFlag::LluToStringTableBracesNewlines)
state.newline(); {
else if (comma)
state.emit(" "); state.newline();
else
state.emit(XorStr(" "));
}
state.emit(closedbrace); state.emit(closedbrace);
state.unsee(&ttv); state.unsee(&ttv);
@ -678,12 +706,12 @@ struct TypeVarStringifier
return; return;
} }
state.emit("{ @metatable "); state.emit(XorStr("{ @metatable "));
stringify(mtv.metatable); stringify(mtv.metatable);
state.emit(","); state.emit(XorStr(","));
state.newline(); state.newline();
stringify(mtv.table); stringify(mtv.table);
state.emit(" }"); state.emit(XorStr(" }"));
} }
void operator()(TypeId, const ClassTypeVar& ctv) void operator()(TypeId, const ClassTypeVar& ctv)
@ -693,7 +721,7 @@ struct TypeVarStringifier
void operator()(TypeId, const AnyTypeVar&) void operator()(TypeId, const AnyTypeVar&)
{ {
state.emit("any"); state.emit(XorStr("any"));
} }
void operator()(TypeId, const UnionTypeVar& uv) void operator()(TypeId, const UnionTypeVar& uv)
@ -701,10 +729,7 @@ struct TypeVarStringifier
if (state.hasSeen(&uv)) if (state.hasSeen(&uv))
{ {
state.result.cycle = true; state.result.cycle = true;
if (FFlag::LuauSpecialTypesAsterisked) state.emit(XorStr("<CYCLE>"));
state.emit("*CYCLE*");
else
state.emit("<CYCLE>");
return; return;
} }
@ -726,12 +751,12 @@ struct TypeVarStringifier
bool needParens = !state.cycleNames.count(el) && (get<IntersectionTypeVar>(el) || get<FunctionTypeVar>(el)); bool needParens = !state.cycleNames.count(el) && (get<IntersectionTypeVar>(el) || get<FunctionTypeVar>(el));
if (needParens) if (needParens)
state.emit("("); state.emit(XorStr("("));
stringify(el); stringify(el);
if (needParens) if (needParens)
state.emit(")"); state.emit(XorStr(")"));
results.push_back(std::move(state.result.name)); results.push_back(std::move(state.result.name));
state.result.name = std::move(saved); state.result.name = std::move(saved);
@ -742,7 +767,7 @@ struct TypeVarStringifier
std::sort(results.begin(), results.end()); std::sort(results.begin(), results.end());
if (optional && results.size() > 1) if (optional && results.size() > 1)
state.emit("("); state.emit(XorStr("("));
bool first = true; bool first = true;
for (std::string& ss : results) for (std::string& ss : results)
@ -750,7 +775,7 @@ struct TypeVarStringifier
if (!first) if (!first)
{ {
state.newline(); state.newline();
state.emit("| "); state.emit(XorStr("| "));
} }
state.emit(ss); state.emit(ss);
first = false; first = false;
@ -771,10 +796,7 @@ struct TypeVarStringifier
if (state.hasSeen(&uv)) if (state.hasSeen(&uv))
{ {
state.result.cycle = true; state.result.cycle = true;
if (FFlag::LuauSpecialTypesAsterisked) state.emit(XorStr("<CYCLE>"));
state.emit("*CYCLE*");
else
state.emit("<CYCLE>");
return; return;
} }
@ -788,12 +810,12 @@ struct TypeVarStringifier
bool needParens = !state.cycleNames.count(el) && (get<UnionTypeVar>(el) || get<FunctionTypeVar>(el)); bool needParens = !state.cycleNames.count(el) && (get<UnionTypeVar>(el) || get<FunctionTypeVar>(el));
if (needParens) if (needParens)
state.emit("("); state.emit(XorStr("("));
stringify(el); stringify(el);
if (needParens) if (needParens)
state.emit(")"); state.emit(XorStr(")"));
results.push_back(std::move(state.result.name)); results.push_back(std::move(state.result.name));
state.result.name = std::move(saved); state.result.name = std::move(saved);
@ -809,7 +831,7 @@ struct TypeVarStringifier
if (!first) if (!first)
{ {
state.newline(); state.newline();
state.emit("& "); state.emit(XorStr("& "));
} }
state.emit(ss); state.emit(ss);
first = false; first = false;
@ -819,28 +841,16 @@ struct TypeVarStringifier
void operator()(TypeId, const ErrorTypeVar& tv) void operator()(TypeId, const ErrorTypeVar& tv)
{ {
state.result.error = true; state.result.error = true;
if (FFlag::LuauSpecialTypesAsterisked) state.emit(XorStr("*unknown*"));
state.emit(FFlag::LuauUnknownAndNeverType ? "*error-type*" : "*unknown*");
else
state.emit(FFlag::LuauUnknownAndNeverType ? "<error-type>" : "*unknown*");
} }
void operator()(TypeId, const LazyTypeVar& ltv) void operator()(TypeId, const LazyTypeVar& ltv)
{ {
state.result.invalid = true; state.result.invalid = true;
state.emit("lazy?"); state.emit(XorStr("lazy?"));
} }
void operator()(TypeId, const UnknownTypeVar& ttv) }; // namespace
{
state.emit("unknown");
}
void operator()(TypeId, const NeverTypeVar& ttv)
{
state.emit("never");
}
};
struct TypePackStringifier struct TypePackStringifier
{ {
@ -876,10 +886,7 @@ struct TypePackStringifier
if (tp->ty.valueless_by_exception()) if (tp->ty.valueless_by_exception())
{ {
state.result.error = true; state.result.error = true;
if (FFlag::LuauSpecialTypesAsterisked) state.emit(XorStr("< VALUELESS TP BY EXCEPTION >"));
state.emit("* VALUELESS TP BY EXCEPTION *");
else
state.emit("< VALUELESS TP BY EXCEPTION >");
return; return;
} }
@ -890,7 +897,7 @@ struct TypePackStringifier
return; return;
} }
Luau::visit( lluz::visit(
[this, tp](auto&& t) [this, tp](auto&& t)
{ {
return (*this)(tp, t); return (*this)(tp, t);
@ -903,10 +910,7 @@ struct TypePackStringifier
if (state.hasSeen(&tp)) if (state.hasSeen(&tp))
{ {
state.result.cycle = true; state.result.cycle = true;
if (FFlag::LuauSpecialTypesAsterisked) state.emit(XorStr("<CYCLETP>"));
state.emit("*CYCLETP*");
else
state.emit("<CYCLETP>");
return; return;
} }
@ -917,13 +921,13 @@ struct TypePackStringifier
if (first) if (first)
first = false; first = false;
else else
state.emit(", "); state.emit(XorStr(", "));
// Do not respect opts.namedFunctionOverrideArgNames here // Do not respect opts.namedFunctionOverrideArgNames here
if (elemIndex < elemNames.size() && elemNames[elemIndex]) if (elemIndex < elemNames.size() && elemNames[elemIndex])
{ {
state.emit(elemNames[elemIndex]->name); state.emit(elemNames[elemIndex]->name);
state.emit(": "); state.emit(XorStr(": "));
} }
elemIndex++; elemIndex++;
@ -934,12 +938,12 @@ struct TypePackStringifier
if (tp.tail && !isEmpty(*tp.tail)) if (tp.tail && !isEmpty(*tp.tail))
{ {
TypePackId tail = follow(*tp.tail); TypePackId tail = follow(*tp.tail);
if (auto vtp = get<VariadicTypePack>(tail); !vtp || (!FFlag::DebugLuauVerboseTypeNames && !vtp->hidden)) if (auto vtp = get<VariadicTypePack>(tail); !vtp || (!FFlag::DebugLluVerboseTypeNames && !vtp->hidden))
{ {
if (first) if (first)
first = false; first = false;
else else
state.emit(", "); state.emit(XorStr(", "));
stringify(tail); stringify(tail);
} }
@ -951,29 +955,21 @@ struct TypePackStringifier
void operator()(TypePackId, const Unifiable::Error& error) void operator()(TypePackId, const Unifiable::Error& error)
{ {
state.result.error = true; state.result.error = true;
if (FFlag::LuauSpecialTypesAsterisked) state.emit(XorStr("*unknown*"));
state.emit(FFlag::LuauUnknownAndNeverType ? "*error-type*" : "*unknown*");
else
state.emit(FFlag::LuauUnknownAndNeverType ? "<error-type>" : "*unknown*");
} }
void operator()(TypePackId, const VariadicTypePack& pack) void operator()(TypePackId, const VariadicTypePack& pack)
{ {
state.emit("..."); state.emit(XorStr("..."));
if (FFlag::DebugLuauVerboseTypeNames && pack.hidden) if (FFlag::DebugLluVerboseTypeNames && pack.hidden)
{ state.emit(XorStr("<hidden>"));
if (FFlag::LuauSpecialTypesAsterisked)
state.emit("*hidden*");
else
state.emit("<hidden>");
}
stringify(pack.ty); stringify(pack.ty);
} }
void operator()(TypePackId tp, const GenericTypePack& pack) void operator()(TypePackId tp, const GenericTypePack& pack)
{ {
if (FFlag::DebugLuauVerboseTypeNames) if (FFlag::DebugLluVerboseTypeNames)
state.emit("gen-"); state.emit(XorStr("gen-"));
if (pack.explicitName) if (pack.explicitName)
{ {
state.usedNames.insert(pack.name); state.usedNames.insert(pack.name);
@ -984,23 +980,23 @@ struct TypePackStringifier
{ {
state.emit(state.getName(tp)); state.emit(state.getName(tp));
} }
state.emit("..."); state.emit(XorStr("..."));
} }
void operator()(TypePackId tp, const FreeTypePack& pack) void operator()(TypePackId tp, const FreeTypePack& pack)
{ {
state.result.invalid = true; state.result.invalid = true;
if (FFlag::DebugLuauVerboseTypeNames) if (FFlag::DebugLluVerboseTypeNames)
state.emit("free-"); state.emit(XorStr("free-"));
state.emit(state.getName(tp)); state.emit(state.getName(tp));
if (FFlag::DebugLuauVerboseTypeNames) if (FFlag::DebugLluVerboseTypeNames)
{ {
state.emit("-"); state.emit(XorStr("-"));
state.emit(pack.level); state.emit(pack.level);
} }
state.emit("..."); state.emit(XorStr("..."));
} }
void operator()(TypePackId, const BoundTypePack& btv) void operator()(TypePackId, const BoundTypePack& btv)
@ -1097,7 +1093,7 @@ ToStringResult toStringDetailed(TypeId ty, const ToStringOptions& opts)
result.invalid = true; result.invalid = true;
if (moduleName) if (moduleName)
result.name = format("%s.", moduleName->c_str()); result.name = format(XorStr("%s."), moduleName->c_str());
} }
result.name += ttv->name ? *ttv->name : *ttv->syntheticName; result.name += ttv->name ? *ttv->name : *ttv->syntheticName;
@ -1128,7 +1124,7 @@ ToStringResult toStringDetailed(TypeId ty, const ToStringOptions& opts)
if (!state.cycleNames.empty()) if (!state.cycleNames.empty())
{ {
result.cycle = true; result.cycle = true;
state.emit(" where "); state.emit(XorStr(" where "));
} }
state.exhaustive = true; state.exhaustive = true;
@ -1144,11 +1140,11 @@ ToStringResult toStringDetailed(TypeId ty, const ToStringOptions& opts)
for (const auto& [cycleTy, name] : sortedCycleNames) for (const auto& [cycleTy, name] : sortedCycleNames)
{ {
if (semi) if (semi)
state.emit(" ; "); state.emit(XorStr(" ; "));
state.emit(name); state.emit(name);
state.emit(" = "); state.emit(XorStr(" = "));
Luau::visit( lluz::visit(
[&tvs, cycleTy = cycleTy](auto&& t) [&tvs, cycleTy = cycleTy](auto&& t)
{ {
return tvs(cycleTy, t); return tvs(cycleTy, t);
@ -1161,11 +1157,7 @@ ToStringResult toStringDetailed(TypeId ty, const ToStringOptions& opts)
if (opts.maxTypeLength > 0 && result.name.length() > opts.maxTypeLength) if (opts.maxTypeLength > 0 && result.name.length() > opts.maxTypeLength)
{ {
result.truncated = true; result.truncated = true;
result.name += "... <TRUNCATED>";
if (FFlag::LuauSpecialTypesAsterisked)
result.name += "... *TRUNCATED*";
else
result.name += "... <TRUNCATED>";
} }
return result; return result;
@ -1205,7 +1197,7 @@ ToStringResult toStringDetailed(TypePackId tp, const ToStringOptions& opts)
if (!cycles.empty()) if (!cycles.empty())
{ {
result.cycle = true; result.cycle = true;
state.emit(" where "); state.emit(XorStr(" where "));
} }
state.exhaustive = true; state.exhaustive = true;
@ -1221,11 +1213,11 @@ ToStringResult toStringDetailed(TypePackId tp, const ToStringOptions& opts)
for (const auto& [cycleTy, name] : sortedCycleNames) for (const auto& [cycleTy, name] : sortedCycleNames)
{ {
if (semi) if (semi)
state.emit(" ; "); state.emit(XorStr(" ; "));
state.emit(name); state.emit(name);
state.emit(" = "); state.emit(XorStr(" = "));
Luau::visit( lluz::visit(
[&tvs, cycleTy = cycleTy](auto t) [&tvs, cycleTy = cycleTy](auto t)
{ {
return tvs(cycleTy, t); return tvs(cycleTy, t);
@ -1236,12 +1228,7 @@ ToStringResult toStringDetailed(TypePackId tp, const ToStringOptions& opts)
} }
if (opts.maxTypeLength > 0 && result.name.length() > opts.maxTypeLength) if (opts.maxTypeLength > 0 && result.name.length() > opts.maxTypeLength)
{ result.name += "... <TRUNCATED>";
if (FFlag::LuauSpecialTypesAsterisked)
result.name += "... *TRUNCATED*";
else
result.name += "... <TRUNCATED>";
}
return result; return result;
} }
@ -1277,7 +1264,7 @@ std::string toStringNamedFunction(const std::string& funcName, const FunctionTyp
if (!opts.hideNamedFunctionTypeParameters) if (!opts.hideNamedFunctionTypeParameters)
tvs.stringify(ftv.generics, ftv.genericPacks); tvs.stringify(ftv.generics, ftv.genericPacks);
state.emit("("); state.emit(XorStr("("));
auto argPackIter = begin(ftv.argTypes); auto argPackIter = begin(ftv.argTypes);
@ -1294,21 +1281,21 @@ std::string toStringNamedFunction(const std::string& funcName, const FunctionTyp
} }
if (!first) if (!first)
state.emit(", "); state.emit(XorStr(", "));
first = false; first = false;
// We don't respect opts.functionTypeArguments // We don't respect opts.functionTypeArguments
if (idx < opts.namedFunctionOverrideArgNames.size()) if (idx < opts.namedFunctionOverrideArgNames.size())
{ {
state.emit(opts.namedFunctionOverrideArgNames[idx] + ": "); state.emit(opts.namedFunctionOverrideArgNames[idx] + XorStr(": "));
} }
else if (idx < ftv.argNames.size() && ftv.argNames[idx]) else if (idx < ftv.argNames.size() && ftv.argNames[idx])
{ {
state.emit(ftv.argNames[idx]->name + ": "); state.emit(ftv.argNames[idx]->name + XorStr(": "));
} }
else else
{ {
state.emit("_: "); state.emit(XorStr("_: "));
} }
tvs.stringify(*argPackIter); tvs.stringify(*argPackIter);
@ -1321,9 +1308,9 @@ std::string toStringNamedFunction(const std::string& funcName, const FunctionTyp
if (auto vtp = get<VariadicTypePack>(*argPackIter.tail()); !vtp || !vtp->hidden) if (auto vtp = get<VariadicTypePack>(*argPackIter.tail()); !vtp || !vtp->hidden)
{ {
if (!first) if (!first)
state.emit(", "); state.emit(XorStr(", "));
state.emit("...: "); state.emit(XorStr("...: "));
if (vtp) if (vtp)
tvs.stringify(vtp->ty); tvs.stringify(vtp->ty);
@ -1332,19 +1319,19 @@ std::string toStringNamedFunction(const std::string& funcName, const FunctionTyp
} }
} }
state.emit("): "); state.emit(XorStr("): "));
size_t retSize = size(ftv.retTypes); size_t retSize = size(ftv.retTypes);
bool hasTail = !finite(ftv.retTypes); bool hasTail = !finite(ftv.retTypes);
bool wrap = get<TypePack>(follow(ftv.retTypes)) && (hasTail ? retSize != 0 : retSize != 1); bool wrap = get<TypePack>(follow(ftv.retTypes)) && (hasTail ? retSize != 0 : retSize != 1);
if (wrap) if (wrap)
state.emit("("); state.emit(XorStr("("));
tvs.stringify(ftv.retTypes); tvs.stringify(ftv.retTypes);
if (wrap) if (wrap)
state.emit(")"); state.emit(XorStr(")"));
return result.name; return result.name;
} }
@ -1457,10 +1444,10 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
{ {
ToStringResult namedStr = toStringDetailed(c.namedType, opts); ToStringResult namedStr = toStringDetailed(c.namedType, opts);
opts.nameMap = std::move(namedStr.nameMap); opts.nameMap = std::move(namedStr.nameMap);
return "@name(" + namedStr.name + ") = " + c.name; return XorStr("@name(") + namedStr.name + ") = " + c.name;
} }
else else
static_assert(always_false_v<T>, "Non-exhaustive constraint switch"); static_assert(always_false_v<T>, XorStr("Non-exhaustive constraint switch"));
}; };
return visit(go, constraint.c); return visit(go, constraint.c);
@ -1476,4 +1463,4 @@ std::string dump(const Constraint& c)
return s; return s;
} }
} // namespace Luau } // namespace lluz

View file

@ -1,5 +1,5 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/TopoSortStatements.h" #include "lluz/TopoSortStatements.h"
/* Decide the order in which we typecheck Lua statements in a block. /* Decide the order in which we typecheck Lua statements in a block.
* *
@ -26,10 +26,10 @@
* 3. Cyclic dependencies can be resolved by picking an arbitrary statement to check first. * 3. Cyclic dependencies can be resolved by picking an arbitrary statement to check first.
*/ */
#include "Luau/Ast.h" #include "lluz/Ast.h"
#include "Luau/DenseHash.h" #include "lluz/DenseHash.h"
#include "Luau/Common.h" #include "lluz/Common.h"
#include "Luau/StringUtils.h" #include "lluz/StringUtils.h"
#include <algorithm> #include <algorithm>
#include <deque> #include <deque>
@ -40,7 +40,7 @@
#include <stdexcept> #include <stdexcept>
#include <optional> #include <optional>
namespace Luau namespace lluz
{ {
// For some reason, natvis interacts really poorly with anonymous data types // For some reason, natvis interacts really poorly with anonymous data types
@ -127,7 +127,7 @@ std::optional<Identifier> mkName(const AstExprIndexName& expr)
Identifier mkName(const AstExprError& expr) Identifier mkName(const AstExprError& expr)
{ {
return {format("error#%d", expr.messageIndex), nullptr}; return {format(XorStr("error#%d"), expr.messageIndex), nullptr};
} }
std::optional<Identifier> mkName(const AstExpr& expr) std::optional<Identifier> mkName(const AstExpr& expr)
@ -147,9 +147,9 @@ std::optional<Identifier> mkName(const AstExpr& expr)
Identifier mkName(const AstStatFunction& function) Identifier mkName(const AstStatFunction& function)
{ {
auto name = mkName(*function.name); auto name = mkName(*function.name);
LUAU_ASSERT(bool(name)); lluz_ASSERT(bool(name));
if (!name) if (!name)
throw std::runtime_error("Internal error: Function declaration has a bad name"); throw std::runtime_error(XorStr("Internal error: Function declaration has a bad name"));
return *name; return *name;
} }
@ -255,7 +255,7 @@ struct ArcCollector : public AstVisitor
{ {
auto name = mkName(*node->name); auto name = mkName(*node->name);
if (!name) if (!name)
throw std::runtime_error("Internal error: AstStatFunction has a bad name"); throw std::runtime_error(XorStr("Internal error: AstStatFunction has a bad name"));
add(*name); add(*name);
return true; return true;
@ -382,14 +382,14 @@ void prune(Node* next)
for (const auto& node : next->provides) for (const auto& node : next->provides)
{ {
auto it = node->depends.find(next); auto it = node->depends.find(next);
LUAU_ASSERT(it != node->depends.end()); lluz_ASSERT(it != node->depends.end());
node->depends.erase(it); node->depends.erase(it);
} }
for (const auto& node : next->depends) for (const auto& node : next->depends)
{ {
auto it = node->provides.find(next); auto it = node->provides.find(next);
LUAU_ASSERT(it != node->provides.end()); lluz_ASSERT(it != node->provides.end());
node->provides.erase(it); node->provides.erase(it);
} }
} }
@ -439,7 +439,7 @@ void drain(NodeList& Q, std::vector<AstStat*>& result, Node* target)
if (isBlockTerminator(*iter->get()->element)) if (isBlockTerminator(*iter->get()->element))
continue; continue;
LUAU_ASSERT(allArcs.end() != allArcs.find(iter->get())); lluz_ASSERT(allArcs.end() != allArcs.find(iter->get()));
const Arcs& arcs = allArcs[iter->get()]; const Arcs& arcs = allArcs[iter->get()];
if (arcs.depends.empty()) if (arcs.depends.empty())
@ -463,7 +463,7 @@ void drain(NodeList& Q, std::vector<AstStat*>& result, Node* target)
if (allArcs.end() != it) if (allArcs.end() != it)
{ {
auto i2 = it->second.depends.find(nextNode.get()); auto i2 = it->second.depends.find(nextNode.get());
LUAU_ASSERT(i2 != it->second.depends.end()); lluz_ASSERT(i2 != it->second.depends.end());
it->second.depends.erase(i2); it->second.depends.erase(i2);
} }
} }
@ -474,7 +474,7 @@ void drain(NodeList& Q, std::vector<AstStat*>& result, Node* target)
if (allArcs.end() != it) if (allArcs.end() != it)
{ {
auto i2 = it->second.provides.find(nextNode.get()); auto i2 = it->second.provides.find(nextNode.get());
LUAU_ASSERT(i2 != it->second.provides.end()); lluz_ASSERT(i2 != it->second.provides.end());
it->second.provides.erase(i2); it->second.provides.erase(i2);
} }
} }
@ -576,4 +576,4 @@ void toposort(std::vector<AstStat*>& stats)
std::swap(stats, result); std::swap(stats, result);
} }
} // namespace Luau } // namespace lluz

View file

@ -1,9 +1,9 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Transpiler.h" #include "lluz/Transpiler.h"
#include "Luau/Parser.h" #include "lluz/Parser.h"
#include "Luau/StringUtils.h" #include "lluz/StringUtils.h"
#include "Luau/Common.h" #include "lluz/Common.h"
#include <algorithm> #include <algorithm>
#include <memory> #include <memory>
@ -32,7 +32,7 @@ const std::vector<std::string> keywords = {"and", "break", "do", "else", "elseif
} // namespace } // namespace
namespace Luau namespace lluz
{ {
struct Writer struct Writer
@ -175,7 +175,7 @@ public:
if (first) if (first)
first = !first; first = !first;
else else
writer.symbol(","); writer.symbol(XorStr(","));
} }
private: private:
@ -200,7 +200,7 @@ struct Printer
writer.identifier(local.name.value); writer.identifier(local.name.value);
if (writeTypes && local.annotation) if (writeTypes && local.annotation)
{ {
writer.symbol(":"); writer.symbol(XorStr(":"));
visualizeTypeAnnotation(*local.annotation); visualizeTypeAnnotation(*local.annotation);
} }
} }
@ -211,23 +211,23 @@ struct Printer
if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>()) if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>())
{ {
if (!forVarArg) if (!forVarArg)
writer.symbol("..."); writer.symbol(XorStr("..."));
visualizeTypeAnnotation(*variadicTp->variadicType); visualizeTypeAnnotation(*variadicTp->variadicType);
} }
else if (const AstTypePackGeneric* genericTp = annotation.as<AstTypePackGeneric>()) else if (const AstTypePackGeneric* genericTp = annotation.as<AstTypePackGeneric>())
{ {
writer.symbol(genericTp->genericName.value); writer.symbol(genericTp->genericName.value);
writer.symbol("..."); writer.symbol(XorStr("..."));
} }
else if (const AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>()) else if (const AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>())
{ {
LUAU_ASSERT(!forVarArg); lluz_ASSERT(!forVarArg);
visualizeTypeList(explicitTp->typeList, true); visualizeTypeList(explicitTp->typeList, true);
} }
else else
{ {
LUAU_ASSERT(!"Unknown TypePackAnnotation kind"); lluz_ASSERT(!XorStr("Unknown TypePackAnnotation kind"));
} }
} }
@ -236,13 +236,13 @@ struct Printer
size_t typeCount = list.types.size + (list.tailType != nullptr ? 1 : 0); size_t typeCount = list.types.size + (list.tailType != nullptr ? 1 : 0);
if (typeCount == 0) if (typeCount == 0)
{ {
writer.symbol("("); writer.symbol(XorStr("("));
writer.symbol(")"); writer.symbol(XorStr(")"));
} }
else if (typeCount == 1) else if (typeCount == 1)
{ {
if (unconditionallyParenthesize) if (unconditionallyParenthesize)
writer.symbol("("); writer.symbol(XorStr("("));
// Only variadic tail // Only variadic tail
if (list.types.size == 0) if (list.types.size == 0)
@ -255,11 +255,11 @@ struct Printer
} }
if (unconditionallyParenthesize) if (unconditionallyParenthesize)
writer.symbol(")"); writer.symbol(XorStr(")"));
} }
else else
{ {
writer.symbol("("); writer.symbol(XorStr("("));
bool first = true; bool first = true;
for (const auto& el : list.types) for (const auto& el : list.types)
@ -267,18 +267,18 @@ struct Printer
if (first) if (first)
first = false; first = false;
else else
writer.symbol(","); writer.symbol(XorStr(","));
visualizeTypeAnnotation(*el); visualizeTypeAnnotation(*el);
} }
if (list.tailType) if (list.tailType)
{ {
writer.symbol(","); writer.symbol(XorStr(","));
visualizeTypePackAnnotation(*list.tailType, false); visualizeTypePackAnnotation(*list.tailType, false);
} }
writer.symbol(")"); writer.symbol(XorStr(")"));
} }
} }
@ -296,32 +296,32 @@ struct Printer
if (const auto& a = expr.as<AstExprGroup>()) if (const auto& a = expr.as<AstExprGroup>())
{ {
writer.symbol("("); writer.symbol(XorStr("("));
visualize(*a->expr); visualize(*a->expr);
writer.symbol(")"); writer.symbol(XorStr(")"));
} }
else if (expr.is<AstExprConstantNil>()) else if (expr.is<AstExprConstantNil>())
{ {
writer.keyword("nil"); writer.keyword(XorStr("nil"));
} }
else if (const auto& a = expr.as<AstExprConstantBool>()) else if (const auto& a = expr.as<AstExprConstantBool>())
{ {
if (a->value) if (a->value)
writer.keyword("true"); writer.keyword(XorStr("true"));
else else
writer.keyword("false"); writer.keyword(XorStr("false"));
} }
else if (const auto& a = expr.as<AstExprConstantNumber>()) else if (const auto& a = expr.as<AstExprConstantNumber>())
{ {
if (isinf(a->value)) if (isinf(a->value))
{ {
if (a->value > 0) if (a->value > 0)
writer.literal("1e500"); writer.literal(XorStr("1e500"));
else else
writer.literal("-1e500"); writer.literal(XorStr("-1e500"));
} }
else if (isnan(a->value)) else if (isnan(a->value))
writer.literal("0/0"); writer.literal(XorStr("0/0"));
else else
{ {
if (isIntegerish(a->value)) if (isIntegerish(a->value))
@ -348,12 +348,12 @@ struct Printer
} }
else if (expr.is<AstExprVarargs>()) else if (expr.is<AstExprVarargs>())
{ {
writer.symbol("..."); writer.symbol(XorStr("..."));
} }
else if (const auto& a = expr.as<AstExprCall>()) else if (const auto& a = expr.as<AstExprCall>())
{ {
visualize(*a->func); visualize(*a->func);
writer.symbol("("); writer.symbol(XorStr("("));
bool first = true; bool first = true;
for (const auto& arg : a->args) for (const auto& arg : a->args)
@ -361,12 +361,12 @@ struct Printer
if (first) if (first)
first = false; first = false;
else else
writer.symbol(","); writer.symbol(XorStr(","));
visualize(*arg); visualize(*arg);
} }
writer.symbol(")"); writer.symbol(XorStr(")"));
} }
else if (const auto& a = expr.as<AstExprIndexName>()) else if (const auto& a = expr.as<AstExprIndexName>())
{ {
@ -377,18 +377,18 @@ struct Printer
else if (const auto& a = expr.as<AstExprIndexExpr>()) else if (const auto& a = expr.as<AstExprIndexExpr>())
{ {
visualize(*a->expr); visualize(*a->expr);
writer.symbol("["); writer.symbol(XorStr("["));
visualize(*a->index); visualize(*a->index);
writer.symbol("]"); writer.symbol(XorStr("]"));
} }
else if (const auto& a = expr.as<AstExprFunction>()) else if (const auto& a = expr.as<AstExprFunction>())
{ {
writer.keyword("function"); writer.keyword(XorStr("function"));
visualizeFunctionBody(*a); visualizeFunctionBody(*a);
} }
else if (const auto& a = expr.as<AstExprTable>()) else if (const auto& a = expr.as<AstExprTable>())
{ {
writer.symbol("{"); writer.symbol(XorStr("{"));
bool first = true; bool first = true;
@ -397,7 +397,7 @@ struct Printer
if (first) if (first)
first = false; first = false;
else else
writer.symbol(","); writer.symbol(XorStr(","));
switch (item.kind) switch (item.kind)
{ {
@ -410,22 +410,22 @@ struct Printer
advance(item.key->location.begin); advance(item.key->location.begin);
writer.identifier(std::string_view(value.data, value.size)); writer.identifier(std::string_view(value.data, value.size));
writer.maybeSpace(item.value->location.begin, 1); writer.maybeSpace(item.value->location.begin, 1);
writer.symbol("="); writer.symbol(XorStr("="));
} }
break; break;
case AstExprTable::Item::General: case AstExprTable::Item::General:
{ {
writer.symbol("["); writer.symbol(XorStr("["));
visualize(*item.key); visualize(*item.key);
writer.symbol("]"); writer.symbol(XorStr("]"));
writer.maybeSpace(item.value->location.begin, 1); writer.maybeSpace(item.value->location.begin, 1);
writer.symbol("="); writer.symbol(XorStr("="));
} }
break; break;
default: default:
LUAU_ASSERT(!"Unknown table item kind"); lluz_ASSERT(!XorStr("Unknown table item kind"));
} }
advance(item.value->location.begin); advance(item.value->location.begin);
@ -438,7 +438,7 @@ struct Printer
advance(endPos); advance(endPos);
writer.symbol("}"); writer.symbol(XorStr("}"));
advance(expr.location.end); advance(expr.location.end);
} }
else if (const auto& a = expr.as<AstExprUnary>()) else if (const auto& a = expr.as<AstExprUnary>())
@ -446,13 +446,13 @@ struct Printer
switch (a->op) switch (a->op)
{ {
case AstExprUnary::Not: case AstExprUnary::Not:
writer.keyword("not"); writer.keyword(XorStr("not"));
break; break;
case AstExprUnary::Minus: case AstExprUnary::Minus:
writer.symbol("-"); writer.symbol(XorStr("-"));
break; break;
case AstExprUnary::Len: case AstExprUnary::Len:
writer.symbol("#"); writer.symbol(XorStr("#"));
break; break;
} }
visualize(*a->expr); visualize(*a->expr);
@ -498,34 +498,34 @@ struct Printer
if (writeTypes) if (writeTypes)
{ {
writer.maybeSpace(a->annotation->location.begin, 2); writer.maybeSpace(a->annotation->location.begin, 2);
writer.symbol("::"); writer.symbol(XorStr("::"));
visualizeTypeAnnotation(*a->annotation); visualizeTypeAnnotation(*a->annotation);
} }
} }
else if (const auto& a = expr.as<AstExprIfElse>()) else if (const auto& a = expr.as<AstExprIfElse>())
{ {
writer.keyword("if"); writer.keyword(XorStr("if"));
visualize(*a->condition); visualize(*a->condition);
writer.keyword("then"); writer.keyword(XorStr("then"));
visualize(*a->trueExpr); visualize(*a->trueExpr);
writer.keyword("else"); writer.keyword(XorStr("else"));
visualize(*a->falseExpr); visualize(*a->falseExpr);
} }
else if (const auto& a = expr.as<AstExprError>()) else if (const auto& a = expr.as<AstExprError>())
{ {
writer.symbol("(error-expr"); writer.symbol(XorStr("(error-expr"));
for (size_t i = 0; i < a->expressions.size; i++) for (size_t i = 0; i < a->expressions.size; i++)
{ {
writer.symbol(i == 0 ? ": " : ", "); writer.symbol(i == 0 ? XorStr(": " : ", "));
visualize(*a->expressions.data[i]); visualize(*a->expressions.data[i]);
} }
writer.symbol(")"); writer.symbol(XorStr(")"));
} }
else else
{ {
LUAU_ASSERT(!"Unknown AstExpr"); lluz_ASSERT(!XorStr("Unknown AstExpr"));
} }
} }
@ -535,7 +535,7 @@ struct Printer
if (endPos.column >= 3) if (endPos.column >= 3)
endPos.column -= 3; endPos.column -= 3;
advance(endPos); advance(endPos);
writer.keyword("end"); writer.keyword(XorStr("end"));
} }
void advance(const Position& newPos) void advance(const Position& newPos)
@ -549,7 +549,7 @@ struct Printer
if (const auto& block = program.as<AstStatBlock>()) if (const auto& block = program.as<AstStatBlock>())
{ {
writer.keyword("do"); writer.keyword(XorStr("do"));
for (const auto& s : block->body) for (const auto& s : block->body)
visualize(*s); visualize(*s);
writer.advance(block->location.end); writer.advance(block->location.end);
@ -557,33 +557,33 @@ struct Printer
} }
else if (const auto& a = program.as<AstStatIf>()) else if (const auto& a = program.as<AstStatIf>())
{ {
writer.keyword("if"); writer.keyword(XorStr("if"));
visualizeElseIf(*a); visualizeElseIf(*a);
} }
else if (const auto& a = program.as<AstStatWhile>()) else if (const auto& a = program.as<AstStatWhile>())
{ {
writer.keyword("while"); writer.keyword(XorStr("while"));
visualize(*a->condition); visualize(*a->condition);
writer.keyword("do"); writer.keyword(XorStr("do"));
visualizeBlock(*a->body); visualizeBlock(*a->body);
writeEnd(program.location); writeEnd(program.location);
} }
else if (const auto& a = program.as<AstStatRepeat>()) else if (const auto& a = program.as<AstStatRepeat>())
{ {
writer.keyword("repeat"); writer.keyword(XorStr("repeat"));
visualizeBlock(*a->body); visualizeBlock(*a->body);
if (a->condition->location.begin.column > 5) if (a->condition->location.begin.column > 5)
writer.advance(Position{a->condition->location.begin.line, a->condition->location.begin.column - 6}); writer.advance(Position{a->condition->location.begin.line, a->condition->location.begin.column - 6});
writer.keyword("until"); writer.keyword(XorStr("until"));
visualize(*a->condition); visualize(*a->condition);
} }
else if (program.is<AstStatBreak>()) else if (program.is<AstStatBreak>())
writer.keyword("break"); writer.keyword(XorStr("break"));
else if (program.is<AstStatContinue>()) else if (program.is<AstStatContinue>())
writer.keyword("continue"); writer.keyword(XorStr("continue"));
else if (const auto& a = program.as<AstStatReturn>()) else if (const auto& a = program.as<AstStatReturn>())
{ {
writer.keyword("return"); writer.keyword(XorStr("return"));
bool first = true; bool first = true;
for (const auto& expr : a->list) for (const auto& expr : a->list)
@ -591,7 +591,7 @@ struct Printer
if (first) if (first)
first = false; first = false;
else else
writer.symbol(","); writer.symbol(XorStr(","));
visualize(*expr); visualize(*expr);
} }
} }
@ -601,7 +601,7 @@ struct Printer
} }
else if (const auto& a = program.as<AstStatLocal>()) else if (const auto& a = program.as<AstStatLocal>())
{ {
writer.keyword("local"); writer.keyword(XorStr("local"));
bool first = true; bool first = true;
for (const auto& local : a->vars) for (const auto& local : a->vars)
@ -609,7 +609,7 @@ struct Printer
if (first) if (first)
first = false; first = false;
else else
writer.write(","); writer.write(XorStr(","));
visualize(*local); visualize(*local);
} }
@ -621,36 +621,36 @@ struct Printer
{ {
first = false; first = false;
writer.maybeSpace(value->location.begin, 2); writer.maybeSpace(value->location.begin, 2);
writer.symbol("="); writer.symbol(XorStr("="));
} }
else else
writer.symbol(","); writer.symbol(XorStr(","));
visualize(*value); visualize(*value);
} }
} }
else if (const auto& a = program.as<AstStatFor>()) else if (const auto& a = program.as<AstStatFor>())
{ {
writer.keyword("for"); writer.keyword(XorStr("for"));
visualize(*a->var); visualize(*a->var);
writer.symbol("="); writer.symbol(XorStr("="));
visualize(*a->from); visualize(*a->from);
writer.symbol(","); writer.symbol(XorStr(","));
visualize(*a->to); visualize(*a->to);
if (a->step) if (a->step)
{ {
writer.symbol(","); writer.symbol(XorStr(","));
visualize(*a->step); visualize(*a->step);
} }
writer.keyword("do"); writer.keyword(XorStr("do"));
visualizeBlock(*a->body); visualizeBlock(*a->body);
writeEnd(program.location); writeEnd(program.location);
} }
else if (const auto& a = program.as<AstStatForIn>()) else if (const auto& a = program.as<AstStatForIn>())
{ {
writer.keyword("for"); writer.keyword(XorStr("for"));
bool first = true; bool first = true;
for (const auto& var : a->vars) for (const auto& var : a->vars)
@ -658,12 +658,12 @@ struct Printer
if (first) if (first)
first = false; first = false;
else else
writer.symbol(","); writer.symbol(XorStr(","));
visualize(*var); visualize(*var);
} }
writer.keyword("in"); writer.keyword(XorStr("in"));
first = true; first = true;
for (const auto& val : a->values) for (const auto& val : a->values)
@ -671,12 +671,12 @@ struct Printer
if (first) if (first)
first = false; first = false;
else else
writer.symbol(","); writer.symbol(XorStr(","));
visualize(*val); visualize(*val);
} }
writer.keyword("do"); writer.keyword(XorStr("do"));
visualizeBlock(*a->body); visualizeBlock(*a->body);
@ -690,7 +690,7 @@ struct Printer
if (first) if (first)
first = false; first = false;
else else
writer.symbol(","); writer.symbol(XorStr(","));
visualize(*var); visualize(*var);
} }
@ -700,11 +700,11 @@ struct Printer
if (first) if (first)
{ {
writer.maybeSpace(value->location.begin, 1); writer.maybeSpace(value->location.begin, 1);
writer.symbol("="); writer.symbol(XorStr("="));
first = false; first = false;
} }
else else
writer.symbol(","); writer.symbol(XorStr(","));
visualize(*value); visualize(*value);
} }
@ -717,47 +717,47 @@ struct Printer
{ {
case AstExprBinary::Add: case AstExprBinary::Add:
writer.maybeSpace(a->value->location.begin, 2); writer.maybeSpace(a->value->location.begin, 2);
writer.symbol("+="); writer.symbol(XorStr("+="));
break; break;
case AstExprBinary::Sub: case AstExprBinary::Sub:
writer.maybeSpace(a->value->location.begin, 2); writer.maybeSpace(a->value->location.begin, 2);
writer.symbol("-="); writer.symbol(XorStr("-="));
break; break;
case AstExprBinary::Mul: case AstExprBinary::Mul:
writer.maybeSpace(a->value->location.begin, 2); writer.maybeSpace(a->value->location.begin, 2);
writer.symbol("*="); writer.symbol(XorStr("*="));
break; break;
case AstExprBinary::Div: case AstExprBinary::Div:
writer.maybeSpace(a->value->location.begin, 2); writer.maybeSpace(a->value->location.begin, 2);
writer.symbol("/="); writer.symbol(XorStr("/="));
break; break;
case AstExprBinary::Mod: case AstExprBinary::Mod:
writer.maybeSpace(a->value->location.begin, 2); writer.maybeSpace(a->value->location.begin, 2);
writer.symbol("%="); writer.symbol(XorStr("%="));
break; break;
case AstExprBinary::Pow: case AstExprBinary::Pow:
writer.maybeSpace(a->value->location.begin, 2); writer.maybeSpace(a->value->location.begin, 2);
writer.symbol("^="); writer.symbol(XorStr("^="));
break; break;
case AstExprBinary::Concat: case AstExprBinary::Concat:
writer.maybeSpace(a->value->location.begin, 3); writer.maybeSpace(a->value->location.begin, 3);
writer.symbol("..="); writer.symbol(XorStr("..="));
break; break;
default: default:
LUAU_ASSERT(!"Unexpected compound assignment op"); lluz_ASSERT(!XorStr("Unexpected compound assignment op"));
} }
visualize(*a->value); visualize(*a->value);
} }
else if (const auto& a = program.as<AstStatFunction>()) else if (const auto& a = program.as<AstStatFunction>())
{ {
writer.keyword("function"); writer.keyword(XorStr("function"));
visualize(*a->name); visualize(*a->name);
visualizeFunctionBody(*a->func); visualizeFunctionBody(*a->func);
} }
else if (const auto& a = program.as<AstStatLocalFunction>()) else if (const auto& a = program.as<AstStatLocalFunction>())
{ {
writer.keyword("local function"); writer.keyword(XorStr("local function"));
advance(a->name->location.begin); advance(a->name->location.begin);
writer.identifier(a->name->name.value); writer.identifier(a->name->name.value);
visualizeFunctionBody(*a->func); visualizeFunctionBody(*a->func);
@ -767,13 +767,13 @@ struct Printer
if (writeTypes) if (writeTypes)
{ {
if (a->exported) if (a->exported)
writer.keyword("export"); writer.keyword(XorStr("export"));
writer.keyword("type"); writer.keyword(XorStr("type"));
writer.identifier(a->name.value); writer.identifier(a->name.value);
if (a->generics.size > 0 || a->genericPacks.size > 0) if (a->generics.size > 0 || a->genericPacks.size > 0)
{ {
writer.symbol("<"); writer.symbol(XorStr("<"));
CommaSeparatorInserter comma(writer); CommaSeparatorInserter comma(writer);
for (auto o : a->generics) for (auto o : a->generics)
@ -786,7 +786,7 @@ struct Printer
if (o.defaultValue) if (o.defaultValue)
{ {
writer.maybeSpace(o.defaultValue->location.begin, 2); writer.maybeSpace(o.defaultValue->location.begin, 2);
writer.symbol("="); writer.symbol(XorStr("="));
visualizeTypeAnnotation(*o.defaultValue); visualizeTypeAnnotation(*o.defaultValue);
} }
} }
@ -797,48 +797,48 @@ struct Printer
writer.advance(o.location.begin); writer.advance(o.location.begin);
writer.identifier(o.name.value); writer.identifier(o.name.value);
writer.symbol("..."); writer.symbol(XorStr("..."));
if (o.defaultValue) if (o.defaultValue)
{ {
writer.maybeSpace(o.defaultValue->location.begin, 2); writer.maybeSpace(o.defaultValue->location.begin, 2);
writer.symbol("="); writer.symbol(XorStr("="));
visualizeTypePackAnnotation(*o.defaultValue, false); visualizeTypePackAnnotation(*o.defaultValue, false);
} }
} }
writer.symbol(">"); writer.symbol(XorStr(">"));
} }
writer.maybeSpace(a->type->location.begin, 2); writer.maybeSpace(a->type->location.begin, 2);
writer.symbol("="); writer.symbol(XorStr("="));
visualizeTypeAnnotation(*a->type); visualizeTypeAnnotation(*a->type);
} }
} }
else if (const auto& a = program.as<AstStatError>()) else if (const auto& a = program.as<AstStatError>())
{ {
writer.symbol("(error-stat"); writer.symbol(XorStr("(error-stat"));
for (size_t i = 0; i < a->expressions.size; i++) for (size_t i = 0; i < a->expressions.size; i++)
{ {
writer.symbol(i == 0 ? ": " : ", "); writer.symbol(i == 0 ? XorStr(": " : ", "));
visualize(*a->expressions.data[i]); visualize(*a->expressions.data[i]);
} }
for (size_t i = 0; i < a->statements.size; i++) for (size_t i = 0; i < a->statements.size; i++)
{ {
writer.symbol(i == 0 && a->expressions.size == 0 ? ": " : ", "); writer.symbol(i == 0 && a->expressions.size == 0 ? XorStr(": " : ", "));
visualize(*a->statements.data[i]); visualize(*a->statements.data[i]);
} }
writer.symbol(")"); writer.symbol(XorStr(")"));
} }
else else
{ {
LUAU_ASSERT(!"Unknown AstStat"); lluz_ASSERT(!XorStr("Unknown AstStat"));
} }
if (program.hasSemicolon) if (program.hasSemicolon)
writer.symbol(";"); writer.symbol(XorStr(";"));
} }
void visualizeFunctionBody(AstExprFunction& func) void visualizeFunctionBody(AstExprFunction& func)
@ -846,7 +846,7 @@ struct Printer
if (func.generics.size > 0 || func.genericPacks.size > 0) if (func.generics.size > 0 || func.genericPacks.size > 0)
{ {
CommaSeparatorInserter comma(writer); CommaSeparatorInserter comma(writer);
writer.symbol("<"); writer.symbol(XorStr("<"));
for (const auto& o : func.generics) for (const auto& o : func.generics)
{ {
comma(); comma();
@ -860,12 +860,12 @@ struct Printer
writer.advance(o.location.begin); writer.advance(o.location.begin);
writer.identifier(o.name.value); writer.identifier(o.name.value);
writer.symbol("..."); writer.symbol(XorStr("..."));
} }
writer.symbol(">"); writer.symbol(XorStr(">"));
} }
writer.symbol("("); writer.symbol(XorStr("("));
CommaSeparatorInserter comma(writer); CommaSeparatorInserter comma(writer);
for (size_t i = 0; i < func.args.size; ++i) for (size_t i = 0; i < func.args.size; ++i)
@ -878,7 +878,7 @@ struct Printer
writer.identifier(local->name.value); writer.identifier(local->name.value);
if (writeTypes && local->annotation) if (writeTypes && local->annotation)
{ {
writer.symbol(":"); writer.symbol(XorStr(":"));
visualizeTypeAnnotation(*local->annotation); visualizeTypeAnnotation(*local->annotation);
} }
} }
@ -887,20 +887,20 @@ struct Printer
{ {
comma(); comma();
advance(func.varargLocation.begin); advance(func.varargLocation.begin);
writer.symbol("..."); writer.symbol(XorStr("..."));
if (func.varargAnnotation) if (func.varargAnnotation)
{ {
writer.symbol(":"); writer.symbol(XorStr(":"));
visualizeTypePackAnnotation(*func.varargAnnotation, true); visualizeTypePackAnnotation(*func.varargAnnotation, true);
} }
} }
writer.symbol(")"); writer.symbol(XorStr(")"));
if (writeTypes && func.returnAnnotation) if (writeTypes && func.returnAnnotation)
{ {
writer.symbol(":"); writer.symbol(XorStr(":"));
writer.space(); writer.space();
visualizeTypeList(*func.returnAnnotation, false); visualizeTypeList(*func.returnAnnotation, false);
@ -922,13 +922,13 @@ struct Printer
if (AstStatBlock* block = stat.as<AstStatBlock>()) if (AstStatBlock* block = stat.as<AstStatBlock>())
visualizeBlock(*block); visualizeBlock(*block);
else else
LUAU_ASSERT(!"visualizeBlock was expecting an AstStatBlock"); lluz_ASSERT(!XorStr("visualizeBlock was expecting an AstStatBlock"));
} }
void visualizeElseIf(AstStatIf& elseif) void visualizeElseIf(AstStatIf& elseif)
{ {
visualize(*elseif.condition); visualize(*elseif.condition);
writer.keyword("then"); writer.keyword(XorStr("then"));
visualizeBlock(*elseif.thenbody); visualizeBlock(*elseif.thenbody);
if (elseif.elsebody == nullptr) if (elseif.elsebody == nullptr)
@ -937,12 +937,12 @@ struct Printer
} }
else if (auto elseifelseif = elseif.elsebody->as<AstStatIf>()) else if (auto elseifelseif = elseif.elsebody->as<AstStatIf>())
{ {
writer.keyword("elseif"); writer.keyword(XorStr("elseif"));
visualizeElseIf(*elseifelseif); visualizeElseIf(*elseifelseif);
} }
else else
{ {
writer.keyword("else"); writer.keyword(XorStr("else"));
visualizeBlock(*elseif.elsebody); visualizeBlock(*elseif.elsebody);
writeEnd(elseif.location); writeEnd(elseif.location);
@ -957,14 +957,14 @@ struct Printer
if (a->prefix) if (a->prefix)
{ {
writer.write(a->prefix->value); writer.write(a->prefix->value);
writer.symbol("."); writer.symbol(XorStr("."));
} }
writer.write(a->name.value); writer.write(a->name.value);
if (a->parameters.size > 0 || a->hasParameterList) if (a->parameters.size > 0 || a->hasParameterList)
{ {
CommaSeparatorInserter comma(writer); CommaSeparatorInserter comma(writer);
writer.symbol("<"); writer.symbol(XorStr("<"));
for (auto o : a->parameters) for (auto o : a->parameters)
{ {
comma(); comma();
@ -975,7 +975,7 @@ struct Printer
visualizeTypePackAnnotation(*o.typePack, false); visualizeTypePackAnnotation(*o.typePack, false);
} }
writer.symbol(">"); writer.symbol(XorStr(">"));
} }
} }
else if (const auto& a = typeAnnotation.as<AstTypeFunction>()) else if (const auto& a = typeAnnotation.as<AstTypeFunction>())
@ -983,7 +983,7 @@ struct Printer
if (a->generics.size > 0 || a->genericPacks.size > 0) if (a->generics.size > 0 || a->genericPacks.size > 0)
{ {
CommaSeparatorInserter comma(writer); CommaSeparatorInserter comma(writer);
writer.symbol("<"); writer.symbol(XorStr("<"));
for (const auto& o : a->generics) for (const auto& o : a->generics)
{ {
comma(); comma();
@ -997,33 +997,33 @@ struct Printer
writer.advance(o.location.begin); writer.advance(o.location.begin);
writer.identifier(o.name.value); writer.identifier(o.name.value);
writer.symbol("..."); writer.symbol(XorStr("..."));
} }
writer.symbol(">"); writer.symbol(XorStr(">"));
} }
{ {
visualizeTypeList(a->argTypes, true); visualizeTypeList(a->argTypes, true);
} }
writer.symbol("->"); writer.symbol(XorStr("->"));
visualizeTypeList(a->returnTypes, true); visualizeTypeList(a->returnTypes, true);
} }
else if (const auto& a = typeAnnotation.as<AstTypeTable>()) else if (const auto& a = typeAnnotation.as<AstTypeTable>())
{ {
AstTypeReference* indexType = a->indexer ? a->indexer->indexType->as<AstTypeReference>() : nullptr; AstTypeReference* indexType = a->indexer ? a->indexer->indexType->as<AstTypeReference>() : nullptr;
if (a->props.size == 0 && indexType && indexType->name == "number") if (a->props.size == 0 && indexType && indexType->name == XorStr("number"))
{ {
writer.symbol("{"); writer.symbol(XorStr("{"));
visualizeTypeAnnotation(*a->indexer->resultType); visualizeTypeAnnotation(*a->indexer->resultType);
writer.symbol("}"); writer.symbol(XorStr("}"));
} }
else else
{ {
CommaSeparatorInserter comma(writer); CommaSeparatorInserter comma(writer);
writer.symbol("{"); writer.symbol(XorStr("{"));
for (std::size_t i = 0; i < a->props.size; ++i) for (std::size_t i = 0; i < a->props.size; ++i)
{ {
@ -1032,28 +1032,28 @@ struct Printer
writer.identifier(a->props.data[i].name.value); writer.identifier(a->props.data[i].name.value);
if (a->props.data[i].type) if (a->props.data[i].type)
{ {
writer.symbol(":"); writer.symbol(XorStr(":"));
visualizeTypeAnnotation(*a->props.data[i].type); visualizeTypeAnnotation(*a->props.data[i].type);
} }
} }
if (a->indexer) if (a->indexer)
{ {
comma(); comma();
writer.symbol("["); writer.symbol(XorStr("["));
visualizeTypeAnnotation(*a->indexer->indexType); visualizeTypeAnnotation(*a->indexer->indexType);
writer.symbol("]"); writer.symbol(XorStr("]"));
writer.symbol(":"); writer.symbol(XorStr(":"));
visualizeTypeAnnotation(*a->indexer->resultType); visualizeTypeAnnotation(*a->indexer->resultType);
} }
writer.symbol("}"); writer.symbol(XorStr("}"));
} }
} }
else if (auto a = typeAnnotation.as<AstTypeTypeof>()) else if (auto a = typeAnnotation.as<AstTypeTypeof>())
{ {
writer.keyword("typeof"); writer.keyword(XorStr("typeof"));
writer.symbol("("); writer.symbol(XorStr("("));
visualize(*a->expr); visualize(*a->expr);
writer.symbol(")"); writer.symbol(XorStr(")"));
} }
else if (const auto& a = typeAnnotation.as<AstTypeUnion>()) else if (const auto& a = typeAnnotation.as<AstTypeUnion>())
{ {
@ -1063,24 +1063,24 @@ struct Printer
AstType* r = a->types.data[1]; AstType* r = a->types.data[1];
auto lta = l->as<AstTypeReference>(); auto lta = l->as<AstTypeReference>();
if (lta && lta->name == "nil") if (lta && lta->name == XorStr("nil"))
std::swap(l, r); std::swap(l, r);
// it's still possible that we had a (T | U) or (T | nil) and not (nil | T) // it's still possible that we had a (T | U) or (T | nil) and not (nil | T)
auto rta = r->as<AstTypeReference>(); auto rta = r->as<AstTypeReference>();
if (rta && rta->name == "nil") if (rta && rta->name == XorStr("nil"))
{ {
bool wrap = l->as<AstTypeIntersection>() || l->as<AstTypeFunction>(); bool wrap = l->as<AstTypeIntersection>() || l->as<AstTypeFunction>();
if (wrap) if (wrap)
writer.symbol("("); writer.symbol(XorStr("("));
visualizeTypeAnnotation(*l); visualizeTypeAnnotation(*l);
if (wrap) if (wrap)
writer.symbol(")"); writer.symbol(XorStr(")"));
writer.symbol("?"); writer.symbol(XorStr("?"));
return; return;
} }
} }
@ -1090,18 +1090,18 @@ struct Printer
if (i > 0) if (i > 0)
{ {
writer.maybeSpace(a->types.data[i]->location.begin, 2); writer.maybeSpace(a->types.data[i]->location.begin, 2);
writer.symbol("|"); writer.symbol(XorStr("|"));
} }
bool wrap = a->types.data[i]->as<AstTypeIntersection>() || a->types.data[i]->as<AstTypeFunction>(); bool wrap = a->types.data[i]->as<AstTypeIntersection>() || a->types.data[i]->as<AstTypeFunction>();
if (wrap) if (wrap)
writer.symbol("("); writer.symbol(XorStr("("));
visualizeTypeAnnotation(*a->types.data[i]); visualizeTypeAnnotation(*a->types.data[i]);
if (wrap) if (wrap)
writer.symbol(")"); writer.symbol(XorStr(")"));
} }
} }
else if (const auto& a = typeAnnotation.as<AstTypeIntersection>()) else if (const auto& a = typeAnnotation.as<AstTypeIntersection>())
@ -1111,23 +1111,23 @@ struct Printer
if (i > 0) if (i > 0)
{ {
writer.maybeSpace(a->types.data[i]->location.begin, 2); writer.maybeSpace(a->types.data[i]->location.begin, 2);
writer.symbol("&"); writer.symbol(XorStr("&"));
} }
bool wrap = a->types.data[i]->as<AstTypeUnion>() || a->types.data[i]->as<AstTypeFunction>(); bool wrap = a->types.data[i]->as<AstTypeUnion>() || a->types.data[i]->as<AstTypeFunction>();
if (wrap) if (wrap)
writer.symbol("("); writer.symbol(XorStr("("));
visualizeTypeAnnotation(*a->types.data[i]); visualizeTypeAnnotation(*a->types.data[i]);
if (wrap) if (wrap)
writer.symbol(")"); writer.symbol(XorStr(")"));
} }
} }
else if (const auto& a = typeAnnotation.as<AstTypeSingletonBool>()) else if (const auto& a = typeAnnotation.as<AstTypeSingletonBool>())
{ {
writer.keyword(a->value ? "true" : "false"); writer.keyword(a->value ? XorStr("true" : "false"));
} }
else if (const auto& a = typeAnnotation.as<AstTypeSingletonString>()) else if (const auto& a = typeAnnotation.as<AstTypeSingletonString>())
{ {
@ -1135,11 +1135,11 @@ struct Printer
} }
else if (typeAnnotation.is<AstTypeError>()) else if (typeAnnotation.is<AstTypeError>())
{ {
writer.symbol("%error-type%"); writer.symbol(XorStr("%error-type%"));
} }
else else
{ {
LUAU_ASSERT(!"Unknown AstType"); lluz_ASSERT(!XorStr("Unknown AstType"));
} }
} }
}; };
@ -1197,7 +1197,7 @@ TranspileResult transpile(std::string_view source, ParseOptions options, bool wi
return TranspileResult{"", error.getLocation(), error.what()}; return TranspileResult{"", error.getLocation(), error.what()};
} }
LUAU_ASSERT(parseResult.root); lluz_ASSERT(parseResult.root);
if (!parseResult.root) if (!parseResult.root)
return TranspileResult{"", {}, "Internal error: Parser yielded empty parse tree"}; return TranspileResult{"", {}, "Internal error: Parser yielded empty parse tree"};
@ -1207,4 +1207,4 @@ TranspileResult transpile(std::string_view source, ParseOptions options, bool wi
return TranspileResult{transpile(*parseResult.root)}; return TranspileResult{transpile(*parseResult.root)};
} }
} // namespace Luau } // namespace lluz

View file

@ -1,15 +1,15 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/TxnLog.h" #include "lluz/TxnLog.h"
#include "Luau/ToString.h" #include "lluz/ToString.h"
#include "Luau/TypePack.h" #include "lluz/TypePack.h"
#include <algorithm> #include <algorithm>
#include <stdexcept> #include <stdexcept>
LUAU_FASTFLAG(LuauUnknownAndNeverType) lluz_FASTFLAG(LluNonCopyableTypeVarFields)
namespace Luau namespace lluz
{ {
const std::string nullPendingResult = "<nullptr>"; const std::string nullPendingResult = "<nullptr>";
@ -81,10 +81,34 @@ void TxnLog::concat(TxnLog rhs)
void TxnLog::commit() void TxnLog::commit()
{ {
for (auto& [ty, rep] : typeVarChanges) for (auto& [ty, rep] : typeVarChanges)
asMutable(ty)->reassign(rep.get()->pending); {
if (FFlag::LluNonCopyableTypeVarFields)
{
asMutable(ty)->reassign(rep.get()->pending);
}
else
{
TypeArena* owningArena = ty->owningArena;
TypeVar* mtv = asMutable(ty);
*mtv = rep.get()->pending;
mtv->owningArena = owningArena;
}
}
for (auto& [tp, rep] : typePackChanges) for (auto& [tp, rep] : typePackChanges)
asMutable(tp)->reassign(rep.get()->pending); {
if (FFlag::LluNonCopyableTypeVarFields)
{
asMutable(tp)->reassign(rep.get()->pending);
}
else
{
TypeArena* owningArena = tp->owningArena;
TypePackVar* mpv = asMutable(tp);
*mpv = rep.get()->pending;
mpv->owningArena = owningArena;
}
}
clear(); clear();
} }
@ -158,13 +182,13 @@ void TxnLog::pushSeen(TypeOrPackId lhs, TypeOrPackId rhs)
void TxnLog::popSeen(TypeOrPackId lhs, TypeOrPackId rhs) void TxnLog::popSeen(TypeOrPackId lhs, TypeOrPackId rhs)
{ {
const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs); const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
LUAU_ASSERT(sortedPair == sharedSeen->back()); lluz_ASSERT(sortedPair == sharedSeen->back());
sharedSeen->pop_back(); sharedSeen->pop_back();
} }
PendingType* TxnLog::queue(TypeId ty) PendingType* TxnLog::queue(TypeId ty)
{ {
LUAU_ASSERT(!ty->persistent); lluz_ASSERT(!ty->persistent);
// Explicitly don't look in ancestors. If we have discovered something new // Explicitly don't look in ancestors. If we have discovered something new
// about this type, we don't want to mutate the parent's state. // about this type, we don't want to mutate the parent's state.
@ -172,7 +196,9 @@ PendingType* TxnLog::queue(TypeId ty)
if (!pending) if (!pending)
{ {
pending = std::make_unique<PendingType>(*ty); pending = std::make_unique<PendingType>(*ty);
pending->pending.owningArena = nullptr;
if (FFlag::LluNonCopyableTypeVarFields)
pending->pending.owningArena = nullptr;
} }
return pending.get(); return pending.get();
@ -180,7 +206,7 @@ PendingType* TxnLog::queue(TypeId ty)
PendingTypePack* TxnLog::queue(TypePackId tp) PendingTypePack* TxnLog::queue(TypePackId tp)
{ {
LUAU_ASSERT(!tp->persistent); lluz_ASSERT(!tp->persistent);
// Explicitly don't look in ancestors. If we have discovered something new // Explicitly don't look in ancestors. If we have discovered something new
// about this type, we don't want to mutate the parent's state. // about this type, we don't want to mutate the parent's state.
@ -188,7 +214,9 @@ PendingTypePack* TxnLog::queue(TypePackId tp)
if (!pending) if (!pending)
{ {
pending = std::make_unique<PendingTypePack>(*tp); pending = std::make_unique<PendingTypePack>(*tp);
pending->pending.owningArena = nullptr;
if (FFlag::LluNonCopyableTypeVarFields)
pending->pending.owningArena = nullptr;
} }
return pending.get(); return pending.get();
@ -198,7 +226,7 @@ PendingType* TxnLog::pending(TypeId ty) const
{ {
// This function will technically work if `this` is nullptr, but this // This function will technically work if `this` is nullptr, but this
// indicates a bug, so we explicitly assert. // indicates a bug, so we explicitly assert.
LUAU_ASSERT(static_cast<const void*>(this) != nullptr); lluz_ASSERT(static_cast<const void*>(this) != nullptr);
for (const TxnLog* current = this; current; current = current->parent) for (const TxnLog* current = this; current; current = current->parent)
{ {
@ -213,7 +241,7 @@ PendingTypePack* TxnLog::pending(TypePackId tp) const
{ {
// This function will technically work if `this` is nullptr, but this // This function will technically work if `this` is nullptr, but this
// indicates a bug, so we explicitly assert. // indicates a bug, so we explicitly assert.
LUAU_ASSERT(static_cast<const void*>(this) != nullptr); lluz_ASSERT(static_cast<const void*>(this) != nullptr);
for (const TxnLog* current = this; current; current = current->parent) for (const TxnLog* current = this; current; current = current->parent)
{ {
@ -227,23 +255,33 @@ PendingTypePack* TxnLog::pending(TypePackId tp) const
PendingType* TxnLog::replace(TypeId ty, TypeVar replacement) PendingType* TxnLog::replace(TypeId ty, TypeVar replacement)
{ {
PendingType* newTy = queue(ty); PendingType* newTy = queue(ty);
newTy->pending.reassign(replacement);
if (FFlag::LluNonCopyableTypeVarFields)
newTy->pending.reassign(replacement);
else
newTy->pending = replacement;
return newTy; return newTy;
} }
PendingTypePack* TxnLog::replace(TypePackId tp, TypePackVar replacement) PendingTypePack* TxnLog::replace(TypePackId tp, TypePackVar replacement)
{ {
PendingTypePack* newTp = queue(tp); PendingTypePack* newTp = queue(tp);
newTp->pending.reassign(replacement);
if (FFlag::LluNonCopyableTypeVarFields)
newTp->pending.reassign(replacement);
else
newTp->pending = replacement;
return newTp; return newTp;
} }
PendingType* TxnLog::bindTable(TypeId ty, std::optional<TypeId> newBoundTo) PendingType* TxnLog::bindTable(TypeId ty, std::optional<TypeId> newBoundTo)
{ {
LUAU_ASSERT(get<TableTypeVar>(ty)); lluz_ASSERT(get<TableTypeVar>(ty));
PendingType* newTy = queue(ty); PendingType* newTy = queue(ty);
if (TableTypeVar* ttv = Luau::getMutable<TableTypeVar>(newTy)) if (TableTypeVar* ttv = lluz::getMutable<TableTypeVar>(newTy))
ttv->boundTo = newBoundTo; ttv->boundTo = newBoundTo;
return newTy; return newTy;
@ -251,37 +289,32 @@ PendingType* TxnLog::bindTable(TypeId ty, std::optional<TypeId> newBoundTo)
PendingType* TxnLog::changeLevel(TypeId ty, TypeLevel newLevel) PendingType* TxnLog::changeLevel(TypeId ty, TypeLevel newLevel)
{ {
LUAU_ASSERT(get<FreeTypeVar>(ty) || get<TableTypeVar>(ty) || get<FunctionTypeVar>(ty) || get<ConstrainedTypeVar>(ty)); lluz_ASSERT(get<FreeTypeVar>(ty) || get<TableTypeVar>(ty) || get<FunctionTypeVar>(ty));
PendingType* newTy = queue(ty); PendingType* newTy = queue(ty);
if (FreeTypeVar* ftv = Luau::getMutable<FreeTypeVar>(newTy)) if (FreeTypeVar* ftv = lluz::getMutable<FreeTypeVar>(newTy))
{ {
ftv->level = newLevel; ftv->level = newLevel;
} }
else if (TableTypeVar* ttv = Luau::getMutable<TableTypeVar>(newTy)) else if (TableTypeVar* ttv = lluz::getMutable<TableTypeVar>(newTy))
{ {
LUAU_ASSERT(ttv->state == TableState::Free || ttv->state == TableState::Generic); lluz_ASSERT(ttv->state == TableState::Free || ttv->state == TableState::Generic);
ttv->level = newLevel; ttv->level = newLevel;
} }
else if (FunctionTypeVar* ftv = Luau::getMutable<FunctionTypeVar>(newTy)) else if (FunctionTypeVar* ftv = lluz::getMutable<FunctionTypeVar>(newTy))
{ {
ftv->level = newLevel; ftv->level = newLevel;
} }
else if (ConstrainedTypeVar* ctv = Luau::getMutable<ConstrainedTypeVar>(newTy))
{
if (FFlag::LuauUnknownAndNeverType)
ctv->level = newLevel;
}
return newTy; return newTy;
} }
PendingTypePack* TxnLog::changeLevel(TypePackId tp, TypeLevel newLevel) PendingTypePack* TxnLog::changeLevel(TypePackId tp, TypeLevel newLevel)
{ {
LUAU_ASSERT(get<FreeTypePack>(tp)); lluz_ASSERT(get<FreeTypePack>(tp));
PendingTypePack* newTp = queue(tp); PendingTypePack* newTp = queue(tp);
if (FreeTypePack* ftp = Luau::getMutable<FreeTypePack>(newTp)) if (FreeTypePack* ftp = lluz::getMutable<FreeTypePack>(newTp))
{ {
ftp->level = newLevel; ftp->level = newLevel;
} }
@ -291,10 +324,10 @@ PendingTypePack* TxnLog::changeLevel(TypePackId tp, TypeLevel newLevel)
PendingType* TxnLog::changeIndexer(TypeId ty, std::optional<TableIndexer> indexer) PendingType* TxnLog::changeIndexer(TypeId ty, std::optional<TableIndexer> indexer)
{ {
LUAU_ASSERT(get<TableTypeVar>(ty)); lluz_ASSERT(get<TableTypeVar>(ty));
PendingType* newTy = queue(ty); PendingType* newTy = queue(ty);
if (TableTypeVar* ttv = Luau::getMutable<TableTypeVar>(newTy)) if (TableTypeVar* ttv = lluz::getMutable<TableTypeVar>(newTy))
{ {
ttv->indexer = indexer; ttv->indexer = indexer;
} }
@ -316,7 +349,7 @@ std::optional<TypeLevel> TxnLog::getLevel(TypeId ty) const
TypeId TxnLog::follow(TypeId ty) const TypeId TxnLog::follow(TypeId ty) const
{ {
return Luau::follow(ty, [this](TypeId ty) { return lluz::follow(ty, [this](TypeId ty) {
PendingType* state = this->pending(ty); PendingType* state = this->pending(ty);
if (state == nullptr) if (state == nullptr)
@ -331,7 +364,7 @@ TypeId TxnLog::follow(TypeId ty) const
TypePackId TxnLog::follow(TypePackId tp) const TypePackId TxnLog::follow(TypePackId tp) const
{ {
return Luau::follow(tp, [this](TypePackId tp) { return lluz::follow(tp, [this](TypePackId tp) {
PendingTypePack* state = this->pending(tp); PendingTypePack* state = this->pending(tp);
if (state == nullptr) if (state == nullptr)
@ -344,4 +377,4 @@ TypePackId TxnLog::follow(TypePackId tp) const
}); });
} }
} // namespace Luau } // namespace lluz

View file

@ -1,10 +1,10 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/TypeArena.h" #include "lluz/TypeArena.h"
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false); lluz_FASTFLAGVARIABLE(DebugLluFreezeArena, false);
namespace Luau namespace lluz
{ {
void TypeArena::clear() void TypeArena::clear()
@ -69,7 +69,7 @@ TypePackId TypeArena::addTypePack(TypePackVar tp)
void freeze(TypeArena& arena) void freeze(TypeArena& arena)
{ {
if (!FFlag::DebugLuauFreezeArena) if (!FFlag::DebugLluFreezeArena)
return; return;
arena.typeVars.freeze(); arena.typeVars.freeze();
@ -78,11 +78,11 @@ void freeze(TypeArena& arena)
void unfreeze(TypeArena& arena) void unfreeze(TypeArena& arena)
{ {
if (!FFlag::DebugLuauFreezeArena) if (!FFlag::DebugLluFreezeArena)
return; return;
arena.typeVars.unfreeze(); arena.typeVars.unfreeze();
arena.typePacks.unfreeze(); arena.typePacks.unfreeze();
} }
} // namespace Luau } // namespace lluz

View file

@ -1,18 +1,18 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/TypeAttach.h" #include "lluz/TypeAttach.h"
#include "Luau/Error.h" #include "lluz/Error.h"
#include "Luau/Module.h" #include "lluz/Module.h"
#include "Luau/RecursionCounter.h" #include "lluz/RecursionCounter.h"
#include "Luau/Scope.h" #include "lluz/Scope.h"
#include "Luau/ToString.h" #include "lluz/ToString.h"
#include "Luau/TypeInfer.h" #include "lluz/TypeInfer.h"
#include "Luau/TypePack.h" #include "lluz/TypePack.h"
#include "Luau/TypeVar.h" #include "lluz/TypeVar.h"
#include <string> #include <string>
static char* allocateString(Luau::Allocator& allocator, std::string_view contents) static char* allocateString(lluz::Allocator& allocator, std::string_view contents)
{ {
char* result = (char*)allocator.allocate(contents.size() + 1); char* result = (char*)allocator.allocate(contents.size() + 1);
memcpy(result, contents.data(), contents.size()); memcpy(result, contents.data(), contents.size());
@ -22,7 +22,7 @@ static char* allocateString(Luau::Allocator& allocator, std::string_view content
} }
template<typename... Data> template<typename... Data>
static char* allocateString(Luau::Allocator& allocator, const char* format, Data... data) static char* allocateString(lluz::Allocator& allocator, const char* format, Data... data)
{ {
int len = snprintf(nullptr, 0, format, data...); int len = snprintf(nullptr, 0, format, data...);
char* result = (char*)allocator.allocate(len + 1); char* result = (char*)allocator.allocate(len + 1);
@ -32,7 +32,7 @@ static char* allocateString(Luau::Allocator& allocator, const char* format, Data
using SyntheticNames = std::unordered_map<const void*, char*>; using SyntheticNames = std::unordered_map<const void*, char*>;
namespace Luau namespace lluz
{ {
static const char* getName(Allocator* allocator, SyntheticNames* syntheticNames, const Unifiable::Generic& gen) static const char* getName(Allocator* allocator, SyntheticNames* syntheticNames, const Unifiable::Generic& gen)
@ -105,7 +105,7 @@ public:
types.size = ctv.parts.size(); types.size = ctv.parts.size();
types.data = static_cast<AstType**>(allocator->allocate(sizeof(AstType*) * ctv.parts.size())); types.data = static_cast<AstType**>(allocator->allocate(sizeof(AstType*) * ctv.parts.size()));
for (size_t i = 0; i < ctv.parts.size(); ++i) for (size_t i = 0; i < ctv.parts.size(); ++i)
types.data[i] = Luau::visit(*this, ctv.parts[i]->ty); types.data[i] = lluz::visit(*this, ctv.parts[i]->ty);
return allocator->alloc<AstTypeIntersection>(Location(), types); return allocator->alloc<AstTypeIntersection>(Location(), types);
} }
@ -140,7 +140,7 @@ public:
for (size_t i = 0; i < ttv.instantiatedTypeParams.size(); ++i) for (size_t i = 0; i < ttv.instantiatedTypeParams.size(); ++i)
{ {
parameters.data[i] = {Luau::visit(*this, ttv.instantiatedTypeParams[i]->ty), {}}; parameters.data[i] = {lluz::visit(*this, ttv.instantiatedTypeParams[i]->ty), {}};
} }
for (size_t i = 0; i < ttv.instantiatedTypePackParams.size(); ++i) for (size_t i = 0; i < ttv.instantiatedTypePackParams.size(); ++i)
@ -170,7 +170,7 @@ public:
char* name = allocateString(*allocator, propName); char* name = allocateString(*allocator, propName);
props.data[idx].name = AstName(name); props.data[idx].name = AstName(name);
props.data[idx].type = Luau::visit(*this, prop.type->ty); props.data[idx].type = lluz::visit(*this, prop.type->ty);
props.data[idx].location = Location(); props.data[idx].location = Location();
idx++; idx++;
} }
@ -181,15 +181,15 @@ public:
RecursionCounter counter(&count); RecursionCounter counter(&count);
indexer = allocator->alloc<AstTableIndexer>(); indexer = allocator->alloc<AstTableIndexer>();
indexer->indexType = Luau::visit(*this, ttv.indexer->indexType->ty); indexer->indexType = lluz::visit(*this, ttv.indexer->indexType->ty);
indexer->resultType = Luau::visit(*this, ttv.indexer->indexResultType->ty); indexer->resultType = lluz::visit(*this, ttv.indexer->indexResultType->ty);
} }
return allocator->alloc<AstTypeTable>(Location(), props, indexer); return allocator->alloc<AstTypeTable>(Location(), props, indexer);
} }
AstType* operator()(const MetatableTypeVar& mtv) AstType* operator()(const MetatableTypeVar& mtv)
{ {
return Luau::visit(*this, mtv.table->ty); return lluz::visit(*this, mtv.table->ty);
} }
AstType* operator()(const ClassTypeVar& ctv) AstType* operator()(const ClassTypeVar& ctv)
@ -211,7 +211,7 @@ public:
char* name = allocateString(*allocator, propName); char* name = allocateString(*allocator, propName);
props.data[idx].name = AstName{name}; props.data[idx].name = AstName{name};
props.data[idx].type = Luau::visit(*this, prop.type->ty); props.data[idx].type = lluz::visit(*this, prop.type->ty);
props.data[idx].location = Location(); props.data[idx].location = Location();
idx++; idx++;
} }
@ -254,7 +254,7 @@ public:
{ {
RecursionCounter counter(&count); RecursionCounter counter(&count);
argTypes.data[i] = Luau::visit(*this, (argVector[i])->ty); argTypes.data[i] = lluz::visit(*this, (argVector[i])->ty);
} }
AstTypePack* argTailAnnotation = nullptr; AstTypePack* argTailAnnotation = nullptr;
@ -283,7 +283,7 @@ public:
{ {
RecursionCounter counter(&count); RecursionCounter counter(&count);
returnTypes.data[i] = Luau::visit(*this, (retVector[i])->ty); returnTypes.data[i] = lluz::visit(*this, (retVector[i])->ty);
} }
AstTypePack* retTailAnnotation = nullptr; AstTypePack* retTailAnnotation = nullptr;
@ -303,7 +303,7 @@ public:
} }
AstType* operator()(const Unifiable::Bound<TypeId>& bound) AstType* operator()(const Unifiable::Bound<TypeId>& bound)
{ {
return Luau::visit(*this, bound.boundTo->ty); return lluz::visit(*this, bound.boundTo->ty);
} }
AstType* operator()(const FreeTypeVar& ftv) AstType* operator()(const FreeTypeVar& ftv)
{ {
@ -316,7 +316,7 @@ public:
unionTypes.data = static_cast<AstType**>(allocator->allocate(sizeof(AstType*) * unionTypes.size)); unionTypes.data = static_cast<AstType**>(allocator->allocate(sizeof(AstType*) * unionTypes.size));
for (size_t i = 0; i < unionTypes.size; ++i) for (size_t i = 0; i < unionTypes.size; ++i)
{ {
unionTypes.data[i] = Luau::visit(*this, uv.options[i]->ty); unionTypes.data[i] = lluz::visit(*this, uv.options[i]->ty);
} }
return allocator->alloc<AstTypeUnion>(Location(), unionTypes); return allocator->alloc<AstTypeUnion>(Location(), unionTypes);
} }
@ -327,7 +327,7 @@ public:
intersectionTypes.data = static_cast<AstType**>(allocator->allocate(sizeof(AstType*) * intersectionTypes.size)); intersectionTypes.data = static_cast<AstType**>(allocator->allocate(sizeof(AstType*) * intersectionTypes.size));
for (size_t i = 0; i < intersectionTypes.size; ++i) for (size_t i = 0; i < intersectionTypes.size; ++i)
{ {
intersectionTypes.data[i] = Luau::visit(*this, uv.parts[i]->ty); intersectionTypes.data[i] = lluz::visit(*this, uv.parts[i]->ty);
} }
return allocator->alloc<AstTypeIntersection>(Location(), intersectionTypes); return allocator->alloc<AstTypeIntersection>(Location(), intersectionTypes);
} }
@ -335,14 +335,6 @@ public:
{ {
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("<Lazy?>")); return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("<Lazy?>"));
} }
AstType* operator()(const UnknownTypeVar& ttv)
{
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName{"unknown"});
}
AstType* operator()(const NeverTypeVar& ttv)
{
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName{"never"});
}
private: private:
Allocator* allocator; Allocator* allocator;
@ -358,14 +350,14 @@ public:
, syntheticNames(syntheticNames) , syntheticNames(syntheticNames)
, typeVisitor(typeVisitor) , typeVisitor(typeVisitor)
{ {
LUAU_ASSERT(allocator); lluz_ASSERT(allocator);
LUAU_ASSERT(syntheticNames); lluz_ASSERT(syntheticNames);
LUAU_ASSERT(typeVisitor); lluz_ASSERT(typeVisitor);
} }
AstTypePack* operator()(const BoundTypePack& btp) const AstTypePack* operator()(const BoundTypePack& btp) const
{ {
return Luau::visit(*this, btp.boundTo->ty); return lluz::visit(*this, btp.boundTo->ty);
} }
AstTypePack* operator()(const TypePack& tp) const AstTypePack* operator()(const TypePack& tp) const
@ -375,12 +367,12 @@ public:
head.data = static_cast<AstType**>(allocator->allocate(sizeof(AstType*) * tp.head.size())); head.data = static_cast<AstType**>(allocator->allocate(sizeof(AstType*) * tp.head.size()));
for (size_t i = 0; i < tp.head.size(); i++) for (size_t i = 0; i < tp.head.size(); i++)
head.data[i] = Luau::visit(*typeVisitor, tp.head[i]->ty); head.data[i] = lluz::visit(*typeVisitor, tp.head[i]->ty);
AstTypePack* tail = nullptr; AstTypePack* tail = nullptr;
if (tp.tail) if (tp.tail)
tail = Luau::visit(*this, (*tp.tail)->ty); tail = lluz::visit(*this, (*tp.tail)->ty);
return allocator->alloc<AstTypePackExplicit>(Location(), AstTypeList{head, tail}); return allocator->alloc<AstTypePackExplicit>(Location(), AstTypeList{head, tail});
} }
@ -390,7 +382,7 @@ public:
if (vtp.hidden) if (vtp.hidden)
return nullptr; return nullptr;
return allocator->alloc<AstTypePackVariadic>(Location(), Luau::visit(*typeVisitor, vtp.ty->ty)); return allocator->alloc<AstTypePackVariadic>(Location(), lluz::visit(*typeVisitor, vtp.ty->ty));
} }
AstTypePack* operator()(const GenericTypePack& gtp) const AstTypePack* operator()(const GenericTypePack& gtp) const
@ -417,13 +409,13 @@ private:
AstTypePack* TypeRehydrationVisitor::rehydrate(TypePackId tp) AstTypePack* TypeRehydrationVisitor::rehydrate(TypePackId tp)
{ {
TypePackRehydrationVisitor tprv(allocator, syntheticNames, this); TypePackRehydrationVisitor tprv(allocator, syntheticNames, this);
return Luau::visit(tprv, tp->ty); return lluz::visit(tprv, tp->ty);
} }
class TypeAttacher : public AstVisitor class TypeAttacher : public AstVisitor
{ {
public: public:
TypeAttacher(Module& checker, Luau::Allocator* alloc) TypeAttacher(Module& checker, lluz::Allocator* alloc)
: module(checker) : module(checker)
, allocator(alloc) , allocator(alloc)
{ {
@ -451,10 +443,10 @@ public:
{ {
if (!type) if (!type)
return nullptr; return nullptr;
return Luau::visit(TypeRehydrationVisitor(allocator, &syntheticNames), (*type)->ty); return lluz::visit(TypeRehydrationVisitor(allocator, &syntheticNames), (*type)->ty);
} }
AstArray<Luau::AstType*> typeAstPack(TypePackId type) AstArray<lluz::AstType*> typeAstPack(TypePackId type)
{ {
const auto& [v, tail] = flatten(type); const auto& [v, tail] = flatten(type);
@ -463,7 +455,7 @@ public:
result.data = static_cast<AstType**>(allocator->allocate(sizeof(AstType*) * v.size())); result.data = static_cast<AstType**>(allocator->allocate(sizeof(AstType*) * v.size()));
for (size_t i = 0; i < v.size(); ++i) for (size_t i = 0; i < v.size(); ++i)
{ {
result.data[i] = Luau::visit(TypeRehydrationVisitor(allocator, &syntheticNames), v[i]->ty); result.data[i] = lluz::visit(TypeRehydrationVisitor(allocator, &syntheticNames), v[i]->ty);
} }
return result; return result;
} }
@ -549,7 +541,7 @@ void attachTypeData(SourceModule& source, Module& result)
AstType* rehydrateAnnotation(TypeId type, Allocator* allocator, const TypeRehydrationOptions& options) AstType* rehydrateAnnotation(TypeId type, Allocator* allocator, const TypeRehydrationOptions& options)
{ {
SyntheticNames syntheticNames; SyntheticNames syntheticNames;
return Luau::visit(TypeRehydrationVisitor(allocator, &syntheticNames, options), type->ty); return lluz::visit(TypeRehydrationVisitor(allocator, &syntheticNames, options), type->ty);
} }
} // namespace Luau } // namespace lluz

View file

@ -1,19 +1,19 @@
#include "Luau/TypeChecker2.h" #include "lluz/TypeChecker2.h"
#include <algorithm> #include <algorithm>
#include "Luau/Ast.h" #include "lluz/Ast.h"
#include "Luau/AstQuery.h" #include "lluz/AstQuery.h"
#include "Luau/Clone.h" #include "lluz/Clone.h"
#include "Luau/Instantiation.h" #include "lluz/Instantiation.h"
#include "Luau/Normalize.h" #include "lluz/Normalize.h"
#include "Luau/TxnLog.h" #include "lluz/TxnLog.h"
#include "Luau/TypeUtils.h" #include "lluz/TypeUtils.h"
#include "Luau/Unifier.h" #include "lluz/Unifier.h"
#include "Luau/ToString.h" #include "lluz/ToString.h"
namespace Luau namespace lluz
{ {
struct TypeChecker2 : public AstVisitor struct TypeChecker2 : public AstVisitor
@ -63,15 +63,12 @@ struct TypeChecker2 : public AstVisitor
TypeId lookupAnnotation(AstType* annotation) TypeId lookupAnnotation(AstType* annotation)
{ {
TypeId* ty = module->astResolvedTypes.find(annotation); TypeId* ty = module->astResolvedTypes.find(annotation);
LUAU_ASSERT(ty); lluz_ASSERT(ty);
return follow(*ty); return follow(*ty);
} }
TypePackId reconstructPack(AstArray<AstExpr*> exprs, TypeArena& arena) TypePackId reconstructPack(AstArray<AstExpr*> exprs, TypeArena& arena)
{ {
if (exprs.size == 0)
return arena.addTypePack(TypePack{{}, std::nullopt});
std::vector<TypeId> head; std::vector<TypeId> head;
for (size_t i = 0; i < exprs.size - 1; ++i) for (size_t i = 0; i < exprs.size - 1; ++i)
@ -83,14 +80,14 @@ struct TypeChecker2 : public AstVisitor
return arena.addTypePack(TypePack{head, tail}); return arena.addTypePack(TypePack{head, tail});
} }
Scope* findInnermostScope(Location location) Scope2* findInnermostScope(Location location)
{ {
Scope* bestScope = module->getModuleScope().get(); Scope2* bestScope = module->getModuleScope2();
Location bestLocation = module->scopes[0].first; Location bestLocation = module->scope2s[0].first;
for (size_t i = 0; i < module->scopes.size(); ++i) for (size_t i = 0; i < module->scope2s.size(); ++i)
{ {
auto& [scopeBounds, scope] = module->scopes[i]; auto& [scopeBounds, scope] = module->scope2s[i];
if (scopeBounds.encloses(location)) if (scopeBounds.encloses(location))
{ {
if (scopeBounds.begin > bestLocation.begin || scopeBounds.end < bestLocation.end) if (scopeBounds.begin > bestLocation.begin || scopeBounds.end < bestLocation.end)
@ -184,7 +181,7 @@ struct TypeChecker2 : public AstVisitor
bool visit(AstStatReturn* ret) override bool visit(AstStatReturn* ret) override
{ {
Scope* scope = findInnermostScope(ret->location); Scope2* scope = findInnermostScope(ret->location);
TypePackId expectedRetType = scope->returnType; TypePackId expectedRetType = scope->returnType;
TypeArena arena; TypeArena arena;
@ -214,13 +211,13 @@ struct TypeChecker2 : public AstVisitor
TypePackId expectedRetType = lookupPack(call); TypePackId expectedRetType = lookupPack(call);
TypeId functionType = lookupType(call->func); TypeId functionType = lookupType(call->func);
TypeId instantiatedFunctionType = instantiation.substitute(functionType).value_or(nullptr); TypeId instantiatedFunctionType = instantiation.substitute(functionType).value_or(nullptr);
LUAU_ASSERT(functionType); lluz_ASSERT(functionType);
TypePack args; TypePack args;
for (const auto& arg : call->args) for (const auto& arg : call->args)
{ {
TypeId argTy = module->astTypes[arg]; TypeId argTy = module->astTypes[arg];
LUAU_ASSERT(argTy); lluz_ASSERT(argTy);
args.head.push_back(argTy); args.head.push_back(argTy);
} }
@ -243,7 +240,7 @@ struct TypeChecker2 : public AstVisitor
{ {
TypeId inferredFnTy = lookupType(fn); TypeId inferredFnTy = lookupType(fn);
const FunctionTypeVar* inferredFtv = get<FunctionTypeVar>(inferredFnTy); const FunctionTypeVar* inferredFtv = get<FunctionTypeVar>(inferredFnTy);
LUAU_ASSERT(inferredFtv); lluz_ASSERT(inferredFtv);
auto argIt = begin(inferredFtv->argTypes); auto argIt = begin(inferredFtv->argTypes);
for (const auto& arg : fn->args) for (const auto& arg : fn->args)
@ -325,13 +322,10 @@ struct TypeChecker2 : public AstVisitor
{ {
pack = follow(pack); pack = follow(pack);
while (true) while (auto tp = get<TypePack>(pack))
{ {
auto tp = get<TypePack>(pack); if (tp->head.empty() && tp->tail)
if (tp && tp->head.empty() && tp->tail)
pack = *tp->tail; pack = *tp->tail;
else
break;
} }
if (auto ty = first(pack)) if (auto ty = first(pack))
@ -352,7 +346,7 @@ struct TypeChecker2 : public AstVisitor
else if (get<Unifiable::Error>(pack)) else if (get<Unifiable::Error>(pack))
return singletonTypes.errorRecoveryType(); return singletonTypes.errorRecoveryType();
else else
ice.ice("flattenPack got a weird pack!"); ice.ice(XorStr("flattenPack got a weird pack!"));
} }
bool visit(AstType* ty) override bool visit(AstType* ty) override
@ -362,7 +356,7 @@ struct TypeChecker2 : public AstVisitor
bool visit(AstTypeReference* ty) override bool visit(AstTypeReference* ty) override
{ {
Scope* scope = findInnermostScope(ty->location); Scope2* scope = findInnermostScope(ty->location);
// TODO: Imported types // TODO: Imported types
// TODO: Generic types // TODO: Generic types
@ -392,4 +386,4 @@ void check(const SourceModule& sourceModule, Module* module)
sourceModule.root->visit(&typeChecker); sourceModule.root->visit(&typeChecker);
} }
} // namespace Luau } // namespace lluz

File diff suppressed because it is too large Load diff

View file

@ -1,11 +1,13 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/TypePack.h" #include "lluz/TypePack.h"
#include "Luau/TxnLog.h" #include "lluz/TxnLog.h"
#include <stdexcept> #include <stdexcept>
namespace Luau lluz_FASTFLAG(LluNonCopyableTypeVarFields)
namespace lluz
{ {
TypePackVar::TypePackVar(const TypePackVariant& tp) TypePackVar::TypePackVar(const TypePackVariant& tp)
@ -38,10 +40,19 @@ TypePackVar& TypePackVar::operator=(TypePackVariant&& tp)
TypePackVar& TypePackVar::operator=(const TypePackVar& rhs) TypePackVar& TypePackVar::operator=(const TypePackVar& rhs)
{ {
LUAU_ASSERT(owningArena == rhs.owningArena); if (FFlag::LluNonCopyableTypeVarFields)
LUAU_ASSERT(!rhs.persistent); {
lluz_ASSERT(owningArena == rhs.owningArena);
lluz_ASSERT(!rhs.persistent);
reassign(rhs); reassign(rhs);
}
else
{
ty = rhs.ty;
persistent = rhs.persistent;
owningArena = rhs.owningArena;
}
return *this; return *this;
} }
@ -66,7 +77,7 @@ TypePackIterator::TypePackIterator(TypePackId typePack, const TxnLog* log)
TypePackIterator& TypePackIterator::operator++() TypePackIterator& TypePackIterator::operator++()
{ {
LUAU_ASSERT(tp); lluz_ASSERT(tp);
++currentIndex; ++currentIndex;
while (tp && currentIndex >= tp->head.size()) while (tp && currentIndex >= tp->head.size())
@ -99,13 +110,13 @@ bool TypePackIterator::operator==(const TypePackIterator& rhs)
const TypeId& TypePackIterator::operator*() const TypeId& TypePackIterator::operator*()
{ {
LUAU_ASSERT(tp); lluz_ASSERT(tp);
return tp->head[currentIndex]; return tp->head[currentIndex];
} }
std::optional<TypePackId> TypePackIterator::tail() std::optional<TypePackId> TypePackIterator::tail()
{ {
LUAU_ASSERT(!tp); lluz_ASSERT(!tp);
return currentTypePack ? std::optional<TypePackId>{currentTypePack} : std::nullopt; return currentTypePack ? std::optional<TypePackId>{currentTypePack} : std::nullopt;
} }
@ -227,7 +238,7 @@ TypePackId follow(TypePackId tp, std::function<TypePackId(TypePackId)> mapper)
cycleTester = nullptr; cycleTester = nullptr;
if (tp == cycleTester) if (tp == cycleTester)
throw std::runtime_error("Luau::follow detected a TypeVar cycle!!"); throw std::runtime_error(XorStr("lluz::follow detected a TypeVar cycle!!"));
} }
} }
} }
@ -283,16 +294,6 @@ std::optional<TypeId> first(TypePackId tp, bool ignoreHiddenVariadics)
return std::nullopt; return std::nullopt;
} }
TypePackVar* asMutable(TypePackId tp)
{
return const_cast<TypePackVar*>(tp);
}
TypePack* asMutable(const TypePack* tp)
{
return const_cast<TypePack*>(tp);
}
bool isEmpty(TypePackId tp) bool isEmpty(TypePackId tp)
{ {
tp = follow(tp); tp = follow(tp);
@ -359,25 +360,13 @@ bool isVariadic(TypePackId tp, const TxnLog& log)
return false; return false;
} }
bool containsNever(TypePackId tp) TypePackVar* asMutable(TypePackId tp)
{ {
auto it = begin(tp); return const_cast<TypePackVar*>(tp);
auto endIt = end(tp);
while (it != endIt)
{
if (get<NeverTypeVar>(follow(*it)))
return true;
++it;
}
if (auto tail = it.tail())
{
if (auto vtp = get<VariadicTypePack>(*tail); vtp && get<NeverTypeVar>(follow(vtp->ty)))
return true;
}
return false;
} }
} // namespace Luau TypePack* asMutable(const TypePack* tp)
{
return const_cast<TypePack*>(tp);
}
} // namespace lluz

View file

@ -1,11 +1,11 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/TypeUtils.h" #include "lluz/TypeUtils.h"
#include "Luau/Scope.h" #include "lluz/Scope.h"
#include "Luau/ToString.h" #include "lluz/ToString.h"
#include "Luau/TypeInfer.h" #include "lluz/TypeInfer.h"
namespace Luau namespace lluz
{ {
std::optional<TypeId> findMetatableEntry(ErrorVec& errors, TypeId type, std::string entry, Location location) std::optional<TypeId> findMetatableEntry(ErrorVec& errors, TypeId type, std::string entry, Location location)
@ -24,7 +24,7 @@ std::optional<TypeId> findMetatableEntry(ErrorVec& errors, TypeId type, std::str
const TableTypeVar* mtt = getTableType(unwrapped); const TableTypeVar* mtt = getTableType(unwrapped);
if (!mtt) if (!mtt)
{ {
errors.push_back(TypeError{location, GenericError{"Metatable was not a table"}}); errors.push_back(TypeError{location, GenericError{"Metatable was not a table."}});
return std::nullopt; return std::nullopt;
} }
@ -83,4 +83,4 @@ std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, TypeId t
return std::nullopt; return std::nullopt;
} }
} // namespace Luau } // namespace lluz

View file

@ -1,16 +1,16 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/TypeVar.h" #include "lluz/TypeVar.h"
#include "Luau/BuiltinDefinitions.h" #include "lluz/BuiltinDefinitions.h"
#include "Luau/Common.h" #include "lluz/Common.h"
#include "Luau/DenseHash.h" #include "lluz/DenseHash.h"
#include "Luau/Error.h" #include "lluz/Error.h"
#include "Luau/RecursionCounter.h" #include "lluz/RecursionCounter.h"
#include "Luau/StringUtils.h" #include "lluz/StringUtils.h"
#include "Luau/ToString.h" #include "lluz/ToString.h"
#include "Luau/TypeInfer.h" #include "lluz/TypeInfer.h"
#include "Luau/TypePack.h" #include "lluz/TypePack.h"
#include "Luau/VisitTypeVar.h" #include "lluz/VisitTypeVar.h"
#include <algorithm> #include <algorithm>
#include <optional> #include <optional>
@ -18,31 +18,19 @@
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
LUAU_FASTFLAG(DebugLuauFreezeArena) lluz_FASTFLAG(DebugLluFreezeArena)
LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500) lluz_FASTINTVARIABLE(LluTypeMaximumStringifierLength, 500)
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0) lluz_FASTINTVARIABLE(LluTableTypeMaximumStringifierLength, 0)
LUAU_FASTINT(LuauTypeInferRecursionLimit) lluz_FASTINT(LluTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauUnknownAndNeverType) lluz_FASTFLAG(LluNonCopyableTypeVarFields)
LUAU_FASTFLAGVARIABLE(LuauDeduceGmatchReturnTypes, false)
LUAU_FASTFLAGVARIABLE(LuauMaybeGenericIntersectionTypes, false)
LUAU_FASTFLAGVARIABLE(LuauDeduceFindMatchReturnTypes, false)
namespace Luau namespace lluz
{ {
std::optional<WithPredicate<TypePackId>> magicFunctionFormat( std::optional<WithPredicate<TypePackId>> magicFunctionFormat(
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate); TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
static std::optional<WithPredicate<TypePackId>> magicFunctionGmatch(
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
static std::optional<WithPredicate<TypePackId>> magicFunctionMatch(
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
static std::optional<WithPredicate<TypePackId>> magicFunctionFind(
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
TypeId follow(TypeId t) TypeId follow(TypeId t)
{ {
return follow(t, [](TypeId t) { return follow(t, [](TypeId t) {
@ -66,7 +54,7 @@ TypeId follow(TypeId t, std::function<TypeId(TypeId)> mapper)
{ {
TypeId res = ltv->thunk(); TypeId res = ltv->thunk();
if (get<LazyTypeVar>(res)) if (get<LazyTypeVar>(res))
throw std::runtime_error("Lazy TypeVar cannot resolve to another Lazy TypeVar"); throw std::runtime_error(XorStr("Lazy TypeVar cannot resolve to another Lazy TypeVar"));
*asMutable(ty) = BoundTypeVar(res); *asMutable(ty) = BoundTypeVar(res);
} }
@ -104,7 +92,7 @@ TypeId follow(TypeId t, std::function<TypeId(TypeId)> mapper)
cycleTester = nullptr; cycleTester = nullptr;
if (t == cycleTester) if (t == cycleTester)
throw std::runtime_error("Luau::follow detected a TypeVar cycle!!"); throw std::runtime_error(XorStr("lluz::follow detected a TypeVar cycle!!"));
} }
} }
} }
@ -171,12 +159,10 @@ bool isNumber(TypeId ty)
// Returns true when ty is a subtype of string // Returns true when ty is a subtype of string
bool isString(TypeId ty) bool isString(TypeId ty)
{ {
ty = follow(ty); if (isPrim(ty, PrimitiveTypeVar::String) || get<StringSingleton>(get<SingletonTypeVar>(follow(ty))))
if (isPrim(ty, PrimitiveTypeVar::String) || get<StringSingleton>(get<SingletonTypeVar>(ty)))
return true; return true;
if (auto utv = get<UnionTypeVar>(ty)) if (auto utv = get<UnionTypeVar>(follow(ty)))
return std::all_of(begin(utv), end(utv), isString); return std::all_of(begin(utv), end(utv), isString);
return false; return false;
@ -208,7 +194,7 @@ bool isOptional(TypeId ty)
ty = follow(ty); ty = follow(ty);
if (get<AnyTypeVar>(ty) || (FFlag::LuauUnknownAndNeverType && get<UnknownTypeVar>(ty))) if (get<AnyTypeVar>(ty))
return true; return true;
auto utv = get<UnionTypeVar>(ty); auto utv = get<UnionTypeVar>(ty);
@ -242,8 +228,6 @@ bool isOverloadedFunction(TypeId ty)
std::optional<TypeId> getMetatable(TypeId type) std::optional<TypeId> getMetatable(TypeId type)
{ {
type = follow(type);
if (const MetatableTypeVar* mtType = get<MetatableTypeVar>(type)) if (const MetatableTypeVar* mtType = get<MetatableTypeVar>(type))
return mtType->metatable; return mtType->metatable;
else if (const ClassTypeVar* classType = get<ClassTypeVar>(type)) else if (const ClassTypeVar* classType = get<ClassTypeVar>(type))
@ -251,7 +235,7 @@ std::optional<TypeId> getMetatable(TypeId type)
else if (isString(type)) else if (isString(type))
{ {
auto ptv = get<PrimitiveTypeVar>(getSingletonTypes().stringType); auto ptv = get<PrimitiveTypeVar>(getSingletonTypes().stringType);
LUAU_ASSERT(ptv && ptv->metatable); lluz_ASSERT(ptv && ptv->metatable);
return ptv->metatable; return ptv->metatable;
} }
@ -350,28 +334,6 @@ bool isGeneric(TypeId ty)
bool maybeGeneric(TypeId ty) bool maybeGeneric(TypeId ty)
{ {
if (FFlag::LuauMaybeGenericIntersectionTypes)
{
ty = follow(ty);
if (get<FreeTypeVar>(ty))
return true;
if (auto ttv = get<TableTypeVar>(ty))
{
// TODO: recurse on table types CLI-39914
(void)ttv;
return true;
}
if (auto itv = get<IntersectionTypeVar>(ty))
{
return std::any_of(begin(itv), end(itv), maybeGeneric);
}
return isGeneric(ty);
}
ty = follow(ty); ty = follow(ty);
if (get<FreeTypeVar>(ty)) if (get<FreeTypeVar>(ty))
return true; return true;
@ -399,7 +361,7 @@ bool maybeSingleton(TypeId ty)
bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount) bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount)
{ {
RecursionLimiter _rl(recursionCount, FInt::LuauTypeInferRecursionLimit); RecursionLimiter _rl(recursionCount, FInt::LluTypeInferRecursionLimit);
ty = follow(ty); ty = follow(ty);
@ -684,10 +646,20 @@ TypeVar& TypeVar::operator=(TypeVariant&& rhs)
TypeVar& TypeVar::operator=(const TypeVar& rhs) TypeVar& TypeVar::operator=(const TypeVar& rhs)
{ {
LUAU_ASSERT(owningArena == rhs.owningArena); if (FFlag::LluNonCopyableTypeVarFields)
LUAU_ASSERT(!rhs.persistent); {
lluz_ASSERT(owningArena == rhs.owningArena);
lluz_ASSERT(!rhs.persistent);
reassign(rhs); reassign(rhs);
}
else
{
ty = rhs.ty;
persistent = rhs.persistent;
normal = rhs.normal;
owningArena = rhs.owningArena;
}
return *this; return *this;
} }
@ -704,14 +676,10 @@ static TypeVar threadType_{PrimitiveTypeVar{PrimitiveTypeVar::Thread}, /*persist
static TypeVar trueType_{SingletonTypeVar{BooleanSingleton{true}}, /*persistent*/ true}; static TypeVar trueType_{SingletonTypeVar{BooleanSingleton{true}}, /*persistent*/ true};
static TypeVar falseType_{SingletonTypeVar{BooleanSingleton{false}}, /*persistent*/ true}; static TypeVar falseType_{SingletonTypeVar{BooleanSingleton{false}}, /*persistent*/ true};
static TypeVar anyType_{AnyTypeVar{}, /*persistent*/ true}; static TypeVar anyType_{AnyTypeVar{}, /*persistent*/ true};
static TypeVar unknownType_{UnknownTypeVar{}, /*persistent*/ true};
static TypeVar neverType_{NeverTypeVar{}, /*persistent*/ true};
static TypeVar errorType_{ErrorTypeVar{}, /*persistent*/ true}; static TypeVar errorType_{ErrorTypeVar{}, /*persistent*/ true};
static TypePackVar anyTypePack_{VariadicTypePack{&anyType_}, /*persistent*/ true}; static TypePackVar anyTypePack_{VariadicTypePack{&anyType_}, true};
static TypePackVar errorTypePack_{Unifiable::Error{}, /*persistent*/ true}; static TypePackVar errorTypePack_{Unifiable::Error{}};
static TypePackVar neverTypePack_{VariadicTypePack{&neverType_}, /*persistent*/ true};
static TypePackVar uninhabitableTypePack_{TypePack{{&neverType_}, &neverTypePack_}, /*persistent*/ true};
SingletonTypes::SingletonTypes() SingletonTypes::SingletonTypes()
: nilType(&nilType_) : nilType(&nilType_)
@ -722,31 +690,27 @@ SingletonTypes::SingletonTypes()
, trueType(&trueType_) , trueType(&trueType_)
, falseType(&falseType_) , falseType(&falseType_)
, anyType(&anyType_) , anyType(&anyType_)
, unknownType(&unknownType_)
, neverType(&neverType_)
, anyTypePack(&anyTypePack_) , anyTypePack(&anyTypePack_)
, neverTypePack(&neverTypePack_)
, uninhabitableTypePack(&uninhabitableTypePack_)
, arena(new TypeArena) , arena(new TypeArena)
{ {
TypeId stringMetatable = makeStringMetatable(); TypeId stringMetatable = makeStringMetatable();
stringType_.ty = PrimitiveTypeVar{PrimitiveTypeVar::String, stringMetatable}; stringType_.ty = PrimitiveTypeVar{PrimitiveTypeVar::String, stringMetatable};
persist(stringMetatable); persist(stringMetatable);
debugFreezeArena = FFlag::DebugLuauFreezeArena; debugFreezeArena = FFlag::DebugLluFreezeArena;
freeze(*arena); freeze(*arena);
} }
SingletonTypes::~SingletonTypes() SingletonTypes::~SingletonTypes()
{ {
// Destroy the arena with the same memory management flags it was created with // Destroy the arena with the same memory management flags it was created with
bool prevFlag = FFlag::DebugLuauFreezeArena; bool prevFlag = FFlag::DebugLluFreezeArena;
FFlag::DebugLuauFreezeArena.value = debugFreezeArena; FFlag::DebugLluFreezeArena.value = debugFreezeArena;
unfreeze(*arena); unfreeze(*arena);
arena.reset(nullptr); arena.reset(nullptr);
FFlag::DebugLuauFreezeArena.value = prevFlag; FFlag::DebugLluFreezeArena.value = prevFlag;
} }
TypeId SingletonTypes::makeStringMetatable() TypeId SingletonTypes::makeStringMetatable()
@ -774,26 +738,19 @@ TypeId SingletonTypes::makeStringMetatable()
const TypeId gsubFunc = makeFunction(*arena, stringType, {}, {}, {stringType, replArgType, optionalNumber}, {}, {stringType, numberType}); const TypeId gsubFunc = makeFunction(*arena, stringType, {}, {}, {stringType, replArgType, optionalNumber}, {}, {stringType, numberType});
const TypeId gmatchFunc = const TypeId gmatchFunc =
makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionTypeVar{emptyPack, stringVariadicList})}); makeFunction(*arena, stringType, {}, {}, {stringType}, {}, {arena->addType(FunctionTypeVar{emptyPack, stringVariadicList})});
attachMagicFunction(gmatchFunc, magicFunctionGmatch);
const TypeId matchFunc = arena->addType(FunctionTypeVar{arena->addTypePack({stringType, stringType, optionalNumber}),
arena->addTypePack(TypePackVar{VariadicTypePack{FFlag::LuauDeduceFindMatchReturnTypes ? stringType : optionalString}})});
attachMagicFunction(matchFunc, magicFunctionMatch);
const TypeId findFunc = arena->addType(FunctionTypeVar{arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}),
arena->addTypePack(TypePack{{optionalNumber, optionalNumber}, stringVariadicList})});
attachMagicFunction(findFunc, magicFunctionFind);
TableTypeVar::Props stringLib = { TableTypeVar::Props stringLib = {
{"byte", {arena->addType(FunctionTypeVar{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList})}}, {"byte", {arena->addType(FunctionTypeVar{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList})}},
{"char", {arena->addType(FunctionTypeVar{numberVariadicList, arena->addTypePack({stringType})})}}, {"char", {arena->addType(FunctionTypeVar{numberVariadicList, arena->addTypePack({stringType})})}},
{"find", {findFunc}}, {"find", {arena->addType(FunctionTypeVar{arena->addTypePack({stringType, stringType, optionalNumber, optionalBoolean}),
arena->addTypePack(TypePack{{optionalNumber, optionalNumber}, stringVariadicList})})}},
{"format", {formatFn}}, // FIXME {"format", {formatFn}}, // FIXME
{"gmatch", {gmatchFunc}}, {"gmatch", {gmatchFunc}},
{"gsub", {gsubFunc}}, {"gsub", {gsubFunc}},
{"len", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType})}}, {"len", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType})}},
{"lower", {stringToStringType}}, {"lower", {stringToStringType}},
{"match", {matchFunc}}, {"match", {arena->addType(FunctionTypeVar{arena->addTypePack({stringType, stringType, optionalNumber}),
arena->addTypePack(TypePackVar{VariadicTypePack{optionalString}})})}},
{"rep", {makeFunction(*arena, stringType, {}, {}, {numberType}, {}, {stringType})}}, {"rep", {makeFunction(*arena, stringType, {}, {}, {numberType}, {}, {stringType})}},
{"reverse", {stringToStringType}}, {"reverse", {stringToStringType}},
{"sub", {makeFunction(*arena, stringType, {}, {}, {numberType, optionalNumber}, {}, {stringType})}}, {"sub", {makeFunction(*arena, stringType, {}, {}, {numberType, optionalNumber}, {}, {stringType})}},
@ -811,7 +768,7 @@ TypeId SingletonTypes::makeStringMetatable()
})}}, })}},
}; };
assignPropDocumentationSymbols(stringLib, "@luau/global/string"); assignPropDocumentationSymbols(stringLib, XorStr("@lluz/global/string"));
TypeId tableType = arena->addType(TableTypeVar{std::move(stringLib), std::nullopt, TypeLevel{}, TableState::Sealed}); TypeId tableType = arena->addType(TableTypeVar{std::move(stringLib), std::nullopt, TypeLevel{}, TableState::Sealed});
@ -871,7 +828,7 @@ void persist(TypeId ty)
} }
else if (auto ttv = get<TableTypeVar>(t)) else if (auto ttv = get<TableTypeVar>(t))
{ {
LUAU_ASSERT(ttv->state != TableState::Free && ttv->state != TableState::Unsealed); lluz_ASSERT(ttv->state != TableState::Free && ttv->state != TableState::Unsealed);
for (const auto& [_name, prop] : ttv->props) for (const auto& [_name, prop] : ttv->props)
queue.push_back(prop.type); queue.push_back(prop.type);
@ -912,7 +869,7 @@ void persist(TypeId ty)
} }
else else
{ {
LUAU_ASSERT(!"TypeId is not supported in a persist call"); lluz_ASSERT(!XorStr("TypeId is not supported in a persist call"));
} }
} }
} }
@ -940,7 +897,7 @@ void persist(TypePackId tp)
} }
else else
{ {
LUAU_ASSERT(!"TypePackId is not supported in a persist call"); lluz_ASSERT(!XorStr("TypePackId is not supported in a persist call"));
} }
} }
@ -954,8 +911,6 @@ const TypeLevel* getLevel(TypeId ty)
return &ttv->level; return &ttv->level;
else if (auto ftv = get<FunctionTypeVar>(ty)) else if (auto ftv = get<FunctionTypeVar>(ty))
return &ftv->level; return &ftv->level;
else if (auto ctv = get<ConstrainedTypeVar>(ty))
return &ctv->level;
else else
return nullptr; return nullptr;
} }
@ -988,7 +943,7 @@ const Property* lookupClassProp(const ClassTypeVar* cls, const Name& name)
else else
return nullptr; return nullptr;
LUAU_ASSERT(cls); lluz_ASSERT(cls);
} }
return nullptr; return nullptr;
@ -1004,25 +959,100 @@ bool isSubclass(const ClassTypeVar* cls, const ClassTypeVar* parent)
return false; return false;
cls = get<ClassTypeVar>(*cls->parent); cls = get<ClassTypeVar>(*cls->parent);
LUAU_ASSERT(cls); lluz_ASSERT(cls);
} }
return false; return false;
} }
const std::vector<TypeId>& getTypes(const UnionTypeVar* utv) UnionTypeVarIterator::UnionTypeVarIterator(const UnionTypeVar* utv)
{ {
return utv->options; lluz_ASSERT(utv);
if (!utv->options.empty())
stack.push_front({utv, 0});
seen.insert(utv);
} }
const std::vector<TypeId>& getTypes(const IntersectionTypeVar* itv) UnionTypeVarIterator& UnionTypeVarIterator::operator++()
{ {
return itv->parts; advance();
descend();
return *this;
} }
const std::vector<TypeId>& getTypes(const ConstrainedTypeVar* ctv) UnionTypeVarIterator UnionTypeVarIterator::operator++(int)
{ {
return ctv->parts; UnionTypeVarIterator copy = *this;
++copy;
return copy;
}
bool UnionTypeVarIterator::operator!=(const UnionTypeVarIterator& rhs)
{
return !(*this == rhs);
}
bool UnionTypeVarIterator::operator==(const UnionTypeVarIterator& rhs)
{
if (!stack.empty() && !rhs.stack.empty())
return stack.front() == rhs.stack.front();
return stack.empty() && rhs.stack.empty();
}
const TypeId& UnionTypeVarIterator::operator*()
{
lluz_ASSERT(!stack.empty());
descend();
auto [utv, currentIndex] = stack.front();
lluz_ASSERT(utv);
lluz_ASSERT(currentIndex < utv->options.size());
const TypeId& ty = utv->options[currentIndex];
lluz_ASSERT(!get<UnionTypeVar>(follow(ty)));
return ty;
}
void UnionTypeVarIterator::advance()
{
while (!stack.empty())
{
auto& [utv, currentIndex] = stack.front();
++currentIndex;
if (currentIndex >= utv->options.size())
stack.pop_front();
else
break;
}
}
void UnionTypeVarIterator::descend()
{
while (!stack.empty())
{
auto [utv, currentIndex] = stack.front();
if (auto innerUnion = get<UnionTypeVar>(follow(utv->options[currentIndex])))
{
// If we're about to descend into a cyclic UnionTypeVar, we should skip over this.
// Ideally this should never happen, but alas it does from time to time. :(
if (seen.find(innerUnion) != seen.end())
advance();
else
{
seen.insert(innerUnion);
stack.push_front({innerUnion, 0});
}
continue;
}
break;
}
} }
UnionTypeVarIterator begin(const UnionTypeVar* utv) UnionTypeVarIterator begin(const UnionTypeVar* utv)
@ -1035,27 +1065,6 @@ UnionTypeVarIterator end(const UnionTypeVar* utv)
return UnionTypeVarIterator{}; return UnionTypeVarIterator{};
} }
IntersectionTypeVarIterator begin(const IntersectionTypeVar* itv)
{
return IntersectionTypeVarIterator{itv};
}
IntersectionTypeVarIterator end(const IntersectionTypeVar* itv)
{
return IntersectionTypeVarIterator{};
}
ConstrainedTypeVarIterator begin(const ConstrainedTypeVar* ctv)
{
return ConstrainedTypeVarIterator{ctv};
}
ConstrainedTypeVarIterator end(const ConstrainedTypeVar* ctv)
{
return ConstrainedTypeVarIterator{};
}
static std::vector<TypeId> parseFormatString(TypeChecker& typechecker, const char* data, size_t size) static std::vector<TypeId> parseFormatString(TypeChecker& typechecker, const char* data, size_t size)
{ {
const char* options = "cdiouxXeEfgGqs"; const char* options = "cdiouxXeEfgGqs";
@ -1135,197 +1144,6 @@ std::optional<WithPredicate<TypePackId>> magicFunctionFormat(
return WithPredicate<TypePackId>{arena.addTypePack({typechecker.stringType})}; return WithPredicate<TypePackId>{arena.addTypePack({typechecker.stringType})};
} }
static std::vector<TypeId> parsePatternString(TypeChecker& typechecker, const char* data, size_t size)
{
std::vector<TypeId> result;
int depth = 0;
bool parsingSet = false;
for (size_t i = 0; i < size; ++i)
{
if (data[i] == '%')
{
++i;
if (!parsingSet && i < size && data[i] == 'b')
i += 2;
}
else if (!parsingSet && data[i] == '[')
{
parsingSet = true;
if (i + 1 < size && data[i + 1] == ']')
i += 1;
}
else if (parsingSet && data[i] == ']')
{
parsingSet = false;
}
else if (data[i] == '(')
{
if (parsingSet)
continue;
if (i + 1 < size && data[i + 1] == ')')
{
i++;
result.push_back(typechecker.numberType);
continue;
}
++depth;
result.push_back(typechecker.stringType);
}
else if (data[i] == ')')
{
if (parsingSet)
continue;
--depth;
if (depth < 0)
break;
}
}
if (depth != 0 || parsingSet)
return std::vector<TypeId>();
if (result.empty())
result.push_back(typechecker.stringType);
return result;
}
static std::optional<WithPredicate<TypePackId>> magicFunctionGmatch(
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
{
if (!FFlag::LuauDeduceGmatchReturnTypes)
return std::nullopt;
auto [paramPack, _predicates] = withPredicate;
const auto& [params, tail] = flatten(paramPack);
if (params.size() != 2)
return std::nullopt;
TypeArena& arena = typechecker.currentModule->internalTypes;
AstExprConstantString* pattern = nullptr;
size_t index = expr.self ? 0 : 1;
if (expr.args.size > index)
pattern = expr.args.data[index]->as<AstExprConstantString>();
if (!pattern)
return std::nullopt;
std::vector<TypeId> returnTypes = parsePatternString(typechecker, pattern->value.data, pattern->value.size);
if (returnTypes.empty())
return std::nullopt;
typechecker.unify(params[0], typechecker.stringType, expr.args.data[0]->location);
const TypePackId emptyPack = arena.addTypePack({});
const TypePackId returnList = arena.addTypePack(returnTypes);
const TypeId iteratorType = arena.addType(FunctionTypeVar{emptyPack, returnList});
return WithPredicate<TypePackId>{arena.addTypePack({iteratorType})};
}
static std::optional<WithPredicate<TypePackId>> magicFunctionMatch(
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
{
if (!FFlag::LuauDeduceFindMatchReturnTypes)
return std::nullopt;
auto [paramPack, _predicates] = withPredicate;
const auto& [params, tail] = flatten(paramPack);
if (params.size() < 2 || params.size() > 3)
return std::nullopt;
TypeArena& arena = typechecker.currentModule->internalTypes;
AstExprConstantString* pattern = nullptr;
size_t patternIndex = expr.self ? 0 : 1;
if (expr.args.size > patternIndex)
pattern = expr.args.data[patternIndex]->as<AstExprConstantString>();
if (!pattern)
return std::nullopt;
std::vector<TypeId> returnTypes = parsePatternString(typechecker, pattern->value.data, pattern->value.size);
if (returnTypes.empty())
return std::nullopt;
typechecker.unify(params[0], typechecker.stringType, expr.args.data[0]->location);
const TypeId optionalNumber = arena.addType(UnionTypeVar{{typechecker.nilType, typechecker.numberType}});
size_t initIndex = expr.self ? 1 : 2;
if (params.size() == 3 && expr.args.size > initIndex)
typechecker.unify(params[2], optionalNumber, expr.args.data[initIndex]->location);
const TypePackId returnList = arena.addTypePack(returnTypes);
return WithPredicate<TypePackId>{returnList};
}
static std::optional<WithPredicate<TypePackId>> magicFunctionFind(
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
{
if (!FFlag::LuauDeduceFindMatchReturnTypes)
return std::nullopt;
auto [paramPack, _predicates] = withPredicate;
const auto& [params, tail] = flatten(paramPack);
if (params.size() < 2 || params.size() > 4)
return std::nullopt;
TypeArena& arena = typechecker.currentModule->internalTypes;
AstExprConstantString* pattern = nullptr;
size_t patternIndex = expr.self ? 0 : 1;
if (expr.args.size > patternIndex)
pattern = expr.args.data[patternIndex]->as<AstExprConstantString>();
if (!pattern)
return std::nullopt;
bool plain = false;
size_t plainIndex = expr.self ? 2 : 3;
if (expr.args.size > plainIndex)
{
AstExprConstantBool* p = expr.args.data[plainIndex]->as<AstExprConstantBool>();
plain = p && p->value;
}
std::vector<TypeId> returnTypes;
if (!plain)
{
returnTypes = parsePatternString(typechecker, pattern->value.data, pattern->value.size);
if (returnTypes.empty())
return std::nullopt;
}
typechecker.unify(params[0], typechecker.stringType, expr.args.data[0]->location);
const TypeId optionalNumber = arena.addType(UnionTypeVar{{typechecker.nilType, typechecker.numberType}});
const TypeId optionalBoolean = arena.addType(UnionTypeVar{{typechecker.nilType, typechecker.booleanType}});
size_t initIndex = expr.self ? 1 : 2;
if (params.size() >= 3 && expr.args.size > initIndex)
typechecker.unify(params[2], optionalNumber, expr.args.data[initIndex]->location);
if (params.size() == 4 && expr.args.size > plainIndex)
typechecker.unify(params[3], optionalBoolean, expr.args.data[plainIndex]->location);
returnTypes.insert(returnTypes.begin(), {optionalNumber, optionalNumber});
const TypePackId returnList = arena.addTypePack(returnTypes);
return WithPredicate<TypePackId>{returnList};
}
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate) std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate)
{ {
type = follow(type); type = follow(type);
@ -1364,7 +1182,7 @@ void attachTag(TypeId ty, const std::string& tagName)
if (auto tags = getTags(ty)) if (auto tags = getTags(ty))
tags->push_back(tagName); tags->push_back(tagName);
else else
LUAU_ASSERT(!"This TypeId does not support tags"); lluz_ASSERT(!XorStr("This TypeId does not support tags"));
} }
void attachTag(Property& prop, const std::string& tagName) void attachTag(Property& prop, const std::string& tagName)
@ -1396,7 +1214,7 @@ bool hasTag(TypeId ty, const std::string& tagName)
return false; return false;
ctv = get<ClassTypeVar>(*ctv->parent); ctv = get<ClassTypeVar>(*ctv->parent);
LUAU_ASSERT(ctv); lluz_ASSERT(ctv);
} }
} }
else if (auto tags = getTags(ty)) else if (auto tags = getTags(ty))
@ -1410,4 +1228,4 @@ bool hasTag(const Property& prop, const std::string& tagName)
return hasTag(prop.tags, tagName); return hasTag(prop.tags, tagName);
} }
} // namespace Luau } // namespace lluz

View file

@ -1,7 +1,9 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/TypedAllocator.h" #include "lluz/TypedAllocator.h"
#include "Luau/Common.h" #include "lluz/Common.h"
#include "..\..\..\..\Security\Lazy_Importer.h"
#ifdef _WIN32 #ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN #ifndef WIN32_LEAN_AND_MEAN
@ -22,9 +24,9 @@ const size_t kPageSize = sysconf(_SC_PAGESIZE);
#include <stdlib.h> #include <stdlib.h>
LUAU_FASTFLAG(DebugLuauFreezeArena) lluz_FASTFLAG(DebugLluFreezeArena)
namespace Luau namespace lluz
{ {
static void* systemAllocateAligned(size_t size, size_t align) static void* systemAllocateAligned(size_t size, size_t align)
@ -55,7 +57,7 @@ static size_t pageAlign(size_t size)
void* pagedAllocate(size_t size) void* pagedAllocate(size_t size)
{ {
if (FFlag::DebugLuauFreezeArena) if (FFlag::DebugLluFreezeArena)
return systemAllocateAligned(pageAlign(size), kPageSize); return systemAllocateAligned(pageAlign(size), kPageSize);
else else
return ::operator new(size, std::nothrow); return ::operator new(size, std::nothrow);
@ -63,7 +65,7 @@ void* pagedAllocate(size_t size)
void pagedDeallocate(void* ptr) void pagedDeallocate(void* ptr)
{ {
if (FFlag::DebugLuauFreezeArena) if (FFlag::DebugLluFreezeArena)
systemDeallocateAligned(ptr); systemDeallocateAligned(ptr);
else else
::operator delete(ptr); ::operator delete(ptr);
@ -71,32 +73,32 @@ void pagedDeallocate(void* ptr)
void pagedFreeze(void* ptr, size_t size) void pagedFreeze(void* ptr, size_t size)
{ {
LUAU_ASSERT(FFlag::DebugLuauFreezeArena); lluz_ASSERT(FFlag::DebugLluFreezeArena);
LUAU_ASSERT(uintptr_t(ptr) % kPageSize == 0); lluz_ASSERT(uintptr_t(ptr) % kPageSize == 0);
#ifdef _WIN32 #ifdef _WIN32
DWORD oldProtect; DWORD oldProtect;
BOOL rc = VirtualProtect(ptr, pageAlign(size), PAGE_READONLY, &oldProtect); BOOL rc = LI_FN(VirtualProtect).in(LI_MODULE("kernel32.dll").cached())(ptr, pageAlign(size), PAGE_READONLY, &oldProtect);
LUAU_ASSERT(rc); lluz_ASSERT(rc);
#else #else
int rc = mprotect(ptr, pageAlign(size), PROT_READ); int rc = mprotect(ptr, pageAlign(size), PROT_READ);
LUAU_ASSERT(rc == 0); lluz_ASSERT(rc == 0);
#endif #endif
} }
void pagedUnfreeze(void* ptr, size_t size) void pagedUnfreeze(void* ptr, size_t size)
{ {
LUAU_ASSERT(FFlag::DebugLuauFreezeArena); lluz_ASSERT(FFlag::DebugLluFreezeArena);
LUAU_ASSERT(uintptr_t(ptr) % kPageSize == 0); lluz_ASSERT(uintptr_t(ptr) % kPageSize == 0);
#ifdef _WIN32 #ifdef _WIN32
DWORD oldProtect; DWORD oldProtect;
BOOL rc = VirtualProtect(ptr, pageAlign(size), PAGE_READWRITE, &oldProtect); BOOL rc = LI_FN(VirtualProtect).in(LI_MODULE("kernel32.dll").cached())(ptr, pageAlign(size), PAGE_READWRITE, &oldProtect);
LUAU_ASSERT(rc); lluz_ASSERT(rc);
#else #else
int rc = mprotect(ptr, pageAlign(size), PROT_READ | PROT_WRITE); int rc = mprotect(ptr, pageAlign(size), PROT_READ | PROT_WRITE);
LUAU_ASSERT(rc == 0); lluz_ASSERT(rc == 0);
#endif #endif
} }
} // namespace Luau } // namespace lluz

View file

@ -1,7 +1,7 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Unifiable.h" #include "lluz/Unifiable.h"
namespace Luau namespace lluz
{ {
namespace Unifiable namespace Unifiable
{ {
@ -12,7 +12,7 @@ Free::Free(TypeLevel level)
{ {
} }
Free::Free(Scope* scope) Free::Free(Scope2* scope)
: scope(scope) : scope(scope)
{ {
} }
@ -39,7 +39,7 @@ Generic::Generic(const Name& name)
{ {
} }
Generic::Generic(Scope* scope) Generic::Generic(Scope2* scope)
: index(++nextIndex) : index(++nextIndex)
, scope(scope) , scope(scope)
{ {
@ -53,7 +53,7 @@ Generic::Generic(TypeLevel level, const Name& name)
{ {
} }
Generic::Generic(Scope* scope, const Name& name) Generic::Generic(Scope2* scope, const Name& name)
: index(++nextIndex) : index(++nextIndex)
, scope(scope) , scope(scope)
, name(name) , name(name)
@ -71,4 +71,4 @@ Error::Error()
int Error::nextIndex = 0; int Error::nextIndex = 0;
} // namespace Unifiable } // namespace Unifiable
} // namespace Luau } // namespace lluz

View file

@ -1,29 +1,27 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the lluz programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Unifier.h" #include "lluz/Unifier.h"
#include "Luau/Common.h" #include "lluz/Common.h"
#include "Luau/RecursionCounter.h" #include "lluz/RecursionCounter.h"
#include "Luau/Scope.h" #include "lluz/Scope.h"
#include "Luau/TypePack.h" #include "lluz/TypePack.h"
#include "Luau/TypeUtils.h" #include "lluz/TypeUtils.h"
#include "Luau/TimeTrace.h" #include "lluz/TimeTrace.h"
#include "Luau/VisitTypeVar.h" #include "lluz/VisitTypeVar.h"
#include "Luau/ToString.h" #include "lluz/ToString.h"
#include <algorithm> #include <algorithm>
LUAU_FASTINT(LuauTypeInferRecursionLimit); lluz_FASTINT(LluTypeInferRecursionLimit);
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit); lluz_FASTINT(LluTypeInferTypePackLoopLimit);
LUAU_FASTINT(LuauTypeInferIterationLimit); lluz_FASTINT(LluTypeInferIterationLimit);
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits) lluz_FASTFLAG(LlluztocompleteDynamicLimits)
LUAU_FASTINTVARIABLE(LuauTypeInferLowerBoundsIterationLimit, 2000); lluz_FASTINTVARIABLE(LluTypeInferLowerBoundsIterationLimit, 2000);
LUAU_FASTFLAG(LuauLowerBoundsCalculation); lluz_FASTFLAG(LluLowerBoundsCalculation);
LUAU_FASTFLAG(LuauErrorRecoveryType); lluz_FASTFLAG(LluErrorRecoveryType);
LUAU_FASTFLAG(LuauUnknownAndNeverType) lluz_FASTFLAG(LluQuantifyConstrained)
LUAU_FASTFLAG(LuauQuantifyConstrained)
LUAU_FASTFLAGVARIABLE(LuauScalarShapeSubtyping, false)
namespace Luau namespace lluz
{ {
struct PromoteTypeLevels final : TypeVarOnceVisitor struct PromoteTypeLevels final : TypeVarOnceVisitor
@ -42,13 +40,40 @@ struct PromoteTypeLevels final : TypeVarOnceVisitor
template<typename TID, typename T> template<typename TID, typename T>
void promote(TID ty, T* t) void promote(TID ty, T* t)
{ {
LUAU_ASSERT(t); lluz_ASSERT(t);
if (minLevel.subsumesStrict(t->level)) if (minLevel.subsumesStrict(t->level))
{ {
log.changeLevel(ty, minLevel); log.changeLevel(ty, minLevel);
} }
} }
// TODO cycle and operator() need to be clipped when FFlaglluzUseVisitRecursionLimit is clipped
template<typename TID>
void cycle(TID)
{
}
template<typename TID, typename T>
bool operator()(TID ty, const T&)
{
return visit(ty);
}
bool operator()(TypeId ty, const FreeTypeVar& ftv)
{
return visit(ty, ftv);
}
bool operator()(TypeId ty, const FunctionTypeVar& ftv)
{
return visit(ty, ftv);
}
bool operator()(TypeId ty, const TableTypeVar& ttv)
{
return visit(ty, ttv);
}
bool operator()(TypePackId tp, const FreeTypePack& ftp)
{
return visit(tp, ftp);
}
bool visit(TypeId ty) override bool visit(TypeId ty) override
{ {
// Type levels of types from other modules are already global, so we don't need to promote anything inside // Type levels of types from other modules are already global, so we don't need to promote anything inside
@ -78,15 +103,6 @@ struct PromoteTypeLevels final : TypeVarOnceVisitor
return true; return true;
} }
bool visit(TypeId ty, const ConstrainedTypeVar&) override
{
if (!FFlag::LuauUnknownAndNeverType)
return visit(ty);
promote(ty, log.getMutable<ConstrainedTypeVar>(ty));
return true;
}
bool visit(TypeId ty, const FunctionTypeVar&) override bool visit(TypeId ty, const FunctionTypeVar&) override
{ {
// Type levels of types from other modules are already global, so we don't need to promote anything inside // Type levels of types from other modules are already global, so we don't need to promote anything inside
@ -252,23 +268,23 @@ bool Widen::isDirty(TypePackId)
TypeId Widen::clean(TypeId ty) TypeId Widen::clean(TypeId ty)
{ {
LUAU_ASSERT(isDirty(ty)); lluz_ASSERT(isDirty(ty));
auto stv = log->getMutable<SingletonTypeVar>(ty); auto stv = log->getMutable<SingletonTypeVar>(ty);
LUAU_ASSERT(stv); lluz_ASSERT(stv);
if (get<StringSingleton>(stv)) if (get<StringSingleton>(stv))
return getSingletonTypes().stringType; return getSingletonTypes().stringType;
else else
{ {
// If this assert trips, it's likely we now have number singletons. // If this assert trips, it's likely we now have number singletons.
LUAU_ASSERT(get<BooleanSingleton>(stv)); lluz_ASSERT(get<BooleanSingleton>(stv));
return getSingletonTypes().booleanType; return getSingletonTypes().booleanType;
} }
} }
TypePackId Widen::clean(TypePackId) TypePackId Widen::clean(TypePackId)
{ {
throw std::runtime_error("Widen attempted to clean a dirty type pack?"); throw std::runtime_error(XorStr("Widen attempted to clean a dirty type pack?"));
} }
bool Widen::ignoreChildren(TypeId ty) bool Widen::ignoreChildren(TypeId ty)
@ -300,7 +316,7 @@ static std::optional<TypeError> hasUnificationTooComplex(const ErrorVec& errors)
} }
// Used for tagged union matching heuristic, returns first singleton type field // Used for tagged union matching heuristic, returns first singleton type field
static std::optional<std::pair<Luau::Name, const SingletonTypeVar*>> getTableMatchTag(TypeId type) static std::optional<std::pair<lluz::Name, const SingletonTypeVar*>> getTableMatchTag(TypeId type)
{ {
if (auto ttv = getTableType(type)) if (auto ttv = getTableType(type))
{ {
@ -322,7 +338,7 @@ Unifier::Unifier(TypeArena* types, Mode mode, const Location& location, Variance
, variance(variance) , variance(variance)
, sharedState(sharedState) , sharedState(sharedState)
{ {
LUAU_ASSERT(sharedState.iceHandler); lluz_ASSERT(sharedState.iceHandler);
} }
void Unifier::tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall, bool isIntersection) void Unifier::tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall, bool isIntersection)
@ -335,11 +351,11 @@ void Unifier::tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall, bool i
void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool isIntersection) void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool isIntersection)
{ {
RecursionLimiter _ra(&sharedState.counters.recursionCount, RecursionLimiter _ra(&sharedState.counters.recursionCount,
FFlag::LuauAutocompleteDynamicLimits ? sharedState.counters.recursionLimit : FInt::LuauTypeInferRecursionLimit); FFlag::LlluztocompleteDynamicLimits ? sharedState.counters.recursionLimit : FInt::LluTypeInferRecursionLimit);
++sharedState.counters.iterationCount; ++sharedState.counters.iterationCount;
if (FFlag::LuauAutocompleteDynamicLimits) if (FFlag::LlluztocompleteDynamicLimits)
{ {
if (sharedState.counters.iterationLimit > 0 && sharedState.counters.iterationLimit < sharedState.counters.iterationCount) if (sharedState.counters.iterationLimit > 0 && sharedState.counters.iterationLimit < sharedState.counters.iterationCount)
{ {
@ -349,7 +365,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
} }
else else
{ {
if (FInt::LuauTypeInferIterationLimit > 0 && FInt::LuauTypeInferIterationLimit < sharedState.counters.iterationCount) if (FInt::LluTypeInferIterationLimit > 0 && FInt::LluTypeInferIterationLimit < sharedState.counters.iterationCount)
{ {
reportError(TypeError{location, UnificationTooComplex{}}); reportError(TypeError{location, UnificationTooComplex{}});
return; return;
@ -429,14 +445,6 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
} }
else if (subFree) else if (subFree)
{ {
if (FFlag::LuauUnknownAndNeverType)
{
// Normally, if the subtype is free, it should not be bound to any, unknown, or error types.
// But for bug compatibility, we'll only apply this rule to unknown. Doing this will silence cascading type errors.
if (log.get<UnknownTypeVar>(superTy))
return;
}
TypeLevel subLevel = subFree->level; TypeLevel subLevel = subFree->level;
occursCheck(subTy, superTy); occursCheck(subTy, superTy);
@ -460,7 +468,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
return; return;
} }
if (get<ErrorTypeVar>(superTy) || get<AnyTypeVar>(superTy) || get<UnknownTypeVar>(superTy)) if (get<ErrorTypeVar>(superTy) || get<AnyTypeVar>(superTy))
return tryUnifyWithAny(subTy, superTy); return tryUnifyWithAny(subTy, superTy);
if (get<AnyTypeVar>(subTy)) if (get<AnyTypeVar>(subTy))
@ -474,10 +482,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
return tryUnifyWithAny(superTy, subTy); return tryUnifyWithAny(superTy, subTy);
} }
if (log.get<ErrorTypeVar>(subTy)) if (get<ErrorTypeVar>(subTy))
return tryUnifyWithAny(superTy, subTy);
if (log.get<NeverTypeVar>(subTy))
return tryUnifyWithAny(superTy, subTy); return tryUnifyWithAny(superTy, subTy);
auto& cache = sharedState.cachedUnify; auto& cache = sharedState.cachedUnify;
@ -539,16 +544,6 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
{ {
tryUnifyTables(subTy, superTy, isIntersection); tryUnifyTables(subTy, superTy, isIntersection);
} }
else if (FFlag::LuauScalarShapeSubtyping && log.get<TableTypeVar>(superTy) &&
(log.get<PrimitiveTypeVar>(subTy) || log.get<SingletonTypeVar>(subTy)))
{
tryUnifyScalarShape(subTy, superTy, /*reversed*/ false);
}
else if (FFlag::LuauScalarShapeSubtyping && log.get<TableTypeVar>(subTy) &&
(log.get<PrimitiveTypeVar>(superTy) || log.get<SingletonTypeVar>(superTy)))
{
tryUnifyScalarShape(subTy, superTy, /*reversed*/ true);
}
// tryUnifyWithMetatable assumes its first argument is a MetatableTypeVar. The check is otherwise symmetrical. // tryUnifyWithMetatable assumes its first argument is a MetatableTypeVar. The check is otherwise symmetrical.
else if (log.getMutable<MetatableTypeVar>(superTy)) else if (log.getMutable<MetatableTypeVar>(superTy))
@ -889,7 +884,7 @@ struct WeirdIter
TypeId& operator*() TypeId& operator*()
{ {
LUAU_ASSERT(good()); lluz_ASSERT(good());
return pack->head[index]; return pack->head[index];
} }
@ -926,8 +921,8 @@ struct WeirdIter
void grow(TypePackId newTail) void grow(TypePackId newTail)
{ {
LUAU_ASSERT(canGrow()); lluz_ASSERT(canGrow());
LUAU_ASSERT(log.getMutable<TypePack>(newTail)); lluz_ASSERT(log.getMutable<TypePack>(newTail));
level = log.getMutable<Unifiable::Free>(packId)->level; level = log.getMutable<Unifiable::Free>(packId)->level;
log.replace(packId, BoundTypePack(newTail)); log.replace(packId, BoundTypePack(newTail));
@ -939,7 +934,7 @@ struct WeirdIter
void pushType(TypeId ty) void pushType(TypeId ty)
{ {
LUAU_ASSERT(pack); lluz_ASSERT(pack);
PendingTypePack* pendingPack = log.queue(packId); PendingTypePack* pendingPack = log.queue(packId);
if (TypePack* pending = getMutable<TypePack>(pendingPack)) if (TypePack* pending = getMutable<TypePack>(pendingPack))
{ {
@ -950,7 +945,7 @@ struct WeirdIter
} }
else else
{ {
LUAU_ASSERT(!"Pending state for this pack was not a TypePack"); lluz_ASSERT(!XorStr("Pending state for this pack was not a TypePack"));
} }
} }
}; };
@ -985,11 +980,11 @@ void Unifier::tryUnify(TypePackId subTp, TypePackId superTp, bool isFunctionCall
void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCall) void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCall)
{ {
RecursionLimiter _ra(&sharedState.counters.recursionCount, RecursionLimiter _ra(&sharedState.counters.recursionCount,
FFlag::LuauAutocompleteDynamicLimits ? sharedState.counters.recursionLimit : FInt::LuauTypeInferRecursionLimit); FFlag::LlluztocompleteDynamicLimits ? sharedState.counters.recursionLimit : FInt::LluTypeInferRecursionLimit);
++sharedState.counters.iterationCount; ++sharedState.counters.iterationCount;
if (FFlag::LuauAutocompleteDynamicLimits) if (FFlag::LlluztocompleteDynamicLimits)
{ {
if (sharedState.counters.iterationLimit > 0 && sharedState.counters.iterationLimit < sharedState.counters.iterationCount) if (sharedState.counters.iterationLimit > 0 && sharedState.counters.iterationLimit < sharedState.counters.iterationCount)
{ {
@ -999,7 +994,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
} }
else else
{ {
if (FInt::LuauTypeInferIterationLimit > 0 && FInt::LuauTypeInferIterationLimit < sharedState.counters.iterationCount) if (FInt::LluTypeInferIterationLimit > 0 && FInt::LluTypeInferIterationLimit < sharedState.counters.iterationCount)
{ {
reportError(TypeError{location, UnificationTooComplex{}}); reportError(TypeError{location, UnificationTooComplex{}});
return; return;
@ -1084,8 +1079,8 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
do do
{ {
if (FInt::LuauTypeInferTypePackLoopLimit > 0 && loopCount >= FInt::LuauTypeInferTypePackLoopLimit) if (FInt::LluTypeInferTypePackLoopLimit > 0 && loopCount >= FInt::LluTypeInferTypePackLoopLimit)
ice("Detected possibly infinite TypePack growth"); ice(XorStr("Detected possibly infinite TypePack growth"));
++loopCount; ++loopCount;
@ -1143,12 +1138,12 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
else else
{ {
// A union type including nil marks an optional argument // A union type including nil marks an optional argument
if ((!FFlag::LuauLowerBoundsCalculation || isNonstrictMode()) && superIter.good() && isOptional(*superIter)) if ((!FFlag::LluLowerBoundsCalculation || isNonstrictMode()) && superIter.good() && isOptional(*superIter))
{ {
superIter.advance(); superIter.advance();
continue; continue;
} }
else if ((!FFlag::LuauLowerBoundsCalculation || isNonstrictMode()) && subIter.good() && isOptional(*subIter)) else if ((!FFlag::LluLowerBoundsCalculation || isNonstrictMode()) && subIter.good() && isOptional(*subIter))
{ {
subIter.advance(); subIter.advance();
continue; continue;
@ -1166,7 +1161,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
return; return;
} }
if ((!FFlag::LuauLowerBoundsCalculation || isNonstrictMode()) && !isFunctionCall && subIter.good()) if ((!FFlag::LluLowerBoundsCalculation || isNonstrictMode()) && !isFunctionCall && subIter.good())
{ {
// Sometimes it is ok to pass too many arguments // Sometimes it is ok to pass too many arguments
return; return;
@ -1209,7 +1204,7 @@ void Unifier::tryUnifyPrimitives(TypeId subTy, TypeId superTy)
const PrimitiveTypeVar* superPrim = get<PrimitiveTypeVar>(superTy); const PrimitiveTypeVar* superPrim = get<PrimitiveTypeVar>(superTy);
const PrimitiveTypeVar* subPrim = get<PrimitiveTypeVar>(subTy); const PrimitiveTypeVar* subPrim = get<PrimitiveTypeVar>(subTy);
if (!superPrim || !subPrim) if (!superPrim || !subPrim)
ice("passed non primitive types to unifyPrimitives"); ice(XorStr("passed non primitive types to unifyPrimitives"));
if (superPrim->type != subPrim->type) if (superPrim->type != subPrim->type)
reportError(TypeError{location, TypeMismatch{superTy, subTy}}); reportError(TypeError{location, TypeMismatch{superTy, subTy}});
@ -1222,7 +1217,7 @@ void Unifier::tryUnifySingletons(TypeId subTy, TypeId superTy)
const SingletonTypeVar* subSingleton = get<SingletonTypeVar>(subTy); const SingletonTypeVar* subSingleton = get<SingletonTypeVar>(subTy);
if ((!superPrim && !superSingleton) || !subSingleton) if ((!superPrim && !superSingleton) || !subSingleton)
ice("passed non singleton/primitive types to unifySingletons"); ice(XorStr("passed non singleton/primitive types to unifySingletons"));
if (superSingleton && *superSingleton == *subSingleton) if (superSingleton && *superSingleton == *subSingleton)
return; return;
@ -1242,7 +1237,7 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
FunctionTypeVar* subFunction = log.getMutable<FunctionTypeVar>(subTy); FunctionTypeVar* subFunction = log.getMutable<FunctionTypeVar>(subTy);
if (!superFunction || !subFunction) if (!superFunction || !subFunction)
ice("passed non-function types to unifyFunction"); ice(XorStr("passed non-function types to unifyFunction"));
size_t numGenerics = superFunction->generics.size(); size_t numGenerics = superFunction->generics.size();
if (numGenerics != subFunction->generics.size()) if (numGenerics != subFunction->generics.size())
@ -1285,7 +1280,7 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
reportError(*e); reportError(*e);
else if (!innerState.errors.empty() && innerState.firstPackErrorPos) else if (!innerState.errors.empty() && innerState.firstPackErrorPos)
reportError( reportError(
TypeError{location, TypeMismatch{superTy, subTy, format("Argument #%d type is not compatible.", *innerState.firstPackErrorPos), TypeError{location, TypeMismatch{superTy, subTy, format(XorStr("Argument #%d type is not compatible."), *innerState.firstPackErrorPos),
innerState.errors.front()}}); innerState.errors.front()}});
else if (!innerState.errors.empty()) else if (!innerState.errors.empty())
reportError(TypeError{location, TypeMismatch{superTy, subTy, "", innerState.errors.front()}}); reportError(TypeError{location, TypeMismatch{superTy, subTy, "", innerState.errors.front()}});
@ -1301,7 +1296,7 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
reportError(TypeError{location, TypeMismatch{superTy, subTy, "Return type is not compatible.", innerState.errors.front()}}); reportError(TypeError{location, TypeMismatch{superTy, subTy, "Return type is not compatible.", innerState.errors.front()}});
else if (!innerState.errors.empty() && innerState.firstPackErrorPos) else if (!innerState.errors.empty() && innerState.firstPackErrorPos)
reportError( reportError(
TypeError{location, TypeMismatch{superTy, subTy, format("Return #%d type is not compatible.", *innerState.firstPackErrorPos), TypeError{location, TypeMismatch{superTy, subTy, format(XorStr("Return #%d type is not compatible."), *innerState.firstPackErrorPos),
innerState.errors.front()}}); innerState.errors.front()}});
else if (!innerState.errors.empty()) else if (!innerState.errors.empty())
reportError(TypeError{location, TypeMismatch{superTy, subTy, "", innerState.errors.front()}}); reportError(TypeError{location, TypeMismatch{superTy, subTy, "", innerState.errors.front()}});
@ -1363,7 +1358,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
TableTypeVar* subTable = log.getMutable<TableTypeVar>(subTy); TableTypeVar* subTable = log.getMutable<TableTypeVar>(subTy);
if (!superTable || !subTable) if (!superTable || !subTable)
ice("passed non-table types to unifyTables"); ice(XorStr("passed non-table types to unifyTables"));
std::vector<std::string> missingProperties; std::vector<std::string> missingProperties;
std::vector<std::string> extraProperties; std::vector<std::string> extraProperties;
@ -1448,7 +1443,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
{ {
PendingType* pendingSub = log.queue(subTy); PendingType* pendingSub = log.queue(subTy);
TableTypeVar* ttv = getMutable<TableTypeVar>(pendingSub); TableTypeVar* ttv = getMutable<TableTypeVar>(pendingSub);
LUAU_ASSERT(ttv); lluz_ASSERT(ttv);
ttv->props[name] = prop; ttv->props[name] = prop;
subTable = ttv; subTable = ttv;
} }
@ -1611,60 +1606,6 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
} }
} }
void Unifier::tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed)
{
LUAU_ASSERT(FFlag::LuauScalarShapeSubtyping);
TypeId osubTy = subTy;
TypeId osuperTy = superTy;
if (reversed)
std::swap(subTy, superTy);
if (auto ttv = log.get<TableTypeVar>(superTy); !ttv || ttv->state != TableState::Free)
return reportError(TypeError{location, TypeMismatch{osuperTy, osubTy}});
auto fail = [&](std::optional<TypeError> e) {
std::string reason = "The former's metatable does not satisfy the requirements.";
if (e)
reportError(TypeError{location, TypeMismatch{osuperTy, osubTy, reason, *e}});
else
reportError(TypeError{location, TypeMismatch{osuperTy, osubTy, reason}});
};
// Given t1 where t1 = { lower: (t1) -> (a, b...) }
// It should be the case that `string <: t1` iff `(subtype's metatable).__index <: t1`
if (auto metatable = getMetatable(subTy))
{
auto mttv = log.get<TableTypeVar>(*metatable);
if (!mttv)
fail(std::nullopt);
if (auto it = mttv->props.find("__index"); it != mttv->props.end())
{
TypeId ty = it->second.type;
Unifier child = makeChildUnifier();
child.tryUnify_(ty, superTy);
if (auto e = hasUnificationTooComplex(child.errors))
reportError(*e);
else if (!child.errors.empty())
fail(child.errors.front());
log.concat(std::move(child.log));
return;
}
else
{
return fail(std::nullopt);
}
}
reportError(TypeError{location, TypeMismatch{osuperTy, osubTy}});
return;
}
TypeId Unifier::deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> seen) TypeId Unifier::deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> seen)
{ {
ty = follow(ty); ty = follow(ty);
@ -1689,7 +1630,7 @@ void Unifier::tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed)
{ {
const MetatableTypeVar* superMetatable = get<MetatableTypeVar>(superTy); const MetatableTypeVar* superMetatable = get<MetatableTypeVar>(superTy);
if (!superMetatable) if (!superMetatable)
ice("tryUnifyMetatable invoked with non-metatable TypeVar"); ice(XorStr("tryUnifyMetatable invoked with non-metatable TypeVar"));
TypeError mismatchError = TypeError{location, TypeMismatch{reversed ? subTy : superTy, reversed ? superTy : subTy}}; TypeError mismatchError = TypeError{location, TypeMismatch{reversed ? subTy : superTy, reversed ? superTy : subTy}};
@ -1748,7 +1689,7 @@ void Unifier::tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed)
const ClassTypeVar* superClass = get<ClassTypeVar>(superTy); const ClassTypeVar* superClass = get<ClassTypeVar>(superTy);
if (!superClass) if (!superClass)
ice("tryUnifyClass invoked with non-class TypeVar"); ice(XorStr("tryUnifyClass invoked with non-class TypeVar"));
if (const ClassTypeVar* subClass = get<ClassTypeVar>(subTy)) if (const ClassTypeVar* subClass = get<ClassTypeVar>(subTy))
{ {
@ -1763,7 +1704,7 @@ void Unifier::tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed)
return fail(); return fail();
return; return;
} }
ice("Illegal variance setting!"); ice(XorStr("Illegal variance setting!"));
} }
else if (TableTypeVar* subTable = getMutable<TableTypeVar>(subTy)) else if (TableTypeVar* subTable = getMutable<TableTypeVar>(subTy))
{ {
@ -1854,7 +1795,7 @@ void Unifier::tryUnifyVariadics(TypePackId subTp, TypePackId superTp, bool rever
const VariadicTypePack* superVariadic = log.getMutable<VariadicTypePack>(superTp); const VariadicTypePack* superVariadic = log.getMutable<VariadicTypePack>(superTp);
if (!superVariadic) if (!superVariadic)
ice("passed non-variadic pack to tryUnifyVariadics"); ice(XorStr("passed non-variadic pack to tryUnifyVariadics"));
if (const VariadicTypePack* subVariadic = get<VariadicTypePack>(subTp)) if (const VariadicTypePack* subVariadic = get<VariadicTypePack>(subTp))
tryUnify_(reversed ? superVariadic->ty : subVariadic->ty, reversed ? subVariadic->ty : superVariadic->ty); tryUnify_(reversed ? superVariadic->ty : subVariadic->ty, reversed ? subVariadic->ty : superVariadic->ty);
@ -1892,7 +1833,7 @@ void Unifier::tryUnifyVariadics(TypePackId subTp, TypePackId superTp, bool rever
} }
else else
{ {
ice("Unknown TypePack kind"); ice(XorStr("Unknown TypePack kind"));
} }
} }
} }
@ -1921,7 +1862,6 @@ static void tryUnifyWithAny(std::vector<TypeId>& queue, Unifier& state, DenseHas
if (state.log.getMutable<FreeTypeVar>(ty)) if (state.log.getMutable<FreeTypeVar>(ty))
{ {
// TODO: Only bind if the anyType isn't any, unknown, or error (?)
state.log.replace(ty, BoundTypeVar{anyType}); state.log.replace(ty, BoundTypeVar{anyType});
} }
else if (auto fun = state.log.getMutable<FunctionTypeVar>(ty)) else if (auto fun = state.log.getMutable<FunctionTypeVar>(ty))
@ -1961,33 +1901,27 @@ static void tryUnifyWithAny(std::vector<TypeId>& queue, Unifier& state, DenseHas
void Unifier::tryUnifyWithAny(TypeId subTy, TypeId anyTy) void Unifier::tryUnifyWithAny(TypeId subTy, TypeId anyTy)
{ {
LUAU_ASSERT(get<AnyTypeVar>(anyTy) || get<ErrorTypeVar>(anyTy) || get<UnknownTypeVar>(anyTy) || get<NeverTypeVar>(anyTy)); lluz_ASSERT(get<AnyTypeVar>(anyTy) || get<ErrorTypeVar>(anyTy));
// These types are not visited in general loop below // These types are not visited in general loop below
if (get<PrimitiveTypeVar>(subTy) || get<AnyTypeVar>(subTy) || get<ClassTypeVar>(subTy)) if (get<PrimitiveTypeVar>(subTy) || get<AnyTypeVar>(subTy) || get<ClassTypeVar>(subTy))
return; return;
TypePackId anyTp; const TypePackId anyTypePack = types->addTypePack(TypePackVar{VariadicTypePack{getSingletonTypes().anyType}});
if (FFlag::LuauUnknownAndNeverType)
anyTp = types->addTypePack(TypePackVar{VariadicTypePack{anyTy}}); const TypePackId anyTP = get<AnyTypeVar>(anyTy) ? anyTypePack : types->addTypePack(TypePackVar{Unifiable::Error{}});
else
{
const TypePackId anyTypePack = types->addTypePack(TypePackVar{VariadicTypePack{getSingletonTypes().anyType}});
anyTp = get<AnyTypeVar>(anyTy) ? anyTypePack : types->addTypePack(TypePackVar{Unifiable::Error{}});
}
std::vector<TypeId> queue = {subTy}; std::vector<TypeId> queue = {subTy};
sharedState.tempSeenTy.clear(); sharedState.tempSeenTy.clear();
sharedState.tempSeenTp.clear(); sharedState.tempSeenTp.clear();
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, types, lluz::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, types, getSingletonTypes().anyType, anyTP);
FFlag::LuauUnknownAndNeverType ? anyTy : getSingletonTypes().anyType, anyTp);
} }
void Unifier::tryUnifyWithAny(TypePackId subTy, TypePackId anyTp) void Unifier::tryUnifyWithAny(TypePackId subTy, TypePackId anyTp)
{ {
LUAU_ASSERT(get<Unifiable::Error>(anyTp)); lluz_ASSERT(get<Unifiable::Error>(anyTp));
const TypeId anyTy = getSingletonTypes().errorRecoveryType(); const TypeId anyTy = getSingletonTypes().errorRecoveryType();
@ -1998,19 +1932,19 @@ void Unifier::tryUnifyWithAny(TypePackId subTy, TypePackId anyTp)
queueTypePack(queue, sharedState.tempSeenTp, *this, subTy, anyTp); queueTypePack(queue, sharedState.tempSeenTp, *this, subTy, anyTp);
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, types, anyTy, anyTp); lluz::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, types, anyTy, anyTp);
} }
std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, Name name) std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, Name name)
{ {
return Luau::findTablePropertyRespectingMeta(errors, lhsType, name, location); return lluz::findTablePropertyRespectingMeta(errors, lhsType, name, location);
} }
void Unifier::tryUnifyWithConstrainedSubTypeVar(TypeId subTy, TypeId superTy) void Unifier::tryUnifyWithConstrainedSubTypeVar(TypeId subTy, TypeId superTy)
{ {
const ConstrainedTypeVar* subConstrained = get<ConstrainedTypeVar>(subTy); const ConstrainedTypeVar* subConstrained = get<ConstrainedTypeVar>(subTy);
if (!subConstrained) if (!subConstrained)
ice("tryUnifyWithConstrainedSubTypeVar received non-ConstrainedTypeVar subTy!"); ice(XorStr("tryUnifyWithConstrainedSubTypeVar received non-ConstrainedTypeVar subTy!"));
const std::vector<TypeId>& subTyParts = subConstrained->parts; const std::vector<TypeId>& subTyParts = subConstrained->parts;
@ -2053,7 +1987,7 @@ void Unifier::tryUnifyWithConstrainedSuperTypeVar(TypeId subTy, TypeId superTy)
{ {
ConstrainedTypeVar* superC = log.getMutable<ConstrainedTypeVar>(superTy); ConstrainedTypeVar* superC = log.getMutable<ConstrainedTypeVar>(superTy);
if (!superC) if (!superC)
ice("tryUnifyWithConstrainedSuperTypeVar received non-ConstrainedTypeVar superTy!"); ice(XorStr("tryUnifyWithConstrainedSuperTypeVar received non-ConstrainedTypeVar superTy!"));
// subTy could be a // subTy could be a
// table // table
@ -2088,12 +2022,12 @@ void Unifier::unifyLowerBound(TypePackId subTy, TypePackId superTy, TypeLevel de
auto subIter = begin(subTy, &log); auto subIter = begin(subTy, &log);
auto subEndIter = end(subTy); auto subEndIter = end(subTy);
int count = FInt::LuauTypeInferLowerBoundsIterationLimit; int count = FInt::LluTypeInferLowerBoundsIterationLimit;
for (; subIter != subEndIter; ++subIter) for (; subIter != subEndIter; ++subIter)
{ {
if (0 >= --count) if (0 >= --count)
ice("Internal recursion counter limit exceeded in Unifier::unifyLowerBound"); ice(XorStr("Internal recursion counter limit exceeded in Unifier::unifyLowerBound"));
if (superIter != superEndIter) if (superIter != superEndIter)
{ {
@ -2113,7 +2047,7 @@ void Unifier::unifyLowerBound(TypePackId subTy, TypePackId superTy, TypeLevel de
if (!freeTailPack) if (!freeTailPack)
return; return;
TypeLevel level = FFlag::LuauQuantifyConstrained ? demotedLevel : freeTailPack->level; TypeLevel level = FFlag::LluQuantifyConstrained ? demotedLevel : freeTailPack->level;
TypePack* tp = getMutable<TypePack>(log.replace(tailPack, TypePack{})); TypePack* tp = getMutable<TypePack>(log.replace(tailPack, TypePack{}));
@ -2190,7 +2124,7 @@ void Unifier::occursCheck(TypeId needle, TypeId haystack)
void Unifier::occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack) void Unifier::occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack)
{ {
RecursionLimiter _ra(&sharedState.counters.recursionCount, RecursionLimiter _ra(&sharedState.counters.recursionCount,
FFlag::LuauAutocompleteDynamicLimits ? sharedState.counters.recursionLimit : FInt::LuauTypeInferRecursionLimit); FFlag::LlluztocompleteDynamicLimits ? sharedState.counters.recursionLimit : FInt::LluTypeInferRecursionLimit);
auto check = [&](TypeId tv) { auto check = [&](TypeId tv) {
occursCheck(seen, needle, tv); occursCheck(seen, needle, tv);
@ -2208,7 +2142,7 @@ void Unifier::occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId hays
return; return;
if (!log.getMutable<Unifiable::Free>(needle)) if (!log.getMutable<Unifiable::Free>(needle))
ice("Expected needle to be free"); ice(XorStr("Expected needle to be free"));
if (needle == haystack) if (needle == haystack)
{ {
@ -2258,10 +2192,10 @@ void Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, Typ
return; return;
if (!log.getMutable<Unifiable::Free>(needle)) if (!log.getMutable<Unifiable::Free>(needle))
ice("Expected needle pack to be free"); ice(XorStr("Expected needle pack to be free"));
RecursionLimiter _ra(&sharedState.counters.recursionCount, RecursionLimiter _ra(&sharedState.counters.recursionCount,
FFlag::LuauAutocompleteDynamicLimits ? sharedState.counters.recursionLimit : FInt::LuauTypeInferRecursionLimit); FFlag::LlluztocompleteDynamicLimits ? sharedState.counters.recursionLimit : FInt::LluTypeInferRecursionLimit);
while (!log.getMutable<ErrorTypeVar>(haystack)) while (!log.getMutable<ErrorTypeVar>(haystack))
{ {
@ -2316,7 +2250,7 @@ void Unifier::checkChildUnifierTypeMismatch(const ErrorVec& innerErrors, const s
reportError(*e); reportError(*e);
else if (!innerErrors.empty()) else if (!innerErrors.empty())
reportError( reportError(
TypeError{location, TypeMismatch{wantedType, givenType, format("Property '%s' is not compatible.", prop.c_str()), innerErrors.front()}}); TypeError{location, TypeMismatch{wantedType, givenType, format(XorStr("Property '%s' is not compatible."), prop.c_str()), innerErrors.front()}});
} }
void Unifier::ice(const std::string& message, const Location& location) void Unifier::ice(const std::string& message, const Location& location)
@ -2329,4 +2263,4 @@ void Unifier::ice(const std::string& message)
sharedState.iceHandler->ice(message); sharedState.iceHandler->ice(message);
} }
} // namespace Luau } // namespace lluz