Yes, agreed, the fix for Chris’s brain-bender shouldn’t revisit any of
SE-0155’s matching & labeling rules.
How about:
1. Disallow labels for bare tuples in patterns. (By “bare tuples” I mean “not
representing associated values on an enum.”)
let (a: x, b: y) = foo // disallowed
let (x, y) = foo // OK
2. Maybe require “let” before individual identifiers when pattern matching on
associated values, while preserving SE-0155’s rules for when labels may appear:
case let .foo(a: x, b: y) // disallowed
case .foo(a: let x, b: let y) // OK
#2 is debatable. It would solve an enum-based parallel to Chris’s original:
case let .foo(a: Int, b: String) // disallowed
case .foo(a: let Int, b: let String) // allowed, and Int/String no
longer look like types
P
> On Jun 16, 2017, at 10:55 PM, Xiaodi Wu <[email protected]> wrote:
>
> See:
> https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170417/035972.html
>
> <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170417/035972.html>
>
>
> On Fri, Jun 16, 2017 at 22:32 Paul Cantrell <[email protected]
> <mailto:[email protected]>> wrote:
> Under these not-yet-implemented plans, if associated value labels are no
> longer tuple labels, then how will pattern matching work? And what existing
> pattern matching code will break / continue to work?
>
> P
>
>> On Jun 16, 2017, at 10:22 PM, Xiaodi Wu <[email protected]
>> <mailto:[email protected]>> wrote:
>>
>> Keep in mind that once the latest proposal about enum cases is implemented,
>> these will be at least notionally no longer tuple labels but rather a
>> sugared way of spelling part of the case name. The rules surrounding labels
>> during case matching have only just been revised and approved and have not
>> even yet been implemented. I don’t think it would be wise to fiddle with
>> them again.
>>
>>
>> On Fri, Jun 16, 2017 at 21:21 Paul Cantrell <[email protected]
>> <mailto:[email protected]>> wrote:
>>> On Jun 16, 2017, at 5:23 PM, Mark Lacey <[email protected]
>>> <mailto:[email protected]>> wrote:
>>>
>>>
>>>> On Jun 16, 2017, at 2:09 PM, Paul Cantrell <[email protected]
>>>> <mailto:[email protected]>> wrote:
>>>>
>>>>>
>>>>> On Jun 16, 2017, at 3:43 PM, Mark Lacey <[email protected]
>>>>> <mailto:[email protected]>> wrote:
>>>>>
>>>>>
>>>>>> On Jun 16, 2017, at 1:21 PM, Mark Lacey <[email protected]
>>>>>> <mailto:[email protected]>> wrote:
>>>>>>
>>>>>>>
>>>>>>> On Jun 16, 2017, at 11:13 AM, Paul Cantrell via swift-evolution
>>>>>>> <[email protected] <mailto:[email protected]>> wrote:
>>>>>>>
>>>>>>>>
>>>>>>>> On Jun 15, 2017, at 7:17 PM, Xiaodi Wu via swift-evolution
>>>>>>>> <[email protected] <mailto:[email protected]>> wrote:
>>>>>>>>
>>>>>>>>
>>>>>>>> On Thu, Jun 15, 2017 at 19:03 Víctor Pimentel <[email protected]
>>>>>>>> <mailto:[email protected]>> wrote:
>>>>>>>> On 16 Jun 2017, at 01:55, Xiaodi Wu via swift-evolution
>>>>>>>> <[email protected] <mailto:[email protected]>> wrote:
>>>>>>>>
>>>>>>>>> On Thu, Jun 15, 2017 at 17:43 David Hart <[email protected]
>>>>>>>>> <mailto:[email protected]>> wrote:
>>>>>>>>>
>>>>>>>>> By the way, I’m not attempting to deduce that nobody uses this
>>>>>>>>> feature by the fact I didn’t know about it. But I think it’s one
>>>>>>>>> interesting datapoint when comparing it to SE-0110.
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> SE-0110, **in retrospect**, has had impacts on a lot of users;
>>>>>>>>> prospectively, it was thought to be a minor change, even after review
>>>>>>>>> and acceptance.
>>>>>>>>>
>>>>>>>>> Keep in mind that this proposed change would also eliminate inline
>>>>>>>>> tuple shuffle. For instance, the following code will cease to compile:
>>>>>>>>>
>>>>>>>>> let x = (a: 1.0, r: 0.5, g: 0.5, b: 0.5)
>>>>>>>>> func f(color: (r: Double, g: Double, b: Double, a: Double)) {
>>>>>>>>> print(color)
>>>>>>>>> }
>>>>>>>>> f(color: x)
>>>>>>>>>
>>>>>>>>> It is an open question how frequently this is used. But like implicit
>>>>>>>>> tuple destructuring, it currently Just Works(TM) and users may not
>>>>>>>>> realize they’re making use of the feature until it’s gone.
>>>>>>>>
>>>>>>>> It's much much less used, by looking at open source projects I doubt
>>>>>>>> that a significant portion of projects would have to change code
>>>>>>>> because of this.
>>>>>>>>
>>>>>>>> The reason that I’m urging caution is because, if I recall correctly,
>>>>>>>> that is also what we said about SE-0110 on this list. Then, as now, we
>>>>>>>> were discussing an issue with something left over from the Swift 1
>>>>>>>> model of tuples. Then, as now, we believed that the feature in
>>>>>>>> question was rarely used. Then, as now, we believed that removing that
>>>>>>>> feature would improve consistency in the language, better both for the
>>>>>>>> compiler and for users. Then, as now, leaving it in was thought to
>>>>>>>> prevent moving forward with other features that could improve Swift.
>>>>>>>
>>>>>>> Data:
>>>>>>>
>>>>>>> I hacked up a regexp that will catch most uses of labeled tuples in
>>>>>>> pattern matches, e.g. “let (foo: bar) = baz”. That’s what we’re talking
>>>>>>> about, right?
>>>>>>
>>>>>> That’s the obvious example that people find confusing.
>>>>>>
>>>>>> Less obvious places that labeled tuple patterns show up are ‘case let’
>>>>>> and ‘case’ (see below).
>>>>>
>>>>> Okay, I should have looked at your regex and read further. It looks like
>>>>> you were already trying to match these.
>>>>
>>>> I did walk the grammar for all occurrences of _pattern_.
>>>>
>>>> I’m only matching named tuple patterns that immediately follow one of the
>>>> keywords which a pattern follows (for, case, let, var, and catch). As I
>>>> mentioned, I’m not matching patterns that come later in comma-separated
>>>> lists. I’m also not matching named tuples inside nested patterns, e.g. let
>>>> ((a: b), (c: d)).
>>>>
>>>> But again, if even the most basic form of this construct is so rare, I
>>>> doubt more robust matching would turn up that much more usage.
>>>>
>>>>> I’m surprised you’re not seeing any uses of ‘case’ with labels.
>>>>
>>>> Me too. But I just verified that my pattern does match them.
>>>
>>> Are you sure? It doesn’t look like it’s going to match the example I gave
>>> due to the leading ‘.’ on the enum case.
>>
>> Ah! I should have read your original message more carefully. You’re quite
>> right, I only was checking case statements for raw tuples like this:
>>
>> case let (i: a, f: b):
>>
>> …and not for anything involving associated values. I hadn’t even considered
>> that associated values would be affected by this, but looking at the grammar
>> it seems they would indeed be.
>>
>> Another clumsy regex search, this time for patterns with tuple labels on
>> associated values, turned up 111 results (one per ~3800 lines). Not super
>> common, but certainly nothing to sneeze at. Here they are:
>>
>> https://gist.github.com/pcantrell/d32cdb5f7db6d6626e45e80011163efb
>> <https://gist.github.com/pcantrell/d32cdb5f7db6d6626e45e80011163efb>
>>
>> Looking through that gist, these usages mostly strike me as being just fine:
>>
>> case .cover(from: .bottom):
>>
>> case .reference(with: let ref):
>>
>> case .update(tableName: let tableName, columnNames: _):
>>
>> I’d even say that removing the tuple labels would make things worse.
>> Consider:
>>
>> case .name(last: let firstName, first: _): // mistake is clear
>> case .name(let firstName, _): // mistake is buried
>>
>> In Chris’s original brain-bending example, the confusion is that there’s no
>> “let” after the colon, so Int and Float look like types instead of variable
>> names:
>>
>> let (a : Int, b : Float) = foo()
>>
>> However, in the examples in the gist above, most of the patterns either (1)
>> declare variables using a `let` after the colon:
>>
>> case .reference(with: let ref):
>>
>> …or (2) don’t declare a variable at all:
>>
>> case .string(format: .some(.uri)):
>>
>> What if we allowed labels on associated values, but required a `let` after
>> the colon to bind a variable?
>>
>> case let .a(b: c): // disallowed
>> case .a(b: let c): // OK
>>
>> Only 15 of those 111 run afoul of _that_ rule. Here they are:
>>
>> https://gist.github.com/pcantrell/9f61045d7d7c8d18eeec8ebbef6cd8f8
>> <https://gist.github.com/pcantrell/9f61045d7d7c8d18eeec8ebbef6cd8f8>
>>
>> That’s one breakage every ~28000 lines, which seems much more acceptable.
>> The drawback is that you can’t declare variables for a bunch of associated
>> value en masse anymore; you need one let per value. (See line 2 in that
>> gist.)
>>
>>> You might want to try the patch I sent as it will definitely catch any
>>> tuple pattern that makes it to the verifier and does have labels.
>>
>> I’m not set up to build the compiler, unfortunately. One of these days.
>>
>> P
>>
>>>
>>> Mark
>>>
>>>>
>>>> P
>>>>
>>>>>
>>>>> Mark
>>>>>
>>>>>> Fortunately we do not appear to allow shuffling in these cases. I’m not
>>>>>> sure if the human disambiguation is easier here because of the context
>>>>>> (‘case let’ and ‘case’), but I don’t recall seeing complain about these
>>>>>> being confusing (having said that it’s entirely possible they are very
>>>>>> confusing the first time someone sees them, in particular ‘cast let’ and
>>>>>> the binding form of ‘case’.
>>>>>>
>>>>>> enum X {
>>>>>> case e(i: Int, f: Float)
>>>>>> }
>>>>>>
>>>>>> let x = X.e(i: 7, f: 12)
>>>>>>
>>>>>> if case let X.e(i: hi, f: bye) = x {
>>>>>> print("(i: \(hi), f: \(bye))")
>>>>>> }
>>>>>>
>>>>>> func test(_ x: X, _ a: Int, _ b: Float) {
>>>>>> switch x {
>>>>>> case .e(i: a, f: b):
>>>>>> print("match values")
>>>>>> case .e(i: let _, f: let _):
>>>>>> print("bind values")
>>>>>> default:
>>>>>> break
>>>>>> }
>>>>>> }
>>>>>>
>>>>>> test(X.e(i: 1, f: 2), 1, 2)
>>>>>> test(X.e(i: 1, f: 2), 3, 4)
>>>>>>
>>>>>>
>>>>>>>
>>>>>>> I ran that against all 55 projects in swift-source-compat-suite,
>>>>>>> comprising about over 400,000 lines of Swift code, and found … drumroll
>>>>>>> … exactly one match:
>>>>>>>
>>>>>>>
>>>>>>> neota (swift-source-compat-suite)$ find project_cache -name '*.swift'
>>>>>>> -print0 | xargs -0 pcregrep -M
>>>>>>> '(for|case|let|var|catch)\s+\([a-zA-Z0-9_]+\s*:'
>>>>>>> project_cache/RxSwift/RxExample/RxExample-iOSTests/TestScheduler+MarbleTests.swift:
>>>>>>> let (time: _, events: events) = segments.reduce((time:
>>>>>>> 0, events: [RecordedEvent]())) { state, event in
>>>>>>>
>>>>>>>
>>>>>>> Caveats about this method:
>>>>>>>
>>>>>>> • My regexp won’t match second and third patterns in a comma-separated
>>>>>>> let or case, e.g.:
>>>>>>>
>>>>>>> let a = b, (c: d) = e
>>>>>>>
>>>>>>> • It doesn’t match non-ascii identifiers.
>>>>>>>
>>>>>>> • This experiment only considers labeled tuples in pattern matches,
>>>>>>> what I took Chris’s original puzzler to be about. Label-based tuple
>>>>>>> shuffling is a separate question.
>>>>>>>
>>>>>>> Still, even if it’s undercounting slightly, one breakage in half a
>>>>>>> million lines of code should put to rest concerns about unexpected
>>>>>>> widespread impact.
>>>>>>>
>>>>>>> (Anything else I’m missing?)
>>>>>>>
>>>>>>> • • •
>>>>>>>
>>>>>>> Aside for those who know the tools out there: what would it take to run
>>>>>>> inspections like this against ASTs instead of using a regex? Could we
>>>>>>> instrument the compiler as Brent suggested?
>>>>>>
>>>>>> If you want to catch *all* of these cases then the patch below will do
>>>>>> it by failing the AST verifier when it hits a pattern with labels. If
>>>>>> you only want to find the plain let-binding versions of this and not the
>>>>>> ‘case let’ and ‘case’ ones, I’d suggest looking at the parser to see if
>>>>>> there’s an easy place to instrument (I don’t know offhand).
>>>>>>
>>>>>> Mark
>>>>>>
>>>>>> diff --git a/lib/AST/ASTVerifier.cpp b/lib/AST/ASTVerifier.cpp
>>>>>> index b59a7ade23..ba4b2a245d 100644
>>>>>> --- a/lib/AST/ASTVerifier.cpp
>>>>>> +++ b/lib/AST/ASTVerifier.cpp
>>>>>> @@ -2772,6 +2772,13 @@ public:
>>>>>> }
>>>>>>
>>>>>> void verifyParsed(TuplePattern *TP) {
>>>>>> + for (auto &elt : TP->getElements()) {
>>>>>> + if (!elt.getLabel().empty()) {
>>>>>> + Out << "Labeled tuple patterns are offensive!\n";
>>>>>> + abort();
>>>>>> + }
>>>>>> + }
>>>>>> +
>>>>>> PrettyStackTracePattern debugStack(Ctx, "verifying TuplePattern",
>>>>>> TP);
>>>>>> verifyParsedBase(TP);
>>>>>> }
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>> Or can SourceKit / SourceKitten give a full AST? Or has anybody written
>>>>>>> a Swift parser in Swift?
>>>>>>>
>>>>>>> Cheers,
>>>>>>>
>>>>>>> Paul
>>>>>>>
>>>>>>> _______________________________________________
>>>>>>> swift-evolution mailing list
>>>>>>> [email protected] <mailto:[email protected]>
>>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution