Why is it allowed to shadow the `Self` type?

in Swift 6, it became illegal to shadow generic parameters from outer scopes:

struct S<T>
{
    func f<T>() -> T
//         ~
//  Generic parameter 'T' shadows generic parameter from outer scope with the same name
    {
        let t:T = { fatalError() }()
        return t
    }
}

however, it appears it is still legal to shadow the Self type:

struct S<T>
{
    func f<Self>() -> Self
    {
        let t:Self = { fatalError() }()
        return t
    }
}

why?

6 Likes

In this example, you declare a generic parameter called Self, which doesn't shadow a generic parameter of the same name. The way I understand it is the generic parameters and declared types are separate entities, and the Self declared type is just a typealias for the declaration type. You're returning a type of the generic parameter in your function, not the Self type. You aren't shadowing it either because one is a generic parameter and the other is a declared type. Ergo, not illegal logically.

As a side node, you should avoid naming a generic parameter after a declared type with the same name or Self. That looks very wrong.

I could be convinced either way to disallow certain names for a generic parameter. In my opinion this should be allowed, but show a compiler warning about this edge case.

This looks like a bug to me.

Here's a modified example that confirms that the usual meaning of Self here is shadowed away, and it's just behaving as if it were some other name like T.

struct S {
    func f<Self: BinaryInteger>() -> [Self] {
        return [.zero]
    }
}

let result: [Int] = S().f() // Note result is `[Int]`, not `[S]`.
print(result) // => [0]

From a quick glance, I suspect this occurs because genericParamDecls doesn't include Self, so there's no shadowing ever detected by this code:

2 Likes

I agree that it looks like a bug. However the Self type doesn't actually exist and all references to it get replaced by the declared type at compile-time. This edge case makes it appear to behave abnormally, even shadowing the Self type, but it makes complete sense logically. Which is why it should have a compiler warning.

This is necessary in order to support the very important feature of being able to use Self here as well:

struct S<Self> {
  // Compiles.
  var itIsImportantToSupportThis: S<Self> { self }

  // Cannot convert return expression of type 'S<Self>' to return type 'Self'
  var nobodyNeedsSelfUnlessItIsAGenericTypeParameter: Self { self }
}

The following is an even more valuable construct, because it protects the type so well that it can't be referred to within itself (without utilizing the module name).

struct S<S, Self> { }
1 Like

This doesn't appear to be the case with a reference type.

struct S {
    func f () -> S {
        S ()
    }
    
    func g () -> Self {
        S () // Ok
    }
}
final class C {
    func f () -> C {
        C ()
    }
    
    func g () -> Self {
        C ()  // Error: Cannot convert return expression of type 'C' to return type 'Self'
    }
}

What is Self above?

I am surprised by this behavior (I barely use reference types, but when I do, I never referenced Self).

I found a post explaining it (with a lot of discussion): Why Is Covariant Self more flexible on protocols than classes? - #3 by Slava_Pestov . Basically it behaves differently due to interoperability with Objective-C.

Without that understanding this would be very confusing:

func g() -> Self {
    C()  // Error: Cannot convert return expression of type 'C' to return type 'Self'
}
func h() -> C {
    Self()  // Ok
}

Self is a headache in reference types. Duly noted.

1 Like

Why is this feature important? Can you give a realistic example of it being useful for something?

Why is this construct valuable? Preventing people from referencing the type name sounds to me like it would be strictly detrimental.

5 Likes