- Proposal: SE-NNNN
- Author: Adrian Zubarev, Anton Zhilin
- Status: Awaiting review
- Review manager: TBD
This proposal want to revise metatypes T.Type, repurpose public T.self notation to return a new Type<T> type instance rather than a metatype, merge SE-0101 into Type<T>, rename the global function from SE-0096 to match the changes of this proposal and finally rename current Mirror type to introduce a new (lazy) Mirror type.
Swift-evolution threads:
GitHub Gist thread:
The following tasks require metatype-like types:
- Explicit specialization of functions and expressing specific static types.
- Dynamic dispatch of
staticmethods. - Representing any value as a tree, for debug purposes.
- Retrieving and passing around information about dynamic types - Reflection.
Current state of things:
[1] is given to metatypes T.Type:
- The metatype instance is usually ignored.
- For example, if you pass
Derived.self as Base.selfinto function takingT.Type, it will work withBase. - This raises concerns: are metatypes perfectly suited for that purpose?
[2] is also given to metatypes T.Type:
- Because they are used so often, it's tempting to add useful methods to them, but we can't, because metatypes are not extensible types.
[3] is given to Mirror:
- Does its name reflect what it's intended to do?
Mirror.DisplayStylecontainsoptionalandsetas special cases, but does not containfunctionat all.Mirrorcollects all information possible at initialization, while for "true" reflection we want laziness.Mirrorallows customization. For example,Array<T>is represented with a field for each of its elements. Do we want this for "true" reflection we want to add in the future?
[4] is given to both metatypes T.Type and Mirror:
- Metatypes are generic. But do we want genericity in reflection? No, we almost always want to cast to
Any.Type. - Metatypes are used for getting both static and dynamic sizes.
- In this context, distinction between generic parameter
Tand value of metatype instance is unclear. - People are confused that
Mirroris intended to be used for full-featured reflection, while it does not aim for that.
Assume this function that checks if an Int type conforms to a specific protocol. This check uses current model of metatypes combined in a generic context:
func intConformsTo<T>(_: T.Type) -> Bool {
return Int.self is T.Type
}
intConformsTo(CustomReflectable.self) //=> FALSE[1] When
Tis a protocolP,T.Typeis the metatype of the protocol type itself,P.Protocol.Int.selfisnot P.self.[2] There isn't a way to generically expression
P.Typeyet.[3] The syntax would have to be changed in the compiler to get something that behaves like
.Typetoday.
A possible workaround might look like the example below, but does not allow to decompose P.Type which is a major implementation problem of this proposal:
func intConformsTo<T>(_: T.Type) -> Bool {
return Int.self is T
}
intConformsTo(CustomReflectable.Type.self) //=> TRUEThis issue was first found and documented as a strange issue in SR-2085. It also raises the concerns: do we need .Protocol at all?
We can extend this issue and find the second problem by checking agains the metatype of Any:
func intConformsTo<T>(_: T.Type) -> Bool {
return Int.self is T
}
intConformsTo(Any.Type.self) //=> TRUE
intConformsTo(Any.self) //=> TRUEAs you clearly can see, when using Any the compiler does not require .Type at all.
The third issue will show itself whenever we would try to check protocol relationship with another protocol. Currently there is no way (that we know of) to solve this problem:
protocol P {}
protocol R : P {}
func rIsSubtypeOf<T>(_: T.Type) -> Bool {
return R.self is T
}
rIsSubtypeOf(P.Type.self) //=> FALSEWe also believe that this issue is also the reason why the current gloabl functions sizeof, strideof and alignof make use of generic <T>(_: T.Type) declaration notation instead of (_: Any.Type).
- Revise metatypes in generic context so the old
T.Typenotation does not produceT.Protocolwhen a protocol metatype is passed around. - Intoduce a distinction between public and internal
T.selfnotation where the internalT.selfnotation will be renamed toT.metatype. - Rename old metatype
T.Typenotation toMetatype<T>. - Make internal
T.metatypenotation (Buildin - not visible in public Swift) return an instance ofMetatype<T>. - Public construction of metatypes will look like
Type<T>.metatypeorT.self.metatype, see below. - Metatypes will be used only for dynamic dispatch of static methods, see example below:
protocol HasStatic { static func staticMethod() -> String; init() }
struct A : HasStatic { static func staticMethod() -> String { return "I am A" }; init() {} }
struct B : HasStatic { static func staticMethod() -> String { return "I am B" }; init() {} }
func callStatic(_ metatype: Metatype<HasStatic>) {
let result = metatype.staticMethod()
print(result)
let instance = metatype.init()
print(instance)
}
let a = Type<A>.metatype
let b = Type<B>.metatype
callStatic(a) //=> "I am A" "A()"
callStatic(b) //=> "A am B" "B()"T.self will be repurposed to return ab instance of Type<T> that is declared as follows:
public struct Type<T> : Hashable, CustomStringConvertible, CustomDebugStringConvertible {
/// Creates an instance that reflects `T`.
/// Example: `let type = T.self`
public init()
/// Returns the contiguous memory footprint of `T`.
///
/// Does not include any dynamically-allocated or "remote" storage.
/// In particular, `Type<T>.size`, when `T` is a class type, is the
/// same regardless of how many stored properties `T` has.
public static var size: Int { get }
/// Returns the least possible interval between distinct instances of
/// `T` in memory. The result is always positive.
public static var stride: Int { get }
/// Returns the default memory alignment of `T`.
public static var alignment: Int { get }
/// Returns an instance of `Metatype<T>` from captured `T` literal.
public static var metatype: Metatype<T> { get }
/// Returns the contiguous memory footprint of `T`.
///
/// Does not include any dynamically-allocated or "remote" storage.
/// In particular, `Type<T>().size`, when `T` is a class type, is the
/// same regardless of how many stored properties `T` has.
public var size: Int { get }
/// Returns the least possible interval between distinct instances of
/// `T` in memory. The result is always positive.
public var stride: Int { get }
/// Returns the default memory alignment of `T`.
public var alignment: Int { get }
/// Returns an instance of `Metatype<T>` from captured `T` literal.
public var metatype: Metatype<T> { get }
/// Hash values are not guaranteed to be equal across different executions of
/// your program. Do not save hash values to use during a future execution.
public var hashValue: Int { get }
/// A textual representation of `self`.
public var description: String { get }
/// A textual representation of `self`, suitable for debugging.
public var debugDescription: String { get }
}
public func ==<T>(lhs: Type<T>, rhs: Type<T>) -> BoolSize of Type<T> struct equals 0. It will be used for generic function specialization:
func performWithType(_ type: Type<T>)
performWithType(Float.self)The global dynamicType function from SE-0096 will be renamed to dynamicMetatype and receive the following declaration:
/// Returns a dynamic instance of `Metatype<T>`. A dynamic
/// metatype can reflect type `U` where `U : T`.
public func dynamicMetatype<T>(_ instance: T) -> Metatype<T>Rename current Mirror (Swift 2.2) to DebugRepresentation and CustomReflectable to CustomDebugRepresentable.
A completely different Mirror type will be introduced in Swift 3.
Mirrorwraps metatypes and allows checking subtype relationships at runtime.Mirrorcontains dynamic versions ofsize,strideandalignment.- Size of
Mirroritself is always8bytes, because it only needs to store a single metatype. Mirrorprovides a starting point for adding fully functional (lazy) reflection in the future.
public struct Mirror : Hashable, CustomStringConvertible, CustomDebugStringConvertible {
/// Creates an instance of `Mirror`, reflecting type, which is
/// reflected by a metatype.
public init(_ metatype: Metatype<Any>)
/// Creates an instance of `Mirror`, reflecting type `T`
public init<T>(_ type: Type<T>)
/// Creates an instance of `Mirror`, reflecting
/// dynamic metatype of a given instance.
public init<T>(reflecting instance: T)
/// Returns the contiguous memory footprint of reflected metatype.
public var size: Int { get }
/// Returns the least possible interval between distinct instances of
/// the dynamic type in memory calculated from the reflected dynamic
/// metatype. The result is always positive.
public var stride: Int { get }
/// Returns the minimum memory alignment of the reflected dynamic
/// metatype.
public var alignment: Int { get }
/// Returns an instance of `Metatype<Any>` from reflected dynamic metatype.
public var metatype: Metatype<Any> { get }
/// Checks if type reflected by `self` is a subtype of type reflected by another `Mirror`.
public func `is`(_ mirror: Mirror) -> Bool { get }
/// Checks if type reflected by `self` is a subtype of `T`.
public func `is`<T>(_ type: Type<T>) -> Bool { get }
/// Checks if type reflected by `self` is a subtype of type reflected by a metatype.
public func `is`<T>(_ metatype: Metatype<T>) -> Bool { get }
/// Hash values are not guaranteed to be equal across different executions of
/// your program. Do not save hash values to use during a future execution.
public var hashValue: Int { get }
/// A textual representation of `self`.
public var description: String { get }
/// A textual representation of `self`, suitable for debugging.
public var debugDescription: String { get }
}
public func ==(lhs: Mirror, rhs: Mirror) -> BoolT.Typedoes three things:- Specialization of functions.
- Dynamic dispatch of static methods.
- Partial reflection using dynamic casts and functions like
sizeof,strideofetc.
Mirrordoes two things:- It is primarily intended for use in debugging, like
PlaygroundQuickLook. - With less success, it can be used for reflection.
- It is primarily intended for use in debugging, like
Type<T>does specialization of functions.Mirrordoes reflection.Metatype<T>does dynamic dispatch of static methods.DebugRepresentationis used in debugging.
public struct Type<T> : Hashable, CustomStringConvertible, CustomDebugStringConvertible {
/// Creates an instance that reflects `T`.
/// Example: `let type = T.self`
public init() {}
/// Returns the contiguous memory footprint of `T`.
///
/// Does not include any dynamically-allocated or "remote" storage.
/// In particular, `Type<T>.size`, when `T` is a class type, is the
/// same regardless of how many stored properties `T` has.
public static var size: Int { return _size(of: T.metatype) }
/// Returns the least possible interval between distinct instances of
/// `T` in memory. The result is always positive.
public static var stride: Int { return _stride(of: T.metatype) }
/// Returns the default memory alignment of `T`.
public static var alignment: Int { return _alignment(of: T.metatype) }
/// Returns an instance of `Metatype<T>` from captured `T` literal.
public static var metatype: Metatype<T> { return T.metatype }
/// Returns the contiguous memory footprint of `T`.
///
/// Does not include any dynamically-allocated or "remote" storage.
/// In particular, `Type<T>().size`, when `T` is a class type, is the
/// same regardless of how many stored properties `T` has.
public var size: Int { return Type<T>.size }
/// Returns the least possible interval between distinct instances of
/// `T` in memory. The result is always positive.
public var stride: Int { return Type<T>.stride }
/// Returns the default memory alignment of `T`.
public var alignment: Int { return Type<T>.alignment }
/// Returns an instance of `Metatype<T>` from captured `T` literal.
public var metatype: Metatype<T> { return Type<T>.metatype }
/// Hash values are not guaranteed to be equal across different executions of
/// your program. Do not save hash values to use during a future execution.
public var hashValue: Int { return _uniqueIdentifier(for: self.metatype) }
/// A textual representation of `self`.
public var description: String { return "Type<\(self.metatype)>()" }
/// A textual representation of `self`, suitable for debugging.
public var debugDescription: String {
return "[" + self.description
+ " metatype: \(self.metatype)"
+ " size: \(self.size)"
+ " stride: \(self.stride)"
+ " alignment: \(self.alignment)]"
}
}
public func ==<T>(lhs: Type<T>, rhs: Type<T>) -> Bool { return true }
/// Returns a dynamic instance of `Metatype<T>`. A dynamic
/// metatype can reflect type `U` where `U : T`.
public func dynamicMetatype<T>(_ instance: T) -> Metatype<T> {
return /* implement */
}
public struct Mirror : Hashable, CustomStringConvertible, CustomDebugStringConvertible {
/// Storage for any dynamic metatype.
internal let _metatype: Metatype<Any>
/// Creates an instance of `Mirror`, reflecting type, which is
/// reflected by a metatype.
public init(_ metatype: Metatype<Any>) {
self._metatype = metatype
}
/// Creates an instance of `Mirror`, reflecting type `T`
public init<T>(_ type: Type<T>) {
self._metatype = type.metatype
}
/// Creates an instance of `Mirror`, reflecting
/// dynamic type of a given instance.
public init<T>(reflecting instance: T) {
self._metatype = dynamicMetatype(instance)
}
/// Returns the contiguous memory footprint of reflected metatype.
public var size: Int { return _size(of: self._metatype) }
/// Returns the least possible interval between distinct instances of
/// the dynamic type in memory calculated from the reflected dynamic
/// metatype. The result is always positive.
public var stride: Int { return _stride(of: self._metatype) }
/// Returns the minimum memory alignment of the reflected dynamic
/// metatype.
public var alignment: Int { return _alignment(of: self._metatype) }
/// Returns an instance of `Metatype<T>` from reflected dynamic metatype.
public var metatype: Any.Type { return self._metatype }
/// Checks if type reflected by `self` is a subtype of type reflected by another `Mirror`.
public func `is`(_ mirror: Mirror) -> Bool {
return _is(metatype: self._metatype, also: mirror.metatype)
}
/// Checks if type reflected by `self` is a subtype of `T`.
public func `is`<T>(_ type: Type<T>) -> Bool {
return _is(metatype: self._metatype, also: type.metatype)
}
/// Checks if type reflected by `self` is a subtype of type reflected by a metatype.
public func `is`<T>(_ metatype: Metatype<T>) -> Bool {
return _is(metatype: self._metatype, also: metatype)
}
/// Hash values are not guaranteed to be equal across different executions of
/// your program. Do not save hash values to use during a future execution.
public var hashValue: Int { return _uniqueIdentifier(for: self._metatype) }
/// A textual representation of `self`.
public var description: String { return "Mirror(\(self._metatype))" }
/// A textual representation of `self`, suitable for debugging.
public var debugDescription: String {
return "[" + self.description
+ " metatype: \(self._metatype)"
+ " size: \(self.size)"
+ " stride: \(self.stride)"
+ " alignment: \(self.alignment)]"
}
}
public func ==(lhs: Mirror, rhs: Mirror) -> Bool {
return lhs.hashValue == rhs.hashValue
}These functions were used in the implementation above to calculate metatype related informations.
-
_size(of:),_stride(of:)and_alignment(of:)functions need some additional tweaking so they will work with any matatype stored in an instance ofMetatype<Any>rather than a dynamic<T>(of metatype: Metatype<T>)variant, which is not suitable for calculations needed inMirror. -
_uniqueIdentifier(for:)function is fully implemented and should just work when the current generic issue with.Protocolmetatypes is resolved. -
_is(metatype:also:)relies on the resolved.Protocolissue. The final implementation should allow to check type relationship between two different metatype instances.
internal func _size(of metatype: Metatype<Any>) -> Int {
// Fix this to allow any metatype
return Int(Builtin.sizeof(metatype))
}
internal func _stride(of metatype: Metatype<Any>) -> Int {
// Fix this to allow any metatype
return Int(Builtin.strideof_nonzero(metatype))
}
internal func _alignment(of metatype: Metatype<Any>) -> Int {
// Fix this to allow any metatype
return Int(Builtin.alignof(metatype))
}
internal func _uniqueIdentifier(for metatype: Metatype<Any>) -> Int {
let rawPointerMetatype = unsafeBitCast(metatype, to: Builtin.RawPointer.metatype)
return Int(Builtin.ptrtoint_Word(rawPointerMetatype))
}
internal func _is(metatype m1: Metatype<Any>, also m2: Metatype<Any>) -> Bool {
return /* implement - checks type ralationshiop `M1 : M2` and `M1 == M2` */
}- Revise metatypes in generic context so the old
T.Typenotation does not produceT.Protocolwhen a protocol metatype is passed around. - Make public
T.selfnotation return an instance ofType<T>. - Rename internal
T.selfnotation toT.metatype(Buildin - not visible in public Swift). - Rename old metatype
T.Typenotation toMetatype<T>. - Make internal
T.metatypenotation return an instance ofMetatype<T>. - Revise APIs with current
T.Typenotation to useType<T>and in few edge casesMetatype<T>. - Move
size,strideandalignmentfrom SE-0101 toType<T>. - Provide a concrete declaration for SE-0096 and rename it to
dynamicMetatype. - Rename current
Mirrortype (Swift 2.2) toDebugRepresentationandCustomReflectabletoCustomDebugRepresentable. - Introduce a new
Mirrortype that is intended to replace metatypes for most use cases and extended with reflection in a future release.
This is a source-breaking change that can be automated by a migrator.
The following steps reflects our suggestion of the migration process, these can differ from the final migration process implemented by the core team if this proposal will be accepted:
T.Type→Metatype<T>T.self→Type<T>.metatypeMirror→DebugRepresentationCustomReflectable→CustomDebugRepresentablecustomMirror→customDebugRepresentation
sizeof(T.self)→Type<T>.sizesizeof(metatype)→Mirror(metatype).size
Metatype<T> is a safe default for transition, but we want to discourage usage of metatypes.
In some cases, we can provide fix-its to replace usage of Metatype<T> with Type<T> or Mirror.
To change type of a variable named type from Metatype<T> to Type<T>:
- Replace its type with
Type<T>. - Use the migration patterns below.
- If some use case does not match any of these, the variable cannot be migrated to type
Type<T>.
Migration patterns:
type = T.self.metatype→type = T.selftype = U.self.metatypewhereU != T→ Automatic migration impossibletype = Type<T>.metatype→type = T.selftype = Type<U>.metatypewhereU != T→ Automatic migration impossibletype = otherMetatypewhereotherMetatype: Metatype<T>→type = T.selftype = otherMetatypewhereotherMetatype: Metatype<U>,U != T→ Automatic migration impossibletype = mirror.metatypewheremirror: Mirror→ Automatic migration impossibleotherMetatype = typewhereotherMetatype: Metatype<U>→otherMetatype = Type<T>.metatypeMirror(type)→Mirror(type)type as otherMetatypewhereotherMetatype: Metatype<U>→type.metatype as metatype<U>type as? otherMetatype→ Automatic migration impossibletype as! otherMetatype→ Automatic migration impossibletype is otherMetatype→ Automatic migration impossible
How to change type of a variable named type from Metatype<T> to Mirror:
- Replace its type with
Mirror. - Use the migration patterns below.
- If some use case does not match any of these, the variable cannot be migrated to type
Mirror.
Migration patterns:
type: Metatype<T>→type: Mirrortype = U.self.metatype→type = Mirror(U.self)type = Type<U>.metatype→type = Mirror(U.self)type = otherMetatype→type = Mirror(otherMetatype)type = mirror.metatypewheremirror: Mirror→type = mirrorotherMetatype = type→otherMetatype = type.metatypeMirror(type)→typetype as otherMetatype→type.metatype as! otherMetatypetype as? otherMetatype→type.metatype as? otherMetatypetype as! otherMetatype→type.metatype as! otherMetatypetype is otherMetatype→type.is(otherMetatype)
We can also migrate metatype parameters of a function, where assignment means passing an argument to that function.
In two cases we can apply these automatically:
- If a generic function takes parameter
Metatype<T>, then we can try to replaceMetatype<T>withType<T>. - We can try to replace usage of
Metatype<Any>(akaAnyMetatype) withMirror.
-
After refactoring metatypes it is assumed that any metatype can be stored inside an instance of
Metatype<Any>. If that will not be the case, then we propose to introduce a new standalone type for explained behavior. That type could be named asAnyMetatype. Therefore any type marked withMetatype<Any>in this proposal will becomeAnyMetatype. -
If the community and the core team are strongly against the repurposing of
Mirrorwe'd like to consider to merge the proposed functionality into a single type. For such a change we do believeType<T>might be the right type here. However this introduces further complications such as storing dynamic metatypes inside ofType<T>and a few other that we don't want go in detail here.
When SE-0090 is accepted we will remove T.self notation and only have type literals like T.
Examples:
let someInstance = unsafeBitCast(1.0, to: Int)
let dynamicSize = Mirror(reflecting: someInstance).sizeThen we can add Type(_: Type<T>) initializer for disambiguation:
Int.self.size // Works fine with this proposal, but what if we drop `.self`?
Int.size // Will be an error after dropping `.self`.
Type<Int>().size // Would work, but looks odd.
Type(Int).size // This version looks much better.When combined with this proposal, the result will be to eliminate all 'magical' members that existed in the language:
.dynamicType.Type.self
There is also Self, but it acts like an associatedtype.
Reflection is one of stated goals for Swift 4. With this proposal, adding reflection becomes as simple as extending Mirror. For example, we could add the following computed property:
typealias FieldDescriptor = (name: String, type: Mirror, getter: (Any) -> Any, setter: (inout Any, Any) -> ())
var fields: [FieldDescriptor] { get }