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

Adrian Zubarev adrian.zubarev at devandartist.com
Wed Jul 13 16:38:46 CDT 2016


I’m still not fully convinced about fully removing the access to the metatype.

If you look at Ericas and Daves current proposal for MemoryLayout it would completely break and it would become impossible to build something like this.

And yes I borrowed the idea of putting properties like size into Type<T> from SE–0101. That was mentioned in my previews posts.

If we remove .Type, how’d we access static members in other types like MemoryLayout<T>?

This is another contra argument to consider.
@Erica @Dave: Any statement on our conversation about Type<T>?

I apologize that this whole idea raised in parallel to your proposal.

PS: We can merge our ideas into a single formal proposal. Ping me off-list when you get some time tomorrow.



-- 
Adrian Zubarev
Sent with Airmail

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

An even better explanation of my suggestion: instead of sealing T.Type, I suggest to rename it to Type<T>, formally turning it into a struct, and then add size and align properties to it.
And regardless of how you interpret it, I suggest to remove .Type notation.

> In Swift, only class instances and metatypes have unique identities. There is no notion of identity for structs, enums, functions, or tuples.
So Type<T> will lose identity. That's fine. They will contain unique integer identifiers (assigned to types during compilation) to do its work.

> There is no special compiler magic needed
Maybe. At least, this behaviour will be needed in construction from type literals:

func foo(_ x: Type<BaseClass>)
foo(DerivedClass)  // ok
foo(Int)           // compilation error

With Type<T>, I can see reflection coming! For example, Type<T> can have the following property (although we shouldn't right now):

var fields: [String: (Type<Any>, (T) -> Any)] { get }

I suggest you to prepare a formal proposal, or I will try tomorrow. I would take the direction on removal of T.Type notation.

2016-07-13 22:48 GMT+03:00 Adrian Zubarev via swift-evolution <swift-evolution at swift.org>:
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.


_______________________________________________
swift-evolution mailing list
swift-evolution at swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


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


More information about the swift-evolution mailing list