SE-0287: Extend implicit member syntax to cover chains of member references

The review of SE-0287 "Extend implicit member syntax to cover chains of member references" begins now and runs through August 24th, 2020.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to the review manager (via email or direct message in the Swift forums).

What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift.

When reviewing a proposal, here are some questions to consider:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

As always, thank you for your contribution to making Swift a better language.

Doug Gregor
Review Manager

46 Likes
  • What is your evaluation of the proposal?

+1, seems like a great quality of life improvement.

  • Is the problem being addressed significant enough to warrant a change to Swift?

It's less of a change and more of an improvement, so yes.

  • Does this proposal fit well with the feel and direction of Swift?

Absolutely.

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

N/A

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Read trough of the proposal and the counter arguments regarding the alternatives.

3 Likes

I love it. It fits my mental model better than whatever happens today. What is being proposed makes a lot of sense, and feels like something that is a natural part of swift.

The only confusing part is when the base is Optional, but this part is already confusing, so it's not something that should prevent adding this feature.

This proposal would provide the model mentioned earlier for implicit member expressions: anywhere that a contextual type T can be inferred, writing

.member1.member2.(...).memberN

Will behave as if the user had written:

T.member1.member2.(...).memberN

I assume that there would still be a rule that it could also mean Wrapped.member1.member2.(...).memberN if T is an Optional<Wrapped> (like in the example of milkyChance)

3 Likes

Yeah, there is—thanks for calling this out. There's no proposed change for the existing rule that member lookup at an implicit base looks through Optional. I'll add an aside that this behavior remains the same for multi-member chains.

ETA: Clarification has been merged!

2 Likes
  • What is your evaluation of the proposal?

+1, I would love to see this.

  • Is the problem being addressed significant enough to warrant a change to Swift?

It is significant enough to warrant an additive improvement, yes.

  • Does this proposal fit well with the feel and direction of Swift?

Yes. As the proposal describes, this fills a hole that many Swift users may already intuitively expect to work.

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

n/a

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Read the entire proposal, and considered it in the context of many personal encounters with implicit member syntax where this ability would have been helpful.

1 Like

I want to take an aside to compliment @Jumhyn on a well-written proposal. I especially appreciated the discussion of rejecting heterogeneous chains in "Alternatives Considered".

(I'm mildly positive on the proposal itself, with only a little concern on the relative compile times between cases where the base is provided and cases where it isn't.)

7 Likes

The base type of the implicit member expression would be constrained to match the contextual/resultant type of the whole chain.

Clarification: Did you mean to say that name lookup will look in the contextual type? The examples given, and the detailed design imply that the base expression does not need to match the context type.

let _: Foo = .bar.anotherFoo

CC @rintaro

To ensure this doesn't negatively impact code-completion, we should be careful that the priority of implicit members matching the context type remains much higher than other members. I expect that is still by far the more common case.


  • What is your evaluation of the proposal?

Overall I'm positive on this change. I recently had occasion to use code like .value + 4 and found it pretty nice that the implicit member "just worked" with a binary operator. I see chaining as an extension of that versatility.

  • Is the problem being addressed significant enough to warrant a change to Swift?

Not a huge win, but seems worth it to me.

  • Does this proposal fit well with the feel and direction of Swift?

Yes, seems like a natural extension from existing features.

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Quick reading. In particular, I have not thought at all about this part of the proposal:

Members of the chain are allowed to participate in generic parameter inference as well.

3 Likes

In most of the 'simple' cases (where the result of the member chain can be immediately matched to a concrete contextual type) I believe the impact should be negligible, since the base can be inferred as soon as constraints are generated for the 'tail' of the member chain.

More complex cases (e.g., where the result of the implicit member expression is a generic parameter that has to be inferred separately) can definitely be slow compared to specifying the base explicitly, but this issue is already exhibited by single-member chains as well.

By "base type of the implicit member expression" I was referring to the implicit base of the expression, i.e., the type that would have been written before the leading dot if we weren't allowed to omit it. In your example, Foo is the "base type of the implicit member expression". Do you have a suggestion for wording that would make this clearer?

There's no proposed changes to code completion here, so code completion for the implicit member expression should continue to offer only the options which match the contextual type with later members in the chain offering the full list of matches.

Perhaps it would make sense to constrain later members in the chain to the contextual type as well, but I couldn't convince myself one way or the other and so left the existing behavior as-is.

FWIW, this is already supported for single-member chains (and for chains with an explicit base), and so the proposal isn't really introducing any new inference rules regarding generic parameters:

struct S<T> {
    static var s: S { S() }
}

extension S where T == Int {
    static var sInt: S { S() }
}

extension S where T == String {
    var anotherSInt: S<Int> { S<Int>() }
}

let s1: S = .sInt // base (and result) inferred as S<Int>
let s2 = S.s.anotherSInt // base inferred as S<String>
1 Like

I'm generally in favor of this proposal. I've certainly run into some of the cases mentioned in the proposal.

One thing not mentioned is ambiguity where there are duplicate names that can't be decided among by the compiler. I think there are multiple constants named .zero. In the case where the type isn't clearly specified I guess there should be an ambiguity error. I think this should be addressed in the proposal.

let x = .zero.incrementedBy(1)  // what is the type of x?

This is illegal for the same reason that let x = .zero is illegal—there is not enough contextual information to determine the result type of the chain (and by extension, the base of the chain). The proposal states that the specified behavior only applies "[where] a contextual type T can be inferred." If the compiler cannot infer a contextual type, the existing "cannot infer contextual base in reference to member zero" diagnostic is maintained.

5 Likes
  • What is your evaluation of the proposal?

+1. I'm really happy to see that generic type parameters are allowed to change along the chain. I think it would be nice if this would also be made to work:

let foo: Foo<String> = .fooInt.anotherFooIntString

In this example, the type constructor is the same along the chain, but the type parameters at the start and end of the chain are different. I have seen many scenarios where this form of the shorthand would be extremely useful.

  • Is the problem being addressed significant enough to warrant a change to Swift?

Yes

  • Does this proposal fit well with the feel and direction of Swift?

Very much. This is something many people familiar with the language intuitively expect to work.

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

N/A

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

A quick read, although I am very familiar with dot shorthand in general and have written pitches for enhancing it in the past.

1 Like

Big +1. Even though I think that this could lead to some unreadable code in the wrong scenarios, it is a really great (and often missed) capability of the Swiift compiler.

+1

Major +1. This is one of those quality of life things in Swift that I'm happy to see getting cleaned up

+1. I've always wanted to do this but never thought it would be possible :o

Finally it's here, bravo~
+1

+1

+1. I think the proposal presents a more intuitive model than the current design of implicit member references.

Sounds like a great improvement—I’ve been hoping for this for a while now.

Are the toolchains built from the implementation in good enough shape to share? This seems like the sort of proposal that would benefit from folks prototyping APIs with it and seeing whether it suits their needs in practice.

let milky: UIColor = .white.withAlphaComponent(0.5)
let milky2: UIColor = .init(named: "white")!.withAlphaComponent(0.5)
let milkyChance: UIColor? = .init(named: "white")?.withAlphaComponent(0.5)

ill admit i didnt know this was not possible!

+1 from me


  • What is your evaluation of the proposal?
    lgtm (looks good to me)

  • Is the problem being addressed significant enough to warrant a change to Swift?
    yes, seems inconsistent that this already isnt supported

  • Does this proposal fit well with the feel and direction of Swift?
    yes

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
    no

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
    read the whole proposal, tried it out in repl