- Proposal: SE-NNNN
- Author: Adrian Zubarev, Anton Zhilin
- Status: Awaiting review
- Review manager: TBD
- Replace
T.self
notation withT.metatype
andT.Type
withMetatype<T>
- Make
T.self
instead return an instance ofType<T>
struct - Move
size
,stride
andalignment
toType<T>
Swift-evolution thread: [Discussion] Seal T.Type
into Type<T>
For any type T
, T.Type
creates a new type.
T.Type
behaves like generics, and it would be more consistent for it to be a generic type, if possible.
SE-0101 moves size
, stride
and alignment
to a new MemoryLayout
type.
However, this type does not have any use besides acting as a container for these functions.
We can move a step further and make these properties of types themselves.
One of stated goals of future Swift releases is addition of reflection API.
With T.Type
construct, we will need an additional wrapper Reflection<T>
that specializes
on retrieving information about type T
.
Is that the most elegant solution possible?
struct Type<T> : Hashable, CustomStringConvertible, CustomDebugStringConvertible {
/// Creates instance that reflects 'T'
init()
/// Metatype -> Type convertion
init(_ meta: AnyMetatype)
/// If 'otherType' reflects a subtype of 'T', creates an instance reflecting that subtype
/// Otherwise, returns 'nil'
init?<U>(casting otherType: Type<U>)
/// Checks is current instance reflects a subtype of 'U'
/// Value of 'otherType' is ignored
func `is`<U>(_ otherType: Type<U>) -> Bool
/// Type -> Metatype convertion
var metatype: Metatype<T> { get }
static var metatype: Metatype<T> { get }
/// These properties replace global 'size', 'stride', 'alignment' functions
var size: Int { get }
var stride: Int { get }
var alignment: Int { get }
static var size: Int { get }
static var stride: Int { get }
static var alignment: Int { get }
/// Implementation of protocols
public var hashValue: Int { get }
public var description: String { get }
public var debugDescription: String { get }
}
/// Checks if 'lhs' and 'rhs' reflect the same type
func ==<T, U>(lhs: Type<T>, rhs: Type<U>) -> Bool
/// Reflects dynamic type of 'instance', which can be any subtype of T
public func dynamicType<T>(_ instance: T) -> Type<T>
Type<T>
struct stores a type identifier. Each instance of Type<T>
reflects one subtype of T
.
Type<T>
can be instantiated in one of the following ways:
- Default initializer
init()
creates an instance that reflects toT
T.self
will become an alias forType<T>()
init?<U>(casting: Type<U>?)
convertscasting
toType<T>
if type reflected bycasting
is a subtype ofT
Checking type relationships:
- Instances of
Type<T>
andType<U>
are considered equal if they reflect the same type is<U>(_: Type<U>)
method checks whether reflected type is a subtype ofU
For example, let's imagine that we have types Derived : Base
:
let a = Type<Base>()
print(a) //=> Base
let b = Derived.self
print(b) //=> Derived
let c = Type<Any>(casting: a)!
print(c) //=> Base
let d = Type<Base>(casting: b)!
print(d) //=> Derived
let e = Type<Derived>(casting: a)
print(e) //=> nil
// These are true:
a != b
a == c
b == d
b.is(Derived.self)
b.is(Base.self)
b.is(Any.self)
Type<T>
replaces T.Type
in generic functions:
func unsafeBitCast<T, U>(_: T, to: Type<U>) -> U
unsafeBitCast(1.0, to: UInt64.self) //=> 4607182418800017408
An important function that works with types is dynamicType
from SE-0096:
func dynamicType<T>(_ instance: T) -> Type<T>
let x: Base = compute()
let type: Type<Base> = dynamicType(x)
if type.is(Derived.self) { print("x is Derived") }
Here, type
will reflect Base
or Derived
in runtime.
Current proposal combines with SE-101, which renamed old sizeOf
, strideOf
, alignOf
global functions.
These functions move from global scope to Type<T>
.
Static properties size
, stride
, alignment
return respective parameters of type T
.
In addition, instance properties with same names return these parameters of type reflected by current instance.
Example:
Type<Int>.size //=> 8
Type<CustomStringConvertible>.size //=> 40
let a = Int.self
let b = Type<CustomStringConvertible>(casting: Int.self)
print(a) //=> Int
print(a.size) //=> 8
print(b) //=> Int
print(b.size) //=> 8
Current metatypes T.Type
have one additional feature that Type<T>
will not have.
They can invoke static methods of types they reflect.
Thus, we cannot drop metatypes.
But they will be renamed: old T.Type
will become Metatype<T>
and old T.self
will become T.metatype
.
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(_ type: Metatype<HasStatic>) {
let result = type.staticMethod() // Type<T> cannot do this!
print(result)
}
let a = A() as HasStatic
let b = B() as HasStatic
callStatic(a) //=> A
callStatic(b) //=> B
In other words, metatypes (continue to) allow dynamic polymorphism for static methods.
Convertion between Type<T>
and Metatype<T>
is possible:
metatype: Metatype<T>
property ofType<T>
allows to convert `Type<T>.init(_: Metatype<T>)
allows to convertMetatype
toType
Metatype<T>
is oldT.Type
T.metatype
is oldT.self
. It returnsMetatype<T>
Type<T>
is the new struct that should replace metatypes in 99% casesT.self
now creates an instance ofType<T>
AnyMetatype
is the supertype of all metatypes
internal func _sizeof(_ metatype: AnyMetatype) -> Int {
return Int(Builtin.sizeof(metatype))
}
internal func _strideof(_ metatype: AnyMetatype) -> Int {
return Int(Builtin.strideof_nonzero(metatype))
}
internal func _alignof(_ metatype: AnyMetatype) -> Int {
return Int(Builtin.alignof(metatype))
}
internal func _uniqueIdentifierOf(_ metatype: AnyMetatype) -> Int {
let rawPointerMetatype = unsafeBitCast(metatype, to: Builtin.RawPointer.metatype)
return Int(Builtin.ptrtoint_Word(rawPointerMetatype))
}
public func unsafeBitCast<T, U>(_ x: T, to: Type<U>) -> U {
_precondition(_sizeof(T.metatype) == _sizeof(U.metatype), "can't unsafeBitCast between types of different sizes")
return Builtin.reinterpretCast(x)
}
///
/// Alternative implementation:
/// - Rename `T.Type` to `T.Metatype`
/// - Disallow `T.Metatype` in public declarations
/// - Introduce a generic typealias: `public typealias Metatype<T> = T.Metatype`
///
internal var _typeStorage = Set<Type<Any>>()
public final class Type<T> : Hashable, CustomStringConvertible, CustomDebugStringConvertible {
// Size of `AnyMetatype` is 8 Bytes
// `AnyMetatype` can store any metatype
// It's like `Any` but only for metatypes
internal let _metatype: AnyMetatype
// Bug: SR-2085
// Workaround: Check explicitly if `T` is `Any`
//
// init(metatype: Any.Type) {
//
// let canProceed = _uniqueIdentifierOf(Any.metatype) == _uniqueIdentifierOf(T.metatype) || metatype is Metatype<T>
// guard canProceed else { fatalError("'metatype' is not an instace of 'Metatype<T>'") }
//
// ...
// }
internal init(metatype: AnyMetatype) {
self._metatype = metatype
}
internal convenience init() {
self.init(metatype: T.metatype)
}
public static var sharedInstance: Type<T> {
let identifier = _uniqueIdentifierOf(Type<T>.metatype)
let typeFromStorage = _typeStorage.first(where: { $0.hashValue == identifier })
if let type = Type<T>.cast(typeFromStorage) {
return type
}
let newType = Type<T>()
// downcast `T` to `Any`
if let type = Type<Any>.cast(newType) {
_typeStorage.insert(type)
}
return newType
}
public static func cast<U>(_ optionalType: Type<U>?) -> Type<T>? {
guard let otherType = optionalType else { return nil }
// Check if we can up- or downcast the metatype from `otherType` to `Metatype<T>`
// Bug: SR-2085
// Workaround: Check explicitly if `T` is `Any`
//
// let isTAny = _uniqueIdentifierOf(Any.metatype) == _uniqueIdentifierOf(T.metatype)
// guard isTAny || otherType._metatype is Metatype<T> else {
// return nil
// }
guard otherType._metatype is Metatype<T> else {
return nil
}
// `Type<T>` is implicitly converted to `Type<Type<T>>()`
return unsafeBitCast(otherType, to: Type<T>)
}
public func `is`<U>(_ otherType: Type<U>) -> Bool {
// check directly the metatype instead of instantiating a new `Type<T>`
return self._metatype is Metatype<U>
}
public var metatype: Metatype<T> {
return unsafeBitCast(self._metatype, to: Metatype<T>)
}
public static var metatype: Metatype<T> { return T.metatype }
// do not construct full `Type<T>` - use lightweight static calculation instead
public var size: Int { return _sizeof(self._metatype) }
public var stride: Int { return _strideof(self._metatype) }
public var alignment: Int { return _alignof(self._metatype) }
public static var size: Int { return _sizeof(T.metatype) }
public static var stride: Int { return _strideof(T.metatype) }
public static var alignment: Int { return _alignof(T.metatype) }
public var hashValue: Int { return _uniqueIdentifierOf(self._metatype) }
public var description: String {
return "Type<\(self.metatype)>"
}
public var debugDescription: String {
return "<" + self.description
+ " metatype: \(self.metatype)"
+ " size: \(self.size)"
+ " stride: \(self.stride)"
+ " alignment: \(self.alignment)>"
}
}
public func ==<T, U>(lhs: Type<T>, rhs: Type<U>) -> Bool {
return lhs.hashValue == rhs.hashValue
}
///
/// typealias Metatype<T> = T.Type
///
/// class A {}
/// class B: A {}
/// class C {}
///
/// let metatype: Any = B.self
/// (metatype is Metatype<A>) == true
/// (metatype is Metatype<B>) == true
/// (metatype is Metatype<C>) == false
///
public func dynamicType<T>(_ instance: T) -> Type<T> {
let dynamicMetatype = /* extract dynamic metatype from the `instance` */
let identifier = _uniqueIdentifierOf(dynamicMetatype)
// Check if the type storage contains an instance of our dynamicType
if let type = _typeStorage.first(where: { $0.hashValue == identifier }) {
// We only need to switch `Any` to `T` but keep the reference
// `Type<T>` is implicitly converted to `Type<Type<T>>()`
return unsafeBitCast(type, to: Type<T>)
}
// Create and store an instance of `Type<T>`
var type = Type<T>.sharedInstance
// Check if the identifier for our dynamic metatype is equivalent
// to the created instance, which implies `dynamicMetatype == T`
if type.hashValue == identifier {
return type
}
// If the identifiers are not equivalent then
// T is probably downcasted from dynamicMetatype
type = Type<Any>(metatype: dynamicMetatype)
_typeStorage.insert(type)
// `Type<T>` is implicitly converted to `Type<Type<T>>()`
return unsafeBitCast(type, to: Type<T>)
}
This is a source-breaking change that can be automated by a migrator.
T.Type
will be replaced withType<T>
- Calls to old
size
,stride
,alignment
with type literals will be replaced with calls to static members ofType<T>
- Other calls to old
size
,stride
,alignment
will be replaced with calls to instance members ofType<T>
- Casting of type expressions will be replaced with usage of
init?(casting:)
andis(_:)
.
One of stated goals for future Swift releases is reflection API.
With this proposal, implementing it in Swift 4 will be as easy as adding some methods to Type
struct.
On [Date], the core team decided to (TBD) this proposal. When the core team makes a decision regarding this proposal, their rationale for the decision will be written here.