Merge branch 'master' into feat/benchmarks-multi-os

This commit is contained in:
Allan Jeremy 2022-06-16 18:14:27 +03:00
commit 439f59b37f
157 changed files with 9184 additions and 4490 deletions

View file

@ -1,6 +1,7 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/TypeArena.h"
#include "Luau/TypeVar.h"
#include <unordered_map>
@ -18,11 +19,12 @@ struct CloneState
SeenTypePacks seenTypePacks;
int recursionCount = 0;
bool encounteredFreeType = false; // TODO: Remove with LuauLosslessClone.
};
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState);
TypeId clone(TypeId tp, TypeArena& dest, CloneState& cloneState);
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState);
TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log);
} // namespace Luau

View file

@ -0,0 +1,162 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include <memory>
#include <vector>
#include "Luau/Ast.h"
#include "Luau/Module.h"
#include "Luau/Symbol.h"
#include "Luau/TypeVar.h"
#include "Luau/Variant.h"
namespace Luau
{
struct Scope2;
// subType <: superType
struct SubtypeConstraint
{
TypeId subType;
TypeId superType;
};
// subPack <: superPack
struct PackSubtypeConstraint
{
TypePackId subPack;
TypePackId superPack;
};
// subType ~ gen superType
struct GeneralizationConstraint
{
TypeId subType;
TypeId superType;
Scope2* scope;
};
// subType ~ inst superType
struct InstantiationConstraint
{
TypeId subType;
TypeId superType;
};
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint>;
using ConstraintPtr = std::unique_ptr<struct Constraint>;
struct Constraint
{
Constraint(ConstraintV&& c);
Constraint(ConstraintV&& c, std::vector<Constraint*> dependencies);
Constraint(const Constraint&) = delete;
Constraint& operator=(const Constraint&) = delete;
ConstraintV c;
std::vector<Constraint*> dependencies;
};
inline Constraint& asMutable(const Constraint& c)
{
return const_cast<Constraint&>(c);
}
template<typename T>
T* getMutable(Constraint& c)
{
return ::Luau::get_if<T>(&c.c);
}
template<typename T>
const T* get(const Constraint& c)
{
return getMutable<T>(asMutable(c));
}
struct Scope2
{
// The parent scope of this scope. Null if there is no parent (i.e. this
// is the module-level scope).
Scope2* parent = nullptr;
// All the children of this scope.
std::vector<Scope2*> children;
std::unordered_map<Symbol, TypeId> bindings; // TODO: I think this can be a DenseHashMap
TypePackId returnType;
// All constraints belonging to this scope.
std::vector<ConstraintPtr> constraints;
std::optional<TypeId> lookup(Symbol sym);
};
struct ConstraintGraphBuilder
{
// A list of all the scopes in the module. This vector holds ownership of the
// scope pointers; the scopes themselves borrow pointers to other scopes to
// define the scope hierarchy.
std::vector<std::pair<Location, std::unique_ptr<Scope2>>> scopes;
SingletonTypes& singletonTypes;
TypeArena* const arena;
// The root scope of the module we're generating constraints for.
Scope2* rootScope;
explicit ConstraintGraphBuilder(TypeArena* arena);
/**
* Fabricates a new free type belonging to a given scope.
* @param scope the scope the free type belongs to. Must not be null.
*/
TypeId freshType(Scope2* scope);
/**
* Fabricates a new free type pack belonging to a given scope.
* @param scope the scope the free type pack belongs to. Must not be null.
*/
TypePackId freshTypePack(Scope2* scope);
/**
* Fabricates a scope that is a child of another scope.
* @param location the lexical extent of the scope in the source code.
* @param parent the parent scope of the new scope. Must not be null.
*/
Scope2* childScope(Location location, Scope2* parent);
/**
* Adds a new constraint with no dependencies to a given scope.
* @param scope the scope to add the constraint to. Must not be null.
* @param cv the constraint variant to add.
*/
void addConstraint(Scope2* scope, ConstraintV cv);
/**
* Adds a constraint to a given scope.
* @param scope the scope to add the constraint to. Must not be null.
* @param c the constraint to add.
*/
void addConstraint(Scope2* scope, std::unique_ptr<Constraint> c);
/**
* The entry point to the ConstraintGraphBuilder. This will construct a set
* of scopes, constraints, and free types that can be solved later.
* @param block the root block to generate constraints for.
*/
void visit(AstStatBlock* block);
void visit(Scope2* scope, AstStat* stat);
void visit(Scope2* scope, AstStatBlock* block);
void visit(Scope2* scope, AstStatLocal* local);
void visit(Scope2* scope, AstStatLocalFunction* local);
void visit(Scope2* scope, AstStatReturn* local);
TypePackId checkPack(Scope2* scope, AstArray<AstExpr*> exprs);
TypePackId checkPack(Scope2* scope, AstExpr* expr);
TypeId check(Scope2* scope, AstExpr* expr);
};
std::vector<const Constraint*> collectConstraints(Scope2* rootScope);
} // namespace Luau

View file

@ -0,0 +1,106 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Error.h"
#include "Luau/Variant.h"
#include "Luau/ConstraintGraphBuilder.h"
#include "Luau/TypeVar.h"
#include <vector>
namespace Luau
{
// TypeId, TypePackId, or Constraint*. It is impossible to know which, but we
// never dereference this pointer.
using BlockedConstraintId = const void*;
struct ConstraintSolver
{
TypeArena* arena;
InternalErrorReporter iceReporter;
// The entire set of constraints that the solver is trying to resolve.
std::vector<const Constraint*> constraints;
Scope2* rootScope;
std::vector<TypeError> errors;
// This includes every constraint that has not been fully solved.
// A constraint can be both blocked and unsolved, for instance.
std::unordered_set<const Constraint*> unsolvedConstraints;
// A mapping of constraint pointer to how many things the constraint is
// blocked on. Can be empty or 0 for constraints that are not blocked on
// anything.
std::unordered_map<const Constraint*, size_t> blockedConstraints;
// A mapping of type/pack pointers to the constraints they block.
std::unordered_map<BlockedConstraintId, std::vector<const Constraint*>> blocked;
explicit ConstraintSolver(TypeArena* arena, Scope2* rootScope);
/**
* Attempts to dispatch all pending constraints and reach a type solution
* that satisfies all of the constraints, recording any errors that are
* encountered.
**/
void run();
bool done();
bool tryDispatch(const Constraint* c);
bool tryDispatch(const SubtypeConstraint& c);
bool tryDispatch(const PackSubtypeConstraint& c);
bool tryDispatch(const GeneralizationConstraint& c);
bool tryDispatch(const InstantiationConstraint& c, const Constraint* constraint);
/**
* Marks a constraint as being blocked on a type or type pack. The constraint
* solver will not attempt to dispatch blocked constraints until their
* dependencies have made progress.
* @param target the type or type pack pointer that the constraint is blocked on.
* @param constraint the constraint to block.
**/
void block_(BlockedConstraintId target, const Constraint* constraint);
void block(const Constraint* target, const Constraint* constraint);
void block(TypeId target, const Constraint* constraint);
void block(TypePackId target, const Constraint* constraint);
/**
* Informs the solver that progress has been made on a type or type pack. The
* solver will wake up all constraints that are blocked on the type or type pack,
* and will resume attempting to dispatch them.
* @param progressed the type or type pack pointer that has progressed.
**/
void unblock_(BlockedConstraintId progressed);
void unblock(const Constraint* progressed);
void unblock(TypeId progressed);
void unblock(TypePackId progressed);
/**
* Returns whether the constraint is blocked on anything.
* @param constraint the constraint to check.
*/
bool isBlocked(const Constraint* constraint);
void reportErrors(const std::vector<TypeError>& errors);
/**
* Creates a new Unifier and performs a single unification operation. Commits
* the result and reports errors if necessary.
* @param subType the sub-type to unify.
* @param superType the super-type to unify.
*/
void unify(TypeId subType, TypeId superType);
/**
* Creates a new Unifier and performs a single unification operation. Commits
* the result and reports errors if necessary.
* @param subPack the sub-type pack to unify.
* @param superPack the super-type pack to unify.
*/
void unify(TypePackId subPack, TypePackId superPack);
};
void dump(Scope2* rootScope, struct ToStringOptions& opts);
} // namespace Luau

View file

@ -5,6 +5,7 @@
#include "Luau/Location.h"
#include "Luau/TypeVar.h"
#include "Luau/Variant.h"
#include "Luau/TypeArena.h"
namespace Luau
{
@ -108,9 +109,6 @@ struct FunctionDoesNotTakeSelf
struct FunctionRequiresSelf
{
// TODO: Delete with LuauAnyInIsOptionalIsOptional
int requiredExtraNils = 0;
bool operator==(const FunctionRequiresSelf& rhs) const;
};

View file

@ -12,9 +12,6 @@
#include <vector>
#include <optional>
LUAU_FASTFLAG(LuauSeparateTypechecks)
LUAU_FASTFLAG(LuauDirtySourceModule)
namespace Luau
{
@ -60,17 +57,12 @@ struct SourceNode
{
bool hasDirtySourceModule() const
{
LUAU_ASSERT(FFlag::LuauDirtySourceModule);
return dirtySourceModule;
}
bool hasDirtyModule(bool forAutocomplete) const
{
if (FFlag::LuauSeparateTypechecks)
return forAutocomplete ? dirtyModuleForAutocomplete : dirtyModule;
else
return dirtyModule;
return forAutocomplete ? dirtyModuleForAutocomplete : dirtyModule;
}
ModuleName name;
@ -90,10 +82,6 @@ struct FrontendOptions
// is complete.
bool retainFullTypeGraphs = false;
// When true, we run typechecking twice, once in the regular mode, and once in strict mode
// in order to get more precise type information (e.g. for autocomplete).
bool typecheckTwice_DEPRECATED = false;
// Run typechecking only in mode required for autocomplete (strict mode in order to get more precise type information)
bool forAutocomplete = false;
};
@ -171,7 +159,9 @@ struct Frontend
void applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName);
private:
std::pair<SourceNode*, SourceModule*> getSourceNode(CheckResult& checkResult, const ModuleName& name, bool forAutocomplete_DEPRECATED);
ModulePtr check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope);
std::pair<SourceNode*, SourceModule*> getSourceNode(CheckResult& checkResult, const ModuleName& name);
SourceModule parse(const ModuleName& name, std::string_view src, const ParseOptions& parseOptions);
bool parseGraph(std::vector<ModuleName>& buildQueue, CheckResult& checkResult, const ModuleName& root, bool forAutocomplete);

View file

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

View file

@ -34,10 +34,6 @@ const LValue* baseof(const LValue& lvalue);
std::optional<LValue> tryGetLValue(const class AstExpr& expr);
// Utility function: breaks down an LValue to get at the Symbol, and reverses the vector of keys.
// TODO: remove with FFlagLuauTypecheckOptPass
std::pair<Symbol, std::vector<std::string>> getFullName(const LValue& lvalue);
// Utility function: breaks down an LValue to get at the Symbol
Symbol getBaseSymbol(const LValue& lvalue);

View file

@ -2,11 +2,10 @@
#pragma once
#include "Luau/FileResolver.h"
#include "Luau/TypePack.h"
#include "Luau/TypedAllocator.h"
#include "Luau/ParseOptions.h"
#include "Luau/Error.h"
#include "Luau/ParseResult.h"
#include "Luau/TypeArena.h"
#include <memory>
#include <vector>
@ -20,6 +19,7 @@ struct Module;
using ScopePtr = std::shared_ptr<struct Scope>;
using ModulePtr = std::shared_ptr<Module>;
struct Scope2;
/// Root of the AST of a parsed source file
struct SourceModule
@ -54,35 +54,6 @@ struct RequireCycle
std::vector<ModuleName> path; // one of the paths for a require() to go all the way back to the originating module
};
struct TypeArena
{
TypedAllocator<TypeVar> typeVars;
TypedAllocator<TypePackVar> typePacks;
void clear();
template<typename T>
TypeId addType(T tv)
{
if constexpr (std::is_same_v<T, UnionTypeVar>)
LUAU_ASSERT(tv.options.size() >= 2);
return addTV(TypeVar(std::move(tv)));
}
TypeId addTV(TypeVar&& tv);
TypeId freshType(TypeLevel level);
TypePackId addTypePack(std::initializer_list<TypeId> types);
TypePackId addTypePack(std::vector<TypeId> types);
TypePackId addTypePack(TypePack pack);
TypePackId addTypePack(TypePackVar pack);
};
void freeze(TypeArena& arena);
void unfreeze(TypeArena& arena);
struct Module
{
~Module();
@ -95,6 +66,7 @@ struct Module
std::shared_ptr<AstNameTable> names;
std::vector<std::pair<Location, ScopePtr>> scopes; // never empty
std::vector<std::pair<Location, std::unique_ptr<Scope2>>> scope2s; // never empty
DenseHashMap<const AstExpr*, TypeId> astTypes{nullptr};
DenseHashMap<const AstExpr*, TypeId> astExpectedTypes{nullptr};
@ -108,12 +80,11 @@ struct Module
bool timeout = false;
ScopePtr getModuleScope() const;
Scope2* getModuleScope2() const;
// Once a module has been typechecked, we clone its public interface into a separate arena.
// This helps us to force TypeVar ownership into a DAG rather than a DCG.
// Returns true if there were any free types encountered in the public interface. This
// indicates a bug in the type checker that we want to surface.
bool clonePublicInterface(InternalErrorReporter& ice);
void clonePublicInterface(InternalErrorReporter& ice);
};
} // namespace Luau

View file

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

View file

@ -6,6 +6,9 @@
namespace Luau
{
struct Scope2;
void quantify(TypeId ty, TypeLevel level);
void quantify(TypeId ty, Scope2* scope);
} // namespace Luau

View file

@ -1,8 +1,7 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Module.h"
#include "Luau/ModuleResolver.h"
#include "Luau/TypeArena.h"
#include "Luau/TypePack.h"
#include "Luau/TypeVar.h"
#include "Luau/DenseHash.h"

View file

@ -30,6 +30,9 @@ struct Symbol
{
}
template<typename T>
Symbol(const T&) = delete;
AstLocal* local;
AstName global;

View file

@ -3,6 +3,7 @@
#include "Luau/Common.h"
#include "Luau/TypeVar.h"
#include "Luau/ConstraintGraphBuilder.h"
#include <unordered_map>
#include <optional>
@ -28,6 +29,7 @@ struct ToStringOptions
bool functionTypeArguments = false; // If true, output function type argument names when they are available
bool hideTableKind = false; // If true, all tables will be surrounded with plain '{}'
bool hideNamedFunctionTypeParameters = false; // If true, type parameters of functions will be hidden at top-level.
bool hideFunctionSelfArgument = false; // If true, `self: X` will be omitted from the function signature if the function has self
bool indent = false;
size_t maxTableLength = size_t(FInt::LuauTableTypeMaximumStringifierLength); // Only applied to TableTypeVars
size_t maxTypeLength = size_t(FInt::LuauTypeMaximumStringifierLength);
@ -52,6 +54,7 @@ ToStringResult toStringDetailed(TypePackId ty, const ToStringOptions& opts = {})
std::string toString(TypeId ty, const ToStringOptions& opts);
std::string toString(TypePackId ty, const ToStringOptions& opts);
std::string toString(const Constraint& c, ToStringOptions& opts);
// These are offered as overloads rather than a default parameter so that they can be easily invoked from within the MSVC debugger.
// You can use them in watch expressions!
@ -63,6 +66,11 @@ inline std::string toString(TypePackId ty)
{
return toString(ty, ToStringOptions{});
}
inline std::string toString(const Constraint& c)
{
ToStringOptions opts;
return toString(c, opts);
}
std::string toString(const TypeVar& tv, const ToStringOptions& opts = {});
std::string toString(const TypePackVar& tp, const ToStringOptions& opts = {});
@ -73,6 +81,7 @@ std::string toStringNamedFunction(const std::string& funcName, const FunctionTyp
// These functions will dump the type to stdout and can be evaluated in Watch/Immediate windows or as gdb/lldb expression
std::string dump(TypeId ty);
std::string dump(TypePackId ty);
std::string dump(const Constraint& c);
std::string dump(const std::shared_ptr<Scope>& scope, const char* name);

View file

@ -7,8 +7,6 @@
#include "Luau/TypeVar.h"
#include "Luau/TypePack.h"
LUAU_FASTFLAG(LuauTypecheckOptPass)
namespace Luau
{
@ -93,15 +91,6 @@ struct TxnLog
{
}
TxnLog(TxnLog* parent, std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen)
: typeVarChanges(nullptr)
, typePackChanges(nullptr)
, parent(parent)
, sharedSeen(sharedSeen)
{
LUAU_ASSERT(!FFlag::LuauTypecheckOptPass);
}
TxnLog(const TxnLog&) = delete;
TxnLog& operator=(const TxnLog&) = delete;

View file

@ -0,0 +1,42 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/TypedAllocator.h"
#include "Luau/TypeVar.h"
#include "Luau/TypePack.h"
#include <vector>
namespace Luau
{
struct TypeArena
{
TypedAllocator<TypeVar> typeVars;
TypedAllocator<TypePackVar> typePacks;
void clear();
template<typename T>
TypeId addType(T tv)
{
if constexpr (std::is_same_v<T, UnionTypeVar>)
LUAU_ASSERT(tv.options.size() >= 2);
return addTV(TypeVar(std::move(tv)));
}
TypeId addTV(TypeVar&& tv);
TypeId freshType(TypeLevel level);
TypePackId addTypePack(std::initializer_list<TypeId> types);
TypePackId addTypePack(std::vector<TypeId> types);
TypePackId addTypePack(TypePack pack);
TypePackId addTypePack(TypePackVar pack);
};
void freeze(TypeArena& arena);
void unfreeze(TypeArena& arena);
} // namespace Luau

View file

@ -34,45 +34,6 @@ const AstStat* getFallthrough(const AstStat* node);
struct UnifierOptions;
struct Unifier;
// A substitution which replaces generic types in a given set by free types.
struct ReplaceGenerics : Substitution
{
ReplaceGenerics(
const TxnLog* log, TypeArena* arena, TypeLevel level, const std::vector<TypeId>& generics, const std::vector<TypePackId>& genericPacks)
: Substitution(log, arena)
, level(level)
, generics(generics)
, genericPacks(genericPacks)
{
}
TypeLevel level;
std::vector<TypeId> generics;
std::vector<TypePackId> genericPacks;
bool ignoreChildren(TypeId ty) override;
bool isDirty(TypeId ty) override;
bool isDirty(TypePackId tp) override;
TypeId clean(TypeId ty) override;
TypePackId clean(TypePackId tp) override;
};
// A substitution which replaces generic functions by monomorphic functions
struct Instantiation : Substitution
{
Instantiation(const TxnLog* log, TypeArena* arena, TypeLevel level)
: Substitution(log, arena)
, level(level)
{
}
TypeLevel level;
bool ignoreChildren(TypeId ty) override;
bool isDirty(TypeId ty) override;
bool isDirty(TypePackId tp) override;
TypeId clean(TypeId ty) override;
TypePackId clean(TypePackId tp) override;
};
// A substitution which replaces free types by any
struct Anyification : Substitution
{
@ -187,7 +148,6 @@ struct TypeChecker
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprIndexExpr& expr);
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprFunction& expr, std::optional<TypeId> expectedType = std::nullopt);
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprTable& expr, std::optional<TypeId> expectedType = std::nullopt);
ExprResult<TypeId> checkExpr_(const ScopePtr& scope, const AstExprTable& expr, std::optional<TypeId> expectedType = std::nullopt);
ExprResult<TypeId> checkExpr(const ScopePtr& scope, const AstExprUnary& expr);
TypeId checkRelationalOperation(
const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates = {});
@ -395,7 +355,7 @@ private:
const AstArray<AstGenericType>& genericNames, const AstArray<AstGenericTypePack>& genericPackNames, bool useCache = false);
public:
ErrorVec resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense);
void resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense);
private:
void refineLValue(const LValue& lvalue, RefinementMap& refis, const ScopePtr& scope, TypeIdPredicate predicate);
@ -403,14 +363,14 @@ private:
std::optional<TypeId> resolveLValue(const ScopePtr& scope, const LValue& lvalue);
std::optional<TypeId> resolveLValue(const RefinementMap& refis, const ScopePtr& scope, const LValue& lvalue);
void resolve(const PredicateVec& predicates, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr = false);
void resolve(const Predicate& predicate, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr);
void resolve(const TruthyPredicate& truthyP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr);
void resolve(const AndPredicate& andP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
void resolve(const OrPredicate& orP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
void resolve(const IsAPredicate& isaP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
void resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
void resolve(const EqPredicate& eqP, ErrorVec& errVec, RefinementMap& refis, const ScopePtr& scope, bool sense);
void resolve(const PredicateVec& predicates, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr = false);
void resolve(const Predicate& predicate, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr);
void resolve(const TruthyPredicate& truthyP, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr);
void resolve(const AndPredicate& andP, RefinementMap& refis, const ScopePtr& scope, bool sense);
void resolve(const OrPredicate& orP, RefinementMap& refis, const ScopePtr& scope, bool sense);
void resolve(const IsAPredicate& isaP, RefinementMap& refis, const ScopePtr& scope, bool sense);
void resolve(const TypeGuardPredicate& typeguardP, RefinementMap& refis, const ScopePtr& scope, bool sense);
void resolve(const EqPredicate& eqP, RefinementMap& refis, const ScopePtr& scope, bool sense);
bool isNonstrictMode() const;
bool useConstrainedIntersections() const;

View file

@ -48,13 +48,24 @@ struct TypePackVar
explicit TypePackVar(const TypePackVariant& ty);
explicit TypePackVar(TypePackVariant&& ty);
TypePackVar(TypePackVariant&& ty, bool persistent);
bool operator==(const TypePackVar& rhs) const;
TypePackVar& operator=(TypePackVariant&& tp);
TypePackVar& operator=(const TypePackVar& rhs);
// Re-assignes the content of the pack, but doesn't change the owning arena and can't make pack persistent.
void reassign(const TypePackVar& rhs)
{
ty = rhs.ty;
}
TypePackVariant ty;
bool persistent = false;
// Pointer to the type arena that allocated this type.
// Pointer to the type arena that allocated this pack.
TypeArena* owningArena = nullptr;
};

View file

@ -24,6 +24,7 @@ namespace Luau
{
struct TypeArena;
struct Scope2;
/**
* There are three kinds of type variables:
@ -124,6 +125,7 @@ struct ConstrainedTypeVar
std::vector<TypeId> parts;
TypeLevel level;
Scope2* scope = nullptr;
};
// Singleton types https://github.com/Roblox/luau/blob/master/rfcs/syntax-singleton-types.md
@ -255,6 +257,7 @@ struct FunctionTypeVar
std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
TypeLevel level;
Scope2* scope = nullptr;
/// These should all be generic
std::vector<TypeId> generics;
std::vector<TypePackId> genericPacks;
@ -266,6 +269,7 @@ struct FunctionTypeVar
bool hasSelf;
Tags tags;
bool hasNoGenerics = false;
bool generalized = false;
};
enum class TableState
@ -323,13 +327,13 @@ struct TableTypeVar
TableState state = TableState::Unsealed;
TypeLevel level;
Scope2* scope = nullptr;
std::optional<std::string> name;
// Sometimes we throw a type on a name to make for nicer error messages, but without creating any entry in the type namespace
// We need to know which is which when we stringify types.
std::optional<std::string> syntheticName;
std::map<Name, Location> methodDefinitionLocations;
std::vector<TypeId> instantiatedTypeParams;
std::vector<TypePackId> instantiatedTypePackParams;
ModuleName definitionModuleName;
@ -460,6 +464,14 @@ struct TypeVar final
{
}
// Re-assignes the content of the type, but doesn't change the owning arena and can't make type persistent.
void reassign(const TypeVar& rhs)
{
ty = rhs.ty;
normal = rhs.normal;
documentationSymbol = rhs.documentationSymbol;
}
TypeVariant ty;
// Kludge: A persistent TypeVar is one that belongs to the global scope.
@ -481,6 +493,8 @@ struct TypeVar final
TypeVar& operator=(const TypeVariant& rhs);
TypeVar& operator=(TypeVariant&& rhs);
TypeVar& operator=(const TypeVar& rhs);
};
using SeenSet = std::set<std::pair<const void*, const void*>>;

View file

@ -23,6 +23,12 @@ public:
currentBlockSize = kBlockSize;
}
TypedAllocator(const TypedAllocator&) = delete;
TypedAllocator& operator=(const TypedAllocator&) = delete;
TypedAllocator(TypedAllocator&&) = default;
TypedAllocator& operator=(TypedAllocator&&) = default;
~TypedAllocator()
{
if (frozen)

View file

@ -8,6 +8,8 @@
namespace Luau
{
struct Scope2;
/**
* The 'level' of a TypeVar is an indirect way to talk about the scope that it 'belongs' too.
* To start, read http://okmij.org/ftp/ML/generalization.html
@ -82,9 +84,11 @@ using Name = std::string;
struct Free
{
explicit Free(TypeLevel level);
explicit Free(Scope2* scope);
int index;
TypeLevel level;
Scope2* scope = nullptr;
// True if this free type variable is part of a mutually
// recursive type alias whose definitions haven't been
// resolved yet.
@ -111,12 +115,14 @@ struct Generic
Generic();
explicit Generic(TypeLevel level);
explicit Generic(const Name& name);
explicit Generic(Scope2* scope);
Generic(TypeLevel level, const Name& name);
int index;
TypeLevel level;
Scope2* scope = nullptr;
Name name;
bool explicitName;
bool explicitName = false;
private:
static int nextIndex;

View file

@ -5,7 +5,7 @@
#include "Luau/Location.h"
#include "Luau/TxnLog.h"
#include "Luau/TypeInfer.h"
#include "Luau/Module.h" // FIXME: For TypeArena. It merits breaking out into its own header.
#include "Luau/TypeArena.h"
#include "Luau/UnifierSharedState.h"
#include <unordered_set>
@ -32,6 +32,9 @@ struct Widen : Substitution
TypeId clean(TypeId ty) override;
TypePackId clean(TypePackId ty) override;
bool ignoreChildren(TypeId ty) override;
TypeId operator()(TypeId ty);
TypePackId operator()(TypePackId ty);
};
// TODO: Use this more widely.
@ -55,8 +58,6 @@ struct Unifier
UnifierSharedState& sharedState;
Unifier(TypeArena* types, Mode mode, const Location& location, Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog = nullptr);
Unifier(TypeArena* types, Mode mode, std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen, const Location& location, Variance variance,
UnifierSharedState& sharedState, TxnLog* parentLog = nullptr);
// Test whether the two type vars unify. Never commits the result.
ErrorVec canUnify(TypeId subTy, TypeId superTy);

View file

@ -42,7 +42,6 @@ struct UnifierSharedState
InternalErrorReporter* iceHandler;
DenseHashSet<void*> seenAny{nullptr};
DenseHashMap<TypeId, bool> skipCacheForType{nullptr};
DenseHashSet<std::pair<TypeId, TypeId>, TypeIdPairHash> cachedUnify{{nullptr, nullptr}};
DenseHashMap<std::pair<TypeId, TypeId>, TypeErrorData, TypeIdPairHash> cachedUnifyError{{nullptr, nullptr}};

View file

@ -6,6 +6,7 @@
#include <type_traits>
#include <initializer_list>
#include <stddef.h>
#include <utility>
namespace Luau
{
@ -95,6 +96,20 @@ public:
return *this;
}
template<typename T, typename... Args>
T& emplace(Args&&... args)
{
using TT = std::decay_t<T>;
constexpr int tid = getTypeId<T>();
static_assert(tid >= 0, "unsupported T");
tableDtor[typeId](&storage);
typeId = tid;
new (&storage) TT(std::forward<Args>(args)...);
return *reinterpret_cast<T*>(&storage);
}
template<typename T>
const T* get_if() const
{

View file

@ -8,8 +8,8 @@
#include "Luau/TypePack.h"
#include "Luau/TypeVar.h"
LUAU_FASTFLAG(LuauUseVisitRecursionLimit)
LUAU_FASTINT(LuauVisitRecursionLimit)
LUAU_FASTFLAG(LuauNormalizeFlagIsConservative)
namespace Luau
{
@ -61,168 +61,6 @@ inline void unsee(DenseHashSet<void*>& seen, const void* tv)
// When DenseHashSet is used for 'visitTypeVarOnce', where don't forget visited elements
}
template<typename F, typename Set>
void visit(TypePackId tp, F& f, Set& seen);
template<typename F, typename Set>
void visit(TypeId ty, F& f, Set& seen)
{
if (visit_detail::hasSeen(seen, ty))
{
f.cycle(ty);
return;
}
if (auto btv = get<BoundTypeVar>(ty))
{
if (apply(ty, *btv, seen, f))
visit(btv->boundTo, f, seen);
}
else if (auto ftv = get<FreeTypeVar>(ty))
apply(ty, *ftv, seen, f);
else if (auto gtv = get<GenericTypeVar>(ty))
apply(ty, *gtv, seen, f);
else if (auto etv = get<ErrorTypeVar>(ty))
apply(ty, *etv, seen, f);
else if (auto ctv = get<ConstrainedTypeVar>(ty))
{
if (apply(ty, *ctv, seen, f))
{
for (TypeId part : ctv->parts)
visit(part, f, seen);
}
}
else if (auto ptv = get<PrimitiveTypeVar>(ty))
apply(ty, *ptv, seen, f);
else if (auto ftv = get<FunctionTypeVar>(ty))
{
if (apply(ty, *ftv, seen, f))
{
visit(ftv->argTypes, f, seen);
visit(ftv->retType, f, seen);
}
}
else if (auto ttv = get<TableTypeVar>(ty))
{
// Some visitors want to see bound tables, that's why we visit the original type
if (apply(ty, *ttv, seen, f))
{
if (ttv->boundTo)
{
visit(*ttv->boundTo, f, seen);
}
else
{
for (auto& [_name, prop] : ttv->props)
visit(prop.type, f, seen);
if (ttv->indexer)
{
visit(ttv->indexer->indexType, f, seen);
visit(ttv->indexer->indexResultType, f, seen);
}
}
}
}
else if (auto mtv = get<MetatableTypeVar>(ty))
{
if (apply(ty, *mtv, seen, f))
{
visit(mtv->table, f, seen);
visit(mtv->metatable, f, seen);
}
}
else if (auto ctv = get<ClassTypeVar>(ty))
{
if (apply(ty, *ctv, seen, f))
{
for (const auto& [name, prop] : ctv->props)
visit(prop.type, f, seen);
if (ctv->parent)
visit(*ctv->parent, f, seen);
if (ctv->metatable)
visit(*ctv->metatable, f, seen);
}
}
else if (auto atv = get<AnyTypeVar>(ty))
apply(ty, *atv, seen, f);
else if (auto utv = get<UnionTypeVar>(ty))
{
if (apply(ty, *utv, seen, f))
{
for (TypeId optTy : utv->options)
visit(optTy, f, seen);
}
}
else if (auto itv = get<IntersectionTypeVar>(ty))
{
if (apply(ty, *itv, seen, f))
{
for (TypeId partTy : itv->parts)
visit(partTy, f, seen);
}
}
visit_detail::unsee(seen, ty);
}
template<typename F, typename Set>
void visit(TypePackId tp, F& f, Set& seen)
{
if (visit_detail::hasSeen(seen, tp))
{
f.cycle(tp);
return;
}
if (auto btv = get<BoundTypePack>(tp))
{
if (apply(tp, *btv, seen, f))
visit(btv->boundTo, f, seen);
}
else if (auto ftv = get<Unifiable::Free>(tp))
apply(tp, *ftv, seen, f);
else if (auto gtv = get<Unifiable::Generic>(tp))
apply(tp, *gtv, seen, f);
else if (auto etv = get<Unifiable::Error>(tp))
apply(tp, *etv, seen, f);
else if (auto pack = get<TypePack>(tp))
{
apply(tp, *pack, seen, f);
for (TypeId ty : pack->head)
visit(ty, f, seen);
if (pack->tail)
visit(*pack->tail, f, seen);
}
else if (auto pack = get<VariadicTypePack>(tp))
{
apply(tp, *pack, seen, f);
visit(pack->ty, f, seen);
}
visit_detail::unsee(seen, tp);
}
} // namespace visit_detail
template<typename S>
@ -471,18 +309,21 @@ struct GenericTypeVarVisitor
else if (auto pack = get<TypePack>(tp))
{
visit(tp, *pack);
bool res = visit(tp, *pack);
if (!FFlag::LuauNormalizeFlagIsConservative || res)
{
for (TypeId ty : pack->head)
traverse(ty);
for (TypeId ty : pack->head)
traverse(ty);
if (pack->tail)
traverse(*pack->tail);
if (pack->tail)
traverse(*pack->tail);
}
}
else if (auto pack = get<VariadicTypePack>(tp))
{
visit(tp, *pack);
traverse(pack->ty);
bool res = visit(tp, *pack);
if (!FFlag::LuauNormalizeFlagIsConservative || res)
traverse(pack->ty);
}
else
LUAU_ASSERT(!"GenericTypeVarVisitor::traverse(TypePackId) is not exhaustive!");
@ -509,37 +350,4 @@ struct TypeVarOnceVisitor : GenericTypeVarVisitor<DenseHashSet<void*>>
}
};
// Clip with FFlagLuauUseVisitRecursionLimit
template<typename TID, typename F>
void DEPRECATED_visitTypeVar(TID ty, F& f, std::unordered_set<void*>& seen)
{
visit_detail::visit(ty, f, seen);
}
// Delete and inline when clipping FFlagLuauUseVisitRecursionLimit
template<typename TID, typename F>
void DEPRECATED_visitTypeVar(TID ty, F& f)
{
if (FFlag::LuauUseVisitRecursionLimit)
f.traverse(ty);
else
{
std::unordered_set<void*> seen;
visit_detail::visit(ty, f, seen);
}
}
// Delete and inline when clipping FFlagLuauUseVisitRecursionLimit
template<typename TID, typename F>
void DEPRECATED_visitTypeVarOnce(TID ty, F& f, DenseHashSet<void*>& seen)
{
if (FFlag::LuauUseVisitRecursionLimit)
f.traverse(ty);
else
{
seen.clear();
visit_detail::visit(ty, f, seen);
}
}
} // namespace Luau

View file

@ -71,9 +71,11 @@ struct FindFullAncestry final : public AstVisitor
{
std::vector<AstNode*> nodes;
Position pos;
Position documentEnd;
explicit FindFullAncestry(Position pos)
explicit FindFullAncestry(Position pos, Position documentEnd)
: pos(pos)
, documentEnd(documentEnd)
{
}
@ -84,6 +86,16 @@ struct FindFullAncestry final : public AstVisitor
nodes.push_back(node);
return true;
}
// Edge case: If we ask for the node at the position that is the very end of the document
// return the innermost AST element that ends at that position.
if (node->location.end == documentEnd && pos >= documentEnd)
{
nodes.push_back(node);
return true;
}
return false;
}
};
@ -92,7 +104,11 @@ struct FindFullAncestry final : public AstVisitor
std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos)
{
FindFullAncestry finder(pos);
const Position end = source.root->location.end;
if (pos > end)
pos = end;
FindFullAncestry finder(pos, end);
source.root->visit(&finder);
return std::move(finder.nodes);
}

View file

@ -14,8 +14,7 @@
#include <utility>
LUAU_FASTFLAGVARIABLE(LuauIfElseExprFixCompletionIssue, false);
LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteClassSecurityLevel, false);
LUAU_FASTFLAG(LuauSelfCallAutocompleteFix)
LUAU_FASTFLAG(LuauSelfCallAutocompleteFix2)
static const std::unordered_set<std::string> kStatementStartingKeywords = {
"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
@ -248,7 +247,7 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
ty = follow(ty);
auto canUnify = [&typeArena](TypeId subTy, TypeId superTy) {
LUAU_ASSERT(!FFlag::LuauSelfCallAutocompleteFix);
LUAU_ASSERT(!FFlag::LuauSelfCallAutocompleteFix2);
InternalErrorReporter iceReporter;
UnifierSharedState unifierState(&iceReporter);
@ -267,7 +266,7 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
TypeId expectedType = follow(*typeAtPosition);
auto checkFunctionType = [typeArena, &canUnify, &expectedType](const FunctionTypeVar* ftv) {
if (FFlag::LuauSelfCallAutocompleteFix)
if (FFlag::LuauSelfCallAutocompleteFix2)
{
if (std::optional<TypeId> firstRetTy = first(ftv->retType))
return checkTypeMatch(typeArena, *firstRetTy, expectedType);
@ -308,7 +307,7 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ
}
}
if (FFlag::LuauSelfCallAutocompleteFix)
if (FFlag::LuauSelfCallAutocompleteFix2)
return checkTypeMatch(typeArena, ty, expectedType) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
else
return canUnify(ty, expectedType) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
@ -325,7 +324,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
const std::vector<AstNode*>& nodes, AutocompleteEntryMap& result, std::unordered_set<TypeId>& seen,
std::optional<const ClassTypeVar*> containingClass = std::nullopt)
{
if (FFlag::LuauSelfCallAutocompleteFix)
if (FFlag::LuauSelfCallAutocompleteFix2)
rootTy = follow(rootTy);
ty = follow(ty);
@ -335,7 +334,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
seen.insert(ty);
auto isWrongIndexer_DEPRECATED = [indexType, useStrictFunctionIndexers = !!get<ClassTypeVar>(ty)](Luau::TypeId type) {
LUAU_ASSERT(!FFlag::LuauSelfCallAutocompleteFix);
LUAU_ASSERT(!FFlag::LuauSelfCallAutocompleteFix2);
if (indexType == PropIndexType::Key)
return false;
@ -368,7 +367,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
}
};
auto isWrongIndexer = [typeArena, rootTy, indexType](Luau::TypeId type) {
LUAU_ASSERT(FFlag::LuauSelfCallAutocompleteFix);
LUAU_ASSERT(FFlag::LuauSelfCallAutocompleteFix2);
if (indexType == PropIndexType::Key)
return false;
@ -382,10 +381,15 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
return calledWithSelf == ftv->hasSelf;
}
if (std::optional<TypeId> firstArgTy = first(ftv->argTypes))
// If a call is made with ':', it is invalid if a function has incompatible first argument or no arguments at all
// If a call is made with '.', but it was declared with 'self', it is considered invalid if first argument is compatible
if (calledWithSelf || ftv->hasSelf)
{
if (checkTypeMatch(typeArena, rootTy, *firstArgTy))
return calledWithSelf;
if (std::optional<TypeId> firstArgTy = first(ftv->argTypes))
{
if (checkTypeMatch(typeArena, rootTy, *firstArgTy))
return calledWithSelf;
}
}
return !calledWithSelf;
@ -427,7 +431,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
AutocompleteEntryKind::Property,
type,
prop.deprecated,
FFlag::LuauSelfCallAutocompleteFix ? isWrongIndexer(type) : isWrongIndexer_DEPRECATED(type),
FFlag::LuauSelfCallAutocompleteFix2 ? isWrongIndexer(type) : isWrongIndexer_DEPRECATED(type),
typeCorrect,
containingClass,
&prop,
@ -462,8 +466,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
containingClass = containingClass.value_or(cls);
fillProps(cls->props);
if (cls->parent)
autocompleteProps(module, typeArena, rootTy, *cls->parent, indexType, nodes, result, seen,
FFlag::LuauFixAutocompleteClassSecurityLevel ? containingClass : cls);
autocompleteProps(module, typeArena, rootTy, *cls->parent, indexType, nodes, result, seen, containingClass);
}
else if (auto tbl = get<TableTypeVar>(ty))
fillProps(tbl->props);
@ -471,7 +474,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
{
autocompleteProps(module, typeArena, rootTy, mt->table, indexType, nodes, result, seen);
if (FFlag::LuauSelfCallAutocompleteFix)
if (FFlag::LuauSelfCallAutocompleteFix2)
{
if (auto mtable = get<TableTypeVar>(mt->metatable))
fillMetatableProps(mtable);
@ -537,7 +540,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
AutocompleteEntryMap inner;
std::unordered_set<TypeId> innerSeen;
if (!FFlag::LuauSelfCallAutocompleteFix)
if (!FFlag::LuauSelfCallAutocompleteFix2)
innerSeen = seen;
if (isNil(*iter))
@ -563,7 +566,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
++iter;
}
}
else if (auto pt = get<PrimitiveTypeVar>(ty); pt && FFlag::LuauSelfCallAutocompleteFix)
else if (auto pt = get<PrimitiveTypeVar>(ty); pt && FFlag::LuauSelfCallAutocompleteFix2)
{
if (pt->metatable)
{
@ -571,7 +574,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId
fillMetatableProps(mtable);
}
}
else if (FFlag::LuauSelfCallAutocompleteFix && get<StringSingleton>(get<SingletonTypeVar>(ty)))
else if (FFlag::LuauSelfCallAutocompleteFix2 && get<StringSingleton>(get<SingletonTypeVar>(ty)))
{
autocompleteProps(module, typeArena, rootTy, getSingletonTypes().stringType, indexType, nodes, result, seen);
}
@ -1501,7 +1504,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
TypeId ty = follow(*it);
PropIndexType indexType = indexName->op == ':' ? PropIndexType::Colon : PropIndexType::Point;
if (!FFlag::LuauSelfCallAutocompleteFix && isString(ty))
if (!FFlag::LuauSelfCallAutocompleteFix2 && isString(ty))
return {autocompleteProps(*module, typeArena, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, finder.ancestry),
finder.ancestry};
else
@ -1700,31 +1703,18 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback)
{
if (FFlag::LuauSeparateTypechecks)
{
// FIXME: We can improve performance here by parsing without checking.
// The old type graph is probably fine. (famous last words!)
FrontendOptions opts;
opts.forAutocomplete = true;
frontend.check(moduleName, opts);
}
else
{
// FIXME: We can improve performance here by parsing without checking.
// The old type graph is probably fine. (famous last words!)
// FIXME: We don't need to typecheck for script analysis here, just for autocomplete.
frontend.check(moduleName);
}
// FIXME: We can improve performance here by parsing without checking.
// The old type graph is probably fine. (famous last words!)
FrontendOptions opts;
opts.forAutocomplete = true;
frontend.check(moduleName, opts);
const SourceModule* sourceModule = frontend.getSourceModule(moduleName);
if (!sourceModule)
return {};
TypeChecker& typeChecker =
(frontend.options.typecheckTwice_DEPRECATED || FFlag::LuauSeparateTypechecks ? frontend.typeCheckerForAutocomplete : frontend.typeChecker);
ModulePtr module =
(frontend.options.typecheckTwice_DEPRECATED || FFlag::LuauSeparateTypechecks ? frontend.moduleResolverForAutocomplete.getModule(moduleName)
: frontend.moduleResolver.getModule(moduleName));
TypeChecker& typeChecker = frontend.typeCheckerForAutocomplete;
ModulePtr module = frontend.moduleResolverForAutocomplete.getModule(moduleName);
if (!module)
return {};
@ -1752,9 +1742,7 @@ OwningAutocompleteResult autocompleteSource(Frontend& frontend, std::string_view
sourceModule->mode = Mode::Strict;
sourceModule->commentLocations = std::move(result.commentLocations);
TypeChecker& typeChecker =
(frontend.options.typecheckTwice_DEPRECATED || FFlag::LuauSeparateTypechecks ? frontend.typeCheckerForAutocomplete : frontend.typeChecker);
TypeChecker& typeChecker = frontend.typeCheckerForAutocomplete;
ModulePtr module = typeChecker.check(*sourceModule, Mode::Strict);
OwningAutocompleteResult autocompleteResult = {

View file

@ -8,7 +8,6 @@
#include <algorithm>
LUAU_FASTFLAG(LuauAssertStripsFalsyTypes)
LUAU_FASTFLAGVARIABLE(LuauSetMetaTableArgsCheck, false)
/** FIXME: Many of these type definitions are not quite completely accurate.
@ -180,44 +179,13 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
LUAU_ASSERT(!typeChecker.globalTypes.typeVars.isFrozen());
LUAU_ASSERT(!typeChecker.globalTypes.typePacks.isFrozen());
TypeId numberType = typeChecker.numberType;
TypeId booleanType = typeChecker.booleanType;
TypeId nilType = typeChecker.nilType;
TypeArena& arena = typeChecker.globalTypes;
TypePackId oneNumberPack = arena.addTypePack({numberType});
TypePackId oneBooleanPack = arena.addTypePack({booleanType});
TypePackId numberVariadicList = arena.addTypePack(TypePackVar{VariadicTypePack{numberType}});
TypePackId listOfAtLeastOneNumber = arena.addTypePack(TypePack{{numberType}, numberVariadicList});
TypeId listOfAtLeastOneNumberToNumberType = arena.addType(FunctionTypeVar{
listOfAtLeastOneNumber,
oneNumberPack,
});
TypeId listOfAtLeastZeroNumbersToNumberType = arena.addType(FunctionTypeVar{numberVariadicList, oneNumberPack});
LoadDefinitionFileResult loadResult = Luau::loadDefinitionFile(typeChecker, typeChecker.globalScope, getBuiltinDefinitionSource(), "@luau");
LUAU_ASSERT(loadResult.success);
TypeId mathLibType = getGlobalBinding(typeChecker, "math");
if (TableTypeVar* ttv = getMutable<TableTypeVar>(mathLibType))
{
ttv->props["min"] = makeProperty(listOfAtLeastOneNumberToNumberType, "@luau/global/math.min");
ttv->props["max"] = makeProperty(listOfAtLeastOneNumberToNumberType, "@luau/global/math.max");
}
TypeId bit32LibType = getGlobalBinding(typeChecker, "bit32");
if (TableTypeVar* ttv = getMutable<TableTypeVar>(bit32LibType))
{
ttv->props["band"] = makeProperty(listOfAtLeastZeroNumbersToNumberType, "@luau/global/bit32.band");
ttv->props["bor"] = makeProperty(listOfAtLeastZeroNumbersToNumberType, "@luau/global/bit32.bor");
ttv->props["bxor"] = makeProperty(listOfAtLeastZeroNumbersToNumberType, "@luau/global/bit32.bxor");
ttv->props["btest"] = makeProperty(arena.addType(FunctionTypeVar{listOfAtLeastOneNumber, oneBooleanPack}), "@luau/global/bit32.btest");
}
TypeId genericK = arena.addType(GenericTypeVar{"K"});
TypeId genericV = arena.addType(GenericTypeVar{"V"});
TypeId mapOfKtoV = arena.addType(TableTypeVar{{}, TableIndexer(genericK, genericV), typeChecker.globalScope->level, TableState::Generic});
@ -232,7 +200,7 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
addGlobalBinding(typeChecker, "string", it->second.type, "@luau");
// next<K, V>(t: Table<K, V>, i: K | nil) -> (K, V)
// next<K, V>(t: Table<K, V>, i: K?) -> (K, V)
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(typeChecker, arena, genericK)}});
addGlobalBinding(typeChecker, "next",
arena.addType(FunctionTypeVar{{genericK, genericV}, {}, nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})}), "@luau");
@ -242,8 +210,7 @@ void registerBuiltinTypes(TypeChecker& typeChecker)
TypeId pairsNext = arena.addType(FunctionTypeVar{nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})});
TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, nilType}});
// NOTE we are missing 'i: K | nil' argument in the first return types' argument.
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>) -> (K, V), Table<K, V>, nil)
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil)
addGlobalBinding(typeChecker, "pairs", arena.addType(FunctionTypeVar{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
TypeId genericMT = arena.addType(GenericTypeVar{"MT"});
@ -408,41 +375,29 @@ static std::optional<ExprResult<TypePackId>> magicFunctionAssert(
{
auto [paramPack, predicates] = exprResult;
if (FFlag::LuauAssertStripsFalsyTypes)
TypeArena& arena = typechecker.currentModule->internalTypes;
auto [head, tail] = flatten(paramPack);
if (head.empty() && tail)
{
TypeArena& arena = typechecker.currentModule->internalTypes;
auto [head, tail] = flatten(paramPack);
if (head.empty() && tail)
{
std::optional<TypeId> fst = first(*tail);
if (!fst)
return ExprResult<TypePackId>{paramPack};
head.push_back(*fst);
}
typechecker.reportErrors(typechecker.resolve(predicates, scope, true));
if (head.size() > 0)
{
std::optional<TypeId> newhead = typechecker.pickTypesFromSense(head[0], true);
if (!newhead)
head = {typechecker.nilType};
else
head[0] = *newhead;
}
return ExprResult<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})};
}
else
{
if (expr.args.size < 1)
std::optional<TypeId> fst = first(*tail);
if (!fst)
return ExprResult<TypePackId>{paramPack};
typechecker.reportErrors(typechecker.resolve(predicates, scope, true));
return ExprResult<TypePackId>{paramPack};
head.push_back(*fst);
}
typechecker.resolve(predicates, scope, true);
if (head.size() > 0)
{
std::optional<TypeId> newhead = typechecker.pickTypesFromSense(head[0], true);
if (!newhead)
head = {typechecker.nilType};
else
head[0] = *newhead;
}
return ExprResult<TypePackId>{arena.addTypePack(TypePack{std::move(head), tail})};
}
static std::optional<ExprResult<TypePackId>> magicFunctionPack(

View file

@ -1,16 +1,14 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Clone.h"
#include "Luau/Module.h"
#include "Luau/RecursionCounter.h"
#include "Luau/TxnLog.h"
#include "Luau/TypePack.h"
#include "Luau/Unifiable.h"
LUAU_FASTFLAG(DebugLuauCopyBeforeNormalizing)
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
LUAU_FASTFLAG(LuauTypecheckOptPass)
LUAU_FASTFLAGVARIABLE(LuauLosslessClone, false)
namespace Luau
{
@ -88,20 +86,8 @@ struct TypePackCloner
void operator()(const Unifiable::Free& t)
{
if (FFlag::LuauLosslessClone)
{
defaultClone(t);
}
else
{
cloneState.encounteredFreeType = true;
TypePackId err = getSingletonTypes().errorRecoveryTypePack(getSingletonTypes().anyTypePack);
TypePackId cloned = dest.addTypePack(*err);
seenTypePacks[typePackId] = cloned;
}
defaultClone(t);
}
void operator()(const Unifiable::Generic& t)
{
defaultClone(t);
@ -151,18 +137,7 @@ void TypeCloner::defaultClone(const T& t)
void TypeCloner::operator()(const Unifiable::Free& t)
{
if (FFlag::LuauLosslessClone)
{
defaultClone(t);
}
else
{
cloneState.encounteredFreeType = true;
TypeId err = getSingletonTypes().errorRecoveryType(getSingletonTypes().anyType);
TypeId cloned = dest.addType(*err);
seenTypes[typeId] = cloned;
}
defaultClone(t);
}
void TypeCloner::operator()(const Unifiable::Generic& t)
@ -190,9 +165,6 @@ void TypeCloner::operator()(const PrimitiveTypeVar& t)
void TypeCloner::operator()(const ConstrainedTypeVar& t)
{
if (!FFlag::LuauLosslessClone)
cloneState.encounteredFreeType = true;
TypeId res = dest.addType(ConstrainedTypeVar{t.level});
ConstrainedTypeVar* ctv = getMutable<ConstrainedTypeVar>(res);
LUAU_ASSERT(ctv);
@ -229,9 +201,7 @@ void TypeCloner::operator()(const FunctionTypeVar& t)
ftv->argTypes = clone(t.argTypes, dest, cloneState);
ftv->argNames = t.argNames;
ftv->retType = clone(t.retType, dest, cloneState);
if (FFlag::LuauTypecheckOptPass)
ftv->hasNoGenerics = t.hasNoGenerics;
ftv->hasNoGenerics = t.hasNoGenerics;
}
void TypeCloner::operator()(const TableTypeVar& t)
@ -269,15 +239,7 @@ void TypeCloner::operator()(const TableTypeVar& t)
for (TypePackId& arg : ttv->instantiatedTypePackParams)
arg = clone(arg, dest, cloneState);
if (!FFlag::LuauLosslessClone && ttv->state == TableState::Free)
{
cloneState.encounteredFreeType = true;
ttv->state = TableState::Sealed;
}
ttv->definitionModuleName = t.definitionModuleName;
ttv->methodDefinitionLocations = t.methodDefinitionLocations;
ttv->tags = t.tags;
}
@ -418,4 +380,65 @@ TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState)
return result;
}
TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log)
{
ty = log->follow(ty);
TypeId result = ty;
if (auto pty = log->pending(ty))
ty = &pty->pending;
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
{
FunctionTypeVar clone = FunctionTypeVar{ftv->level, ftv->argTypes, ftv->retType, ftv->definition, ftv->hasSelf};
clone.generics = ftv->generics;
clone.genericPacks = ftv->genericPacks;
clone.magicFunction = ftv->magicFunction;
clone.tags = ftv->tags;
clone.argNames = ftv->argNames;
result = dest.addType(std::move(clone));
}
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
{
LUAU_ASSERT(!ttv->boundTo);
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state};
clone.definitionModuleName = ttv->definitionModuleName;
clone.name = ttv->name;
clone.syntheticName = ttv->syntheticName;
clone.instantiatedTypeParams = ttv->instantiatedTypeParams;
clone.instantiatedTypePackParams = ttv->instantiatedTypePackParams;
clone.tags = ttv->tags;
result = dest.addType(std::move(clone));
}
else if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(ty))
{
MetatableTypeVar clone = MetatableTypeVar{mtv->table, mtv->metatable};
clone.syntheticName = mtv->syntheticName;
result = dest.addType(std::move(clone));
}
else if (const UnionTypeVar* utv = get<UnionTypeVar>(ty))
{
UnionTypeVar clone;
clone.options = utv->options;
result = dest.addType(std::move(clone));
}
else if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(ty))
{
IntersectionTypeVar clone;
clone.parts = itv->parts;
result = dest.addType(std::move(clone));
}
else if (const ConstrainedTypeVar* ctv = get<ConstrainedTypeVar>(ty))
{
ConstrainedTypeVar clone{ctv->level, ctv->parts};
result = dest.addType(std::move(clone));
}
else
return result;
asMutable(result)->documentationSymbol = ty->documentationSymbol;
return result;
}
} // namespace Luau

View file

@ -0,0 +1,300 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/ConstraintGraphBuilder.h"
namespace Luau
{
Constraint::Constraint(ConstraintV&& c)
: c(std::move(c))
{
}
Constraint::Constraint(ConstraintV&& c, std::vector<Constraint*> dependencies)
: c(std::move(c))
, dependencies(dependencies)
{
}
std::optional<TypeId> Scope2::lookup(Symbol sym)
{
Scope2* s = this;
while (true)
{
auto it = s->bindings.find(sym);
if (it != s->bindings.end())
return it->second;
if (s->parent)
s = s->parent;
else
return std::nullopt;
}
}
ConstraintGraphBuilder::ConstraintGraphBuilder(TypeArena* arena)
: singletonTypes(getSingletonTypes())
, arena(arena)
, rootScope(nullptr)
{
LUAU_ASSERT(arena);
}
TypeId ConstraintGraphBuilder::freshType(Scope2* scope)
{
LUAU_ASSERT(scope);
return arena->addType(FreeTypeVar{scope});
}
TypePackId ConstraintGraphBuilder::freshTypePack(Scope2* scope)
{
LUAU_ASSERT(scope);
FreeTypePack f{scope};
return arena->addTypePack(TypePackVar{std::move(f)});
}
Scope2* ConstraintGraphBuilder::childScope(Location location, Scope2* parent)
{
LUAU_ASSERT(parent);
auto scope = std::make_unique<Scope2>();
Scope2* borrow = scope.get();
scopes.emplace_back(location, std::move(scope));
borrow->parent = parent;
borrow->returnType = parent->returnType;
parent->children.push_back(borrow);
return borrow;
}
void ConstraintGraphBuilder::addConstraint(Scope2* scope, ConstraintV cv)
{
LUAU_ASSERT(scope);
scope->constraints.emplace_back(new Constraint{std::move(cv)});
}
void ConstraintGraphBuilder::addConstraint(Scope2* scope, std::unique_ptr<Constraint> c)
{
LUAU_ASSERT(scope);
scope->constraints.emplace_back(std::move(c));
}
void ConstraintGraphBuilder::visit(AstStatBlock* block)
{
LUAU_ASSERT(scopes.empty());
LUAU_ASSERT(rootScope == nullptr);
scopes.emplace_back(block->location, std::make_unique<Scope2>());
rootScope = scopes.back().second.get();
rootScope->returnType = freshTypePack(rootScope);
visit(rootScope, block);
}
void ConstraintGraphBuilder::visit(Scope2* scope, AstStat* stat)
{
LUAU_ASSERT(scope);
if (auto s = stat->as<AstStatBlock>())
visit(scope, s);
else if (auto s = stat->as<AstStatLocal>())
visit(scope, s);
else if (auto f = stat->as<AstStatLocalFunction>())
visit(scope, f);
else if (auto r = stat->as<AstStatReturn>())
visit(scope, r);
else
LUAU_ASSERT(0);
}
void ConstraintGraphBuilder::visit(Scope2* scope, AstStatLocal* local)
{
LUAU_ASSERT(scope);
std::vector<TypeId> varTypes;
for (AstLocal* local : local->vars)
{
// TODO annotations
TypeId ty = freshType(scope);
varTypes.push_back(ty);
scope->bindings[local] = ty;
}
for (size_t i = 0; i < local->vars.size; ++i)
{
if (i < local->values.size)
{
TypeId exprType = check(scope, local->values.data[i]);
addConstraint(scope, SubtypeConstraint{varTypes[i], exprType});
}
}
}
void addConstraints(Constraint* constraint, Scope2* scope)
{
LUAU_ASSERT(scope);
scope->constraints.reserve(scope->constraints.size() + scope->constraints.size());
for (const auto& c : scope->constraints)
constraint->dependencies.push_back(c.get());
for (Scope2* childScope : scope->children)
addConstraints(constraint, childScope);
}
void ConstraintGraphBuilder::visit(Scope2* scope, AstStatLocalFunction* function)
{
LUAU_ASSERT(scope);
// Local
// Global
// Dotted path
// Self?
TypeId functionType = nullptr;
auto ty = scope->lookup(function->name);
LUAU_ASSERT(!ty.has_value()); // The parser ensures that every local function has a distinct Symbol for its name.
functionType = freshType(scope);
scope->bindings[function->name] = functionType;
Scope2* innerScope = childScope(function->func->body->location, scope);
TypePackId returnType = freshTypePack(scope);
innerScope->returnType = returnType;
std::vector<TypeId> argTypes;
for (AstLocal* local : function->func->args)
{
TypeId t = freshType(innerScope);
argTypes.push_back(t);
innerScope->bindings[local] = t; // TODO annotations
}
for (AstStat* stat : function->func->body->body)
visit(innerScope, stat);
FunctionTypeVar actualFunction{arena->addTypePack(argTypes), returnType};
TypeId actualFunctionType = arena->addType(std::move(actualFunction));
std::unique_ptr<Constraint> c{new Constraint{GeneralizationConstraint{functionType, actualFunctionType, innerScope}}};
addConstraints(c.get(), innerScope);
addConstraint(scope, std::move(c));
}
void ConstraintGraphBuilder::visit(Scope2* scope, AstStatReturn* ret)
{
LUAU_ASSERT(scope);
TypePackId exprTypes = checkPack(scope, ret->list);
addConstraint(scope, PackSubtypeConstraint{exprTypes, scope->returnType});
}
void ConstraintGraphBuilder::visit(Scope2* scope, AstStatBlock* block)
{
LUAU_ASSERT(scope);
for (AstStat* stat : block->body)
visit(scope, stat);
}
TypePackId ConstraintGraphBuilder::checkPack(Scope2* scope, AstArray<AstExpr*> exprs)
{
LUAU_ASSERT(scope);
if (exprs.size == 0)
return arena->addTypePack({});
std::vector<TypeId> types;
TypePackId last = nullptr;
for (size_t i = 0; i < exprs.size; ++i)
{
if (i < exprs.size - 1)
types.push_back(check(scope, exprs.data[i]));
else
last = checkPack(scope, exprs.data[i]);
}
LUAU_ASSERT(last != nullptr);
return arena->addTypePack(TypePack{std::move(types), last});
}
TypePackId ConstraintGraphBuilder::checkPack(Scope2* scope, AstExpr* expr)
{
LUAU_ASSERT(scope);
// TEMP TEMP TEMP HACK HACK HACK FIXME FIXME
TypeId t = check(scope, expr);
return arena->addTypePack({t});
}
TypeId ConstraintGraphBuilder::check(Scope2* scope, AstExpr* expr)
{
LUAU_ASSERT(scope);
if (auto a = expr->as<AstExprConstantString>())
return singletonTypes.stringType;
else if (auto a = expr->as<AstExprConstantNumber>())
return singletonTypes.numberType;
else if (auto a = expr->as<AstExprConstantBool>())
return singletonTypes.booleanType;
else if (auto a = expr->as<AstExprConstantNil>())
return singletonTypes.nilType;
else if (auto a = expr->as<AstExprLocal>())
{
std::optional<TypeId> ty = scope->lookup(a->local);
if (ty)
return *ty;
else
return singletonTypes.errorRecoveryType(singletonTypes.anyType); // FIXME? Record an error at this point?
}
else if (auto a = expr->as<AstExprCall>())
{
std::vector<TypeId> args;
for (AstExpr* arg : a->args)
{
args.push_back(check(scope, arg));
}
TypeId fnType = check(scope, a->func);
TypeId instantiatedType = freshType(scope);
addConstraint(scope, InstantiationConstraint{instantiatedType, fnType});
TypeId firstRet = freshType(scope);
TypePackId rets = arena->addTypePack(TypePack{{firstRet}, arena->addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}})});
FunctionTypeVar ftv(arena->addTypePack(TypePack{args, {}}), rets);
TypeId inferredFnType = arena->addType(ftv);
addConstraint(scope, SubtypeConstraint{inferredFnType, instantiatedType});
return firstRet;
}
else
{
LUAU_ASSERT(0);
return freshType(scope);
}
}
static void collectConstraints(std::vector<const Constraint*>& result, Scope2* scope)
{
for (const auto& c : scope->constraints)
result.push_back(c.get());
for (Scope2* child : scope->children)
collectConstraints(result, child);
}
std::vector<const Constraint*> collectConstraints(Scope2* rootScope)
{
std::vector<const Constraint*> result;
collectConstraints(result, rootScope);
return result;
}
} // namespace Luau

View file

@ -0,0 +1,306 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/ConstraintSolver.h"
#include "Luau/Instantiation.h"
#include "Luau/Quantify.h"
#include "Luau/ToString.h"
#include "Luau/Unifier.h"
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false);
namespace Luau
{
[[maybe_unused]] static void dumpBindings(Scope2* scope, ToStringOptions& opts)
{
for (const auto& [k, v] : scope->bindings)
{
auto d = toStringDetailed(v, opts);
opts.nameMap = d.nameMap;
printf("\t%s : %s\n", k.c_str(), d.name.c_str());
}
for (Scope2* child : scope->children)
dumpBindings(child, opts);
}
static void dumpConstraints(Scope2* scope, ToStringOptions& opts)
{
for (const ConstraintPtr& c : scope->constraints)
{
printf("\t%s\n", toString(*c, opts).c_str());
}
for (Scope2* child : scope->children)
dumpConstraints(child, opts);
}
void dump(Scope2* rootScope, ToStringOptions& opts)
{
printf("constraints:\n");
dumpConstraints(rootScope, opts);
}
void dump(ConstraintSolver* cs, ToStringOptions& opts)
{
printf("constraints:\n");
for (const Constraint* c : cs->unsolvedConstraints)
{
printf("\t%s\n", toString(*c, opts).c_str());
for (const Constraint* dep : c->dependencies)
printf("\t\t%s\n", toString(*dep, opts).c_str());
}
}
ConstraintSolver::ConstraintSolver(TypeArena* arena, Scope2* rootScope)
: arena(arena)
, constraints(collectConstraints(rootScope))
, rootScope(rootScope)
{
for (const Constraint* c : constraints)
{
unsolvedConstraints.insert(c);
for (const Constraint* dep : c->dependencies)
{
block(dep, c);
}
}
}
void ConstraintSolver::run()
{
if (done())
return;
bool progress = false;
ToStringOptions opts;
if (FFlag::DebugLuauLogSolver)
{
printf("Starting solver\n");
dump(this, opts);
}
do
{
progress = false;
auto it = begin(unsolvedConstraints);
auto endIt = end(unsolvedConstraints);
while (it != endIt)
{
if (isBlocked(*it))
{
++it;
continue;
}
std::string saveMe = FFlag::DebugLuauLogSolver ? toString(**it, opts) : std::string{};
bool success = tryDispatch(*it);
progress = progress || success;
auto saveIt = it;
++it;
if (success)
{
unsolvedConstraints.erase(saveIt);
if (FFlag::DebugLuauLogSolver)
{
printf("Dispatched\n\t%s\n", saveMe.c_str());
dump(this, opts);
}
}
}
} while (progress);
if (FFlag::DebugLuauLogSolver)
dumpBindings(rootScope, opts);
LUAU_ASSERT(done());
}
bool ConstraintSolver::done()
{
return unsolvedConstraints.empty();
}
bool ConstraintSolver::tryDispatch(const Constraint* constraint)
{
if (isBlocked(constraint))
return false;
bool success = false;
if (auto sc = get<SubtypeConstraint>(*constraint))
success = tryDispatch(*sc);
else if (auto psc = get<PackSubtypeConstraint>(*constraint))
success = tryDispatch(*psc);
else if (auto gc = get<GeneralizationConstraint>(*constraint))
success = tryDispatch(*gc);
else if (auto ic = get<InstantiationConstraint>(*constraint))
success = tryDispatch(*ic, constraint);
else
LUAU_ASSERT(0);
if (success)
{
unblock(constraint);
}
return success;
}
bool ConstraintSolver::tryDispatch(const SubtypeConstraint& c)
{
unify(c.subType, c.superType);
unblock(c.subType);
unblock(c.superType);
return true;
}
bool ConstraintSolver::tryDispatch(const PackSubtypeConstraint& c)
{
unify(c.subPack, c.superPack);
unblock(c.subPack);
unblock(c.superPack);
return true;
}
bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& constraint)
{
unify(constraint.subType, constraint.superType);
quantify(constraint.superType, constraint.scope);
unblock(constraint.subType);
unblock(constraint.superType);
return true;
}
bool ConstraintSolver::tryDispatch(const InstantiationConstraint& c, const Constraint* constraint)
{
TypeId superType = follow(c.superType);
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(superType))
{
if (!ftv->generalized)
{
block(superType, constraint);
return false;
}
}
else if (get<FreeTypeVar>(superType))
{
block(superType, constraint);
return false;
}
// TODO: Error if it's a primitive or something
Instantiation inst(TxnLog::empty(), arena, TypeLevel{});
std::optional<TypeId> instantiated = inst.substitute(c.superType);
LUAU_ASSERT(instantiated); // TODO FIXME HANDLE THIS
unify(c.subType, *instantiated);
unblock(c.subType);
return true;
}
void ConstraintSolver::block_(BlockedConstraintId target, const Constraint* constraint)
{
blocked[target].push_back(constraint);
auto& count = blockedConstraints[constraint];
count += 1;
}
void ConstraintSolver::block(const Constraint* target, const Constraint* constraint)
{
block_(target, constraint);
}
void ConstraintSolver::block(TypeId target, const Constraint* constraint)
{
block_(target, constraint);
}
void ConstraintSolver::block(TypePackId target, const Constraint* constraint)
{
block_(target, constraint);
}
void ConstraintSolver::unblock_(BlockedConstraintId progressed)
{
auto it = blocked.find(progressed);
if (it == blocked.end())
return;
// unblocked should contain a value always, because of the above check
for (const Constraint* unblockedConstraint : it->second)
{
auto& count = blockedConstraints[unblockedConstraint];
// This assertion being hit indicates that `blocked` and
// `blockedConstraints` desynchronized at some point. This is problematic
// because we rely on this count being correct to skip over blocked
// constraints.
LUAU_ASSERT(count > 0);
count -= 1;
}
blocked.erase(it);
}
void ConstraintSolver::unblock(const Constraint* progressed)
{
return unblock_(progressed);
}
void ConstraintSolver::unblock(TypeId progressed)
{
return unblock_(progressed);
}
void ConstraintSolver::unblock(TypePackId progressed)
{
return unblock_(progressed);
}
bool ConstraintSolver::isBlocked(const Constraint* constraint)
{
auto blockedIt = blockedConstraints.find(constraint);
return blockedIt != blockedConstraints.end() && blockedIt->second > 0;
}
void ConstraintSolver::reportErrors(const std::vector<TypeError>& errors)
{
this->errors.insert(end(this->errors), begin(errors), end(errors));
}
void ConstraintSolver::unify(TypeId subType, TypeId superType)
{
UnifierSharedState sharedState{&iceReporter};
Unifier u{arena, Mode::Strict, Location{}, Covariant, sharedState};
u.tryUnify(subType, superType);
u.log.commit();
reportErrors(u.errors);
}
void ConstraintSolver::unify(TypePackId subPack, TypePackId superPack)
{
UnifierSharedState sharedState{&iceReporter};
Unifier u{arena, Mode::Strict, Location{}, Covariant, sharedState};
u.tryUnify(subPack, superPack);
u.log.commit();
reportErrors(u.errors);
}
} // namespace Luau

View file

@ -7,7 +7,10 @@ namespace Luau
static const std::string kBuiltinDefinitionLuaSrc = R"BUILTIN_SRC(
declare bit32: {
-- band, bor, bxor, and btest are declared in C++
band: (...number) -> number,
bor: (...number) -> number,
bxor: (...number) -> number,
btest: (number, ...number) -> boolean,
rrotate: (number, number) -> number,
lrotate: (number, number) -> number,
lshift: (number, number) -> number,
@ -50,7 +53,8 @@ declare math: {
asin: (number) -> number,
atan2: (number, number) -> number,
-- min and max are declared in C++.
min: (number, ...number) -> number,
max: (number, ...number) -> number,
pi: number,
huge: number,
@ -143,7 +147,7 @@ declare coroutine: {
create: <A..., R...>((A...) -> R...) -> thread,
resume: <A..., R...>(thread, A...) -> (boolean, R...),
running: () -> thread,
status: (thread) -> string,
status: (thread) -> "dead" | "running" | "normal" | "suspended",
-- FIXME: This technically returns a function, but we can't represent this yet.
wrap: <A..., R...>((A...) -> R...) -> any,
yield: <A..., R...>(A...) -> R...,
@ -179,7 +183,7 @@ declare debug: {
}
declare utf8: {
char: (number, ...number) -> string,
char: (...number) -> string,
charpattern: string,
codes: (string) -> ((string, number) -> (number, number), string, number),
-- FIXME

View file

@ -2,7 +2,6 @@
#include "Luau/Error.h"
#include "Luau/Clone.h"
#include "Luau/Module.h"
#include "Luau/StringUtils.h"
#include "Luau/ToString.h"
@ -178,15 +177,7 @@ struct ErrorConverter
std::string operator()(const Luau::FunctionRequiresSelf& e) const
{
if (e.requiredExtraNils)
{
const char* plural = e.requiredExtraNils == 1 ? "" : "s";
return format("This function was declared to accept self, but you did not pass enough arguments. Use a colon instead of a dot or "
"pass %i extra nil%s to suppress this warning",
e.requiredExtraNils, plural);
}
else
return "This function must be called with self. Did you mean to use a colon instead of a dot?";
return "This function must be called with self. Did you mean to use a colon instead of a dot?";
}
std::string operator()(const Luau::OccursCheckFailed&) const
@ -539,7 +530,7 @@ bool FunctionDoesNotTakeSelf::operator==(const FunctionDoesNotTakeSelf&) const
bool FunctionRequiresSelf::operator==(const FunctionRequiresSelf& e) const
{
return requiredExtraNils == e.requiredExtraNils;
return true;
}
bool OccursCheckFailed::operator==(const OccursCheckFailed&) const

View file

@ -5,6 +5,8 @@
#include "Luau/Clone.h"
#include "Luau/Config.h"
#include "Luau/FileResolver.h"
#include "Luau/ConstraintGraphBuilder.h"
#include "Luau/ConstraintSolver.h"
#include "Luau/Parser.h"
#include "Luau/Scope.h"
#include "Luau/StringUtils.h"
@ -20,10 +22,9 @@ LUAU_FASTINT(LuauTypeInferIterationLimit)
LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
LUAU_FASTFLAGVARIABLE(LuauSeparateTypechecks, false)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteDynamicLimits, false)
LUAU_FASTFLAGVARIABLE(LuauDirtySourceModule, false)
LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100)
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
namespace Luau
{
@ -361,32 +362,21 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
if (it != sourceNodes.end() && !it->second.hasDirtyModule(frontendOptions.forAutocomplete))
{
// No recheck required.
if (FFlag::LuauSeparateTypechecks)
if (frontendOptions.forAutocomplete)
{
if (frontendOptions.forAutocomplete)
{
auto it2 = moduleResolverForAutocomplete.modules.find(name);
if (it2 == moduleResolverForAutocomplete.modules.end() || it2->second == nullptr)
throw std::runtime_error("Frontend::modules does not have data for " + name);
}
else
{
auto it2 = moduleResolver.modules.find(name);
if (it2 == moduleResolver.modules.end() || it2->second == nullptr)
throw std::runtime_error("Frontend::modules does not have data for " + name);
}
return CheckResult{accumulateErrors(
sourceNodes, frontendOptions.forAutocomplete ? moduleResolverForAutocomplete.modules : moduleResolver.modules, name)};
auto it2 = moduleResolverForAutocomplete.modules.find(name);
if (it2 == moduleResolverForAutocomplete.modules.end() || it2->second == nullptr)
throw std::runtime_error("Frontend::modules does not have data for " + name);
}
else
{
auto it2 = moduleResolver.modules.find(name);
if (it2 == moduleResolver.modules.end() || it2->second == nullptr)
throw std::runtime_error("Frontend::modules does not have data for " + name);
return CheckResult{accumulateErrors(sourceNodes, moduleResolver.modules, name)};
}
return CheckResult{
accumulateErrors(sourceNodes, frontendOptions.forAutocomplete ? moduleResolverForAutocomplete.modules : moduleResolver.modules, name)};
}
std::vector<ModuleName> buildQueue;
@ -428,7 +418,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
// This is used by the type checker to replace the resulting type of cyclic modules with any
sourceModule.cyclic = !requireCycles.empty();
if (FFlag::LuauSeparateTypechecks && frontendOptions.forAutocomplete)
if (frontendOptions.forAutocomplete)
{
// The autocomplete typecheck is always in strict mode with DM awareness
// to provide better type information for IDE features
@ -483,18 +473,8 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
typeChecker.requireCycles = requireCycles;
ModulePtr module = typeChecker.check(sourceModule, mode, environmentScope);
// If we're typechecking twice, we do so.
// The second typecheck is always in strict mode with DM awareness
// to provide better typen information for IDE features.
if (!FFlag::LuauSeparateTypechecks && frontendOptions.typecheckTwice_DEPRECATED)
{
typeCheckerForAutocomplete.requireCycles = requireCycles;
ModulePtr moduleForAutocomplete = typeCheckerForAutocomplete.check(sourceModule, Mode::Strict);
moduleResolverForAutocomplete.modules[moduleName] = moduleForAutocomplete;
}
ModulePtr module = FFlag::DebugLuauDeferredConstraintResolution ? check(sourceModule, mode, environmentScope)
: typeChecker.check(sourceModule, mode, environmentScope);
stats.timeCheck += getTimestamp() - timestamp;
stats.filesStrict += mode == Mode::Strict;
@ -563,7 +543,7 @@ bool Frontend::parseGraph(std::vector<ModuleName>& buildQueue, CheckResult& chec
bool cyclic = false;
{
auto [sourceNode, _] = getSourceNode(checkResult, root, forAutocomplete);
auto [sourceNode, _] = getSourceNode(checkResult, root);
if (sourceNode)
stack.push_back(sourceNode);
}
@ -627,7 +607,7 @@ bool Frontend::parseGraph(std::vector<ModuleName>& buildQueue, CheckResult& chec
}
}
auto [sourceNode, _] = getSourceNode(checkResult, dep, forAutocomplete);
auto [sourceNode, _] = getSourceNode(checkResult, dep);
if (sourceNode)
{
stack.push_back(sourceNode);
@ -671,7 +651,7 @@ LintResult Frontend::lint(const ModuleName& name, std::optional<Luau::LintOption
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
CheckResult checkResult;
auto [_sourceNode, sourceModule] = getSourceNode(checkResult, name, false);
auto [_sourceNode, sourceModule] = getSourceNode(checkResult, name);
if (!sourceModule)
return LintResult{}; // FIXME: We really should do something a bit more obvious when a file is too broken to lint.
@ -752,16 +732,8 @@ bool Frontend::isDirty(const ModuleName& name, bool forAutocomplete) const
*/
void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty)
{
if (FFlag::LuauSeparateTypechecks)
{
if (!moduleResolver.modules.count(name) && !moduleResolverForAutocomplete.modules.count(name))
return;
}
else
{
if (!moduleResolver.modules.count(name))
return;
}
if (!moduleResolver.modules.count(name) && !moduleResolverForAutocomplete.modules.count(name))
return;
std::unordered_map<ModuleName, std::vector<ModuleName>> reverseDeps;
for (const auto& module : sourceNodes)
@ -783,32 +755,12 @@ void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* marked
if (markedDirty)
markedDirty->push_back(next);
if (FFlag::LuauDirtySourceModule)
{
LUAU_ASSERT(FFlag::LuauSeparateTypechecks);
if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
continue;
if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
continue;
sourceNode.dirtySourceModule = true;
sourceNode.dirtyModule = true;
sourceNode.dirtyModuleForAutocomplete = true;
}
else if (FFlag::LuauSeparateTypechecks)
{
if (sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
continue;
sourceNode.dirtyModule = true;
sourceNode.dirtyModuleForAutocomplete = true;
}
else
{
if (sourceNode.dirtyModule)
continue;
sourceNode.dirtyModule = true;
}
sourceNode.dirtySourceModule = true;
sourceNode.dirtyModule = true;
sourceNode.dirtyModuleForAutocomplete = true;
if (0 == reverseDeps.count(name))
continue;
@ -834,15 +786,31 @@ const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) cons
return const_cast<Frontend*>(this)->getSourceModule(moduleName);
}
ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope)
{
ModulePtr result = std::make_shared<Module>();
ConstraintGraphBuilder cgb{&result->internalTypes};
cgb.visit(sourceModule.root);
ConstraintSolver cs{&result->internalTypes, cgb.rootScope};
cs.run();
result->scope2s = std::move(cgb.scopes);
result->clonePublicInterface(iceHandler);
return result;
}
// Read AST into sourceModules if necessary. Trace require()s. Report parse errors.
std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(CheckResult& checkResult, const ModuleName& name, bool forAutocomplete_DEPRECATED)
std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(CheckResult& checkResult, const ModuleName& name)
{
LUAU_TIMETRACE_SCOPE("Frontend::getSourceNode", "Frontend");
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
auto it = sourceNodes.find(name);
if (it != sourceNodes.end() &&
(FFlag::LuauDirtySourceModule ? !it->second.hasDirtySourceModule() : !it->second.hasDirtyModule(forAutocomplete_DEPRECATED)))
if (it != sourceNodes.end() && !it->second.hasDirtySourceModule())
{
auto moduleIt = sourceModules.find(name);
if (moduleIt != sourceModules.end())
@ -885,21 +853,12 @@ std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(CheckResult& check
sourceNode.name = name;
sourceNode.requires.clear();
sourceNode.requireLocations.clear();
sourceNode.dirtySourceModule = false;
if (FFlag::LuauDirtySourceModule)
sourceNode.dirtySourceModule = false;
if (FFlag::LuauSeparateTypechecks)
{
if (it == sourceNodes.end())
{
sourceNode.dirtyModule = true;
sourceNode.dirtyModuleForAutocomplete = true;
}
}
else
if (it == sourceNodes.end())
{
sourceNode.dirtyModule = true;
sourceNode.dirtyModuleForAutocomplete = true;
}
for (const auto& [moduleName, location] : requireTrace.requires)

View file

@ -0,0 +1,124 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Common.h"
#include "Luau/Instantiation.h"
#include "Luau/TxnLog.h"
#include "Luau/TypeArena.h"
namespace Luau
{
bool Instantiation::isDirty(TypeId ty)
{
if (const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty))
{
if (ftv->hasNoGenerics)
return false;
return true;
}
else
{
return false;
}
}
bool Instantiation::isDirty(TypePackId tp)
{
return false;
}
bool Instantiation::ignoreChildren(TypeId ty)
{
if (log->getMutable<FunctionTypeVar>(ty))
return true;
else
return false;
}
TypeId Instantiation::clean(TypeId ty)
{
const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty);
LUAU_ASSERT(ftv);
FunctionTypeVar clone = FunctionTypeVar{level, ftv->argTypes, ftv->retType, ftv->definition, ftv->hasSelf};
clone.magicFunction = ftv->magicFunction;
clone.tags = ftv->tags;
clone.argNames = ftv->argNames;
TypeId result = addType(std::move(clone));
// Annoyingly, we have to do this even if there are no generics,
// to replace any generic tables.
ReplaceGenerics replaceGenerics{log, arena, level, ftv->generics, ftv->genericPacks};
// TODO: What to do if this returns nullopt?
// We don't have access to the error-reporting machinery
result = replaceGenerics.substitute(result).value_or(result);
asMutable(result)->documentationSymbol = ty->documentationSymbol;
return result;
}
TypePackId Instantiation::clean(TypePackId tp)
{
LUAU_ASSERT(false);
return tp;
}
bool ReplaceGenerics::ignoreChildren(TypeId ty)
{
if (const FunctionTypeVar* ftv = log->getMutable<FunctionTypeVar>(ty))
{
if (ftv->hasNoGenerics)
return true;
// We aren't recursing in the case of a generic function which
// binds the same generics. This can happen if, for example, there's recursive types.
// If T = <a>(a,T)->T then instantiating T should produce T' = (X,T)->T not T' = (X,T')->T'.
// It's OK to use vector equality here, since we always generate fresh generics
// whenever we quantify, so the vectors overlap if and only if they are equal.
return (!generics.empty() || !genericPacks.empty()) && (ftv->generics == generics) && (ftv->genericPacks == genericPacks);
}
else
{
return false;
}
}
bool ReplaceGenerics::isDirty(TypeId ty)
{
if (const TableTypeVar* ttv = log->getMutable<TableTypeVar>(ty))
return ttv->state == TableState::Generic;
else if (log->getMutable<GenericTypeVar>(ty))
return std::find(generics.begin(), generics.end(), ty) != generics.end();
else
return false;
}
bool ReplaceGenerics::isDirty(TypePackId tp)
{
if (log->getMutable<GenericTypePack>(tp))
return std::find(genericPacks.begin(), genericPacks.end(), tp) != genericPacks.end();
else
return false;
}
TypeId ReplaceGenerics::clean(TypeId ty)
{
LUAU_ASSERT(isDirty(ty));
if (const TableTypeVar* ttv = log->getMutable<TableTypeVar>(ty))
{
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, level, TableState::Free};
clone.definitionModuleName = ttv->definitionModuleName;
return addType(std::move(clone));
}
else
return addType(FreeTypeVar{level});
}
TypePackId ReplaceGenerics::clean(TypePackId tp)
{
LUAU_ASSERT(isDirty(tp));
return addTypePack(TypePackVar(FreeTypePack{level}));
}
} // namespace Luau

View file

@ -48,7 +48,7 @@ static void errorToString(std::ostream& stream, const T& err)
else if constexpr (std::is_same_v<T, FunctionDoesNotTakeSelf>)
stream << "FunctionDoesNotTakeSelf { }";
else if constexpr (std::is_same_v<T, FunctionRequiresSelf>)
stream << "FunctionRequiresSelf { extraNils " << err.requiredExtraNils << " }";
stream << "FunctionRequiresSelf { }";
else if constexpr (std::is_same_v<T, OccursCheckFailed>)
stream << "OccursCheckFailed { }";
else if constexpr (std::is_same_v<T, UnknownRequire>)

View file

@ -5,8 +5,6 @@
#include <vector>
LUAU_FASTFLAG(LuauTypecheckOptPass)
namespace Luau
{
@ -79,27 +77,8 @@ std::optional<LValue> tryGetLValue(const AstExpr& node)
return std::nullopt;
}
std::pair<Symbol, std::vector<std::string>> getFullName(const LValue& lvalue)
{
LUAU_ASSERT(!FFlag::LuauTypecheckOptPass);
const LValue* current = &lvalue;
std::vector<std::string> keys;
while (auto field = get<Field>(*current))
{
keys.push_back(field->key);
current = baseof(*current);
}
const Symbol* symbol = get<Symbol>(*current);
LUAU_ASSERT(symbol);
return {*symbol, std::vector<std::string>(keys.rbegin(), keys.rend())};
}
Symbol getBaseSymbol(const LValue& lvalue)
{
LUAU_ASSERT(FFlag::LuauTypecheckOptPass);
const LValue* current = &lvalue;
while (auto field = get<Field>(*current))
current = baseof(*current);

View file

@ -3,6 +3,7 @@
#include "Luau/Clone.h"
#include "Luau/Common.h"
#include "Luau/ConstraintGraphBuilder.h"
#include "Luau/Normalize.h"
#include "Luau/RecursionCounter.h"
#include "Luau/Scope.h"
@ -10,12 +11,13 @@
#include "Luau/TypePack.h"
#include "Luau/TypeVar.h"
#include "Luau/VisitTypeVar.h"
#include "Luau/ConstraintGraphBuilder.h" // FIXME: For Scope2 TODO pull out into its own header
#include <algorithm>
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
LUAU_FASTFLAG(LuauLosslessClone)
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
LUAU_FASTFLAG(LuauNormalizeFlagIsConservative);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
namespace Luau
{
@ -55,89 +57,35 @@ bool isWithinComment(const SourceModule& sourceModule, Position pos)
return contains(pos, *iter);
}
void TypeArena::clear()
struct ForceNormal : TypeVarOnceVisitor
{
typeVars.clear();
typePacks.clear();
}
const TypeArena* typeArena = nullptr;
TypeId TypeArena::addTV(TypeVar&& tv)
{
TypeId allocated = typeVars.allocate(std::move(tv));
ForceNormal(const TypeArena* typeArena)
: typeArena(typeArena)
{
}
asMutable(allocated)->owningArena = this;
bool visit(TypeId ty) override
{
if (ty->owningArena != typeArena)
return false;
return allocated;
}
asMutable(ty)->normal = true;
return true;
}
TypeId TypeArena::freshType(TypeLevel level)
{
TypeId allocated = typeVars.allocate(FreeTypeVar{level});
bool visit(TypeId ty, const FreeTypeVar& ftv) override
{
visit(ty);
return true;
}
asMutable(allocated)->owningArena = this;
return allocated;
}
TypePackId TypeArena::addTypePack(std::initializer_list<TypeId> types)
{
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
asMutable(allocated)->owningArena = this;
return allocated;
}
TypePackId TypeArena::addTypePack(std::vector<TypeId> types)
{
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
asMutable(allocated)->owningArena = this;
return allocated;
}
TypePackId TypeArena::addTypePack(TypePack tp)
{
TypePackId allocated = typePacks.allocate(std::move(tp));
asMutable(allocated)->owningArena = this;
return allocated;
}
TypePackId TypeArena::addTypePack(TypePackVar tp)
{
TypePackId allocated = typePacks.allocate(std::move(tp));
asMutable(allocated)->owningArena = this;
return allocated;
}
ScopePtr Module::getModuleScope() const
{
LUAU_ASSERT(!scopes.empty());
return scopes.front().second;
}
void freeze(TypeArena& arena)
{
if (!FFlag::DebugLuauFreezeArena)
return;
arena.typeVars.freeze();
arena.typePacks.freeze();
}
void unfreeze(TypeArena& arena)
{
if (!FFlag::DebugLuauFreezeArena)
return;
arena.typeVars.unfreeze();
arena.typePacks.unfreeze();
}
bool visit(TypePackId tp, const FreeTypePack& ftp) override
{
return true;
}
};
Module::~Module()
{
@ -145,34 +93,67 @@ Module::~Module()
unfreeze(internalTypes);
}
bool Module::clonePublicInterface(InternalErrorReporter& ice)
void Module::clonePublicInterface(InternalErrorReporter& ice)
{
LUAU_ASSERT(interfaceTypes.typeVars.empty());
LUAU_ASSERT(interfaceTypes.typePacks.empty());
CloneState cloneState;
ScopePtr moduleScope = getModuleScope();
ScopePtr moduleScope = FFlag::DebugLuauDeferredConstraintResolution ? nullptr : getModuleScope();
Scope2* moduleScope2 = FFlag::DebugLuauDeferredConstraintResolution ? getModuleScope2() : nullptr;
moduleScope->returnType = clone(moduleScope->returnType, interfaceTypes, cloneState);
if (moduleScope->varargPack)
moduleScope->varargPack = clone(*moduleScope->varargPack, interfaceTypes, cloneState);
TypePackId returnType = FFlag::DebugLuauDeferredConstraintResolution ? moduleScope2->returnType : moduleScope->returnType;
std::optional<TypePackId> varargPack = FFlag::DebugLuauDeferredConstraintResolution ? std::nullopt : moduleScope->varargPack;
std::unordered_map<Name, TypeFun>* exportedTypeBindings =
FFlag::DebugLuauDeferredConstraintResolution ? nullptr : &moduleScope->exportedTypeBindings;
returnType = clone(returnType, interfaceTypes, cloneState);
if (moduleScope)
{
moduleScope->returnType = returnType;
if (varargPack)
{
varargPack = clone(*varargPack, interfaceTypes, cloneState);
moduleScope->varargPack = varargPack;
}
}
else
{
LUAU_ASSERT(moduleScope2);
moduleScope2->returnType = returnType; // TODO varargPack
}
if (FFlag::LuauLowerBoundsCalculation)
{
normalize(moduleScope->returnType, interfaceTypes, ice);
if (moduleScope->varargPack)
normalize(*moduleScope->varargPack, interfaceTypes, ice);
normalize(returnType, interfaceTypes, ice);
if (varargPack)
normalize(*varargPack, interfaceTypes, ice);
}
for (auto& [name, tf] : moduleScope->exportedTypeBindings)
ForceNormal forceNormal{&interfaceTypes};
if (exportedTypeBindings)
{
tf = clone(tf, interfaceTypes, cloneState);
if (FFlag::LuauLowerBoundsCalculation)
normalize(tf.type, interfaceTypes, ice);
for (auto& [name, tf] : *exportedTypeBindings)
{
tf = clone(tf, interfaceTypes, cloneState);
if (FFlag::LuauLowerBoundsCalculation)
{
normalize(tf.type, interfaceTypes, ice);
if (FFlag::LuauNormalizeFlagIsConservative)
{
// We're about to freeze the memory. We know that the flag is conservative by design. Cyclic tables
// won't be marked normal. If the types aren't normal by now, they never will be.
forceNormal.traverse(tf.type);
}
}
}
}
for (TypeId ty : moduleScope->returnType)
for (TypeId ty : returnType)
{
if (get<GenericTypeVar>(follow(ty)))
{
@ -191,11 +172,18 @@ bool Module::clonePublicInterface(InternalErrorReporter& ice)
freeze(internalTypes);
freeze(interfaceTypes);
}
if (FFlag::LuauLosslessClone)
return false; // TODO: make function return void.
else
return cloneState.encounteredFreeType;
ScopePtr Module::getModuleScope() const
{
LUAU_ASSERT(!scopes.empty());
return scopes.front().second;
}
Scope2* Module::getModuleScope2() const
{
LUAU_ASSERT(!scope2s.empty());
return scope2s.front().second.get();
}
} // namespace Luau

View file

@ -14,7 +14,9 @@ LUAU_FASTFLAGVARIABLE(DebugLuauCopyBeforeNormalizing, false)
// This could theoretically be 2000 on amd64, but x86 requires this.
LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineTableFix, false);
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineIntersectionFix, false);
LUAU_FASTFLAGVARIABLE(LuauNormalizeFlagIsConservative, false);
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineEqFix, false);
LUAU_FASTFLAGVARIABLE(LuauReplaceReplacer, false);
namespace Luau
{
@ -230,11 +232,30 @@ struct Replacer : Substitution
TypeId smartClone(TypeId t)
{
std::optional<TypeId> res = replace(t);
LUAU_ASSERT(res.has_value()); // TODO think about this
if (*res == t)
return clone(t);
return *res;
if (FFlag::LuauReplaceReplacer)
{
// The new smartClone is just a memoized clone()
// TODO: Remove the Substitution base class and all other methods from this struct.
// Add DenseHashMap<TypeId, TypeId> newTypes;
t = log->follow(t);
TypeId* res = newTypes.find(t);
if (res)
return *res;
TypeId result = shallowClone(t, *arena, TxnLog::empty());
newTypes[t] = result;
newTypes[result] = result;
return result;
}
else
{
std::optional<TypeId> res = replace(t);
LUAU_ASSERT(res.has_value()); // TODO think about this
if (*res == t)
return clone(t);
return *res;
}
}
};
@ -261,8 +282,13 @@ static bool areNormal_(const T& t, const std::unordered_set<void*>& seen, Intern
if (count >= FInt::LuauNormalizeIterationLimit)
ice.ice("Luau::areNormal hit iteration limit");
// The follow is here because a bound type may not be normal, but the bound type is normal.
return ty->normal || follow(ty)->normal || seen.find(asMutable(ty)) != seen.end();
if (FFlag::LuauNormalizeFlagIsConservative)
return ty->normal;
else
{
// The follow is here because a bound type may not be normal, but the bound type is normal.
return ty->normal || follow(ty)->normal || seen.find(asMutable(ty)) != seen.end();
}
};
return std::all_of(begin(t), end(t), isNormal);
@ -320,245 +346,6 @@ struct Normalize final : TypeVarVisitor
int iterationLimit = 0;
bool limitExceeded = false;
// TODO: Clip with FFlag::LuauUseVisitRecursionLimit
bool operator()(TypeId ty, const BoundTypeVar& btv, std::unordered_set<void*>& seen)
{
// A type could be considered normal when it is in the stack, but we will eventually find out it is not normal as normalization progresses.
// So we need to avoid eagerly saying that this bound type is normal if the thing it is bound to is in the stack.
if (seen.find(asMutable(btv.boundTo)) != seen.end())
return false;
// It should never be the case that this TypeVar is normal, but is bound to a non-normal type, except in nontrivial cases.
LUAU_ASSERT(!ty->normal || ty->normal == btv.boundTo->normal);
asMutable(ty)->normal = btv.boundTo->normal;
return !ty->normal;
}
bool operator()(TypeId ty, const FreeTypeVar& ftv)
{
return visit(ty, ftv);
}
bool operator()(TypeId ty, const PrimitiveTypeVar& ptv)
{
return visit(ty, ptv);
}
bool operator()(TypeId ty, const GenericTypeVar& gtv)
{
return visit(ty, gtv);
}
bool operator()(TypeId ty, const ErrorTypeVar& etv)
{
return visit(ty, etv);
}
bool operator()(TypeId ty, const ConstrainedTypeVar& ctvRef, std::unordered_set<void*>& seen)
{
CHECK_ITERATION_LIMIT(false);
ConstrainedTypeVar* ctv = const_cast<ConstrainedTypeVar*>(&ctvRef);
std::vector<TypeId> parts = std::move(ctv->parts);
// We might transmute, so it's not safe to rely on the builtin traversal logic of visitTypeVar
for (TypeId part : parts)
visit_detail::visit(part, *this, seen);
std::vector<TypeId> newParts = normalizeUnion(parts);
const bool normal = areNormal(newParts, seen, ice);
if (newParts.size() == 1)
*asMutable(ty) = BoundTypeVar{newParts[0]};
else
*asMutable(ty) = UnionTypeVar{std::move(newParts)};
asMutable(ty)->normal = normal;
return false;
}
bool operator()(TypeId ty, const FunctionTypeVar& ftv, std::unordered_set<void*>& seen)
{
CHECK_ITERATION_LIMIT(false);
if (ty->normal)
return false;
visit_detail::visit(ftv.argTypes, *this, seen);
visit_detail::visit(ftv.retType, *this, seen);
asMutable(ty)->normal = areNormal(ftv.argTypes, seen, ice) && areNormal(ftv.retType, seen, ice);
return false;
}
bool operator()(TypeId ty, const TableTypeVar& ttv, std::unordered_set<void*>& seen)
{
CHECK_ITERATION_LIMIT(false);
if (ty->normal)
return false;
bool normal = true;
auto checkNormal = [&](TypeId t) {
// if t is on the stack, it is possible that this type is normal.
// If t is not normal and it is not on the stack, this type is definitely not normal.
if (!t->normal && seen.find(asMutable(t)) == seen.end())
normal = false;
};
if (ttv.boundTo)
{
visit_detail::visit(*ttv.boundTo, *this, seen);
asMutable(ty)->normal = (*ttv.boundTo)->normal;
return false;
}
for (const auto& [_name, prop] : ttv.props)
{
visit_detail::visit(prop.type, *this, seen);
checkNormal(prop.type);
}
if (ttv.indexer)
{
visit_detail::visit(ttv.indexer->indexType, *this, seen);
checkNormal(ttv.indexer->indexType);
visit_detail::visit(ttv.indexer->indexResultType, *this, seen);
checkNormal(ttv.indexer->indexResultType);
}
asMutable(ty)->normal = normal;
return false;
}
bool operator()(TypeId ty, const MetatableTypeVar& mtv, std::unordered_set<void*>& seen)
{
CHECK_ITERATION_LIMIT(false);
if (ty->normal)
return false;
visit_detail::visit(mtv.table, *this, seen);
visit_detail::visit(mtv.metatable, *this, seen);
asMutable(ty)->normal = mtv.table->normal && mtv.metatable->normal;
return false;
}
bool operator()(TypeId ty, const ClassTypeVar& ctv)
{
return visit(ty, ctv);
}
bool operator()(TypeId ty, const AnyTypeVar& atv)
{
return visit(ty, atv);
}
bool operator()(TypeId ty, const UnionTypeVar& utvRef, std::unordered_set<void*>& seen)
{
CHECK_ITERATION_LIMIT(false);
if (ty->normal)
return false;
UnionTypeVar* utv = &const_cast<UnionTypeVar&>(utvRef);
std::vector<TypeId> options = std::move(utv->options);
// We might transmute, so it's not safe to rely on the builtin traversal logic of visitTypeVar
for (TypeId option : options)
visit_detail::visit(option, *this, seen);
std::vector<TypeId> newOptions = normalizeUnion(options);
const bool normal = areNormal(newOptions, seen, ice);
LUAU_ASSERT(!newOptions.empty());
if (newOptions.size() == 1)
*asMutable(ty) = BoundTypeVar{newOptions[0]};
else
utv->options = std::move(newOptions);
asMutable(ty)->normal = normal;
return false;
}
bool operator()(TypeId ty, const IntersectionTypeVar& itvRef, std::unordered_set<void*>& seen)
{
CHECK_ITERATION_LIMIT(false);
if (ty->normal)
return false;
IntersectionTypeVar* itv = &const_cast<IntersectionTypeVar&>(itvRef);
std::vector<TypeId> oldParts = std::move(itv->parts);
for (TypeId part : oldParts)
visit_detail::visit(part, *this, seen);
std::vector<TypeId> tables;
for (TypeId part : oldParts)
{
part = follow(part);
if (get<TableTypeVar>(part))
tables.push_back(part);
else
{
Replacer replacer{&arena, nullptr, nullptr}; // FIXME this is super super WEIRD
combineIntoIntersection(replacer, itv, part);
}
}
// Don't allocate a new table if there's just one in the intersection.
if (tables.size() == 1)
itv->parts.push_back(tables[0]);
else if (!tables.empty())
{
const TableTypeVar* first = get<TableTypeVar>(tables[0]);
LUAU_ASSERT(first);
TypeId newTable = arena.addType(TableTypeVar{first->state, first->level});
TableTypeVar* ttv = getMutable<TableTypeVar>(newTable);
for (TypeId part : tables)
{
// Intuition: If combineIntoTable() needs to clone a table, any references to 'part' are cyclic and need
// to be rewritten to point at 'newTable' in the clone.
Replacer replacer{&arena, part, newTable};
combineIntoTable(replacer, ttv, part);
}
itv->parts.push_back(newTable);
}
asMutable(ty)->normal = areNormal(itv->parts, seen, ice);
if (itv->parts.size() == 1)
{
TypeId part = itv->parts[0];
*asMutable(ty) = BoundTypeVar{part};
}
return false;
}
// TODO: Clip with FFlag::LuauUseVisitRecursionLimit
template<typename T>
bool operator()(TypePackId, const T&)
{
return true;
}
// TODO: Clip with FFlag::LuauUseVisitRecursionLimit
template<typename TID>
void cycle(TID)
{
}
bool visit(TypeId ty, const FreeTypeVar&) override
{
LUAU_ASSERT(!ty->normal);
@ -863,7 +650,7 @@ struct Normalize final : TypeVarVisitor
TypeId theTable = result->parts.back();
if (!get<TableTypeVar>(FFlag::LuauNormalizeCombineIntersectionFix ? follow(theTable) : theTable))
if (!get<TableTypeVar>(follow(theTable)))
{
result->parts.push_back(arena.addType(TableTypeVar{TableState::Sealed, TypeLevel{}}));
theTable = result->parts.back();
@ -963,6 +750,9 @@ struct Normalize final : TypeVarVisitor
*/
TypeId combine(Replacer& replacer, TypeId a, TypeId b)
{
if (FFlag::LuauNormalizeCombineEqFix)
b = follow(b);
if (FFlag::LuauNormalizeCombineTableFix && a == b)
return a;
@ -981,7 +771,7 @@ struct Normalize final : TypeVarVisitor
}
else if (auto ttv = getMutable<TableTypeVar>(a))
{
if (FFlag::LuauNormalizeCombineTableFix && !get<TableTypeVar>(follow(b)))
if (FFlag::LuauNormalizeCombineTableFix && !get<TableTypeVar>(FFlag::LuauNormalizeCombineEqFix ? b : follow(b)))
return arena.addType(IntersectionTypeVar{{a, b}});
combineIntoTable(replacer, ttv, b);
return a;
@ -1004,8 +794,7 @@ std::pair<TypeId, bool> normalize(TypeId ty, TypeArena& arena, InternalErrorRepo
(void)clone(ty, arena, state);
Normalize n{arena, ice};
std::unordered_set<void*> seen;
DEPRECATED_visitTypeVar(ty, n, seen);
n.traverse(ty);
return {ty, !n.limitExceeded};
}
@ -1029,8 +818,7 @@ std::pair<TypePackId, bool> normalize(TypePackId tp, TypeArena& arena, InternalE
(void)clone(tp, arena, state);
Normalize n{arena, ice};
std::unordered_set<void*> seen;
DEPRECATED_visitTypeVar(tp, n, seen);
n.traverse(tp);
return {tp, !n.limitExceeded};
}

View file

@ -3,8 +3,10 @@
#include "Luau/Quantify.h"
#include "Luau/VisitTypeVar.h"
#include "Luau/ConstraintGraphBuilder.h" // TODO for Scope2; move to separate header
LUAU_FASTFLAG(LuauTypecheckOptPass)
LUAU_FASTFLAG(LuauAlwaysQuantify);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
namespace Luau
{
@ -14,58 +16,47 @@ struct Quantifier final : TypeVarOnceVisitor
TypeLevel level;
std::vector<TypeId> generics;
std::vector<TypePackId> genericPacks;
Scope2* scope = nullptr;
bool seenGenericType = false;
bool seenMutableType = false;
explicit Quantifier(TypeLevel level)
: level(level)
{
LUAU_ASSERT(!FFlag::DebugLuauDeferredConstraintResolution);
}
void cycle(TypeId) override {}
void cycle(TypePackId) override {}
bool operator()(TypeId ty, const FreeTypeVar& ftv)
explicit Quantifier(Scope2* scope)
: scope(scope)
{
return visit(ty, ftv);
LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution);
}
template<typename T>
bool operator()(TypeId ty, const T& t)
/// @return true if outer encloses inner
bool subsumes(Scope2* outer, Scope2* inner)
{
return true;
}
while (inner)
{
if (inner == outer)
return true;
inner = inner->parent;
}
template<typename T>
bool operator()(TypePackId, const T&)
{
return true;
}
bool operator()(TypeId ty, const ConstrainedTypeVar&)
{
return true;
}
bool operator()(TypeId ty, const TableTypeVar& ttv)
{
return visit(ty, ttv);
}
bool operator()(TypePackId tp, const FreeTypePack& ftp)
{
return visit(tp, ftp);
return false;
}
bool visit(TypeId ty, const FreeTypeVar& ftv) override
{
if (FFlag::LuauTypecheckOptPass)
seenMutableType = true;
seenMutableType = true;
if (!level.subsumes(ftv.level))
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ftv.scope) : !level.subsumes(ftv.level))
return false;
*asMutable(ty) = GenericTypeVar{level};
if (FFlag::DebugLuauDeferredConstraintResolution)
*asMutable(ty) = GenericTypeVar{scope};
else
*asMutable(ty) = GenericTypeVar{level};
generics.push_back(ty);
return false;
@ -76,20 +67,17 @@ struct Quantifier final : TypeVarOnceVisitor
LUAU_ASSERT(getMutable<TableTypeVar>(ty));
TableTypeVar& ttv = *getMutable<TableTypeVar>(ty);
if (FFlag::LuauTypecheckOptPass)
{
if (ttv.state == TableState::Generic)
seenGenericType = true;
if (ttv.state == TableState::Generic)
seenGenericType = true;
if (ttv.state == TableState::Free)
seenMutableType = true;
}
if (ttv.state == TableState::Free)
seenMutableType = true;
if (ttv.state == TableState::Sealed || ttv.state == TableState::Generic)
return false;
if (!level.subsumes(ttv.level))
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ttv.scope) : !level.subsumes(ttv.level))
{
if (FFlag::LuauTypecheckOptPass && ttv.state == TableState::Unsealed)
if (ttv.state == TableState::Unsealed)
seenMutableType = true;
return false;
}
@ -97,9 +85,7 @@ struct Quantifier final : TypeVarOnceVisitor
if (ttv.state == TableState::Free)
{
ttv.state = TableState::Generic;
if (FFlag::LuauTypecheckOptPass)
seenGenericType = true;
seenGenericType = true;
}
else if (ttv.state == TableState::Unsealed)
ttv.state = TableState::Sealed;
@ -111,10 +97,9 @@ struct Quantifier final : TypeVarOnceVisitor
bool visit(TypePackId tp, const FreeTypePack& ftp) override
{
if (FFlag::LuauTypecheckOptPass)
seenMutableType = true;
seenMutableType = true;
if (!level.subsumes(ftp.level))
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ftp.scope) : !level.subsumes(ftp.level))
return false;
*asMutable(tp) = GenericTypePack{level};
@ -126,16 +111,49 @@ struct Quantifier final : TypeVarOnceVisitor
void quantify(TypeId ty, TypeLevel level)
{
Quantifier q{level};
DenseHashSet<void*> seen{nullptr};
DEPRECATED_visitTypeVarOnce(ty, q, seen);
q.traverse(ty);
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty);
LUAU_ASSERT(ftv);
ftv->generics = q.generics;
ftv->genericPacks = q.genericPacks;
if (FFlag::LuauAlwaysQuantify)
{
ftv->generics.insert(ftv->generics.end(), q.generics.begin(), q.generics.end());
ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end());
}
else
{
ftv->generics = q.generics;
ftv->genericPacks = q.genericPacks;
}
if (FFlag::LuauTypecheckOptPass && ftv->generics.empty() && ftv->genericPacks.empty() && !q.seenMutableType && !q.seenGenericType)
if (ftv->generics.empty() && ftv->genericPacks.empty() && !q.seenMutableType && !q.seenGenericType)
ftv->hasNoGenerics = true;
ftv->generalized = true;
}
void quantify(TypeId ty, Scope2* scope)
{
Quantifier q{scope};
q.traverse(ty);
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty);
LUAU_ASSERT(ftv);
if (FFlag::LuauAlwaysQuantify)
{
ftv->generics.insert(ftv->generics.end(), q.generics.begin(), q.generics.end());
ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end());
}
else
{
ftv->generics = q.generics;
ftv->genericPacks = q.genericPacks;
}
if (ftv->generics.empty() && ftv->genericPacks.empty() && !q.seenMutableType && !q.seenGenericType)
ftv->hasNoGenerics = true;
ftv->generalized = true;
}
} // namespace Luau

View file

@ -2,8 +2,6 @@
#include "Luau/Scope.h"
LUAU_FASTFLAG(LuauTwoPassAliasDefinitionFix);
namespace Luau
{
@ -19,8 +17,7 @@ Scope::Scope(const ScopePtr& parent, int subLevel)
, returnType(parent->returnType)
, level(parent->level.incr())
{
if (FFlag::LuauTwoPassAliasDefinitionFix)
level = level.incr();
level = level.incr();
level.subLevel = subLevel;
}

View file

@ -2,6 +2,7 @@
#include "Luau/Substitution.h"
#include "Luau/Common.h"
#include "Luau/Clone.h"
#include "Luau/TxnLog.h"
#include <algorithm>
@ -9,35 +10,26 @@
LUAU_FASTFLAG(LuauLowerBoundsCalculation)
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
LUAU_FASTFLAG(LuauTypecheckOptPass)
LUAU_FASTFLAGVARIABLE(LuauSubstituteFollowNewTypes, false)
LUAU_FASTFLAGVARIABLE(LuauSubstituteFollowPossibleMutations, false)
namespace Luau
{
void Tarjan::visitChildren(TypeId ty, int index)
{
if (FFlag::LuauTypecheckOptPass)
LUAU_ASSERT(ty == log->follow(ty));
else
ty = log->follow(ty);
LUAU_ASSERT(ty == log->follow(ty));
if (ignoreChildren(ty))
return;
if (FFlag::LuauTypecheckOptPass)
{
if (auto pty = log->pending(ty))
ty = &pty->pending;
}
if (auto pty = log->pending(ty))
ty = &pty->pending;
if (const FunctionTypeVar* ftv = FFlag::LuauTypecheckOptPass ? get<FunctionTypeVar>(ty) : log->getMutable<FunctionTypeVar>(ty))
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(ty))
{
visitChild(ftv->argTypes);
visitChild(ftv->retType);
}
else if (const TableTypeVar* ttv = FFlag::LuauTypecheckOptPass ? get<TableTypeVar>(ty) : log->getMutable<TableTypeVar>(ty))
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
{
LUAU_ASSERT(!ttv->boundTo);
for (const auto& [name, prop] : ttv->props)
@ -54,17 +46,17 @@ void Tarjan::visitChildren(TypeId ty, int index)
for (TypePackId itp : ttv->instantiatedTypePackParams)
visitChild(itp);
}
else if (const MetatableTypeVar* mtv = FFlag::LuauTypecheckOptPass ? get<MetatableTypeVar>(ty) : log->getMutable<MetatableTypeVar>(ty))
else if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(ty))
{
visitChild(mtv->table);
visitChild(mtv->metatable);
}
else if (const UnionTypeVar* utv = FFlag::LuauTypecheckOptPass ? get<UnionTypeVar>(ty) : log->getMutable<UnionTypeVar>(ty))
else if (const UnionTypeVar* utv = get<UnionTypeVar>(ty))
{
for (TypeId opt : utv->options)
visitChild(opt);
}
else if (const IntersectionTypeVar* itv = FFlag::LuauTypecheckOptPass ? get<IntersectionTypeVar>(ty) : log->getMutable<IntersectionTypeVar>(ty))
else if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(ty))
{
for (TypeId part : itv->parts)
visitChild(part);
@ -78,28 +70,22 @@ void Tarjan::visitChildren(TypeId ty, int index)
void Tarjan::visitChildren(TypePackId tp, int index)
{
if (FFlag::LuauTypecheckOptPass)
LUAU_ASSERT(tp == log->follow(tp));
else
tp = log->follow(tp);
LUAU_ASSERT(tp == log->follow(tp));
if (ignoreChildren(tp))
return;
if (FFlag::LuauTypecheckOptPass)
{
if (auto ptp = log->pending(tp))
tp = &ptp->pending;
}
if (auto ptp = log->pending(tp))
tp = &ptp->pending;
if (const TypePack* tpp = FFlag::LuauTypecheckOptPass ? get<TypePack>(tp) : log->getMutable<TypePack>(tp))
if (const TypePack* tpp = get<TypePack>(tp))
{
for (TypeId tv : tpp->head)
visitChild(tv);
if (tpp->tail)
visitChild(*tpp->tail);
}
else if (const VariadicTypePack* vtp = FFlag::LuauTypecheckOptPass ? get<VariadicTypePack>(tp) : log->getMutable<VariadicTypePack>(tp))
else if (const VariadicTypePack* vtp = get<VariadicTypePack>(tp))
{
visitChild(vtp->ty);
}
@ -107,10 +93,7 @@ void Tarjan::visitChildren(TypePackId tp, int index)
std::pair<int, bool> Tarjan::indexify(TypeId ty)
{
if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations)
LUAU_ASSERT(ty == log->follow(ty));
else
ty = log->follow(ty);
ty = log->follow(ty);
bool fresh = !typeToIndex.contains(ty);
int& index = typeToIndex[ty];
@ -128,10 +111,7 @@ std::pair<int, bool> Tarjan::indexify(TypeId ty)
std::pair<int, bool> Tarjan::indexify(TypePackId tp)
{
if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations)
LUAU_ASSERT(tp == log->follow(tp));
else
tp = log->follow(tp);
tp = log->follow(tp);
bool fresh = !packToIndex.contains(tp);
int& index = packToIndex[tp];
@ -149,8 +129,7 @@ std::pair<int, bool> Tarjan::indexify(TypePackId tp)
void Tarjan::visitChild(TypeId ty)
{
if (!FFlag::LuauSubstituteFollowPossibleMutations)
ty = log->follow(ty);
ty = log->follow(ty);
edgesTy.push_back(ty);
edgesTp.push_back(nullptr);
@ -158,8 +137,7 @@ void Tarjan::visitChild(TypeId ty)
void Tarjan::visitChild(TypePackId tp)
{
if (!FFlag::LuauSubstituteFollowPossibleMutations)
tp = log->follow(tp);
tp = log->follow(tp);
edgesTy.push_back(nullptr);
edgesTp.push_back(tp);
@ -384,85 +362,24 @@ std::optional<TypePackId> Substitution::substitute(TypePackId tp)
TypeId Substitution::clone(TypeId ty)
{
ty = log->follow(ty);
TypeId result = ty;
if (FFlag::LuauTypecheckOptPass)
{
if (auto pty = log->pending(ty))
ty = &pty->pending;
}
if (const FunctionTypeVar* ftv = FFlag::LuauTypecheckOptPass ? get<FunctionTypeVar>(ty) : log->getMutable<FunctionTypeVar>(ty))
{
FunctionTypeVar clone = FunctionTypeVar{ftv->level, ftv->argTypes, ftv->retType, ftv->definition, ftv->hasSelf};
clone.generics = ftv->generics;
clone.genericPacks = ftv->genericPacks;
clone.magicFunction = ftv->magicFunction;
clone.tags = ftv->tags;
clone.argNames = ftv->argNames;
result = addType(std::move(clone));
}
else if (const TableTypeVar* ttv = FFlag::LuauTypecheckOptPass ? get<TableTypeVar>(ty) : log->getMutable<TableTypeVar>(ty))
{
LUAU_ASSERT(!ttv->boundTo);
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state};
clone.methodDefinitionLocations = ttv->methodDefinitionLocations;
clone.definitionModuleName = ttv->definitionModuleName;
clone.name = ttv->name;
clone.syntheticName = ttv->syntheticName;
clone.instantiatedTypeParams = ttv->instantiatedTypeParams;
clone.instantiatedTypePackParams = ttv->instantiatedTypePackParams;
clone.tags = ttv->tags;
result = addType(std::move(clone));
}
else if (const MetatableTypeVar* mtv = FFlag::LuauTypecheckOptPass ? get<MetatableTypeVar>(ty) : log->getMutable<MetatableTypeVar>(ty))
{
MetatableTypeVar clone = MetatableTypeVar{mtv->table, mtv->metatable};
clone.syntheticName = mtv->syntheticName;
result = addType(std::move(clone));
}
else if (const UnionTypeVar* utv = FFlag::LuauTypecheckOptPass ? get<UnionTypeVar>(ty) : log->getMutable<UnionTypeVar>(ty))
{
UnionTypeVar clone;
clone.options = utv->options;
result = addType(std::move(clone));
}
else if (const IntersectionTypeVar* itv = FFlag::LuauTypecheckOptPass ? get<IntersectionTypeVar>(ty) : log->getMutable<IntersectionTypeVar>(ty))
{
IntersectionTypeVar clone;
clone.parts = itv->parts;
result = addType(std::move(clone));
}
else if (const ConstrainedTypeVar* ctv = get<ConstrainedTypeVar>(ty))
{
ConstrainedTypeVar clone{ctv->level, ctv->parts};
result = addType(std::move(clone));
}
asMutable(result)->documentationSymbol = ty->documentationSymbol;
return result;
return shallowClone(ty, *arena, log);
}
TypePackId Substitution::clone(TypePackId tp)
{
tp = log->follow(tp);
if (FFlag::LuauTypecheckOptPass)
{
if (auto ptp = log->pending(tp))
tp = &ptp->pending;
}
if (auto ptp = log->pending(tp))
tp = &ptp->pending;
if (const TypePack* tpp = FFlag::LuauTypecheckOptPass ? get<TypePack>(tp) : log->getMutable<TypePack>(tp))
if (const TypePack* tpp = get<TypePack>(tp))
{
TypePack clone;
clone.head = tpp->head;
clone.tail = tpp->tail;
return addTypePack(std::move(clone));
}
else if (const VariadicTypePack* vtp = FFlag::LuauTypecheckOptPass ? get<VariadicTypePack>(tp) : log->getMutable<VariadicTypePack>(tp))
else if (const VariadicTypePack* vtp = get<VariadicTypePack>(tp))
{
VariadicTypePack clone;
clone.ty = vtp->ty;
@ -474,28 +391,22 @@ TypePackId Substitution::clone(TypePackId tp)
void Substitution::foundDirty(TypeId ty)
{
if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations)
LUAU_ASSERT(ty == log->follow(ty));
else
ty = log->follow(ty);
ty = log->follow(ty);
if (isDirty(ty))
newTypes[ty] = FFlag::LuauSubstituteFollowNewTypes ? follow(clean(ty)) : clean(ty);
newTypes[ty] = follow(clean(ty));
else
newTypes[ty] = FFlag::LuauSubstituteFollowNewTypes ? follow(clone(ty)) : clone(ty);
newTypes[ty] = follow(clone(ty));
}
void Substitution::foundDirty(TypePackId tp)
{
if (FFlag::LuauTypecheckOptPass && !FFlag::LuauSubstituteFollowPossibleMutations)
LUAU_ASSERT(tp == log->follow(tp));
else
tp = log->follow(tp);
tp = log->follow(tp);
if (isDirty(tp))
newPacks[tp] = FFlag::LuauSubstituteFollowNewTypes ? follow(clean(tp)) : clean(tp);
newPacks[tp] = follow(clean(tp));
else
newPacks[tp] = FFlag::LuauSubstituteFollowNewTypes ? follow(clone(tp)) : clone(tp);
newPacks[tp] = follow(clone(tp));
}
TypeId Substitution::replace(TypeId ty)
@ -523,10 +434,7 @@ void Substitution::replaceChildren(TypeId ty)
if (BoundTypeVar* btv = log->getMutable<BoundTypeVar>(ty); FFlag::LuauLowerBoundsCalculation && btv)
btv->boundTo = replace(btv->boundTo);
if (FFlag::LuauTypecheckOptPass)
LUAU_ASSERT(ty == log->follow(ty));
else
ty = log->follow(ty);
LUAU_ASSERT(ty == log->follow(ty));
if (ignoreChildren(ty))
return;
@ -577,10 +485,7 @@ void Substitution::replaceChildren(TypeId ty)
void Substitution::replaceChildren(TypePackId tp)
{
if (FFlag::LuauTypecheckOptPass)
LUAU_ASSERT(tp == log->follow(tp));
else
tp = log->follow(tp);
LUAU_ASSERT(tp == log->follow(tp));
if (ignoreChildren(tp))
return;

View file

@ -18,7 +18,7 @@ LUAU_FASTFLAG(LuauLowerBoundsCalculation)
* Fair warning: Setting this will break a lot of Luau unit tests.
*/
LUAU_FASTFLAGVARIABLE(DebugLuauVerboseTypeNames, false)
LUAU_FASTFLAGVARIABLE(LuauDocFuncParameters, false)
LUAU_FASTFLAGVARIABLE(LuauToStringTableBracesNewlines, false)
namespace Luau
{
@ -48,46 +48,6 @@ struct FindCyclicTypes final : TypeVarVisitor
cycleTPs.insert(tp);
}
// TODO: Clip all the operator()s when we clip FFlagLuauUseVisitRecursionLimit
template<typename T>
bool operator()(TypeId ty, const T&)
{
return visit(ty);
}
bool operator()(TypeId ty, const TableTypeVar& ttv) = delete;
bool operator()(TypeId ty, const TableTypeVar& ttv, std::unordered_set<void*>& seen)
{
if (!visited.insert(ty).second)
return false;
if (ttv.name || ttv.syntheticName)
{
for (TypeId itp : ttv.instantiatedTypeParams)
DEPRECATED_visitTypeVar(itp, *this, seen);
for (TypePackId itp : ttv.instantiatedTypePackParams)
DEPRECATED_visitTypeVar(itp, *this, seen);
return exhaustive;
}
return true;
}
bool operator()(TypeId, const ClassTypeVar&)
{
return false;
}
template<typename T>
bool operator()(TypePackId tp, const T&)
{
return visit(tp);
}
bool visit(TypeId ty) override
{
return visited.insert(ty).second;
@ -128,7 +88,7 @@ void findCyclicTypes(std::set<TypeId>& cycles, std::set<TypePackId>& cycleTPs, T
{
FindCyclicTypes fct;
fct.exhaustive = exhaustive;
DEPRECATED_visitTypeVar(ty, fct);
fct.traverse(ty);
cycles = std::move(fct.cycles);
cycleTPs = std::move(fct.cycleTPs);
@ -219,6 +179,8 @@ struct StringifierState
return generateName(s);
}
int previousNameIndex = 0;
std::string getName(TypePackId ty)
{
const size_t s = result.nameMap.typePacks.size();
@ -228,9 +190,10 @@ struct StringifierState
for (int count = 0; count < 256; ++count)
{
std::string candidate = generateName(usedNames.size() + count);
std::string candidate = generateName(previousNameIndex + count);
if (!usedNames.count(candidate))
{
previousNameIndex += count;
usedNames.insert(candidate);
n = candidate;
return candidate;
@ -248,6 +211,13 @@ struct StringifierState
result.name += s;
}
void emit(TypeLevel level)
{
emit(std::to_string(level.level));
emit("-");
emit(std::to_string(level.subLevel));
}
void emit(const char* s)
{
if (opts.maxTypeLength > 0 && result.name.length() > opts.maxTypeLength)
@ -314,7 +284,8 @@ struct TypeVarStringifier
}
Luau::visit(
[this, tv](auto&& t) {
[this, tv](auto&& t)
{
return (*this)(tv, t);
},
tv->ty);
@ -379,7 +350,7 @@ struct TypeVarStringifier
if (FFlag::DebugLuauVerboseTypeNames)
{
state.emit("-");
state.emit(std::to_string(ftv.level.level));
state.emit(ftv.level);
}
}
@ -392,6 +363,7 @@ struct TypeVarStringifier
{
if (gtv.explicitName)
{
state.usedNames.insert(gtv.name);
state.result.nameMap.typeVars[ty] = gtv.name;
state.emit(gtv.name);
}
@ -403,7 +375,10 @@ struct TypeVarStringifier
{
state.result.invalid = true;
state.emit("[[");
state.emit("[");
if (FFlag::DebugLuauVerboseTypeNames)
state.emit(ctv.level);
state.emit("[");
bool first = true;
for (TypeId ty : ctv.parts)
@ -584,22 +559,54 @@ struct TypeVarStringifier
{
case TableState::Sealed:
state.result.invalid = true;
openbrace = "{| ";
closedbrace = " |}";
if (FFlag::LuauToStringTableBracesNewlines)
{
openbrace = "{|";
closedbrace = "|}";
}
else
{
openbrace = "{| ";
closedbrace = " |}";
}
break;
case TableState::Unsealed:
openbrace = "{ ";
closedbrace = " }";
if (FFlag::LuauToStringTableBracesNewlines)
{
openbrace = "{";
closedbrace = "}";
}
else
{
openbrace = "{ ";
closedbrace = " }";
}
break;
case TableState::Free:
state.result.invalid = true;
openbrace = "{- ";
closedbrace = " -}";
if (FFlag::LuauToStringTableBracesNewlines)
{
openbrace = "{-";
closedbrace = "-}";
}
else
{
openbrace = "{- ";
closedbrace = " -}";
}
break;
case TableState::Generic:
state.result.invalid = true;
openbrace = "{+ ";
closedbrace = " +}";
if (FFlag::LuauToStringTableBracesNewlines)
{
openbrace = "{+";
closedbrace = "+}";
}
else
{
openbrace = "{+ ";
closedbrace = " +}";
}
break;
}
@ -618,6 +625,8 @@ struct TypeVarStringifier
bool comma = false;
if (ttv.indexer)
{
if (FFlag::LuauToStringTableBracesNewlines)
state.newline();
state.emit("[");
stringify(ttv.indexer->indexType);
state.emit("]: ");
@ -634,6 +643,10 @@ struct TypeVarStringifier
state.emit(",");
state.newline();
}
else if (FFlag::LuauToStringTableBracesNewlines)
{
state.newline();
}
size_t length = state.result.name.length() - oldLength;
@ -660,6 +673,13 @@ struct TypeVarStringifier
}
state.dedent();
if (FFlag::LuauToStringTableBracesNewlines)
{
if (comma)
state.newline();
else
state.emit(" ");
}
state.emit(closedbrace);
state.unsee(&ttv);
@ -735,7 +755,10 @@ struct TypeVarStringifier
for (std::string& ss : results)
{
if (!first)
state.emit(" | ");
{
state.newline();
state.emit("| ");
}
state.emit(ss);
first = false;
}
@ -788,7 +811,10 @@ struct TypeVarStringifier
for (std::string& ss : results)
{
if (!first)
state.emit(" & ");
{
state.newline();
state.emit("& ");
}
state.emit(ss);
first = false;
}
@ -854,7 +880,8 @@ struct TypePackStringifier
}
Luau::visit(
[this, tp](auto&& t) {
[this, tp](auto&& t)
{
return (*this)(tp, t);
},
tp->ty);
@ -927,6 +954,7 @@ struct TypePackStringifier
state.emit("gen-");
if (pack.explicitName)
{
state.usedNames.insert(pack.name);
state.result.nameMap.typePacks[tp] = pack.name;
state.emit(pack.name);
}
@ -947,7 +975,7 @@ struct TypePackStringifier
if (FFlag::DebugLuauVerboseTypeNames)
{
state.emit("-");
state.emit(std::to_string(pack.level.level));
state.emit(pack.level);
}
state.emit("...");
@ -984,9 +1012,11 @@ static void assignCycleNames(const std::set<TypeId>& cycles, const std::set<Type
if (auto ttv = get<TableTypeVar>(follow(cycleTy)); !exhaustive && ttv && (ttv->syntheticName || ttv->name))
{
// If we have a cycle type in type parameters, assign a cycle name for this named table
if (std::find_if(ttv->instantiatedTypeParams.begin(), ttv->instantiatedTypeParams.end(), [&](auto&& el) {
return cycles.count(follow(el));
}) != ttv->instantiatedTypeParams.end())
if (std::find_if(ttv->instantiatedTypeParams.begin(), ttv->instantiatedTypeParams.end(),
[&](auto&& el)
{
return cycles.count(follow(el));
}) != ttv->instantiatedTypeParams.end())
cycleNames[cycleTy] = ttv->name ? *ttv->name : *ttv->syntheticName;
continue;
@ -1082,9 +1112,11 @@ ToStringResult toStringDetailed(TypeId ty, const ToStringOptions& opts)
state.exhaustive = true;
std::vector<std::pair<TypeId, std::string>> sortedCycleNames{state.cycleNames.begin(), state.cycleNames.end()};
std::sort(sortedCycleNames.begin(), sortedCycleNames.end(), [](const auto& a, const auto& b) {
return a.second < b.second;
});
std::sort(sortedCycleNames.begin(), sortedCycleNames.end(),
[](const auto& a, const auto& b)
{
return a.second < b.second;
});
bool semi = false;
for (const auto& [cycleTy, name] : sortedCycleNames)
@ -1095,7 +1127,8 @@ ToStringResult toStringDetailed(TypeId ty, const ToStringOptions& opts)
state.emit(name);
state.emit(" = ");
Luau::visit(
[&tvs, cycleTy = cycleTy](auto&& t) {
[&tvs, cycleTy = cycleTy](auto&& t)
{
return tvs(cycleTy, t);
},
cycleTy->ty);
@ -1152,9 +1185,11 @@ ToStringResult toStringDetailed(TypePackId tp, const ToStringOptions& opts)
state.exhaustive = true;
std::vector<std::pair<TypeId, std::string>> sortedCycleNames{state.cycleNames.begin(), state.cycleNames.end()};
std::sort(sortedCycleNames.begin(), sortedCycleNames.end(), [](const auto& a, const auto& b) {
return a.second < b.second;
});
std::sort(sortedCycleNames.begin(), sortedCycleNames.end(),
[](const auto& a, const auto& b)
{
return a.second < b.second;
});
bool semi = false;
for (const auto& [cycleTy, name] : sortedCycleNames)
@ -1165,7 +1200,8 @@ ToStringResult toStringDetailed(TypePackId tp, const ToStringOptions& opts)
state.emit(name);
state.emit(" = ");
Luau::visit(
[&tvs, cycleTy = cycleTy](auto t) {
[&tvs, cycleTy = cycleTy](auto t)
{
return tvs(cycleTy, t);
},
cycleTy->ty);
@ -1215,57 +1251,38 @@ std::string toStringNamedFunction(const std::string& funcName, const FunctionTyp
auto argPackIter = begin(ftv.argTypes);
bool first = true;
if (FFlag::LuauDocFuncParameters)
size_t idx = 0;
while (argPackIter != end(ftv.argTypes))
{
size_t idx = 0;
while (argPackIter != end(ftv.argTypes))
// ftv takes a self parameter as the first argument, skip it if specified in option
if (idx == 0 && ftv.hasSelf && opts.hideFunctionSelfArgument)
{
if (!first)
state.emit(", ");
first = false;
// We don't respect opts.functionTypeArguments
if (idx < opts.namedFunctionOverrideArgNames.size())
{
state.emit(opts.namedFunctionOverrideArgNames[idx] + ": ");
}
else if (idx < ftv.argNames.size() && ftv.argNames[idx])
{
state.emit(ftv.argNames[idx]->name + ": ");
}
else
{
state.emit("_: ");
}
tvs.stringify(*argPackIter);
++argPackIter;
++idx;
continue;
}
}
else
{
auto argNameIter = ftv.argNames.begin();
while (argPackIter != end(ftv.argTypes))
if (!first)
state.emit(", ");
first = false;
// We don't respect opts.functionTypeArguments
if (idx < opts.namedFunctionOverrideArgNames.size())
{
if (!first)
state.emit(", ");
first = false;
// We don't currently respect opts.functionTypeArguments. I don't think this function should.
if (argNameIter != ftv.argNames.end())
{
state.emit((*argNameIter ? (*argNameIter)->name : "_") + ": ");
++argNameIter;
}
else
{
state.emit("_: ");
}
tvs.stringify(*argPackIter);
++argPackIter;
state.emit(opts.namedFunctionOverrideArgNames[idx] + ": ");
}
else if (idx < ftv.argNames.size() && ftv.argNames[idx])
{
state.emit(ftv.argNames[idx]->name + ": ");
}
else
{
state.emit("_: ");
}
tvs.stringify(*argPackIter);
++argPackIter;
++idx;
}
if (argPackIter.tail())
@ -1348,4 +1365,55 @@ std::string generateName(size_t i)
return n;
}
std::string toString(const Constraint& c, ToStringOptions& opts)
{
if (const SubtypeConstraint* sc = Luau::get_if<SubtypeConstraint>(&c.c))
{
ToStringResult subStr = toStringDetailed(sc->subType, opts);
opts.nameMap = std::move(subStr.nameMap);
ToStringResult superStr = toStringDetailed(sc->superType, opts);
opts.nameMap = std::move(superStr.nameMap);
return subStr.name + " <: " + superStr.name;
}
else if (const PackSubtypeConstraint* psc = Luau::get_if<PackSubtypeConstraint>(&c.c))
{
ToStringResult subStr = toStringDetailed(psc->subPack, opts);
opts.nameMap = std::move(subStr.nameMap);
ToStringResult superStr = toStringDetailed(psc->superPack, opts);
opts.nameMap = std::move(superStr.nameMap);
return subStr.name + " <: " + superStr.name;
}
else if (const GeneralizationConstraint* gc = Luau::get_if<GeneralizationConstraint>(&c.c))
{
ToStringResult subStr = toStringDetailed(gc->subType, opts);
opts.nameMap = std::move(subStr.nameMap);
ToStringResult superStr = toStringDetailed(gc->superType, opts);
opts.nameMap = std::move(superStr.nameMap);
return subStr.name + " ~ gen " + superStr.name;
}
else if (const InstantiationConstraint* ic = Luau::get_if<InstantiationConstraint>(&c.c))
{
ToStringResult subStr = toStringDetailed(ic->subType, opts);
opts.nameMap = std::move(subStr.nameMap);
ToStringResult superStr = toStringDetailed(ic->superType, opts);
opts.nameMap = std::move(superStr.nameMap);
return subStr.name + " ~ inst " + superStr.name;
}
else
{
LUAU_ASSERT(false);
return "";
}
}
std::string dump(const Constraint& c)
{
ToStringOptions opts;
opts.exhaustive = true;
opts.functionTypeArguments = true;
std::string s = toString(c, opts);
printf("%s\n", s.c_str());
return s;
}
} // namespace Luau

View file

@ -7,7 +7,7 @@
#include <algorithm>
#include <stdexcept>
LUAU_FASTFLAGVARIABLE(LuauJustOneCallFrameForHaveSeen, false)
LUAU_FASTFLAG(LuauNonCopyableTypeVarFields)
namespace Luau
{
@ -82,18 +82,32 @@ void TxnLog::commit()
{
for (auto& [ty, rep] : typeVarChanges)
{
TypeArena* owningArena = ty->owningArena;
TypeVar* mtv = asMutable(ty);
*mtv = rep.get()->pending;
mtv->owningArena = owningArena;
if (FFlag::LuauNonCopyableTypeVarFields)
{
asMutable(ty)->reassign(rep.get()->pending);
}
else
{
TypeArena* owningArena = ty->owningArena;
TypeVar* mtv = asMutable(ty);
*mtv = rep.get()->pending;
mtv->owningArena = owningArena;
}
}
for (auto& [tp, rep] : typePackChanges)
{
TypeArena* owningArena = tp->owningArena;
TypePackVar* mpv = asMutable(tp);
*mpv = rep.get()->pending;
mpv->owningArena = owningArena;
if (FFlag::LuauNonCopyableTypeVarFields)
{
asMutable(tp)->reassign(rep.get()->pending);
}
else
{
TypeArena* owningArena = tp->owningArena;
TypePackVar* mpv = asMutable(tp);
*mpv = rep.get()->pending;
mpv->owningArena = owningArena;
}
}
clear();
@ -150,37 +164,13 @@ void TxnLog::popSeen(TypePackId lhs, TypePackId rhs)
bool TxnLog::haveSeen(TypeOrPackId lhs, TypeOrPackId rhs) const
{
if (FFlag::LuauJustOneCallFrameForHaveSeen && !FFlag::LuauTypecheckOptPass)
const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
if (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair))
{
// This function will technically work if `this` is nullptr, but this
// indicates a bug, so we explicitly assert.
LUAU_ASSERT(static_cast<const void*>(this) != nullptr);
const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
for (const TxnLog* current = this; current; current = current->parent)
{
if (current->sharedSeen->end() != std::find(current->sharedSeen->begin(), current->sharedSeen->end(), sortedPair))
return true;
}
return false;
return true;
}
else
{
const std::pair<TypeOrPackId, TypeOrPackId> sortedPair = (lhs > rhs) ? std::make_pair(lhs, rhs) : std::make_pair(rhs, lhs);
if (sharedSeen->end() != std::find(sharedSeen->begin(), sharedSeen->end(), sortedPair))
{
return true;
}
if (!FFlag::LuauTypecheckOptPass && parent)
{
return parent->haveSeen(lhs, rhs);
}
return false;
}
return false;
}
void TxnLog::pushSeen(TypeOrPackId lhs, TypeOrPackId rhs)
@ -204,8 +194,13 @@ PendingType* TxnLog::queue(TypeId ty)
// about this type, we don't want to mutate the parent's state.
auto& pending = typeVarChanges[ty];
if (!pending)
{
pending = std::make_unique<PendingType>(*ty);
if (FFlag::LuauNonCopyableTypeVarFields)
pending->pending.owningArena = nullptr;
}
return pending.get();
}
@ -217,8 +212,13 @@ PendingTypePack* TxnLog::queue(TypePackId tp)
// about this type, we don't want to mutate the parent's state.
auto& pending = typePackChanges[tp];
if (!pending)
{
pending = std::make_unique<PendingTypePack>(*tp);
if (FFlag::LuauNonCopyableTypeVarFields)
pending->pending.owningArena = nullptr;
}
return pending.get();
}
@ -255,14 +255,24 @@ PendingTypePack* TxnLog::pending(TypePackId tp) const
PendingType* TxnLog::replace(TypeId ty, TypeVar replacement)
{
PendingType* newTy = queue(ty);
newTy->pending = replacement;
if (FFlag::LuauNonCopyableTypeVarFields)
newTy->pending.reassign(replacement);
else
newTy->pending = replacement;
return newTy;
}
PendingTypePack* TxnLog::replace(TypePackId tp, TypePackVar replacement)
{
PendingTypePack* newTp = queue(tp);
newTp->pending = replacement;
if (FFlag::LuauNonCopyableTypeVarFields)
newTp->pending.reassign(replacement);
else
newTp->pending = replacement;
return newTp;
}

View file

@ -0,0 +1,88 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/TypeArena.h"
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false);
namespace Luau
{
void TypeArena::clear()
{
typeVars.clear();
typePacks.clear();
}
TypeId TypeArena::addTV(TypeVar&& tv)
{
TypeId allocated = typeVars.allocate(std::move(tv));
asMutable(allocated)->owningArena = this;
return allocated;
}
TypeId TypeArena::freshType(TypeLevel level)
{
TypeId allocated = typeVars.allocate(FreeTypeVar{level});
asMutable(allocated)->owningArena = this;
return allocated;
}
TypePackId TypeArena::addTypePack(std::initializer_list<TypeId> types)
{
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
asMutable(allocated)->owningArena = this;
return allocated;
}
TypePackId TypeArena::addTypePack(std::vector<TypeId> types)
{
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
asMutable(allocated)->owningArena = this;
return allocated;
}
TypePackId TypeArena::addTypePack(TypePack tp)
{
TypePackId allocated = typePacks.allocate(std::move(tp));
asMutable(allocated)->owningArena = this;
return allocated;
}
TypePackId TypeArena::addTypePack(TypePackVar tp)
{
TypePackId allocated = typePacks.allocate(std::move(tp));
asMutable(allocated)->owningArena = this;
return allocated;
}
void freeze(TypeArena& arena)
{
if (!FFlag::DebugLuauFreezeArena)
return;
arena.typeVars.freeze();
arena.typePacks.freeze();
}
void unfreeze(TypeArena& arena)
{
if (!FFlag::DebugLuauFreezeArena)
return;
arena.typeVars.unfreeze();
arena.typePacks.unfreeze();
}
} // namespace Luau

File diff suppressed because it is too large Load diff

View file

@ -5,6 +5,8 @@
#include <stdexcept>
LUAU_FASTFLAG(LuauNonCopyableTypeVarFields)
namespace Luau
{
@ -36,6 +38,25 @@ TypePackVar& TypePackVar::operator=(TypePackVariant&& tp)
return *this;
}
TypePackVar& TypePackVar::operator=(const TypePackVar& rhs)
{
if (FFlag::LuauNonCopyableTypeVarFields)
{
LUAU_ASSERT(owningArena == rhs.owningArena);
LUAU_ASSERT(!rhs.persistent);
reassign(rhs);
}
else
{
ty = rhs.ty;
persistent = rhs.persistent;
owningArena = rhs.owningArena;
}
return *this;
}
TypePackIterator::TypePackIterator(TypePackId typePack)
: TypePackIterator(typePack, TxnLog::empty())
{

View file

@ -5,8 +5,6 @@
#include "Luau/ToString.h"
#include "Luau/TypeInfer.h"
LUAU_FASTFLAGVARIABLE(LuauTerminateCyclicMetatableIndexLookup, false)
namespace Luau
{
@ -55,13 +53,10 @@ std::optional<TypeId> findTablePropertyRespectingMeta(ErrorVec& errors, TypeId t
{
TypeId index = follow(*mtIndex);
if (FFlag::LuauTerminateCyclicMetatableIndexLookup)
{
if (count >= 100)
return std::nullopt;
if (count >= 100)
return std::nullopt;
++count;
}
++count;
if (const auto& itt = getTableType(index))
{

View file

@ -24,9 +24,7 @@ LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauSubtypingAddOptPropsToUnsealedTables)
LUAU_FASTFLAG(LuauDiscriminableUnions2)
LUAU_FASTFLAGVARIABLE(LuauAnyInIsOptionalIsOptional, false)
LUAU_FASTFLAGVARIABLE(LuauClassDefinitionModuleInError, false)
LUAU_FASTFLAG(LuauNonCopyableTypeVarFields)
namespace Luau
{
@ -204,14 +202,14 @@ bool isOptional(TypeId ty)
ty = follow(ty);
if (FFlag::LuauAnyInIsOptionalIsOptional && get<AnyTypeVar>(ty))
if (get<AnyTypeVar>(ty))
return true;
auto utv = get<UnionTypeVar>(ty);
if (!utv)
return false;
return std::any_of(begin(utv), end(utv), FFlag::LuauAnyInIsOptionalIsOptional ? isOptional : isNil);
return std::any_of(begin(utv), end(utv), isOptional);
}
bool isTableIntersection(TypeId ty)
@ -304,7 +302,7 @@ std::optional<ModuleName> getDefinitionModuleName(TypeId type)
if (ftv->definition)
return ftv->definition->definitionModuleName;
}
else if (auto ctv = get<ClassTypeVar>(type); ctv && FFlag::LuauClassDefinitionModuleInError)
else if (auto ctv = get<ClassTypeVar>(type))
{
if (!ctv->definitionModuleName.empty())
return ctv->definitionModuleName;
@ -378,8 +376,7 @@ bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount)
if (seen.contains(ty))
return true;
bool isStr = FFlag::LuauDiscriminableUnions2 ? isString(ty) : isPrim(ty, PrimitiveTypeVar::String);
if (isStr || get<AnyTypeVar>(ty) || get<TableTypeVar>(ty) || get<MetatableTypeVar>(ty))
if (isString(ty) || get<AnyTypeVar>(ty) || get<TableTypeVar>(ty) || get<MetatableTypeVar>(ty))
return true;
if (auto uty = get<UnionTypeVar>(ty))
@ -648,6 +645,26 @@ TypeVar& TypeVar::operator=(TypeVariant&& rhs)
return *this;
}
TypeVar& TypeVar::operator=(const TypeVar& rhs)
{
if (FFlag::LuauNonCopyableTypeVarFields)
{
LUAU_ASSERT(owningArena == rhs.owningArena);
LUAU_ASSERT(!rhs.persistent);
reassign(rhs);
}
else
{
ty = rhs.ty;
persistent = rhs.persistent;
normal = rhs.normal;
owningArena = rhs.owningArena;
}
return *this;
}
TypeId makeFunction(TypeArena& arena, std::optional<TypeId> selfType, std::initializer_list<TypeId> generics,
std::initializer_list<TypePackId> genericPacks, std::initializer_list<TypeId> paramTypes, std::initializer_list<std::string> paramNames,
std::initializer_list<TypeId> retTypes);
@ -727,7 +744,7 @@ TypeId SingletonTypes::makeStringMetatable()
TableTypeVar::Props stringLib = {
{"byte", {arena->addType(FunctionTypeVar{arena->addTypePack({stringType, optionalNumber, optionalNumber}), numberVariadicList})}},
{"char", {arena->addType(FunctionTypeVar{arena->addTypePack(TypePack{{numberType}, numberVariadicList}), arena->addTypePack({stringType})})}},
{"char", {arena->addType(FunctionTypeVar{numberVariadicList, arena->addTypePack({stringType})})}},
{"find", {makeFunction(*arena, stringType, {}, {}, {stringType, optionalNumber, optionalBoolean}, {}, {optionalNumber, optionalNumber})}},
{"format", {formatFn}}, // FIXME
{"gmatch", {gmatchFunc}},

View file

@ -12,12 +12,16 @@ Free::Free(TypeLevel level)
{
}
Free::Free(Scope2* scope)
: scope(scope)
{
}
int Free::nextIndex = 0;
Generic::Generic()
: index(++nextIndex)
, name("g" + std::to_string(index))
, explicitName(false)
{
}
@ -25,7 +29,6 @@ Generic::Generic(TypeLevel level)
: index(++nextIndex)
, level(level)
, name("g" + std::to_string(index))
, explicitName(false)
{
}
@ -36,6 +39,12 @@ Generic::Generic(const Name& name)
{
}
Generic::Generic(Scope2* scope)
: index(++nextIndex)
, scope(scope)
{
}
Generic::Generic(TypeLevel level, const Name& name)
: index(++nextIndex)
, level(level)

View file

@ -21,11 +21,7 @@ LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false);
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
LUAU_FASTFLAG(LuauErrorRecoveryType);
LUAU_FASTFLAGVARIABLE(LuauSubtypingAddOptPropsToUnsealedTables, false)
LUAU_FASTFLAGVARIABLE(LuauWidenIfSupertypeIsFree2, false)
LUAU_FASTFLAGVARIABLE(LuauDifferentOrderOfUnificationDoesntMatter2, false)
LUAU_FASTFLAGVARIABLE(LuauTxnLogRefreshFunctionPointers, false)
LUAU_FASTFLAG(LuauAnyInIsOptionalIsOptional)
LUAU_FASTFLAG(LuauTypecheckOptPass)
namespace Luau
{
@ -151,8 +147,7 @@ static void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel
return;
PromoteTypeLevels ptl{log, typeArena, minLevel};
DenseHashSet<void*> seen{nullptr};
DEPRECATED_visitTypeVarOnce(ty, ptl, seen);
ptl.traverse(ty);
}
void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypePackId tp)
@ -162,8 +157,7 @@ void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLev
return;
PromoteTypeLevels ptl{log, typeArena, minLevel};
DenseHashSet<void*> seen{nullptr};
DEPRECATED_visitTypeVarOnce(tp, ptl, seen);
ptl.traverse(tp);
}
struct SkipCacheForType final : TypeVarOnceVisitor
@ -174,49 +168,6 @@ struct SkipCacheForType final : TypeVarOnceVisitor
{
}
// TODO cycle() and operator() can be clipped with FFlagLuauUseVisitRecursionLimit
void cycle(TypeId) override {}
void cycle(TypePackId) override {}
bool operator()(TypeId ty, const FreeTypeVar& ftv)
{
return visit(ty, ftv);
}
bool operator()(TypeId ty, const BoundTypeVar& btv)
{
return visit(ty, btv);
}
bool operator()(TypeId ty, const GenericTypeVar& gtv)
{
return visit(ty, gtv);
}
bool operator()(TypeId ty, const TableTypeVar& ttv)
{
return visit(ty, ttv);
}
bool operator()(TypePackId tp, const FreeTypePack& ftp)
{
return visit(tp, ftp);
}
bool operator()(TypePackId tp, const BoundTypePack& ftp)
{
return visit(tp, ftp);
}
bool operator()(TypePackId tp, const GenericTypePack& ftp)
{
return visit(tp, ftp);
}
template<typename T>
bool operator()(TypeId ty, const T& t)
{
return visit(ty);
}
template<typename T>
bool operator()(TypePackId tp, const T&)
{
return visit(tp);
}
bool visit(TypeId, const FreeTypeVar&) override
{
result = true;
@ -343,6 +294,16 @@ bool Widen::ignoreChildren(TypeId ty)
return !log->is<UnionTypeVar>(ty);
}
TypeId Widen::operator()(TypeId ty)
{
return substitute(ty).value_or(ty);
}
TypePackId Widen::operator()(TypePackId tp)
{
return substitute(tp).value_or(tp);
}
static std::optional<TypeError> hasUnificationTooComplex(const ErrorVec& errors)
{
auto isUnificationTooComplex = [](const TypeError& te) {
@ -382,19 +343,6 @@ Unifier::Unifier(TypeArena* types, Mode mode, const Location& location, Variance
LUAU_ASSERT(sharedState.iceHandler);
}
Unifier::Unifier(TypeArena* types, Mode mode, std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen, const Location& location,
Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog)
: types(types)
, mode(mode)
, log(parentLog, sharedSeen)
, location(location)
, variance(variance)
, sharedState(sharedState)
{
LUAU_ASSERT(!FFlag::LuauTypecheckOptPass);
LUAU_ASSERT(sharedState.iceHandler);
}
void Unifier::tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall, bool isIntersection)
{
sharedState.counters.iterationCount = 0;
@ -490,6 +438,8 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
if (!occursFailed)
{
promoteTypeLevels(log, types, superLevel, subTy);
Widen widen{types};
log.replace(superTy, BoundTypeVar(widen(subTy)));
}
@ -627,9 +577,6 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId
std::optional<TypeError> unificationTooComplex;
std::optional<TypeError> firstFailedOption;
size_t count = uv->options.size();
size_t i = 0;
for (TypeId type : uv->options)
{
Unifier innerState = makeChildUnifier();
@ -645,60 +592,44 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* uv, TypeId
failed = true;
}
if (FFlag::LuauDifferentOrderOfUnificationDoesntMatter2)
{
}
else
{
if (i == count - 1)
{
log.concat(std::move(innerState.log));
}
++i;
}
}
// even if A | B <: T fails, we want to bind some options of T with A | B iff A | B was a subtype of that option.
if (FFlag::LuauDifferentOrderOfUnificationDoesntMatter2)
{
auto tryBind = [this, subTy](TypeId superOption) {
superOption = log.follow(superOption);
auto tryBind = [this, subTy](TypeId superOption) {
superOption = log.follow(superOption);
// just skip if the superOption is not free-ish.
auto ttv = log.getMutable<TableTypeVar>(superOption);
if (!log.is<FreeTypeVar>(superOption) && (!ttv || ttv->state != TableState::Free))
return;
// just skip if the superOption is not free-ish.
auto ttv = log.getMutable<TableTypeVar>(superOption);
if (!log.is<FreeTypeVar>(superOption) && (!ttv || ttv->state != TableState::Free))
return;
// If superOption is already present in subTy, do nothing. Nothing new has been learned, but the subtype
// test is successful.
if (auto subUnion = get<UnionTypeVar>(subTy))
{
if (end(subUnion) != std::find(begin(subUnion), end(subUnion), superOption))
return;
}
// Since we have already checked if S <: T, checking it again will not queue up the type for replacement.
// So we'll have to do it ourselves. We assume they unified cleanly if they are still in the seen set.
if (log.haveSeen(subTy, superOption))
{
// TODO: would it be nice for TxnLog::replace to do this?
if (log.is<TableTypeVar>(superOption))
log.bindTable(superOption, subTy);
else
log.replace(superOption, *subTy);
}
};
if (auto utv = log.getMutable<UnionTypeVar>(superTy))
// If superOption is already present in subTy, do nothing. Nothing new has been learned, but the subtype
// test is successful.
if (auto subUnion = get<UnionTypeVar>(subTy))
{
for (TypeId ty : utv)
tryBind(ty);
if (end(subUnion) != std::find(begin(subUnion), end(subUnion), superOption))
return;
}
else
tryBind(superTy);
// Since we have already checked if S <: T, checking it again will not queue up the type for replacement.
// So we'll have to do it ourselves. We assume they unified cleanly if they are still in the seen set.
if (log.haveSeen(subTy, superOption))
{
// TODO: would it be nice for TxnLog::replace to do this?
if (log.is<TableTypeVar>(superOption))
log.bindTable(superOption, subTy);
else
log.replace(superOption, *subTy);
}
};
if (auto utv = log.getMutable<UnionTypeVar>(superTy))
{
for (TypeId ty : utv)
tryBind(ty);
}
else
tryBind(superTy);
if (unificationTooComplex)
reportError(*unificationTooComplex);
@ -898,7 +829,7 @@ bool Unifier::canCacheResult(TypeId subTy, TypeId superTy)
auto skipCacheFor = [this](TypeId ty) {
SkipCacheForType visitor{sharedState.skipCacheForType, types};
DEPRECATED_visitTypeVarOnce(ty, visitor, sharedState.seenAny);
visitor.traverse(ty);
sharedState.skipCacheForType[ty] = visitor.result;
@ -1103,6 +1034,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
if (!log.getMutable<ErrorTypeVar>(superTp))
{
Widen widen{types};
log.replace(superTp, Unifiable::Bound<TypePackId>(widen(subTp)));
}
}
@ -1219,14 +1151,6 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
continue;
}
// In nonstrict mode, any also marks an optional argument.
else if (!FFlag::LuauAnyInIsOptionalIsOptional && superIter.good() && isNonstrictMode() &&
log.getMutable<AnyTypeVar>(log.follow(*superIter)))
{
superIter.advance();
continue;
}
if (log.getMutable<VariadicTypePack>(superIter.packId))
{
tryUnifyVariadics(subIter.packId, superIter.packId, false, int(subIter.index));
@ -1454,21 +1378,9 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
{
auto subIter = subTable->props.find(propName);
if (FFlag::LuauAnyInIsOptionalIsOptional)
{
if (subIter == subTable->props.end() &&
(!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && !isOptional(superProp.type))
missingProperties.push_back(propName);
}
else
{
bool isAny = log.getMutable<AnyTypeVar>(log.follow(superProp.type));
if (subIter == subTable->props.end() &&
(!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && !isOptional(superProp.type) &&
!isAny)
missingProperties.push_back(propName);
}
if (subIter == subTable->props.end() && (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) &&
!isOptional(superProp.type))
missingProperties.push_back(propName);
}
if (!missingProperties.empty())
@ -1485,18 +1397,8 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
{
auto superIter = superTable->props.find(propName);
if (FFlag::LuauAnyInIsOptionalIsOptional)
{
if (superIter == superTable->props.end() && (FFlag::LuauSubtypingAddOptPropsToUnsealedTables || !isOptional(subProp.type)))
extraProperties.push_back(propName);
}
else
{
bool isAny = log.is<AnyTypeVar>(log.follow(subProp.type));
if (superIter == superTable->props.end() &&
(FFlag::LuauSubtypingAddOptPropsToUnsealedTables || (!isOptional(subProp.type) && !isAny)))
extraProperties.push_back(propName);
}
if (superIter == superTable->props.end() && (FFlag::LuauSubtypingAddOptPropsToUnsealedTables || !isOptional(subProp.type)))
extraProperties.push_back(propName);
}
if (!extraProperties.empty())
@ -1540,21 +1442,12 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
if (innerState.errors.empty())
log.concat(std::move(innerState.log));
}
else if (FFlag::LuauAnyInIsOptionalIsOptional &&
(!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && isOptional(prop.type))
else if ((!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && isOptional(prop.type))
// This is sound because unsealed table types are precise, so `{ p : T } <: { p : T, q : U? }`
// since if `t : { p : T }` then we are guaranteed that `t.q` is `nil`.
// TODO: if the supertype is written to, the subtype may no longer be precise (alias analysis?)
{
}
else if ((!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) &&
(isOptional(prop.type) || get<AnyTypeVar>(follow(prop.type))))
// This is sound because unsealed table types are precise, so `{ p : T } <: { p : T, q : U? }`
// since if `t : { p : T }` then we are guaranteed that `t.q` is `nil`.
// TODO: should isOptional(anyType) be true?
// TODO: if the supertype is written to, the subtype may no longer be precise (alias analysis?)
{
}
else if (subTable->state == TableState::Free)
{
PendingType* pendingSub = log.queue(subTy);
@ -1618,10 +1511,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
else if (variance == Covariant)
{
}
else if (FFlag::LuauAnyInIsOptionalIsOptional && !FFlag::LuauSubtypingAddOptPropsToUnsealedTables && isOptional(prop.type))
{
}
else if (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables && (isOptional(prop.type) || get<AnyTypeVar>(follow(prop.type))))
else if (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables && isOptional(prop.type))
{
}
else if (superTable->state == TableState::Free)
@ -1728,34 +1618,10 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
}
}
TypeId Unifier::widen(TypeId ty)
{
if (!FFlag::LuauWidenIfSupertypeIsFree2)
return ty;
Widen widen{types};
std::optional<TypeId> result = widen.substitute(ty);
// TODO: what does it mean for substitution to fail to widen?
return result.value_or(ty);
}
TypePackId Unifier::widen(TypePackId tp)
{
if (!FFlag::LuauWidenIfSupertypeIsFree2)
return tp;
Widen widen{types};
std::optional<TypePackId> result = widen.substitute(tp);
// TODO: what does it mean for substitution to fail to widen?
return result.value_or(tp);
}
TypeId Unifier::deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> seen)
{
ty = follow(ty);
if (!FFlag::LuauAnyInIsOptionalIsOptional && get<AnyTypeVar>(ty))
return ty;
else if (isOptional(ty))
if (isOptional(ty))
return ty;
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
{
@ -1868,10 +1734,7 @@ void Unifier::tryUnifyFreeTable(TypeId subTy, TypeId superTy)
{
if (auto subProp = findTablePropertyRespectingMeta(subTy, freeName))
{
if (FFlag::LuauWidenIfSupertypeIsFree2)
tryUnify_(*subProp, freeProp.type);
else
tryUnify_(freeProp.type, *subProp);
tryUnify_(*subProp, freeProp.type);
/*
* TypeVars are commonly cyclic, so it is entirely possible
@ -2666,14 +2529,7 @@ void Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, Typ
Unifier Unifier::makeChildUnifier()
{
if (FFlag::LuauTypecheckOptPass)
{
Unifier u = Unifier{types, mode, location, variance, sharedState, &log};
u.anyIsTop = anyIsTop;
return u;
}
Unifier u = Unifier{types, mode, log.sharedSeen, location, variance, sharedState, &log};
Unifier u = Unifier{types, mode, location, variance, sharedState, &log};
u.anyIsTop = anyIsTop;
return u;
}

View file

@ -1,7 +1,7 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Common.h"
#include "Luau/Common.h"
#include <vector>

View file

@ -10,7 +10,9 @@
// See docs/SyntaxChanges.md for an explanation.
LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000)
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauParseLocationIgnoreCommentSkipInCapture, false)
LUAU_FASTFLAGVARIABLE(LuauParserFunctionKeywordAsTypeHelp, false)
LUAU_FASTFLAGVARIABLE(LuauReturnTypeTokenConfusion, false)
namespace Luau
{
@ -1117,8 +1119,12 @@ AstTypePack* Parser::parseTypeList(TempVector<AstType*>& result, TempVector<std:
std::optional<AstTypeList> Parser::parseOptionalReturnTypeAnnotation()
{
if (options.allowTypeAnnotations && lexer.current().type == ':')
if (options.allowTypeAnnotations &&
(lexer.current().type == ':' || (FFlag::LuauReturnTypeTokenConfusion && lexer.current().type == Lexeme::SkinnyArrow)))
{
if (FFlag::LuauReturnTypeTokenConfusion && lexer.current().type == Lexeme::SkinnyArrow)
report(lexer.current().location, "Function return type annotations are written after ':' instead of '->'");
nextLexeme();
unsigned int oldRecursionCount = recursionCounter;
@ -1349,8 +1355,12 @@ AstTypeOrPack Parser::parseFunctionTypeAnnotation(bool allowPack)
AstArray<AstType*> paramTypes = copy(params);
bool returnTypeIntroducer =
FFlag::LuauReturnTypeTokenConfusion ? lexer.current().type == Lexeme::SkinnyArrow || lexer.current().type == ':' : false;
// Not a function at all. Just a parenthesized type. Or maybe a type pack with a single element
if (params.size() == 1 && !varargAnnotation && monomorphic && lexer.current().type != Lexeme::SkinnyArrow)
if (params.size() == 1 && !varargAnnotation && monomorphic &&
(FFlag::LuauReturnTypeTokenConfusion ? !returnTypeIntroducer : lexer.current().type != Lexeme::SkinnyArrow))
{
if (allowPack)
return {{}, allocator.alloc<AstTypePackExplicit>(begin.location, AstTypeList{paramTypes, nullptr})};
@ -1358,7 +1368,7 @@ AstTypeOrPack Parser::parseFunctionTypeAnnotation(bool allowPack)
return {params[0], {}};
}
if (lexer.current().type != Lexeme::SkinnyArrow && monomorphic && allowPack)
if ((FFlag::LuauReturnTypeTokenConfusion ? !returnTypeIntroducer : lexer.current().type != Lexeme::SkinnyArrow) && monomorphic && allowPack)
return {{}, allocator.alloc<AstTypePackExplicit>(begin.location, AstTypeList{paramTypes, varargAnnotation})};
AstArray<std::optional<AstArgumentName>> paramNames = copy(names);
@ -1372,8 +1382,13 @@ AstType* Parser::parseFunctionTypeAnnotationTail(const Lexeme& begin, AstArray<A
{
incrementRecursionCounter("type annotation");
if (FFlag::LuauReturnTypeTokenConfusion && lexer.current().type == ':')
{
report(lexer.current().location, "Return types in function type annotations are written after '->' instead of ':'");
lexer.next();
}
// Users occasionally write '()' as the 'unit' type when they actually want to use 'nil', here we'll try to give a more specific error
if (lexer.current().type != Lexeme::SkinnyArrow && generics.size == 0 && genericPacks.size == 0 && params.size == 0)
else if (lexer.current().type != Lexeme::SkinnyArrow && generics.size == 0 && genericPacks.size == 0 && params.size == 0)
{
report(Location(begin.location, lexer.previousLocation()), "Expected '->' after '()' when parsing function type; did you mean 'nil'?");
@ -1590,6 +1605,17 @@ AstTypeOrPack Parser::parseSimpleTypeAnnotation(bool allowPack)
{
return parseFunctionTypeAnnotation(allowPack);
}
else if (FFlag::LuauParserFunctionKeywordAsTypeHelp && lexer.current().type == Lexeme::ReservedFunction)
{
Location location = lexer.current().location;
nextLexeme();
return {reportTypeAnnotationError(location, {}, /*isMissing*/ false,
"Using 'function' as a type annotation is not supported, consider replacing with a function type annotation e.g. '(...any) -> "
"...any'"),
{}};
}
else
{
Location location = lexer.current().location;
@ -2821,7 +2847,7 @@ void Parser::nextLexeme()
hotcomments.push_back({hotcommentHeader, lexeme.location, std::string(text + 1, text + end)});
}
type = lexer.next(/* skipComments= */ false, !FFlag::LuauParseLocationIgnoreCommentSkipInCapture).type;
type = lexer.next(/* skipComments= */ false, /* updatePrevLocation= */ false).type;
}
}
else

View file

@ -19,9 +19,11 @@ if(LUAU_STATIC_CRT)
endif()
project(Luau LANGUAGES CXX C)
add_library(Luau.Common INTERFACE)
add_library(Luau.Ast STATIC)
add_library(Luau.Compiler STATIC)
add_library(Luau.Analysis STATIC)
add_library(Luau.CodeGen STATIC)
add_library(Luau.VM STATIC)
add_library(isocline STATIC)
@ -48,8 +50,11 @@ endif()
include(Sources.cmake)
target_include_directories(Luau.Common INTERFACE Common/include)
target_compile_features(Luau.Ast PUBLIC cxx_std_17)
target_include_directories(Luau.Ast PUBLIC Ast/include)
target_link_libraries(Luau.Ast PUBLIC Luau.Common)
target_compile_features(Luau.Compiler PUBLIC cxx_std_17)
target_include_directories(Luau.Compiler PUBLIC Compiler/include)
@ -59,8 +64,13 @@ target_compile_features(Luau.Analysis PUBLIC cxx_std_17)
target_include_directories(Luau.Analysis PUBLIC Analysis/include)
target_link_libraries(Luau.Analysis PUBLIC Luau.Ast)
target_compile_features(Luau.CodeGen PRIVATE cxx_std_17)
target_include_directories(Luau.CodeGen PUBLIC CodeGen/include)
target_link_libraries(Luau.CodeGen PUBLIC Luau.Common)
target_compile_features(Luau.VM PRIVATE cxx_std_11)
target_include_directories(Luau.VM PUBLIC VM/include)
target_link_libraries(Luau.VM PUBLIC Luau.Common)
target_include_directories(isocline PUBLIC extern/isocline/include)
@ -101,6 +111,7 @@ endif()
target_compile_options(Luau.Ast PRIVATE ${LUAU_OPTIONS})
target_compile_options(Luau.Analysis PRIVATE ${LUAU_OPTIONS})
target_compile_options(Luau.CodeGen PRIVATE ${LUAU_OPTIONS})
target_compile_options(Luau.VM PRIVATE ${LUAU_OPTIONS})
target_compile_options(isocline PRIVATE ${LUAU_OPTIONS} ${ISOCLINE_OPTIONS})
@ -110,10 +121,17 @@ if (MSVC AND MSVC_VERSION GREATER_EQUAL 1924)
set_source_files_properties(VM/src/lvmexecute.cpp PROPERTIES COMPILE_FLAGS /d2ssa-pre-)
endif()
if(MSVC AND LUAU_BUILD_CLI)
# the default stack size that MSVC linker uses is 1 MB; we need more stack space in Debug because stack frames are larger
set_target_properties(Luau.Analyze.CLI PROPERTIES LINK_FLAGS_DEBUG /STACK:2097152)
set_target_properties(Luau.Repl.CLI PROPERTIES LINK_FLAGS_DEBUG /STACK:2097152)
endif()
# embed .natvis inside the library debug information
if(MSVC)
target_link_options(Luau.Ast INTERFACE /NATVIS:${CMAKE_CURRENT_SOURCE_DIR}/tools/natvis/Ast.natvis)
target_link_options(Luau.Analysis INTERFACE /NATVIS:${CMAKE_CURRENT_SOURCE_DIR}/tools/natvis/Analysis.natvis)
target_link_options(Luau.CodeGen INTERFACE /NATVIS:${CMAKE_CURRENT_SOURCE_DIR}/tools/natvis/CodeGen.natvis)
target_link_options(Luau.VM INTERFACE /NATVIS:${CMAKE_CURRENT_SOURCE_DIR}/tools/natvis/VM.natvis)
endif()
@ -121,6 +139,7 @@ endif()
if(MSVC_IDE)
target_sources(Luau.Ast PRIVATE tools/natvis/Ast.natvis)
target_sources(Luau.Analysis PRIVATE tools/natvis/Analysis.natvis)
target_sources(Luau.CodeGen PRIVATE tools/natvis/CodeGen.natvis)
target_sources(Luau.VM PRIVATE tools/natvis/VM.natvis)
endif()
@ -148,7 +167,7 @@ endif()
if(LUAU_BUILD_TESTS)
target_compile_options(Luau.UnitTest PRIVATE ${LUAU_OPTIONS})
target_include_directories(Luau.UnitTest PRIVATE extern)
target_link_libraries(Luau.UnitTest PRIVATE Luau.Analysis Luau.Compiler)
target_link_libraries(Luau.UnitTest PRIVATE Luau.Analysis Luau.Compiler Luau.CodeGen)
target_compile_options(Luau.Conformance PRIVATE ${LUAU_OPTIONS})
target_include_directories(Luau.Conformance PRIVATE extern)

View file

@ -0,0 +1,169 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Common.h"
#include "Luau/Condition.h"
#include "Luau/Label.h"
#include "Luau/OperandX64.h"
#include "Luau/RegisterX64.h"
#include <string>
#include <vector>
namespace Luau
{
namespace CodeGen
{
class AssemblyBuilderX64
{
public:
explicit AssemblyBuilderX64(bool logText);
~AssemblyBuilderX64();
// Base two operand instructions with 9 opcode selection
void add(OperandX64 lhs, OperandX64 rhs);
void sub(OperandX64 lhs, OperandX64 rhs);
void cmp(OperandX64 lhs, OperandX64 rhs);
void and_(OperandX64 lhs, OperandX64 rhs);
void or_(OperandX64 lhs, OperandX64 rhs);
void xor_(OperandX64 lhs, OperandX64 rhs);
// Binary shift instructions with special rhs handling
void sal(OperandX64 lhs, OperandX64 rhs);
void sar(OperandX64 lhs, OperandX64 rhs);
void shl(OperandX64 lhs, OperandX64 rhs);
void shr(OperandX64 lhs, OperandX64 rhs);
// Two operand mov instruction has additional specialized encodings
void mov(OperandX64 lhs, OperandX64 rhs);
void mov64(RegisterX64 lhs, int64_t imm);
// Base one operand instruction with 2 opcode selection
void div(OperandX64 op);
void idiv(OperandX64 op);
void mul(OperandX64 op);
void neg(OperandX64 op);
void not_(OperandX64 op);
void test(OperandX64 lhs, OperandX64 rhs);
void lea(OperandX64 lhs, OperandX64 rhs);
void push(OperandX64 op);
void pop(OperandX64 op);
void ret();
// Control flow
void jcc(Condition cond, Label& label);
void jmp(Label& label);
void jmp(OperandX64 op);
// AVX
void vaddpd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vaddps(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vaddsd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vaddss(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vsqrtpd(OperandX64 dst, OperandX64 src);
void vsqrtps(OperandX64 dst, OperandX64 src);
void vsqrtsd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vsqrtss(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vmovsd(OperandX64 dst, OperandX64 src);
void vmovsd(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vmovss(OperandX64 dst, OperandX64 src);
void vmovss(OperandX64 dst, OperandX64 src1, OperandX64 src2);
void vmovapd(OperandX64 dst, OperandX64 src);
void vmovaps(OperandX64 dst, OperandX64 src);
void vmovupd(OperandX64 dst, OperandX64 src);
void vmovups(OperandX64 dst, OperandX64 src);
// Run final checks
void finalize();
// Places a label at current location and returns it
Label setLabel();
// Assigns label position to the current location
void setLabel(Label& label);
// Constant allocation (uses rip-relative addressing)
OperandX64 i64(int64_t value);
OperandX64 f32(float value);
OperandX64 f64(double value);
OperandX64 f32x4(float x, float y, float z, float w);
// Resulting data and code that need to be copied over one after the other
// The *end* of 'data' has to be aligned to 16 bytes, this will also align 'code'
std::vector<uint8_t> data;
std::vector<uint8_t> code;
std::string text;
private:
// Instruction archetypes
void placeBinary(const char* name, OperandX64 lhs, OperandX64 rhs, uint8_t codeimm8, uint8_t codeimm, uint8_t codeimmImm8, uint8_t code8rev,
uint8_t coderev, uint8_t code8, uint8_t code, uint8_t opreg);
void placeBinaryRegMemAndImm(OperandX64 lhs, OperandX64 rhs, uint8_t code8, uint8_t code, uint8_t codeImm8, uint8_t opreg);
void placeBinaryRegAndRegMem(OperandX64 lhs, OperandX64 rhs, uint8_t code8, uint8_t code);
void placeBinaryRegMemAndReg(OperandX64 lhs, OperandX64 rhs, uint8_t code8, uint8_t code);
void placeUnaryModRegMem(const char* name, OperandX64 op, uint8_t code8, uint8_t code, uint8_t opreg);
void placeShift(const char* name, OperandX64 lhs, OperandX64 rhs, uint8_t opreg);
void placeJcc(const char* name, Label& label, uint8_t cc);
void placeAvx(const char* name, OperandX64 dst, OperandX64 src, uint8_t code, bool setW, uint8_t mode, uint8_t prefix);
void placeAvx(const char* name, OperandX64 dst, OperandX64 src, uint8_t code, uint8_t coderev, bool setW, uint8_t mode, uint8_t prefix);
void placeAvx(const char* name, OperandX64 dst, OperandX64 src1, OperandX64 src2, uint8_t code, bool setW, uint8_t mode, uint8_t prefix);
// Instruction components
void placeRegAndModRegMem(OperandX64 lhs, OperandX64 rhs);
void placeModRegMem(OperandX64 rhs, uint8_t regop);
void placeRex(RegisterX64 op);
void placeRex(OperandX64 op);
void placeRex(RegisterX64 lhs, OperandX64 rhs);
void placeVex(OperandX64 dst, OperandX64 src1, OperandX64 src2, bool setW, uint8_t mode, uint8_t prefix);
void placeImm8Or32(int32_t imm);
void placeImm8(int32_t imm);
void placeImm32(int32_t imm);
void placeImm64(int64_t imm);
void placeLabel(Label& label);
void place(uint8_t byte);
void commit();
LUAU_NOINLINE void extend();
uint32_t getCodeSize();
// Data
size_t allocateData(size_t size, size_t align);
// Logging of assembly in text form (Intel asm with VS disassembly formatting)
LUAU_NOINLINE void log(const char* opcode);
LUAU_NOINLINE void log(const char* opcode, OperandX64 op);
LUAU_NOINLINE void log(const char* opcode, OperandX64 op1, OperandX64 op2);
LUAU_NOINLINE void log(const char* opcode, OperandX64 op1, OperandX64 op2, OperandX64 op3);
LUAU_NOINLINE void log(Label label);
LUAU_NOINLINE void log(const char* opcode, Label label);
void log(OperandX64 op);
void logAppend(const char* fmt, ...);
const char* getSizeName(SizeX64 size);
const char* getRegisterName(RegisterX64 reg);
uint32_t nextLabel = 1;
std::vector<Label> pendingLabels;
std::vector<uint32_t> labelLocations;
bool logText = false;
bool finalized = false;
size_t dataPos = 0;
uint8_t* codePos = nullptr;
uint8_t* codeEnd = nullptr;
};
} // namespace CodeGen
} // namespace Luau

View file

@ -0,0 +1,46 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
namespace Luau
{
namespace CodeGen
{
enum class Condition
{
Overflow,
NoOverflow,
Carry,
NoCarry,
Below,
BelowEqual,
Above,
AboveEqual,
Equal,
Less,
LessEqual,
Greater,
GreaterEqual,
NotBelow,
NotBelowEqual,
NotAbove,
NotAboveEqual,
NotEqual,
NotLess,
NotLessEqual,
NotGreater,
NotGreaterEqual,
Zero,
NotZero,
// TODO: ordered and unordered floating-point conditions
Count
};
} // namespace CodeGen
} // namespace Luau

View file

@ -0,0 +1,18 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include <stdint.h>
namespace Luau
{
namespace CodeGen
{
struct Label
{
uint32_t id = 0;
uint32_t location = ~0u;
};
} // namespace CodeGen
} // namespace Luau

View file

@ -0,0 +1,136 @@
#pragma once
#include "Luau/Common.h"
#include "Luau/RegisterX64.h"
#include <stdint.h>
namespace Luau
{
namespace CodeGen
{
enum class CategoryX64 : uint8_t
{
reg,
mem,
imm,
};
struct OperandX64
{
constexpr OperandX64(RegisterX64 reg)
: cat(CategoryX64::reg)
, index(noreg)
, base(reg)
, memSize(SizeX64::none)
, scale(1)
, imm(0)
{
}
constexpr OperandX64(int32_t imm)
: cat(CategoryX64::imm)
, index(noreg)
, base(noreg)
, memSize(SizeX64::none)
, scale(1)
, imm(imm)
{
}
constexpr explicit OperandX64(SizeX64 size, RegisterX64 index, uint8_t scale, RegisterX64 base, int32_t disp)
: cat(CategoryX64::mem)
, index(index)
, base(base)
, memSize(size)
, scale(scale)
, imm(disp)
{
}
// Fields are carefully placed to make this struct fit into an 8 byte register
CategoryX64 cat;
RegisterX64 index;
RegisterX64 base;
SizeX64 memSize : 4;
uint8_t scale : 4;
int32_t imm;
constexpr OperandX64 operator[](OperandX64&& addr) const
{
LUAU_ASSERT(cat == CategoryX64::mem);
LUAU_ASSERT(memSize != SizeX64::none && index == noreg && scale == 1 && base == noreg && imm == 0);
LUAU_ASSERT(addr.memSize == SizeX64::none);
addr.cat = CategoryX64::mem;
addr.memSize = memSize;
return addr;
}
};
constexpr OperandX64 byte{SizeX64::byte, noreg, 1, noreg, 0};
constexpr OperandX64 word{SizeX64::word, noreg, 1, noreg, 0};
constexpr OperandX64 dword{SizeX64::dword, noreg, 1, noreg, 0};
constexpr OperandX64 qword{SizeX64::qword, noreg, 1, noreg, 0};
constexpr OperandX64 xmmword{SizeX64::xmmword, noreg, 1, noreg, 0};
constexpr OperandX64 ymmword{SizeX64::ymmword, noreg, 1, noreg, 0};
constexpr OperandX64 ptr{sizeof(void*) == 4 ? SizeX64::dword : SizeX64::qword, noreg, 1, noreg, 0};
constexpr OperandX64 operator*(RegisterX64 reg, uint8_t scale)
{
if (scale == 1)
return OperandX64(reg);
LUAU_ASSERT(scale == 1 || scale == 2 || scale == 4 || scale == 8);
LUAU_ASSERT(reg.index != 0b100 && "can't scale SP");
return OperandX64(SizeX64::none, reg, scale, noreg, 0);
}
constexpr OperandX64 operator+(RegisterX64 reg, int32_t disp)
{
return OperandX64(SizeX64::none, noreg, 1, reg, disp);
}
constexpr OperandX64 operator+(RegisterX64 base, RegisterX64 index)
{
LUAU_ASSERT(index.index != 4 && "sp cannot be used as index");
LUAU_ASSERT(base.size == index.size);
return OperandX64(SizeX64::none, index, 1, base, 0);
}
constexpr OperandX64 operator+(OperandX64 op, int32_t disp)
{
LUAU_ASSERT(op.cat == CategoryX64::mem);
LUAU_ASSERT(op.memSize == SizeX64::none);
op.imm += disp;
return op;
}
constexpr OperandX64 operator+(OperandX64 op, RegisterX64 base)
{
LUAU_ASSERT(op.cat == CategoryX64::mem);
LUAU_ASSERT(op.memSize == SizeX64::none);
LUAU_ASSERT(op.base == noreg);
LUAU_ASSERT(op.index == noreg || op.index.size == base.size);
op.base = base;
return op;
}
constexpr OperandX64 operator+(RegisterX64 base, OperandX64 op)
{
LUAU_ASSERT(op.cat == CategoryX64::mem);
LUAU_ASSERT(op.memSize == SizeX64::none);
LUAU_ASSERT(op.base == noreg);
LUAU_ASSERT(op.index == noreg || op.index.size == base.size);
op.base = base;
return op;
}
} // namespace CodeGen
} // namespace Luau

View file

@ -0,0 +1,116 @@
#pragma once
#include "Luau/Common.h"
#include <stdint.h>
namespace Luau
{
namespace CodeGen
{
enum class SizeX64 : uint8_t
{
none,
byte,
word,
dword,
qword,
xmmword,
ymmword,
};
struct RegisterX64
{
SizeX64 size : 3;
uint8_t index : 5;
constexpr bool operator==(RegisterX64 rhs) const
{
return size == rhs.size && index == rhs.index;
}
constexpr bool operator!=(RegisterX64 rhs) const
{
return !(*this == rhs);
}
};
constexpr RegisterX64 noreg{SizeX64::none, 16};
constexpr RegisterX64 rip{SizeX64::none, 0};
constexpr RegisterX64 al{SizeX64::byte, 0};
constexpr RegisterX64 cl{SizeX64::byte, 1};
constexpr RegisterX64 dl{SizeX64::byte, 2};
constexpr RegisterX64 bl{SizeX64::byte, 3};
constexpr RegisterX64 eax{SizeX64::dword, 0};
constexpr RegisterX64 ecx{SizeX64::dword, 1};
constexpr RegisterX64 edx{SizeX64::dword, 2};
constexpr RegisterX64 ebx{SizeX64::dword, 3};
constexpr RegisterX64 esp{SizeX64::dword, 4};
constexpr RegisterX64 ebp{SizeX64::dword, 5};
constexpr RegisterX64 esi{SizeX64::dword, 6};
constexpr RegisterX64 edi{SizeX64::dword, 7};
constexpr RegisterX64 r8d{SizeX64::dword, 8};
constexpr RegisterX64 r9d{SizeX64::dword, 9};
constexpr RegisterX64 r10d{SizeX64::dword, 10};
constexpr RegisterX64 r11d{SizeX64::dword, 11};
constexpr RegisterX64 r12d{SizeX64::dword, 12};
constexpr RegisterX64 r13d{SizeX64::dword, 13};
constexpr RegisterX64 r14d{SizeX64::dword, 14};
constexpr RegisterX64 r15d{SizeX64::dword, 15};
constexpr RegisterX64 rax{SizeX64::qword, 0};
constexpr RegisterX64 rcx{SizeX64::qword, 1};
constexpr RegisterX64 rdx{SizeX64::qword, 2};
constexpr RegisterX64 rbx{SizeX64::qword, 3};
constexpr RegisterX64 rsp{SizeX64::qword, 4};
constexpr RegisterX64 rbp{SizeX64::qword, 5};
constexpr RegisterX64 rsi{SizeX64::qword, 6};
constexpr RegisterX64 rdi{SizeX64::qword, 7};
constexpr RegisterX64 r8{SizeX64::qword, 8};
constexpr RegisterX64 r9{SizeX64::qword, 9};
constexpr RegisterX64 r10{SizeX64::qword, 10};
constexpr RegisterX64 r11{SizeX64::qword, 11};
constexpr RegisterX64 r12{SizeX64::qword, 12};
constexpr RegisterX64 r13{SizeX64::qword, 13};
constexpr RegisterX64 r14{SizeX64::qword, 14};
constexpr RegisterX64 r15{SizeX64::qword, 15};
constexpr RegisterX64 xmm0{SizeX64::xmmword, 0};
constexpr RegisterX64 xmm1{SizeX64::xmmword, 1};
constexpr RegisterX64 xmm2{SizeX64::xmmword, 2};
constexpr RegisterX64 xmm3{SizeX64::xmmword, 3};
constexpr RegisterX64 xmm4{SizeX64::xmmword, 4};
constexpr RegisterX64 xmm5{SizeX64::xmmword, 5};
constexpr RegisterX64 xmm6{SizeX64::xmmword, 6};
constexpr RegisterX64 xmm7{SizeX64::xmmword, 7};
constexpr RegisterX64 xmm8{SizeX64::xmmword, 8};
constexpr RegisterX64 xmm9{SizeX64::xmmword, 9};
constexpr RegisterX64 xmm10{SizeX64::xmmword, 10};
constexpr RegisterX64 xmm11{SizeX64::xmmword, 11};
constexpr RegisterX64 xmm12{SizeX64::xmmword, 12};
constexpr RegisterX64 xmm13{SizeX64::xmmword, 13};
constexpr RegisterX64 xmm14{SizeX64::xmmword, 14};
constexpr RegisterX64 xmm15{SizeX64::xmmword, 15};
constexpr RegisterX64 ymm0{SizeX64::ymmword, 0};
constexpr RegisterX64 ymm1{SizeX64::ymmword, 1};
constexpr RegisterX64 ymm2{SizeX64::ymmword, 2};
constexpr RegisterX64 ymm3{SizeX64::ymmword, 3};
constexpr RegisterX64 ymm4{SizeX64::ymmword, 4};
constexpr RegisterX64 ymm5{SizeX64::ymmword, 5};
constexpr RegisterX64 ymm6{SizeX64::ymmword, 6};
constexpr RegisterX64 ymm7{SizeX64::ymmword, 7};
constexpr RegisterX64 ymm8{SizeX64::ymmword, 8};
constexpr RegisterX64 ymm9{SizeX64::ymmword, 9};
constexpr RegisterX64 ymm10{SizeX64::ymmword, 10};
constexpr RegisterX64 ymm11{SizeX64::ymmword, 11};
constexpr RegisterX64 ymm12{SizeX64::ymmword, 12};
constexpr RegisterX64 ymm13{SizeX64::ymmword, 13};
constexpr RegisterX64 ymm14{SizeX64::ymmword, 14};
constexpr RegisterX64 ymm15{SizeX64::ymmword, 15};
} // namespace CodeGen
} // namespace Luau

File diff suppressed because it is too large Load diff

View file

@ -224,6 +224,7 @@ private:
DenseHashMap<ConstantKey, int32_t, ConstantKeyHash> constantMap;
DenseHashMap<TableShape, int32_t, TableShapeHash> tableShapeMap;
DenseHashMap<uint32_t, int16_t> protoMap;
int debugLine = 0;
@ -246,7 +247,7 @@ private:
void validate() const;
std::string dumpCurrentFunction() const;
const uint32_t* dumpInstruction(const uint32_t* opcode, std::string& output) const;
void dumpInstruction(const uint32_t* opcode, std::string& output, int targetLabel) const;
void writeFunction(std::string& ss, uint32_t id) const;
void writeLineInfo(std::string& ss) const;

View file

@ -128,6 +128,20 @@ inline bool isSkipC(LuauOpcode op)
}
}
static int getJumpTarget(uint32_t insn, uint32_t pc)
{
LuauOpcode op = LuauOpcode(LUAU_INSN_OP(insn));
if (isJumpD(op))
return int(pc + LUAU_INSN_D(insn) + 1);
else if (isSkipC(op) && LUAU_INSN_C(insn))
return int(pc + LUAU_INSN_C(insn) + 1);
else if (op == LOP_JUMPX)
return int(pc + LUAU_INSN_E(insn) + 1);
else
return -1;
}
bool BytecodeBuilder::StringRef::operator==(const StringRef& other) const
{
return (data && other.data) ? (length == other.length && memcmp(data, other.data, length) == 0) : (data == other.data);
@ -181,6 +195,7 @@ size_t BytecodeBuilder::TableShapeHash::operator()(const TableShape& v) const
BytecodeBuilder::BytecodeBuilder(BytecodeEncoder* encoder)
: constantMap({Constant::Type_Nil, ~0ull})
, tableShapeMap(TableShape())
, protoMap(~0u)
, stringTable({nullptr, 0})
, encoder(encoder)
{
@ -250,6 +265,7 @@ void BytecodeBuilder::endFunction(uint8_t maxstacksize, uint8_t numupvalues)
constantMap.clear();
tableShapeMap.clear();
protoMap.clear();
debugRemarks.clear();
debugRemarkBuffer.clear();
@ -372,11 +388,15 @@ int32_t BytecodeBuilder::addConstantClosure(uint32_t fid)
int16_t BytecodeBuilder::addChildFunction(uint32_t fid)
{
if (int16_t* cache = protoMap.find(fid))
return *cache;
uint32_t id = uint32_t(protos.size());
if (id >= kMaxClosureCount)
return -1;
protoMap[fid] = int16_t(id);
protos.push_back(fid);
return int16_t(id);
@ -1398,7 +1418,7 @@ void BytecodeBuilder::validate() const
}
#endif
const uint32_t* BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result) const
void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result, int targetLabel) const
{
uint32_t insn = *code++;
@ -1493,39 +1513,39 @@ const uint32_t* BytecodeBuilder::dumpInstruction(const uint32_t* code, std::stri
break;
case LOP_JUMP:
formatAppend(result, "JUMP %+d\n", LUAU_INSN_D(insn));
formatAppend(result, "JUMP L%d\n", targetLabel);
break;
case LOP_JUMPIF:
formatAppend(result, "JUMPIF R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
formatAppend(result, "JUMPIF R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break;
case LOP_JUMPIFNOT:
formatAppend(result, "JUMPIFNOT R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
formatAppend(result, "JUMPIFNOT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break;
case LOP_JUMPIFEQ:
formatAppend(result, "JUMPIFEQ R%d R%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn));
formatAppend(result, "JUMPIFEQ R%d R%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
break;
case LOP_JUMPIFLE:
formatAppend(result, "JUMPIFLE R%d R%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn));
formatAppend(result, "JUMPIFLE R%d R%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
break;
case LOP_JUMPIFLT:
formatAppend(result, "JUMPIFLT R%d R%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn));
formatAppend(result, "JUMPIFLT R%d R%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
break;
case LOP_JUMPIFNOTEQ:
formatAppend(result, "JUMPIFNOTEQ R%d R%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn));
formatAppend(result, "JUMPIFNOTEQ R%d R%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
break;
case LOP_JUMPIFNOTLE:
formatAppend(result, "JUMPIFNOTLE R%d R%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn));
formatAppend(result, "JUMPIFNOTLE R%d R%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
break;
case LOP_JUMPIFNOTLT:
formatAppend(result, "JUMPIFNOTLT R%d R%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn));
formatAppend(result, "JUMPIFNOTLT R%d R%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
break;
case LOP_ADD:
@ -1621,35 +1641,35 @@ const uint32_t* BytecodeBuilder::dumpInstruction(const uint32_t* code, std::stri
break;
case LOP_FORNPREP:
formatAppend(result, "FORNPREP R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
formatAppend(result, "FORNPREP R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break;
case LOP_FORNLOOP:
formatAppend(result, "FORNLOOP R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
formatAppend(result, "FORNLOOP R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break;
case LOP_FORGPREP:
formatAppend(result, "FORGPREP R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
formatAppend(result, "FORGPREP R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break;
case LOP_FORGLOOP:
formatAppend(result, "FORGLOOP R%d %+d %d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn), *code++);
formatAppend(result, "FORGLOOP R%d L%d %d\n", LUAU_INSN_A(insn), targetLabel, *code++);
break;
case LOP_FORGPREP_INEXT:
formatAppend(result, "FORGPREP_INEXT R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
formatAppend(result, "FORGPREP_INEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break;
case LOP_FORGLOOP_INEXT:
formatAppend(result, "FORGLOOP_INEXT R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
formatAppend(result, "FORGLOOP_INEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break;
case LOP_FORGPREP_NEXT:
formatAppend(result, "FORGPREP_NEXT R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
formatAppend(result, "FORGPREP_NEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break;
case LOP_FORGLOOP_NEXT:
formatAppend(result, "FORGLOOP_NEXT R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_D(insn));
formatAppend(result, "FORGLOOP_NEXT R%d L%d\n", LUAU_INSN_A(insn), targetLabel);
break;
case LOP_GETVARARGS:
@ -1665,7 +1685,7 @@ const uint32_t* BytecodeBuilder::dumpInstruction(const uint32_t* code, std::stri
break;
case LOP_JUMPBACK:
formatAppend(result, "JUMPBACK %+d\n", LUAU_INSN_D(insn));
formatAppend(result, "JUMPBACK L%d\n", targetLabel);
break;
case LOP_LOADKX:
@ -1673,26 +1693,26 @@ const uint32_t* BytecodeBuilder::dumpInstruction(const uint32_t* code, std::stri
break;
case LOP_JUMPX:
formatAppend(result, "JUMPX %+d\n", LUAU_INSN_E(insn));
formatAppend(result, "JUMPX L%d\n", targetLabel);
break;
case LOP_FASTCALL:
formatAppend(result, "FASTCALL %d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_C(insn));
formatAppend(result, "FASTCALL %d L%d\n", LUAU_INSN_A(insn), targetLabel);
break;
case LOP_FASTCALL1:
formatAppend(result, "FASTCALL1 %d R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), LUAU_INSN_C(insn));
formatAppend(result, "FASTCALL1 %d R%d L%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), targetLabel);
break;
case LOP_FASTCALL2:
{
uint32_t aux = *code++;
formatAppend(result, "FASTCALL2 %d R%d R%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), aux, LUAU_INSN_C(insn));
formatAppend(result, "FASTCALL2 %d R%d R%d L%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), aux, targetLabel);
break;
}
case LOP_FASTCALL2K:
{
uint32_t aux = *code++;
formatAppend(result, "FASTCALL2K %d R%d K%d %+d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), aux, LUAU_INSN_C(insn));
formatAppend(result, "FASTCALL2K %d R%d K%d L%d\n", LUAU_INSN_A(insn), LUAU_INSN_B(insn), aux, targetLabel);
break;
}
@ -1702,23 +1722,24 @@ const uint32_t* BytecodeBuilder::dumpInstruction(const uint32_t* code, std::stri
case LOP_CAPTURE:
formatAppend(result, "CAPTURE %s %c%d\n",
LUAU_INSN_A(insn) == LCT_UPVAL ? "UPVAL" : LUAU_INSN_A(insn) == LCT_REF ? "REF" : LUAU_INSN_A(insn) == LCT_VAL ? "VAL" : "",
LUAU_INSN_A(insn) == LCT_UPVAL ? "UPVAL"
: LUAU_INSN_A(insn) == LCT_REF ? "REF"
: LUAU_INSN_A(insn) == LCT_VAL ? "VAL"
: "",
LUAU_INSN_A(insn) == LCT_UPVAL ? 'U' : 'R', LUAU_INSN_B(insn));
break;
case LOP_JUMPIFEQK:
formatAppend(result, "JUMPIFEQK R%d K%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn));
formatAppend(result, "JUMPIFEQK R%d K%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
break;
case LOP_JUMPIFNOTEQK:
formatAppend(result, "JUMPIFNOTEQK R%d K%d %+d\n", LUAU_INSN_A(insn), *code++, LUAU_INSN_D(insn));
formatAppend(result, "JUMPIFNOTEQK R%d K%d L%d\n", LUAU_INSN_A(insn), *code++, targetLabel);
break;
default:
LUAU_ASSERT(!"Unsupported opcode");
}
return code;
}
std::string BytecodeBuilder::dumpCurrentFunction() const
@ -1726,9 +1747,6 @@ std::string BytecodeBuilder::dumpCurrentFunction() const
if ((dumpFlags & Dump_Code) == 0)
return std::string();
const uint32_t* code = insns.data();
const uint32_t* codeEnd = insns.data() + insns.size();
int lastLine = -1;
size_t nextRemark = 0;
@ -1750,21 +1768,45 @@ std::string BytecodeBuilder::dumpCurrentFunction() const
}
}
while (code != codeEnd)
std::vector<int> labels(insns.size(), -1);
// annotate valid jump targets with 0
for (size_t i = 0; i < insns.size();)
{
int target = getJumpTarget(insns[i], uint32_t(i));
if (target >= 0)
{
LUAU_ASSERT(size_t(target) < insns.size());
labels[target] = 0;
}
i += getOpLength(LuauOpcode(LUAU_INSN_OP(insns[i])));
LUAU_ASSERT(i <= insns.size());
}
int nextLabel = 0;
// compute label ids (sequential integers for all jump targets)
for (size_t i = 0; i < labels.size(); ++i)
if (labels[i] == 0)
labels[i] = nextLabel++;
for (size_t i = 0; i < insns.size();)
{
const uint32_t* code = &insns[i];
uint8_t op = LUAU_INSN_OP(*code);
uint32_t pc = uint32_t(code - insns.data());
if (op == LOP_PREPVARARGS)
{
// Don't emit function header in bytecode - it's used for call dispatching and doesn't contain "interesting" information
code++;
i++;
continue;
}
if (dumpFlags & Dump_Remarks)
{
while (nextRemark < debugRemarks.size() && debugRemarks[nextRemark].first == pc)
while (nextRemark < debugRemarks.size() && debugRemarks[nextRemark].first == i)
{
formatAppend(result, "REMARK %s\n", debugRemarkBuffer.c_str() + debugRemarks[nextRemark].second);
nextRemark++;
@ -1773,7 +1815,7 @@ std::string BytecodeBuilder::dumpCurrentFunction() const
if (dumpFlags & Dump_Source)
{
int line = lines[pc];
int line = lines[i];
if (line > 0 && line != lastLine)
{
@ -1784,11 +1826,17 @@ std::string BytecodeBuilder::dumpCurrentFunction() const
}
if (dumpFlags & Dump_Lines)
{
formatAppend(result, "%d: ", lines[pc]);
}
formatAppend(result, "%d: ", lines[i]);
code = dumpInstruction(code, result);
if (labels[i] != -1)
formatAppend(result, "L%d: ", labels[i]);
int target = getJumpTarget(*code, uint32_t(i));
dumpInstruction(code, result, target >= 0 ? labels[target] : -1);
i += getOpLength(LuauOpcode(op));
LUAU_ASSERT(i <= insns.size());
}
return result;

View file

@ -15,12 +15,7 @@
#include <algorithm>
#include <bitset>
#include <math.h>
#include <limits.h>
LUAU_FASTFLAGVARIABLE(LuauCompileSupportInlining, false)
LUAU_FASTFLAGVARIABLE(LuauCompileIter, false)
LUAU_FASTFLAGVARIABLE(LuauCompileIterNoReserve, false)
LUAU_FASTFLAGVARIABLE(LuauCompileIterNoPairs, false)
LUAU_FASTINTVARIABLE(LuauCompileLoopUnrollThreshold, 25)
@ -100,13 +95,11 @@ struct Compiler
upvals.reserve(16);
}
uint8_t getLocal(AstLocal* local)
int getLocalReg(AstLocal* local)
{
Local* l = locals.find(local);
LUAU_ASSERT(l);
LUAU_ASSERT(l->allocated);
return l->reg;
return l && l->allocated ? l->reg : -1;
}
uint8_t getUpval(AstLocal* local)
@ -159,50 +152,23 @@ struct Compiler
AstExprFunction* getFunctionExpr(AstExpr* node)
{
if (AstExprLocal* le = node->as<AstExprLocal>())
if (AstExprLocal* expr = node->as<AstExprLocal>())
{
Variable* lv = variables.find(le->local);
Variable* lv = variables.find(expr->local);
if (!lv || lv->written || !lv->init)
return nullptr;
return getFunctionExpr(lv->init);
}
else if (AstExprGroup* ge = node->as<AstExprGroup>())
return getFunctionExpr(ge->expr);
else if (AstExprGroup* expr = node->as<AstExprGroup>())
return getFunctionExpr(expr->expr);
else if (AstExprTypeAssertion* expr = node->as<AstExprTypeAssertion>())
return getFunctionExpr(expr->expr);
else
return node->as<AstExprFunction>();
}
bool canInlineFunctionBody(AstStat* stat)
{
struct CanInlineVisitor : AstVisitor
{
bool result = true;
bool visit(AstExpr* node) override
{
// nested functions may capture function arguments, and our upval handling doesn't handle elided variables (constant)
// TODO: we could remove this case if we changed function compilation to create temporary locals for constant upvalues
// TODO: additionally we would need to change upvalue handling in compileExprFunction to handle upvalue->local migration
result = result && !node->is<AstExprFunction>();
return result;
}
bool visit(AstStat* node) override
{
// loops may need to be unrolled which can result in cost amplification
result = result && !node->is<AstStatFor>();
return result;
}
};
CanInlineVisitor canInline;
stat->visit(&canInline);
return canInline.result;
}
uint32_t compileFunction(AstExprFunction* func)
{
LUAU_TIMETRACE_SCOPE("Compiler::compileFunction", "Compiler");
@ -275,8 +241,7 @@ struct Compiler
f.upvals = upvals;
// record information for inlining
if (FFlag::LuauCompileSupportInlining && options.optimizationLevel >= 2 && !func->vararg && canInlineFunctionBody(func->body) &&
!getfenvUsed && !setfenvUsed)
if (options.optimizationLevel >= 2 && !func->vararg && !getfenvUsed && !setfenvUsed)
{
f.canInline = true;
f.stackSize = stackSize;
@ -346,8 +311,8 @@ struct Compiler
uint8_t argreg;
if (isExprLocalReg(arg))
argreg = getLocal(arg->as<AstExprLocal>()->local);
if (int reg = getExprLocalReg(arg); reg >= 0)
argreg = uint8_t(reg);
else
{
argreg = uint8_t(regs + 1);
@ -403,8 +368,8 @@ struct Compiler
}
}
if (isExprLocalReg(expr->args.data[i]))
args[i] = getLocal(expr->args.data[i]->as<AstExprLocal>()->local);
if (int reg = getExprLocalReg(expr->args.data[i]); reg >= 0)
args[i] = uint8_t(reg);
else
{
args[i] = uint8_t(regs + 1 + i);
@ -489,19 +454,19 @@ struct Compiler
return false;
}
// TODO: we can compile functions with mismatching arity at call site but it's more annoying
if (func->args.size != expr->args.size)
{
bytecode.addDebugRemark("inlining failed: argument count mismatch (expected %d, got %d)", int(func->args.size), int(expr->args.size));
return false;
}
// we use a dynamic cost threshold that's based on the fixed limit boosted by the cost advantage we gain due to inlining
// compute constant bitvector for all arguments to feed the cost model
bool varc[8] = {};
for (size_t i = 0; i < expr->args.size && i < 8; ++i)
for (size_t i = 0; i < func->args.size && i < expr->args.size && i < 8; ++i)
varc[i] = isConstant(expr->args.data[i]);
int inlinedCost = computeCost(fi->costModel, varc, std::min(int(expr->args.size), 8));
// if the last argument only returns a single value, all following arguments are nil
if (expr->args.size != 0 &&
!(expr->args.data[expr->args.size - 1]->is<AstExprCall>() || expr->args.data[expr->args.size - 1]->is<AstExprVarargs>()))
for (size_t i = expr->args.size; i < func->args.size && i < 8; ++i)
varc[i] = true;
// we use a dynamic cost threshold that's based on the fixed limit boosted by the cost advantage we gain due to inlining
int inlinedCost = computeCost(fi->costModel, varc, std::min(int(func->args.size), 8));
int baselineCost = computeCost(fi->costModel, nullptr, 0) + 3;
int inlineProfit = (inlinedCost == 0) ? thresholdMaxBoost : std::min(thresholdMaxBoost, 100 * baselineCost / inlinedCost);
@ -533,15 +498,44 @@ struct Compiler
for (size_t i = 0; i < func->args.size; ++i)
{
AstLocal* var = func->args.data[i];
AstExpr* arg = expr->args.data[i];
AstExpr* arg = i < expr->args.size ? expr->args.data[i] : nullptr;
if (Variable* vv = variables.find(var); vv && vv->written)
if (i + 1 == expr->args.size && func->args.size > expr->args.size && (arg->is<AstExprCall>() || arg->is<AstExprVarargs>()))
{
// if the last argument can return multiple values, we need to compute all of them into the remaining arguments
unsigned int tail = unsigned(func->args.size - expr->args.size) + 1;
uint8_t reg = allocReg(arg, tail);
if (AstExprCall* expr = arg->as<AstExprCall>())
compileExprCall(expr, reg, tail, /* targetTop= */ true);
else if (AstExprVarargs* expr = arg->as<AstExprVarargs>())
compileExprVarargs(expr, reg, tail);
else
LUAU_ASSERT(!"Unexpected expression type");
for (size_t j = i; j < func->args.size; ++j)
pushLocal(func->args.data[j], uint8_t(reg + (j - i)));
// all remaining function arguments have been allocated and assigned to
break;
}
else if (Variable* vv = variables.find(var); vv && vv->written)
{
// if the argument is mutated, we need to allocate a fresh register even if it's a constant
uint8_t reg = allocReg(arg, 1);
compileExprTemp(arg, reg);
if (arg)
compileExprTemp(arg, reg);
else
bytecode.emitABC(LOP_LOADNIL, reg, 0, 0);
pushLocal(var, reg);
}
else if (arg == nullptr)
{
// since the argument is not mutated, we can simply fold the value into the expressions that need it
locstants[var] = {Constant::Type_Nil};
}
else if (const Constant* cv = constants.find(arg); cv && cv->type != Constant::Type_Unknown)
{
// since the argument is not mutated, we can simply fold the value into the expressions that need it
@ -553,20 +547,26 @@ struct Compiler
Variable* lv = le ? variables.find(le->local) : nullptr;
// if the argument is a local that isn't mutated, we will simply reuse the existing register
if (isExprLocalReg(arg) && (!lv || !lv->written))
if (int reg = le ? getExprLocalReg(le) : -1; reg >= 0 && (!lv || !lv->written))
{
uint8_t reg = getLocal(le->local);
pushLocal(var, reg);
pushLocal(var, uint8_t(reg));
}
else
{
uint8_t reg = allocReg(arg, 1);
compileExprTemp(arg, reg);
pushLocal(var, reg);
uint8_t temp = allocReg(arg, 1);
compileExprTemp(arg, temp);
pushLocal(var, temp);
}
}
}
// evaluate extra expressions for side effects
for (size_t i = func->args.size; i < expr->args.size; ++i)
{
RegScope rsi(this);
compileExprAuto(expr->args.data[i], rsi);
}
// fold constant values updated above into expressions in the function body
foldConstants(constants, variables, locstants, func->body);
@ -627,8 +627,16 @@ struct Compiler
FInt::LuauCompileInlineThresholdMaxBoost, FInt::LuauCompileInlineDepth))
return;
if (fi && !fi->canInline)
bytecode.addDebugRemark("inlining failed: complex constructs in function body");
// add a debug remark for cases when we didn't even call tryCompileInlinedCall
if (func && !(fi && fi->canInline))
{
if (func->vararg)
bytecode.addDebugRemark("inlining failed: function is variadic");
else if (!fi)
bytecode.addDebugRemark("inlining failed: can't inline recursive calls");
else if (getfenvUsed || setfenvUsed)
bytecode.addDebugRemark("inlining failed: module uses getfenv/setfenv");
}
}
RegScope rs(this);
@ -672,9 +680,9 @@ struct Compiler
LUAU_ASSERT(fi);
// Optimization: use local register directly in NAMECALL if possible
if (isExprLocalReg(fi->expr))
if (int reg = getExprLocalReg(fi->expr); reg >= 0)
{
selfreg = getLocal(fi->expr->as<AstExprLocal>()->local);
selfreg = uint8_t(reg);
}
else
{
@ -780,6 +788,8 @@ struct Compiler
void compileExprFunction(AstExprFunction* expr, uint8_t target)
{
RegScope rs(this);
const Function* f = functions.find(expr);
LUAU_ASSERT(f);
@ -790,48 +800,62 @@ struct Compiler
if (pid < 0)
CompileError::raise(expr->location, "Exceeded closure limit; simplify the code to compile");
bool shared = false;
// Optimization: when closure has no upvalues, or upvalues are safe to share, instead of allocating it every time we can share closure
// objects (this breaks assumptions about function identity which can lead to setfenv not working as expected, so we disable this when it
// is used)
if (options.optimizationLevel >= 1 && shouldShareClosure(expr) && !setfenvUsed)
{
int32_t cid = bytecode.addConstantClosure(f->id);
if (cid >= 0 && cid < 32768)
{
bytecode.emitAD(LOP_DUPCLOSURE, target, int16_t(cid));
shared = true;
}
}
if (!shared)
bytecode.emitAD(LOP_NEWCLOSURE, target, pid);
// we use a scratch vector to reduce allocations; this is safe since compileExprFunction is not reentrant
captures.clear();
captures.reserve(f->upvals.size());
for (AstLocal* uv : f->upvals)
{
LUAU_ASSERT(uv->functionDepth < expr->functionDepth);
Variable* ul = variables.find(uv);
bool immutable = !ul || !ul->written;
if (uv->functionDepth == expr->functionDepth - 1)
if (int reg = getLocalReg(uv); reg >= 0)
{
// get local variable
uint8_t reg = getLocal(uv);
// note: we can't check if uv is an upvalue in the current frame because inlining can migrate from upvalues to locals
Variable* ul = variables.find(uv);
bool immutable = !ul || !ul->written;
bytecode.emitABC(LOP_CAPTURE, uint8_t(immutable ? LCT_VAL : LCT_REF), reg, 0);
captures.push_back({immutable ? LCT_VAL : LCT_REF, uint8_t(reg)});
}
else if (const Constant* uc = locstants.find(uv); uc && uc->type != Constant::Type_Unknown)
{
// inlining can result in an upvalue capture of a constant, in which case we can't capture without a temporary register
uint8_t reg = allocReg(expr, 1);
compileExprConstant(expr, uc, reg);
captures.push_back({LCT_VAL, reg});
}
else
{
LUAU_ASSERT(uv->functionDepth < expr->functionDepth - 1);
// get upvalue from parent frame
// note: this will add uv to the current upvalue list if necessary
uint8_t uid = getUpval(uv);
bytecode.emitABC(LOP_CAPTURE, LCT_UPVAL, uid, 0);
captures.push_back({LCT_UPVAL, uid});
}
}
// Optimization: when closure has no upvalues, or upvalues are safe to share, instead of allocating it every time we can share closure
// objects (this breaks assumptions about function identity which can lead to setfenv not working as expected, so we disable this when it
// is used)
int16_t shared = -1;
if (options.optimizationLevel >= 1 && shouldShareClosure(expr) && !setfenvUsed)
{
int32_t cid = bytecode.addConstantClosure(f->id);
if (cid >= 0 && cid < 32768)
shared = int16_t(cid);
}
if (shared >= 0)
bytecode.emitAD(LOP_DUPCLOSURE, target, shared);
else
bytecode.emitAD(LOP_NEWCLOSURE, target, pid);
for (const Capture& c : captures)
bytecode.emitABC(LOP_CAPTURE, uint8_t(c.type), c.data, 0);
}
LuauOpcode getUnaryOp(AstExprUnary::Op op)
@ -926,6 +950,13 @@ struct Compiler
return cv && cv->type != Constant::Type_Unknown && !cv->isTruthful();
}
Constant getConstant(AstExpr* node)
{
const Constant* cv = constants.find(node);
return cv ? *cv : Constant{Constant::Type_Unknown};
}
size_t compileCompareJump(AstExprBinary* expr, bool not_ = false)
{
RegScope rs(this);
@ -1036,9 +1067,7 @@ struct Compiler
void compileConditionValue(AstExpr* node, const uint8_t* target, std::vector<size_t>& skipJump, bool onlyTruth)
{
// Optimization: we don't need to compute constant values
const Constant* cv = constants.find(node);
if (cv && cv->type != Constant::Type_Unknown)
if (const Constant* cv = constants.find(node); cv && cv->type != Constant::Type_Unknown)
{
// note that we only need to compute the value if it's truthy; otherwise we cal fall through
if (cv->isTruthful() == onlyTruth)
@ -1196,9 +1225,7 @@ struct Compiler
RegScope rs(this);
// Optimization: when left hand side is a constant, we can emit left hand side or right hand side
const Constant* cl = constants.find(expr->left);
if (cl && cl->type != Constant::Type_Unknown)
if (const Constant* cl = constants.find(expr->left); cl && cl->type != Constant::Type_Unknown)
{
compileExpr(and_ == cl->isTruthful() ? expr->right : expr->left, target, targetTemp);
return;
@ -1208,10 +1235,10 @@ struct Compiler
if (!isConditionFast(expr->left))
{
// Optimization: when right hand side is a local variable, we can use AND/OR
if (isExprLocalReg(expr->right))
if (int reg = getExprLocalReg(expr->right); reg >= 0)
{
uint8_t lr = compileExprAuto(expr->left, rs);
uint8_t rr = getLocal(expr->right->as<AstExprLocal>()->local);
uint8_t rr = uint8_t(reg);
bytecode.emitABC(and_ ? LOP_AND : LOP_OR, target, lr, rr);
return;
@ -1630,13 +1657,11 @@ struct Compiler
{
RegScope rs(this);
// note: cv may be invalidated by compileExpr* so we stop using it before calling compile recursively
const Constant* cv = constants.find(expr->index);
Constant cv = getConstant(expr->index);
if (cv && cv->type == Constant::Type_Number && cv->valueNumber >= 1 && cv->valueNumber <= 256 &&
double(int(cv->valueNumber)) == cv->valueNumber)
if (cv.type == Constant::Type_Number && cv.valueNumber >= 1 && cv.valueNumber <= 256 && double(int(cv.valueNumber)) == cv.valueNumber)
{
uint8_t i = uint8_t(int(cv->valueNumber) - 1);
uint8_t i = uint8_t(int(cv.valueNumber) - 1);
uint8_t rt = compileExprAuto(expr->expr, rs);
@ -1644,9 +1669,9 @@ struct Compiler
bytecode.emitABC(LOP_GETTABLEN, target, rt, i);
}
else if (cv && cv->type == Constant::Type_String)
else if (cv.type == Constant::Type_String)
{
BytecodeBuilder::StringRef iname = sref(cv->getString());
BytecodeBuilder::StringRef iname = sref(cv.getString());
int32_t cid = bytecode.addConstantString(iname);
if (cid < 0)
CompileError::raise(expr->location, "Exceeded constant limit; simplify the code to compile");
@ -1759,13 +1784,10 @@ struct Compiler
}
// Optimization: if expression has a constant value, we can emit it directly
if (const Constant* cv = constants.find(node))
if (const Constant* cv = constants.find(node); cv && cv->type != Constant::Type_Unknown)
{
if (cv->type != Constant::Type_Unknown)
{
compileExprConstant(node, cv, target);
return;
}
compileExprConstant(node, cv, target);
return;
}
if (AstExprGroup* expr = node->as<AstExprGroup>())
@ -1798,19 +1820,18 @@ struct Compiler
}
else if (AstExprLocal* expr = node->as<AstExprLocal>())
{
if (FFlag::LuauCompileSupportInlining ? !isExprLocalReg(expr) : expr->upvalue)
// note: this can't check expr->upvalue because upvalues may be upgraded to locals during inlining
if (int reg = getExprLocalReg(expr); reg >= 0)
{
bytecode.emitABC(LOP_MOVE, target, uint8_t(reg), 0);
}
else
{
LUAU_ASSERT(expr->upvalue);
uint8_t uid = getUpval(expr->local);
bytecode.emitABC(LOP_GETUPVAL, target, uid, 0);
}
else
{
uint8_t reg = getLocal(expr->local);
bytecode.emitABC(LOP_MOVE, target, reg, 0);
}
}
else if (AstExprGlobal* expr = node->as<AstExprGlobal>())
{
@ -1874,8 +1895,8 @@ struct Compiler
uint8_t compileExprAuto(AstExpr* node, RegScope&)
{
// Optimization: directly return locals instead of copying them to a temporary
if (isExprLocalReg(node))
return getLocal(node->as<AstExprLocal>()->local);
if (int reg = getExprLocalReg(node); reg >= 0)
return uint8_t(reg);
// note: the register is owned by the parent scope
uint8_t reg = allocReg(node, 1);
@ -1905,7 +1926,7 @@ struct Compiler
for (size_t i = 0; i < targetCount; ++i)
compileExprTemp(list.data[i], uint8_t(target + i));
// compute expressions with values that go nowhere; this is required to run side-effecting code if any
// evaluate extra expressions for side effects
for (size_t i = targetCount; i < list.size; ++i)
{
RegScope rsi(this);
@ -1965,23 +1986,22 @@ struct Compiler
LValue compileLValueIndex(uint8_t reg, AstExpr* index, RegScope& rs)
{
const Constant* cv = constants.find(index);
Constant cv = getConstant(index);
if (cv && cv->type == Constant::Type_Number && cv->valueNumber >= 1 && cv->valueNumber <= 256 &&
double(int(cv->valueNumber)) == cv->valueNumber)
if (cv.type == Constant::Type_Number && cv.valueNumber >= 1 && cv.valueNumber <= 256 && double(int(cv.valueNumber)) == cv.valueNumber)
{
LValue result = {LValue::Kind_IndexNumber};
result.reg = reg;
result.number = uint8_t(int(cv->valueNumber) - 1);
result.number = uint8_t(int(cv.valueNumber) - 1);
result.location = index->location;
return result;
}
else if (cv && cv->type == Constant::Type_String)
else if (cv.type == Constant::Type_String)
{
LValue result = {LValue::Kind_IndexName};
result.reg = reg;
result.name = sref(cv->getString());
result.name = sref(cv.getString());
result.location = index->location;
return result;
@ -2003,20 +2023,21 @@ struct Compiler
if (AstExprLocal* expr = node->as<AstExprLocal>())
{
if (FFlag::LuauCompileSupportInlining ? !isExprLocalReg(expr) : expr->upvalue)
// note: this can't check expr->upvalue because upvalues may be upgraded to locals during inlining
if (int reg = getExprLocalReg(expr); reg >= 0)
{
LUAU_ASSERT(expr->upvalue);
LValue result = {LValue::Kind_Upvalue};
result.upval = getUpval(expr->local);
LValue result = {LValue::Kind_Local};
result.reg = uint8_t(reg);
result.location = node->location;
return result;
}
else
{
LValue result = {LValue::Kind_Local};
result.reg = getLocal(expr->local);
LUAU_ASSERT(expr->upvalue);
LValue result = {LValue::Kind_Upvalue};
result.upval = getUpval(expr->local);
result.location = node->location;
return result;
@ -2110,15 +2131,21 @@ struct Compiler
compileLValueUse(lv, source, /* set= */ true);
}
bool isExprLocalReg(AstExpr* expr)
int getExprLocalReg(AstExpr* node)
{
AstExprLocal* le = expr->as<AstExprLocal>();
if (!le || (!FFlag::LuauCompileSupportInlining && le->upvalue))
return false;
if (AstExprLocal* expr = node->as<AstExprLocal>())
{
// note: this can't check expr->upvalue because upvalues may be upgraded to locals during inlining
Local* l = locals.find(expr->local);
Local* l = locals.find(le->local);
return l && l->allocated;
return l && l->allocated ? l->reg : -1;
}
else if (AstExprGroup* expr = node->as<AstExprGroup>())
return getExprLocalReg(expr->expr);
else if (AstExprTypeAssertion* expr = node->as<AstExprTypeAssertion>())
return getExprLocalReg(expr->expr);
else
return -1;
}
bool isStatBreak(AstStat* node)
@ -2342,17 +2369,25 @@ struct Compiler
RegScope rs(this);
uint8_t temp = 0;
bool consecutive = false;
bool multRet = false;
// Optimization: return local value directly instead of copying it into a temporary
if (stat->list.size == 1 && isExprLocalReg(stat->list.data[0]))
// Optimization: return locals directly instead of copying them into a temporary
// this is very important for a single return value and occasionally effective for multiple values
if (int reg = stat->list.size > 0 ? getExprLocalReg(stat->list.data[0]) : -1; reg >= 0)
{
AstExprLocal* le = stat->list.data[0]->as<AstExprLocal>();
LUAU_ASSERT(le);
temp = uint8_t(reg);
consecutive = true;
temp = getLocal(le->local);
for (size_t i = 1; i < stat->list.size; ++i)
if (getExprLocalReg(stat->list.data[i]) != int(temp + i))
{
consecutive = false;
break;
}
}
else if (stat->list.size > 0)
if (!consecutive && stat->list.size > 0)
{
temp = allocReg(stat, unsigned(stat->list.size));
@ -2401,66 +2436,28 @@ struct Compiler
pushLocal(stat->vars.data[i], uint8_t(vars + i));
}
int getConstantShort(AstExpr* expr)
{
const Constant* c = constants.find(expr);
if (c && c->type == Constant::Type_Number)
{
double n = c->valueNumber;
if (n >= -32767 && n <= 32767 && double(int(n)) == n)
return int(n);
}
return INT_MIN;
}
bool canUnrollForBody(AstStatFor* stat)
{
struct CanUnrollVisitor : AstVisitor
{
bool result = true;
bool visit(AstExpr* node) override
{
// functions may capture loop variable, and our upval handling doesn't handle elided variables (constant)
// TODO: we could remove this case if we changed function compilation to create temporary locals for constant upvalues
result = result && !node->is<AstExprFunction>();
return result;
}
bool visit(AstStat* node) override
{
// while we can easily unroll nested loops, our cost model doesn't take unrolling into account so this can result in code explosion
// we also avoid continue/break since they introduce control flow across iterations
result = result && !node->is<AstStatFor>() && !node->is<AstStatContinue>() && !node->is<AstStatBreak>();
return result;
}
};
CanUnrollVisitor canUnroll;
stat->body->visit(&canUnroll);
return canUnroll.result;
}
bool tryCompileUnrolledFor(AstStatFor* stat, int thresholdBase, int thresholdMaxBoost)
{
int from = getConstantShort(stat->from);
int to = getConstantShort(stat->to);
int step = stat->step ? getConstantShort(stat->step) : 1;
Constant one = {Constant::Type_Number};
one.valueNumber = 1.0;
// check that limits are reasonably small and trip count can be computed
if (from == INT_MIN || to == INT_MIN || step == INT_MIN || step == 0 || (step < 0 && to > from) || (step > 0 && to < from))
Constant fromc = getConstant(stat->from);
Constant toc = getConstant(stat->to);
Constant stepc = stat->step ? getConstant(stat->step) : one;
int tripCount = (fromc.type == Constant::Type_Number && toc.type == Constant::Type_Number && stepc.type == Constant::Type_Number)
? getTripCount(fromc.valueNumber, toc.valueNumber, stepc.valueNumber)
: -1;
if (tripCount < 0)
{
bytecode.addDebugRemark("loop unroll failed: invalid iteration count");
return false;
}
if (!canUnrollForBody(stat))
if (tripCount > thresholdBase)
{
bytecode.addDebugRemark("loop unroll failed: unsupported loop body");
bytecode.addDebugRemark("loop unroll failed: too many iterations (%d)", tripCount);
return false;
}
@ -2470,14 +2467,6 @@ struct Compiler
return false;
}
int tripCount = (to - from) / step + 1;
if (tripCount > thresholdBase)
{
bytecode.addDebugRemark("loop unroll failed: too many iterations (%d)", tripCount);
return false;
}
AstLocal* var = stat->var;
uint64_t costModel = modelCost(stat->body, &var, 1);
@ -2498,23 +2487,54 @@ struct Compiler
bytecode.addDebugRemark("loop unroll succeeded (iterations %d, cost %d, profit %.2fx)", tripCount, unrolledCost, double(unrollProfit) / 100);
for (int i = from; step > 0 ? i <= to : i >= to; i += step)
compileUnrolledFor(stat, tripCount, fromc.valueNumber, stepc.valueNumber);
return true;
}
void compileUnrolledFor(AstStatFor* stat, int tripCount, double from, double step)
{
AstLocal* var = stat->var;
size_t oldLocals = localStack.size();
size_t oldJumps = loopJumps.size();
loops.push_back({oldLocals, nullptr});
for (int iv = 0; iv < tripCount; ++iv)
{
// we need to re-fold constants in the loop body with the new value; this reuses computed constant values elsewhere in the tree
locstants[var].type = Constant::Type_Number;
locstants[var].valueNumber = i;
locstants[var].valueNumber = from + iv * step;
foldConstants(constants, variables, locstants, stat);
size_t iterJumps = loopJumps.size();
compileStat(stat->body);
// all continue jumps need to go to the next iteration
size_t contLabel = bytecode.emitLabel();
for (size_t i = iterJumps; i < loopJumps.size(); ++i)
if (loopJumps[i].type == LoopJump::Continue)
patchJump(stat, loopJumps[i].label, contLabel);
}
// all break jumps need to go past the loop
size_t endLabel = bytecode.emitLabel();
for (size_t i = oldJumps; i < loopJumps.size(); ++i)
if (loopJumps[i].type == LoopJump::Break)
patchJump(stat, loopJumps[i].label, endLabel);
loopJumps.resize(oldJumps);
loops.pop_back();
// clean up fold state in case we need to recompile - normally we compile the loop body once, but due to inlining we may need to do it again
locstants[var].type = Constant::Type_Unknown;
foldConstants(constants, variables, locstants, stat);
return true;
}
void compileStatFor(AstStatFor* stat)
@ -2601,26 +2621,16 @@ struct Compiler
// this puts initial values of (generator, state, index) into the loop registers
compileExprListTemp(stat->values, regs, 3, /* targetTop= */ true);
// we don't need this because the extra stack space is just for calling the function with a loop protocol which is similar to calling
// metamethods - it should fit into the extra stack reservation
if (!FFlag::LuauCompileIterNoReserve)
{
// for the general case, we will execute a CALL for every iteration that needs to evaluate "variables... = generator(state, index)"
// this requires at least extra 3 stack slots after index
// note that these stack slots overlap with the variables so we only need to reserve them to make sure stack frame is large enough
reserveReg(stat, 3);
}
// note that we reserve at least 2 variables; this allows our fast path to assume that we need 2 variables instead of 1 or 2
uint8_t vars = allocReg(stat, std::max(unsigned(stat->vars.size), 2u));
LUAU_ASSERT(vars == regs + 3);
// Optimization: when we iterate through pairs/ipairs, we generate special bytecode that optimizes the traversal using internal iteration
// index These instructions dynamically check if generator is equal to next/inext and bail out They assume that the generator produces 2
// variables, which is why we allocate at least 2 above (see vars assignment)
LuauOpcode skipOp = FFlag::LuauCompileIter ? LOP_FORGPREP : LOP_JUMP;
LuauOpcode skipOp = LOP_FORGPREP;
LuauOpcode loopOp = LOP_FORGLOOP;
// Optimization: when we iterate via pairs/ipairs, we generate special bytecode that optimizes the traversal using internal iteration index
// These instructions dynamically check if generator is equal to next/inext and bail out
// They assume that the generator produces 2 variables, which is why we allocate at least 2 above (see vars assignment)
if (options.optimizationLevel >= 1 && stat->vars.size <= 2)
{
if (stat->values.size == 1 && stat->values.data[0]->is<AstExprCall>())
@ -2858,12 +2868,9 @@ struct Compiler
void compileStatFunction(AstStatFunction* stat)
{
// Optimization: compile value expresion directly into target local register
if (isExprLocalReg(stat->name))
if (int reg = getExprLocalReg(stat->name); reg >= 0)
{
AstExprLocal* le = stat->name->as<AstExprLocal>();
LUAU_ASSERT(le);
compileExpr(stat->func, getLocal(le->local));
compileExpr(stat->func, uint8_t(reg));
return;
}
@ -3383,6 +3390,12 @@ struct Compiler
std::vector<size_t> returnJumps;
};
struct Capture
{
LuauCaptureType type;
uint8_t data;
};
BytecodeBuilder& bytecode;
CompileOptions options;
@ -3406,6 +3419,7 @@ struct Compiler
std::vector<LoopJump> loopJumps;
std::vector<Loop> loops;
std::vector<InlineFrame> inlineFrames;
std::vector<Capture> captures;
};
void compileOrThrow(BytecodeBuilder& bytecode, AstStatBlock* root, const AstNameTable& names, const CompileOptions& options)
@ -3449,6 +3463,9 @@ void compileOrThrow(BytecodeBuilder& bytecode, AstStatBlock* root, const AstName
/* self= */ nullptr, AstArray<AstLocal*>(), /* vararg= */ Luau::Location(), root, /* functionDepth= */ 0, /* debugname= */ AstName());
uint32_t mainid = compiler.compileFunction(&main);
const Compiler::Function* mainf = compiler.functions.find(&main);
LUAU_ASSERT(mainf && mainf->upvals.empty());
bytecode.setMainFunction(mainid);
bytecode.finalize();
}

View file

@ -3,8 +3,6 @@
#include <math.h>
LUAU_FASTFLAG(LuauCompileSupportInlining)
namespace Luau
{
namespace Compile
@ -195,12 +193,16 @@ struct ConstantVisitor : AstVisitor
DenseHashMap<AstLocal*, Variable>& variables;
DenseHashMap<AstLocal*, Constant>& locals;
bool wasEmpty = false;
ConstantVisitor(
DenseHashMap<AstExpr*, Constant>& constants, DenseHashMap<AstLocal*, Variable>& variables, DenseHashMap<AstLocal*, Constant>& locals)
: constants(constants)
, variables(variables)
, locals(locals)
{
// since we do a single pass over the tree, if the initial state was empty we don't need to clear out old entries
wasEmpty = constants.empty() && locals.empty();
}
Constant analyze(AstExpr* node)
@ -326,7 +328,7 @@ struct ConstantVisitor : AstVisitor
{
if (value.type != Constant::Type_Unknown)
map[key] = value;
else if (!FFlag::LuauCompileSupportInlining)
else if (wasEmpty)
;
else if (Constant* old = map.find(key))
old->type = Constant::Type_Unknown;

View file

@ -4,6 +4,8 @@
#include "Luau/Common.h"
#include "Luau/DenseHash.h"
#include <limits.h>
namespace Luau
{
namespace Compile
@ -11,10 +13,49 @@ namespace Compile
inline uint64_t parallelAddSat(uint64_t x, uint64_t y)
{
uint64_t s = x + y;
uint64_t m = s & 0x8080808080808080ull; // saturation mask
uint64_t r = x + y;
uint64_t s = r & 0x8080808080808080ull; // saturation mask
return (s ^ m) | (m - (m >> 7));
return (r ^ s) | (s - (s >> 7));
}
static uint64_t parallelMulSat(uint64_t a, int b)
{
int bs = (b < 127) ? b : 127;
// multiply every other value by b, yielding 14-bit products
uint64_t l = bs * ((a >> 0) & 0x007f007f007f007full);
uint64_t h = bs * ((a >> 8) & 0x007f007f007f007full);
// each product is 14-bit, so adding 32768-128 sets high bit iff the sum is 128 or larger without an overflow
uint64_t ls = l + 0x7f807f807f807f80ull;
uint64_t hs = h + 0x7f807f807f807f80ull;
// we now merge saturation bits as well as low 7-bits of each product into one
uint64_t s = (hs & 0x8000800080008000ull) | ((ls & 0x8000800080008000ull) >> 8);
uint64_t r = ((h & 0x007f007f007f007full) << 8) | (l & 0x007f007f007f007full);
// the low bits are now correct for values that didn't saturate, and we simply need to mask them if high bit is 1
return r | (s - (s >> 7));
}
inline bool getNumber(AstExpr* node, double& result)
{
// since constant model doesn't use constant folding atm, we perform the basic extraction that's sufficient to handle positive/negative literals
if (AstExprConstantNumber* ne = node->as<AstExprConstantNumber>())
{
result = ne->value;
return true;
}
if (AstExprUnary* ue = node->as<AstExprUnary>(); ue && ue->op == AstExprUnary::Minus)
if (AstExprConstantNumber* ne = ue->expr->as<AstExprConstantNumber>())
{
result = -ne->value;
return true;
}
return false;
}
struct Cost
@ -46,6 +87,13 @@ struct Cost
return *this;
}
Cost operator*(int other) const
{
Cost result;
result.model = parallelMulSat(model, other);
return result;
}
static Cost fold(const Cost& x, const Cost& y)
{
uint64_t newmodel = parallelAddSat(x.model, y.model);
@ -173,6 +221,16 @@ struct CostVisitor : AstVisitor
*i = 0;
}
void loop(AstStatBlock* body, Cost iterCost, int factor = 3)
{
Cost before = result;
result = Cost();
body->visit(this);
result = before + (result + iterCost) * factor;
}
bool visit(AstExpr* node) override
{
// note: we short-circuit the visitor traversal through any expression trees by returning false
@ -182,12 +240,52 @@ struct CostVisitor : AstVisitor
return false;
}
bool visit(AstStatFor* node) override
{
result += model(node->from);
result += model(node->to);
if (node->step)
result += model(node->step);
int tripCount = -1;
double from, to, step = 1;
if (getNumber(node->from, from) && getNumber(node->to, to) && (!node->step || getNumber(node->step, step)))
tripCount = getTripCount(from, to, step);
loop(node->body, 1, tripCount < 0 ? 3 : tripCount);
return false;
}
bool visit(AstStatForIn* node) override
{
for (size_t i = 0; i < node->values.size; ++i)
result += model(node->values.data[i]);
loop(node->body, 1);
return false;
}
bool visit(AstStatWhile* node) override
{
Cost condition = model(node->condition);
loop(node->body, condition);
return false;
}
bool visit(AstStatRepeat* node) override
{
Cost condition = model(node->condition);
loop(node->body, condition);
return false;
}
bool visit(AstStat* node) override
{
if (node->is<AstStatIf>())
result += 2;
else if (node->is<AstStatWhile>() || node->is<AstStatRepeat>() || node->is<AstStatFor>() || node->is<AstStatForIn>())
result += 2;
else if (node->is<AstStatBreak>() || node->is<AstStatContinue>())
result += 1;
@ -254,5 +352,21 @@ int computeCost(uint64_t model, const bool* varsConst, size_t varCount)
return cost;
}
int getTripCount(double from, double to, double step)
{
// we compute trip count in integers because that way we know that the loop math (repeated addition) is precise
int fromi = (from >= -32767 && from <= 32767 && double(int(from)) == from) ? int(from) : INT_MIN;
int toi = (to >= -32767 && to <= 32767 && double(int(to)) == to) ? int(to) : INT_MIN;
int stepi = (step >= -32767 && step <= 32767 && double(int(step)) == step) ? int(step) : INT_MIN;
if (fromi == INT_MIN || toi == INT_MIN || stepi == INT_MIN || stepi == 0)
return -1;
if ((stepi < 0 && toi > fromi) || (stepi > 0 && toi < fromi))
return 0;
return (toi - fromi) / stepi + 1;
}
} // namespace Compile
} // namespace Luau

View file

@ -14,5 +14,8 @@ uint64_t modelCost(AstNode* root, AstLocal* const* vars, size_t varCount);
// cost is computed as B - sum(Di * Ci), where B is baseline cost, Di is the discount for each variable and Ci is 1 when variable #i is constant
int computeCost(uint64_t model, const bool* varsConst, size_t varCount);
// get loop trip count or -1 if we can't compute it precisely
int getTripCount(double from, double to, double step);
} // namespace Compile
} // namespace Luau

View file

@ -19,6 +19,10 @@ ANALYSIS_SOURCES=$(wildcard Analysis/src/*.cpp)
ANALYSIS_OBJECTS=$(ANALYSIS_SOURCES:%=$(BUILD)/%.o)
ANALYSIS_TARGET=$(BUILD)/libluauanalysis.a
CODEGEN_SOURCES=$(wildcard CodeGen/src/*.cpp)
CODEGEN_OBJECTS=$(CODEGEN_SOURCES:%=$(BUILD)/%.o)
CODEGEN_TARGET=$(BUILD)/libluaucodegen.a
VM_SOURCES=$(wildcard VM/src/*.cpp)
VM_OBJECTS=$(VM_SOURCES:%=$(BUILD)/%.o)
VM_TARGET=$(BUILD)/libluauvm.a
@ -47,7 +51,7 @@ ifneq ($(flags),)
TESTS_ARGS+=--fflags=$(flags)
endif
OBJECTS=$(AST_OBJECTS) $(COMPILER_OBJECTS) $(ANALYSIS_OBJECTS) $(VM_OBJECTS) $(ISOCLINE_OBJECTS) $(TESTS_OBJECTS) $(CLI_OBJECTS) $(FUZZ_OBJECTS)
OBJECTS=$(AST_OBJECTS) $(COMPILER_OBJECTS) $(ANALYSIS_OBJECTS) $(CODEGEN_OBJECTS) $(VM_OBJECTS) $(ISOCLINE_OBJECTS) $(TESTS_OBJECTS) $(CLI_OBJECTS) $(FUZZ_OBJECTS)
# common flags
CXXFLAGS=-g -Wall
@ -90,15 +94,16 @@ ifeq ($(config),fuzz)
endif
# target-specific flags
$(AST_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include
$(COMPILER_OBJECTS): CXXFLAGS+=-std=c++17 -ICompiler/include -IAst/include
$(ANALYSIS_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include -IAnalysis/include
$(VM_OBJECTS): CXXFLAGS+=-std=c++11 -IVM/include
$(AST_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include
$(COMPILER_OBJECTS): CXXFLAGS+=-std=c++17 -ICompiler/include -ICommon/include -IAst/include
$(ANALYSIS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include
$(CODEGEN_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -ICodeGen/include
$(VM_OBJECTS): CXXFLAGS+=-std=c++11 -ICommon/include -IVM/include
$(ISOCLINE_OBJECTS): CXXFLAGS+=-Wno-unused-function -Iextern/isocline/include
$(TESTS_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include -ICompiler/include -IAnalysis/include -IVM/include -ICLI -Iextern
$(REPL_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include -ICompiler/include -IVM/include -Iextern -Iextern/isocline/include
$(ANALYZE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include -IAnalysis/include -Iextern
$(FUZZ_OBJECTS): CXXFLAGS+=-std=c++17 -IAst/include -ICompiler/include -IAnalysis/include -IVM/include
$(TESTS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -ICodeGen/include -IVM/include -ICLI -Iextern
$(REPL_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IVM/include -Iextern -Iextern/isocline/include
$(ANALYZE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include -Iextern
$(FUZZ_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -IVM/include
$(TESTS_TARGET): LDFLAGS+=-lpthread
$(REPL_CLI_TARGET): LDFLAGS+=-lpthread
@ -126,7 +131,7 @@ coverage: $(TESTS_TARGET)
llvm-cov export -ignore-filename-regex=\(tests\|extern\|CLI\)/.* -format lcov --instr-profile default.profdata build/coverage/luau-tests >coverage.info
format:
find . -name '*.h' -or -name '*.cpp' | xargs clang-format -i
find . -name '*.h' -or -name '*.cpp' | xargs clang-format-11 -i
luau-size: luau
nm --print-size --demangle luau | grep ' t void luau_execute<false>' | awk -F ' ' '{sum += strtonum("0x" $$2)} END {print sum " interpreter" }'
@ -140,7 +145,7 @@ luau-analyze: $(ANALYZE_CLI_TARGET)
ln -fs $^ $@
# executable targets
$(TESTS_TARGET): $(TESTS_OBJECTS) $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET)
$(TESTS_TARGET): $(TESTS_OBJECTS) $(ANALYSIS_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET)
$(REPL_CLI_TARGET): $(REPL_CLI_OBJECTS) $(COMPILER_TARGET) $(AST_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET)
$(ANALYZE_CLI_TARGET): $(ANALYZE_CLI_OBJECTS) $(ANALYSIS_TARGET) $(AST_TARGET)
@ -158,10 +163,11 @@ fuzz-prototest: $(BUILD)/fuzz/prototest.cpp.o $(BUILD)/fuzz/protoprint.cpp.o $(B
$(AST_TARGET): $(AST_OBJECTS)
$(COMPILER_TARGET): $(COMPILER_OBJECTS)
$(ANALYSIS_TARGET): $(ANALYSIS_OBJECTS)
$(CODEGEN_TARGET): $(CODEGEN_OBJECTS)
$(VM_TARGET): $(VM_OBJECTS)
$(ISOCLINE_TARGET): $(ISOCLINE_OBJECTS)
$(AST_TARGET) $(COMPILER_TARGET) $(ANALYSIS_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET):
$(AST_TARGET) $(COMPILER_TARGET) $(ANALYSIS_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(ISOCLINE_TARGET):
ar rcs $@ $^
# object file targets

View file

@ -1,7 +1,15 @@
# Luau.Common Sources
# Note: Until 3.19, INTERFACE targets couldn't have SOURCES property set
if(NOT ${CMAKE_VERSION} VERSION_LESS "3.19")
target_sources(Luau.Common PRIVATE
Common/include/Luau/Common.h
Common/include/Luau/Bytecode.h
)
endif()
# Luau.Ast Sources
target_sources(Luau.Ast PRIVATE
Ast/include/Luau/Ast.h
Ast/include/Luau/Common.h
Ast/include/Luau/Confusables.h
Ast/include/Luau/DenseHash.h
Ast/include/Luau/Lexer.h
@ -23,7 +31,6 @@ target_sources(Luau.Ast PRIVATE
# Luau.Compiler Sources
target_sources(Luau.Compiler PRIVATE
Compiler/include/Luau/Bytecode.h
Compiler/include/Luau/BytecodeBuilder.h
Compiler/include/Luau/Compiler.h
Compiler/include/luacode.h
@ -43,17 +50,32 @@ target_sources(Luau.Compiler PRIVATE
Compiler/src/ValueTracking.h
)
# Luau.CodeGen Sources
target_sources(Luau.CodeGen PRIVATE
CodeGen/include/Luau/AssemblyBuilderX64.h
CodeGen/include/Luau/Condition.h
CodeGen/include/Luau/Label.h
CodeGen/include/Luau/OperandX64.h
CodeGen/include/Luau/RegisterX64.h
CodeGen/src/AssemblyBuilderX64.cpp
)
# Luau.Analysis Sources
target_sources(Luau.Analysis PRIVATE
Analysis/include/Luau/AstQuery.h
Analysis/include/Luau/Autocomplete.h
Analysis/include/Luau/NotNull.h
Analysis/include/Luau/BuiltinDefinitions.h
Analysis/include/Luau/Config.h
Analysis/include/Luau/Clone.h
Analysis/include/Luau/Config.h
Analysis/include/Luau/ConstraintGraphBuilder.h
Analysis/include/Luau/ConstraintSolver.h
Analysis/include/Luau/Documentation.h
Analysis/include/Luau/Error.h
Analysis/include/Luau/FileResolver.h
Analysis/include/Luau/Frontend.h
Analysis/include/Luau/Instantiation.h
Analysis/include/Luau/IostreamHelpers.h
Analysis/include/Luau/JsonEncoder.h
Analysis/include/Luau/Linter.h
@ -73,6 +95,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/include/Luau/ToString.h
Analysis/include/Luau/Transpiler.h
Analysis/include/Luau/TxnLog.h
Analysis/include/Luau/TypeArena.h
Analysis/include/Luau/TypeAttach.h
Analysis/include/Luau/TypedAllocator.h
Analysis/include/Luau/TypeInfer.h
@ -88,10 +111,13 @@ target_sources(Luau.Analysis PRIVATE
Analysis/src/AstQuery.cpp
Analysis/src/Autocomplete.cpp
Analysis/src/BuiltinDefinitions.cpp
Analysis/src/Config.cpp
Analysis/src/Clone.cpp
Analysis/src/Config.cpp
Analysis/src/ConstraintGraphBuilder.cpp
Analysis/src/ConstraintSolver.cpp
Analysis/src/Error.cpp
Analysis/src/Frontend.cpp
Analysis/src/Instantiation.cpp
Analysis/src/IostreamHelpers.cpp
Analysis/src/JsonEncoder.cpp
Analysis/src/Linter.cpp
@ -108,6 +134,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/src/ToString.cpp
Analysis/src/Transpiler.cpp
Analysis/src/TxnLog.cpp
Analysis/src/TypeArena.cpp
Analysis/src/TypeAttach.cpp
Analysis/src/TypedAllocator.cpp
Analysis/src/TypeInfer.cpp
@ -218,6 +245,7 @@ if(TARGET Luau.UnitTest)
tests/AstQuery.test.cpp
tests/AstVisitor.test.cpp
tests/Autocomplete.test.cpp
tests/NotNull.test.cpp
tests/BuiltinDefinitions.test.cpp
tests/Compiler.test.cpp
tests/Config.test.cpp
@ -230,6 +258,8 @@ if(TARGET Luau.UnitTest)
tests/Module.test.cpp
tests/NonstrictMode.test.cpp
tests/Normalize.test.cpp
tests/ConstraintGraphBuilder.test.cpp
tests/ConstraintSolver.test.cpp
tests/Parser.test.cpp
tests/RequireTracer.test.cpp
tests/RuntimeLimits.test.cpp
@ -265,6 +295,7 @@ if(TARGET Luau.UnitTest)
tests/TypeVar.test.cpp
tests/Variant.test.cpp
tests/VisitTypeVar.test.cpp
tests/AssemblyBuilderX64.test.cpp
tests/main.cpp)
endif()

View file

@ -148,6 +148,7 @@ LUA_API const char* lua_tostringatom(lua_State* L, int idx, int* atom);
LUA_API const char* lua_namecallatom(lua_State* L, int* atom);
LUA_API int lua_objlen(lua_State* L, int idx);
LUA_API lua_CFunction lua_tocfunction(lua_State* L, int idx);
LUA_API void* lua_tolightuserdata(lua_State* L, int idx);
LUA_API void* lua_touserdata(lua_State* L, int idx);
LUA_API void* lua_touserdatatagged(lua_State* L, int idx, int tag);
LUA_API int lua_userdatatag(lua_State* L, int idx);

View file

@ -14,7 +14,25 @@
#include <string.h>
LUAU_FASTFLAG(LuauGcWorkTrackFix)
/*
* This file contains most implementations of core Lua APIs from lua.h.
*
* These implementations should use api_check macros to verify that stack and type contracts hold; it's the callers
* responsibility to, for example, pass a valid table index to lua_rawgetfield. Generally errors should only be raised
* for conditions caller can't predict such as an out-of-memory error.
*
* The caller is expected to handle stack reservation (by using less than LUA_MINSTACK slots or by calling lua_checkstack).
* To ensure this is handled correctly, use api_incr_top(L) when pushing values to the stack.
*
* Functions that push any collectable objects to the stack *should* call luaC_checkthreadsleep. Failure to do this can result
* in stack references that point to dead objects since sleeping threads don't get rescanned.
*
* Functions that push newly created objects to the stack *should* call luaC_checkGC in addition to luaC_checkthreadsleep.
* Failure to do this can result in OOM since GC may never run.
*
* Note that luaC_checkGC may scan the thread and put it back to sleep; functions that call both before pushing objects must
* therefore call luaC_checkGC before luaC_checkthreadsleep to guarantee the object is pushed to an awake thread.
*/
const char* lua_ident = "$Lua: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio $\n"
"$Authors: R. Ierusalimschy, L. H. de Figueiredo & W. Celes $\n"
@ -223,15 +241,13 @@ void lua_insert(lua_State* L, int idx)
void lua_replace(lua_State* L, int idx)
{
/* explicit test for incompatible code */
if (idx == LUA_ENVIRONINDEX && L->ci == L->base_ci)
luaG_runerror(L, "no calling environment");
api_checknelems(L, 1);
luaC_checkthreadsleep(L);
StkId o = index2addr(L, idx);
api_checkvalidindex(L, o);
if (idx == LUA_ENVIRONINDEX)
{
api_check(L, L->ci != L->base_ci);
Closure* func = curr_func(L);
api_check(L, ttistable(L->top - 1));
func->env = hvalue(L->top - 1);
@ -445,9 +461,7 @@ const float* lua_tovector(lua_State* L, int idx)
{
StkId o = index2addr(L, idx);
if (!ttisvector(o))
{
return NULL;
}
return vvalue(o);
}
@ -462,11 +476,6 @@ int lua_objlen(lua_State* L, int idx)
return uvalue(o)->len;
case LUA_TTABLE:
return luaH_getn(hvalue(o));
case LUA_TNUMBER:
{
int l = (luaV_tostring(L, o) ? tsvalue(o)->len : 0);
return l;
}
default:
return 0;
}
@ -478,18 +487,21 @@ lua_CFunction lua_tocfunction(lua_State* L, int idx)
return (!iscfunction(o)) ? NULL : cast_to(lua_CFunction, clvalue(o)->c.f);
}
void* lua_tolightuserdata(lua_State* L, int idx)
{
StkId o = index2addr(L, idx);
return (!ttislightuserdata(o)) ? NULL : pvalue(o);
}
void* lua_touserdata(lua_State* L, int idx)
{
StkId o = index2addr(L, idx);
switch (ttype(o))
{
case LUA_TUSERDATA:
if (ttisuserdata(o))
return uvalue(o)->data;
case LUA_TLIGHTUSERDATA:
else if (ttislightuserdata(o))
return pvalue(o);
default:
else
return NULL;
}
}
void* lua_touserdatatagged(lua_State* L, int idx, int tag)
@ -524,8 +536,9 @@ const void* lua_topointer(lua_State* L, int idx)
case LUA_TTHREAD:
return thvalue(o);
case LUA_TUSERDATA:
return uvalue(o)->data;
case LUA_TLIGHTUSERDATA:
return lua_touserdata(L, idx);
return pvalue(o);
default:
return NULL;
}
@ -750,10 +763,9 @@ void lua_setsafeenv(lua_State* L, int objindex, int enabled)
int lua_getmetatable(lua_State* L, int objindex)
{
const TValue* obj;
luaC_checkthreadsleep(L);
Table* mt = NULL;
int res;
obj = index2addr(L, objindex);
const TValue* obj = index2addr(L, objindex);
switch (ttype(obj))
{
case LUA_TTABLE:
@ -766,21 +778,18 @@ int lua_getmetatable(lua_State* L, int objindex)
mt = L->global->mt[ttype(obj)];
break;
}
if (mt == NULL)
res = 0;
else
if (mt)
{
sethvalue(L, L->top, mt);
api_incr_top(L);
res = 1;
}
return res;
return mt != NULL;
}
void lua_getfenv(lua_State* L, int idx)
{
StkId o;
o = index2addr(L, idx);
luaC_checkthreadsleep(L);
StkId o = index2addr(L, idx);
api_checkvalidindex(L, o);
switch (ttype(o))
{
@ -804,9 +813,8 @@ void lua_getfenv(lua_State* L, int idx)
void lua_settable(lua_State* L, int idx)
{
StkId t;
api_checknelems(L, 2);
t = index2addr(L, idx);
StkId t = index2addr(L, idx);
api_checkvalidindex(L, t);
luaV_settable(L, t, L->top - 2, L->top - 1);
L->top -= 2; /* pop index and value */
@ -815,22 +823,20 @@ void lua_settable(lua_State* L, int idx)
void lua_setfield(lua_State* L, int idx, const char* k)
{
StkId t;
TValue key;
api_checknelems(L, 1);
t = index2addr(L, idx);
StkId t = index2addr(L, idx);
api_checkvalidindex(L, t);
TValue key;
setsvalue(L, &key, luaS_new(L, k));
luaV_settable(L, t, &key, L->top - 1);
L->top--; /* pop value */
L->top--;
return;
}
void lua_rawset(lua_State* L, int idx)
{
StkId t;
api_checknelems(L, 2);
t = index2addr(L, idx);
StkId t = index2addr(L, idx);
api_check(L, ttistable(t));
if (hvalue(t)->readonly)
luaG_runerror(L, "Attempt to modify a readonly table");
@ -842,9 +848,8 @@ void lua_rawset(lua_State* L, int idx)
void lua_rawseti(lua_State* L, int idx, int n)
{
StkId o;
api_checknelems(L, 1);
o = index2addr(L, idx);
StkId o = index2addr(L, idx);
api_check(L, ttistable(o));
if (hvalue(o)->readonly)
luaG_runerror(L, "Attempt to modify a readonly table");
@ -856,14 +861,11 @@ void lua_rawseti(lua_State* L, int idx, int n)
int lua_setmetatable(lua_State* L, int objindex)
{
TValue* obj;
Table* mt;
api_checknelems(L, 1);
obj = index2addr(L, objindex);
TValue* obj = index2addr(L, objindex);
api_checkvalidindex(L, obj);
if (ttisnil(L->top - 1))
mt = NULL;
else
Table* mt = NULL;
if (!ttisnil(L->top - 1))
{
api_check(L, ttistable(L->top - 1));
mt = hvalue(L->top - 1);
@ -898,10 +900,9 @@ int lua_setmetatable(lua_State* L, int objindex)
int lua_setfenv(lua_State* L, int idx)
{
StkId o;
int res = 1;
api_checknelems(L, 1);
o = index2addr(L, idx);
StkId o = index2addr(L, idx);
api_checkvalidindex(L, o);
api_check(L, ttistable(L->top - 1));
switch (ttype(o))
@ -968,24 +969,21 @@ static void f_call(lua_State* L, void* ud)
int lua_pcall(lua_State* L, int nargs, int nresults, int errfunc)
{
struct CallS c;
int status;
ptrdiff_t func;
api_checknelems(L, nargs + 1);
api_check(L, L->status == 0);
checkresults(L, nargs, nresults);
if (errfunc == 0)
func = 0;
else
ptrdiff_t func = 0;
if (errfunc != 0)
{
StkId o = index2addr(L, errfunc);
api_checkvalidindex(L, o);
func = savestack(L, o);
}
struct CallS c;
c.func = L->top - (nargs + 1); /* function to be called */
c.nresults = nresults;
status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);
int status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);
adjustresults(L, nresults);
return status;
@ -1050,7 +1048,6 @@ int lua_gc(lua_State* L, int what, int data)
}
case LUA_GCSTEP:
{
size_t prevthreshold = g->GCthreshold;
size_t amount = (cast_to(size_t, data) << 10);
ptrdiff_t oldcredit = g->gcstate == GCSpause ? 0 : g->GCthreshold - g->totalbytes;
@ -1060,8 +1057,6 @@ int lua_gc(lua_State* L, int what, int data)
else
g->GCthreshold = 0;
bool waspaused = g->gcstate == GCSpause;
#ifdef LUAI_GCMETRICS
double startmarktime = g->gcmetrics.currcycle.marktime;
double startsweeptime = g->gcmetrics.currcycle.sweeptime;
@ -1074,7 +1069,7 @@ int lua_gc(lua_State* L, int what, int data)
{
size_t stepsize = luaC_step(L, false);
actualwork += FFlag::LuauGcWorkTrackFix ? stepsize : g->gcstepsize;
actualwork += stepsize;
if (g->gcstate == GCSpause)
{ /* end of cycle? */
@ -1110,20 +1105,9 @@ int lua_gc(lua_State* L, int what, int data)
// if cycle hasn't finished, advance threshold forward for the amount of extra work performed
if (g->gcstate != GCSpause)
{
if (FFlag::LuauGcWorkTrackFix)
{
// if a new cycle was triggered by explicit step, old 'credit' of GC work is 0
ptrdiff_t newthreshold = g->totalbytes + actualwork + oldcredit;
g->GCthreshold = newthreshold < 0 ? 0 : newthreshold;
}
else
{
// if a new cycle was triggered by explicit step, we ignore old threshold as that shows an incorrect 'credit' of GC work
if (waspaused)
g->GCthreshold = g->totalbytes + actualwork;
else
g->GCthreshold = prevthreshold + actualwork;
}
// if a new cycle was triggered by explicit step, old 'credit' of GC work is 0
ptrdiff_t newthreshold = g->totalbytes + actualwork + oldcredit;
g->GCthreshold = newthreshold < 0 ? 0 : newthreshold;
}
break;
}
@ -1259,12 +1243,10 @@ const char* lua_getupvalue(lua_State* L, int funcindex, int n)
const char* lua_setupvalue(lua_State* L, int funcindex, int n)
{
const char* name;
TValue* val;
StkId fi;
fi = index2addr(L, funcindex);
api_checknelems(L, 1);
name = aux_upvalue(fi, n, &val);
StkId fi = index2addr(L, funcindex);
TValue* val;
const char* name = aux_upvalue(fi, n, &val);
if (name)
{
L->top--;
@ -1331,14 +1313,16 @@ void lua_setuserdatadtor(lua_State* L, int tag, void (*dtor)(lua_State*, void*))
void lua_clonefunction(lua_State* L, int idx)
{
luaC_checkGC(L);
luaC_checkthreadsleep(L);
StkId p = index2addr(L, idx);
api_check(L, isLfunction(p));
luaC_checkthreadsleep(L);
Closure* cl = clvalue(p);
Closure* newcl = luaF_newLclosure(L, 0, L->gt, cl->l.p);
setclvalue(L, L->top - 1, newcl);
Closure* newcl = luaF_newLclosure(L, cl->nupvalues, L->gt, cl->l.p);
for (int i = 0; i < cl->nupvalues; ++i)
setobj2n(L, &newcl->l.uprefs[i], &cl->l.uprefs[i]);
setclvalue(L, L->top, newcl);
api_incr_top(L);
}
lua_Callbacks* lua_callbacks(lua_State* L)

View file

@ -15,8 +15,6 @@
#include <intrin.h>
#endif
LUAU_FASTFLAGVARIABLE(LuauFixBuiltinsStackLimit, false)
// luauF functions implement FASTCALL instruction that performs a direct execution of some builtin functions from the VM
// The rule of thumb is that FASTCALL functions can not call user code, yield, fail, or reallocate stack.
// If types of the arguments mismatch, luauF_* needs to return -1 and the execution will fall back to the usual call path
@ -1005,7 +1003,7 @@ static int luauF_tunpack(lua_State* L, StkId res, TValue* arg0, int nresults, St
else if (nparams == 3 && ttisnumber(args) && ttisnumber(args + 1) && nvalue(args) == 1.0)
n = int(nvalue(args + 1));
if (n >= 0 && n <= t->sizearray && cast_int(L->stack_last - res) >= n && (!FFlag::LuauFixBuiltinsStackLimit || n + nparams <= LUAI_MAXCSTACK))
if (n >= 0 && n <= t->sizearray && cast_int(L->stack_last - res) >= n && n + nparams <= LUAI_MAXCSTACK)
{
TValue* array = t->array;
for (int i = 0; i < n; ++i)
@ -1020,18 +1018,20 @@ static int luauF_tunpack(lua_State* L, StkId res, TValue* arg0, int nresults, St
static int luauF_vector(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams)
{
#if LUA_VECTOR_SIZE == 4
if (nparams >= 4 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1) && ttisnumber(args + 2))
#else
if (nparams >= 3 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1))
#endif
{
double x = nvalue(arg0);
double y = nvalue(args);
double z = nvalue(args + 1);
#if LUA_VECTOR_SIZE == 4
double w = nvalue(args + 2);
double w = 0.0;
if (nparams >= 4)
{
if (!ttisnumber(args + 2))
return -1;
w = nvalue(args + 2);
}
setvvalue(res, float(x), float(y), float(z), float(w));
#else
setvvalue(res, float(x), float(y), float(z), 0.0f);

View file

@ -3,7 +3,4 @@
#pragma once
// This is a forwarding header for Luau bytecode definition
// Luau consists of several components, including compiler (Ast, Compiler) and VM (virtual machine)
// These components are fully independent, but they both need the bytecode format defined in this header
// so it needs to be shared.
#include "../../Compiler/include/Luau/Bytecode.h"
#include "Luau/Bytecode.h"

View file

@ -7,11 +7,7 @@
#include "luaconf.h"
// This is a forwarding header for Luau common definition (assertions, flags)
// Luau consists of several components, including compiler (Ast, Compiler) and VM (virtual machine)
// These components are fully independent, but they need a common set of utilities defined in this header
// so it needs to be shared.
#include "../../Ast/include/Luau/Common.h"
#include "Luau/Common.h"
typedef LUAI_USER_ALIGNMENT_T L_Umaxalign;

View file

@ -202,17 +202,32 @@ void luaD_growstack(lua_State* L, int n)
CallInfo* luaD_growCI(lua_State* L)
{
if (L->size_ci > LUAI_MAXCALLS) /* overflow while handling overflow? */
luaD_throw(L, LUA_ERRERR);
else
{
luaD_reallocCI(L, 2 * L->size_ci);
if (L->size_ci > LUAI_MAXCALLS)
luaG_runerror(L, "stack overflow");
}
/* allow extra stack space to handle stack overflow in xpcall */
const int hardlimit = LUAI_MAXCALLS + (LUAI_MAXCALLS >> 3);
if (L->size_ci >= hardlimit)
luaD_throw(L, LUA_ERRERR); /* error while handling stack error */
int request = L->size_ci * 2;
luaD_reallocCI(L, L->size_ci >= LUAI_MAXCALLS ? hardlimit : request < LUAI_MAXCALLS ? request : LUAI_MAXCALLS);
if (L->size_ci > LUAI_MAXCALLS)
luaG_runerror(L, "stack overflow");
return ++L->ci;
}
void luaD_checkCstack(lua_State* L)
{
/* allow extra stack space to handle stack overflow in xpcall */
const int hardlimit = LUAI_MAXCCALLS + (LUAI_MAXCCALLS >> 3);
if (L->nCcalls == LUAI_MAXCCALLS)
luaG_runerror(L, "C stack overflow");
else if (L->nCcalls >= hardlimit)
luaD_throw(L, LUA_ERRERR); /* error while handling stack error */
}
/*
** Call a function (C or Lua). The function to be called is at *func.
** The arguments are on the stack, right after the function.
@ -222,12 +237,8 @@ CallInfo* luaD_growCI(lua_State* L)
void luaD_call(lua_State* L, StkId func, int nResults)
{
if (++L->nCcalls >= LUAI_MAXCCALLS)
{
if (L->nCcalls == LUAI_MAXCCALLS)
luaG_runerror(L, "C stack overflow");
else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS >> 3)))
luaD_throw(L, LUA_ERRERR); /* error while handing stack error */
}
luaD_checkCstack(L);
if (luau_precall(L, func, nResults) == PCRLUA)
{ /* is a Lua function? */
L->ci->flags |= LUA_CALLINFO_RETURN; /* luau_execute will stop after returning from the stack frame */
@ -241,6 +252,7 @@ void luaD_call(lua_State* L, StkId func, int nResults)
if (!oldactive)
resetbit(L->stackstate, THREAD_ACTIVEBIT);
}
L->nCcalls--;
luaC_checkGC(L);
}

View file

@ -49,6 +49,7 @@ LUAI_FUNC int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t oldtop, pt
LUAI_FUNC void luaD_reallocCI(lua_State* L, int newsize);
LUAI_FUNC void luaD_reallocstack(lua_State* L, int newsize);
LUAI_FUNC void luaD_growstack(lua_State* L, int n);
LUAI_FUNC void luaD_checkCstack(lua_State* L);
LUAI_FUNC l_noret luaD_throw(lua_State* L, int errcode);
LUAI_FUNC int luaD_rawrunprotected(lua_State* L, Pfunc f, void* ud);

View file

@ -13,10 +13,7 @@
#include <string.h>
LUAU_FASTFLAGVARIABLE(LuauGcWorkTrackFix, false)
LUAU_FASTFLAGVARIABLE(LuauGcSweepCostFix, false)
#define GC_SWEEPPAGESTEPCOST (FFlag::LuauGcSweepCostFix ? 16 : 4)
#define GC_SWEEPPAGESTEPCOST 16
#define GC_INTERRUPT(state) \
{ \
@ -881,7 +878,7 @@ size_t luaC_step(lua_State* L, bool assist)
{
global_State* g = L->global;
int lim = FFlag::LuauGcWorkTrackFix ? g->gcstepsize * g->gcstepmul / 100 : (g->gcstepsize / 100) * g->gcstepmul; /* how much to work */
int lim = g->gcstepsize * g->gcstepmul / 100; /* how much to work */
LUAU_ASSERT(g->totalbytes >= g->GCthreshold);
size_t debt = g->totalbytes - g->GCthreshold;
@ -927,10 +924,10 @@ size_t luaC_step(lua_State* L, bool assist)
}
else
{
g->GCthreshold = g->totalbytes + (FFlag::LuauGcWorkTrackFix ? actualstepsize : g->gcstepsize);
g->GCthreshold = g->totalbytes + actualstepsize;
// compensate if GC is "behind schedule" (has some debt to pay)
if (FFlag::LuauGcWorkTrackFix ? g->GCthreshold >= debt : g->GCthreshold > debt)
if (g->GCthreshold >= debt)
g->GCthreshold -= debt;
}

View file

@ -10,10 +10,6 @@
#include "ldebug.h"
#include "lvm.h"
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauTableMoveTelemetry2, false)
void (*lua_table_move_telemetry)(lua_State* L, int f, int e, int t, int nf, int nt);
static int foreachi(lua_State* L)
{
luaL_checktype(L, 1, LUA_TTABLE);
@ -199,29 +195,6 @@ static int tmove(lua_State* L)
int tt = !lua_isnoneornil(L, 5) ? 5 : 1; /* destination table */
luaL_checktype(L, tt, LUA_TTABLE);
void (*telemetrycb)(lua_State * L, int f, int e, int t, int nf, int nt) = lua_table_move_telemetry;
if (DFFlag::LuauTableMoveTelemetry2 && telemetrycb && e >= f)
{
int nf = lua_objlen(L, 1);
int nt = lua_objlen(L, tt);
bool report = false;
// source index range must be in bounds in source table unless the table is empty (permits 1..#t moves)
if (!(f == 1 || (f >= 1 && f <= nf)))
report = true;
if (!(e == nf || (e >= 1 && e <= nf)))
report = true;
// destination index must be in bounds in dest table or be exactly at the first empty element (permits concats)
if (!(t == nt + 1 || (t >= 1 && t <= nt)))
report = true;
if (report)
telemetrycb(L, f, e, t, nf, nt);
}
if (e >= f)
{ /* otherwise, nothing to move */
luaL_argcheck(L, f > 0 || e < INT_MAX + f, 3, "too many elements to move");

View file

@ -16,11 +16,6 @@
#include <string.h>
LUAU_FASTFLAGVARIABLE(LuauIter, false)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauIterCallTelemetry, false)
void (*lua_iter_call_telemetry)(lua_State* L);
// Disable c99-designator to avoid the warning in CGOTO dispatch table
#ifdef __clang__
#if __has_warning("-Wc99-designator")
@ -157,17 +152,6 @@ LUAU_NOINLINE static bool luau_loopFORG(lua_State* L, int a, int c)
StkId ra = &L->base[a];
LUAU_ASSERT(ra + 3 <= L->top);
if (DFFlag::LuauIterCallTelemetry)
{
/* TODO: we might be able to stop supporting this depending on whether it's used in practice */
void (*telemetrycb)(lua_State* L) = lua_iter_call_telemetry;
if (telemetrycb && ttistable(ra) && fasttm(L, hvalue(ra)->metatable, TM_CALL))
telemetrycb(L);
if (telemetrycb && ttisuserdata(ra) && fasttm(L, uvalue(ra)->metatable, TM_CALL))
telemetrycb(L);
}
setobjs2s(L, ra + 3 + 2, ra + 2);
setobjs2s(L, ra + 3 + 1, ra + 1);
setobjs2s(L, ra + 3, ra);
@ -195,7 +179,7 @@ LUAU_NOINLINE static void luau_callTM(lua_State* L, int nparams, int res)
++L->nCcalls;
if (L->nCcalls >= LUAI_MAXCCALLS)
luaG_runerror(L, "C stack overflow");
luaD_checkCstack(L);
luaD_checkstack(L, LUA_MINSTACK);
@ -708,7 +692,7 @@ static void luau_execute(lua_State* L)
}
else
{
// slow-path, may invoke Lua calls via __index metamethod
// slow-path, may invoke Lua calls via __newindex metamethod
L->cachedslot = slot;
VM_PROTECT(luaV_settable(L, rb, kv, ra));
// save cachedslot to accelerate future lookups; patches currently executing instruction since pc-2 rolls back two pc++
@ -718,7 +702,7 @@ static void luau_execute(lua_State* L)
}
else
{
// fast-path: user data with C __index TM
// fast-path: user data with C __newindex TM
const TValue* fn = 0;
if (ttisuserdata(rb) && (fn = fasttm(L, uvalue(rb)->metatable, TM_NEWINDEX)) && ttisfunction(fn) && clvalue(fn)->isC)
{
@ -739,7 +723,7 @@ static void luau_execute(lua_State* L)
}
else
{
// slow-path, may invoke Lua calls via __index metamethod
// slow-path, may invoke Lua calls via __newindex metamethod
VM_PROTECT(luaV_settable(L, rb, kv, ra));
VM_NEXT();
}
@ -2228,7 +2212,7 @@ static void luau_execute(lua_State* L)
{
/* will be called during FORGLOOP */
}
else if (FFlag::LuauIter)
else
{
Table* mt = ttistable(ra) ? hvalue(ra)->metatable : ttisuserdata(ra) ? uvalue(ra)->metatable : cast_to(Table*, NULL);
@ -2273,17 +2257,6 @@ static void luau_execute(lua_State* L)
StkId ra = VM_REG(LUAU_INSN_A(insn));
uint32_t aux = *pc;
if (!FFlag::LuauIter)
{
bool stop;
VM_PROTECT(stop = luau_loopFORG(L, LUAU_INSN_A(insn), aux));
// note that we need to increment pc by 1 to exit the loop since we need to skip over aux
pc += stop ? 1 : LUAU_INSN_D(insn);
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
VM_NEXT();
}
// fast-path: builtin table iteration
if (ttisnil(ra) && ttistable(ra + 1) && ttislightuserdata(ra + 2))
{
@ -2372,12 +2345,11 @@ static void luau_execute(lua_State* L)
// fast-path: ipairs/inext
if (cl->env->safeenv && ttistable(ra + 1) && ttisnumber(ra + 2) && nvalue(ra + 2) == 0.0)
{
if (FFlag::LuauIter)
setnilvalue(ra);
setnilvalue(ra);
/* ra+1 is already the table */
setpvalue(ra + 2, reinterpret_cast<void*>(uintptr_t(0)));
}
else if (FFlag::LuauIter && !ttisfunction(ra))
else if (!ttisfunction(ra))
{
VM_PROTECT(luaG_typeerror(L, ra, "iterate over"));
}
@ -2394,7 +2366,7 @@ static void luau_execute(lua_State* L)
StkId ra = VM_REG(LUAU_INSN_A(insn));
// fast-path: ipairs/inext
if (ttistable(ra + 1) && ttislightuserdata(ra + 2))
if (ttisnil(ra) && ttistable(ra + 1) && ttislightuserdata(ra + 2))
{
Table* h = hvalue(ra + 1);
int index = int(reinterpret_cast<uintptr_t>(pvalue(ra + 2)));
@ -2445,12 +2417,11 @@ static void luau_execute(lua_State* L)
// fast-path: pairs/next
if (cl->env->safeenv && ttistable(ra + 1) && ttisnil(ra + 2))
{
if (FFlag::LuauIter)
setnilvalue(ra);
setnilvalue(ra);
/* ra+1 is already the table */
setpvalue(ra + 2, reinterpret_cast<void*>(uintptr_t(0)));
}
else if (FFlag::LuauIter && !ttisfunction(ra))
else if (!ttisfunction(ra))
{
VM_PROTECT(luaG_typeerror(L, ra, "iterate over"));
}
@ -2467,7 +2438,7 @@ static void luau_execute(lua_State* L)
StkId ra = VM_REG(LUAU_INSN_A(insn));
// fast-path: pairs/next
if (ttistable(ra + 1) && ttislightuserdata(ra + 2))
if (ttisnil(ra) && ttistable(ra + 1) && ttislightuserdata(ra + 2))
{
Table* h = hvalue(ra + 1);
int index = int(reinterpret_cast<uintptr_t>(pvalue(ra + 2)));

View file

@ -69,6 +69,7 @@ Sandboxing challenges are [covered in the dedicated section](sandbox).
| `\0` in patterns | ✔️ | |
| `bit32` library | ✔️ | |
| `string.gsub` is stricter about using `%` on special characters only | ✔️ | |
| light C functions | 😞 | this changes semantics of fenv on C functions and has complex implications wrt runtime performance |
Two things that are important to call out here are various new metamethods for tables and yielding in metamethods. In both cases, there are performance implications to supporting this - our implementation is *very* highly tuned for performance, so any changes that affect the core fundamentals of how Lua works have a price. To support yielding in metamethods we'd need to make the core of the VM more involved, since almost every single "interesting" opcode would need to learn how to be resumable - which also complicates future JIT/AOT story. Metamethods in general are important for extensibility, but very challenging to deal with in implementation, so we err on the side of not supporting any new metamethods unless a strong need arises.

View file

@ -92,12 +92,22 @@ As a result, builtin calls are very fast in Luau - they are still slightly slowe
## Optimized table iteration
Luau implements a fully generic iteration protocol; however, for iteration through tables it recognizes three common idioms (`for .. in ipairs(t)`, `for .. in pairs(t)` and `for .. in next, t`) and emits specialized bytecode that is carefully optimized using custom internal iterators.
Luau implements a fully generic iteration protocol; however, for iteration through tables in addition to generalized iteration (`for .. in t`) it recognizes three common idioms (`for .. in ipairs(t)`, `for .. in pairs(t)` and `for .. in next, t`) and emits specialized bytecode that is carefully optimized using custom internal iterators.
As a result, iteration through tables typically doesn't result in function calls for every iteration; the performance of iteration using `pairs` and `ipairs` is comparable, so it's recommended to pick the iteration style based on readability instead of performance.
As a result, iteration through tables typically doesn't result in function calls for every iteration; the performance of iteration using generalized iteration, `pairs` and `ipairs` is comparable, so generalized iteration (without the use of `pairs`/`ipairs`) is recommended unless the code needs to be compatible with vanilla Lua or the specific semantics of `ipairs` (which stops at the first `nil` element) is required. Additionally, using generalized iteration avoids calling `pairs` when the loop starts which can be noticeable when the table is very short.
Iterating through array-like tables using `for i=1,#t` tends to be slightly slower because of extra cost incurred when reading elements from the table.
## Optimized table length
Luau tables use a hybrid array/hash storage, like in Lua; in some sense "arrays" don't truly exist and are an internal optimization, but some operations, notably `#t` and functions that depend on it, like `table.insert`, are defined by the Luau/Lua language to allow internal optimizations. Luau takes advantage of that fact.
Unlike Lua, Luau guarantees that the element at index `#t` is stored in the array part of the table. This can accelerate various table operations that use indices limited by `#t`, and this makes `#t` worst-case complexity O(logN), unlike Lua where the worst case complexity is O(N). This also accelerates computation of this value for small tables like `{ [1] = 1 }` since we never need to look at the hash part.
The "default" implementation of `#t` in both Lua and Luau is a binary search. Luau uses a special branch-free (depending on the compiler...) implementation of the binary search which results in 50+% faster computation of table length when it needs to be computed from scratch.
Additionally, Luau can cache the length of the table and adjust it following operations like `table.insert`/`table.remove`; this means that in practice, `#t` is almost always a constant time operation.
## Creating and modifying tables
Luau implements several optimizations for table creation. When creating object-like tables, it's recommended to use table literals (`{ ... }`) and to specify all table fields in the literal in one go instead of assigning fields later; this triggers an optimization inspired by LuaJIT's "table templates" and results in higher performance when creating objects. When creating array-like tables, if the maximum size of the table is known up front, it's recommended to use `table.create` function which can create an empty table with preallocated storage, and optionally fill it with a given value.
@ -112,7 +122,7 @@ v.z = 3
return v
```
When appending elements to tables, it's recommended to use `table.insert` (which is the fastest method to append an element to a table if the table size is not known). In cases when a table is filled sequentially, however, it's much more efficient to use a known index for insertion - together with preallocating tables using `table.create` this can result in much faster code, for example this is the fastest way to build a table of squares:
When appending elements to tables, it's recommended to use `table.insert` (which is the fastest method to append an element to a table if the table size is not known). In cases when a table is filled sequentially, however, it can be more efficient to use a known index for insertion - together with preallocating tables using `table.create` this can result in much faster code, for example this is the fastest way to build a table of squares:
```lua
local t = table.create(N)

View file

@ -0,0 +1,97 @@
---
layout: single
title: "Luau Recap: May 2022"
---
This month Luau team has worked to bring you a new language feature together with more typechecking improvements and bugfixes!
[Cross-posted to the [Roblox Developer Forum](https://devforum.roblox.com/t/luau-recap-may-2022/).]
## Generalized iteration
We have extended the semantics of standard Lua syntax for iterating through containers, `for vars in values` with support for generalized iteration.
In Lua, to iterate over a table you need to use an iterator like `next` or a function that returns one like `pairs` or `ipairs`. In Luau, you can now simply iterate over a table:
```lua
for k, v in {1, 4, 9} do
assert(k * k == v)
end
```
This works for tables but can also be customized for tables or userdata by implementing `__iter` metamethod. It is called before the iteration begins, and should return an iterator function like `next` (or a custom one):
```lua
local obj = { items = {1, 4, 9} }
setmetatable(obj, { __iter = function(o) return next, o.items end })
for k, v in obj do
assert(k * k == v)
end
```
The default iteration order for tables is specified to be consecutive for elements `1..#t` and unordered after that, visiting every element.
Similar to iteration using `pairs`, modifying the table entries for keys other than the current one results in unspecified behavior.
## Typechecking improvements
We have added a missing check to compare implicit table keys against the key type of the table indexer:
```lua
-- error is correctly reported, implicit keys (1,2,3) are not compatible with [string]
local t: { [string]: boolean } = { true, true, false }
```
Rules for `==` and `~=` have been relaxed for union types, if any of the union parts can be compared, operation succeeds:
```lua
--!strict
local function compare(v1: Vector3, v2: Vector3?)
return v1 == v2 -- no longer an error
end
```
Table value type propagation now correctly works with `[any]` key type:
```lua
--!strict
type X = {[any]: string | boolean}
local x: X = { key = "str" } -- no longer gives an incorrect error
```
If a generic function doesn't provide type annotations for all arguments and the return value, additional generic type parameters might be added automatically:
```lua
-- previously it was foo<T>, now it's foo<T, b>, because second argument is also generic
function foo<T>(x: T, y) end
```
We have also fixed various issues that have caused crashes, with many of them coming from your bug reports.
## Linter improvements
`GlobalUsedAsLocal` lint warning has been extended to notice when global variable writes always happen before their use in a local scope, suggesting that they can be replaced with a local variable:
```lua
function bar()
foo = 6 -- Global 'foo' is never read before being written. Consider changing it to local
return foo
end
function baz()
foo = 10
return foo
end
```
## Performance improvements
Garbage collection CPU utilization has been tuned to further reduce frame time spikes of individual collection steps and to bring different GC stages to the same level of CPU utilization.
Returning a type-cast local (`return a :: type`) as well as returning multiple local variables (`return a, b, c`) is now a little bit more efficient.
### Function inlining and loop unrolling
In the open-source release of Luau, when optimization level 2 is enabled, the compiler will now perform function inlining and loop unrolling.
Only loops with loop bounds known at compile time, such as `for i=1,4 do`, can be unrolled. The loop body must be simple enough for the optimization to be profitable; compiler uses heuristics to estimate the performance benefit and automatically decide if unrolling should be performed.
Only local functions (defined either as `local function foo` or `local foo = function`) can be inlined. The function body must be simple enough for the optimization to be profitable; compiler uses heuristics to estimate the performance benefit and automatically decide if each call to the function should be inlined instead. Additionally recursive invocations of a function can't be inlined at this time, and inlining is completely disabled for modules that use `getfenv`/`setfenv` functions.

View file

@ -575,6 +575,7 @@ ic_private const char* parse_tag_value( tag_t* tag, char* idbuf, const char* s,
}
// limit name and attr to 128 bytes
char valbuf[128];
valbuf[0] = 0; // fixes gcc uninitialized warning
ic_strncpy( idbuf, 128, id, idend - id);
ic_strncpy( valbuf, 128, val, valend - val);
ic_str_tolower(idbuf);

View file

@ -1,38 +0,0 @@
{-# OPTIONS --rewriting #-}
open import FFI.Data.Either using (Either; Left; Right)
open import Luau.Type using (Type; nil; number; string; boolean; never; unknown; _⇒_; __; _∩_)
open import Luau.TypeNormalization using (normalize)
module Luau.FunctionTypes where
-- The domain of a normalized type
srcⁿ : Type Type
srcⁿ (S T) = S
srcⁿ (S T) = srcⁿ S srcⁿ T
srcⁿ never = unknown
srcⁿ T = never
-- To get the domain of a type, we normalize it first We need to do
-- this, since if we try to use it on non-normalized types, we get
--
-- src(number ∩ string) = src(number) src(string) = never never
-- src(never) = unknown
--
-- so src doesn't respect type equivalence.
src : Type Type
src (S T) = S
src T = srcⁿ(normalize T)
-- The codomain of a type
tgt : Type Type
tgt nil = never
tgt (S T) = T
tgt never = never
tgt unknown = unknown
tgt number = never
tgt boolean = never
tgt string = never
tgt (S T) = (tgt S) (tgt T)
tgt (S T) = (tgt S) (tgt T)

View file

@ -0,0 +1,98 @@
{-# OPTIONS --rewriting #-}
module Luau.ResolveOverloads where
open import FFI.Data.Either using (Left; Right)
open import Luau.Subtyping using (_<:_; _≮:_; Language; witness; scalar; unknown; never; function-ok)
open import Luau.Type using (Type ; _⇒_; _∩_; __; unknown; never)
open import Luau.TypeSaturation using (saturate)
open import Luau.TypeNormalization using (normalize)
open import Properties.Contradiction using (CONTRADICTION)
open import Properties.DecSubtyping using (dec-subtyping; dec-subtypingⁿ; <:-impl-<:ᵒ)
open import Properties.Functions using (_∘_)
open import Properties.Subtyping using (<:-refl; <:-trans; <:-trans-≮:; ≮:-trans-<:; <:-∩-left; <:-∩-right; <:-∩-glb; <:-impl-¬≮:; <:-unknown; <:-function; function-≮:-never; <:-never; unknown-≮:-function; scalar-≮:-function; ≮:--right; scalar-≮:-never; <:--left; <:--right)
open import Properties.TypeNormalization using (Normal; FunType; normal; _⇒_; _∩_; __; never; unknown; <:-normalize; normalize-<:; fun-≮:-never; unknown-≮:-fun; scalar-≮:-fun)
open import Properties.TypeSaturation using (Overloads; Saturated; _⊆ᵒ_; _<:ᵒ_; normal-saturate; saturated; <:-saturate; saturate-<:; defn; here; left; right)
-- The domain of a normalized type
srcⁿ : Type Type
srcⁿ (S T) = S
srcⁿ (S T) = srcⁿ S srcⁿ T
srcⁿ never = unknown
srcⁿ T = never
-- To get the domain of a type, we normalize it first We need to do
-- this, since if we try to use it on non-normalized types, we get
--
-- src(number ∩ string) = src(number) src(string) = never never
-- src(never) = unknown
--
-- so src doesn't respect type equivalence.
src : Type Type
src (S T) = S
src T = srcⁿ(normalize T)
-- Calculate the result of applying a function type `F` to an argument type `V`.
-- We do this by finding an overload of `F` that has the most precise type,
-- that is an overload `(Sʳ ⇒ Tʳ)` where `V <: Sʳ` and moreover
-- for any other such overload `(S ⇒ T)` we have that `Tʳ <: T`.
-- For example if `F` is `(number -> number) & (nil -> nil) & (number? -> number?)`
-- then to resolve `F` with argument type `number`, we pick the `number -> number`
-- overload, but if the argument is `number?`, we pick `number? -> number?`./
-- Not all types have such a most precise overload, but saturated ones do.
data ResolvedTo F G V : Set where
yes :
Overloads F ( )
(V <: )
( {S T} Overloads G (S T) (V <: S) ( <: T))
--------------------------------------------
ResolvedTo F G V
no :
( {S T} Overloads G (S T) (V ≮: S))
--------------------------------------------
ResolvedTo F G V
Resolved : Type Type Set
Resolved F V = ResolvedTo F F V
target : {F V} Resolved F V Type
target (yes _ T _ _ _) = T
target (no _) = unknown
-- We can resolve any saturated function type
resolveˢ : {F G V} FunType G Saturated F Normal V (G ⊆ᵒ F) ResolvedTo F G V
resolveˢ (Sⁿ Tⁿ) (defn sat-∩ sat-) Vⁿ G⊆F with dec-subtypingⁿ Vⁿ Sⁿ
resolveˢ (Sⁿ Tⁿ) (defn sat-∩ sat-) Vⁿ G⊆F | Left V≮:S = no (λ { here V≮:S })
resolveˢ (Sⁿ Tⁿ) (defn sat-∩ sat-) Vⁿ G⊆F | Right V<:S = yes _ _ (G⊆F here) V<:S (λ { here _ <:-refl })
resolveˢ (Gᶠ Hᶠ) (defn sat-∩ sat-) Vⁿ G⊆F with resolveˢ Gᶠ (defn sat-∩ sat-) Vⁿ (G⊆F left) | resolveˢ Hᶠ (defn sat-∩ sat-) Vⁿ (G⊆F right)
resolveˢ (Gᶠ Hᶠ) (defn sat-∩ sat-) Vⁿ G⊆F | yes S₁ T₁ o₁ V<:S₁ tgt₁ | yes S₂ T₂ o₂ V<:S₂ tgt₂ with sat-∩ o₁ o₂
resolveˢ (Gᶠ Hᶠ) (defn sat-∩ sat-) Vⁿ G⊆F | yes S₁ T₁ o₁ V<:S₁ tgt₁ | yes S₂ T₂ o₂ V<:S₂ tgt₂ | defn o p₁ p₂ =
yes _ _ o (<:-trans (<:-∩-glb V<:S₁ V<:S₂) p₁) (λ { (left o) p <:-trans p₂ (<:-trans <:-∩-left (tgt₁ o p)) ; (right o) p <:-trans p₂ (<:-trans <:-∩-right (tgt₂ o p)) })
resolveˢ (Gᶠ Hᶠ) (defn sat-∩ sat-) Vⁿ G⊆F | yes S₁ T₁ o₁ V<:S₁ tgt₁ | no src₂ =
yes _ _ o₁ V<:S₁ (λ { (left o) p tgt₁ o p ; (right o) p CONTRADICTION (<:-impl-¬≮: p (src₂ o)) })
resolveˢ (Gᶠ Hᶠ) (defn sat-∩ sat-) Vⁿ G⊆F | no src₁ | yes S₂ T₂ o₂ V<:S₂ tgt₂ =
yes _ _ o₂ V<:S₂ (λ { (left o) p CONTRADICTION (<:-impl-¬≮: p (src₁ o)) ; (right o) p tgt₂ o p })
resolveˢ (Gᶠ Hᶠ) (defn sat-∩ sat-) Vⁿ G⊆F | no src₁ | no src₂ =
no (λ { (left o) src₁ o ; (right o) src₂ o })
-- Which means we can resolve any normalized type, by saturating it first
resolveᶠ : {F V} FunType F Normal V Type
resolveᶠ Fᶠ Vⁿ = target (resolveˢ (normal-saturate Fᶠ) (saturated Fᶠ) Vⁿ (λ o o))
resolveⁿ : {F V} Normal F Normal V Type
resolveⁿ (Sⁿ Tⁿ) Vⁿ = resolveᶠ (Sⁿ Tⁿ) Vⁿ
resolveⁿ (Fᶠ Gᶠ) Vⁿ = resolveᶠ (Fᶠ Gᶠ) Vⁿ
resolveⁿ (Sⁿ ) Vⁿ = unknown
resolveⁿ unknown Vⁿ = unknown
resolveⁿ never Vⁿ = never
-- Which means we can resolve any type, by normalizing it first
resolve : Type Type Type
resolve F V = resolveⁿ (normal F) (normal V)

View file

@ -5,8 +5,8 @@ module Luau.StrictMode where
open import Agda.Builtin.Equality using (_≡_)
open import FFI.Data.Maybe using (just; nothing)
open import Luau.Syntax using (Expr; Stat; Block; BinaryOperator; yes; nil; addr; var; binexp; var_∈_; _⟨_⟩∈_; function_is_end; _$_; block_is_end; local_←_; _∙_; done; return; name; +; -; *; /; <; >; <=; >=; ··)
open import Luau.FunctionTypes using (src; tgt)
open import Luau.Type using (Type; nil; number; string; boolean; _⇒_; __; _∩_)
open import Luau.ResolveOverloads using (src; resolve)
open import Luau.Subtyping using (_≮:_)
open import Luau.Heap using (Heap; function_is_end) renaming (_[_] to _[_]ᴴ)
open import Luau.VarCtxt using (VarCtxt; ∅; _⋒_; _↦_; _⊕_↦_; _⊝_) renaming (_[_] to _[_]ⱽ)

View file

@ -4,7 +4,7 @@ module Luau.StrictMode.ToString where
open import Agda.Builtin.Nat using (Nat; suc)
open import FFI.Data.String using (String; _++_)
open import Luau.Subtyping using (_≮:_; Tree; witness; scalar; function; function-ok; function-err)
open import Luau.Subtyping using (_≮:_; Tree; witness; scalar; function; function-ok; function-err; function-tgt)
open import Luau.StrictMode using (Warningᴱ; Warningᴮ; UnallocatedAddress; UnboundVariable; FunctionCallMismatch; FunctionDefnMismatch; BlockMismatch; app₁; app₂; BinOpMismatch₁; BinOpMismatch₂; bin₁; bin₂; block₁; return; LocalVarMismatch; local₁; local₂; function₁; function₂; heap; expr; block; addr)
open import Luau.Syntax using (Expr; val; yes; var; var_∈_; _⟨_⟩∈_; _$_; addr; number; binexp; nil; function_is_end; block_is_end; done; return; local_←_; _∙_; fun; arg; name)
open import Luau.Type using (number; boolean; string; nil)
@ -27,8 +27,9 @@ treeToString (scalar boolean) n v = v ++ " is a boolean"
treeToString (scalar string) n v = v ++ " is a string"
treeToString (scalar nil) n v = v ++ " is nil"
treeToString function n v = v ++ " is a function"
treeToString (function-ok t) n v = treeToString t n (v ++ "()")
treeToString (function-ok s t) n v = treeToString t (suc n) (v ++ "(" ++ w ++ ")") ++ " when\n " ++ treeToString s (suc n) w where w = tmp n
treeToString (function-err t) n v = v ++ "(" ++ w ++ ") can error when\n " ++ treeToString t (suc n) w where w = tmp n
treeToString (function-tgt t) n v = treeToString t n (v ++ "()")
subtypeWarningToString : {T U} (T ≮: U) String
subtypeWarningToString (witness t p q) = "\n because provided type contains v, where " ++ treeToString t 0 "v"

View file

@ -13,8 +13,9 @@ data Tree : Set where
scalar : {T} Scalar T Tree
function : Tree
function-ok : Tree Tree
function-ok : Tree Tree Tree
function-err : Tree Tree
function-tgt : Tree Tree
data Language : Type Tree Set
data ¬Language : Type Tree Set
@ -23,8 +24,10 @@ data Language where
scalar : {T} (s : Scalar T) Language T (scalar s)
function : {T U} Language (T U) function
function-ok : {T U u} (Language U u) Language (T U) (function-ok u)
function-ok₁ : {T U t u} (¬Language T t) Language (T U) (function-ok t u)
function-ok₂ : {T U t u} (Language U u) Language (T U) (function-ok t u)
function-err : {T U t} (¬Language T t) Language (T U) (function-err t)
function-tgt : {T U t} (Language U t) Language (T U) (function-tgt t)
left : {T U t} Language T t Language (T U) t
right : {T U u} Language U u Language (T U) u
_,_ : {T U t} Language T t Language U t Language (T U) t
@ -34,11 +37,13 @@ data ¬Language where
scalar-scalar : {S T} (s : Scalar S) (Scalar T) (S T) ¬Language T (scalar s)
scalar-function : {S} (Scalar S) ¬Language S function
scalar-function-ok : {S u} (Scalar S) ¬Language S (function-ok u)
scalar-function-ok : {S t u} (Scalar S) ¬Language S (function-ok t u)
scalar-function-err : {S t} (Scalar S) ¬Language S (function-err t)
scalar-function-tgt : {S t} (Scalar S) ¬Language S (function-tgt t)
function-scalar : {S T U} (s : Scalar S) ¬Language (T U) (scalar s)
function-ok : {T U u} (¬Language U u) ¬Language (T U) (function-ok u)
function-ok : {T U t u} (Language T t) (¬Language U u) ¬Language (T U) (function-ok t u)
function-err : {T U t} (Language T t) ¬Language (T U) (function-err t)
function-tgt : {T U t} (¬Language U t) ¬Language (T U) (function-tgt t)
_,_ : {T U t} ¬Language T t ¬Language U t ¬Language (T U) t
left : {T U t} ¬Language T t ¬Language (T U) t
right : {T U u} ¬Language U u ¬Language (T U) u

View file

@ -3,16 +3,18 @@
module Luau.TypeCheck where
open import Agda.Builtin.Equality using (_≡_)
open import FFI.Data.Either using (Either; Left; Right)
open import FFI.Data.Maybe using (Maybe; just)
open import Luau.ResolveOverloads using (resolve)
open import Luau.Syntax using (Expr; Stat; Block; BinaryOperator; yes; nil; addr; number; bool; string; val; var; var_∈_; _⟨_⟩∈_; function_is_end; _$_; block_is_end; binexp; local_←_; _∙_; done; return; name; +; -; *; /; <; >; ==; ~=; <=; >=; ··)
open import Luau.Var using (Var)
open import Luau.Addr using (Addr)
open import Luau.FunctionTypes using (src; tgt)
open import Luau.Heap using (Heap; Object; function_is_end) renaming (_[_] to _[_]ᴴ)
open import Luau.Type using (Type; nil; unknown; number; boolean; string; _⇒_)
open import Luau.VarCtxt using (VarCtxt; ∅; _⋒_; _↦_; _⊕_↦_; _⊝_) renaming (_[_] to _[_]ⱽ)
open import FFI.Data.Vector using (Vector)
open import FFI.Data.Maybe using (Maybe; just; nothing)
open import Properties.DecSubtyping using (dec-subtyping)
open import Properties.Product using (_×_; _,_)
orUnknown : Maybe Type Type
@ -113,8 +115,8 @@ data _⊢ᴱ_∈_ where
Γ ⊢ᴱ M T
Γ ⊢ᴱ N U
----------------------
Γ ⊢ᴱ (M $ N) (tgt T)
----------------------------
Γ ⊢ᴱ (M $ N) (resolve T U)
function : {f x B T U V Γ}

View file

@ -2,11 +2,7 @@ module Luau.TypeNormalization where
open import Luau.Type using (Type; nil; number; string; boolean; never; unknown; _⇒_; __; _∩_)
-- The top non-function type
¬function : Type
¬function = number (string (nil boolean))
-- Unions and intersections of normalized types
-- Operations on normalized types
_ᶠ_ : Type Type Type
_ⁿˢ_ : Type Type Type
_∩ⁿˢ_ : Type Type Type
@ -23,8 +19,8 @@ F ∪ᶠ G = F G
S ∪ⁿ (T₁ T₂) = (S ∪ⁿ T₁) T₂
S ∪ⁿ unknown = unknown
S ∪ⁿ never = S
unknown ∪ⁿ T = unknown
never ∪ⁿ T = T
unknown ∪ⁿ T = unknown
(S₁ S₂) ∪ⁿ G = (S₁ ∪ⁿ G) S₂
F ∪ⁿ G = F ∪ᶠ G

View file

@ -0,0 +1,66 @@
module Luau.TypeSaturation where
open import Luau.Type using (Type; _⇒_; _∩_; __)
open import Luau.TypeNormalization using (_ⁿ_; _∩ⁿ_)
-- So, there's a problem with overloaded functions
-- (of the form (S_1 ⇒ T_1) ∩⋯∩ (S_n ⇒ T_n))
-- which is that it's not good enough to compare them
-- for subtyping by comparing all of their overloads.
-- For example (nil → nil) is a subtype of (number? → number?) ∩ (string? → string?)
-- but not a subtype of any of its overloads.
-- To fix this, we adapt the semantic subtyping algorithm for
-- function types, given in
-- https://www.irif.fr/~gc/papers/covcon-again.pdf and
-- https://pnwamk.github.io/sst-tutorial/
-- A function type is *intersection-saturated* if for any overloads
-- (S₁ ⇒ T₁) and (S₂ ⇒ T₂), there exists an overload which is a subtype
-- of ((S₁ ∩ S₂) ⇒ (T₁ ∩ T₂)).
-- A function type is *union-saturated* if for any overloads
-- (S₁ ⇒ T₁) and (S₂ ⇒ T₂), there exists an overload which is a subtype
-- of ((S₁ S₂) ⇒ (T₁ T₂)).
-- A function type is *saturated* if it is both intersection- and
-- union-saturated.
-- For example (number? → number?) ∩ (string? → string?)
-- is not saturated, but (number? → number?) ∩ (string? → string?) ∩ (nil → nil) ∩ ((number string)? → (number string)?)
-- is.
-- Saturated function types have the nice property that they can ber
-- compared by just comparing their overloads: F <: G whenever for any
-- overload of G, there is an overload os F which is a subtype of it.
-- Forunately every function type can be saturated!
_⋓_ : Type Type Type
(S₁ T₁) (S₂ T₂) = (S₁ ∪ⁿ S₂) (T₁ ∪ⁿ T₂)
(F₁ G₁) F₂ = (F₁ F₂) (G₁ F₂)
F₁ (F₂ G₂) = (F₁ F₂) (F₁ G₂)
F G = F G
_⋒_ : Type Type Type
(S₁ T₁) (S₂ T₂) = (S₁ ∩ⁿ S₂) (T₁ ∩ⁿ T₂)
(F₁ G₁) F₂ = (F₁ F₂) (G₁ F₂)
F₁ (F₂ G₂) = (F₁ F₂) (F₁ G₂)
F G = F G
_∩ᵘ_ : Type Type Type
F ∩ᵘ G = (F G) (F G)
_∩ⁱ_ : Type Type Type
F ∩ⁱ G = (F G) (F G)
-saturate : Type Type
-saturate (F G) = (-saturate F ∩ᵘ -saturate G)
-saturate F = F
∩-saturate : Type Type
∩-saturate (F G) = (∩-saturate F ∩ⁱ ∩-saturate G)
∩-saturate F = F
saturate : Type Type
saturate F = -saturate (∩-saturate F)

View file

@ -7,7 +7,6 @@ import Properties.Dec
import Properties.DecSubtyping
import Properties.Equality
import Properties.Functions
import Properties.FunctionTypes
import Properties.Remember
import Properties.Step
import Properties.StrictMode

View file

@ -4,21 +4,23 @@ module Properties.DecSubtyping where
open import Agda.Builtin.Equality using (_≡_; refl)
open import FFI.Data.Either using (Either; Left; Right; mapLR; swapLR; cond)
open import Luau.FunctionTypes using (src; srcⁿ; tgt)
open import Luau.Subtyping using (_<:_; _≮:_; Tree; Language; ¬Language; witness; unknown; never; scalar; function; scalar-function; scalar-function-ok; scalar-function-err; scalar-scalar; function-scalar; function-ok; function-err; left; right; _,_)
open import Luau.Subtyping using (_<:_; _≮:_; Tree; Language; ¬Language; witness; unknown; never; scalar; function; scalar-function; scalar-function-ok; scalar-function-err; scalar-function-tgt; scalar-scalar; function-scalar; function-ok; function-ok₁; function-ok₂; function-err; function-tgt; left; right; _,_)
open import Luau.Type using (Type; Scalar; nil; number; string; boolean; never; unknown; _⇒_; __; _∩_)
open import Luau.TypeNormalization using (_ⁿ_; _∩ⁿ_)
open import Luau.TypeSaturation using (saturate)
open import Properties.Contradiction using (CONTRADICTION; ¬)
open import Properties.Functions using (_∘_)
open import Properties.Subtyping using (<:-refl; <:-trans; ≮:-trans-<:; <:-trans-≮:; <:-never; <:-unknown; <:--left; <:--right; <:--lub; ≮:--left; ≮:--right; <:-∩-left; <:-∩-right; <:-∩-glb; ≮:-∩-left; ≮:-∩-right; dec-language; scalar-<:; <:-everything; <:-function; ≮:-function-left; ≮:-function-right)
open import Properties.TypeNormalization using (FunType; Normal; never; unknown; _∩_; __; _⇒_; normal; <:-normalize; normalize-<:)
open import Properties.FunctionTypes using (fun-¬scalar; ¬fun-scalar; fun-function; src-unknown-≮:; tgt-never-≮:; src-tgtᶠ-<:)
open import Properties.Subtyping using (<:-refl; <:-trans; ≮:-trans-<:; <:-trans-≮:; <:-never; <:-unknown; <:--left; <:--right; <:--lub; ≮:--left; ≮:--right; <:-∩-left; <:-∩-right; <:-∩-glb; ≮:-∩-left; ≮:-∩-right; dec-language; scalar-<:; <:-everything; <:-function; ≮:-function-left; ≮:-function-right; <:-impl-¬≮:; <:-intersect; <:-function-∩-; <:-function-∩; <:-union; ≮:-left-; ≮:-right-; <:-∩-distr-; <:-impl-⊇; language-comp)
open import Properties.TypeNormalization using (FunType; Normal; never; unknown; _∩_; __; _⇒_; normal; <:-normalize; normalize-<:; normal-∩ⁿ; normal-∪ⁿ; -<:-∪ⁿ; ∪ⁿ-<:-; ∩ⁿ-<:-∩; ∩-<:-∩ⁿ; normalᶠ; fun-top; fun-function; fun-¬scalar)
open import Properties.TypeSaturation using (Overloads; Saturated; _⊆ᵒ_; _<:ᵒ_; defn; here; left; right; ov-language; ov-<:; saturated; normal-saturate; normal-overload-src; normal-overload-tgt; saturate-<:; <:-saturate; <:ᵒ-impl-<:; _>>=ˡ_; _>>=ʳ_)
open import Properties.Equality using (_≢_)
-- Honest this terminates, since src and tgt reduce the depth of nested arrows
-- Honest this terminates, since saturation maintains the depth of nested arrows
{-# TERMINATING #-}
dec-subtypingˢⁿ : {T U} Scalar T Normal U Either (T ≮: U) (T <: U)
dec-subtypingᶠ : {T U} FunType T FunType U Either (T ≮: U) (T <: U)
dec-subtypingᶠⁿ : {T U} FunType T Normal U Either (T ≮: U) (T <: U)
dec-subtypingˢᶠ : {F G} FunType F Saturated F FunType G Either (F ≮: G) (F <:ᵒ G)
dec-subtypingᶠ : {F G} FunType F FunType G Either (F ≮: G) (F <: G)
dec-subtypingᶠⁿ : {F U} FunType F Normal U Either (F ≮: U) (F <: U)
dec-subtypingⁿ : {T U} Normal T Normal U Either (T ≮: U) (T <: U)
dec-subtyping : T U Either (T ≮: U) (T <: U)
@ -26,22 +28,116 @@ dec-subtypingˢⁿ T U with dec-language _ (scalar T)
dec-subtypingˢⁿ T U | Left p = Left (witness (scalar T) (scalar T) p)
dec-subtypingˢⁿ T U | Right p = Right (scalar-<: T p)
dec-subtypingᶠ {T = T} _ (U V) with dec-subtypingⁿ U (normal (src T)) | dec-subtypingⁿ (normal (tgt T)) V
dec-subtypingᶠ {T = T} _ (U V) | Left p | q = Left (≮:-trans-<: (src-unknown-≮: (≮:-trans-<: p (<:-normalize (src T)))) (<:-function <:-refl <:-unknown))
dec-subtypingᶠ {T = T} _ (U V) | Right p | Left q = Left (≮:-trans-<: (tgt-never-≮: (<:-trans-≮: (normalize-<: (tgt T)) q)) (<:-trans (<:-function <:-never <:-refl) <:--right))
dec-subtypingᶠ T (U V) | Right p | Right q = Right (src-tgtᶠ-<: T (<:-trans p (normalize-<: _)) (<:-trans (<:-normalize _) q))
dec-subtypingˢᶠ {F} {S T} Fᶠ (defn sat-∩ sat-) (Sⁿ Tⁿ) = result (top Fᶠ (λ o o)) where
dec-subtypingᶠ T (U V) with dec-subtypingᶠ T U | dec-subtypingᶠ T V
dec-subtypingᶠ T (U V) | Left p | q = Left (≮:-∩-left p)
dec-subtypingᶠ T (U V) | Right p | Left q = Left (≮:-∩-right q)
dec-subtypingᶠ T (U V) | Right p | Right q = Right (<:-∩-glb p q)
data Top G : Set where
defn : Sᵗ Tᵗ
Overloads F (Sᵗ Tᵗ)
( {S T} Overloads G (S T) (S <: Sᵗ))
-------------
Top G
top : {G} (FunType G) (G ⊆ᵒ F) Top G
top {S T} _ G⊆F = defn S T (G⊆F here) (λ { here <:-refl })
top (Gᶠ Hᶠ) G⊆F with top Gᶠ (G⊆F left) | top Hᶠ (G⊆F right)
top (Gᶠ Hᶠ) G⊆F | defn Rᵗ Sᵗ p p₁ | defn Tᵗ Uᵗ q q₁ with sat- p q
top (Gᶠ Hᶠ) G⊆F | defn Rᵗ Sᵗ p p₁ | defn Tᵗ Uᵗ q q₁ | defn n r r₁ = defn _ _ n
(λ { (left o) <:-trans (<:-trans (p₁ o) <:--left) r ; (right o) <:-trans (<:-trans (q₁ o) <:--right) r })
result : Top F Either (F ≮: (S T)) (F <:ᵒ (S T))
result (defn Sᵗ Tᵗ oᵗ srcᵗ) with dec-subtypingⁿ Sⁿ (normal-overload-src Fᶠ oᵗ)
result (defn Sᵗ Tᵗ oᵗ srcᵗ) | Left (witness s Ss ¬Sᵗs) = Left (witness (function-err s) (ov-language Fᶠ (λ o function-err (<:-impl-⊇ (srcᵗ o) s ¬Sᵗs))) (function-err Ss))
result (defn Sᵗ Tᵗ oᵗ srcᵗ) | Right S<:Sᵗ = result₀ (largest Fᶠ (λ o o)) where
data LargestSrc (G : Type) : Set where
yes : S₀ T₀
Overloads F (S₀ T₀)
T₀ <: T
( {S T} Overloads G (S T) T <: T (S <: S₀))
-----------------------
LargestSrc G
no : S₀ T₀
Overloads F (S₀ T₀)
T₀ ≮: T
( {S T} Overloads G (S T) T₀ <: T)
-----------------------
LargestSrc G
largest : {G} (FunType G) (G ⊆ᵒ F) LargestSrc G
largest {S T} (S T) G⊆F with dec-subtypingⁿ T Tⁿ
largest {S T} (S T) G⊆F | Left T≮:T = no S T (G⊆F here) T≮:T λ { here <:-refl }
largest {S T} (S T) G⊆F | Right T<:T = yes S T (G⊆F here) T<:T (λ { here _ <:-refl })
largest (Gᶠ Hᶠ) GH⊆F with largest Gᶠ (GH⊆F left) | largest Hᶠ (GH⊆F right)
largest (Gᶠ Hᶠ) GH⊆F | no S₁ T₁ o₁ T₁≮:T tgt₁ | no S₂ T₂ o₂ T₂≮:T tgt₂ with sat-∩ o₁ o₂
largest (Gᶠ Hᶠ) GH⊆F | no S₁ T₁ o₁ T₁≮:T tgt₁ | no S₂ T₂ o₂ T₂≮:T tgt₂ | defn o src tgt with dec-subtypingⁿ (normal-overload-tgt Fᶠ o) Tⁿ
largest (Gᶠ Hᶠ) GH⊆F | no S₁ T₁ o₁ T₁≮:T tgt₁ | no S₂ T₂ o₂ T₂≮:T tgt₂ | defn o src tgt | Left T₀≮:T = no _ _ o T₀≮:T (λ { (left o) <:-trans tgt (<:-trans <:-∩-left (tgt₁ o)) ; (right o) <:-trans tgt (<:-trans <:-∩-right (tgt₂ o)) })
largest (Gᶠ Hᶠ) GH⊆F | no S₁ T₁ o₁ T₁≮:T tgt₁ | no S₂ T₂ o₂ T₂≮:T tgt₂ | defn o src tgt | Right T₀<:T = yes _ _ o T₀<:T (λ { (left o) p CONTRADICTION (<:-impl-¬≮: p (<:-trans-≮: (tgt₁ o) T₁≮:T)) ; (right o) p CONTRADICTION (<:-impl-¬≮: p (<:-trans-≮: (tgt₂ o) T₂≮:T)) })
largest (Gᶠ Hᶠ) GH⊆F | no S₁ T₁ o₁ T₁≮:T tgt₁ | yes S₂ T₂ o₂ T₂<:T src₂ = yes S₂ T₂ o₂ T₂<:T (λ { (left o) p CONTRADICTION (<:-impl-¬≮: p (<:-trans-≮: (tgt₁ o) T₁≮:T)) ; (right o) p src₂ o p })
largest (Gᶠ Hᶠ) GH⊆F | yes S₁ T₁ o₁ T₁<:T src₁ | no S₂ T₂ o₂ T₂≮:T tgt₂ = yes S₁ T₁ o₁ T₁<:T (λ { (left o) p src₁ o p ; (right o) p CONTRADICTION (<:-impl-¬≮: p (<:-trans-≮: (tgt₂ o) T₂≮:T)) })
largest (Gᶠ Hᶠ) GH⊆F | yes S₁ T₁ o₁ T₁<:T src₁ | yes S₂ T₂ o₂ T₂<:T src₂ with sat- o₁ o₂
largest (Gᶠ Hᶠ) GH⊆F | yes S₁ T₁ o₁ T₁<:T src₁ | yes S₂ T₂ o₂ T₂<:T src₂ | defn o src tgt = yes _ _ o (<:-trans tgt (<:--lub T₁<:T T₂<:T))
(λ { (left o) T<:T <:-trans (src₁ o T<:T) (<:-trans <:--left src)
; (right o) T<:T <:-trans (src₂ o T<:T) (<:-trans <:--right src)
})
result₀ : LargestSrc F Either (F ≮: (S T)) (F <:ᵒ (S T))
result₀ (no S₀ T₀ o₀ (witness t T₀t ¬Tt) tgt₀) = Left (witness (function-tgt t) (ov-language Fᶠ (λ o function-tgt (tgt₀ o t T₀t))) (function-tgt ¬Tt))
result₀ (yes S₀ T₀ o₀ T₀<:T src₀) with dec-subtypingⁿ Sⁿ (normal-overload-src Fᶠ o₀)
result₀ (yes S₀ T₀ o₀ T₀<:T src₀) | Right S<:S₀ = Right λ { here defn o₀ S<:S₀ T₀<:T }
result₀ (yes S₀ T₀ o₀ T₀<:T src₀) | Left (witness s Ss ¬S₀s) = Left (result₁ (smallest Fᶠ (λ o o))) where
data SmallestTgt (G : Type) : Set where
defn : S₁ T₁
Overloads F (S₁ T₁)
Language S₁ s
( {S T} Overloads G (S T) Language S s (T₁ <: T))
-----------------------
SmallestTgt G
smallest : {G} (FunType G) (G ⊆ᵒ F) SmallestTgt G
smallest {S T} _ G⊆F with dec-language S s
smallest {S T} _ G⊆F | Left ¬Ss = defn Sᵗ Tᵗ oᵗ (S<:Sᵗ s Ss) λ { here Ss CONTRADICTION (language-comp s ¬Ss Ss) }
smallest {S T} _ G⊆F | Right Ss = defn S T (G⊆F here) Ss (λ { here _ <:-refl })
smallest (Gᶠ Hᶠ) GH⊆F with smallest Gᶠ (GH⊆F left) | smallest Hᶠ (GH⊆F right)
smallest (Gᶠ Hᶠ) GH⊆F | defn S₁ T₁ o₁ R₁s tgt₁ | defn S₂ T₂ o₂ R₂s tgt₂ with sat-∩ o₁ o₂
smallest (Gᶠ Hᶠ) GH⊆F | defn S₁ T₁ o₁ R₁s tgt₁ | defn S₂ T₂ o₂ R₂s tgt₂ | defn o src tgt = defn _ _ o (src s (R₁s , R₂s))
(λ { (left o) Ss <:-trans (<:-trans tgt <:-∩-left) (tgt₁ o Ss)
; (right o) Ss <:-trans (<:-trans tgt <:-∩-right) (tgt₂ o Ss)
})
result₁ : SmallestTgt F (F ≮: (S T))
result₁ (defn S₁ T₁ o₁ S₁s tgt₁) with dec-subtypingⁿ (normal-overload-tgt Fᶠ o₁) Tⁿ
result₁ (defn S₁ T₁ o₁ S₁s tgt₁) | Right T₁<:T = CONTRADICTION (language-comp s ¬S₀s (src₀ o₁ T₁<:T s S₁s))
result₁ (defn S₁ T₁ o₁ S₁s tgt₁) | Left (witness t T₁t ¬Tt) = witness (function-ok s t) (ov-language Fᶠ lemma) (function-ok Ss ¬Tt) where
lemma : {S T} Overloads F (S T) Language (S T) (function-ok s t)
lemma {S} o with dec-language S s
lemma {S} o | Left ¬Ss = function-ok₁ ¬Ss
lemma {S} o | Right Ss = function-ok₂ (tgt₁ o Ss t T₁t)
dec-subtypingˢᶠ F (G H) with dec-subtypingˢᶠ F G | dec-subtypingˢᶠ F H
dec-subtypingˢᶠ F (G H) | Left F≮:G | _ = Left (≮:-∩-left F≮:G)
dec-subtypingˢᶠ F (G H) | _ | Left F≮:H = Left (≮:-∩-right F≮:H)
dec-subtypingˢᶠ F (G H) | Right F<:G | Right F<:H = Right (λ { (left o) F<:G o ; (right o) F<:H o })
dec-subtypingᶠ F G with dec-subtypingˢᶠ (normal-saturate F) (saturated F) G
dec-subtypingᶠ F G | Left H≮:G = Left (<:-trans-≮: (saturate-<: F) H≮:G)
dec-subtypingᶠ F G | Right H<:G = Right (<:-trans (<:-saturate F) (<:ᵒ-impl-<: (normal-saturate F) G H<:G))
dec-subtypingᶠⁿ T never = Left (witness function (fun-function T) never)
dec-subtypingᶠⁿ T unknown = Right <:-unknown
dec-subtypingᶠⁿ T (U V) = dec-subtypingᶠ T (U V)
dec-subtypingᶠⁿ T (U V) = dec-subtypingᶠ T (U V)
dec-subtypingᶠⁿ T (U V) with dec-subtypingᶠⁿ T U
dec-subtypingᶠⁿ T (U V) | Left (witness t p q) = Left (witness t p (q , ¬fun-scalar V T p))
dec-subtypingᶠⁿ T (U V) | Left (witness t p q) = Left (witness t p (q , fun-¬scalar V T p))
dec-subtypingᶠⁿ T (U V) | Right p = Right (<:-trans p <:--left)
dec-subtypingⁿ never U = Right <:-never
@ -68,3 +164,11 @@ dec-subtyping T U with dec-subtypingⁿ (normal T) (normal U)
dec-subtyping T U | Left p = Left (<:-trans-≮: (normalize-<: T) (≮:-trans-<: p (<:-normalize U)))
dec-subtyping T U | Right p = Right (<:-trans (<:-normalize T) (<:-trans p (normalize-<: U)))
-- As a corollary, for saturated functions
-- <:ᵒ coincides with <:, that is F is a subtype of (S ⇒ T) precisely
-- when one of its overloads is.
<:-impl-<:ᵒ : {F G} FunType F Saturated F FunType G (F <: G) (F <:ᵒ G)
<:-impl-<: {F} {G} Fᶠ Gᶠ F<:G with dec-subtypingˢᶠ Fᶠ Gᶠ
<:-impl-<: {F} {G} Fᶠ Gᶠ F<:G | Left F≮:G = CONTRADICTION (<:-impl-¬≮: F<:G F≮:G)
<:-impl-<: {F} {G} Fᶠ Gᶠ F<:G | Right F<:ᵒG = F<:ᵒG

View file

@ -1,150 +0,0 @@
{-# OPTIONS --rewriting #-}
module Properties.FunctionTypes where
open import FFI.Data.Either using (Either; Left; Right; mapLR; swapLR; cond)
open import Luau.FunctionTypes using (srcⁿ; src; tgt)
open import Luau.Subtyping using (_<:_; _≮:_; Tree; Language; ¬Language; witness; unknown; never; scalar; function; scalar-function; scalar-function-ok; scalar-function-err; scalar-scalar; function-scalar; function-ok; function-err; left; right; _,_)
open import Luau.Type using (Type; Scalar; nil; number; string; boolean; never; unknown; _⇒_; __; _∩_; skalar)
open import Properties.Contradiction using (CONTRADICTION; ¬; )
open import Properties.Functions using (_∘_)
open import Properties.Subtyping using (<:-refl; ≮:-refl; <:-trans-≮:; skalar-scalar; <:-impl-⊇; skalar-function-ok; language-comp)
open import Properties.TypeNormalization using (FunType; Normal; never; unknown; _∩_; __; _⇒_; normal; <:-normalize; normalize-<:)
-- Properties of src
function-err-srcⁿ : {T t} (FunType T) (¬Language (srcⁿ T) t) Language T (function-err t)
function-err-srcⁿ (S T) p = function-err p
function-err-srcⁿ (S T) (p₁ , p₂) = (function-err-srcⁿ S p₁ , function-err-srcⁿ T p₂)
¬function-err-srcᶠ : {T t} (FunType T) (Language (srcⁿ T) t) ¬Language T (function-err t)
¬function-err-srcᶠ (S T) p = function-err p
¬function-err-srcᶠ (S T) (left p) = left (¬function-err-srcᶠ S p)
¬function-err-srcᶠ (S T) (right p) = right (¬function-err-srcᶠ T p)
¬function-err-srcⁿ : {T t} (Normal T) (Language (srcⁿ T) t) ¬Language T (function-err t)
¬function-err-srcⁿ never p = never
¬function-err-srcⁿ unknown (scalar ())
¬function-err-srcⁿ (S T) p = function-err p
¬function-err-srcⁿ (S T) (left p) = left (¬function-err-srcᶠ S p)
¬function-err-srcⁿ (S T) (right p) = right (¬function-err-srcᶠ T p)
¬function-err-srcⁿ (S T) (scalar ())
¬function-err-src : {T t} (Language (src T) t) ¬Language T (function-err t)
¬function-err-src {T = S T} p = function-err p
¬function-err-src {T = nil} p = scalar-function-err nil
¬function-err-src {T = never} p = never
¬function-err-src {T = unknown} (scalar ())
¬function-err-src {T = boolean} p = scalar-function-err boolean
¬function-err-src {T = number} p = scalar-function-err number
¬function-err-src {T = string} p = scalar-function-err string
¬function-err-src {T = S T} p = <:-impl-⊇ (<:-normalize (S T)) _ (¬function-err-srcⁿ (normal (S T)) p)
¬function-err-src {T = S T} p = <:-impl-⊇ (<:-normalize (S T)) _ (¬function-err-srcⁿ (normal (S T)) p)
src-¬function-errᶠ : {T t} (FunType T) Language T (function-err t) (¬Language (srcⁿ T) t)
src-¬function-errᶠ (S T) (function-err p) = p
src-¬function-errᶠ (S T) (p₁ , p₂) = (src-¬function-errᶠ S p₁ , src-¬function-errᶠ T p₂)
src-¬function-errⁿ : {T t} (Normal T) Language T (function-err t) (¬Language (srcⁿ T) t)
src-¬function-errⁿ unknown p = never
src-¬function-errⁿ (S T) (function-err p) = p
src-¬function-errⁿ (S T) (p₁ , p₂) = (src-¬function-errᶠ S p₁ , src-¬function-errᶠ T p₂)
src-¬function-errⁿ (S T) p = never
src-¬function-err : {T t} Language T (function-err t) (¬Language (src T) t)
src-¬function-err {T = S T} (function-err p) = p
src-¬function-err {T = unknown} p = never
src-¬function-err {T = S T} p = src-¬function-errⁿ (normal (S T)) (<:-normalize (S T) _ p)
src-¬function-err {T = S T} p = src-¬function-errⁿ (normal (S T)) (<:-normalize (S T) _ p)
fun-¬scalar : {S T} (s : Scalar S) FunType T ¬Language T (scalar s)
fun-¬scalar s (S T) = function-scalar s
fun-¬scalar s (S T) = left (fun-¬scalar s S)
¬fun-scalar : {S T t} (s : Scalar S) FunType T Language T t ¬Language S t
¬fun-scalar s (S T) function = scalar-function s
¬fun-scalar s (S T) (function-ok p) = scalar-function-ok s
¬fun-scalar s (S T) (function-err p) = scalar-function-err s
¬fun-scalar s (S T) (p₁ , p₂) = ¬fun-scalar s T p₂
fun-function : {T} FunType T Language T function
fun-function (S T) = function
fun-function (S T) = (fun-function S , fun-function T)
srcⁿ-¬scalar : {S T t} (s : Scalar S) Normal T Language T (scalar s) (¬Language (srcⁿ T) t)
srcⁿ-¬scalar s never (scalar ())
srcⁿ-¬scalar s unknown p = never
srcⁿ-¬scalar s (S T) (scalar ())
srcⁿ-¬scalar s (S T) (p₁ , p₂) = CONTRADICTION (language-comp (scalar s) (fun-¬scalar s S) p₁)
srcⁿ-¬scalar s (S T) p = never
src-¬scalar : {S T t} (s : Scalar S) Language T (scalar s) (¬Language (src T) t)
src-¬scalar {T = nil} s p = never
src-¬scalar {T = T U} s (scalar ())
src-¬scalar {T = never} s (scalar ())
src-¬scalar {T = unknown} s p = never
src-¬scalar {T = boolean} s p = never
src-¬scalar {T = number} s p = never
src-¬scalar {T = string} s p = never
src-¬scalar {T = T U} s p = srcⁿ-¬scalar s (normal (T U)) (<:-normalize (T U) (scalar s) p)
src-¬scalar {T = T U} s p = srcⁿ-¬scalar s (normal (T U)) (<:-normalize (T U) (scalar s) p)
srcⁿ-unknown-≮: : {T U} (Normal U) (T ≮: srcⁿ U) (U ≮: (T unknown))
srcⁿ-unknown-≮: never (witness t p q) = CONTRADICTION (language-comp t q unknown)
srcⁿ-unknown-≮: unknown (witness t p q) = witness (function-err t) unknown (function-err p)
srcⁿ-unknown-≮: (U V) (witness t p q) = witness (function-err t) (function-err q) (function-err p)
srcⁿ-unknown-≮: (U V) (witness t p q) = witness (function-err t) (function-err-srcⁿ (U V) q) (function-err p)
srcⁿ-unknown-≮: (U V) (witness t p q) = witness (scalar V) (right (scalar V)) (function-scalar V)
src-unknown-≮: : {T U} (T ≮: src U) (U ≮: (T unknown))
src-unknown-≮: {U = nil} (witness t p q) = witness (scalar nil) (scalar nil) (function-scalar nil)
src-unknown-≮: {U = T U} (witness t p q) = witness (function-err t) (function-err q) (function-err p)
src-unknown-≮: {U = never} (witness t p q) = CONTRADICTION (language-comp t q unknown)
src-unknown-≮: {U = unknown} (witness t p q) = witness (function-err t) unknown (function-err p)
src-unknown-≮: {U = boolean} (witness t p q) = witness (scalar boolean) (scalar boolean) (function-scalar boolean)
src-unknown-≮: {U = number} (witness t p q) = witness (scalar number) (scalar number) (function-scalar number)
src-unknown-≮: {U = string} (witness t p q) = witness (scalar string) (scalar string) (function-scalar string)
src-unknown-≮: {U = T U} p = <:-trans-≮: (normalize-<: (T U)) (srcⁿ-unknown-≮: (normal (T U)) p)
src-unknown-≮: {U = T U} p = <:-trans-≮: (normalize-<: (T U)) (srcⁿ-unknown-≮: (normal (T U)) p)
unknown-src-≮: : {S T U} (U ≮: S) (T ≮: (U unknown)) (U ≮: src T)
unknown-src-≮: (witness t x x₁) (witness (scalar s) p (function-scalar s)) = witness t x (src-¬scalar s p)
unknown-src-≮: r (witness (function-ok (scalar s)) p (function-ok (scalar-scalar s () q)))
unknown-src-≮: r (witness (function-ok (function-ok _)) p (function-ok (scalar-function-ok ())))
unknown-src-≮: r (witness (function-err t) p (function-err q)) = witness t q (src-¬function-err p)
-- Properties of tgt
tgt-function-ok : {T t} (Language (tgt T) t) Language T (function-ok t)
tgt-function-ok {T = nil} (scalar ())
tgt-function-ok {T = T₁ T₂} p = function-ok p
tgt-function-ok {T = never} (scalar ())
tgt-function-ok {T = unknown} p = unknown
tgt-function-ok {T = boolean} (scalar ())
tgt-function-ok {T = number} (scalar ())
tgt-function-ok {T = string} (scalar ())
tgt-function-ok {T = T₁ T₂} (left p) = left (tgt-function-ok p)
tgt-function-ok {T = T₁ T₂} (right p) = right (tgt-function-ok p)
tgt-function-ok {T = T₁ T₂} (p₁ , p₂) = (tgt-function-ok p₁ , tgt-function-ok p₂)
function-ok-tgt : {T t} Language T (function-ok t) (Language (tgt T) t)
function-ok-tgt (function-ok p) = p
function-ok-tgt (left p) = left (function-ok-tgt p)
function-ok-tgt (right p) = right (function-ok-tgt p)
function-ok-tgt (p₁ , p₂) = (function-ok-tgt p₁ , function-ok-tgt p₂)
function-ok-tgt unknown = unknown
tgt-never-≮: : {T U} (tgt T ≮: U) (T ≮: (skalar (never U)))
tgt-never-≮: (witness t p q) = witness (function-ok t) (tgt-function-ok p) (skalar-function-ok , function-ok q)
never-tgt-≮: : {T U} (T ≮: (skalar (never U))) (tgt T ≮: U)
never-tgt-≮: (witness (scalar s) p (q₁ , q₂)) = CONTRADICTION (≮:-refl (witness (scalar s) (skalar-scalar s) q₁))
never-tgt-≮: (witness function p (q₁ , scalar-function ()))
never-tgt-≮: (witness (function-ok t) p (q₁ , function-ok q₂)) = witness t (function-ok-tgt p) q₂
never-tgt-≮: (witness (function-err (scalar s)) p (q₁ , function-err (scalar ())))
src-tgtᶠ-<: : {T U V} (FunType T) (U <: src T) (tgt T <: V) (T <: (U V))
src-tgtᶠ-<: T p q (scalar s) r = CONTRADICTION (language-comp (scalar s) (fun-¬scalar s T) r)
src-tgtᶠ-<: T p q function r = function
src-tgtᶠ-<: T p q (function-ok s) r = function-ok (q s (function-ok-tgt r))
src-tgtᶠ-<: T p q (function-err s) r = function-err (<:-impl-⊇ p s (src-¬function-err r))

View file

@ -0,0 +1,189 @@
{-# OPTIONS --rewriting #-}
module Properties.ResolveOverloads where
open import FFI.Data.Either using (Left; Right)
open import Luau.ResolveOverloads using (Resolved; src; srcⁿ; resolve; resolveⁿ; resolveᶠ; resolveˢ; target; yes; no)
open import Luau.Subtyping using (_<:_; _≮:_; Language; ¬Language; witness; scalar; unknown; never; function; function-ok; function-err; function-tgt; function-scalar; function-ok₁; function-ok₂; scalar-scalar; scalar-function; scalar-function-ok; scalar-function-err; scalar-function-tgt; _,_; left; right)
open import Luau.Type using (Type ; Scalar; _⇒_; _∩_; __; nil; boolean; number; string; unknown; never)
open import Luau.TypeSaturation using (saturate)
open import Luau.TypeNormalization using (normalize)
open import Properties.Contradiction using (CONTRADICTION)
open import Properties.DecSubtyping using (dec-subtyping; dec-subtypingⁿ; <:-impl-<:ᵒ)
open import Properties.Functions using (_∘_)
open import Properties.Subtyping using (<:-refl; <:-trans; <:-trans-≮:; ≮:-trans-<:; <:-∩-left; <:-∩-right; <:-∩-glb; <:-impl-¬≮:; <:-unknown; <:-function; function-≮:-never; <:-never; unknown-≮:-function; scalar-≮:-function; ≮:--right; scalar-≮:-never; <:--left; <:--right; <:-impl-⊇; language-comp)
open import Properties.TypeNormalization using (Normal; FunType; normal; _⇒_; _∩_; __; never; unknown; <:-normalize; normalize-<:; fun-≮:-never; unknown-≮:-fun; scalar-≮:-fun)
open import Properties.TypeSaturation using (Overloads; Saturated; _⊆ᵒ_; _<:ᵒ_; normal-saturate; saturated; <:-saturate; saturate-<:; defn; here; left; right)
-- Properties of src
function-err-srcⁿ : {T t} (FunType T) (¬Language (srcⁿ T) t) Language T (function-err t)
function-err-srcⁿ (S T) p = function-err p
function-err-srcⁿ (S T) (p₁ , p₂) = (function-err-srcⁿ S p₁ , function-err-srcⁿ T p₂)
¬function-err-srcᶠ : {T t} (FunType T) (Language (srcⁿ T) t) ¬Language T (function-err t)
¬function-err-srcᶠ (S T) p = function-err p
¬function-err-srcᶠ (S T) (left p) = left (¬function-err-srcᶠ S p)
¬function-err-srcᶠ (S T) (right p) = right (¬function-err-srcᶠ T p)
¬function-err-srcⁿ : {T t} (Normal T) (Language (srcⁿ T) t) ¬Language T (function-err t)
¬function-err-srcⁿ never p = never
¬function-err-srcⁿ unknown (scalar ())
¬function-err-srcⁿ (S T) p = function-err p
¬function-err-srcⁿ (S T) (left p) = left (¬function-err-srcᶠ S p)
¬function-err-srcⁿ (S T) (right p) = right (¬function-err-srcᶠ T p)
¬function-err-srcⁿ (S T) (scalar ())
¬function-err-src : {T t} (Language (src T) t) ¬Language T (function-err t)
¬function-err-src {T = S T} p = function-err p
¬function-err-src {T = nil} p = scalar-function-err nil
¬function-err-src {T = never} p = never
¬function-err-src {T = unknown} (scalar ())
¬function-err-src {T = boolean} p = scalar-function-err boolean
¬function-err-src {T = number} p = scalar-function-err number
¬function-err-src {T = string} p = scalar-function-err string
¬function-err-src {T = S T} p = <:-impl-⊇ (<:-normalize (S T)) _ (¬function-err-srcⁿ (normal (S T)) p)
¬function-err-src {T = S T} p = <:-impl-⊇ (<:-normalize (S T)) _ (¬function-err-srcⁿ (normal (S T)) p)
src-¬function-errᶠ : {T t} (FunType T) Language T (function-err t) (¬Language (srcⁿ T) t)
src-¬function-errᶠ (S T) (function-err p) = p
src-¬function-errᶠ (S T) (p₁ , p₂) = (src-¬function-errᶠ S p₁ , src-¬function-errᶠ T p₂)
src-¬function-errⁿ : {T t} (Normal T) Language T (function-err t) (¬Language (srcⁿ T) t)
src-¬function-errⁿ unknown p = never
src-¬function-errⁿ (S T) (function-err p) = p
src-¬function-errⁿ (S T) (p₁ , p₂) = (src-¬function-errᶠ S p₁ , src-¬function-errᶠ T p₂)
src-¬function-errⁿ (S T) p = never
src-¬function-err : {T t} Language T (function-err t) (¬Language (src T) t)
src-¬function-err {T = S T} (function-err p) = p
src-¬function-err {T = unknown} p = never
src-¬function-err {T = S T} p = src-¬function-errⁿ (normal (S T)) (<:-normalize (S T) _ p)
src-¬function-err {T = S T} p = src-¬function-errⁿ (normal (S T)) (<:-normalize (S T) _ p)
fun-¬scalar : {S T} (s : Scalar S) FunType T ¬Language T (scalar s)
fun-¬scalar s (S T) = function-scalar s
fun-¬scalar s (S T) = left (fun-¬scalar s S)
¬fun-scalar : {S T t} (s : Scalar S) FunType T Language T t ¬Language S t
¬fun-scalar s (S T) function = scalar-function s
¬fun-scalar s (S T) (function-ok₁ p) = scalar-function-ok s
¬fun-scalar s (S T) (function-ok₂ p) = scalar-function-ok s
¬fun-scalar s (S T) (function-err p) = scalar-function-err s
¬fun-scalar s (S T) (function-tgt p) = scalar-function-tgt s
¬fun-scalar s (S T) (p₁ , p₂) = ¬fun-scalar s T p₂
fun-function : {T} FunType T Language T function
fun-function (S T) = function
fun-function (S T) = (fun-function S , fun-function T)
srcⁿ-¬scalar : {S T t} (s : Scalar S) Normal T Language T (scalar s) (¬Language (srcⁿ T) t)
srcⁿ-¬scalar s never (scalar ())
srcⁿ-¬scalar s unknown p = never
srcⁿ-¬scalar s (S T) (scalar ())
srcⁿ-¬scalar s (S T) (p₁ , p₂) = CONTRADICTION (language-comp (scalar s) (fun-¬scalar s S) p₁)
srcⁿ-¬scalar s (S T) p = never
src-¬scalar : {S T t} (s : Scalar S) Language T (scalar s) (¬Language (src T) t)
src-¬scalar {T = nil} s p = never
src-¬scalar {T = T U} s (scalar ())
src-¬scalar {T = never} s (scalar ())
src-¬scalar {T = unknown} s p = never
src-¬scalar {T = boolean} s p = never
src-¬scalar {T = number} s p = never
src-¬scalar {T = string} s p = never
src-¬scalar {T = T U} s p = srcⁿ-¬scalar s (normal (T U)) (<:-normalize (T U) (scalar s) p)
src-¬scalar {T = T U} s p = srcⁿ-¬scalar s (normal (T U)) (<:-normalize (T U) (scalar s) p)
srcⁿ-unknown-≮: : {T U} (Normal U) (T ≮: srcⁿ U) (U ≮: (T unknown))
srcⁿ-unknown-≮: never (witness t p q) = CONTRADICTION (language-comp t q unknown)
srcⁿ-unknown-≮: unknown (witness t p q) = witness (function-err t) unknown (function-err p)
srcⁿ-unknown-≮: (U V) (witness t p q) = witness (function-err t) (function-err q) (function-err p)
srcⁿ-unknown-≮: (U V) (witness t p q) = witness (function-err t) (function-err-srcⁿ (U V) q) (function-err p)
srcⁿ-unknown-≮: (U V) (witness t p q) = witness (scalar V) (right (scalar V)) (function-scalar V)
src-unknown-≮: : {T U} (T ≮: src U) (U ≮: (T unknown))
src-unknown-≮: {U = nil} (witness t p q) = witness (scalar nil) (scalar nil) (function-scalar nil)
src-unknown-≮: {U = T U} (witness t p q) = witness (function-err t) (function-err q) (function-err p)
src-unknown-≮: {U = never} (witness t p q) = CONTRADICTION (language-comp t q unknown)
src-unknown-≮: {U = unknown} (witness t p q) = witness (function-err t) unknown (function-err p)
src-unknown-≮: {U = boolean} (witness t p q) = witness (scalar boolean) (scalar boolean) (function-scalar boolean)
src-unknown-≮: {U = number} (witness t p q) = witness (scalar number) (scalar number) (function-scalar number)
src-unknown-≮: {U = string} (witness t p q) = witness (scalar string) (scalar string) (function-scalar string)
src-unknown-≮: {U = T U} p = <:-trans-≮: (normalize-<: (T U)) (srcⁿ-unknown-≮: (normal (T U)) p)
src-unknown-≮: {U = T U} p = <:-trans-≮: (normalize-<: (T U)) (srcⁿ-unknown-≮: (normal (T U)) p)
unknown-src-≮: : {S T U} (U ≮: S) (T ≮: (U unknown)) (U ≮: src T)
unknown-src-≮: (witness t x x₁) (witness (scalar s) p (function-scalar s)) = witness t x (src-¬scalar s p)
unknown-src-≮: r (witness (function-ok s .(scalar s₁)) p (function-ok x (scalar-scalar s₁ () x₂)))
unknown-src-≮: r (witness (function-ok s .function) p (function-ok x (scalar-function ())))
unknown-src-≮: r (witness (function-ok s .(function-ok _ _)) p (function-ok x (scalar-function-ok ())))
unknown-src-≮: r (witness (function-ok s .(function-err _)) p (function-ok x (scalar-function-err ())))
unknown-src-≮: r (witness (function-err t) p (function-err q)) = witness t q (src-¬function-err p)
unknown-src-≮: r (witness (function-tgt t) p (function-tgt (scalar-function-tgt ())))
-- Properties of resolve
resolveˢ-<:-⇒ : {F V U} (FunType F) (Saturated F) (FunType (V U)) (r : Resolved F V) (F <: (V U)) (target r <: U)
resolveˢ-<:-⇒ Fᶠ V⇒Uᶠ r F<:V⇒U with <:-impl-<:ᵒ Fᶠ V⇒Uᶠ F<:V⇒U here
resolveˢ-<:-⇒ Fᶠ V⇒Uᶠ (yes V<:Sʳ tgtʳ) F<:V⇒U | defn o o₁ o₂ = <:-trans (tgtʳ o o₁) o₂
resolveˢ-<:-⇒ Fᶠ V⇒Uᶠ (no tgtʳ) F<:V⇒U | defn o o₁ o₂ = CONTRADICTION (<:-impl-¬≮: o₁ (tgtʳ o))
resolveⁿ-<:-⇒ : {F V U} (Fⁿ : Normal F) (Vⁿ : Normal V) (Uⁿ : Normal U) (F <: (V U)) (resolveⁿ Fⁿ Vⁿ <: U)
resolveⁿ-<:-⇒ (Sⁿ Tⁿ) Vⁿ Uⁿ F<:V⇒U = resolveˢ-<:-⇒ (normal-saturate (Sⁿ Tⁿ)) (saturated (Sⁿ Tⁿ)) (Vⁿ Uⁿ) (resolveˢ (normal-saturate (Sⁿ Tⁿ)) (saturated (Sⁿ Tⁿ)) Vⁿ (λ o o)) F<:V⇒U
resolveⁿ-<:-⇒ (Fⁿ Gⁿ) Vⁿ Uⁿ F<:V⇒U = resolveˢ-<:-⇒ (normal-saturate (Fⁿ Gⁿ)) (saturated (Fⁿ Gⁿ)) (Vⁿ Uⁿ) (resolveˢ (normal-saturate (Fⁿ Gⁿ)) (saturated (Fⁿ Gⁿ)) Vⁿ (λ o o)) (<:-trans (saturate-<: (Fⁿ Gⁿ)) F<:V⇒U)
resolveⁿ-<:-⇒ (Sⁿ ) Vⁿ Uⁿ F<:V⇒U = CONTRADICTION (<:-impl-¬≮: F<:V⇒U (<:-trans-≮: <:--right (scalar-≮:-function )))
resolveⁿ-<:-⇒ never Vⁿ Uⁿ F<:V⇒U = <:-never
resolveⁿ-<:-⇒ unknown Vⁿ Uⁿ F<:V⇒U = CONTRADICTION (<:-impl-¬≮: F<:V⇒U unknown-≮:-function)
resolve-<:-⇒ : {F V U} (F <: (V U)) (resolve F V <: U)
resolve-<:-⇒ {F} {V} {U} F<:V⇒U = <:-trans (resolveⁿ-<:-⇒ (normal F) (normal V) (normal U) (<:-trans (normalize-<: F) (<:-trans F<:V⇒U (<:-normalize (V U))))) (normalize-<: U)
resolve-≮:-⇒ : {F V U} (resolve F V ≮: U) (F ≮: (V U))
resolve-≮:-⇒ {F} {V} {U} FV≮:U with dec-subtyping F (V U)
resolve-≮:-⇒ {F} {V} {U} FV≮:U | Left F≮:V⇒U = F≮:V⇒U
resolve-≮:-⇒ {F} {V} {U} FV≮:U | Right F<:V⇒U = CONTRADICTION (<:-impl-¬≮: (resolve-<:-⇒ F<:V⇒U) FV≮:U)
<:-resolveˢ-⇒ : {S T V} (r : Resolved (S T) V) (V <: S) T <: target r
<:-resolveˢ-⇒ (yes S T here _ _) V<:S = <:-refl
<:-resolveˢ-⇒ (no _) V<:S = <:-unknown
<:-resolveⁿ-⇒ : {S T V} (Sⁿ : Normal S) (Tⁿ : Normal T) (Vⁿ : Normal V) (V <: S) T <: resolveⁿ (Sⁿ Tⁿ) Vⁿ
<:-resolveⁿ-⇒ Sⁿ Tⁿ Vⁿ V<:S = <:-resolveˢ-⇒ (resolveˢ (Sⁿ Tⁿ) (saturated (Sⁿ Tⁿ)) Vⁿ (λ o o)) V<:S
<:-resolve-⇒ : {S T V} (V <: S) T <: resolve (S T) V
<:-resolve-⇒ {S} {T} {V} V<:S = <:-trans (<:-normalize T) (<:-resolveⁿ-⇒ (normal S) (normal T) (normal V) (<:-trans (normalize-<: V) (<:-trans V<:S (<:-normalize S))))
<:-resolveˢ : {F G V W} (r : Resolved F V) (s : Resolved G W) (F <:ᵒ G) (V <: W) target r <: target s
<:-resolveˢ (yes V<:Sʳ tgtʳ) (yes W<:Sˢ tgtˢ) F<:G V<:W with F<:G
<:-resolveˢ (yes V<:Sʳ tgtʳ) (yes W<:Sˢ tgtˢ) F<:G V<:W | defn o o₁ o₂ = <:-trans (tgtʳ o (<:-trans (<:-trans V<:W W<:Sˢ) o₁)) o₂
<:-resolveˢ (no r) (yes W<:Sˢ tgtˢ) F<:G V<:W with F<:G
<:-resolveˢ (no r) (yes W<:Sˢ tgtˢ) F<:G V<:W | defn o o₁ o₂ = CONTRADICTION (<:-impl-¬≮: (<:-trans V<:W (<:-trans W<:Sˢ o₁)) (r o))
<:-resolveˢ r (no s) F<:G V<:W = <:-unknown
<:-resolveᶠ : {F G V W} (Fᶠ : FunType F) (Gᶠ : FunType G) (Vⁿ : Normal V) (Wⁿ : Normal W) (F <: G) (V <: W) resolveᶠ Fᶠ Vⁿ <: resolveᶠ Gᶠ Wⁿ
<:-resolveᶠ Fᶠ Gᶠ Vⁿ Wⁿ F<:G V<:W = <:-resolveˢ
(resolveˢ (normal-saturate Fᶠ) (saturated Fᶠ) Vⁿ (λ o o))
(resolveˢ (normal-saturate Gᶠ) (saturated Gᶠ) Wⁿ (λ o o))
(<:-impl-<:ᵒ (normal-saturate Fᶠ) (saturated Fᶠ) (normal-saturate Gᶠ) (<:-trans (saturate-<: Fᶠ) (<:-trans F<:G (<:-saturate Gᶠ))))
V<:W
<:-resolveⁿ : {F G V W} (Fⁿ : Normal F) (Gⁿ : Normal G) (Vⁿ : Normal V) (Wⁿ : Normal W) (F <: G) (V <: W) resolveⁿ Fⁿ Vⁿ <: resolveⁿ Gⁿ Wⁿ
<:-resolveⁿ (Rⁿ Sⁿ) (Tⁿ Uⁿ) Vⁿ Wⁿ F<:G V<:W = <:-resolveᶠ (Rⁿ Sⁿ) (Tⁿ Uⁿ) Vⁿ Wⁿ F<:G V<:W
<:-resolveⁿ (Rⁿ Sⁿ) (Gⁿ Hⁿ) Vⁿ Wⁿ F<:G V<:W = <:-resolveᶠ (Rⁿ Sⁿ) (Gⁿ Hⁿ) Vⁿ Wⁿ F<:G V<:W
<:-resolveⁿ (Eⁿ Fⁿ) (Tⁿ Uⁿ) Vⁿ Wⁿ F<:G V<:W = <:-resolveᶠ (Eⁿ Fⁿ) (Tⁿ Uⁿ) Vⁿ Wⁿ F<:G V<:W
<:-resolveⁿ (Eⁿ Fⁿ) (Gⁿ Hⁿ) Vⁿ Wⁿ F<:G V<:W = <:-resolveᶠ (Eⁿ Fⁿ) (Gⁿ Hⁿ) Vⁿ Wⁿ F<:G V<:W
<:-resolveⁿ (Fⁿ ) (Tⁿ Uⁿ) Vⁿ Wⁿ F<:G V<:W = CONTRADICTION (<:-impl-¬≮: F<:G (≮:--right (scalar-≮:-function )))
<:-resolveⁿ unknown (Tⁿ Uⁿ) Vⁿ Wⁿ F<:G V<:W = CONTRADICTION (<:-impl-¬≮: F<:G unknown-≮:-function)
<:-resolveⁿ (Fⁿ ) (Gⁿ Hⁿ) Vⁿ Wⁿ F<:G V<:W = CONTRADICTION (<:-impl-¬≮: F<:G (≮:--right (scalar-≮:-fun (Gⁿ Hⁿ) )))
<:-resolveⁿ unknown (Gⁿ Hⁿ) Vⁿ Wⁿ F<:G V<:W = CONTRADICTION (<:-impl-¬≮: F<:G (unknown-≮:-fun (Gⁿ Hⁿ)))
<:-resolveⁿ (Rⁿ Sⁿ) never Vⁿ Wⁿ F<:G V<:W = CONTRADICTION (<:-impl-¬≮: F<:G (fun-≮:-never (Rⁿ Sⁿ)))
<:-resolveⁿ (Eⁿ Fⁿ) never Vⁿ Wⁿ F<:G V<:W = CONTRADICTION (<:-impl-¬≮: F<:G (fun-≮:-never (Eⁿ Fⁿ)))
<:-resolveⁿ (Fⁿ ) never Vⁿ Wⁿ F<:G V<:W = CONTRADICTION (<:-impl-¬≮: F<:G (≮:--right (scalar-≮:-never )))
<:-resolveⁿ unknown never Vⁿ Wⁿ F<:G V<:W = F<:G
<:-resolveⁿ never Gⁿ Vⁿ Wⁿ F<:G V<:W = <:-never
<:-resolveⁿ Fⁿ (Gⁿ ) Vⁿ Wⁿ F<:G V<:W = <:-unknown
<:-resolveⁿ Fⁿ unknown Vⁿ Wⁿ F<:G V<:W = <:-unknown
<:-resolve : {F G V W} (F <: G) (V <: W) resolve F V <: resolve G W
<:-resolve {F} {G} {V} {W} F<:G V<:W = <:-resolveⁿ (normal F) (normal G) (normal V) (normal W)
(<:-trans (normalize-<: F) (<:-trans F<:G (<:-normalize G)))
(<:-trans (normalize-<: V) (<:-trans V<:W (<:-normalize W)))

View file

@ -7,11 +7,11 @@ open import Agda.Builtin.Equality using (_≡_; refl)
open import FFI.Data.Either using (Either; Left; Right; mapL; mapR; mapLR; swapLR; cond)
open import FFI.Data.Maybe using (Maybe; just; nothing)
open import Luau.Heap using (Heap; Object; function_is_end; defn; alloc; ok; next; lookup-not-allocated) renaming (_≡_⊕_↦_ to _≡ᴴ_⊕_↦_; _[_] to _[_]ᴴ; to ∅ᴴ)
open import Luau.ResolveOverloads using (src; resolve)
open import Luau.StrictMode using (Warningᴱ; Warningᴮ; Warningᴼ; Warningᴴ; UnallocatedAddress; UnboundVariable; FunctionCallMismatch; app₁; app₂; BinOpMismatch₁; BinOpMismatch₂; bin₁; bin₂; BlockMismatch; block₁; return; LocalVarMismatch; local₁; local₂; FunctionDefnMismatch; function₁; function₂; heap; expr; block; addr)
open import Luau.Substitution using (_[_/_]ᴮ; _[_/_]ᴱ; _[_/_]ᴮunless_; var_[_/_]ᴱwhenever_)
open import Luau.Subtyping using (_≮:_; witness; unknown; never; scalar; function; scalar-function; scalar-function-ok; scalar-function-err; scalar-scalar; function-scalar; function-ok; function-err; left; right; _,_; Tree; Language; ¬Language)
open import Luau.Subtyping using (_<:_; _≮:_; witness; unknown; never; scalar; function; scalar-function; scalar-function-ok; scalar-function-err; scalar-scalar; function-scalar; function-ok; function-err; left; right; _,_; Tree; Language; ¬Language)
open import Luau.Syntax using (Expr; yes; var; val; var_∈_; _⟨_⟩∈_; _$_; addr; number; bool; string; binexp; nil; function_is_end; block_is_end; done; return; local_←_; _∙_; fun; arg; name; ==; ~=)
open import Luau.FunctionTypes using (src; tgt)
open import Luau.Type using (Type; nil; number; boolean; string; _⇒_; never; unknown; _∩_; __; _≡ᵀ_; _≡ᴹᵀ_)
open import Luau.TypeCheck using (_⊢ᴮ_∈_; _⊢ᴱ_∈_; _⊢ᴴᴮ_▷_∈_; _⊢ᴴᴱ_▷_∈_; nil; var; addr; app; function; block; done; return; local; orUnknown; srcBinOp; tgtBinOp)
open import Luau.Var using (_≡ⱽ_)
@ -23,8 +23,10 @@ open import Properties.Equality using (_≢_; sym; cong; trans; subst₁)
open import Properties.Dec using (Dec; yes; no)
open import Properties.Contradiction using (CONTRADICTION; ¬)
open import Properties.Functions using (_∘_)
open import Properties.FunctionTypes using (never-tgt-≮:; tgt-never-≮:; src-unknown-≮:; unknown-src-≮:)
open import Properties.Subtyping using (unknown-≮:; ≡-trans-≮:; ≮:-trans-≡; ≮:-trans; ≮:-refl; scalar-≢-impl-≮:; function-≮:-scalar; scalar-≮:-function; function-≮:-never; unknown-≮:-scalar; scalar-≮:-never; unknown-≮:-never)
open import Properties.DecSubtyping using (dec-subtyping)
open import Properties.Subtyping using (unknown-≮:; ≡-trans-≮:; ≮:-trans-≡; ≮:-trans; ≮:-refl; scalar-≢-impl-≮:; function-≮:-scalar; scalar-≮:-function; function-≮:-never; unknown-≮:-scalar; scalar-≮:-never; unknown-≮:-never; <:-refl; <:-unknown; <:-impl-¬≮:)
open import Properties.ResolveOverloads using (src-unknown-≮:; unknown-src-≮:; <:-resolve; resolve-<:-⇒; <:-resolve-⇒)
open import Properties.Subtyping using (unknown-≮:; ≡-trans-≮:; ≮:-trans-≡; ≮:-trans; <:-trans-≮:; ≮:-refl; scalar-≢-impl-≮:; function-≮:-scalar; scalar-≮:-function; function-≮:-never; unknown-≮:-scalar; scalar-≮:-never; unknown-≮:-never; ≡-impl-<:; ≡-trans-<:; <:-trans-≡; ≮:-trans-<:; <:-trans)
open import Properties.TypeCheck using (typeOfᴼ; typeOfᴹᴼ; typeOfⱽ; typeOfᴱ; typeOfᴮ; typeCheckᴱ; typeCheckᴮ; typeCheckᴼ; typeCheckᴴ)
open import Luau.OpSem using (_⟦_⟧_⟶_; _⊢_⟶*_⊣_; _⊢_⟶ᴮ_⊣_; _⊢_⟶ᴱ_⊣_; app₁; app₂; function; beta; return; block; done; local; subst; binOp₀; binOp₁; binOp₂; refl; step; +; -; *; /; <; >; ==; ~=; <=; >=; ··)
open import Luau.RuntimeError using (BinOpError; RuntimeErrorᴱ; RuntimeErrorᴮ; FunctionMismatch; BinOpMismatch₁; BinOpMismatch₂; UnboundVariable; SEGV; app₁; app₂; bin₁; bin₂; block; local; return; +; -; *; /; <; >; <=; >=; ··)
@ -63,51 +65,32 @@ lookup-⊑-nothing {H} a (snoc defn) p with a ≡ᴬ next H
lookup-⊑-nothing {H} a (snoc defn) p | yes refl = refl
lookup-⊑-nothing {H} a (snoc o) p | no q = trans (lookup-not-allocated o q) p
heap-weakeningᴱ : Γ H M {H U} (H H) (typeOfᴱ H Γ M ≮: U) (typeOfᴱ H Γ M ≮: U)
heap-weakeningᴱ Γ H (var x) h p = p
heap-weakeningᴱ Γ H (val nil) h p = p
heap-weakeningᴱ Γ H (val (addr a)) refl p = p
heap-weakeningᴱ Γ H (val (addr a)) (snoc {a = b} q) p with a ≡ᴬ b
heap-weakeningᴱ Γ H (val (addr a)) (snoc {a = a} defn) p | yes refl = unknown-≮: p
heap-weakeningᴱ Γ H (val (addr a)) (snoc {a = b} q) p | no r = ≡-trans-≮: (cong orUnknown (cong typeOfᴹᴼ (lookup-not-allocated q r))) p
heap-weakeningᴱ Γ H (val (number x)) h p = p
heap-weakeningᴱ Γ H (val (bool x)) h p = p
heap-weakeningᴱ Γ H (val (string x)) h p = p
heap-weakeningᴱ Γ H (M $ N) h p = never-tgt-≮: (heap-weakeningᴱ Γ H M h (tgt-never-≮: p))
heap-weakeningᴱ Γ H (function f var x T ⟩∈ U is B end) h p = p
heap-weakeningᴱ Γ H (block var b T is B end) h p = p
heap-weakeningᴱ Γ H (binexp M op N) h p = p
<:-heap-weakeningᴱ : Γ H M {H} (H H) (typeOfᴱ H Γ M <: typeOfᴱ H Γ M)
<:-heap-weakeningᴱ Γ H (var x) h = <:-refl
<:-heap-weakeningᴱ Γ H (val nil) h = <:-refl
<:-heap-weakeningᴱ Γ H (val (addr a)) refl = <:-refl
<:-heap-weakeningᴱ Γ H (val (addr a)) (snoc {a = b} q) with a ≡ᴬ b
<:-heap-weakeningᴱ Γ H (val (addr a)) (snoc {a = a} defn) | yes refl = <:-unknown
<:-heap-weakeningᴱ Γ H (val (addr a)) (snoc {a = b} q) | no r = ≡-impl-<: (sym (cong orUnknown (cong typeOfᴹᴼ (lookup-not-allocated q r))))
<:-heap-weakeningᴱ Γ H (val (number n)) h = <:-refl
<:-heap-weakeningᴱ Γ H (val (bool b)) h = <:-refl
<:-heap-weakeningᴱ Γ H (val (string s)) h = <:-refl
<:-heap-weakeningᴱ Γ H (M $ N) h = <:-resolve (<:-heap-weakeningᴱ Γ H M h) (<:-heap-weakeningᴱ Γ H N h)
<:-heap-weakeningᴱ Γ H (function f var x S ⟩∈ T is B end) h = <:-refl
<:-heap-weakeningᴱ Γ H (block var b T is N end) h = <:-refl
<:-heap-weakeningᴱ Γ H (binexp M op N) h = <:-refl
heap-weakeningᴮ : Γ H B {H U} (H H) (typeOfᴮ H Γ B ≮: U) (typeOfᴮ H Γ B ≮: U)
heap-weakeningᴮ Γ H (function f var x T ⟩∈ U is C end B) h p = heap-weakeningᴮ (Γ f (T U)) H B h p
heap-weakeningᴮ Γ H (local var x T M B) h p = heap-weakeningᴮ (Γ x T) H B h p
heap-weakeningᴮ Γ H (return M B) h p = heap-weakeningᴱ Γ H M h p
heap-weakeningᴮ Γ H done h p = p
<:-heap-weakeningᴮ : Γ H B {H} (H H) (typeOfᴮ H Γ B <: typeOfᴮ H Γ B)
<:-heap-weakeningᴮ Γ H (function f var x T ⟩∈ U is C end B) h = <:-heap-weakeningᴮ (Γ f (T U)) H B h
<:-heap-weakeningᴮ Γ H (local var x T M B) h = <:-heap-weakeningᴮ (Γ x T) H B h
<:-heap-weakeningᴮ Γ H (return M B) h = <:-heap-weakeningᴱ Γ H M h
<:-heap-weakeningᴮ Γ H done h = <:-refl
substitutivityᴱ : {Γ T U} H M v x (typeOfᴱ H Γ (M [ v / x ]ᴱ) ≮: U) Either (typeOfᴱ H (Γ x T) M ≮: U) (typeOfᴱ H (val v) ≮: T)
substitutivityᴱ-whenever : {Γ T U} H v x y (r : Dec(x y)) (typeOfᴱ H Γ (var y [ v / x ]ᴱwhenever r) ≮: U) Either (typeOfᴱ H (Γ x T) (var y) ≮: U) (typeOfᴱ H (val v) ≮: T)
substitutivityᴮ : {Γ T U} H B v x (typeOfᴮ H Γ (B [ v / x ]ᴮ) ≮: U) Either (typeOfᴮ H (Γ x T) B ≮: U) (typeOfᴱ H (val v) ≮: T)
substitutivityᴮ-unless : {Γ T U V} H B v x y (r : Dec(x y)) (typeOfᴮ H (Γ y U) (B [ v / x ]ᴮunless r) ≮: V) Either (typeOfᴮ H ((Γ x T) y U) B ≮: V) (typeOfᴱ H (val v) ≮: T)
substitutivityᴮ-unless-yes : {Γ Γ′ T V} H B v x y (r : x y) (Γ′ Γ) (typeOfᴮ H Γ (B [ v / x ]ᴮunless yes r) ≮: V) Either (typeOfᴮ H Γ′ B ≮: V) (typeOfᴱ H (val v) ≮: T)
substitutivityᴮ-unless-no : {Γ Γ′ T V} H B v x y (r : x y) (Γ′ Γ x T) (typeOfᴮ H Γ (B [ v / x ]ᴮunless no r) ≮: V) Either (typeOfᴮ H Γ′ B ≮: V) (typeOfᴱ H (val v) ≮: T)
≮:-heap-weakeningᴱ : Γ H M {H U} (H H) (typeOfᴱ H Γ M ≮: U) (typeOfᴱ H Γ M ≮: U)
:-heap-weakeningᴱ Γ H M h p = <:-trans-≮: (<:-heap-weakeningᴱ Γ H M h) p
substitutivityᴱ H (var y) v x p = substitutivityᴱ-whenever H v x y (x ≡ⱽ y) p
substitutivityᴱ H (val w) v x p = Left p
substitutivityᴱ H (binexp M op N) v x p = Left p
substitutivityᴱ H (M $ N) v x p = mapL never-tgt-≮: (substitutivityᴱ H M v x (tgt-never-≮: p))
substitutivityᴱ H (function f var y T ⟩∈ U is B end) v x p = Left p
substitutivityᴱ H (block var b T is B end) v x p = Left p
substitutivityᴱ-whenever H v x x (yes refl) q = swapLR (≮:-trans q)
substitutivityᴱ-whenever H v x y (no p) q = Left (≡-trans-≮: (cong orUnknown (sym (⊕-lookup-miss x y _ _ p))) q)
substitutivityᴮ H (function f var y T ⟩∈ U is C end B) v x p = substitutivityᴮ-unless H B v x f (x ≡ⱽ f) p
substitutivityᴮ H (local var y T M B) v x p = substitutivityᴮ-unless H B v x y (x ≡ⱽ y) p
substitutivityᴮ H (return M B) v x p = substitutivityᴱ H M v x p
substitutivityᴮ H done v x p = Left p
substitutivityᴮ-unless H B v x y (yes p) q = substitutivityᴮ-unless-yes H B v x y p (⊕-over p) q
substitutivityᴮ-unless H B v x y (no p) q = substitutivityᴮ-unless-no H B v x y p (⊕-swap p) q
substitutivityᴮ-unless-yes H B v x y refl refl p = Left p
substitutivityᴮ-unless-no H B v x y p refl q = substitutivityᴮ H B v x q
≮:-heap-weakeningᴮ : Γ H B {H U} (H H) (typeOfᴮ H Γ B ≮: U) (typeOfᴮ H Γ B ≮: U)
:-heap-weakeningᴮ Γ H B h p = <:-trans-≮: (<:-heap-weakeningᴮ Γ H B h) p
binOpPreservation : H {op v w x} (v op w x) (tgtBinOp op typeOfᴱ H (val x))
binOpPreservation H (+ m n) = refl
@ -122,24 +105,78 @@ binOpPreservation H (== v w) = refl
binOpPreservation H (~= v w) = refl
binOpPreservation H (·· v w) = refl
reflect-subtypingᴱ : H M {H M T} (H M ⟶ᴱ M H) (typeOfᴱ H M ≮: T) Either (typeOfᴱ H M ≮: T) (Warningᴱ H (typeCheckᴱ H M))
reflect-subtypingᴮ : H B {H B T} (H B ⟶ᴮ B H) (typeOfᴮ H B ≮: T) Either (typeOfᴮ H B ≮: T) (Warningᴮ H (typeCheckᴮ H B))
<:-substitutivityᴱ : {Γ T} H M v x (typeOfᴱ H (val v) <: T) (typeOfᴱ H Γ (M [ v / x ]ᴱ) <: typeOfᴱ H (Γ x T) M)
<:-substitutivityᴱ-whenever : {Γ T} H v x y (r : Dec(x y)) (typeOfᴱ H (val v) <: T) (typeOfᴱ H Γ (var y [ v / x ]ᴱwhenever r) <: typeOfᴱ H (Γ x T) (var y))
<:-substitutivityᴮ : {Γ T} H B v x (typeOfᴱ H (val v) <: T) (typeOfᴮ H Γ (B [ v / x ]ᴮ) <: typeOfᴮ H (Γ x T) B)
<:-substitutivityᴮ-unless : {Γ T U} H B v x y (r : Dec(x y)) (typeOfᴱ H (val v) <: T) (typeOfᴮ H (Γ y U) (B [ v / x ]ᴮunless r) <: typeOfᴮ H ((Γ x T) y U) B)
<:-substitutivityᴮ-unless-yes : {Γ Γ′} H B v x y (r : x y) (Γ′ Γ) (typeOfᴮ H Γ (B [ v / x ]ᴮunless yes r) <: typeOfᴮ H Γ′ B)
<:-substitutivityᴮ-unless-no : {Γ Γ′ T} H B v x y (r : x y) (Γ′ Γ x T) (typeOfᴱ H (val v) <: T) (typeOfᴮ H Γ (B [ v / x ]ᴮunless no r) <: typeOfᴮ H Γ′ B)
reflect-subtypingᴱ H (M $ N) (app₁ s) p = mapLR never-tgt-≮: app₁ (reflect-subtypingᴱ H M s (tgt-never-≮: p))
reflect-subtypingᴱ H (M $ N) (app₂ v s) p = Left (never-tgt-≮: (heap-weakeningᴱ H M (rednᴱ⊑ s) (tgt-never-≮: p)))
reflect-subtypingᴱ H (M $ N) (beta (function f var y T ⟩∈ U is B end) v refl q) p = Left (≡-trans-≮: (cong tgt (cong orUnknown (cong typeOfᴹᴼ q))) p)
reflect-subtypingᴱ H (function f var x T ⟩∈ U is B end) (function a defn) p = Left p
reflect-subtypingᴱ H (block var b T is B end) (block s) p = Left p
reflect-subtypingᴱ H (block var b T is return (val v) B end) (return v) p = mapR BlockMismatch (swapLR (≮:-trans p))
reflect-subtypingᴱ H (block var b T is done end) done p = mapR BlockMismatch (swapLR (≮:-trans p))
reflect-subtypingᴱ H (binexp M op N) (binOp₀ s) p = Left (≡-trans-≮: (binOpPreservation H s) p)
reflect-subtypingᴱ H (binexp M op N) (binOp₁ s) p = Left p
reflect-subtypingᴱ H (binexp M op N) (binOp₂ s) p = Left p
<:-substitutivityᴱ H (var y) v x p = <:-substitutivityᴱ-whenever H v x y (x ≡ⱽ y) p
<:-substitutivityᴱ H (val w) v x p = <:-refl
<:-substitutivityᴱ H (binexp M op N) v x p = <:-refl
<:-substitutivityᴱ H (M $ N) v x p = <:-resolve (<:-substitutivityᴱ H M v x p) (<:-substitutivityᴱ H N v x p)
<:-substitutivityᴱ H (function f var y T ⟩∈ U is B end) v x p = <:-refl
<:-substitutivityᴱ H (block var b T is B end) v x p = <:-refl
<:-substitutivityᴱ-whenever H v x x (yes refl) p = p
<:-substitutivityᴱ-whenever H v x y (no o) p = (≡-impl-<: (cong orUnknown (⊕-lookup-miss x y _ _ o)))
reflect-subtypingᴮ H (function f var x T ⟩∈ U is C end B) (function a defn) p = mapLR (heap-weakeningᴮ _ _ B (snoc defn)) (CONTRADICTION ≮:-refl) (substitutivityᴮ _ B (addr a) f p)
reflect-subtypingᴮ H (local var x T M B) (local s) p = Left (heap-weakeningᴮ (x T) H B (rednᴱ⊑ s) p)
reflect-subtypingᴮ H (local var x T M B) (subst v) p = mapR LocalVarMismatch (substitutivityᴮ H B v x p)
reflect-subtypingᴮ H (return M B) (return s) p = mapR return (reflect-subtypingᴱ H M s p)
<:-substitutivityᴮ H (function f var y T ⟩∈ U is C end B) v x p = <:-substitutivityᴮ-unless H B v x f (x ≡ⱽ f) p
<:-substitutivityᴮ H (local var y T M B) v x p = <:-substitutivityᴮ-unless H B v x y (x ≡ⱽ y) p
<:-substitutivityᴮ H (return M B) v x p = <:-substitutivityᴱ H M v x p
<:-substitutivityᴮ H done v x p = <:-refl
<:-substitutivityᴮ-unless H B v x y (yes r) p = <:-substitutivityᴮ-unless-yes H B v x y r (⊕-over r)
<:-substitutivityᴮ-unless H B v x y (no r) p = <:-substitutivityᴮ-unless-no H B v x y r (⊕-swap r) p
<:-substitutivityᴮ-unless-yes H B v x y refl refl = <:-refl
<:-substitutivityᴮ-unless-no H B v x y r refl p = <:-substitutivityᴮ H B v x p
≮:-substitutivityᴱ : {Γ T U} H M v x (typeOfᴱ H Γ (M [ v / x ]ᴱ) ≮: U) Either (typeOfᴱ H (Γ x T) M ≮: U) (typeOfᴱ H (val v) ≮: T)
:-substitutivityᴱ {T = T} H M v x p with dec-subtyping (typeOfᴱ H (val v)) T
:-substitutivityᴱ H M v x p | Left q = Right q
:-substitutivityᴱ H M v x p | Right q = Left (<:-trans-≮: (<:-substitutivityᴱ H M v x q) p)
≮:-substitutivityᴮ : {Γ T U} H B v x (typeOfᴮ H Γ (B [ v / x ]ᴮ) ≮: U) Either (typeOfᴮ H (Γ x T) B ≮: U) (typeOfᴱ H (val v) ≮: T)
:-substitutivityᴮ {T = T} H M v x p with dec-subtyping (typeOfᴱ H (val v)) T
:-substitutivityᴮ H M v x p | Left q = Right q
:-substitutivityᴮ H M v x p | Right q = Left (<:-trans-≮: (<:-substitutivityᴮ H M v x q) p)
≮:-substitutivityᴮ-unless : {Γ T U V} H B v x y (r : Dec(x y)) (typeOfᴮ H (Γ y U) (B [ v / x ]ᴮunless r) ≮: V) Either (typeOfᴮ H ((Γ x T) y U) B ≮: V) (typeOfᴱ H (val v) ≮: T)
:-substitutivityᴮ-unless {T = T} H B v x y r p with dec-subtyping (typeOfᴱ H (val v)) T
:-substitutivityᴮ-unless H B v x y r p | Left q = Right q
:-substitutivityᴮ-unless H B v x y r p | Right q = Left (<:-trans-≮: (<:-substitutivityᴮ-unless H B v x y r q) p)
<:-reductionᴱ : H M {H M} (H M ⟶ᴱ M H) Either (typeOfᴱ H M <: typeOfᴱ H M) (Warningᴱ H (typeCheckᴱ H M))
<:-reductionᴮ : H B {H B} (H B ⟶ᴮ B H) Either (typeOfᴮ H B <: typeOfᴮ H B) (Warningᴮ H (typeCheckᴮ H B))
<:-reductionᴱ H (M $ N) (app₁ s) = mapLR (λ p <:-resolve p (<:-heap-weakeningᴱ H N (rednᴱ⊑ s))) app₁ (<:-reductionᴱ H M s)
<:-reductionᴱ H (M $ N) (app₂ q s) = mapLR (λ p <:-resolve (<:-heap-weakeningᴱ H M (rednᴱ⊑ s)) p) app₂ (<:-reductionᴱ H N s)
<:-reductionᴱ H (M $ N) (beta (function f var y S ⟩∈ U is B end) v refl q) with dec-subtyping (typeOfᴱ H (val v)) S
<:-reductionᴱ H (M $ N) (beta (function f var y S ⟩∈ U is B end) v refl q) | Left r = Right (FunctionCallMismatch (≮:-trans-≡ r (cong src (cong orUnknown (cong typeOfᴹᴼ (sym q))))))
<:-reductionᴱ H (M $ N) (beta (function f var y S ⟩∈ U is B end) v refl q) | Right r = Left (<:-trans-≡ (<:-resolve-⇒ r) (cong (λ F resolve F (typeOfᴱ H N)) (cong orUnknown (cong typeOfᴹᴼ (sym q)))))
<:-reductionᴱ H (function f var x T ⟩∈ U is B end) (function a defn) = Left <:-refl
<:-reductionᴱ H (block var b T is B end) (block s) = Left <:-refl
<:-reductionᴱ H (block var b T is return (val v) B end) (return v) with dec-subtyping (typeOfᴱ H (val v)) T
<:-reductionᴱ H (block var b T is return (val v) B end) (return v) | Left p = Right (BlockMismatch p)
<:-reductionᴱ H (block var b T is return (val v) B end) (return v) | Right p = Left p
<:-reductionᴱ H (block var b T is done end) done with dec-subtyping nil T
<:-reductionᴱ H (block var b T is done end) done | Left p = Right (BlockMismatch p)
<:-reductionᴱ H (block var b T is done end) done | Right p = Left p
<:-reductionᴱ H (binexp M op N) (binOp₀ s) = Left (≡-impl-<: (sym (binOpPreservation H s)))
<:-reductionᴱ H (binexp M op N) (binOp₁ s) = Left <:-refl
<:-reductionᴱ H (binexp M op N) (binOp₂ s) = Left <:-refl
<:-reductionᴮ H (function f var x T ⟩∈ U is C end B) (function a defn) = Left (<:-trans (<:-substitutivityᴮ _ B (addr a) f <:-refl) (<:-heap-weakeningᴮ (f (T U)) H B (snoc defn)))
<:-reductionᴮ H (local var x T M B) (local s) = Left (<:-heap-weakeningᴮ (x T) H B (rednᴱ⊑ s))
<:-reductionᴮ H (local var x T M B) (subst v) with dec-subtyping (typeOfᴱ H (val v)) T
<:-reductionᴮ H (local var x T M B) (subst v) | Left p = Right (LocalVarMismatch p)
<:-reductionᴮ H (local var x T M B) (subst v) | Right p = Left (<:-substitutivityᴮ H B v x p)
<:-reductionᴮ H (return M B) (return s) = mapR return (<:-reductionᴱ H M s)
≮:-reductionᴱ : H M {H M T} (H M ⟶ᴱ M H) (typeOfᴱ H M ≮: T) Either (typeOfᴱ H M ≮: T) (Warningᴱ H (typeCheckᴱ H M))
:-reductionᴱ H M s p = mapL (λ q <:-trans-≮: q p) (<:-reductionᴱ H M s)
≮:-reductionᴮ : H B {H B T} (H B ⟶ᴮ B H) (typeOfᴮ H B ≮: T) Either (typeOfᴮ H B ≮: T) (Warningᴮ H (typeCheckᴮ H B))
:-reductionᴮ H B s p = mapL (λ q <:-trans-≮: q p) (<:-reductionᴮ H B s)
reflect-substitutionᴱ : {Γ T} H M v x Warningᴱ H (typeCheckᴱ H Γ (M [ v / x ]ᴱ)) Either (Warningᴱ H (typeCheckᴱ H (Γ x T) M)) (Either (Warningᴱ H (typeCheckᴱ H (val v))) (typeOfᴱ H (val v) ≮: T))
reflect-substitutionᴱ-whenever : {Γ T} H v x y (p : Dec(x y)) Warningᴱ H (typeCheckᴱ H Γ (var y [ v / x ]ᴱwhenever p)) Either (Warningᴱ H (typeCheckᴱ H (Γ x T) (var y))) (Either (Warningᴱ H (typeCheckᴱ H (val v))) (typeOfᴱ H (val v) ≮: T))
@ -150,29 +187,29 @@ reflect-substitutionᴮ-unless-no : ∀ {Γ Γ′ T} H B v x y (r : x ≢ y) →
reflect-substitutionᴱ H (var y) v x W = reflect-substitutionᴱ-whenever H v x y (x ≡ⱽ y) W
reflect-substitutionᴱ H (val (addr a)) v x (UnallocatedAddress r) = Left (UnallocatedAddress r)
reflect-substitutionᴱ H (M $ N) v x (FunctionCallMismatch p) with substitutivityᴱ H N v x p
reflect-substitutionᴱ H (M $ N) v x (FunctionCallMismatch p) with ≮:-substitutivityᴱ H N v x p
reflect-substitutionᴱ H (M $ N) v x (FunctionCallMismatch p) | Right W = Right (Right W)
reflect-substitutionᴱ H (M $ N) v x (FunctionCallMismatch p) | Left q with substitutivityᴱ H M v x (src-unknown-≮: q)
reflect-substitutionᴱ H (M $ N) v x (FunctionCallMismatch p) | Left q with ≮:-substitutivityᴱ H M v x (src-unknown-≮: q)
reflect-substitutionᴱ H (M $ N) v x (FunctionCallMismatch p) | Left q | Left r = Left ((FunctionCallMismatch unknown-src-≮: q) r)
reflect-substitutionᴱ H (M $ N) v x (FunctionCallMismatch p) | Left q | Right W = Right (Right W)
reflect-substitutionᴱ H (M $ N) v x (app₁ W) = mapL app₁ (reflect-substitutionᴱ H M v x W)
reflect-substitutionᴱ H (M $ N) v x (app₂ W) = mapL app₂ (reflect-substitutionᴱ H N v x W)
reflect-substitutionᴱ H (function f var y T ⟩∈ U is B end) v x (FunctionDefnMismatch q) = mapLR FunctionDefnMismatch Right (substitutivityᴮ-unless H B v x y (x ≡ⱽ y) q)
reflect-substitutionᴱ H (function f var y T ⟩∈ U is B end) v x (FunctionDefnMismatch q) = mapLR FunctionDefnMismatch Right (≮:-substitutivityᴮ-unless H B v x y (x ≡ⱽ y) q)
reflect-substitutionᴱ H (function f var y T ⟩∈ U is B end) v x (function₁ W) = mapL function₁ (reflect-substitutionᴮ-unless H B v x y (x ≡ⱽ y) W)
reflect-substitutionᴱ H (block var b T is B end) v x (BlockMismatch q) = mapLR BlockMismatch Right (substitutivityᴮ H B v x q)
reflect-substitutionᴱ H (block var b T is B end) v x (BlockMismatch q) = mapLR BlockMismatch Right (≮:-substitutivityᴮ H B v x q)
reflect-substitutionᴱ H (block var b T is B end) v x (block₁ W) = mapL block₁ (reflect-substitutionᴮ H B v x W)
reflect-substitutionᴱ H (binexp M op N) v x (BinOpMismatch₁ q) = mapLR BinOpMismatch₁ Right (substitutivityᴱ H M v x q)
reflect-substitutionᴱ H (binexp M op N) v x (BinOpMismatch₂ q) = mapLR BinOpMismatch₂ Right (substitutivityᴱ H N v x q)
reflect-substitutionᴱ H (binexp M op N) v x (BinOpMismatch₁ q) = mapLR BinOpMismatch₁ Right (≮:-substitutivityᴱ H M v x q)
reflect-substitutionᴱ H (binexp M op N) v x (BinOpMismatch₂ q) = mapLR BinOpMismatch₂ Right (≮:-substitutivityᴱ H N v x q)
reflect-substitutionᴱ H (binexp M op N) v x (bin₁ W) = mapL bin₁ (reflect-substitutionᴱ H M v x W)
reflect-substitutionᴱ H (binexp M op N) v x (bin₂ W) = mapL bin₂ (reflect-substitutionᴱ H N v x W)
reflect-substitutionᴱ-whenever H a x x (yes refl) (UnallocatedAddress p) = Right (Left (UnallocatedAddress p))
reflect-substitutionᴱ-whenever H v x y (no p) (UnboundVariable q) = Left (UnboundVariable (trans (sym (⊕-lookup-miss x y _ _ p)) q))
reflect-substitutionᴮ H (function f var y T ⟩∈ U is C end B) v x (FunctionDefnMismatch q) = mapLR FunctionDefnMismatch Right (substitutivityᴮ-unless H C v x y (x ≡ⱽ y) q)
reflect-substitutionᴮ H (function f var y T ⟩∈ U is C end B) v x (FunctionDefnMismatch q) = mapLR FunctionDefnMismatch Right (≮:-substitutivityᴮ-unless H C v x y (x ≡ⱽ y) q)
reflect-substitutionᴮ H (function f var y T ⟩∈ U is C end B) v x (function₁ W) = mapL function₁ (reflect-substitutionᴮ-unless H C v x y (x ≡ⱽ y) W)
reflect-substitutionᴮ H (function f var y T ⟩∈ U is C end B) v x (function₂ W) = mapL function₂ (reflect-substitutionᴮ-unless H B v x f (x ≡ⱽ f) W)
reflect-substitutionᴮ H (local var y T M B) v x (LocalVarMismatch q) = mapLR LocalVarMismatch Right (substitutivityᴱ H M v x q)
reflect-substitutionᴮ H (local var y T M B) v x (LocalVarMismatch q) = mapLR LocalVarMismatch Right (≮:-substitutivityᴱ H M v x q)
reflect-substitutionᴮ H (local var y T M B) v x (local₁ W) = mapL local₁ (reflect-substitutionᴱ H M v x W)
reflect-substitutionᴮ H (local var y T M B) v x (local₂ W) = mapL local₂ (reflect-substitutionᴮ-unless H B v x y (x ≡ⱽ y) W)
reflect-substitutionᴮ H (return M B) v x (return W) = mapL return (reflect-substitutionᴱ H M v x W)
@ -187,61 +224,61 @@ reflect-weakeningᴮ : ∀ Γ H B {H} → (H ⊑ H) → Warningᴮ H (t
reflect-weakeningᴱ Γ H (var x) h (UnboundVariable p) = (UnboundVariable p)
reflect-weakeningᴱ Γ H (val (addr a)) h (UnallocatedAddress p) = UnallocatedAddress (lookup-⊑-nothing a h p)
reflect-weakeningᴱ Γ H (M $ N) h (FunctionCallMismatch p) = FunctionCallMismatch (heap-weakeningᴱ Γ H N h (unknown-src-≮: p (heap-weakeningᴱ Γ H M h (src-unknown-≮: p))))
reflect-weakeningᴱ Γ H (M $ N) h (FunctionCallMismatch p) = FunctionCallMismatch (≮:-heap-weakeningᴱ Γ H N h (unknown-src-≮: p (≮:-heap-weakeningᴱ Γ H M h (src-unknown-≮: p))))
reflect-weakeningᴱ Γ H (M $ N) h (app₁ W) = app₁ (reflect-weakeningᴱ Γ H M h W)
reflect-weakeningᴱ Γ H (M $ N) h (app₂ W) = app₂ (reflect-weakeningᴱ Γ H N h W)
reflect-weakeningᴱ Γ H (binexp M op N) h (BinOpMismatch₁ p) = BinOpMismatch₁ (heap-weakeningᴱ Γ H M h p)
reflect-weakeningᴱ Γ H (binexp M op N) h (BinOpMismatch₂ p) = BinOpMismatch₂ (heap-weakeningᴱ Γ H N h p)
reflect-weakeningᴱ Γ H (binexp M op N) h (BinOpMismatch₁ p) = BinOpMismatch₁ (≮:-heap-weakeningᴱ Γ H M h p)
reflect-weakeningᴱ Γ H (binexp M op N) h (BinOpMismatch₂ p) = BinOpMismatch₂ (≮:-heap-weakeningᴱ Γ H N h p)
reflect-weakeningᴱ Γ H (binexp M op N) h (bin₁ W) = bin₁ (reflect-weakeningᴱ Γ H M h W)
reflect-weakeningᴱ Γ H (binexp M op N) h (bin₂ W) = bin₂ (reflect-weakeningᴱ Γ H N h W)
reflect-weakeningᴱ Γ H (function f var y T ⟩∈ U is B end) h (FunctionDefnMismatch p) = FunctionDefnMismatch (heap-weakeningᴮ (Γ y T) H B h p)
reflect-weakeningᴱ Γ H (function f var y T ⟩∈ U is B end) h (FunctionDefnMismatch p) = FunctionDefnMismatch (≮:-heap-weakeningᴮ (Γ y T) H B h p)
reflect-weakeningᴱ Γ H (function f var y T ⟩∈ U is B end) h (function₁ W) = function₁ (reflect-weakeningᴮ (Γ y T) H B h W)
reflect-weakeningᴱ Γ H (block var b T is B end) h (BlockMismatch p) = BlockMismatch (heap-weakeningᴮ Γ H B h p)
reflect-weakeningᴱ Γ H (block var b T is B end) h (BlockMismatch p) = BlockMismatch (≮:-heap-weakeningᴮ Γ H B h p)
reflect-weakeningᴱ Γ H (block var b T is B end) h (block₁ W) = block₁ (reflect-weakeningᴮ Γ H B h W)
reflect-weakeningᴮ Γ H (return M B) h (return W) = return (reflect-weakeningᴱ Γ H M h W)
reflect-weakeningᴮ Γ H (local var y T M B) h (LocalVarMismatch p) = LocalVarMismatch (heap-weakeningᴱ Γ H M h p)
reflect-weakeningᴮ Γ H (local var y T M B) h (LocalVarMismatch p) = LocalVarMismatch (≮:-heap-weakeningᴱ Γ H M h p)
reflect-weakeningᴮ Γ H (local var y T M B) h (local₁ W) = local₁ (reflect-weakeningᴱ Γ H M h W)
reflect-weakeningᴮ Γ H (local var y T M B) h (local₂ W) = local₂ (reflect-weakeningᴮ (Γ y T) H B h W)
reflect-weakeningᴮ Γ H (function f var x T ⟩∈ U is C end B) h (FunctionDefnMismatch p) = FunctionDefnMismatch (heap-weakeningᴮ (Γ x T) H C h p)
reflect-weakeningᴮ Γ H (function f var x T ⟩∈ U is C end B) h (FunctionDefnMismatch p) = FunctionDefnMismatch (≮:-heap-weakeningᴮ (Γ x T) H C h p)
reflect-weakeningᴮ Γ H (function f var x T ⟩∈ U is C end B) h (function₁ W) = function₁ (reflect-weakeningᴮ (Γ x T) H C h W)
reflect-weakeningᴮ Γ H (function f var x T ⟩∈ U is C end B) h (function₂ W) = function₂ (reflect-weakeningᴮ (Γ f (T U)) H B h W)
reflect-weakeningᴼ : H O {H} (H H) Warningᴼ H (typeCheckᴼ H O) Warningᴼ H (typeCheckᴼ H O)
reflect-weakeningᴼ H (just function f var x T ⟩∈ U is B end) h (FunctionDefnMismatch p) = FunctionDefnMismatch (heap-weakeningᴮ (x T) H B h p)
reflect-weakeningᴼ H (just function f var x T ⟩∈ U is B end) h (FunctionDefnMismatch p) = FunctionDefnMismatch (≮:-heap-weakeningᴮ (x T) H B h p)
reflect-weakeningᴼ H (just function f var x T ⟩∈ U is B end) h (function₁ W) = function₁ (reflect-weakeningᴮ (x T) H B h W)
reflectᴱ : H M {H M} (H M ⟶ᴱ M H) Warningᴱ H (typeCheckᴱ H M) Either (Warningᴱ H (typeCheckᴱ H M)) (Warningᴴ H (typeCheckᴴ H))
reflectᴮ : H B {H B} (H B ⟶ᴮ B H) Warningᴮ H (typeCheckᴮ H B) Either (Warningᴮ H (typeCheckᴮ H B)) (Warningᴴ H (typeCheckᴴ H))
reflectᴱ H (M $ N) (app₁ s) (FunctionCallMismatch p) = cond (Left FunctionCallMismatch heap-weakeningᴱ H N (rednᴱ⊑ s) unknown-src-≮: p) (Left app₁) (reflect-subtyping H M s (src-unknown-≮: p))
reflectᴱ H (M $ N) (app₁ s) (FunctionCallMismatch p) = cond (Left FunctionCallMismatch ≮:-heap-weakeningᴱ H N (rednᴱ⊑ s) unknown-src-≮: p) (Left app₁) (≮:-reduction H M s (src-unknown-≮: p))
reflectᴱ H (M $ N) (app₁ s) (app₁ W) = mapL app₁ (reflectᴱ H M s W)
reflectᴱ H (M $ N) (app₁ s) (app₂ W) = Left (app₂ (reflect-weakeningᴱ H N (rednᴱ⊑ s) W))
reflectᴱ H (M $ N) (app₂ p s) (FunctionCallMismatch q) = cond (λ r Left (FunctionCallMismatch (unknown-src-≮: r (heap-weakeningᴱ H M (rednᴱ⊑ s) (src-unknown-≮: r))))) (Left app₂) (reflect-subtyping H N s q)
reflectᴱ H (M $ N) (app₂ p s) (FunctionCallMismatch q) = cond (λ r Left (FunctionCallMismatch (unknown-src-≮: r (≮:-heap-weakeningᴱ H M (rednᴱ⊑ s) (src-unknown-≮: r))))) (Left app₂) (≮:-reduction H N s q)
reflectᴱ H (M $ N) (app₂ p s) (app₁ W) = Left (app₁ (reflect-weakeningᴱ H M (rednᴱ⊑ s) W))
reflectᴱ H (M $ N) (app₂ p s) (app₂ W) = mapL app₂ (reflectᴱ H N s W)
reflectᴱ H (val (addr a) $ N) (beta (function f var x T ⟩∈ U is B end) v refl p) (BlockMismatch q) with substitutivityᴮ H B v x q
reflectᴱ H (val (addr a) $ N) (beta (function f var x T ⟩∈ U is B end) v refl p) (BlockMismatch q) with ≮:-substitutivityᴮ H B v x q
reflectᴱ H (val (addr a) $ N) (beta (function f var x T ⟩∈ U is B end) v refl p) (BlockMismatch q) | Left r = Right (addr a p (FunctionDefnMismatch r))
reflectᴱ H (val (addr a) $ N) (beta (function f var x T ⟩∈ U is B end) v refl p) (BlockMismatch q) | Right r = Left (FunctionCallMismatch (≮:-trans-≡ r ((cong src (cong orUnknown (cong typeOfᴹᴼ (sym p)))))))
reflectᴱ H (val (addr a) $ N) (beta (function f var x T ⟩∈ U is B end) v refl p) (block₁ W) with reflect-substitutionᴮ _ B v x W
reflectᴱ H (val (addr a) $ N) (beta (function f var x T ⟩∈ U is B end) v refl p) (block₁ W) | Left W = Right (addr a p (function₁ W))
reflectᴱ H (val (addr a) $ N) (beta (function f var x T ⟩∈ U is B end) v refl p) (block₁ W) | Right (Left W) = Left (app₂ W)
reflectᴱ H (val (addr a) $ N) (beta (function f var x T ⟩∈ U is B end) v refl p) (block₁ W) | Right (Right q) = Left (FunctionCallMismatch (≮:-trans-≡ q (cong src (cong orUnknown (cong typeOfᴹᴼ (sym p))))))
reflectᴱ H (block var b T is B end) (block s) (BlockMismatch p) = Left (cond BlockMismatch block₁ (reflect-subtyping H B s p))
reflectᴱ H (block var b T is B end) (block s) (BlockMismatch p) = Left (cond BlockMismatch block₁ (≮:-reduction H B s p))
reflectᴱ H (block var b T is B end) (block s) (block₁ W) = mapL block₁ (reflectᴮ H B s W)
reflectᴱ H (block var b T is B end) (return v) W = Left (block₁ (return W))
reflectᴱ H (function f var x T ⟩∈ U is B end) (function a defn) (UnallocatedAddress ())
reflectᴱ H (binexp M op N) (binOp₀ ()) (UnallocatedAddress p)
reflectᴱ H (binexp M op N) (binOp₁ s) (BinOpMismatch₁ p) = Left (cond BinOpMismatch₁ bin₁ (reflect-subtyping H M s p))
reflectᴱ H (binexp M op N) (binOp₁ s) (BinOpMismatch₂ p) = Left (BinOpMismatch₂ (heap-weakeningᴱ H N (rednᴱ⊑ s) p))
reflectᴱ H (binexp M op N) (binOp₁ s) (BinOpMismatch₁ p) = Left (cond BinOpMismatch₁ bin₁ (≮:-reduction H M s p))
reflectᴱ H (binexp M op N) (binOp₁ s) (BinOpMismatch₂ p) = Left (BinOpMismatch₂ (≮:-heap-weakeningᴱ H N (rednᴱ⊑ s) p))
reflectᴱ H (binexp M op N) (binOp₁ s) (bin₁ W) = mapL bin₁ (reflectᴱ H M s W)
reflectᴱ H (binexp M op N) (binOp₁ s) (bin₂ W) = Left (bin₂ (reflect-weakeningᴱ H N (rednᴱ⊑ s) W))
reflectᴱ H (binexp M op N) (binOp₂ s) (BinOpMismatch₁ p) = Left (BinOpMismatch₁ (heap-weakeningᴱ H M (rednᴱ⊑ s) p))
reflectᴱ H (binexp M op N) (binOp₂ s) (BinOpMismatch₂ p) = Left (cond BinOpMismatch₂ bin₂ (reflect-subtyping H N s p))
reflectᴱ H (binexp M op N) (binOp₂ s) (BinOpMismatch₁ p) = Left (BinOpMismatch₁ (≮:-heap-weakeningᴱ H M (rednᴱ⊑ s) p))
reflectᴱ H (binexp M op N) (binOp₂ s) (BinOpMismatch₂ p) = Left (cond BinOpMismatch₂ bin₂ (≮:-reduction H N s p))
reflectᴱ H (binexp M op N) (binOp₂ s) (bin₁ W) = Left (bin₁ (reflect-weakeningᴱ H M (rednᴱ⊑ s) W))
reflectᴱ H (binexp M op N) (binOp₂ s) (bin₂ W) = mapL bin₂ (reflectᴱ H N s W)
reflectᴮ H (local var x T M B) (local s) (LocalVarMismatch p) = Left (cond LocalVarMismatch local₁ (reflect-subtyping H M s p))
reflectᴮ H (local var x T M B) (local s) (LocalVarMismatch p) = Left (cond LocalVarMismatch local₁ (≮:-reduction H M s p))
reflectᴮ H (local var x T M B) (local s) (local₁ W) = mapL local₁ (reflectᴱ H M s W)
reflectᴮ H (local var x T M B) (local s) (local₂ W) = Left (local₂ (reflect-weakeningᴮ (x T) H B (rednᴱ⊑ s) W))
reflectᴮ H (local var x T M B) (subst v) W = Left (cond local₂ (cond local₁ LocalVarMismatch) (reflect-substitutionᴮ H B v x W))
@ -258,7 +295,7 @@ reflectᴴᴱ H (M $ N) (app₁ s) W = mapL app₁ (reflectᴴᴱ H M s W)
reflectᴴᴱ H (M $ N) (app₂ v s) W = mapL app₂ (reflectᴴᴱ H N s W)
reflectᴴᴱ H (M $ N) (beta O v refl p) W = Right W
reflectᴴᴱ H (function f var x T ⟩∈ U is B end) (function a p) (addr b refl W) with b ≡ᴬ a
reflectᴴᴱ H (function f var x T ⟩∈ U is B end) (function a defn) (addr b refl (FunctionDefnMismatch p)) | yes refl = Left (FunctionDefnMismatch (heap-weakeningᴮ (x T) H B (snoc defn) p))
reflectᴴᴱ H (function f var x T ⟩∈ U is B end) (function a defn) (addr b refl (FunctionDefnMismatch p)) | yes refl = Left (FunctionDefnMismatch (≮:-heap-weakeningᴮ (x T) H B (snoc defn) p))
reflectᴴᴱ H (function f var x T ⟩∈ U is B end) (function a defn) (addr b refl (function₁ W)) | yes refl = Left (function₁ (reflect-weakeningᴮ (x T) H B (snoc defn) W))
reflectᴴᴱ H (function f var x T ⟩∈ U is B end) (function a p) (addr b refl W) | no q = Right (addr b (lookup-not-allocated p q) (reflect-weakeningᴼ H _ (snoc p) W))
reflectᴴᴱ H (block var b T is B end) (block s) W = mapL block₁ (reflectᴴᴮ H B s W)
@ -269,7 +306,7 @@ reflectᴴᴱ H (binexp M op N) (binOp₁ s) W = mapL bin₁ (reflectᴴᴱ H M
reflectᴴᴱ H (binexp M op N) (binOp₂ s) W = mapL bin₂ (reflectᴴᴱ H N s W)
reflectᴴᴮ H (function f var x T ⟩∈ U is C end B) (function a p) (addr b refl W) with b ≡ᴬ a
reflectᴴᴮ H (function f var x T ⟩∈ U is C end B) (function a defn) (addr b refl (FunctionDefnMismatch p)) | yes refl = Left (FunctionDefnMismatch (heap-weakeningᴮ (x T) H C (snoc defn) p))
reflectᴴᴮ H (function f var x T ⟩∈ U is C end B) (function a defn) (addr b refl (FunctionDefnMismatch p)) | yes refl = Left (FunctionDefnMismatch (≮:-heap-weakeningᴮ (x T) H C (snoc defn) p))
reflectᴴᴮ H (function f var x T ⟩∈ U is C end B) (function a defn) (addr b refl (function₁ W)) | yes refl = Left (function₁ (reflect-weakeningᴮ (x T) H C (snoc defn) W))
reflectᴴᴮ H (function f var x T ⟩∈ U is C end B) (function a p) (addr b refl W) | no q = Right (addr b (lookup-not-allocated p q) (reflect-weakeningᴼ H _ (snoc p) W))
reflectᴴᴮ H (local var x T M B) (local s) W = mapL local₁ (reflectᴴᴱ H M s W)

View file

@ -5,7 +5,7 @@ module Properties.Subtyping where
open import Agda.Builtin.Equality using (_≡_; refl)
open import FFI.Data.Either using (Either; Left; Right; mapLR; swapLR; cond)
open import FFI.Data.Maybe using (Maybe; just; nothing)
open import Luau.Subtyping using (_<:_; _≮:_; Tree; Language; ¬Language; witness; unknown; never; scalar; function; scalar-function; scalar-function-ok; scalar-function-err; scalar-scalar; function-scalar; function-ok; function-err; left; right; _,_)
open import Luau.Subtyping using (_<:_; _≮:_; Tree; Language; ¬Language; witness; unknown; never; scalar; function; scalar-function; scalar-function-ok; scalar-function-err; scalar-function-tgt; scalar-scalar; function-scalar; function-ok; function-ok₁; function-ok₂; function-err; function-tgt; left; right; _,_)
open import Luau.Type using (Type; Scalar; nil; number; string; boolean; never; unknown; _⇒_; __; _∩_; skalar)
open import Properties.Contradiction using (CONTRADICTION; ¬; )
open import Properties.Equality using (_≢_)
@ -19,37 +19,42 @@ dec-language nil (scalar boolean) = Left (scalar-scalar boolean nil (λ ()))
dec-language nil (scalar string) = Left (scalar-scalar string nil (λ ()))
dec-language nil (scalar nil) = Right (scalar nil)
dec-language nil function = Left (scalar-function nil)
dec-language nil (function-ok t) = Left (scalar-function-ok nil)
dec-language nil (function-ok s t) = Left (scalar-function-ok nil)
dec-language nil (function-err t) = Left (scalar-function-err nil)
dec-language boolean (scalar number) = Left (scalar-scalar number boolean (λ ()))
dec-language boolean (scalar boolean) = Right (scalar boolean)
dec-language boolean (scalar string) = Left (scalar-scalar string boolean (λ ()))
dec-language boolean (scalar nil) = Left (scalar-scalar nil boolean (λ ()))
dec-language boolean function = Left (scalar-function boolean)
dec-language boolean (function-ok t) = Left (scalar-function-ok boolean)
dec-language boolean (function-ok s t) = Left (scalar-function-ok boolean)
dec-language boolean (function-err t) = Left (scalar-function-err boolean)
dec-language number (scalar number) = Right (scalar number)
dec-language number (scalar boolean) = Left (scalar-scalar boolean number (λ ()))
dec-language number (scalar string) = Left (scalar-scalar string number (λ ()))
dec-language number (scalar nil) = Left (scalar-scalar nil number (λ ()))
dec-language number function = Left (scalar-function number)
dec-language number (function-ok t) = Left (scalar-function-ok number)
dec-language number (function-ok s t) = Left (scalar-function-ok number)
dec-language number (function-err t) = Left (scalar-function-err number)
dec-language string (scalar number) = Left (scalar-scalar number string (λ ()))
dec-language string (scalar boolean) = Left (scalar-scalar boolean string (λ ()))
dec-language string (scalar string) = Right (scalar string)
dec-language string (scalar nil) = Left (scalar-scalar nil string (λ ()))
dec-language string function = Left (scalar-function string)
dec-language string (function-ok t) = Left (scalar-function-ok string)
dec-language string (function-ok s t) = Left (scalar-function-ok string)
dec-language string (function-err t) = Left (scalar-function-err string)
dec-language (T₁ T₂) (scalar s) = Left (function-scalar s)
dec-language (T₁ T₂) function = Right function
dec-language (T₁ T₂) (function-ok t) = mapLR function-ok function-ok (dec-language T₂ t)
dec-language (T₁ T₂) (function-ok s t) = cond (Right function-ok₁) (λ p mapLR (function-ok p) function-ok (dec-language T₂ t)) (dec-language T₁ s)
dec-language (T₁ T₂) (function-err t) = mapLR function-err function-err (swapLR (dec-language T₁ t))
dec-language never t = Left never
dec-language unknown t = Right unknown
dec-language (T₁ T₂) t = cond (λ p cond (Left _,_ p) (Right right) (dec-language T₂ t)) (Right left) (dec-language T₁ t)
dec-language (T₁ T₂) t = cond (Left left) (λ p cond (Left right) (Right _,_ p) (dec-language T₂ t)) (dec-language T₁ t)
dec-language nil (function-tgt t) = Left (scalar-function-tgt nil)
dec-language (T₁ T₂) (function-tgt t) = mapLR function-tgt function-tgt (dec-language T₂ t)
dec-language boolean (function-tgt t) = Left (scalar-function-tgt boolean)
dec-language number (function-tgt t) = Left (scalar-function-tgt number)
dec-language string (function-tgt t) = Left (scalar-function-tgt string)
-- ¬Language T is the complement of Language T
language-comp : {T} t ¬Language T t ¬(Language T t)
@ -61,9 +66,12 @@ language-comp (scalar s) (scalar-scalar s p₁ p₂) (scalar s) = p₂ refl
language-comp (scalar s) (function-scalar s) (scalar s) = language-comp function (scalar-function s) function
language-comp (scalar s) never (scalar ())
language-comp function (scalar-function ()) function
language-comp (function-ok t) (scalar-function-ok ()) (function-ok q)
language-comp (function-ok t) (function-ok p) (function-ok q) = language-comp t p q
language-comp (function-err t) (function-err p) (function-err q) = language-comp t q p
language-comp (function-ok s t) (scalar-function-ok ()) (function-ok₁ p)
language-comp (function-ok s t) (function-ok p₁ p₂) (function-ok₁ q) = language-comp s q p₁
language-comp (function-ok s t) (function-ok p₁ p₂) (function-ok₂ q) = language-comp t p₂ q
language-comp (function-err t) (function-err p) (function-err q) = language-comp t q p
language-comp (function-tgt t) (scalar-function-tgt ()) (function-tgt q)
language-comp (function-tgt t) (function-tgt p) (function-tgt q) = language-comp t p q
-- ≮: is the complement of <:
¬≮:-impl-<: : {T U} ¬(T ≮: U) (T <: U)
@ -90,9 +98,18 @@ language-comp (function-err t) (function-err p) (function-err q) = language-comp
≮:-trans-≡ : {S T U} (S ≮: T) (T U) (S ≮: U)
:-trans-≡ p refl = p
<:-trans-≡ : {S T U} (S <: T) (T U) (S <: U)
<:-trans-≡ p refl = p
≡-impl-<: : {T U} (T U) (T <: U)
≡-impl-<: refl = <:-refl
≡-trans-≮: : {S T U} (S T) (T ≮: U) (S ≮: U)
≡-trans-≮: refl p = p
≡-trans-<: : {S T U} (S T) (T <: U) (S <: U)
≡-trans-<: refl p = p
≮:-trans : {S T U} (S ≮: U) Either (S ≮: T) (T ≮: U)
:-trans {T = T} (witness t p q) = mapLR (witness t p) (λ z witness t z q) (dec-language T t)
@ -141,6 +158,12 @@ language-comp (function-err t) (function-err p) (function-err q) = language-comp
≮:--right : {S T U} (T ≮: U) ((S T) ≮: U)
:--right (witness t p q) = witness t (right p) q
≮:-left- : {S T U} (S ≮: (T U)) (S ≮: T)
:-left- (witness t p (q₁ , q₂)) = witness t p q₁
≮:-right- : {S T U} (S ≮: (T U)) (S ≮: U)
:-right- (witness t p (q₁ , q₂)) = witness t p q₂
-- Properties of intersection
<:-intersect : {R S T U} (R <: T) (S <: U) ((R S) <: (T U))
@ -158,6 +181,12 @@ language-comp (function-err t) (function-err p) (function-err q) = language-comp
<:-∩-symm : {T U} (T U) <: (U T)
<:-∩-symm t (p₁ , p₂) = (p₂ , p₁)
<:-∩-assocl : {S T U} (S (T U)) <: ((S T) U)
<:-∩-assocl t (p , (p₁ , p₂)) = (p , p₁) , p₂
<:-∩-assocr : {S T U} ((S T) U) <: (S (T U))
<:-∩-assocr t ((p , p₁) , p₂) = p , (p₁ , p₂)
≮:-∩-left : {S T U} (S ≮: T) (S ≮: (T U))
:-∩-left (witness t p q) = witness t p (left q)
@ -199,47 +228,84 @@ language-comp (function-err t) (function-err p) (function-err q) = language-comp
-distr-∩-<: t (left p₁ , right p₂) = right p₂
-distr-∩-<: t (right p₁ , p₂) = right p₁
∩-<:- : {S T} (S T) <: (S T)
∩-<:- t (p , _) = left p
-- Properties of functions
<:-function : {R S T U} (R <: S) (T <: U) (S T) <: (R U)
<:-function p q function function = function
<:-function p q (function-ok t) (function-ok r) = function-ok (q t r)
<:-function p q (function-ok s t) (function-ok₁ r) = function-ok₁ (<:-impl-⊇ p s r)
<:-function p q (function-ok s t) (function-ok₂ r) = function-ok₂ (q t r)
<:-function p q (function-err s) (function-err r) = function-err (<:-impl-⊇ p s r)
<:-function p q (function-tgt t) (function-tgt r) = function-tgt (q t r)
<:-function-∩-∩ : {R S T U} ((R T) (S U)) <: ((R S) (T U))
<:-function-∩-∩ function (function , function) = function
<:-function-∩-∩ (function-ok s t) (function-ok₁ p , q) = function-ok₁ (left p)
<:-function-∩-∩ (function-ok s t) (function-ok₂ p , function-ok₁ q) = function-ok₁ (right q)
<:-function-∩-∩ (function-ok s t) (function-ok₂ p , function-ok₂ q) = function-ok₂ (p , q)
<:-function-∩-∩ (function-err s) (function-err p , q) = function-err (left p)
<:-function-∩-∩ (function-tgt s) (function-tgt p , function-tgt q) = function-tgt (p , q)
<:-function-∩- : {R S T U} ((R T) (S U)) <: ((R S) (T U))
<:-function-∩- function (function , function) = function
<:-function-∩- (function-ok t) (function-ok p₁ , function-ok p₂) = function-ok (right p₂)
<:-function-∩- (function-err _) (function-err p₁ , function-err q₂) = function-err (p₁ , q₂)
<:-function-∩- (function-ok s t) (function-ok₁ p₁ , function-ok₁ p₂) = function-ok₁ (p₁ , p₂)
<:-function-∩- (function-ok s t) (p₁ , function-ok₂ p₂) = function-ok₂ (right p₂)
<:-function-∩- (function-ok s t) (function-ok₂ p₁ , p₂) = function-ok₂ (left p₁)
<:-function-∩- (function-err s) (function-err p₁ , function-err q₂) = function-err (p₁ , q₂)
<:-function-∩- (function-tgt t) (function-tgt p , q) = function-tgt (left p)
<:-function-∩ : {S T U} ((S T) (S U)) <: (S (T U))
<:-function-∩ function (function , function) = function
<:-function-∩ (function-ok t) (function-ok p₁ , function-ok p₂) = function-ok (p₁ , p₂)
<:-function-∩ (function-ok s t) (p₁ , function-ok₁ p₂) = function-ok₁ p₂
<:-function-∩ (function-ok s t) (function-ok₁ p₁ , p₂) = function-ok₁ p₁
<:-function-∩ (function-ok s t) (function-ok₂ p₁ , function-ok₂ p₂) = function-ok₂ (p₁ , p₂)
<:-function-∩ (function-err s) (function-err p₁ , function-err p₂) = function-err p₂
<:-function-∩ (function-tgt t) (function-tgt p₁ , function-tgt p₂) = function-tgt (p₁ , p₂)
<:-function- : {R S T U} ((R S) (T U)) <: ((R T) (S U))
<:-function- function (left function) = function
<:-function- (function-ok t) (left (function-ok p)) = function-ok (left p)
<:-function- (function-ok s t) (left (function-ok₁ p)) = function-ok₁ (left p)
<:-function- (function-ok s t) (left (function-ok₂ p)) = function-ok₂ (left p)
<:-function- (function-err s) (left (function-err p)) = function-err (left p)
<:-function- (scalar s) (left (scalar ()))
<:-function- function (right function) = function
<:-function- (function-ok t) (right (function-ok p)) = function-ok (right p)
<:-function- (function-ok s t) (right (function-ok₁ p)) = function-ok₁ (right p)
<:-function- (function-ok s t) (right (function-ok₂ p)) = function-ok₂ (right p)
<:-function- (function-err s) (right (function-err x)) = function-err (right x)
<:-function- (scalar s) (right (scalar ()))
<:-function- (function-tgt t) (left (function-tgt p)) = function-tgt (left p)
<:-function- (function-tgt t) (right (function-tgt p)) = function-tgt (right p)
<:-function--∩ : {R S T U} ((R S) (T U)) <: ((R T) (S U))
<:-function--∩ function function = left function
<:-function--∩ (function-ok t) (function-ok (left p)) = left (function-ok p)
<:-function--∩ (function-ok t) (function-ok (right p)) = right (function-ok p)
<:-function--∩ (function-ok s t) (function-ok₁ (left p)) = left (function-ok₁ p)
<:-function--∩ (function-ok s t) (function-ok₂ (left p)) = left (function-ok₂ p)
<:-function--∩ (function-ok s t) (function-ok₁ (right p)) = right (function-ok₁ p)
<:-function--∩ (function-ok s t) (function-ok₂ (right p)) = right (function-ok₂ p)
<:-function--∩ (function-err s) (function-err (left p)) = left (function-err p)
<:-function--∩ (function-err s) (function-err (right p)) = right (function-err p)
<:-function--∩ (function-tgt t) (function-tgt (left p)) = left (function-tgt p)
<:-function--∩ (function-tgt t) (function-tgt (right p)) = right (function-tgt p)
<:-function-left : {R S T U} (S T) <: (R U) (R <: S)
<:-function-left {R} {S} p s Rs with dec-language S s
<:-function-left p s Rs | Right Ss = Ss
<:-function-left p s Rs | Left ¬Ss with p (function-err s) (function-err ¬Ss)
<:-function-left p s Rs | Left ¬Ss | function-err ¬Rs = CONTRADICTION (language-comp s ¬Rs Rs)
<:-function-right : {R S T U} (S T) <: (R U) (T <: U)
<:-function-right p t Tt with p (function-tgt t) (function-tgt Tt)
<:-function-right p t Tt | function-tgt St = St
≮:-function-left : {R S T U} (R ≮: S) (S T) ≮: (R U)
:-function-left (witness t p q) = witness (function-err t) (function-err q) (function-err p)
≮:-function-right : {R S T U} (T ≮: U) (S T) ≮: (R U)
:-function-right (witness t p q) = witness (function-ok t) (function-ok p) (function-ok q)
:-function-right (witness t p q) = witness (function-tgt t) (function-tgt p) (function-tgt q)
-- Properties of scalars
skalar-function-ok : {t} (¬Language skalar (function-ok t))
skalar-function-ok : {s t} (¬Language skalar (function-ok s t))
skalar-function-ok = (scalar-function-ok number , (scalar-function-ok string , (scalar-function-ok nil , scalar-function-ok boolean)))
scalar-<: : {S T} (s : Scalar S) Language T (scalar s) (S <: T)
@ -261,7 +327,7 @@ scalar-≮:-function : ∀ {S T U} → (Scalar U) → (U ≮: (S ⇒ T))
scalar-≮:-function s = witness (scalar s) (scalar s) (function-scalar s)
unknown-≮:-scalar : {U} (Scalar U) (unknown ≮: U)
unknown-≮:-scalar s = witness (function-ok (scalar s)) unknown (scalar-function-ok s)
unknown-≮:-scalar s = witness function unknown (scalar-function s)
scalar-≮:-never : {U} (Scalar U) (U ≮: never)
scalar-≮:-never s = witness (scalar s) (scalar s) never
@ -288,6 +354,9 @@ never-≮: (witness t p q) = witness t p never
unknown-≮:-never : (unknown ≮: never)
unknown-≮:-never = witness (scalar nil) unknown never
unknown-≮:-function : {S T} (unknown ≮: (S T))
unknown-≮:-function = witness (scalar nil) unknown (function-scalar nil)
function-≮:-never : {T U} ((T U) ≮: never)
function-≮:-never = witness function function never
@ -310,8 +379,9 @@ function-≮:-never = witness function function never
<:-everything : unknown <: ((never unknown) skalar)
<:-everything (scalar s) p = right (skalar-scalar s)
<:-everything function p = left function
<:-everything (function-ok t) p = left (function-ok unknown)
<:-everything (function-ok s t) p = left (function-ok never)
<:-everything (function-err s) p = left (function-err never)
<:-everything (function-tgt t) p = left (function-tgt unknown)
-- A Gentle Introduction To Semantic Subtyping (https://www.cduce.org/papers/gentle.pdf)
-- defines a "set-theoretic" model (sec 2.5)
@ -351,8 +421,9 @@ set-theoretic-if {S₁} {T₁} {S₂} {T₂} p Q q (t , just u) Qtu (S₂t , ¬T
S₁t | Right r = r
¬T₁u : ¬(Language T₁ u)
¬T₁u T₁u with p (function-ok u) (function-ok T₁u)
¬T₁u T₁u | function-ok T₂u = ¬T₂u T₂u
¬T₁u T₁u with p (function-ok t u) (function-ok₂ T₁u)
¬T₁u T₁u | function-ok₁ ¬S₂t = language-comp t ¬S₂t S₂t
¬T₁u T₁u | function-ok₂ T₂u = ¬T₂u T₂u
set-theoretic-if {S₁} {T₁} {S₂} {T₂} p Q q (t , nothing) Qt- (S₂t , _) = q (t , nothing) Qt- (S₁t , λ ()) where
@ -365,33 +436,41 @@ set-theoretic-if {S₁} {T₁} {S₂} {T₂} p Q q (t , nothing) Qt- (S₂t , _)
not-quite-set-theoretic-only-if : {S₁ T₁ S₂ T₂}
-- We don't quite have that this is a set-theoretic model
-- it's only true when Language T₁ and ¬Language T₂ t₂ are inhabited
-- in particular it's not true when T₁ is never, or T₂ is unknown.
s₂ t₂ Language S₂ s₂ ¬Language T₂ t₂
-- it's only true when Language S₂ is inhabited
-- in particular it's not true when S₂ is never,
s₂ Language S₂ s₂
-- This is the "only if" part of being a set-theoretic model
( Q Q Comp((Language S₁) Comp(Lift(Language T₁))) Q Comp((Language S₂) Comp(Lift(Language T₂))))
(Language (S₁ T₁) Language (S₂ T₂))
not-quite-set-theoretic-only-if {S₁} {T₁} {S₂} {T₂} s₂ t₂ S₂s₂ ¬T₂t p = r where
not-quite-set-theoretic-only-if {S₁} {T₁} {S₂} {T₂} s₂ S₂s p = r where
Q : (Tree × Maybe Tree) Set
Q (t , just u) = Either (¬Language S₁ t) (Language T₁ u)
Q (t , nothing) = ¬Language S₁ t
q : Q Comp((Language S₁) Comp(Lift(Language T₁)))
q : Q Comp(Language S₁ Comp(Lift(Language T₁)))
q (t , just u) (Left ¬S₁t) (S₁t , ¬T₁u) = language-comp t ¬S₁t S₁t
q (t , just u) (Right T₂u) (S₁t , ¬T₁u) = ¬T₁u T₂u
q (t , nothing) ¬S₁t (S₁t , _) = language-comp t ¬S₁t S₁t
r : Language (S₁ T₁) Language (S₂ T₂)
r function function = function
r (function-err s) (function-err ¬S₁s) with dec-language S₂ s
r (function-err s) (function-err ¬S₁s) | Left ¬S₂s = function-err ¬S₂s
r (function-err s) (function-err ¬S₁s) | Right S₂s = CONTRADICTION (p Q q (s , nothing) ¬S₁s (S₂s , λ ()))
r (function-ok t) (function-ok T₁t) with dec-language T₂ t
r (function-ok t) (function-ok T₁t) | Left ¬T₂t = CONTRADICTION (p Q q (s₂ , just t) (Right T₁t) (S₂s₂ , language-comp t ¬T₂t))
r (function-ok t) (function-ok T₁t) | Right T₂t = function-ok T₂t
r (function-ok s t) (function-ok₁ ¬S₁s) with dec-language S₂ s
r (function-ok s t) (function-ok₁ ¬S₁s) | Left ¬S₂s = function-ok₁ ¬S₂s
r (function-ok s t) (function-ok₁ ¬S₁s) | Right S₂s = CONTRADICTION (p Q q (s , nothing) ¬S₁s (S₂s , λ ()))
r (function-ok s t) (function-ok₂ T₁t) with dec-language T₂ t
r (function-ok s t) (function-ok₂ T₁t) | Left ¬T₂t with dec-language S₂ s
r (function-ok s t) (function-ok₂ T₁t) | Left ¬T₂t | Left ¬S₂s = function-ok₁ ¬S₂s
r (function-ok s t) (function-ok₂ T₁t) | Left ¬T₂t | Right S₂s = CONTRADICTION (p Q q (s , just t) (Right T₁t) (S₂s , language-comp t ¬T₂t))
r (function-ok s t) (function-ok₂ T₁t) | Right T₂t = function-ok₂ T₂t
r (function-tgt t) (function-tgt T₁t) with dec-language T₂ t
r (function-tgt t) (function-tgt T₁t) | Left ¬T₂t = CONTRADICTION (p Q q (s₂ , just t) (Right T₁t) (S₂s₂ , language-comp t ¬T₂t))
r (function-tgt t) (function-tgt T₁t) | Right T₂t = function-tgt T₂t
-- A counterexample when the argument type is empty.
@ -399,22 +478,4 @@ set-theoretic-counterexample-one : (∀ Q → Q ⊆ Comp((Language never) ⊗ Co
set-theoretic-counterexample-one Q q ((scalar s) , u) Qtu (scalar () , p)
set-theoretic-counterexample-two : (never number) ≮: (never string)
set-theoretic-counterexample-two = witness
(function-ok (scalar number)) (function-ok (scalar number))
(function-ok (scalar-scalar number string (λ ())))
-- At some point we may deal with overloaded function resolution, which should fix this problem...
-- The reason why this is connected to overloaded functions is that currently we have that the type of
-- f(x) is (tgt T) where f:T. Really we should have the type depend on the type of x, that is use (tgt T U),
-- where U is the type of x. In particular (tgt (S => T) (U & V)) should be the same as (tgt ((S&U) => T) V)
-- and tgt(never => T) should be unknown. For example
--
-- tgt((number => string) & (string => bool))(number)
-- is tgt(number => string)(number) & tgt(string => bool)(number)
-- is tgt(number => string)(number) & tgt(string => bool)(number&unknown)
-- is tgt(number => string)(number) & tgt(string&number => bool)(unknown)
-- is tgt(number => string)(number) & tgt(never => bool)(unknown)
-- is string & unknown
-- is string
--
-- there's some discussion of this in the Gentle Introduction paper.
set-theoretic-counterexample-two = witness (function-tgt (scalar number)) (function-tgt (scalar number)) (function-tgt (scalar-scalar number string (λ ())))

View file

@ -6,9 +6,9 @@ open import Agda.Builtin.Equality using (_≡_; refl)
open import Agda.Builtin.Bool using (Bool; true; false)
open import FFI.Data.Maybe using (Maybe; just; nothing)
open import FFI.Data.Either using (Either)
open import Luau.ResolveOverloads using (resolve)
open import Luau.TypeCheck using (_⊢ᴱ_∈_; _⊢ᴮ_∈_; ⊢ᴼ_; ⊢ᴴ_; _⊢ᴴᴱ_▷_∈_; _⊢ᴴᴮ_▷_∈_; nil; var; addr; number; bool; string; app; function; block; binexp; done; return; local; nothing; orUnknown; tgtBinOp)
open import Luau.Syntax using (Block; Expr; Value; BinaryOperator; yes; nil; addr; number; bool; string; val; var; binexp; _$_; function_is_end; block_is_end; _∙_; return; done; local_←_; _⟨_⟩; _⟨_⟩∈_; var_∈_; name; fun; arg; +; -; *; /; <; >; ==; ~=; <=; >=)
open import Luau.FunctionTypes using (src; tgt)
open import Luau.Type using (Type; nil; unknown; never; number; boolean; string; _⇒_)
open import Luau.RuntimeType using (RuntimeType; nil; number; function; string; valueType)
open import Luau.VarCtxt using (VarCtxt; ∅; _↦_; _⊕_↦_; _⋒_; _⊝_) renaming (_[_] to _[_]ⱽ)
@ -40,7 +40,7 @@ typeOfᴮ : Heap yes → VarCtxt → (Block yes) → Type
typeOfᴱ H Γ (var x) = orUnknown(Γ [ x ]ⱽ)
typeOfᴱ H Γ (val v) = orUnknown(typeOfⱽ H v)
typeOfᴱ H Γ (M $ N) = tgt(typeOfᴱ H Γ M)
typeOfᴱ H Γ (M $ N) = resolve (typeOfᴱ H Γ M) (typeOfᴱ H Γ N)
typeOfᴱ H Γ (function f var x S ⟩∈ T is B end) = S T
typeOfᴱ H Γ (block var b T is B end) = T
typeOfᴱ H Γ (binexp M op N) = tgtBinOp op
@ -50,14 +50,6 @@ typeOfᴮ H Γ (local var x ∈ T ← M ∙ B) = typeOfᴮ H (Γ ⊕ x ↦ T) B
typeOfᴮ H Γ (return M B) = typeOfᴱ H Γ M
typeOfᴮ H Γ done = nil
mustBeFunction : H Γ v (never src (typeOfᴱ H Γ (val v))) (function valueType(v))
mustBeFunction H Γ nil p = CONTRADICTION (p refl)
mustBeFunction H Γ (addr a) p = refl
mustBeFunction H Γ (number n) p = CONTRADICTION (p refl)
mustBeFunction H Γ (bool true) p = CONTRADICTION (p refl)
mustBeFunction H Γ (bool false) p = CONTRADICTION (p refl)
mustBeFunction H Γ (string x) p = CONTRADICTION (p refl)
mustBeNumber : H Γ v (typeOfᴱ H Γ (val v) number) (valueType(v) number)
mustBeNumber H Γ (addr a) p with remember (H [ a ]ᴴ)
mustBeNumber H Γ (addr a) p | (just O , q) with trans (cong orUnknown (cong typeOfᴹᴼ (sym q))) p

View file

@ -3,12 +3,12 @@
module Properties.TypeNormalization where
open import Luau.Type using (Type; Scalar; nil; number; string; boolean; never; unknown; _⇒_; __; _∩_)
open import Luau.Subtyping using (scalar-function-err)
open import Luau.Subtyping using (Tree; Language; ¬Language; function; scalar; unknown; left; right; function-ok₁; function-ok₂; function-err; function-tgt; scalar-function; scalar-function-ok; scalar-function-err; scalar-function-tgt; function-scalar; _,_)
open import Luau.TypeNormalization using (_ⁿ_; _∩ⁿ_; _ᶠ_; _ⁿˢ_; _∩ⁿˢ_; normalize)
open import Luau.Subtyping using (_<:_)
open import Luau.Subtyping using (_<:_; _≮:_; witness; never)
open import Properties.Subtyping using (<:-trans; <:-refl; <:-unknown; <:-never; <:--left; <:--right; <:--lub; <:-∩-left; <:-∩-right; <:-∩-glb; <:-∩-symm; <:-function; <:-function--∩; <:-function-∩-; <:-function-; <:-everything; <:-union; <:--assocl; <:--assocr; <:--symm; <:-intersect; -distl-∩-<:; -distr-∩-<:; <:--distr-∩; <:--distl-∩; ∩-distl--<:; <:-∩-distl-; <:-∩-distr-; scalar-∩-function-<:-never; scalar-≢-∩-<:-never)
-- Notmal forms for types
-- Normal forms for types
data FunType : Type Set
data Normal : Type Set
@ -17,11 +17,11 @@ data FunType where
_∩_ : {F G} FunType F FunType G FunType (F G)
data Normal where
never : Normal never
unknown : Normal unknown
_⇒_ : {S T} Normal S Normal T Normal (S T)
_∩_ : {F G} FunType F FunType G Normal (F G)
__ : {S T} Normal S Scalar T Normal (S T)
never : Normal never
unknown : Normal unknown
data OptScalar : Type Set where
never : OptScalar never
@ -30,6 +30,38 @@ data OptScalar : Type → Set where
string : OptScalar string
nil : OptScalar nil
-- Top function type
fun-top : {F} (FunType F) (F <: (never unknown))
fun-top (S T) = <:-function <:-never <:-unknown
fun-top (F G) = <:-trans <:-∩-left (fun-top F)
-- function types are inhabited
fun-function : {F} FunType F Language F function
fun-function (S T) = function
fun-function (F G) = (fun-function F , fun-function G)
fun-≮:-never : {F} FunType F (F ≮: never)
fun-≮:-never F = witness function (fun-function F) never
-- function types aren't scalars
fun-¬scalar : {F S t} (s : Scalar S) FunType F Language F t ¬Language S t
fun-¬scalar s (S T) function = scalar-function s
fun-¬scalar s (S T) (function-ok₁ p) = scalar-function-ok s
fun-¬scalar s (S T) (function-ok₂ p) = scalar-function-ok s
fun-¬scalar s (S T) (function-err p) = scalar-function-err s
fun-¬scalar s (S T) (function-tgt p) = scalar-function-tgt s
fun-¬scalar s (F G) (p₁ , p₂) = fun-¬scalar s G p₂
¬scalar-fun : {F S} FunType F (s : Scalar S) ¬Language F (scalar s)
¬scalar-fun (S T) s = function-scalar s
¬scalar-fun (F G) s = left (¬scalar-fun F s)
scalar-≮:-fun : {F S} FunType F Scalar S S ≮: F
scalar-≮:-fun F s = witness (scalar s) (scalar s) (¬scalar-fun F s)
unknown-≮:-fun : {F} FunType F unknown ≮: F
unknown-≮:-fun F = witness (scalar nil) unknown (¬scalar-fun F nil)
-- Normalization produces normal types
normal : T Normal (normalize T)
normalᶠ : {F} FunType F Normal F
@ -40,7 +72,7 @@ normal-∩ⁿˢ : ∀ {S T} → Normal S → Scalar T → OptScalar (S ∩ⁿˢ
normal-∪ᶠ : {F G} FunType F FunType G FunType (F ∪ᶠ G)
normal nil = never nil
normal (S T) = normalᶠ ((normal S) (normal T))
normal (S T) = (normal S) (normal T)
normal never = never
normal unknown = unknown
normal boolean = never boolean
@ -338,7 +370,7 @@ flipper = <:-trans <:--assocr (<:-trans (<:-union <:-refl <:--symm) <:-
-<:-∪ⁿ unknown (T U) = <:-unknown
-<:-∪ⁿ (R S) (T U) = -<:-∪ᶠ (R S) (T U)
-<:-∪ⁿ (R S) (T U) = -<:-∪ᶠ (R S) (T U)
-<:-∪ⁿ (R S) (T U) = <:-trans <:--assocr (<:-trans (<:-union <:-refl <:--symm) (<:-trans <:--assocl (<:-union (-<:-∪ⁿ R (T U)) <:-refl)))
-<:-∪ⁿ (R S) (T U) = <:-trans <:--assocr (<:-trans (<:-union <:-refl <:--symm) (<:-trans <:--assocl (<:-union (-<:-∪ⁿ R (T U)) <:-refl)))
-<:-∪ⁿ never (T U) = <:--lub <:-never <:-refl
-<:-∪ⁿ unknown (T U) = <:-unknown
-<:-∪ⁿ (R S) (T U) = -<:-∪ᶠ (R S) (T U)

View file

@ -0,0 +1,433 @@
{-# OPTIONS --rewriting #-}
module Properties.TypeSaturation where
open import Agda.Builtin.Equality using (_≡_; refl)
open import FFI.Data.Either using (Either; Left; Right)
open import Luau.Subtyping using (Tree; Language; ¬Language; _<:_; _≮:_; witness; scalar; function; function-err; function-ok; function-ok₁; function-ok₂; scalar-function; _,_; never)
open import Luau.Type using (Type; _⇒_; _∩_; __; never; unknown)
open import Luau.TypeNormalization using (_∩ⁿ_; _ⁿ_)
open import Luau.TypeSaturation using (_⋓_; _⋒_; _∩ᵘ_; _∩ⁱ_; -saturate; ∩-saturate; saturate)
open import Properties.Subtyping using (dec-language; language-comp; <:-impl-⊇; <:-refl; <:-trans; <:-trans-≮:; <:-impl-¬≮: ; <:-never; <:-unknown; <:-function; <:-union; <:--symm; <:--left; <:--right; <:--lub; <:--assocl; <:--assocr; <:-intersect; <:-∩-symm; <:-∩-left; <:-∩-right; <:-∩-glb; ≮:-function-left; ≮:-function-right; <:-function-∩-; <:-function-∩-∩; <:-∩-assocl; <:-∩-assocr; ∩-<:-; <:-∩-distl-; ∩-distl--<:; <:-∩-distr-; ∩-distr--<:)
open import Properties.TypeNormalization using (Normal; FunType; _⇒_; _∩_; __; never; unknown; normal-∪ⁿ; normal-∩ⁿ; ∪ⁿ-<:-; -<:-∪ⁿ; ∩ⁿ-<:-∩; ∩-<:-∩ⁿ)
open import Properties.Contradiction using (CONTRADICTION)
open import Properties.Functions using (_∘_)
-- Saturation preserves normalization
normal-⋒ : {F G} FunType F FunType G FunType (F G)
normal-⋒ (R S) (T U) = (normal-∩ⁿ R T) (normal-∩ⁿ S U)
normal-⋒ (R S) (G H) = normal-⋒ (R S) G normal-⋒ (R S) H
normal-⋒ (E F) G = normal-⋒ E G normal-⋒ F G
normal-⋓ : {F G} FunType F FunType G FunType (F G)
normal-⋓ (R S) (T U) = (normal-∪ⁿ R T) (normal-∪ⁿ S U)
normal-⋓ (R S) (G H) = normal-⋓ (R S) G normal-⋓ (R S) H
normal-⋓ (E F) G = normal-⋓ E G normal-⋓ F G
normal-∩-saturate : {F} FunType F FunType (∩-saturate F)
normal-∩-saturate (S T) = S T
normal-∩-saturate (F G) = (normal-∩-saturate F normal-∩-saturate G) normal-⋒ (normal-∩-saturate F) (normal-∩-saturate G)
normal--saturate : {F} FunType F FunType (-saturate F)
normal--saturate (S T) = S T
normal--saturate (F G) = (normal--saturate F normal--saturate G) normal-⋓ (normal--saturate F) (normal--saturate G)
normal-saturate : {F} FunType F FunType (saturate F)
normal-saturate F = normal--saturate (normal-∩-saturate F)
-- Saturation resects subtyping
-saturate-<: : {F} FunType F -saturate F <: F
-saturate-<: (S T) = <:-refl
-saturate-<: (F G) = <:-trans <:-∩-left (<:-intersect (-saturate-<: F) (-saturate-<: G))
∩-saturate-<: : {F} FunType F ∩-saturate F <: F
∩-saturate-<: (S T) = <:-refl
∩-saturate-<: (F G) = <:-trans <:-∩-left (<:-intersect (∩-saturate-<: F) (∩-saturate-<: G))
saturate-<: : {F} FunType F saturate F <: F
saturate-<: F = <:-trans (-saturate-<: (normal-∩-saturate F)) (∩-saturate-<: F)
∩-<:-⋓ : {F G} FunType F FunType G (F G) <: (F G)
∩-<:-⋓ (R S) (T U) = <:-trans <:-function-∩- (<:-function (∪ⁿ-<:- R T) (-<:-∪ⁿ S U))
∩-<:-⋓ (R S) (G H) = <:-trans (<:-∩-glb (<:-intersect <:-refl <:-∩-left) (<:-intersect <:-refl <:-∩-right)) (<:-intersect (∩-<:-⋓ (R S) G) (∩-<:-⋓ (R S) H))
∩-<:-⋓ (E F) G = <:-trans (<:-∩-glb (<:-intersect <:-∩-left <:-refl) (<:-intersect <:-∩-right <:-refl)) (<:-intersect (∩-<:-⋓ E G) (∩-<:-⋓ F G))
∩-<:-⋒ : {F G} FunType F FunType G (F G) <: (F G)
∩-<:-⋒ (R S) (T U) = <:-trans <:-function-∩-∩ (<:-function (∩ⁿ-<:-∩ R T) (∩-<:-∩ⁿ S U))
∩-<:-⋒ (R S) (G H) = <:-trans (<:-∩-glb (<:-intersect <:-refl <:-∩-left) (<:-intersect <:-refl <:-∩-right)) (<:-intersect (∩-<:-⋒ (R S) G) (∩-<:-⋒ (R S) H))
∩-<:-⋒ (E F) G = <:-trans (<:-∩-glb (<:-intersect <:-∩-left <:-refl) (<:-intersect <:-∩-right <:-refl)) (<:-intersect (∩-<:-⋒ E G) (∩-<:-⋒ F G))
<:--saturate : {F} FunType F F <: -saturate F
<:--saturate (S T) = <:-refl
<:--saturate (F G) = <:-∩-glb (<:-intersect (<:--saturate F) (<:--saturate G)) (<:-trans (<:-intersect (<:--saturate F) (<:--saturate G)) (∩-<:-⋓ (normal--saturate F) (normal--saturate G)))
<:-∩-saturate : {F} FunType F F <: ∩-saturate F
<:-∩-saturate (S T) = <:-refl
<:-∩-saturate (F G) = <:-∩-glb (<:-intersect (<:-∩-saturate F) (<:-∩-saturate G)) (<:-trans (<:-intersect (<:-∩-saturate F) (<:-∩-saturate G)) (∩-<:-⋒ (normal-∩-saturate F) (normal-∩-saturate G)))
<:-saturate : {F} FunType F F <: saturate F
<:-saturate F = <:-trans (<:-∩-saturate F) (<:--saturate (normal-∩-saturate F))
-- Overloads F is the set of overloads of F
data Overloads : Type Type Set where
here : {S T} Overloads (S T) (S T)
left : {S T F G} Overloads F (S T) Overloads (F G) (S T)
right : {S T F G} Overloads G (S T) Overloads (F G) (S T)
normal-overload-src : {F S T} FunType F Overloads F (S T) Normal S
normal-overload-src (S T) here = S
normal-overload-src (F G) (left o) = normal-overload-src F o
normal-overload-src (F G) (right o) = normal-overload-src G o
normal-overload-tgt : {F S T} FunType F Overloads F (S T) Normal T
normal-overload-tgt (S T) here = T
normal-overload-tgt (F G) (left o) = normal-overload-tgt F o
normal-overload-tgt (F G) (right o) = normal-overload-tgt G o
-- An inductive presentation of the overloads of F ⋓ G
data -Lift (P Q : Type Set) : Type Set where
union : {R S T U}
P (R S)
Q (T U)
--------------------
-Lift P Q ((R T) (S U))
-- An inductive presentation of the overloads of F ⋒ G
data ∩-Lift (P Q : Type Set) : Type Set where
intersect : {R S T U}
P (R S)
Q (T U)
--------------------
∩-Lift P Q ((R T) (S U))
-- An inductive presentation of the overloads of -saturate F
data -Saturate (P : Type Set) : Type Set where
base : {S T}
P (S T)
--------------------
-Saturate P (S T)
union : {R S T U}
-Saturate P (R S)
-Saturate P (T U)
--------------------
-Saturate P ((R T) (S U))
-- An inductive presentation of the overloads of ∩-saturate F
data ∩-Saturate (P : Type Set) : Type Set where
base : {S T}
P (S T)
--------------------
∩-Saturate P (S T)
intersect : {R S T U}
∩-Saturate P (R S)
∩-Saturate P (T U)
--------------------
∩-Saturate P ((R T) (S U))
-- The <:-up-closure of a set of function types
data <:-Close (P : Type Set) : Type Set where
defn : {R S T U}
P (S T)
R <: S
T <: U
------------------
<:-Close P (R U)
-- F ⊆ᵒ G whenever every overload of F is an overload of G
_⊆ᵒ_ : Type Type Set
F ⊆ᵒ G = {S T} Overloads F (S T) Overloads G (S T)
-- F <:ᵒ G when every overload of G is a supertype of an overload of F
_<:ᵒ_ : Type Type Set
_<:ᵒ_ F G = {S T} Overloads G (S T) <:-Close (Overloads F) (S T)
-- P ⊂: Q when any type in P is a subtype of some type in Q
_⊂:_ : (Type Set) (Type Set) Set
P ⊂: Q = {S T} P (S T) <:-Close Q (S T)
-- <:-Close is a monad
just : {P S T} P (S T) <:-Close P (S T)
just p = defn p <:-refl <:-refl
infixl 5 _>>=_ _>>=ˡ_ _>>=ʳ_
_>>=_ : {P Q S T} <:-Close P (S T) (P ⊂: Q) <:-Close Q (S T)
(defn p p₁ p₂) >>= P⊂Q with P⊂Q p
(defn p p₁ p₂) >>= P⊂Q | defn q q₁ q₂ = defn q (<:-trans p₁ q₁) (<:-trans q₂ p₂)
_>>=ˡ_ : {P R S T} <:-Close P (S T) (R <: S) <:-Close P (R T)
(defn p p₁ p₂) >>=ˡ q = defn p (<:-trans q p₁) p₂
_>>=ʳ_ : {P S T U} <:-Close P (S T) (T <: U) <:-Close P (S U)
(defn p p₁ p₂) >>=ʳ q = defn p p₁ (<:-trans p₂ q)
-- Properties of ⊂:
⊂:-refl : {P} P ⊂: P
:-refl p = just p
_[]_ : {P Q R S T U} <:-Close P (R S) <:-Close Q (T U) <:-Close (-Lift P Q) ((R T) (S U))
(defn p p₁ p₂) [] (defn q q₁ q₂) = defn (union p q) (<:-union p₁ q₁) (<:-union p₂ q₂)
_[∩]_ : {P Q R S T U} <:-Close P (R S) <:-Close Q (T U) <:-Close (∩-Lift P Q) ((R T) (S U))
(defn p p₁ p₂) [∩] (defn q q₁ q₂) = defn (intersect p q) (<:-intersect p₁ q₁) (<:-intersect p₂ q₂)
⊂:-∩-saturate-inj : {P} P ⊂: ∩-Saturate P
:-∩-saturate-inj p = defn (base p) <:-refl <:-refl
⊂:--saturate-inj : {P} P ⊂: -Saturate P
:--saturate-inj p = just (base p)
⊂:-∩-lift-saturate : {P} ∩-Lift (∩-Saturate P) (∩-Saturate P) ⊂: ∩-Saturate P
:-∩-lift-saturate (intersect p q) = just (intersect p q)
⊂:--lift-saturate : {P} -Lift (-Saturate P) (-Saturate P) ⊂: -Saturate P
:--lift-saturate (union p q) = just (union p q)
⊂:-∩-lift : {P Q R S} (P ⊂: Q) (R ⊂: S) (∩-Lift P R ⊂: ∩-Lift Q S)
:-∩-lift P⊂Q R⊂S (intersect n o) = P⊂Q n [∩] R⊂S o
⊂:--lift : {P Q R S} (P ⊂: Q) (R ⊂: S) (-Lift P R ⊂: -Lift Q S)
:--lift P⊂Q R⊂S (union n o) = P⊂Q n [] R⊂S o
⊂:-∩-saturate : {P Q} (P ⊂: Q) (∩-Saturate P ⊂: ∩-Saturate Q)
:-∩-saturate P⊂Q (base p) = P⊂Q p >>= ⊂:-∩-saturate-inj
:-∩-saturate P⊂Q (intersect p q) = (⊂:-∩-saturate P⊂Q p [∩] ⊂:-∩-saturate P⊂Q q) >>= ⊂:-∩-lift-saturate
⊂:--saturate : {P Q} (P ⊂: Q) (-Saturate P ⊂: -Saturate Q)
:--saturate P⊂Q (base p) = P⊂Q p >>= ⊂:--saturate-inj
:--saturate P⊂Q (union p q) = (⊂:--saturate P⊂Q p [] ⊂:--saturate P⊂Q q) >>= ⊂:--lift-saturate
⊂:-∩-saturate-indn : {P Q} (P ⊂: Q) (∩-Lift Q Q ⊂: Q) (∩-Saturate P ⊂: Q)
:-∩-saturate-indn P⊂Q QQ⊂Q (base p) = P⊂Q p
:-∩-saturate-indn P⊂Q QQ⊂Q (intersect p q) = (⊂:-∩-saturate-indn P⊂Q QQ⊂Q p [∩] ⊂:-∩-saturate-indn P⊂Q QQ⊂Q q) >>= QQ⊂Q
⊂:--saturate-indn : {P Q} (P ⊂: Q) (-Lift Q Q ⊂: Q) (-Saturate P ⊂: Q)
:--saturate-indn P⊂Q QQ⊂Q (base p) = P⊂Q p
:--saturate-indn P⊂Q QQ⊂Q (union p q) = (⊂:--saturate-indn P⊂Q QQ⊂Q p [] ⊂:--saturate-indn P⊂Q QQ⊂Q q) >>= QQ⊂Q
-saturate-resp-∩-saturation : {P} (∩-Lift P P ⊂: P) (∩-Lift (-Saturate P) (-Saturate P) ⊂: -Saturate P)
-saturate-resp-∩-saturation ∩P⊂P (intersect (base p) (base q)) = ∩P⊂P (intersect p q) >>= ⊂:--saturate-inj
-saturate-resp-∩-saturation ∩P⊂P (intersect p (union q q₁)) = (-saturate-resp-∩-saturation ∩P⊂P (intersect p q) [] -saturate-resp-∩-saturation ∩P⊂P (intersect p q₁)) >>= ⊂:--lift-saturate >>=ˡ <:-∩-distl- >>=ʳ ∩-distl--<:
-saturate-resp-∩-saturation ∩P⊂P (intersect (union p p₁) q) = (-saturate-resp-∩-saturation ∩P⊂P (intersect p q) [] -saturate-resp-∩-saturation ∩P⊂P (intersect p₁ q)) >>= ⊂:--lift-saturate >>=ˡ <:-∩-distr- >>=ʳ ∩-distr--<:
ov-language : {F t} FunType F ( {S T} Overloads F (S T) Language (S T) t) Language F t
ov-language (S T) p = p here
ov-language (F G) p = (ov-language F (p left) , ov-language G (p right))
ov-<: : {F R S T U} FunType F Overloads F (R S) ((R S) <: (T U)) F <: (T U)
ov-<: F here p = p
ov-<: (F G) (left o) p = <:-trans <:-∩-left (ov-<: F o p)
ov-<: (F G) (right o) p = <:-trans <:-∩-right (ov-<: G o p)
<:ᵒ-impl-<: : {F G} FunType F FunType G (F <:ᵒ G) (F <: G)
<:ᵒ-impl-<: F (T U) F<G with F<G here
<:ᵒ-impl-<: F (T U) F<G | defn o o₁ o₂ = ov-<: F o (<:-function o₁ o₂)
<:ᵒ-impl-<: F (G H) F<G = <:-∩-glb (<:ᵒ-impl-<: F G (F<G left)) (<:ᵒ-impl-<: F H (F<G right))
⊂:-overloads-left : {F G} Overloads F ⊂: Overloads (F G)
:-overloads-left p = just (left p)
⊂:-overloads-right : {F G} Overloads G ⊂: Overloads (F G)
:-overloads-right p = just (right p)
⊂:-overloads-⋒ : {F G} FunType F FunType G ∩-Lift (Overloads F) (Overloads G) ⊂: Overloads (F G)
:-overloads-⋒ (R S) (T U) (intersect here here) = defn here (∩-<:-∩ⁿ R T) (∩ⁿ-<:-∩ S U)
:-overloads-⋒ (R S) (G H) (intersect here (left o)) = ⊂:-overloads-⋒ (R S) G (intersect here o) >>= ⊂:-overloads-left
:-overloads-⋒ (R S) (G H) (intersect here (right o)) = ⊂:-overloads-⋒ (R S) H (intersect here o) >>= ⊂:-overloads-right
:-overloads-⋒ (E F) G (intersect (left n) o) = ⊂:-overloads-⋒ E G (intersect n o) >>= ⊂:-overloads-left
:-overloads-⋒ (E F) G (intersect (right n) o) = ⊂:-overloads-⋒ F G (intersect n o) >>= ⊂:-overloads-right
⊂:-⋒-overloads : {F G} FunType F FunType G Overloads (F G) ⊂: ∩-Lift (Overloads F) (Overloads G)
:-⋒-overloads (R S) (T U) here = defn (intersect here here) (∩ⁿ-<:-∩ R T) (∩-<:-∩ⁿ S U)
:-⋒-overloads (R S) (G H) (left o) = ⊂:-⋒-overloads (R S) G o >>= ⊂:-∩-lift ⊂:-refl ⊂:-overloads-left
:-⋒-overloads (R S) (G H) (right o) = ⊂:-⋒-overloads (R S) H o >>= ⊂:-∩-lift ⊂:-refl ⊂:-overloads-right
:-⋒-overloads (E F) G (left o) = ⊂:-⋒-overloads E G o >>= ⊂:-∩-lift ⊂:-overloads-left ⊂:-refl
:-⋒-overloads (E F) G (right o) = ⊂:-⋒-overloads F G o >>= ⊂:-∩-lift ⊂:-overloads-right ⊂:-refl
⊂:-overloads-⋓ : {F G} FunType F FunType G -Lift (Overloads F) (Overloads G) ⊂: Overloads (F G)
:-overloads-⋓ (R S) (T U) (union here here) = defn here (-<:-∪ⁿ R T) (∪ⁿ-<:- S U)
:-overloads-⋓ (R S) (G H) (union here (left o)) = ⊂:-overloads-⋓ (R S) G (union here o) >>= ⊂:-overloads-left
:-overloads-⋓ (R S) (G H) (union here (right o)) = ⊂:-overloads-⋓ (R S) H (union here o) >>= ⊂:-overloads-right
:-overloads-⋓ (E F) G (union (left n) o) = ⊂:-overloads-⋓ E G (union n o) >>= ⊂:-overloads-left
:-overloads-⋓ (E F) G (union (right n) o) = ⊂:-overloads-⋓ F G (union n o) >>= ⊂:-overloads-right
⊂:-⋓-overloads : {F G} FunType F FunType G Overloads (F G) ⊂: -Lift (Overloads F) (Overloads G)
:-⋓-overloads (R S) (T U) here = defn (union here here) (∪ⁿ-<:- R T) (-<:-∪ⁿ S U)
:-⋓-overloads (R S) (G H) (left o) = ⊂:-⋓-overloads (R S) G o >>= ⊂:--lift ⊂:-refl ⊂:-overloads-left
:-⋓-overloads (R S) (G H) (right o) = ⊂:-⋓-overloads (R S) H o >>= ⊂:--lift ⊂:-refl ⊂:-overloads-right
:-⋓-overloads (E F) G (left o) = ⊂:-⋓-overloads E G o >>= ⊂:--lift ⊂:-overloads-left ⊂:-refl
:-⋓-overloads (E F) G (right o) = ⊂:-⋓-overloads F G o >>= ⊂:--lift ⊂:-overloads-right ⊂:-refl
-saturate-overloads : {F} FunType F Overloads (-saturate F) ⊂: -Saturate (Overloads F)
-saturate-overloads (S T) here = just (base here)
-saturate-overloads (F G) (left (left o)) = -saturate-overloads F o >>= ⊂:--saturate ⊂:-overloads-left
-saturate-overloads (F G) (left (right o)) = -saturate-overloads G o >>= ⊂:--saturate ⊂:-overloads-right
-saturate-overloads (F G) (right o) =
:-⋓-overloads (normal--saturate F) (normal--saturate G) o >>=
:--lift (-saturate-overloads F) (-saturate-overloads G) >>=
:--lift (⊂:--saturate ⊂:-overloads-left) (⊂:--saturate ⊂:-overloads-right) >>=
:--lift-saturate
overloads--saturate : {F} FunType F -Saturate (Overloads F) ⊂: Overloads (-saturate F)
overloads--saturate F = ⊂:--saturate-indn (inj F) (step F) where
inj : {F} FunType F Overloads F ⊂: Overloads (-saturate F)
inj (S T) here = just here
inj (F G) (left p) = inj F p >>= ⊂:-overloads-left >>= ⊂:-overloads-left
inj (F G) (right p) = inj G p >>= ⊂:-overloads-right >>= ⊂:-overloads-left
step : {F} FunType F -Lift (Overloads (-saturate F)) (Overloads (-saturate F)) ⊂: Overloads (-saturate F)
step (S T) (union here here) = defn here (<:--lub <:-refl <:-refl) <:--left
step (F G) (union (left (left p)) (left (left q))) = step F (union p q) >>= ⊂:-overloads-left >>= ⊂:-overloads-left
step (F G) (union (left (left p)) (left (right q))) = ⊂:-overloads-⋓ (normal--saturate F) (normal--saturate G) (union p q) >>= ⊂:-overloads-right
step (F G) (union (left (right p)) (left (left q))) = ⊂:-overloads-⋓ (normal--saturate F) (normal--saturate G) (union q p) >>= ⊂:-overloads-right >>=ˡ <:--symm >>=ʳ <:--symm
step (F G) (union (left (right p)) (left (right q))) = step G (union p q) >>= ⊂:-overloads-right >>= ⊂:-overloads-left
step (F G) (union p (right q)) with ⊂:-⋓-overloads (normal--saturate F) (normal--saturate G) q
step (F G) (union (left (left p)) (right q)) | defn (union q₁ q₂) q₃ q₄ =
(step F (union p q₁) [] just q₂) >>=
:-overloads-⋓ (normal--saturate F) (normal--saturate G) >>=
:-overloads-right >>=ˡ
<:-trans (<:-union <:-refl q₃) <:--assocl >>=ʳ
<:-trans <:--assocr (<:-union <:-refl q₄)
step (F G) (union (left (right p)) (right q)) | defn (union q₁ q₂) q₃ q₄ =
(just q₁ [] step G (union p q₂)) >>=
:-overloads-⋓ (normal--saturate F) (normal--saturate G) >>=
:-overloads-right >>=ˡ
<:-trans (<:-union <:-refl q₃) (<:--lub (<:-trans <:--left <:--right) (<:--lub <:--left (<:-trans <:--right <:--right))) >>=ʳ
<:-trans (<:--lub (<:-trans <:--left <:--right) (<:--lub <:--left (<:-trans <:--right <:--right))) (<:-union <:-refl q₄)
step (F G) (union (right p) (right q)) | defn (union q₁ q₂) q₃ q₄ with ⊂:-⋓-overloads (normal--saturate F) (normal--saturate G) p
step (F G) (union (right p) (right q)) | defn (union q₁ q₂) q₃ q₄ | defn (union p₁ p₂) p₃ p₄ =
(step F (union p₁ q₁) [] step G (union p₂ q₂)) >>=
:-overloads-⋓ (normal--saturate F) (normal--saturate G) >>=
:-overloads-right >>=ˡ
<:-trans (<:-union p₃ q₃) (<:--lub (<:-union <:--left <:--left) (<:-union <:--right <:--right)) >>=ʳ
<:-trans (<:--lub (<:-union <:--left <:--left) (<:-union <:--right <:--right)) (<:-union p₄ q₄)
step (F G) (union (right p) q) with ⊂:-⋓-overloads (normal--saturate F) (normal--saturate G) p
step (F G) (union (right p) (left (left q))) | defn (union p₁ p₂) p₃ p₄ =
(step F (union p₁ q) [] just p₂) >>=
:-overloads-⋓ (normal--saturate F) (normal--saturate G) >>=
:-overloads-right >>=ˡ
<:-trans (<:-union p₃ <:-refl) (<:--lub (<:-union <:--left <:-refl) (<:-trans <:--right <:--left)) >>=ʳ
<:-trans (<:--lub (<:-union <:--left <:-refl) (<:-trans <:--right <:--left)) (<:-union p₄ <:-refl)
step (F G) (union (right p) (left (right q))) | defn (union p₁ p₂) p₃ p₄ =
(just p₁ [] step G (union p₂ q)) >>=
:-overloads-⋓ (normal--saturate F) (normal--saturate G) >>=
:-overloads-right >>=ˡ
<:-trans (<:-union p₃ <:-refl) <:--assocr >>=ʳ
<:-trans <:--assocl (<:-union p₄ <:-refl)
step (F G) (union (right p) (right q)) | defn (union p₁ p₂) p₃ p₄ with ⊂:-⋓-overloads (normal--saturate F) (normal--saturate G) q
step (F G) (union (right p) (right q)) | defn (union p₁ p₂) p₃ p₄ | defn (union q₁ q₂) q₃ q₄ =
(step F (union p₁ q₁) [] step G (union p₂ q₂)) >>=
:-overloads-⋓ (normal--saturate F) (normal--saturate G) >>=
:-overloads-right >>=ˡ
<:-trans (<:-union p₃ q₃) (<:--lub (<:-union <:--left <:--left) (<:-union <:--right <:--right)) >>=ʳ
<:-trans (<:--lub (<:-union <:--left <:--left) (<:-union <:--right <:--right)) (<:-union p₄ q₄)
-saturated : {F} FunType F -Lift (Overloads (-saturate F)) (Overloads (-saturate F)) ⊂: Overloads (-saturate F)
-saturated F o =
:--lift (-saturate-overloads F) (-saturate-overloads F) o >>=
:--lift-saturate >>=
overloads--saturate F
∩-saturate-overloads : {F} FunType F Overloads (∩-saturate F) ⊂: ∩-Saturate (Overloads F)
∩-saturate-overloads (S T) here = just (base here)
∩-saturate-overloads (F G) (left (left o)) = ∩-saturate-overloads F o >>= ⊂:-∩-saturate ⊂:-overloads-left
∩-saturate-overloads (F G) (left (right o)) = ∩-saturate-overloads G o >>= ⊂:-∩-saturate ⊂:-overloads-right
∩-saturate-overloads (F G) (right o) =
:-⋒-overloads (normal-∩-saturate F) (normal-∩-saturate G) o >>=
:-∩-lift (∩-saturate-overloads F) (∩-saturate-overloads G) >>=
:-∩-lift (⊂:-∩-saturate ⊂:-overloads-left) (⊂:-∩-saturate ⊂:-overloads-right) >>=
:-∩-lift-saturate
overloads-∩-saturate : {F} FunType F ∩-Saturate (Overloads F) ⊂: Overloads (∩-saturate F)
overloads-∩-saturate F = ⊂:-∩-saturate-indn (inj F) (step F) where
inj : {F} FunType F Overloads F ⊂: Overloads (∩-saturate F)
inj (S T) here = just here
inj (F G) (left p) = inj F p >>= ⊂:-overloads-left >>= ⊂:-overloads-left
inj (F G) (right p) = inj G p >>= ⊂:-overloads-right >>= ⊂:-overloads-left
step : {F} FunType F ∩-Lift (Overloads (∩-saturate F)) (Overloads (∩-saturate F)) ⊂: Overloads (∩-saturate F)
step (S T) (intersect here here) = defn here <:-∩-left (<:-∩-glb <:-refl <:-refl)
step (F G) (intersect (left (left p)) (left (left q))) = step F (intersect p q) >>= ⊂:-overloads-left >>= ⊂:-overloads-left
step (F G) (intersect (left (left p)) (left (right q))) = ⊂:-overloads-⋒ (normal-∩-saturate F) (normal-∩-saturate G) (intersect p q) >>= ⊂:-overloads-right
step (F G) (intersect (left (right p)) (left (left q))) = ⊂:-overloads-⋒ (normal-∩-saturate F) (normal-∩-saturate G) (intersect q p) >>= ⊂:-overloads-right >>=ˡ <:-∩-symm >>=ʳ <:-∩-symm
step (F G) (intersect (left (right p)) (left (right q))) = step G (intersect p q) >>= ⊂:-overloads-right >>= ⊂:-overloads-left
step (F G) (intersect (right p) q) with ⊂:-⋒-overloads (normal-∩-saturate F) (normal-∩-saturate G) p
step (F G) (intersect (right p) (left (left q))) | defn (intersect p₁ p₂) p₃ p₄ =
(step F (intersect p₁ q) [∩] just p₂) >>=
:-overloads-⋒ (normal-∩-saturate F) (normal-∩-saturate G) >>=
:-overloads-right >>=ˡ
<:-trans (<:-intersect p₃ <:-refl) (<:-∩-glb (<:-intersect <:-∩-left <:-refl) (<:-trans <:-∩-left <:-∩-right)) >>=ʳ
<:-trans (<:-∩-glb (<:-intersect <:-∩-left <:-refl) (<:-trans <:-∩-left <:-∩-right)) (<:-intersect p₄ <:-refl)
step (F G) (intersect (right p) (left (right q))) | defn (intersect p₁ p₂) p₃ p₄ =
(just p₁ [∩] step G (intersect p₂ q)) >>=
:-overloads-⋒ (normal-∩-saturate F) (normal-∩-saturate G) >>=
:-overloads-right >>=ˡ
<:-trans (<:-intersect p₃ <:-refl) <:-∩-assocr >>=ʳ
<:-trans <:-∩-assocl (<:-intersect p₄ <:-refl)
step (F G) (intersect (right p) (right q)) | defn (intersect p₁ p₂) p₃ p₄ with ⊂:-⋒-overloads (normal-∩-saturate F) (normal-∩-saturate G) q
step (F G) (intersect (right p) (right q)) | defn (intersect p₁ p₂) p₃ p₄ | defn (intersect q₁ q₂) q₃ q₄ =
(step F (intersect p₁ q₁) [∩] step G (intersect p₂ q₂)) >>=
:-overloads-⋒ (normal-∩-saturate F) (normal-∩-saturate G) >>=
:-overloads-right >>=ˡ
<:-trans (<:-intersect p₃ q₃) (<:-∩-glb (<:-intersect <:-∩-left <:-∩-left) (<:-intersect <:-∩-right <:-∩-right)) >>=ʳ
<:-trans (<:-∩-glb (<:-intersect <:-∩-left <:-∩-left) (<:-intersect <:-∩-right <:-∩-right)) (<:-intersect p₄ q₄)
step (F G) (intersect p (right q)) with ⊂:-⋒-overloads (normal-∩-saturate F) (normal-∩-saturate G) q
step (F G) (intersect (left (left p)) (right q)) | defn (intersect q₁ q₂) q₃ q₄ =
(step F (intersect p q₁) [∩] just q₂) >>=
:-overloads-⋒ (normal-∩-saturate F) (normal-∩-saturate G) >>=
:-overloads-right >>=ˡ
<:-trans (<:-intersect <:-refl q₃) <:-∩-assocl >>=ʳ
<:-trans <:-∩-assocr (<:-intersect <:-refl q₄)
step (F G) (intersect (left (right p)) (right q)) | defn (intersect q₁ q₂) q₃ q₄ =
(just q₁ [∩] step G (intersect p q₂) ) >>=
:-overloads-⋒ (normal-∩-saturate F) (normal-∩-saturate G) >>=
:-overloads-right >>=ˡ
<:-trans (<:-intersect <:-refl q₃) (<:-∩-glb (<:-trans <:-∩-right <:-∩-left) (<:-∩-glb <:-∩-left (<:-trans <:-∩-right <:-∩-right))) >>=ʳ
<:-∩-glb (<:-trans <:-∩-right <:-∩-left) (<:-trans (<:-∩-glb <:-∩-left (<:-trans <:-∩-right <:-∩-right)) q₄)
step (F G) (intersect (right p) (right q)) | defn (intersect q₁ q₂) q₃ q₄ with ⊂:-⋒-overloads (normal-∩-saturate F) (normal-∩-saturate G) p
step (F G) (intersect (right p) (right q)) | defn (intersect q₁ q₂) q₃ q₄ | defn (intersect p₁ p₂) p₃ p₄ =
(step F (intersect p₁ q₁) [∩] step G (intersect p₂ q₂)) >>=
:-overloads-⋒ (normal-∩-saturate F) (normal-∩-saturate G) >>=
:-overloads-right >>=ˡ
<:-trans (<:-intersect p₃ q₃) (<:-∩-glb (<:-intersect <:-∩-left <:-∩-left) (<:-intersect <:-∩-right <:-∩-right)) >>=ʳ
<:-trans (<:-∩-glb (<:-intersect <:-∩-left <:-∩-left) (<:-intersect <:-∩-right <:-∩-right)) (<:-intersect p₄ q₄)
saturate-overloads : {F} FunType F Overloads (saturate F) ⊂: -Saturate (∩-Saturate (Overloads F))
saturate-overloads F o = -saturate-overloads (normal-∩-saturate F) o >>= (⊂:--saturate (∩-saturate-overloads F))
overloads-saturate : {F} FunType F -Saturate (∩-Saturate (Overloads F)) ⊂: Overloads (saturate F)
overloads-saturate F o = ⊂:--saturate (overloads-∩-saturate F) o >>= overloads--saturate (normal-∩-saturate F)
-- Saturated F whenever
-- * if F has overloads (R ⇒ S) and (T ⇒ U) then F has an overload which is a subtype of ((R ∩ T) ⇒ (S ∩ U))
-- * ditto union
data Saturated (F : Type) : Set where
defn :
( {R S T U} Overloads F (R S) Overloads F (T U) <:-Close (Overloads F) ((R T) (S U)))
( {R S T U} Overloads F (R S) Overloads F (T U) <:-Close (Overloads F) ((R T) (S U)))
-----------
Saturated F
-- saturated F is saturated!
saturated : {F} FunType F Saturated (saturate F)
saturated F = defn
(λ n o (saturate-overloads F n [∩] saturate-overloads F o) >>= -saturate-resp-∩-saturation ⊂:-∩-lift-saturate >>= overloads-saturate F)
(λ n o -saturated (normal-∩-saturate F) (union n o))

Some files were not shown because too many files have changed in this diff Show more