-
Notifications
You must be signed in to change notification settings - Fork 382
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
Improve parsing of access level modifier #704
Conversation
Sources/SwiftParser/Modifiers.swift
Outdated
afterDetail = beforeDetail | ||
beforeDetail = [] | ||
} | ||
let rparen = consume(if: .rightParen) ?? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is expect
not sufficient to recover to the right paren? I would expect commas wouldn't bind as tightly as parentheses here so you wouldn't have to do the extra token munging here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made single expect(.rightParen)
version here.
Is this match with your recommendation?
https://github.com/omochi/swift-syntax/pull/1/files
This implementation is simpler.
And safer when there is no stopper token.
For example:
private(
#endif
expect
stops to scanning at #endif
without eating many tokens following from here.
But diagnostics for
private(get, set, didSet)
is worse than before.
(see diff https://github.com/omochi/swift-syntax/pull/1/files#diff-ad19f5e399c23c3b244b7e592ced5c7b7013e224463b61962f131d4fbec70599R159-R161)
The reason of changing diagnostics is comma and right-paren have same TokenPrecedence
level.
expect
stops to search right-paren when see comma.
How do you think about this change of diagnostics?
If you say that previous diagnostics is better,
I try to achieve both using expect
and better diagnostics.
I could be it with expectAny([.rightParen, .comma)]
and some loops.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think what @CodaFi meant was:
- var afterDetail: [RawTokenSyntax] = []
- if let _ = detail {
- while !atAny([.eof, .rightParen]) {
- afterDetail.append(consumeAnyToken())
- }
- } else {
- afterDetail = beforeDetail
- beforeDetail = []
- }
- let rparen = consume(if: .rightParen) ??
- RawTokenSyntax(missing: .rightParen, arena: arena)
+ let (unexpectedBeforeRParen, rparen) = self.expect(.rightParen)
And then use unexpectedBeforeRParen
instead of afterDetail
.
I did try this right now and recovery was indeed not optimal at the moment, TokenPrecedence
needed some adjustments that I was planning to do anyway. I’m making them now in #707.
I believe Alex has improved things here such that |
@CodaFi The new token precedence was useful here. I tried to use only single For example with following, during find private(get, set
The behavior what i want is that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I had this review pending and apparently didn’t submit it.
Once cf4605f makes it into main
, I think we should be able to search for set
by calling expectContextualKeyword("set")
.
While we are waiting for that, I would prefer to have a single expect
call. If the diagnostic is sub-optimal, could you add a FIXME
to it so we can re-visit it once the extended recovery infrastructure from #718 is in main
?
Sources/SwiftParser/Modifiers.swift
Outdated
assert( | ||
[ | ||
RawTokenKind.privateKeyword, | ||
.fileprivateKeyword, | ||
.internalKeyword, | ||
.publicKeyword | ||
].contains(self.currentToken.tokenKind) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use atAny
here:
assert( | |
[ | |
RawTokenKind.privateKeyword, | |
.fileprivateKeyword, | |
.internalKeyword, | |
.publicKeyword | |
].contains(self.currentToken.tokenKind) | |
) | |
assert(self.atAny([.privateKeyword, .fileprivateKeyword, .internalKeyword, .publicKeyword]) |
Sources/SwiftParser/Modifiers.swift
Outdated
afterDetail = beforeDetail | ||
beforeDetail = [] | ||
} | ||
let rparen = consume(if: .rightParen) ?? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think what @CodaFi meant was:
- var afterDetail: [RawTokenSyntax] = []
- if let _ = detail {
- while !atAny([.eof, .rightParen]) {
- afterDetail.append(consumeAnyToken())
- }
- } else {
- afterDetail = beforeDetail
- beforeDetail = []
- }
- let rparen = consume(if: .rightParen) ??
- RawTokenSyntax(missing: .rightParen, arena: arena)
+ let (unexpectedBeforeRParen, rparen) = self.expect(.rightParen)
And then use unexpectedBeforeRParen
instead of afterDetail
.
I did try this right now and recovery was indeed not optimal at the moment, TokenPrecedence
needed some adjustments that I was planning to do anyway. I’m making them now in #707.
Sources/SwiftParser/Modifiers.swift
Outdated
), | ||
detail: detail ?? RawTokenSyntax( | ||
missing: .contextualKeyword, | ||
text: arena.intern("set"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
arena.intern("set")
is not necessary here because "set"
is a StaticString
that doesn’t get deallocated because it lives in the binary itself (instead of stack or heap memory). Thus, it doesn’t need to be managed by a SyntaxArena
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I simply wrote text: "set"
here,
I got following error:
SwiftSyntax/RawSyntax.swift:427: Assertion failed: token text must be managed by the arena, or known default text for the token
2022-09-08 12:00:12.816402+0900 swift-parser-test[59612:27324768] SwiftSyntax/RawSyntax.swift:427: Assertion failed: token text must be managed by the arena, or known default text for the token
It come from this assertion.
swift-syntax/Sources/SwiftSyntax/Raw/RawSyntax.swift
Lines 427 to 428 in 1754a24
assert(arena.contains(text: text) || kind.defaultText?.baseAddress == text.baseAddress, | |
"token text must be managed by the arena, or known default text for the token") |
How do I write?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see. Makes sense. I’ll probably remove that assert
in another PR but you can just call arena.intern
for now.
9e531d1
to
b02d6fe
Compare
@ahoppen I rewrote it as single To mimic By the way, |
@omochi Things have landed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m sorry that this PR took so many iterations due to my recovery-related changes that would have conflicted with this one. But since it’s merged now, I think we can go ahead with this one.
Left one suggestion inline. Also, I think you need to adjust the test cases to actually have a declaration that we can attach the modifier to. Adding var x: Int
or something of the sort to the test cases should do the job.
I think your helpers might also not be needed anymore.
Sources/SwiftParser/Modifiers.swift
Outdated
), | ||
detail: detail ?? RawTokenSyntax( | ||
missing: .contextualKeyword, | ||
text: arena.intern("set"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see. Makes sense. I’ll probably remove that assert
in another PR but you can just call arena.intern
for now.
Sources/SwiftParser/Modifiers.swift
Outdated
var detail: RawTokenSyntax? = nil | ||
var afterDetail: [RawSyntax] = [] | ||
|
||
let (content, rightParen) = expect(.rightParen) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m not really a fan of taking apart the unexpected tokens here. Fortunately, with the new recovery API, you should be able to just expect the set
keyword followed by a )
. Setting the precedence of the set
keyword to weakBracketClose
will make sure that we can skip over weak punctuators (like ,
or :
) in the details.
let unexpectedBeforeDetail: RawUnexpectedNodesSyntax?
let detail: RawTokenSyntax
if let setHandle = self.canRecoverToContextualKeyword("set", precedence: .weakBracketClose) {
(unexpectedBeforeDetail, detail) = self.eat(setHandle)
} else {
unexpectedBeforeDetail = nil
detail = RawTokenSyntax(
missing: .contextualKeyword,
text: arena.intern("set"),
arena: arena
)
}
let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen)
details = RawDeclModifierDetailSyntax(
leftParen: lparen,
unexpectedBeforeDetail,
detail: detail,
unexpectedBeforeRightParen,
rightParen: rightParen,
arena: arena
)
b02d6fe
to
de9e0d3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ahoppen @CodaFi Thank you for all support for me.
My new code becomes beautiful and perfect. (really almost same as Alex suggested)
New expect
utilities family is very useful and have good semantics.
I found small bug at canRecoverToContextualKeyword
and fixed it.
I added var
keywords to test cases keywords to run it correctly.
I saw changes for parseDeclaration
.
Using canRecoverTo
here is nice.
return lookahead.canRecoverTo( | ||
[], | ||
contextualKeywords: [name], | ||
recoveryPrecedence: precedence |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused parameter precedence
is expected to pass here.
unexpectedBeforeDetail = nil | ||
detail = RawTokenSyntax( | ||
missing: .contextualKeyword, | ||
text: "set", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can use literal now. Thank you.
] | ||
) | ||
|
||
AssertParse( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is reported input at #631.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your patience. This looks great now. I’ve got one minor comment inline, otherwise this looks good to be merged.
DiagnosticSpec(message: "Expected 'set' in modifier"), | ||
DiagnosticSpec(message: "Expected ')' to end modifier"), | ||
// FIXME: It should print `+` as detail of text. | ||
DiagnosticSpec(message: "Unexpected text in variable") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just FYI (not any request to change this in this PR): The unexpected text spans multiple lines here:
+
set
and that’s why we print the generic “Unexpected text” instead of “Unexpected '+ set'” (which would look ugly if there’s a line break in the diagnostic message).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see.
I think that escaped representation is best in such case.
For example,
Unexpected '+\n '
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I got an idea.
In this case, can parser split code into +
and \n set
?
Currently \n
is trailing trivia of +
.
If they are moved to leading trivia of set
,
unexpected text is only +
and \n set
is valid code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you’ve got ideas, I would be very open to discuss them in follow-up PRs. One thing to keep in mind though, is that these lines might be >100 characters long and the unexpected text might span more than 2 lines.
Also, I don’t think we should be showing \n
in the diagnostic message because it’s non-trivial to read and confusing to newcomers who don’t know about the \n
escape sequence.
Sources/SwiftParser/Modifiers.swift
Outdated
mutating func parseAccessLevelModifier() -> RawDeclModifierSyntax { | ||
let (unexpectedBeforeName, name) = expectAny( | ||
[.privateKeyword, .fileprivateKeyword, .internalKeyword, .publicKeyword], | ||
default: .privateKeyword |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn’t really matter too much but I think .internalKeyword
would be the better default here because it’ Swift’s default access level if you don’t specify anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah thats right. I fixed it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Thank you!
@swift-ci Please test |
This PR fix #631.
It can round-trip correctly without crashing.
Not only that, I tried to improve recovering logic here.
For example, the logic recover such wrong inputs intuitively way.
private(get, set, didSet)
See my test cases for detail.
How do you think?