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
nikola
(Nikola)
2
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
paiv
(🇺🇦 Pavel Ivashkov)
4
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
ksluder
(Kyle Sluder)
10
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
edu.art
(Eduard)
12
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
tera
15
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