- Proposal: SE-NNNN
- Author: Anton Zhilin
- Status: Awaiting review
- Review manager: TBD
- Rename metatypes
T.TypetoMetatype<T> - Add
Type<T>struct that wraps a static type - Make
T.selfsyntax createType<T> - Move static
size,stride,alignmentfunctions toType<T> - Rename
MirrortoDebugRepresentationandCustomReflectabletoCustomDebugRepresentable - Add
Mirrorstruct that is intended to replace metatypes for most use cases - Move dynamic
size,stride,alignmentfunctions toMirror
Swift-evolution thread: Discussion thread topic for that proposal
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- Instance of metatype 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
- Because they are used so often, it's tempting to add useful methods to them, but we can't, because it's not a
struct
- Because they are used so often, it's tempting to add useful methods to them, but we can't, because it's not a
- (3) is given to
Mirror- Does its name reflect what it's intended to do?
Mirror.DisplayStylecontainsoptionalandsetas special cases, but does not containfunctionMirrorcollects all information possible at initialization, while for true reflection we want lazinessMirrorallows 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 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
- Metatypes are generic. But do we want genericity in reflection? No, we almost always want to cast to
- Rename
T.TypetoMetatype<T> - Remove
T.self. Construction of metatypes will look likeType<T>.metatype, see below - Metatypes will be used only for dynamic dispatch of static methods, see example
protocol HasStatic { static func staticMethod() -> String }
struct A : HasStatic { static func staticMethod() -> String { return "A" } }
struct B : HasStatic { static func staticMethod() -> String { return "B" } }
func callStatic(_ metatype: Metatype<HasStatic>) {
let result = metatype.staticMethod()
print(result)
}
let a = Type<A>.metatype
let b = Type<B>.metatype
callStatic(a) //=> A
callStatic(b) //=> BdynamicType will be renamed to dynamicMetatype:
func dynamicMetatype<T>(_ instance: T) -> Metatype<T>T.self will be repurposed to return Type<T> that is declared as follows:
struct Type<T> : Hashable, CustomStringConvertible, CustomDebugStringConvertible {
// Type<T> is equivalent to (), but parametrized with type T
init()
static var size: Int { get }
static var stride: Int { get }
static var alignment: Int { get }
static var metatype: Metatype<T> { get }
// Equivalent to static versions
var size: Int { get }
var stride: Int { get }
var alignment: Int { get }
var metatype: Metatype<T> { get }
// Implementation of protocols
var hashValue: Int { get }
var description: String { get }
var debugDescription: String { get }
}Size of Type<T> struct equals 0. It will be used for generic function specialization:
func performWithType(_ type: Type<T>)
performWithType(Float.self)Rename current Mirror to DebugRepresentation and CustomReflectable to CustomDebugRepresentable.
A completely different type with name Mirror will be introduced.
Mirror wraps metatypes and replaces usage of metatypes with as?, is, etc.
Mirror contains dynamic versions of size, stride and alignment. Size of Mirror is 8 bytes.
Mirror provides a starting point for adding fully functional reflection in the future.
struct Mirror : Hashable, CustomStringConvertible, CustomDebugStringConvertible {
/// Create mirror, reflecting type, which is reflected by a metatype
init(_: Metatype<Any>)
/// Create mirror, reflecting type T
init<T>(_: Type<T>)
/// Create mirror, reflecting dynamic type of a given instance
init<T>(reflecting: T)
var size: Int { get }
var stride: Int { get }
var alignment: Int { get }
var metatype: Metatype<Any> { get }
var hashValue: Int { get }
var description: String { get }
var debugDescription: String { get }
/// Checks if type reflected by `self` is a subtype of type reflected by another `Mirror`.
func `is`(_ mirror: Mirror) -> Bool
/// Checks if type reflected by `self` is a subtype of `T`.
func `is`<T>(_ type: Type<T>) -> Bool
/// Checks if type reflected by `self` is a subtype of type reflected by a metatype.
func `is`<T>(_ metatype: Metatype<T>) -> Bool
}T.Typedoes 3 things:- Specialization of functions
- Dynamic dispatch of static methods
- Partial reflection using
as,isand functions likesizeofValue
Mirrordoes 2 things:- It is primarily indented for use in debugging, like
PlaygroundQuickLook - With less success, it can be used for reflection
- It is primarily indented for use in debugging, like
Type<T>does specialization of functionsMirrordoes reflectionMetatype<T>does dynamic dispatch of static methodsDebugRepresentationis used in debugging
Changes are source-breaking, automatic migration is possible.
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 will apply these transitions 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
Besides dynamicMetatype, the following global functions will be affected:
func transcode<Input, InputEncoding, OutputEncoding>(
_: Input,
from: Type<InputEncoding>,
to: Type<OutputEncoding>,
stoppingOnError: Bool,
sendingOutputTo: @noescape (OutputEncoding.CodeUnit) -> Swift.Void
) where Input : IteratorProtocol, InputEncoding : UnicodeCodec,
OutputEncoding : UnicodeCodec, InputEncoding.CodeUnit == Input.Element
func unsafeBitCast<T, U>(_: T, to: Type<U>)
func unsafeDowncast<T : AnyObject>(_: AnyObject, to: Type<T>)Other standard library functions that took T.Type will most likely take Type<T>.
We can replace T.self notation with type literals T. Examples:
unsafeBitCast(1.0, to: Int)
let dynamicSize = Mirror(reflecting: something).sizeThen we can add Type(_: Type<T>) initializer for disambiguation:
Int.self.size // works with this proposal, but what if we drop `.self`?
Int.size // will be error after dropping `.self`
Type<Int>().size // works, but looks worse
Type(Int).size // looks much betterWhen combined with this proposal, the result will be to eliminate all "magical" members that existed in the language:
dynamicType, Type and 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 can add the following computed property:
typealias FieldDescriptor = (name: String, type: Mirror, getter: (Any) -> Any, setter: (inout Any, Any) -> ())
var fields: [FieldDescriptor] { get }
Standard library section is not correct there are way more affected function (not speaking of computed properties - if there are any). I just checked some older release of swift in Xcode and searched for
.Typein stdlib or only in stdlib/public:Here are a few examples:
I don't want to check every of them, it's not our job if the proposal gets passed. We need to make it to sound plausible as possible.