Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type inference fails when a literal is passed to a generic function and the return value is applied to a binary operator. #65061

Open
kishikawakatsumi opened this issue Apr 11, 2023 · 2 comments
Assignees
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself generics Feature: generic declarations and types operators Feature: operators regression swift 5.9 type checker Area → compiler: Semantic analysis type inference Feature: type inference types Feature: types unexpected error Bug: Unexpected error

Comments

@kishikawakatsumi
Copy link

Description

When I pass a literal expression as an argument to a generic function and compare its return value to another literal with a binary operator, the == case is not a problem, but ! = causes the type inference failure and a compilation error.

I have confirmed that some cases compile correctly in Swift 4.2.3. Also, the difference between == and ! = operators gives different type inference results on either side, which seems odd. So this seems to be some kind of regression in Swift 5.

Steps to reproduce

  1. Define the following generic function:
func f<T>(_ val: T) -> T {
  return val
}
  1. Use the function in the following code segments a and b:
let i = 0
_ = f([0]).firstIndex(of: 0) == i // a
_ = f([0]).firstIndex(of: 0) != i // b // Error

The Swift compiler produces a compilation error for code segment b, while code segment a compiles without any issues. The only difference between the two is which binary operator is used. The error message is Value of optional type 'Int?' must be unwrapped to a value of type 'Int'. The only difference between the two segments is the binary operator. It seems odd that it would cause an error in one of them.

Both codes compile correctly in Swift 4.2.3. It appears to be a regression that probably occurred in Swift 5.
See https://swiftfiddle.com/ky3flhj6wnc3hkxw6ft4lfnx7q

Expected behavior

Both code segments a and b should compile without errors, as the only difference between them is the binary operator.

Environment

  • Swift compiler version info
    • from Swift version 5.0 (swift-5.0-RELEASE) to Swift version 5.8 (swift-5.8-RELEASE) and 5.9-dev (LLVM 8ef0b2ab8e410d9, Swift 76d60fde58eab47)
  • Xcode version info Xcode 14.3 Build version 14E222b

Additional context

As a temporary workaround, defining an overloaded function as follows fixes the compilation error:

func f<T>(_ val: [T]) -> [T] {
  return val
}

This indicates that the issue might be related to the compiler's inability to infer the type from the literals correctly.

Similar cases

Float literals

This happens not only with array literals but also with float and string literals.

The following code causes a compile error Ambiguous use of operator '==' for line a.
This problem does not occur in the Linux environment but only in the Mac environment, resulting in a compile error.

 _ = f(0.0) == 1 // a
 _ = f(0.0) != 1 // b

This problem does not occur if the following overloads are provided:

func f(_ val: Float) -> Float {
    return val
}
String literals

The following code causes a compile error Value of optional type 'String.Element?' (aka 'Optional<Character>') must be unwrapped to a value of type 'String.Element' (aka 'Character') for line b.

let c: Character = "a"
_ = f("").first == c // a
_ = f("").first != c // b

This issue occurs in both Linux and Mac environments.

Also, as in the first example, it does not occur in a Swift 4.2.4 environment. See https://swiftfiddle.com/b2o6hyjbfjdhre47tkovyq4oja

As with the other examples, this problem does not occur if you provide the following overloads:

func f(_ val: String) -> String {
    return val
}
@kishikawakatsumi kishikawakatsumi added bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. triage needed This issue needs more specific labels labels Apr 11, 2023
@AnthonyLatsis AnthonyLatsis added compiler The Swift compiler in itself type checker Area → compiler: Semantic analysis regression swift 5.9 unexpected error Bug: Unexpected error generics Feature: generic declarations and types operators Feature: operators type inference Feature: type inference types Feature: types and removed triage needed This issue needs more specific labels labels Apr 12, 2023
@AnthonyLatsis
Copy link
Collaborator

The example with floating point literals compiles with a near main revision. The other examples suggest a common cause and can be reduced to the following:

infix operator ***: ComparisonPrecedence

func *** <T>(_: T, _: T) -> Bool {}

func f<T>(_: T) -> T {}

extension Int {
  func toOptional() -> Int? {}
}

do {
  let i: Int
  _ = f(0).toOptional() *** i // error
}

cc @xedin

@kishikawakatsumi
Copy link
Author

Show the real problem I have. I am developing a library with Swift macros here https://github.com/kishikawakatsumi/swift-power-assert.

The macro converts the following code

#expect(array.distance(from: 2, to: 3) == 1, verbose: true)

to the following code:

PowerAssert.Assertion("#expect([zero: one, two: three].count != three)") {}
  $0.capture(
    $0.capture(
      $0.capture(
        [
          $0.capture(zero.self, column: 9, id: 0) $0.capture(one.self, column: 15, id: 1),
          $0.capture(two.self, column: 20, id: 2): $0.capture(three.self, column: 25, id: 3),
        ], column: 8, id: 4
      ).count, column: 32, id: 5) != $0.capture(three.self, column: 41, id: 6), column: 38, id: 7)
}

The definition of the capture function is as follows.

public func capture<T>(_ expr: @autoclosure () throws -> T, column: Int) rethrows -> T {
  let val = try expr()
  store(value: val, column: column, id: id)
  return val
}

This code should compile, but it actually causes the compile error. To avoid this problem, the following overload function is required.

public func capture<T>(_ expr: @autoclosure () throws -> T?, column: Int) rethrows -> T? {
  let val = try expr()
  store(value: val, column: column, id: id)
  return val
}

The following code also causes the error.

#expect(
  #colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1) != .blue
)

So overload functions are also needed for Double and Float.

public func capture(_ expr: @autoclosure () throws -> Float, column: Int) rethrows -> Float {}
  let val = try expr()
  store(value: val, column: column, id: id)
  return val
}

public func capture(_ expr: @autoclosure () throws -> Double, column: Int) rethrows -> Double { return val
  let val = try expr()
  store(value: val, column: column, id: id)
  return val
}

Similarly, I am currently defining many overload functions. Probably all literal types will require overloading, and considering the ExpressibleBy- protocol, it is impossible to cover all cases.

@hborla hborla self-assigned this Jun 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself generics Feature: generic declarations and types operators Feature: operators regression swift 5.9 type checker Area → compiler: Semantic analysis type inference Feature: type inference types Feature: types unexpected error Bug: Unexpected error
Projects
None yet
Development

No branches or pull requests

3 participants