Skip to content

Instantly share code, notes, and snippets.

@Anton3
Last active July 15, 2016 22:54
Show Gist options
  • Save Anton3/08a069a3b6f634bece7ad666922741d2 to your computer and use it in GitHub Desktop.
Save Anton3/08a069a3b6f634bece7ad666922741d2 to your computer and use it in GitHub Desktop.

Seal T.Type into Type<T>

Summary

  • Replace T.self notation with T.metatype and T.Type with Metatype<T>
  • Make T.self instead return an instance of Type<T> struct
  • Move size, stride and alignment to Type<T>

Swift-evolution thread: [Discussion] Seal T.Type into Type<T>

Motivation

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?

Proposed solution

Overview of Type<T>

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>

Introduction

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 to T
  • T.self will become an alias for Type<T>()
  • init?<U>(casting: Type<U>?) converts casting to Type<T> if type reflected by casting is a subtype of T

Checking type relationships:

  • Instances of Type<T> and Type<U> are considered equal if they reflect the same type
  • is<U>(_: Type<U>) method checks whether reflected type is a subtype of U

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)

Typical usage

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.

size, stride, alignment

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

Metatypes

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 of Type<T> allows to convert `
  • Type<T>.init(_: Metatype<T>) allows to convert Metatype to Type

Summary of special types and members

  • Metatype<T> is old T.Type
  • T.metatype is old T.self. It returns Metatype<T>
  • Type<T> is the new struct that should replace metatypes in 99% cases
  • T.self now creates an instance of Type<T>
  • AnyMetatype is the supertype of all metatypes

Detailed design

Implementation of Type<T>

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>)
}

Impact on existing code

This is a source-breaking change that can be automated by a migrator.

  • T.Type will be replaced with Type<T>
  • Calls to old size, stride, alignment with type literals will be replaced with calls to static members of Type<T>
  • Other calls to old size, stride, alignment will be replaced with calls to instance members of Type<T>
  • Casting of type expressions will be replaced with usage of init?(casting:) and is(_:).

Future directions

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.

Rationale

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment