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 <xiaodi...@gmail.com> 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 <cantr...@pobox.com > <mailto:cantr...@pobox.com>> 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 <xiaodi...@gmail.com >> <mailto:xiaodi...@gmail.com>> 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 <cantr...@pobox.com >> <mailto:cantr...@pobox.com>> wrote: >>> On Jun 16, 2017, at 5:23 PM, Mark Lacey <mark.la...@apple.com >>> <mailto:mark.la...@apple.com>> wrote: >>> >>> >>>> On Jun 16, 2017, at 2:09 PM, Paul Cantrell <cantr...@pobox.com >>>> <mailto:cantr...@pobox.com>> wrote: >>>> >>>>> >>>>> On Jun 16, 2017, at 3:43 PM, Mark Lacey <mark.la...@apple.com >>>>> <mailto:mark.la...@apple.com>> wrote: >>>>> >>>>> >>>>>> On Jun 16, 2017, at 1:21 PM, Mark Lacey <mark.la...@apple.com >>>>>> <mailto:mark.la...@apple.com>> wrote: >>>>>> >>>>>>> >>>>>>> On Jun 16, 2017, at 11:13 AM, Paul Cantrell via swift-evolution >>>>>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote: >>>>>>> >>>>>>>> >>>>>>>> On Jun 15, 2017, at 7:17 PM, Xiaodi Wu via swift-evolution >>>>>>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote: >>>>>>>> >>>>>>>> >>>>>>>> On Thu, Jun 15, 2017 at 19:03 Víctor Pimentel <vpimen...@tuenti.com >>>>>>>> <mailto:vpimen...@tuenti.com>> wrote: >>>>>>>> On 16 Jun 2017, at 01:55, Xiaodi Wu via swift-evolution >>>>>>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote: >>>>>>>> >>>>>>>>> On Thu, Jun 15, 2017 at 17:43 David Hart <da...@hartbit.com >>>>>>>>> <mailto:da...@hartbit.com>> 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 >>>>>>> swift-evolution@swift.org <mailto:swift-evolution@swift.org> >>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution> >
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution