TL;DR
The value of Self
in a protocol extension is determined by a complex set of factors. It's almost always preferable to use self
at static level, or type(of: self)
at instance level in place of Self
. This ensures that you're always working with the dynamic type that the method is called on, preventing weird surprises.
First of all let's simplify your example down a bit.
protocol P {
init()
}
extension P {
static func createWithBigSelf() -> Self {
return Self()
}
static func createWithLittleSelf() -> Self {
return self.init()
}
}
class A : P {
required init() {}
}
class B : A {}
let t: A.Type = B.self
print(t.createWithBigSelf()) // A
print(t.createWithLittleSelf()) // B
We can see that using Self
will return a new instance of A
, whereas using self
will return a new instance of B
.
To understand just why this is the case, we need to understand exactly how Swift calls protocol extension methods.
Looking at the IR, the signature for createWithBigSelf()
is:
define hidden void @static (extension in main):main.P.createWithBigSelf () -> A (
%swift.opaque* noalias nocapture sret, // opaque pointer to where return should be stored
%swift.type* %Self, // the metatype to be used as Self.
i8** %Self.P, // protocol witness table for the metatype.
%swift.type* // the actual metatype the method is called on (self).
) #0 {
(Signature for createWithLittleSelf()
is almost identical.)
4 invisible arguments are generated by the compiler – one for a pointer for the return, one for the protocol witness table of the conforming type, and two swift.type*
arguments to represent self
and Self
.
This therefore means that different metatypes can be passed to represent self
or Self
.
Looking at how this method is called:
// get metatype for B (B.self).
%3 = call %swift.type* @type metadata accessor for main.B() #4
// store this to to t, which is of type A.Type.
store %swift.type* %3, %swift.type** @main.t : main.A.Type, align 8
// load the metatype from t.
%4 = load %swift.type*, %swift.type** @main.t : main.A.Type, align 8
// get A's metatype.
%5 = call %swift.type* @type metadata accessor for main.A() #4
// call P.createWithBigSelf() with the following parameters...
call void @static (extension in main):main.P.createWithBigSelf () -> A(
%swift.opaque* noalias nocapture sret bitcast ( // the address to store
%C4main1A** @main.freshA : main.A to %swift.opaque* // the return value (freshA)
),
%swift.type* %5, // The metatype for A – this is to be used for Self.
i8** getelementptr inbounds ( // The protocol witness table for A conforming to P.
[1 x i8*],
[1 x i8*]* @protocol witness table for main.A : main.P in main, i32 0, i32 0
),
%swift.type* %4 // The metatype stored at t (B.self) – this is to be used for self.
)
We can see that A
's metatype is getting passed in for Self
, and B
's metatype (stored in t
) is getting passed in for self
. This actually makes quite a lot of sense if you consider that the return type of createWithBigSelf()
if called on a value of type A.Type
will be A
. Thus Self
is A.self
, while self
remains B.self
.
As a general rule then, the type of Self
is determined by the static type of the thing that the method is called on. (Therefore in your case when you call bigName()
, Self.getName()
is calling getName()
on SomeBaseClass.self
).
This also holds for instance methods, for example:
// ...
extension P {
func createWithBigSelf() -> Self {
return Self()
}
func createWithLittleSelf() -> Self {
return type(of: self).init()
}
}
// ...
let b: A = B()
print(b.createWithBigSelf()) // A
print(b.createWithLittleSelf()) // B
The methods are called with a Self
of A.self
, and a self
that's an instance of B
.
Existentials
Things get much more complicated when you start working with existentials (see this great WWDC talk on them). If you're calling the extension methods directly (i.e they aren't protocol requirements), then for instance methods, the value of Self
is determined by the static type of the value when you box it in the existential container, for example:
let b: A = B()
let p: P = b // metatype of b stored as A.self.
print(p.createWithBigSelf()) // A()
print(p.createWithLittleSelf()) // B()
let b = B()
let p: P = b // metatype of b stored as B.self.
print(p.createWithBigSelf()) // B()
print(p.createWithLittleSelf()) // B()
What happens is that the existential container also stores the metatype of the value (along with the value buffer and protocol and value witness tables), which is taken from its static type at the time of boxing. This metatype is then used for Self
, leading to the somewhat surprising behaviour demonstrated above.
With metatype existentials (e.g P.Type
), the existential container just stores the metatype along with the protocol witness table. This metatype is then used for both Self
and self
in a call to a static method in a P
extension, when that method isn't a protocol requirement.
Methods that are implementations of protocol requirements will be dispatched to dynamically via the protocol witness table for the type conforming to that protocol. In this case, the value of Self
is replaced by the type that directly conforms to the protocol (although I'm not entirely sure why the compiler does this).
For example:
protocol P {
static func testBigSelf()
}
extension P {
static func testBigSelf() {
print(Self.self)
}
}
class A : P {}
class B : A {}
let t: P.Type = A.self // box in existential P.Type
t.testBigSelf() // A
let t1: P.Type = B.self
t1.testBigSelf() // A
In both cases, the call to testBigSelf()
is dispatched dynamically via A
's protocol witness table for conformance to P
(B
doesn't get its own protocol witness table for P
conformance). Therefore Self
is A.self
. It's exactly the same story with instance methods.
This most commonly comes up in generic functions, which dispatch protocol requirements dynamically via the protocol witness table*. For example:
func foo<T : P>(t: T) {
t.testBigSelf() // dispatch dynamically via A's PWT for conformance to P.
}
foo(t: A()) // A
foo(t: B()) // A
It doesn't matter whether an instance of A
or B
is passed in – testBigSelf()
is dispatched via A
's PWT for conformance to P
, therefore Self
is A.self
.
(* Although the compiler can optimise by generating specialised versions of generic functions, this doesn't change the observed behaviour.)
Conclusion
For the most part, the type of Self
is determined by the static type of whatever the method is called on. The value of self
is simply the value itself that the method is called on (a metatype for a static method, an instance for an instance method), passed in as an implicit parameter.
The full breakdown of what we discovered is that the values of self
, Self
& type(of: self)
in protocol extensions are:
Due to the sheer complexity of factors that determine what the value of Self
is, in most cases I would recommend using self
and type(of: self)
instead. That way there's far less chance of being bitten.
Answering your additional questions
Additionally, is there any reason why self
's type is used when either Self
or self
is omitted, as in the return statement return getName()
in the ambiguousName()
function?
That's just the way it is – getName()
is merely syntactic sugar for self.getName()
. It would be inconsistent with instance methods if were syntactic sugar for Self.getName()
, as in instance methods Self
is a metatype, whereas self
is the actual instance – and it's much more common to be accessing other instance members, rather than type members from a given instance method.
For me, I think the weirdest part is when type(of: self)
returns SomeBaseClass.Type
when called from the child.littleName()
function invocation. Shouldn't the "dynamic type" still be of SomeChildClass
?
Yeah, that puzzles me too. I would expect the dynamic type of child</code