Today I hit something which at contradicts my two assumptions
type(of:) returns the dynamic type of an object
- The constructor of a type returns an instance of that type
However ExpressibleByStringInterpolation seems to not play by these rules:
class Base {
let value: String
init(value: String) {
self.value = value
}
}
class StringExpressible: Base, ExpressibleByStringLiteral, ExpressibleByStringInterpolation {
required init(stringLiteral value: StringLiteralType) {
super.init(value: value)
}
}
class Child: StringExpressible {}
let works = Child("string literal")
print(type(of: works)) // Child
let wtf = Child("interpolated string: \(42)")
print(type(of: wtf)) // StringExpressible
Specifying the variable types (works: Child and wtf: Child) doesn't change anything. Neither does explicitly implementing the ExpressibleByStringInterpolation constructor
class StringExpressible: Base, ExpressibleByStringLiteral, ExpressibleByStringInterpolation {
required init(stringLiteral value: StringLiteralType) {
super.init(value: value)
}
required init(stringInterpolation: DefaultStringInterpolation) {
super.init(value: stringInterpolation.description)
}
}
Oh, and defining a method on Child and calling it on wtf causes a EXC_BAD_ACCESS 
class Child: StringExpressible {
func childMethod() {}
}
let wtf: Child = Child("interpolated string: \(42)")
wtf.childMethod() // EXC_BAD_ACCESS
If someone could shed some light on what's going on or where my understanding is wrong that'd be highly appreciated.
3 Likes
trochoid
(Trochoid)
2
I tried your first block of code and after adding the parameter names…
let works = Child(stringLiteral: "string literal")
let wtf = Child(stringLiteral: "interpolated string: \(42)")
…they both printed “Child”. I’m not sure what’s up. This is in Swift 5.8
I assume if you're using stringLiteral: both times, then the ExpressibleByStringInterpolation constructor isn't called and instead the string interpolation happens during the argument evaluation before the method is called.
xwu
(Xiaodi Wu)
4
This seems to be a...bug? cc @beccadax
Yeah, it looks like a type checker bug. There's a language feature (SE-0213) where if Foo is a type conforming to an ExpressibleBy*Literal protocol and x is some literal, then Foo(x) is interpreted as if it were written as x as Foo, so it constructs the literal as a Foo, instead of calling Foo.init(_:) with the literal. In this case though, it appears to not handle the case where the conformance is inherited by a subclass. In the -dump-ast output, I see the InterpolatedStringLiteralExpr has the wrong class in the substitution map:
(substitution_map generic_signature=<Self where Self : ExpressibleByStringInterpolation, Self.StringInterpolation == DefaultStringInterpolation> (substitution Self -> StringExpressible))
1 Like
xedin
(Pavel Yaskevich)
6
Hm, I'm not sure that this is specific to the SE-0213, it seems to be the problem with interpolations in general:
class Base : ExpressibleByStringLiteral, ExpressibleByStringInterpolation {
typealias StringLiteralType = String
required init(stringLiteral value: StringLiteralType) {
}
required init(stringInterpolation: DefaultStringInterpolation) {
}
}
class Test : Base {
func compute() -> Int { 42 }
}
let test = "hello \(42)" as Test
print(test.compute())
Crashes too:
IntToPtr source must be an integral
%41 = inttoptr %T4main4BaseC* %40 to %T4main4TestC*, !dbg !103
<unknown>:0: error: fatal error encountered during compilation; please submit a bug report (https://swift.org/contributing/#reporting-bugs)
<unknown>:0: note: Broken module found, compilation aborted!