Allow chained member references in implicit member expressions

Right. The concern being discussed here is not of a false-positive overflow diagnostic (since, as you note, the overflow detection step runs after all the types are fully solved). Instead, the concern is that the type-checking behavior changes in such a way that code which previously inferred an integer literal type as, say, Int64, changes to infer the type of that literal as Int, which would be a source compatibility break if the literal overflows a 32-bit integer (but not an Int64).

On a more general note, the prototype implementation and proposal PRs have been opened. If anyone has any feedback I'd love to hear, otherwise this is ready to head towards review!

The "different generic arguments" aspect of this feature has been deferred from this proposal, since certain design choices may necessitate a source break/new language version depending on how we want to approach it.

4 Likes

The draft looks great, it’s concise yet detailed and I think it follows the standard mental model for implicit member chains. Great work!

1 Like

Is there a summary of the issues involved here? Most (or maybe even all) of the use cases I have for this feature would all require different generic arguments.

1 Like

The discussion about the complexities of the "different generic arguments" case begins at this post. The short version is that if different generic arguments are allowed at the head and the tail of the chain, it's not obvious that this example:

struct S<T> {}

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

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

let _: S<Int> = .sInt

should compile, since there's no "link" between the generic arguments at the head and tail. Since this example compiles today, changing it would require a new language version.

Ultimately, extending the rule without the "different generic arguments" rule doesn't introduce any new issues (except that the above example would apply to multi-member chains as well), so I opted to keep this proposal smaller and revisit the discussion about generic arguments until after this first step has gone through review. I'm not abandoning that plan (you've convinced me that the feature is useful!), just separating it out into its own discussion/proposal.

I love where the prototype pitch and implementation have ended up. The beauty of it is that you're generalizing an existing mental model to work in more cases, simplifying rather than complicating the language.

Frankly, even after reviewing the posts above a second time, I'm having trouble seeing how "different generic arguments" would fit into that scheme.

Since implicit member expressions are a convenience (and a very ergonomic one—this is no small feat and by no means would I downplay the importance of its ergonomics), it can certainly be made more convenient for more use cases, which would be made more ergonomic. I get that. I too would want the maximum ergonomics that could be gained from generalizing the existing rules around implicit member expressions.

However, you've enumerated a variety of cases in which allowing "different generic arguments" could break existing code without additional rules or caveats. It seems that taking that additional step would involve more than generalizing an existing mental model, and we'd then be trading some complexity for the additional convenience and ergonomics. This changes the pros and cons of such a plan and makes that additional step not a slam dunk, although it may still in the end be worth it.

The issues you mention don't end when (if) we find a set of rules that work for the compiler to preserve backwards compatibility. I'd like to think it an uncontroversial view that what we allow to be implicit should be (reasonably) easily imputed by a machine but also—crucially—(reasonably) easily discerned by the human reader.

To my view, your example involving integer literals and even your example sInt fall down inexorably on the latter point even if we can devise a set of rules that work for the compiler to preserve backwards compatibility. It's simply not immediately clear to a human what the behavior will be without going through in one's head a set of complex rules like a compiler. Having the option, then, not to state the type in those cases seems a questionable tradeoff; we have to ask if the ergonomics are improved at all if what we allow to be elided isn't actually clear to the human reader upon elision. Recall that clarity at the point of use is a tentpole principle of Swift language design.

Perhaps we will find that these are the most corner of corner cases, and that in general these issues won't arise when we allow "different generic arguments." We do, after all, trust users to use powerful features wisely, and we don't withhold useful features just because in pathologic cases it can be used poorly. But I certainly think that this question requires a more thorough exploration, and we must think seriously if we go down that road about whether some design can allow the majority (or even all) of the non-pathologic cases without incurring the difficulties explored here.

I think all this is to say that the question involving "different generic arguments" is a significant one, greater larger in its scope that the entirety of what's proposed here, and that I agree with @Jumhyn that it merits its own pitch and process.

3 Likes

Yeah, IMO this is a really good way to think about it. Allowing different generic arguments changes this from a pitch focused on straightforwardly generalizing an existing model and reaches into the realm of adding a new (relatively complex and subtle) language feature. The more I worked on the implementation "different generic arguments" aspect the more it became clear that I was making a lot of design choices that were just coming down to my personal judgement since they hadn't been sufficiently discussed.

Separating those concerns out from what has otherwise been an almost universally favored proposal will make it much easier for us to "get it right" when it comes time to reconsider the role of generic arguments in implicit member chains. And, as mentioned, this straightforward extension does not close us off from potential designs that are not already complicated by the single-member case. Thank you for your thoughts @xwu!

1 Like