Search code examples
swiftreturnswift3

Swift 3 compiler displays wrong message when returning a value from a void function


Normally, if you write this code;

func test1() {
    return CGPoint.zero
}

The compiler will tell you

unexpected non-void return value in void function

which is very nice. Everyone knows what they did wrong.

But this code will cause the compiler to say something different:

func test2() {
    let a: CGFloat = 1
    return CGPoint(x: 0.0, y: a)
}

The compiler now says:

cannot invoke initializer for type 'CGPoint' with an argument list of type '(x: Double, y: CGFloat)'

note: overloads for 'CGPoint' exist with these partially matching parameter lists: (x: CGFloat, y: CGFloat), (x: Double, y: Double)

Now I am confused.

Why doesn't the compiler mention anything about returning a value in a void function, and instead complain about calling CGPoint.init with (x: 0.0, y: a), which is completely fine?

Since 0.0 is a float literal, and CGFloat conforms to BinaryFloatingPoint which conforms to ExpressibleByFloatLiteral, 0.0 is converted to CGFloat at compile time. As a result, the CGPoint(x: CGFloat, y: CGFloat) initializer should have been used. Why does the compiler complain that CGPoint can't be initialized with (x: Double, y: CGFloat)?

I thought this was because the compiler saw that I was returning a value where I shouldn't and stopped evaulating the expression so 0.0 is not converted to CGFloat. But if that's the case, the compiler should have said something about returning a value in a void function!

Is this a bug or intended?


Solution

  • CGPoint has three overloaded initializers with the same argument names:

     public init(x: CGFloat, y: CGFloat)
     public init(x: Double, y: Double)
     public init(x: Int, y: Int)
    

    You'll get the same error message with

    let v: Void = CGPoint(x: 1.0, y: 2)
    // error: cannot invoke initializer for type 'CGPoint' with an argument list of type '(x: Double, y: Int)'
    // note: overloads for 'CGPoint' exist with these partially matching parameter lists: (x: Int, y: Int), (x: Double, y: Double)
    

    Here is another example demonstrating the effect:

    struct A {
        init(x: Double, y: Double) { }
    }
    
    struct B {
        init(x: Double, y: Double) { }
        init(xi: Int, yi: Int) { }
    }
    
    struct C {
        init(x: Double, y: Double) { }
        init(x: Int, y: Int) { }
    }
    
    let v1: Void = A(x: 1, y: 2.0)
    // error: cannot convert value of type 'A' to specified type 'Void' (aka '()')
    
    let v2: Void = B(x: 1, y: 2.0)
    // error: cannot convert value of type 'B' to specified type 'Void' (aka '()')
    
    let v3: Void = C(x: 1.0, y: 2.0)
    // error: cannot convert value of type 'C' to specified type 'Void' (aka '()')
    
    let v4: Void = C(x: 1, y: 2.0)
    // error: cannot invoke initializer for type 'C' with an argument list of type '(x: Int, y: Double)'
    

    Only in the last line, C(x: 1, y: 2.0) matches two initializers. I assume that the compiler tries to match against both, fails to convert the return value to Void in both cases, and therefore emits a different error message.

    The error message is not very helpful, so you might consider to file a bug report at http://bugs.swift.org.