How to avoid self as! Self

Good day.

I encountered an issue regarding type checking in a relatively simple case. Am I missing something here? Maybe some nuance between Self within extensions and inheritance?

Or is it actually a bug?

open class Gift {}

struct Box<Contents> {
    var contents: Contents
}

extension Gift {
    var box: Any { Box<Self>(contents: self) }
        // Cannot convert value of type 'Gift' to expected argument type 'Self'
}

Here, both removing <Self> and adding self as! Self helps, resulting in different types of Box. Considering the latter is preferable for my case, can as! actually fail here? Is there a better workaround?

1 Like

Does anything block you from explicitly passing Gift as the type parameter of Box?

extension Gift {
    var box: Any { Box<Gift>(contents: self) } // compiles
}

I'm uncertain why Self causes a compilation error, it's my understanding that reaching for it should also work in this scenario.

my understanding is that this is the same reason this is not allowed:

class Gift {}

extension Gift 
{
    var box:[Self] { [self] }
}
Swift version 5.10 (swift-5.10-RELEASE)
Target: x86_64-unknown-linux-gnu
<stdin>:5:9: error: covariant 'Self' or 'Self?' can only appear at the top level of property type
    var box:[Self] { [self] }
        ^

the Self loses its dynamicness when it is bound to a generic, which is why you cannot create Box<Self> even if you erase it later.

2 Likes
class Gift {
    func give() {
        print(self) // app.Gift
        print(Self.self) // Gift
        print(self as Self) // error: 'Gift' is not convertible to 'Self'
        print(self as! Self) // app.Gift
    }
}

Probably prevents compiler blowout than anything related to variance.

The type of self is Gift, not Self, except for in the special case of a method that returns Self. You probably should just write Gift instead of Self unless you really mean Self.

In general the dynamic Self type (what you get when you write Self in a class; other nominal types are fine) is not well-designed and is mostly a compatibility hack for Objective-C.

6 Likes

He could also make Gift final to make Self invariant, but I seem to remember that the compiler still didn't quite understand that in some situations.

Yeah, declaring a class as final doesnโ€™t actually change the behavior of the dynamic Self type if one is stated, it just requires it in fewer situations.

The type of self is Gift , not Self , except for in the special case of a method that returns Self

That's so interesting! I never noticed this difference in behavior before.

In general the dynamic Self type ... is not well-designed and is mostly a compatibility hack for Objective-C.

Does that mean there's no point in asking to rectify this? Sure there are arguments in favor beyond instancetype:

  • Self seems to be available either way, so why not make it also the static type of self
  • It would come into symmetry with protocols:
struct Box<Contents> {
    var contents: Contents
}

extension Gift {
    var box: Any { Box<Self>(contents: self) }
}
1 Like

The protocol Self is a generic parameter which makes everything nice and neat. The dynamic Self inside a class is its own thing.

Formally, it's a value-dependent type, like an opened existential. Probably the best model for such a feature would be something like this:

  • Treat class methods as having an implicit Self generic parameter that is subject to a superclass requirement, just like Self behaves in protocols today (except it has a conformance requirement of course).
  • A value of class type like C is really an existential any C which is opened when you call a method on it.

Instead, dynamic Self is a special case today. This causes some odd behaviors and other problems, for example because the association between the type and the value isn't really tracked, we cannot inline or specialize code that uses dynamic Self (imagine you inline two method calls that both involve Self into the same function; how do you tell which Self is which self?).

A representation change could probably fix most of these problems without breaking compatibility.

If we ever do another source-break -swift-version mode again, we could also change self to have dynamic Self type in all class methods, not just those returning Self, but the fallout might be too great. Another possibly massively breaking change is to interpret Self as the static class type inside a final class.

There are also some soundness issues in conformance checking related to this weird modeling of dynamic Self. In general, the interaction between conformances and subclassing is hard to get right, and in my personal opinion, future languages in the Swift family should not allow subclassing.

3 Likes

This behavior, combined with the invisible difference between protocol-based and type-based dispatch, are some of the biggest obstacles affecting anyone migrating to Swift from other OO languages. There are too many variables affecting how a method call is bound in Swift, and I wouldnโ€™t be surprised if this has inadvertently steered the Swift ecosystem away from classes and toward protocols. Iโ€™m also curious if this is will become an issue for C++ interop. The binding rules for C++ are straightforward; virtual methods are late-bound, everything else is early-bound. How much extra mental work does Swift impose, and will that discourage porting code from C++?

3 Likes

Iโ€™m not sure what Swift does any differently here except that class methods are virtual by default and explicitly declared final otherwise. (The dynamic Self type does not change class method dispatch semantics FWIW).

1 Like
final class Gift {}

struct Box<Contents> {
    var contents: Contents
}

extension Gift {
    var box: Any { Box<Self>(contents: a()) } // Cannot convert value of type 'Gift' to expected argument type 'Self'
    
    func a() -> Self {
        self
    }
}

This error is even more confusing, it still stays on the line with box declaration

2 Likes

i literally just ran into a compiler crash (on 5.10 and main) with the following reduction:

final class A
{
    static
    func _run(with body:(Self) -> ())
    {
    }
}
func f()
{
    A._run { _ in }
}
error: compile command failed due to signal 11 (use -v to see invocation)
Please submit a bug report (https://swift.org/contributing/#reporting-bugs) and include the crash backtrace.
Stack dump:
0.      Program arguments: /usr/bin/swift-frontend -frontend -c -primary-file crash2.swift -target x86_64-unknown-linux-gnu -disable-objc-interop -color-diagnostics -new-driver-path /usr/bin/swift-driver -empty-abi-descriptor -resource-dir /usr/lib/swift -module-name crash2 -plugin-path /usr/lib/swift/host/plugins -plugin-path /usr/local/lib/swift/host/plugins -o /tmp/TemporaryDirectory.6OZwMw/crash2-1.o
1.      Swift version 5.10 (swift-5.10-RELEASE)
2.      Compiling with the current language version
3.      While evaluating request ASTLoweringRequest(Lowering AST to SIL for file "crash2.swift")
4.      While silgen emitFunction SIL function "@$s6crash21fyyF".
 for 'f()' (at crash2.swift:8:1)
Stack dump without symbol names (ensure you have llvm-symbolizer in your PATH or set the environment var `LLVM_SYMBOLIZER_PATH` to point to it):
/usr/bin/swift-frontend(+0x61bbee3)[0x64cfd81e5ee3]
/usr/bin/swift-frontend(+0x61b9e9e)[0x64cfd81e3e9e]
/usr/bin/swift-frontend(+0x61bc25a)[0x64cfd81e625a]
/lib64/libc.so.6(+0x54dd0)[0x78a146bb1dd0]
/usr/bin/swift-frontend(+0x1a111fb)[0x64cfd3a3b1fb]
/usr/bin/swift-frontend(+0x102e813)[0x64cfd3058813]
/usr/bin/swift-frontend(+0x12b6f9e)[0x64cfd32e0f9e]
/usr/bin/swift-frontend(+0x12b912b)[0x64cfd32e312b]
/usr/bin/swift-frontend(+0x12afe92)[0x64cfd32d9e92]
/usr/bin/swift-frontend(+0x129f812)[0x64cfd32c9812]
/usr/bin/swift-frontend(+0x1295ab9)[0x64cfd32bfab9]
/usr/bin/swift-frontend(+0x13431a2)[0x64cfd336d1a2]
/usr/bin/swift-frontend(+0x1332993)[0x64cfd335c993]
/usr/bin/swift-frontend(+0x1342120)[0x64cfd336c120]
/usr/bin/swift-frontend(+0x134d6a2)[0x64cfd33776a2]
/usr/bin/swift-frontend(+0x134d0b7)[0x64cfd33770b7]
/usr/bin/swift-frontend(+0x1336807)[0x64cfd3360807]
/usr/bin/swift-frontend(+0x1334aab)[0x64cfd335eaab]
/usr/bin/swift-frontend(+0x129f871)[0x64cfd32c9871]
/usr/bin/swift-frontend(+0x129607c)[0x64cfd32c007c]
/usr/bin/swift-frontend(+0x130a45e)[0x64cfd333445e]
/usr/bin/swift-frontend(+0x13091ed)[0x64cfd33331ed]
/usr/bin/swift-frontend(+0x12b955f)[0x64cfd32e355f]
/usr/bin/swift-frontend(+0x125a24a)[0x64cfd328424a]
/usr/bin/swift-frontend(+0x125a7c3)[0x64cfd32847c3]
/usr/bin/swift-frontend(+0x125875a)[0x64cfd328275a]
/usr/bin/swift-frontend(+0x125d427)[0x64cfd3287427]
/usr/bin/swift-frontend(+0x1308cb4)[0x64cfd3332cb4]
/usr/bin/swift-frontend(+0x1308bc9)[0x64cfd3332bc9]
/usr/bin/swift-frontend(+0x125fd4a)[0x64cfd3289d4a]
/usr/bin/swift-frontend(+0x125def6)[0x64cfd3287ef6]
/usr/bin/swift-frontend(+0xc624ea)[0x64cfd2c8c4ea]
/usr/bin/swift-frontend(+0xc65a39)[0x64cfd2c8fa39]
/usr/bin/swift-frontend(+0xc644fd)[0x64cfd2c8e4fd]
/usr/bin/swift-frontend(+0xafd840)[0x64cfd2b27840]
/lib64/libc.so.6(+0x3feb0)[0x78a146b9ceb0]
/lib64/libc.so.6(__libc_start_main+0x80)[0x78a146b9cf60]
/usr/bin/swift-frontend(+0xafc975)[0x64cfd2b26975]

*** Signal 11: Backtracing from 0x64cfd3a3b1fb... done ***

*** Program crashed: Bad pointer dereference at 0x0000000000000028 ***

Thread 0 "swift-frontend" crashed:

0  0x000064cfd3a3b1fb swift::MetatypeInst::create(swift::SILDebugLocation, swift::SILType, swift::SILFunction*) + 459 in swift-frontend


Registers:

rax 0x000064cfdc949c48  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
rdx 0x000064cfdc949bf0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
rcx 0x0000000000000000  0
rbx 0x0000000000000028  40
rsi 0x000064cfdc949c18  48 26 c6 da cf 64 00 00 ff ff ff ff ff ff ff ff  H&ร†รšรdยทยทรฟรฟรฟรฟรฟรฟรฟรฟ
rdi 0x0000000000000000  0
rbp 0x0000000000000008  8
rsp 0x00007ffc12363650  d0 04 67 dc cf 64 00 00 e8 6c 94 dc cf 64 00 00  รยทgรœรdยทยทรจlยทรœรdยทยท
 r8 0x00007ffc12363678  00 00 00 00 00 00 00 00 52 68 8f dc cf 64 00 00  ยทยทยทยทยทยทยทยทRhยทรœรdยทยท
 r9 0x0000000000000000  0
r10 0x000064cfdc949e00  30 01 00 00 00 00 00 00 10 0a 00 00 00 00 00 00  0ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
r11 0x000078a146d56c60  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
r12 0x000064cfdc8f6852  00 00 00 00 00 00 58 6c 90 dc cf 64 00 00 00 00  ยทยทยทยทยทยทXlยทรœรdยทยทยทยท
r13 0x000064cfdc94ab18  4c 66 8f dc cf 64 00 00 09 00 00 00 00 00 00 00  Lfยทรœรdยทยทยทยทยทยทยทยทยทยท
r14 0x00007ffc12363678  00 00 00 00 00 00 00 00 52 68 8f dc cf 64 00 00  ยทยทยทยทยทยทยทยทRhยทรœรdยทยท
r15 0x00007ffc12363629  6c 94 dc cf 64 00 00 80 a9 94 dc cf 64 00 00 51  lยทรœรdยทยทยทยฉยทรœรdยทยทQ
rip 0x000064cfd3a3b1fb  48 8b 76 28 48 89 70 08 48 85 f6 74 c8 48 8d 78  Hยทv(HยทpยทHยทรถtรˆHยทx

rflags 0x0000000000010246  ZF PF

cs 0x0033  fs 0x0000  gs 0x0000


Images (26 omitted):

0x000064cfd202a000โ€“0x000064cfd8da8800 <no build ID> swift-frontend /usr/bin/swift-frontend

Backtrace took 0.16s

error: fatalError

having recently read this thread i wondered if this had something to do with Self and discovered the crash doesnโ€™t occur if i replace Self with A.

i filed it here:

3 Likes

today i discovered another compiler bug (this one a miscompile!) related to dynamic Self, the details are in the GitHub issue below, but long story short required initializer inheritance seems to be broken on 5.10 and on main.

iโ€™m not sure if my test program is unsound or if the compiler is missing a diagnostic here. however, right now it compiles and behaves differently between debug mode and release mode.

4 Likes

Interestingly adding inline never:

        @inline(never)
        init(context: any ResponseContext) {

or adding a print statement:

        init(context: any ResponseContext) {
            print("here \(context)")
            ....

changes the outcome.

1 Like