mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
02241b6d24
Some checks failed
benchmark / callgrind (map[branch:main name:luau-lang/benchmark-data], ubuntu-22.04) (push) Has been cancelled
build / ${{matrix.os.name}} (map[name:macos version:macos-latest]) (push) Has been cancelled
build / ${{matrix.os.name}} (map[name:macos-arm version:macos-14]) (push) Has been cancelled
build / ${{matrix.os.name}} (map[name:ubuntu version:ubuntu-latest]) (push) Has been cancelled
build / windows (Win32) (push) Has been cancelled
build / windows (x64) (push) Has been cancelled
build / coverage (push) Has been cancelled
build / web (push) Has been cancelled
release / ${{matrix.os.name}} (map[name:macos version:macos-latest]) (push) Has been cancelled
release / ${{matrix.os.name}} (map[name:ubuntu version:ubuntu-20.04]) (push) Has been cancelled
release / ${{matrix.os.name}} (map[name:windows version:windows-latest]) (push) Has been cancelled
release / web (push) Has been cancelled
In this update, we continue to improve the overall stability of the new type solver. We're also shipping some early bits of two new features, one of the language and one of the analysis API: user-defined type functions and an incremental typechecking API. If you use the new solver and want to use all new fixes included in this release, you have to reference an additional Luau flag: ```c++ LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease) ``` And set its value to `645`: ```c++ DFInt::LuauTypeSolverRelease.value = 645; // Or a higher value for future updates ``` ## New Solver * Fix a crash where scopes are incorrectly accessed cross-module after they've been deallocated by appropriately zeroing out associated scope pointers for free types, generic types, table types, etc. * Fix a crash where we were incorrectly caching results for bound types in generalization. * Eliminated some unnecessary intermediate allocations in the constraint solver and type function infrastructure. * Built some initial groundwork for an incremental typecheck API for use by language servers. * Built an initial technical preview for [user-defined type functions](https://rfcs.luau-lang.org/user-defined-type-functions.html), more work still to come (including calling type functions from other type functions), but adventurous folks wanting to experiment with it can try it out by enabling `FFlag::LuauUserDefinedTypeFunctionsSyntax` and `FFlag::LuauUserDefinedTypeFunction` in their local environment. Special thanks to @joonyoo181 who built up all the initial infrastructure for this during his internship! ## Miscellaneous changes * Fix a compilation error on Ubuntu (fixes #1437) --- Internal Contributors: Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com> Co-authored-by: Jeremy Yoo <jyoo@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> Co-authored-by: Junseo Yoo <jyoo@roblox.com>
312 lines
12 KiB
C++
312 lines
12 KiB
C++
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
|
#pragma once
|
|
|
|
#include "Luau/Set.h"
|
|
#include "Luau/TypeFwd.h"
|
|
#include "Luau/TypePairHash.h"
|
|
#include "Luau/TypePath.h"
|
|
#include "Luau/TypeFunction.h"
|
|
#include "Luau/TypeCheckLimits.h"
|
|
#include "Luau/DenseHash.h"
|
|
|
|
#include <vector>
|
|
#include <optional>
|
|
|
|
namespace Luau
|
|
{
|
|
|
|
template<typename A, typename B>
|
|
struct TryPair;
|
|
struct InternalErrorReporter;
|
|
|
|
class TypeIds;
|
|
class Normalizer;
|
|
struct NormalizedClassType;
|
|
struct NormalizedFunctionType;
|
|
struct NormalizedStringType;
|
|
struct NormalizedType;
|
|
struct Property;
|
|
struct Scope;
|
|
struct TableIndexer;
|
|
struct TypeArena;
|
|
struct TypeCheckLimits;
|
|
|
|
enum class SubtypingVariance
|
|
{
|
|
// Used for an empty key. Should never appear in actual code.
|
|
Invalid,
|
|
Covariant,
|
|
// This is used to identify cases where we have a covariant + a
|
|
// contravariant reason and we need to merge them.
|
|
Contravariant,
|
|
Invariant,
|
|
};
|
|
|
|
struct SubtypingReasoning
|
|
{
|
|
// The path, relative to the _root subtype_, where subtyping failed.
|
|
Path subPath;
|
|
// The path, relative to the _root supertype_, where subtyping failed.
|
|
Path superPath;
|
|
SubtypingVariance variance = SubtypingVariance::Covariant;
|
|
|
|
bool operator==(const SubtypingReasoning& other) const;
|
|
};
|
|
|
|
struct SubtypingReasoningHash
|
|
{
|
|
size_t operator()(const SubtypingReasoning& r) const;
|
|
};
|
|
|
|
using SubtypingReasonings = DenseHashSet<SubtypingReasoning, SubtypingReasoningHash>;
|
|
static const SubtypingReasoning kEmptyReasoning = SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Invalid};
|
|
|
|
struct SubtypingResult
|
|
{
|
|
bool isSubtype = false;
|
|
bool normalizationTooComplex = false;
|
|
bool isCacheable = true;
|
|
ErrorVec errors;
|
|
/// The reason for isSubtype to be false. May not be present even if
|
|
/// isSubtype is false, depending on the input types.
|
|
SubtypingReasonings reasoning{kEmptyReasoning};
|
|
|
|
SubtypingResult& andAlso(const SubtypingResult& other);
|
|
SubtypingResult& orElse(const SubtypingResult& other);
|
|
SubtypingResult& withBothComponent(TypePath::Component component);
|
|
SubtypingResult& withSuperComponent(TypePath::Component component);
|
|
SubtypingResult& withSubComponent(TypePath::Component component);
|
|
SubtypingResult& withBothPath(TypePath::Path path);
|
|
SubtypingResult& withSubPath(TypePath::Path path);
|
|
SubtypingResult& withSuperPath(TypePath::Path path);
|
|
SubtypingResult& withErrors(ErrorVec& err);
|
|
SubtypingResult& withError(TypeError err);
|
|
|
|
// Only negates the `isSubtype`.
|
|
static SubtypingResult negate(const SubtypingResult& result);
|
|
static SubtypingResult all(const std::vector<SubtypingResult>& results);
|
|
static SubtypingResult any(const std::vector<SubtypingResult>& results);
|
|
};
|
|
|
|
struct SubtypingEnvironment
|
|
{
|
|
struct GenericBounds
|
|
{
|
|
DenseHashSet<TypeId> lowerBound{nullptr};
|
|
DenseHashSet<TypeId> upperBound{nullptr};
|
|
};
|
|
|
|
/* For nested subtyping relationship tests of mapped generic bounds, we keep the outer environment immutable */
|
|
SubtypingEnvironment* parent = nullptr;
|
|
|
|
/// Applies `mappedGenerics` to the given type.
|
|
/// This is used specifically to substitute for generics in type function instances.
|
|
std::optional<TypeId> applyMappedGenerics(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty);
|
|
|
|
const TypeId* tryFindSubstitution(TypeId ty) const;
|
|
const SubtypingResult* tryFindSubtypingResult(std::pair<TypeId, TypeId> subAndSuper) const;
|
|
|
|
bool containsMappedType(TypeId ty) const;
|
|
bool containsMappedPack(TypePackId tp) const;
|
|
|
|
GenericBounds& getMappedTypeBounds(TypeId ty);
|
|
TypePackId* getMappedPackBounds(TypePackId tp);
|
|
|
|
/*
|
|
* When we encounter a generic over the course of a subtyping test, we need
|
|
* to tentatively map that generic onto a type on the other side.
|
|
*/
|
|
DenseHashMap<TypeId, GenericBounds> mappedGenerics{nullptr};
|
|
DenseHashMap<TypePackId, TypePackId> mappedGenericPacks{nullptr};
|
|
|
|
/*
|
|
* See the test cyclic_tables_are_assumed_to_be_compatible_with_classes for
|
|
* details.
|
|
*
|
|
* An empty value is equivalent to a nonexistent key.
|
|
*/
|
|
DenseHashMap<TypeId, TypeId> substitutions{nullptr};
|
|
|
|
DenseHashMap<std::pair<TypeId, TypeId>, SubtypingResult, TypePairHash> ephemeralCache{{}};
|
|
};
|
|
|
|
struct Subtyping
|
|
{
|
|
NotNull<BuiltinTypes> builtinTypes;
|
|
NotNull<TypeArena> arena;
|
|
NotNull<Normalizer> normalizer;
|
|
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
|
|
NotNull<InternalErrorReporter> iceReporter;
|
|
|
|
TypeCheckLimits limits;
|
|
|
|
enum class Variance
|
|
{
|
|
Covariant,
|
|
Contravariant
|
|
};
|
|
|
|
Variance variance = Variance::Covariant;
|
|
|
|
using SeenSet = Set<std::pair<TypeId, TypeId>, TypePairHash>;
|
|
|
|
SeenSet seenTypes{{}};
|
|
|
|
Subtyping(
|
|
NotNull<BuiltinTypes> builtinTypes,
|
|
NotNull<TypeArena> typeArena,
|
|
NotNull<Normalizer> normalizer,
|
|
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
|
|
NotNull<InternalErrorReporter> iceReporter
|
|
);
|
|
|
|
Subtyping(const Subtyping&) = delete;
|
|
Subtyping& operator=(const Subtyping&) = delete;
|
|
|
|
Subtyping(Subtyping&&) = default;
|
|
Subtyping& operator=(Subtyping&&) = default;
|
|
|
|
// Only used by unit tests to test that the cache works.
|
|
const DenseHashMap<std::pair<TypeId, TypeId>, SubtypingResult, TypePairHash>& peekCache() const
|
|
{
|
|
return resultCache;
|
|
}
|
|
|
|
// TODO cache
|
|
// TODO cyclic types
|
|
// TODO recursion limits
|
|
|
|
SubtypingResult isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope);
|
|
SubtypingResult isSubtype(TypePackId subTy, TypePackId superTy, NotNull<Scope> scope);
|
|
|
|
private:
|
|
DenseHashMap<std::pair<TypeId, TypeId>, SubtypingResult, TypePairHash> resultCache{{}};
|
|
|
|
SubtypingResult cache(SubtypingEnvironment& env, SubtypingResult res, TypeId subTy, TypeId superTy);
|
|
|
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, TypeId superTy, NotNull<Scope> scope);
|
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp, NotNull<Scope> scope);
|
|
|
|
template<typename SubTy, typename SuperTy>
|
|
SubtypingResult isContravariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy, NotNull<Scope> scope);
|
|
|
|
template<typename SubTy, typename SuperTy>
|
|
SubtypingResult isInvariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy, NotNull<Scope> scope);
|
|
|
|
template<typename SubTy, typename SuperTy>
|
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair, NotNull<Scope> scope);
|
|
|
|
template<typename SubTy, typename SuperTy>
|
|
SubtypingResult isContravariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair, NotNull<Scope>);
|
|
|
|
template<typename SubTy, typename SuperTy>
|
|
SubtypingResult isInvariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair, NotNull<Scope>);
|
|
|
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const UnionType* superUnion, NotNull<Scope> scope);
|
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const UnionType* subUnion, TypeId superTy, NotNull<Scope> scope);
|
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const IntersectionType* superIntersection, NotNull<Scope> scope);
|
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const IntersectionType* subIntersection, TypeId superTy, NotNull<Scope> scope);
|
|
|
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NegationType* subNegation, TypeId superTy, NotNull<Scope> scope);
|
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const NegationType* superNegation, NotNull<Scope> scope);
|
|
|
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const PrimitiveType* superPrim, NotNull<Scope> scope);
|
|
SubtypingResult isCovariantWith(
|
|
SubtypingEnvironment& env,
|
|
const SingletonType* subSingleton,
|
|
const PrimitiveType* superPrim,
|
|
NotNull<Scope> scope
|
|
);
|
|
SubtypingResult isCovariantWith(
|
|
SubtypingEnvironment& env,
|
|
const SingletonType* subSingleton,
|
|
const SingletonType* superSingleton,
|
|
NotNull<Scope> scope
|
|
);
|
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const TableType* superTable, NotNull<Scope> scope);
|
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull<Scope> scope);
|
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull<Scope> scope);
|
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass, NotNull<Scope> scope);
|
|
SubtypingResult
|
|
isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ClassType* subClass, TypeId superTy, const TableType* superTable, NotNull<Scope>);
|
|
SubtypingResult isCovariantWith(
|
|
SubtypingEnvironment& env,
|
|
const FunctionType* subFunction,
|
|
const FunctionType* superFunction,
|
|
NotNull<Scope> scope
|
|
);
|
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const PrimitiveType* superPrim, NotNull<Scope> scope);
|
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const TableType* superTable, NotNull<Scope> scope);
|
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const TableType* superTable, NotNull<Scope> scope);
|
|
|
|
SubtypingResult isCovariantWith(
|
|
SubtypingEnvironment& env,
|
|
const TableIndexer& subIndexer,
|
|
const TableIndexer& superIndexer,
|
|
NotNull<Scope> scope
|
|
);
|
|
SubtypingResult
|
|
isCovariantWith(SubtypingEnvironment& env, const Property& subProperty, const Property& superProperty, const std::string& name, NotNull<Scope>);
|
|
|
|
SubtypingResult isCovariantWith(
|
|
SubtypingEnvironment& env,
|
|
const std::shared_ptr<const NormalizedType>& subNorm,
|
|
const std::shared_ptr<const NormalizedType>& superNorm,
|
|
NotNull<Scope> scope
|
|
);
|
|
SubtypingResult isCovariantWith(
|
|
SubtypingEnvironment& env,
|
|
const NormalizedClassType& subClass,
|
|
const NormalizedClassType& superClass,
|
|
NotNull<Scope> scope
|
|
);
|
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const TypeIds& superTables, NotNull<Scope> scope);
|
|
SubtypingResult isCovariantWith(
|
|
SubtypingEnvironment& env,
|
|
const NormalizedStringType& subString,
|
|
const NormalizedStringType& superString,
|
|
NotNull<Scope> scope
|
|
);
|
|
SubtypingResult isCovariantWith(
|
|
SubtypingEnvironment& env,
|
|
const NormalizedStringType& subString,
|
|
const TypeIds& superTables,
|
|
NotNull<Scope> scope
|
|
);
|
|
SubtypingResult
|
|
isCovariantWith(SubtypingEnvironment& env, const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction, NotNull<Scope>);
|
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeIds& subTypes, const TypeIds& superTypes, NotNull<Scope> scope);
|
|
|
|
SubtypingResult isCovariantWith(
|
|
SubtypingEnvironment& env,
|
|
const VariadicTypePack* subVariadic,
|
|
const VariadicTypePack* superVariadic,
|
|
NotNull<Scope> scope
|
|
);
|
|
SubtypingResult isCovariantWith(
|
|
SubtypingEnvironment& env,
|
|
const TypeFunctionInstanceType* subFunctionInstance,
|
|
const TypeId superTy,
|
|
NotNull<Scope> scope
|
|
);
|
|
SubtypingResult isCovariantWith(
|
|
SubtypingEnvironment& env,
|
|
const TypeId subTy,
|
|
const TypeFunctionInstanceType* superFunctionInstance,
|
|
NotNull<Scope> scope
|
|
);
|
|
|
|
bool bindGeneric(SubtypingEnvironment& env, TypeId subTp, TypeId superTp);
|
|
bool bindGeneric(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp);
|
|
|
|
template<typename T, typename Container>
|
|
TypeId makeAggregateType(const Container& container, TypeId orElse);
|
|
|
|
std::pair<TypeId, ErrorVec> handleTypeFunctionReductionResult(const TypeFunctionInstanceType* functionInstance, NotNull<Scope> scope);
|
|
|
|
[[noreturn]] void unexpected(TypeId ty);
|
|
[[noreturn]] void unexpected(TypePackId tp);
|
|
};
|
|
|
|
} // namespace Luau
|