mirror of
https://github.com/luau-lang/luau.git
synced 2025-05-04 10:33:46 +01:00
Merge branch 'master' of https://github.com/Roblox/luau into to-string-tables
This commit is contained in:
commit
95bc87cc0f
122 changed files with 6899 additions and 3250 deletions
|
@ -1,6 +1,7 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
@ -18,11 +19,12 @@ struct CloneState
|
|||
SeenTypePacks seenTypePacks;
|
||||
|
||||
int recursionCount = 0;
|
||||
bool encounteredFreeType = false; // TODO: Remove with LuauLosslessClone.
|
||||
};
|
||||
|
||||
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 Luau
|
||||
|
|
162
Analysis/include/Luau/ConstraintGraphBuilder.h
Normal file
162
Analysis/include/Luau/ConstraintGraphBuilder.h
Normal file
|
@ -0,0 +1,162 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/Symbol.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct Scope2;
|
||||
|
||||
// subType <: superType
|
||||
struct SubtypeConstraint
|
||||
{
|
||||
TypeId subType;
|
||||
TypeId superType;
|
||||
};
|
||||
|
||||
// subPack <: superPack
|
||||
struct PackSubtypeConstraint
|
||||
{
|
||||
TypePackId subPack;
|
||||
TypePackId superPack;
|
||||
};
|
||||
|
||||
// subType ~ gen superType
|
||||
struct GeneralizationConstraint
|
||||
{
|
||||
TypeId subType;
|
||||
TypeId superType;
|
||||
Scope2* scope;
|
||||
};
|
||||
|
||||
// subType ~ inst superType
|
||||
struct InstantiationConstraint
|
||||
{
|
||||
TypeId subType;
|
||||
TypeId superType;
|
||||
};
|
||||
|
||||
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint>;
|
||||
using ConstraintPtr = std::unique_ptr<struct Constraint>;
|
||||
|
||||
struct Constraint
|
||||
{
|
||||
Constraint(ConstraintV&& c);
|
||||
Constraint(ConstraintV&& c, std::vector<Constraint*> dependencies);
|
||||
|
||||
Constraint(const Constraint&) = delete;
|
||||
Constraint& operator=(const Constraint&) = delete;
|
||||
|
||||
ConstraintV c;
|
||||
std::vector<Constraint*> dependencies;
|
||||
};
|
||||
|
||||
inline Constraint& asMutable(const Constraint& c)
|
||||
{
|
||||
return const_cast<Constraint&>(c);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* getMutable(Constraint& c)
|
||||
{
|
||||
return ::Luau::get_if<T>(&c.c);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const T* get(const Constraint& c)
|
||||
{
|
||||
return getMutable<T>(asMutable(c));
|
||||
}
|
||||
|
||||
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<Scope2*> children;
|
||||
std::unordered_map<Symbol, TypeId> bindings; // TODO: I think this can be a DenseHashMap
|
||||
TypePackId returnType;
|
||||
// All constraints belonging to this scope.
|
||||
std::vector<ConstraintPtr> constraints;
|
||||
|
||||
std::optional<TypeId> lookup(Symbol sym);
|
||||
};
|
||||
|
||||
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;
|
||||
SingletonTypes& singletonTypes;
|
||||
TypeArena* const arena;
|
||||
// The root scope of the module we're generating constraints for.
|
||||
Scope2* rootScope;
|
||||
|
||||
explicit ConstraintGraphBuilder(TypeArena* arena);
|
||||
|
||||
/**
|
||||
* Fabricates a new free type belonging to a given scope.
|
||||
* @param scope the scope the free type belongs to. Must not be null.
|
||||
*/
|
||||
TypeId freshType(Scope2* scope);
|
||||
|
||||
/**
|
||||
* Fabricates a new free type pack belonging to a given scope.
|
||||
* @param scope the scope the free type pack belongs to. Must not be null.
|
||||
*/
|
||||
TypePackId freshTypePack(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.
|
||||
*/
|
||||
Scope2* childScope(Location location, Scope2* parent);
|
||||
|
||||
/**
|
||||
* Adds a new constraint with no dependencies to a given scope.
|
||||
* @param scope the scope to add the constraint to. Must not be null.
|
||||
* @param cv the constraint variant to add.
|
||||
*/
|
||||
void addConstraint(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(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 visit(Scope2* scope, AstStat* stat);
|
||||
void visit(Scope2* scope, AstStatBlock* block);
|
||||
void visit(Scope2* scope, AstStatLocal* local);
|
||||
void visit(Scope2* scope, AstStatLocalFunction* local);
|
||||
void visit(Scope2* scope, AstStatReturn* local);
|
||||
|
||||
TypePackId checkPack(Scope2* scope, AstArray<AstExpr*> exprs);
|
||||
TypePackId checkPack(Scope2* scope, AstExpr* expr);
|
||||
|
||||
TypeId check(Scope2* scope, AstExpr* expr);
|
||||
};
|
||||
|
||||
std::vector<const Constraint*> collectConstraints(Scope2* rootScope);
|
||||
|
||||
} // namespace Luau
|
106
Analysis/include/Luau/ConstraintSolver.h
Normal file
106
Analysis/include/Luau/ConstraintSolver.h
Normal file
|
@ -0,0 +1,106 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Variant.h"
|
||||
#include "Luau/ConstraintGraphBuilder.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
// 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.
|
||||
std::vector<const Constraint*> constraints;
|
||||
Scope2* rootScope;
|
||||
std::vector<TypeError> errors;
|
||||
|
||||
// This includes every constraint that has not been fully solved.
|
||||
// A constraint can be both blocked and unsolved, for instance.
|
||||
std::unordered_set<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<const Constraint*, size_t> blockedConstraints;
|
||||
// A mapping of type/pack pointers to the constraints they block.
|
||||
std::unordered_map<BlockedConstraintId, std::vector<const Constraint*>> blocked;
|
||||
|
||||
explicit ConstraintSolver(TypeArena* arena, Scope2* rootScope);
|
||||
|
||||
/**
|
||||
* Attempts to dispatch all pending constraints and reach a type solution
|
||||
* that satisfies all of the constraints, recording any errors that are
|
||||
* encountered.
|
||||
**/
|
||||
void run();
|
||||
|
||||
bool done();
|
||||
|
||||
bool tryDispatch(const Constraint* c);
|
||||
bool tryDispatch(const SubtypeConstraint& c);
|
||||
bool tryDispatch(const PackSubtypeConstraint& c);
|
||||
bool tryDispatch(const GeneralizationConstraint& c);
|
||||
bool tryDispatch(const InstantiationConstraint& c, const Constraint* constraint);
|
||||
|
||||
/**
|
||||
* 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, const Constraint* constraint);
|
||||
void block(const Constraint* target, const Constraint* constraint);
|
||||
void block(TypeId target, const Constraint* constraint);
|
||||
void block(TypePackId target, 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 unblock(const Constraint* progressed);
|
||||
void unblock(TypeId progressed);
|
||||
void unblock(TypePackId progressed);
|
||||
|
||||
/**
|
||||
* Returns whether the constraint is blocked on anything.
|
||||
* @param constraint the constraint to check.
|
||||
*/
|
||||
bool isBlocked(const Constraint* constraint);
|
||||
|
||||
void reportErrors(const std::vector<TypeError>& errors);
|
||||
|
||||
/**
|
||||
* Creates a new Unifier and performs a single unification operation. Commits
|
||||
* the result and reports errors if necessary.
|
||||
* @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 and reports errors if necessary.
|
||||
* @param subPack the sub-type pack to unify.
|
||||
* @param superPack the super-type pack to unify.
|
||||
*/
|
||||
void unify(TypePackId subPack, TypePackId superPack);
|
||||
};
|
||||
|
||||
void dump(Scope2* rootScope, struct ToStringOptions& opts);
|
||||
|
||||
} // namespace Luau
|
|
@ -5,6 +5,7 @@
|
|||
#include "Luau/Location.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/Variant.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -108,9 +109,6 @@ struct FunctionDoesNotTakeSelf
|
|||
|
||||
struct FunctionRequiresSelf
|
||||
{
|
||||
// TODO: Delete with LuauAnyInIsOptionalIsOptional
|
||||
int requiredExtraNils = 0;
|
||||
|
||||
bool operator==(const FunctionRequiresSelf& rhs) const;
|
||||
};
|
||||
|
||||
|
|
|
@ -12,9 +12,6 @@
|
|||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
LUAU_FASTFLAG(LuauSeparateTypechecks)
|
||||
LUAU_FASTFLAG(LuauDirtySourceModule)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -60,17 +57,12 @@ struct SourceNode
|
|||
{
|
||||
bool hasDirtySourceModule() const
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDirtySourceModule);
|
||||
|
||||
return dirtySourceModule;
|
||||
}
|
||||
|
||||
bool hasDirtyModule(bool forAutocomplete) const
|
||||
{
|
||||
if (FFlag::LuauSeparateTypechecks)
|
||||
return forAutocomplete ? dirtyModuleForAutocomplete : dirtyModule;
|
||||
else
|
||||
return dirtyModule;
|
||||
}
|
||||
|
||||
ModuleName name;
|
||||
|
@ -90,10 +82,6 @@ struct FrontendOptions
|
|||
// is complete.
|
||||
bool retainFullTypeGraphs = false;
|
||||
|
||||
// When true, we run typechecking twice, once in the regular mode, and once in strict mode
|
||||
// in order to get more precise type information (e.g. for autocomplete).
|
||||
bool typecheckTwice_DEPRECATED = false;
|
||||
|
||||
// Run typechecking only in mode required for autocomplete (strict mode in order to get more precise type information)
|
||||
bool forAutocomplete = false;
|
||||
};
|
||||
|
@ -171,7 +159,9 @@ struct Frontend
|
|||
void applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName);
|
||||
|
||||
private:
|
||||
std::pair<SourceNode*, SourceModule*> getSourceNode(CheckResult& checkResult, const ModuleName& name, bool forAutocomplete_DEPRECATED);
|
||||
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);
|
||||
|
|
53
Analysis/include/Luau/Instantiation.h
Normal file
53
Analysis/include/Luau/Instantiation.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Substitution.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/Unifiable.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
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 Luau
|
|
@ -34,10 +34,6 @@ 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, and reverses the vector of keys.
|
||||
// TODO: remove with FFlagLuauTypecheckOptPass
|
||||
std::pair<Symbol, std::vector<std::string>> getFullName(const LValue& lvalue);
|
||||
|
||||
// Utility function: breaks down an LValue to get at the Symbol
|
||||
Symbol getBaseSymbol(const LValue& lvalue);
|
||||
|
||||
|
|
|
@ -2,11 +2,10 @@
|
|||
#pragma once
|
||||
|
||||
#include "Luau/FileResolver.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypedAllocator.h"
|
||||
#include "Luau/ParseOptions.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/ParseResult.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
@ -20,6 +19,7 @@ struct Module;
|
|||
|
||||
using ScopePtr = std::shared_ptr<struct Scope>;
|
||||
using ModulePtr = std::shared_ptr<Module>;
|
||||
struct Scope2;
|
||||
|
||||
/// Root of the AST of a parsed source file
|
||||
struct SourceModule
|
||||
|
@ -54,35 +54,6 @@ struct RequireCycle
|
|||
std::vector<ModuleName> path; // one of the paths for a require() to go all the way back to the originating module
|
||||
};
|
||||
|
||||
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>)
|
||||
LUAU_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);
|
||||
};
|
||||
|
||||
void freeze(TypeArena& arena);
|
||||
void unfreeze(TypeArena& arena);
|
||||
|
||||
struct Module
|
||||
{
|
||||
~Module();
|
||||
|
@ -95,6 +66,7 @@ struct Module
|
|||
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*, TypeId> astExpectedTypes{nullptr};
|
||||
|
@ -108,12 +80,11 @@ struct Module
|
|||
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.
|
||||
// Returns true if there were any free types encountered in the public interface. This
|
||||
// indicates a bug in the type checker that we want to surface.
|
||||
bool clonePublicInterface(InternalErrorReporter& ice);
|
||||
void clonePublicInterface(InternalErrorReporter& ice);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
|
75
Analysis/include/Luau/NotNull.h
Normal file
75
Analysis/include/Luau/NotNull.h
Normal file
|
@ -0,0 +1,75 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
/** A non-owning, non-null pointer to a T.
|
||||
*
|
||||
* A NotNull<T> is notionally identical to a T* with the added restriction that it
|
||||
* can never store nullptr.
|
||||
*
|
||||
* The sole conversion rule from T* to NotNull<T> is the single-argument constructor, which
|
||||
* is intentionally marked explicit. This constructor performs a runtime test to verify
|
||||
* that the passed pointer is never nullptr.
|
||||
*
|
||||
* Pointer arithmetic, increment, decrement, and array indexing are all forbidden.
|
||||
*
|
||||
* An implicit coersion from NotNull<T> to T* is afforded, as are the pointer indirection and member
|
||||
* access operators. (*p and p->prop)
|
||||
*
|
||||
* The explicit delete statement is permitted on a NotNull<T> through this implicit conversion.
|
||||
*/
|
||||
template <typename T>
|
||||
struct NotNull
|
||||
{
|
||||
explicit NotNull(T* t)
|
||||
: ptr(t)
|
||||
{
|
||||
LUAU_ASSERT(t);
|
||||
}
|
||||
|
||||
explicit NotNull(std::nullptr_t) = delete;
|
||||
void operator=(std::nullptr_t) = delete;
|
||||
|
||||
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* ptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
template <typename T> struct hash<Luau::NotNull<T>>
|
||||
{
|
||||
size_t operator()(const Luau::NotNull<T>& p) const
|
||||
{
|
||||
return std::hash<T*>()(p.ptr);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -6,6 +6,9 @@
|
|||
namespace Luau
|
||||
{
|
||||
|
||||
struct Scope2;
|
||||
|
||||
void quantify(TypeId ty, TypeLevel level);
|
||||
void quantify(TypeId ty, Scope2* scope);
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/ModuleResolver.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
|
|
|
@ -30,6 +30,9 @@ struct Symbol
|
|||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Symbol(const T&) = delete;
|
||||
|
||||
AstLocal* local;
|
||||
AstName global;
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/ConstraintGraphBuilder.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
|
@ -53,6 +54,7 @@ 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!
|
||||
|
@ -64,6 +66,11 @@ 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 = {});
|
||||
|
@ -74,6 +81,7 @@ std::string toStringNamedFunction(const std::string& funcName, const FunctionTyp
|
|||
// 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);
|
||||
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/TypePack.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauTypecheckOptPass)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -93,15 +91,6 @@ struct TxnLog
|
|||
{
|
||||
}
|
||||
|
||||
TxnLog(TxnLog* parent, std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen)
|
||||
: typeVarChanges(nullptr)
|
||||
, typePackChanges(nullptr)
|
||||
, parent(parent)
|
||||
, sharedSeen(sharedSeen)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauTypecheckOptPass);
|
||||
}
|
||||
|
||||
TxnLog(const TxnLog&) = delete;
|
||||
TxnLog& operator=(const TxnLog&) = delete;
|
||||
|
||||
|
|
42
Analysis/include/Luau/TypeArena.h
Normal file
42
Analysis/include/Luau/TypeArena.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/TypedAllocator.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/TypePack.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
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>)
|
||||
LUAU_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);
|
||||
};
|
||||
|
||||
void freeze(TypeArena& arena);
|
||||
void unfreeze(TypeArena& arena);
|
||||
|
||||
} // namespace Luau
|
|
@ -34,45 +34,6 @@ const AstStat* getFallthrough(const AstStat* node);
|
|||
struct UnifierOptions;
|
||||
struct Unifier;
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
// A substitution which replaces free types by any
|
||||
struct Anyification : Substitution
|
||||
{
|
||||
|
@ -187,7 +148,6 @@ struct TypeChecker
|
|||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprIndexExpr& expr);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprFunction& expr, std::optional<TypeId> expectedType = std::nullopt);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprTable& expr, std::optional<TypeId> expectedType = std::nullopt);
|
||||
ExprResult<TypeId> checkExpr_(const ScopePtr& scope, const AstExprTable& expr, std::optional<TypeId> expectedType = std::nullopt);
|
||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprUnary& expr);
|
||||
TypeId checkRelationalOperation(
|
||||
const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates = {});
|
||||
|
@ -395,7 +355,7 @@ private:
|
|||
const AstArray<AstGenericType>& genericNames, const AstArray<AstGenericTypePack>& genericPackNames, bool useCache = false);
|
||||
|
||||
public:
|
||||
ErrorVec resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense);
|
||||
void resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense);
|
||||
|
||||
private:
|
||||
void refineLValue(const LValue& lvalue, RefinementMap& refis, const ScopePtr& scope, TypeIdPredicate predicate);
|
||||
|
@ -403,14 +363,14 @@ private:
|
|||
std::optional<TypeId> resolveLValue(const ScopePtr& scope, const LValue& lvalue);
|
||||
std::optional<TypeId> resolveLValue(const RefinementMap& refis, const ScopePtr& scope, const LValue& lvalue);
|
||||
|
||||
void resolve(const PredicateVec& predicates, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr = false);
|
||||
void resolve(const Predicate& predicate, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr);
|
||||
void resolve(const TruthyPredicate& truthyP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr);
|
||||
void resolve(const AndPredicate& andP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||
void resolve(const OrPredicate& orP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||
void resolve(const IsAPredicate& isaP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||
void resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||
void resolve(const EqPredicate& eqP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
|
||||
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;
|
||||
|
|
|
@ -24,6 +24,7 @@ namespace Luau
|
|||
{
|
||||
|
||||
struct TypeArena;
|
||||
struct Scope2;
|
||||
|
||||
/**
|
||||
* There are three kinds of type variables:
|
||||
|
@ -124,6 +125,7 @@ struct ConstrainedTypeVar
|
|||
|
||||
std::vector<TypeId> parts;
|
||||
TypeLevel level;
|
||||
Scope2* scope = nullptr;
|
||||
};
|
||||
|
||||
// Singleton types https://github.com/Roblox/luau/blob/master/rfcs/syntax-singleton-types.md
|
||||
|
@ -255,6 +257,7 @@ struct FunctionTypeVar
|
|||
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;
|
||||
|
@ -266,6 +269,7 @@ struct FunctionTypeVar
|
|||
bool hasSelf;
|
||||
Tags tags;
|
||||
bool hasNoGenerics = false;
|
||||
bool generalized = false;
|
||||
};
|
||||
|
||||
enum class TableState
|
||||
|
@ -323,6 +327,7 @@ struct TableTypeVar
|
|||
|
||||
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
|
||||
|
|
|
@ -23,6 +23,12 @@ public:
|
|||
currentBlockSize = kBlockSize;
|
||||
}
|
||||
|
||||
TypedAllocator(const TypedAllocator&) = delete;
|
||||
TypedAllocator& operator=(const TypedAllocator&) = delete;
|
||||
|
||||
TypedAllocator(TypedAllocator&&) = default;
|
||||
TypedAllocator& operator=(TypedAllocator&&) = default;
|
||||
|
||||
~TypedAllocator()
|
||||
{
|
||||
if (frozen)
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
namespace Luau
|
||||
{
|
||||
|
||||
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
|
||||
|
@ -82,9 +84,11 @@ 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.
|
||||
|
@ -111,12 +115,14 @@ struct Generic
|
|||
Generic();
|
||||
explicit Generic(TypeLevel level);
|
||||
explicit Generic(const Name& name);
|
||||
explicit Generic(Scope2* scope);
|
||||
Generic(TypeLevel level, const Name& name);
|
||||
|
||||
int index;
|
||||
TypeLevel level;
|
||||
Scope2* scope = nullptr;
|
||||
Name name;
|
||||
bool explicitName;
|
||||
bool explicitName = false;
|
||||
|
||||
private:
|
||||
static int nextIndex;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
#include "Luau/Location.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/Module.h" // FIXME: For TypeArena. It merits breaking out into its own header.
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/UnifierSharedState.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
@ -32,6 +32,9 @@ struct Widen : Substitution
|
|||
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.
|
||||
|
@ -55,8 +58,6 @@ struct Unifier
|
|||
UnifierSharedState& sharedState;
|
||||
|
||||
Unifier(TypeArena* types, Mode mode, const Location& location, Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog = nullptr);
|
||||
Unifier(TypeArena* types, Mode mode, std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen, 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);
|
||||
|
|
|
@ -42,7 +42,6 @@ struct UnifierSharedState
|
|||
|
||||
InternalErrorReporter* iceHandler;
|
||||
|
||||
DenseHashSet<void*> seenAny{nullptr};
|
||||
DenseHashMap<TypeId, bool> skipCacheForType{nullptr};
|
||||
DenseHashSet<std::pair<TypeId, TypeId>, TypeIdPairHash> cachedUnify{{nullptr, nullptr}};
|
||||
DenseHashMap<std::pair<TypeId, TypeId>, TypeErrorData, TypeIdPairHash> cachedUnifyError{{nullptr, nullptr}};
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <type_traits>
|
||||
#include <initializer_list>
|
||||
#include <stddef.h>
|
||||
#include <utility>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -95,6 +96,20 @@ public:
|
|||
return *this;
|
||||
}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
T& emplace(Args&&... args)
|
||||
{
|
||||
using TT = std::decay_t<T>;
|
||||
constexpr int tid = getTypeId<T>();
|
||||
static_assert(tid >= 0, "unsupported T");
|
||||
|
||||
tableDtor[typeId](&storage);
|
||||
typeId = tid;
|
||||
new (&storage) TT(std::forward<Args>(args)...);
|
||||
|
||||
return *reinterpret_cast<T*>(&storage);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const T* get_if() const
|
||||
{
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauUseVisitRecursionLimit)
|
||||
LUAU_FASTINT(LuauVisitRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauNormalizeFlagIsConservative)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -61,168 +61,6 @@ inline void unsee(DenseHashSet<void*>& seen, const void* tv)
|
|||
// When DenseHashSet is used for 'visitTypeVarOnce', where don't forget visited elements
|
||||
}
|
||||
|
||||
template<typename F, typename Set>
|
||||
void visit(TypePackId tp, F& f, Set& seen);
|
||||
|
||||
template<typename F, typename Set>
|
||||
void visit(TypeId ty, F& f, Set& seen)
|
||||
{
|
||||
if (visit_detail::hasSeen(seen, ty))
|
||||
{
|
||||
f.cycle(ty);
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto btv = get<BoundTypeVar>(ty))
|
||||
{
|
||||
if (apply(ty, *btv, seen, f))
|
||||
visit(btv->boundTo, f, seen);
|
||||
}
|
||||
|
||||
else if (auto ftv = get<FreeTypeVar>(ty))
|
||||
apply(ty, *ftv, seen, f);
|
||||
|
||||
else if (auto gtv = get<GenericTypeVar>(ty))
|
||||
apply(ty, *gtv, seen, f);
|
||||
|
||||
else if (auto etv = get<ErrorTypeVar>(ty))
|
||||
apply(ty, *etv, seen, f);
|
||||
|
||||
else if (auto ctv = get<ConstrainedTypeVar>(ty))
|
||||
{
|
||||
if (apply(ty, *ctv, seen, f))
|
||||
{
|
||||
for (TypeId part : ctv->parts)
|
||||
visit(part, f, seen);
|
||||
}
|
||||
}
|
||||
|
||||
else if (auto ptv = get<PrimitiveTypeVar>(ty))
|
||||
apply(ty, *ptv, seen, f);
|
||||
|
||||
else if (auto ftv = get<FunctionTypeVar>(ty))
|
||||
{
|
||||
if (apply(ty, *ftv, seen, f))
|
||||
{
|
||||
visit(ftv->argTypes, f, seen);
|
||||
visit(ftv->retType, f, seen);
|
||||
}
|
||||
}
|
||||
|
||||
else if (auto ttv = get<TableTypeVar>(ty))
|
||||
{
|
||||
// Some visitors want to see bound tables, that's why we visit the original type
|
||||
if (apply(ty, *ttv, seen, f))
|
||||
{
|
||||
if (ttv->boundTo)
|
||||
{
|
||||
visit(*ttv->boundTo, f, seen);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto& [_name, prop] : ttv->props)
|
||||
visit(prop.type, f, seen);
|
||||
|
||||
if (ttv->indexer)
|
||||
{
|
||||
visit(ttv->indexer->indexType, f, seen);
|
||||
visit(ttv->indexer->indexResultType, f, seen);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (auto mtv = get<MetatableTypeVar>(ty))
|
||||
{
|
||||
if (apply(ty, *mtv, seen, f))
|
||||
{
|
||||
visit(mtv->table, f, seen);
|
||||
visit(mtv->metatable, f, seen);
|
||||
}
|
||||
}
|
||||
|
||||
else if (auto ctv = get<ClassTypeVar>(ty))
|
||||
{
|
||||
if (apply(ty, *ctv, seen, f))
|
||||
{
|
||||
for (const auto& [name, prop] : ctv->props)
|
||||
visit(prop.type, f, seen);
|
||||
|
||||
if (ctv->parent)
|
||||
visit(*ctv->parent, f, seen);
|
||||
|
||||
if (ctv->metatable)
|
||||
visit(*ctv->metatable, f, seen);
|
||||
}
|
||||
}
|
||||
|
||||
else if (auto atv = get<AnyTypeVar>(ty))
|
||||
apply(ty, *atv, seen, f);
|
||||
|
||||
else if (auto utv = get<UnionTypeVar>(ty))
|
||||
{
|
||||
if (apply(ty, *utv, seen, f))
|
||||
{
|
||||
for (TypeId optTy : utv->options)
|
||||
visit(optTy, f, seen);
|
||||
}
|
||||
}
|
||||
|
||||
else if (auto itv = get<IntersectionTypeVar>(ty))
|
||||
{
|
||||
if (apply(ty, *itv, seen, f))
|
||||
{
|
||||
for (TypeId partTy : itv->parts)
|
||||
visit(partTy, f, seen);
|
||||
}
|
||||
}
|
||||
|
||||
visit_detail::unsee(seen, ty);
|
||||
}
|
||||
|
||||
template<typename F, typename Set>
|
||||
void visit(TypePackId tp, F& f, Set& seen)
|
||||
{
|
||||
if (visit_detail::hasSeen(seen, tp))
|
||||
{
|
||||
f.cycle(tp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto btv = get<BoundTypePack>(tp))
|
||||
{
|
||||
if (apply(tp, *btv, seen, f))
|
||||
visit(btv->boundTo, f, seen);
|
||||
}
|
||||
|
||||
else if (auto ftv = get<Unifiable::Free>(tp))
|
||||
apply(tp, *ftv, seen, f);
|
||||
|
||||
else if (auto gtv = get<Unifiable::Generic>(tp))
|
||||
apply(tp, *gtv, seen, f);
|
||||
|
||||
else if (auto etv = get<Unifiable::Error>(tp))
|
||||
apply(tp, *etv, seen, f);
|
||||
|
||||
else if (auto pack = get<TypePack>(tp))
|
||||
{
|
||||
apply(tp, *pack, seen, f);
|
||||
|
||||
for (TypeId ty : pack->head)
|
||||
visit(ty, f, seen);
|
||||
|
||||
if (pack->tail)
|
||||
visit(*pack->tail, f, seen);
|
||||
}
|
||||
else if (auto pack = get<VariadicTypePack>(tp))
|
||||
{
|
||||
apply(tp, *pack, seen, f);
|
||||
visit(pack->ty, f, seen);
|
||||
}
|
||||
|
||||
visit_detail::unsee(seen, tp);
|
||||
}
|
||||
|
||||
} // namespace visit_detail
|
||||
|
||||
template<typename S>
|
||||
|
@ -471,17 +309,20 @@ struct GenericTypeVarVisitor
|
|||
|
||||
else if (auto pack = get<TypePack>(tp))
|
||||
{
|
||||
visit(tp, *pack);
|
||||
|
||||
bool res = visit(tp, *pack);
|
||||
if (!FFlag::LuauNormalizeFlagIsConservative || res)
|
||||
{
|
||||
for (TypeId ty : pack->head)
|
||||
traverse(ty);
|
||||
|
||||
if (pack->tail)
|
||||
traverse(*pack->tail);
|
||||
}
|
||||
}
|
||||
else if (auto pack = get<VariadicTypePack>(tp))
|
||||
{
|
||||
visit(tp, *pack);
|
||||
bool res = visit(tp, *pack);
|
||||
if (!FFlag::LuauNormalizeFlagIsConservative || res)
|
||||
traverse(pack->ty);
|
||||
}
|
||||
else
|
||||
|
@ -509,37 +350,4 @@ struct TypeVarOnceVisitor : GenericTypeVarVisitor<DenseHashSet<void*>>
|
|||
}
|
||||
};
|
||||
|
||||
// Clip with FFlagLuauUseVisitRecursionLimit
|
||||
template<typename TID, typename F>
|
||||
void DEPRECATED_visitTypeVar(TID ty, F& f, std::unordered_set<void*>& seen)
|
||||
{
|
||||
visit_detail::visit(ty, f, seen);
|
||||
}
|
||||
|
||||
// Delete and inline when clipping FFlagLuauUseVisitRecursionLimit
|
||||
template<typename TID, typename F>
|
||||
void DEPRECATED_visitTypeVar(TID ty, F& f)
|
||||
{
|
||||
if (FFlag::LuauUseVisitRecursionLimit)
|
||||
f.traverse(ty);
|
||||
else
|
||||
{
|
||||
std::unordered_set<void*> seen;
|
||||
visit_detail::visit(ty, f, seen);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete and inline when clipping FFlagLuauUseVisitRecursionLimit
|
||||
template<typename TID, typename F>
|
||||
void DEPRECATED_visitTypeVarOnce(TID ty, F& f, DenseHashSet<void*>& seen)
|
||||
{
|
||||
if (FFlag::LuauUseVisitRecursionLimit)
|
||||
f.traverse(ty);
|
||||
else
|
||||
{
|
||||
seen.clear();
|
||||
visit_detail::visit(ty, f, seen);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -71,9 +71,11 @@ struct FindFullAncestry final : public AstVisitor
|
|||
{
|
||||
std::vector<AstNode*> nodes;
|
||||
Position pos;
|
||||
Position documentEnd;
|
||||
|
||||
explicit FindFullAncestry(Position pos)
|
||||
explicit FindFullAncestry(Position pos, Position documentEnd)
|
||||
: pos(pos)
|
||||
, documentEnd(documentEnd)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -84,6 +86,16 @@ struct FindFullAncestry final : public AstVisitor
|
|||
nodes.push_back(node);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Edge case: If we ask for the node at the position that is the very end of the document
|
||||
// return the innermost AST element that ends at that position.
|
||||
|
||||
if (node->location.end == documentEnd && pos >= documentEnd)
|
||||
{
|
||||
nodes.push_back(node);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
@ -92,7 +104,11 @@ struct FindFullAncestry final : public AstVisitor
|
|||
|
||||
std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos)
|
||||
{
|
||||
FindFullAncestry finder(pos);
|
||||
const Position end = source.root->location.end;
|
||||
if (pos > end)
|
||||
pos = end;
|
||||
|
||||
FindFullAncestry finder(pos, end);
|
||||
source.root->visit(&finder);
|
||||
return std::move(finder.nodes);
|
||||
}
|
||||
|
|
|
@ -1699,32 +1699,19 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
|||
}
|
||||
|
||||
AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback)
|
||||
{
|
||||
if (FFlag::LuauSeparateTypechecks)
|
||||
{
|
||||
// FIXME: We can improve performance here by parsing without checking.
|
||||
// The old type graph is probably fine. (famous last words!)
|
||||
FrontendOptions opts;
|
||||
opts.forAutocomplete = true;
|
||||
frontend.check(moduleName, opts);
|
||||
}
|
||||
else
|
||||
{
|
||||
// FIXME: We can improve performance here by parsing without checking.
|
||||
// The old type graph is probably fine. (famous last words!)
|
||||
// FIXME: We don't need to typecheck for script analysis here, just for autocomplete.
|
||||
frontend.check(moduleName);
|
||||
}
|
||||
|
||||
const SourceModule* sourceModule = frontend.getSourceModule(moduleName);
|
||||
if (!sourceModule)
|
||||
return {};
|
||||
|
||||
TypeChecker& typeChecker =
|
||||
(frontend.options.typecheckTwice_DEPRECATED || FFlag::LuauSeparateTypechecks ? frontend.typeCheckerForAutocomplete : frontend.typeChecker);
|
||||
ModulePtr module =
|
||||
(frontend.options.typecheckTwice_DEPRECATED || FFlag::LuauSeparateTypechecks ? frontend.moduleResolverForAutocomplete.getModule(moduleName)
|
||||
: frontend.moduleResolver.getModule(moduleName));
|
||||
TypeChecker& typeChecker = frontend.typeCheckerForAutocomplete;
|
||||
ModulePtr module = frontend.moduleResolverForAutocomplete.getModule(moduleName);
|
||||
|
||||
if (!module)
|
||||
return {};
|
||||
|
@ -1752,9 +1739,7 @@ OwningAutocompleteResult autocompleteSource(Frontend& frontend, std::string_view
|
|||
sourceModule->mode = Mode::Strict;
|
||||
sourceModule->commentLocations = std::move(result.commentLocations);
|
||||
|
||||
TypeChecker& typeChecker =
|
||||
(frontend.options.typecheckTwice_DEPRECATED || FFlag::LuauSeparateTypechecks ? frontend.typeCheckerForAutocomplete : frontend.typeChecker);
|
||||
|
||||
TypeChecker& typeChecker = frontend.typeCheckerForAutocomplete;
|
||||
ModulePtr module = typeChecker.check(*sourceModule, Mode::Strict);
|
||||
|
||||
OwningAutocompleteResult autocompleteResult = {
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(LuauAssertStripsFalsyTypes)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSetMetaTableArgsCheck, false)
|
||||
|
||||
/** FIXME: Many of these type definitions are not quite completely accurate.
|
||||
|
@ -408,8 +407,6 @@ static std::optional<ExprResult<TypePackId>> magicFunctionAssert(
|
|||
{
|
||||
auto [paramPack, predicates] = exprResult;
|
||||
|
||||
if (FFlag::LuauAssertStripsFalsyTypes)
|
||||
{
|
||||
TypeArena& arena = typechecker.currentModule->internalTypes;
|
||||
|
||||
auto [head, tail] = flatten(paramPack);
|
||||
|
@ -421,7 +418,7 @@ static std::optional<ExprResult<TypePackId>> magicFunctionAssert(
|
|||
head.push_back(*fst);
|
||||
}
|
||||
|
||||
typechecker.reportErrors(typechecker.resolve(predicates, scope, true));
|
||||
typechecker.resolve(predicates, scope, true);
|
||||
|
||||
if (head.size() > 0)
|
||||
{
|
||||
|
@ -434,16 +431,6 @@ static std::optional<ExprResult<TypePackId>> magicFunctionAssert(
|
|||
|
||||
return ExprResult<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (expr.args.size < 1)
|
||||
return ExprResult<TypePackId>{paramPack};
|
||||
|
||||
typechecker.reportErrors(typechecker.resolve(predicates, scope, true));
|
||||
|
||||
return ExprResult<TypePackId>{paramPack};
|
||||
}
|
||||
}
|
||||
|
||||
static std::optional<ExprResult<TypePackId>> magicFunctionPack(
|
||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult)
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#include "Luau/Clone.h"
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/RecursionCounter.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/Unifiable.h"
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauCopyBeforeNormalizing)
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
|
||||
LUAU_FASTFLAG(LuauTypecheckOptPass)
|
||||
LUAU_FASTFLAGVARIABLE(LuauLosslessClone, false)
|
||||
LUAU_FASTFLAG(LuauNoMethodLocations)
|
||||
|
||||
namespace Luau
|
||||
|
@ -88,21 +86,9 @@ struct TypePackCloner
|
|||
}
|
||||
|
||||
void operator()(const Unifiable::Free& t)
|
||||
{
|
||||
if (FFlag::LuauLosslessClone)
|
||||
{
|
||||
defaultClone(t);
|
||||
}
|
||||
else
|
||||
{
|
||||
cloneState.encounteredFreeType = true;
|
||||
|
||||
TypePackId err = getSingletonTypes().errorRecoveryTypePack(getSingletonTypes().anyTypePack);
|
||||
TypePackId cloned = dest.addTypePack(*err);
|
||||
seenTypePacks[typePackId] = cloned;
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(const Unifiable::Generic& t)
|
||||
{
|
||||
defaultClone(t);
|
||||
|
@ -151,20 +137,9 @@ void TypeCloner::defaultClone(const T& t)
|
|||
}
|
||||
|
||||
void TypeCloner::operator()(const Unifiable::Free& t)
|
||||
{
|
||||
if (FFlag::LuauLosslessClone)
|
||||
{
|
||||
defaultClone(t);
|
||||
}
|
||||
else
|
||||
{
|
||||
cloneState.encounteredFreeType = true;
|
||||
|
||||
TypeId err = getSingletonTypes().errorRecoveryType(getSingletonTypes().anyType);
|
||||
TypeId cloned = dest.addType(*err);
|
||||
seenTypes[typeId] = cloned;
|
||||
}
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const Unifiable::Generic& t)
|
||||
{
|
||||
|
@ -191,9 +166,6 @@ void TypeCloner::operator()(const PrimitiveTypeVar& t)
|
|||
|
||||
void TypeCloner::operator()(const ConstrainedTypeVar& t)
|
||||
{
|
||||
if (!FFlag::LuauLosslessClone)
|
||||
cloneState.encounteredFreeType = true;
|
||||
|
||||
TypeId res = dest.addType(ConstrainedTypeVar{t.level});
|
||||
ConstrainedTypeVar* ctv = getMutable<ConstrainedTypeVar>(res);
|
||||
LUAU_ASSERT(ctv);
|
||||
|
@ -230,8 +202,6 @@ void TypeCloner::operator()(const FunctionTypeVar& t)
|
|||
ftv->argTypes = clone(t.argTypes, dest, cloneState);
|
||||
ftv->argNames = t.argNames;
|
||||
ftv->retType = clone(t.retType, dest, cloneState);
|
||||
|
||||
if (FFlag::LuauTypecheckOptPass)
|
||||
ftv->hasNoGenerics = t.hasNoGenerics;
|
||||
}
|
||||
|
||||
|
@ -270,13 +240,6 @@ void TypeCloner::operator()(const TableTypeVar& t)
|
|||
for (TypePackId& arg : ttv->instantiatedTypePackParams)
|
||||
arg = clone(arg, dest, cloneState);
|
||||
|
||||
if (!FFlag::LuauLosslessClone && ttv->state == TableState::Free)
|
||||
{
|
||||
cloneState.encounteredFreeType = true;
|
||||
|
||||
ttv->state = TableState::Sealed;
|
||||
}
|
||||
|
||||
ttv->definitionModuleName = t.definitionModuleName;
|
||||
if (!FFlag::LuauNoMethodLocations)
|
||||
ttv->methodDefinitionLocations = t.methodDefinitionLocations;
|
||||
|
@ -420,4 +383,67 @@ TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState)
|
|||
return result;
|
||||
}
|
||||
|
||||
TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log)
|
||||
{
|
||||
ty = log->follow(ty);
|
||||
|
||||
TypeId result = ty;
|
||||
|
||||
if (auto pty = log->pending(ty))
|
||||
ty = &pty->pending;
|
||||
|
||||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
|
||||
{
|
||||
FunctionTypeVar clone = FunctionTypeVar{ftv->level, ftv->argTypes, ftv->retType, ftv->definition, ftv->hasSelf};
|
||||
clone.generics = ftv->generics;
|
||||
clone.genericPacks = ftv->genericPacks;
|
||||
clone.magicFunction = ftv->magicFunction;
|
||||
clone.tags = ftv->tags;
|
||||
clone.argNames = ftv->argNames;
|
||||
result = dest.addType(std::move(clone));
|
||||
}
|
||||
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
|
||||
{
|
||||
LUAU_ASSERT(!ttv->boundTo);
|
||||
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state};
|
||||
if (!FFlag::LuauNoMethodLocations)
|
||||
clone.methodDefinitionLocations = ttv->methodDefinitionLocations;
|
||||
clone.definitionModuleName = ttv->definitionModuleName;
|
||||
clone.name = ttv->name;
|
||||
clone.syntheticName = ttv->syntheticName;
|
||||
clone.instantiatedTypeParams = ttv->instantiatedTypeParams;
|
||||
clone.instantiatedTypePackParams = ttv->instantiatedTypePackParams;
|
||||
clone.tags = ttv->tags;
|
||||
result = dest.addType(std::move(clone));
|
||||
}
|
||||
else if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(ty))
|
||||
{
|
||||
MetatableTypeVar clone = MetatableTypeVar{mtv->table, mtv->metatable};
|
||||
clone.syntheticName = mtv->syntheticName;
|
||||
result = dest.addType(std::move(clone));
|
||||
}
|
||||
else if (const UnionTypeVar* utv = get<UnionTypeVar>(ty))
|
||||
{
|
||||
UnionTypeVar clone;
|
||||
clone.options = utv->options;
|
||||
result = dest.addType(std::move(clone));
|
||||
}
|
||||
else if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(ty))
|
||||
{
|
||||
IntersectionTypeVar clone;
|
||||
clone.parts = itv->parts;
|
||||
result = dest.addType(std::move(clone));
|
||||
}
|
||||
else if (const ConstrainedTypeVar* ctv = get<ConstrainedTypeVar>(ty))
|
||||
{
|
||||
ConstrainedTypeVar clone{ctv->level, ctv->parts};
|
||||
result = dest.addType(std::move(clone));
|
||||
}
|
||||
else
|
||||
return result;
|
||||
|
||||
asMutable(result)->documentationSymbol = ty->documentationSymbol;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
300
Analysis/src/ConstraintGraphBuilder.cpp
Normal file
300
Analysis/src/ConstraintGraphBuilder.cpp
Normal file
|
@ -0,0 +1,300 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#include "Luau/ConstraintGraphBuilder.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
Constraint::Constraint(ConstraintV&& c)
|
||||
: c(std::move(c))
|
||||
{
|
||||
}
|
||||
|
||||
Constraint::Constraint(ConstraintV&& c, std::vector<Constraint*> dependencies)
|
||||
: c(std::move(c))
|
||||
, dependencies(dependencies)
|
||||
{
|
||||
}
|
||||
|
||||
std::optional<TypeId> Scope2::lookup(Symbol sym)
|
||||
{
|
||||
Scope2* s = this;
|
||||
|
||||
while (true)
|
||||
{
|
||||
auto it = s->bindings.find(sym);
|
||||
if (it != s->bindings.end())
|
||||
return it->second;
|
||||
|
||||
if (s->parent)
|
||||
s = s->parent;
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
ConstraintGraphBuilder::ConstraintGraphBuilder(TypeArena* arena)
|
||||
: singletonTypes(getSingletonTypes())
|
||||
, arena(arena)
|
||||
, rootScope(nullptr)
|
||||
{
|
||||
LUAU_ASSERT(arena);
|
||||
}
|
||||
|
||||
TypeId ConstraintGraphBuilder::freshType(Scope2* scope)
|
||||
{
|
||||
LUAU_ASSERT(scope);
|
||||
return arena->addType(FreeTypeVar{scope});
|
||||
}
|
||||
|
||||
TypePackId ConstraintGraphBuilder::freshTypePack(Scope2* scope)
|
||||
{
|
||||
LUAU_ASSERT(scope);
|
||||
FreeTypePack f{scope};
|
||||
return arena->addTypePack(TypePackVar{std::move(f)});
|
||||
}
|
||||
|
||||
Scope2* ConstraintGraphBuilder::childScope(Location location, Scope2* parent)
|
||||
{
|
||||
LUAU_ASSERT(parent);
|
||||
auto scope = std::make_unique<Scope2>();
|
||||
Scope2* borrow = scope.get();
|
||||
scopes.emplace_back(location, std::move(scope));
|
||||
|
||||
borrow->parent = parent;
|
||||
borrow->returnType = parent->returnType;
|
||||
parent->children.push_back(borrow);
|
||||
|
||||
return borrow;
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::addConstraint(Scope2* scope, ConstraintV cv)
|
||||
{
|
||||
LUAU_ASSERT(scope);
|
||||
scope->constraints.emplace_back(new Constraint{std::move(cv)});
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::addConstraint(Scope2* scope, std::unique_ptr<Constraint> c)
|
||||
{
|
||||
LUAU_ASSERT(scope);
|
||||
scope->constraints.emplace_back(std::move(c));
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::visit(AstStatBlock* block)
|
||||
{
|
||||
LUAU_ASSERT(scopes.empty());
|
||||
LUAU_ASSERT(rootScope == nullptr);
|
||||
scopes.emplace_back(block->location, std::make_unique<Scope2>());
|
||||
rootScope = scopes.back().second.get();
|
||||
rootScope->returnType = freshTypePack(rootScope);
|
||||
|
||||
visit(rootScope, block);
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::visit(Scope2* scope, AstStat* stat)
|
||||
{
|
||||
LUAU_ASSERT(scope);
|
||||
|
||||
if (auto s = stat->as<AstStatBlock>())
|
||||
visit(scope, s);
|
||||
else if (auto s = stat->as<AstStatLocal>())
|
||||
visit(scope, s);
|
||||
else if (auto f = stat->as<AstStatLocalFunction>())
|
||||
visit(scope, f);
|
||||
else if (auto r = stat->as<AstStatReturn>())
|
||||
visit(scope, r);
|
||||
else
|
||||
LUAU_ASSERT(0);
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::visit(Scope2* scope, AstStatLocal* local)
|
||||
{
|
||||
LUAU_ASSERT(scope);
|
||||
|
||||
std::vector<TypeId> varTypes;
|
||||
|
||||
for (AstLocal* local : local->vars)
|
||||
{
|
||||
// TODO annotations
|
||||
TypeId ty = freshType(scope);
|
||||
varTypes.push_back(ty);
|
||||
scope->bindings[local] = ty;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < local->vars.size; ++i)
|
||||
{
|
||||
if (i < local->values.size)
|
||||
{
|
||||
TypeId exprType = check(scope, local->values.data[i]);
|
||||
addConstraint(scope, SubtypeConstraint{varTypes[i], exprType});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addConstraints(Constraint* constraint, Scope2* scope)
|
||||
{
|
||||
LUAU_ASSERT(scope);
|
||||
|
||||
scope->constraints.reserve(scope->constraints.size() + scope->constraints.size());
|
||||
|
||||
for (const auto& c : scope->constraints)
|
||||
constraint->dependencies.push_back(c.get());
|
||||
|
||||
for (Scope2* childScope : scope->children)
|
||||
addConstraints(constraint, childScope);
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::visit(Scope2* scope, AstStatLocalFunction* function)
|
||||
{
|
||||
LUAU_ASSERT(scope);
|
||||
|
||||
// Local
|
||||
// Global
|
||||
// Dotted path
|
||||
// Self?
|
||||
|
||||
TypeId functionType = nullptr;
|
||||
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.
|
||||
|
||||
functionType = freshType(scope);
|
||||
scope->bindings[function->name] = functionType;
|
||||
|
||||
Scope2* innerScope = childScope(function->func->body->location, scope);
|
||||
TypePackId returnType = freshTypePack(scope);
|
||||
innerScope->returnType = returnType;
|
||||
|
||||
std::vector<TypeId> argTypes;
|
||||
|
||||
for (AstLocal* local : function->func->args)
|
||||
{
|
||||
TypeId t = freshType(innerScope);
|
||||
argTypes.push_back(t);
|
||||
innerScope->bindings[local] = t; // TODO annotations
|
||||
}
|
||||
|
||||
for (AstStat* stat : function->func->body->body)
|
||||
visit(innerScope, stat);
|
||||
|
||||
FunctionTypeVar actualFunction{arena->addTypePack(argTypes), returnType};
|
||||
TypeId actualFunctionType = arena->addType(std::move(actualFunction));
|
||||
|
||||
std::unique_ptr<Constraint> c{new Constraint{GeneralizationConstraint{functionType, actualFunctionType, innerScope}}};
|
||||
addConstraints(c.get(), innerScope);
|
||||
|
||||
addConstraint(scope, std::move(c));
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::visit(Scope2* scope, AstStatReturn* ret)
|
||||
{
|
||||
LUAU_ASSERT(scope);
|
||||
|
||||
TypePackId exprTypes = checkPack(scope, ret->list);
|
||||
addConstraint(scope, PackSubtypeConstraint{exprTypes, scope->returnType});
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::visit(Scope2* scope, AstStatBlock* block)
|
||||
{
|
||||
LUAU_ASSERT(scope);
|
||||
|
||||
for (AstStat* stat : block->body)
|
||||
visit(scope, stat);
|
||||
}
|
||||
|
||||
TypePackId ConstraintGraphBuilder::checkPack(Scope2* scope, AstArray<AstExpr*> exprs)
|
||||
{
|
||||
LUAU_ASSERT(scope);
|
||||
|
||||
if (exprs.size == 0)
|
||||
return arena->addTypePack({});
|
||||
|
||||
std::vector<TypeId> types;
|
||||
TypePackId last = nullptr;
|
||||
|
||||
for (size_t i = 0; i < exprs.size; ++i)
|
||||
{
|
||||
if (i < exprs.size - 1)
|
||||
types.push_back(check(scope, exprs.data[i]));
|
||||
else
|
||||
last = checkPack(scope, exprs.data[i]);
|
||||
}
|
||||
|
||||
LUAU_ASSERT(last != nullptr);
|
||||
|
||||
return arena->addTypePack(TypePack{std::move(types), last});
|
||||
}
|
||||
|
||||
TypePackId ConstraintGraphBuilder::checkPack(Scope2* scope, AstExpr* expr)
|
||||
{
|
||||
LUAU_ASSERT(scope);
|
||||
|
||||
// TEMP TEMP TEMP HACK HACK HACK FIXME FIXME
|
||||
TypeId t = check(scope, expr);
|
||||
return arena->addTypePack({t});
|
||||
}
|
||||
|
||||
TypeId ConstraintGraphBuilder::check(Scope2* scope, AstExpr* expr)
|
||||
{
|
||||
LUAU_ASSERT(scope);
|
||||
|
||||
if (auto a = expr->as<AstExprConstantString>())
|
||||
return singletonTypes.stringType;
|
||||
else if (auto a = expr->as<AstExprConstantNumber>())
|
||||
return singletonTypes.numberType;
|
||||
else if (auto a = expr->as<AstExprConstantBool>())
|
||||
return singletonTypes.booleanType;
|
||||
else if (auto a = expr->as<AstExprConstantNil>())
|
||||
return singletonTypes.nilType;
|
||||
else if (auto a = expr->as<AstExprLocal>())
|
||||
{
|
||||
std::optional<TypeId> ty = scope->lookup(a->local);
|
||||
if (ty)
|
||||
return *ty;
|
||||
else
|
||||
return singletonTypes.errorRecoveryType(singletonTypes.anyType); // FIXME? Record an error at this point?
|
||||
}
|
||||
else if (auto a = expr->as<AstExprCall>())
|
||||
{
|
||||
std::vector<TypeId> args;
|
||||
|
||||
for (AstExpr* arg : a->args)
|
||||
{
|
||||
args.push_back(check(scope, arg));
|
||||
}
|
||||
|
||||
TypeId fnType = check(scope, a->func);
|
||||
TypeId instantiatedType = freshType(scope);
|
||||
addConstraint(scope, InstantiationConstraint{instantiatedType, fnType});
|
||||
|
||||
TypeId firstRet = freshType(scope);
|
||||
TypePackId rets = arena->addTypePack(TypePack{{firstRet}, arena->addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}})});
|
||||
FunctionTypeVar ftv(arena->addTypePack(TypePack{args, {}}), rets);
|
||||
TypeId inferredFnType = arena->addType(ftv);
|
||||
|
||||
addConstraint(scope, SubtypeConstraint{inferredFnType, instantiatedType});
|
||||
return firstRet;
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(0);
|
||||
return freshType(scope);
|
||||
}
|
||||
}
|
||||
|
||||
static void collectConstraints(std::vector<const Constraint*>& result, Scope2* scope)
|
||||
{
|
||||
for (const auto& c : scope->constraints)
|
||||
result.push_back(c.get());
|
||||
|
||||
for (Scope2* child : scope->children)
|
||||
collectConstraints(result, child);
|
||||
}
|
||||
|
||||
std::vector<const Constraint*> collectConstraints(Scope2* rootScope)
|
||||
{
|
||||
std::vector<const Constraint*> result;
|
||||
collectConstraints(result, rootScope);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
306
Analysis/src/ConstraintSolver.cpp
Normal file
306
Analysis/src/ConstraintSolver.cpp
Normal file
|
@ -0,0 +1,306 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#include "Luau/ConstraintSolver.h"
|
||||
#include "Luau/Instantiation.h"
|
||||
#include "Luau/Quantify.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/Unifier.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
[[maybe_unused]] static void dumpBindings(Scope2* scope, ToStringOptions& opts)
|
||||
{
|
||||
for (const auto& [k, v] : scope->bindings)
|
||||
{
|
||||
auto d = toStringDetailed(v, opts);
|
||||
opts.nameMap = d.nameMap;
|
||||
printf("\t%s : %s\n", k.c_str(), d.name.c_str());
|
||||
}
|
||||
|
||||
for (Scope2* child : scope->children)
|
||||
dumpBindings(child, opts);
|
||||
}
|
||||
|
||||
static void dumpConstraints(Scope2* scope, ToStringOptions& opts)
|
||||
{
|
||||
for (const ConstraintPtr& c : scope->constraints)
|
||||
{
|
||||
printf("\t%s\n", toString(*c, opts).c_str());
|
||||
}
|
||||
|
||||
for (Scope2* child : scope->children)
|
||||
dumpConstraints(child, opts);
|
||||
}
|
||||
|
||||
void dump(Scope2* rootScope, ToStringOptions& opts)
|
||||
{
|
||||
printf("constraints:\n");
|
||||
dumpConstraints(rootScope, opts);
|
||||
}
|
||||
|
||||
void dump(ConstraintSolver* cs, ToStringOptions& opts)
|
||||
{
|
||||
printf("constraints:\n");
|
||||
for (const Constraint* c : cs->unsolvedConstraints)
|
||||
{
|
||||
printf("\t%s\n", toString(*c, opts).c_str());
|
||||
|
||||
for (const Constraint* dep : c->dependencies)
|
||||
printf("\t\t%s\n", toString(*dep, opts).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
ConstraintSolver::ConstraintSolver(TypeArena* arena, Scope2* rootScope)
|
||||
: arena(arena)
|
||||
, constraints(collectConstraints(rootScope))
|
||||
, rootScope(rootScope)
|
||||
{
|
||||
for (const Constraint* c : constraints)
|
||||
{
|
||||
unsolvedConstraints.insert(c);
|
||||
|
||||
for (const Constraint* dep : c->dependencies)
|
||||
{
|
||||
block(dep, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConstraintSolver::run()
|
||||
{
|
||||
if (done())
|
||||
return;
|
||||
|
||||
bool progress = false;
|
||||
|
||||
ToStringOptions opts;
|
||||
|
||||
if (FFlag::DebugLuauLogSolver)
|
||||
{
|
||||
printf("Starting solver\n");
|
||||
dump(this, opts);
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
progress = false;
|
||||
|
||||
auto it = begin(unsolvedConstraints);
|
||||
auto endIt = end(unsolvedConstraints);
|
||||
|
||||
while (it != endIt)
|
||||
{
|
||||
if (isBlocked(*it))
|
||||
{
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string saveMe = FFlag::DebugLuauLogSolver ? toString(**it, opts) : std::string{};
|
||||
|
||||
bool success = tryDispatch(*it);
|
||||
progress = progress || success;
|
||||
|
||||
auto saveIt = it;
|
||||
++it;
|
||||
if (success)
|
||||
{
|
||||
unsolvedConstraints.erase(saveIt);
|
||||
if (FFlag::DebugLuauLogSolver)
|
||||
{
|
||||
printf("Dispatched\n\t%s\n", saveMe.c_str());
|
||||
dump(this, opts);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (progress);
|
||||
|
||||
if (FFlag::DebugLuauLogSolver)
|
||||
dumpBindings(rootScope, opts);
|
||||
|
||||
LUAU_ASSERT(done());
|
||||
}
|
||||
|
||||
bool ConstraintSolver::done()
|
||||
{
|
||||
return unsolvedConstraints.empty();
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const Constraint* constraint)
|
||||
{
|
||||
if (isBlocked(constraint))
|
||||
return false;
|
||||
|
||||
bool success = false;
|
||||
|
||||
if (auto sc = get<SubtypeConstraint>(*constraint))
|
||||
success = tryDispatch(*sc);
|
||||
else if (auto psc = get<PackSubtypeConstraint>(*constraint))
|
||||
success = tryDispatch(*psc);
|
||||
else if (auto gc = get<GeneralizationConstraint>(*constraint))
|
||||
success = tryDispatch(*gc);
|
||||
else if (auto ic = get<InstantiationConstraint>(*constraint))
|
||||
success = tryDispatch(*ic, constraint);
|
||||
else
|
||||
LUAU_ASSERT(0);
|
||||
|
||||
if (success)
|
||||
{
|
||||
unblock(constraint);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const SubtypeConstraint& c)
|
||||
{
|
||||
unify(c.subType, c.superType);
|
||||
unblock(c.subType);
|
||||
unblock(c.superType);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const PackSubtypeConstraint& c)
|
||||
{
|
||||
unify(c.subPack, c.superPack);
|
||||
unblock(c.subPack);
|
||||
unblock(c.superPack);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& constraint)
|
||||
{
|
||||
unify(constraint.subType, constraint.superType);
|
||||
|
||||
quantify(constraint.superType, constraint.scope);
|
||||
unblock(constraint.subType);
|
||||
unblock(constraint.superType);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const InstantiationConstraint& c, const Constraint* constraint)
|
||||
{
|
||||
TypeId superType = follow(c.superType);
|
||||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(superType))
|
||||
{
|
||||
if (!ftv->generalized)
|
||||
{
|
||||
block(superType, constraint);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (get<FreeTypeVar>(superType))
|
||||
{
|
||||
block(superType, constraint);
|
||||
return false;
|
||||
}
|
||||
// TODO: Error if it's a primitive or something
|
||||
|
||||
Instantiation inst(TxnLog::empty(), arena, TypeLevel{});
|
||||
|
||||
std::optional<TypeId> instantiated = inst.substitute(c.superType);
|
||||
LUAU_ASSERT(instantiated); // TODO FIXME HANDLE THIS
|
||||
|
||||
unify(c.subType, *instantiated);
|
||||
unblock(c.subType);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConstraintSolver::block_(BlockedConstraintId target, const Constraint* constraint)
|
||||
{
|
||||
blocked[target].push_back(constraint);
|
||||
|
||||
auto& count = blockedConstraints[constraint];
|
||||
count += 1;
|
||||
}
|
||||
|
||||
void ConstraintSolver::block(const Constraint* target, const Constraint* constraint)
|
||||
{
|
||||
block_(target, constraint);
|
||||
}
|
||||
|
||||
void ConstraintSolver::block(TypeId target, const Constraint* constraint)
|
||||
{
|
||||
block_(target, constraint);
|
||||
}
|
||||
|
||||
void ConstraintSolver::block(TypePackId target, const Constraint* constraint)
|
||||
{
|
||||
block_(target, constraint);
|
||||
}
|
||||
|
||||
void ConstraintSolver::unblock_(BlockedConstraintId progressed)
|
||||
{
|
||||
auto it = blocked.find(progressed);
|
||||
if (it == blocked.end())
|
||||
return;
|
||||
|
||||
// unblocked should contain a value always, because of the above check
|
||||
for (const Constraint* unblockedConstraint : it->second)
|
||||
{
|
||||
auto& count = blockedConstraints[unblockedConstraint];
|
||||
// This assertion being hit indicates that `blocked` and
|
||||
// `blockedConstraints` desynchronized at some point. This is problematic
|
||||
// because we rely on this count being correct to skip over blocked
|
||||
// constraints.
|
||||
LUAU_ASSERT(count > 0);
|
||||
count -= 1;
|
||||
}
|
||||
|
||||
blocked.erase(it);
|
||||
}
|
||||
|
||||
void ConstraintSolver::unblock(const Constraint* progressed)
|
||||
{
|
||||
return unblock_(progressed);
|
||||
}
|
||||
|
||||
void ConstraintSolver::unblock(TypeId progressed)
|
||||
{
|
||||
return unblock_(progressed);
|
||||
}
|
||||
|
||||
void ConstraintSolver::unblock(TypePackId progressed)
|
||||
{
|
||||
return unblock_(progressed);
|
||||
}
|
||||
|
||||
bool ConstraintSolver::isBlocked(const Constraint* constraint)
|
||||
{
|
||||
auto blockedIt = blockedConstraints.find(constraint);
|
||||
return blockedIt != blockedConstraints.end() && blockedIt->second > 0;
|
||||
}
|
||||
|
||||
void ConstraintSolver::reportErrors(const std::vector<TypeError>& errors)
|
||||
{
|
||||
this->errors.insert(end(this->errors), begin(errors), end(errors));
|
||||
}
|
||||
|
||||
void ConstraintSolver::unify(TypeId subType, TypeId superType)
|
||||
{
|
||||
UnifierSharedState sharedState{&iceReporter};
|
||||
Unifier u{arena, Mode::Strict, Location{}, Covariant, sharedState};
|
||||
|
||||
u.tryUnify(subType, superType);
|
||||
u.log.commit();
|
||||
reportErrors(u.errors);
|
||||
}
|
||||
|
||||
void ConstraintSolver::unify(TypePackId subPack, TypePackId superPack)
|
||||
{
|
||||
UnifierSharedState sharedState{&iceReporter};
|
||||
Unifier u{arena, Mode::Strict, Location{}, Covariant, sharedState};
|
||||
|
||||
u.tryUnify(subPack, superPack);
|
||||
u.log.commit();
|
||||
reportErrors(u.errors);
|
||||
}
|
||||
|
||||
} // namespace Luau
|
|
@ -143,7 +143,7 @@ declare coroutine: {
|
|||
create: <A..., R...>((A...) -> R...) -> thread,
|
||||
resume: <A..., R...>(thread, A...) -> (boolean, R...),
|
||||
running: () -> thread,
|
||||
status: (thread) -> string,
|
||||
status: (thread) -> "dead" | "running" | "normal" | "suspended",
|
||||
-- FIXME: This technically returns a function, but we can't represent this yet.
|
||||
wrap: <A..., R...>((A...) -> R...) -> any,
|
||||
yield: <A..., R...>(A...) -> R...,
|
||||
|
@ -179,7 +179,7 @@ declare debug: {
|
|||
}
|
||||
|
||||
declare utf8: {
|
||||
char: (number, ...number) -> string,
|
||||
char: (...number) -> string,
|
||||
charpattern: string,
|
||||
codes: (string) -> ((string, number) -> (number, number), string, number),
|
||||
-- FIXME
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#include "Luau/Error.h"
|
||||
|
||||
#include "Luau/Clone.h"
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/ToString.h"
|
||||
|
||||
|
@ -178,14 +177,6 @@ struct ErrorConverter
|
|||
|
||||
std::string operator()(const Luau::FunctionRequiresSelf& e) const
|
||||
{
|
||||
if (e.requiredExtraNils)
|
||||
{
|
||||
const char* plural = e.requiredExtraNils == 1 ? "" : "s";
|
||||
return format("This function was declared to accept self, but you did not pass enough arguments. Use a colon instead of a dot or "
|
||||
"pass %i extra nil%s to suppress this warning",
|
||||
e.requiredExtraNils, plural);
|
||||
}
|
||||
else
|
||||
return "This function must be called with self. Did you mean to use a colon instead of a dot?";
|
||||
}
|
||||
|
||||
|
@ -539,7 +530,7 @@ bool FunctionDoesNotTakeSelf::operator==(const FunctionDoesNotTakeSelf&) const
|
|||
|
||||
bool FunctionRequiresSelf::operator==(const FunctionRequiresSelf& e) const
|
||||
{
|
||||
return requiredExtraNils == e.requiredExtraNils;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OccursCheckFailed::operator==(const OccursCheckFailed&) const
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#include "Luau/Clone.h"
|
||||
#include "Luau/Config.h"
|
||||
#include "Luau/FileResolver.h"
|
||||
#include "Luau/ConstraintGraphBuilder.h"
|
||||
#include "Luau/ConstraintSolver.h"
|
||||
#include "Luau/Parser.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
|
@ -20,10 +22,9 @@ LUAU_FASTINT(LuauTypeInferIterationLimit)
|
|||
LUAU_FASTINT(LuauTarjanChildLimit)
|
||||
LUAU_FASTFLAG(LuauInferInNoCheckMode)
|
||||
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSeparateTypechecks, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteDynamicLimits, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDirtySourceModule, false)
|
||||
LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -361,8 +362,6 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
|||
if (it != sourceNodes.end() && !it->second.hasDirtyModule(frontendOptions.forAutocomplete))
|
||||
{
|
||||
// No recheck required.
|
||||
if (FFlag::LuauSeparateTypechecks)
|
||||
{
|
||||
if (frontendOptions.forAutocomplete)
|
||||
{
|
||||
auto it2 = moduleResolverForAutocomplete.modules.find(name);
|
||||
|
@ -376,17 +375,8 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
|||
throw std::runtime_error("Frontend::modules does not have data for " + name);
|
||||
}
|
||||
|
||||
return CheckResult{accumulateErrors(
|
||||
sourceNodes, frontendOptions.forAutocomplete ? moduleResolverForAutocomplete.modules : moduleResolver.modules, name)};
|
||||
}
|
||||
else
|
||||
{
|
||||
auto it2 = moduleResolver.modules.find(name);
|
||||
if (it2 == moduleResolver.modules.end() || it2->second == nullptr)
|
||||
throw std::runtime_error("Frontend::modules does not have data for " + name);
|
||||
|
||||
return CheckResult{accumulateErrors(sourceNodes, moduleResolver.modules, name)};
|
||||
}
|
||||
return CheckResult{
|
||||
accumulateErrors(sourceNodes, frontendOptions.forAutocomplete ? moduleResolverForAutocomplete.modules : moduleResolver.modules, name)};
|
||||
}
|
||||
|
||||
std::vector<ModuleName> buildQueue;
|
||||
|
@ -428,7 +418,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
|||
// This is used by the type checker to replace the resulting type of cyclic modules with any
|
||||
sourceModule.cyclic = !requireCycles.empty();
|
||||
|
||||
if (FFlag::LuauSeparateTypechecks && frontendOptions.forAutocomplete)
|
||||
if (frontendOptions.forAutocomplete)
|
||||
{
|
||||
// The autocomplete typecheck is always in strict mode with DM awareness
|
||||
// to provide better type information for IDE features
|
||||
|
@ -483,18 +473,8 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
|||
|
||||
typeChecker.requireCycles = requireCycles;
|
||||
|
||||
ModulePtr module = typeChecker.check(sourceModule, mode, environmentScope);
|
||||
|
||||
// If we're typechecking twice, we do so.
|
||||
// The second typecheck is always in strict mode with DM awareness
|
||||
// to provide better typen information for IDE features.
|
||||
if (!FFlag::LuauSeparateTypechecks && frontendOptions.typecheckTwice_DEPRECATED)
|
||||
{
|
||||
typeCheckerForAutocomplete.requireCycles = requireCycles;
|
||||
|
||||
ModulePtr moduleForAutocomplete = typeCheckerForAutocomplete.check(sourceModule, Mode::Strict);
|
||||
moduleResolverForAutocomplete.modules[moduleName] = moduleForAutocomplete;
|
||||
}
|
||||
ModulePtr module = FFlag::DebugLuauDeferredConstraintResolution ? check(sourceModule, mode, environmentScope)
|
||||
: typeChecker.check(sourceModule, mode, environmentScope);
|
||||
|
||||
stats.timeCheck += getTimestamp() - timestamp;
|
||||
stats.filesStrict += mode == Mode::Strict;
|
||||
|
@ -563,7 +543,7 @@ bool Frontend::parseGraph(std::vector<ModuleName>& buildQueue, CheckResult& chec
|
|||
bool cyclic = false;
|
||||
|
||||
{
|
||||
auto [sourceNode, _] = getSourceNode(checkResult, root, forAutocomplete);
|
||||
auto [sourceNode, _] = getSourceNode(checkResult, root);
|
||||
if (sourceNode)
|
||||
stack.push_back(sourceNode);
|
||||
}
|
||||
|
@ -627,7 +607,7 @@ bool Frontend::parseGraph(std::vector<ModuleName>& buildQueue, CheckResult& chec
|
|||
}
|
||||
}
|
||||
|
||||
auto [sourceNode, _] = getSourceNode(checkResult, dep, forAutocomplete);
|
||||
auto [sourceNode, _] = getSourceNode(checkResult, dep);
|
||||
if (sourceNode)
|
||||
{
|
||||
stack.push_back(sourceNode);
|
||||
|
@ -671,7 +651,7 @@ LintResult Frontend::lint(const ModuleName& name, std::optional<Luau::LintOption
|
|||
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
|
||||
|
||||
CheckResult checkResult;
|
||||
auto [_sourceNode, sourceModule] = getSourceNode(checkResult, name, false);
|
||||
auto [_sourceNode, sourceModule] = getSourceNode(checkResult, name);
|
||||
|
||||
if (!sourceModule)
|
||||
return LintResult{}; // FIXME: We really should do something a bit more obvious when a file is too broken to lint.
|
||||
|
@ -751,17 +731,9 @@ bool Frontend::isDirty(const ModuleName& name, bool forAutocomplete) const
|
|||
* It would be nice for this function to be O(1)
|
||||
*/
|
||||
void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty)
|
||||
{
|
||||
if (FFlag::LuauSeparateTypechecks)
|
||||
{
|
||||
if (!moduleResolver.modules.count(name) && !moduleResolverForAutocomplete.modules.count(name))
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!moduleResolver.modules.count(name))
|
||||
return;
|
||||
}
|
||||
|
||||
std::unordered_map<ModuleName, std::vector<ModuleName>> reverseDeps;
|
||||
for (const auto& module : sourceNodes)
|
||||
|
@ -783,32 +755,12 @@ void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* marked
|
|||
if (markedDirty)
|
||||
markedDirty->push_back(next);
|
||||
|
||||
if (FFlag::LuauDirtySourceModule)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauSeparateTypechecks);
|
||||
|
||||
if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
|
||||
continue;
|
||||
|
||||
sourceNode.dirtySourceModule = true;
|
||||
sourceNode.dirtyModule = true;
|
||||
sourceNode.dirtyModuleForAutocomplete = true;
|
||||
}
|
||||
else if (FFlag::LuauSeparateTypechecks)
|
||||
{
|
||||
if (sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
|
||||
continue;
|
||||
|
||||
sourceNode.dirtyModule = true;
|
||||
sourceNode.dirtyModuleForAutocomplete = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sourceNode.dirtyModule)
|
||||
continue;
|
||||
|
||||
sourceNode.dirtyModule = true;
|
||||
}
|
||||
|
||||
if (0 == reverseDeps.count(name))
|
||||
continue;
|
||||
|
@ -834,15 +786,31 @@ const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) cons
|
|||
return const_cast<Frontend*>(this)->getSourceModule(moduleName);
|
||||
}
|
||||
|
||||
ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope)
|
||||
{
|
||||
ModulePtr result = std::make_shared<Module>();
|
||||
|
||||
ConstraintGraphBuilder cgb{&result->internalTypes};
|
||||
cgb.visit(sourceModule.root);
|
||||
|
||||
ConstraintSolver cs{&result->internalTypes, cgb.rootScope};
|
||||
cs.run();
|
||||
|
||||
result->scope2s = std::move(cgb.scopes);
|
||||
|
||||
result->clonePublicInterface(iceHandler);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Read AST into sourceModules if necessary. Trace require()s. Report parse errors.
|
||||
std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(CheckResult& checkResult, const ModuleName& name, bool forAutocomplete_DEPRECATED)
|
||||
std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(CheckResult& checkResult, const ModuleName& name)
|
||||
{
|
||||
LUAU_TIMETRACE_SCOPE("Frontend::getSourceNode", "Frontend");
|
||||
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
|
||||
|
||||
auto it = sourceNodes.find(name);
|
||||
if (it != sourceNodes.end() &&
|
||||
(FFlag::LuauDirtySourceModule ? !it->second.hasDirtySourceModule() : !it->second.hasDirtyModule(forAutocomplete_DEPRECATED)))
|
||||
if (it != sourceNodes.end() && !it->second.hasDirtySourceModule())
|
||||
{
|
||||
auto moduleIt = sourceModules.find(name);
|
||||
if (moduleIt != sourceModules.end())
|
||||
|
@ -885,22 +853,13 @@ std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(CheckResult& check
|
|||
sourceNode.name = name;
|
||||
sourceNode.requires.clear();
|
||||
sourceNode.requireLocations.clear();
|
||||
|
||||
if (FFlag::LuauDirtySourceModule)
|
||||
sourceNode.dirtySourceModule = false;
|
||||
|
||||
if (FFlag::LuauSeparateTypechecks)
|
||||
{
|
||||
if (it == sourceNodes.end())
|
||||
{
|
||||
sourceNode.dirtyModule = true;
|
||||
sourceNode.dirtyModuleForAutocomplete = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sourceNode.dirtyModule = true;
|
||||
}
|
||||
|
||||
for (const auto& [moduleName, location] : requireTrace.requires)
|
||||
sourceNode.requires.insert(moduleName);
|
||||
|
|
128
Analysis/src/Instantiation.cpp
Normal file
128
Analysis/src/Instantiation.cpp
Normal file
|
@ -0,0 +1,128 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Instantiation.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauNoMethodLocations)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
bool Instantiation::isDirty(TypeId ty)
|
||||
{
|
||||
if (const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty))
|
||||
{
|
||||
if (ftv->hasNoGenerics)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Instantiation::isDirty(TypePackId tp)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Instantiation::ignoreChildren(TypeId ty)
|
||||
{
|
||||
if (log->getMutable<FunctionTypeVar>(ty))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
TypeId Instantiation::clean(TypeId ty)
|
||||
{
|
||||
const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty);
|
||||
LUAU_ASSERT(ftv);
|
||||
|
||||
FunctionTypeVar clone = FunctionTypeVar{level, ftv->argTypes, ftv->retType, ftv->definition, ftv->hasSelf};
|
||||
clone.magicFunction = ftv->magicFunction;
|
||||
clone.tags = ftv->tags;
|
||||
clone.argNames = ftv->argNames;
|
||||
TypeId result = addType(std::move(clone));
|
||||
|
||||
// Annoyingly, we have to do this even if there are no generics,
|
||||
// to replace any generic tables.
|
||||
ReplaceGenerics replaceGenerics{log, arena, level, ftv->generics, ftv->genericPacks};
|
||||
|
||||
// TODO: What to do if this returns nullopt?
|
||||
// We don't have access to the error-reporting machinery
|
||||
result = replaceGenerics.substitute(result).value_or(result);
|
||||
|
||||
asMutable(result)->documentationSymbol = ty->documentationSymbol;
|
||||
return result;
|
||||
}
|
||||
|
||||
TypePackId Instantiation::clean(TypePackId tp)
|
||||
{
|
||||
LUAU_ASSERT(false);
|
||||
return tp;
|
||||
}
|
||||
|
||||
bool ReplaceGenerics::ignoreChildren(TypeId ty)
|
||||
{
|
||||
if (const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty))
|
||||
{
|
||||
if (ftv->hasNoGenerics)
|
||||
return true;
|
||||
|
||||
// We aren't recursing in the case of a generic function which
|
||||
// binds the same generics. This can happen if, for example, there's recursive types.
|
||||
// If T = <a>(a,T)->T then instantiating T should produce T' = (X,T)->T not T' = (X,T')->T'.
|
||||
// It's OK to use vector equality here, since we always generate fresh generics
|
||||
// whenever we quantify, so the vectors overlap if and only if they are equal.
|
||||
return (!generics.empty() || !genericPacks.empty()) && (ftv->generics == generics) && (ftv->genericPacks == genericPacks);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ReplaceGenerics::isDirty(TypeId ty)
|
||||
{
|
||||
if (const TableTypeVar* ttv = log->getMutable<TableTypeVar>(ty))
|
||||
return ttv->state == TableState::Generic;
|
||||
else if (log->getMutable<GenericTypeVar>(ty))
|
||||
return std::find(generics.begin(), generics.end(), ty) != generics.end();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ReplaceGenerics::isDirty(TypePackId tp)
|
||||
{
|
||||
if (log->getMutable<GenericTypePack>(tp))
|
||||
return std::find(genericPacks.begin(), genericPacks.end(), tp) != genericPacks.end();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
TypeId ReplaceGenerics::clean(TypeId ty)
|
||||
{
|
||||
LUAU_ASSERT(isDirty(ty));
|
||||
if (const TableTypeVar* ttv = log->getMutable<TableTypeVar>(ty))
|
||||
{
|
||||
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, level, TableState::Free};
|
||||
if (!FFlag::LuauNoMethodLocations)
|
||||
clone.methodDefinitionLocations = ttv->methodDefinitionLocations;
|
||||
clone.definitionModuleName = ttv->definitionModuleName;
|
||||
return addType(std::move(clone));
|
||||
}
|
||||
else
|
||||
return addType(FreeTypeVar{level});
|
||||
}
|
||||
|
||||
TypePackId ReplaceGenerics::clean(TypePackId tp)
|
||||
{
|
||||
LUAU_ASSERT(isDirty(tp));
|
||||
return addTypePack(TypePackVar(FreeTypePack{level}));
|
||||
}
|
||||
|
||||
} // namespace Luau
|
|
@ -48,7 +48,7 @@ static void errorToString(std::ostream& stream, const T& err)
|
|||
else if constexpr (std::is_same_v<T, FunctionDoesNotTakeSelf>)
|
||||
stream << "FunctionDoesNotTakeSelf { }";
|
||||
else if constexpr (std::is_same_v<T, FunctionRequiresSelf>)
|
||||
stream << "FunctionRequiresSelf { extraNils " << err.requiredExtraNils << " }";
|
||||
stream << "FunctionRequiresSelf { }";
|
||||
else if constexpr (std::is_same_v<T, OccursCheckFailed>)
|
||||
stream << "OccursCheckFailed { }";
|
||||
else if constexpr (std::is_same_v<T, UnknownRequire>)
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
|
||||
#include <vector>
|
||||
|
||||
LUAU_FASTFLAG(LuauTypecheckOptPass)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -79,27 +77,8 @@ std::optional<LValue> tryGetLValue(const AstExpr& node)
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::pair<Symbol, std::vector<std::string>> getFullName(const LValue& lvalue)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauTypecheckOptPass);
|
||||
|
||||
const LValue* current = &lvalue;
|
||||
std::vector<std::string> keys;
|
||||
while (auto field = get<Field>(*current))
|
||||
{
|
||||
keys.push_back(field->key);
|
||||
current = baseof(*current);
|
||||
}
|
||||
|
||||
const Symbol* symbol = get<Symbol>(*current);
|
||||
LUAU_ASSERT(symbol);
|
||||
return {*symbol, std::vector<std::string>(keys.rbegin(), keys.rend())};
|
||||
}
|
||||
|
||||
Symbol getBaseSymbol(const LValue& lvalue)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauTypecheckOptPass);
|
||||
|
||||
const LValue* current = &lvalue;
|
||||
while (auto field = get<Field>(*current))
|
||||
current = baseof(*current);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "Luau/Clone.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/ConstraintGraphBuilder.h"
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/RecursionCounter.h"
|
||||
#include "Luau/Scope.h"
|
||||
|
@ -10,12 +11,13 @@
|
|||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/VisitTypeVar.h"
|
||||
#include "Luau/ConstraintGraphBuilder.h" // FIXME: For Scope2 TODO pull out into its own header
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||
LUAU_FASTFLAG(LuauLosslessClone)
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||
LUAU_FASTFLAG(LuauNormalizeFlagIsConservative);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -55,89 +57,35 @@ bool isWithinComment(const SourceModule& sourceModule, Position pos)
|
|||
return contains(pos, *iter);
|
||||
}
|
||||
|
||||
void TypeArena::clear()
|
||||
struct ForceNormal : TypeVarOnceVisitor
|
||||
{
|
||||
const TypeArena* typeArena = nullptr;
|
||||
|
||||
ForceNormal(const TypeArena* typeArena)
|
||||
: typeArena(typeArena)
|
||||
{
|
||||
typeVars.clear();
|
||||
typePacks.clear();
|
||||
}
|
||||
|
||||
TypeId TypeArena::addTV(TypeVar&& tv)
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
TypeId allocated = typeVars.allocate(std::move(tv));
|
||||
if (ty->owningArena != typeArena)
|
||||
return false;
|
||||
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
asMutable(ty)->normal = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
TypeId TypeArena::freshType(TypeLevel level)
|
||||
bool visit(TypeId ty, const FreeTypeVar& ftv) override
|
||||
{
|
||||
TypeId allocated = typeVars.allocate(FreeTypeVar{level});
|
||||
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
visit(ty);
|
||||
return true;
|
||||
}
|
||||
|
||||
TypePackId TypeArena::addTypePack(std::initializer_list<TypeId> types)
|
||||
bool visit(TypePackId tp, const FreeTypePack& ftp) override
|
||||
{
|
||||
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
|
||||
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
}
|
||||
|
||||
TypePackId TypeArena::addTypePack(std::vector<TypeId> types)
|
||||
{
|
||||
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
|
||||
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
}
|
||||
|
||||
TypePackId TypeArena::addTypePack(TypePack tp)
|
||||
{
|
||||
TypePackId allocated = typePacks.allocate(std::move(tp));
|
||||
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
}
|
||||
|
||||
TypePackId TypeArena::addTypePack(TypePackVar tp)
|
||||
{
|
||||
TypePackId allocated = typePacks.allocate(std::move(tp));
|
||||
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
}
|
||||
|
||||
ScopePtr Module::getModuleScope() const
|
||||
{
|
||||
LUAU_ASSERT(!scopes.empty());
|
||||
return scopes.front().second;
|
||||
}
|
||||
|
||||
void freeze(TypeArena& arena)
|
||||
{
|
||||
if (!FFlag::DebugLuauFreezeArena)
|
||||
return;
|
||||
|
||||
arena.typeVars.freeze();
|
||||
arena.typePacks.freeze();
|
||||
}
|
||||
|
||||
void unfreeze(TypeArena& arena)
|
||||
{
|
||||
if (!FFlag::DebugLuauFreezeArena)
|
||||
return;
|
||||
|
||||
arena.typeVars.unfreeze();
|
||||
arena.typePacks.unfreeze();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
Module::~Module()
|
||||
{
|
||||
|
@ -145,34 +93,67 @@ Module::~Module()
|
|||
unfreeze(internalTypes);
|
||||
}
|
||||
|
||||
bool Module::clonePublicInterface(InternalErrorReporter& ice)
|
||||
void Module::clonePublicInterface(InternalErrorReporter& ice)
|
||||
{
|
||||
LUAU_ASSERT(interfaceTypes.typeVars.empty());
|
||||
LUAU_ASSERT(interfaceTypes.typePacks.empty());
|
||||
|
||||
CloneState cloneState;
|
||||
|
||||
ScopePtr moduleScope = getModuleScope();
|
||||
ScopePtr moduleScope = FFlag::DebugLuauDeferredConstraintResolution ? nullptr : getModuleScope();
|
||||
Scope2* moduleScope2 = FFlag::DebugLuauDeferredConstraintResolution ? getModuleScope2() : nullptr;
|
||||
|
||||
moduleScope->returnType = clone(moduleScope->returnType, interfaceTypes, cloneState);
|
||||
if (moduleScope->varargPack)
|
||||
moduleScope->varargPack = clone(*moduleScope->varargPack, interfaceTypes, cloneState);
|
||||
TypePackId returnType = FFlag::DebugLuauDeferredConstraintResolution ? moduleScope2->returnType : moduleScope->returnType;
|
||||
std::optional<TypePackId> varargPack = FFlag::DebugLuauDeferredConstraintResolution ? std::nullopt : moduleScope->varargPack;
|
||||
std::unordered_map<Name, TypeFun>* exportedTypeBindings =
|
||||
FFlag::DebugLuauDeferredConstraintResolution ? nullptr : &moduleScope->exportedTypeBindings;
|
||||
|
||||
returnType = clone(returnType, interfaceTypes, cloneState);
|
||||
|
||||
if (moduleScope)
|
||||
{
|
||||
moduleScope->returnType = returnType;
|
||||
if (varargPack)
|
||||
{
|
||||
varargPack = clone(*varargPack, interfaceTypes, cloneState);
|
||||
moduleScope->varargPack = varargPack;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(moduleScope2);
|
||||
moduleScope2->returnType = returnType; // TODO varargPack
|
||||
}
|
||||
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
{
|
||||
normalize(moduleScope->returnType, interfaceTypes, ice);
|
||||
if (moduleScope->varargPack)
|
||||
normalize(*moduleScope->varargPack, interfaceTypes, ice);
|
||||
normalize(returnType, interfaceTypes, ice);
|
||||
if (varargPack)
|
||||
normalize(*varargPack, interfaceTypes, ice);
|
||||
}
|
||||
|
||||
for (auto& [name, tf] : moduleScope->exportedTypeBindings)
|
||||
ForceNormal forceNormal{&interfaceTypes};
|
||||
|
||||
if (exportedTypeBindings)
|
||||
{
|
||||
for (auto& [name, tf] : *exportedTypeBindings)
|
||||
{
|
||||
tf = clone(tf, interfaceTypes, cloneState);
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
{
|
||||
normalize(tf.type, interfaceTypes, ice);
|
||||
|
||||
if (FFlag::LuauNormalizeFlagIsConservative)
|
||||
{
|
||||
// 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.
|
||||
forceNormal.traverse(tf.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (TypeId ty : moduleScope->returnType)
|
||||
for (TypeId ty : returnType)
|
||||
{
|
||||
if (get<GenericTypeVar>(follow(ty)))
|
||||
{
|
||||
|
@ -191,11 +172,18 @@ bool Module::clonePublicInterface(InternalErrorReporter& ice)
|
|||
|
||||
freeze(internalTypes);
|
||||
freeze(interfaceTypes);
|
||||
}
|
||||
|
||||
if (FFlag::LuauLosslessClone)
|
||||
return false; // TODO: make function return void.
|
||||
else
|
||||
return cloneState.encounteredFreeType;
|
||||
ScopePtr Module::getModuleScope() const
|
||||
{
|
||||
LUAU_ASSERT(!scopes.empty());
|
||||
return scopes.front().second;
|
||||
}
|
||||
|
||||
Scope2* Module::getModuleScope2() const
|
||||
{
|
||||
LUAU_ASSERT(!scope2s.empty());
|
||||
return scope2s.front().second.get();
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -14,6 +14,9 @@ LUAU_FASTFLAGVARIABLE(DebugLuauCopyBeforeNormalizing, false)
|
|||
// This could theoretically be 2000 on amd64, but x86 requires this.
|
||||
LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineTableFix, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeFlagIsConservative, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineEqFix, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauReplaceReplacer, false);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -228,6 +231,24 @@ struct Replacer : Substitution
|
|||
}
|
||||
|
||||
TypeId smartClone(TypeId t)
|
||||
{
|
||||
if (FFlag::LuauReplaceReplacer)
|
||||
{
|
||||
// The new smartClone is just a memoized clone()
|
||||
// TODO: Remove the Substitution base class and all other methods from this struct.
|
||||
// Add DenseHashMap<TypeId, TypeId> newTypes;
|
||||
t = log->follow(t);
|
||||
TypeId* res = newTypes.find(t);
|
||||
if (res)
|
||||
return *res;
|
||||
|
||||
TypeId result = shallowClone(t, *arena, TxnLog::empty());
|
||||
newTypes[t] = result;
|
||||
newTypes[result] = result;
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::optional<TypeId> res = replace(t);
|
||||
LUAU_ASSERT(res.has_value()); // TODO think about this
|
||||
|
@ -235,6 +256,7 @@ struct Replacer : Substitution
|
|||
return clone(t);
|
||||
return *res;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
@ -260,8 +282,13 @@ static bool areNormal_(const T& t, const std::unordered_set<void*>& seen, Intern
|
|||
if (count >= FInt::LuauNormalizeIterationLimit)
|
||||
ice.ice("Luau::areNormal hit iteration limit");
|
||||
|
||||
if (FFlag::LuauNormalizeFlagIsConservative)
|
||||
return ty->normal;
|
||||
else
|
||||
{
|
||||
// The follow is here because a bound type may not be normal, but the bound type is normal.
|
||||
return ty->normal || follow(ty)->normal || seen.find(asMutable(ty)) != seen.end();
|
||||
}
|
||||
};
|
||||
|
||||
return std::all_of(begin(t), end(t), isNormal);
|
||||
|
@ -319,245 +346,6 @@ struct Normalize final : TypeVarVisitor
|
|||
int iterationLimit = 0;
|
||||
bool limitExceeded = false;
|
||||
|
||||
// TODO: Clip with FFlag::LuauUseVisitRecursionLimit
|
||||
bool operator()(TypeId ty, const BoundTypeVar& btv, std::unordered_set<void*>& seen)
|
||||
{
|
||||
// A type could be considered normal when it is in the stack, but we will eventually find out it is not normal as normalization progresses.
|
||||
// So we need to avoid eagerly saying that this bound type is normal if the thing it is bound to is in the stack.
|
||||
if (seen.find(asMutable(btv.boundTo)) != seen.end())
|
||||
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.
|
||||
LUAU_ASSERT(!ty->normal || ty->normal == btv.boundTo->normal);
|
||||
|
||||
asMutable(ty)->normal = btv.boundTo->normal;
|
||||
return !ty->normal;
|
||||
}
|
||||
|
||||
bool operator()(TypeId ty, const FreeTypeVar& ftv)
|
||||
{
|
||||
return visit(ty, ftv);
|
||||
}
|
||||
bool operator()(TypeId ty, const PrimitiveTypeVar& ptv)
|
||||
{
|
||||
return visit(ty, ptv);
|
||||
}
|
||||
bool operator()(TypeId ty, const GenericTypeVar& gtv)
|
||||
{
|
||||
return visit(ty, gtv);
|
||||
}
|
||||
bool operator()(TypeId ty, const ErrorTypeVar& etv)
|
||||
{
|
||||
return visit(ty, etv);
|
||||
}
|
||||
bool operator()(TypeId ty, const ConstrainedTypeVar& ctvRef, std::unordered_set<void*>& seen)
|
||||
{
|
||||
CHECK_ITERATION_LIMIT(false);
|
||||
|
||||
ConstrainedTypeVar* ctv = const_cast<ConstrainedTypeVar*>(&ctvRef);
|
||||
|
||||
std::vector<TypeId> parts = std::move(ctv->parts);
|
||||
|
||||
// We might transmute, so it's not safe to rely on the builtin traversal logic of visitTypeVar
|
||||
for (TypeId part : parts)
|
||||
visit_detail::visit(part, *this, seen);
|
||||
|
||||
std::vector<TypeId> newParts = normalizeUnion(parts);
|
||||
|
||||
const bool normal = areNormal(newParts, seen, ice);
|
||||
|
||||
if (newParts.size() == 1)
|
||||
*asMutable(ty) = BoundTypeVar{newParts[0]};
|
||||
else
|
||||
*asMutable(ty) = UnionTypeVar{std::move(newParts)};
|
||||
|
||||
asMutable(ty)->normal = normal;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator()(TypeId ty, const FunctionTypeVar& ftv, std::unordered_set<void*>& seen)
|
||||
{
|
||||
CHECK_ITERATION_LIMIT(false);
|
||||
|
||||
if (ty->normal)
|
||||
return false;
|
||||
|
||||
visit_detail::visit(ftv.argTypes, *this, seen);
|
||||
visit_detail::visit(ftv.retType, *this, seen);
|
||||
|
||||
asMutable(ty)->normal = areNormal(ftv.argTypes, seen, ice) && areNormal(ftv.retType, seen, ice);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator()(TypeId ty, const TableTypeVar& ttv, std::unordered_set<void*>& seen)
|
||||
{
|
||||
CHECK_ITERATION_LIMIT(false);
|
||||
|
||||
if (ty->normal)
|
||||
return false;
|
||||
|
||||
bool normal = true;
|
||||
|
||||
auto checkNormal = [&](TypeId t) {
|
||||
// if t is on the stack, it is possible that this type is normal.
|
||||
// If t is not normal and it is not on the stack, this type is definitely not normal.
|
||||
if (!t->normal && seen.find(asMutable(t)) == seen.end())
|
||||
normal = false;
|
||||
};
|
||||
|
||||
if (ttv.boundTo)
|
||||
{
|
||||
visit_detail::visit(*ttv.boundTo, *this, seen);
|
||||
asMutable(ty)->normal = (*ttv.boundTo)->normal;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& [_name, prop] : ttv.props)
|
||||
{
|
||||
visit_detail::visit(prop.type, *this, seen);
|
||||
checkNormal(prop.type);
|
||||
}
|
||||
|
||||
if (ttv.indexer)
|
||||
{
|
||||
visit_detail::visit(ttv.indexer->indexType, *this, seen);
|
||||
checkNormal(ttv.indexer->indexType);
|
||||
visit_detail::visit(ttv.indexer->indexResultType, *this, seen);
|
||||
checkNormal(ttv.indexer->indexResultType);
|
||||
}
|
||||
|
||||
asMutable(ty)->normal = normal;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator()(TypeId ty, const MetatableTypeVar& mtv, std::unordered_set<void*>& seen)
|
||||
{
|
||||
CHECK_ITERATION_LIMIT(false);
|
||||
|
||||
if (ty->normal)
|
||||
return false;
|
||||
|
||||
visit_detail::visit(mtv.table, *this, seen);
|
||||
visit_detail::visit(mtv.metatable, *this, seen);
|
||||
|
||||
asMutable(ty)->normal = mtv.table->normal && mtv.metatable->normal;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator()(TypeId ty, const ClassTypeVar& ctv)
|
||||
{
|
||||
return visit(ty, ctv);
|
||||
}
|
||||
bool operator()(TypeId ty, const AnyTypeVar& atv)
|
||||
{
|
||||
return visit(ty, atv);
|
||||
}
|
||||
bool operator()(TypeId ty, const UnionTypeVar& utvRef, std::unordered_set<void*>& seen)
|
||||
{
|
||||
CHECK_ITERATION_LIMIT(false);
|
||||
|
||||
if (ty->normal)
|
||||
return false;
|
||||
|
||||
UnionTypeVar* utv = &const_cast<UnionTypeVar&>(utvRef);
|
||||
std::vector<TypeId> options = std::move(utv->options);
|
||||
|
||||
// We might transmute, so it's not safe to rely on the builtin traversal logic of visitTypeVar
|
||||
for (TypeId option : options)
|
||||
visit_detail::visit(option, *this, seen);
|
||||
|
||||
std::vector<TypeId> newOptions = normalizeUnion(options);
|
||||
|
||||
const bool normal = areNormal(newOptions, seen, ice);
|
||||
|
||||
LUAU_ASSERT(!newOptions.empty());
|
||||
|
||||
if (newOptions.size() == 1)
|
||||
*asMutable(ty) = BoundTypeVar{newOptions[0]};
|
||||
else
|
||||
utv->options = std::move(newOptions);
|
||||
|
||||
asMutable(ty)->normal = normal;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator()(TypeId ty, const IntersectionTypeVar& itvRef, std::unordered_set<void*>& seen)
|
||||
{
|
||||
CHECK_ITERATION_LIMIT(false);
|
||||
|
||||
if (ty->normal)
|
||||
return false;
|
||||
|
||||
IntersectionTypeVar* itv = &const_cast<IntersectionTypeVar&>(itvRef);
|
||||
|
||||
std::vector<TypeId> oldParts = std::move(itv->parts);
|
||||
|
||||
for (TypeId part : oldParts)
|
||||
visit_detail::visit(part, *this, seen);
|
||||
|
||||
std::vector<TypeId> tables;
|
||||
for (TypeId part : oldParts)
|
||||
{
|
||||
part = follow(part);
|
||||
if (get<TableTypeVar>(part))
|
||||
tables.push_back(part);
|
||||
else
|
||||
{
|
||||
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.
|
||||
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});
|
||||
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);
|
||||
}
|
||||
|
||||
asMutable(ty)->normal = areNormal(itv->parts, seen, ice);
|
||||
|
||||
if (itv->parts.size() == 1)
|
||||
{
|
||||
TypeId part = itv->parts[0];
|
||||
*asMutable(ty) = BoundTypeVar{part};
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Clip with FFlag::LuauUseVisitRecursionLimit
|
||||
template<typename T>
|
||||
bool operator()(TypePackId, const T&)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Clip with FFlag::LuauUseVisitRecursionLimit
|
||||
template<typename TID>
|
||||
void cycle(TID)
|
||||
{
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const FreeTypeVar&) override
|
||||
{
|
||||
LUAU_ASSERT(!ty->normal);
|
||||
|
@ -962,6 +750,9 @@ struct Normalize final : TypeVarVisitor
|
|||
*/
|
||||
TypeId combine(Replacer& replacer, TypeId a, TypeId b)
|
||||
{
|
||||
if (FFlag::LuauNormalizeCombineEqFix)
|
||||
b = follow(b);
|
||||
|
||||
if (FFlag::LuauNormalizeCombineTableFix && a == b)
|
||||
return a;
|
||||
|
||||
|
@ -980,7 +771,7 @@ struct Normalize final : TypeVarVisitor
|
|||
}
|
||||
else if (auto ttv = getMutable<TableTypeVar>(a))
|
||||
{
|
||||
if (FFlag::LuauNormalizeCombineTableFix && !get<TableTypeVar>(follow(b)))
|
||||
if (FFlag::LuauNormalizeCombineTableFix && !get<TableTypeVar>(FFlag::LuauNormalizeCombineEqFix ? b : follow(b)))
|
||||
return arena.addType(IntersectionTypeVar{{a, b}});
|
||||
combineIntoTable(replacer, ttv, b);
|
||||
return a;
|
||||
|
@ -1003,8 +794,7 @@ std::pair<TypeId, bool> normalize(TypeId ty, TypeArena& arena, InternalErrorRepo
|
|||
(void)clone(ty, arena, state);
|
||||
|
||||
Normalize n{arena, ice};
|
||||
std::unordered_set<void*> seen;
|
||||
DEPRECATED_visitTypeVar(ty, n, seen);
|
||||
n.traverse(ty);
|
||||
|
||||
return {ty, !n.limitExceeded};
|
||||
}
|
||||
|
@ -1028,8 +818,7 @@ std::pair<TypePackId, bool> normalize(TypePackId tp, TypeArena& arena, InternalE
|
|||
(void)clone(tp, arena, state);
|
||||
|
||||
Normalize n{arena, ice};
|
||||
std::unordered_set<void*> seen;
|
||||
DEPRECATED_visitTypeVar(tp, n, seen);
|
||||
n.traverse(tp);
|
||||
|
||||
return {tp, !n.limitExceeded};
|
||||
}
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
#include "Luau/Quantify.h"
|
||||
|
||||
#include "Luau/VisitTypeVar.h"
|
||||
#include "Luau/ConstraintGraphBuilder.h" // TODO for Scope2; move to separate header
|
||||
|
||||
LUAU_FASTFLAG(LuauTypecheckOptPass)
|
||||
LUAU_FASTFLAG(LuauAlwaysQuantify);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -14,12 +16,20 @@ struct Quantifier final : TypeVarOnceVisitor
|
|||
TypeLevel level;
|
||||
std::vector<TypeId> generics;
|
||||
std::vector<TypePackId> genericPacks;
|
||||
Scope2* scope = nullptr;
|
||||
bool seenGenericType = false;
|
||||
bool seenMutableType = false;
|
||||
|
||||
explicit Quantifier(TypeLevel level)
|
||||
: level(level)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::DebugLuauDeferredConstraintResolution);
|
||||
}
|
||||
|
||||
explicit Quantifier(Scope2* scope)
|
||||
: scope(scope)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution);
|
||||
}
|
||||
|
||||
void cycle(TypeId) override {}
|
||||
|
@ -57,15 +67,31 @@ struct Quantifier final : TypeVarOnceVisitor
|
|||
return visit(tp, ftp);
|
||||
}
|
||||
|
||||
/// @return true if outer encloses inner
|
||||
bool subsumes(Scope2* outer, Scope2* inner)
|
||||
{
|
||||
while (inner)
|
||||
{
|
||||
if (inner == outer)
|
||||
return true;
|
||||
inner = inner->parent;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const FreeTypeVar& ftv) override
|
||||
{
|
||||
if (FFlag::LuauTypecheckOptPass)
|
||||
seenMutableType = true;
|
||||
|
||||
if (!level.subsumes(ftv.level))
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ftv.scope) : !level.subsumes(ftv.level))
|
||||
return false;
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
*asMutable(ty) = GenericTypeVar{scope};
|
||||
else
|
||||
*asMutable(ty) = GenericTypeVar{level};
|
||||
|
||||
generics.push_back(ty);
|
||||
|
||||
return false;
|
||||
|
@ -76,20 +102,17 @@ struct Quantifier final : TypeVarOnceVisitor
|
|||
LUAU_ASSERT(getMutable<TableTypeVar>(ty));
|
||||
TableTypeVar& ttv = *getMutable<TableTypeVar>(ty);
|
||||
|
||||
if (FFlag::LuauTypecheckOptPass)
|
||||
{
|
||||
if (ttv.state == TableState::Generic)
|
||||
seenGenericType = true;
|
||||
|
||||
if (ttv.state == TableState::Free)
|
||||
seenMutableType = true;
|
||||
}
|
||||
|
||||
if (ttv.state == TableState::Sealed || ttv.state == TableState::Generic)
|
||||
return false;
|
||||
if (!level.subsumes(ttv.level))
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ttv.scope) : !level.subsumes(ttv.level))
|
||||
{
|
||||
if (FFlag::LuauTypecheckOptPass && ttv.state == TableState::Unsealed)
|
||||
if (ttv.state == TableState::Unsealed)
|
||||
seenMutableType = true;
|
||||
return false;
|
||||
}
|
||||
|
@ -97,8 +120,6 @@ struct Quantifier final : TypeVarOnceVisitor
|
|||
if (ttv.state == TableState::Free)
|
||||
{
|
||||
ttv.state = TableState::Generic;
|
||||
|
||||
if (FFlag::LuauTypecheckOptPass)
|
||||
seenGenericType = true;
|
||||
}
|
||||
else if (ttv.state == TableState::Unsealed)
|
||||
|
@ -111,10 +132,9 @@ struct Quantifier final : TypeVarOnceVisitor
|
|||
|
||||
bool visit(TypePackId tp, const FreeTypePack& ftp) override
|
||||
{
|
||||
if (FFlag::LuauTypecheckOptPass)
|
||||
seenMutableType = true;
|
||||
|
||||
if (!level.subsumes(ftp.level))
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ftp.scope) : !level.subsumes(ftp.level))
|
||||
return false;
|
||||
|
||||
*asMutable(tp) = GenericTypePack{level};
|
||||
|
@ -126,16 +146,49 @@ struct Quantifier final : TypeVarOnceVisitor
|
|||
void quantify(TypeId ty, TypeLevel level)
|
||||
{
|
||||
Quantifier q{level};
|
||||
DenseHashSet<void*> seen{nullptr};
|
||||
DEPRECATED_visitTypeVarOnce(ty, q, seen);
|
||||
q.traverse(ty);
|
||||
|
||||
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty);
|
||||
LUAU_ASSERT(ftv);
|
||||
if (FFlag::LuauAlwaysQuantify)
|
||||
{
|
||||
ftv->generics.insert(ftv->generics.end(), q.generics.begin(), q.generics.end());
|
||||
ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
ftv->generics = q.generics;
|
||||
ftv->genericPacks = q.genericPacks;
|
||||
}
|
||||
|
||||
if (FFlag::LuauTypecheckOptPass && ftv->generics.empty() && ftv->genericPacks.empty() && !q.seenMutableType && !q.seenGenericType)
|
||||
if (ftv->generics.empty() && ftv->genericPacks.empty() && !q.seenMutableType && !q.seenGenericType)
|
||||
ftv->hasNoGenerics = true;
|
||||
|
||||
ftv->generalized = true;
|
||||
}
|
||||
|
||||
void quantify(TypeId ty, Scope2* scope)
|
||||
{
|
||||
Quantifier q{scope};
|
||||
q.traverse(ty);
|
||||
|
||||
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty);
|
||||
LUAU_ASSERT(ftv);
|
||||
if (FFlag::LuauAlwaysQuantify)
|
||||
{
|
||||
ftv->generics.insert(ftv->generics.end(), q.generics.begin(), q.generics.end());
|
||||
ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
ftv->generics = q.generics;
|
||||
ftv->genericPacks = q.genericPacks;
|
||||
}
|
||||
|
||||
if (ftv->generics.empty() && ftv->genericPacks.empty() && !q.seenMutableType && !q.seenGenericType)
|
||||
ftv->hasNoGenerics = true;
|
||||
|
||||
ftv->generalized = true;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "Luau/Substitution.h"
|
||||
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Clone.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
@ -9,9 +10,6 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
||||
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
|
||||
LUAU_FASTFLAG(LuauTypecheckOptPass)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSubstituteFollowNewTypes, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSubstituteFollowPossibleMutations, false)
|
||||
LUAU_FASTFLAG(LuauNoMethodLocations)
|
||||
|
||||
namespace Luau
|
||||
|
@ -19,26 +17,20 @@ namespace Luau
|
|||
|
||||
void Tarjan::visitChildren(TypeId ty, int index)
|
||||
{
|
||||
if (FFlag::LuauTypecheckOptPass)
|
||||
LUAU_ASSERT(ty == log->follow(ty));
|
||||
else
|
||||
ty = log->follow(ty);
|
||||
|
||||
if (ignoreChildren(ty))
|
||||
return;
|
||||
|
||||
if (FFlag::LuauTypecheckOptPass)
|
||||
{
|
||||
if (auto pty = log->pending(ty))
|
||||
ty = &pty->pending;
|
||||
}
|
||||
|
||||
if (const FunctionTypeVar* ftv = FFlag::LuauTypecheckOptPass ? get<FunctionTypeVar>(ty) : log->getMutable<FunctionTypeVar>(ty))
|
||||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
|
||||
{
|
||||
visitChild(ftv->argTypes);
|
||||
visitChild(ftv->retType);
|
||||
}
|
||||
else if (const TableTypeVar* ttv = FFlag::LuauTypecheckOptPass ? get<TableTypeVar>(ty) : log->getMutable<TableTypeVar>(ty))
|
||||
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
|
||||
{
|
||||
LUAU_ASSERT(!ttv->boundTo);
|
||||
for (const auto& [name, prop] : ttv->props)
|
||||
|
@ -55,17 +47,17 @@ void Tarjan::visitChildren(TypeId ty, int index)
|
|||
for (TypePackId itp : ttv->instantiatedTypePackParams)
|
||||
visitChild(itp);
|
||||
}
|
||||
else if (const MetatableTypeVar* mtv = FFlag::LuauTypecheckOptPass ? get<MetatableTypeVar>(ty) : log->getMutable<MetatableTypeVar>(ty))
|
||||
else if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(ty))
|
||||
{
|
||||
visitChild(mtv->table);
|
||||
visitChild(mtv->metatable);
|
||||
}
|
||||
else if (const UnionTypeVar* utv = FFlag::LuauTypecheckOptPass ? get<UnionTypeVar>(ty) : log->getMutable<UnionTypeVar>(ty))
|
||||
else if (const UnionTypeVar* utv = get<UnionTypeVar>(ty))
|
||||
{
|
||||
for (TypeId opt : utv->options)
|
||||
visitChild(opt);
|
||||
}
|
||||
else if (const IntersectionTypeVar* itv = FFlag::LuauTypecheckOptPass ? get<IntersectionTypeVar>(ty) : log->getMutable<IntersectionTypeVar>(ty))
|
||||
else if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(ty))
|
||||
{
|
||||
for (TypeId part : itv->parts)
|
||||
visitChild(part);
|
||||
|
@ -79,28 +71,22 @@ void Tarjan::visitChildren(TypeId ty, int index)
|
|||
|
||||
void Tarjan::visitChildren(TypePackId tp, int index)
|
||||
{
|
||||
if (FFlag::LuauTypecheckOptPass)
|
||||
LUAU_ASSERT(tp == log->follow(tp));
|
||||
else
|
||||
tp = log->follow(tp);
|
||||
|
||||
if (ignoreChildren(tp))
|
||||
return;
|
||||
|
||||
if (FFlag::LuauTypecheckOptPass)
|
||||
{
|
||||
if (auto ptp = log->pending(tp))
|
||||
tp = &ptp->pending;
|
||||
}
|
||||
|
||||
if (const TypePack* tpp = FFlag::LuauTypecheckOptPass ? get<TypePack>(tp) : log->getMutable<TypePack>(tp))
|
||||
if (const TypePack* tpp = get<TypePack>(tp))
|
||||
{
|
||||
for (TypeId tv : tpp->head)
|
||||
visitChild(tv);
|
||||
if (tpp->tail)
|
||||
visitChild(*tpp->tail);
|
||||
}
|
||||
else if (const VariadicTypePack* vtp = FFlag::LuauTypecheckOptPass ? get<VariadicTypePack>(tp) : log->getMutable<VariadicTypePack>(tp))
|
||||
else if (const VariadicTypePack* vtp = get<VariadicTypePack>(tp))
|
||||
{
|
||||
visitChild(vtp->ty);
|
||||
}
|
||||
|
@ -108,9 +94,6 @@ void Tarjan::visitChildren(TypePackId tp, int index)
|
|||
|
||||
std::pair<int, bool> Tarjan::indexify(TypeId ty)
|
||||
{
|
||||
if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations)
|
||||
LUAU_ASSERT(ty == log->follow(ty));
|
||||
else
|
||||
ty = log->follow(ty);
|
||||
|
||||
bool fresh = !typeToIndex.contains(ty);
|
||||
|
@ -129,9 +112,6 @@ std::pair<int, bool> Tarjan::indexify(TypeId ty)
|
|||
|
||||
std::pair<int, bool> Tarjan::indexify(TypePackId tp)
|
||||
{
|
||||
if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations)
|
||||
LUAU_ASSERT(tp == log->follow(tp));
|
||||
else
|
||||
tp = log->follow(tp);
|
||||
|
||||
bool fresh = !packToIndex.contains(tp);
|
||||
|
@ -150,7 +130,6 @@ std::pair<int, bool> Tarjan::indexify(TypePackId tp)
|
|||
|
||||
void Tarjan::visitChild(TypeId ty)
|
||||
{
|
||||
if (!FFlag::LuauSubstituteFollowPossibleMutations)
|
||||
ty = log->follow(ty);
|
||||
|
||||
edgesTy.push_back(ty);
|
||||
|
@ -159,7 +138,6 @@ void Tarjan::visitChild(TypeId ty)
|
|||
|
||||
void Tarjan::visitChild(TypePackId tp)
|
||||
{
|
||||
if (!FFlag::LuauSubstituteFollowPossibleMutations)
|
||||
tp = log->follow(tp);
|
||||
|
||||
edgesTy.push_back(nullptr);
|
||||
|
@ -385,86 +363,24 @@ std::optional<TypePackId> Substitution::substitute(TypePackId tp)
|
|||
|
||||
TypeId Substitution::clone(TypeId ty)
|
||||
{
|
||||
ty = log->follow(ty);
|
||||
|
||||
TypeId result = ty;
|
||||
|
||||
if (FFlag::LuauTypecheckOptPass)
|
||||
{
|
||||
if (auto pty = log->pending(ty))
|
||||
ty = &pty->pending;
|
||||
}
|
||||
|
||||
if (const FunctionTypeVar* ftv = FFlag::LuauTypecheckOptPass ? get<FunctionTypeVar>(ty) : log->getMutable<FunctionTypeVar>(ty))
|
||||
{
|
||||
FunctionTypeVar clone = FunctionTypeVar{ftv->level, ftv->argTypes, ftv->retType, ftv->definition, ftv->hasSelf};
|
||||
clone.generics = ftv->generics;
|
||||
clone.genericPacks = ftv->genericPacks;
|
||||
clone.magicFunction = ftv->magicFunction;
|
||||
clone.tags = ftv->tags;
|
||||
clone.argNames = ftv->argNames;
|
||||
result = addType(std::move(clone));
|
||||
}
|
||||
else if (const TableTypeVar* ttv = FFlag::LuauTypecheckOptPass ? get<TableTypeVar>(ty) : log->getMutable<TableTypeVar>(ty))
|
||||
{
|
||||
LUAU_ASSERT(!ttv->boundTo);
|
||||
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state};
|
||||
if (!FFlag::LuauNoMethodLocations)
|
||||
clone.methodDefinitionLocations = ttv->methodDefinitionLocations;
|
||||
clone.definitionModuleName = ttv->definitionModuleName;
|
||||
clone.name = ttv->name;
|
||||
clone.syntheticName = ttv->syntheticName;
|
||||
clone.instantiatedTypeParams = ttv->instantiatedTypeParams;
|
||||
clone.instantiatedTypePackParams = ttv->instantiatedTypePackParams;
|
||||
clone.tags = ttv->tags;
|
||||
result = addType(std::move(clone));
|
||||
}
|
||||
else if (const MetatableTypeVar* mtv = FFlag::LuauTypecheckOptPass ? get<MetatableTypeVar>(ty) : log->getMutable<MetatableTypeVar>(ty))
|
||||
{
|
||||
MetatableTypeVar clone = MetatableTypeVar{mtv->table, mtv->metatable};
|
||||
clone.syntheticName = mtv->syntheticName;
|
||||
result = addType(std::move(clone));
|
||||
}
|
||||
else if (const UnionTypeVar* utv = FFlag::LuauTypecheckOptPass ? get<UnionTypeVar>(ty) : log->getMutable<UnionTypeVar>(ty))
|
||||
{
|
||||
UnionTypeVar clone;
|
||||
clone.options = utv->options;
|
||||
result = addType(std::move(clone));
|
||||
}
|
||||
else if (const IntersectionTypeVar* itv = FFlag::LuauTypecheckOptPass ? get<IntersectionTypeVar>(ty) : log->getMutable<IntersectionTypeVar>(ty))
|
||||
{
|
||||
IntersectionTypeVar clone;
|
||||
clone.parts = itv->parts;
|
||||
result = addType(std::move(clone));
|
||||
}
|
||||
else if (const ConstrainedTypeVar* ctv = get<ConstrainedTypeVar>(ty))
|
||||
{
|
||||
ConstrainedTypeVar clone{ctv->level, ctv->parts};
|
||||
result = addType(std::move(clone));
|
||||
}
|
||||
|
||||
asMutable(result)->documentationSymbol = ty->documentationSymbol;
|
||||
return result;
|
||||
return shallowClone(ty, *arena, log);
|
||||
}
|
||||
|
||||
TypePackId Substitution::clone(TypePackId tp)
|
||||
{
|
||||
tp = log->follow(tp);
|
||||
|
||||
if (FFlag::LuauTypecheckOptPass)
|
||||
{
|
||||
if (auto ptp = log->pending(tp))
|
||||
tp = &ptp->pending;
|
||||
}
|
||||
|
||||
if (const TypePack* tpp = FFlag::LuauTypecheckOptPass ? get<TypePack>(tp) : log->getMutable<TypePack>(tp))
|
||||
if (const TypePack* tpp = get<TypePack>(tp))
|
||||
{
|
||||
TypePack clone;
|
||||
clone.head = tpp->head;
|
||||
clone.tail = tpp->tail;
|
||||
return addTypePack(std::move(clone));
|
||||
}
|
||||
else if (const VariadicTypePack* vtp = FFlag::LuauTypecheckOptPass ? get<VariadicTypePack>(tp) : log->getMutable<VariadicTypePack>(tp))
|
||||
else if (const VariadicTypePack* vtp = get<VariadicTypePack>(tp))
|
||||
{
|
||||
VariadicTypePack clone;
|
||||
clone.ty = vtp->ty;
|
||||
|
@ -476,28 +392,22 @@ TypePackId Substitution::clone(TypePackId tp)
|
|||
|
||||
void Substitution::foundDirty(TypeId ty)
|
||||
{
|
||||
if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations)
|
||||
LUAU_ASSERT(ty == log->follow(ty));
|
||||
else
|
||||
ty = log->follow(ty);
|
||||
|
||||
if (isDirty(ty))
|
||||
newTypes[ty] = FFlag::LuauSubstituteFollowNewTypes ? follow(clean(ty)) : clean(ty);
|
||||
newTypes[ty] = follow(clean(ty));
|
||||
else
|
||||
newTypes[ty] = FFlag::LuauSubstituteFollowNewTypes ? follow(clone(ty)) : clone(ty);
|
||||
newTypes[ty] = follow(clone(ty));
|
||||
}
|
||||
|
||||
void Substitution::foundDirty(TypePackId tp)
|
||||
{
|
||||
if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations)
|
||||
LUAU_ASSERT(tp == log->follow(tp));
|
||||
else
|
||||
tp = log->follow(tp);
|
||||
|
||||
if (isDirty(tp))
|
||||
newPacks[tp] = FFlag::LuauSubstituteFollowNewTypes ? follow(clean(tp)) : clean(tp);
|
||||
newPacks[tp] = follow(clean(tp));
|
||||
else
|
||||
newPacks[tp] = FFlag::LuauSubstituteFollowNewTypes ? follow(clone(tp)) : clone(tp);
|
||||
newPacks[tp] = follow(clone(tp));
|
||||
}
|
||||
|
||||
TypeId Substitution::replace(TypeId ty)
|
||||
|
@ -525,10 +435,7 @@ void Substitution::replaceChildren(TypeId ty)
|
|||
if (BoundTypeVar* btv = log->getMutable<BoundTypeVar>(ty); FFlag::LuauLowerBoundsCalculation && btv)
|
||||
btv->boundTo = replace(btv->boundTo);
|
||||
|
||||
if (FFlag::LuauTypecheckOptPass)
|
||||
LUAU_ASSERT(ty == log->follow(ty));
|
||||
else
|
||||
ty = log->follow(ty);
|
||||
|
||||
if (ignoreChildren(ty))
|
||||
return;
|
||||
|
@ -579,10 +486,7 @@ void Substitution::replaceChildren(TypeId ty)
|
|||
|
||||
void Substitution::replaceChildren(TypePackId tp)
|
||||
{
|
||||
if (FFlag::LuauTypecheckOptPass)
|
||||
LUAU_ASSERT(tp == log->follow(tp));
|
||||
else
|
||||
tp = log->follow(tp);
|
||||
|
||||
if (ignoreChildren(tp))
|
||||
return;
|
||||
|
|
|
@ -18,7 +18,6 @@ LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
|||
* Fair warning: Setting this will break a lot of Luau unit tests.
|
||||
*/
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauVerboseTypeNames, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDocFuncParameters, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauToStringTableBracesNewlines, false)
|
||||
|
||||
namespace Luau
|
||||
|
@ -49,46 +48,6 @@ struct FindCyclicTypes final : TypeVarVisitor
|
|||
cycleTPs.insert(tp);
|
||||
}
|
||||
|
||||
// TODO: Clip all the operator()s when we clip FFlagLuauUseVisitRecursionLimit
|
||||
|
||||
template<typename T>
|
||||
bool operator()(TypeId ty, const T&)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
|
||||
bool operator()(TypeId ty, const TableTypeVar& ttv) = delete;
|
||||
|
||||
bool operator()(TypeId ty, const TableTypeVar& ttv, std::unordered_set<void*>& seen)
|
||||
{
|
||||
if (!visited.insert(ty).second)
|
||||
return false;
|
||||
|
||||
if (ttv.name || ttv.syntheticName)
|
||||
{
|
||||
for (TypeId itp : ttv.instantiatedTypeParams)
|
||||
DEPRECATED_visitTypeVar(itp, *this, seen);
|
||||
|
||||
for (TypePackId itp : ttv.instantiatedTypePackParams)
|
||||
DEPRECATED_visitTypeVar(itp, *this, seen);
|
||||
|
||||
return exhaustive;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator()(TypeId, const ClassTypeVar&)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool operator()(TypePackId tp, const T&)
|
||||
{
|
||||
return visit(tp);
|
||||
}
|
||||
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
return visited.insert(ty).second;
|
||||
|
@ -129,7 +88,7 @@ void findCyclicTypes(std::set<TypeId>& cycles, std::set<TypePackId>& cycleTPs, T
|
|||
{
|
||||
FindCyclicTypes fct;
|
||||
fct.exhaustive = exhaustive;
|
||||
DEPRECATED_visitTypeVar(ty, fct);
|
||||
fct.traverse(ty);
|
||||
|
||||
cycles = std::move(fct.cycles);
|
||||
cycleTPs = std::move(fct.cycleTPs);
|
||||
|
@ -220,6 +179,8 @@ struct StringifierState
|
|||
return generateName(s);
|
||||
}
|
||||
|
||||
int previousNameIndex = 0;
|
||||
|
||||
std::string getName(TypePackId ty)
|
||||
{
|
||||
const size_t s = result.nameMap.typePacks.size();
|
||||
|
@ -229,9 +190,10 @@ struct StringifierState
|
|||
|
||||
for (int count = 0; count < 256; ++count)
|
||||
{
|
||||
std::string candidate = generateName(usedNames.size() + count);
|
||||
std::string candidate = generateName(previousNameIndex + count);
|
||||
if (!usedNames.count(candidate))
|
||||
{
|
||||
previousNameIndex += count;
|
||||
usedNames.insert(candidate);
|
||||
n = candidate;
|
||||
return candidate;
|
||||
|
@ -401,6 +363,7 @@ struct TypeVarStringifier
|
|||
{
|
||||
if (gtv.explicitName)
|
||||
{
|
||||
state.usedNames.insert(gtv.name);
|
||||
state.result.nameMap.typeVars[ty] = gtv.name;
|
||||
state.emit(gtv.name);
|
||||
}
|
||||
|
@ -991,6 +954,7 @@ struct TypePackStringifier
|
|||
state.emit("gen-");
|
||||
if (pack.explicitName)
|
||||
{
|
||||
state.usedNames.insert(pack.name);
|
||||
state.result.nameMap.typePacks[tp] = pack.name;
|
||||
state.emit(pack.name);
|
||||
}
|
||||
|
@ -1287,8 +1251,6 @@ std::string toStringNamedFunction(const std::string& funcName, const FunctionTyp
|
|||
auto argPackIter = begin(ftv.argTypes);
|
||||
|
||||
bool first = true;
|
||||
if (FFlag::LuauDocFuncParameters)
|
||||
{
|
||||
size_t idx = 0;
|
||||
while (argPackIter != end(ftv.argTypes))
|
||||
{
|
||||
|
@ -1322,31 +1284,6 @@ std::string toStringNamedFunction(const std::string& funcName, const FunctionTyp
|
|||
++argPackIter;
|
||||
++idx;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto argNameIter = ftv.argNames.begin();
|
||||
while (argPackIter != end(ftv.argTypes))
|
||||
{
|
||||
if (!first)
|
||||
state.emit(", ");
|
||||
first = false;
|
||||
|
||||
// We don't currently respect opts.functionTypeArguments. I don't think this function should.
|
||||
if (argNameIter != ftv.argNames.end())
|
||||
{
|
||||
state.emit((*argNameIter ? (*argNameIter)->name : "_") + ": ");
|
||||
++argNameIter;
|
||||
}
|
||||
else
|
||||
{
|
||||
state.emit("_: ");
|
||||
}
|
||||
|
||||
tvs.stringify(*argPackIter);
|
||||
++argPackIter;
|
||||
}
|
||||
}
|
||||
|
||||
if (argPackIter.tail())
|
||||
{
|
||||
|
@ -1428,4 +1365,55 @@ std::string generateName(size_t i)
|
|||
return n;
|
||||
}
|
||||
|
||||
std::string toString(const Constraint& c, ToStringOptions& opts)
|
||||
{
|
||||
if (const SubtypeConstraint* sc = Luau::get_if<SubtypeConstraint>(&c.c))
|
||||
{
|
||||
ToStringResult subStr = toStringDetailed(sc->subType, opts);
|
||||
opts.nameMap = std::move(subStr.nameMap);
|
||||
ToStringResult superStr = toStringDetailed(sc->superType, opts);
|
||||
opts.nameMap = std::move(superStr.nameMap);
|
||||
return subStr.name + " <: " + superStr.name;
|
||||
}
|
||||
else if (const PackSubtypeConstraint* psc = Luau::get_if<PackSubtypeConstraint>(&c.c))
|
||||
{
|
||||
ToStringResult subStr = toStringDetailed(psc->subPack, opts);
|
||||
opts.nameMap = std::move(subStr.nameMap);
|
||||
ToStringResult superStr = toStringDetailed(psc->superPack, opts);
|
||||
opts.nameMap = std::move(superStr.nameMap);
|
||||
return subStr.name + " <: " + superStr.name;
|
||||
}
|
||||
else if (const GeneralizationConstraint* gc = Luau::get_if<GeneralizationConstraint>(&c.c))
|
||||
{
|
||||
ToStringResult subStr = toStringDetailed(gc->subType, opts);
|
||||
opts.nameMap = std::move(subStr.nameMap);
|
||||
ToStringResult superStr = toStringDetailed(gc->superType, opts);
|
||||
opts.nameMap = std::move(superStr.nameMap);
|
||||
return subStr.name + " ~ gen " + superStr.name;
|
||||
}
|
||||
else if (const InstantiationConstraint* ic = Luau::get_if<InstantiationConstraint>(&c.c))
|
||||
{
|
||||
ToStringResult subStr = toStringDetailed(ic->subType, opts);
|
||||
opts.nameMap = std::move(subStr.nameMap);
|
||||
ToStringResult superStr = toStringDetailed(ic->superType, opts);
|
||||
opts.nameMap = std::move(superStr.nameMap);
|
||||
return subStr.name + " ~ inst " + superStr.name;
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(false);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
std::string dump(const Constraint& c)
|
||||
{
|
||||
ToStringOptions opts;
|
||||
opts.exhaustive = true;
|
||||
opts.functionTypeArguments = true;
|
||||
std::string s = toString(c, opts);
|
||||
printf("%s\n", s.c_str());
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauJustOneCallFrameForHaveSeen, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -149,24 +147,6 @@ void TxnLog::popSeen(TypePackId lhs, TypePackId rhs)
|
|||
}
|
||||
|
||||
bool TxnLog::haveSeen(TypeOrPackId lhs, TypeOrPackId rhs) const
|
||||
{
|
||||
if (FFlag::LuauJustOneCallFrameForHaveSeen && !FFlag::LuauTypecheckOptPass)
|
||||
{
|
||||
// This function will technically work if `this` is nullptr, but this
|
||||
// indicates a bug, so we explicitly assert.
|
||||
LUAU_ASSERT(static_cast<const void*>(this) != nullptr);
|
||||
|
||||
const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
||||
|
||||
for (const TxnLog* current = this; current; current = current->parent)
|
||||
{
|
||||
if (current->sharedSeen->end() != std::find(current->sharedSeen->begin(), current->sharedSeen->end(), sortedPair))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
|
||||
if (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair))
|
||||
|
@ -174,14 +154,8 @@ bool TxnLog::haveSeen(TypeOrPackId lhs, TypeOrPackId rhs) const
|
|||
return true;
|
||||
}
|
||||
|
||||
if (!FFlag::LuauTypecheckOptPass && parent)
|
||||
{
|
||||
return parent->haveSeen(lhs, rhs);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void TxnLog::pushSeen(TypeOrPackId lhs, TypeOrPackId rhs)
|
||||
{
|
||||
|
|
88
Analysis/src/TypeArena.cpp
Normal file
88
Analysis/src/TypeArena.cpp
Normal file
|
@ -0,0 +1,88 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#include "Luau/TypeArena.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
void TypeArena::clear()
|
||||
{
|
||||
typeVars.clear();
|
||||
typePacks.clear();
|
||||
}
|
||||
|
||||
TypeId TypeArena::addTV(TypeVar&& tv)
|
||||
{
|
||||
TypeId allocated = typeVars.allocate(std::move(tv));
|
||||
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
}
|
||||
|
||||
TypeId TypeArena::freshType(TypeLevel level)
|
||||
{
|
||||
TypeId allocated = typeVars.allocate(FreeTypeVar{level});
|
||||
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
}
|
||||
|
||||
TypePackId TypeArena::addTypePack(std::initializer_list<TypeId> types)
|
||||
{
|
||||
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
|
||||
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
}
|
||||
|
||||
TypePackId TypeArena::addTypePack(std::vector<TypeId> types)
|
||||
{
|
||||
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
|
||||
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
}
|
||||
|
||||
TypePackId TypeArena::addTypePack(TypePack tp)
|
||||
{
|
||||
TypePackId allocated = typePacks.allocate(std::move(tp));
|
||||
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
}
|
||||
|
||||
TypePackId TypeArena::addTypePack(TypePackVar tp)
|
||||
{
|
||||
TypePackId allocated = typePacks.allocate(std::move(tp));
|
||||
|
||||
asMutable(allocated)->owningArena = this;
|
||||
|
||||
return allocated;
|
||||
}
|
||||
|
||||
void freeze(TypeArena& arena)
|
||||
{
|
||||
if (!FFlag::DebugLuauFreezeArena)
|
||||
return;
|
||||
|
||||
arena.typeVars.freeze();
|
||||
arena.typePacks.freeze();
|
||||
}
|
||||
|
||||
void unfreeze(TypeArena& arena)
|
||||
{
|
||||
if (!FFlag::DebugLuauFreezeArena)
|
||||
return;
|
||||
|
||||
arena.typeVars.unfreeze();
|
||||
arena.typePacks.unfreeze();
|
||||
}
|
||||
|
||||
} // namespace Luau
|
File diff suppressed because it is too large
Load diff
|
@ -5,8 +5,6 @@
|
|||
#include "Luau/ToString.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauTerminateCyclicMetatableIndexLookup, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -55,13 +53,10 @@ std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, TypeId t
|
|||
{
|
||||
TypeId index = follow(*mtIndex);
|
||||
|
||||
if (FFlag::LuauTerminateCyclicMetatableIndexLookup)
|
||||
{
|
||||
if (count >= 100)
|
||||
return std::nullopt;
|
||||
|
||||
++count;
|
||||
}
|
||||
|
||||
if (const auto& itt = getTableType(index))
|
||||
{
|
||||
|
|
|
@ -24,9 +24,6 @@ LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
|
|||
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauSubtypingAddOptPropsToUnsealedTables)
|
||||
LUAU_FASTFLAG(LuauDiscriminableUnions2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAnyInIsOptionalIsOptional, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauClassDefinitionModuleInError, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -204,14 +201,14 @@ bool isOptional(TypeId ty)
|
|||
|
||||
ty = follow(ty);
|
||||
|
||||
if (FFlag::LuauAnyInIsOptionalIsOptional && get<AnyTypeVar>(ty))
|
||||
if (get<AnyTypeVar>(ty))
|
||||
return true;
|
||||
|
||||
auto utv = get<UnionTypeVar>(ty);
|
||||
if (!utv)
|
||||
return false;
|
||||
|
||||
return std::any_of(begin(utv), end(utv), FFlag::LuauAnyInIsOptionalIsOptional ? isOptional : isNil);
|
||||
return std::any_of(begin(utv), end(utv), isOptional);
|
||||
}
|
||||
|
||||
bool isTableIntersection(TypeId ty)
|
||||
|
@ -304,7 +301,7 @@ std::optional<ModuleName> getDefinitionModuleName(TypeId type)
|
|||
if (ftv->definition)
|
||||
return ftv->definition->definitionModuleName;
|
||||
}
|
||||
else if (auto ctv = get<ClassTypeVar>(type); ctv && FFlag::LuauClassDefinitionModuleInError)
|
||||
else if (auto ctv = get<ClassTypeVar>(type))
|
||||
{
|
||||
if (!ctv->definitionModuleName.empty())
|
||||
return ctv->definitionModuleName;
|
||||
|
@ -378,8 +375,7 @@ bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount)
|
|||
if (seen.contains(ty))
|
||||
return true;
|
||||
|
||||
bool isStr = FFlag::LuauDiscriminableUnions2 ? isString(ty) : isPrim(ty, PrimitiveTypeVar::String);
|
||||
if (isStr || get<AnyTypeVar>(ty) || get<TableTypeVar>(ty) || get<MetatableTypeVar>(ty))
|
||||
if (isString(ty) || get<AnyTypeVar>(ty) || get<TableTypeVar>(ty) || get<MetatableTypeVar>(ty))
|
||||
return true;
|
||||
|
||||
if (auto uty = get<UnionTypeVar>(ty))
|
||||
|
@ -727,7 +723,7 @@ TypeId SingletonTypes::makeStringMetatable()
|
|||
|
||||
TableTypeVar::Props stringLib = {
|
||||
{"byte", {arena->addType(FunctionTypeVar{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList})}},
|
||||
{"char", {arena->addType(FunctionTypeVar{arena->addTypePack(TypePack{{numberType}, numberVariadicList}), arena->addTypePack({stringType})})}},
|
||||
{"char", {arena->addType(FunctionTypeVar{numberVariadicList, arena->addTypePack({stringType})})}},
|
||||
{"find", {makeFunction(*arena, stringType, {}, {}, {stringType, optionalNumber, optionalBoolean}, {}, {optionalNumber, optionalNumber})}},
|
||||
{"format", {formatFn}}, // FIXME
|
||||
{"gmatch", {gmatchFunc}},
|
||||
|
|
|
@ -12,12 +12,16 @@ Free::Free(TypeLevel level)
|
|||
{
|
||||
}
|
||||
|
||||
Free::Free(Scope2* scope)
|
||||
: scope(scope)
|
||||
{
|
||||
}
|
||||
|
||||
int Free::nextIndex = 0;
|
||||
|
||||
Generic::Generic()
|
||||
: index(++nextIndex)
|
||||
, name("g" + std::to_string(index))
|
||||
, explicitName(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -25,7 +29,6 @@ Generic::Generic(TypeLevel level)
|
|||
: index(++nextIndex)
|
||||
, level(level)
|
||||
, name("g" + std::to_string(index))
|
||||
, explicitName(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -36,6 +39,12 @@ Generic::Generic(const Name& name)
|
|||
{
|
||||
}
|
||||
|
||||
Generic::Generic(Scope2* scope)
|
||||
: index(++nextIndex)
|
||||
, scope(scope)
|
||||
{
|
||||
}
|
||||
|
||||
Generic::Generic(TypeLevel level, const Name& name)
|
||||
: index(++nextIndex)
|
||||
, level(level)
|
||||
|
|
|
@ -21,11 +21,7 @@ LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false);
|
|||
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||
LUAU_FASTFLAG(LuauErrorRecoveryType);
|
||||
LUAU_FASTFLAGVARIABLE(LuauSubtypingAddOptPropsToUnsealedTables, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauWidenIfSupertypeIsFree2, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDifferentOrderOfUnificationDoesntMatter2, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTxnLogRefreshFunctionPointers, false)
|
||||
LUAU_FASTFLAG(LuauAnyInIsOptionalIsOptional)
|
||||
LUAU_FASTFLAG(LuauTypecheckOptPass)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -151,8 +147,7 @@ static void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel
|
|||
return;
|
||||
|
||||
PromoteTypeLevels ptl{log, typeArena, minLevel};
|
||||
DenseHashSet<void*> seen{nullptr};
|
||||
DEPRECATED_visitTypeVarOnce(ty, ptl, seen);
|
||||
ptl.traverse(ty);
|
||||
}
|
||||
|
||||
void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypePackId tp)
|
||||
|
@ -162,8 +157,7 @@ void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLev
|
|||
return;
|
||||
|
||||
PromoteTypeLevels ptl{log, typeArena, minLevel};
|
||||
DenseHashSet<void*> seen{nullptr};
|
||||
DEPRECATED_visitTypeVarOnce(tp, ptl, seen);
|
||||
ptl.traverse(tp);
|
||||
}
|
||||
|
||||
struct SkipCacheForType final : TypeVarOnceVisitor
|
||||
|
@ -174,49 +168,6 @@ struct SkipCacheForType final : TypeVarOnceVisitor
|
|||
{
|
||||
}
|
||||
|
||||
// TODO cycle() and operator() can be clipped with FFlagLuauUseVisitRecursionLimit
|
||||
void cycle(TypeId) override {}
|
||||
void cycle(TypePackId) override {}
|
||||
|
||||
bool operator()(TypeId ty, const FreeTypeVar& ftv)
|
||||
{
|
||||
return visit(ty, ftv);
|
||||
}
|
||||
bool operator()(TypeId ty, const BoundTypeVar& btv)
|
||||
{
|
||||
return visit(ty, btv);
|
||||
}
|
||||
bool operator()(TypeId ty, const GenericTypeVar& gtv)
|
||||
{
|
||||
return visit(ty, gtv);
|
||||
}
|
||||
bool operator()(TypeId ty, const TableTypeVar& ttv)
|
||||
{
|
||||
return visit(ty, ttv);
|
||||
}
|
||||
bool operator()(TypePackId tp, const FreeTypePack& ftp)
|
||||
{
|
||||
return visit(tp, ftp);
|
||||
}
|
||||
bool operator()(TypePackId tp, const BoundTypePack& ftp)
|
||||
{
|
||||
return visit(tp, ftp);
|
||||
}
|
||||
bool operator()(TypePackId tp, const GenericTypePack& ftp)
|
||||
{
|
||||
return visit(tp, ftp);
|
||||
}
|
||||
template<typename T>
|
||||
bool operator()(TypeId ty, const T& t)
|
||||
{
|
||||
return visit(ty);
|
||||
}
|
||||
template<typename T>
|
||||
bool operator()(TypePackId tp, const T&)
|
||||
{
|
||||
return visit(tp);
|
||||
}
|
||||
|
||||
bool visit(TypeId, const FreeTypeVar&) override
|
||||
{
|
||||
result = true;
|
||||
|
@ -343,6 +294,16 @@ bool Widen::ignoreChildren(TypeId ty)
|
|||
return !log->is<UnionTypeVar>(ty);
|
||||
}
|
||||
|
||||
TypeId Widen::operator()(TypeId ty)
|
||||
{
|
||||
return substitute(ty).value_or(ty);
|
||||
}
|
||||
|
||||
TypePackId Widen::operator()(TypePackId tp)
|
||||
{
|
||||
return substitute(tp).value_or(tp);
|
||||
}
|
||||
|
||||
static std::optional<TypeError> hasUnificationTooComplex(const ErrorVec& errors)
|
||||
{
|
||||
auto isUnificationTooComplex = [](const TypeError& te) {
|
||||
|
@ -382,19 +343,6 @@ Unifier::Unifier(TypeArena* types, Mode mode, const Location& location, Variance
|
|||
LUAU_ASSERT(sharedState.iceHandler);
|
||||
}
|
||||
|
||||
Unifier::Unifier(TypeArena* types, Mode mode, std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen, const Location& location,
|
||||
Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog)
|
||||
: types(types)
|
||||
, mode(mode)
|
||||
, log(parentLog, sharedSeen)
|
||||
, location(location)
|
||||
, variance(variance)
|
||||
, sharedState(sharedState)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauTypecheckOptPass);
|
||||
LUAU_ASSERT(sharedState.iceHandler);
|
||||
}
|
||||
|
||||
void Unifier::tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall, bool isIntersection)
|
||||
{
|
||||
sharedState.counters.iterationCount = 0;
|
||||
|
@ -490,6 +438,8 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||
if (!occursFailed)
|
||||
{
|
||||
promoteTypeLevels(log, types, superLevel, subTy);
|
||||
|
||||
Widen widen{types};
|
||||
log.replace(superTy, BoundTypeVar(widen(subTy)));
|
||||
}
|
||||
|
||||
|
@ -627,9 +577,6 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId
|
|||
std::optional<TypeError> unificationTooComplex;
|
||||
std::optional<TypeError> firstFailedOption;
|
||||
|
||||
size_t count = uv->options.size();
|
||||
size_t i = 0;
|
||||
|
||||
for (TypeId type : uv->options)
|
||||
{
|
||||
Unifier innerState = makeChildUnifier();
|
||||
|
@ -645,24 +592,9 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId
|
|||
|
||||
failed = true;
|
||||
}
|
||||
|
||||
if (FFlag::LuauDifferentOrderOfUnificationDoesntMatter2)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i == count - 1)
|
||||
{
|
||||
log.concat(std::move(innerState.log));
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
// even if A | B <: T fails, we want to bind some options of T with A | B iff A | B was a subtype of that option.
|
||||
if (FFlag::LuauDifferentOrderOfUnificationDoesntMatter2)
|
||||
{
|
||||
auto tryBind = [this, subTy](TypeId superOption) {
|
||||
superOption = log.follow(superOption);
|
||||
|
||||
|
@ -698,7 +630,6 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId
|
|||
}
|
||||
else
|
||||
tryBind(superTy);
|
||||
}
|
||||
|
||||
if (unificationTooComplex)
|
||||
reportError(*unificationTooComplex);
|
||||
|
@ -898,7 +829,7 @@ bool Unifier::canCacheResult(TypeId subTy, TypeId superTy)
|
|||
|
||||
auto skipCacheFor = [this](TypeId ty) {
|
||||
SkipCacheForType visitor{sharedState.skipCacheForType, types};
|
||||
DEPRECATED_visitTypeVarOnce(ty, visitor, sharedState.seenAny);
|
||||
visitor.traverse(ty);
|
||||
|
||||
sharedState.skipCacheForType[ty] = visitor.result;
|
||||
|
||||
|
@ -1103,6 +1034,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
|||
|
||||
if (!log.getMutable<ErrorTypeVar>(superTp))
|
||||
{
|
||||
Widen widen{types};
|
||||
log.replace(superTp, Unifiable::Bound<TypePackId>(widen(subTp)));
|
||||
}
|
||||
}
|
||||
|
@ -1219,14 +1151,6 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
|||
continue;
|
||||
}
|
||||
|
||||
// In nonstrict mode, any also marks an optional argument.
|
||||
else if (!FFlag::LuauAnyInIsOptionalIsOptional && superIter.good() && isNonstrictMode() &&
|
||||
log.getMutable<AnyTypeVar>(log.follow(*superIter)))
|
||||
{
|
||||
superIter.advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (log.getMutable<VariadicTypePack>(superIter.packId))
|
||||
{
|
||||
tryUnifyVariadics(subIter.packId, superIter.packId, false, int(subIter.index));
|
||||
|
@ -1454,22 +1378,10 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||
{
|
||||
auto subIter = subTable->props.find(propName);
|
||||
|
||||
if (FFlag::LuauAnyInIsOptionalIsOptional)
|
||||
{
|
||||
if (subIter == subTable->props.end() &&
|
||||
(!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && !isOptional(superProp.type))
|
||||
if (subIter == subTable->props.end() && (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) &&
|
||||
!isOptional(superProp.type))
|
||||
missingProperties.push_back(propName);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool isAny = log.getMutable<AnyTypeVar>(log.follow(superProp.type));
|
||||
|
||||
if (subIter == subTable->props.end() &&
|
||||
(!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && !isOptional(superProp.type) &&
|
||||
!isAny)
|
||||
missingProperties.push_back(propName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!missingProperties.empty())
|
||||
{
|
||||
|
@ -1485,19 +1397,9 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||
{
|
||||
auto superIter = superTable->props.find(propName);
|
||||
|
||||
if (FFlag::LuauAnyInIsOptionalIsOptional)
|
||||
{
|
||||
if (superIter == superTable->props.end() && (FFlag::LuauSubtypingAddOptPropsToUnsealedTables || !isOptional(subProp.type)))
|
||||
extraProperties.push_back(propName);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool isAny = log.is<AnyTypeVar>(log.follow(subProp.type));
|
||||
if (superIter == superTable->props.end() &&
|
||||
(FFlag::LuauSubtypingAddOptPropsToUnsealedTables || (!isOptional(subProp.type) && !isAny)))
|
||||
extraProperties.push_back(propName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!extraProperties.empty())
|
||||
{
|
||||
|
@ -1540,21 +1442,12 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||
if (innerState.errors.empty())
|
||||
log.concat(std::move(innerState.log));
|
||||
}
|
||||
else if (FFlag::LuauAnyInIsOptionalIsOptional &&
|
||||
(!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && isOptional(prop.type))
|
||||
else if ((!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && isOptional(prop.type))
|
||||
// This is sound because unsealed table types are precise, so `{ p : T } <: { p : T, q : U? }`
|
||||
// since if `t : { p : T }` then we are guaranteed that `t.q` is `nil`.
|
||||
// TODO: if the supertype is written to, the subtype may no longer be precise (alias analysis?)
|
||||
{
|
||||
}
|
||||
else if ((!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) &&
|
||||
(isOptional(prop.type) || get<AnyTypeVar>(follow(prop.type))))
|
||||
// This is sound because unsealed table types are precise, so `{ p : T } <: { p : T, q : U? }`
|
||||
// since if `t : { p : T }` then we are guaranteed that `t.q` is `nil`.
|
||||
// TODO: should isOptional(anyType) be true?
|
||||
// TODO: if the supertype is written to, the subtype may no longer be precise (alias analysis?)
|
||||
{
|
||||
}
|
||||
else if (subTable->state == TableState::Free)
|
||||
{
|
||||
PendingType* pendingSub = log.queue(subTy);
|
||||
|
@ -1618,10 +1511,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||
else if (variance == Covariant)
|
||||
{
|
||||
}
|
||||
else if (FFlag::LuauAnyInIsOptionalIsOptional && !FFlag::LuauSubtypingAddOptPropsToUnsealedTables && isOptional(prop.type))
|
||||
{
|
||||
}
|
||||
else if (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables && (isOptional(prop.type) || get<AnyTypeVar>(follow(prop.type))))
|
||||
else if (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables && isOptional(prop.type))
|
||||
{
|
||||
}
|
||||
else if (superTable->state == TableState::Free)
|
||||
|
@ -1728,34 +1618,10 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||
}
|
||||
}
|
||||
|
||||
TypeId Unifier::widen(TypeId ty)
|
||||
{
|
||||
if (!FFlag::LuauWidenIfSupertypeIsFree2)
|
||||
return ty;
|
||||
|
||||
Widen widen{types};
|
||||
std::optional<TypeId> result = widen.substitute(ty);
|
||||
// TODO: what does it mean for substitution to fail to widen?
|
||||
return result.value_or(ty);
|
||||
}
|
||||
|
||||
TypePackId Unifier::widen(TypePackId tp)
|
||||
{
|
||||
if (!FFlag::LuauWidenIfSupertypeIsFree2)
|
||||
return tp;
|
||||
|
||||
Widen widen{types};
|
||||
std::optional<TypePackId> result = widen.substitute(tp);
|
||||
// TODO: what does it mean for substitution to fail to widen?
|
||||
return result.value_or(tp);
|
||||
}
|
||||
|
||||
TypeId Unifier::deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> seen)
|
||||
{
|
||||
ty = follow(ty);
|
||||
if (!FFlag::LuauAnyInIsOptionalIsOptional && get<AnyTypeVar>(ty))
|
||||
return ty;
|
||||
else if (isOptional(ty))
|
||||
if (isOptional(ty))
|
||||
return ty;
|
||||
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
|
||||
{
|
||||
|
@ -1868,10 +1734,7 @@ void Unifier::tryUnifyFreeTable(TypeId subTy, TypeId superTy)
|
|||
{
|
||||
if (auto subProp = findTablePropertyRespectingMeta(subTy, freeName))
|
||||
{
|
||||
if (FFlag::LuauWidenIfSupertypeIsFree2)
|
||||
tryUnify_(*subProp, freeProp.type);
|
||||
else
|
||||
tryUnify_(freeProp.type, *subProp);
|
||||
|
||||
/*
|
||||
* TypeVars are commonly cyclic, so it is entirely possible
|
||||
|
@ -2665,19 +2528,12 @@ void Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, Typ
|
|||
}
|
||||
|
||||
Unifier Unifier::makeChildUnifier()
|
||||
{
|
||||
if (FFlag::LuauTypecheckOptPass)
|
||||
{
|
||||
Unifier u = Unifier{types, mode, location, variance, sharedState, &log};
|
||||
u.anyIsTop = anyIsTop;
|
||||
return u;
|
||||
}
|
||||
|
||||
Unifier u = Unifier{types, mode, log.sharedSeen, location, variance, sharedState, &log};
|
||||
u.anyIsTop = anyIsTop;
|
||||
return u;
|
||||
}
|
||||
|
||||
// A utility function that appends the given error to the unifier's error log.
|
||||
// This allows setting a breakpoint wherever the unifier reports an error.
|
||||
void Unifier::reportError(TypeError err)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Common.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000)
|
||||
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauParserFunctionKeywordAsTypeHelp, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -1589,6 +1591,17 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
|
|||
{
|
||||
return parseFunctionTypeAnnotation(allowPack);
|
||||
}
|
||||
else if (FFlag::LuauParserFunctionKeywordAsTypeHelp && lexer.current().type == Lexeme::ReservedFunction)
|
||||
{
|
||||
Location location = lexer.current().location;
|
||||
|
||||
nextLexeme();
|
||||
|
||||
return {reportTypeAnnotationError(location, {}, /*isMissing*/ false,
|
||||
"Using 'function' as a type annotation is not supported, consider replacing with a function type annotation e.g. '(...any) -> "
|
||||
"...any'"),
|
||||
{}};
|
||||
}
|
||||
else
|
||||
{
|
||||
Location location = lexer.current().location;
|
||||
|
|
|
@ -19,9 +19,11 @@ if(LUAU_STATIC_CRT)
|
|||
endif()
|
||||
|
||||
project(Luau LANGUAGES CXX C)
|
||||
add_library(Luau.Common INTERFACE)
|
||||
add_library(Luau.Ast STATIC)
|
||||
add_library(Luau.Compiler STATIC)
|
||||
add_library(Luau.Analysis STATIC)
|
||||
add_library(Luau.CodeGen STATIC)
|
||||
add_library(Luau.VM STATIC)
|
||||
add_library(isocline STATIC)
|
||||
|
||||
|
@ -48,8 +50,11 @@ endif()
|
|||
|
||||
include(Sources.cmake)
|
||||
|
||||
target_include_directories(Luau.Common INTERFACE Common/include)
|
||||
|
||||
target_compile_features(Luau.Ast PUBLIC cxx_std_17)
|
||||
target_include_directories(Luau.Ast PUBLIC Ast/include)
|
||||
target_link_libraries(Luau.Ast PUBLIC Luau.Common)
|
||||
|
||||
target_compile_features(Luau.Compiler PUBLIC cxx_std_17)
|
||||
target_include_directories(Luau.Compiler PUBLIC Compiler/include)
|
||||
|
@ -59,8 +64,13 @@ target_compile_features(Luau.Analysis PUBLIC cxx_std_17)
|
|||
target_include_directories(Luau.Analysis PUBLIC Analysis/include)
|
||||
target_link_libraries(Luau.Analysis PUBLIC Luau.Ast)
|
||||
|
||||
target_compile_features(Luau.CodeGen PRIVATE cxx_std_17)
|
||||
target_include_directories(Luau.CodeGen PUBLIC CodeGen/include)
|
||||
target_link_libraries(Luau.CodeGen PUBLIC Luau.Common)
|
||||
|
||||
target_compile_features(Luau.VM PRIVATE cxx_std_11)
|
||||
target_include_directories(Luau.VM PUBLIC VM/include)
|
||||
target_link_libraries(Luau.VM PUBLIC Luau.Common)
|
||||
|
||||
target_include_directories(isocline PUBLIC extern/isocline/include)
|
||||
|
||||
|
@ -101,6 +111,7 @@ endif()
|
|||
|
||||
target_compile_options(Luau.Ast PRIVATE ${LUAU_OPTIONS})
|
||||
target_compile_options(Luau.Analysis PRIVATE ${LUAU_OPTIONS})
|
||||
target_compile_options(Luau.CodeGen PRIVATE ${LUAU_OPTIONS})
|
||||
target_compile_options(Luau.VM PRIVATE ${LUAU_OPTIONS})
|
||||
target_compile_options(isocline PRIVATE ${LUAU_OPTIONS} ${ISOCLINE_OPTIONS})
|
||||
|
||||
|
@ -120,6 +131,7 @@ endif()
|
|||
if(MSVC)
|
||||
target_link_options(Luau.Ast INTERFACE /NATVIS:${CMAKE_CURRENT_SOURCE_DIR}/tools/natvis/Ast.natvis)
|
||||
target_link_options(Luau.Analysis INTERFACE /NATVIS:${CMAKE_CURRENT_SOURCE_DIR}/tools/natvis/Analysis.natvis)
|
||||
target_link_options(Luau.CodeGen INTERFACE /NATVIS:${CMAKE_CURRENT_SOURCE_DIR}/tools/natvis/CodeGen.natvis)
|
||||
target_link_options(Luau.VM INTERFACE /NATVIS:${CMAKE_CURRENT_SOURCE_DIR}/tools/natvis/VM.natvis)
|
||||
endif()
|
||||
|
||||
|
@ -127,6 +139,7 @@ endif()
|
|||
if(MSVC_IDE)
|
||||
target_sources(Luau.Ast PRIVATE tools/natvis/Ast.natvis)
|
||||
target_sources(Luau.Analysis PRIVATE tools/natvis/Analysis.natvis)
|
||||
target_sources(Luau.CodeGen PRIVATE tools/natvis/CodeGen.natvis)
|
||||
target_sources(Luau.VM PRIVATE tools/natvis/VM.natvis)
|
||||
endif()
|
||||
|
||||
|
@ -154,7 +167,7 @@ endif()
|
|||
if(LUAU_BUILD_TESTS)
|
||||
target_compile_options(Luau.UnitTest PRIVATE ${LUAU_OPTIONS})
|
||||
target_include_directories(Luau.UnitTest PRIVATE extern)
|
||||
target_link_libraries(Luau.UnitTest PRIVATE Luau.Analysis Luau.Compiler)
|
||||
target_link_libraries(Luau.UnitTest PRIVATE Luau.Analysis Luau.Compiler Luau.CodeGen)
|
||||
|
||||
target_compile_options(Luau.Conformance PRIVATE ${LUAU_OPTIONS})
|
||||
target_include_directories(Luau.Conformance PRIVATE extern)
|
||||
|
|
169
CodeGen/include/Luau/AssemblyBuilderX64.h
Normal file
169
CodeGen/include/Luau/AssemblyBuilderX64.h
Normal file
|
@ -0,0 +1,169 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Condition.h"
|
||||
#include "Luau/Label.h"
|
||||
#include "Luau/OperandX64.h"
|
||||
#include "Luau/RegisterX64.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
class AssemblyBuilderX64
|
||||
{
|
||||
public:
|
||||
explicit AssemblyBuilderX64(bool logText);
|
||||
~AssemblyBuilderX64();
|
||||
|
||||
// Base two operand instructions with 9 opcode selection
|
||||
void add(OperandX64 lhs, OperandX64 rhs);
|
||||
void sub(OperandX64 lhs, OperandX64 rhs);
|
||||
void cmp(OperandX64 lhs, OperandX64 rhs);
|
||||
void and_(OperandX64 lhs, OperandX64 rhs);
|
||||
void or_(OperandX64 lhs, OperandX64 rhs);
|
||||
void xor_(OperandX64 lhs, OperandX64 rhs);
|
||||
|
||||
// Binary shift instructions with special rhs handling
|
||||
void sal(OperandX64 lhs, OperandX64 rhs);
|
||||
void sar(OperandX64 lhs, OperandX64 rhs);
|
||||
void shl(OperandX64 lhs, OperandX64 rhs);
|
||||
void shr(OperandX64 lhs, OperandX64 rhs);
|
||||
|
||||
// Two operand mov instruction has additional specialized encodings
|
||||
void mov(OperandX64 lhs, OperandX64 rhs);
|
||||
void mov64(RegisterX64 lhs, int64_t imm);
|
||||
|
||||
// Base one operand instruction with 2 opcode selection
|
||||
void div(OperandX64 op);
|
||||
void idiv(OperandX64 op);
|
||||
void mul(OperandX64 op);
|
||||
void neg(OperandX64 op);
|
||||
void not_(OperandX64 op);
|
||||
|
||||
void test(OperandX64 lhs, OperandX64 rhs);
|
||||
void lea(OperandX64 lhs, OperandX64 rhs);
|
||||
|
||||
void push(OperandX64 op);
|
||||
void pop(OperandX64 op);
|
||||
void ret();
|
||||
|
||||
// Control flow
|
||||
void jcc(Condition cond, Label& label);
|
||||
void jmp(Label& label);
|
||||
void jmp(OperandX64 op);
|
||||
|
||||
// AVX
|
||||
void vaddpd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
|
||||
void vaddps(OperandX64 dst, OperandX64 src1, OperandX64 src2);
|
||||
void vaddsd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
|
||||
void vaddss(OperandX64 dst, OperandX64 src1, OperandX64 src2);
|
||||
|
||||
void vsqrtpd(OperandX64 dst, OperandX64 src);
|
||||
void vsqrtps(OperandX64 dst, OperandX64 src);
|
||||
void vsqrtsd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
|
||||
void vsqrtss(OperandX64 dst, OperandX64 src1, OperandX64 src2);
|
||||
|
||||
void vmovsd(OperandX64 dst, OperandX64 src);
|
||||
void vmovsd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
|
||||
void vmovss(OperandX64 dst, OperandX64 src);
|
||||
void vmovss(OperandX64 dst, OperandX64 src1, OperandX64 src2);
|
||||
void vmovapd(OperandX64 dst, OperandX64 src);
|
||||
void vmovaps(OperandX64 dst, OperandX64 src);
|
||||
void vmovupd(OperandX64 dst, OperandX64 src);
|
||||
void vmovups(OperandX64 dst, OperandX64 src);
|
||||
|
||||
// Run final checks
|
||||
void finalize();
|
||||
|
||||
// Places a label at current location and returns it
|
||||
Label setLabel();
|
||||
|
||||
// Assigns label position to the current location
|
||||
void setLabel(Label& label);
|
||||
|
||||
// Constant allocation (uses rip-relative addressing)
|
||||
OperandX64 i64(int64_t value);
|
||||
OperandX64 f32(float value);
|
||||
OperandX64 f64(double value);
|
||||
OperandX64 f32x4(float x, float y, float z, float w);
|
||||
|
||||
// Resulting data and code that need to be copied over one after the other
|
||||
// The *end* of 'data' has to be aligned to 16 bytes, this will also align 'code'
|
||||
std::vector<uint8_t> data;
|
||||
std::vector<uint8_t> code;
|
||||
|
||||
std::string text;
|
||||
|
||||
private:
|
||||
// Instruction archetypes
|
||||
void placeBinary(const char* name, OperandX64 lhs, OperandX64 rhs, uint8_t codeimm8, uint8_t codeimm, uint8_t codeimmImm8, uint8_t code8rev,
|
||||
uint8_t coderev, uint8_t code8, uint8_t code, uint8_t opreg);
|
||||
void placeBinaryRegMemAndImm(OperandX64 lhs, OperandX64 rhs, uint8_t code8, uint8_t code, uint8_t codeImm8, uint8_t opreg);
|
||||
void placeBinaryRegAndRegMem(OperandX64 lhs, OperandX64 rhs, uint8_t code8, uint8_t code);
|
||||
void placeBinaryRegMemAndReg(OperandX64 lhs, OperandX64 rhs, uint8_t code8, uint8_t code);
|
||||
|
||||
void placeUnaryModRegMem(const char* name, OperandX64 op, uint8_t code8, uint8_t code, uint8_t opreg);
|
||||
|
||||
void placeShift(const char* name, OperandX64 lhs, OperandX64 rhs, uint8_t opreg);
|
||||
|
||||
void placeJcc(const char* name, Label& label, uint8_t cc);
|
||||
|
||||
void placeAvx(const char* name, OperandX64 dst, OperandX64 src, uint8_t code, bool setW, uint8_t mode, uint8_t prefix);
|
||||
void placeAvx(const char* name, OperandX64 dst, OperandX64 src, uint8_t code, uint8_t coderev, bool setW, uint8_t mode, uint8_t prefix);
|
||||
void placeAvx(const char* name, OperandX64 dst, OperandX64 src1, OperandX64 src2, uint8_t code, bool setW, uint8_t mode, uint8_t prefix);
|
||||
|
||||
// Instruction components
|
||||
void placeRegAndModRegMem(OperandX64 lhs, OperandX64 rhs);
|
||||
void placeModRegMem(OperandX64 rhs, uint8_t regop);
|
||||
void placeRex(RegisterX64 op);
|
||||
void placeRex(OperandX64 op);
|
||||
void placeRex(RegisterX64 lhs, OperandX64 rhs);
|
||||
void placeVex(OperandX64 dst, OperandX64 src1, OperandX64 src2, bool setW, uint8_t mode, uint8_t prefix);
|
||||
void placeImm8Or32(int32_t imm);
|
||||
void placeImm8(int32_t imm);
|
||||
void placeImm32(int32_t imm);
|
||||
void placeImm64(int64_t imm);
|
||||
void placeLabel(Label& label);
|
||||
void place(uint8_t byte);
|
||||
|
||||
void commit();
|
||||
LUAU_NOINLINE void extend();
|
||||
uint32_t getCodeSize();
|
||||
|
||||
// Data
|
||||
size_t allocateData(size_t size, size_t align);
|
||||
|
||||
// Logging of assembly in text form (Intel asm with VS disassembly formatting)
|
||||
LUAU_NOINLINE void log(const char* opcode);
|
||||
LUAU_NOINLINE void log(const char* opcode, OperandX64 op);
|
||||
LUAU_NOINLINE void log(const char* opcode, OperandX64 op1, OperandX64 op2);
|
||||
LUAU_NOINLINE void log(const char* opcode, OperandX64 op1, OperandX64 op2, OperandX64 op3);
|
||||
LUAU_NOINLINE void log(Label label);
|
||||
LUAU_NOINLINE void log(const char* opcode, Label label);
|
||||
void log(OperandX64 op);
|
||||
void logAppend(const char* fmt, ...);
|
||||
|
||||
const char* getSizeName(SizeX64 size);
|
||||
const char* getRegisterName(RegisterX64 reg);
|
||||
|
||||
uint32_t nextLabel = 1;
|
||||
std::vector<Label> pendingLabels;
|
||||
std::vector<uint32_t> labelLocations;
|
||||
|
||||
bool logText = false;
|
||||
bool finalized = false;
|
||||
|
||||
size_t dataPos = 0;
|
||||
|
||||
uint8_t* codePos = nullptr;
|
||||
uint8_t* codeEnd = nullptr;
|
||||
};
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
46
CodeGen/include/Luau/Condition.h
Normal file
46
CodeGen/include/Luau/Condition.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
enum class Condition
|
||||
{
|
||||
Overflow,
|
||||
NoOverflow,
|
||||
|
||||
Carry,
|
||||
NoCarry,
|
||||
|
||||
Below,
|
||||
BelowEqual,
|
||||
Above,
|
||||
AboveEqual,
|
||||
Equal,
|
||||
Less,
|
||||
LessEqual,
|
||||
Greater,
|
||||
GreaterEqual,
|
||||
|
||||
NotBelow,
|
||||
NotBelowEqual,
|
||||
NotAbove,
|
||||
NotAboveEqual,
|
||||
NotEqual,
|
||||
NotLess,
|
||||
NotLessEqual,
|
||||
NotGreater,
|
||||
NotGreaterEqual,
|
||||
|
||||
Zero,
|
||||
NotZero,
|
||||
|
||||
// TODO: ordered and unordered floating-point conditions
|
||||
|
||||
Count
|
||||
};
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
18
CodeGen/include/Luau/Label.h
Normal file
18
CodeGen/include/Luau/Label.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
struct Label
|
||||
{
|
||||
uint32_t id = 0;
|
||||
uint32_t location = ~0u;
|
||||
};
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
136
CodeGen/include/Luau/OperandX64.h
Normal file
136
CodeGen/include/Luau/OperandX64.h
Normal file
|
@ -0,0 +1,136 @@
|
|||
#pragma once
|
||||
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/RegisterX64.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
enum class CategoryX64 : uint8_t
|
||||
{
|
||||
reg,
|
||||
mem,
|
||||
imm,
|
||||
};
|
||||
|
||||
struct OperandX64
|
||||
{
|
||||
constexpr OperandX64(RegisterX64 reg)
|
||||
: cat(CategoryX64::reg)
|
||||
, index(noreg)
|
||||
, base(reg)
|
||||
, memSize(SizeX64::none)
|
||||
, scale(1)
|
||||
, imm(0)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr OperandX64(int32_t imm)
|
||||
: cat(CategoryX64::imm)
|
||||
, index(noreg)
|
||||
, base(noreg)
|
||||
, memSize(SizeX64::none)
|
||||
, scale(1)
|
||||
, imm(imm)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr explicit OperandX64(SizeX64 size, RegisterX64 index, uint8_t scale, RegisterX64 base, int32_t disp)
|
||||
: cat(CategoryX64::mem)
|
||||
, index(index)
|
||||
, base(base)
|
||||
, memSize(size)
|
||||
, scale(scale)
|
||||
, imm(disp)
|
||||
{
|
||||
}
|
||||
|
||||
// Fields are carefully placed to make this struct fit into an 8 byte register
|
||||
CategoryX64 cat;
|
||||
RegisterX64 index;
|
||||
RegisterX64 base;
|
||||
SizeX64 memSize : 4;
|
||||
uint8_t scale : 4;
|
||||
int32_t imm;
|
||||
|
||||
constexpr OperandX64 operator[](OperandX64&& addr) const
|
||||
{
|
||||
LUAU_ASSERT(cat == CategoryX64::mem);
|
||||
LUAU_ASSERT(memSize != SizeX64::none && index == noreg && scale == 1 && base == noreg && imm == 0);
|
||||
LUAU_ASSERT(addr.memSize == SizeX64::none);
|
||||
|
||||
addr.cat = CategoryX64::mem;
|
||||
addr.memSize = memSize;
|
||||
return addr;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr OperandX64 byte{SizeX64::byte, noreg, 1, noreg, 0};
|
||||
constexpr OperandX64 word{SizeX64::word, noreg, 1, noreg, 0};
|
||||
constexpr OperandX64 dword{SizeX64::dword, noreg, 1, noreg, 0};
|
||||
constexpr OperandX64 qword{SizeX64::qword, noreg, 1, noreg, 0};
|
||||
constexpr OperandX64 xmmword{SizeX64::xmmword, noreg, 1, noreg, 0};
|
||||
constexpr OperandX64 ymmword{SizeX64::ymmword, noreg, 1, noreg, 0};
|
||||
constexpr OperandX64 ptr{sizeof(void*) == 4 ? SizeX64::dword : SizeX64::qword, noreg, 1, noreg, 0};
|
||||
|
||||
constexpr OperandX64 operator*(RegisterX64 reg, uint8_t scale)
|
||||
{
|
||||
if (scale == 1)
|
||||
return OperandX64(reg);
|
||||
|
||||
LUAU_ASSERT(scale == 1 || scale == 2 || scale == 4 || scale == 8);
|
||||
LUAU_ASSERT(reg.index != 0b100 && "can't scale SP");
|
||||
|
||||
return OperandX64(SizeX64::none, reg, scale, noreg, 0);
|
||||
}
|
||||
|
||||
constexpr OperandX64 operator+(RegisterX64 reg, int32_t disp)
|
||||
{
|
||||
return OperandX64(SizeX64::none, noreg, 1, reg, disp);
|
||||
}
|
||||
|
||||
constexpr OperandX64 operator+(RegisterX64 base, RegisterX64 index)
|
||||
{
|
||||
LUAU_ASSERT(index.index != 4 && "sp cannot be used as index");
|
||||
LUAU_ASSERT(base.size == index.size);
|
||||
|
||||
return OperandX64(SizeX64::none, index, 1, base, 0);
|
||||
}
|
||||
|
||||
constexpr OperandX64 operator+(OperandX64 op, int32_t disp)
|
||||
{
|
||||
LUAU_ASSERT(op.cat == CategoryX64::mem);
|
||||
LUAU_ASSERT(op.memSize == SizeX64::none);
|
||||
|
||||
op.imm += disp;
|
||||
return op;
|
||||
}
|
||||
|
||||
constexpr OperandX64 operator+(OperandX64 op, RegisterX64 base)
|
||||
{
|
||||
LUAU_ASSERT(op.cat == CategoryX64::mem);
|
||||
LUAU_ASSERT(op.memSize == SizeX64::none);
|
||||
LUAU_ASSERT(op.base == noreg);
|
||||
LUAU_ASSERT(op.index == noreg || op.index.size == base.size);
|
||||
|
||||
op.base = base;
|
||||
return op;
|
||||
}
|
||||
|
||||
constexpr OperandX64 operator+(RegisterX64 base, OperandX64 op)
|
||||
{
|
||||
LUAU_ASSERT(op.cat == CategoryX64::mem);
|
||||
LUAU_ASSERT(op.memSize == SizeX64::none);
|
||||
LUAU_ASSERT(op.base == noreg);
|
||||
LUAU_ASSERT(op.index == noreg || op.index.size == base.size);
|
||||
|
||||
op.base = base;
|
||||
return op;
|
||||
}
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
116
CodeGen/include/Luau/RegisterX64.h
Normal file
116
CodeGen/include/Luau/RegisterX64.h
Normal file
|
@ -0,0 +1,116 @@
|
|||
#pragma once
|
||||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
enum class SizeX64 : uint8_t
|
||||
{
|
||||
none,
|
||||
byte,
|
||||
word,
|
||||
dword,
|
||||
qword,
|
||||
xmmword,
|
||||
ymmword,
|
||||
};
|
||||
|
||||
struct RegisterX64
|
||||
{
|
||||
SizeX64 size : 3;
|
||||
uint8_t index : 5;
|
||||
|
||||
constexpr bool operator==(RegisterX64 rhs) const
|
||||
{
|
||||
return size == rhs.size && index == rhs.index;
|
||||
}
|
||||
|
||||
constexpr bool operator!=(RegisterX64 rhs) const
|
||||
{
|
||||
return !(*this == rhs);
|
||||
}
|
||||
};
|
||||
|
||||
constexpr RegisterX64 noreg{SizeX64::none, 16};
|
||||
constexpr RegisterX64 rip{SizeX64::none, 0};
|
||||
|
||||
constexpr RegisterX64 al{SizeX64::byte, 0};
|
||||
constexpr RegisterX64 cl{SizeX64::byte, 1};
|
||||
constexpr RegisterX64 dl{SizeX64::byte, 2};
|
||||
constexpr RegisterX64 bl{SizeX64::byte, 3};
|
||||
|
||||
constexpr RegisterX64 eax{SizeX64::dword, 0};
|
||||
constexpr RegisterX64 ecx{SizeX64::dword, 1};
|
||||
constexpr RegisterX64 edx{SizeX64::dword, 2};
|
||||
constexpr RegisterX64 ebx{SizeX64::dword, 3};
|
||||
constexpr RegisterX64 esp{SizeX64::dword, 4};
|
||||
constexpr RegisterX64 ebp{SizeX64::dword, 5};
|
||||
constexpr RegisterX64 esi{SizeX64::dword, 6};
|
||||
constexpr RegisterX64 edi{SizeX64::dword, 7};
|
||||
constexpr RegisterX64 r8d{SizeX64::dword, 8};
|
||||
constexpr RegisterX64 r9d{SizeX64::dword, 9};
|
||||
constexpr RegisterX64 r10d{SizeX64::dword, 10};
|
||||
constexpr RegisterX64 r11d{SizeX64::dword, 11};
|
||||
constexpr RegisterX64 r12d{SizeX64::dword, 12};
|
||||
constexpr RegisterX64 r13d{SizeX64::dword, 13};
|
||||
constexpr RegisterX64 r14d{SizeX64::dword, 14};
|
||||
constexpr RegisterX64 r15d{SizeX64::dword, 15};
|
||||
|
||||
constexpr RegisterX64 rax{SizeX64::qword, 0};
|
||||
constexpr RegisterX64 rcx{SizeX64::qword, 1};
|
||||
constexpr RegisterX64 rdx{SizeX64::qword, 2};
|
||||
constexpr RegisterX64 rbx{SizeX64::qword, 3};
|
||||
constexpr RegisterX64 rsp{SizeX64::qword, 4};
|
||||
constexpr RegisterX64 rbp{SizeX64::qword, 5};
|
||||
constexpr RegisterX64 rsi{SizeX64::qword, 6};
|
||||
constexpr RegisterX64 rdi{SizeX64::qword, 7};
|
||||
constexpr RegisterX64 r8{SizeX64::qword, 8};
|
||||
constexpr RegisterX64 r9{SizeX64::qword, 9};
|
||||
constexpr RegisterX64 r10{SizeX64::qword, 10};
|
||||
constexpr RegisterX64 r11{SizeX64::qword, 11};
|
||||
constexpr RegisterX64 r12{SizeX64::qword, 12};
|
||||
constexpr RegisterX64 r13{SizeX64::qword, 13};
|
||||
constexpr RegisterX64 r14{SizeX64::qword, 14};
|
||||
constexpr RegisterX64 r15{SizeX64::qword, 15};
|
||||
|
||||
constexpr RegisterX64 xmm0{SizeX64::xmmword, 0};
|
||||
constexpr RegisterX64 xmm1{SizeX64::xmmword, 1};
|
||||
constexpr RegisterX64 xmm2{SizeX64::xmmword, 2};
|
||||
constexpr RegisterX64 xmm3{SizeX64::xmmword, 3};
|
||||
constexpr RegisterX64 xmm4{SizeX64::xmmword, 4};
|
||||
constexpr RegisterX64 xmm5{SizeX64::xmmword, 5};
|
||||
constexpr RegisterX64 xmm6{SizeX64::xmmword, 6};
|
||||
constexpr RegisterX64 xmm7{SizeX64::xmmword, 7};
|
||||
constexpr RegisterX64 xmm8{SizeX64::xmmword, 8};
|
||||
constexpr RegisterX64 xmm9{SizeX64::xmmword, 9};
|
||||
constexpr RegisterX64 xmm10{SizeX64::xmmword, 10};
|
||||
constexpr RegisterX64 xmm11{SizeX64::xmmword, 11};
|
||||
constexpr RegisterX64 xmm12{SizeX64::xmmword, 12};
|
||||
constexpr RegisterX64 xmm13{SizeX64::xmmword, 13};
|
||||
constexpr RegisterX64 xmm14{SizeX64::xmmword, 14};
|
||||
constexpr RegisterX64 xmm15{SizeX64::xmmword, 15};
|
||||
|
||||
constexpr RegisterX64 ymm0{SizeX64::ymmword, 0};
|
||||
constexpr RegisterX64 ymm1{SizeX64::ymmword, 1};
|
||||
constexpr RegisterX64 ymm2{SizeX64::ymmword, 2};
|
||||
constexpr RegisterX64 ymm3{SizeX64::ymmword, 3};
|
||||
constexpr RegisterX64 ymm4{SizeX64::ymmword, 4};
|
||||
constexpr RegisterX64 ymm5{SizeX64::ymmword, 5};
|
||||
constexpr RegisterX64 ymm6{SizeX64::ymmword, 6};
|
||||
constexpr RegisterX64 ymm7{SizeX64::ymmword, 7};
|
||||
constexpr RegisterX64 ymm8{SizeX64::ymmword, 8};
|
||||
constexpr RegisterX64 ymm9{SizeX64::ymmword, 9};
|
||||
constexpr RegisterX64 ymm10{SizeX64::ymmword, 10};
|
||||
constexpr RegisterX64 ymm11{SizeX64::ymmword, 11};
|
||||
constexpr RegisterX64 ymm12{SizeX64::ymmword, 12};
|
||||
constexpr RegisterX64 ymm13{SizeX64::ymmword, 13};
|
||||
constexpr RegisterX64 ymm14{SizeX64::ymmword, 14};
|
||||
constexpr RegisterX64 ymm15{SizeX64::ymmword, 15};
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
1005
CodeGen/src/AssemblyBuilderX64.cpp
Normal file
1005
CodeGen/src/AssemblyBuilderX64.cpp
Normal file
File diff suppressed because it is too large
Load diff
|
@ -224,6 +224,7 @@ private:
|
|||
|
||||
DenseHashMap<ConstantKey, int32_t, ConstantKeyHash> constantMap;
|
||||
DenseHashMap<TableShape, int32_t, TableShapeHash> tableShapeMap;
|
||||
DenseHashMap<uint32_t, int16_t> protoMap;
|
||||
|
||||
int debugLine = 0;
|
||||
|
||||
|
@ -246,7 +247,7 @@ private:
|
|||
void validate() const;
|
||||
|
||||
std::string dumpCurrentFunction() const;
|
||||
const uint32_t* dumpInstruction(const uint32_t* opcode, std::string& output) const;
|
||||
void dumpInstruction(const uint32_t* opcode, std::string& output, int targetLabel) const;
|
||||
|
||||
void writeFunction(std::string& ss, uint32_t id) const;
|
||||
void writeLineInfo(std::string& ss) const;
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
#include <algorithm>
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAG(LuauCompileNestedClosureO2)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -128,6 +130,20 @@ inline bool isSkipC(LuauOpcode op)
|
|||
}
|
||||
}
|
||||
|
||||
static int getJumpTarget(uint32_t insn, uint32_t pc)
|
||||
{
|
||||
LuauOpcode op = LuauOpcode(LUAU_INSN_OP(insn));
|
||||
|
||||
if (isJumpD(op))
|
||||
return int(pc + LUAU_INSN_D(insn) + 1);
|
||||
else if (isSkipC(op) && LUAU_INSN_C(insn))
|
||||
return int(pc + LUAU_INSN_C(insn) + 1);
|
||||
else if (op == LOP_JUMPX)
|
||||
return int(pc + LUAU_INSN_E(insn) + 1);
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool BytecodeBuilder::StringRef::operator==(const StringRef& other) const
|
||||
{
|
||||
return (data && other.data) ? (length == other.length && memcmp(data, other.data, length) == 0) : (data == other.data);
|
||||
|
@ -181,6 +197,7 @@ size_t BytecodeBuilder::TableShapeHash::operator()(const TableShape& v) const
|
|||
BytecodeBuilder::BytecodeBuilder(BytecodeEncoder* encoder)
|
||||
: constantMap({Constant::Type_Nil, ~0ull})
|
||||
, tableShapeMap(TableShape())
|
||||
, protoMap(~0u)
|
||||
, stringTable({nullptr, 0})
|
||||
, encoder(encoder)
|
||||
{
|
||||
|
@ -250,6 +267,7 @@ void BytecodeBuilder::endFunction(uint8_t maxstacksize, uint8_t numupvalues)
|
|||
|
||||
constantMap.clear();
|
||||
tableShapeMap.clear();
|
||||
protoMap.clear();
|
||||
|
||||
debugRemarks.clear();
|
||||
debugRemarkBuffer.clear();
|
||||
|
@ -372,11 +390,17 @@ int32_t BytecodeBuilder::addConstantClosure(uint32_t fid)
|
|||
|
||||
int16_t BytecodeBuilder::addChildFunction(uint32_t fid)
|
||||
{
|
||||
if (FFlag::LuauCompileNestedClosureO2)
|
||||
if (int16_t* cache = protoMap.find(fid))
|
||||
return *cache;
|
||||
|
||||
uint32_t id = uint32_t(protos.size());
|
||||
|
||||
if (id >= kMaxClosureCount)
|
||||
return -1;
|
||||
|
||||
if (FFlag::LuauCompileNestedClosureO2)
|
||||
protoMap[fid] = int16_t(id);
|
||||
protos.push_back(fid);
|
||||
|
||||
return int16_t(id);
|
||||
|
@ -1398,7 +1422,7 @@ void BytecodeBuilder::validate() const
|
|||
}
|
||||
#endif
|
||||
|
||||
const uint32_t* BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result) const
|
||||
void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result, int targetLabel) const
|
||||
{
|
||||
uint32_t insn = *code++;
|
||||
|
||||
|
@ -1493,39 +1517,39 @@ const uint32_t* BytecodeBuilder::dumpInstruction(const uint32_t* code, std::stri
|
|||
break;
|
||||
|
||||
case LOP_JUMP:
|
||||
formatAppend(result, "JUMP %+d\n", LUAU_INSN_D(insn));
|
||||
formatAppend(result, "JUMP L%d\n", targetLabel);
|
||||
break;
|
||||
|
||||
case LOP_JUMPIF:
|
||||
formatAppend(result, "JUMPIF R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
|
||||
formatAppend(result, "JUMPIF R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
|
||||
break;
|
||||
|
||||
case LOP_JUMPIFNOT:
|
||||
formatAppend(result, "JUMPIFNOT R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
|
||||
formatAppend(result, "JUMPIFNOT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
|
||||
break;
|
||||
|
||||
case LOP_JUMPIFEQ:
|
||||
formatAppend(result, "JUMPIFEQ R%d R%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn));
|
||||
formatAppend(result, "JUMPIFEQ R%d R%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
|
||||
break;
|
||||
|
||||
case LOP_JUMPIFLE:
|
||||
formatAppend(result, "JUMPIFLE R%d R%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn));
|
||||
formatAppend(result, "JUMPIFLE R%d R%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
|
||||
break;
|
||||
|
||||
case LOP_JUMPIFLT:
|
||||
formatAppend(result, "JUMPIFLT R%d R%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn));
|
||||
formatAppend(result, "JUMPIFLT R%d R%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
|
||||
break;
|
||||
|
||||
case LOP_JUMPIFNOTEQ:
|
||||
formatAppend(result, "JUMPIFNOTEQ R%d R%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn));
|
||||
formatAppend(result, "JUMPIFNOTEQ R%d R%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
|
||||
break;
|
||||
|
||||
case LOP_JUMPIFNOTLE:
|
||||
formatAppend(result, "JUMPIFNOTLE R%d R%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn));
|
||||
formatAppend(result, "JUMPIFNOTLE R%d R%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
|
||||
break;
|
||||
|
||||
case LOP_JUMPIFNOTLT:
|
||||
formatAppend(result, "JUMPIFNOTLT R%d R%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn));
|
||||
formatAppend(result, "JUMPIFNOTLT R%d R%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
|
||||
break;
|
||||
|
||||
case LOP_ADD:
|
||||
|
@ -1621,35 +1645,35 @@ const uint32_t* BytecodeBuilder::dumpInstruction(const uint32_t* code, std::stri
|
|||
break;
|
||||
|
||||
case LOP_FORNPREP:
|
||||
formatAppend(result, "FORNPREP R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
|
||||
formatAppend(result, "FORNPREP R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
|
||||
break;
|
||||
|
||||
case LOP_FORNLOOP:
|
||||
formatAppend(result, "FORNLOOP R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
|
||||
formatAppend(result, "FORNLOOP R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
|
||||
break;
|
||||
|
||||
case LOP_FORGPREP:
|
||||
formatAppend(result, "FORGPREP R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
|
||||
formatAppend(result, "FORGPREP R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
|
||||
break;
|
||||
|
||||
case LOP_FORGLOOP:
|
||||
formatAppend(result, "FORGLOOP R%d %+d %d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn), *code++);
|
||||
formatAppend(result, "FORGLOOP R%d L%d %d\n", LUAU_INSN_A(insn), targetLabel, *code++);
|
||||
break;
|
||||
|
||||
case LOP_FORGPREP_INEXT:
|
||||
formatAppend(result, "FORGPREP_INEXT R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
|
||||
formatAppend(result, "FORGPREP_INEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
|
||||
break;
|
||||
|
||||
case LOP_FORGLOOP_INEXT:
|
||||
formatAppend(result, "FORGLOOP_INEXT R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
|
||||
formatAppend(result, "FORGLOOP_INEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
|
||||
break;
|
||||
|
||||
case LOP_FORGPREP_NEXT:
|
||||
formatAppend(result, "FORGPREP_NEXT R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
|
||||
formatAppend(result, "FORGPREP_NEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
|
||||
break;
|
||||
|
||||
case LOP_FORGLOOP_NEXT:
|
||||
formatAppend(result, "FORGLOOP_NEXT R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
|
||||
formatAppend(result, "FORGLOOP_NEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
|
||||
break;
|
||||
|
||||
case LOP_GETVARARGS:
|
||||
|
@ -1665,7 +1689,7 @@ const uint32_t* BytecodeBuilder::dumpInstruction(const uint32_t* code, std::stri
|
|||
break;
|
||||
|
||||
case LOP_JUMPBACK:
|
||||
formatAppend(result, "JUMPBACK %+d\n", LUAU_INSN_D(insn));
|
||||
formatAppend(result, "JUMPBACK L%d\n", targetLabel);
|
||||
break;
|
||||
|
||||
case LOP_LOADKX:
|
||||
|
@ -1673,26 +1697,26 @@ const uint32_t* BytecodeBuilder::dumpInstruction(const uint32_t* code, std::stri
|
|||
break;
|
||||
|
||||
case LOP_JUMPX:
|
||||
formatAppend(result, "JUMPX %+d\n", LUAU_INSN_E(insn));
|
||||
formatAppend(result, "JUMPX L%d\n", targetLabel);
|
||||
break;
|
||||
|
||||
case LOP_FASTCALL:
|
||||
formatAppend(result, "FASTCALL %d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_C(insn));
|
||||
formatAppend(result, "FASTCALL %d L%d\n", LUAU_INSN_A(insn), targetLabel);
|
||||
break;
|
||||
|
||||
case LOP_FASTCALL1:
|
||||
formatAppend(result, "FASTCALL1 %d R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
|
||||
formatAppend(result, "FASTCALL1 %d R%d L%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), targetLabel);
|
||||
break;
|
||||
case LOP_FASTCALL2:
|
||||
{
|
||||
uint32_t aux = *code++;
|
||||
formatAppend(result, "FASTCALL2 %d R%d R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), aux, LUAU_INSN_C(insn));
|
||||
formatAppend(result, "FASTCALL2 %d R%d R%d L%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), aux, targetLabel);
|
||||
break;
|
||||
}
|
||||
case LOP_FASTCALL2K:
|
||||
{
|
||||
uint32_t aux = *code++;
|
||||
formatAppend(result, "FASTCALL2K %d R%d K%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), aux, LUAU_INSN_C(insn));
|
||||
formatAppend(result, "FASTCALL2K %d R%d K%d L%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), aux, targetLabel);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1702,23 +1726,24 @@ const uint32_t* BytecodeBuilder::dumpInstruction(const uint32_t* code, std::stri
|
|||
|
||||
case LOP_CAPTURE:
|
||||
formatAppend(result, "CAPTURE %s %c%d\n",
|
||||
LUAU_INSN_A(insn) == LCT_UPVAL ? "UPVAL" : LUAU_INSN_A(insn) == LCT_REF ? "REF" : LUAU_INSN_A(insn) == LCT_VAL ? "VAL" : "",
|
||||
LUAU_INSN_A(insn) == LCT_UPVAL ? "UPVAL"
|
||||
: LUAU_INSN_A(insn) == LCT_REF ? "REF"
|
||||
: LUAU_INSN_A(insn) == LCT_VAL ? "VAL"
|
||||
: "",
|
||||
LUAU_INSN_A(insn) == LCT_UPVAL ? 'U' : 'R', LUAU_INSN_B(insn));
|
||||
break;
|
||||
|
||||
case LOP_JUMPIFEQK:
|
||||
formatAppend(result, "JUMPIFEQK R%d K%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn));
|
||||
formatAppend(result, "JUMPIFEQK R%d K%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
|
||||
break;
|
||||
|
||||
case LOP_JUMPIFNOTEQK:
|
||||
formatAppend(result, "JUMPIFNOTEQK R%d K%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn));
|
||||
formatAppend(result, "JUMPIFNOTEQK R%d K%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
|
||||
break;
|
||||
|
||||
default:
|
||||
LUAU_ASSERT(!"Unsupported opcode");
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
std::string BytecodeBuilder::dumpCurrentFunction() const
|
||||
|
@ -1726,9 +1751,6 @@ std::string BytecodeBuilder::dumpCurrentFunction() const
|
|||
if ((dumpFlags & Dump_Code) == 0)
|
||||
return std::string();
|
||||
|
||||
const uint32_t* code = insns.data();
|
||||
const uint32_t* codeEnd = insns.data() + insns.size();
|
||||
|
||||
int lastLine = -1;
|
||||
size_t nextRemark = 0;
|
||||
|
||||
|
@ -1750,21 +1772,45 @@ std::string BytecodeBuilder::dumpCurrentFunction() const
|
|||
}
|
||||
}
|
||||
|
||||
while (code != codeEnd)
|
||||
std::vector<int> labels(insns.size(), -1);
|
||||
|
||||
// annotate valid jump targets with 0
|
||||
for (size_t i = 0; i < insns.size();)
|
||||
{
|
||||
int target = getJumpTarget(insns[i], uint32_t(i));
|
||||
|
||||
if (target >= 0)
|
||||
{
|
||||
LUAU_ASSERT(size_t(target) < insns.size());
|
||||
labels[target] = 0;
|
||||
}
|
||||
|
||||
i += getOpLength(LuauOpcode(LUAU_INSN_OP(insns[i])));
|
||||
LUAU_ASSERT(i <= insns.size());
|
||||
}
|
||||
|
||||
int nextLabel = 0;
|
||||
|
||||
// compute label ids (sequential integers for all jump targets)
|
||||
for (size_t i = 0; i < labels.size(); ++i)
|
||||
if (labels[i] == 0)
|
||||
labels[i] = nextLabel++;
|
||||
|
||||
for (size_t i = 0; i < insns.size();)
|
||||
{
|
||||
const uint32_t* code = &insns[i];
|
||||
uint8_t op = LUAU_INSN_OP(*code);
|
||||
uint32_t pc = uint32_t(code - insns.data());
|
||||
|
||||
if (op == LOP_PREPVARARGS)
|
||||
{
|
||||
// Don't emit function header in bytecode - it's used for call dispatching and doesn't contain "interesting" information
|
||||
code++;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dumpFlags & Dump_Remarks)
|
||||
{
|
||||
while (nextRemark < debugRemarks.size() && debugRemarks[nextRemark].first == pc)
|
||||
while (nextRemark < debugRemarks.size() && debugRemarks[nextRemark].first == i)
|
||||
{
|
||||
formatAppend(result, "REMARK %s\n", debugRemarkBuffer.c_str() + debugRemarks[nextRemark].second);
|
||||
nextRemark++;
|
||||
|
@ -1773,7 +1819,7 @@ std::string BytecodeBuilder::dumpCurrentFunction() const
|
|||
|
||||
if (dumpFlags & Dump_Source)
|
||||
{
|
||||
int line = lines[pc];
|
||||
int line = lines[i];
|
||||
|
||||
if (line > 0 && line != lastLine)
|
||||
{
|
||||
|
@ -1784,11 +1830,17 @@ std::string BytecodeBuilder::dumpCurrentFunction() const
|
|||
}
|
||||
|
||||
if (dumpFlags & Dump_Lines)
|
||||
{
|
||||
formatAppend(result, "%d: ", lines[pc]);
|
||||
}
|
||||
formatAppend(result, "%d: ", lines[i]);
|
||||
|
||||
code = dumpInstruction(code, result);
|
||||
if (labels[i] != -1)
|
||||
formatAppend(result, "L%d: ", labels[i]);
|
||||
|
||||
int target = getJumpTarget(*code, uint32_t(i));
|
||||
|
||||
dumpInstruction(code, result, target >= 0 ? labels[target] : -1);
|
||||
|
||||
i += getOpLength(LuauOpcode(op));
|
||||
LUAU_ASSERT(i <= insns.size());
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
@ -15,12 +15,8 @@
|
|||
#include <algorithm>
|
||||
#include <bitset>
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileSupportInlining, false)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileIter, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileIterNoReserve, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileIterNoPairs, false)
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauCompileLoopUnrollThreshold, 25)
|
||||
|
@ -30,6 +26,8 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25)
|
|||
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
|
||||
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileNestedClosureO2, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -100,13 +98,11 @@ struct Compiler
|
|||
upvals.reserve(16);
|
||||
}
|
||||
|
||||
uint8_t getLocal(AstLocal* local)
|
||||
int getLocalReg(AstLocal* local)
|
||||
{
|
||||
Local* l = locals.find(local);
|
||||
LUAU_ASSERT(l);
|
||||
LUAU_ASSERT(l->allocated);
|
||||
|
||||
return l->reg;
|
||||
return l && l->allocated ? l->reg : -1;
|
||||
}
|
||||
|
||||
uint8_t getUpval(AstLocal* local)
|
||||
|
@ -159,41 +155,38 @@ struct Compiler
|
|||
|
||||
AstExprFunction* getFunctionExpr(AstExpr* node)
|
||||
{
|
||||
if (AstExprLocal* le = node->as<AstExprLocal>())
|
||||
if (AstExprLocal* expr = node->as<AstExprLocal>())
|
||||
{
|
||||
Variable* lv = variables.find(le->local);
|
||||
Variable* lv = variables.find(expr->local);
|
||||
|
||||
if (!lv || lv->written || !lv->init)
|
||||
return nullptr;
|
||||
|
||||
return getFunctionExpr(lv->init);
|
||||
}
|
||||
else if (AstExprGroup* ge = node->as<AstExprGroup>())
|
||||
return getFunctionExpr(ge->expr);
|
||||
else if (AstExprGroup* expr = node->as<AstExprGroup>())
|
||||
return getFunctionExpr(expr->expr);
|
||||
else if (AstExprTypeAssertion* expr = node->as<AstExprTypeAssertion>())
|
||||
return getFunctionExpr(expr->expr);
|
||||
else
|
||||
return node->as<AstExprFunction>();
|
||||
}
|
||||
|
||||
bool canInlineFunctionBody(AstStat* stat)
|
||||
{
|
||||
if (FFlag::LuauCompileNestedClosureO2)
|
||||
return true; // TODO: remove this function
|
||||
|
||||
struct CanInlineVisitor : AstVisitor
|
||||
{
|
||||
bool result = true;
|
||||
|
||||
bool visit(AstExpr* node) override
|
||||
bool visit(AstExprFunction* node) override
|
||||
{
|
||||
// nested functions may capture function arguments, and our upval handling doesn't handle elided variables (constant)
|
||||
// TODO: we could remove this case if we changed function compilation to create temporary locals for constant upvalues
|
||||
// TODO: additionally we would need to change upvalue handling in compileExprFunction to handle upvalue->local migration
|
||||
result = result && !node->is<AstExprFunction>();
|
||||
return result;
|
||||
}
|
||||
result = false;
|
||||
|
||||
bool visit(AstStat* node) override
|
||||
{
|
||||
// loops may need to be unrolled which can result in cost amplification
|
||||
result = result && !node->is<AstStatFor>();
|
||||
return result;
|
||||
// short-circuit to avoid analyzing nested closure bodies
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -275,8 +268,7 @@ struct Compiler
|
|||
f.upvals = upvals;
|
||||
|
||||
// record information for inlining
|
||||
if (FFlag::LuauCompileSupportInlining && options.optimizationLevel >= 2 && !func->vararg && canInlineFunctionBody(func->body) &&
|
||||
!getfenvUsed && !setfenvUsed)
|
||||
if (options.optimizationLevel >= 2 && !func->vararg && canInlineFunctionBody(func->body) && !getfenvUsed && !setfenvUsed)
|
||||
{
|
||||
f.canInline = true;
|
||||
f.stackSize = stackSize;
|
||||
|
@ -346,8 +338,8 @@ struct Compiler
|
|||
|
||||
uint8_t argreg;
|
||||
|
||||
if (isExprLocalReg(arg))
|
||||
argreg = getLocal(arg->as<AstExprLocal>()->local);
|
||||
if (int reg = getExprLocalReg(arg); reg >= 0)
|
||||
argreg = uint8_t(reg);
|
||||
else
|
||||
{
|
||||
argreg = uint8_t(regs + 1);
|
||||
|
@ -403,8 +395,8 @@ struct Compiler
|
|||
}
|
||||
}
|
||||
|
||||
if (isExprLocalReg(expr->args.data[i]))
|
||||
args[i] = getLocal(expr->args.data[i]->as<AstExprLocal>()->local);
|
||||
if (int reg = getExprLocalReg(expr->args.data[i]); reg >= 0)
|
||||
args[i] = uint8_t(reg);
|
||||
else
|
||||
{
|
||||
args[i] = uint8_t(regs + 1 + i);
|
||||
|
@ -489,19 +481,19 @@ struct Compiler
|
|||
return false;
|
||||
}
|
||||
|
||||
// TODO: we can compile functions with mismatching arity at call site but it's more annoying
|
||||
if (func->args.size != expr->args.size)
|
||||
{
|
||||
bytecode.addDebugRemark("inlining failed: argument count mismatch (expected %d, got %d)", int(func->args.size), int(expr->args.size));
|
||||
return false;
|
||||
}
|
||||
|
||||
// we use a dynamic cost threshold that's based on the fixed limit boosted by the cost advantage we gain due to inlining
|
||||
// compute constant bitvector for all arguments to feed the cost model
|
||||
bool varc[8] = {};
|
||||
for (size_t i = 0; i < expr->args.size && i < 8; ++i)
|
||||
for (size_t i = 0; i < func->args.size && i < expr->args.size && i < 8; ++i)
|
||||
varc[i] = isConstant(expr->args.data[i]);
|
||||
|
||||
int inlinedCost = computeCost(fi->costModel, varc, std::min(int(expr->args.size), 8));
|
||||
// if the last argument only returns a single value, all following arguments are nil
|
||||
if (expr->args.size != 0 &&
|
||||
!(expr->args.data[expr->args.size - 1]->is<AstExprCall>() || expr->args.data[expr->args.size - 1]->is<AstExprVarargs>()))
|
||||
for (size_t i = expr->args.size; i < func->args.size && i < 8; ++i)
|
||||
varc[i] = true;
|
||||
|
||||
// we use a dynamic cost threshold that's based on the fixed limit boosted by the cost advantage we gain due to inlining
|
||||
int inlinedCost = computeCost(fi->costModel, varc, std::min(int(func->args.size), 8));
|
||||
int baselineCost = computeCost(fi->costModel, nullptr, 0) + 3;
|
||||
int inlineProfit = (inlinedCost == 0) ? thresholdMaxBoost : std::min(thresholdMaxBoost, 100 * baselineCost / inlinedCost);
|
||||
|
||||
|
@ -533,15 +525,44 @@ struct Compiler
|
|||
for (size_t i = 0; i < func->args.size; ++i)
|
||||
{
|
||||
AstLocal* var = func->args.data[i];
|
||||
AstExpr* arg = expr->args.data[i];
|
||||
AstExpr* arg = i < expr->args.size ? expr->args.data[i] : nullptr;
|
||||
|
||||
if (Variable* vv = variables.find(var); vv && vv->written)
|
||||
if (i + 1 == expr->args.size && func->args.size > expr->args.size && (arg->is<AstExprCall>() || arg->is<AstExprVarargs>()))
|
||||
{
|
||||
// if the last argument can return multiple values, we need to compute all of them into the remaining arguments
|
||||
unsigned int tail = unsigned(func->args.size - expr->args.size) + 1;
|
||||
uint8_t reg = allocReg(arg, tail);
|
||||
|
||||
if (AstExprCall* expr = arg->as<AstExprCall>())
|
||||
compileExprCall(expr, reg, tail, /* targetTop= */ true);
|
||||
else if (AstExprVarargs* expr = arg->as<AstExprVarargs>())
|
||||
compileExprVarargs(expr, reg, tail);
|
||||
else
|
||||
LUAU_ASSERT(!"Unexpected expression type");
|
||||
|
||||
for (size_t j = i; j < func->args.size; ++j)
|
||||
pushLocal(func->args.data[j], uint8_t(reg + (j - i)));
|
||||
|
||||
// all remaining function arguments have been allocated and assigned to
|
||||
break;
|
||||
}
|
||||
else if (Variable* vv = variables.find(var); vv && vv->written)
|
||||
{
|
||||
// if the argument is mutated, we need to allocate a fresh register even if it's a constant
|
||||
uint8_t reg = allocReg(arg, 1);
|
||||
|
||||
if (arg)
|
||||
compileExprTemp(arg, reg);
|
||||
else
|
||||
bytecode.emitABC(LOP_LOADNIL, reg, 0, 0);
|
||||
|
||||
pushLocal(var, reg);
|
||||
}
|
||||
else if (arg == nullptr)
|
||||
{
|
||||
// since the argument is not mutated, we can simply fold the value into the expressions that need it
|
||||
locstants[var] = {Constant::Type_Nil};
|
||||
}
|
||||
else if (const Constant* cv = constants.find(arg); cv && cv->type != Constant::Type_Unknown)
|
||||
{
|
||||
// since the argument is not mutated, we can simply fold the value into the expressions that need it
|
||||
|
@ -553,20 +574,26 @@ struct Compiler
|
|||
Variable* lv = le ? variables.find(le->local) : nullptr;
|
||||
|
||||
// if the argument is a local that isn't mutated, we will simply reuse the existing register
|
||||
if (isExprLocalReg(arg) && (!lv || !lv->written))
|
||||
if (int reg = le ? getExprLocalReg(le) : -1; reg >= 0 && (!lv || !lv->written))
|
||||
{
|
||||
uint8_t reg = getLocal(le->local);
|
||||
pushLocal(var, reg);
|
||||
pushLocal(var, uint8_t(reg));
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t reg = allocReg(arg, 1);
|
||||
compileExprTemp(arg, reg);
|
||||
pushLocal(var, reg);
|
||||
uint8_t temp = allocReg(arg, 1);
|
||||
compileExprTemp(arg, temp);
|
||||
pushLocal(var, temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// evaluate extra expressions for side effects
|
||||
for (size_t i = func->args.size; i < expr->args.size; ++i)
|
||||
{
|
||||
RegScope rsi(this);
|
||||
compileExprAuto(expr->args.data[i], rsi);
|
||||
}
|
||||
|
||||
// fold constant values updated above into expressions in the function body
|
||||
foldConstants(constants, variables, locstants, func->body);
|
||||
|
||||
|
@ -627,12 +654,15 @@ struct Compiler
|
|||
FInt::LuauCompileInlineThresholdMaxBoost, FInt::LuauCompileInlineDepth))
|
||||
return;
|
||||
|
||||
if (fi && !fi->canInline)
|
||||
// add a debug remark for cases when we didn't even call tryCompileInlinedCall
|
||||
if (func && !(fi && fi->canInline))
|
||||
{
|
||||
if (func->vararg)
|
||||
bytecode.addDebugRemark("inlining failed: function is variadic");
|
||||
else
|
||||
bytecode.addDebugRemark("inlining failed: complex constructs in function body");
|
||||
else if (!fi)
|
||||
bytecode.addDebugRemark("inlining failed: can't inline recursive calls");
|
||||
else if (getfenvUsed || setfenvUsed)
|
||||
bytecode.addDebugRemark("inlining failed: module uses getfenv/setfenv");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -677,9 +707,9 @@ struct Compiler
|
|||
LUAU_ASSERT(fi);
|
||||
|
||||
// Optimization: use local register directly in NAMECALL if possible
|
||||
if (isExprLocalReg(fi->expr))
|
||||
if (int reg = getExprLocalReg(fi->expr); reg >= 0)
|
||||
{
|
||||
selfreg = getLocal(fi->expr->as<AstExprLocal>()->local);
|
||||
selfreg = uint8_t(reg);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -785,6 +815,8 @@ struct Compiler
|
|||
|
||||
void compileExprFunction(AstExprFunction* expr, uint8_t target)
|
||||
{
|
||||
RegScope rs(this);
|
||||
|
||||
const Function* f = functions.find(expr);
|
||||
LUAU_ASSERT(f);
|
||||
|
||||
|
@ -795,6 +827,67 @@ struct Compiler
|
|||
if (pid < 0)
|
||||
CompileError::raise(expr->location, "Exceeded closure limit; simplify the code to compile");
|
||||
|
||||
if (FFlag::LuauCompileNestedClosureO2)
|
||||
{
|
||||
captures.clear();
|
||||
captures.reserve(f->upvals.size());
|
||||
|
||||
for (AstLocal* uv : f->upvals)
|
||||
{
|
||||
LUAU_ASSERT(uv->functionDepth < expr->functionDepth);
|
||||
|
||||
if (int reg = getLocalReg(uv); reg >= 0)
|
||||
{
|
||||
// note: we can't check if uv is an upvalue in the current frame because inlining can migrate from upvalues to locals
|
||||
Variable* ul = variables.find(uv);
|
||||
bool immutable = !ul || !ul->written;
|
||||
|
||||
captures.push_back({immutable ? LCT_VAL : LCT_REF, uint8_t(reg)});
|
||||
}
|
||||
else if (const Constant* uc = locstants.find(uv); uc && uc->type != Constant::Type_Unknown)
|
||||
{
|
||||
// inlining can result in an upvalue capture of a constant, in which case we can't capture without a temporary register
|
||||
uint8_t reg = allocReg(expr, 1);
|
||||
compileExprConstant(expr, uc, reg);
|
||||
|
||||
captures.push_back({LCT_VAL, reg});
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(uv->functionDepth < expr->functionDepth - 1);
|
||||
|
||||
// get upvalue from parent frame
|
||||
// note: this will add uv to the current upvalue list if necessary
|
||||
uint8_t uid = getUpval(uv);
|
||||
|
||||
captures.push_back({LCT_UPVAL, uid});
|
||||
}
|
||||
}
|
||||
|
||||
// Optimization: when closure has no upvalues, or upvalues are safe to share, instead of allocating it every time we can share closure
|
||||
// objects (this breaks assumptions about function identity which can lead to setfenv not working as expected, so we disable this when it
|
||||
// is used)
|
||||
int16_t shared = -1;
|
||||
|
||||
if (options.optimizationLevel >= 1 && shouldShareClosure(expr) && !setfenvUsed)
|
||||
{
|
||||
int32_t cid = bytecode.addConstantClosure(f->id);
|
||||
|
||||
if (cid >= 0 && cid < 32768)
|
||||
shared = int16_t(cid);
|
||||
}
|
||||
|
||||
if (shared >= 0)
|
||||
bytecode.emitAD(LOP_DUPCLOSURE, target, shared);
|
||||
else
|
||||
bytecode.emitAD(LOP_NEWCLOSURE, target, pid);
|
||||
|
||||
for (const Capture& c : captures)
|
||||
bytecode.emitABC(LOP_CAPTURE, uint8_t(c.type), c.data, 0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bool shared = false;
|
||||
|
||||
// Optimization: when closure has no upvalues, or upvalues are safe to share, instead of allocating it every time we can share closure
|
||||
|
@ -824,9 +917,10 @@ struct Compiler
|
|||
if (uv->functionDepth == expr->functionDepth - 1)
|
||||
{
|
||||
// get local variable
|
||||
uint8_t reg = getLocal(uv);
|
||||
int reg = getLocalReg(uv);
|
||||
LUAU_ASSERT(reg >= 0);
|
||||
|
||||
bytecode.emitABC(LOP_CAPTURE, uint8_t(immutable ? LCT_VAL : LCT_REF), reg, 0);
|
||||
bytecode.emitABC(LOP_CAPTURE, uint8_t(immutable ? LCT_VAL : LCT_REF), uint8_t(reg), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -931,6 +1025,13 @@ struct Compiler
|
|||
return cv && cv->type != Constant::Type_Unknown && !cv->isTruthful();
|
||||
}
|
||||
|
||||
Constant getConstant(AstExpr* node)
|
||||
{
|
||||
const Constant* cv = constants.find(node);
|
||||
|
||||
return cv ? *cv : Constant{Constant::Type_Unknown};
|
||||
}
|
||||
|
||||
size_t compileCompareJump(AstExprBinary* expr, bool not_ = false)
|
||||
{
|
||||
RegScope rs(this);
|
||||
|
@ -1041,9 +1142,7 @@ struct Compiler
|
|||
void compileConditionValue(AstExpr* node, const uint8_t* target, std::vector<size_t>& skipJump, bool onlyTruth)
|
||||
{
|
||||
// Optimization: we don't need to compute constant values
|
||||
const Constant* cv = constants.find(node);
|
||||
|
||||
if (cv && cv->type != Constant::Type_Unknown)
|
||||
if (const Constant* cv = constants.find(node); cv && cv->type != Constant::Type_Unknown)
|
||||
{
|
||||
// note that we only need to compute the value if it's truthy; otherwise we cal fall through
|
||||
if (cv->isTruthful() == onlyTruth)
|
||||
|
@ -1201,9 +1300,7 @@ struct Compiler
|
|||
RegScope rs(this);
|
||||
|
||||
// Optimization: when left hand side is a constant, we can emit left hand side or right hand side
|
||||
const Constant* cl = constants.find(expr->left);
|
||||
|
||||
if (cl && cl->type != Constant::Type_Unknown)
|
||||
if (const Constant* cl = constants.find(expr->left); cl && cl->type != Constant::Type_Unknown)
|
||||
{
|
||||
compileExpr(and_ == cl->isTruthful() ? expr->right : expr->left, target, targetTemp);
|
||||
return;
|
||||
|
@ -1213,10 +1310,10 @@ struct Compiler
|
|||
if (!isConditionFast(expr->left))
|
||||
{
|
||||
// Optimization: when right hand side is a local variable, we can use AND/OR
|
||||
if (isExprLocalReg(expr->right))
|
||||
if (int reg = getExprLocalReg(expr->right); reg >= 0)
|
||||
{
|
||||
uint8_t lr = compileExprAuto(expr->left, rs);
|
||||
uint8_t rr = getLocal(expr->right->as<AstExprLocal>()->local);
|
||||
uint8_t rr = uint8_t(reg);
|
||||
|
||||
bytecode.emitABC(and_ ? LOP_AND : LOP_OR, target, lr, rr);
|
||||
return;
|
||||
|
@ -1635,13 +1732,11 @@ struct Compiler
|
|||
{
|
||||
RegScope rs(this);
|
||||
|
||||
// note: cv may be invalidated by compileExpr* so we stop using it before calling compile recursively
|
||||
const Constant* cv = constants.find(expr->index);
|
||||
Constant cv = getConstant(expr->index);
|
||||
|
||||
if (cv && cv->type == Constant::Type_Number && cv->valueNumber >= 1 && cv->valueNumber <= 256 &&
|
||||
double(int(cv->valueNumber)) == cv->valueNumber)
|
||||
if (cv.type == Constant::Type_Number && cv.valueNumber >= 1 && cv.valueNumber <= 256 && double(int(cv.valueNumber)) == cv.valueNumber)
|
||||
{
|
||||
uint8_t i = uint8_t(int(cv->valueNumber) - 1);
|
||||
uint8_t i = uint8_t(int(cv.valueNumber) - 1);
|
||||
|
||||
uint8_t rt = compileExprAuto(expr->expr, rs);
|
||||
|
||||
|
@ -1649,9 +1744,9 @@ struct Compiler
|
|||
|
||||
bytecode.emitABC(LOP_GETTABLEN, target, rt, i);
|
||||
}
|
||||
else if (cv && cv->type == Constant::Type_String)
|
||||
else if (cv.type == Constant::Type_String)
|
||||
{
|
||||
BytecodeBuilder::StringRef iname = sref(cv->getString());
|
||||
BytecodeBuilder::StringRef iname = sref(cv.getString());
|
||||
int32_t cid = bytecode.addConstantString(iname);
|
||||
if (cid < 0)
|
||||
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
|
||||
|
@ -1764,14 +1859,11 @@ struct Compiler
|
|||
}
|
||||
|
||||
// Optimization: if expression has a constant value, we can emit it directly
|
||||
if (const Constant* cv = constants.find(node))
|
||||
{
|
||||
if (cv->type != Constant::Type_Unknown)
|
||||
if (const Constant* cv = constants.find(node); cv && cv->type != Constant::Type_Unknown)
|
||||
{
|
||||
compileExprConstant(node, cv, target);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (AstExprGroup* expr = node->as<AstExprGroup>())
|
||||
{
|
||||
|
@ -1803,19 +1895,18 @@ struct Compiler
|
|||
}
|
||||
else if (AstExprLocal* expr = node->as<AstExprLocal>())
|
||||
{
|
||||
if (FFlag::LuauCompileSupportInlining ? !isExprLocalReg(expr) : expr->upvalue)
|
||||
// note: this can't check expr->upvalue because upvalues may be upgraded to locals during inlining
|
||||
if (int reg = getExprLocalReg(expr); reg >= 0)
|
||||
{
|
||||
bytecode.emitABC(LOP_MOVE, target, uint8_t(reg), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(expr->upvalue);
|
||||
uint8_t uid = getUpval(expr->local);
|
||||
|
||||
bytecode.emitABC(LOP_GETUPVAL, target, uid, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t reg = getLocal(expr->local);
|
||||
|
||||
bytecode.emitABC(LOP_MOVE, target, reg, 0);
|
||||
}
|
||||
}
|
||||
else if (AstExprGlobal* expr = node->as<AstExprGlobal>())
|
||||
{
|
||||
|
@ -1879,8 +1970,8 @@ struct Compiler
|
|||
uint8_t compileExprAuto(AstExpr* node, RegScope&)
|
||||
{
|
||||
// Optimization: directly return locals instead of copying them to a temporary
|
||||
if (isExprLocalReg(node))
|
||||
return getLocal(node->as<AstExprLocal>()->local);
|
||||
if (int reg = getExprLocalReg(node); reg >= 0)
|
||||
return uint8_t(reg);
|
||||
|
||||
// note: the register is owned by the parent scope
|
||||
uint8_t reg = allocReg(node, 1);
|
||||
|
@ -1910,7 +2001,7 @@ struct Compiler
|
|||
for (size_t i = 0; i < targetCount; ++i)
|
||||
compileExprTemp(list.data[i], uint8_t(target + i));
|
||||
|
||||
// compute expressions with values that go nowhere; this is required to run side-effecting code if any
|
||||
// evaluate extra expressions for side effects
|
||||
for (size_t i = targetCount; i < list.size; ++i)
|
||||
{
|
||||
RegScope rsi(this);
|
||||
|
@ -1970,23 +2061,22 @@ struct Compiler
|
|||
|
||||
LValue compileLValueIndex(uint8_t reg, AstExpr* index, RegScope& rs)
|
||||
{
|
||||
const Constant* cv = constants.find(index);
|
||||
Constant cv = getConstant(index);
|
||||
|
||||
if (cv && cv->type == Constant::Type_Number && cv->valueNumber >= 1 && cv->valueNumber <= 256 &&
|
||||
double(int(cv->valueNumber)) == cv->valueNumber)
|
||||
if (cv.type == Constant::Type_Number && cv.valueNumber >= 1 && cv.valueNumber <= 256 && double(int(cv.valueNumber)) == cv.valueNumber)
|
||||
{
|
||||
LValue result = {LValue::Kind_IndexNumber};
|
||||
result.reg = reg;
|
||||
result.number = uint8_t(int(cv->valueNumber) - 1);
|
||||
result.number = uint8_t(int(cv.valueNumber) - 1);
|
||||
result.location = index->location;
|
||||
|
||||
return result;
|
||||
}
|
||||
else if (cv && cv->type == Constant::Type_String)
|
||||
else if (cv.type == Constant::Type_String)
|
||||
{
|
||||
LValue result = {LValue::Kind_IndexName};
|
||||
result.reg = reg;
|
||||
result.name = sref(cv->getString());
|
||||
result.name = sref(cv.getString());
|
||||
result.location = index->location;
|
||||
|
||||
return result;
|
||||
|
@ -2008,20 +2098,21 @@ struct Compiler
|
|||
|
||||
if (AstExprLocal* expr = node->as<AstExprLocal>())
|
||||
{
|
||||
if (FFlag::LuauCompileSupportInlining ? !isExprLocalReg(expr) : expr->upvalue)
|
||||
// note: this can't check expr->upvalue because upvalues may be upgraded to locals during inlining
|
||||
if (int reg = getExprLocalReg(expr); reg >= 0)
|
||||
{
|
||||
LUAU_ASSERT(expr->upvalue);
|
||||
|
||||
LValue result = {LValue::Kind_Upvalue};
|
||||
result.upval = getUpval(expr->local);
|
||||
LValue result = {LValue::Kind_Local};
|
||||
result.reg = uint8_t(reg);
|
||||
result.location = node->location;
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
LValue result = {LValue::Kind_Local};
|
||||
result.reg = getLocal(expr->local);
|
||||
LUAU_ASSERT(expr->upvalue);
|
||||
|
||||
LValue result = {LValue::Kind_Upvalue};
|
||||
result.upval = getUpval(expr->local);
|
||||
result.location = node->location;
|
||||
|
||||
return result;
|
||||
|
@ -2115,15 +2206,21 @@ struct Compiler
|
|||
compileLValueUse(lv, source, /* set= */ true);
|
||||
}
|
||||
|
||||
bool isExprLocalReg(AstExpr* expr)
|
||||
int getExprLocalReg(AstExpr* node)
|
||||
{
|
||||
AstExprLocal* le = expr->as<AstExprLocal>();
|
||||
if (!le || (!FFlag::LuauCompileSupportInlining && le->upvalue))
|
||||
return false;
|
||||
if (AstExprLocal* expr = node->as<AstExprLocal>())
|
||||
{
|
||||
// note: this can't check expr->upvalue because upvalues may be upgraded to locals during inlining
|
||||
Local* l = locals.find(expr->local);
|
||||
|
||||
Local* l = locals.find(le->local);
|
||||
|
||||
return l && l->allocated;
|
||||
return l && l->allocated ? l->reg : -1;
|
||||
}
|
||||
else if (AstExprGroup* expr = node->as<AstExprGroup>())
|
||||
return getExprLocalReg(expr->expr);
|
||||
else if (AstExprTypeAssertion* expr = node->as<AstExprTypeAssertion>())
|
||||
return getExprLocalReg(expr->expr);
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool isStatBreak(AstStat* node)
|
||||
|
@ -2352,21 +2449,18 @@ struct Compiler
|
|||
|
||||
// Optimization: return locals directly instead of copying them into a temporary
|
||||
// this is very important for a single return value and occasionally effective for multiple values
|
||||
if (stat->list.size > 0 && isExprLocalReg(stat->list.data[0]))
|
||||
if (int reg = stat->list.size > 0 ? getExprLocalReg(stat->list.data[0]) : -1; reg >= 0)
|
||||
{
|
||||
temp = getLocal(stat->list.data[0]->as<AstExprLocal>()->local);
|
||||
temp = uint8_t(reg);
|
||||
consecutive = true;
|
||||
|
||||
for (size_t i = 1; i < stat->list.size; ++i)
|
||||
{
|
||||
AstExpr* v = stat->list.data[i];
|
||||
if (!isExprLocalReg(v) || getLocal(v->as<AstExprLocal>()->local) != temp + i)
|
||||
if (getExprLocalReg(stat->list.data[i]) != int(temp + i))
|
||||
{
|
||||
consecutive = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!consecutive && stat->list.size > 0)
|
||||
{
|
||||
|
@ -2417,41 +2511,21 @@ struct Compiler
|
|||
pushLocal(stat->vars.data[i], uint8_t(vars + i));
|
||||
}
|
||||
|
||||
int getConstantShort(AstExpr* expr)
|
||||
{
|
||||
const Constant* c = constants.find(expr);
|
||||
|
||||
if (c && c->type == Constant::Type_Number)
|
||||
{
|
||||
double n = c->valueNumber;
|
||||
|
||||
if (n >= -32767 && n <= 32767 && double(int(n)) == n)
|
||||
return int(n);
|
||||
}
|
||||
|
||||
return INT_MIN;
|
||||
}
|
||||
|
||||
bool canUnrollForBody(AstStatFor* stat)
|
||||
{
|
||||
if (FFlag::LuauCompileNestedClosureO2)
|
||||
return true; // TODO: remove this function
|
||||
|
||||
struct CanUnrollVisitor : AstVisitor
|
||||
{
|
||||
bool result = true;
|
||||
|
||||
bool visit(AstExpr* node) override
|
||||
bool visit(AstExprFunction* node) override
|
||||
{
|
||||
// functions may capture loop variable, and our upval handling doesn't handle elided variables (constant)
|
||||
// TODO: we could remove this case if we changed function compilation to create temporary locals for constant upvalues
|
||||
result = result && !node->is<AstExprFunction>();
|
||||
return result;
|
||||
}
|
||||
result = false;
|
||||
|
||||
bool visit(AstStat* node) override
|
||||
{
|
||||
// while we can easily unroll nested loops, our cost model doesn't take unrolling into account so this can result in code explosion
|
||||
// we also avoid continue/break since they introduce control flow across iterations
|
||||
result = result && !node->is<AstStatFor>() && !node->is<AstStatContinue>() && !node->is<AstStatBreak>();
|
||||
return result;
|
||||
// short-circuit to avoid analyzing nested closure bodies
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2463,17 +2537,29 @@ struct Compiler
|
|||
|
||||
bool tryCompileUnrolledFor(AstStatFor* stat, int thresholdBase, int thresholdMaxBoost)
|
||||
{
|
||||
int from = getConstantShort(stat->from);
|
||||
int to = getConstantShort(stat->to);
|
||||
int step = stat->step ? getConstantShort(stat->step) : 1;
|
||||
Constant one = {Constant::Type_Number};
|
||||
one.valueNumber = 1.0;
|
||||
|
||||
// check that limits are reasonably small and trip count can be computed
|
||||
if (from == INT_MIN || to == INT_MIN || step == INT_MIN || step == 0 || (step < 0 && to > from) || (step > 0 && to < from))
|
||||
Constant fromc = getConstant(stat->from);
|
||||
Constant toc = getConstant(stat->to);
|
||||
Constant stepc = stat->step ? getConstant(stat->step) : one;
|
||||
|
||||
int tripCount = (fromc.type == Constant::Type_Number && toc.type == Constant::Type_Number && stepc.type == Constant::Type_Number)
|
||||
? getTripCount(fromc.valueNumber, toc.valueNumber, stepc.valueNumber)
|
||||
: -1;
|
||||
|
||||
if (tripCount < 0)
|
||||
{
|
||||
bytecode.addDebugRemark("loop unroll failed: invalid iteration count");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tripCount > thresholdBase)
|
||||
{
|
||||
bytecode.addDebugRemark("loop unroll failed: too many iterations (%d)", tripCount);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!canUnrollForBody(stat))
|
||||
{
|
||||
bytecode.addDebugRemark("loop unroll failed: unsupported loop body");
|
||||
|
@ -2486,14 +2572,6 @@ struct Compiler
|
|||
return false;
|
||||
}
|
||||
|
||||
int tripCount = (to - from) / step + 1;
|
||||
|
||||
if (tripCount > thresholdBase)
|
||||
{
|
||||
bytecode.addDebugRemark("loop unroll failed: too many iterations (%d)", tripCount);
|
||||
return false;
|
||||
}
|
||||
|
||||
AstLocal* var = stat->var;
|
||||
uint64_t costModel = modelCost(stat->body, &var, 1);
|
||||
|
||||
|
@ -2514,23 +2592,54 @@ struct Compiler
|
|||
|
||||
bytecode.addDebugRemark("loop unroll succeeded (iterations %d, cost %d, profit %.2fx)", tripCount, unrolledCost, double(unrollProfit) / 100);
|
||||
|
||||
for (int i = from; step > 0 ? i <= to : i >= to; i += step)
|
||||
compileUnrolledFor(stat, tripCount, fromc.valueNumber, stepc.valueNumber);
|
||||
return true;
|
||||
}
|
||||
|
||||
void compileUnrolledFor(AstStatFor* stat, int tripCount, double from, double step)
|
||||
{
|
||||
AstLocal* var = stat->var;
|
||||
|
||||
size_t oldLocals = localStack.size();
|
||||
size_t oldJumps = loopJumps.size();
|
||||
|
||||
loops.push_back({oldLocals, nullptr});
|
||||
|
||||
for (int iv = 0; iv < tripCount; ++iv)
|
||||
{
|
||||
// we need to re-fold constants in the loop body with the new value; this reuses computed constant values elsewhere in the tree
|
||||
locstants[var].type = Constant::Type_Number;
|
||||
locstants[var].valueNumber = i;
|
||||
locstants[var].valueNumber = from + iv * step;
|
||||
|
||||
foldConstants(constants, variables, locstants, stat);
|
||||
|
||||
size_t iterJumps = loopJumps.size();
|
||||
|
||||
compileStat(stat->body);
|
||||
|
||||
// all continue jumps need to go to the next iteration
|
||||
size_t contLabel = bytecode.emitLabel();
|
||||
|
||||
for (size_t i = iterJumps; i < loopJumps.size(); ++i)
|
||||
if (loopJumps[i].type == LoopJump::Continue)
|
||||
patchJump(stat, loopJumps[i].label, contLabel);
|
||||
}
|
||||
|
||||
// all break jumps need to go past the loop
|
||||
size_t endLabel = bytecode.emitLabel();
|
||||
|
||||
for (size_t i = oldJumps; i < loopJumps.size(); ++i)
|
||||
if (loopJumps[i].type == LoopJump::Break)
|
||||
patchJump(stat, loopJumps[i].label, endLabel);
|
||||
|
||||
loopJumps.resize(oldJumps);
|
||||
|
||||
loops.pop_back();
|
||||
|
||||
// clean up fold state in case we need to recompile - normally we compile the loop body once, but due to inlining we may need to do it again
|
||||
locstants[var].type = Constant::Type_Unknown;
|
||||
|
||||
foldConstants(constants, variables, locstants, stat);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void compileStatFor(AstStatFor* stat)
|
||||
|
@ -2617,16 +2726,6 @@ struct Compiler
|
|||
// this puts initial values of (generator, state, index) into the loop registers
|
||||
compileExprListTemp(stat->values, regs, 3, /* targetTop= */ true);
|
||||
|
||||
// we don't need this because the extra stack space is just for calling the function with a loop protocol which is similar to calling
|
||||
// metamethods - it should fit into the extra stack reservation
|
||||
if (!FFlag::LuauCompileIterNoReserve)
|
||||
{
|
||||
// for the general case, we will execute a CALL for every iteration that needs to evaluate "variables... = generator(state, index)"
|
||||
// this requires at least extra 3 stack slots after index
|
||||
// note that these stack slots overlap with the variables so we only need to reserve them to make sure stack frame is large enough
|
||||
reserveReg(stat, 3);
|
||||
}
|
||||
|
||||
// note that we reserve at least 2 variables; this allows our fast path to assume that we need 2 variables instead of 1 or 2
|
||||
uint8_t vars = allocReg(stat, std::max(unsigned(stat->vars.size), 2u));
|
||||
LUAU_ASSERT(vars == regs + 3);
|
||||
|
@ -2874,12 +2973,9 @@ struct Compiler
|
|||
void compileStatFunction(AstStatFunction* stat)
|
||||
{
|
||||
// Optimization: compile value expresion directly into target local register
|
||||
if (isExprLocalReg(stat->name))
|
||||
if (int reg = getExprLocalReg(stat->name); reg >= 0)
|
||||
{
|
||||
AstExprLocal* le = stat->name->as<AstExprLocal>();
|
||||
LUAU_ASSERT(le);
|
||||
|
||||
compileExpr(stat->func, getLocal(le->local));
|
||||
compileExpr(stat->func, uint8_t(reg));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -3399,6 +3495,12 @@ struct Compiler
|
|||
std::vector<size_t> returnJumps;
|
||||
};
|
||||
|
||||
struct Capture
|
||||
{
|
||||
LuauCaptureType type;
|
||||
uint8_t data;
|
||||
};
|
||||
|
||||
BytecodeBuilder& bytecode;
|
||||
|
||||
CompileOptions options;
|
||||
|
@ -3422,6 +3524,7 @@ struct Compiler
|
|||
std::vector<LoopJump> loopJumps;
|
||||
std::vector<Loop> loops;
|
||||
std::vector<InlineFrame> inlineFrames;
|
||||
std::vector<Capture> captures;
|
||||
};
|
||||
|
||||
void compileOrThrow(BytecodeBuilder& bytecode, AstStatBlock* root, const AstNameTable& names, const CompileOptions& options)
|
||||
|
@ -3465,6 +3568,9 @@ void compileOrThrow(BytecodeBuilder& bytecode, AstStatBlock* root, const AstName
|
|||
/* self= */ nullptr, AstArray<AstLocal*>(), /* vararg= */ Luau::Location(), root, /* functionDepth= */ 0, /* debugname= */ AstName());
|
||||
uint32_t mainid = compiler.compileFunction(&main);
|
||||
|
||||
const Compiler::Function* mainf = compiler.functions.find(&main);
|
||||
LUAU_ASSERT(mainf && mainf->upvals.empty());
|
||||
|
||||
bytecode.setMainFunction(mainid);
|
||||
bytecode.finalize();
|
||||
}
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
#include <math.h>
|
||||
|
||||
LUAU_FASTFLAG(LuauCompileSupportInlining)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace Compile
|
||||
|
@ -330,7 +328,7 @@ struct ConstantVisitor : AstVisitor
|
|||
{
|
||||
if (value.type != Constant::Type_Unknown)
|
||||
map[key] = value;
|
||||
else if (!FFlag::LuauCompileSupportInlining || wasEmpty)
|
||||
else if (wasEmpty)
|
||||
;
|
||||
else if (Constant* old = map.find(key))
|
||||
old->type = Constant::Type_Unknown;
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
#include "Luau/Common.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace Compile
|
||||
|
@ -11,10 +13,49 @@ namespace Compile
|
|||
|
||||
inline uint64_t parallelAddSat(uint64_t x, uint64_t y)
|
||||
{
|
||||
uint64_t s = x + y;
|
||||
uint64_t m = s & 0x8080808080808080ull; // saturation mask
|
||||
uint64_t r = x + y;
|
||||
uint64_t s = r & 0x8080808080808080ull; // saturation mask
|
||||
|
||||
return (s ^ m) | (m - (m >> 7));
|
||||
return (r ^ s) | (s - (s >> 7));
|
||||
}
|
||||
|
||||
static uint64_t parallelMulSat(uint64_t a, int b)
|
||||
{
|
||||
int bs = (b < 127) ? b : 127;
|
||||
|
||||
// multiply every other value by b, yielding 14-bit products
|
||||
uint64_t l = bs * ((a >> 0) & 0x007f007f007f007full);
|
||||
uint64_t h = bs * ((a >> 8) & 0x007f007f007f007full);
|
||||
|
||||
// each product is 14-bit, so adding 32768-128 sets high bit iff the sum is 128 or larger without an overflow
|
||||
uint64_t ls = l + 0x7f807f807f807f80ull;
|
||||
uint64_t hs = h + 0x7f807f807f807f80ull;
|
||||
|
||||
// we now merge saturation bits as well as low 7-bits of each product into one
|
||||
uint64_t s = (hs & 0x8000800080008000ull) | ((ls & 0x8000800080008000ull) >> 8);
|
||||
uint64_t r = ((h & 0x007f007f007f007full) << 8) | (l & 0x007f007f007f007full);
|
||||
|
||||
// the low bits are now correct for values that didn't saturate, and we simply need to mask them if high bit is 1
|
||||
return r | (s - (s >> 7));
|
||||
}
|
||||
|
||||
inline bool getNumber(AstExpr* node, double& result)
|
||||
{
|
||||
// since constant model doesn't use constant folding atm, we perform the basic extraction that's sufficient to handle positive/negative literals
|
||||
if (AstExprConstantNumber* ne = node->as<AstExprConstantNumber>())
|
||||
{
|
||||
result = ne->value;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (AstExprUnary* ue = node->as<AstExprUnary>(); ue && ue->op == AstExprUnary::Minus)
|
||||
if (AstExprConstantNumber* ne = ue->expr->as<AstExprConstantNumber>())
|
||||
{
|
||||
result = -ne->value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
struct Cost
|
||||
|
@ -46,6 +87,13 @@ struct Cost
|
|||
return *this;
|
||||
}
|
||||
|
||||
Cost operator*(int other) const
|
||||
{
|
||||
Cost result;
|
||||
result.model = parallelMulSat(model, other);
|
||||
return result;
|
||||
}
|
||||
|
||||
static Cost fold(const Cost& x, const Cost& y)
|
||||
{
|
||||
uint64_t newmodel = parallelAddSat(x.model, y.model);
|
||||
|
@ -173,6 +221,16 @@ struct CostVisitor : AstVisitor
|
|||
*i = 0;
|
||||
}
|
||||
|
||||
void loop(AstStatBlock* body, Cost iterCost, int factor = 3)
|
||||
{
|
||||
Cost before = result;
|
||||
|
||||
result = Cost();
|
||||
body->visit(this);
|
||||
|
||||
result = before + (result + iterCost) * factor;
|
||||
}
|
||||
|
||||
bool visit(AstExpr* node) override
|
||||
{
|
||||
// note: we short-circuit the visitor traversal through any expression trees by returning false
|
||||
|
@ -182,12 +240,52 @@ struct CostVisitor : AstVisitor
|
|||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstStatFor* node) override
|
||||
{
|
||||
result += model(node->from);
|
||||
result += model(node->to);
|
||||
|
||||
if (node->step)
|
||||
result += model(node->step);
|
||||
|
||||
int tripCount = -1;
|
||||
double from, to, step = 1;
|
||||
if (getNumber(node->from, from) && getNumber(node->to, to) && (!node->step || getNumber(node->step, step)))
|
||||
tripCount = getTripCount(from, to, step);
|
||||
|
||||
loop(node->body, 1, tripCount < 0 ? 3 : tripCount);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstStatForIn* node) override
|
||||
{
|
||||
for (size_t i = 0; i < node->values.size; ++i)
|
||||
result += model(node->values.data[i]);
|
||||
|
||||
loop(node->body, 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstStatWhile* node) override
|
||||
{
|
||||
Cost condition = model(node->condition);
|
||||
|
||||
loop(node->body, condition);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstStatRepeat* node) override
|
||||
{
|
||||
Cost condition = model(node->condition);
|
||||
|
||||
loop(node->body, condition);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstStat* node) override
|
||||
{
|
||||
if (node->is<AstStatIf>())
|
||||
result += 2;
|
||||
else if (node->is<AstStatWhile>() || node->is<AstStatRepeat>() || node->is<AstStatFor>() || node->is<AstStatForIn>())
|
||||
result += 5;
|
||||
else if (node->is<AstStatBreak>() || node->is<AstStatContinue>())
|
||||
result += 1;
|
||||
|
||||
|
@ -254,5 +352,21 @@ int computeCost(uint64_t model, const bool* varsConst, size_t varCount)
|
|||
return cost;
|
||||
}
|
||||
|
||||
int getTripCount(double from, double to, double step)
|
||||
{
|
||||
// we compute trip count in integers because that way we know that the loop math (repeated addition) is precise
|
||||
int fromi = (from >= -32767 && from <= 32767 && double(int(from)) == from) ? int(from) : INT_MIN;
|
||||
int toi = (to >= -32767 && to <= 32767 && double(int(to)) == to) ? int(to) : INT_MIN;
|
||||
int stepi = (step >= -32767 && step <= 32767 && double(int(step)) == step) ? int(step) : INT_MIN;
|
||||
|
||||
if (fromi == INT_MIN || toi == INT_MIN || stepi == INT_MIN || stepi == 0)
|
||||
return -1;
|
||||
|
||||
if ((stepi < 0 && toi > fromi) || (stepi > 0 && toi < fromi))
|
||||
return 0;
|
||||
|
||||
return (toi - fromi) / stepi + 1;
|
||||
}
|
||||
|
||||
} // namespace Compile
|
||||
} // namespace Luau
|
||||
|
|
|
@ -14,5 +14,8 @@ uint64_t modelCost(AstNode* root, AstLocal* const* vars, size_t varCount);
|
|||
// cost is computed as B - sum(Di * Ci), where B is baseline cost, Di is the discount for each variable and Ci is 1 when variable #i is constant
|
||||
int computeCost(uint64_t model, const bool* varsConst, size_t varCount);
|
||||
|
||||
// get loop trip count or -1 if we can't compute it precisely
|
||||
int getTripCount(double from, double to, double step);
|
||||
|
||||
} // namespace Compile
|
||||
} // namespace Luau
|
||||
|
|
30
Makefile
30
Makefile
|
@ -19,6 +19,10 @@ ANALYSIS_SOURCES=$(wildcard Analysis/src/*.cpp)
|
|||
ANALYSIS_OBJECTS=$(ANALYSIS_SOURCES:%=$(BUILD)/%.o)
|
||||
ANALYSIS_TARGET=$(BUILD)/libluauanalysis.a
|
||||
|
||||
CODEGEN_SOURCES=$(wildcard CodeGen/src/*.cpp)
|
||||
CODEGEN_OBJECTS=$(CODEGEN_SOURCES:%=$(BUILD)/%.o)
|
||||
CODEGEN_TARGET=$(BUILD)/libluaucodegen.a
|
||||
|
||||
VM_SOURCES=$(wildcard VM/src/*.cpp)
|
||||
VM_OBJECTS=$(VM_SOURCES:%=$(BUILD)/%.o)
|
||||
VM_TARGET=$(BUILD)/libluauvm.a
|
||||
|
@ -47,7 +51,7 @@ ifneq ($(flags),)
|
|||
TESTS_ARGS+=--fflags=$(flags)
|
||||
endif
|
||||
|
||||
OBJECTS=$(AST_OBJECTS) $(COMPILER_OBJECTS) $(ANALYSIS_OBJECTS) $(VM_OBJECTS) $(ISOCLINE_OBJECTS) $(TESTS_OBJECTS) $(CLI_OBJECTS) $(FUZZ_OBJECTS)
|
||||
OBJECTS=$(AST_OBJECTS) $(COMPILER_OBJECTS) $(ANALYSIS_OBJECTS) $(CODEGEN_OBJECTS) $(VM_OBJECTS) $(ISOCLINE_OBJECTS) $(TESTS_OBJECTS) $(CLI_OBJECTS) $(FUZZ_OBJECTS)
|
||||
|
||||
# common flags
|
||||
CXXFLAGS=-g -Wall
|
||||
|
@ -90,15 +94,16 @@ ifeq ($(config),fuzz)
|
|||
endif
|
||||
|
||||
# target-specific flags
|
||||
$(AST_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include
|
||||
$(COMPILER_OBJECTS): CXXFLAGS+=-std=c++17 -ICompiler/include -IAst/include
|
||||
$(ANALYSIS_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include -IAnalysis/include
|
||||
$(VM_OBJECTS): CXXFLAGS+=-std=c++11 -IVM/include
|
||||
$(AST_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include
|
||||
$(COMPILER_OBJECTS): CXXFLAGS+=-std=c++17 -ICompiler/include -ICommon/include -IAst/include
|
||||
$(ANALYSIS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include
|
||||
$(CODEGEN_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -ICodeGen/include
|
||||
$(VM_OBJECTS): CXXFLAGS+=-std=c++11 -ICommon/include -IVM/include
|
||||
$(ISOCLINE_OBJECTS): CXXFLAGS+=-Wno-unused-function -Iextern/isocline/include
|
||||
$(TESTS_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include -ICompiler/include -IAnalysis/include -IVM/include -ICLI -Iextern
|
||||
$(REPL_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include -ICompiler/include -IVM/include -Iextern -Iextern/isocline/include
|
||||
$(ANALYZE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include -IAnalysis/include -Iextern
|
||||
$(FUZZ_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include -ICompiler/include -IAnalysis/include -IVM/include
|
||||
$(TESTS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -ICodeGen/include -IVM/include -ICLI -Iextern
|
||||
$(REPL_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IVM/include -Iextern -Iextern/isocline/include
|
||||
$(ANALYZE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include -Iextern
|
||||
$(FUZZ_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -IVM/include
|
||||
|
||||
$(TESTS_TARGET): LDFLAGS+=-lpthread
|
||||
$(REPL_CLI_TARGET): LDFLAGS+=-lpthread
|
||||
|
@ -126,7 +131,7 @@ coverage: $(TESTS_TARGET)
|
|||
llvm-cov export -ignore-filename-regex=\(tests\|extern\|CLI\)/.* -format lcov --instr-profile default.profdata build/coverage/luau-tests >coverage.info
|
||||
|
||||
format:
|
||||
find . -name '*.h' -or -name '*.cpp' | xargs clang-format -i
|
||||
find . -name '*.h' -or -name '*.cpp' | xargs clang-format-11 -i
|
||||
|
||||
luau-size: luau
|
||||
nm --print-size --demangle luau | grep ' t void luau_execute<false>' | awk -F ' ' '{sum += strtonum("0x" $$2)} END {print sum " interpreter" }'
|
||||
|
@ -140,7 +145,7 @@ luau-analyze: $(ANALYZE_CLI_TARGET)
|
|||
ln -fs $^ $@
|
||||
|
||||
# executable targets
|
||||
$(TESTS_TARGET): $(TESTS_OBJECTS) $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET)
|
||||
$(TESTS_TARGET): $(TESTS_OBJECTS) $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET)
|
||||
$(REPL_CLI_TARGET): $(REPL_CLI_OBJECTS) $(COMPILER_TARGET) $(AST_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET)
|
||||
$(ANALYZE_CLI_TARGET): $(ANALYZE_CLI_OBJECTS) $(ANALYSIS_TARGET) $(AST_TARGET)
|
||||
|
||||
|
@ -158,10 +163,11 @@ fuzz-prototest: $(BUILD)/fuzz/prototest.cpp.o $(BUILD)/fuzz/protoprint.cpp.o $(B
|
|||
$(AST_TARGET): $(AST_OBJECTS)
|
||||
$(COMPILER_TARGET): $(COMPILER_OBJECTS)
|
||||
$(ANALYSIS_TARGET): $(ANALYSIS_OBJECTS)
|
||||
$(CODEGEN_TARGET): $(CODEGEN_OBJECTS)
|
||||
$(VM_TARGET): $(VM_OBJECTS)
|
||||
$(ISOCLINE_TARGET): $(ISOCLINE_OBJECTS)
|
||||
|
||||
$(AST_TARGET) $(COMPILER_TARGET) $(ANALYSIS_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET):
|
||||
$(AST_TARGET) $(COMPILER_TARGET) $(ANALYSIS_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET):
|
||||
ar rcs $@ $^
|
||||
|
||||
# object file targets
|
||||
|
|
|
@ -1,7 +1,15 @@
|
|||
# Luau.Common Sources
|
||||
# Note: Until 3.19, INTERFACE targets couldn't have SOURCES property set
|
||||
if(NOT ${CMAKE_VERSION} VERSION_LESS "3.19")
|
||||
target_sources(Luau.Common PRIVATE
|
||||
Common/include/Luau/Common.h
|
||||
Common/include/Luau/Bytecode.h
|
||||
)
|
||||
endif()
|
||||
|
||||
# Luau.Ast Sources
|
||||
target_sources(Luau.Ast PRIVATE
|
||||
Ast/include/Luau/Ast.h
|
||||
Ast/include/Luau/Common.h
|
||||
Ast/include/Luau/Confusables.h
|
||||
Ast/include/Luau/DenseHash.h
|
||||
Ast/include/Luau/Lexer.h
|
||||
|
@ -23,7 +31,6 @@ target_sources(Luau.Ast PRIVATE
|
|||
|
||||
# Luau.Compiler Sources
|
||||
target_sources(Luau.Compiler PRIVATE
|
||||
Compiler/include/Luau/Bytecode.h
|
||||
Compiler/include/Luau/BytecodeBuilder.h
|
||||
Compiler/include/Luau/Compiler.h
|
||||
Compiler/include/luacode.h
|
||||
|
@ -43,17 +50,32 @@ target_sources(Luau.Compiler PRIVATE
|
|||
Compiler/src/ValueTracking.h
|
||||
)
|
||||
|
||||
# Luau.CodeGen Sources
|
||||
target_sources(Luau.CodeGen PRIVATE
|
||||
CodeGen/include/Luau/AssemblyBuilderX64.h
|
||||
CodeGen/include/Luau/Condition.h
|
||||
CodeGen/include/Luau/Label.h
|
||||
CodeGen/include/Luau/OperandX64.h
|
||||
CodeGen/include/Luau/RegisterX64.h
|
||||
|
||||
CodeGen/src/AssemblyBuilderX64.cpp
|
||||
)
|
||||
|
||||
# Luau.Analysis Sources
|
||||
target_sources(Luau.Analysis PRIVATE
|
||||
Analysis/include/Luau/AstQuery.h
|
||||
Analysis/include/Luau/Autocomplete.h
|
||||
Analysis/include/Luau/NotNull.h
|
||||
Analysis/include/Luau/BuiltinDefinitions.h
|
||||
Analysis/include/Luau/Config.h
|
||||
Analysis/include/Luau/Clone.h
|
||||
Analysis/include/Luau/Config.h
|
||||
Analysis/include/Luau/ConstraintGraphBuilder.h
|
||||
Analysis/include/Luau/ConstraintSolver.h
|
||||
Analysis/include/Luau/Documentation.h
|
||||
Analysis/include/Luau/Error.h
|
||||
Analysis/include/Luau/FileResolver.h
|
||||
Analysis/include/Luau/Frontend.h
|
||||
Analysis/include/Luau/Instantiation.h
|
||||
Analysis/include/Luau/IostreamHelpers.h
|
||||
Analysis/include/Luau/JsonEncoder.h
|
||||
Analysis/include/Luau/Linter.h
|
||||
|
@ -73,6 +95,7 @@ target_sources(Luau.Analysis PRIVATE
|
|||
Analysis/include/Luau/ToString.h
|
||||
Analysis/include/Luau/Transpiler.h
|
||||
Analysis/include/Luau/TxnLog.h
|
||||
Analysis/include/Luau/TypeArena.h
|
||||
Analysis/include/Luau/TypeAttach.h
|
||||
Analysis/include/Luau/TypedAllocator.h
|
||||
Analysis/include/Luau/TypeInfer.h
|
||||
|
@ -88,10 +111,13 @@ target_sources(Luau.Analysis PRIVATE
|
|||
Analysis/src/AstQuery.cpp
|
||||
Analysis/src/Autocomplete.cpp
|
||||
Analysis/src/BuiltinDefinitions.cpp
|
||||
Analysis/src/Config.cpp
|
||||
Analysis/src/Clone.cpp
|
||||
Analysis/src/Config.cpp
|
||||
Analysis/src/ConstraintGraphBuilder.cpp
|
||||
Analysis/src/ConstraintSolver.cpp
|
||||
Analysis/src/Error.cpp
|
||||
Analysis/src/Frontend.cpp
|
||||
Analysis/src/Instantiation.cpp
|
||||
Analysis/src/IostreamHelpers.cpp
|
||||
Analysis/src/JsonEncoder.cpp
|
||||
Analysis/src/Linter.cpp
|
||||
|
@ -108,6 +134,7 @@ target_sources(Luau.Analysis PRIVATE
|
|||
Analysis/src/ToString.cpp
|
||||
Analysis/src/Transpiler.cpp
|
||||
Analysis/src/TxnLog.cpp
|
||||
Analysis/src/TypeArena.cpp
|
||||
Analysis/src/TypeAttach.cpp
|
||||
Analysis/src/TypedAllocator.cpp
|
||||
Analysis/src/TypeInfer.cpp
|
||||
|
@ -218,6 +245,7 @@ if(TARGET Luau.UnitTest)
|
|||
tests/AstQuery.test.cpp
|
||||
tests/AstVisitor.test.cpp
|
||||
tests/Autocomplete.test.cpp
|
||||
tests/NotNull.test.cpp
|
||||
tests/BuiltinDefinitions.test.cpp
|
||||
tests/Compiler.test.cpp
|
||||
tests/Config.test.cpp
|
||||
|
@ -230,6 +258,8 @@ if(TARGET Luau.UnitTest)
|
|||
tests/Module.test.cpp
|
||||
tests/NonstrictMode.test.cpp
|
||||
tests/Normalize.test.cpp
|
||||
tests/ConstraintGraphBuilder.test.cpp
|
||||
tests/ConstraintSolver.test.cpp
|
||||
tests/Parser.test.cpp
|
||||
tests/RequireTracer.test.cpp
|
||||
tests/RuntimeLimits.test.cpp
|
||||
|
@ -265,6 +295,7 @@ if(TARGET Luau.UnitTest)
|
|||
tests/TypeVar.test.cpp
|
||||
tests/Variant.test.cpp
|
||||
tests/VisitTypeVar.test.cpp
|
||||
tests/AssemblyBuilderX64.test.cpp
|
||||
tests/main.cpp)
|
||||
endif()
|
||||
|
||||
|
|
|
@ -148,6 +148,7 @@ LUA_API const char* lua_tostringatom(lua_State* L, int idx, int* atom);
|
|||
LUA_API const char* lua_namecallatom(lua_State* L, int* atom);
|
||||
LUA_API int lua_objlen(lua_State* L, int idx);
|
||||
LUA_API lua_CFunction lua_tocfunction(lua_State* L, int idx);
|
||||
LUA_API void* lua_tolightuserdata(lua_State* L, int idx);
|
||||
LUA_API void* lua_touserdata(lua_State* L, int idx);
|
||||
LUA_API void* lua_touserdatatagged(lua_State* L, int idx, int tag);
|
||||
LUA_API int lua_userdatatag(lua_State* L, int idx);
|
||||
|
|
|
@ -14,8 +14,6 @@
|
|||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAG(LuauGcWorkTrackFix)
|
||||
|
||||
const char* lua_ident = "$Lua: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio $\n"
|
||||
"$Authors: R. Ierusalimschy, L. H. de Figueiredo & W. Celes $\n"
|
||||
"$URL: www.lua.org $\n";
|
||||
|
@ -478,19 +476,22 @@ lua_CFunction lua_tocfunction(lua_State* L, int idx)
|
|||
return (!iscfunction(o)) ? NULL : cast_to(lua_CFunction, clvalue(o)->c.f);
|
||||
}
|
||||
|
||||
void* lua_tolightuserdata(lua_State* L, int idx)
|
||||
{
|
||||
StkId o = index2addr(L, idx);
|
||||
return (!ttislightuserdata(o)) ? NULL : pvalue(o);
|
||||
}
|
||||
|
||||
void* lua_touserdata(lua_State* L, int idx)
|
||||
{
|
||||
StkId o = index2addr(L, idx);
|
||||
switch (ttype(o))
|
||||
{
|
||||
case LUA_TUSERDATA:
|
||||
if (ttisuserdata(o))
|
||||
return uvalue(o)->data;
|
||||
case LUA_TLIGHTUSERDATA:
|
||||
else if (ttislightuserdata(o))
|
||||
return pvalue(o);
|
||||
default:
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void* lua_touserdatatagged(lua_State* L, int idx, int tag)
|
||||
{
|
||||
|
@ -524,8 +525,9 @@ const void* lua_topointer(lua_State* L, int idx)
|
|||
case LUA_TTHREAD:
|
||||
return thvalue(o);
|
||||
case LUA_TUSERDATA:
|
||||
return uvalue(o)->data;
|
||||
case LUA_TLIGHTUSERDATA:
|
||||
return lua_touserdata(L, idx);
|
||||
return pvalue(o);
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
|
@ -1050,7 +1052,6 @@ int lua_gc(lua_State* L, int what, int data)
|
|||
}
|
||||
case LUA_GCSTEP:
|
||||
{
|
||||
size_t prevthreshold = g->GCthreshold;
|
||||
size_t amount = (cast_to(size_t, data) << 10);
|
||||
ptrdiff_t oldcredit = g->gcstate == GCSpause ? 0 : g->GCthreshold - g->totalbytes;
|
||||
|
||||
|
@ -1060,8 +1061,6 @@ int lua_gc(lua_State* L, int what, int data)
|
|||
else
|
||||
g->GCthreshold = 0;
|
||||
|
||||
bool waspaused = g->gcstate == GCSpause;
|
||||
|
||||
#ifdef LUAI_GCMETRICS
|
||||
double startmarktime = g->gcmetrics.currcycle.marktime;
|
||||
double startsweeptime = g->gcmetrics.currcycle.sweeptime;
|
||||
|
@ -1074,7 +1073,7 @@ int lua_gc(lua_State* L, int what, int data)
|
|||
{
|
||||
size_t stepsize = luaC_step(L, false);
|
||||
|
||||
actualwork += FFlag::LuauGcWorkTrackFix ? stepsize : g->gcstepsize;
|
||||
actualwork += stepsize;
|
||||
|
||||
if (g->gcstate == GCSpause)
|
||||
{ /* end of cycle? */
|
||||
|
@ -1109,22 +1108,11 @@ int lua_gc(lua_State* L, int what, int data)
|
|||
|
||||
// if cycle hasn't finished, advance threshold forward for the amount of extra work performed
|
||||
if (g->gcstate != GCSpause)
|
||||
{
|
||||
if (FFlag::LuauGcWorkTrackFix)
|
||||
{
|
||||
// if a new cycle was triggered by explicit step, old 'credit' of GC work is 0
|
||||
ptrdiff_t newthreshold = g->totalbytes + actualwork + oldcredit;
|
||||
g->GCthreshold = newthreshold < 0 ? 0 : newthreshold;
|
||||
}
|
||||
else
|
||||
{
|
||||
// if a new cycle was triggered by explicit step, we ignore old threshold as that shows an incorrect 'credit' of GC work
|
||||
if (waspaused)
|
||||
g->GCthreshold = g->totalbytes + actualwork;
|
||||
else
|
||||
g->GCthreshold = prevthreshold + actualwork;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LUA_GCSETGOAL:
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixBuiltinsStackLimit, false)
|
||||
|
||||
// luauF functions implement FASTCALL instruction that performs a direct execution of some builtin functions from the VM
|
||||
// The rule of thumb is that FASTCALL functions can not call user code, yield, fail, or reallocate stack.
|
||||
// If types of the arguments mismatch, luauF_* needs to return -1 and the execution will fall back to the usual call path
|
||||
|
@ -1005,7 +1003,7 @@ static int luauF_tunpack(lua_State* L, StkId res, TValue* arg0, int nresults, St
|
|||
else if (nparams == 3 && ttisnumber(args) && ttisnumber(args + 1) && nvalue(args) == 1.0)
|
||||
n = int(nvalue(args + 1));
|
||||
|
||||
if (n >= 0 && n <= t->sizearray && cast_int(L->stack_last - res) >= n && (!FFlag::LuauFixBuiltinsStackLimit || n + nparams <= LUAI_MAXCSTACK))
|
||||
if (n >= 0 && n <= t->sizearray && cast_int(L->stack_last - res) >= n && n + nparams <= LUAI_MAXCSTACK)
|
||||
{
|
||||
TValue* array = t->array;
|
||||
for (int i = 0; i < n; ++i)
|
||||
|
@ -1020,18 +1018,20 @@ static int luauF_tunpack(lua_State* L, StkId res, TValue* arg0, int nresults, St
|
|||
|
||||
static int luauF_vector(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
|
||||
{
|
||||
#if LUA_VECTOR_SIZE == 4
|
||||
if (nparams >= 4 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1) && ttisnumber(args + 2))
|
||||
#else
|
||||
if (nparams >= 3 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1))
|
||||
#endif
|
||||
{
|
||||
double x = nvalue(arg0);
|
||||
double y = nvalue(args);
|
||||
double z = nvalue(args + 1);
|
||||
|
||||
#if LUA_VECTOR_SIZE == 4
|
||||
double w = nvalue(args + 2);
|
||||
double w = 0.0;
|
||||
if (nparams >= 4)
|
||||
{
|
||||
if (!ttisnumber(args + 2))
|
||||
return -1;
|
||||
w = nvalue(args + 2);
|
||||
}
|
||||
setvvalue(res, float(x), float(y), float(z), float(w));
|
||||
#else
|
||||
setvvalue(res, float(x), float(y), float(z), 0.0f);
|
||||
|
|
|
@ -3,7 +3,4 @@
|
|||
#pragma once
|
||||
|
||||
// This is a forwarding header for Luau bytecode definition
|
||||
// Luau consists of several components, including compiler (Ast, Compiler) and VM (virtual machine)
|
||||
// These components are fully independent, but they both need the bytecode format defined in this header
|
||||
// so it needs to be shared.
|
||||
#include "../../Compiler/include/Luau/Bytecode.h"
|
||||
#include "Luau/Bytecode.h"
|
||||
|
|
|
@ -7,11 +7,7 @@
|
|||
|
||||
#include "luaconf.h"
|
||||
|
||||
// This is a forwarding header for Luau common definition (assertions, flags)
|
||||
// Luau consists of several components, including compiler (Ast, Compiler) and VM (virtual machine)
|
||||
// These components are fully independent, but they need a common set of utilities defined in this header
|
||||
// so it needs to be shared.
|
||||
#include "../../Ast/include/Luau/Common.h"
|
||||
#include "Luau/Common.h"
|
||||
|
||||
typedef LUAI_USER_ALIGNMENT_T L_Umaxalign;
|
||||
|
||||
|
|
|
@ -213,6 +213,14 @@ CallInfo* luaD_growCI(lua_State* L)
|
|||
return ++L->ci;
|
||||
}
|
||||
|
||||
void luaD_checkCstack(lua_State* L)
|
||||
{
|
||||
if (L->nCcalls == LUAI_MAXCCALLS)
|
||||
luaG_runerror(L, "C stack overflow");
|
||||
else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS >> 3)))
|
||||
luaD_throw(L, LUA_ERRERR); /* error while handling stack error */
|
||||
}
|
||||
|
||||
/*
|
||||
** Call a function (C or Lua). The function to be called is at *func.
|
||||
** The arguments are on the stack, right after the function.
|
||||
|
@ -222,12 +230,8 @@ CallInfo* luaD_growCI(lua_State* L)
|
|||
void luaD_call(lua_State* L, StkId func, int nResults)
|
||||
{
|
||||
if (++L->nCcalls >= LUAI_MAXCCALLS)
|
||||
{
|
||||
if (L->nCcalls == LUAI_MAXCCALLS)
|
||||
luaG_runerror(L, "C stack overflow");
|
||||
else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS >> 3)))
|
||||
luaD_throw(L, LUA_ERRERR); /* error while handing stack error */
|
||||
}
|
||||
luaD_checkCstack(L);
|
||||
|
||||
if (luau_precall(L, func, nResults) == PCRLUA)
|
||||
{ /* is a Lua function? */
|
||||
L->ci->flags |= LUA_CALLINFO_RETURN; /* luau_execute will stop after returning from the stack frame */
|
||||
|
@ -241,6 +245,7 @@ void luaD_call(lua_State* L, StkId func, int nResults)
|
|||
if (!oldactive)
|
||||
resetbit(L->stackstate, THREAD_ACTIVEBIT);
|
||||
}
|
||||
|
||||
L->nCcalls--;
|
||||
luaC_checkGC(L);
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ LUAI_FUNC int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t oldtop, pt
|
|||
LUAI_FUNC void luaD_reallocCI(lua_State* L, int newsize);
|
||||
LUAI_FUNC void luaD_reallocstack(lua_State* L, int newsize);
|
||||
LUAI_FUNC void luaD_growstack(lua_State* L, int n);
|
||||
LUAI_FUNC void luaD_checkCstack(lua_State* L);
|
||||
|
||||
LUAI_FUNC l_noret luaD_throw(lua_State* L, int errcode);
|
||||
LUAI_FUNC int luaD_rawrunprotected(lua_State* L, Pfunc f, void* ud);
|
||||
|
|
|
@ -13,10 +13,7 @@
|
|||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauGcWorkTrackFix, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauGcSweepCostFix, false)
|
||||
|
||||
#define GC_SWEEPPAGESTEPCOST (FFlag::LuauGcSweepCostFix ? 16 : 4)
|
||||
#define GC_SWEEPPAGESTEPCOST 16
|
||||
|
||||
#define GC_INTERRUPT(state) \
|
||||
{ \
|
||||
|
@ -881,7 +878,7 @@ size_t luaC_step(lua_State* L, bool assist)
|
|||
{
|
||||
global_State* g = L->global;
|
||||
|
||||
int lim = FFlag::LuauGcWorkTrackFix ? g->gcstepsize * g->gcstepmul / 100 : (g->gcstepsize / 100) * g->gcstepmul; /* how much to work */
|
||||
int lim = g->gcstepsize * g->gcstepmul / 100; /* how much to work */
|
||||
LUAU_ASSERT(g->totalbytes >= g->GCthreshold);
|
||||
size_t debt = g->totalbytes - g->GCthreshold;
|
||||
|
||||
|
@ -927,10 +924,10 @@ size_t luaC_step(lua_State* L, bool assist)
|
|||
}
|
||||
else
|
||||
{
|
||||
g->GCthreshold = g->totalbytes + (FFlag::LuauGcWorkTrackFix ? actualstepsize : g->gcstepsize);
|
||||
g->GCthreshold = g->totalbytes + actualstepsize;
|
||||
|
||||
// compensate if GC is "behind schedule" (has some debt to pay)
|
||||
if (FFlag::LuauGcWorkTrackFix ? g->GCthreshold >= debt : g->GCthreshold > debt)
|
||||
if (g->GCthreshold >= debt)
|
||||
g->GCthreshold -= debt;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,10 +10,6 @@
|
|||
#include "ldebug.h"
|
||||
#include "lvm.h"
|
||||
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauTableMoveTelemetry2, false)
|
||||
|
||||
void (*lua_table_move_telemetry)(lua_State* L, int f, int e, int t, int nf, int nt);
|
||||
|
||||
static int foreachi(lua_State* L)
|
||||
{
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
|
@ -199,29 +195,6 @@ static int tmove(lua_State* L)
|
|||
int tt = !lua_isnoneornil(L, 5) ? 5 : 1; /* destination table */
|
||||
luaL_checktype(L, tt, LUA_TTABLE);
|
||||
|
||||
void (*telemetrycb)(lua_State * L, int f, int e, int t, int nf, int nt) = lua_table_move_telemetry;
|
||||
|
||||
if (DFFlag::LuauTableMoveTelemetry2 && telemetrycb && e >= f)
|
||||
{
|
||||
int nf = lua_objlen(L, 1);
|
||||
int nt = lua_objlen(L, tt);
|
||||
|
||||
bool report = false;
|
||||
|
||||
// source index range must be in bounds in source table unless the table is empty (permits 1..#t moves)
|
||||
if (!(f == 1 || (f >= 1 && f <= nf)))
|
||||
report = true;
|
||||
if (!(e == nf || (e >= 1 && e <= nf)))
|
||||
report = true;
|
||||
|
||||
// destination index must be in bounds in dest table or be exactly at the first empty element (permits concats)
|
||||
if (!(t == nt + 1 || (t >= 1 && t <= nt)))
|
||||
report = true;
|
||||
|
||||
if (report)
|
||||
telemetrycb(L, f, e, t, nf, nt);
|
||||
}
|
||||
|
||||
if (e >= f)
|
||||
{ /* otherwise, nothing to move */
|
||||
luaL_argcheck(L, f > 0 || e < INT_MAX + f, 3, "too many elements to move");
|
||||
|
|
|
@ -17,9 +17,6 @@
|
|||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauIter, false)
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauIterCallTelemetry, false)
|
||||
|
||||
void (*lua_iter_call_telemetry)(lua_State* L);
|
||||
|
||||
// Disable c99-designator to avoid the warning in CGOTO dispatch table
|
||||
#ifdef __clang__
|
||||
|
@ -157,17 +154,6 @@ LUAU_NOINLINE static bool luau_loopFORG(lua_State* L, int a, int c)
|
|||
StkId ra = &L->base[a];
|
||||
LUAU_ASSERT(ra + 3 <= L->top);
|
||||
|
||||
if (DFFlag::LuauIterCallTelemetry)
|
||||
{
|
||||
/* TODO: we might be able to stop supporting this depending on whether it's used in practice */
|
||||
void (*telemetrycb)(lua_State* L) = lua_iter_call_telemetry;
|
||||
|
||||
if (telemetrycb && ttistable(ra) && fasttm(L, hvalue(ra)->metatable, TM_CALL))
|
||||
telemetrycb(L);
|
||||
if (telemetrycb && ttisuserdata(ra) && fasttm(L, uvalue(ra)->metatable, TM_CALL))
|
||||
telemetrycb(L);
|
||||
}
|
||||
|
||||
setobjs2s(L, ra + 3 + 2, ra + 2);
|
||||
setobjs2s(L, ra + 3 + 1, ra + 1);
|
||||
setobjs2s(L, ra + 3, ra);
|
||||
|
@ -195,7 +181,7 @@ LUAU_NOINLINE static void luau_callTM(lua_State* L, int nparams, int res)
|
|||
++L->nCcalls;
|
||||
|
||||
if (L->nCcalls >= LUAI_MAXCCALLS)
|
||||
luaG_runerror(L, "C stack overflow");
|
||||
luaD_checkCstack(L);
|
||||
|
||||
luaD_checkstack(L, LUA_MINSTACK);
|
||||
|
||||
|
@ -708,7 +694,7 @@ static void luau_execute(lua_State* L)
|
|||
}
|
||||
else
|
||||
{
|
||||
// slow-path, may invoke Lua calls via __index metamethod
|
||||
// slow-path, may invoke Lua calls via __newindex metamethod
|
||||
L->cachedslot = slot;
|
||||
VM_PROTECT(luaV_settable(L, rb, kv, ra));
|
||||
// save cachedslot to accelerate future lookups; patches currently executing instruction since pc-2 rolls back two pc++
|
||||
|
@ -718,7 +704,7 @@ static void luau_execute(lua_State* L)
|
|||
}
|
||||
else
|
||||
{
|
||||
// fast-path: user data with C __index TM
|
||||
// fast-path: user data with C __newindex TM
|
||||
const TValue* fn = 0;
|
||||
if (ttisuserdata(rb) && (fn = fasttm(L, uvalue(rb)->metatable, TM_NEWINDEX)) && ttisfunction(fn) && clvalue(fn)->isC)
|
||||
{
|
||||
|
@ -739,7 +725,7 @@ static void luau_execute(lua_State* L)
|
|||
}
|
||||
else
|
||||
{
|
||||
// slow-path, may invoke Lua calls via __index metamethod
|
||||
// slow-path, may invoke Lua calls via __newindex metamethod
|
||||
VM_PROTECT(luaV_settable(L, rb, kv, ra));
|
||||
VM_NEXT();
|
||||
}
|
||||
|
@ -2372,9 +2358,8 @@ static void luau_execute(lua_State* L)
|
|||
// fast-path: ipairs/inext
|
||||
if (cl->env->safeenv && ttistable(ra + 1) && ttisnumber(ra + 2) && nvalue(ra + 2) == 0.0)
|
||||
{
|
||||
if (FFlag::LuauIter)
|
||||
setnilvalue(ra);
|
||||
|
||||
/* ra+1 is already the table */
|
||||
setpvalue(ra + 2, reinterpret_cast<void*>(uintptr_t(0)));
|
||||
}
|
||||
else if (FFlag::LuauIter && !ttisfunction(ra))
|
||||
|
@ -2394,7 +2379,7 @@ static void luau_execute(lua_State* L)
|
|||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
|
||||
// fast-path: ipairs/inext
|
||||
if (ttistable(ra + 1) && ttislightuserdata(ra + 2))
|
||||
if (ttisnil(ra) && ttistable(ra + 1) && ttislightuserdata(ra + 2))
|
||||
{
|
||||
Table* h = hvalue(ra + 1);
|
||||
int index = int(reinterpret_cast<uintptr_t>(pvalue(ra + 2)));
|
||||
|
@ -2445,9 +2430,8 @@ static void luau_execute(lua_State* L)
|
|||
// fast-path: pairs/next
|
||||
if (cl->env->safeenv && ttistable(ra + 1) && ttisnil(ra + 2))
|
||||
{
|
||||
if (FFlag::LuauIter)
|
||||
setnilvalue(ra);
|
||||
|
||||
/* ra+1 is already the table */
|
||||
setpvalue(ra + 2, reinterpret_cast<void*>(uintptr_t(0)));
|
||||
}
|
||||
else if (FFlag::LuauIter && !ttisfunction(ra))
|
||||
|
@ -2467,7 +2451,7 @@ static void luau_execute(lua_State* L)
|
|||
StkId ra = VM_REG(LUAU_INSN_A(insn));
|
||||
|
||||
// fast-path: pairs/next
|
||||
if (ttistable(ra + 1) && ttislightuserdata(ra + 2))
|
||||
if (ttisnil(ra) && ttistable(ra + 1) && ttislightuserdata(ra + 2))
|
||||
{
|
||||
Table* h = hvalue(ra + 1);
|
||||
int index = int(reinterpret_cast<uintptr_t>(pvalue(ra + 2)));
|
||||
|
|
|
@ -69,6 +69,7 @@ Sandboxing challenges are [covered in the dedicated section](sandbox).
|
|||
| `\0` in patterns | ✔️ | |
|
||||
| `bit32` library | ✔️ | |
|
||||
| `string.gsub` is stricter about using `%` on special characters only | ✔️ | |
|
||||
| light C functions | 😞 | this changes semantics of fenv on C functions and has complex implications wrt runtime performance |
|
||||
|
||||
Two things that are important to call out here are various new metamethods for tables and yielding in metamethods. In both cases, there are performance implications to supporting this - our implementation is *very* highly tuned for performance, so any changes that affect the core fundamentals of how Lua works have a price. To support yielding in metamethods we'd need to make the core of the VM more involved, since almost every single "interesting" opcode would need to learn how to be resumable - which also complicates future JIT/AOT story. Metamethods in general are important for extensibility, but very challenging to deal with in implementation, so we err on the side of not supporting any new metamethods unless a strong need arises.
|
||||
|
||||
|
|
|
@ -92,12 +92,22 @@ As a result, builtin calls are very fast in Luau - they are still slightly slowe
|
|||
|
||||
## Optimized table iteration
|
||||
|
||||
Luau implements a fully generic iteration protocol; however, for iteration through tables it recognizes three common idioms (`for .. in ipairs(t)`, `for .. in pairs(t)` and `for .. in next, t`) and emits specialized bytecode that is carefully optimized using custom internal iterators.
|
||||
Luau implements a fully generic iteration protocol; however, for iteration through tables in addition to generalized iteration (`for .. in t`) it recognizes three common idioms (`for .. in ipairs(t)`, `for .. in pairs(t)` and `for .. in next, t`) and emits specialized bytecode that is carefully optimized using custom internal iterators.
|
||||
|
||||
As a result, iteration through tables typically doesn't result in function calls for every iteration; the performance of iteration using `pairs` and `ipairs` is comparable, so it's recommended to pick the iteration style based on readability instead of performance.
|
||||
As a result, iteration through tables typically doesn't result in function calls for every iteration; the performance of iteration using generalized iteration, `pairs` and `ipairs` is comparable, so generalized iteration (without the use of `pairs`/`ipairs`) is recommended unless the code needs to be compatible with vanilla Lua or the specific semantics of `ipairs` (which stops at the first `nil` element) is required. Additionally, using generalized iteration avoids calling `pairs` when the loop starts which can be noticeable when the table is very short.
|
||||
|
||||
Iterating through array-like tables using `for i=1,#t` tends to be slightly slower because of extra cost incurred when reading elements from the table.
|
||||
|
||||
## Optimized table length
|
||||
|
||||
Luau tables use a hybrid array/hash storage, like in Lua; in some sense "arrays" don't truly exist and are an internal optimization, but some operations, notably `#t` and functions that depend on it, like `table.insert`, are defined by the Luau/Lua language to allow internal optimizations. Luau takes advantage of that fact.
|
||||
|
||||
Unlike Lua, Luau guarantees that the element at index `#t` is stored in the array part of the table. This can accelerate various table operations that use indices limited by `#t`, and this makes `#t` worst-case complexity O(logN), unlike Lua where the worst case complexity is O(N). This also accelerates computation of this value for small tables like `{ [1] = 1 }` since we never need to look at the hash part.
|
||||
|
||||
The "default" implementation of `#t` in both Lua and Luau is a binary search. Luau uses a special branch-free (depending on the compiler...) implementation of the binary search which results in 50+% faster computation of table length when it needs to be computed from scratch.
|
||||
|
||||
Additionally, Luau can cache the length of the table and adjust it following operations like `table.insert`/`table.remove`; this means that in practice, `#t` is almost always a constant time operation.
|
||||
|
||||
## Creating and modifying tables
|
||||
|
||||
Luau implements several optimizations for table creation. When creating object-like tables, it's recommended to use table literals (`{ ... }`) and to specify all table fields in the literal in one go instead of assigning fields later; this triggers an optimization inspired by LuaJIT's "table templates" and results in higher performance when creating objects. When creating array-like tables, if the maximum size of the table is known up front, it's recommended to use `table.create` function which can create an empty table with preallocated storage, and optionally fill it with a given value.
|
||||
|
@ -112,7 +122,7 @@ v.z = 3
|
|||
return v
|
||||
```
|
||||
|
||||
When appending elements to tables, it's recommended to use `table.insert` (which is the fastest method to append an element to a table if the table size is not known). In cases when a table is filled sequentially, however, it's much more efficient to use a known index for insertion - together with preallocating tables using `table.create` this can result in much faster code, for example this is the fastest way to build a table of squares:
|
||||
When appending elements to tables, it's recommended to use `table.insert` (which is the fastest method to append an element to a table if the table size is not known). In cases when a table is filled sequentially, however, it can be more efficient to use a known index for insertion - together with preallocating tables using `table.create` this can result in much faster code, for example this is the fastest way to build a table of squares:
|
||||
|
||||
```lua
|
||||
local t = table.create(N)
|
||||
|
|
97
docs/_posts/2022-06-01-luau-recap-may-2022.md
Normal file
97
docs/_posts/2022-06-01-luau-recap-may-2022.md
Normal file
|
@ -0,0 +1,97 @@
|
|||
---
|
||||
layout: single
|
||||
title: "Luau Recap: May 2022"
|
||||
---
|
||||
|
||||
This month Luau team has worked to bring you a new language feature together with more typechecking improvements and bugfixes!
|
||||
|
||||
[Cross-posted to the [Roblox Developer Forum](https://devforum.roblox.com/t/luau-recap-may-2022/).]
|
||||
|
||||
## Generalized iteration
|
||||
|
||||
We have extended the semantics of standard Lua syntax for iterating through containers, `for vars in values` with support for generalized iteration.
|
||||
In Lua, to iterate over a table you need to use an iterator like `next` or a function that returns one like `pairs` or `ipairs`. In Luau, you can now simply iterate over a table:
|
||||
|
||||
```lua
|
||||
for k, v in {1, 4, 9} do
|
||||
assert(k * k == v)
|
||||
end
|
||||
```
|
||||
|
||||
This works for tables but can also be customized for tables or userdata by implementing `__iter` metamethod. It is called before the iteration begins, and should return an iterator function like `next` (or a custom one):
|
||||
|
||||
```lua
|
||||
local obj = { items = {1, 4, 9} }
|
||||
setmetatable(obj, { __iter = function(o) return next, o.items end })
|
||||
|
||||
for k, v in obj do
|
||||
assert(k * k == v)
|
||||
end
|
||||
```
|
||||
|
||||
The default iteration order for tables is specified to be consecutive for elements `1..#t` and unordered after that, visiting every element.
|
||||
Similar to iteration using `pairs`, modifying the table entries for keys other than the current one results in unspecified behavior.
|
||||
|
||||
## Typechecking improvements
|
||||
|
||||
We have added a missing check to compare implicit table keys against the key type of the table indexer:
|
||||
|
||||
```lua
|
||||
-- error is correctly reported, implicit keys (1,2,3) are not compatible with [string]
|
||||
local t: { [string]: boolean } = { true, true, false }
|
||||
```
|
||||
|
||||
Rules for `==` and `~=` have been relaxed for union types, if any of the union parts can be compared, operation succeeds:
|
||||
|
||||
```lua
|
||||
--!strict
|
||||
local function compare(v1: Vector3, v2: Vector3?)
|
||||
return v1 == v2 -- no longer an error
|
||||
end
|
||||
```
|
||||
|
||||
Table value type propagation now correctly works with `[any]` key type:
|
||||
|
||||
```lua
|
||||
--!strict
|
||||
type X = {[any]: string | boolean}
|
||||
local x: X = { key = "str" } -- no longer gives an incorrect error
|
||||
```
|
||||
|
||||
If a generic function doesn't provide type annotations for all arguments and the return value, additional generic type parameters might be added automatically:
|
||||
|
||||
```lua
|
||||
-- previously it was foo<T>, now it's foo<T, b>, because second argument is also generic
|
||||
function foo<T>(x: T, y) end
|
||||
```
|
||||
|
||||
We have also fixed various issues that have caused crashes, with many of them coming from your bug reports.
|
||||
|
||||
## Linter improvements
|
||||
|
||||
`GlobalUsedAsLocal` lint warning has been extended to notice when global variable writes always happen before their use in a local scope, suggesting that they can be replaced with a local variable:
|
||||
|
||||
```lua
|
||||
function bar()
|
||||
foo = 6 -- Global 'foo' is never read before being written. Consider changing it to local
|
||||
return foo
|
||||
end
|
||||
function baz()
|
||||
foo = 10
|
||||
return foo
|
||||
end
|
||||
```
|
||||
|
||||
## Performance improvements
|
||||
|
||||
Garbage collection CPU utilization has been tuned to further reduce frame time spikes of individual collection steps and to bring different GC stages to the same level of CPU utilization.
|
||||
|
||||
Returning a type-cast local (`return a :: type`) as well as returning multiple local variables (`return a, b, c`) is now a little bit more efficient.
|
||||
|
||||
### Function inlining and loop unrolling
|
||||
|
||||
In the open-source release of Luau, when optimization level 2 is enabled, the compiler will now perform function inlining and loop unrolling.
|
||||
|
||||
Only loops with loop bounds known at compile time, such as `for i=1,4 do`, can be unrolled. The loop body must be simple enough for the optimization to be profitable; compiler uses heuristics to estimate the performance benefit and automatically decide if unrolling should be performed.
|
||||
|
||||
Only local functions (defined either as `local function foo` or `local foo = function`) can be inlined. The function body must be simple enough for the optimization to be profitable; compiler uses heuristics to estimate the performance benefit and automatically decide if each call to the function should be inlined instead. Additionally recursive invocations of a function can't be inlined at this time, and inlining is completely disabled for modules that use `getfenv`/`setfenv` functions.
|
410
tests/AssemblyBuilderX64.test.cpp
Normal file
410
tests/AssemblyBuilderX64.test.cpp
Normal file
|
@ -0,0 +1,410 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/AssemblyBuilderX64.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
#include <functional>
|
||||
#include <string.h>
|
||||
|
||||
using namespace Luau::CodeGen;
|
||||
|
||||
std::string bytecodeAsArray(const std::vector<uint8_t>& bytecode)
|
||||
{
|
||||
std::string result = "{";
|
||||
|
||||
for (size_t i = 0; i < bytecode.size(); i++)
|
||||
Luau::formatAppend(result, "%s0x%02x", i == 0 ? "" : ", ", bytecode[i]);
|
||||
|
||||
return result.append("}");
|
||||
}
|
||||
|
||||
class AssemblyBuilderX64Fixture
|
||||
{
|
||||
public:
|
||||
void check(std::function<void(AssemblyBuilderX64& build)> f, std::vector<uint8_t> result)
|
||||
{
|
||||
AssemblyBuilderX64 build(/* logText= */ false);
|
||||
|
||||
f(build);
|
||||
|
||||
build.finalize();
|
||||
|
||||
if (build.code != result)
|
||||
{
|
||||
printf("Expected: %s\nReceived: %s\n", bytecodeAsArray(result).c_str(), bytecodeAsArray(build.code).c_str());
|
||||
CHECK(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_SUITE_BEGIN("x64Assembly");
|
||||
|
||||
#define SINGLE_COMPARE(inst, ...) \
|
||||
check( \
|
||||
[](AssemblyBuilderX64& build) { \
|
||||
build.inst; \
|
||||
}, \
|
||||
{__VA_ARGS__})
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "BaseBinaryInstructionForms")
|
||||
{
|
||||
// reg, reg
|
||||
SINGLE_COMPARE(add(rax, rcx), 0x48, 0x03, 0xc1);
|
||||
SINGLE_COMPARE(add(rsp, r12), 0x49, 0x03, 0xe4);
|
||||
SINGLE_COMPARE(add(r14, r10), 0x4d, 0x03, 0xf2);
|
||||
|
||||
// reg, imm
|
||||
SINGLE_COMPARE(add(rax, 0), 0x48, 0x83, 0xc0, 0x00);
|
||||
SINGLE_COMPARE(add(rax, 0x7f), 0x48, 0x83, 0xc0, 0x7f);
|
||||
SINGLE_COMPARE(add(rax, 0x80), 0x48, 0x81, 0xc0, 0x80, 0x00, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(r10, 0x7fffffff), 0x49, 0x81, 0xc2, 0xff, 0xff, 0xff, 0x7f);
|
||||
|
||||
// reg, [reg]
|
||||
SINGLE_COMPARE(add(rax, qword[rax]), 0x48, 0x03, 0x00);
|
||||
SINGLE_COMPARE(add(rax, qword[rbx]), 0x48, 0x03, 0x03);
|
||||
SINGLE_COMPARE(add(rax, qword[rsp]), 0x48, 0x03, 0x04, 0x24);
|
||||
SINGLE_COMPARE(add(rax, qword[rbp]), 0x48, 0x03, 0x45, 0x00);
|
||||
SINGLE_COMPARE(add(rax, qword[r10]), 0x49, 0x03, 0x02);
|
||||
SINGLE_COMPARE(add(rax, qword[r12]), 0x49, 0x03, 0x04, 0x24);
|
||||
SINGLE_COMPARE(add(rax, qword[r13]), 0x49, 0x03, 0x45, 0x00);
|
||||
|
||||
SINGLE_COMPARE(add(r12, qword[rax]), 0x4c, 0x03, 0x20);
|
||||
SINGLE_COMPARE(add(r12, qword[rbx]), 0x4c, 0x03, 0x23);
|
||||
SINGLE_COMPARE(add(r12, qword[rsp]), 0x4c, 0x03, 0x24, 0x24);
|
||||
SINGLE_COMPARE(add(r12, qword[rbp]), 0x4c, 0x03, 0x65, 0x00);
|
||||
SINGLE_COMPARE(add(r12, qword[r10]), 0x4d, 0x03, 0x22);
|
||||
SINGLE_COMPARE(add(r12, qword[r12]), 0x4d, 0x03, 0x24, 0x24);
|
||||
SINGLE_COMPARE(add(r12, qword[r13]), 0x4d, 0x03, 0x65, 0x00);
|
||||
|
||||
// reg, [base+imm8]
|
||||
SINGLE_COMPARE(add(rax, qword[rax + 0x1b]), 0x48, 0x03, 0x40, 0x1b);
|
||||
SINGLE_COMPARE(add(rax, qword[rbx + 0x1b]), 0x48, 0x03, 0x43, 0x1b);
|
||||
SINGLE_COMPARE(add(rax, qword[rsp + 0x1b]), 0x48, 0x03, 0x44, 0x24, 0x1b);
|
||||
SINGLE_COMPARE(add(rax, qword[rbp + 0x1b]), 0x48, 0x03, 0x45, 0x1b);
|
||||
SINGLE_COMPARE(add(rax, qword[r10 + 0x1b]), 0x49, 0x03, 0x42, 0x1b);
|
||||
SINGLE_COMPARE(add(rax, qword[r12 + 0x1b]), 0x49, 0x03, 0x44, 0x24, 0x1b);
|
||||
SINGLE_COMPARE(add(rax, qword[r13 + 0x1b]), 0x49, 0x03, 0x45, 0x1b);
|
||||
|
||||
SINGLE_COMPARE(add(r12, qword[rax + 0x1b]), 0x4c, 0x03, 0x60, 0x1b);
|
||||
SINGLE_COMPARE(add(r12, qword[rbx + 0x1b]), 0x4c, 0x03, 0x63, 0x1b);
|
||||
SINGLE_COMPARE(add(r12, qword[rsp + 0x1b]), 0x4c, 0x03, 0x64, 0x24, 0x1b);
|
||||
SINGLE_COMPARE(add(r12, qword[rbp + 0x1b]), 0x4c, 0x03, 0x65, 0x1b);
|
||||
SINGLE_COMPARE(add(r12, qword[r10 + 0x1b]), 0x4d, 0x03, 0x62, 0x1b);
|
||||
SINGLE_COMPARE(add(r12, qword[r12 + 0x1b]), 0x4d, 0x03, 0x64, 0x24, 0x1b);
|
||||
SINGLE_COMPARE(add(r12, qword[r13 + 0x1b]), 0x4d, 0x03, 0x65, 0x1b);
|
||||
|
||||
// reg, [base+imm32]
|
||||
SINGLE_COMPARE(add(rax, qword[rax + 0xabab]), 0x48, 0x03, 0x80, 0xab, 0xab, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(rax, qword[rbx + 0xabab]), 0x48, 0x03, 0x83, 0xab, 0xab, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(rax, qword[rsp + 0xabab]), 0x48, 0x03, 0x84, 0x24, 0xab, 0xab, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(rax, qword[rbp + 0xabab]), 0x48, 0x03, 0x85, 0xab, 0xab, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(rax, qword[r10 + 0xabab]), 0x49, 0x03, 0x82, 0xab, 0xab, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(rax, qword[r12 + 0xabab]), 0x49, 0x03, 0x84, 0x24, 0xab, 0xab, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(rax, qword[r13 + 0xabab]), 0x49, 0x03, 0x85, 0xab, 0xab, 0x00, 0x00);
|
||||
|
||||
SINGLE_COMPARE(add(r12, qword[rax + 0xabab]), 0x4c, 0x03, 0xa0, 0xab, 0xab, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(r12, qword[rbx + 0xabab]), 0x4c, 0x03, 0xa3, 0xab, 0xab, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(r12, qword[rsp + 0xabab]), 0x4c, 0x03, 0xa4, 0x24, 0xab, 0xab, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(r12, qword[rbp + 0xabab]), 0x4c, 0x03, 0xa5, 0xab, 0xab, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(r12, qword[r10 + 0xabab]), 0x4d, 0x03, 0xa2, 0xab, 0xab, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(r12, qword[r12 + 0xabab]), 0x4d, 0x03, 0xa4, 0x24, 0xab, 0xab, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(r12, qword[r13 + 0xabab]), 0x4d, 0x03, 0xa5, 0xab, 0xab, 0x00, 0x00);
|
||||
|
||||
// reg, [index*scale]
|
||||
SINGLE_COMPARE(add(rax, qword[rax * 2]), 0x48, 0x03, 0x04, 0x45, 0x00, 0x00, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(rax, qword[rbx * 2]), 0x48, 0x03, 0x04, 0x5d, 0x00, 0x00, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(rax, qword[rbp * 2]), 0x48, 0x03, 0x04, 0x6d, 0x00, 0x00, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(rax, qword[r10 * 2]), 0x4a, 0x03, 0x04, 0x55, 0x00, 0x00, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(rax, qword[r12 * 2]), 0x4a, 0x03, 0x04, 0x65, 0x00, 0x00, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(rax, qword[r13 * 2]), 0x4a, 0x03, 0x04, 0x6d, 0x00, 0x00, 0x00, 0x00);
|
||||
|
||||
SINGLE_COMPARE(add(r12, qword[rax * 2]), 0x4c, 0x03, 0x24, 0x45, 0x00, 0x00, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(r12, qword[rbx * 2]), 0x4c, 0x03, 0x24, 0x5d, 0x00, 0x00, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(r12, qword[rbp * 2]), 0x4c, 0x03, 0x24, 0x6d, 0x00, 0x00, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(r12, qword[r10 * 2]), 0x4e, 0x03, 0x24, 0x55, 0x00, 0x00, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(r12, qword[r12 * 2]), 0x4e, 0x03, 0x24, 0x65, 0x00, 0x00, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(r12, qword[r13 * 2]), 0x4e, 0x03, 0x24, 0x6d, 0x00, 0x00, 0x00, 0x00);
|
||||
|
||||
// reg, [base+index*scale+imm]
|
||||
SINGLE_COMPARE(add(rax, qword[rax + rax * 2]), 0x48, 0x03, 0x04, 0x40);
|
||||
SINGLE_COMPARE(add(rax, qword[rax + rbx * 2 + 0x1b]), 0x48, 0x03, 0x44, 0x58, 0x1b);
|
||||
SINGLE_COMPARE(add(rax, qword[rax + rbp * 2]), 0x48, 0x03, 0x04, 0x68);
|
||||
SINGLE_COMPARE(add(rax, qword[rax + rbp + 0xabab]), 0x48, 0x03, 0x84, 0x28, 0xAB, 0xab, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(rax, qword[rax + r12 + 0x1b]), 0x4a, 0x03, 0x44, 0x20, 0x1b);
|
||||
SINGLE_COMPARE(add(rax, qword[rax + r12 * 4 + 0xabab]), 0x4a, 0x03, 0x84, 0xa0, 0xab, 0xab, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(rax, qword[rax + r13 * 2 + 0x1b]), 0x4a, 0x03, 0x44, 0x68, 0x1b);
|
||||
SINGLE_COMPARE(add(rax, qword[rax + r13 + 0xabab]), 0x4a, 0x03, 0x84, 0x28, 0xab, 0xab, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(r12, qword[rax + r12 * 2]), 0x4e, 0x03, 0x24, 0x60);
|
||||
SINGLE_COMPARE(add(r12, qword[rax + r13 + 0xabab]), 0x4e, 0x03, 0xA4, 0x28, 0xab, 0xab, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(r12, qword[rax + rbp * 2 + 0x1b]), 0x4c, 0x03, 0x64, 0x68, 0x1b);
|
||||
|
||||
// reg, [imm32]
|
||||
SINGLE_COMPARE(add(rax, qword[0]), 0x48, 0x03, 0x04, 0x25, 0x00, 0x00, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(rax, qword[0xabab]), 0x48, 0x03, 0x04, 0x25, 0xab, 0xab, 0x00, 0x00);
|
||||
|
||||
// [addr], reg
|
||||
SINGLE_COMPARE(add(qword[rax], rax), 0x48, 0x01, 0x00);
|
||||
SINGLE_COMPARE(add(qword[rax + rax * 4 + 0xabab], rax), 0x48, 0x01, 0x84, 0x80, 0xab, 0xab, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(qword[rbx + rax * 2 + 0x1b], rax), 0x48, 0x01, 0x44, 0x43, 0x1b);
|
||||
SINGLE_COMPARE(add(qword[rbx + rbp * 2 + 0x1b], rax), 0x48, 0x01, 0x44, 0x6b, 0x1b);
|
||||
SINGLE_COMPARE(add(qword[rbp + rbp * 4 + 0xabab], rax), 0x48, 0x01, 0x84, 0xad, 0xab, 0xab, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(qword[rbp + r12 + 0x1b], rax), 0x4a, 0x01, 0x44, 0x25, 0x1b);
|
||||
SINGLE_COMPARE(add(qword[r12], rax), 0x49, 0x01, 0x04, 0x24);
|
||||
SINGLE_COMPARE(add(qword[r13 + rbx + 0xabab], rax), 0x49, 0x01, 0x84, 0x1d, 0xab, 0xab, 0x00, 0x00);
|
||||
SINGLE_COMPARE(add(qword[rax + r13 * 2 + 0x1b], rsi), 0x4a, 0x01, 0x74, 0x68, 0x1b);
|
||||
SINGLE_COMPARE(add(qword[rbp + rbx * 2], rsi), 0x48, 0x01, 0x74, 0x5d, 0x00);
|
||||
SINGLE_COMPARE(add(qword[rsp + r10 * 2 + 0x1b], r10), 0x4e, 0x01, 0x54, 0x54, 0x1b);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "BaseUnaryInstructionForms")
|
||||
{
|
||||
SINGLE_COMPARE(div(rcx), 0x48, 0xf7, 0xf1);
|
||||
SINGLE_COMPARE(idiv(qword[rax]), 0x48, 0xf7, 0x38);
|
||||
SINGLE_COMPARE(mul(qword[rax + rbx]), 0x48, 0xf7, 0x24, 0x18);
|
||||
SINGLE_COMPARE(neg(r9), 0x49, 0xf7, 0xd9);
|
||||
SINGLE_COMPARE(not_(r12), 0x49, 0xf7, 0xd4);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfMov")
|
||||
{
|
||||
SINGLE_COMPARE(mov(rcx, 1), 0x48, 0xb9, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
|
||||
SINGLE_COMPARE(mov64(rcx, 0x1234567812345678ll), 0x48, 0xb9, 0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12);
|
||||
SINGLE_COMPARE(mov(ecx, 2), 0xb9, 0x02, 0x00, 0x00, 0x00);
|
||||
SINGLE_COMPARE(mov(cl, 2), 0xb1, 0x02);
|
||||
SINGLE_COMPARE(mov(rcx, qword[rdi]), 0x48, 0x8b, 0x0f);
|
||||
SINGLE_COMPARE(mov(dword[rax], 0xabcd), 0xc7, 0x00, 0xcd, 0xab, 0x00, 0x00);
|
||||
SINGLE_COMPARE(mov(r13, 1), 0x49, 0xbd, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
|
||||
SINGLE_COMPARE(mov64(r13, 0x1234567812345678ll), 0x49, 0xbd, 0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12);
|
||||
SINGLE_COMPARE(mov(r13d, 2), 0x41, 0xbd, 0x02, 0x00, 0x00, 0x00);
|
||||
SINGLE_COMPARE(mov(r13, qword[r12]), 0x4d, 0x8b, 0x2c, 0x24);
|
||||
SINGLE_COMPARE(mov(dword[r13], 0xabcd), 0x41, 0xc7, 0x45, 0x00, 0xcd, 0xab, 0x00, 0x00);
|
||||
SINGLE_COMPARE(mov(qword[rdx], r9), 0x4c, 0x89, 0x0a);
|
||||
SINGLE_COMPARE(mov(byte[rsi], 0x3), 0xc6, 0x06, 0x03);
|
||||
SINGLE_COMPARE(mov(byte[rsi], al), 0x88, 0x06);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfTest")
|
||||
{
|
||||
SINGLE_COMPARE(test(al, 8), 0xf6, 0xc0, 0x08);
|
||||
SINGLE_COMPARE(test(eax, 8), 0xf7, 0xc0, 0x08, 0x00, 0x00, 0x00);
|
||||
SINGLE_COMPARE(test(rax, 8), 0x48, 0xf7, 0xc0, 0x08, 0x00, 0x00, 0x00);
|
||||
SINGLE_COMPARE(test(rcx, 0xabab), 0x48, 0xf7, 0xc1, 0xab, 0xab, 0x00, 0x00);
|
||||
SINGLE_COMPARE(test(rcx, rax), 0x48, 0x85, 0xc8);
|
||||
SINGLE_COMPARE(test(rax, qword[rcx]), 0x48, 0x85, 0x01);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfShift")
|
||||
{
|
||||
SINGLE_COMPARE(shl(al, 1), 0xd0, 0xe0);
|
||||
SINGLE_COMPARE(shl(al, cl), 0xd2, 0xe0);
|
||||
SINGLE_COMPARE(shr(al, 4), 0xc0, 0xe8, 0x04);
|
||||
SINGLE_COMPARE(shr(eax, 1), 0xd1, 0xe8);
|
||||
SINGLE_COMPARE(sal(eax, cl), 0xd3, 0xe0);
|
||||
SINGLE_COMPARE(sal(eax, 4), 0xc1, 0xe0, 0x04);
|
||||
SINGLE_COMPARE(sar(rax, 4), 0x48, 0xc1, 0xf8, 0x04);
|
||||
SINGLE_COMPARE(sar(r11, 1), 0x49, 0xd1, 0xfb);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfLea")
|
||||
{
|
||||
SINGLE_COMPARE(lea(rax, qword[rdx + rcx]), 0x48, 0x8d, 0x04, 0x0a);
|
||||
SINGLE_COMPARE(lea(rax, qword[rdx + rax * 4]), 0x48, 0x8d, 0x04, 0x82);
|
||||
SINGLE_COMPARE(lea(rax, qword[r13 + r12 * 4 + 4]), 0x4b, 0x8d, 0x44, 0xa5, 0x04);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "ControlFlow")
|
||||
{
|
||||
// Jump back
|
||||
check(
|
||||
[](AssemblyBuilderX64& build) {
|
||||
Label start = build.setLabel();
|
||||
build.add(rsi, 1);
|
||||
build.cmp(rsi, rdi);
|
||||
build.jcc(Condition::Equal, start);
|
||||
},
|
||||
{0x48, 0x83, 0xc6, 0x01, 0x48, 0x3b, 0xf7, 0x0f, 0x84, 0xf3, 0xff, 0xff, 0xff});
|
||||
|
||||
// Jump back, but the label is set before use
|
||||
check(
|
||||
[](AssemblyBuilderX64& build) {
|
||||
Label start;
|
||||
build.add(rsi, 1);
|
||||
build.setLabel(start);
|
||||
build.cmp(rsi, rdi);
|
||||
build.jcc(Condition::Equal, start);
|
||||
},
|
||||
{0x48, 0x83, 0xc6, 0x01, 0x48, 0x3b, 0xf7, 0x0f, 0x84, 0xf7, 0xff, 0xff, 0xff});
|
||||
|
||||
// Jump forward
|
||||
check(
|
||||
[](AssemblyBuilderX64& build) {
|
||||
Label skip;
|
||||
|
||||
build.cmp(rsi, rdi);
|
||||
build.jcc(Condition::Greater, skip);
|
||||
build.or_(rdi, 0x3e);
|
||||
build.setLabel(skip);
|
||||
},
|
||||
{0x48, 0x3b, 0xf7, 0x0f, 0x8f, 0x04, 0x00, 0x00, 0x00, 0x48, 0x83, 0xcf, 0x3e});
|
||||
|
||||
// Regular jump
|
||||
check(
|
||||
[](AssemblyBuilderX64& build) {
|
||||
Label skip;
|
||||
|
||||
build.jmp(skip);
|
||||
build.and_(rdi, 0x3e);
|
||||
build.setLabel(skip);
|
||||
},
|
||||
{0xe9, 0x04, 0x00, 0x00, 0x00, 0x48, 0x83, 0xe7, 0x3e});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXBinaryInstructionForms")
|
||||
{
|
||||
SINGLE_COMPARE(vaddpd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0xa9, 0x58, 0xc6);
|
||||
SINGLE_COMPARE(vaddpd(xmm8, xmm10, xmmword[r9]), 0xc4, 0x41, 0xa9, 0x58, 0x01);
|
||||
SINGLE_COMPARE(vaddpd(ymm8, ymm10, ymm14), 0xc4, 0x41, 0xad, 0x58, 0xc6);
|
||||
SINGLE_COMPARE(vaddpd(ymm8, ymm10, ymmword[r9]), 0xc4, 0x41, 0xad, 0x58, 0x01);
|
||||
SINGLE_COMPARE(vaddps(xmm8, xmm10, xmm14), 0xc4, 0x41, 0xa8, 0x58, 0xc6);
|
||||
SINGLE_COMPARE(vaddps(xmm8, xmm10, xmmword[r9]), 0xc4, 0x41, 0xa8, 0x58, 0x01);
|
||||
SINGLE_COMPARE(vaddsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0xab, 0x58, 0xc6);
|
||||
SINGLE_COMPARE(vaddsd(xmm8, xmm10, qword[r9]), 0xc4, 0x41, 0xab, 0x58, 0x01);
|
||||
SINGLE_COMPARE(vaddss(xmm8, xmm10, xmm14), 0xc4, 0x41, 0xaa, 0x58, 0xc6);
|
||||
SINGLE_COMPARE(vaddss(xmm8, xmm10, dword[r9]), 0xc4, 0x41, 0xaa, 0x58, 0x01);
|
||||
|
||||
SINGLE_COMPARE(vaddps(xmm1, xmm2, xmm3), 0xc4, 0xe1, 0xe8, 0x58, 0xcb);
|
||||
SINGLE_COMPARE(vaddps(xmm9, xmm12, xmmword[r9 + r14 * 2 + 0x1c]), 0xc4, 0x01, 0x98, 0x58, 0x4c, 0x71, 0x1c);
|
||||
SINGLE_COMPARE(vaddps(ymm1, ymm2, ymm3), 0xc4, 0xe1, 0xec, 0x58, 0xcb);
|
||||
SINGLE_COMPARE(vaddps(ymm9, ymm12, ymmword[r9 + r14 * 2 + 0x1c]), 0xc4, 0x01, 0x9c, 0x58, 0x4c, 0x71, 0x1c);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXUnaryMergeInstructionForms")
|
||||
{
|
||||
SINGLE_COMPARE(vsqrtpd(xmm8, xmm10), 0xc4, 0x41, 0xf9, 0x51, 0xc2);
|
||||
SINGLE_COMPARE(vsqrtpd(xmm8, xmmword[r9]), 0xc4, 0x41, 0xf9, 0x51, 0x01);
|
||||
SINGLE_COMPARE(vsqrtpd(ymm8, ymm10), 0xc4, 0x41, 0xfd, 0x51, 0xc2);
|
||||
SINGLE_COMPARE(vsqrtpd(ymm8, ymmword[r9]), 0xc4, 0x41, 0xfd, 0x51, 0x01);
|
||||
SINGLE_COMPARE(vsqrtps(xmm8, xmm10), 0xc4, 0x41, 0xf8, 0x51, 0xc2);
|
||||
SINGLE_COMPARE(vsqrtps(xmm8, xmmword[r9]), 0xc4, 0x41, 0xf8, 0x51, 0x01);
|
||||
SINGLE_COMPARE(vsqrtsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0xab, 0x51, 0xc6);
|
||||
SINGLE_COMPARE(vsqrtsd(xmm8, xmm10, qword[r9]), 0xc4, 0x41, 0xab, 0x51, 0x01);
|
||||
SINGLE_COMPARE(vsqrtss(xmm8, xmm10, xmm14), 0xc4, 0x41, 0xaa, 0x51, 0xc6);
|
||||
SINGLE_COMPARE(vsqrtss(xmm8, xmm10, dword[r9]), 0xc4, 0x41, 0xaa, 0x51, 0x01);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXMoveInstructionForms")
|
||||
{
|
||||
SINGLE_COMPARE(vmovsd(qword[r9], xmm10), 0xc4, 0x41, 0xfb, 0x11, 0x11);
|
||||
SINGLE_COMPARE(vmovsd(xmm8, qword[r9]), 0xc4, 0x41, 0xfb, 0x10, 0x01);
|
||||
SINGLE_COMPARE(vmovsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0xab, 0x10, 0xc6);
|
||||
SINGLE_COMPARE(vmovss(dword[r9], xmm10), 0xc4, 0x41, 0xfa, 0x11, 0x11);
|
||||
SINGLE_COMPARE(vmovss(xmm8, dword[r9]), 0xc4, 0x41, 0xfa, 0x10, 0x01);
|
||||
SINGLE_COMPARE(vmovss(xmm8, xmm10, xmm14), 0xc4, 0x41, 0xaa, 0x10, 0xc6);
|
||||
SINGLE_COMPARE(vmovapd(xmm8, xmmword[r9]), 0xc4, 0x41, 0xf9, 0x28, 0x01);
|
||||
SINGLE_COMPARE(vmovapd(xmmword[r9], xmm10), 0xc4, 0x41, 0xf9, 0x29, 0x11);
|
||||
SINGLE_COMPARE(vmovapd(ymm8, ymmword[r9]), 0xc4, 0x41, 0xfd, 0x28, 0x01);
|
||||
SINGLE_COMPARE(vmovaps(xmm8, xmmword[r9]), 0xc4, 0x41, 0xf8, 0x28, 0x01);
|
||||
SINGLE_COMPARE(vmovaps(xmmword[r9], xmm10), 0xc4, 0x41, 0xf8, 0x29, 0x11);
|
||||
SINGLE_COMPARE(vmovaps(ymm8, ymmword[r9]), 0xc4, 0x41, 0xfc, 0x28, 0x01);
|
||||
SINGLE_COMPARE(vmovupd(xmm8, xmmword[r9]), 0xc4, 0x41, 0xf9, 0x10, 0x01);
|
||||
SINGLE_COMPARE(vmovupd(xmmword[r9], xmm10), 0xc4, 0x41, 0xf9, 0x11, 0x11);
|
||||
SINGLE_COMPARE(vmovupd(ymm8, ymmword[r9]), 0xc4, 0x41, 0xfd, 0x10, 0x01);
|
||||
SINGLE_COMPARE(vmovups(xmm8, xmmword[r9]), 0xc4, 0x41, 0xf8, 0x10, 0x01);
|
||||
SINGLE_COMPARE(vmovups(xmmword[r9], xmm10), 0xc4, 0x41, 0xf8, 0x11, 0x11);
|
||||
SINGLE_COMPARE(vmovups(ymm8, ymmword[r9]), 0xc4, 0x41, 0xfc, 0x10, 0x01);
|
||||
}
|
||||
|
||||
TEST_CASE("LogTest")
|
||||
{
|
||||
AssemblyBuilderX64 build(/* logText= */ true);
|
||||
|
||||
build.push(r12);
|
||||
build.add(rax, rdi);
|
||||
build.add(rcx, 8);
|
||||
build.sub(dword[rax], 0x1fdc);
|
||||
build.and_(dword[rcx], 0x37);
|
||||
build.mov(rdi, qword[rax + rsi * 2]);
|
||||
build.vaddss(xmm0, xmm0, dword[rax + r14 * 2 + 0x1c]);
|
||||
|
||||
Label start = build.setLabel();
|
||||
build.cmp(rsi, rdi);
|
||||
build.jcc(Condition::Equal, start);
|
||||
|
||||
build.jmp(qword[rdx]);
|
||||
build.vaddps(ymm9, ymm12, ymmword[rbp + 0xc]);
|
||||
build.vaddpd(ymm2, ymm7, build.f64(2.5));
|
||||
build.neg(qword[rbp + r12 * 2]);
|
||||
build.mov64(r10, 0x1234567812345678ll);
|
||||
build.vmovapd(xmmword[rax], xmm11);
|
||||
build.pop(r12);
|
||||
build.ret();
|
||||
|
||||
build.finalize();
|
||||
|
||||
bool same = "\n" + build.text == R"(
|
||||
push r12
|
||||
add rax,rdi
|
||||
add rcx,8
|
||||
sub dword ptr [rax],1FDCh
|
||||
and dword ptr [rcx],37h
|
||||
mov rdi,qword ptr [rax+rsi*2]
|
||||
vaddss xmm0,xmm0,dword ptr [rax+r14*2+01Ch]
|
||||
.L1:
|
||||
cmp rsi,rdi
|
||||
je .L1
|
||||
jmp qword ptr [rdx]
|
||||
vaddps ymm9,ymm12,ymmword ptr [rbp+0Ch]
|
||||
vaddpd ymm2,ymm7,qword ptr [.start-8]
|
||||
neg qword ptr [rbp+r12*2]
|
||||
mov r10,1234567812345678h
|
||||
vmovapd xmmword ptr [rax],xmm11
|
||||
pop r12
|
||||
ret
|
||||
)";
|
||||
CHECK(same);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "Constants")
|
||||
{
|
||||
// clang-format off
|
||||
check(
|
||||
[](AssemblyBuilderX64& build) {
|
||||
build.xor_(rax, rax);
|
||||
build.add(rax, build.i64(0x1234567887654321));
|
||||
build.vmovss(xmm2, build.f32(1.0f));
|
||||
build.vmovsd(xmm3, build.f64(1.0));
|
||||
build.vmovaps(xmm4, build.f32x4(1.0f, 2.0f, 4.0f, 8.0f));
|
||||
build.ret();
|
||||
},
|
||||
{
|
||||
0x48, 0x33, 0xc0,
|
||||
0x48, 0x03, 0x05, 0xee, 0xff, 0xff, 0xff,
|
||||
0xc4, 0xe1, 0xfa, 0x10, 0x15, 0xe1, 0xff, 0xff, 0xff,
|
||||
0xc4, 0xe1, 0xfb, 0x10, 0x1d, 0xcc, 0xff, 0xff, 0xff,
|
||||
0xc4, 0xe1, 0xf8, 0x28, 0x25, 0xab, 0xff, 0xff, 0xff,
|
||||
0xc3
|
||||
});
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
TEST_CASE("ConstantStorage")
|
||||
{
|
||||
AssemblyBuilderX64 build(/* logText= */ false);
|
||||
|
||||
for (int i = 0; i <= 3000; i++)
|
||||
build.vaddss(xmm0, xmm0, build.f32(float(i)));
|
||||
|
||||
build.finalize();
|
||||
|
||||
LUAU_ASSERT(build.data.size() == 12004);
|
||||
|
||||
for (int i = 0; i <= 3000; i++)
|
||||
{
|
||||
float v;
|
||||
memcpy(&v, &build.data[build.data.size() - (i + 1) * sizeof(float)], sizeof(v));
|
||||
LUAU_ASSERT(v == float(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
|
@ -92,4 +92,17 @@ bar(foo())
|
|||
CHECK_EQ("number", toString(*expectedOty));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "ast_ancestry_at_eof")
|
||||
{
|
||||
check(R"(
|
||||
if true then
|
||||
)");
|
||||
|
||||
std::vector<AstNode*> ancestry = findAstAncestryOfPosition(*getMainSourceModule(), Position(2, 4));
|
||||
REQUIRE_GE(ancestry.size(), 2);
|
||||
AstStat* parentStat = ancestry[ancestry.size() - 2]->asStat();
|
||||
REQUIRE(bool(parentStat));
|
||||
REQUIRE(parentStat->is<AstStatIf>());
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
|
||||
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
|
||||
LUAU_FASTFLAG(LuauSeparateTypechecks)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -82,8 +81,6 @@ struct ACFixtureImpl : BaseType
|
|||
}
|
||||
|
||||
LoadDefinitionFileResult loadDefinition(const std::string& source)
|
||||
{
|
||||
if (FFlag::LuauSeparateTypechecks)
|
||||
{
|
||||
TypeChecker& typeChecker = this->frontend.typeCheckerForAutocomplete;
|
||||
unfreeze(typeChecker.globalTypes);
|
||||
|
@ -93,11 +90,6 @@ struct ACFixtureImpl : BaseType
|
|||
REQUIRE_MESSAGE(result.success, "loadDefinition: unable to load definition file");
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return BaseType::loadDefinition(source);
|
||||
}
|
||||
}
|
||||
|
||||
const Position& getPosition(char marker) const
|
||||
{
|
||||
|
@ -2772,6 +2764,8 @@ TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_on_string_singletons")
|
|||
|
||||
TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singletons")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauTwoPassAliasDefinitionFix", true};
|
||||
|
||||
check(R"(
|
||||
type tag = "cat" | "dog"
|
||||
local function f(a: tag) end
|
||||
|
@ -2844,6 +2838,8 @@ f(@1)
|
|||
|
||||
TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singleton_escape")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauTwoPassAliasDefinitionFix", true};
|
||||
|
||||
check(R"(
|
||||
type tag = "strange\t\"cat\"" | 'nice\t"dog"'
|
||||
local function f(x: tag) end
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -241,9 +241,17 @@ TEST_CASE("Math")
|
|||
|
||||
TEST_CASE("Table")
|
||||
{
|
||||
ScopedFastFlag sff("LuauFixBuiltinsStackLimit", true);
|
||||
|
||||
runConformance("nextvar.lua");
|
||||
runConformance("nextvar.lua", [](lua_State* L) {
|
||||
lua_pushcfunction(
|
||||
L,
|
||||
[](lua_State* L) {
|
||||
unsigned v = luaL_checkunsigned(L, 1);
|
||||
lua_pushlightuserdata(L, reinterpret_cast<void*>(uintptr_t(v)));
|
||||
return 1;
|
||||
},
|
||||
"makelud");
|
||||
lua_setglobal(L, "makelud");
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("PatternMatch")
|
||||
|
@ -1066,6 +1074,7 @@ TEST_CASE("UserdataApi")
|
|||
int lud;
|
||||
lua_pushlightuserdata(L, &lud);
|
||||
|
||||
CHECK(lua_tolightuserdata(L, -1) == &lud);
|
||||
CHECK(lua_touserdata(L, -1) == &lud);
|
||||
CHECK(lua_topointer(L, -1) == &lud);
|
||||
|
||||
|
@ -1073,6 +1082,7 @@ TEST_CASE("UserdataApi")
|
|||
int* ud1 = (int*)lua_newuserdata(L, 4);
|
||||
*ud1 = 42;
|
||||
|
||||
CHECK(lua_tolightuserdata(L, -1) == nullptr);
|
||||
CHECK(lua_touserdata(L, -1) == ud1);
|
||||
CHECK(lua_topointer(L, -1) == ud1);
|
||||
|
||||
|
@ -1111,4 +1121,205 @@ TEST_CASE("Iter")
|
|||
runConformance("iter.lua");
|
||||
}
|
||||
|
||||
const int kInt64Tag = 1;
|
||||
static int gInt64MT = -1;
|
||||
|
||||
static int64_t getInt64(lua_State* L, int idx)
|
||||
{
|
||||
if (void* p = lua_touserdatatagged(L, idx, kInt64Tag))
|
||||
return *static_cast<int64_t*>(p);
|
||||
|
||||
if (lua_isnumber(L, idx))
|
||||
return lua_tointeger(L, idx);
|
||||
|
||||
luaL_typeerror(L, 1, "int64");
|
||||
}
|
||||
|
||||
static void pushInt64(lua_State* L, int64_t value)
|
||||
{
|
||||
void* p = lua_newuserdatatagged(L, sizeof(int64_t), kInt64Tag);
|
||||
|
||||
lua_getref(L, gInt64MT);
|
||||
lua_setmetatable(L, -2);
|
||||
|
||||
*static_cast<int64_t*>(p) = value;
|
||||
}
|
||||
|
||||
TEST_CASE("Userdata")
|
||||
{
|
||||
runConformance("userdata.lua", [](lua_State* L) {
|
||||
// create metatable with all the metamethods
|
||||
lua_newtable(L);
|
||||
gInt64MT = lua_ref(L, -1);
|
||||
|
||||
// __index
|
||||
lua_pushcfunction(
|
||||
L,
|
||||
[](lua_State* L) {
|
||||
void* p = lua_touserdatatagged(L, 1, kInt64Tag);
|
||||
if (!p)
|
||||
luaL_typeerror(L, 1, "int64");
|
||||
|
||||
const char* name = luaL_checkstring(L, 2);
|
||||
|
||||
if (strcmp(name, "value") == 0)
|
||||
{
|
||||
lua_pushnumber(L, double(*static_cast<int64_t*>(p)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
luaL_error(L, "unknown field %s", name);
|
||||
},
|
||||
nullptr);
|
||||
lua_setfield(L, -2, "__index");
|
||||
|
||||
// __newindex
|
||||
lua_pushcfunction(
|
||||
L,
|
||||
[](lua_State* L) {
|
||||
void* p = lua_touserdatatagged(L, 1, kInt64Tag);
|
||||
if (!p)
|
||||
luaL_typeerror(L, 1, "int64");
|
||||
|
||||
const char* name = luaL_checkstring(L, 2);
|
||||
|
||||
if (strcmp(name, "value") == 0)
|
||||
{
|
||||
double value = luaL_checknumber(L, 3);
|
||||
*static_cast<int64_t*>(p) = int64_t(value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
luaL_error(L, "unknown field %s", name);
|
||||
},
|
||||
nullptr);
|
||||
lua_setfield(L, -2, "__newindex");
|
||||
|
||||
// __eq
|
||||
lua_pushcfunction(
|
||||
L,
|
||||
[](lua_State* L) {
|
||||
lua_pushboolean(L, getInt64(L, 1) == getInt64(L, 2));
|
||||
return 1;
|
||||
},
|
||||
nullptr);
|
||||
lua_setfield(L, -2, "__eq");
|
||||
|
||||
// __lt
|
||||
lua_pushcfunction(
|
||||
L,
|
||||
[](lua_State* L) {
|
||||
lua_pushboolean(L, getInt64(L, 1) < getInt64(L, 2));
|
||||
return 1;
|
||||
},
|
||||
nullptr);
|
||||
lua_setfield(L, -2, "__lt");
|
||||
|
||||
// __le
|
||||
lua_pushcfunction(
|
||||
L,
|
||||
[](lua_State* L) {
|
||||
lua_pushboolean(L, getInt64(L, 1) <= getInt64(L, 2));
|
||||
return 1;
|
||||
},
|
||||
nullptr);
|
||||
lua_setfield(L, -2, "__le");
|
||||
|
||||
// __add
|
||||
lua_pushcfunction(
|
||||
L,
|
||||
[](lua_State* L) {
|
||||
pushInt64(L, getInt64(L, 1) + getInt64(L, 2));
|
||||
return 1;
|
||||
},
|
||||
nullptr);
|
||||
lua_setfield(L, -2, "__add");
|
||||
|
||||
// __sub
|
||||
lua_pushcfunction(
|
||||
L,
|
||||
[](lua_State* L) {
|
||||
pushInt64(L, getInt64(L, 1) - getInt64(L, 2));
|
||||
return 1;
|
||||
},
|
||||
nullptr);
|
||||
lua_setfield(L, -2, "__sub");
|
||||
|
||||
// __mul
|
||||
lua_pushcfunction(
|
||||
L,
|
||||
[](lua_State* L) {
|
||||
pushInt64(L, getInt64(L, 1) * getInt64(L, 2));
|
||||
return 1;
|
||||
},
|
||||
nullptr);
|
||||
lua_setfield(L, -2, "__mul");
|
||||
|
||||
// __div
|
||||
lua_pushcfunction(
|
||||
L,
|
||||
[](lua_State* L) {
|
||||
// ideally we'd guard against 0 but it's a test so eh
|
||||
pushInt64(L, getInt64(L, 1) / getInt64(L, 2));
|
||||
return 1;
|
||||
},
|
||||
nullptr);
|
||||
lua_setfield(L, -2, "__div");
|
||||
|
||||
// __mod
|
||||
lua_pushcfunction(
|
||||
L,
|
||||
[](lua_State* L) {
|
||||
// ideally we'd guard against 0 and INT64_MIN but it's a test so eh
|
||||
pushInt64(L, getInt64(L, 1) % getInt64(L, 2));
|
||||
return 1;
|
||||
},
|
||||
nullptr);
|
||||
lua_setfield(L, -2, "__mod");
|
||||
|
||||
// __pow
|
||||
lua_pushcfunction(
|
||||
L,
|
||||
[](lua_State* L) {
|
||||
pushInt64(L, int64_t(pow(double(getInt64(L, 1)), double(getInt64(L, 2)))));
|
||||
return 1;
|
||||
},
|
||||
nullptr);
|
||||
lua_setfield(L, -2, "__pow");
|
||||
|
||||
// __unm
|
||||
lua_pushcfunction(
|
||||
L,
|
||||
[](lua_State* L) {
|
||||
pushInt64(L, -getInt64(L, 1));
|
||||
return 1;
|
||||
},
|
||||
nullptr);
|
||||
lua_setfield(L, -2, "__unm");
|
||||
|
||||
// __tostring
|
||||
lua_pushcfunction(
|
||||
L,
|
||||
[](lua_State* L) {
|
||||
int64_t value = getInt64(L, 1);
|
||||
std::string str = std::to_string(value);
|
||||
lua_pushlstring(L, str.c_str(), str.length());
|
||||
return 1;
|
||||
},
|
||||
nullptr);
|
||||
lua_setfield(L, -2, "__tostring");
|
||||
|
||||
// ctor
|
||||
lua_pushcfunction(
|
||||
L,
|
||||
[](lua_State* L) {
|
||||
double v = luaL_checknumber(L, 1);
|
||||
pushInt64(L, int64_t(v));
|
||||
return 1;
|
||||
},
|
||||
"int64");
|
||||
lua_setglobal(L, "int64");
|
||||
});
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
107
tests/ConstraintGraphBuilder.test.cpp
Normal file
107
tests/ConstraintGraphBuilder.test.cpp
Normal file
|
@ -0,0 +1,107 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#include "Fixture.h"
|
||||
#include "Luau/ConstraintGraphBuilder.h"
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
TEST_SUITE_BEGIN("ConstraintGraphBuilder");
|
||||
|
||||
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "hello_world")
|
||||
{
|
||||
AstStatBlock* block = parse(R"(
|
||||
local a = "hello"
|
||||
local b = a
|
||||
)");
|
||||
|
||||
cgb.visit(block);
|
||||
std::vector<const Constraint*> constraints = collectConstraints(cgb.rootScope);
|
||||
|
||||
REQUIRE(2 == constraints.size());
|
||||
|
||||
ToStringOptions opts;
|
||||
CHECK("a <: string" == toString(*constraints[0], opts));
|
||||
CHECK("b <: a" == toString(*constraints[1], opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "primitives")
|
||||
{
|
||||
AstStatBlock* block = parse(R"(
|
||||
local s = "hello"
|
||||
local n = 555
|
||||
local b = true
|
||||
local n2 = nil
|
||||
)");
|
||||
|
||||
cgb.visit(block);
|
||||
std::vector<const Constraint*> constraints = collectConstraints(cgb.rootScope);
|
||||
|
||||
REQUIRE(4 == constraints.size());
|
||||
|
||||
ToStringOptions opts;
|
||||
CHECK("a <: string" == toString(*constraints[0], opts));
|
||||
CHECK("b <: number" == toString(*constraints[1], opts));
|
||||
CHECK("c <: boolean" == toString(*constraints[2], opts));
|
||||
CHECK("d <: nil" == toString(*constraints[3], opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "function_application")
|
||||
{
|
||||
AstStatBlock* block = parse(R"(
|
||||
local a = "hello"
|
||||
local b = a("world")
|
||||
)");
|
||||
|
||||
cgb.visit(block);
|
||||
std::vector<const Constraint*> constraints = collectConstraints(cgb.rootScope);
|
||||
|
||||
REQUIRE(4 == constraints.size());
|
||||
|
||||
ToStringOptions opts;
|
||||
CHECK("a <: string" == toString(*constraints[0], opts));
|
||||
CHECK("b ~ inst a" == toString(*constraints[1], opts));
|
||||
CHECK("(string) -> (c, d...) <: b" == toString(*constraints[2], opts));
|
||||
CHECK("e <: c" == toString(*constraints[3], opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "local_function_definition")
|
||||
{
|
||||
AstStatBlock* block = parse(R"(
|
||||
local function f(a)
|
||||
return a
|
||||
end
|
||||
)");
|
||||
|
||||
cgb.visit(block);
|
||||
std::vector<const Constraint*> constraints = collectConstraints(cgb.rootScope);
|
||||
|
||||
REQUIRE(2 == constraints.size());
|
||||
|
||||
ToStringOptions opts;
|
||||
CHECK("a ~ gen (b) -> (c...)" == toString(*constraints[0], opts));
|
||||
CHECK("b <: c..." == toString(*constraints[1], opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "recursive_function")
|
||||
{
|
||||
AstStatBlock* block = parse(R"(
|
||||
local function f(a)
|
||||
return f(a)
|
||||
end
|
||||
)");
|
||||
|
||||
cgb.visit(block);
|
||||
std::vector<const Constraint*> constraints = collectConstraints(cgb.rootScope);
|
||||
|
||||
REQUIRE(4 == constraints.size());
|
||||
|
||||
ToStringOptions opts;
|
||||
CHECK("a ~ gen (b) -> (c...)" == toString(*constraints[0], opts));
|
||||
CHECK("d ~ inst a" == toString(*constraints[1], opts));
|
||||
CHECK("(b) -> (e, f...) <: d" == toString(*constraints[2], opts));
|
||||
CHECK("e <: c..." == toString(*constraints[3], opts));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
87
tests/ConstraintSolver.test.cpp
Normal file
87
tests/ConstraintSolver.test.cpp
Normal file
|
@ -0,0 +1,87 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#include "Fixture.h"
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
#include "Luau/ConstraintGraphBuilder.h"
|
||||
#include "Luau/ConstraintSolver.h"
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
static TypeId requireBinding(Scope2* scope, const char* name)
|
||||
{
|
||||
auto b = linearSearchForBinding(scope, name);
|
||||
LUAU_ASSERT(b.has_value());
|
||||
return *b;
|
||||
}
|
||||
|
||||
TEST_SUITE_BEGIN("ConstraintSolver");
|
||||
|
||||
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "hello")
|
||||
{
|
||||
AstStatBlock* block = parse(R"(
|
||||
local a = 55
|
||||
local b = a
|
||||
)");
|
||||
|
||||
cgb.visit(block);
|
||||
|
||||
ConstraintSolver cs{&arena, cgb.rootScope};
|
||||
|
||||
cs.run();
|
||||
|
||||
TypeId bType = requireBinding(cgb.rootScope, "b");
|
||||
|
||||
CHECK("number" == toString(bType));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "generic_function")
|
||||
{
|
||||
AstStatBlock* block = parse(R"(
|
||||
local function id(a)
|
||||
return a
|
||||
end
|
||||
)");
|
||||
|
||||
cgb.visit(block);
|
||||
|
||||
ConstraintSolver cs{&arena, cgb.rootScope};
|
||||
|
||||
cs.run();
|
||||
|
||||
TypeId idType = requireBinding(cgb.rootScope, "id");
|
||||
|
||||
CHECK("<a>(a) -> a" == toString(idType));
|
||||
}
|
||||
|
||||
#if 1
|
||||
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "proper_let_generalization")
|
||||
{
|
||||
AstStatBlock* block = parse(R"(
|
||||
local function a(c)
|
||||
local function d(e)
|
||||
return c
|
||||
end
|
||||
|
||||
return d
|
||||
end
|
||||
|
||||
local b = a(5)
|
||||
)");
|
||||
|
||||
cgb.visit(block);
|
||||
|
||||
ToStringOptions opts;
|
||||
|
||||
ConstraintSolver cs{&arena, cgb.rootScope};
|
||||
|
||||
cs.run();
|
||||
|
||||
TypeId idType = requireBinding(cgb.rootScope, "b");
|
||||
|
||||
CHECK("<a>(a) -> number" == toString(idType, opts));
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_SUITE_END();
|
|
@ -136,7 +136,7 @@ function test(a)
|
|||
while a < 0 do
|
||||
a += 1
|
||||
end
|
||||
for i=1,2 do
|
||||
for i=10,1,-1 do
|
||||
a += 1
|
||||
end
|
||||
for i in pairs({}) do
|
||||
|
@ -154,8 +154,8 @@ end
|
|||
const bool args1[] = {false};
|
||||
const bool args2[] = {true};
|
||||
|
||||
CHECK_EQ(50, Luau::Compile::computeCost(model, args1, 1));
|
||||
CHECK_EQ(49, Luau::Compile::computeCost(model, args2, 1));
|
||||
CHECK_EQ(82, Luau::Compile::computeCost(model, args1, 1));
|
||||
CHECK_EQ(79, Luau::Compile::computeCost(model, args2, 1));
|
||||
}
|
||||
|
||||
TEST_CASE("Conditional")
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
static const char* mainModuleName = "MainModule";
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -249,6 +251,9 @@ std::optional<TypeId> Fixture::getType(const std::string& name)
|
|||
ModulePtr module = getMainModule();
|
||||
REQUIRE(module);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return linearSearchForBinding(module->getModuleScope2(), name.c_str());
|
||||
else
|
||||
return lookupName(module->getModuleScope(), name);
|
||||
}
|
||||
|
||||
|
@ -421,6 +426,12 @@ BuiltinsFixture::BuiltinsFixture(bool freeze, bool prepareAutocomplete)
|
|||
Luau::freeze(frontend.typeCheckerForAutocomplete.globalTypes);
|
||||
}
|
||||
|
||||
ConstraintGraphBuilderFixture::ConstraintGraphBuilderFixture()
|
||||
: Fixture()
|
||||
, forceTheFlag{"DebugLuauDeferredConstraintResolution", true}
|
||||
{
|
||||
}
|
||||
|
||||
ModuleName fromString(std::string_view name)
|
||||
{
|
||||
return ModuleName(name);
|
||||
|
@ -460,4 +471,27 @@ std::optional<TypeId> lookupName(ScopePtr scope, const std::string& name)
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<TypeId> linearSearchForBinding(Scope2* scope, const char* name)
|
||||
{
|
||||
while (scope)
|
||||
{
|
||||
for (const auto& [n, ty] : scope->bindings)
|
||||
{
|
||||
if (n.astName() == name)
|
||||
return ty;
|
||||
}
|
||||
|
||||
scope = scope->parent;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void dump(const std::vector<Constraint>& constraints)
|
||||
{
|
||||
ToStringOptions opts;
|
||||
for (const auto& c : constraints)
|
||||
printf("%s\n", toString(c, opts).c_str());
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "Luau/Config.h"
|
||||
#include "Luau/ConstraintGraphBuilder.h"
|
||||
#include "Luau/FileResolver.h"
|
||||
#include "Luau/Frontend.h"
|
||||
#include "Luau/IostreamHelpers.h"
|
||||
|
@ -156,6 +157,16 @@ struct BuiltinsFixture : Fixture
|
|||
BuiltinsFixture(bool freeze = true, bool prepareAutocomplete = false);
|
||||
};
|
||||
|
||||
struct ConstraintGraphBuilderFixture : Fixture
|
||||
{
|
||||
TypeArena arena;
|
||||
ConstraintGraphBuilder cgb{&arena};
|
||||
|
||||
ScopedFastFlag forceTheFlag;
|
||||
|
||||
ConstraintGraphBuilderFixture();
|
||||
};
|
||||
|
||||
ModuleName fromString(std::string_view name);
|
||||
|
||||
template<typename T>
|
||||
|
@ -175,9 +186,12 @@ bool isInArena(TypeId t, const TypeArena& arena);
|
|||
void dumpErrors(const ModulePtr& module);
|
||||
void dumpErrors(const Module& module);
|
||||
void dump(const std::string& name, TypeId ty);
|
||||
void dump(const std::vector<Constraint>& constraints);
|
||||
|
||||
std::optional<TypeId> lookupName(ScopePtr scope, const std::string& name); // Warning: This function runs in O(n**2)
|
||||
|
||||
std::optional<TypeId> linearSearchForBinding(Scope2* scope, const char* name);
|
||||
|
||||
} // namespace Luau
|
||||
|
||||
#define LUAU_REQUIRE_ERRORS(result) \
|
||||
|
|
|
@ -1031,8 +1031,6 @@ return false;
|
|||
|
||||
TEST_CASE("check_without_builtin_next")
|
||||
{
|
||||
ScopedFastFlag luauDoNotRelyOnNextBinding{"LuauDoNotRelyOnNextBinding", true};
|
||||
|
||||
TestFileResolver fileResolver;
|
||||
TestConfigResolver configResolver;
|
||||
Frontend frontend(&fileResolver, &configResolver);
|
||||
|
|
|
@ -116,7 +116,8 @@ TEST_CASE("encode_AstExprGroup")
|
|||
|
||||
std::string json = toJson(&group);
|
||||
|
||||
const std::string expected = R"({"type":"AstExprGroup","location":"0,0 - 0,0","expr":{"type":"AstExprConstantNumber","location":"0,0 - 0,0","value":5}})";
|
||||
const std::string expected =
|
||||
R"({"type":"AstExprGroup","location":"0,0 - 0,0","expr":{"type":"AstExprConstantNumber","location":"0,0 - 0,0","value":5}})";
|
||||
|
||||
CHECK(json == expected);
|
||||
}
|
||||
|
@ -149,7 +150,8 @@ TEST_CASE("encode_AstExprVarargs")
|
|||
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprCall")
|
||||
{
|
||||
AstExpr* expr = expectParseExpr("foo(1, 2, 3)");
|
||||
std::string_view expected = R"({"type":"AstExprCall","location":"0,4 - 0,16","func":{"type":"AstExprGlobal","location":"0,4 - 0,7","global":"foo"},"args":[{"type":"AstExprConstantNumber","location":"0,8 - 0,9","value":1},{"type":"AstExprConstantNumber","location":"0,11 - 0,12","value":2},{"type":"AstExprConstantNumber","location":"0,14 - 0,15","value":3}],"self":false,"argLocation":"0,8 - 0,16"})";
|
||||
std::string_view expected =
|
||||
R"({"type":"AstExprCall","location":"0,4 - 0,16","func":{"type":"AstExprGlobal","location":"0,4 - 0,7","global":"foo"},"args":[{"type":"AstExprConstantNumber","location":"0,8 - 0,9","value":1},{"type":"AstExprConstantNumber","location":"0,11 - 0,12","value":2},{"type":"AstExprConstantNumber","location":"0,14 - 0,15","value":3}],"self":false,"argLocation":"0,8 - 0,16"})";
|
||||
|
||||
CHECK(toJson(expr) == expected);
|
||||
}
|
||||
|
@ -158,7 +160,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprIndexName")
|
|||
{
|
||||
AstExpr* expr = expectParseExpr("foo.bar");
|
||||
|
||||
std::string_view expected = R"({"type":"AstExprIndexName","location":"0,4 - 0,11","expr":{"type":"AstExprGlobal","location":"0,4 - 0,7","global":"foo"},"index":"bar","indexLocation":"0,8 - 0,11","op":"."})";
|
||||
std::string_view expected =
|
||||
R"({"type":"AstExprIndexName","location":"0,4 - 0,11","expr":{"type":"AstExprGlobal","location":"0,4 - 0,7","global":"foo"},"index":"bar","indexLocation":"0,8 - 0,11","op":"."})";
|
||||
|
||||
CHECK(toJson(expr) == expected);
|
||||
}
|
||||
|
@ -167,7 +170,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprIndexExpr")
|
|||
{
|
||||
AstExpr* expr = expectParseExpr("foo['bar']");
|
||||
|
||||
std::string_view expected = R"({"type":"AstExprIndexExpr","location":"0,4 - 0,14","expr":{"type":"AstExprGlobal","location":"0,4 - 0,7","global":"foo"},"index":{"type":"AstExprConstantString","location":"0,8 - 0,13","value":"bar"}})";
|
||||
std::string_view expected =
|
||||
R"({"type":"AstExprIndexExpr","location":"0,4 - 0,14","expr":{"type":"AstExprGlobal","location":"0,4 - 0,7","global":"foo"},"index":{"type":"AstExprConstantString","location":"0,8 - 0,13","value":"bar"}})";
|
||||
|
||||
CHECK(toJson(expr) == expected);
|
||||
}
|
||||
|
@ -176,7 +180,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprFunction")
|
|||
{
|
||||
AstExpr* expr = expectParseExpr("function (a) return a end");
|
||||
|
||||
std::string_view expected = R"({"type":"AstExprFunction","location":"0,4 - 0,29","generics":[],"genericPacks":[],"args":[{"type":null,"name":"a","location":"0,14 - 0,15"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,16 - 0,26","body":[{"type":"AstStatReturn","location":"0,17 - 0,25","list":[{"type":"AstExprLocal","location":"0,24 - 0,25","local":{"type":null,"name":"a","location":"0,14 - 0,15"}}]}]},"functionDepth":1,"debugname":"","hasEnd":true})";
|
||||
std::string_view expected =
|
||||
R"({"type":"AstExprFunction","location":"0,4 - 0,29","generics":[],"genericPacks":[],"args":[{"type":null,"name":"a","location":"0,14 - 0,15"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,16 - 0,26","body":[{"type":"AstStatReturn","location":"0,17 - 0,25","list":[{"type":"AstExprLocal","location":"0,24 - 0,25","local":{"type":null,"name":"a","location":"0,14 - 0,15"}}]}]},"functionDepth":1,"debugname":"","hasEnd":true})";
|
||||
|
||||
CHECK(toJson(expr) == expected);
|
||||
}
|
||||
|
@ -185,7 +190,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprTable")
|
|||
{
|
||||
AstExpr* expr = expectParseExpr("{true, key=true, [key2]=true}");
|
||||
|
||||
std::string_view expected = R"({"type":"AstExprTable","location":"0,4 - 0,33","items":[{"kind":"item","value":{"type":"AstExprConstantBool","location":"0,5 - 0,9","value":true}},{"kind":"record","key":{"type":"AstExprConstantString","location":"0,11 - 0,14","value":"key"},"value":{"type":"AstExprConstantBool","location":"0,15 - 0,19","value":true}},{"kind":"general","key":{"type":"AstExprGlobal","location":"0,22 - 0,26","global":"key2"},"value":{"type":"AstExprConstantBool","location":"0,28 - 0,32","value":true}}]})";
|
||||
std::string_view expected =
|
||||
R"({"type":"AstExprTable","location":"0,4 - 0,33","items":[{"kind":"item","value":{"type":"AstExprConstantBool","location":"0,5 - 0,9","value":true}},{"kind":"record","key":{"type":"AstExprConstantString","location":"0,11 - 0,14","value":"key"},"value":{"type":"AstExprConstantBool","location":"0,15 - 0,19","value":true}},{"kind":"general","key":{"type":"AstExprGlobal","location":"0,22 - 0,26","global":"key2"},"value":{"type":"AstExprConstantBool","location":"0,28 - 0,32","value":true}}]})";
|
||||
|
||||
CHECK(toJson(expr) == expected);
|
||||
}
|
||||
|
@ -194,7 +200,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprUnary")
|
|||
{
|
||||
AstExpr* expr = expectParseExpr("-b");
|
||||
|
||||
std::string_view expected = R"({"type":"AstExprUnary","location":"0,4 - 0,6","op":"minus","expr":{"type":"AstExprGlobal","location":"0,5 - 0,6","global":"b"}})";
|
||||
std::string_view expected =
|
||||
R"({"type":"AstExprUnary","location":"0,4 - 0,6","op":"minus","expr":{"type":"AstExprGlobal","location":"0,5 - 0,6","global":"b"}})";
|
||||
|
||||
CHECK(toJson(expr) == expected);
|
||||
}
|
||||
|
@ -203,7 +210,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprBinary")
|
|||
{
|
||||
AstExpr* expr = expectParseExpr("b + c");
|
||||
|
||||
std::string_view expected = R"({"type":"AstExprBinary","location":"0,4 - 0,9","op":"Add","left":{"type":"AstExprGlobal","location":"0,4 - 0,5","global":"b"},"right":{"type":"AstExprGlobal","location":"0,8 - 0,9","global":"c"}})";
|
||||
std::string_view expected =
|
||||
R"({"type":"AstExprBinary","location":"0,4 - 0,9","op":"Add","left":{"type":"AstExprGlobal","location":"0,4 - 0,5","global":"b"},"right":{"type":"AstExprGlobal","location":"0,8 - 0,9","global":"c"}})";
|
||||
|
||||
CHECK(toJson(expr) == expected);
|
||||
}
|
||||
|
@ -212,7 +220,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprTypeAssertion")
|
|||
{
|
||||
AstExpr* expr = expectParseExpr("b :: any");
|
||||
|
||||
std::string_view expected = R"({"type":"AstExprTypeAssertion","location":"0,4 - 0,12","expr":{"type":"AstExprGlobal","location":"0,4 - 0,5","global":"b"},"annotation":{"type":"AstTypeReference","location":"0,9 - 0,12","name":"any","parameters":[]}})";
|
||||
std::string_view expected =
|
||||
R"({"type":"AstExprTypeAssertion","location":"0,4 - 0,12","expr":{"type":"AstExprGlobal","location":"0,4 - 0,5","global":"b"},"annotation":{"type":"AstTypeReference","location":"0,9 - 0,12","name":"any","parameters":[]}})";
|
||||
|
||||
CHECK(toJson(expr) == expected);
|
||||
}
|
||||
|
@ -239,7 +248,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatIf")
|
|||
{
|
||||
AstStat* statement = expectParseStatement("if true then else end");
|
||||
|
||||
std::string_view expected = R"({"type":"AstStatIf","location":"0,0 - 0,21","condition":{"type":"AstExprConstantBool","location":"0,3 - 0,7","value":true},"thenbody":{"type":"AstStatBlock","location":"0,12 - 0,13","body":[]},"elsebody":{"type":"AstStatBlock","location":"0,17 - 0,18","body":[]},"hasThen":true,"hasEnd":true})";
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatIf","location":"0,0 - 0,21","condition":{"type":"AstExprConstantBool","location":"0,3 - 0,7","value":true},"thenbody":{"type":"AstStatBlock","location":"0,12 - 0,13","body":[]},"elsebody":{"type":"AstStatBlock","location":"0,17 - 0,18","body":[]},"hasThen":true,"hasEnd":true})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
|
@ -248,7 +258,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatWhile")
|
|||
{
|
||||
AstStat* statement = expectParseStatement("while true do end");
|
||||
|
||||
std::string_view expected = R"({"type":"AtStatWhile","location":"0,0 - 0,17","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,14","body":[]},"hasDo":true,"hasEnd":true})";
|
||||
std::string_view expected =
|
||||
R"({"type":"AtStatWhile","location":"0,0 - 0,17","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,14","body":[]},"hasDo":true,"hasEnd":true})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
|
@ -257,7 +268,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatRepeat")
|
|||
{
|
||||
AstStat* statement = expectParseStatement("repeat until true");
|
||||
|
||||
std::string_view expected = R"({"type":"AstStatRepeat","location":"0,0 - 0,17","condition":{"type":"AstExprConstantBool","location":"0,13 - 0,17","value":true},"body":{"type":"AstStatBlock","location":"0,6 - 0,7","body":[]},"hasUntil":true})";
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatRepeat","location":"0,0 - 0,17","condition":{"type":"AstExprConstantBool","location":"0,13 - 0,17","value":true},"body":{"type":"AstStatBlock","location":"0,6 - 0,7","body":[]},"hasUntil":true})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
|
@ -266,7 +278,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatBreak")
|
|||
{
|
||||
AstStat* statement = expectParseStatement("while true do break end");
|
||||
|
||||
std::string_view expected = R"({"type":"AtStatWhile","location":"0,0 - 0,23","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,20","body":[{"type":"AstStatBreak","location":"0,14 - 0,19"}]},"hasDo":true,"hasEnd":true})";
|
||||
std::string_view expected =
|
||||
R"({"type":"AtStatWhile","location":"0,0 - 0,23","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,20","body":[{"type":"AstStatBreak","location":"0,14 - 0,19"}]},"hasDo":true,"hasEnd":true})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
|
@ -275,7 +288,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatContinue")
|
|||
{
|
||||
AstStat* statement = expectParseStatement("while true do continue end");
|
||||
|
||||
std::string_view expected = R"({"type":"AtStatWhile","location":"0,0 - 0,26","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,23","body":[{"type":"AstStatContinue","location":"0,14 - 0,22"}]},"hasDo":true,"hasEnd":true})";
|
||||
std::string_view expected =
|
||||
R"({"type":"AtStatWhile","location":"0,0 - 0,26","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,23","body":[{"type":"AstStatContinue","location":"0,14 - 0,22"}]},"hasDo":true,"hasEnd":true})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
|
@ -284,7 +298,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatFor")
|
|||
{
|
||||
AstStat* statement = expectParseStatement("for a=0,1 do end");
|
||||
|
||||
std::string_view expected = R"({"type":"AstStatFor","location":"0,0 - 0,16","var":{"type":null,"name":"a","location":"0,4 - 0,5"},"from":{"type":"AstExprConstantNumber","location":"0,6 - 0,7","value":0},"to":{"type":"AstExprConstantNumber","location":"0,8 - 0,9","value":1},"body":{"type":"AstStatBlock","location":"0,12 - 0,13","body":[]},"hasDo":true,"hasEnd":true})";
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatFor","location":"0,0 - 0,16","var":{"type":null,"name":"a","location":"0,4 - 0,5"},"from":{"type":"AstExprConstantNumber","location":"0,6 - 0,7","value":0},"to":{"type":"AstExprConstantNumber","location":"0,8 - 0,9","value":1},"body":{"type":"AstStatBlock","location":"0,12 - 0,13","body":[]},"hasDo":true,"hasEnd":true})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
|
@ -293,7 +308,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatForIn")
|
|||
{
|
||||
AstStat* statement = expectParseStatement("for a in b do end");
|
||||
|
||||
std::string_view expected = R"({"type":"AstStatForIn","location":"0,0 - 0,17","vars":[{"type":null,"name":"a","location":"0,4 - 0,5"}],"values":[{"type":"AstExprGlobal","location":"0,9 - 0,10","global":"b"}],"body":{"type":"AstStatBlock","location":"0,13 - 0,14","body":[]},"hasIn":true,"hasDo":true,"hasEnd":true})";
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatForIn","location":"0,0 - 0,17","vars":[{"type":null,"name":"a","location":"0,4 - 0,5"}],"values":[{"type":"AstExprGlobal","location":"0,9 - 0,10","global":"b"}],"body":{"type":"AstStatBlock","location":"0,13 - 0,14","body":[]},"hasIn":true,"hasDo":true,"hasEnd":true})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
|
@ -302,7 +318,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatCompoundAssign")
|
|||
{
|
||||
AstStat* statement = expectParseStatement("a += b");
|
||||
|
||||
std::string_view expected = R"({"type":"AstStatCompoundAssign","location":"0,0 - 0,6","op":"Add","var":{"type":"AstExprGlobal","location":"0,0 - 0,1","global":"a"},"value":{"type":"AstExprGlobal","location":"0,5 - 0,6","global":"b"}})";
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatCompoundAssign","location":"0,0 - 0,6","op":"Add","var":{"type":"AstExprGlobal","location":"0,0 - 0,1","global":"a"},"value":{"type":"AstExprGlobal","location":"0,5 - 0,6","global":"b"}})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
|
@ -311,7 +328,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatLocalFunction")
|
|||
{
|
||||
AstStat* statement = expectParseStatement("local function a(b) return end");
|
||||
|
||||
std::string_view expected = R"({"type":"AstStatLocalFunction","location":"0,0 - 0,30","name":{"type":null,"name":"a","location":"0,15 - 0,16"},"func":{"type":"AstExprFunction","location":"0,0 - 0,30","generics":[],"genericPacks":[],"args":[{"type":null,"name":"b","location":"0,17 - 0,18"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,19 - 0,27","body":[{"type":"AstStatReturn","location":"0,20 - 0,26","list":[]}]},"functionDepth":1,"debugname":"a","hasEnd":true}})";
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatLocalFunction","location":"0,0 - 0,30","name":{"type":null,"name":"a","location":"0,15 - 0,16"},"func":{"type":"AstExprFunction","location":"0,0 - 0,30","generics":[],"genericPacks":[],"args":[{"type":null,"name":"b","location":"0,17 - 0,18"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,19 - 0,27","body":[{"type":"AstStatReturn","location":"0,20 - 0,26","list":[]}]},"functionDepth":1,"debugname":"a","hasEnd":true}})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
|
@ -320,7 +338,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatTypeAlias")
|
|||
{
|
||||
AstStat* statement = expectParseStatement("type A = B");
|
||||
|
||||
std::string_view expected = R"({"type":"AstStatTypeAlias","location":"0,0 - 0,10","name":"A","generics":[],"genericPacks":[],"type":{"type":"AstTypeReference","location":"0,9 - 0,10","name":"B","parameters":[]},"exported":false})";
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,10","name":"A","generics":[],"genericPacks":[],"type":{"type":"AstTypeReference","location":"0,9 - 0,10","name":"B","parameters":[]},"exported":false})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
|
@ -329,7 +348,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareFunction")
|
|||
{
|
||||
AstStat* statement = expectParseStatement("declare function foo(x: number): string");
|
||||
|
||||
std::string_view expected = R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,39","name":"foo","params":{"types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","parameters":[]}]},"retTypes":{"types":[{"type":"AstTypeReference","location":"0,33 - 0,39","name":"string","parameters":[]}]},"generics":[],"genericPacks":[]})";
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,39","name":"foo","params":{"types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","parameters":[]}]},"retTypes":{"types":[{"type":"AstTypeReference","location":"0,33 - 0,39","name":"string","parameters":[]}]},"generics":[],"genericPacks":[]})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
|
@ -349,10 +369,12 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareClass")
|
|||
|
||||
REQUIRE(2 == root->body.size);
|
||||
|
||||
std::string_view expected1 = R"({"type":"AstStatDeclareClass","location":"1,22 - 4,11","name":"Foo","props":[{"name":"prop","type":{"type":"AstTypeReference","location":"2,18 - 2,24","name":"number","parameters":[]}},{"name":"method","type":{"type":"AstTypeFunction","location":"3,21 - 4,11","generics":[],"genericPacks":[],"argTypes":{"types":[{"type":"AstTypeReference","location":"3,39 - 3,45","name":"number","parameters":[]}]},"returnTypes":{"types":[{"type":"AstTypeReference","location":"3,48 - 3,54","name":"string","parameters":[]}]}}}]})";
|
||||
std::string_view expected1 =
|
||||
R"({"type":"AstStatDeclareClass","location":"1,22 - 4,11","name":"Foo","props":[{"name":"prop","type":{"type":"AstTypeReference","location":"2,18 - 2,24","name":"number","parameters":[]}},{"name":"method","type":{"type":"AstTypeFunction","location":"3,21 - 4,11","generics":[],"genericPacks":[],"argTypes":{"types":[{"type":"AstTypeReference","location":"3,39 - 3,45","name":"number","parameters":[]}]},"returnTypes":{"types":[{"type":"AstTypeReference","location":"3,48 - 3,54","name":"string","parameters":[]}]}}}]})";
|
||||
CHECK(toJson(root->body.data[0]) == expected1);
|
||||
|
||||
std::string_view expected2 = R"({"type":"AstStatDeclareClass","location":"6,22 - 8,11","name":"Bar","superName":"Foo","props":[{"name":"prop2","type":{"type":"AstTypeReference","location":"7,19 - 7,25","name":"string","parameters":[]}}]})";
|
||||
std::string_view expected2 =
|
||||
R"({"type":"AstStatDeclareClass","location":"6,22 - 8,11","name":"Bar","superName":"Foo","props":[{"name":"prop2","type":{"type":"AstTypeReference","location":"7,19 - 7,25","name":"string","parameters":[]}}]})";
|
||||
CHECK(toJson(root->body.data[1]) == expected2);
|
||||
}
|
||||
|
||||
|
@ -360,7 +382,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_annotation")
|
|||
{
|
||||
AstStat* statement = expectParseStatement("type T = ((number) -> (string | nil)) & ((string) -> ())");
|
||||
|
||||
std::string_view expected = R"({"type":"AstStatTypeAlias","location":"0,0 - 0,55","name":"T","generics":[],"genericPacks":[],"type":{"type":"AstTypeIntersection","location":"0,9 - 0,55","types":[{"type":"AstTypeFunction","location":"0,10 - 0,35","generics":[],"genericPacks":[],"argTypes":{"types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","parameters":[]}]},"returnTypes":{"types":[{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","parameters":[]}]}]}},{"type":"AstTypeFunction","location":"0,41 - 0,55","generics":[],"genericPacks":[],"argTypes":{"types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","parameters":[]}]},"returnTypes":{"types":[]}}]},"exported":false})";
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,55","name":"T","generics":[],"genericPacks":[],"type":{"type":"AstTypeIntersection","location":"0,9 - 0,55","types":[{"type":"AstTypeFunction","location":"0,10 - 0,35","generics":[],"genericPacks":[],"argTypes":{"types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","parameters":[]}]},"returnTypes":{"types":[{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","parameters":[]}]}]}},{"type":"AstTypeFunction","location":"0,41 - 0,55","generics":[],"genericPacks":[],"argTypes":{"types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","parameters":[]}]},"returnTypes":{"types":[]}}]},"exported":false})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
|
@ -372,7 +395,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypeError")
|
|||
|
||||
AstStat* statement = parseResult.root->body.data[0];
|
||||
|
||||
std::string_view expected = R"({"type":"AstStatTypeAlias","location":"0,0 - 0,9","name":"T","generics":[],"genericPacks":[],"type":{"type":"AstTypeError","location":"0,8 - 0,9","types":[],"messageIndex":0},"exported":false})";
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,9","name":"T","generics":[],"genericPacks":[],"type":{"type":"AstTypeError","location":"0,8 - 0,9","types":[],"messageIndex":0},"exported":false})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
|
@ -386,7 +410,8 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypePackExplicit")
|
|||
|
||||
CHECK(2 == root->body.size);
|
||||
|
||||
std::string_view expected = R"({"type":"AstStatLocal","location":"2,8 - 2,36","vars":[{"type":{"type":"AstTypeReference","location":"2,17 - 2,36","name":"A","parameters":[{"type":"AstTypePackExplicit","location":"2,19 - 2,20","typeList":{"types":[{"type":"AstTypeReference","location":"2,20 - 2,26","name":"number","parameters":[]},{"type":"AstTypeReference","location":"2,28 - 2,34","name":"string","parameters":[]}]}}]},"name":"a","location":"2,14 - 2,15"}],"values":[]})";
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatLocal","location":"2,8 - 2,36","vars":[{"type":{"type":"AstTypeReference","location":"2,17 - 2,36","name":"A","parameters":[{"type":"AstTypePackExplicit","location":"2,19 - 2,20","typeList":{"types":[{"type":"AstTypeReference","location":"2,20 - 2,26","name":"number","parameters":[]},{"type":"AstTypeReference","location":"2,28 - 2,34","name":"string","parameters":[]}]}}]},"name":"a","location":"2,14 - 2,15"}],"values":[]})";
|
||||
|
||||
CHECK(toJson(root->body.data[1]) == expected);
|
||||
}
|
||||
|
|
|
@ -198,10 +198,6 @@ TEST_CASE_FIXTURE(Fixture, "clone_class")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "clone_free_types")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauLosslessClone", true},
|
||||
};
|
||||
|
||||
TypeVar freeTy(FreeTypeVar{TypeLevel{}});
|
||||
TypePackVar freeTp(FreeTypePack{TypeLevel{}});
|
||||
|
||||
|
@ -218,8 +214,6 @@ TEST_CASE_FIXTURE(Fixture, "clone_free_types")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "clone_free_tables")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauLosslessClone", true};
|
||||
|
||||
TypeVar tableTy{TableTypeVar{}};
|
||||
TableTypeVar* ttv = getMutable<TableTypeVar>(&tableTy);
|
||||
ttv->state = TableState::Free;
|
||||
|
@ -252,8 +246,6 @@ TEST_CASE_FIXTURE(Fixture, "clone_constrained_intersection")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "clone_self_property")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauAnyInIsOptionalIsOptional", true};
|
||||
|
||||
fileResolver.source["Module/A"] = R"(
|
||||
--!nonstrict
|
||||
local a = {}
|
||||
|
|
|
@ -15,10 +15,7 @@ TEST_SUITE_BEGIN("NonstrictModeTests");
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "function_returns_number_or_string")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauReturnTypeInferenceInNonstrict", true},
|
||||
{"LuauLowerBoundsCalculation", true}
|
||||
};
|
||||
ScopedFastFlag sff[]{{"LuauReturnTypeInferenceInNonstrict", true}, {"LuauLowerBoundsCalculation", true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!nonstrict
|
||||
|
@ -150,8 +147,6 @@ TEST_CASE_FIXTURE(Fixture, "parameters_having_type_any_are_optional")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "local_tables_are_not_any")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauAnyInIsOptionalIsOptional", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!nonstrict
|
||||
local T = {}
|
||||
|
@ -169,8 +164,6 @@ TEST_CASE_FIXTURE(Fixture, "local_tables_are_not_any")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "offer_a_hint_if_you_use_a_dot_instead_of_a_colon")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauAnyInIsOptionalIsOptional", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!nonstrict
|
||||
local T = {}
|
||||
|
|
|
@ -44,6 +44,9 @@ void createSomeClasses(TypeChecker& typeChecker)
|
|||
addGlobalBinding(typeChecker, "Unrelated", {unrelatedType});
|
||||
typeChecker.globalScope->exportedTypeBindings["Unrelated"] = TypeFun{{}, unrelatedType};
|
||||
|
||||
for (const auto& [name, ty] : typeChecker.globalScope->exportedTypeBindings)
|
||||
persist(ty.type);
|
||||
|
||||
freeze(arena);
|
||||
}
|
||||
|
||||
|
@ -681,9 +684,7 @@ TEST_CASE_FIXTURE(Fixture, "higher_order_function_with_annotation")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "cyclic_table_is_marked_normal")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
};
|
||||
ScopedFastFlag flags[] = {{"LuauLowerBoundsCalculation", true}, {"LuauNormalizeFlagIsConservative", false}};
|
||||
|
||||
check(R"(
|
||||
type Fiber = {
|
||||
|
@ -697,6 +698,23 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_table_is_marked_normal")
|
|||
CHECK(t->normal);
|
||||
}
|
||||
|
||||
// Unfortunately, getting this right in the general case is difficult.
|
||||
TEST_CASE_FIXTURE(Fixture, "cyclic_table_is_not_marked_normal")
|
||||
{
|
||||
ScopedFastFlag flags[] = {{"LuauLowerBoundsCalculation", true}, {"LuauNormalizeFlagIsConservative", true}};
|
||||
|
||||
check(R"(
|
||||
type Fiber = {
|
||||
return_: Fiber?
|
||||
}
|
||||
|
||||
local f: Fiber
|
||||
)");
|
||||
|
||||
TypeId t = requireType("f");
|
||||
CHECK(!t->normal);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "variadic_tail_is_marked_normal")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
|
@ -997,4 +1015,51 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_failure_bound_type_is_normal_but_not_its_bounde
|
|||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
// We had an issue where a normal BoundTypeVar might point at a non-normal BoundTypeVar if it in turn pointed to a
|
||||
// normal TypeVar because we were calling follow() in an improper place.
|
||||
TEST_CASE_FIXTURE(Fixture, "bound_typevars_should_only_be_marked_normal_if_their_pointee_is_normal")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauLowerBoundsCalculation", true},
|
||||
{"LuauNormalizeFlagIsConservative", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local T = {}
|
||||
|
||||
function T:M()
|
||||
local function f(a)
|
||||
print(self.prop)
|
||||
self:g(a)
|
||||
self.prop = a
|
||||
end
|
||||
end
|
||||
|
||||
return T
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "skip_force_normal_on_external_types")
|
||||
{
|
||||
createSomeClasses(typeChecker);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
export type t0 = { a: Child }
|
||||
export type t1 = { a: typeof(string.byte) }
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersection_combine_on_bound_self")
|
||||
{
|
||||
ScopedFastFlag luauNormalizeCombineEqFix{"LuauNormalizeCombineEqFix", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
export type t0 = (((any)&({_:l0.t0,n0:t0,_G:any,}))&({_:any,}))&(((any)&({_:l0.t0,n0:t0,_G:any,}))&({_:any,}))
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
116
tests/NotNull.test.cpp
Normal file
116
tests/NotNull.test.cpp
Normal file
|
@ -0,0 +1,116 @@
|
|||
#include "Luau/NotNull.h"
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
using Luau::NotNull;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct Test
|
||||
{
|
||||
int x;
|
||||
float y;
|
||||
|
||||
static int count;
|
||||
Test()
|
||||
{
|
||||
++count;
|
||||
}
|
||||
|
||||
~Test()
|
||||
{
|
||||
--count;
|
||||
}
|
||||
};
|
||||
|
||||
int Test::count = 0;
|
||||
|
||||
}
|
||||
|
||||
int foo(NotNull<int> p)
|
||||
{
|
||||
return *p;
|
||||
}
|
||||
|
||||
void bar(int* q)
|
||||
{}
|
||||
|
||||
TEST_SUITE_BEGIN("NotNull");
|
||||
|
||||
TEST_CASE("basic_stuff")
|
||||
{
|
||||
NotNull<int> a = NotNull{new int(55)}; // Does runtime test
|
||||
NotNull<int> b{new int(55)}; // As above
|
||||
// NotNull<int> c = new int(55); // Nope. Mildly regrettable, but implicit conversion from T* to NotNull<T> in the general case is not good.
|
||||
|
||||
// a = nullptr; // nope
|
||||
|
||||
NotNull<int> d = a; // No runtime test. a is known not to be null.
|
||||
|
||||
int e = *d;
|
||||
*d = 1;
|
||||
CHECK(e == 55);
|
||||
|
||||
const NotNull<int> f = d;
|
||||
*f = 5; // valid: there is a difference between const NotNull<T> and NotNull<const T>
|
||||
// f = a; // nope
|
||||
|
||||
CHECK_EQ(a, d);
|
||||
CHECK(a != b);
|
||||
|
||||
NotNull<const int> g(a);
|
||||
CHECK(g == a);
|
||||
|
||||
// *g = 123; // nope
|
||||
|
||||
(void)f;
|
||||
|
||||
NotNull<Test> t{new Test};
|
||||
t->x = 5;
|
||||
t->y = 3.14f;
|
||||
|
||||
const NotNull<Test> u = t;
|
||||
// u->x = 44; // nope
|
||||
int v = u->x;
|
||||
CHECK(v == 5);
|
||||
|
||||
bar(a);
|
||||
|
||||
// a++; // nope
|
||||
// a[41]; // nope
|
||||
// a + 41; // nope
|
||||
// a - 41; // nope
|
||||
|
||||
delete a;
|
||||
delete b;
|
||||
delete t;
|
||||
|
||||
CHECK_EQ(0, Test::count);
|
||||
}
|
||||
|
||||
TEST_CASE("hashable")
|
||||
{
|
||||
std::unordered_map<NotNull<int>, const char*> map;
|
||||
NotNull<int> a{new int(8)};
|
||||
NotNull<int> b{new int(10)};
|
||||
|
||||
std::string hello = "hello";
|
||||
std::string world = "world";
|
||||
|
||||
map[a] = hello.c_str();
|
||||
map[b] = world.c_str();
|
||||
|
||||
CHECK_EQ(2, map.size());
|
||||
CHECK_EQ(hello.c_str(), map[a]);
|
||||
CHECK_EQ(world.c_str(), map[b]);
|
||||
|
||||
delete a;
|
||||
delete b;
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
|
@ -2622,4 +2622,15 @@ type Z<T> = { a: string | T..., b: number }
|
|||
REQUIRE_EQ(3, result.errors.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_message_for_using_function_as_type_annotation")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauParserFunctionKeywordAsTypeHelp", true};
|
||||
ParseResult result = tryParse(R"(
|
||||
type Foo = function
|
||||
)");
|
||||
REQUIRE_EQ(1, result.errors.size());
|
||||
CHECK_EQ("Using 'function' as a type annotation is not supported, consider replacing with a function type annotation e.g. '(...any) -> ...any'",
|
||||
result.errors[0].getMessage());
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -22,8 +22,6 @@ struct LimitFixture : BuiltinsFixture
|
|||
#if defined(_NOOPT) || defined(_DEBUG)
|
||||
ScopedFastInt LuauTypeInferRecursionLimit{"LuauTypeInferRecursionLimit", 100};
|
||||
#endif
|
||||
|
||||
ScopedFastFlag LuauJustOneCallFrameForHaveSeen{"LuauJustOneCallFrameForHaveSeen", true};
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
|
|
|
@ -33,6 +33,9 @@ struct ToDotClassFixture : Fixture
|
|||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["ChildClass"] = TypeFun{{}, childClassInstanceType};
|
||||
|
||||
for (const auto& [name, ty] : typeChecker.globalScope->exportedTypeBindings)
|
||||
persist(ty.type);
|
||||
|
||||
freeze(arena);
|
||||
}
|
||||
};
|
||||
|
@ -431,7 +434,8 @@ n1 -> n4;
|
|||
n4 [label="SingletonTypeVar boolean: true"];
|
||||
n1 -> n5;
|
||||
n5 [label="SingletonTypeVar boolean: false"];
|
||||
})", toDot(requireType("x"), opts));
|
||||
})",
|
||||
toDot(requireType("x"), opts));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -541,7 +541,6 @@ TEST_CASE_FIXTURE(Fixture, "self_recursive_instantiated_param")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_id")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauDocFuncParameters", true};
|
||||
CheckResult result = check(R"(
|
||||
local function id(x) return x end
|
||||
)");
|
||||
|
@ -554,7 +553,6 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_id")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_map")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauDocFuncParameters", true};
|
||||
CheckResult result = check(R"(
|
||||
local function map(arr, fn)
|
||||
local t = {}
|
||||
|
@ -573,7 +571,6 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_map")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_generic_pack")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauDocFuncParameters", true};
|
||||
CheckResult result = check(R"(
|
||||
local function f(a: number, b: string) end
|
||||
local function test<T..., U...>(...: T...): U...
|
||||
|
@ -590,7 +587,6 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_generic_pack")
|
|||
|
||||
TEST_CASE("toStringNamedFunction_unit_f")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauDocFuncParameters", true};
|
||||
TypePackVar empty{TypePack{}};
|
||||
FunctionTypeVar ftv{&empty, &empty, {}, false};
|
||||
CHECK_EQ("f(): ()", toStringNamedFunction("f", ftv));
|
||||
|
@ -598,7 +594,6 @@ TEST_CASE("toStringNamedFunction_unit_f")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_variadics")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauDocFuncParameters", true};
|
||||
CheckResult result = check(R"(
|
||||
local function f<a, b...>(x: a, ...): (a, a, b...)
|
||||
return x, x, ...
|
||||
|
@ -613,7 +608,6 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_variadics")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_variadics2")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauDocFuncParameters", true};
|
||||
CheckResult result = check(R"(
|
||||
local function f(): ...number
|
||||
return 1, 2, 3
|
||||
|
@ -628,7 +622,6 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_variadics2")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_variadics3")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauDocFuncParameters", true};
|
||||
CheckResult result = check(R"(
|
||||
local function f(): (string, ...number)
|
||||
return 'a', 1, 2, 3
|
||||
|
@ -643,7 +636,6 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_variadics3")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_type_annotation_has_partial_argnames")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauDocFuncParameters", true};
|
||||
CheckResult result = check(R"(
|
||||
local f: (number, y: number) -> number
|
||||
)");
|
||||
|
@ -656,7 +648,6 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_type_annotation_has_partial_ar
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_hide_type_params")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauDocFuncParameters", true};
|
||||
CheckResult result = check(R"(
|
||||
local function f<T>(x: T, g: <U>(T) -> U)): ()
|
||||
end
|
||||
|
@ -672,8 +663,6 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_hide_type_params")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_overrides_param_names")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauDocFuncParameters", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function test(a, b : string, ... : number) return a end
|
||||
)");
|
||||
|
@ -686,9 +675,21 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_overrides_param_names")
|
|||
CHECK_EQ("test<a>(first: a, second: string, ...: number): a", toStringNamedFunction("test", *ftv, opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "pick_distinct_names_for_mixed_explicit_and_implicit_generics")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"LuauAlwaysQuantify", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo<a>(x: a, y) end
|
||||
)");
|
||||
|
||||
CHECK("<a, b>(a, b) -> ()" == toString(requireType("foo")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_include_self_param")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauDocFuncParameters", true};
|
||||
CheckResult result = check(R"(
|
||||
local foo = {}
|
||||
function foo:method(arg: string): ()
|
||||
|
@ -705,7 +706,6 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_include_self_param")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_hide_self_param")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauDocFuncParameters", true};
|
||||
CheckResult result = check(R"(
|
||||
local foo = {}
|
||||
function foo:method(arg: string): ()
|
||||
|
@ -721,5 +721,4 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_hide_self_param")
|
|||
CHECK_EQ("foo:method<a>(arg: string): ()", toStringNamedFunction("foo:method", *ftv, opts));
|
||||
}
|
||||
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -600,7 +600,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "select_with_decimal_argument_is_rounded_down
|
|||
}
|
||||
|
||||
// Could be flaky if the fix has regressed.
|
||||
TEST_CASE_FIXTURE(Fixture, "bad_select_should_not_crash")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "bad_select_should_not_crash")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
do end
|
||||
|
@ -612,7 +612,9 @@ TEST_CASE_FIXTURE(Fixture, "bad_select_should_not_crash")
|
|||
end
|
||||
)");
|
||||
|
||||
CHECK_LE(0, result.errors.size());
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
CHECK_EQ("Argument count mismatch. Function expects at least 1 argument, but none are specified", toString(result.errors[0]));
|
||||
CHECK_EQ("Argument count mismatch. Function expects 1 argument, but none are specified", toString(result.errors[1]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "select_way_out_of_range")
|
||||
|
@ -877,12 +879,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dont_add_definitions_to_persistent_types")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauAssertStripsFalsyTypes", true},
|
||||
{"LuauDiscriminableUnions2", true},
|
||||
{"LuauWidenIfSupertypeIsFree2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: (number | boolean)?)
|
||||
return assert(x)
|
||||
|
@ -898,12 +894,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types2")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauAssertStripsFalsyTypes", true},
|
||||
{"LuauDiscriminableUnions2", true},
|
||||
{"LuauWidenIfSupertypeIsFree2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: (number | boolean)?): number | true
|
||||
return assert(x)
|
||||
|
@ -916,11 +906,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types2")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types_even_from_type_pack_tail_but_only_for_the_first_type")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauAssertStripsFalsyTypes", true},
|
||||
{"LuauDiscriminableUnions2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(...: number?)
|
||||
return assert(...)
|
||||
|
@ -933,11 +918,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types_even_from_type_pa
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_returns_false_and_string_iff_it_knows_the_first_argument_cannot_be_truthy")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{"LuauAssertStripsFalsyTypes", true},
|
||||
{"LuauDiscriminableUnions2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: nil)
|
||||
return assert(x, "hmm")
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue