Skip to content

[QoI] Diagnose initializers written as typed patterns (SR-1461) #4371

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 24, 2016

Conversation

jtbandes
Copy link
Contributor

@jtbandes jtbandes commented Aug 18, 2016

What's in this pull request?

Diagnose and offer to replace var x: [Int]() with var x = [Int]().

error: unexpected initializer in pattern; did you mean to use '='?
var x: [Int]()
     ~ ~~~~~^~
      =

Resolved bug number: SR-1461


Before merging this pull request to apple/swift repository:

  • Test pull request on Swift continuous integration.

Triggering Swift CI

The swift-ci is triggered by writing a comment on this PR addressed to the GitHub user @swift-ci. Different tests will run depending on the specific comment that you use. The currently available comments are:

Smoke Testing

Platform Comment
All supported platforms @swift-ci Please smoke test
All supported platforms @swift-ci Please smoke test and merge
OS X platform @swift-ci Please smoke test OS X platform
Linux platform @swift-ci Please smoke test Linux platform

A smoke test on macOS does the following:

  1. Builds the compiler incrementally.
  2. Builds the standard library only for macOS. Simulator standard libraries and
    device standard libraries are not built.
  3. lldb is not built.
  4. The test and validation-test targets are run only for macOS. The optimized
    version of these tests are not run.

A smoke test on Linux does the following:

  1. Builds the compiler incrementally.
  2. Builds the standard library incrementally.
  3. lldb is built incrementally.
  4. The swift test and validation-test targets are run. The optimized version of these
    tests are not run.
  5. lldb is tested.

Validation Testing

Platform Comment
All supported platforms @swift-ci Please test
All supported platforms @swift-ci Please test and merge
OS X platform @swift-ci Please test OS X platform
OS X platform @swift-ci Please benchmark
Linux platform @swift-ci Please test Linux platform

Lint Testing

Language Comment
Python @swift-ci Please Python lint

Note: Only members of the Apple organization can trigger swift-ci.

}
diagnose(lParenLoc, diag::initializer_as_typed_pattern)
.highlightChars(Ty.get()->getStartLoc(),
rParenLoc.getAdvancedLoc(1))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not .highlight({Ty.get()->getStartLoc(), rParenLoc})

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I guess I was wrong about the need to use getAdvancedLoc to highlight all the way to the closing paren. Will change.

replacement = "=";
}
diagnose(lParenLoc, diag::initializer_as_typed_pattern)
.highlight({Ty.get()->getStartLoc(), rParenLoc})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation is wrong here

diagnose(lParenLoc, diag::initializer_as_typed_pattern)
.highlight({Ty.get()->getStartLoc(), rParenLoc})
.fixItReplace(colonLoc, replacement);
result.setIsParseError();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little unhappy that the parsed expr doesn't end up anywhere. That will probably break syntax highlighting and code completion. Is it possible to put it into the AST somewhere?

Copy link
Contributor Author

@jtbandes jtbandes Aug 19, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any ideas how? Might be able to hoist this into the caller, so if after parsing the pattern we find it's actually an initializer, we can parse it as such. It looked much easier the way it is when I implemented it, but I'll take another look later today.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. @benlangmuir, @nkcsgexi, any ideas?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrm. I take it Swift's lexer isn't fancy enough to be able to modify the token stream? If we backtracked and replaced the : with `=' the caller would probably recover very well.

Short of that, yeah trying to handle more of this in the caller might be workable...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we at least preserve the TypeRepr, maybe parsed like var x: [Int]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, my bad. You do preserve the TypeRepr. For the expressions in call arg, I've no other suggestions except hoisting to the caller as another expression.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I guess there is a question of whether we want PatternBindingDecl parsing to generally recognize incorrect var x: «expr» and treat it as var x = «expr» instead.

(The fix as written is pretty simple because it only handles exprs that look like initializer calls. It handles var x: [Int](), but it won't handle var x: Unmanaged.fromOpaque(y) or var x: MemoryLayout<Int>.stride, for instance.)

In general, if the user made a typo and wrote : instead of =, the parser might only notice a problem after successfully parsing a typed pattern binding. So, unless we want to try parsing var x = «expr» first every time we look at a pattern (even if we see a :), I think this would need to be hoisted even further out than the caller of parseTypedPattern(), because it needs to invoke all the logic in parseExpr/parseExprPostfix, and it might need to do so even if parseTypedPattern succeeded.

Which is to say: the fix as written is pretty simple and self-contained. After reading some more of the parser implementation, I'm inclined to think producing better results than this would be considerably more involved. Feel free to prove me wrong, if you can think of a clean way to do it!

Copy link
Contributor

@nkcsgexi nkcsgexi Aug 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of recognizing var x : <<expr>> as var x = <<expr>>. However, there may be other problems by doing so; for instance, if there is a function called getEqualPos() in the AST node, what will it return? an invalid position or the position of : instead.

The fixit per se is for sure an improvement! Thanks for working on that. Preserving erroneous AST nodes and providing insight of them is ideal for tooling but I have to say it is not easy in this case.

@jrose-apple
Copy link
Contributor

This is a great fix to have.

@jtbandes
Copy link
Contributor Author

jtbandes commented Aug 20, 2016

@jrose-apple Thanks for the prompting on additional tests — that led me to discover trailing closures weren't handled properly at the top level, so I added a ContextChange. I also added a Backtrack for good measure (although if we find a ( and then can't parse an initializer call, there's not going to be anything we can successfully continue parsing anyway).

I also improved the logic for choosing a replacement string, so the fix-it won't produce invalid expressions like var x =[Int]().

@swift-ci please smoke test macOS

// Suggest replacing ':' with '=' (ensuring proper whitespace).

bool needSpaceBefore =
SourceMgr.getByteDistance(pastEndOfPrevLoc, colonLoc) == 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can just use ==.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can just use ==.

@jtbandes
Copy link
Contributor Author

@jrose-apple done!
@swift-ci smoke test

@jtbandes
Copy link
Contributor Author

friendly bump @jrose-apple @DougGregor

@jrose-apple
Copy link
Contributor

I'm happy if @nkcsgexi and @rintaro are happy with it.

@DougGregor
Copy link
Member

LGTM!

@DougGregor DougGregor merged commit 9c943f0 into swiftlang:master Aug 24, 2016
@jtbandes jtbandes deleted the diag branch August 25, 2016 02:32

bool needSpaceBefore = (pastEndOfPrevLoc == colonLoc);
bool needSpaceAfter =
SourceMgr.getByteDistance(colonLoc, startOfNextLoc) <= 1;
Copy link
Member

@rintaro rintaro Aug 25, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jtbandes
Sorry, this didn't work when a comment is involved:

let i:/* comment */Int(42)
let i =/* comment */Int(42) // As of now, we can't parse this.

How do you think about #4499?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants