mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-12 13:00:38 +00:00
Sync to upstream/release/532 (#545)
This commit is contained in:
parent
948f678f93
commit
f1b46f4b96
63 changed files with 2186 additions and 737 deletions
82
Analysis/include/Luau/Constraint.h
Normal file
82
Analysis/include/Luau/Constraint.h
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Luau/Location.h"
|
||||||
|
#include "Luau/NotNull.h"
|
||||||
|
#include "Luau/Variant.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
struct Scope2;
|
||||||
|
struct TypeVar;
|
||||||
|
using TypeId = const TypeVar*;
|
||||||
|
|
||||||
|
struct TypePackVar;
|
||||||
|
using TypePackId = const TypePackVar*;
|
||||||
|
|
||||||
|
// subType <: superType
|
||||||
|
struct SubtypeConstraint
|
||||||
|
{
|
||||||
|
TypeId subType;
|
||||||
|
TypeId superType;
|
||||||
|
};
|
||||||
|
|
||||||
|
// subPack <: superPack
|
||||||
|
struct PackSubtypeConstraint
|
||||||
|
{
|
||||||
|
TypePackId subPack;
|
||||||
|
TypePackId superPack;
|
||||||
|
};
|
||||||
|
|
||||||
|
// subType ~ gen superType
|
||||||
|
struct GeneralizationConstraint
|
||||||
|
{
|
||||||
|
TypeId generalizedType;
|
||||||
|
TypeId sourceType;
|
||||||
|
Scope2* scope;
|
||||||
|
};
|
||||||
|
|
||||||
|
// subType ~ inst superType
|
||||||
|
struct InstantiationConstraint
|
||||||
|
{
|
||||||
|
TypeId subType;
|
||||||
|
TypeId superType;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint>;
|
||||||
|
using ConstraintPtr = std::unique_ptr<struct Constraint>;
|
||||||
|
|
||||||
|
struct Constraint
|
||||||
|
{
|
||||||
|
Constraint(ConstraintV&& c, Location location);
|
||||||
|
|
||||||
|
Constraint(const Constraint&) = delete;
|
||||||
|
Constraint& operator=(const Constraint&) = delete;
|
||||||
|
|
||||||
|
ConstraintV c;
|
||||||
|
Location location;
|
||||||
|
std::vector<NotNull<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));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Luau
|
|
@ -4,9 +4,12 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "Luau/Ast.h"
|
#include "Luau/Ast.h"
|
||||||
|
#include "Luau/Constraint.h"
|
||||||
#include "Luau/Module.h"
|
#include "Luau/Module.h"
|
||||||
|
#include "Luau/NotNull.h"
|
||||||
#include "Luau/Symbol.h"
|
#include "Luau/Symbol.h"
|
||||||
#include "Luau/TypeVar.h"
|
#include "Luau/TypeVar.h"
|
||||||
#include "Luau/Variant.h"
|
#include "Luau/Variant.h"
|
||||||
|
@ -14,69 +17,6 @@
|
||||||
namespace Luau
|
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
|
struct Scope2
|
||||||
{
|
{
|
||||||
// The parent scope of this scope. Null if there is no parent (i.e. this
|
// The parent scope of this scope. Null if there is no parent (i.e. this
|
||||||
|
@ -102,6 +42,11 @@ struct ConstraintGraphBuilder
|
||||||
TypeArena* const arena;
|
TypeArena* const arena;
|
||||||
// The root scope of the module we're generating constraints for.
|
// The root scope of the module we're generating constraints for.
|
||||||
Scope2* rootScope;
|
Scope2* rootScope;
|
||||||
|
// A mapping of AST node to TypeId.
|
||||||
|
DenseHashMap<const AstExpr*, TypeId> astTypes{nullptr};
|
||||||
|
// A mapping of AST node to TypePackId.
|
||||||
|
DenseHashMap<const AstExpr*, TypePackId> astTypePacks{nullptr};
|
||||||
|
DenseHashMap<const AstExpr*, TypeId> astOriginalCallTypes{nullptr};
|
||||||
|
|
||||||
explicit ConstraintGraphBuilder(TypeArena* arena);
|
explicit ConstraintGraphBuilder(TypeArena* arena);
|
||||||
|
|
||||||
|
@ -128,8 +73,9 @@ struct ConstraintGraphBuilder
|
||||||
* Adds a new constraint with no dependencies to a given scope.
|
* 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 scope the scope to add the constraint to. Must not be null.
|
||||||
* @param cv the constraint variant to add.
|
* @param cv the constraint variant to add.
|
||||||
|
* @param location the location to attribute to the constraint.
|
||||||
*/
|
*/
|
||||||
void addConstraint(Scope2* scope, ConstraintV cv);
|
void addConstraint(Scope2* scope, ConstraintV cv, Location location);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a constraint to a given scope.
|
* Adds a constraint to a given scope.
|
||||||
|
@ -148,15 +94,48 @@ struct ConstraintGraphBuilder
|
||||||
void visit(Scope2* scope, AstStat* stat);
|
void visit(Scope2* scope, AstStat* stat);
|
||||||
void visit(Scope2* scope, AstStatBlock* block);
|
void visit(Scope2* scope, AstStatBlock* block);
|
||||||
void visit(Scope2* scope, AstStatLocal* local);
|
void visit(Scope2* scope, AstStatLocal* local);
|
||||||
void visit(Scope2* scope, AstStatLocalFunction* local);
|
void visit(Scope2* scope, AstStatLocalFunction* function);
|
||||||
void visit(Scope2* scope, AstStatReturn* local);
|
void visit(Scope2* scope, AstStatFunction* function);
|
||||||
|
void visit(Scope2* scope, AstStatReturn* ret);
|
||||||
|
void visit(Scope2* scope, AstStatAssign* assign);
|
||||||
|
void visit(Scope2* scope, AstStatIf* ifStatement);
|
||||||
|
|
||||||
|
TypePackId checkExprList(Scope2* scope, const AstArray<AstExpr*>& exprs);
|
||||||
|
|
||||||
TypePackId checkPack(Scope2* scope, AstArray<AstExpr*> exprs);
|
TypePackId checkPack(Scope2* scope, AstArray<AstExpr*> exprs);
|
||||||
TypePackId checkPack(Scope2* scope, AstExpr* expr);
|
TypePackId checkPack(Scope2* scope, AstExpr* expr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks an expression that is expected to evaluate to one type.
|
||||||
|
* @param scope the scope the expression is contained within.
|
||||||
|
* @param expr the expression to check.
|
||||||
|
* @return the type of the expression.
|
||||||
|
*/
|
||||||
TypeId check(Scope2* scope, AstExpr* expr);
|
TypeId check(Scope2* scope, AstExpr* expr);
|
||||||
|
|
||||||
|
TypeId checkExprTable(Scope2* scope, AstExprTable* expr);
|
||||||
|
TypeId check(Scope2* scope, AstExprIndexName* indexName);
|
||||||
|
|
||||||
|
std::pair<TypeId, Scope2*> checkFunctionSignature(Scope2* parent, AstExprFunction* fn);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the body of a function expression.
|
||||||
|
* @param scope the interior scope of the body of the function.
|
||||||
|
* @param fn the function expression to check.
|
||||||
|
*/
|
||||||
|
void checkFunctionBody(Scope2* scope, AstExprFunction* fn);
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<const Constraint*> collectConstraints(Scope2* rootScope);
|
/**
|
||||||
|
* Collects a vector of borrowed constraints from the scope and all its child
|
||||||
|
* scopes. It is important to only call this function when you're done adding
|
||||||
|
* constraints to the scope or its descendants, lest the borrowed pointers
|
||||||
|
* become invalid due to a container reallocation.
|
||||||
|
* @param rootScope the root scope of the scope graph to collect constraints
|
||||||
|
* from.
|
||||||
|
* @return a list of pointers to constraints contained within the scope graph.
|
||||||
|
* None of these pointers should be null.
|
||||||
|
*/
|
||||||
|
std::vector<NotNull<Constraint>> collectConstraints(Scope2* rootScope);
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
|
|
||||||
#include "Luau/Error.h"
|
#include "Luau/Error.h"
|
||||||
#include "Luau/Variant.h"
|
#include "Luau/Variant.h"
|
||||||
#include "Luau/ConstraintGraphBuilder.h"
|
#include "Luau/Constraint.h"
|
||||||
|
#include "Luau/ConstraintSolverLogger.h"
|
||||||
#include "Luau/TypeVar.h"
|
#include "Luau/TypeVar.h"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -20,39 +21,81 @@ struct ConstraintSolver
|
||||||
{
|
{
|
||||||
TypeArena* arena;
|
TypeArena* arena;
|
||||||
InternalErrorReporter iceReporter;
|
InternalErrorReporter iceReporter;
|
||||||
// The entire set of constraints that the solver is trying to resolve.
|
// The entire set of constraints that the solver is trying to resolve. It
|
||||||
std::vector<const Constraint*> constraints;
|
// is important to not add elements to this vector, lest the underlying
|
||||||
|
// storage that we retain pointers to be mutated underneath us.
|
||||||
|
const std::vector<NotNull<Constraint>> constraints;
|
||||||
Scope2* rootScope;
|
Scope2* rootScope;
|
||||||
std::vector<TypeError> errors;
|
|
||||||
|
|
||||||
// This includes every constraint that has not been fully solved.
|
// This includes every constraint that has not been fully solved.
|
||||||
// A constraint can be both blocked and unsolved, for instance.
|
// A constraint can be both blocked and unsolved, for instance.
|
||||||
std::unordered_set<const Constraint*> unsolvedConstraints;
|
std::vector<NotNull<const Constraint>> unsolvedConstraints;
|
||||||
|
|
||||||
// A mapping of constraint pointer to how many things the constraint is
|
// 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
|
// blocked on. Can be empty or 0 for constraints that are not blocked on
|
||||||
// anything.
|
// anything.
|
||||||
std::unordered_map<const Constraint*, size_t> blockedConstraints;
|
std::unordered_map<NotNull<const Constraint>, size_t> blockedConstraints;
|
||||||
// A mapping of type/pack pointers to the constraints they block.
|
// A mapping of type/pack pointers to the constraints they block.
|
||||||
std::unordered_map<BlockedConstraintId, std::vector<const Constraint*>> blocked;
|
std::unordered_map<BlockedConstraintId, std::vector<NotNull<const Constraint>>> blocked;
|
||||||
|
|
||||||
|
ConstraintSolverLogger logger;
|
||||||
|
|
||||||
explicit ConstraintSolver(TypeArena* arena, Scope2* rootScope);
|
explicit ConstraintSolver(TypeArena* arena, Scope2* rootScope);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to dispatch all pending constraints and reach a type solution
|
* Attempts to dispatch all pending constraints and reach a type solution
|
||||||
* that satisfies all of the constraints, recording any errors that are
|
* that satisfies all of the constraints.
|
||||||
* encountered.
|
|
||||||
**/
|
**/
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
bool done();
|
bool done();
|
||||||
|
|
||||||
bool tryDispatch(const Constraint* c);
|
bool tryDispatch(NotNull<const Constraint> c, bool force);
|
||||||
bool tryDispatch(const SubtypeConstraint& c);
|
bool tryDispatch(const SubtypeConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
bool tryDispatch(const PackSubtypeConstraint& c);
|
bool tryDispatch(const PackSubtypeConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
bool tryDispatch(const GeneralizationConstraint& c);
|
bool tryDispatch(const GeneralizationConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
bool tryDispatch(const InstantiationConstraint& c, const Constraint* constraint);
|
bool tryDispatch(const InstantiationConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
|
|
||||||
|
void block(NotNull<const Constraint> target, NotNull<const Constraint> constraint);
|
||||||
|
/**
|
||||||
|
* Block a constraint on the resolution of a TypeVar.
|
||||||
|
* @returns false always. This is just to allow tryDispatch to return the result of block()
|
||||||
|
*/
|
||||||
|
bool block(TypeId target, NotNull<const Constraint> constraint);
|
||||||
|
bool block(TypePackId target, NotNull<const Constraint> constraint);
|
||||||
|
|
||||||
|
void unblock(NotNull<const Constraint> progressed);
|
||||||
|
void unblock(TypeId progressed);
|
||||||
|
void unblock(TypePackId progressed);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns true if the TypeId is in a blocked state.
|
||||||
|
*/
|
||||||
|
bool isBlocked(TypeId ty);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the constraint is blocked on anything.
|
||||||
|
* @param constraint the constraint to check.
|
||||||
|
*/
|
||||||
|
bool isBlocked(NotNull<const Constraint> constraint);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Unifier and performs a single unification operation. Commits
|
||||||
|
* the result.
|
||||||
|
* @param subType the sub-type to unify.
|
||||||
|
* @param superType the super-type to unify.
|
||||||
|
*/
|
||||||
|
void unify(TypeId subType, TypeId superType, Location location);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Unifier and performs a single unification operation. Commits
|
||||||
|
* the result.
|
||||||
|
* @param subPack the sub-type pack to unify.
|
||||||
|
* @param superPack the super-type pack to unify.
|
||||||
|
*/
|
||||||
|
void unify(TypePackId subPack, TypePackId superPack, Location location);
|
||||||
|
|
||||||
|
private:
|
||||||
/**
|
/**
|
||||||
* Marks a constraint as being blocked on a type or type pack. The 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
|
* solver will not attempt to dispatch blocked constraints until their
|
||||||
|
@ -60,10 +103,7 @@ struct ConstraintSolver
|
||||||
* @param target the type or type pack pointer that the constraint is blocked on.
|
* @param target the type or type pack pointer that the constraint is blocked on.
|
||||||
* @param constraint the constraint to block.
|
* @param constraint the constraint to block.
|
||||||
**/
|
**/
|
||||||
void block_(BlockedConstraintId target, const Constraint* constraint);
|
void block_(BlockedConstraintId target, NotNull<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
|
* Informs the solver that progress has been made on a type or type pack. The
|
||||||
|
@ -72,33 +112,6 @@ struct ConstraintSolver
|
||||||
* @param progressed the type or type pack pointer that has progressed.
|
* @param progressed the type or type pack pointer that has progressed.
|
||||||
**/
|
**/
|
||||||
void unblock_(BlockedConstraintId 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);
|
void dump(Scope2* rootScope, struct ToStringOptions& opts);
|
||||||
|
|
26
Analysis/include/Luau/ConstraintSolverLogger.h
Normal file
26
Analysis/include/Luau/ConstraintSolverLogger.h
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
|
||||||
|
#include "Luau/ConstraintGraphBuilder.h"
|
||||||
|
#include "Luau/ToString.h"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
struct ConstraintSolverLogger
|
||||||
|
{
|
||||||
|
std::string compileOutput();
|
||||||
|
void captureBoundarySnapshot(const Scope2* rootScope, std::vector<NotNull<const Constraint>>& unsolvedConstraints);
|
||||||
|
void prepareStepSnapshot(const Scope2* rootScope, NotNull<const Constraint> current, std::vector<NotNull<const Constraint>>& unsolvedConstraints);
|
||||||
|
void commitPreparedStepSnapshot();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::string> snapshots;
|
||||||
|
std::optional<std::string> preparedSnapshot;
|
||||||
|
ToStringOptions opts;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Luau
|
|
@ -66,7 +66,7 @@ struct SourceNode
|
||||||
}
|
}
|
||||||
|
|
||||||
ModuleName name;
|
ModuleName name;
|
||||||
std::unordered_set<ModuleName> requires;
|
std::unordered_set<ModuleName> requireSet;
|
||||||
std::vector<std::pair<ModuleName, Location>> requireLocations;
|
std::vector<std::pair<ModuleName, Location>> requireLocations;
|
||||||
bool dirtySourceModule = true;
|
bool dirtySourceModule = true;
|
||||||
bool dirtyModule = true;
|
bool dirtyModule = true;
|
||||||
|
@ -186,7 +186,7 @@ public:
|
||||||
|
|
||||||
std::unordered_map<ModuleName, SourceNode> sourceNodes;
|
std::unordered_map<ModuleName, SourceNode> sourceNodes;
|
||||||
std::unordered_map<ModuleName, SourceModule> sourceModules;
|
std::unordered_map<ModuleName, SourceModule> sourceModules;
|
||||||
std::unordered_map<ModuleName, RequireTraceResult> requires;
|
std::unordered_map<ModuleName, RequireTraceResult> requireTrace;
|
||||||
|
|
||||||
Stats stats = {};
|
Stats stats = {};
|
||||||
};
|
};
|
||||||
|
|
|
@ -69,6 +69,7 @@ struct Module
|
||||||
std::vector<std::pair<Location, std::unique_ptr<Scope2>>> scope2s; // never empty
|
std::vector<std::pair<Location, std::unique_ptr<Scope2>>> scope2s; // never empty
|
||||||
|
|
||||||
DenseHashMap<const AstExpr*, TypeId> astTypes{nullptr};
|
DenseHashMap<const AstExpr*, TypeId> astTypes{nullptr};
|
||||||
|
DenseHashMap<const AstExpr*, TypePackId> astTypePacks{nullptr};
|
||||||
DenseHashMap<const AstExpr*, TypeId> astExpectedTypes{nullptr};
|
DenseHashMap<const AstExpr*, TypeId> astExpectedTypes{nullptr};
|
||||||
DenseHashMap<const AstExpr*, TypeId> astOriginalCallTypes{nullptr};
|
DenseHashMap<const AstExpr*, TypeId> astOriginalCallTypes{nullptr};
|
||||||
DenseHashMap<const AstExpr*, TypeId> astOverloadResolvedTypes{nullptr};
|
DenseHashMap<const AstExpr*, TypeId> astOverloadResolvedTypes{nullptr};
|
||||||
|
|
|
@ -10,6 +10,7 @@ namespace Luau
|
||||||
struct InternalErrorReporter;
|
struct InternalErrorReporter;
|
||||||
|
|
||||||
bool isSubtype(TypeId superTy, TypeId subTy, InternalErrorReporter& ice);
|
bool isSubtype(TypeId superTy, TypeId subTy, InternalErrorReporter& ice);
|
||||||
|
bool isSubtype(TypePackId superTy, TypePackId subTy, InternalErrorReporter& ice);
|
||||||
|
|
||||||
std::pair<TypeId, bool> normalize(TypeId ty, TypeArena& arena, InternalErrorReporter& ice);
|
std::pair<TypeId, bool> normalize(TypeId ty, TypeArena& arena, InternalErrorReporter& ice);
|
||||||
std::pair<TypeId, bool> normalize(TypeId ty, const ModulePtr& module, InternalErrorReporter& ice);
|
std::pair<TypeId, bool> normalize(TypeId ty, const ModulePtr& module, InternalErrorReporter& ice);
|
||||||
|
|
|
@ -9,20 +9,22 @@ namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
/** A non-owning, non-null pointer to a T.
|
/** 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.
|
* A NotNull<T> is notionally identical to a T* with the added restriction that
|
||||||
|
* it can never store nullptr.
|
||||||
|
*
|
||||||
|
* The sole conversion rule from T* to NotNull<T> is the single-argument
|
||||||
|
* constructor, which is intentionally marked explicit. This constructor
|
||||||
|
* performs a runtime test to verify that the passed pointer is never nullptr.
|
||||||
|
*
|
||||||
|
* Pointer arithmetic, increment, decrement, and array indexing are all
|
||||||
|
* forbidden.
|
||||||
|
*
|
||||||
|
* An implicit coersion from NotNull<T> to T* is afforded, as are the pointer
|
||||||
|
* indirection and member access operators. (*p and p->prop)
|
||||||
|
*
|
||||||
|
* The explicit delete statement is permitted (but not recommended) on a
|
||||||
|
* NotNull<T> through this implicit conversion.
|
||||||
*/
|
*/
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct NotNull
|
struct NotNull
|
||||||
|
@ -36,6 +38,11 @@ struct NotNull
|
||||||
explicit NotNull(std::nullptr_t) = delete;
|
explicit NotNull(std::nullptr_t) = delete;
|
||||||
void operator=(std::nullptr_t) = delete;
|
void operator=(std::nullptr_t) = delete;
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
NotNull(NotNull<U> other)
|
||||||
|
: ptr(other.get())
|
||||||
|
{}
|
||||||
|
|
||||||
operator T*() const noexcept
|
operator T*() const noexcept
|
||||||
{
|
{
|
||||||
return ptr;
|
return ptr;
|
||||||
|
@ -56,6 +63,12 @@ struct NotNull
|
||||||
T& operator+(int) = delete;
|
T& operator+(int) = delete;
|
||||||
T& operator-(int) = delete;
|
T& operator-(int) = delete;
|
||||||
|
|
||||||
|
T* get() const noexcept
|
||||||
|
{
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
T* ptr;
|
T* ptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -68,7 +81,7 @@ template <typename T> struct hash<Luau::NotNull<T>>
|
||||||
{
|
{
|
||||||
size_t operator()(const Luau::NotNull<T>& p) const
|
size_t operator()(const Luau::NotNull<T>& p) const
|
||||||
{
|
{
|
||||||
return std::hash<T*>()(p.ptr);
|
return std::hash<T*>()(p.get());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,10 @@
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
struct TypeArena;
|
||||||
struct Scope2;
|
struct Scope2;
|
||||||
|
|
||||||
void quantify(TypeId ty, TypeLevel level);
|
void quantify(TypeId ty, TypeLevel level);
|
||||||
void quantify(TypeId ty, Scope2* scope);
|
TypeId quantify(TypeArena* arena, TypeId ty, Scope2* scope);
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -19,7 +19,7 @@ struct RequireTraceResult
|
||||||
{
|
{
|
||||||
DenseHashMap<const AstExpr*, ModuleInfo> exprs{nullptr};
|
DenseHashMap<const AstExpr*, ModuleInfo> exprs{nullptr};
|
||||||
|
|
||||||
std::vector<std::pair<ModuleName, Location>> requires;
|
std::vector<std::pair<ModuleName, Location>> requireList;
|
||||||
};
|
};
|
||||||
|
|
||||||
RequireTraceResult traceRequires(FileResolver* fileResolver, AstStatBlock* root, const ModuleName& currentModuleName);
|
RequireTraceResult traceRequires(FileResolver* fileResolver, AstStatBlock* root, const ModuleName& currentModuleName);
|
||||||
|
|
13
Analysis/include/Luau/TypeChecker2.h
Normal file
13
Analysis/include/Luau/TypeChecker2.h
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Luau/Ast.h"
|
||||||
|
#include "Luau/Module.h"
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
void check(const SourceModule& sourceModule, Module* module);
|
||||||
|
|
||||||
|
} // namespace Luau
|
|
@ -138,25 +138,25 @@ struct TypeChecker
|
||||||
void checkBlockWithoutRecursionCheck(const ScopePtr& scope, const AstStatBlock& statement);
|
void checkBlockWithoutRecursionCheck(const ScopePtr& scope, const AstStatBlock& statement);
|
||||||
void checkBlockTypeAliases(const ScopePtr& scope, std::vector<AstStat*>& sorted);
|
void checkBlockTypeAliases(const ScopePtr& scope, std::vector<AstStat*>& sorted);
|
||||||
|
|
||||||
ExprResult<TypeId> checkExpr(
|
WithPredicate<TypeId> checkExpr(
|
||||||
const ScopePtr& scope, const AstExpr& expr, std::optional<TypeId> expectedType = std::nullopt, bool forceSingleton = false);
|
const ScopePtr& scope, const AstExpr& expr, std::optional<TypeId> expectedType = std::nullopt, bool forceSingleton = false);
|
||||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprLocal& expr);
|
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprLocal& expr);
|
||||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprGlobal& expr);
|
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprGlobal& expr);
|
||||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprVarargs& expr);
|
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprVarargs& expr);
|
||||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprCall& expr);
|
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprCall& expr);
|
||||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprIndexName& expr);
|
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprIndexName& expr);
|
||||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprIndexExpr& expr);
|
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprIndexExpr& expr);
|
||||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprFunction& expr, std::optional<TypeId> expectedType = std::nullopt);
|
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprFunction& expr, std::optional<TypeId> expectedType = std::nullopt);
|
||||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprTable& expr, std::optional<TypeId> expectedType = std::nullopt);
|
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprTable& expr, std::optional<TypeId> expectedType = std::nullopt);
|
||||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprUnary& expr);
|
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprUnary& expr);
|
||||||
TypeId checkRelationalOperation(
|
TypeId checkRelationalOperation(
|
||||||
const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates = {});
|
const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates = {});
|
||||||
TypeId checkBinaryOperation(
|
TypeId checkBinaryOperation(
|
||||||
const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates = {});
|
const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates = {});
|
||||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprBinary& expr);
|
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprBinary& expr);
|
||||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprTypeAssertion& expr);
|
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprTypeAssertion& expr);
|
||||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprError& expr);
|
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprError& expr);
|
||||||
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprIfElse& expr, std::optional<TypeId> expectedType = std::nullopt);
|
WithPredicate<TypeId> checkExpr(const ScopePtr& scope, const AstExprIfElse& expr, std::optional<TypeId> expectedType = std::nullopt);
|
||||||
|
|
||||||
TypeId checkExprTable(const ScopePtr& scope, const AstExprTable& expr, const std::vector<std::pair<TypeId, TypeId>>& fieldTypes,
|
TypeId checkExprTable(const ScopePtr& scope, const AstExprTable& expr, const std::vector<std::pair<TypeId, TypeId>>& fieldTypes,
|
||||||
std::optional<TypeId> expectedType);
|
std::optional<TypeId> expectedType);
|
||||||
|
@ -179,11 +179,11 @@ struct TypeChecker
|
||||||
void checkArgumentList(
|
void checkArgumentList(
|
||||||
const ScopePtr& scope, Unifier& state, TypePackId paramPack, TypePackId argPack, const std::vector<Location>& argLocations);
|
const ScopePtr& scope, Unifier& state, TypePackId paramPack, TypePackId argPack, const std::vector<Location>& argLocations);
|
||||||
|
|
||||||
ExprResult<TypePackId> checkExprPack(const ScopePtr& scope, const AstExpr& expr);
|
WithPredicate<TypePackId> checkExprPack(const ScopePtr& scope, const AstExpr& expr);
|
||||||
ExprResult<TypePackId> checkExprPack(const ScopePtr& scope, const AstExprCall& expr);
|
WithPredicate<TypePackId> checkExprPack(const ScopePtr& scope, const AstExprCall& expr);
|
||||||
std::vector<std::optional<TypeId>> getExpectedTypesForCall(const std::vector<TypeId>& overloads, size_t argumentCount, bool selfCall);
|
std::vector<std::optional<TypeId>> getExpectedTypesForCall(const std::vector<TypeId>& overloads, size_t argumentCount, bool selfCall);
|
||||||
std::optional<ExprResult<TypePackId>> checkCallOverload(const ScopePtr& scope, const AstExprCall& expr, TypeId fn, TypePackId retPack,
|
std::optional<WithPredicate<TypePackId>> checkCallOverload(const ScopePtr& scope, const AstExprCall& expr, TypeId fn, TypePackId retPack,
|
||||||
TypePackId argPack, TypePack* args, const std::vector<Location>* argLocations, const ExprResult<TypePackId>& argListResult,
|
TypePackId argPack, TypePack* args, const std::vector<Location>* argLocations, const WithPredicate<TypePackId>& argListResult,
|
||||||
std::vector<TypeId>& overloadsThatMatchArgCount, std::vector<TypeId>& overloadsThatDont, std::vector<OverloadErrorEntry>& errors);
|
std::vector<TypeId>& overloadsThatMatchArgCount, std::vector<TypeId>& overloadsThatDont, std::vector<OverloadErrorEntry>& errors);
|
||||||
bool handleSelfCallMismatch(const ScopePtr& scope, const AstExprCall& expr, TypePack* args, const std::vector<Location>& argLocations,
|
bool handleSelfCallMismatch(const ScopePtr& scope, const AstExprCall& expr, TypePack* args, const std::vector<Location>& argLocations,
|
||||||
const std::vector<OverloadErrorEntry>& errors);
|
const std::vector<OverloadErrorEntry>& errors);
|
||||||
|
@ -191,7 +191,7 @@ struct TypeChecker
|
||||||
const std::vector<Location>& argLocations, const std::vector<TypeId>& overloads, const std::vector<TypeId>& overloadsThatMatchArgCount,
|
const std::vector<Location>& argLocations, const std::vector<TypeId>& overloads, const std::vector<TypeId>& overloadsThatMatchArgCount,
|
||||||
const std::vector<OverloadErrorEntry>& errors);
|
const std::vector<OverloadErrorEntry>& errors);
|
||||||
|
|
||||||
ExprResult<TypePackId> checkExprList(const ScopePtr& scope, const Location& location, const AstArray<AstExpr*>& exprs,
|
WithPredicate<TypePackId> checkExprList(const ScopePtr& scope, const Location& location, const AstArray<AstExpr*>& exprs,
|
||||||
bool substituteFreeForNil = false, const std::vector<bool>& lhsAnnotations = {},
|
bool substituteFreeForNil = false, const std::vector<bool>& lhsAnnotations = {},
|
||||||
const std::vector<std::optional<TypeId>>& expectedTypes = {});
|
const std::vector<std::optional<TypeId>>& expectedTypes = {});
|
||||||
|
|
||||||
|
@ -234,7 +234,7 @@ struct TypeChecker
|
||||||
ErrorVec canUnify(TypeId subTy, TypeId superTy, const Location& location);
|
ErrorVec canUnify(TypeId subTy, TypeId superTy, const Location& location);
|
||||||
ErrorVec canUnify(TypePackId subTy, TypePackId superTy, const Location& location);
|
ErrorVec canUnify(TypePackId subTy, TypePackId superTy, const Location& location);
|
||||||
|
|
||||||
void unifyLowerBound(TypePackId subTy, TypePackId superTy, const Location& location);
|
void unifyLowerBound(TypePackId subTy, TypePackId superTy, TypeLevel demotedLevel, const Location& location);
|
||||||
|
|
||||||
std::optional<TypeId> findMetatableEntry(TypeId type, std::string entry, const Location& location);
|
std::optional<TypeId> findMetatableEntry(TypeId type, std::string entry, const Location& location);
|
||||||
std::optional<TypeId> findTablePropertyRespectingMeta(TypeId lhsType, Name name, const Location& location);
|
std::optional<TypeId> findTablePropertyRespectingMeta(TypeId lhsType, Name name, const Location& location);
|
||||||
|
@ -412,7 +412,6 @@ public:
|
||||||
const TypeId booleanType;
|
const TypeId booleanType;
|
||||||
const TypeId threadType;
|
const TypeId threadType;
|
||||||
const TypeId anyType;
|
const TypeId anyType;
|
||||||
const TypeId optionalNumberType;
|
|
||||||
|
|
||||||
const TypePackId anyTypePack;
|
const TypePackId anyTypePack;
|
||||||
|
|
||||||
|
|
|
@ -84,6 +84,24 @@ using Tags = std::vector<std::string>;
|
||||||
|
|
||||||
using ModuleName = std::string;
|
using ModuleName = std::string;
|
||||||
|
|
||||||
|
/** A TypeVar that cannot be computed.
|
||||||
|
*
|
||||||
|
* BlockedTypeVars essentially serve as a way to encode partial ordering on the
|
||||||
|
* constraint graph. Until a BlockedTypeVar is unblocked by its owning
|
||||||
|
* constraint, nothing at all can be said about it. Constraints that need to
|
||||||
|
* process a BlockedTypeVar cannot be dispatched.
|
||||||
|
*
|
||||||
|
* Whenever a BlockedTypeVar is added to the graph, we also record a constraint
|
||||||
|
* that will eventually unblock it.
|
||||||
|
*/
|
||||||
|
struct BlockedTypeVar
|
||||||
|
{
|
||||||
|
BlockedTypeVar();
|
||||||
|
int index;
|
||||||
|
|
||||||
|
static int nextIndex;
|
||||||
|
};
|
||||||
|
|
||||||
struct PrimitiveTypeVar
|
struct PrimitiveTypeVar
|
||||||
{
|
{
|
||||||
enum Type
|
enum Type
|
||||||
|
@ -231,29 +249,29 @@ struct FunctionDefinition
|
||||||
// TODO: Do we actually need this? We'll find out later if we can delete this.
|
// TODO: Do we actually need this? We'll find out later if we can delete this.
|
||||||
// Does not exactly belong in TypeVar.h, but this is the only way to appease the compiler.
|
// Does not exactly belong in TypeVar.h, but this is the only way to appease the compiler.
|
||||||
template<typename T>
|
template<typename T>
|
||||||
struct ExprResult
|
struct WithPredicate
|
||||||
{
|
{
|
||||||
T type;
|
T type;
|
||||||
PredicateVec predicates;
|
PredicateVec predicates;
|
||||||
};
|
};
|
||||||
|
|
||||||
using MagicFunction = std::function<std::optional<ExprResult<TypePackId>>(
|
using MagicFunction = std::function<std::optional<WithPredicate<TypePackId>>(
|
||||||
struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, ExprResult<TypePackId>)>;
|
struct TypeChecker&, const std::shared_ptr<struct Scope>&, const class AstExprCall&, WithPredicate<TypePackId>)>;
|
||||||
|
|
||||||
struct FunctionTypeVar
|
struct FunctionTypeVar
|
||||||
{
|
{
|
||||||
// Global monomorphic function
|
// Global monomorphic function
|
||||||
FunctionTypeVar(TypePackId argTypes, TypePackId retType, std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
FunctionTypeVar(TypePackId argTypes, TypePackId retTypes, std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
||||||
|
|
||||||
// Global polymorphic function
|
// Global polymorphic function
|
||||||
FunctionTypeVar(std::vector<TypeId> generics, std::vector<TypePackId> genericPacks, TypePackId argTypes, TypePackId retType,
|
FunctionTypeVar(std::vector<TypeId> generics, std::vector<TypePackId> genericPacks, TypePackId argTypes, TypePackId retTypes,
|
||||||
std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
||||||
|
|
||||||
// Local monomorphic function
|
// Local monomorphic function
|
||||||
FunctionTypeVar(TypeLevel level, TypePackId argTypes, TypePackId retType, std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
FunctionTypeVar(TypeLevel level, TypePackId argTypes, TypePackId retTypes, std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
||||||
|
|
||||||
// Local polymorphic function
|
// Local polymorphic function
|
||||||
FunctionTypeVar(TypeLevel level, std::vector<TypeId> generics, std::vector<TypePackId> genericPacks, TypePackId argTypes, TypePackId retType,
|
FunctionTypeVar(TypeLevel level, std::vector<TypeId> generics, std::vector<TypePackId> genericPacks, TypePackId argTypes, TypePackId retTypes,
|
||||||
std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
||||||
|
|
||||||
TypeLevel level;
|
TypeLevel level;
|
||||||
|
@ -263,7 +281,7 @@ struct FunctionTypeVar
|
||||||
std::vector<TypePackId> genericPacks;
|
std::vector<TypePackId> genericPacks;
|
||||||
TypePackId argTypes;
|
TypePackId argTypes;
|
||||||
std::vector<std::optional<FunctionArgument>> argNames;
|
std::vector<std::optional<FunctionArgument>> argNames;
|
||||||
TypePackId retType;
|
TypePackId retTypes;
|
||||||
std::optional<FunctionDefinition> definition;
|
std::optional<FunctionDefinition> definition;
|
||||||
MagicFunction magicFunction = nullptr; // Function pointer, can be nullptr.
|
MagicFunction magicFunction = nullptr; // Function pointer, can be nullptr.
|
||||||
bool hasSelf;
|
bool hasSelf;
|
||||||
|
@ -442,7 +460,7 @@ struct LazyTypeVar
|
||||||
|
|
||||||
using ErrorTypeVar = Unifiable::Error;
|
using ErrorTypeVar = Unifiable::Error;
|
||||||
|
|
||||||
using TypeVariant = Unifiable::Variant<TypeId, PrimitiveTypeVar, ConstrainedTypeVar, SingletonTypeVar, FunctionTypeVar, TableTypeVar,
|
using TypeVariant = Unifiable::Variant<TypeId, PrimitiveTypeVar, ConstrainedTypeVar, BlockedTypeVar, SingletonTypeVar, FunctionTypeVar, TableTypeVar,
|
||||||
MetatableTypeVar, ClassTypeVar, AnyTypeVar, UnionTypeVar, IntersectionTypeVar, LazyTypeVar>;
|
MetatableTypeVar, ClassTypeVar, AnyTypeVar, UnionTypeVar, IntersectionTypeVar, LazyTypeVar>;
|
||||||
|
|
||||||
struct TypeVar final
|
struct TypeVar final
|
||||||
|
@ -555,7 +573,6 @@ struct SingletonTypes
|
||||||
const TypeId trueType;
|
const TypeId trueType;
|
||||||
const TypeId falseType;
|
const TypeId falseType;
|
||||||
const TypeId anyType;
|
const TypeId anyType;
|
||||||
const TypeId optionalNumberType;
|
|
||||||
|
|
||||||
const TypePackId anyTypePack;
|
const TypePackId anyTypePack;
|
||||||
|
|
||||||
|
|
|
@ -110,7 +110,7 @@ private:
|
||||||
void tryUnifyWithConstrainedSuperTypeVar(TypeId subTy, TypeId superTy);
|
void tryUnifyWithConstrainedSuperTypeVar(TypeId subTy, TypeId superTy);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void unifyLowerBound(TypePackId subTy, TypePackId superTy);
|
void unifyLowerBound(TypePackId subTy, TypePackId superTy, TypeLevel demotedLevel);
|
||||||
|
|
||||||
// Report an "infinite type error" if the type "needle" already occurs within "haystack"
|
// Report an "infinite type error" if the type "needle" already occurs within "haystack"
|
||||||
void occursCheck(TypeId needle, TypeId haystack);
|
void occursCheck(TypeId needle, TypeId haystack);
|
||||||
|
|
|
@ -209,7 +209,7 @@ struct GenericTypeVarVisitor
|
||||||
if (visit(ty, *ftv))
|
if (visit(ty, *ftv))
|
||||||
{
|
{
|
||||||
traverse(ftv->argTypes);
|
traverse(ftv->argTypes);
|
||||||
traverse(ftv->retType);
|
traverse(ftv->retTypes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauIfElseExprFixCompletionIssue, false);
|
|
||||||
LUAU_FASTFLAG(LuauSelfCallAutocompleteFix2)
|
LUAU_FASTFLAG(LuauSelfCallAutocompleteFix2)
|
||||||
|
|
||||||
static const std::unordered_set<std::string> kStatementStartingKeywords = {
|
static const std::unordered_set<std::string> kStatementStartingKeywords = {
|
||||||
|
@ -268,14 +267,14 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
|
||||||
auto checkFunctionType = [typeArena, &canUnify, &expectedType](const FunctionTypeVar* ftv) {
|
auto checkFunctionType = [typeArena, &canUnify, &expectedType](const FunctionTypeVar* ftv) {
|
||||||
if (FFlag::LuauSelfCallAutocompleteFix2)
|
if (FFlag::LuauSelfCallAutocompleteFix2)
|
||||||
{
|
{
|
||||||
if (std::optional<TypeId> firstRetTy = first(ftv->retType))
|
if (std::optional<TypeId> firstRetTy = first(ftv->retTypes))
|
||||||
return checkTypeMatch(typeArena, *firstRetTy, expectedType);
|
return checkTypeMatch(typeArena, *firstRetTy, expectedType);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto [retHead, retTail] = flatten(ftv->retType);
|
auto [retHead, retTail] = flatten(ftv->retTypes);
|
||||||
|
|
||||||
if (!retHead.empty() && canUnify(retHead.front(), expectedType))
|
if (!retHead.empty() && canUnify(retHead.front(), expectedType))
|
||||||
return true;
|
return true;
|
||||||
|
@ -454,7 +453,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
|
||||||
}
|
}
|
||||||
else if (auto indexFunction = get<FunctionTypeVar>(followed))
|
else if (auto indexFunction = get<FunctionTypeVar>(followed))
|
||||||
{
|
{
|
||||||
std::optional<TypeId> indexFunctionResult = first(indexFunction->retType);
|
std::optional<TypeId> indexFunctionResult = first(indexFunction->retTypes);
|
||||||
if (indexFunctionResult)
|
if (indexFunctionResult)
|
||||||
autocompleteProps(module, typeArena, rootTy, *indexFunctionResult, indexType, nodes, result, seen);
|
autocompleteProps(module, typeArena, rootTy, *indexFunctionResult, indexType, nodes, result, seen);
|
||||||
}
|
}
|
||||||
|
@ -493,7 +492,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
|
||||||
autocompleteProps(module, typeArena, rootTy, followed, indexType, nodes, result, seen);
|
autocompleteProps(module, typeArena, rootTy, followed, indexType, nodes, result, seen);
|
||||||
else if (auto indexFunction = get<FunctionTypeVar>(followed))
|
else if (auto indexFunction = get<FunctionTypeVar>(followed))
|
||||||
{
|
{
|
||||||
std::optional<TypeId> indexFunctionResult = first(indexFunction->retType);
|
std::optional<TypeId> indexFunctionResult = first(indexFunction->retTypes);
|
||||||
if (indexFunctionResult)
|
if (indexFunctionResult)
|
||||||
autocompleteProps(module, typeArena, rootTy, *indexFunctionResult, indexType, nodes, result, seen);
|
autocompleteProps(module, typeArena, rootTy, *indexFunctionResult, indexType, nodes, result, seen);
|
||||||
}
|
}
|
||||||
|
@ -742,7 +741,7 @@ static std::optional<TypeId> findTypeElementAt(AstType* astType, TypeId ty, Posi
|
||||||
if (auto element = findTypeElementAt(type->argTypes, ftv->argTypes, position))
|
if (auto element = findTypeElementAt(type->argTypes, ftv->argTypes, position))
|
||||||
return element;
|
return element;
|
||||||
|
|
||||||
if (auto element = findTypeElementAt(type->returnTypes, ftv->retType, position))
|
if (auto element = findTypeElementAt(type->returnTypes, ftv->retTypes, position))
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -958,7 +957,7 @@ AutocompleteEntryMap autocompleteTypeNames(const Module& module, Position positi
|
||||||
{
|
{
|
||||||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(follow(*it)))
|
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(follow(*it)))
|
||||||
{
|
{
|
||||||
if (auto ty = tryGetTypePackTypeAt(ftv->retType, tailPos))
|
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, tailPos))
|
||||||
inferredType = *ty;
|
inferredType = *ty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1050,7 +1049,7 @@ AutocompleteEntryMap autocompleteTypeNames(const Module& module, Position positi
|
||||||
{
|
{
|
||||||
if (const FunctionTypeVar* ftv = tryGetExpectedFunctionType(module, node))
|
if (const FunctionTypeVar* ftv = tryGetExpectedFunctionType(module, node))
|
||||||
{
|
{
|
||||||
if (auto ty = tryGetTypePackTypeAt(ftv->retType, i))
|
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, i))
|
||||||
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
|
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1067,7 +1066,7 @@ AutocompleteEntryMap autocompleteTypeNames(const Module& module, Position positi
|
||||||
{
|
{
|
||||||
if (const FunctionTypeVar* ftv = tryGetExpectedFunctionType(module, node))
|
if (const FunctionTypeVar* ftv = tryGetExpectedFunctionType(module, node))
|
||||||
{
|
{
|
||||||
if (auto ty = tryGetTypePackTypeAt(ftv->retType, ~0u))
|
if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, ~0u))
|
||||||
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
|
tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1266,7 +1265,7 @@ static bool autocompleteIfElseExpression(
|
||||||
if (!parent)
|
if (!parent)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (FFlag::LuauIfElseExprFixCompletionIssue && node->is<AstExprIfElse>())
|
if (node->is<AstExprIfElse>())
|
||||||
{
|
{
|
||||||
// Don't try to complete when the current node is an if-else expression (i.e. only try to complete when the node is a child of an if-else
|
// Don't try to complete when the current node is an if-else expression (i.e. only try to complete when the node is a child of an if-else
|
||||||
// expression.
|
// expression.
|
||||||
|
|
|
@ -19,16 +19,16 @@ LUAU_FASTFLAGVARIABLE(LuauSetMetaTableArgsCheck, false)
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
static std::optional<ExprResult<TypePackId>> magicFunctionSelect(
|
static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
|
||||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult);
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
|
||||||
static std::optional<ExprResult<TypePackId>> magicFunctionSetMetaTable(
|
static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
|
||||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult);
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
|
||||||
static std::optional<ExprResult<TypePackId>> magicFunctionAssert(
|
static std::optional<WithPredicate<TypePackId>> magicFunctionAssert(
|
||||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult);
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
|
||||||
static std::optional<ExprResult<TypePackId>> magicFunctionPack(
|
static std::optional<WithPredicate<TypePackId>> magicFunctionPack(
|
||||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult);
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
|
||||||
static std::optional<ExprResult<TypePackId>> magicFunctionRequire(
|
static std::optional<WithPredicate<TypePackId>> magicFunctionRequire(
|
||||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult);
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
|
||||||
|
|
||||||
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types)
|
TypeId makeUnion(TypeArena& arena, std::vector<TypeId>&& types)
|
||||||
{
|
{
|
||||||
|
@ -263,10 +263,10 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
|
||||||
attachMagicFunction(getGlobalBinding(typeChecker, "require"), magicFunctionRequire);
|
attachMagicFunction(getGlobalBinding(typeChecker, "require"), magicFunctionRequire);
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::optional<ExprResult<TypePackId>> magicFunctionSelect(
|
static std::optional<WithPredicate<TypePackId>> magicFunctionSelect(
|
||||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult)
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
||||||
{
|
{
|
||||||
auto [paramPack, _predicates] = exprResult;
|
auto [paramPack, _predicates] = withPredicate;
|
||||||
|
|
||||||
(void)scope;
|
(void)scope;
|
||||||
|
|
||||||
|
@ -287,10 +287,10 @@ static std::optional<ExprResult<TypePackId>> magicFunctionSelect(
|
||||||
if (size_t(offset) < v.size())
|
if (size_t(offset) < v.size())
|
||||||
{
|
{
|
||||||
std::vector<TypeId> result(v.begin() + offset, v.end());
|
std::vector<TypeId> result(v.begin() + offset, v.end());
|
||||||
return ExprResult<TypePackId>{typechecker.currentModule->internalTypes.addTypePack(TypePack{std::move(result), tail})};
|
return WithPredicate<TypePackId>{typechecker.currentModule->internalTypes.addTypePack(TypePack{std::move(result), tail})};
|
||||||
}
|
}
|
||||||
else if (tail)
|
else if (tail)
|
||||||
return ExprResult<TypePackId>{*tail};
|
return WithPredicate<TypePackId>{*tail};
|
||||||
}
|
}
|
||||||
|
|
||||||
typechecker.reportError(TypeError{arg1->location, GenericError{"bad argument #1 to select (index out of range)"}});
|
typechecker.reportError(TypeError{arg1->location, GenericError{"bad argument #1 to select (index out of range)"}});
|
||||||
|
@ -298,16 +298,16 @@ static std::optional<ExprResult<TypePackId>> magicFunctionSelect(
|
||||||
else if (AstExprConstantString* str = arg1->as<AstExprConstantString>())
|
else if (AstExprConstantString* str = arg1->as<AstExprConstantString>())
|
||||||
{
|
{
|
||||||
if (str->value.size == 1 && str->value.data[0] == '#')
|
if (str->value.size == 1 && str->value.data[0] == '#')
|
||||||
return ExprResult<TypePackId>{typechecker.currentModule->internalTypes.addTypePack({typechecker.numberType})};
|
return WithPredicate<TypePackId>{typechecker.currentModule->internalTypes.addTypePack({typechecker.numberType})};
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::optional<ExprResult<TypePackId>> magicFunctionSetMetaTable(
|
static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
|
||||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult)
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
||||||
{
|
{
|
||||||
auto [paramPack, _predicates] = exprResult;
|
auto [paramPack, _predicates] = withPredicate;
|
||||||
|
|
||||||
TypeArena& arena = typechecker.currentModule->internalTypes;
|
TypeArena& arena = typechecker.currentModule->internalTypes;
|
||||||
|
|
||||||
|
@ -343,7 +343,7 @@ static std::optional<ExprResult<TypePackId>> magicFunctionSetMetaTable(
|
||||||
|
|
||||||
if (FFlag::LuauSetMetaTableArgsCheck && expr.args.size < 1)
|
if (FFlag::LuauSetMetaTableArgsCheck && expr.args.size < 1)
|
||||||
{
|
{
|
||||||
return ExprResult<TypePackId>{};
|
return WithPredicate<TypePackId>{};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!FFlag::LuauSetMetaTableArgsCheck || !expr.self)
|
if (!FFlag::LuauSetMetaTableArgsCheck || !expr.self)
|
||||||
|
@ -356,7 +356,7 @@ static std::optional<ExprResult<TypePackId>> magicFunctionSetMetaTable(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ExprResult<TypePackId>{arena.addTypePack({mtTy})};
|
return WithPredicate<TypePackId>{arena.addTypePack({mtTy})};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (get<AnyTypeVar>(target) || get<ErrorTypeVar>(target) || isTableIntersection(target))
|
else if (get<AnyTypeVar>(target) || get<ErrorTypeVar>(target) || isTableIntersection(target))
|
||||||
|
@ -367,13 +367,13 @@ static std::optional<ExprResult<TypePackId>> magicFunctionSetMetaTable(
|
||||||
typechecker.reportError(TypeError{expr.location, GenericError{"setmetatable should take a table"}});
|
typechecker.reportError(TypeError{expr.location, GenericError{"setmetatable should take a table"}});
|
||||||
}
|
}
|
||||||
|
|
||||||
return ExprResult<TypePackId>{arena.addTypePack({target})};
|
return WithPredicate<TypePackId>{arena.addTypePack({target})};
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::optional<ExprResult<TypePackId>> magicFunctionAssert(
|
static std::optional<WithPredicate<TypePackId>> magicFunctionAssert(
|
||||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult)
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
||||||
{
|
{
|
||||||
auto [paramPack, predicates] = exprResult;
|
auto [paramPack, predicates] = withPredicate;
|
||||||
|
|
||||||
TypeArena& arena = typechecker.currentModule->internalTypes;
|
TypeArena& arena = typechecker.currentModule->internalTypes;
|
||||||
|
|
||||||
|
@ -382,7 +382,7 @@ static std::optional<ExprResult<TypePackId>> magicFunctionAssert(
|
||||||
{
|
{
|
||||||
std::optional<TypeId> fst = first(*tail);
|
std::optional<TypeId> fst = first(*tail);
|
||||||
if (!fst)
|
if (!fst)
|
||||||
return ExprResult<TypePackId>{paramPack};
|
return WithPredicate<TypePackId>{paramPack};
|
||||||
head.push_back(*fst);
|
head.push_back(*fst);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,13 +397,13 @@ static std::optional<ExprResult<TypePackId>> magicFunctionAssert(
|
||||||
head[0] = *newhead;
|
head[0] = *newhead;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ExprResult<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})};
|
return WithPredicate<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})};
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::optional<ExprResult<TypePackId>> magicFunctionPack(
|
static std::optional<WithPredicate<TypePackId>> magicFunctionPack(
|
||||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult)
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
||||||
{
|
{
|
||||||
auto [paramPack, _predicates] = exprResult;
|
auto [paramPack, _predicates] = withPredicate;
|
||||||
|
|
||||||
TypeArena& arena = typechecker.currentModule->internalTypes;
|
TypeArena& arena = typechecker.currentModule->internalTypes;
|
||||||
|
|
||||||
|
@ -436,7 +436,7 @@ static std::optional<ExprResult<TypePackId>> magicFunctionPack(
|
||||||
TypeId packedTable = arena.addType(
|
TypeId packedTable = arena.addType(
|
||||||
TableTypeVar{{{"n", {typechecker.numberType}}}, TableIndexer(typechecker.numberType, result), scope->level, TableState::Sealed});
|
TableTypeVar{{{"n", {typechecker.numberType}}}, TableIndexer(typechecker.numberType, result), scope->level, TableState::Sealed});
|
||||||
|
|
||||||
return ExprResult<TypePackId>{arena.addTypePack({packedTable})};
|
return WithPredicate<TypePackId>{arena.addTypePack({packedTable})};
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool checkRequirePath(TypeChecker& typechecker, AstExpr* expr)
|
static bool checkRequirePath(TypeChecker& typechecker, AstExpr* expr)
|
||||||
|
@ -461,8 +461,8 @@ static bool checkRequirePath(TypeChecker& typechecker, AstExpr* expr)
|
||||||
return good;
|
return good;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::optional<ExprResult<TypePackId>> magicFunctionRequire(
|
static std::optional<WithPredicate<TypePackId>> magicFunctionRequire(
|
||||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult)
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
||||||
{
|
{
|
||||||
TypeArena& arena = typechecker.currentModule->internalTypes;
|
TypeArena& arena = typechecker.currentModule->internalTypes;
|
||||||
|
|
||||||
|
@ -476,7 +476,7 @@ static std::optional<ExprResult<TypePackId>> magicFunctionRequire(
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
if (auto moduleInfo = typechecker.resolver->resolveModuleInfo(typechecker.currentModuleName, expr))
|
if (auto moduleInfo = typechecker.resolver->resolveModuleInfo(typechecker.currentModuleName, expr))
|
||||||
return ExprResult<TypePackId>{arena.addTypePack({typechecker.checkRequire(scope, *moduleInfo, expr.location)})};
|
return WithPredicate<TypePackId>{arena.addTypePack({typechecker.checkRequire(scope, *moduleInfo, expr.location)})};
|
||||||
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,7 @@ struct TypeCloner
|
||||||
void operator()(const Unifiable::Generic& t);
|
void operator()(const Unifiable::Generic& t);
|
||||||
void operator()(const Unifiable::Bound<TypeId>& t);
|
void operator()(const Unifiable::Bound<TypeId>& t);
|
||||||
void operator()(const Unifiable::Error& t);
|
void operator()(const Unifiable::Error& t);
|
||||||
|
void operator()(const BlockedTypeVar& t);
|
||||||
void operator()(const PrimitiveTypeVar& t);
|
void operator()(const PrimitiveTypeVar& t);
|
||||||
void operator()(const ConstrainedTypeVar& t);
|
void operator()(const ConstrainedTypeVar& t);
|
||||||
void operator()(const SingletonTypeVar& t);
|
void operator()(const SingletonTypeVar& t);
|
||||||
|
@ -158,6 +159,11 @@ void TypeCloner::operator()(const Unifiable::Error& t)
|
||||||
defaultClone(t);
|
defaultClone(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TypeCloner::operator()(const BlockedTypeVar& t)
|
||||||
|
{
|
||||||
|
defaultClone(t);
|
||||||
|
}
|
||||||
|
|
||||||
void TypeCloner::operator()(const PrimitiveTypeVar& t)
|
void TypeCloner::operator()(const PrimitiveTypeVar& t)
|
||||||
{
|
{
|
||||||
defaultClone(t);
|
defaultClone(t);
|
||||||
|
@ -200,7 +206,7 @@ void TypeCloner::operator()(const FunctionTypeVar& t)
|
||||||
ftv->tags = t.tags;
|
ftv->tags = t.tags;
|
||||||
ftv->argTypes = clone(t.argTypes, dest, cloneState);
|
ftv->argTypes = clone(t.argTypes, dest, cloneState);
|
||||||
ftv->argNames = t.argNames;
|
ftv->argNames = t.argNames;
|
||||||
ftv->retType = clone(t.retType, dest, cloneState);
|
ftv->retTypes = clone(t.retTypes, dest, cloneState);
|
||||||
ftv->hasNoGenerics = t.hasNoGenerics;
|
ftv->hasNoGenerics = t.hasNoGenerics;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -391,7 +397,7 @@ TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log)
|
||||||
|
|
||||||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
|
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
|
||||||
{
|
{
|
||||||
FunctionTypeVar clone = FunctionTypeVar{ftv->level, ftv->argTypes, ftv->retType, ftv->definition, ftv->hasSelf};
|
FunctionTypeVar clone = FunctionTypeVar{ftv->level, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf};
|
||||||
clone.generics = ftv->generics;
|
clone.generics = ftv->generics;
|
||||||
clone.genericPacks = ftv->genericPacks;
|
clone.genericPacks = ftv->genericPacks;
|
||||||
clone.magicFunction = ftv->magicFunction;
|
clone.magicFunction = ftv->magicFunction;
|
||||||
|
|
14
Analysis/src/Constraint.cpp
Normal file
14
Analysis/src/Constraint.cpp
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
|
||||||
|
#include "Luau/Constraint.h"
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
Constraint::Constraint(ConstraintV&& c, Location location)
|
||||||
|
: c(std::move(c))
|
||||||
|
, location(location)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Luau
|
|
@ -5,16 +5,7 @@
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
Constraint::Constraint(ConstraintV&& c)
|
const AstStat* getFallthrough(const AstStat* node); // TypeInfer.cpp
|
||||||
: 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)
|
std::optional<TypeId> Scope2::lookup(Symbol sym)
|
||||||
{
|
{
|
||||||
|
@ -68,10 +59,10 @@ Scope2* ConstraintGraphBuilder::childScope(Location location, Scope2* parent)
|
||||||
return borrow;
|
return borrow;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConstraintGraphBuilder::addConstraint(Scope2* scope, ConstraintV cv)
|
void ConstraintGraphBuilder::addConstraint(Scope2* scope, ConstraintV cv, Location location)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(scope);
|
LUAU_ASSERT(scope);
|
||||||
scope->constraints.emplace_back(new Constraint{std::move(cv)});
|
scope->constraints.emplace_back(new Constraint{std::move(cv), location});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConstraintGraphBuilder::addConstraint(Scope2* scope, std::unique_ptr<Constraint> c)
|
void ConstraintGraphBuilder::addConstraint(Scope2* scope, std::unique_ptr<Constraint> c)
|
||||||
|
@ -99,10 +90,18 @@ void ConstraintGraphBuilder::visit(Scope2* scope, AstStat* stat)
|
||||||
visit(scope, s);
|
visit(scope, s);
|
||||||
else if (auto s = stat->as<AstStatLocal>())
|
else if (auto s = stat->as<AstStatLocal>())
|
||||||
visit(scope, s);
|
visit(scope, s);
|
||||||
|
else if (auto f = stat->as<AstStatFunction>())
|
||||||
|
visit(scope, f);
|
||||||
else if (auto f = stat->as<AstStatLocalFunction>())
|
else if (auto f = stat->as<AstStatLocalFunction>())
|
||||||
visit(scope, f);
|
visit(scope, f);
|
||||||
else if (auto r = stat->as<AstStatReturn>())
|
else if (auto r = stat->as<AstStatReturn>())
|
||||||
visit(scope, r);
|
visit(scope, r);
|
||||||
|
else if (auto a = stat->as<AstStatAssign>())
|
||||||
|
visit(scope, a);
|
||||||
|
else if (auto e = stat->as<AstStatExpr>())
|
||||||
|
checkPack(scope, e->expr);
|
||||||
|
else if (auto i = stat->as<AstStatIf>())
|
||||||
|
visit(scope, i);
|
||||||
else
|
else
|
||||||
LUAU_ASSERT(0);
|
LUAU_ASSERT(0);
|
||||||
}
|
}
|
||||||
|
@ -121,12 +120,30 @@ void ConstraintGraphBuilder::visit(Scope2* scope, AstStatLocal* local)
|
||||||
scope->bindings[local] = ty;
|
scope->bindings[local] = ty;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < local->vars.size; ++i)
|
for (size_t i = 0; i < local->values.size; ++i)
|
||||||
{
|
{
|
||||||
if (i < local->values.size)
|
if (local->values.data[i]->is<AstExprConstantNil>())
|
||||||
|
{
|
||||||
|
// HACK: we leave nil-initialized things floating under the assumption that they will later be populated.
|
||||||
|
// See the test TypeInfer/infer_locals_with_nil_value.
|
||||||
|
// Better flow awareness should make this obsolete.
|
||||||
|
}
|
||||||
|
else if (i == local->values.size - 1)
|
||||||
|
{
|
||||||
|
TypePackId exprPack = checkPack(scope, local->values.data[i]);
|
||||||
|
|
||||||
|
if (i < local->vars.size)
|
||||||
|
{
|
||||||
|
std::vector<TypeId> tailValues{varTypes.begin() + i, varTypes.end()};
|
||||||
|
TypePackId tailPack = arena->addTypePack(std::move(tailValues));
|
||||||
|
addConstraint(scope, PackSubtypeConstraint{exprPack, tailPack}, local->location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
TypeId exprType = check(scope, local->values.data[i]);
|
TypeId exprType = check(scope, local->values.data[i]);
|
||||||
addConstraint(scope, SubtypeConstraint{varTypes[i], exprType});
|
if (i < varTypes.size())
|
||||||
|
addConstraint(scope, SubtypeConstraint{varTypes[i], exprType}, local->vars.data[i]->location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,7 +155,7 @@ void addConstraints(Constraint* constraint, Scope2* scope)
|
||||||
scope->constraints.reserve(scope->constraints.size() + scope->constraints.size());
|
scope->constraints.reserve(scope->constraints.size() + scope->constraints.size());
|
||||||
|
|
||||||
for (const auto& c : scope->constraints)
|
for (const auto& c : scope->constraints)
|
||||||
constraint->dependencies.push_back(c.get());
|
constraint->dependencies.push_back(NotNull{c.get()});
|
||||||
|
|
||||||
for (Scope2* childScope : scope->children)
|
for (Scope2* childScope : scope->children)
|
||||||
addConstraints(constraint, childScope);
|
addConstraints(constraint, childScope);
|
||||||
|
@ -155,31 +172,75 @@ void ConstraintGraphBuilder::visit(Scope2* scope, AstStatLocalFunction* function
|
||||||
|
|
||||||
TypeId functionType = nullptr;
|
TypeId functionType = nullptr;
|
||||||
auto ty = scope->lookup(function->name);
|
auto ty = scope->lookup(function->name);
|
||||||
LUAU_ASSERT(!ty.has_value()); // The parser ensures that every local function has a distinct Symbol for its name.
|
if (ty.has_value())
|
||||||
|
|
||||||
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);
|
// TODO: This is duplicate definition of a local function. Is this allowed?
|
||||||
argTypes.push_back(t);
|
functionType = *ty;
|
||||||
innerScope->bindings[local] = t; // TODO annotations
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
functionType = arena->addType(BlockedTypeVar{});
|
||||||
|
scope->bindings[function->name] = functionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (AstStat* stat : function->func->body->body)
|
auto [actualFunctionType, innerScope] = checkFunctionSignature(scope, function->func);
|
||||||
visit(innerScope, stat);
|
innerScope->bindings[function->name] = actualFunctionType;
|
||||||
|
|
||||||
FunctionTypeVar actualFunction{arena->addTypePack(argTypes), returnType};
|
checkFunctionBody(innerScope, function->func);
|
||||||
TypeId actualFunctionType = arena->addType(std::move(actualFunction));
|
|
||||||
|
|
||||||
std::unique_ptr<Constraint> c{new Constraint{GeneralizationConstraint{functionType, actualFunctionType, innerScope}}};
|
std::unique_ptr<Constraint> c{new Constraint{GeneralizationConstraint{functionType, actualFunctionType, innerScope}, function->location}};
|
||||||
|
addConstraints(c.get(), innerScope);
|
||||||
|
|
||||||
|
addConstraint(scope, std::move(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConstraintGraphBuilder::visit(Scope2* scope, AstStatFunction* function)
|
||||||
|
{
|
||||||
|
// Name could be AstStatLocal, AstStatGlobal, AstStatIndexName.
|
||||||
|
// With or without self
|
||||||
|
|
||||||
|
TypeId functionType = nullptr;
|
||||||
|
|
||||||
|
auto [actualFunctionType, innerScope] = checkFunctionSignature(scope, function->func);
|
||||||
|
|
||||||
|
if (AstExprLocal* localName = function->name->as<AstExprLocal>())
|
||||||
|
{
|
||||||
|
std::optional<TypeId> existingFunctionTy = scope->lookup(localName->local);
|
||||||
|
if (existingFunctionTy)
|
||||||
|
{
|
||||||
|
// Duplicate definition
|
||||||
|
functionType = *existingFunctionTy;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
functionType = arena->addType(BlockedTypeVar{});
|
||||||
|
scope->bindings[localName->local] = functionType;
|
||||||
|
}
|
||||||
|
innerScope->bindings[localName->local] = actualFunctionType;
|
||||||
|
}
|
||||||
|
else if (AstExprGlobal* globalName = function->name->as<AstExprGlobal>())
|
||||||
|
{
|
||||||
|
std::optional<TypeId> existingFunctionTy = scope->lookup(globalName->name);
|
||||||
|
if (existingFunctionTy)
|
||||||
|
{
|
||||||
|
// Duplicate definition
|
||||||
|
functionType = *existingFunctionTy;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
functionType = arena->addType(BlockedTypeVar{});
|
||||||
|
rootScope->bindings[globalName->name] = functionType;
|
||||||
|
}
|
||||||
|
innerScope->bindings[globalName->name] = actualFunctionType;
|
||||||
|
}
|
||||||
|
else if (AstExprIndexName* indexName = function->name->as<AstExprIndexName>())
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(0); // not yet implemented
|
||||||
|
}
|
||||||
|
|
||||||
|
checkFunctionBody(innerScope, function->func);
|
||||||
|
|
||||||
|
std::unique_ptr<Constraint> c{new Constraint{GeneralizationConstraint{functionType, actualFunctionType, innerScope}, function->location}};
|
||||||
addConstraints(c.get(), innerScope);
|
addConstraints(c.get(), innerScope);
|
||||||
|
|
||||||
addConstraint(scope, std::move(c));
|
addConstraint(scope, std::move(c));
|
||||||
|
@ -190,7 +251,7 @@ void ConstraintGraphBuilder::visit(Scope2* scope, AstStatReturn* ret)
|
||||||
LUAU_ASSERT(scope);
|
LUAU_ASSERT(scope);
|
||||||
|
|
||||||
TypePackId exprTypes = checkPack(scope, ret->list);
|
TypePackId exprTypes = checkPack(scope, ret->list);
|
||||||
addConstraint(scope, PackSubtypeConstraint{exprTypes, scope->returnType});
|
addConstraint(scope, PackSubtypeConstraint{exprTypes, scope->returnType}, ret->location);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConstraintGraphBuilder::visit(Scope2* scope, AstStatBlock* block)
|
void ConstraintGraphBuilder::visit(Scope2* scope, AstStatBlock* block)
|
||||||
|
@ -201,6 +262,28 @@ void ConstraintGraphBuilder::visit(Scope2* scope, AstStatBlock* block)
|
||||||
visit(scope, stat);
|
visit(scope, stat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConstraintGraphBuilder::visit(Scope2* scope, AstStatAssign* assign)
|
||||||
|
{
|
||||||
|
TypePackId varPackId = checkExprList(scope, assign->vars);
|
||||||
|
TypePackId valuePack = checkPack(scope, assign->values);
|
||||||
|
|
||||||
|
addConstraint(scope, PackSubtypeConstraint{valuePack, varPackId}, assign->location);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConstraintGraphBuilder::visit(Scope2* scope, AstStatIf* ifStatement)
|
||||||
|
{
|
||||||
|
check(scope, ifStatement->condition);
|
||||||
|
|
||||||
|
Scope2* thenScope = childScope(ifStatement->thenbody->location, scope);
|
||||||
|
visit(thenScope, ifStatement->thenbody);
|
||||||
|
|
||||||
|
if (ifStatement->elsebody)
|
||||||
|
{
|
||||||
|
Scope2* elseScope = childScope(ifStatement->elsebody->location, scope);
|
||||||
|
visit(elseScope, ifStatement->elsebody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TypePackId ConstraintGraphBuilder::checkPack(Scope2* scope, AstArray<AstExpr*> exprs)
|
TypePackId ConstraintGraphBuilder::checkPack(Scope2* scope, AstArray<AstExpr*> exprs)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(scope);
|
LUAU_ASSERT(scope);
|
||||||
|
@ -224,75 +307,256 @@ TypePackId ConstraintGraphBuilder::checkPack(Scope2* scope, AstArray<AstExpr*> e
|
||||||
return arena->addTypePack(TypePack{std::move(types), last});
|
return arena->addTypePack(TypePack{std::move(types), last});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TypePackId ConstraintGraphBuilder::checkExprList(Scope2* scope, const AstArray<AstExpr*>& exprs)
|
||||||
|
{
|
||||||
|
TypePackId result = arena->addTypePack({});
|
||||||
|
TypePack* resultPack = getMutable<TypePack>(result);
|
||||||
|
LUAU_ASSERT(resultPack);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < exprs.size; ++i)
|
||||||
|
{
|
||||||
|
AstExpr* expr = exprs.data[i];
|
||||||
|
if (i < exprs.size - 1)
|
||||||
|
resultPack->head.push_back(check(scope, expr));
|
||||||
|
else
|
||||||
|
resultPack->tail = checkPack(scope, expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultPack->head.empty() && resultPack->tail)
|
||||||
|
return *resultPack->tail;
|
||||||
|
else
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
TypePackId ConstraintGraphBuilder::checkPack(Scope2* scope, AstExpr* expr)
|
TypePackId ConstraintGraphBuilder::checkPack(Scope2* scope, AstExpr* expr)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(scope);
|
LUAU_ASSERT(scope);
|
||||||
|
|
||||||
// TEMP TEMP TEMP HACK HACK HACK FIXME FIXME
|
TypePackId result = nullptr;
|
||||||
TypeId t = check(scope, expr);
|
|
||||||
return arena->addTypePack({t});
|
if (AstExprCall* call = expr->as<AstExprCall>())
|
||||||
|
{
|
||||||
|
std::vector<TypeId> args;
|
||||||
|
|
||||||
|
for (AstExpr* arg : call->args)
|
||||||
|
{
|
||||||
|
args.push_back(check(scope, arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO self
|
||||||
|
|
||||||
|
TypeId fnType = check(scope, call->func);
|
||||||
|
|
||||||
|
astOriginalCallTypes[call->func] = fnType;
|
||||||
|
|
||||||
|
TypeId instantiatedType = freshType(scope);
|
||||||
|
addConstraint(scope, InstantiationConstraint{instantiatedType, fnType}, expr->location);
|
||||||
|
|
||||||
|
TypePackId rets = freshTypePack(scope);
|
||||||
|
FunctionTypeVar ftv(arena->addTypePack(TypePack{args, {}}), rets);
|
||||||
|
TypeId inferredFnType = arena->addType(ftv);
|
||||||
|
|
||||||
|
addConstraint(scope, SubtypeConstraint{inferredFnType, instantiatedType}, expr->location);
|
||||||
|
result = rets;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TypeId t = check(scope, expr);
|
||||||
|
result = arena->addTypePack({t});
|
||||||
|
}
|
||||||
|
|
||||||
|
LUAU_ASSERT(result);
|
||||||
|
astTypePacks[expr] = result;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeId ConstraintGraphBuilder::check(Scope2* scope, AstExpr* expr)
|
TypeId ConstraintGraphBuilder::check(Scope2* scope, AstExpr* expr)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(scope);
|
LUAU_ASSERT(scope);
|
||||||
|
|
||||||
if (auto a = expr->as<AstExprConstantString>())
|
TypeId result = nullptr;
|
||||||
return singletonTypes.stringType;
|
|
||||||
else if (auto a = expr->as<AstExprConstantNumber>())
|
if (auto group = expr->as<AstExprGroup>())
|
||||||
return singletonTypes.numberType;
|
result = check(scope, group->expr);
|
||||||
else if (auto a = expr->as<AstExprConstantBool>())
|
else if (expr->is<AstExprConstantString>())
|
||||||
return singletonTypes.booleanType;
|
result = singletonTypes.stringType;
|
||||||
else if (auto a = expr->as<AstExprConstantNil>())
|
else if (expr->is<AstExprConstantNumber>())
|
||||||
return singletonTypes.nilType;
|
result = singletonTypes.numberType;
|
||||||
|
else if (expr->is<AstExprConstantBool>())
|
||||||
|
result = singletonTypes.booleanType;
|
||||||
|
else if (expr->is<AstExprConstantNil>())
|
||||||
|
result = singletonTypes.nilType;
|
||||||
else if (auto a = expr->as<AstExprLocal>())
|
else if (auto a = expr->as<AstExprLocal>())
|
||||||
{
|
{
|
||||||
std::optional<TypeId> ty = scope->lookup(a->local);
|
std::optional<TypeId> ty = scope->lookup(a->local);
|
||||||
if (ty)
|
if (ty)
|
||||||
return *ty;
|
result = *ty;
|
||||||
else
|
else
|
||||||
return singletonTypes.errorRecoveryType(singletonTypes.anyType); // FIXME? Record an error at this point?
|
result = singletonTypes.errorRecoveryType(); // FIXME? Record an error at this point?
|
||||||
|
}
|
||||||
|
else if (auto g = expr->as<AstExprGlobal>())
|
||||||
|
{
|
||||||
|
std::optional<TypeId> ty = scope->lookup(g->name);
|
||||||
|
if (ty)
|
||||||
|
result = *ty;
|
||||||
|
else
|
||||||
|
result = singletonTypes.errorRecoveryType(); // FIXME? Record an error at this point?
|
||||||
}
|
}
|
||||||
else if (auto a = expr->as<AstExprCall>())
|
else if (auto a = expr->as<AstExprCall>())
|
||||||
{
|
{
|
||||||
std::vector<TypeId> args;
|
TypePackId packResult = checkPack(scope, expr);
|
||||||
|
if (auto f = first(packResult))
|
||||||
for (AstExpr* arg : a->args)
|
return *f;
|
||||||
|
else if (get<FreeTypePack>(packResult))
|
||||||
{
|
{
|
||||||
args.push_back(check(scope, arg));
|
TypeId typeResult = freshType(scope);
|
||||||
|
TypePack onePack{{typeResult}, freshTypePack(scope)};
|
||||||
|
TypePackId oneTypePack = arena->addTypePack(std::move(onePack));
|
||||||
|
|
||||||
|
addConstraint(scope, PackSubtypeConstraint{packResult, oneTypePack}, expr->location);
|
||||||
|
|
||||||
|
return typeResult;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
TypeId fnType = check(scope, a->func);
|
else if (auto a = expr->as<AstExprFunction>())
|
||||||
TypeId instantiatedType = freshType(scope);
|
{
|
||||||
addConstraint(scope, InstantiationConstraint{instantiatedType, fnType});
|
auto [fnType, functionScope] = checkFunctionSignature(scope, a);
|
||||||
|
checkFunctionBody(functionScope, a);
|
||||||
TypeId firstRet = freshType(scope);
|
return fnType;
|
||||||
TypePackId rets = arena->addTypePack(TypePack{{firstRet}, arena->addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}})});
|
}
|
||||||
FunctionTypeVar ftv(arena->addTypePack(TypePack{args, {}}), rets);
|
else if (auto indexName = expr->as<AstExprIndexName>())
|
||||||
TypeId inferredFnType = arena->addType(ftv);
|
{
|
||||||
|
result = check(scope, indexName);
|
||||||
addConstraint(scope, SubtypeConstraint{inferredFnType, instantiatedType});
|
}
|
||||||
return firstRet;
|
else if (auto table = expr->as<AstExprTable>())
|
||||||
|
{
|
||||||
|
result = checkExprTable(scope, table);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(0);
|
LUAU_ASSERT(0);
|
||||||
return freshType(scope);
|
result = freshType(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
LUAU_ASSERT(result);
|
||||||
|
astTypes[expr] = result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId ConstraintGraphBuilder::check(Scope2* scope, AstExprIndexName* indexName)
|
||||||
|
{
|
||||||
|
TypeId obj = check(scope, indexName->expr);
|
||||||
|
TypeId result = freshType(scope);
|
||||||
|
|
||||||
|
TableTypeVar::Props props{{indexName->index.value, Property{result}}};
|
||||||
|
const std::optional<TableIndexer> indexer;
|
||||||
|
TableTypeVar ttv{std::move(props), indexer, TypeLevel{}, TableState::Free};
|
||||||
|
|
||||||
|
TypeId expectedTableType = arena->addType(std::move(ttv));
|
||||||
|
|
||||||
|
addConstraint(scope, SubtypeConstraint{obj, expectedTableType}, indexName->location);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId ConstraintGraphBuilder::checkExprTable(Scope2* scope, AstExprTable* expr)
|
||||||
|
{
|
||||||
|
TypeId ty = arena->addType(TableTypeVar{});
|
||||||
|
TableTypeVar* ttv = getMutable<TableTypeVar>(ty);
|
||||||
|
LUAU_ASSERT(ttv);
|
||||||
|
|
||||||
|
auto createIndexer = [this, scope, ttv](
|
||||||
|
TypeId currentIndexType, TypeId currentResultType, Location itemLocation, std::optional<Location> keyLocation) {
|
||||||
|
if (!ttv->indexer)
|
||||||
|
{
|
||||||
|
TypeId indexType = this->freshType(scope);
|
||||||
|
TypeId resultType = this->freshType(scope);
|
||||||
|
ttv->indexer = TableIndexer{indexType, resultType};
|
||||||
|
}
|
||||||
|
|
||||||
|
addConstraint(scope, SubtypeConstraint{ttv->indexer->indexType, currentIndexType}, keyLocation ? *keyLocation : itemLocation);
|
||||||
|
addConstraint(scope, SubtypeConstraint{ttv->indexer->indexResultType, currentResultType}, itemLocation);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const AstExprTable::Item& item : expr->items)
|
||||||
|
{
|
||||||
|
TypeId itemTy = check(scope, item.value);
|
||||||
|
|
||||||
|
if (item.key)
|
||||||
|
{
|
||||||
|
// Even though we don't need to use the type of the item's key if
|
||||||
|
// it's a string constant, we still want to check it to populate
|
||||||
|
// astTypes.
|
||||||
|
TypeId keyTy = check(scope, item.key);
|
||||||
|
|
||||||
|
if (AstExprConstantString* key = item.key->as<AstExprConstantString>())
|
||||||
|
{
|
||||||
|
ttv->props[key->value.begin()] = {itemTy};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
createIndexer(keyTy, itemTy, item.value->location, item.key->location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TypeId numberType = singletonTypes.numberType;
|
||||||
|
createIndexer(numberType, itemTy, item.value->location, std::nullopt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ty;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<TypeId, Scope2*> ConstraintGraphBuilder::checkFunctionSignature(Scope2* parent, AstExprFunction* fn)
|
||||||
|
{
|
||||||
|
Scope2* innerScope = childScope(fn->body->location, parent);
|
||||||
|
TypePackId returnType = freshTypePack(innerScope);
|
||||||
|
innerScope->returnType = returnType;
|
||||||
|
|
||||||
|
std::vector<TypeId> argTypes;
|
||||||
|
|
||||||
|
for (AstLocal* local : fn->args)
|
||||||
|
{
|
||||||
|
TypeId t = freshType(innerScope);
|
||||||
|
argTypes.push_back(t);
|
||||||
|
innerScope->bindings[local] = t; // TODO annotations
|
||||||
|
}
|
||||||
|
|
||||||
|
FunctionTypeVar actualFunction{arena->addTypePack(argTypes), returnType};
|
||||||
|
TypeId actualFunctionType = arena->addType(std::move(actualFunction));
|
||||||
|
LUAU_ASSERT(actualFunctionType);
|
||||||
|
astTypes[fn] = actualFunctionType;
|
||||||
|
|
||||||
|
return {actualFunctionType, innerScope};
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConstraintGraphBuilder::checkFunctionBody(Scope2* scope, AstExprFunction* fn)
|
||||||
|
{
|
||||||
|
for (AstStat* stat : fn->body->body)
|
||||||
|
visit(scope, stat);
|
||||||
|
|
||||||
|
// If it is possible for execution to reach the end of the function, the return type must be compatible with ()
|
||||||
|
|
||||||
|
if (nullptr != getFallthrough(fn->body))
|
||||||
|
{
|
||||||
|
TypePackId empty = arena->addTypePack({}); // TODO we could have CSG retain one of these forever
|
||||||
|
addConstraint(scope, PackSubtypeConstraint{scope->returnType, empty}, fn->body->location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void collectConstraints(std::vector<const Constraint*>& result, Scope2* scope)
|
void collectConstraints(std::vector<NotNull<Constraint>>& result, Scope2* scope)
|
||||||
{
|
{
|
||||||
for (const auto& c : scope->constraints)
|
for (const auto& c : scope->constraints)
|
||||||
result.push_back(c.get());
|
result.push_back(NotNull{c.get()});
|
||||||
|
|
||||||
for (Scope2* child : scope->children)
|
for (Scope2* child : scope->children)
|
||||||
collectConstraints(result, child);
|
collectConstraints(result, child);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<const Constraint*> collectConstraints(Scope2* rootScope)
|
std::vector<NotNull<Constraint>> collectConstraints(Scope2* rootScope)
|
||||||
{
|
{
|
||||||
std::vector<const Constraint*> result;
|
std::vector<NotNull<Constraint>> result;
|
||||||
collectConstraints(result, rootScope);
|
collectConstraints(result, rootScope);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "Luau/Unifier.h"
|
#include "Luau/Unifier.h"
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false);
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false);
|
||||||
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false);
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -58,11 +59,11 @@ ConstraintSolver::ConstraintSolver(TypeArena* arena, Scope2* rootScope)
|
||||||
, constraints(collectConstraints(rootScope))
|
, constraints(collectConstraints(rootScope))
|
||||||
, rootScope(rootScope)
|
, rootScope(rootScope)
|
||||||
{
|
{
|
||||||
for (const Constraint* c : constraints)
|
for (NotNull<Constraint> c : constraints)
|
||||||
{
|
{
|
||||||
unsolvedConstraints.insert(c);
|
unsolvedConstraints.push_back(c);
|
||||||
|
|
||||||
for (const Constraint* dep : c->dependencies)
|
for (NotNull<const Constraint> dep : c->dependencies)
|
||||||
{
|
{
|
||||||
block(dep, c);
|
block(dep, c);
|
||||||
}
|
}
|
||||||
|
@ -74,8 +75,6 @@ void ConstraintSolver::run()
|
||||||
if (done())
|
if (done())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool progress = false;
|
|
||||||
|
|
||||||
ToStringOptions opts;
|
ToStringOptions opts;
|
||||||
|
|
||||||
if (FFlag::DebugLuauLogSolver)
|
if (FFlag::DebugLuauLogSolver)
|
||||||
|
@ -84,44 +83,80 @@ void ConstraintSolver::run()
|
||||||
dump(this, opts);
|
dump(this, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
do
|
if (FFlag::DebugLuauLogSolverToJson)
|
||||||
{
|
{
|
||||||
progress = false;
|
logger.captureBoundarySnapshot(rootScope, unsolvedConstraints);
|
||||||
|
}
|
||||||
|
|
||||||
auto it = begin(unsolvedConstraints);
|
auto runSolverPass = [&](bool force) {
|
||||||
auto endIt = end(unsolvedConstraints);
|
bool progress = false;
|
||||||
|
|
||||||
while (it != endIt)
|
size_t i = 0;
|
||||||
|
while (i < unsolvedConstraints.size())
|
||||||
{
|
{
|
||||||
if (isBlocked(*it))
|
NotNull<const Constraint> c = unsolvedConstraints[i];
|
||||||
|
if (!force && isBlocked(c))
|
||||||
{
|
{
|
||||||
++it;
|
++i;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string saveMe = FFlag::DebugLuauLogSolver ? toString(**it, opts) : std::string{};
|
std::string saveMe = FFlag::DebugLuauLogSolver ? toString(*c, opts) : std::string{};
|
||||||
|
|
||||||
bool success = tryDispatch(*it);
|
if (FFlag::DebugLuauLogSolverToJson)
|
||||||
progress = progress || success;
|
{
|
||||||
|
logger.prepareStepSnapshot(rootScope, c, unsolvedConstraints);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = tryDispatch(c, force);
|
||||||
|
|
||||||
|
progress |= success;
|
||||||
|
|
||||||
auto saveIt = it;
|
|
||||||
++it;
|
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
unsolvedConstraints.erase(saveIt);
|
unsolvedConstraints.erase(unsolvedConstraints.begin() + i);
|
||||||
|
|
||||||
|
if (FFlag::DebugLuauLogSolverToJson)
|
||||||
|
{
|
||||||
|
logger.commitPreparedStepSnapshot();
|
||||||
|
}
|
||||||
|
|
||||||
if (FFlag::DebugLuauLogSolver)
|
if (FFlag::DebugLuauLogSolver)
|
||||||
{
|
{
|
||||||
|
if (force)
|
||||||
|
printf("Force ");
|
||||||
printf("Dispatched\n\t%s\n", saveMe.c_str());
|
printf("Dispatched\n\t%s\n", saveMe.c_str());
|
||||||
dump(this, opts);
|
dump(this, opts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
++i;
|
||||||
|
|
||||||
|
if (force && success)
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return progress;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool progress = false;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
progress = runSolverPass(false);
|
||||||
|
if (!progress)
|
||||||
|
progress |= runSolverPass(true);
|
||||||
} while (progress);
|
} while (progress);
|
||||||
|
|
||||||
if (FFlag::DebugLuauLogSolver)
|
if (FFlag::DebugLuauLogSolver)
|
||||||
|
{
|
||||||
dumpBindings(rootScope, opts);
|
dumpBindings(rootScope, opts);
|
||||||
|
}
|
||||||
|
|
||||||
LUAU_ASSERT(done());
|
if (FFlag::DebugLuauLogSolverToJson)
|
||||||
|
{
|
||||||
|
logger.captureBoundarySnapshot(rootScope, unsolvedConstraints);
|
||||||
|
printf("Logger output:\n%s\n", logger.compileOutput().c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConstraintSolver::done()
|
bool ConstraintSolver::done()
|
||||||
|
@ -129,21 +164,21 @@ bool ConstraintSolver::done()
|
||||||
return unsolvedConstraints.empty();
|
return unsolvedConstraints.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConstraintSolver::tryDispatch(const Constraint* constraint)
|
bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool force)
|
||||||
{
|
{
|
||||||
if (isBlocked(constraint))
|
if (!force && isBlocked(constraint))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
|
||||||
if (auto sc = get<SubtypeConstraint>(*constraint))
|
if (auto sc = get<SubtypeConstraint>(*constraint))
|
||||||
success = tryDispatch(*sc);
|
success = tryDispatch(*sc, constraint, force);
|
||||||
else if (auto psc = get<PackSubtypeConstraint>(*constraint))
|
else if (auto psc = get<PackSubtypeConstraint>(*constraint))
|
||||||
success = tryDispatch(*psc);
|
success = tryDispatch(*psc, constraint, force);
|
||||||
else if (auto gc = get<GeneralizationConstraint>(*constraint))
|
else if (auto gc = get<GeneralizationConstraint>(*constraint))
|
||||||
success = tryDispatch(*gc);
|
success = tryDispatch(*gc, constraint, force);
|
||||||
else if (auto ic = get<InstantiationConstraint>(*constraint))
|
else if (auto ic = get<InstantiationConstraint>(*constraint))
|
||||||
success = tryDispatch(*ic, constraint);
|
success = tryDispatch(*ic, constraint, force);
|
||||||
else
|
else
|
||||||
LUAU_ASSERT(0);
|
LUAU_ASSERT(0);
|
||||||
|
|
||||||
|
@ -155,65 +190,66 @@ bool ConstraintSolver::tryDispatch(const Constraint* constraint)
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConstraintSolver::tryDispatch(const SubtypeConstraint& c)
|
bool ConstraintSolver::tryDispatch(const SubtypeConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||||
{
|
{
|
||||||
unify(c.subType, c.superType);
|
if (isBlocked(c.subType))
|
||||||
|
return block(c.subType, constraint);
|
||||||
|
else if (isBlocked(c.superType))
|
||||||
|
return block(c.superType, constraint);
|
||||||
|
|
||||||
|
unify(c.subType, c.superType, constraint->location);
|
||||||
|
|
||||||
unblock(c.subType);
|
unblock(c.subType);
|
||||||
unblock(c.superType);
|
unblock(c.superType);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConstraintSolver::tryDispatch(const PackSubtypeConstraint& c)
|
bool ConstraintSolver::tryDispatch(const PackSubtypeConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||||
{
|
{
|
||||||
unify(c.subPack, c.superPack);
|
unify(c.subPack, c.superPack, constraint->location);
|
||||||
unblock(c.subPack);
|
unblock(c.subPack);
|
||||||
unblock(c.superPack);
|
unblock(c.superPack);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& constraint)
|
bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||||
{
|
{
|
||||||
unify(constraint.subType, constraint.superType);
|
if (isBlocked(c.sourceType))
|
||||||
|
return block(c.sourceType, constraint);
|
||||||
|
|
||||||
quantify(constraint.superType, constraint.scope);
|
if (isBlocked(c.generalizedType))
|
||||||
unblock(constraint.subType);
|
asMutable(c.generalizedType)->ty.emplace<BoundTypeVar>(c.sourceType);
|
||||||
unblock(constraint.superType);
|
else
|
||||||
|
unify(c.generalizedType, c.sourceType, constraint->location);
|
||||||
|
|
||||||
|
TypeId generalized = quantify(arena, c.sourceType, c.scope);
|
||||||
|
*asMutable(c.sourceType) = *generalized;
|
||||||
|
|
||||||
|
unblock(c.generalizedType);
|
||||||
|
unblock(c.sourceType);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConstraintSolver::tryDispatch(const InstantiationConstraint& c, const Constraint* constraint)
|
bool ConstraintSolver::tryDispatch(const InstantiationConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||||
{
|
{
|
||||||
TypeId superType = follow(c.superType);
|
if (isBlocked(c.superType))
|
||||||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(superType))
|
return block(c.superType, constraint);
|
||||||
{
|
|
||||||
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{});
|
Instantiation inst(TxnLog::empty(), arena, TypeLevel{});
|
||||||
|
|
||||||
std::optional<TypeId> instantiated = inst.substitute(c.superType);
|
std::optional<TypeId> instantiated = inst.substitute(c.superType);
|
||||||
LUAU_ASSERT(instantiated); // TODO FIXME HANDLE THIS
|
LUAU_ASSERT(instantiated); // TODO FIXME HANDLE THIS
|
||||||
|
|
||||||
unify(c.subType, *instantiated);
|
unify(c.subType, *instantiated, constraint->location);
|
||||||
unblock(c.subType);
|
unblock(c.subType);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConstraintSolver::block_(BlockedConstraintId target, const Constraint* constraint)
|
void ConstraintSolver::block_(BlockedConstraintId target, NotNull<const Constraint> constraint)
|
||||||
{
|
{
|
||||||
blocked[target].push_back(constraint);
|
blocked[target].push_back(constraint);
|
||||||
|
|
||||||
|
@ -221,19 +257,21 @@ void ConstraintSolver::block_(BlockedConstraintId target, const Constraint* cons
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConstraintSolver::block(const Constraint* target, const Constraint* constraint)
|
void ConstraintSolver::block(NotNull<const Constraint> target, NotNull<const Constraint> constraint)
|
||||||
{
|
{
|
||||||
block_(target, constraint);
|
block_(target, constraint);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConstraintSolver::block(TypeId target, const Constraint* constraint)
|
bool ConstraintSolver::block(TypeId target, NotNull<const Constraint> constraint)
|
||||||
{
|
{
|
||||||
block_(target, constraint);
|
block_(target, constraint);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConstraintSolver::block(TypePackId target, const Constraint* constraint)
|
bool ConstraintSolver::block(TypePackId target, NotNull<const Constraint> constraint)
|
||||||
{
|
{
|
||||||
block_(target, constraint);
|
block_(target, constraint);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConstraintSolver::unblock_(BlockedConstraintId progressed)
|
void ConstraintSolver::unblock_(BlockedConstraintId progressed)
|
||||||
|
@ -243,7 +281,7 @@ void ConstraintSolver::unblock_(BlockedConstraintId progressed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// unblocked should contain a value always, because of the above check
|
// unblocked should contain a value always, because of the above check
|
||||||
for (const Constraint* unblockedConstraint : it->second)
|
for (NotNull<const Constraint> unblockedConstraint : it->second)
|
||||||
{
|
{
|
||||||
auto& count = blockedConstraints[unblockedConstraint];
|
auto& count = blockedConstraints[unblockedConstraint];
|
||||||
// This assertion being hit indicates that `blocked` and
|
// This assertion being hit indicates that `blocked` and
|
||||||
|
@ -257,7 +295,7 @@ void ConstraintSolver::unblock_(BlockedConstraintId progressed)
|
||||||
blocked.erase(it);
|
blocked.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConstraintSolver::unblock(const Constraint* progressed)
|
void ConstraintSolver::unblock(NotNull<const Constraint> progressed)
|
||||||
{
|
{
|
||||||
return unblock_(progressed);
|
return unblock_(progressed);
|
||||||
}
|
}
|
||||||
|
@ -272,35 +310,33 @@ void ConstraintSolver::unblock(TypePackId progressed)
|
||||||
return unblock_(progressed);
|
return unblock_(progressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConstraintSolver::isBlocked(const Constraint* constraint)
|
bool ConstraintSolver::isBlocked(TypeId ty)
|
||||||
|
{
|
||||||
|
return nullptr != get<BlockedTypeVar>(follow(ty));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConstraintSolver::isBlocked(NotNull<const Constraint> constraint)
|
||||||
{
|
{
|
||||||
auto blockedIt = blockedConstraints.find(constraint);
|
auto blockedIt = blockedConstraints.find(constraint);
|
||||||
return blockedIt != blockedConstraints.end() && blockedIt->second > 0;
|
return blockedIt != blockedConstraints.end() && blockedIt->second > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConstraintSolver::reportErrors(const std::vector<TypeError>& errors)
|
void ConstraintSolver::unify(TypeId subType, TypeId superType, Location location)
|
||||||
{
|
|
||||||
this->errors.insert(end(this->errors), begin(errors), end(errors));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConstraintSolver::unify(TypeId subType, TypeId superType)
|
|
||||||
{
|
{
|
||||||
UnifierSharedState sharedState{&iceReporter};
|
UnifierSharedState sharedState{&iceReporter};
|
||||||
Unifier u{arena, Mode::Strict, Location{}, Covariant, sharedState};
|
Unifier u{arena, Mode::Strict, location, Covariant, sharedState};
|
||||||
|
|
||||||
u.tryUnify(subType, superType);
|
u.tryUnify(subType, superType);
|
||||||
u.log.commit();
|
u.log.commit();
|
||||||
reportErrors(u.errors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConstraintSolver::unify(TypePackId subPack, TypePackId superPack)
|
void ConstraintSolver::unify(TypePackId subPack, TypePackId superPack, Location location)
|
||||||
{
|
{
|
||||||
UnifierSharedState sharedState{&iceReporter};
|
UnifierSharedState sharedState{&iceReporter};
|
||||||
Unifier u{arena, Mode::Strict, Location{}, Covariant, sharedState};
|
Unifier u{arena, Mode::Strict, location, Covariant, sharedState};
|
||||||
|
|
||||||
u.tryUnify(subPack, superPack);
|
u.tryUnify(subPack, superPack);
|
||||||
u.log.commit();
|
u.log.commit();
|
||||||
reportErrors(u.errors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
139
Analysis/src/ConstraintSolverLogger.cpp
Normal file
139
Analysis/src/ConstraintSolverLogger.cpp
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
|
||||||
|
#include "Luau/ConstraintSolverLogger.h"
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
static std::string dumpScopeAndChildren(const Scope2* scope, ToStringOptions& opts)
|
||||||
|
{
|
||||||
|
std::string output = "{\"bindings\":{";
|
||||||
|
|
||||||
|
bool comma = false;
|
||||||
|
for (const auto& [name, type] : scope->bindings)
|
||||||
|
{
|
||||||
|
if (comma)
|
||||||
|
output += ",";
|
||||||
|
|
||||||
|
output += "\"";
|
||||||
|
output += name.c_str();
|
||||||
|
output += "\": \"";
|
||||||
|
|
||||||
|
ToStringResult result = toStringDetailed(type, opts);
|
||||||
|
opts.nameMap = std::move(result.nameMap);
|
||||||
|
output += result.name;
|
||||||
|
output += "\"";
|
||||||
|
|
||||||
|
comma = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
output += "},\"children\":[";
|
||||||
|
comma = false;
|
||||||
|
|
||||||
|
for (const Scope2* child : scope->children)
|
||||||
|
{
|
||||||
|
if (comma)
|
||||||
|
output += ",";
|
||||||
|
|
||||||
|
output += dumpScopeAndChildren(child, opts);
|
||||||
|
comma = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
output += "]}";
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string dumpConstraintsToDot(std::vector<NotNull<const Constraint>>& constraints, ToStringOptions& opts)
|
||||||
|
{
|
||||||
|
std::string result = "digraph Constraints {\\n";
|
||||||
|
|
||||||
|
std::unordered_set<NotNull<const Constraint>> contained;
|
||||||
|
for (NotNull<const Constraint> c : constraints)
|
||||||
|
{
|
||||||
|
contained.insert(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (NotNull<const Constraint> c : constraints)
|
||||||
|
{
|
||||||
|
std::string id = std::to_string(reinterpret_cast<size_t>(c.get()));
|
||||||
|
result += id;
|
||||||
|
result += " [label=\\\"";
|
||||||
|
result += toString(*c, opts).c_str();
|
||||||
|
result += "\\\"];\\n";
|
||||||
|
|
||||||
|
for (NotNull<const Constraint> dep : c->dependencies)
|
||||||
|
{
|
||||||
|
if (contained.count(dep) == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
result += std::to_string(reinterpret_cast<size_t>(dep.get()));
|
||||||
|
result += " -> ";
|
||||||
|
result += id;
|
||||||
|
result += ";\\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result += "}";
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ConstraintSolverLogger::compileOutput()
|
||||||
|
{
|
||||||
|
std::string output = "[";
|
||||||
|
bool comma = false;
|
||||||
|
|
||||||
|
for (const std::string& snapshot : snapshots)
|
||||||
|
{
|
||||||
|
if (comma)
|
||||||
|
output += ",";
|
||||||
|
output += snapshot;
|
||||||
|
|
||||||
|
comma = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
output += "]";
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConstraintSolverLogger::captureBoundarySnapshot(const Scope2* rootScope, std::vector<NotNull<const Constraint>>& unsolvedConstraints)
|
||||||
|
{
|
||||||
|
std::string snapshot = "{\"type\":\"boundary\",\"rootScope\":";
|
||||||
|
|
||||||
|
snapshot += dumpScopeAndChildren(rootScope, opts);
|
||||||
|
snapshot += ",\"constraintGraph\":\"";
|
||||||
|
snapshot += dumpConstraintsToDot(unsolvedConstraints, opts);
|
||||||
|
snapshot += "\"}";
|
||||||
|
|
||||||
|
snapshots.push_back(std::move(snapshot));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConstraintSolverLogger::prepareStepSnapshot(
|
||||||
|
const Scope2* rootScope, NotNull<const Constraint> current, std::vector<NotNull<const Constraint>>& unsolvedConstraints)
|
||||||
|
{
|
||||||
|
// LUAU_ASSERT(!preparedSnapshot);
|
||||||
|
|
||||||
|
std::string snapshot = "{\"type\":\"step\",\"rootScope\":";
|
||||||
|
|
||||||
|
snapshot += dumpScopeAndChildren(rootScope, opts);
|
||||||
|
snapshot += ",\"constraintGraph\":\"";
|
||||||
|
snapshot += dumpConstraintsToDot(unsolvedConstraints, opts);
|
||||||
|
snapshot += "\",\"currentId\":\"";
|
||||||
|
snapshot += std::to_string(reinterpret_cast<size_t>(current.get()));
|
||||||
|
snapshot += "\",\"current\":\"";
|
||||||
|
snapshot += toString(*current, opts);
|
||||||
|
snapshot += "\"}";
|
||||||
|
|
||||||
|
preparedSnapshot = std::move(snapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConstraintSolverLogger::commitPreparedStepSnapshot()
|
||||||
|
{
|
||||||
|
if (preparedSnapshot)
|
||||||
|
{
|
||||||
|
snapshots.push_back(std::move(*preparedSnapshot));
|
||||||
|
preparedSnapshot = std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Luau
|
|
@ -1,16 +1,17 @@
|
||||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
#include "Luau/Frontend.h"
|
#include "Luau/Frontend.h"
|
||||||
|
|
||||||
#include "Luau/Common.h"
|
|
||||||
#include "Luau/Clone.h"
|
#include "Luau/Clone.h"
|
||||||
|
#include "Luau/Common.h"
|
||||||
#include "Luau/Config.h"
|
#include "Luau/Config.h"
|
||||||
#include "Luau/FileResolver.h"
|
|
||||||
#include "Luau/ConstraintGraphBuilder.h"
|
#include "Luau/ConstraintGraphBuilder.h"
|
||||||
#include "Luau/ConstraintSolver.h"
|
#include "Luau/ConstraintSolver.h"
|
||||||
|
#include "Luau/FileResolver.h"
|
||||||
#include "Luau/Parser.h"
|
#include "Luau/Parser.h"
|
||||||
#include "Luau/Scope.h"
|
#include "Luau/Scope.h"
|
||||||
#include "Luau/StringUtils.h"
|
#include "Luau/StringUtils.h"
|
||||||
#include "Luau/TimeTrace.h"
|
#include "Luau/TimeTrace.h"
|
||||||
|
#include "Luau/TypeChecker2.h"
|
||||||
#include "Luau/TypeInfer.h"
|
#include "Luau/TypeInfer.h"
|
||||||
#include "Luau/Variant.h"
|
#include "Luau/Variant.h"
|
||||||
|
|
||||||
|
@ -216,7 +217,7 @@ ErrorVec accumulateErrors(
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const SourceNode& sourceNode = it->second;
|
const SourceNode& sourceNode = it->second;
|
||||||
queue.insert(queue.end(), sourceNode.requires.begin(), sourceNode.requires.end());
|
queue.insert(queue.end(), sourceNode.requireSet.begin(), sourceNode.requireSet.end());
|
||||||
|
|
||||||
// FIXME: If a module has a syntax error, we won't be able to re-report it here.
|
// FIXME: If a module has a syntax error, we won't be able to re-report it here.
|
||||||
// The solution is probably to move errors from Module to SourceNode
|
// The solution is probably to move errors from Module to SourceNode
|
||||||
|
@ -586,7 +587,7 @@ bool Frontend::parseGraph(std::vector<ModuleName>& buildQueue, CheckResult& chec
|
||||||
path.push_back(top);
|
path.push_back(top);
|
||||||
|
|
||||||
// push children
|
// push children
|
||||||
for (const ModuleName& dep : top->requires)
|
for (const ModuleName& dep : top->requireSet)
|
||||||
{
|
{
|
||||||
auto it = sourceNodes.find(dep);
|
auto it = sourceNodes.find(dep);
|
||||||
if (it != sourceNodes.end())
|
if (it != sourceNodes.end())
|
||||||
|
@ -738,7 +739,7 @@ void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* marked
|
||||||
std::unordered_map<ModuleName, std::vector<ModuleName>> reverseDeps;
|
std::unordered_map<ModuleName, std::vector<ModuleName>> reverseDeps;
|
||||||
for (const auto& module : sourceNodes)
|
for (const auto& module : sourceNodes)
|
||||||
{
|
{
|
||||||
for (const auto& dep : module.second.requires)
|
for (const auto& dep : module.second.requireSet)
|
||||||
reverseDeps[dep].push_back(module.first);
|
reverseDeps[dep].push_back(module.first);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -797,9 +798,14 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, const Sco
|
||||||
cs.run();
|
cs.run();
|
||||||
|
|
||||||
result->scope2s = std::move(cgb.scopes);
|
result->scope2s = std::move(cgb.scopes);
|
||||||
|
result->astTypes = std::move(cgb.astTypes);
|
||||||
|
result->astTypePacks = std::move(cgb.astTypePacks);
|
||||||
|
result->astOriginalCallTypes = std::move(cgb.astOriginalCallTypes);
|
||||||
|
|
||||||
result->clonePublicInterface(iceHandler);
|
result->clonePublicInterface(iceHandler);
|
||||||
|
|
||||||
|
Luau::check(sourceModule, result.get());
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -841,8 +847,8 @@ std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(CheckResult& check
|
||||||
SourceModule result = parse(name, source->source, opts);
|
SourceModule result = parse(name, source->source, opts);
|
||||||
result.type = source->type;
|
result.type = source->type;
|
||||||
|
|
||||||
RequireTraceResult& requireTrace = requires[name];
|
RequireTraceResult& require = requireTrace[name];
|
||||||
requireTrace = traceRequires(fileResolver, result.root, name);
|
require = traceRequires(fileResolver, result.root, name);
|
||||||
|
|
||||||
SourceNode& sourceNode = sourceNodes[name];
|
SourceNode& sourceNode = sourceNodes[name];
|
||||||
SourceModule& sourceModule = sourceModules[name];
|
SourceModule& sourceModule = sourceModules[name];
|
||||||
|
@ -851,7 +857,7 @@ std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(CheckResult& check
|
||||||
sourceModule.environmentName = environmentName;
|
sourceModule.environmentName = environmentName;
|
||||||
|
|
||||||
sourceNode.name = name;
|
sourceNode.name = name;
|
||||||
sourceNode.requires.clear();
|
sourceNode.requireSet.clear();
|
||||||
sourceNode.requireLocations.clear();
|
sourceNode.requireLocations.clear();
|
||||||
sourceNode.dirtySourceModule = false;
|
sourceNode.dirtySourceModule = false;
|
||||||
|
|
||||||
|
@ -861,10 +867,10 @@ std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(CheckResult& check
|
||||||
sourceNode.dirtyModuleForAutocomplete = true;
|
sourceNode.dirtyModuleForAutocomplete = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& [moduleName, location] : requireTrace.requires)
|
for (const auto& [moduleName, location] : require.requireList)
|
||||||
sourceNode.requires.insert(moduleName);
|
sourceNode.requireSet.insert(moduleName);
|
||||||
|
|
||||||
sourceNode.requireLocations = requireTrace.requires;
|
sourceNode.requireLocations = require.requireList;
|
||||||
|
|
||||||
return {&sourceNode, &sourceModule};
|
return {&sourceNode, &sourceModule};
|
||||||
}
|
}
|
||||||
|
@ -925,8 +931,8 @@ SourceModule Frontend::parse(const ModuleName& name, std::string_view src, const
|
||||||
std::optional<ModuleInfo> FrontendModuleResolver::resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr)
|
std::optional<ModuleInfo> FrontendModuleResolver::resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr)
|
||||||
{
|
{
|
||||||
// FIXME I think this can be pushed into the FileResolver.
|
// FIXME I think this can be pushed into the FileResolver.
|
||||||
auto it = frontend->requires.find(currentModuleName);
|
auto it = frontend->requireTrace.find(currentModuleName);
|
||||||
if (it == frontend->requires.end())
|
if (it == frontend->requireTrace.end())
|
||||||
{
|
{
|
||||||
// CLI-43699
|
// CLI-43699
|
||||||
// If we can't find the current module name, that's because we bypassed the frontend's initializer
|
// If we can't find the current module name, that's because we bypassed the frontend's initializer
|
||||||
|
@ -1025,7 +1031,7 @@ void Frontend::clear()
|
||||||
sourceModules.clear();
|
sourceModules.clear();
|
||||||
moduleResolver.modules.clear();
|
moduleResolver.modules.clear();
|
||||||
moduleResolverForAutocomplete.modules.clear();
|
moduleResolverForAutocomplete.modules.clear();
|
||||||
requires.clear();
|
requireTrace.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -40,7 +40,7 @@ TypeId Instantiation::clean(TypeId ty)
|
||||||
const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty);
|
const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty);
|
||||||
LUAU_ASSERT(ftv);
|
LUAU_ASSERT(ftv);
|
||||||
|
|
||||||
FunctionTypeVar clone = FunctionTypeVar{level, ftv->argTypes, ftv->retType, ftv->definition, ftv->hasSelf};
|
FunctionTypeVar clone = FunctionTypeVar{level, ftv->argTypes, ftv->retTypes, ftv->definition, ftv->hasSelf};
|
||||||
clone.magicFunction = ftv->magicFunction;
|
clone.magicFunction = ftv->magicFunction;
|
||||||
clone.tags = ftv->tags;
|
clone.tags = ftv->tags;
|
||||||
clone.argNames = ftv->argNames;
|
clone.argNames = ftv->argNames;
|
||||||
|
|
|
@ -2282,7 +2282,7 @@ private:
|
||||||
size_t getReturnCount(TypeId ty)
|
size_t getReturnCount(TypeId ty)
|
||||||
{
|
{
|
||||||
if (auto ftv = get<FunctionTypeVar>(ty))
|
if (auto ftv = get<FunctionTypeVar>(ty))
|
||||||
return size(ftv->retType);
|
return size(ftv->retTypes);
|
||||||
|
|
||||||
if (auto itv = get<IntersectionTypeVar>(ty))
|
if (auto itv = get<IntersectionTypeVar>(ty))
|
||||||
{
|
{
|
||||||
|
@ -2291,7 +2291,7 @@ private:
|
||||||
|
|
||||||
for (TypeId part : itv->parts)
|
for (TypeId part : itv->parts)
|
||||||
if (auto ftv = get<FunctionTypeVar>(follow(part)))
|
if (auto ftv = get<FunctionTypeVar>(follow(part)))
|
||||||
result = std::max(result, size(ftv->retType));
|
result = std::max(result, size(ftv->retTypes));
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineTableFix, false);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeFlagIsConservative, false);
|
LUAU_FASTFLAGVARIABLE(LuauNormalizeFlagIsConservative, false);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineEqFix, false);
|
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineEqFix, false);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauReplaceReplacer, false);
|
LUAU_FASTFLAGVARIABLE(LuauReplaceReplacer, false);
|
||||||
|
LUAU_FASTFLAG(LuauQuantifyConstrained)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -273,6 +274,18 @@ bool isSubtype(TypeId subTy, TypeId superTy, InternalErrorReporter& ice)
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isSubtype(TypePackId subPack, TypePackId superPack, InternalErrorReporter& ice)
|
||||||
|
{
|
||||||
|
UnifierSharedState sharedState{&ice};
|
||||||
|
TypeArena arena;
|
||||||
|
Unifier u{&arena, Mode::Strict, Location{}, Covariant, sharedState};
|
||||||
|
u.anyIsTop = true;
|
||||||
|
|
||||||
|
u.tryUnify(subPack, superPack);
|
||||||
|
const bool ok = u.errors.empty() && u.log.empty();
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
static bool areNormal_(const T& t, const std::unordered_set<void*>& seen, InternalErrorReporter& ice)
|
static bool areNormal_(const T& t, const std::unordered_set<void*>& seen, InternalErrorReporter& ice)
|
||||||
{
|
{
|
||||||
|
@ -390,6 +403,7 @@ struct Normalize final : TypeVarVisitor
|
||||||
bool visit(TypeId ty, const ConstrainedTypeVar& ctvRef) override
|
bool visit(TypeId ty, const ConstrainedTypeVar& ctvRef) override
|
||||||
{
|
{
|
||||||
CHECK_ITERATION_LIMIT(false);
|
CHECK_ITERATION_LIMIT(false);
|
||||||
|
LUAU_ASSERT(!ty->normal);
|
||||||
|
|
||||||
ConstrainedTypeVar* ctv = const_cast<ConstrainedTypeVar*>(&ctvRef);
|
ConstrainedTypeVar* ctv = const_cast<ConstrainedTypeVar*>(&ctvRef);
|
||||||
|
|
||||||
|
@ -401,14 +415,21 @@ struct Normalize final : TypeVarVisitor
|
||||||
|
|
||||||
std::vector<TypeId> newParts = normalizeUnion(parts);
|
std::vector<TypeId> newParts = normalizeUnion(parts);
|
||||||
|
|
||||||
const bool normal = areNormal(newParts, seen, ice);
|
if (FFlag::LuauQuantifyConstrained)
|
||||||
|
{
|
||||||
if (newParts.size() == 1)
|
ctv->parts = std::move(newParts);
|
||||||
*asMutable(ty) = BoundTypeVar{newParts[0]};
|
}
|
||||||
else
|
else
|
||||||
*asMutable(ty) = UnionTypeVar{std::move(newParts)};
|
{
|
||||||
|
const bool normal = areNormal(newParts, seen, ice);
|
||||||
|
|
||||||
asMutable(ty)->normal = normal;
|
if (newParts.size() == 1)
|
||||||
|
*asMutable(ty) = BoundTypeVar{newParts[0]};
|
||||||
|
else
|
||||||
|
*asMutable(ty) = UnionTypeVar{std::move(newParts)};
|
||||||
|
|
||||||
|
asMutable(ty)->normal = normal;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -421,9 +442,9 @@ struct Normalize final : TypeVarVisitor
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
traverse(ftv.argTypes);
|
traverse(ftv.argTypes);
|
||||||
traverse(ftv.retType);
|
traverse(ftv.retTypes);
|
||||||
|
|
||||||
asMutable(ty)->normal = areNormal(ftv.argTypes, seen, ice) && areNormal(ftv.retType, seen, ice);
|
asMutable(ty)->normal = areNormal(ftv.argTypes, seen, ice) && areNormal(ftv.retTypes, seen, ice);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -465,7 +486,14 @@ struct Normalize final : TypeVarVisitor
|
||||||
checkNormal(ttv.indexer->indexResultType);
|
checkNormal(ttv.indexer->indexResultType);
|
||||||
}
|
}
|
||||||
|
|
||||||
asMutable(ty)->normal = normal;
|
// An unsealed table can never be normal, ditto for free tables iff the type it is bound to is also not normal.
|
||||||
|
if (FFlag::LuauQuantifyConstrained)
|
||||||
|
{
|
||||||
|
if (ttv.state == TableState::Generic || ttv.state == TableState::Sealed || (ttv.state == TableState::Free && follow(ty)->normal))
|
||||||
|
asMutable(ty)->normal = normal;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
asMutable(ty)->normal = normal;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,32 @@
|
||||||
|
|
||||||
#include "Luau/Quantify.h"
|
#include "Luau/Quantify.h"
|
||||||
|
|
||||||
|
#include "Luau/ConstraintGraphBuilder.h" // TODO for Scope2; move to separate header
|
||||||
|
#include "Luau/TxnLog.h"
|
||||||
|
#include "Luau/Substitution.h"
|
||||||
#include "Luau/VisitTypeVar.h"
|
#include "Luau/VisitTypeVar.h"
|
||||||
#include "Luau/ConstraintGraphBuilder.h" // TODO for Scope2; move to separate header
|
#include "Luau/ConstraintGraphBuilder.h" // TODO for Scope2; move to separate header
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauAlwaysQuantify);
|
LUAU_FASTFLAG(LuauAlwaysQuantify);
|
||||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauQuantifyConstrained, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/// @return true if outer encloses inner
|
||||||
|
static bool subsumes(Scope2* outer, Scope2* inner)
|
||||||
|
{
|
||||||
|
while (inner)
|
||||||
|
{
|
||||||
|
if (inner == outer)
|
||||||
|
return true;
|
||||||
|
inner = inner->parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
struct Quantifier final : TypeVarOnceVisitor
|
struct Quantifier final : TypeVarOnceVisitor
|
||||||
{
|
{
|
||||||
TypeLevel level;
|
TypeLevel level;
|
||||||
|
@ -62,6 +79,34 @@ struct Quantifier final : TypeVarOnceVisitor
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool visit(TypeId ty, const ConstrainedTypeVar&) override
|
||||||
|
{
|
||||||
|
if (FFlag::LuauQuantifyConstrained)
|
||||||
|
{
|
||||||
|
ConstrainedTypeVar* ctv = getMutable<ConstrainedTypeVar>(ty);
|
||||||
|
|
||||||
|
seenMutableType = true;
|
||||||
|
|
||||||
|
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ctv->scope) : !level.subsumes(ctv->level))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::vector<TypeId> opts = std::move(ctv->parts);
|
||||||
|
|
||||||
|
// We might transmute, so it's not safe to rely on the builtin traversal logic
|
||||||
|
for (TypeId opt : opts)
|
||||||
|
traverse(opt);
|
||||||
|
|
||||||
|
if (opts.size() == 1)
|
||||||
|
*asMutable(ty) = BoundTypeVar{opts[0]};
|
||||||
|
else
|
||||||
|
*asMutable(ty) = UnionTypeVar{std::move(opts)};
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool visit(TypeId ty, const TableTypeVar&) override
|
bool visit(TypeId ty, const TableTypeVar&) override
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(getMutable<TableTypeVar>(ty));
|
LUAU_ASSERT(getMutable<TableTypeVar>(ty));
|
||||||
|
@ -73,8 +118,12 @@ struct Quantifier final : TypeVarOnceVisitor
|
||||||
if (ttv.state == TableState::Free)
|
if (ttv.state == TableState::Free)
|
||||||
seenMutableType = true;
|
seenMutableType = true;
|
||||||
|
|
||||||
if (ttv.state == TableState::Sealed || ttv.state == TableState::Generic)
|
if (!FFlag::LuauQuantifyConstrained)
|
||||||
return false;
|
{
|
||||||
|
if (ttv.state == TableState::Sealed || ttv.state == TableState::Generic)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ttv.scope) : !level.subsumes(ttv.level))
|
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ttv.scope) : !level.subsumes(ttv.level))
|
||||||
{
|
{
|
||||||
if (ttv.state == TableState::Unsealed)
|
if (ttv.state == TableState::Unsealed)
|
||||||
|
@ -156,4 +205,104 @@ void quantify(TypeId ty, Scope2* scope)
|
||||||
ftv->generalized = true;
|
ftv->generalized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct PureQuantifier : Substitution
|
||||||
|
{
|
||||||
|
Scope2* scope;
|
||||||
|
std::vector<TypeId> insertedGenerics;
|
||||||
|
std::vector<TypePackId> insertedGenericPacks;
|
||||||
|
|
||||||
|
PureQuantifier(const TxnLog* log, TypeArena* arena, Scope2* scope)
|
||||||
|
: Substitution(log, arena)
|
||||||
|
, scope(scope)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isDirty(TypeId ty) override
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(ty == follow(ty));
|
||||||
|
|
||||||
|
if (auto ftv = get<FreeTypeVar>(ty))
|
||||||
|
{
|
||||||
|
return subsumes(scope, ftv->scope);
|
||||||
|
}
|
||||||
|
else if (auto ttv = get<TableTypeVar>(ty))
|
||||||
|
{
|
||||||
|
return ttv->state == TableState::Free && subsumes(scope, ttv->scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isDirty(TypePackId tp) override
|
||||||
|
{
|
||||||
|
if (auto ftp = get<FreeTypePack>(tp))
|
||||||
|
{
|
||||||
|
return subsumes(scope, ftp->scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId clean(TypeId ty) override
|
||||||
|
{
|
||||||
|
if (auto ftv = get<FreeTypeVar>(ty))
|
||||||
|
{
|
||||||
|
TypeId result = arena->addType(GenericTypeVar{});
|
||||||
|
insertedGenerics.push_back(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else if (auto ttv = get<TableTypeVar>(ty))
|
||||||
|
{
|
||||||
|
TypeId result = arena->addType(TableTypeVar{});
|
||||||
|
TableTypeVar* resultTable = getMutable<TableTypeVar>(result);
|
||||||
|
LUAU_ASSERT(resultTable);
|
||||||
|
|
||||||
|
*resultTable = *ttv;
|
||||||
|
resultTable->scope = nullptr;
|
||||||
|
resultTable->state = TableState::Generic;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ty;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypePackId clean(TypePackId tp) override
|
||||||
|
{
|
||||||
|
if (auto ftp = get<FreeTypePack>(tp))
|
||||||
|
{
|
||||||
|
TypePackId result = arena->addTypePack(TypePackVar{GenericTypePack{}});
|
||||||
|
insertedGenericPacks.push_back(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tp;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ignoreChildren(TypeId ty) override
|
||||||
|
{
|
||||||
|
return ty->persistent;
|
||||||
|
}
|
||||||
|
bool ignoreChildren(TypePackId ty) override
|
||||||
|
{
|
||||||
|
return ty->persistent;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TypeId quantify(TypeArena* arena, TypeId ty, Scope2* scope)
|
||||||
|
{
|
||||||
|
PureQuantifier quantifier{TxnLog::empty(), arena, scope};
|
||||||
|
std::optional<TypeId> result = quantifier.substitute(ty);
|
||||||
|
LUAU_ASSERT(result);
|
||||||
|
|
||||||
|
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(*result);
|
||||||
|
LUAU_ASSERT(ftv);
|
||||||
|
ftv->generics.insert(ftv->generics.end(), quantifier.insertedGenerics.begin(), quantifier.insertedGenerics.end());
|
||||||
|
ftv->genericPacks.insert(ftv->genericPacks.end(), quantifier.insertedGenericPacks.begin(), quantifier.insertedGenericPacks.end());
|
||||||
|
|
||||||
|
// TODO: Set hasNoGenerics.
|
||||||
|
|
||||||
|
return *result;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -28,7 +28,7 @@ struct RequireTracer : AstVisitor
|
||||||
AstExprGlobal* global = expr->func->as<AstExprGlobal>();
|
AstExprGlobal* global = expr->func->as<AstExprGlobal>();
|
||||||
|
|
||||||
if (global && global->name == "require" && expr->args.size >= 1)
|
if (global && global->name == "require" && expr->args.size >= 1)
|
||||||
requires.push_back(expr);
|
requireCalls.push_back(expr);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -84,9 +84,9 @@ struct RequireTracer : AstVisitor
|
||||||
ModuleInfo moduleContext{currentModuleName};
|
ModuleInfo moduleContext{currentModuleName};
|
||||||
|
|
||||||
// seed worklist with require arguments
|
// seed worklist with require arguments
|
||||||
work.reserve(requires.size());
|
work.reserve(requireCalls.size());
|
||||||
|
|
||||||
for (AstExprCall* require : requires)
|
for (AstExprCall* require : requireCalls)
|
||||||
work.push_back(require->args.data[0]);
|
work.push_back(require->args.data[0]);
|
||||||
|
|
||||||
// push all dependent expressions to the work stack; note that the vector is modified during traversal
|
// push all dependent expressions to the work stack; note that the vector is modified during traversal
|
||||||
|
@ -125,15 +125,15 @@ struct RequireTracer : AstVisitor
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolve all requires according to their argument
|
// resolve all requires according to their argument
|
||||||
result.requires.reserve(requires.size());
|
result.requireList.reserve(requireCalls.size());
|
||||||
|
|
||||||
for (AstExprCall* require : requires)
|
for (AstExprCall* require : requireCalls)
|
||||||
{
|
{
|
||||||
AstExpr* arg = require->args.data[0];
|
AstExpr* arg = require->args.data[0];
|
||||||
|
|
||||||
if (const ModuleInfo* info = result.exprs.find(arg))
|
if (const ModuleInfo* info = result.exprs.find(arg))
|
||||||
{
|
{
|
||||||
result.requires.push_back({info->name, require->location});
|
result.requireList.push_back({info->name, require->location});
|
||||||
|
|
||||||
ModuleInfo infoCopy = *info; // copy *info out since next line invalidates info!
|
ModuleInfo infoCopy = *info; // copy *info out since next line invalidates info!
|
||||||
result.exprs[require] = std::move(infoCopy);
|
result.exprs[require] = std::move(infoCopy);
|
||||||
|
@ -151,7 +151,7 @@ struct RequireTracer : AstVisitor
|
||||||
|
|
||||||
DenseHashMap<AstLocal*, AstExpr*> locals;
|
DenseHashMap<AstLocal*, AstExpr*> locals;
|
||||||
std::vector<AstExpr*> work;
|
std::vector<AstExpr*> work;
|
||||||
std::vector<AstExprCall*> requires;
|
std::vector<AstExprCall*> requireCalls;
|
||||||
};
|
};
|
||||||
|
|
||||||
RequireTraceResult traceRequires(FileResolver* fileResolver, AstStatBlock* root, const ModuleName& currentModuleName)
|
RequireTraceResult traceRequires(FileResolver* fileResolver, AstStatBlock* root, const ModuleName& currentModuleName)
|
||||||
|
|
|
@ -27,7 +27,7 @@ void Tarjan::visitChildren(TypeId ty, int index)
|
||||||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
|
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
|
||||||
{
|
{
|
||||||
visitChild(ftv->argTypes);
|
visitChild(ftv->argTypes);
|
||||||
visitChild(ftv->retType);
|
visitChild(ftv->retTypes);
|
||||||
}
|
}
|
||||||
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
|
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
|
||||||
{
|
{
|
||||||
|
@ -442,7 +442,7 @@ void Substitution::replaceChildren(TypeId ty)
|
||||||
if (FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty))
|
if (FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty))
|
||||||
{
|
{
|
||||||
ftv->argTypes = replace(ftv->argTypes);
|
ftv->argTypes = replace(ftv->argTypes);
|
||||||
ftv->retType = replace(ftv->retType);
|
ftv->retTypes = replace(ftv->retTypes);
|
||||||
}
|
}
|
||||||
else if (TableTypeVar* ttv = getMutable<TableTypeVar>(ty))
|
else if (TableTypeVar* ttv = getMutable<TableTypeVar>(ty))
|
||||||
{
|
{
|
||||||
|
|
|
@ -154,7 +154,7 @@ void StateDot::visitChildren(TypeId ty, int index)
|
||||||
finishNode();
|
finishNode();
|
||||||
|
|
||||||
visitChild(ftv->argTypes, index, "arg");
|
visitChild(ftv->argTypes, index, "arg");
|
||||||
visitChild(ftv->retType, index, "ret");
|
visitChild(ftv->retTypes, index, "ret");
|
||||||
}
|
}
|
||||||
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
|
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
|
||||||
{
|
{
|
||||||
|
|
|
@ -226,6 +226,11 @@ struct StringifierState
|
||||||
result.name += s;
|
result.name += s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void emit(int i)
|
||||||
|
{
|
||||||
|
emit(std::to_string(i).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
void indent()
|
void indent()
|
||||||
{
|
{
|
||||||
indentation += 4;
|
indentation += 4;
|
||||||
|
@ -394,6 +399,13 @@ struct TypeVarStringifier
|
||||||
state.emit("]]");
|
state.emit("]]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void operator()(TypeId, const BlockedTypeVar& btv)
|
||||||
|
{
|
||||||
|
state.emit("*blocked-");
|
||||||
|
state.emit(btv.index);
|
||||||
|
state.emit("*");
|
||||||
|
}
|
||||||
|
|
||||||
void operator()(TypeId, const PrimitiveTypeVar& ptv)
|
void operator()(TypeId, const PrimitiveTypeVar& ptv)
|
||||||
{
|
{
|
||||||
switch (ptv.type)
|
switch (ptv.type)
|
||||||
|
@ -480,8 +492,8 @@ struct TypeVarStringifier
|
||||||
|
|
||||||
if (FFlag::LuauLowerBoundsCalculation)
|
if (FFlag::LuauLowerBoundsCalculation)
|
||||||
{
|
{
|
||||||
auto retBegin = begin(ftv.retType);
|
auto retBegin = begin(ftv.retTypes);
|
||||||
auto retEnd = end(ftv.retType);
|
auto retEnd = end(ftv.retTypes);
|
||||||
if (retBegin != retEnd)
|
if (retBegin != retEnd)
|
||||||
{
|
{
|
||||||
++retBegin;
|
++retBegin;
|
||||||
|
@ -491,7 +503,7 @@ struct TypeVarStringifier
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (auto retPack = get<TypePack>(follow(ftv.retType)))
|
if (auto retPack = get<TypePack>(follow(ftv.retTypes)))
|
||||||
{
|
{
|
||||||
if (retPack->head.size() == 1 && !retPack->tail)
|
if (retPack->head.size() == 1 && !retPack->tail)
|
||||||
plural = false;
|
plural = false;
|
||||||
|
@ -501,7 +513,7 @@ struct TypeVarStringifier
|
||||||
if (plural)
|
if (plural)
|
||||||
state.emit("(");
|
state.emit("(");
|
||||||
|
|
||||||
stringify(ftv.retType);
|
stringify(ftv.retTypes);
|
||||||
|
|
||||||
if (plural)
|
if (plural)
|
||||||
state.emit(")");
|
state.emit(")");
|
||||||
|
@ -1303,14 +1315,14 @@ std::string toStringNamedFunction(const std::string& funcName, const FunctionTyp
|
||||||
|
|
||||||
state.emit("): ");
|
state.emit("): ");
|
||||||
|
|
||||||
size_t retSize = size(ftv.retType);
|
size_t retSize = size(ftv.retTypes);
|
||||||
bool hasTail = !finite(ftv.retType);
|
bool hasTail = !finite(ftv.retTypes);
|
||||||
bool wrap = get<TypePack>(follow(ftv.retType)) && (hasTail ? retSize != 0 : retSize != 1);
|
bool wrap = get<TypePack>(follow(ftv.retTypes)) && (hasTail ? retSize != 0 : retSize != 1);
|
||||||
|
|
||||||
if (wrap)
|
if (wrap)
|
||||||
state.emit("(");
|
state.emit("(");
|
||||||
|
|
||||||
tvs.stringify(ftv.retType);
|
tvs.stringify(ftv.retTypes);
|
||||||
|
|
||||||
if (wrap)
|
if (wrap)
|
||||||
state.emit(")");
|
state.emit(")");
|
||||||
|
@ -1385,9 +1397,9 @@ std::string toString(const Constraint& c, ToStringOptions& opts)
|
||||||
}
|
}
|
||||||
else if (const GeneralizationConstraint* gc = Luau::get_if<GeneralizationConstraint>(&c.c))
|
else if (const GeneralizationConstraint* gc = Luau::get_if<GeneralizationConstraint>(&c.c))
|
||||||
{
|
{
|
||||||
ToStringResult subStr = toStringDetailed(gc->subType, opts);
|
ToStringResult subStr = toStringDetailed(gc->generalizedType, opts);
|
||||||
opts.nameMap = std::move(subStr.nameMap);
|
opts.nameMap = std::move(subStr.nameMap);
|
||||||
ToStringResult superStr = toStringDetailed(gc->superType, opts);
|
ToStringResult superStr = toStringDetailed(gc->sourceType, opts);
|
||||||
opts.nameMap = std::move(superStr.nameMap);
|
opts.nameMap = std::move(superStr.nameMap);
|
||||||
return subStr.name + " ~ gen " + superStr.name;
|
return subStr.name + " ~ gen " + superStr.name;
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,6 +94,11 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AstType* operator()(const BlockedTypeVar& btv)
|
||||||
|
{
|
||||||
|
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName("*blocked*"));
|
||||||
|
}
|
||||||
|
|
||||||
AstType* operator()(const ConstrainedTypeVar& ctv)
|
AstType* operator()(const ConstrainedTypeVar& ctv)
|
||||||
{
|
{
|
||||||
AstArray<AstType*> types;
|
AstArray<AstType*> types;
|
||||||
|
@ -271,7 +276,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
AstArray<AstType*> returnTypes;
|
AstArray<AstType*> returnTypes;
|
||||||
const auto& [retVector, retTail] = flatten(ftv.retType);
|
const auto& [retVector, retTail] = flatten(ftv.retTypes);
|
||||||
returnTypes.size = retVector.size();
|
returnTypes.size = retVector.size();
|
||||||
returnTypes.data = static_cast<AstType**>(allocator->allocate(sizeof(AstType*) * returnTypes.size));
|
returnTypes.data = static_cast<AstType**>(allocator->allocate(sizeof(AstType*) * returnTypes.size));
|
||||||
for (size_t i = 0; i < returnTypes.size; ++i)
|
for (size_t i = 0; i < returnTypes.size; ++i)
|
||||||
|
|
160
Analysis/src/TypeChecker2.cpp
Normal file
160
Analysis/src/TypeChecker2.cpp
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
|
||||||
|
#include "Luau/TypeChecker2.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "Luau/Ast.h"
|
||||||
|
#include "Luau/AstQuery.h"
|
||||||
|
#include "Luau/Clone.h"
|
||||||
|
#include "Luau/Normalize.h"
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
struct TypeChecker2 : public AstVisitor
|
||||||
|
{
|
||||||
|
const SourceModule* sourceModule;
|
||||||
|
Module* module;
|
||||||
|
InternalErrorReporter ice; // FIXME accept a pointer from Frontend
|
||||||
|
|
||||||
|
TypeChecker2(const SourceModule* sourceModule, Module* module)
|
||||||
|
: sourceModule(sourceModule)
|
||||||
|
, module(module)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
using AstVisitor::visit;
|
||||||
|
|
||||||
|
TypePackId lookupPack(AstExpr* expr)
|
||||||
|
{
|
||||||
|
TypePackId* tp = module->astTypePacks.find(expr);
|
||||||
|
LUAU_ASSERT(tp);
|
||||||
|
return follow(*tp);
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId lookupType(AstExpr* expr)
|
||||||
|
{
|
||||||
|
TypeId* ty = module->astTypes.find(expr);
|
||||||
|
LUAU_ASSERT(ty);
|
||||||
|
return follow(*ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStatAssign* assign) override
|
||||||
|
{
|
||||||
|
size_t count = std::min(assign->vars.size, assign->values.size);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
AstExpr* lhs = assign->vars.data[i];
|
||||||
|
TypeId* lhsType = module->astTypes.find(lhs);
|
||||||
|
LUAU_ASSERT(lhsType);
|
||||||
|
|
||||||
|
AstExpr* rhs = assign->values.data[i];
|
||||||
|
TypeId* rhsType = module->astTypes.find(rhs);
|
||||||
|
LUAU_ASSERT(rhsType);
|
||||||
|
|
||||||
|
if (!isSubtype(*rhsType, *lhsType, ice))
|
||||||
|
{
|
||||||
|
reportError(TypeMismatch{*lhsType, *rhsType}, rhs->location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstExprCall* call) override
|
||||||
|
{
|
||||||
|
TypePackId expectedRetType = lookupPack(call);
|
||||||
|
TypeId functionType = lookupType(call->func);
|
||||||
|
|
||||||
|
TypeArena arena;
|
||||||
|
TypePack args;
|
||||||
|
for (const auto& arg : call->args)
|
||||||
|
{
|
||||||
|
TypeId argTy = module->astTypes[arg];
|
||||||
|
LUAU_ASSERT(argTy);
|
||||||
|
args.head.push_back(argTy);
|
||||||
|
}
|
||||||
|
|
||||||
|
TypePackId argsTp = arena.addTypePack(args);
|
||||||
|
FunctionTypeVar ftv{argsTp, expectedRetType};
|
||||||
|
TypeId expectedType = arena.addType(ftv);
|
||||||
|
if (!isSubtype(expectedType, functionType, ice))
|
||||||
|
{
|
||||||
|
unfreeze(module->interfaceTypes);
|
||||||
|
CloneState cloneState;
|
||||||
|
expectedType = clone(expectedType, module->interfaceTypes, cloneState);
|
||||||
|
freeze(module->interfaceTypes);
|
||||||
|
reportError(TypeMismatch{expectedType, functionType}, call->location);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstExprIndexName* indexName) override
|
||||||
|
{
|
||||||
|
TypeId leftType = lookupType(indexName->expr);
|
||||||
|
TypeId resultType = lookupType(indexName);
|
||||||
|
|
||||||
|
// leftType must have a property called indexName->index
|
||||||
|
|
||||||
|
if (auto ttv = get<TableTypeVar>(leftType))
|
||||||
|
{
|
||||||
|
auto it = ttv->props.find(indexName->index.value);
|
||||||
|
if (it == ttv->props.end())
|
||||||
|
{
|
||||||
|
reportError(UnknownProperty{leftType, indexName->index.value}, indexName->location);
|
||||||
|
}
|
||||||
|
else if (!isSubtype(resultType, it->second.type, ice))
|
||||||
|
{
|
||||||
|
reportError(TypeMismatch{resultType, it->second.type}, indexName->location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reportError(UnknownProperty{leftType, indexName->index.value}, indexName->location);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstExprConstantNumber* number) override
|
||||||
|
{
|
||||||
|
TypeId actualType = lookupType(number);
|
||||||
|
TypeId numberType = getSingletonTypes().numberType;
|
||||||
|
|
||||||
|
if (!isSubtype(actualType, numberType, ice))
|
||||||
|
{
|
||||||
|
reportError(TypeMismatch{actualType, numberType}, number->location);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstExprConstantString* string) override
|
||||||
|
{
|
||||||
|
TypeId actualType = lookupType(string);
|
||||||
|
TypeId stringType = getSingletonTypes().stringType;
|
||||||
|
|
||||||
|
if (!isSubtype(actualType, stringType, ice))
|
||||||
|
{
|
||||||
|
reportError(TypeMismatch{actualType, stringType}, string->location);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reportError(TypeErrorData&& data, const Location& location)
|
||||||
|
{
|
||||||
|
module->errors.emplace_back(location, sourceModule->name, std::move(data));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void check(const SourceModule& sourceModule, Module* module)
|
||||||
|
{
|
||||||
|
TypeChecker2 typeChecker{&sourceModule, module};
|
||||||
|
|
||||||
|
sourceModule.root->visit(&typeChecker);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Luau
|
|
@ -18,6 +18,7 @@
|
||||||
#include "Luau/TypePack.h"
|
#include "Luau/TypePack.h"
|
||||||
#include "Luau/TypeUtils.h"
|
#include "Luau/TypeUtils.h"
|
||||||
#include "Luau/TypeVar.h"
|
#include "Luau/TypeVar.h"
|
||||||
|
#include "Luau/VisitTypeVar.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
|
@ -30,7 +31,6 @@ LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 300)
|
||||||
LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
|
LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
|
||||||
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
||||||
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
|
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauExpectedPropTypeFromIndexer, false)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false)
|
LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix2, false)
|
LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix2, false)
|
||||||
|
@ -42,9 +42,9 @@ LUAU_FASTFLAG(LuauNormalizeFlagIsConservative)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauReturnTypeInferenceInNonstrict, false)
|
LUAU_FASTFLAGVARIABLE(LuauReturnTypeInferenceInNonstrict, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauRecursionLimitException, false);
|
LUAU_FASTFLAGVARIABLE(LuauRecursionLimitException, false);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauApplyTypeFunctionFix, false);
|
LUAU_FASTFLAGVARIABLE(LuauApplyTypeFunctionFix, false);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauSuccessTypingForEqualityOperations, false)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAlwaysQuantify, false);
|
LUAU_FASTFLAGVARIABLE(LuauAlwaysQuantify, false);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauReportErrorsOnIndexerKeyMismatch, false)
|
LUAU_FASTFLAGVARIABLE(LuauReportErrorsOnIndexerKeyMismatch, false)
|
||||||
|
LUAU_FASTFLAG(LuauQuantifyConstrained)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauFalsyPredicateReturnsNilInstead, false)
|
LUAU_FASTFLAGVARIABLE(LuauFalsyPredicateReturnsNilInstead, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauNonCopyableTypeVarFields, false)
|
LUAU_FASTFLAGVARIABLE(LuauNonCopyableTypeVarFields, false)
|
||||||
|
|
||||||
|
@ -260,7 +260,6 @@ TypeChecker::TypeChecker(ModuleResolver* resolver, InternalErrorReporter* iceHan
|
||||||
, booleanType(getSingletonTypes().booleanType)
|
, booleanType(getSingletonTypes().booleanType)
|
||||||
, threadType(getSingletonTypes().threadType)
|
, threadType(getSingletonTypes().threadType)
|
||||||
, anyType(getSingletonTypes().anyType)
|
, anyType(getSingletonTypes().anyType)
|
||||||
, optionalNumberType(getSingletonTypes().optionalNumberType)
|
|
||||||
, anyTypePack(getSingletonTypes().anyTypePack)
|
, anyTypePack(getSingletonTypes().anyTypePack)
|
||||||
, duplicateTypeAliases{{false, {}}}
|
, duplicateTypeAliases{{false, {}}}
|
||||||
{
|
{
|
||||||
|
@ -679,7 +678,7 @@ static std::optional<Predicate> tryGetTypeGuardPredicate(const AstExprBinary& ex
|
||||||
|
|
||||||
void TypeChecker::check(const ScopePtr& scope, const AstStatIf& statement)
|
void TypeChecker::check(const ScopePtr& scope, const AstStatIf& statement)
|
||||||
{
|
{
|
||||||
ExprResult<TypeId> result = checkExpr(scope, *statement.condition);
|
WithPredicate<TypeId> result = checkExpr(scope, *statement.condition);
|
||||||
|
|
||||||
ScopePtr ifScope = childScope(scope, statement.thenbody->location);
|
ScopePtr ifScope = childScope(scope, statement.thenbody->location);
|
||||||
resolve(result.predicates, ifScope, true);
|
resolve(result.predicates, ifScope, true);
|
||||||
|
@ -712,7 +711,7 @@ ErrorVec TypeChecker::canUnify(TypePackId subTy, TypePackId superTy, const Locat
|
||||||
|
|
||||||
void TypeChecker::check(const ScopePtr& scope, const AstStatWhile& statement)
|
void TypeChecker::check(const ScopePtr& scope, const AstStatWhile& statement)
|
||||||
{
|
{
|
||||||
ExprResult<TypeId> result = checkExpr(scope, *statement.condition);
|
WithPredicate<TypeId> result = checkExpr(scope, *statement.condition);
|
||||||
|
|
||||||
ScopePtr whileScope = childScope(scope, statement.body->location);
|
ScopePtr whileScope = childScope(scope, statement.body->location);
|
||||||
resolve(result.predicates, whileScope, true);
|
resolve(result.predicates, whileScope, true);
|
||||||
|
@ -728,16 +727,64 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatRepeat& statement)
|
||||||
checkExpr(repScope, *statement.condition);
|
checkExpr(repScope, *statement.condition);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TypeChecker::unifyLowerBound(TypePackId subTy, TypePackId superTy, const Location& location)
|
void TypeChecker::unifyLowerBound(TypePackId subTy, TypePackId superTy, TypeLevel demotedLevel, const Location& location)
|
||||||
{
|
{
|
||||||
Unifier state = mkUnifier(location);
|
Unifier state = mkUnifier(location);
|
||||||
state.unifyLowerBound(subTy, superTy);
|
state.unifyLowerBound(subTy, superTy, demotedLevel);
|
||||||
|
|
||||||
state.log.commit();
|
state.log.commit();
|
||||||
|
|
||||||
reportErrors(state.errors);
|
reportErrors(state.errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Demoter : Substitution
|
||||||
|
{
|
||||||
|
Demoter(TypeArena* arena)
|
||||||
|
: Substitution(TxnLog::empty(), arena)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isDirty(TypeId ty) override
|
||||||
|
{
|
||||||
|
return get<FreeTypeVar>(ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isDirty(TypePackId tp) override
|
||||||
|
{
|
||||||
|
return get<FreeTypePack>(tp);
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId clean(TypeId ty) override
|
||||||
|
{
|
||||||
|
auto ftv = get<FreeTypeVar>(ty);
|
||||||
|
LUAU_ASSERT(ftv);
|
||||||
|
return addType(FreeTypeVar{demotedLevel(ftv->level)});
|
||||||
|
}
|
||||||
|
|
||||||
|
TypePackId clean(TypePackId tp) override
|
||||||
|
{
|
||||||
|
auto ftp = get<FreeTypePack>(tp);
|
||||||
|
LUAU_ASSERT(ftp);
|
||||||
|
return addTypePack(TypePackVar{FreeTypePack{demotedLevel(ftp->level)}});
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeLevel demotedLevel(TypeLevel level)
|
||||||
|
{
|
||||||
|
return TypeLevel{level.level + 5000, level.subLevel};
|
||||||
|
}
|
||||||
|
|
||||||
|
void demote(std::vector<std::optional<TypeId>>& expectedTypes)
|
||||||
|
{
|
||||||
|
if (!FFlag::LuauQuantifyConstrained)
|
||||||
|
return;
|
||||||
|
for (std::optional<TypeId>& ty : expectedTypes)
|
||||||
|
{
|
||||||
|
if (ty)
|
||||||
|
ty = substitute(*ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
void TypeChecker::check(const ScopePtr& scope, const AstStatReturn& return_)
|
void TypeChecker::check(const ScopePtr& scope, const AstStatReturn& return_)
|
||||||
{
|
{
|
||||||
std::vector<std::optional<TypeId>> expectedTypes;
|
std::vector<std::optional<TypeId>> expectedTypes;
|
||||||
|
@ -760,11 +807,14 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatReturn& return_)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Demoter demoter{¤tModule->internalTypes};
|
||||||
|
demoter.demote(expectedTypes);
|
||||||
|
|
||||||
TypePackId retPack = checkExprList(scope, return_.location, return_.list, false, {}, expectedTypes).type;
|
TypePackId retPack = checkExprList(scope, return_.location, return_.list, false, {}, expectedTypes).type;
|
||||||
|
|
||||||
if (FFlag::LuauReturnTypeInferenceInNonstrict ? FFlag::LuauLowerBoundsCalculation : useConstrainedIntersections())
|
if (FFlag::LuauReturnTypeInferenceInNonstrict ? FFlag::LuauLowerBoundsCalculation : useConstrainedIntersections())
|
||||||
{
|
{
|
||||||
unifyLowerBound(retPack, scope->returnType, return_.location);
|
unifyLowerBound(retPack, scope->returnType, demoter.demotedLevel(scope->level), return_.location);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1230,7 +1280,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin)
|
||||||
unify(retPack, varPack, forin.location);
|
unify(retPack, varPack, forin.location);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
unify(iterFunc->retType, varPack, forin.location);
|
unify(iterFunc->retTypes, varPack, forin.location);
|
||||||
|
|
||||||
check(loopScope, *forin.body);
|
check(loopScope, *forin.body);
|
||||||
}
|
}
|
||||||
|
@ -1611,7 +1661,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatDeclareFunction& glo
|
||||||
currentModule->getModuleScope()->bindings[global.name] = Binding{fnType, global.location};
|
currentModule->getModuleScope()->bindings[global.name] = Binding{fnType, global.location};
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExpr& expr, std::optional<TypeId> expectedType, bool forceSingleton)
|
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExpr& expr, std::optional<TypeId> expectedType, bool forceSingleton)
|
||||||
{
|
{
|
||||||
RecursionCounter _rc(&checkRecursionCount);
|
RecursionCounter _rc(&checkRecursionCount);
|
||||||
if (FInt::LuauCheckRecursionLimit > 0 && checkRecursionCount >= FInt::LuauCheckRecursionLimit)
|
if (FInt::LuauCheckRecursionLimit > 0 && checkRecursionCount >= FInt::LuauCheckRecursionLimit)
|
||||||
|
@ -1620,7 +1670,7 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExpr&
|
||||||
return {errorRecoveryType(scope)};
|
return {errorRecoveryType(scope)};
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprResult<TypeId> result;
|
WithPredicate<TypeId> result;
|
||||||
|
|
||||||
if (auto a = expr.as<AstExprGroup>())
|
if (auto a = expr.as<AstExprGroup>())
|
||||||
result = checkExpr(scope, *a->expr, expectedType);
|
result = checkExpr(scope, *a->expr, expectedType);
|
||||||
|
@ -1682,7 +1732,7 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExpr&
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprLocal& expr)
|
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprLocal& expr)
|
||||||
{
|
{
|
||||||
std::optional<LValue> lvalue = tryGetLValue(expr);
|
std::optional<LValue> lvalue = tryGetLValue(expr);
|
||||||
LUAU_ASSERT(lvalue); // Guaranteed to not be nullopt - AstExprLocal is an LValue.
|
LUAU_ASSERT(lvalue); // Guaranteed to not be nullopt - AstExprLocal is an LValue.
|
||||||
|
@ -1696,7 +1746,7 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprLo
|
||||||
return {errorRecoveryType(scope)};
|
return {errorRecoveryType(scope)};
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprGlobal& expr)
|
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprGlobal& expr)
|
||||||
{
|
{
|
||||||
std::optional<LValue> lvalue = tryGetLValue(expr);
|
std::optional<LValue> lvalue = tryGetLValue(expr);
|
||||||
LUAU_ASSERT(lvalue); // Guaranteed to not be nullopt - AstExprGlobal is an LValue.
|
LUAU_ASSERT(lvalue); // Guaranteed to not be nullopt - AstExprGlobal is an LValue.
|
||||||
|
@ -1708,7 +1758,7 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprGl
|
||||||
return {errorRecoveryType(scope)};
|
return {errorRecoveryType(scope)};
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprVarargs& expr)
|
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprVarargs& expr)
|
||||||
{
|
{
|
||||||
TypePackId varargPack = checkExprPack(scope, expr).type;
|
TypePackId varargPack = checkExprPack(scope, expr).type;
|
||||||
|
|
||||||
|
@ -1738,9 +1788,9 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprVa
|
||||||
ice("Unknown TypePack type in checkExpr(AstExprVarargs)!");
|
ice("Unknown TypePack type in checkExpr(AstExprVarargs)!");
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprCall& expr)
|
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprCall& expr)
|
||||||
{
|
{
|
||||||
ExprResult<TypePackId> result = checkExprPack(scope, expr);
|
WithPredicate<TypePackId> result = checkExprPack(scope, expr);
|
||||||
TypePackId retPack = follow(result.type);
|
TypePackId retPack = follow(result.type);
|
||||||
|
|
||||||
if (auto pack = get<TypePack>(retPack))
|
if (auto pack = get<TypePack>(retPack))
|
||||||
|
@ -1770,7 +1820,7 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprCa
|
||||||
ice("Unknown TypePack type!", expr.location);
|
ice("Unknown TypePack type!", expr.location);
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprIndexName& expr)
|
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprIndexName& expr)
|
||||||
{
|
{
|
||||||
Name name = expr.index.value;
|
Name name = expr.index.value;
|
||||||
|
|
||||||
|
@ -2031,7 +2081,7 @@ TypeId TypeChecker::stripFromNilAndReport(TypeId ty, const Location& location)
|
||||||
return ty;
|
return ty;
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprIndexExpr& expr)
|
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprIndexExpr& expr)
|
||||||
{
|
{
|
||||||
TypeId ty = checkLValue(scope, expr);
|
TypeId ty = checkLValue(scope, expr);
|
||||||
|
|
||||||
|
@ -2042,7 +2092,7 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprIn
|
||||||
return {ty};
|
return {ty};
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprFunction& expr, std::optional<TypeId> expectedType)
|
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprFunction& expr, std::optional<TypeId> expectedType)
|
||||||
{
|
{
|
||||||
auto [funTy, funScope] = checkFunctionSignature(scope, 0, expr, std::nullopt, expectedType);
|
auto [funTy, funScope] = checkFunctionSignature(scope, 0, expr, std::nullopt, expectedType);
|
||||||
|
|
||||||
|
@ -2108,8 +2158,7 @@ TypeId TypeChecker::checkExprTable(
|
||||||
if (errors.empty())
|
if (errors.empty())
|
||||||
exprType = expectedProp.type;
|
exprType = expectedProp.type;
|
||||||
}
|
}
|
||||||
else if (expectedTable->indexer && (FFlag::LuauExpectedPropTypeFromIndexer ? maybeString(expectedTable->indexer->indexType)
|
else if (expectedTable->indexer && maybeString(expectedTable->indexer->indexType))
|
||||||
: isString(expectedTable->indexer->indexType)))
|
|
||||||
{
|
{
|
||||||
ErrorVec errors = tryUnify(exprType, expectedTable->indexer->indexResultType, k->location);
|
ErrorVec errors = tryUnify(exprType, expectedTable->indexer->indexResultType, k->location);
|
||||||
if (errors.empty())
|
if (errors.empty())
|
||||||
|
@ -2147,7 +2196,7 @@ TypeId TypeChecker::checkExprTable(
|
||||||
return addType(table);
|
return addType(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprTable& expr, std::optional<TypeId> expectedType)
|
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprTable& expr, std::optional<TypeId> expectedType)
|
||||||
{
|
{
|
||||||
RecursionCounter _rc(&checkRecursionCount);
|
RecursionCounter _rc(&checkRecursionCount);
|
||||||
if (FInt::LuauCheckRecursionLimit > 0 && checkRecursionCount >= FInt::LuauCheckRecursionLimit)
|
if (FInt::LuauCheckRecursionLimit > 0 && checkRecursionCount >= FInt::LuauCheckRecursionLimit)
|
||||||
|
@ -2201,7 +2250,7 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprTa
|
||||||
{
|
{
|
||||||
if (auto prop = expectedTable->props.find(key->value.data); prop != expectedTable->props.end())
|
if (auto prop = expectedTable->props.find(key->value.data); prop != expectedTable->props.end())
|
||||||
expectedResultType = prop->second.type;
|
expectedResultType = prop->second.type;
|
||||||
else if (FFlag::LuauExpectedPropTypeFromIndexer && expectedIndexType && maybeString(*expectedIndexType))
|
else if (expectedIndexType && maybeString(*expectedIndexType))
|
||||||
expectedResultType = expectedIndexResultType;
|
expectedResultType = expectedIndexResultType;
|
||||||
}
|
}
|
||||||
else if (expectedUnion)
|
else if (expectedUnion)
|
||||||
|
@ -2236,9 +2285,9 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprTa
|
||||||
return {checkExprTable(scope, expr, fieldTypes, expectedType)};
|
return {checkExprTable(scope, expr, fieldTypes, expectedType)};
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprUnary& expr)
|
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprUnary& expr)
|
||||||
{
|
{
|
||||||
ExprResult<TypeId> result = checkExpr(scope, *expr.expr);
|
WithPredicate<TypeId> result = checkExpr(scope, *expr.expr);
|
||||||
TypeId operandType = follow(result.type);
|
TypeId operandType = follow(result.type);
|
||||||
|
|
||||||
switch (expr.op)
|
switch (expr.op)
|
||||||
|
@ -2466,62 +2515,50 @@ TypeId TypeChecker::checkRelationalOperation(
|
||||||
std::optional<TypeId> leftMetatable = isString(lhsType) ? std::nullopt : getMetatable(follow(lhsType));
|
std::optional<TypeId> leftMetatable = isString(lhsType) ? std::nullopt : getMetatable(follow(lhsType));
|
||||||
std::optional<TypeId> rightMetatable = isString(rhsType) ? std::nullopt : getMetatable(follow(rhsType));
|
std::optional<TypeId> rightMetatable = isString(rhsType) ? std::nullopt : getMetatable(follow(rhsType));
|
||||||
|
|
||||||
if (FFlag::LuauSuccessTypingForEqualityOperations)
|
if (leftMetatable != rightMetatable)
|
||||||
{
|
{
|
||||||
if (leftMetatable != rightMetatable)
|
bool matches = false;
|
||||||
|
if (isEquality)
|
||||||
{
|
{
|
||||||
bool matches = false;
|
if (const UnionTypeVar* utv = get<UnionTypeVar>(leftType); utv && rightMetatable)
|
||||||
if (isEquality)
|
|
||||||
{
|
{
|
||||||
if (const UnionTypeVar* utv = get<UnionTypeVar>(leftType); utv && rightMetatable)
|
for (TypeId leftOption : utv)
|
||||||
{
|
{
|
||||||
for (TypeId leftOption : utv)
|
if (getMetatable(follow(leftOption)) == rightMetatable)
|
||||||
{
|
{
|
||||||
if (getMetatable(follow(leftOption)) == rightMetatable)
|
matches = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!matches)
|
||||||
|
{
|
||||||
|
if (const UnionTypeVar* utv = get<UnionTypeVar>(rhsType); utv && leftMetatable)
|
||||||
|
{
|
||||||
|
for (TypeId rightOption : utv)
|
||||||
|
{
|
||||||
|
if (getMetatable(follow(rightOption)) == leftMetatable)
|
||||||
{
|
{
|
||||||
matches = true;
|
matches = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!matches)
|
|
||||||
{
|
|
||||||
if (const UnionTypeVar* utv = get<UnionTypeVar>(rhsType); utv && leftMetatable)
|
|
||||||
{
|
|
||||||
for (TypeId rightOption : utv)
|
|
||||||
{
|
|
||||||
if (getMetatable(follow(rightOption)) == leftMetatable)
|
|
||||||
{
|
|
||||||
matches = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (!matches)
|
|
||||||
{
|
|
||||||
reportError(
|
|
||||||
expr.location, GenericError{format("Types %s and %s cannot be compared with %s because they do not have the same metatable",
|
|
||||||
toString(lhsType).c_str(), toString(rhsType).c_str(), toString(expr.op).c_str())});
|
|
||||||
return errorRecoveryType(booleanType);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
if (!matches)
|
||||||
if (bool(leftMetatable) != bool(rightMetatable) && leftMetatable != rightMetatable)
|
|
||||||
{
|
{
|
||||||
reportError(
|
reportError(
|
||||||
expr.location, GenericError{format("Types %s and %s cannot be compared with %s because they do not have the same metatable",
|
expr.location, GenericError{format("Types %s and %s cannot be compared with %s because they do not have the same metatable",
|
||||||
toString(lhsType).c_str(), toString(rhsType).c_str(), toString(expr.op).c_str())});
|
toString(lhsType).c_str(), toString(rhsType).c_str(), toString(expr.op).c_str())});
|
||||||
return errorRecoveryType(booleanType);
|
return errorRecoveryType(booleanType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (leftMetatable)
|
if (leftMetatable)
|
||||||
{
|
{
|
||||||
std::optional<TypeId> metamethod = findMetatableEntry(lhsType, metamethodName, expr.location);
|
std::optional<TypeId> metamethod = findMetatableEntry(lhsType, metamethodName, expr.location);
|
||||||
|
@ -2532,7 +2569,7 @@ TypeId TypeChecker::checkRelationalOperation(
|
||||||
if (isEquality)
|
if (isEquality)
|
||||||
{
|
{
|
||||||
Unifier state = mkUnifier(expr.location);
|
Unifier state = mkUnifier(expr.location);
|
||||||
state.tryUnify(addTypePack({booleanType}), ftv->retType);
|
state.tryUnify(addTypePack({booleanType}), ftv->retTypes);
|
||||||
|
|
||||||
if (!state.errors.empty())
|
if (!state.errors.empty())
|
||||||
{
|
{
|
||||||
|
@ -2721,7 +2758,7 @@ TypeId TypeChecker::checkBinaryOperation(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprBinary& expr)
|
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprBinary& expr)
|
||||||
{
|
{
|
||||||
if (expr.op == AstExprBinary::And)
|
if (expr.op == AstExprBinary::And)
|
||||||
{
|
{
|
||||||
|
@ -2752,8 +2789,8 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprBi
|
||||||
if (auto predicate = tryGetTypeGuardPredicate(expr))
|
if (auto predicate = tryGetTypeGuardPredicate(expr))
|
||||||
return {booleanType, {std::move(*predicate)}};
|
return {booleanType, {std::move(*predicate)}};
|
||||||
|
|
||||||
ExprResult<TypeId> lhs = checkExpr(scope, *expr.left, std::nullopt, /*forceSingleton=*/true);
|
WithPredicate<TypeId> lhs = checkExpr(scope, *expr.left, std::nullopt, /*forceSingleton=*/true);
|
||||||
ExprResult<TypeId> rhs = checkExpr(scope, *expr.right, std::nullopt, /*forceSingleton=*/true);
|
WithPredicate<TypeId> rhs = checkExpr(scope, *expr.right, std::nullopt, /*forceSingleton=*/true);
|
||||||
|
|
||||||
PredicateVec predicates;
|
PredicateVec predicates;
|
||||||
|
|
||||||
|
@ -2770,18 +2807,18 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprBi
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ExprResult<TypeId> lhs = checkExpr(scope, *expr.left);
|
WithPredicate<TypeId> lhs = checkExpr(scope, *expr.left);
|
||||||
ExprResult<TypeId> rhs = checkExpr(scope, *expr.right);
|
WithPredicate<TypeId> rhs = checkExpr(scope, *expr.right);
|
||||||
|
|
||||||
// Intentionally discarding predicates with other operators.
|
// Intentionally discarding predicates with other operators.
|
||||||
return {checkBinaryOperation(scope, expr, lhs.type, rhs.type, lhs.predicates)};
|
return {checkBinaryOperation(scope, expr, lhs.type, rhs.type, lhs.predicates)};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprTypeAssertion& expr)
|
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprTypeAssertion& expr)
|
||||||
{
|
{
|
||||||
TypeId annotationType = resolveType(scope, *expr.annotation);
|
TypeId annotationType = resolveType(scope, *expr.annotation);
|
||||||
ExprResult<TypeId> result = checkExpr(scope, *expr.expr, annotationType);
|
WithPredicate<TypeId> result = checkExpr(scope, *expr.expr, annotationType);
|
||||||
|
|
||||||
// Note: As an optimization, we try 'number <: number | string' first, as that is the more likely case.
|
// Note: As an optimization, we try 'number <: number | string' first, as that is the more likely case.
|
||||||
if (canUnify(annotationType, result.type, expr.location).empty())
|
if (canUnify(annotationType, result.type, expr.location).empty())
|
||||||
|
@ -2794,7 +2831,7 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprTy
|
||||||
return {errorRecoveryType(annotationType), std::move(result.predicates)};
|
return {errorRecoveryType(annotationType), std::move(result.predicates)};
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprError& expr)
|
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprError& expr)
|
||||||
{
|
{
|
||||||
const size_t oldSize = currentModule->errors.size();
|
const size_t oldSize = currentModule->errors.size();
|
||||||
|
|
||||||
|
@ -2808,17 +2845,17 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprEr
|
||||||
return {errorRecoveryType(scope)};
|
return {errorRecoveryType(scope)};
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprIfElse& expr, std::optional<TypeId> expectedType)
|
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprIfElse& expr, std::optional<TypeId> expectedType)
|
||||||
{
|
{
|
||||||
ExprResult<TypeId> result = checkExpr(scope, *expr.condition);
|
WithPredicate<TypeId> result = checkExpr(scope, *expr.condition);
|
||||||
|
|
||||||
ScopePtr trueScope = childScope(scope, expr.trueExpr->location);
|
ScopePtr trueScope = childScope(scope, expr.trueExpr->location);
|
||||||
resolve(result.predicates, trueScope, true);
|
resolve(result.predicates, trueScope, true);
|
||||||
ExprResult<TypeId> trueType = checkExpr(trueScope, *expr.trueExpr, expectedType);
|
WithPredicate<TypeId> trueType = checkExpr(trueScope, *expr.trueExpr, expectedType);
|
||||||
|
|
||||||
ScopePtr falseScope = childScope(scope, expr.falseExpr->location);
|
ScopePtr falseScope = childScope(scope, expr.falseExpr->location);
|
||||||
resolve(result.predicates, falseScope, false);
|
resolve(result.predicates, falseScope, false);
|
||||||
ExprResult<TypeId> falseType = checkExpr(falseScope, *expr.falseExpr, expectedType);
|
WithPredicate<TypeId> falseType = checkExpr(falseScope, *expr.falseExpr, expectedType);
|
||||||
|
|
||||||
if (falseType.type == trueType.type)
|
if (falseType.type == trueType.type)
|
||||||
return {trueType.type};
|
return {trueType.type};
|
||||||
|
@ -3170,7 +3207,7 @@ std::pair<TypeId, ScopePtr> TypeChecker::checkFunctionSignature(
|
||||||
retPack = anyTypePack;
|
retPack = anyTypePack;
|
||||||
else if (expectedFunctionType)
|
else if (expectedFunctionType)
|
||||||
{
|
{
|
||||||
auto [head, tail] = flatten(expectedFunctionType->retType);
|
auto [head, tail] = flatten(expectedFunctionType->retTypes);
|
||||||
|
|
||||||
// Do not infer 'nil' as function return type
|
// Do not infer 'nil' as function return type
|
||||||
if (!tail && head.size() == 1 && isNil(head[0]))
|
if (!tail && head.size() == 1 && isNil(head[0]))
|
||||||
|
@ -3354,7 +3391,7 @@ void TypeChecker::checkFunctionBody(const ScopePtr& scope, TypeId ty, const AstE
|
||||||
|
|
||||||
if (useConstrainedIntersections())
|
if (useConstrainedIntersections())
|
||||||
{
|
{
|
||||||
TypePackId retPack = follow(funTy->retType);
|
TypePackId retPack = follow(funTy->retTypes);
|
||||||
// It is possible for a function to have no annotation and no return statement, and yet still have an ascribed return type
|
// It is possible for a function to have no annotation and no return statement, and yet still have an ascribed return type
|
||||||
// if it is expected to conform to some other interface. (eg the function may be a lambda passed as a callback)
|
// if it is expected to conform to some other interface. (eg the function may be a lambda passed as a callback)
|
||||||
if (!hasReturn(function.body) && !function.returnAnnotation.has_value() && get<FreeTypePack>(retPack))
|
if (!hasReturn(function.body) && !function.returnAnnotation.has_value() && get<FreeTypePack>(retPack))
|
||||||
|
@ -3367,20 +3404,20 @@ void TypeChecker::checkFunctionBody(const ScopePtr& scope, TypeId ty, const AstE
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// We explicitly don't follow here to check if we have a 'true' free type instead of bound one
|
// We explicitly don't follow here to check if we have a 'true' free type instead of bound one
|
||||||
if (get_if<FreeTypePack>(&funTy->retType->ty))
|
if (get_if<FreeTypePack>(&funTy->retTypes->ty))
|
||||||
*asMutable(funTy->retType) = TypePack{{}, std::nullopt};
|
*asMutable(funTy->retTypes) = TypePack{{}, std::nullopt};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool reachesImplicitReturn = getFallthrough(function.body) != nullptr;
|
bool reachesImplicitReturn = getFallthrough(function.body) != nullptr;
|
||||||
|
|
||||||
if (reachesImplicitReturn && !allowsNoReturnValues(follow(funTy->retType)))
|
if (reachesImplicitReturn && !allowsNoReturnValues(follow(funTy->retTypes)))
|
||||||
{
|
{
|
||||||
// If we're in nonstrict mode we want to only report this missing return
|
// If we're in nonstrict mode we want to only report this missing return
|
||||||
// statement if there are type annotations on the function. In strict mode
|
// statement if there are type annotations on the function. In strict mode
|
||||||
// we report it regardless.
|
// we report it regardless.
|
||||||
if (!isNonstrictMode() || function.returnAnnotation)
|
if (!isNonstrictMode() || function.returnAnnotation)
|
||||||
{
|
{
|
||||||
reportError(getEndLocation(function), FunctionExitsWithoutReturning{funTy->retType});
|
reportError(getEndLocation(function), FunctionExitsWithoutReturning{funTy->retTypes});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3388,7 +3425,7 @@ void TypeChecker::checkFunctionBody(const ScopePtr& scope, TypeId ty, const AstE
|
||||||
ice("Checking non functional type");
|
ice("Checking non functional type");
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprResult<TypePackId> TypeChecker::checkExprPack(const ScopePtr& scope, const AstExpr& expr)
|
WithPredicate<TypePackId> TypeChecker::checkExprPack(const ScopePtr& scope, const AstExpr& expr)
|
||||||
{
|
{
|
||||||
if (auto a = expr.as<AstExprCall>())
|
if (auto a = expr.as<AstExprCall>())
|
||||||
return checkExprPack(scope, *a);
|
return checkExprPack(scope, *a);
|
||||||
|
@ -3654,7 +3691,7 @@ void TypeChecker::checkArgumentList(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprResult<TypePackId> TypeChecker::checkExprPack(const ScopePtr& scope, const AstExprCall& expr)
|
WithPredicate<TypePackId> TypeChecker::checkExprPack(const ScopePtr& scope, const AstExprCall& expr)
|
||||||
{
|
{
|
||||||
// evaluate type of function
|
// evaluate type of function
|
||||||
// decompose an intersection into its component overloads
|
// decompose an intersection into its component overloads
|
||||||
|
@ -3722,7 +3759,7 @@ ExprResult<TypePackId> TypeChecker::checkExprPack(const ScopePtr& scope, const A
|
||||||
|
|
||||||
std::vector<std::optional<TypeId>> expectedTypes = getExpectedTypesForCall(overloads, expr.args.size, expr.self);
|
std::vector<std::optional<TypeId>> expectedTypes = getExpectedTypesForCall(overloads, expr.args.size, expr.self);
|
||||||
|
|
||||||
ExprResult<TypePackId> argListResult = checkExprList(scope, expr.location, expr.args, false, {}, expectedTypes);
|
WithPredicate<TypePackId> argListResult = checkExprList(scope, expr.location, expr.args, false, {}, expectedTypes);
|
||||||
TypePackId argPack = argListResult.type;
|
TypePackId argPack = argListResult.type;
|
||||||
|
|
||||||
if (get<Unifiable::Error>(argPack))
|
if (get<Unifiable::Error>(argPack))
|
||||||
|
@ -3766,7 +3803,7 @@ ExprResult<TypePackId> TypeChecker::checkExprPack(const ScopePtr& scope, const A
|
||||||
if (!overload && !overloadsThatDont.empty())
|
if (!overload && !overloadsThatDont.empty())
|
||||||
overload = get<FunctionTypeVar>(overloadsThatDont[0]);
|
overload = get<FunctionTypeVar>(overloadsThatDont[0]);
|
||||||
if (overload)
|
if (overload)
|
||||||
return {errorRecoveryTypePack(overload->retType)};
|
return {errorRecoveryTypePack(overload->retTypes)};
|
||||||
|
|
||||||
return {errorRecoveryTypePack(retPack)};
|
return {errorRecoveryTypePack(retPack)};
|
||||||
}
|
}
|
||||||
|
@ -3775,7 +3812,7 @@ std::vector<std::optional<TypeId>> TypeChecker::getExpectedTypesForCall(const st
|
||||||
{
|
{
|
||||||
std::vector<std::optional<TypeId>> expectedTypes;
|
std::vector<std::optional<TypeId>> expectedTypes;
|
||||||
|
|
||||||
auto assignOption = [this, &expectedTypes](size_t index, std::optional<TypeId> ty) {
|
auto assignOption = [this, &expectedTypes](size_t index, TypeId ty) {
|
||||||
if (index == expectedTypes.size())
|
if (index == expectedTypes.size())
|
||||||
{
|
{
|
||||||
expectedTypes.push_back(ty);
|
expectedTypes.push_back(ty);
|
||||||
|
@ -3790,7 +3827,7 @@ std::vector<std::optional<TypeId>> TypeChecker::getExpectedTypesForCall(const st
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::vector<TypeId> result = reduceUnion({*el, *ty});
|
std::vector<TypeId> result = reduceUnion({*el, ty});
|
||||||
el = result.size() == 1 ? result[0] : addType(UnionTypeVar{std::move(result)});
|
el = result.size() == 1 ? result[0] : addType(UnionTypeVar{std::move(result)});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3810,7 +3847,8 @@ std::vector<std::optional<TypeId>> TypeChecker::getExpectedTypesForCall(const st
|
||||||
|
|
||||||
if (argsTail)
|
if (argsTail)
|
||||||
{
|
{
|
||||||
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*argsTail)))
|
argsTail = follow(*argsTail);
|
||||||
|
if (const VariadicTypePack* vtp = get<VariadicTypePack>(*argsTail))
|
||||||
{
|
{
|
||||||
while (index < argumentCount)
|
while (index < argumentCount)
|
||||||
assignOption(index++, vtp->ty);
|
assignOption(index++, vtp->ty);
|
||||||
|
@ -3819,11 +3857,14 @@ std::vector<std::optional<TypeId>> TypeChecker::getExpectedTypesForCall(const st
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Demoter demoter{¤tModule->internalTypes};
|
||||||
|
demoter.demote(expectedTypes);
|
||||||
|
|
||||||
return expectedTypes;
|
return expectedTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<ExprResult<TypePackId>> TypeChecker::checkCallOverload(const ScopePtr& scope, const AstExprCall& expr, TypeId fn, TypePackId retPack,
|
std::optional<WithPredicate<TypePackId>> TypeChecker::checkCallOverload(const ScopePtr& scope, const AstExprCall& expr, TypeId fn, TypePackId retPack,
|
||||||
TypePackId argPack, TypePack* args, const std::vector<Location>* argLocations, const ExprResult<TypePackId>& argListResult,
|
TypePackId argPack, TypePack* args, const std::vector<Location>* argLocations, const WithPredicate<TypePackId>& argListResult,
|
||||||
std::vector<TypeId>& overloadsThatMatchArgCount, std::vector<TypeId>& overloadsThatDont, std::vector<OverloadErrorEntry>& errors)
|
std::vector<TypeId>& overloadsThatMatchArgCount, std::vector<TypeId>& overloadsThatDont, std::vector<OverloadErrorEntry>& errors)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(argLocations);
|
LUAU_ASSERT(argLocations);
|
||||||
|
@ -3918,14 +3959,14 @@ std::optional<ExprResult<TypePackId>> TypeChecker::checkCallOverload(const Scope
|
||||||
if (ftv->magicFunction)
|
if (ftv->magicFunction)
|
||||||
{
|
{
|
||||||
// TODO: We're passing in the wrong TypePackId. Should be argPack, but a unit test fails otherwise. CLI-40458
|
// TODO: We're passing in the wrong TypePackId. Should be argPack, but a unit test fails otherwise. CLI-40458
|
||||||
if (std::optional<ExprResult<TypePackId>> ret = ftv->magicFunction(*this, scope, expr, argListResult))
|
if (std::optional<WithPredicate<TypePackId>> ret = ftv->magicFunction(*this, scope, expr, argListResult))
|
||||||
return *ret;
|
return *ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
Unifier state = mkUnifier(expr.location);
|
Unifier state = mkUnifier(expr.location);
|
||||||
|
|
||||||
// Unify return types
|
// Unify return types
|
||||||
checkArgumentList(scope, state, retPack, ftv->retType, /*argLocations*/ {});
|
checkArgumentList(scope, state, retPack, ftv->retTypes, /*argLocations*/ {});
|
||||||
if (!state.errors.empty())
|
if (!state.errors.empty())
|
||||||
{
|
{
|
||||||
return {};
|
return {};
|
||||||
|
@ -3996,7 +4037,7 @@ bool TypeChecker::handleSelfCallMismatch(const ScopePtr& scope, const AstExprCal
|
||||||
// we eagerly assume that that's what you actually meant and we commit to it.
|
// we eagerly assume that that's what you actually meant and we commit to it.
|
||||||
// This could be incorrect if the function has an additional overload that
|
// This could be incorrect if the function has an additional overload that
|
||||||
// actually works.
|
// actually works.
|
||||||
// checkArgumentList(scope, editedState, retPack, ftv->retType, retLocations, CountMismatch::Return);
|
// checkArgumentList(scope, editedState, retPack, ftv->retTypes, retLocations, CountMismatch::Return);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4027,7 +4068,7 @@ bool TypeChecker::handleSelfCallMismatch(const ScopePtr& scope, const AstExprCal
|
||||||
// we eagerly assume that that's what you actually meant and we commit to it.
|
// we eagerly assume that that's what you actually meant and we commit to it.
|
||||||
// This could be incorrect if the function has an additional overload that
|
// This could be incorrect if the function has an additional overload that
|
||||||
// actually works.
|
// actually works.
|
||||||
// checkArgumentList(scope, editedState, retPack, ftv->retType, retLocations, CountMismatch::Return);
|
// checkArgumentList(scope, editedState, retPack, ftv->retTypes, retLocations, CountMismatch::Return);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4085,7 +4126,7 @@ void TypeChecker::reportOverloadResolutionError(const ScopePtr& scope, const Ast
|
||||||
// Unify return types
|
// Unify return types
|
||||||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(overload))
|
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(overload))
|
||||||
{
|
{
|
||||||
checkArgumentList(scope, state, retPack, ftv->retType, {});
|
checkArgumentList(scope, state, retPack, ftv->retTypes, {});
|
||||||
checkArgumentList(scope, state, argPack, ftv->argTypes, argLocations);
|
checkArgumentList(scope, state, argPack, ftv->argTypes, argLocations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4110,7 +4151,7 @@ void TypeChecker::reportOverloadResolutionError(const ScopePtr& scope, const Ast
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprResult<TypePackId> TypeChecker::checkExprList(const ScopePtr& scope, const Location& location, const AstArray<AstExpr*>& exprs,
|
WithPredicate<TypePackId> TypeChecker::checkExprList(const ScopePtr& scope, const Location& location, const AstArray<AstExpr*>& exprs,
|
||||||
bool substituteFreeForNil, const std::vector<bool>& instantiateGenerics, const std::vector<std::optional<TypeId>>& expectedTypes)
|
bool substituteFreeForNil, const std::vector<bool>& instantiateGenerics, const std::vector<std::optional<TypeId>>& expectedTypes)
|
||||||
{
|
{
|
||||||
TypePackId pack = addTypePack(TypePack{});
|
TypePackId pack = addTypePack(TypePack{});
|
||||||
|
@ -4401,10 +4442,24 @@ TypeId Anyification::clean(TypeId ty)
|
||||||
}
|
}
|
||||||
else if (auto ctv = get<ConstrainedTypeVar>(ty))
|
else if (auto ctv = get<ConstrainedTypeVar>(ty))
|
||||||
{
|
{
|
||||||
auto [t, ok] = normalize(ty, *arena, *iceHandler);
|
if (FFlag::LuauQuantifyConstrained)
|
||||||
if (!ok)
|
{
|
||||||
normalizationTooComplex = true;
|
std::vector<TypeId> copy = ctv->parts;
|
||||||
return t;
|
for (TypeId& ty : copy)
|
||||||
|
ty = replace(ty);
|
||||||
|
TypeId res = copy.size() == 1 ? copy[0] : addType(UnionTypeVar{std::move(copy)});
|
||||||
|
auto [t, ok] = normalize(res, *arena, *iceHandler);
|
||||||
|
if (!ok)
|
||||||
|
normalizationTooComplex = true;
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto [t, ok] = normalize(ty, *arena, *iceHandler);
|
||||||
|
if (!ok)
|
||||||
|
normalizationTooComplex = true;
|
||||||
|
return t;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return anyType;
|
return anyType;
|
||||||
|
|
|
@ -66,7 +66,7 @@ std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, TypeId t
|
||||||
}
|
}
|
||||||
else if (const auto& itf = get<FunctionTypeVar>(index))
|
else if (const auto& itf = get<FunctionTypeVar>(index))
|
||||||
{
|
{
|
||||||
std::optional<TypeId> r = first(follow(itf->retType));
|
std::optional<TypeId> r = first(follow(itf->retTypes));
|
||||||
if (!r)
|
if (!r)
|
||||||
return getSingletonTypes().nilType;
|
return getSingletonTypes().nilType;
|
||||||
else
|
else
|
||||||
|
|
|
@ -29,8 +29,8 @@ LUAU_FASTFLAG(LuauNonCopyableTypeVarFields)
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
std::optional<ExprResult<TypePackId>> magicFunctionFormat(
|
std::optional<WithPredicate<TypePackId>> magicFunctionFormat(
|
||||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult);
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate);
|
||||||
|
|
||||||
TypeId follow(TypeId t)
|
TypeId follow(TypeId t)
|
||||||
{
|
{
|
||||||
|
@ -408,41 +408,48 @@ bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
FunctionTypeVar::FunctionTypeVar(TypePackId argTypes, TypePackId retType, std::optional<FunctionDefinition> defn, bool hasSelf)
|
BlockedTypeVar::BlockedTypeVar()
|
||||||
|
: index(++nextIndex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int BlockedTypeVar::nextIndex = 0;
|
||||||
|
|
||||||
|
FunctionTypeVar::FunctionTypeVar(TypePackId argTypes, TypePackId retTypes, std::optional<FunctionDefinition> defn, bool hasSelf)
|
||||||
: argTypes(argTypes)
|
: argTypes(argTypes)
|
||||||
, retType(retType)
|
, retTypes(retTypes)
|
||||||
, definition(std::move(defn))
|
, definition(std::move(defn))
|
||||||
, hasSelf(hasSelf)
|
, hasSelf(hasSelf)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
FunctionTypeVar::FunctionTypeVar(TypeLevel level, TypePackId argTypes, TypePackId retType, std::optional<FunctionDefinition> defn, bool hasSelf)
|
FunctionTypeVar::FunctionTypeVar(TypeLevel level, TypePackId argTypes, TypePackId retTypes, std::optional<FunctionDefinition> defn, bool hasSelf)
|
||||||
: level(level)
|
: level(level)
|
||||||
, argTypes(argTypes)
|
, argTypes(argTypes)
|
||||||
, retType(retType)
|
, retTypes(retTypes)
|
||||||
, definition(std::move(defn))
|
, definition(std::move(defn))
|
||||||
, hasSelf(hasSelf)
|
, hasSelf(hasSelf)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
FunctionTypeVar::FunctionTypeVar(std::vector<TypeId> generics, std::vector<TypePackId> genericPacks, TypePackId argTypes, TypePackId retType,
|
FunctionTypeVar::FunctionTypeVar(std::vector<TypeId> generics, std::vector<TypePackId> genericPacks, TypePackId argTypes, TypePackId retTypes,
|
||||||
std::optional<FunctionDefinition> defn, bool hasSelf)
|
std::optional<FunctionDefinition> defn, bool hasSelf)
|
||||||
: generics(generics)
|
: generics(generics)
|
||||||
, genericPacks(genericPacks)
|
, genericPacks(genericPacks)
|
||||||
, argTypes(argTypes)
|
, argTypes(argTypes)
|
||||||
, retType(retType)
|
, retTypes(retTypes)
|
||||||
, definition(std::move(defn))
|
, definition(std::move(defn))
|
||||||
, hasSelf(hasSelf)
|
, hasSelf(hasSelf)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
FunctionTypeVar::FunctionTypeVar(TypeLevel level, std::vector<TypeId> generics, std::vector<TypePackId> genericPacks, TypePackId argTypes,
|
FunctionTypeVar::FunctionTypeVar(TypeLevel level, std::vector<TypeId> generics, std::vector<TypePackId> genericPacks, TypePackId argTypes,
|
||||||
TypePackId retType, std::optional<FunctionDefinition> defn, bool hasSelf)
|
TypePackId retTypes, std::optional<FunctionDefinition> defn, bool hasSelf)
|
||||||
: level(level)
|
: level(level)
|
||||||
, generics(generics)
|
, generics(generics)
|
||||||
, genericPacks(genericPacks)
|
, genericPacks(genericPacks)
|
||||||
, argTypes(argTypes)
|
, argTypes(argTypes)
|
||||||
, retType(retType)
|
, retTypes(retTypes)
|
||||||
, definition(std::move(defn))
|
, definition(std::move(defn))
|
||||||
, hasSelf(hasSelf)
|
, hasSelf(hasSelf)
|
||||||
{
|
{
|
||||||
|
@ -488,7 +495,7 @@ bool areEqual(SeenSet& seen, const FunctionTypeVar& lhs, const FunctionTypeVar&
|
||||||
if (!areEqual(seen, *lhs.argTypes, *rhs.argTypes))
|
if (!areEqual(seen, *lhs.argTypes, *rhs.argTypes))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!areEqual(seen, *lhs.retType, *rhs.retType))
|
if (!areEqual(seen, *lhs.retTypes, *rhs.retTypes))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -678,7 +685,6 @@ static TypeVar trueType_{SingletonTypeVar{BooleanSingleton{true}}, /*persistent*
|
||||||
static TypeVar falseType_{SingletonTypeVar{BooleanSingleton{false}}, /*persistent*/ true};
|
static TypeVar falseType_{SingletonTypeVar{BooleanSingleton{false}}, /*persistent*/ true};
|
||||||
static TypeVar anyType_{AnyTypeVar{}, /*persistent*/ true};
|
static TypeVar anyType_{AnyTypeVar{}, /*persistent*/ true};
|
||||||
static TypeVar errorType_{ErrorTypeVar{}, /*persistent*/ true};
|
static TypeVar errorType_{ErrorTypeVar{}, /*persistent*/ true};
|
||||||
static TypeVar optionalNumberType_{UnionTypeVar{{&numberType_, &nilType_}}, /*persistent*/ true};
|
|
||||||
|
|
||||||
static TypePackVar anyTypePack_{VariadicTypePack{&anyType_}, true};
|
static TypePackVar anyTypePack_{VariadicTypePack{&anyType_}, true};
|
||||||
static TypePackVar errorTypePack_{Unifiable::Error{}};
|
static TypePackVar errorTypePack_{Unifiable::Error{}};
|
||||||
|
@ -692,7 +698,6 @@ SingletonTypes::SingletonTypes()
|
||||||
, trueType(&trueType_)
|
, trueType(&trueType_)
|
||||||
, falseType(&falseType_)
|
, falseType(&falseType_)
|
||||||
, anyType(&anyType_)
|
, anyType(&anyType_)
|
||||||
, optionalNumberType(&optionalNumberType_)
|
|
||||||
, anyTypePack(&anyTypePack_)
|
, anyTypePack(&anyTypePack_)
|
||||||
, arena(new TypeArena)
|
, arena(new TypeArena)
|
||||||
{
|
{
|
||||||
|
@ -825,7 +830,7 @@ void persist(TypeId ty)
|
||||||
else if (auto ftv = get<FunctionTypeVar>(t))
|
else if (auto ftv = get<FunctionTypeVar>(t))
|
||||||
{
|
{
|
||||||
persist(ftv->argTypes);
|
persist(ftv->argTypes);
|
||||||
persist(ftv->retType);
|
persist(ftv->retTypes);
|
||||||
}
|
}
|
||||||
else if (auto ttv = get<TableTypeVar>(t))
|
else if (auto ttv = get<TableTypeVar>(t))
|
||||||
{
|
{
|
||||||
|
@ -1100,10 +1105,10 @@ static std::vector<TypeId> parseFormatString(TypeChecker& typechecker, const cha
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<ExprResult<TypePackId>> magicFunctionFormat(
|
std::optional<WithPredicate<TypePackId>> magicFunctionFormat(
|
||||||
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult)
|
TypeChecker& typechecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
||||||
{
|
{
|
||||||
auto [paramPack, _predicates] = exprResult;
|
auto [paramPack, _predicates] = withPredicate;
|
||||||
|
|
||||||
TypeArena& arena = typechecker.currentModule->internalTypes;
|
TypeArena& arena = typechecker.currentModule->internalTypes;
|
||||||
|
|
||||||
|
@ -1142,7 +1147,7 @@ std::optional<ExprResult<TypePackId>> magicFunctionFormat(
|
||||||
if (expected.size() != actualParamSize && (!tail || expected.size() < actualParamSize))
|
if (expected.size() != actualParamSize && (!tail || expected.size() < actualParamSize))
|
||||||
typechecker.reportError(TypeError{expr.location, CountMismatch{expected.size(), actualParamSize}});
|
typechecker.reportError(TypeError{expr.location, CountMismatch{expected.size(), actualParamSize}});
|
||||||
|
|
||||||
return ExprResult<TypePackId>{arena.addTypePack({typechecker.stringType})};
|
return WithPredicate<TypePackId>{arena.addTypePack({typechecker.stringType})};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate)
|
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate)
|
||||||
|
|
|
@ -22,6 +22,7 @@ LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||||
LUAU_FASTFLAG(LuauErrorRecoveryType);
|
LUAU_FASTFLAG(LuauErrorRecoveryType);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauSubtypingAddOptPropsToUnsealedTables, false)
|
LUAU_FASTFLAGVARIABLE(LuauSubtypingAddOptPropsToUnsealedTables, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTxnLogRefreshFunctionPointers, false)
|
LUAU_FASTFLAGVARIABLE(LuauTxnLogRefreshFunctionPointers, false)
|
||||||
|
LUAU_FASTFLAG(LuauQuantifyConstrained)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -1288,13 +1289,13 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
|
||||||
reportError(TypeError{location, TypeMismatch{superTy, subTy, "", innerState.errors.front()}});
|
reportError(TypeError{location, TypeMismatch{superTy, subTy, "", innerState.errors.front()}});
|
||||||
|
|
||||||
innerState.ctx = CountMismatch::Result;
|
innerState.ctx = CountMismatch::Result;
|
||||||
innerState.tryUnify_(subFunction->retType, superFunction->retType);
|
innerState.tryUnify_(subFunction->retTypes, superFunction->retTypes);
|
||||||
|
|
||||||
if (!reported)
|
if (!reported)
|
||||||
{
|
{
|
||||||
if (auto e = hasUnificationTooComplex(innerState.errors))
|
if (auto e = hasUnificationTooComplex(innerState.errors))
|
||||||
reportError(*e);
|
reportError(*e);
|
||||||
else if (!innerState.errors.empty() && size(superFunction->retType) == 1 && finite(superFunction->retType))
|
else if (!innerState.errors.empty() && size(superFunction->retTypes) == 1 && finite(superFunction->retTypes))
|
||||||
reportError(TypeError{location, TypeMismatch{superTy, subTy, "Return type is not compatible.", innerState.errors.front()}});
|
reportError(TypeError{location, TypeMismatch{superTy, subTy, "Return type is not compatible.", innerState.errors.front()}});
|
||||||
else if (!innerState.errors.empty() && innerState.firstPackErrorPos)
|
else if (!innerState.errors.empty() && innerState.firstPackErrorPos)
|
||||||
reportError(
|
reportError(
|
||||||
|
@ -1312,7 +1313,7 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
|
||||||
tryUnify_(superFunction->argTypes, subFunction->argTypes, isFunctionCall);
|
tryUnify_(superFunction->argTypes, subFunction->argTypes, isFunctionCall);
|
||||||
|
|
||||||
ctx = CountMismatch::Result;
|
ctx = CountMismatch::Result;
|
||||||
tryUnify_(subFunction->retType, superFunction->retType);
|
tryUnify_(subFunction->retTypes, superFunction->retTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FFlag::LuauTxnLogRefreshFunctionPointers)
|
if (FFlag::LuauTxnLogRefreshFunctionPointers)
|
||||||
|
@ -2177,7 +2178,7 @@ static void tryUnifyWithAny(std::vector<TypeId>& queue, Unifier& state, DenseHas
|
||||||
else if (auto fun = state.log.getMutable<FunctionTypeVar>(ty))
|
else if (auto fun = state.log.getMutable<FunctionTypeVar>(ty))
|
||||||
{
|
{
|
||||||
queueTypePack(queue, seenTypePacks, state, fun->argTypes, anyTypePack);
|
queueTypePack(queue, seenTypePacks, state, fun->argTypes, anyTypePack);
|
||||||
queueTypePack(queue, seenTypePacks, state, fun->retType, anyTypePack);
|
queueTypePack(queue, seenTypePacks, state, fun->retTypes, anyTypePack);
|
||||||
}
|
}
|
||||||
else if (auto table = state.log.getMutable<TableTypeVar>(ty))
|
else if (auto table = state.log.getMutable<TableTypeVar>(ty))
|
||||||
{
|
{
|
||||||
|
@ -2322,7 +2323,7 @@ void Unifier::tryUnifyWithConstrainedSuperTypeVar(TypeId subTy, TypeId superTy)
|
||||||
superC->parts.push_back(subTy);
|
superC->parts.push_back(subTy);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unifier::unifyLowerBound(TypePackId subTy, TypePackId superTy)
|
void Unifier::unifyLowerBound(TypePackId subTy, TypePackId superTy, TypeLevel demotedLevel)
|
||||||
{
|
{
|
||||||
// The duplication between this and regular typepack unification is tragic.
|
// The duplication between this and regular typepack unification is tragic.
|
||||||
|
|
||||||
|
@ -2357,7 +2358,7 @@ void Unifier::unifyLowerBound(TypePackId subTy, TypePackId superTy)
|
||||||
if (!freeTailPack)
|
if (!freeTailPack)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
TypeLevel level = freeTailPack->level;
|
TypeLevel level = FFlag::LuauQuantifyConstrained ? demotedLevel : freeTailPack->level;
|
||||||
|
|
||||||
TypePack* tp = getMutable<TypePack>(log.replace(tailPack, TypePack{}));
|
TypePack* tp = getMutable<TypePack>(log.replace(tailPack, TypePack{}));
|
||||||
|
|
||||||
|
|
|
@ -1075,6 +1075,8 @@ void BytecodeBuilder::validate() const
|
||||||
LUAU_ASSERT(i <= insns.size());
|
LUAU_ASSERT(i <= insns.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> openCaptures;
|
||||||
|
|
||||||
// second pass: validate the rest of the bytecode
|
// second pass: validate the rest of the bytecode
|
||||||
for (size_t i = 0; i < insns.size();)
|
for (size_t i = 0; i < insns.size();)
|
||||||
{
|
{
|
||||||
|
@ -1121,6 +1123,8 @@ void BytecodeBuilder::validate() const
|
||||||
|
|
||||||
case LOP_CLOSEUPVALS:
|
case LOP_CLOSEUPVALS:
|
||||||
VREG(LUAU_INSN_A(insn));
|
VREG(LUAU_INSN_A(insn));
|
||||||
|
while (openCaptures.size() && openCaptures.back() >= LUAU_INSN_A(insn))
|
||||||
|
openCaptures.pop_back();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LOP_GETIMPORT:
|
case LOP_GETIMPORT:
|
||||||
|
@ -1388,8 +1392,12 @@ void BytecodeBuilder::validate() const
|
||||||
switch (LUAU_INSN_A(insn))
|
switch (LUAU_INSN_A(insn))
|
||||||
{
|
{
|
||||||
case LCT_VAL:
|
case LCT_VAL:
|
||||||
|
VREG(LUAU_INSN_B(insn));
|
||||||
|
break;
|
||||||
|
|
||||||
case LCT_REF:
|
case LCT_REF:
|
||||||
VREG(LUAU_INSN_B(insn));
|
VREG(LUAU_INSN_B(insn));
|
||||||
|
openCaptures.push_back(LUAU_INSN_B(insn));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LCT_UPVAL:
|
case LCT_UPVAL:
|
||||||
|
@ -1409,6 +1417,12 @@ void BytecodeBuilder::validate() const
|
||||||
LUAU_ASSERT(i <= insns.size());
|
LUAU_ASSERT(i <= insns.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// all CAPTURE REF instructions must have a CLOSEUPVALS instruction after them in the bytecode stream
|
||||||
|
// this doesn't guarantee safety as it doesn't perform basic block based analysis, but if this fails
|
||||||
|
// then the bytecode is definitely unsafe to run since the compiler won't generate backwards branches
|
||||||
|
// except for loop edges
|
||||||
|
LUAU_ASSERT(openCaptures.empty());
|
||||||
|
|
||||||
#undef VREG
|
#undef VREG
|
||||||
#undef VREGEND
|
#undef VREGEND
|
||||||
#undef VUPVAL
|
#undef VUPVAL
|
||||||
|
|
|
@ -246,6 +246,14 @@ struct Compiler
|
||||||
f.canInline = true;
|
f.canInline = true;
|
||||||
f.stackSize = stackSize;
|
f.stackSize = stackSize;
|
||||||
f.costModel = modelCost(func->body, func->args.data, func->args.size);
|
f.costModel = modelCost(func->body, func->args.data, func->args.size);
|
||||||
|
|
||||||
|
// track functions that only ever return a single value so that we can convert multret calls to fixedret calls
|
||||||
|
if (allPathsEndWithReturn(func->body))
|
||||||
|
{
|
||||||
|
ReturnVisitor returnVisitor(this);
|
||||||
|
stat->visit(&returnVisitor);
|
||||||
|
f.returnsOne = returnVisitor.returnsOne;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
upvals.clear(); // note: instead of std::move above, we copy & clear to preserve capacity for future pushes
|
upvals.clear(); // note: instead of std::move above, we copy & clear to preserve capacity for future pushes
|
||||||
|
@ -260,6 +268,19 @@ struct Compiler
|
||||||
{
|
{
|
||||||
if (AstExprCall* expr = node->as<AstExprCall>())
|
if (AstExprCall* expr = node->as<AstExprCall>())
|
||||||
{
|
{
|
||||||
|
// Optimization: convert multret calls to functions that always return one value to fixedret calls; this facilitates inlining
|
||||||
|
if (options.optimizationLevel >= 2)
|
||||||
|
{
|
||||||
|
AstExprFunction* func = getFunctionExpr(expr->func);
|
||||||
|
Function* fi = func ? functions.find(func) : nullptr;
|
||||||
|
|
||||||
|
if (fi && fi->returnsOne)
|
||||||
|
{
|
||||||
|
compileExprTemp(node, target);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We temporarily swap out regTop to have targetTop work correctly...
|
// We temporarily swap out regTop to have targetTop work correctly...
|
||||||
// This is a crude hack but it's necessary for correctness :(
|
// This is a crude hack but it's necessary for correctness :(
|
||||||
RegScope rs(this, target);
|
RegScope rs(this, target);
|
||||||
|
@ -447,7 +468,9 @@ struct Compiler
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: we can compile multret functions if all returns of the function are multret as well
|
// we can't inline multret functions because the caller expects L->top to be adjusted:
|
||||||
|
// - inlined return compiles to a JUMP, and we don't have an instruction that adjusts L->top arbitrarily
|
||||||
|
// - even if we did, right now all L->top adjustments are immediately consumed by the next instruction, and for now we want to preserve that
|
||||||
if (multRet)
|
if (multRet)
|
||||||
{
|
{
|
||||||
bytecode.addDebugRemark("inlining failed: can't convert fixed returns to multret");
|
bytecode.addDebugRemark("inlining failed: can't convert fixed returns to multret");
|
||||||
|
@ -492,7 +515,7 @@ struct Compiler
|
||||||
size_t oldLocals = localStack.size();
|
size_t oldLocals = localStack.size();
|
||||||
|
|
||||||
// note that we push the frame early; this is needed to block recursive inline attempts
|
// note that we push the frame early; this is needed to block recursive inline attempts
|
||||||
inlineFrames.push_back({func, target, targetCount});
|
inlineFrames.push_back({func, oldLocals, target, targetCount});
|
||||||
|
|
||||||
// evaluate all arguments; note that we don't emit code for constant arguments (relying on constant folding)
|
// evaluate all arguments; note that we don't emit code for constant arguments (relying on constant folding)
|
||||||
for (size_t i = 0; i < func->args.size; ++i)
|
for (size_t i = 0; i < func->args.size; ++i)
|
||||||
|
@ -593,6 +616,8 @@ struct Compiler
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < targetCount; ++i)
|
for (size_t i = 0; i < targetCount; ++i)
|
||||||
bytecode.emitABC(LOP_LOADNIL, uint8_t(target + i), 0, 0);
|
bytecode.emitABC(LOP_LOADNIL, uint8_t(target + i), 0, 0);
|
||||||
|
|
||||||
|
closeLocals(oldLocals);
|
||||||
}
|
}
|
||||||
|
|
||||||
popLocals(oldLocals);
|
popLocals(oldLocals);
|
||||||
|
@ -2355,6 +2380,8 @@ struct Compiler
|
||||||
|
|
||||||
compileExprListTemp(stat->list, frame.target, frame.targetCount, /* targetTop= */ false);
|
compileExprListTemp(stat->list, frame.target, frame.targetCount, /* targetTop= */ false);
|
||||||
|
|
||||||
|
closeLocals(frame.localOffset);
|
||||||
|
|
||||||
if (!fallthrough)
|
if (!fallthrough)
|
||||||
{
|
{
|
||||||
size_t jumpLabel = bytecode.emitLabel();
|
size_t jumpLabel = bytecode.emitLabel();
|
||||||
|
@ -3316,6 +3343,48 @@ struct Compiler
|
||||||
std::vector<AstLocal*> upvals;
|
std::vector<AstLocal*> upvals;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ReturnVisitor: AstVisitor
|
||||||
|
{
|
||||||
|
Compiler* self;
|
||||||
|
bool returnsOne = true;
|
||||||
|
|
||||||
|
ReturnVisitor(Compiler* self)
|
||||||
|
: self(self)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstExpr* expr) override
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstStatReturn* stat) override
|
||||||
|
{
|
||||||
|
if (stat->list.size == 1)
|
||||||
|
{
|
||||||
|
AstExpr* value = stat->list.data[0];
|
||||||
|
|
||||||
|
if (AstExprCall* expr = value->as<AstExprCall>())
|
||||||
|
{
|
||||||
|
AstExprFunction* func = self->getFunctionExpr(expr->func);
|
||||||
|
Function* fi = func ? self->functions.find(func) : nullptr;
|
||||||
|
|
||||||
|
returnsOne &= fi && fi->returnsOne;
|
||||||
|
}
|
||||||
|
else if (value->is<AstExprVarargs>())
|
||||||
|
{
|
||||||
|
returnsOne = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
returnsOne = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct RegScope
|
struct RegScope
|
||||||
{
|
{
|
||||||
RegScope(Compiler* self)
|
RegScope(Compiler* self)
|
||||||
|
@ -3351,6 +3420,7 @@ struct Compiler
|
||||||
uint64_t costModel = 0;
|
uint64_t costModel = 0;
|
||||||
unsigned int stackSize = 0;
|
unsigned int stackSize = 0;
|
||||||
bool canInline = false;
|
bool canInline = false;
|
||||||
|
bool returnsOne = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Local
|
struct Local
|
||||||
|
@ -3384,6 +3454,8 @@ struct Compiler
|
||||||
{
|
{
|
||||||
AstExprFunction* func;
|
AstExprFunction* func;
|
||||||
|
|
||||||
|
size_t localOffset;
|
||||||
|
|
||||||
uint8_t target;
|
uint8_t target;
|
||||||
uint8_t targetCount;
|
uint8_t targetCount;
|
||||||
|
|
||||||
|
|
|
@ -65,12 +65,13 @@ target_sources(Luau.CodeGen PRIVATE
|
||||||
target_sources(Luau.Analysis PRIVATE
|
target_sources(Luau.Analysis PRIVATE
|
||||||
Analysis/include/Luau/AstQuery.h
|
Analysis/include/Luau/AstQuery.h
|
||||||
Analysis/include/Luau/Autocomplete.h
|
Analysis/include/Luau/Autocomplete.h
|
||||||
Analysis/include/Luau/NotNull.h
|
|
||||||
Analysis/include/Luau/BuiltinDefinitions.h
|
Analysis/include/Luau/BuiltinDefinitions.h
|
||||||
Analysis/include/Luau/Clone.h
|
Analysis/include/Luau/Clone.h
|
||||||
Analysis/include/Luau/Config.h
|
Analysis/include/Luau/Config.h
|
||||||
|
Analysis/include/Luau/Constraint.h
|
||||||
Analysis/include/Luau/ConstraintGraphBuilder.h
|
Analysis/include/Luau/ConstraintGraphBuilder.h
|
||||||
Analysis/include/Luau/ConstraintSolver.h
|
Analysis/include/Luau/ConstraintSolver.h
|
||||||
|
Analysis/include/Luau/ConstraintSolverLogger.h
|
||||||
Analysis/include/Luau/Documentation.h
|
Analysis/include/Luau/Documentation.h
|
||||||
Analysis/include/Luau/Error.h
|
Analysis/include/Luau/Error.h
|
||||||
Analysis/include/Luau/FileResolver.h
|
Analysis/include/Luau/FileResolver.h
|
||||||
|
@ -97,6 +98,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||||
Analysis/include/Luau/TxnLog.h
|
Analysis/include/Luau/TxnLog.h
|
||||||
Analysis/include/Luau/TypeArena.h
|
Analysis/include/Luau/TypeArena.h
|
||||||
Analysis/include/Luau/TypeAttach.h
|
Analysis/include/Luau/TypeAttach.h
|
||||||
|
Analysis/include/Luau/TypeChecker2.h
|
||||||
Analysis/include/Luau/TypedAllocator.h
|
Analysis/include/Luau/TypedAllocator.h
|
||||||
Analysis/include/Luau/TypeInfer.h
|
Analysis/include/Luau/TypeInfer.h
|
||||||
Analysis/include/Luau/TypePack.h
|
Analysis/include/Luau/TypePack.h
|
||||||
|
@ -113,8 +115,10 @@ target_sources(Luau.Analysis PRIVATE
|
||||||
Analysis/src/BuiltinDefinitions.cpp
|
Analysis/src/BuiltinDefinitions.cpp
|
||||||
Analysis/src/Clone.cpp
|
Analysis/src/Clone.cpp
|
||||||
Analysis/src/Config.cpp
|
Analysis/src/Config.cpp
|
||||||
|
Analysis/src/Constraint.cpp
|
||||||
Analysis/src/ConstraintGraphBuilder.cpp
|
Analysis/src/ConstraintGraphBuilder.cpp
|
||||||
Analysis/src/ConstraintSolver.cpp
|
Analysis/src/ConstraintSolver.cpp
|
||||||
|
Analysis/src/ConstraintSolverLogger.cpp
|
||||||
Analysis/src/Error.cpp
|
Analysis/src/Error.cpp
|
||||||
Analysis/src/Frontend.cpp
|
Analysis/src/Frontend.cpp
|
||||||
Analysis/src/Instantiation.cpp
|
Analysis/src/Instantiation.cpp
|
||||||
|
@ -136,6 +140,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||||
Analysis/src/TxnLog.cpp
|
Analysis/src/TxnLog.cpp
|
||||||
Analysis/src/TypeArena.cpp
|
Analysis/src/TypeArena.cpp
|
||||||
Analysis/src/TypeAttach.cpp
|
Analysis/src/TypeAttach.cpp
|
||||||
|
Analysis/src/TypeChecker2.cpp
|
||||||
Analysis/src/TypedAllocator.cpp
|
Analysis/src/TypedAllocator.cpp
|
||||||
Analysis/src/TypeInfer.cpp
|
Analysis/src/TypeInfer.cpp
|
||||||
Analysis/src/TypePack.cpp
|
Analysis/src/TypePack.cpp
|
||||||
|
@ -245,7 +250,6 @@ if(TARGET Luau.UnitTest)
|
||||||
tests/AstQuery.test.cpp
|
tests/AstQuery.test.cpp
|
||||||
tests/AstVisitor.test.cpp
|
tests/AstVisitor.test.cpp
|
||||||
tests/Autocomplete.test.cpp
|
tests/Autocomplete.test.cpp
|
||||||
tests/NotNull.test.cpp
|
|
||||||
tests/BuiltinDefinitions.test.cpp
|
tests/BuiltinDefinitions.test.cpp
|
||||||
tests/Compiler.test.cpp
|
tests/Compiler.test.cpp
|
||||||
tests/Config.test.cpp
|
tests/Config.test.cpp
|
||||||
|
|
|
@ -418,7 +418,7 @@ typedef struct Table
|
||||||
CommonHeader;
|
CommonHeader;
|
||||||
|
|
||||||
|
|
||||||
uint8_t flags; /* 1<<p means tagmethod(p) is not present */
|
uint8_t tmcache; /* 1<<p means tagmethod(p) is not present */
|
||||||
uint8_t readonly; /* sandboxing feature to prohibit writes to table */
|
uint8_t readonly; /* sandboxing feature to prohibit writes to table */
|
||||||
uint8_t safeenv; /* environment doesn't share globals with other scripts */
|
uint8_t safeenv; /* environment doesn't share globals with other scripts */
|
||||||
uint8_t lsizenode; /* log2 of size of `node' array */
|
uint8_t lsizenode; /* log2 of size of `node' array */
|
||||||
|
|
|
@ -45,7 +45,7 @@ static_assert(TKey{{NULL}, {0}, LUA_TNIL, MAXSIZE - 1}.next == MAXSIZE - 1, "not
|
||||||
static_assert(TKey{{NULL}, {0}, LUA_TNIL, -(MAXSIZE - 1)}.next == -(MAXSIZE - 1), "not enough bits for next");
|
static_assert(TKey{{NULL}, {0}, LUA_TNIL, -(MAXSIZE - 1)}.next == -(MAXSIZE - 1), "not enough bits for next");
|
||||||
|
|
||||||
// reset cache of absent metamethods, cache is updated in luaT_gettm
|
// reset cache of absent metamethods, cache is updated in luaT_gettm
|
||||||
#define invalidateTMcache(t) t->flags = 0
|
#define invalidateTMcache(t) t->tmcache = 0
|
||||||
|
|
||||||
// empty hash data points to dummynode so that we can always dereference it
|
// empty hash data points to dummynode so that we can always dereference it
|
||||||
const LuaNode luaH_dummynode = {
|
const LuaNode luaH_dummynode = {
|
||||||
|
@ -479,7 +479,7 @@ Table* luaH_new(lua_State* L, int narray, int nhash)
|
||||||
Table* t = luaM_newgco(L, Table, sizeof(Table), L->activememcat);
|
Table* t = luaM_newgco(L, Table, sizeof(Table), L->activememcat);
|
||||||
luaC_init(L, t, LUA_TTABLE);
|
luaC_init(L, t, LUA_TTABLE);
|
||||||
t->metatable = NULL;
|
t->metatable = NULL;
|
||||||
t->flags = cast_byte(~0);
|
t->tmcache = cast_byte(~0);
|
||||||
t->array = NULL;
|
t->array = NULL;
|
||||||
t->sizearray = 0;
|
t->sizearray = 0;
|
||||||
t->lastfree = 0;
|
t->lastfree = 0;
|
||||||
|
@ -778,7 +778,7 @@ Table* luaH_clone(lua_State* L, Table* tt)
|
||||||
Table* t = luaM_newgco(L, Table, sizeof(Table), L->activememcat);
|
Table* t = luaM_newgco(L, Table, sizeof(Table), L->activememcat);
|
||||||
luaC_init(L, t, LUA_TTABLE);
|
luaC_init(L, t, LUA_TTABLE);
|
||||||
t->metatable = tt->metatable;
|
t->metatable = tt->metatable;
|
||||||
t->flags = tt->flags;
|
t->tmcache = tt->tmcache;
|
||||||
t->array = NULL;
|
t->array = NULL;
|
||||||
t->sizearray = 0;
|
t->sizearray = 0;
|
||||||
t->lsizenode = 0;
|
t->lsizenode = 0;
|
||||||
|
@ -835,5 +835,5 @@ void luaH_clear(Table* tt)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* back to empty -> no tag methods present */
|
/* back to empty -> no tag methods present */
|
||||||
tt->flags = cast_byte(~0);
|
tt->tmcache = cast_byte(~0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,8 +88,8 @@ const TValue* luaT_gettm(Table* events, TMS event, TString* ename)
|
||||||
const TValue* tm = luaH_getstr(events, ename);
|
const TValue* tm = luaH_getstr(events, ename);
|
||||||
LUAU_ASSERT(event <= TM_EQ);
|
LUAU_ASSERT(event <= TM_EQ);
|
||||||
if (ttisnil(tm))
|
if (ttisnil(tm))
|
||||||
{ /* no tag method? */
|
{ /* no tag method? */
|
||||||
events->flags |= cast_byte(1u << event); /* cache this fact */
|
events->tmcache |= cast_byte(1u << event); /* cache this fact */
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -41,10 +41,10 @@ typedef enum
|
||||||
} TMS;
|
} TMS;
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
#define gfasttm(g, et, e) ((et) == NULL ? NULL : ((et)->flags & (1u << (e))) ? NULL : luaT_gettm(et, e, (g)->tmname[e]))
|
#define gfasttm(g, et, e) ((et) == NULL ? NULL : ((et)->tmcache & (1u << (e))) ? NULL : luaT_gettm(et, e, (g)->tmname[e]))
|
||||||
|
|
||||||
#define fasttm(l, et, e) gfasttm(l->global, et, e)
|
#define fasttm(l, et, e) gfasttm(l->global, et, e)
|
||||||
#define fastnotm(et, e) ((et) == NULL || ((et)->flags & (1u << (e))))
|
#define fastnotm(et, e) ((et) == NULL || ((et)->tmcache & (1u << (e))))
|
||||||
|
|
||||||
LUAI_DATA const char* const luaT_typenames[];
|
LUAI_DATA const char* const luaT_typenames[];
|
||||||
LUAI_DATA const char* const luaT_eventname[];
|
LUAI_DATA const char* const luaT_eventname[];
|
||||||
|
|
|
@ -1992,6 +1992,7 @@ local fp: @1= f
|
||||||
|
|
||||||
auto ac = autocomplete('1');
|
auto ac = autocomplete('1');
|
||||||
|
|
||||||
|
REQUIRE_EQ("({| x: number, y: number |}) -> number", toString(requireType("f")));
|
||||||
CHECK(ac.entryMap.count("({ x: number, y: number }) -> number"));
|
CHECK(ac.entryMap.count("({ x: number, y: number }) -> number"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2620,7 +2621,6 @@ a = if temp then even elseif true then temp else e@9
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ACFixture, "autocomplete_if_else_regression")
|
TEST_CASE_FIXTURE(ACFixture, "autocomplete_if_else_regression")
|
||||||
{
|
{
|
||||||
ScopedFastFlag FFlagLuauIfElseExprFixCompletionIssue("LuauIfElseExprFixCompletionIssue", true);
|
|
||||||
check(R"(
|
check(R"(
|
||||||
local abcdef = 0;
|
local abcdef = 0;
|
||||||
local temp = false
|
local temp = false
|
||||||
|
|
|
@ -4992,6 +4992,147 @@ RETURN R1 1
|
||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("InlineCapture")
|
||||||
|
{
|
||||||
|
// if the argument is captured by a nested closure, normally we can rely on capture by value
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
local function foo(a)
|
||||||
|
return function() return a end
|
||||||
|
end
|
||||||
|
|
||||||
|
local x = ...
|
||||||
|
local y = foo(x)
|
||||||
|
return y
|
||||||
|
)",
|
||||||
|
2, 2),
|
||||||
|
R"(
|
||||||
|
DUPCLOSURE R0 K0
|
||||||
|
GETVARARGS R1 1
|
||||||
|
NEWCLOSURE R2 P1
|
||||||
|
CAPTURE VAL R1
|
||||||
|
RETURN R2 1
|
||||||
|
)");
|
||||||
|
|
||||||
|
// if the argument is a constant, we move it to a register so that capture by value can happen
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
local function foo(a)
|
||||||
|
return function() return a end
|
||||||
|
end
|
||||||
|
|
||||||
|
local y = foo(42)
|
||||||
|
return y
|
||||||
|
)",
|
||||||
|
2, 2),
|
||||||
|
R"(
|
||||||
|
DUPCLOSURE R0 K0
|
||||||
|
LOADN R2 42
|
||||||
|
NEWCLOSURE R1 P1
|
||||||
|
CAPTURE VAL R2
|
||||||
|
RETURN R1 1
|
||||||
|
)");
|
||||||
|
|
||||||
|
// if the argument is an externally mutated variable, we copy it to an argument and capture it by value
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
local function foo(a)
|
||||||
|
return function() return a end
|
||||||
|
end
|
||||||
|
|
||||||
|
local x x = 42
|
||||||
|
local y = foo(x)
|
||||||
|
return y
|
||||||
|
)",
|
||||||
|
2, 2),
|
||||||
|
R"(
|
||||||
|
DUPCLOSURE R0 K0
|
||||||
|
LOADNIL R1
|
||||||
|
LOADN R1 42
|
||||||
|
MOVE R3 R1
|
||||||
|
NEWCLOSURE R2 P1
|
||||||
|
CAPTURE VAL R3
|
||||||
|
RETURN R2 1
|
||||||
|
)");
|
||||||
|
|
||||||
|
// finally, if the argument is mutated internally, we must capture it by reference and close the upvalue
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
local function foo(a)
|
||||||
|
a = a or 42
|
||||||
|
return function() return a end
|
||||||
|
end
|
||||||
|
|
||||||
|
local y = foo()
|
||||||
|
return y
|
||||||
|
)",
|
||||||
|
2, 2),
|
||||||
|
R"(
|
||||||
|
DUPCLOSURE R0 K0
|
||||||
|
LOADNIL R2
|
||||||
|
ORK R2 R2 K1
|
||||||
|
NEWCLOSURE R1 P1
|
||||||
|
CAPTURE REF R2
|
||||||
|
CLOSEUPVALS R2
|
||||||
|
RETURN R1 1
|
||||||
|
)");
|
||||||
|
|
||||||
|
// note that capture might need to be performed during the fallthrough block
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
local function foo(a)
|
||||||
|
a = a or 42
|
||||||
|
print(function() return a end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local x = ...
|
||||||
|
local y = foo(x)
|
||||||
|
return y
|
||||||
|
)",
|
||||||
|
2, 2),
|
||||||
|
R"(
|
||||||
|
DUPCLOSURE R0 K0
|
||||||
|
GETVARARGS R1 1
|
||||||
|
MOVE R3 R1
|
||||||
|
ORK R3 R3 K1
|
||||||
|
GETIMPORT R4 3
|
||||||
|
NEWCLOSURE R5 P1
|
||||||
|
CAPTURE REF R3
|
||||||
|
CALL R4 1 0
|
||||||
|
LOADNIL R2
|
||||||
|
CLOSEUPVALS R3
|
||||||
|
RETURN R2 1
|
||||||
|
)");
|
||||||
|
|
||||||
|
// note that mutation and capture might be inside internal control flow
|
||||||
|
// TODO: this has an oddly redundant CLOSEUPVALS after JUMP; it's not due to inlining, and is an artifact of how StatBlock/StatReturn interact
|
||||||
|
// fixing this would reduce the number of redundant CLOSEUPVALS a bit but it only affects bytecode size as these instructions aren't executed
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
local function foo(a)
|
||||||
|
if not a then
|
||||||
|
local b b = 42
|
||||||
|
return function() return b end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local x = ...
|
||||||
|
local y = foo(x)
|
||||||
|
return y, x
|
||||||
|
)",
|
||||||
|
2, 2),
|
||||||
|
R"(
|
||||||
|
DUPCLOSURE R0 K0
|
||||||
|
GETVARARGS R1 1
|
||||||
|
JUMPIF R1 L0
|
||||||
|
LOADNIL R3
|
||||||
|
LOADN R3 42
|
||||||
|
NEWCLOSURE R2 P1
|
||||||
|
CAPTURE REF R3
|
||||||
|
CLOSEUPVALS R3
|
||||||
|
JUMP L1
|
||||||
|
CLOSEUPVALS R3
|
||||||
|
L0: LOADNIL R2
|
||||||
|
L1: MOVE R3 R2
|
||||||
|
MOVE R4 R1
|
||||||
|
RETURN R3 2
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("InlineFallthrough")
|
TEST_CASE("InlineFallthrough")
|
||||||
{
|
{
|
||||||
// if the function doesn't return, we still fill the results with nil
|
// if the function doesn't return, we still fill the results with nil
|
||||||
|
@ -5044,27 +5185,6 @@ RETURN R1 -1
|
||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("InlineCapture")
|
|
||||||
{
|
|
||||||
// can't inline function with nested functions that capture locals because they might be constants
|
|
||||||
CHECK_EQ("\n" + compileFunction(R"(
|
|
||||||
local function foo(a)
|
|
||||||
local function bar()
|
|
||||||
return a
|
|
||||||
end
|
|
||||||
return bar()
|
|
||||||
end
|
|
||||||
)",
|
|
||||||
1, 2),
|
|
||||||
R"(
|
|
||||||
NEWCLOSURE R1 P0
|
|
||||||
CAPTURE VAL R0
|
|
||||||
MOVE R2 R1
|
|
||||||
CALL R2 0 -1
|
|
||||||
RETURN R2 -1
|
|
||||||
)");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("InlineArgMismatch")
|
TEST_CASE("InlineArgMismatch")
|
||||||
{
|
{
|
||||||
// when inlining a function, we must respect all the usual rules
|
// when inlining a function, we must respect all the usual rules
|
||||||
|
@ -5491,6 +5611,96 @@ RETURN R2 1
|
||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("InlineMultret")
|
||||||
|
{
|
||||||
|
// inlining a function in multret context is prohibited since we can't adjust L->top outside of CALL/GETVARARGS
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
local function foo(a)
|
||||||
|
return a()
|
||||||
|
end
|
||||||
|
|
||||||
|
return foo(42)
|
||||||
|
)",
|
||||||
|
1, 2),
|
||||||
|
R"(
|
||||||
|
DUPCLOSURE R0 K0
|
||||||
|
MOVE R1 R0
|
||||||
|
LOADN R2 42
|
||||||
|
CALL R1 1 -1
|
||||||
|
RETURN R1 -1
|
||||||
|
)");
|
||||||
|
|
||||||
|
// however, if we can deduce statically that a function always returns a single value, the inlining will work
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
local function foo(a)
|
||||||
|
return a
|
||||||
|
end
|
||||||
|
|
||||||
|
return foo(42)
|
||||||
|
)",
|
||||||
|
1, 2),
|
||||||
|
R"(
|
||||||
|
DUPCLOSURE R0 K0
|
||||||
|
LOADN R1 42
|
||||||
|
RETURN R1 1
|
||||||
|
)");
|
||||||
|
|
||||||
|
// this analysis will also propagate through other functions
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
local function foo(a)
|
||||||
|
return a
|
||||||
|
end
|
||||||
|
|
||||||
|
local function bar(a)
|
||||||
|
return foo(a)
|
||||||
|
end
|
||||||
|
|
||||||
|
return bar(42)
|
||||||
|
)",
|
||||||
|
2, 2),
|
||||||
|
R"(
|
||||||
|
DUPCLOSURE R0 K0
|
||||||
|
DUPCLOSURE R1 K1
|
||||||
|
LOADN R2 42
|
||||||
|
RETURN R2 1
|
||||||
|
)");
|
||||||
|
|
||||||
|
// we currently don't do this analysis fully for recursive functions since they can't be inlined anyway
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
local function foo(a)
|
||||||
|
return foo(a)
|
||||||
|
end
|
||||||
|
|
||||||
|
return foo(42)
|
||||||
|
)",
|
||||||
|
1, 2),
|
||||||
|
R"(
|
||||||
|
DUPCLOSURE R0 K0
|
||||||
|
CAPTURE VAL R0
|
||||||
|
MOVE R1 R0
|
||||||
|
LOADN R2 42
|
||||||
|
CALL R1 1 -1
|
||||||
|
RETURN R1 -1
|
||||||
|
)");
|
||||||
|
|
||||||
|
// and unfortunately we can't do this analysis for builtins or method calls due to getfenv
|
||||||
|
CHECK_EQ("\n" + compileFunction(R"(
|
||||||
|
local function foo(a)
|
||||||
|
return math.abs(a)
|
||||||
|
end
|
||||||
|
|
||||||
|
return foo(42)
|
||||||
|
)",
|
||||||
|
1, 2),
|
||||||
|
R"(
|
||||||
|
DUPCLOSURE R0 K0
|
||||||
|
MOVE R1 R0
|
||||||
|
LOADN R2 42
|
||||||
|
CALL R1 1 -1
|
||||||
|
RETURN R1 -1
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("ReturnConsecutive")
|
TEST_CASE("ReturnConsecutive")
|
||||||
{
|
{
|
||||||
// we can return a single local directly
|
// we can return a single local directly
|
||||||
|
|
|
@ -17,13 +17,13 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "hello_world")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
cgb.visit(block);
|
cgb.visit(block);
|
||||||
std::vector<const Constraint*> constraints = collectConstraints(cgb.rootScope);
|
auto constraints = collectConstraints(cgb.rootScope);
|
||||||
|
|
||||||
REQUIRE(2 == constraints.size());
|
REQUIRE(2 == constraints.size());
|
||||||
|
|
||||||
ToStringOptions opts;
|
ToStringOptions opts;
|
||||||
CHECK("a <: string" == toString(*constraints[0], opts));
|
CHECK("string <: a" == toString(*constraints[0], opts));
|
||||||
CHECK("b <: a" == toString(*constraints[1], opts));
|
CHECK("a <: b" == toString(*constraints[1], opts));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "primitives")
|
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "primitives")
|
||||||
|
@ -36,15 +36,34 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "primitives")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
cgb.visit(block);
|
cgb.visit(block);
|
||||||
std::vector<const Constraint*> constraints = collectConstraints(cgb.rootScope);
|
auto constraints = collectConstraints(cgb.rootScope);
|
||||||
|
|
||||||
REQUIRE(4 == constraints.size());
|
REQUIRE(3 == constraints.size());
|
||||||
|
|
||||||
ToStringOptions opts;
|
ToStringOptions opts;
|
||||||
CHECK("a <: string" == toString(*constraints[0], opts));
|
CHECK("string <: a" == toString(*constraints[0], opts));
|
||||||
CHECK("b <: number" == toString(*constraints[1], opts));
|
CHECK("number <: b" == toString(*constraints[1], opts));
|
||||||
CHECK("c <: boolean" == toString(*constraints[2], opts));
|
CHECK("boolean <: c" == toString(*constraints[2], opts));
|
||||||
CHECK("d <: nil" == toString(*constraints[3], opts));
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "nil_primitive")
|
||||||
|
{
|
||||||
|
AstStatBlock* block = parse(R"(
|
||||||
|
local function a() return nil end
|
||||||
|
local b = a()
|
||||||
|
)");
|
||||||
|
|
||||||
|
cgb.visit(block);
|
||||||
|
auto constraints = collectConstraints(cgb.rootScope);
|
||||||
|
|
||||||
|
ToStringOptions opts;
|
||||||
|
REQUIRE(5 <= constraints.size());
|
||||||
|
|
||||||
|
CHECK("*blocked-1* ~ gen () -> (a...)" == toString(*constraints[0], opts));
|
||||||
|
CHECK("b ~ inst *blocked-1*" == toString(*constraints[1], opts));
|
||||||
|
CHECK("() -> (c...) <: b" == toString(*constraints[2], opts));
|
||||||
|
CHECK("c... <: d" == toString(*constraints[3], opts));
|
||||||
|
CHECK("nil <: a..." == toString(*constraints[4], opts));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "function_application")
|
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "function_application")
|
||||||
|
@ -55,15 +74,15 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "function_application")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
cgb.visit(block);
|
cgb.visit(block);
|
||||||
std::vector<const Constraint*> constraints = collectConstraints(cgb.rootScope);
|
auto constraints = collectConstraints(cgb.rootScope);
|
||||||
|
|
||||||
REQUIRE(4 == constraints.size());
|
REQUIRE(4 == constraints.size());
|
||||||
|
|
||||||
ToStringOptions opts;
|
ToStringOptions opts;
|
||||||
CHECK("a <: string" == toString(*constraints[0], opts));
|
CHECK("string <: a" == toString(*constraints[0], opts));
|
||||||
CHECK("b ~ inst a" == toString(*constraints[1], opts));
|
CHECK("b ~ inst a" == toString(*constraints[1], opts));
|
||||||
CHECK("(string) -> (c, d...) <: b" == toString(*constraints[2], opts));
|
CHECK("(string) -> (c...) <: b" == toString(*constraints[2], opts));
|
||||||
CHECK("e <: c" == toString(*constraints[3], opts));
|
CHECK("c... <: d" == toString(*constraints[3], opts));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "local_function_definition")
|
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "local_function_definition")
|
||||||
|
@ -75,13 +94,13 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "local_function_definition")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
cgb.visit(block);
|
cgb.visit(block);
|
||||||
std::vector<const Constraint*> constraints = collectConstraints(cgb.rootScope);
|
auto constraints = collectConstraints(cgb.rootScope);
|
||||||
|
|
||||||
REQUIRE(2 == constraints.size());
|
REQUIRE(2 == constraints.size());
|
||||||
|
|
||||||
ToStringOptions opts;
|
ToStringOptions opts;
|
||||||
CHECK("a ~ gen (b) -> (c...)" == toString(*constraints[0], opts));
|
CHECK("*blocked-1* ~ gen (a) -> (b...)" == toString(*constraints[0], opts));
|
||||||
CHECK("b <: c..." == toString(*constraints[1], opts));
|
CHECK("a <: b..." == toString(*constraints[1], opts));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "recursive_function")
|
TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "recursive_function")
|
||||||
|
@ -93,15 +112,15 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "recursive_function")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
cgb.visit(block);
|
cgb.visit(block);
|
||||||
std::vector<const Constraint*> constraints = collectConstraints(cgb.rootScope);
|
auto constraints = collectConstraints(cgb.rootScope);
|
||||||
|
|
||||||
REQUIRE(4 == constraints.size());
|
REQUIRE(4 == constraints.size());
|
||||||
|
|
||||||
ToStringOptions opts;
|
ToStringOptions opts;
|
||||||
CHECK("a ~ gen (b) -> (c...)" == toString(*constraints[0], opts));
|
CHECK("*blocked-1* ~ gen (a) -> (b...)" == toString(*constraints[0], opts));
|
||||||
CHECK("d ~ inst a" == toString(*constraints[1], opts));
|
CHECK("c ~ inst (a) -> (b...)" == toString(*constraints[1], opts));
|
||||||
CHECK("(b) -> (e, f...) <: d" == toString(*constraints[2], opts));
|
CHECK("(a) -> (d...) <: c" == toString(*constraints[2], opts));
|
||||||
CHECK("e <: c..." == toString(*constraints[3], opts));
|
CHECK("d... <: b..." == toString(*constraints[3], opts));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -345,7 +345,7 @@ void Fixture::dumpErrors(std::ostream& os, const std::vector<TypeError>& errors)
|
||||||
if (error.location.begin.line >= lines.size())
|
if (error.location.begin.line >= lines.size())
|
||||||
{
|
{
|
||||||
os << "\tSource not available?" << std::endl;
|
os << "\tSource not available?" << std::endl;
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string_view theLine = lines[error.location.begin.line];
|
std::string_view theLine = lines[error.location.begin.line];
|
||||||
|
@ -430,6 +430,7 @@ ConstraintGraphBuilderFixture::ConstraintGraphBuilderFixture()
|
||||||
: Fixture()
|
: Fixture()
|
||||||
, forceTheFlag{"DebugLuauDeferredConstraintResolution", true}
|
, forceTheFlag{"DebugLuauDeferredConstraintResolution", true}
|
||||||
{
|
{
|
||||||
|
BlockedTypeVar::nextIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModuleName fromString(std::string_view name)
|
ModuleName fromString(std::string_view name)
|
||||||
|
|
|
@ -97,8 +97,8 @@ TEST_CASE_FIXTURE(FrontendFixture, "find_a_require")
|
||||||
NaiveFileResolver naiveFileResolver;
|
NaiveFileResolver naiveFileResolver;
|
||||||
|
|
||||||
auto res = traceRequires(&naiveFileResolver, program, "");
|
auto res = traceRequires(&naiveFileResolver, program, "");
|
||||||
CHECK_EQ(1, res.requires.size());
|
CHECK_EQ(1, res.requireList.size());
|
||||||
CHECK_EQ(res.requires[0].first, "Modules/Foo/Bar");
|
CHECK_EQ(res.requireList[0].first, "Modules/Foo/Bar");
|
||||||
}
|
}
|
||||||
|
|
||||||
// It could be argued that this should not work.
|
// It could be argued that this should not work.
|
||||||
|
@ -113,7 +113,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "find_a_require_inside_a_function")
|
||||||
NaiveFileResolver naiveFileResolver;
|
NaiveFileResolver naiveFileResolver;
|
||||||
|
|
||||||
auto res = traceRequires(&naiveFileResolver, program, "");
|
auto res = traceRequires(&naiveFileResolver, program, "");
|
||||||
CHECK_EQ(1, res.requires.size());
|
CHECK_EQ(1, res.requireList.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(FrontendFixture, "real_source")
|
TEST_CASE_FIXTURE(FrontendFixture, "real_source")
|
||||||
|
@ -138,7 +138,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "real_source")
|
||||||
NaiveFileResolver naiveFileResolver;
|
NaiveFileResolver naiveFileResolver;
|
||||||
|
|
||||||
auto res = traceRequires(&naiveFileResolver, program, "");
|
auto res = traceRequires(&naiveFileResolver, program, "");
|
||||||
CHECK_EQ(8, res.requires.size());
|
CHECK_EQ(8, res.requireList.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(FrontendFixture, "automatically_check_dependent_scripts")
|
TEST_CASE_FIXTURE(FrontendFixture, "automatically_check_dependent_scripts")
|
||||||
|
|
|
@ -102,7 +102,7 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table")
|
||||||
const FunctionTypeVar* ftv = get<FunctionTypeVar>(methodType);
|
const FunctionTypeVar* ftv = get<FunctionTypeVar>(methodType);
|
||||||
REQUIRE(ftv != nullptr);
|
REQUIRE(ftv != nullptr);
|
||||||
|
|
||||||
std::optional<TypeId> methodReturnType = first(ftv->retType);
|
std::optional<TypeId> methodReturnType = first(ftv->retTypes);
|
||||||
REQUIRE(methodReturnType);
|
REQUIRE(methodReturnType);
|
||||||
|
|
||||||
CHECK_EQ(methodReturnType, counterCopy);
|
CHECK_EQ(methodReturnType, counterCopy);
|
||||||
|
|
|
@ -13,6 +13,57 @@ using namespace Luau;
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("NonstrictModeTests");
|
TEST_SUITE_BEGIN("NonstrictModeTests");
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "globals")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
--!nonstrict
|
||||||
|
foo = true
|
||||||
|
foo = "now i'm a string!"
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("any", toString(requireType("foo")));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "globals2")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff[]{
|
||||||
|
{"LuauReturnTypeInferenceInNonstrict", true},
|
||||||
|
{"LuauLowerBoundsCalculation", true},
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
--!nonstrict
|
||||||
|
foo = function() return 1 end
|
||||||
|
foo = "now i'm a string!"
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
|
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||||
|
REQUIRE(tm);
|
||||||
|
CHECK_EQ("() -> number", toString(tm->wantedType));
|
||||||
|
CHECK_EQ("string", toString(tm->givenType));
|
||||||
|
CHECK_EQ("() -> number", toString(requireType("foo")));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "globals_everywhere")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
--!nonstrict
|
||||||
|
foo = 1
|
||||||
|
|
||||||
|
if true then
|
||||||
|
bar = 2
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
|
CHECK_EQ("any", toString(requireType("foo")));
|
||||||
|
CHECK_EQ("any", toString(requireType("bar")));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "function_returns_number_or_string")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "function_returns_number_or_string")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[]{{"LuauReturnTypeInferenceInNonstrict", true}, {"LuauLowerBoundsCalculation", true}};
|
ScopedFastFlag sff[]{{"LuauReturnTypeInferenceInNonstrict", true}, {"LuauLowerBoundsCalculation", true}};
|
||||||
|
@ -51,7 +102,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_nullary_function")
|
||||||
REQUIRE_EQ("any", toString(args[0]));
|
REQUIRE_EQ("any", toString(args[0]));
|
||||||
REQUIRE_EQ("any", toString(args[1]));
|
REQUIRE_EQ("any", toString(args[1]));
|
||||||
|
|
||||||
auto rets = flatten(ftv->retType).first;
|
auto rets = flatten(ftv->retTypes).first;
|
||||||
REQUIRE_EQ(0, rets.size());
|
REQUIRE_EQ(0, rets.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -837,6 +837,7 @@ TEST_CASE_FIXTURE(Fixture, "intersection_inside_a_table_inside_another_intersect
|
||||||
{
|
{
|
||||||
ScopedFastFlag flags[] = {
|
ScopedFastFlag flags[] = {
|
||||||
{"LuauLowerBoundsCalculation", true},
|
{"LuauLowerBoundsCalculation", true},
|
||||||
|
{"LuauQuantifyConstrained", true},
|
||||||
};
|
};
|
||||||
|
|
||||||
// We use a function and inferred parameter types to prevent intermediate normalizations from being performed.
|
// We use a function and inferred parameter types to prevent intermediate normalizations from being performed.
|
||||||
|
@ -867,16 +868,17 @@ TEST_CASE_FIXTURE(Fixture, "intersection_inside_a_table_inside_another_intersect
|
||||||
CHECK("{+ y: number +}" == toString(args[2]));
|
CHECK("{+ y: number +}" == toString(args[2]));
|
||||||
CHECK("{+ z: string +}" == toString(args[3]));
|
CHECK("{+ z: string +}" == toString(args[3]));
|
||||||
|
|
||||||
std::vector<TypeId> ret = flatten(ftv->retType).first;
|
std::vector<TypeId> ret = flatten(ftv->retTypes).first;
|
||||||
|
|
||||||
REQUIRE(1 == ret.size());
|
REQUIRE(1 == ret.size());
|
||||||
CHECK("{| x: a & {- w: boolean, y: number, z: string -} |}" == toString(ret[0]));
|
CHECK("{| x: a & {+ w: boolean, y: number, z: string +} |}" == toString(ret[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "intersection_inside_a_table_inside_another_intersection_3")
|
TEST_CASE_FIXTURE(Fixture, "intersection_inside_a_table_inside_another_intersection_3")
|
||||||
{
|
{
|
||||||
ScopedFastFlag flags[] = {
|
ScopedFastFlag flags[] = {
|
||||||
{"LuauLowerBoundsCalculation", true},
|
{"LuauLowerBoundsCalculation", true},
|
||||||
|
{"LuauQuantifyConstrained", true},
|
||||||
};
|
};
|
||||||
|
|
||||||
// We use a function and inferred parameter types to prevent intermediate normalizations from being performed.
|
// We use a function and inferred parameter types to prevent intermediate normalizations from being performed.
|
||||||
|
@ -906,16 +908,17 @@ TEST_CASE_FIXTURE(Fixture, "intersection_inside_a_table_inside_another_intersect
|
||||||
CHECK("t1 where t1 = {+ y: t1 +}" == toString(args[1]));
|
CHECK("t1 where t1 = {+ y: t1 +}" == toString(args[1]));
|
||||||
CHECK("{+ z: string +}" == toString(args[2]));
|
CHECK("{+ z: string +}" == toString(args[2]));
|
||||||
|
|
||||||
std::vector<TypeId> ret = flatten(ftv->retType).first;
|
std::vector<TypeId> ret = flatten(ftv->retTypes).first;
|
||||||
|
|
||||||
REQUIRE(1 == ret.size());
|
REQUIRE(1 == ret.size());
|
||||||
CHECK("{| x: {- x: boolean, y: t1, z: string -} |} where t1 = {+ y: t1 +}" == toString(ret[0]));
|
CHECK("{| x: {+ x: boolean, y: t1, z: string +} |} where t1 = {+ y: t1 +}" == toString(ret[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "intersection_inside_a_table_inside_another_intersection_4")
|
TEST_CASE_FIXTURE(Fixture, "intersection_inside_a_table_inside_another_intersection_4")
|
||||||
{
|
{
|
||||||
ScopedFastFlag flags[] = {
|
ScopedFastFlag flags[] = {
|
||||||
{"LuauLowerBoundsCalculation", true},
|
{"LuauLowerBoundsCalculation", true},
|
||||||
|
{"LuauQuantifyConstrained", true},
|
||||||
};
|
};
|
||||||
|
|
||||||
// We use a function and inferred parameter types to prevent intermediate normalizations from being performed.
|
// We use a function and inferred parameter types to prevent intermediate normalizations from being performed.
|
||||||
|
@ -944,13 +947,13 @@ TEST_CASE_FIXTURE(Fixture, "intersection_inside_a_table_inside_another_intersect
|
||||||
|
|
||||||
REQUIRE(3 == args.size());
|
REQUIRE(3 == args.size());
|
||||||
CHECK("{+ x: boolean +}" == toString(args[0]));
|
CHECK("{+ x: boolean +}" == toString(args[0]));
|
||||||
CHECK("{+ y: t1 +} where t1 = {| x: {- x: boolean, y: t1, z: string -} |}" == toString(args[1]));
|
CHECK("{+ y: t1 +} where t1 = {| x: {+ x: boolean, y: t1, z: string +} |}" == toString(args[1]));
|
||||||
CHECK("{+ z: string +}" == toString(args[2]));
|
CHECK("{+ z: string +}" == toString(args[2]));
|
||||||
|
|
||||||
std::vector<TypeId> ret = flatten(ftv->retType).first;
|
std::vector<TypeId> ret = flatten(ftv->retTypes).first;
|
||||||
|
|
||||||
REQUIRE(1 == ret.size());
|
REQUIRE(1 == ret.size());
|
||||||
CHECK("t1 where t1 = {| x: {- x: boolean, y: t1, z: string -} |}" == toString(ret[0]));
|
CHECK("t1 where t1 = {| x: {+ x: boolean, y: t1, z: string +} |}" == toString(ret[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "nested_table_normalization_with_non_table__no_ice")
|
TEST_CASE_FIXTURE(Fixture, "nested_table_normalization_with_non_table__no_ice")
|
||||||
|
@ -1062,4 +1065,29 @@ export type t0 = (((any)&({_:l0.t0,n0:t0,_G:any,}))&({_:any,}))&(((any)&({_:l0.t
|
||||||
LUAU_REQUIRE_ERRORS(result);
|
LUAU_REQUIRE_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "normalization_does_not_convert_ever")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff[]{
|
||||||
|
{"LuauLowerBoundsCalculation", true},
|
||||||
|
{"LuauQuantifyConstrained", true},
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
--!strict
|
||||||
|
local function f()
|
||||||
|
if math.random() > 0.5 then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
type Ret = typeof(f())
|
||||||
|
if math.random() > 0.5 then
|
||||||
|
return "something"
|
||||||
|
end
|
||||||
|
return "something" :: Ret
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK_EQ("() -> boolean | string", toString(requireType("f")));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -75,9 +75,9 @@ TEST_CASE("basic_stuff")
|
||||||
t->y = 3.14f;
|
t->y = 3.14f;
|
||||||
|
|
||||||
const NotNull<Test> u = t;
|
const NotNull<Test> u = t;
|
||||||
// u->x = 44; // nope
|
u->x = 44;
|
||||||
int v = u->x;
|
int v = u->x;
|
||||||
CHECK(v == 5);
|
CHECK(v == 44);
|
||||||
|
|
||||||
bar(a);
|
bar(a);
|
||||||
|
|
||||||
|
@ -96,8 +96,11 @@ TEST_CASE("basic_stuff")
|
||||||
TEST_CASE("hashable")
|
TEST_CASE("hashable")
|
||||||
{
|
{
|
||||||
std::unordered_map<NotNull<int>, const char*> map;
|
std::unordered_map<NotNull<int>, const char*> map;
|
||||||
NotNull<int> a{new int(8)};
|
int a_ = 8;
|
||||||
NotNull<int> b{new int(10)};
|
int b_ = 10;
|
||||||
|
|
||||||
|
NotNull<int> a{&a_};
|
||||||
|
NotNull<int> b{&b_};
|
||||||
|
|
||||||
std::string hello = "hello";
|
std::string hello = "hello";
|
||||||
std::string world = "world";
|
std::string world = "world";
|
||||||
|
@ -108,9 +111,47 @@ TEST_CASE("hashable")
|
||||||
CHECK_EQ(2, map.size());
|
CHECK_EQ(2, map.size());
|
||||||
CHECK_EQ(hello.c_str(), map[a]);
|
CHECK_EQ(hello.c_str(), map[a]);
|
||||||
CHECK_EQ(world.c_str(), map[b]);
|
CHECK_EQ(world.c_str(), map[b]);
|
||||||
|
}
|
||||||
|
|
||||||
delete a;
|
TEST_CASE("const")
|
||||||
delete b;
|
{
|
||||||
|
int p = 0;
|
||||||
|
int q = 0;
|
||||||
|
|
||||||
|
NotNull<int> n{&p};
|
||||||
|
|
||||||
|
*n = 123;
|
||||||
|
|
||||||
|
NotNull<const int> m = n; // Conversion from NotNull<T> to NotNull<const T> is allowed
|
||||||
|
|
||||||
|
CHECK(123 == *m); // readonly access of m is ok
|
||||||
|
|
||||||
|
// *m = 321; // nope. m points at const data.
|
||||||
|
|
||||||
|
// NotNull<int> o = m; // nope. Conversion from NotNull<const T> to NotNull<T> is forbidden
|
||||||
|
|
||||||
|
NotNull<int> n2{&q};
|
||||||
|
m = n2; // ok. m points to const data, but is not itself const
|
||||||
|
|
||||||
|
const NotNull<int> m2 = n;
|
||||||
|
// m2 = n2; // nope. m2 is const.
|
||||||
|
*m2 = 321; // ok. m2 is const, but points to mutable data
|
||||||
|
|
||||||
|
CHECK(321 == *n);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("const_compatibility")
|
||||||
|
{
|
||||||
|
int* raw = new int(8);
|
||||||
|
|
||||||
|
NotNull<int> a(raw);
|
||||||
|
NotNull<const int> b(raw);
|
||||||
|
NotNull<const int> c = a;
|
||||||
|
// NotNull<int> d = c; // nope - no conversion from const to non-const
|
||||||
|
|
||||||
|
CHECK_EQ(*c, 8);
|
||||||
|
|
||||||
|
delete raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -70,7 +70,7 @@ TEST_CASE_FIXTURE(Fixture, "function_return_annotations_are_checked")
|
||||||
const FunctionTypeVar* ftv = get<FunctionTypeVar>(fiftyType);
|
const FunctionTypeVar* ftv = get<FunctionTypeVar>(fiftyType);
|
||||||
REQUIRE(ftv != nullptr);
|
REQUIRE(ftv != nullptr);
|
||||||
|
|
||||||
TypePackId retPack = ftv->retType;
|
TypePackId retPack = ftv->retTypes;
|
||||||
const TypePack* tp = get<TypePack>(retPack);
|
const TypePack* tp = get<TypePack>(retPack);
|
||||||
REQUIRE(tp != nullptr);
|
REQUIRE(tp != nullptr);
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_return_type")
|
||||||
const FunctionTypeVar* takeFiveType = get<FunctionTypeVar>(requireType("take_five"));
|
const FunctionTypeVar* takeFiveType = get<FunctionTypeVar>(requireType("take_five"));
|
||||||
REQUIRE(takeFiveType != nullptr);
|
REQUIRE(takeFiveType != nullptr);
|
||||||
|
|
||||||
std::vector<TypeId> retVec = flatten(takeFiveType->retType).first;
|
std::vector<TypeId> retVec = flatten(takeFiveType->retTypes).first;
|
||||||
REQUIRE(!retVec.empty());
|
REQUIRE(!retVec.empty());
|
||||||
|
|
||||||
REQUIRE_EQ(*follow(retVec[0]), *typeChecker.numberType);
|
REQUIRE_EQ(*follow(retVec[0]), *typeChecker.numberType);
|
||||||
|
@ -345,7 +345,7 @@ TEST_CASE_FIXTURE(Fixture, "local_function")
|
||||||
const FunctionTypeVar* ftv = get<FunctionTypeVar>(h);
|
const FunctionTypeVar* ftv = get<FunctionTypeVar>(h);
|
||||||
REQUIRE(ftv != nullptr);
|
REQUIRE(ftv != nullptr);
|
||||||
|
|
||||||
std::optional<TypeId> rt = first(ftv->retType);
|
std::optional<TypeId> rt = first(ftv->retTypes);
|
||||||
REQUIRE(bool(rt));
|
REQUIRE(bool(rt));
|
||||||
|
|
||||||
TypeId retType = follow(*rt);
|
TypeId retType = follow(*rt);
|
||||||
|
@ -361,7 +361,7 @@ TEST_CASE_FIXTURE(Fixture, "func_expr_doesnt_leak_free")
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
const Luau::FunctionTypeVar* fn = get<FunctionTypeVar>(requireType("p"));
|
const Luau::FunctionTypeVar* fn = get<FunctionTypeVar>(requireType("p"));
|
||||||
REQUIRE(fn);
|
REQUIRE(fn);
|
||||||
auto ret = first(fn->retType);
|
auto ret = first(fn->retTypes);
|
||||||
REQUIRE(ret);
|
REQUIRE(ret);
|
||||||
REQUIRE(get<GenericTypeVar>(follow(*ret)));
|
REQUIRE(get<GenericTypeVar>(follow(*ret)));
|
||||||
}
|
}
|
||||||
|
@ -460,7 +460,7 @@ TEST_CASE_FIXTURE(Fixture, "complicated_return_types_require_an_explicit_annotat
|
||||||
|
|
||||||
const FunctionTypeVar* functionType = get<FunctionTypeVar>(requireType("most_of_the_natural_numbers"));
|
const FunctionTypeVar* functionType = get<FunctionTypeVar>(requireType("most_of_the_natural_numbers"));
|
||||||
|
|
||||||
std::optional<TypeId> retType = first(functionType->retType);
|
std::optional<TypeId> retType = first(functionType->retTypes);
|
||||||
REQUIRE(retType);
|
REQUIRE(retType);
|
||||||
CHECK(get<UnionTypeVar>(*retType));
|
CHECK(get<UnionTypeVar>(*retType));
|
||||||
}
|
}
|
||||||
|
@ -1619,4 +1619,56 @@ TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_type_pack")
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "quantify_constrained_types")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff[]{
|
||||||
|
{"LuauLowerBoundsCalculation", true},
|
||||||
|
{"LuauQuantifyConstrained", true},
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
--!strict
|
||||||
|
local function foo(f)
|
||||||
|
f(5)
|
||||||
|
f("hi")
|
||||||
|
local function g()
|
||||||
|
return f
|
||||||
|
end
|
||||||
|
local h = g()
|
||||||
|
h(true)
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
|
CHECK_EQ("<a...>((boolean | number | string) -> (a...)) -> ()", toString(requireType("foo")));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "call_o_with_another_argument_after_foo_was_quantified")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff[]{
|
||||||
|
{"LuauLowerBoundsCalculation", true},
|
||||||
|
{"LuauQuantifyConstrained", true},
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(o)
|
||||||
|
local t = {}
|
||||||
|
t[o] = true
|
||||||
|
|
||||||
|
local function foo(o)
|
||||||
|
o.m1(5)
|
||||||
|
t[o] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
o.m1("hi")
|
||||||
|
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
// TODO: check the normalized type of f
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -224,7 +224,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_generic_function")
|
||||||
const FunctionTypeVar* idFun = get<FunctionTypeVar>(idType);
|
const FunctionTypeVar* idFun = get<FunctionTypeVar>(idType);
|
||||||
REQUIRE(idFun);
|
REQUIRE(idFun);
|
||||||
auto [args, varargs] = flatten(idFun->argTypes);
|
auto [args, varargs] = flatten(idFun->argTypes);
|
||||||
auto [rets, varrets] = flatten(idFun->retType);
|
auto [rets, varrets] = flatten(idFun->retTypes);
|
||||||
|
|
||||||
CHECK_EQ(idFun->generics.size(), 1);
|
CHECK_EQ(idFun->generics.size(), 1);
|
||||||
CHECK_EQ(idFun->genericPacks.size(), 0);
|
CHECK_EQ(idFun->genericPacks.size(), 0);
|
||||||
|
@ -247,7 +247,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_generic_local_function")
|
||||||
const FunctionTypeVar* idFun = get<FunctionTypeVar>(idType);
|
const FunctionTypeVar* idFun = get<FunctionTypeVar>(idType);
|
||||||
REQUIRE(idFun);
|
REQUIRE(idFun);
|
||||||
auto [args, varargs] = flatten(idFun->argTypes);
|
auto [args, varargs] = flatten(idFun->argTypes);
|
||||||
auto [rets, varrets] = flatten(idFun->retType);
|
auto [rets, varrets] = flatten(idFun->retTypes);
|
||||||
|
|
||||||
CHECK_EQ(idFun->generics.size(), 1);
|
CHECK_EQ(idFun->generics.size(), 1);
|
||||||
CHECK_EQ(idFun->genericPacks.size(), 0);
|
CHECK_EQ(idFun->genericPacks.size(), 0);
|
||||||
|
@ -882,7 +882,7 @@ TEST_CASE_FIXTURE(Fixture, "correctly_instantiate_polymorphic_member_functions")
|
||||||
const FunctionTypeVar* foo = get<FunctionTypeVar>(follow(fooProp->type));
|
const FunctionTypeVar* foo = get<FunctionTypeVar>(follow(fooProp->type));
|
||||||
REQUIRE(bool(foo));
|
REQUIRE(bool(foo));
|
||||||
|
|
||||||
std::optional<TypeId> ret_ = first(foo->retType);
|
std::optional<TypeId> ret_ = first(foo->retTypes);
|
||||||
REQUIRE(bool(ret_));
|
REQUIRE(bool(ret_));
|
||||||
TypeId ret = follow(*ret_);
|
TypeId ret = follow(*ret_);
|
||||||
|
|
||||||
|
|
|
@ -90,7 +90,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "primitive_arith_no_metatable")
|
||||||
|
|
||||||
const FunctionTypeVar* functionType = get<FunctionTypeVar>(requireType("add"));
|
const FunctionTypeVar* functionType = get<FunctionTypeVar>(requireType("add"));
|
||||||
|
|
||||||
std::optional<TypeId> retType = first(functionType->retType);
|
std::optional<TypeId> retType = first(functionType->retTypes);
|
||||||
REQUIRE(retType.has_value());
|
REQUIRE(retType.has_value());
|
||||||
CHECK_EQ(typeChecker.numberType, follow(*retType));
|
CHECK_EQ(typeChecker.numberType, follow(*retType));
|
||||||
CHECK_EQ(requireType("n"), typeChecker.numberType);
|
CHECK_EQ(requireType("n"), typeChecker.numberType);
|
||||||
|
@ -777,8 +777,6 @@ TEST_CASE_FIXTURE(Fixture, "infer_any_in_all_modes_when_lhs_is_unknown")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "equality_operations_succeed_if_any_union_branch_succeeds")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "equality_operations_succeed_if_any_union_branch_succeeds")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff("LuauSuccessTypingForEqualityOperations", true);
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local mm = {}
|
local mm = {}
|
||||||
type Foo = typeof(setmetatable({}, mm))
|
type Foo = typeof(setmetatable({}, mm))
|
||||||
|
|
|
@ -472,6 +472,7 @@ TEST_CASE_FIXTURE(Fixture, "constrained_is_level_dependent")
|
||||||
ScopedFastFlag sff[]{
|
ScopedFastFlag sff[]{
|
||||||
{"LuauLowerBoundsCalculation", true},
|
{"LuauLowerBoundsCalculation", true},
|
||||||
{"LuauNormalizeFlagIsConservative", true},
|
{"LuauNormalizeFlagIsConservative", true},
|
||||||
|
{"LuauQuantifyConstrained", true},
|
||||||
};
|
};
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
|
@ -494,8 +495,8 @@ TEST_CASE_FIXTURE(Fixture, "constrained_is_level_dependent")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
// TODO: We're missing generics a... and b...
|
// TODO: We're missing generics b...
|
||||||
CHECK_EQ("(t1) -> {| [t1]: boolean |} where t1 = t2 ; t2 = {+ m1: (t1) -> (a...), m2: (t2) -> (b...) +}", toString(requireType("f")));
|
CHECK_EQ("<a...>(t1) -> {| [t1]: boolean |} where t1 = t2 ; t2 = {+ m1: (t1) -> (a...), m2: (t2) -> (b...) +}", toString(requireType("f")));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -13,8 +13,8 @@ using namespace Luau;
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
std::optional<ExprResult<TypePackId>> magicFunctionInstanceIsA(
|
std::optional<WithPredicate<TypePackId>> magicFunctionInstanceIsA(
|
||||||
TypeChecker& typeChecker, const ScopePtr& scope, const AstExprCall& expr, ExprResult<TypePackId> exprResult)
|
TypeChecker& typeChecker, const ScopePtr& scope, const AstExprCall& expr, WithPredicate<TypePackId> withPredicate)
|
||||||
{
|
{
|
||||||
if (expr.args.size != 1)
|
if (expr.args.size != 1)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
@ -32,7 +32,7 @@ std::optional<ExprResult<TypePackId>> magicFunctionInstanceIsA(
|
||||||
unfreeze(typeChecker.globalTypes);
|
unfreeze(typeChecker.globalTypes);
|
||||||
TypePackId booleanPack = typeChecker.globalTypes.addTypePack({typeChecker.booleanType});
|
TypePackId booleanPack = typeChecker.globalTypes.addTypePack({typeChecker.booleanType});
|
||||||
freeze(typeChecker.globalTypes);
|
freeze(typeChecker.globalTypes);
|
||||||
return ExprResult<TypePackId>{booleanPack, {IsAPredicate{std::move(*lvalue), expr.location, tfun->type}}};
|
return WithPredicate<TypePackId>{booleanPack, {IsAPredicate{std::move(*lvalue), expr.location, tfun->type}}};
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RefinementClassFixture : Fixture
|
struct RefinementClassFixture : Fixture
|
||||||
|
|
|
@ -642,7 +642,7 @@ TEST_CASE_FIXTURE(Fixture, "indexers_quantification_2")
|
||||||
const TableTypeVar* argType = get<TableTypeVar>(follow(argVec[0]));
|
const TableTypeVar* argType = get<TableTypeVar>(follow(argVec[0]));
|
||||||
REQUIRE(argType != nullptr);
|
REQUIRE(argType != nullptr);
|
||||||
|
|
||||||
std::vector<TypeId> retVec = flatten(ftv->retType).first;
|
std::vector<TypeId> retVec = flatten(ftv->retTypes).first;
|
||||||
|
|
||||||
const TableTypeVar* retType = get<TableTypeVar>(follow(retVec[0]));
|
const TableTypeVar* retType = get<TableTypeVar>(follow(retVec[0]));
|
||||||
REQUIRE(retType != nullptr);
|
REQUIRE(retType != nullptr);
|
||||||
|
@ -691,7 +691,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_indexer_from_value_property_in_literal")
|
||||||
const FunctionTypeVar* fType = get<FunctionTypeVar>(requireType("f"));
|
const FunctionTypeVar* fType = get<FunctionTypeVar>(requireType("f"));
|
||||||
REQUIRE(fType != nullptr);
|
REQUIRE(fType != nullptr);
|
||||||
|
|
||||||
auto retType_ = first(fType->retType);
|
auto retType_ = first(fType->retTypes);
|
||||||
REQUIRE(bool(retType_));
|
REQUIRE(bool(retType_));
|
||||||
|
|
||||||
auto retType = get<TableTypeVar>(follow(*retType_));
|
auto retType = get<TableTypeVar>(follow(*retType_));
|
||||||
|
@ -1881,7 +1881,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "quantifying_a_bound_var_works")
|
||||||
REQUIRE(prop.type);
|
REQUIRE(prop.type);
|
||||||
const FunctionTypeVar* ftv = get<FunctionTypeVar>(follow(prop.type));
|
const FunctionTypeVar* ftv = get<FunctionTypeVar>(follow(prop.type));
|
||||||
REQUIRE(ftv);
|
REQUIRE(ftv);
|
||||||
const TypePack* res = get<TypePack>(follow(ftv->retType));
|
const TypePack* res = get<TypePack>(follow(ftv->retTypes));
|
||||||
REQUIRE(res);
|
REQUIRE(res);
|
||||||
REQUIRE(res->head.size() == 1);
|
REQUIRE(res->head.size() == 1);
|
||||||
const MetatableTypeVar* mtv = get<MetatableTypeVar>(follow(res->head[0]));
|
const MetatableTypeVar* mtv = get<MetatableTypeVar>(follow(res->head[0]));
|
||||||
|
@ -2584,7 +2584,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dont_quantify_table_that_belongs_to_outer_sc
|
||||||
const FunctionTypeVar* newType = get<FunctionTypeVar>(follow(counterType->props["new"].type));
|
const FunctionTypeVar* newType = get<FunctionTypeVar>(follow(counterType->props["new"].type));
|
||||||
REQUIRE(newType);
|
REQUIRE(newType);
|
||||||
|
|
||||||
std::optional<TypeId> newRetType = *first(newType->retType);
|
std::optional<TypeId> newRetType = *first(newType->retTypes);
|
||||||
REQUIRE(newRetType);
|
REQUIRE(newRetType);
|
||||||
|
|
||||||
const MetatableTypeVar* newRet = get<MetatableTypeVar>(follow(*newRetType));
|
const MetatableTypeVar* newRet = get<MetatableTypeVar>(follow(*newRetType));
|
||||||
|
@ -2977,7 +2977,6 @@ TEST_CASE_FIXTURE(Fixture, "mixed_tables_with_implicit_numbered_keys")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "expected_indexer_value_type_extra")
|
TEST_CASE_FIXTURE(Fixture, "expected_indexer_value_type_extra")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauExpectedPropTypeFromIndexer{"LuauExpectedPropTypeFromIndexer", true};
|
|
||||||
ScopedFastFlag luauSubtypingAddOptPropsToUnsealedTables{"LuauSubtypingAddOptPropsToUnsealedTables", true};
|
ScopedFastFlag luauSubtypingAddOptPropsToUnsealedTables{"LuauSubtypingAddOptPropsToUnsealedTables", true};
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
|
@ -2992,8 +2991,6 @@ TEST_CASE_FIXTURE(Fixture, "expected_indexer_value_type_extra")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "expected_indexer_value_type_extra_2")
|
TEST_CASE_FIXTURE(Fixture, "expected_indexer_value_type_extra_2")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauExpectedPropTypeFromIndexer{"LuauExpectedPropTypeFromIndexer", true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type X = {[any]: string | boolean}
|
type X = {[any]: string | boolean}
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,9 @@
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
|
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||||
LUAU_FASTFLAG(LuauFixLocationSpanTableIndexExpr)
|
LUAU_FASTFLAG(LuauFixLocationSpanTableIndexExpr);
|
||||||
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
|
@ -43,10 +44,7 @@ TEST_CASE_FIXTURE(Fixture, "tc_error")
|
||||||
CheckResult result = check("local a = 7 local b = 'hi' a = b");
|
CheckResult result = check("local a = 7 local b = 'hi' a = b");
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
CHECK_EQ(result.errors[0], (TypeError{Location{Position{0, 35}, Position{0, 36}}, TypeMismatch{
|
CHECK_EQ(result.errors[0], (TypeError{Location{Position{0, 35}, Position{0, 36}}, TypeMismatch{typeChecker.numberType, typeChecker.stringType}}));
|
||||||
requireType("a"),
|
|
||||||
requireType("b"),
|
|
||||||
}}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "tc_error_2")
|
TEST_CASE_FIXTURE(Fixture, "tc_error_2")
|
||||||
|
@ -86,6 +84,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_locals_via_assignment_from_its_call_site")
|
||||||
TEST_CASE_FIXTURE(Fixture, "infer_in_nocheck_mode")
|
TEST_CASE_FIXTURE(Fixture, "infer_in_nocheck_mode")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[]{
|
ScopedFastFlag sff[]{
|
||||||
|
{"DebugLuauDeferredConstraintResolution", false},
|
||||||
{"LuauReturnTypeInferenceInNonstrict", true},
|
{"LuauReturnTypeInferenceInNonstrict", true},
|
||||||
{"LuauLowerBoundsCalculation", true},
|
{"LuauLowerBoundsCalculation", true},
|
||||||
};
|
};
|
||||||
|
@ -236,10 +235,14 @@ TEST_CASE_FIXTURE(Fixture, "type_errors_infer_types")
|
||||||
CHECK_EQ("boolean", toString(err->table));
|
CHECK_EQ("boolean", toString(err->table));
|
||||||
CHECK_EQ("x", err->key);
|
CHECK_EQ("x", err->key);
|
||||||
|
|
||||||
CHECK_EQ("*unknown*", toString(requireType("c")));
|
// TODO: Should we assert anything about these tests when DCR is being used?
|
||||||
CHECK_EQ("*unknown*", toString(requireType("d")));
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
CHECK_EQ("*unknown*", toString(requireType("e")));
|
{
|
||||||
CHECK_EQ("*unknown*", toString(requireType("f")));
|
CHECK_EQ("*unknown*", toString(requireType("c")));
|
||||||
|
CHECK_EQ("*unknown*", toString(requireType("d")));
|
||||||
|
CHECK_EQ("*unknown*", toString(requireType("e")));
|
||||||
|
CHECK_EQ("*unknown*", toString(requireType("f")));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "should_be_able_to_infer_this_without_stack_overflowing")
|
TEST_CASE_FIXTURE(Fixture, "should_be_able_to_infer_this_without_stack_overflowing")
|
||||||
|
@ -352,40 +355,6 @@ TEST_CASE_FIXTURE(Fixture, "check_expr_recursion_limit")
|
||||||
CHECK(nullptr != get<CodeTooComplex>(result.errors[0]));
|
CHECK(nullptr != get<CodeTooComplex>(result.errors[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "globals")
|
|
||||||
{
|
|
||||||
CheckResult result = check(R"(
|
|
||||||
--!nonstrict
|
|
||||||
foo = true
|
|
||||||
foo = "now i'm a string!"
|
|
||||||
)");
|
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
|
||||||
CHECK_EQ("any", toString(requireType("foo")));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "globals2")
|
|
||||||
{
|
|
||||||
ScopedFastFlag sff[]{
|
|
||||||
{"LuauReturnTypeInferenceInNonstrict", true},
|
|
||||||
{"LuauLowerBoundsCalculation", true},
|
|
||||||
};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
|
||||||
--!nonstrict
|
|
||||||
foo = function() return 1 end
|
|
||||||
foo = "now i'm a string!"
|
|
||||||
)");
|
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
||||||
|
|
||||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
|
||||||
REQUIRE(tm);
|
|
||||||
CHECK_EQ("() -> number", toString(tm->wantedType));
|
|
||||||
CHECK_EQ("string", toString(tm->givenType));
|
|
||||||
CHECK_EQ("() -> number", toString(requireType("foo")));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "globals_are_banned_in_strict_mode")
|
TEST_CASE_FIXTURE(Fixture, "globals_are_banned_in_strict_mode")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
|
@ -400,23 +369,6 @@ TEST_CASE_FIXTURE(Fixture, "globals_are_banned_in_strict_mode")
|
||||||
CHECK_EQ("foo", us->name);
|
CHECK_EQ("foo", us->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "globals_everywhere")
|
|
||||||
{
|
|
||||||
CheckResult result = check(R"(
|
|
||||||
--!nonstrict
|
|
||||||
foo = 1
|
|
||||||
|
|
||||||
if true then
|
|
||||||
bar = 2
|
|
||||||
end
|
|
||||||
)");
|
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
|
||||||
|
|
||||||
CHECK_EQ("any", toString(requireType("foo")));
|
|
||||||
CHECK_EQ("any", toString(requireType("bar")));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "correctly_scope_locals_do")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "correctly_scope_locals_do")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
|
@ -447,21 +399,6 @@ TEST_CASE_FIXTURE(Fixture, "checking_should_not_ice")
|
||||||
CHECK_EQ("any", toString(requireType("value")));
|
CHECK_EQ("any", toString(requireType("value")));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TEST_CASE_FIXTURE(Fixture, "infer_method_signature_of_argument")
|
|
||||||
// {
|
|
||||||
// CheckResult result = check(R"(
|
|
||||||
// function f(a)
|
|
||||||
// if a.cond then
|
|
||||||
// return a.method()
|
|
||||||
// end
|
|
||||||
// end
|
|
||||||
// )");
|
|
||||||
|
|
||||||
// LUAU_REQUIRE_NO_ERRORS(result);
|
|
||||||
|
|
||||||
// CHECK_EQ("A", toString(requireType("f")));
|
|
||||||
// }
|
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "cyclic_follow")
|
TEST_CASE_FIXTURE(Fixture, "cyclic_follow")
|
||||||
{
|
{
|
||||||
check(R"(
|
check(R"(
|
||||||
|
|
|
@ -26,7 +26,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_multi_return")
|
||||||
const FunctionTypeVar* takeTwoType = get<FunctionTypeVar>(requireType("take_two"));
|
const FunctionTypeVar* takeTwoType = get<FunctionTypeVar>(requireType("take_two"));
|
||||||
REQUIRE(takeTwoType != nullptr);
|
REQUIRE(takeTwoType != nullptr);
|
||||||
|
|
||||||
const auto& [returns, tail] = flatten(takeTwoType->retType);
|
const auto& [returns, tail] = flatten(takeTwoType->retTypes);
|
||||||
|
|
||||||
CHECK_EQ(2, returns.size());
|
CHECK_EQ(2, returns.size());
|
||||||
CHECK_EQ(typeChecker.numberType, follow(returns[0]));
|
CHECK_EQ(typeChecker.numberType, follow(returns[0]));
|
||||||
|
@ -73,7 +73,7 @@ TEST_CASE_FIXTURE(Fixture, "last_element_of_return_statement_can_itself_be_a_pac
|
||||||
const FunctionTypeVar* takeOneMoreType = get<FunctionTypeVar>(requireType("take_three"));
|
const FunctionTypeVar* takeOneMoreType = get<FunctionTypeVar>(requireType("take_three"));
|
||||||
REQUIRE(takeOneMoreType != nullptr);
|
REQUIRE(takeOneMoreType != nullptr);
|
||||||
|
|
||||||
const auto& [rets, tail] = flatten(takeOneMoreType->retType);
|
const auto& [rets, tail] = flatten(takeOneMoreType->retTypes);
|
||||||
|
|
||||||
REQUIRE_EQ(3, rets.size());
|
REQUIRE_EQ(3, rets.size());
|
||||||
CHECK_EQ(typeChecker.numberType, follow(rets[0]));
|
CHECK_EQ(typeChecker.numberType, follow(rets[0]));
|
||||||
|
@ -105,10 +105,10 @@ TEST_CASE_FIXTURE(Fixture, "return_type_should_be_empty_if_nothing_is_returned")
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
const FunctionTypeVar* fTy = get<FunctionTypeVar>(requireType("f"));
|
const FunctionTypeVar* fTy = get<FunctionTypeVar>(requireType("f"));
|
||||||
REQUIRE(fTy != nullptr);
|
REQUIRE(fTy != nullptr);
|
||||||
CHECK_EQ(0, size(fTy->retType));
|
CHECK_EQ(0, size(fTy->retTypes));
|
||||||
const FunctionTypeVar* gTy = get<FunctionTypeVar>(requireType("g"));
|
const FunctionTypeVar* gTy = get<FunctionTypeVar>(requireType("g"));
|
||||||
REQUIRE(gTy != nullptr);
|
REQUIRE(gTy != nullptr);
|
||||||
CHECK_EQ(0, size(gTy->retType));
|
CHECK_EQ(0, size(gTy->retTypes));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "no_return_size_should_be_zero")
|
TEST_CASE_FIXTURE(Fixture, "no_return_size_should_be_zero")
|
||||||
|
@ -125,15 +125,15 @@ TEST_CASE_FIXTURE(Fixture, "no_return_size_should_be_zero")
|
||||||
|
|
||||||
const FunctionTypeVar* fTy = get<FunctionTypeVar>(requireType("f"));
|
const FunctionTypeVar* fTy = get<FunctionTypeVar>(requireType("f"));
|
||||||
REQUIRE(fTy != nullptr);
|
REQUIRE(fTy != nullptr);
|
||||||
CHECK_EQ(1, size(follow(fTy->retType)));
|
CHECK_EQ(1, size(follow(fTy->retTypes)));
|
||||||
|
|
||||||
const FunctionTypeVar* gTy = get<FunctionTypeVar>(requireType("g"));
|
const FunctionTypeVar* gTy = get<FunctionTypeVar>(requireType("g"));
|
||||||
REQUIRE(gTy != nullptr);
|
REQUIRE(gTy != nullptr);
|
||||||
CHECK_EQ(0, size(gTy->retType));
|
CHECK_EQ(0, size(gTy->retTypes));
|
||||||
|
|
||||||
const FunctionTypeVar* hTy = get<FunctionTypeVar>(requireType("h"));
|
const FunctionTypeVar* hTy = get<FunctionTypeVar>(requireType("h"));
|
||||||
REQUIRE(hTy != nullptr);
|
REQUIRE(hTy != nullptr);
|
||||||
CHECK_EQ(0, size(hTy->retType));
|
CHECK_EQ(0, size(hTy->retTypes));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "varargs_inference_through_multiple_scopes")
|
TEST_CASE_FIXTURE(Fixture, "varargs_inference_through_multiple_scopes")
|
||||||
|
|
|
@ -6,40 +6,40 @@
|
||||||
</Type>
|
</Type>
|
||||||
|
|
||||||
<Type Name="Luau::Variant<*>">
|
<Type Name="Luau::Variant<*>">
|
||||||
<DisplayString Condition="typeId == 0" Optional="true">{{ index=0, value={*($T1*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 0" Optional="true">{{ typeId=0, value={*($T1*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 1" Optional="true">{{ index=1, value={*($T2*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 1" Optional="true">{{ typeId=1, value={*($T2*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 2" Optional="true">{{ index=2, value={*($T3*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 2" Optional="true">{{ typeId=2, value={*($T3*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 3" Optional="true">{{ index=3, value={*($T4*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 3" Optional="true">{{ typeId=3, value={*($T4*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 4" Optional="true">{{ index=4, value={*($T5*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 4" Optional="true">{{ typeId=4, value={*($T5*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 5" Optional="true">{{ index=5, value={*($T6*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 5" Optional="true">{{ typeId=5, value={*($T6*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 6" Optional="true">{{ index=6, value={*($T7*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 6" Optional="true">{{ typeId=6, value={*($T7*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 7" Optional="true">{{ index=7, value={*($T8*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 7" Optional="true">{{ typeId=7, value={*($T8*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 8" Optional="true">{{ index=8, value={*($T9*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 8" Optional="true">{{ typeId=8, value={*($T9*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 9" Optional="true">{{ index=9, value={*($T10*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 9" Optional="true">{{ typeId=9, value={*($T10*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 10" Optional="true">{{ index=10, value={*($T11*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 10" Optional="true">{{ typeId=10, value={*($T11*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 11" Optional="true">{{ index=11, value={*($T12*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 11" Optional="true">{{ typeId=11, value={*($T12*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 12" Optional="true">{{ index=12, value={*($T13*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 12" Optional="true">{{ typeId=12, value={*($T13*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 13" Optional="true">{{ index=13, value={*($T14*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 13" Optional="true">{{ typeId=13, value={*($T14*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 14" Optional="true">{{ index=14, value={*($T15*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 14" Optional="true">{{ typeId=14, value={*($T15*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 15" Optional="true">{{ index=15, value={*($T16*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 15" Optional="true">{{ typeId=15, value={*($T16*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 16" Optional="true">{{ index=16, value={*($T17*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 16" Optional="true">{{ typeId=16, value={*($T17*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 17" Optional="true">{{ index=17, value={*($T18*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 17" Optional="true">{{ typeId=17, value={*($T18*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 18" Optional="true">{{ index=18, value={*($T19*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 18" Optional="true">{{ typeId=18, value={*($T19*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 19" Optional="true">{{ index=19, value={*($T20*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 19" Optional="true">{{ typeId=19, value={*($T20*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 20" Optional="true">{{ index=20, value={*($T21*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 20" Optional="true">{{ typeId=20, value={*($T21*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 21" Optional="true">{{ index=21, value={*($T22*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 21" Optional="true">{{ typeId=21, value={*($T22*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 22" Optional="true">{{ index=22, value={*($T23*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 22" Optional="true">{{ typeId=22, value={*($T23*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 23" Optional="true">{{ index=23, value={*($T24*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 23" Optional="true">{{ typeId=23, value={*($T24*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 24" Optional="true">{{ index=24, value={*($T25*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 24" Optional="true">{{ typeId=24, value={*($T25*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 25" Optional="true">{{ index=25, value={*($T26*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 25" Optional="true">{{ typeId=25, value={*($T26*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 26" Optional="true">{{ index=26, value={*($T27*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 26" Optional="true">{{ typeId=26, value={*($T27*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 27" Optional="true">{{ index=27, value={*($T28*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 27" Optional="true">{{ typeId=27, value={*($T28*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 28" Optional="true">{{ index=28, value={*($T29*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 28" Optional="true">{{ typeId=28, value={*($T29*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 29" Optional="true">{{ index=29, value={*($T30*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 29" Optional="true">{{ typeId=29, value={*($T30*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 30" Optional="true">{{ index=30, value={*($T31*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 30" Optional="true">{{ typeId=30, value={*($T31*)storage} }}</DisplayString>
|
||||||
<DisplayString Condition="typeId == 31" Optional="true">{{ index=31, value={*($T32*)storage} }}</DisplayString>
|
<DisplayString Condition="typeId == 31" Optional="true">{{ typeId=31, value={*($T32*)storage} }}</DisplayString>
|
||||||
<Expand>
|
<Expand>
|
||||||
<Item Name="index">typeId</Item>
|
<Item Name="typeId">typeId</Item>
|
||||||
<Item Name="[value]" Condition="typeId == 0" Optional="true">*($T1*)storage</Item>
|
<Item Name="[value]" Condition="typeId == 0" Optional="true">*($T1*)storage</Item>
|
||||||
<Item Name="[value]" Condition="typeId == 1" Optional="true">*($T2*)storage</Item>
|
<Item Name="[value]" Condition="typeId == 1" Optional="true">*($T2*)storage</Item>
|
||||||
<Item Name="[value]" Condition="typeId == 2" Optional="true">*($T3*)storage</Item>
|
<Item Name="[value]" Condition="typeId == 2" Optional="true">*($T3*)storage</Item>
|
||||||
|
|
Loading…
Reference in a new issue