[swift-evolution] [Discussion] Seal `T.Type` into `Type<T>`

Adrian Zubarev adrian.zubarev at devandartist.com
Wed Jul 13 14:48:17 CDT 2016


Okay I get it now. You meant the size for a metatype sizeof(T.Type.self). This is indeed 8 bytes, at least not for optimized Bool (as you showed).

Internally, Type contains an Int, i.e. identifier of a type. For every type, compiler must choose a different identifier
We can already implement this with Hashable protocol.

ObjectIdentifier: A unique identifier for a class instance or metatype.

In Swift, only class instances and metatypes have unique identities. There is no notion of identity for structs, enums, functions, or tuples.
// version 1:
public let hashValue: Int = ObjectIdentifier(T.self).hashValue

// version 2 (uses ObjectIdentifier calculation without  
// constructing an instance of ObjectIdentifier):

init() {
    // calculate the hashValue only once
    // I'd rename `.self` to `.metatype`
     
    let rawPointerMetatype = unsafeBitCast(T.self, to: Builtin.RawPointer.self)
    self.hashValue = Int(Builtin.ptrtoint_Word(rawPointerMetatype))
}

public let hashValue: Int
API of Type is defined so that it can only contain identifiers of subtypes of T

For example, when you get a variable of Type, it can correspond to BaseClass or DerivedClass
I did a quick test and I feel like this falls under the part of tweaking dynamic casts to work with Type<T>. There is no special compiler magic needed, unsafeBitCast should do the trick. Or we should teach dynamic casts to work with the inner type T instead of the whole Type<T>.

public struct Type<T> : Hashable {
     
    public let metatype: T.Type = T.self

    public let hashValue: Int = ObjectIdentifier(T.self).hashValue
}

public func ==<T, U>(lhs: Type<T>, rhs: Type<U>) -> Bool {
     
    return lhs.hashValue == rhs.hashValue
}

public func asOptionalCast<U, T>(type: Type<T>) -> Type<U>? {
     
    guard (type.metatype as? U.Type) != nil else {
        return nil
    }
    return unsafeBitCast(type, to: Type<U>.self)
}

class A {}
class B: A {}

let typeB = Type<B>()

// downcast Type<B> to Type<A>
let downcast: Type<A>? = asOptionalCast(type: typeB)

(downcast! == typeB) == true

// cast Type<A> (which here is Type<B>) back to Type<B>
let upcast: Type<B>? = asOptionalCast(type: downcast!)

(upcast! == Type<B>()) == true
The good part here that the hash value of the casted type won’t change and testing against a new instance of the same dynamic type will be always true.



-- 
Adrian Zubarev
Sent with Airmail

Am 13. Juli 2016 um 20:31:22, Anton Zhilin (antonyzhilin at gmail.com) schrieb:

2016-07-13 20:19 GMT+03:00 Adrian Zubarev via swift-evolution <swift-evolution at swift.org>:
Am 13. Juli 2016 um 18:30:53, Anton Zhilin (antonyzhilin at gmail.com) schrieb:

I see model of Type<T> as follows:
Values of Type<T> are identifiers of types (8 bytes, I guess)
All used identifiers are contained in Type<Any>
Type<T> contains a subset of those identifiers
I can’t follow your though here. Is this a totally new behavior?

That's how it works right now:

sizeofValue(Bool.self)              //=> 0 (optimized away)
sizeofValue(Bool.self as Any.self)  //=> 8

I'll put my thoughts another way:
Internally, Type<T> contains an Int, i.e. identifer of a type. For every type, compiler must choose a different identifier
API of Type<T> is defined so that it can only contain identifiers of subtypes of T
For example, when you get a variable of Type<BaseClass>, it can correspond to BaseClass or DerivedClass
Upcasting uses constructor init<U: T>(upcasting: Type<U>)
It does look neat but I can’t implement it. At least I have no clue how to solve the problem that `T` is not a protocol or a class type.

We should add implicit convertion of Type<U> to Type<T>, dulicating current behaviour of `as T.Type`.
That constructor still can be useful, but it will be failable and not that significant in practise.
I don't like adding compiler magic, but I guess we really should in this case, because otherwise Type<T> can't replace T.Type.
Size of Type<Type<T>> is 8, static size of Type<SomeProtocol> is 0
I feel like you mean something different here than this:

```swift

protocol SomeProtocol {}

sizeof(SomeProtocol.self) == 40
```

That was a mistake. Static size of Type<SomeProtocol> will be 40, and size property of its values can be less or greater than 40, depending on internal type identifier.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160713/72bc3c77/attachment.html>


More information about the swift-evolution mailing list