Extending implicit member syntax to look through function return types

Hi y'all!

I wanted to take the community's temperature on a feature that I find myself reaching for relatively often and always have to remind myself why it doesn't work. The minimal example is code such as the following:

struct Foo {
  var x: Int
}

func foo(from x: Int?) -> Foo? {
  return x.map(.init(x:))
}

which fails because the type of the argument to map is (Int) throws -> Foo (not Foo), which doesn't have any init(x:) member. The fix today is relatively simple, but forces us to abandon either point-free syntax or implicit member syntax:

  return x.map { .init(x: $0) }

or

  return x.map(Foo.init(x:))

I think it would be feasible to introduce an additional rule for implicit member syntax of the form:

If the contextual type is a function type (T1, ..., Tn) -> U, then we will look for members in type U.

What do people think? Would this be useful, or too magical?

12 Likes

It seems useful, but also slightly confusing when compared to SE-0249:

  • a (Root) -> Value function, and a \Root.value or \.value key path expression.

  • a (Value) -> Root function, and a Root.init(value:) or .init(value:) expression.

I realize that Root.init(value:) already works, and Swift doesn't support key paths for initializers.


Would your rule be specifically for initializers, or would enum case constructors also qualify?

enum Root {
  case value(Value)
  init(value: Value) { self = .value(value) }
}

var value: Value?
var root: Root?

root = value.map(Root.init(value:)) // Already works.
root = value.map(.init(value:))     // Proposed.

root = value.map(Root.value) // Already works.
root = value.map(.value)     // Proposed?
3 Likes

I was imagining it would follow the usual implicit member lookup rules, so initializers, case constructors, and static functions would all be allowed.

1 Like

I'd love to see this happen. I don't think it's too magical since the expected return type is known all along throughout the member chain.