- Proposal: SE-NNNN
- Author: Anton Zhilin
- Status: Awaiting review
- Review manager: TBD
- Rename metatypes
T.Type
toMetatype<T>
- Add
Type<T>
struct that wraps a static type - Make
T.self
syntax createType<T>
- Move static
size
,stride
,alignment
functions toType<T>
- Rename
Mirror
toDebugRepresentation
andCustomReflectable
toCustomDebugRepresentable
- Add
Mirror
struct that is intended to replace metatypes for most use cases - Move dynamic
size
,stride
,alignment
functions 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
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
- Instance of metatype 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
- 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.DisplayStyle
containsoptional
andset
as special cases, but does not containfunction
Mirror
collects all information possible at initialization, while for true reflection we want lazinessMirror
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 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
- Metatypes are generic. But do we want genericity in reflection? No, we almost always want to cast to
- Rename
T.Type
toMetatype<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) //=> B
dynamicType
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.Type
does 3 things:- Specialization of functions
- Dynamic dispatch of static methods
- Partial reflection using
as
,is
and functions likesizeofValue
Mirror
does 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 functionsMirror
does reflectionMetatype<T>
does dynamic dispatch of static methodsDebugRepresentation
is used in debugging
Changes are source-breaking, automatic migration is possible.
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 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).size
Then 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 better
When 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 }
I'll quickly add another glitch, forgot one.