mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-12 13:00:38 +00:00
Sync to upstream/release/530 (#517)
* Run clang-format * Contains a preliminary implementation of deferred constraint resolution * Reduce stack usage by some recursive functions * Fix a bug when smartCloning a BoundTypeVar * Remove some GC related flags from VM
This commit is contained in:
parent
edd071f99a
commit
55a026811a
43 changed files with 1876 additions and 296 deletions
|
@ -25,4 +25,6 @@ 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
|
|
@ -159,6 +159,8 @@ struct Frontend
|
|||
void applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName);
|
||||
|
||||
private:
|
||||
ModulePtr check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope);
|
||||
|
||||
std::pair<SourceNode*, SourceModule*> getSourceNode(CheckResult& checkResult, const ModuleName& name);
|
||||
SourceModule parse(const ModuleName& name, std::string_view src, const ParseOptions& parseOptions);
|
||||
|
||||
|
|
|
@ -19,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
|
||||
|
@ -65,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};
|
||||
|
@ -78,6 +80,7 @@ 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.
|
||||
|
|
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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -95,6 +95,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
|
||||
{
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "Luau/Clone.h"
|
||||
#include "Luau/RecursionCounter.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/Unifiable.h"
|
||||
|
||||
|
@ -382,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
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
@ -22,6 +24,7 @@ LUAU_FASTFLAG(LuauInferInNoCheckMode)
|
|||
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteDynamicLimits, false)
|
||||
LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -470,7 +473,8 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
|||
|
||||
typeChecker.requireCycles = requireCycles;
|
||||
|
||||
ModulePtr module = typeChecker.check(sourceModule, mode, environmentScope);
|
||||
ModulePtr module = FFlag::DebugLuauDeferredConstraintResolution ? check(sourceModule, mode, environmentScope)
|
||||
: typeChecker.check(sourceModule, mode, environmentScope);
|
||||
|
||||
stats.timeCheck += getTimestamp() - timestamp;
|
||||
stats.filesStrict += mode == Mode::Strict;
|
||||
|
@ -782,6 +786,23 @@ 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)
|
||||
{
|
||||
|
|
|
@ -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,11 +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_FASTFLAG(LuauLowerBoundsCalculation);
|
||||
LUAU_FASTFLAG(LuauNormalizeFlagIsConservative);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -97,38 +100,60 @@ void Module::clonePublicInterface(InternalErrorReporter& ice)
|
|||
|
||||
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);
|
||||
}
|
||||
|
||||
ForceNormal forceNormal{&interfaceTypes};
|
||||
|
||||
for (auto& [name, tf] : moduleScope->exportedTypeBindings)
|
||||
if (exportedTypeBindings)
|
||||
{
|
||||
tf = clone(tf, interfaceTypes, cloneState);
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
for (auto& [name, tf] : *exportedTypeBindings)
|
||||
{
|
||||
normalize(tf.type, interfaceTypes, ice);
|
||||
|
||||
if (FFlag::LuauNormalizeFlagIsConservative)
|
||||
tf = clone(tf, interfaceTypes, cloneState);
|
||||
if (FFlag::LuauLowerBoundsCalculation)
|
||||
{
|
||||
// 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);
|
||||
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)))
|
||||
{
|
||||
|
@ -155,4 +180,10 @@ ScopePtr Module::getModuleScope() const
|
|||
return scopes.front().second;
|
||||
}
|
||||
|
||||
Scope2* Module::getModuleScope2() const
|
||||
{
|
||||
LUAU_ASSERT(!scope2s.empty());
|
||||
return scope2s.front().second.get();
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -16,6 +16,7 @@ LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
|
|||
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineTableFix, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeFlagIsConservative, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineEqFix, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauReplaceReplacer, false);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -231,11 +232,30 @@ struct Replacer : Substitution
|
|||
|
||||
TypeId smartClone(TypeId t)
|
||||
{
|
||||
std::optional<TypeId> res = replace(t);
|
||||
LUAU_ASSERT(res.has_value()); // TODO think about this
|
||||
if (*res == t)
|
||||
return clone(t);
|
||||
return *res;
|
||||
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
|
||||
if (*res == t)
|
||||
return clone(t);
|
||||
return *res;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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(LuauAlwaysQuantify)
|
||||
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,14 +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
|
||||
{
|
||||
seenMutableType = true;
|
||||
|
||||
if (!level.subsumes(ftv.level))
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ftv.scope) : !level.subsumes(ftv.level))
|
||||
return false;
|
||||
|
||||
*asMutable(ty) = GenericTypeVar{level};
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
*asMutable(ty) = GenericTypeVar{scope};
|
||||
else
|
||||
*asMutable(ty) = GenericTypeVar{level};
|
||||
|
||||
generics.push_back(ty);
|
||||
|
||||
return false;
|
||||
|
@ -83,7 +110,7 @@ struct Quantifier final : TypeVarOnceVisitor
|
|||
|
||||
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 (ttv.state == TableState::Unsealed)
|
||||
seenMutableType = true;
|
||||
|
@ -107,7 +134,7 @@ struct Quantifier final : TypeVarOnceVisitor
|
|||
{
|
||||
seenMutableType = true;
|
||||
|
||||
if (!level.subsumes(ftp.level))
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ftp.scope) : !level.subsumes(ftp.level))
|
||||
return false;
|
||||
|
||||
*asMutable(tp) = GenericTypePack{level};
|
||||
|
@ -136,6 +163,32 @@ void quantify(TypeId ty, TypeLevel level)
|
|||
|
||||
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>
|
||||
|
@ -362,63 +363,7 @@ std::optional<TypePackId> Substitution::substitute(TypePackId tp)
|
|||
|
||||
TypeId Substitution::clone(TypeId ty)
|
||||
{
|
||||
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 = 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 = addType(std::move(clone));
|
||||
}
|
||||
else if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(ty))
|
||||
{
|
||||
MetatableTypeVar clone = MetatableTypeVar{mtv->table, mtv->metatable};
|
||||
clone.syntheticName = mtv->syntheticName;
|
||||
result = addType(std::move(clone));
|
||||
}
|
||||
else if (const UnionTypeVar* utv = get<UnionTypeVar>(ty))
|
||||
{
|
||||
UnionTypeVar clone;
|
||||
clone.options = utv->options;
|
||||
result = addType(std::move(clone));
|
||||
}
|
||||
else if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(ty))
|
||||
{
|
||||
IntersectionTypeVar clone;
|
||||
clone.parts = itv->parts;
|
||||
result = addType(std::move(clone));
|
||||
}
|
||||
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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -1196,65 +1195,38 @@ 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))
|
||||
{
|
||||
size_t idx = 0;
|
||||
while (argPackIter != end(ftv.argTypes))
|
||||
// ftv takes a self parameter as the first argument, skip it if specified in option
|
||||
if (idx == 0 && ftv.hasSelf && opts.hideFunctionSelfArgument)
|
||||
{
|
||||
// ftv takes a self parameter as the first argument, skip it if specified in option
|
||||
if (idx == 0 && ftv.hasSelf && opts.hideFunctionSelfArgument)
|
||||
{
|
||||
++argPackIter;
|
||||
++idx;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!first)
|
||||
state.emit(", ");
|
||||
first = false;
|
||||
|
||||
// We don't respect opts.functionTypeArguments
|
||||
if (idx < opts.namedFunctionOverrideArgNames.size())
|
||||
{
|
||||
state.emit(opts.namedFunctionOverrideArgNames[idx] + ": ");
|
||||
}
|
||||
else if (idx < ftv.argNames.size() && ftv.argNames[idx])
|
||||
{
|
||||
state.emit(ftv.argNames[idx]->name + ": ");
|
||||
}
|
||||
else
|
||||
{
|
||||
state.emit("_: ");
|
||||
}
|
||||
tvs.stringify(*argPackIter);
|
||||
|
||||
++argPackIter;
|
||||
++idx;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto argNameIter = ftv.argNames.begin();
|
||||
while (argPackIter != end(ftv.argTypes))
|
||||
|
||||
if (!first)
|
||||
state.emit(", ");
|
||||
first = false;
|
||||
|
||||
// We don't respect opts.functionTypeArguments
|
||||
if (idx < opts.namedFunctionOverrideArgNames.size())
|
||||
{
|
||||
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;
|
||||
state.emit(opts.namedFunctionOverrideArgNames[idx] + ": ");
|
||||
}
|
||||
else if (idx < ftv.argNames.size() && ftv.argNames[idx])
|
||||
{
|
||||
state.emit(ftv.argNames[idx]->name + ": ");
|
||||
}
|
||||
else
|
||||
{
|
||||
state.emit("_: ");
|
||||
}
|
||||
tvs.stringify(*argPackIter);
|
||||
|
||||
++argPackIter;
|
||||
++idx;
|
||||
}
|
||||
|
||||
if (argPackIter.tail())
|
||||
|
@ -1337,4 +1309,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
|
||||
|
|
|
@ -30,7 +30,6 @@ LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 300)
|
|||
LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
|
||||
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
||||
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDoNotRelyOnNextBinding, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauExpectedPropTypeFromIndexer, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
||||
|
@ -1182,12 +1181,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin)
|
|||
unify(varTy, var, forin.location);
|
||||
|
||||
if (!get<ErrorTypeVar>(iterTy) && !get<AnyTypeVar>(iterTy) && !get<FreeTypeVar>(iterTy))
|
||||
{
|
||||
if (FFlag::LuauDoNotRelyOnNextBinding)
|
||||
reportError(firstValue->location, CannotCallNonFunction{iterTy});
|
||||
else
|
||||
reportError(TypeError{firstValue->location, TypeMismatch{globalScope->bindings[AstName{"next"}].typeId, iterTy}});
|
||||
}
|
||||
reportError(firstValue->location, CannotCallNonFunction{iterTy});
|
||||
|
||||
return check(loopScope, *forin.body);
|
||||
}
|
||||
|
@ -3714,7 +3708,7 @@ ExprResult<TypePackId> TypeChecker::checkExprPack(const ScopePtr& scope, const A
|
|||
{
|
||||
retPack = freshTypePack(free->level);
|
||||
TypePackId freshArgPack = freshTypePack(free->level);
|
||||
*asMutable(actualFunctionType) = FunctionTypeVar(free->level, freshArgPack, retPack);
|
||||
asMutable(actualFunctionType)->ty.emplace<FunctionTypeVar>(free->level, freshArgPack, retPack);
|
||||
}
|
||||
else
|
||||
retPack = freshTypePack(scope->level);
|
||||
|
|
|
@ -24,7 +24,6 @@ LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
|
|||
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauSubtypingAddOptPropsToUnsealedTables)
|
||||
LUAU_FASTFLAGVARIABLE(LuauClassDefinitionModuleInError, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -302,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;
|
||||
|
@ -724,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)
|
||||
|
|
|
@ -65,9 +65,12 @@ target_sources(Luau.CodeGen PRIVATE
|
|||
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
|
||||
|
@ -108,8 +111,10 @@ 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
|
||||
|
@ -240,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
|
||||
|
@ -252,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
|
||||
|
|
|
@ -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";
|
||||
|
@ -488,11 +486,11 @@ void* lua_touserdata(lua_State* L, int idx)
|
|||
{
|
||||
StkId o = index2addr(L, idx);
|
||||
if (ttisuserdata(o))
|
||||
return uvalue(o)->data;
|
||||
return uvalue(o)->data;
|
||||
else if (ttislightuserdata(o))
|
||||
return pvalue(o);
|
||||
return pvalue(o);
|
||||
else
|
||||
return NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void* lua_touserdatatagged(lua_State* L, int idx, int tag)
|
||||
|
@ -1054,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;
|
||||
|
||||
|
@ -1064,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;
|
||||
|
@ -1078,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? */
|
||||
|
@ -1114,20 +1109,9 @@ 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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -213,7 +213,7 @@ CallInfo* luaD_growCI(lua_State* L)
|
|||
return ++L->ci;
|
||||
}
|
||||
|
||||
void luaD_checkCstack(lua_State *L)
|
||||
void luaD_checkCstack(lua_State* L)
|
||||
{
|
||||
if (L->nCcalls == LUAI_MAXCCALLS)
|
||||
luaG_runerror(L, "C stack overflow");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -4471,7 +4471,6 @@ end
|
|||
RETURN R0 0
|
||||
RETURN R0 0
|
||||
)");
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("LoopUnrollNestedClosure")
|
||||
|
|
|
@ -242,11 +242,14 @@ TEST_CASE("Math")
|
|||
TEST_CASE("Table")
|
||||
{
|
||||
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_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");
|
||||
});
|
||||
}
|
||||
|
@ -1150,129 +1153,171 @@ TEST_CASE("Userdata")
|
|||
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");
|
||||
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);
|
||||
const char* name = luaL_checkstring(L, 2);
|
||||
|
||||
if (strcmp(name, "value") == 0)
|
||||
{
|
||||
lua_pushnumber(L, double(*static_cast<int64_t*>(p)));
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(name, "value") == 0)
|
||||
{
|
||||
lua_pushnumber(L, double(*static_cast<int64_t*>(p)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
luaL_error(L, "unknown field %s", name);
|
||||
}, nullptr);
|
||||
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");
|
||||
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);
|
||||
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;
|
||||
}
|
||||
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);
|
||||
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_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_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_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_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_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_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_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_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_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_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_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_pushcfunction(
|
||||
L,
|
||||
[](lua_State* L) {
|
||||
double v = luaL_checknumber(L, 1);
|
||||
pushInt64(L, int64_t(v));
|
||||
return 1;
|
||||
},
|
||||
"int64");
|
||||
lua_setglobal(L, "int64");
|
||||
});
|
||||
}
|
||||
|
|
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();
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
static const char* mainModuleName = "MainModule";
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -249,7 +251,10 @@ std::optional<TypeId> Fixture::getType(const std::string& name)
|
|||
ModulePtr module = getMainModule();
|
||||
REQUIRE(module);
|
||||
|
||||
return lookupName(module->getModuleScope(), name);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return linearSearchForBinding(module->getModuleScope2(), name.c_str());
|
||||
else
|
||||
return lookupName(module->getModuleScope(), name);
|
||||
}
|
||||
|
||||
TypeId Fixture::requireType(const std::string& 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
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();
|
|
@ -505,7 +505,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
|
||||
)");
|
||||
|
@ -518,7 +517,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 = {}
|
||||
|
@ -537,7 +535,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...
|
||||
|
@ -554,7 +551,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));
|
||||
|
@ -562,7 +558,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, ...
|
||||
|
@ -577,7 +572,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
|
||||
|
@ -592,7 +586,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
|
||||
|
@ -607,7 +600,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
|
||||
)");
|
||||
|
@ -620,7 +612,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
|
||||
|
@ -636,8 +627,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
|
||||
)");
|
||||
|
@ -665,7 +654,6 @@ TEST_CASE_FIXTURE(Fixture, "pick_distinct_names_for_mixed_explicit_and_implicit_
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_include_self_param")
|
||||
{
|
||||
ScopedFastFlag flag{"LuauDocFuncParameters", true};
|
||||
CheckResult result = check(R"(
|
||||
local foo = {}
|
||||
function foo:method(arg: string): ()
|
||||
|
@ -682,7 +670,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): ()
|
||||
|
|
|
@ -470,8 +470,6 @@ caused by:
|
|||
|
||||
TEST_CASE_FIXTURE(ClassFixture, "class_type_mismatch_with_name_conflict")
|
||||
{
|
||||
ScopedFastFlag luauClassDefinitionModuleInError{"LuauClassDefinitionModuleInError", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local i = ChildClass.New()
|
||||
type ChildClass = { x: number }
|
||||
|
|
|
@ -78,8 +78,6 @@ TEST_CASE_FIXTURE(Fixture, "for_in_with_an_iterator_of_type_any")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "for_in_loop_should_fail_with_non_function_iterator")
|
||||
{
|
||||
ScopedFastFlag luauDoNotRelyOnNextBinding{"LuauDoNotRelyOnNextBinding", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local foo = "bar"
|
||||
for i, v in foo do
|
||||
|
|
|
@ -13,6 +13,25 @@ struct Foo
|
|||
int x = 42;
|
||||
};
|
||||
|
||||
struct Bar
|
||||
{
|
||||
explicit Bar(int x)
|
||||
: prop(x * 2)
|
||||
{
|
||||
++count;
|
||||
}
|
||||
|
||||
~Bar()
|
||||
{
|
||||
--count;
|
||||
}
|
||||
|
||||
int prop;
|
||||
static int count;
|
||||
};
|
||||
|
||||
int Bar::count = 0;
|
||||
|
||||
TEST_SUITE_BEGIN("Variant");
|
||||
|
||||
TEST_CASE("DefaultCtor")
|
||||
|
@ -46,6 +65,29 @@ TEST_CASE("Create")
|
|||
CHECK(get_if<Foo>(&v3)->x == 3);
|
||||
}
|
||||
|
||||
TEST_CASE("Emplace")
|
||||
{
|
||||
{
|
||||
Variant<int, Bar> v1;
|
||||
|
||||
CHECK(0 == Bar::count);
|
||||
int& i = v1.emplace<int>(5);
|
||||
CHECK(5 == i);
|
||||
|
||||
CHECK(0 == Bar::count);
|
||||
|
||||
CHECK(get_if<int>(&v1) == &i);
|
||||
|
||||
Bar& bar = v1.emplace<Bar>(11);
|
||||
CHECK(22 == bar.prop);
|
||||
CHECK(1 == Bar::count);
|
||||
|
||||
CHECK(get_if<Bar>(&v1) == &bar);
|
||||
}
|
||||
|
||||
CHECK(0 == Bar::count);
|
||||
}
|
||||
|
||||
TEST_CASE("NonPOD")
|
||||
{
|
||||
// initialize (copy)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
|
||||
|
||||
<Type Name="Luau::CodeGen::RegisterX64">
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::none && index == 31">noreg</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::none && index == 16">noreg</DisplayString>
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::none && index == 0">rip</DisplayString>
|
||||
|
||||
<DisplayString Condition="size == Luau::CodeGen::SizeX64::byte && index == 0">al</DisplayString>
|
||||
|
@ -36,14 +36,20 @@
|
|||
</Type>
|
||||
|
||||
<Type Name="Luau::CodeGen::OperandX64">
|
||||
<DisplayString Condition="cat == 0">{reg}</DisplayString>
|
||||
<DisplayString Condition="cat == 1">{mem.size,en} ptr[{mem.base} + {mem.index}*{(int)mem.scale,d} + {disp}]</DisplayString>
|
||||
<DisplayString Condition="cat == 0">{base}</DisplayString>
|
||||
<DisplayString Condition="cat == 1 && base.size != 0 && index.size != 0">{memSize,en} ptr[{base} + {index}*{(int)scale,d} + {imm}]</DisplayString>
|
||||
<DisplayString Condition="cat == 1 && index.size != 0">{memSize,en} ptr[{index}*{(int)scale,d} + {imm}]</DisplayString>
|
||||
<DisplayString Condition="cat == 1 && base.size != 0">{memSize,en} ptr[{base} + {imm}]</DisplayString>
|
||||
<DisplayString Condition="cat == 1">{memSize,en} ptr[{imm}]</DisplayString>
|
||||
<DisplayString Condition="cat == 2">{imm}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="cat == 0">reg</ExpandedItem>
|
||||
<ExpandedItem Condition="cat == 1">mem</ExpandedItem>
|
||||
<ExpandedItem Condition="cat == 0">base</ExpandedItem>
|
||||
<ExpandedItem Condition="cat == 2">imm</ExpandedItem>
|
||||
<Item Condition="cat == 1" Name="disp">disp</Item>
|
||||
<Item Condition="cat == 1" Name="memSize">memSize</Item>
|
||||
<Item Condition="cat == 1" Name="base">base</Item>
|
||||
<Item Condition="cat == 1" Name="index">index</Item>
|
||||
<Item Condition="cat == 1" Name="scale">scale</Item>
|
||||
<Item Condition="cat == 1" Name="imm">imm</Item>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
|
|
Loading…
Reference in a new issue