Is type(of: self) identical to an imaginary Self in this context?

The following compiles

struct S {
    static let a = 123
    var b: Bool { return type(of: self).a == 123 }
}

and I guess it is equivalent to this:

struct S {
    static let a = 123
    var b: Bool { return S.a == 123 }
}

?

Does this mean that type(of:) is not always "the dynamic type of a value" as the documentation says, but sometimes it is evaluated at compile time (to S / an imaginary Self).

Also, what's the status of making Self available for structs (instead of having to write the specific type name, S in this case)?

1 Like

I don't think so:

protocol A {
    static var a: String { get }
}

extension A {
    static var a: String { return "A" }
    var x: String { return type(of: self).a }
}

class B: A {
    class var a: String { return "B" }
    var y: String { return type(of: self).a }
}

class C: B {
    override class var a: String { return "C" }
    var z: String { return type(of: self).a }
}

let c = C()

(c as A).x // "C"
(c as B).y // "C"
(c as C).z // "C"

It's implementation details whether it is evaluated at compile time.

Right, type(of: self) is a dynamic value, and sometimes that dynamic value can be known at compile time.

1 Like

In general type(of: self) returns a dynamic metatype which represents always the bottom type of the your type hierarchy. In other words the type which was initially initialized. Since value types have no subtypes type(of: self) is the same like the 'static metatype'. Self behaves similarly.

class A {}
class B : A {}

struct S {}

A.self // static metatype A.Type
B.self // static metatype B.Type
S.self // static metatype S.Type

let a = A()
let a_as_any: Any = a
let b = B()
let b_as_a: A = b

type(of: a)        // dynamic metatype A.Type pointing to a static metatype A.Type
type(of: a_as_any) // dynamic metatype Any.Type pointing to a static matatype A.Type
type(of: b)        // dynamic metatype B.Type pointing to a static metatype B.Type
type(of: b_as_a)   // dynamic metatype A.Type pointing to a static metatype B.Type

type(of: b_as_a) as! B.Type // should be theoretically safe

In case of protocols it get's a little weird. For more information you can read this deferred proposal.

A side note, type(of:) function is due to this strange merged design of metatypes pure compiler magic.

Your explanation is correct but your terminology is a little off here. The static type of type(of: b_as_a) is A.Type. The dynamic value of type(of: b_as_a) is B.self.

2 Likes

Well the proposal was more or less a self-research so please forgive me for possible wrong terminology. ;)

I was a little confused at first, but you refer to the value while I refer to the metatype.

// Subtype hierarchy of the metatypes from my example
A.Type : A.Type : Any.Type
^ static metatype
         ^ dynamic metatype 
                  ^ dynamic metatype 

B.Type : B.Type : A.Type : Any.Type
^ static metatype (1)
         ^ dynamic metatype (2)
                  ^ dynamic metatype (3)
                           ^ dynamic metatype (4)

// returns dynamic metatype (3) which is technically up casted
// static metatype (1)
type(of: b_as_a) // A.Type

@jrose Maybe this would be more correct?

A.Type : A.Type
^ metatype
         ^ metatype existential

In the proposal we used to refer these as static and dynamic metatypes to make the difference btween .Type and .Protocol more understandable.

"refer to the metatype" is the terminology that's incorrect.

Abstractly, outside of Swift, "metatype" means "the type of a type". Within the Swift compiler/runtime world, we say "foo is a metatype" the same way we say "foo is an array", as shorthand for "foo is an instance of a metatype". That means that values that represent types (such as A.self) could conceivably be called "metatype instances"…but we don't say "array instances"; we just say "arrays".

I don't understand your "subtype hierarchy". There is no "A.Type (dynamic)" that's a supertype of "A.Type (static)", and I don't know what you mean by "dynamic metatype". If anything, you've written out a static type hierarchy (of metatypes rather than instances), with no dynamic in it anywhere.

1 Like

I think part of the confusion around A.self and metatypes is that currently in Swift we don't have a way to programmatically manipulate/inspect them, short of Mirroring. If/when Swift's ABI locks down enough that it becomes somewhat stable to mess around with, I'm wondering if we could propose a counterpart to Mirror that allows operations on the direct metatype instances.

Well by A.Type : A.Type I really mean something like a class is a superclass (supertype) of itself A : A. In other words a type of the metatype is a supertype of itself. However this is not fully true in Swift.

protocol P {}
protocol Q : P {}

P.self // P.Protocol
Q.self // Q.Protocol

P.Protocol : Any.Type
Q.Protocol : Any.Type

P.Type : Any.Type
Q.Type : P.Type : Any.Type

I understand that metatype is like struct or class etc.

Now let's say there are two kind of metatypes and pretend they are called static and dynamic.

  • static metatypes: T.Type for classes, structs, enums, tuples, functions and P.Protocol for protocols
  • dynamic metatypes: T.Type for all kinds

As you already see both kinds are partly merged into one thing T.Type. If we now split them apart and rename:

  • static metatype into Type<T>
  • dynamic metatype into AnyType<T>

we can write the supertype relation in a less confusing way:

P.self // Type<P>
B.self // Type<B>

Type<P> : AnyType<Any>
Type<Q> : AnyType<Any>

AnyType<Q> : AnyType<P> : AnyType<Any>

Type<A> : AnyType<A> : AnyType<Any>
Type<B> : AnyType<B> : AnyType<A> : AnyType<Any>

// Lets also rewrite the `type(of:)` function.
func type<T>(of value: T) -> AnyType<T>

// statically returns `AnyType<A>` which is just `Type<B> as AnyType<A>`
type(of: b_as_a) 

This can also allow us to create a new function similar to what we had in Objective-C.

func subtype<T>(of value: T, named: String) -> AnyType<T>?

subtype(of: a, named: "B") // Returns `AnyType<A>?` because `B` exists
subtype(of: a, named: "C") // Returns nil

Maybe @beccadax can explain better and more precise.

Calling them "static" and "dynamic" just doesn't make sense, because it has nothing to do with "compile time vs. run time" or "direct vs. indirect dispatch". There are "values that represent the metadata for a specific nominal" and "values that represent the type of another value", and yes, we conflated those for classes, structs, and enums. For protocols, you can see the difference between "the protocol's metadata" and "values with protocol type (existentials)".

(Tuples and functions don't really belong in the first category, though. Those are structural types and can basically only be types of values, not something with identity.)