- 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
static
methods. - 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.self
into 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.DisplayStyle
containsoptional
andset
as special cases, but does not containfunction
at all.Mirror
collects all information possible at initialization, while for "true" reflection we want laziness.Mirror
allows 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
T
and value of metatype instance is unclear. - People are confused that
Mirror
is 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
T
is a protocolP
,T.Type
is the metatype of the protocol type itself,P.Protocol
.Int.self
isnot P.self
.[2] There isn't a way to generically expression
P.Type
yet.[3] The syntax would have to be changed in the compiler to get something that behaves like
.Type
today.
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) //=> TRUE
This 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) //=> TRUE
As 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) //=> FALSE
We 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.Type
notation does not produceT.Protocol
when a protocol metatype is passed around. - Intoduce a distinction between public and internal
T.self
notation where the internalT.self
notation will be renamed toT.metatype
. - Rename old metatype
T.Type
notation toMetatype<T>
. - Make internal
T.metatype
notation (Buildin - not visible in public Swift) return an instance ofMetatype<T>
. - Public construction of metatypes will look like
Type<T>.metatype
orT.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>) -> Bool
Size 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.
Mirror
wraps metatypes and allows checking subtype relationships at runtime.Mirror
contains dynamic versions ofsize
,stride
andalignment
.- Size of
Mirror
itself is always8
bytes, because it only needs to store a single metatype. Mirror
provides 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) -> Bool
T.Type
does three things:- Specialization of functions.
- Dynamic dispatch of static methods.
- Partial reflection using dynamic casts and functions like
sizeof
,strideof
etc.
Mirror
does 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.Mirror
does reflection.Metatype<T>
does dynamic dispatch of static methods.DebugRepresentation
is 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.Protocol
metatypes is resolved. -
_is(metatype:also:)
relies on the resolved.Protocol
issue. 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.Type
notation does not produceT.Protocol
when a protocol metatype is passed around. - Make public
T.self
notation return an instance ofType<T>
. - Rename internal
T.self
notation toT.metatype
(Buildin - not visible in public Swift). - Rename old metatype
T.Type
notation toMetatype<T>
. - Make internal
T.metatype
notation return an instance ofMetatype<T>
. - Revise APIs with current
T.Type
notation to useType<T>
and in few edge casesMetatype<T>
. - Move
size
,stride
andalignment
from SE-0101 toType<T>
. - Provide a concrete declaration for SE-0096 and rename it to
dynamicMetatype
. - Rename current
Mirror
type (Swift 2.2) toDebugRepresentation
andCustomReflectable
toCustomDebugRepresentable
. - Introduce a new
Mirror
type 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>.metatype
Mirror
→DebugRepresentation
CustomReflectable
→CustomDebugRepresentable
customMirror
→customDebugRepresentation
sizeof(T.self)
→Type<T>.size
sizeof(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.self
type = U.self.metatype
whereU != T
→ Automatic migration impossibletype = Type<T>.metatype
→type = T.self
type = Type<U>.metatype
whereU != T
→ Automatic migration impossibletype = otherMetatype
whereotherMetatype: Metatype<T>
→type = T.self
type = otherMetatype
whereotherMetatype: Metatype<U>
,U != T
→ Automatic migration impossibletype = mirror.metatype
wheremirror: Mirror
→ Automatic migration impossibleotherMetatype = type
whereotherMetatype: Metatype<U>
→otherMetatype = Type<T>.metatype
Mirror(type)
→Mirror(type)
type as otherMetatype
whereotherMetatype: 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: Mirror
type = U.self.metatype
→type = Mirror(U.self)
type = Type<U>.metatype
→type = Mirror(U.self)
type = otherMetatype
→type = Mirror(otherMetatype)
type = mirror.metatype
wheremirror: Mirror
→type = mirror
otherMetatype = type
→otherMetatype = type.metatype
Mirror(type)
→type
type as otherMetatype
→type.metatype as! otherMetatype
type as? otherMetatype
→type.metatype as? otherMetatype
type as! otherMetatype
→type.metatype as! otherMetatype
type 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
Mirror
we'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).size
Then 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 }