Constructor of subtype of type conforming to ExpressibleByStringInterpolation returns wrong type

Today I hit something which at contradicts my two assumptions

  1. type(of:) returns the dynamic type of an object
  2. 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 :roll_eyes:

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

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.

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

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!