-
Notifications
You must be signed in to change notification settings - Fork 440
DNM: Fix up effect specifiers in function signatures #889
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1440,26 +1440,18 @@ extension Parser { | |
arena: self.arena) | ||
} | ||
|
||
/// If a `throws` keyword appears right in front of the `arrow`, it is returned as `misplacedThrowsKeyword` so it can be synthesized in front of the arrow. | ||
@_spi(RawSyntax) | ||
public mutating func parseFunctionReturnClause() -> (returnClause: RawReturnClauseSyntax, misplacedThrowsKeyword: RawTokenSyntax?) { | ||
let (unexpectedBeforeArrow, arrow) = self.expect(.arrow) | ||
var misplacedThrowsKeyword: RawTokenSyntax? = nil | ||
let unexpectedBeforeReturnType: RawUnexpectedNodesSyntax? | ||
if let throwsKeyword = self.consume(ifAny: [.rethrowsKeyword, .throwsKeyword]) { | ||
misplacedThrowsKeyword = throwsKeyword | ||
unexpectedBeforeReturnType = RawUnexpectedNodesSyntax(elements: [RawSyntax(throwsKeyword)], arena: self.arena) | ||
} else { | ||
unexpectedBeforeReturnType = nil | ||
} | ||
/// If effect specifiers appear in front of the `arrow` or after the return type, they are returned so that they can be synthesized in front of the arrow. | ||
mutating func parseReturnClause(_ handle: RecoveryConsumptionHandle) -> (returnClause: RawReturnClauseSyntax, misplacedSpecifiers: [SpecifierWithToken]) { | ||
let (unexpectedBeforeArrow, arrow) = self.eat(handle) | ||
let misplacedSpecifiers = parseAllEffectsSpecifiers() | ||
let result = self.parseResultType() | ||
let returnClause = RawReturnClauseSyntax( | ||
unexpectedBeforeArrow, | ||
arrow: arrow, | ||
unexpectedBeforeReturnType, | ||
RawUnexpectedNodesSyntax(misplacedSpecifiers, arena: self.arena), | ||
returnType: result, | ||
arena: self.arena) | ||
return (returnClause, misplacedThrowsKeyword) | ||
return (returnClause, misplacedSpecifiers) | ||
} | ||
} | ||
|
||
|
@@ -1544,33 +1536,27 @@ extension Parser { | |
public mutating func parseFunctionSignature() -> RawFunctionSignatureSyntax { | ||
let input = self.parseParameterClause(for: .functionParameters) | ||
|
||
let async: RawTokenSyntax? | ||
if let asyncTok = self.consumeIfContextualKeyword("async") { | ||
async = asyncTok | ||
} else if let reasync = self.consumeIfContextualKeyword("reasync") { | ||
async = reasync | ||
} else { | ||
async = nil | ||
} | ||
|
||
var throwsKeyword = self.consume(ifAny: [.throwsKeyword, .rethrowsKeyword]) | ||
var (asyncOrReasync, throwsOrRethrows, specifiersAfterThrows) = parseEffectsSpecifiers(atDecl: true) | ||
|
||
let output: RawReturnClauseSyntax? | ||
if self.at(.arrow) { | ||
let result = self.parseFunctionReturnClause() | ||
output = result.returnClause | ||
if let misplacedThrowsKeyword = result.misplacedThrowsKeyword, throwsKeyword == nil { | ||
throwsKeyword = RawTokenSyntax(missing: misplacedThrowsKeyword.tokenKind, arena: self.arena) | ||
} | ||
if let handle = self.canRecoverTo(.arrow) { | ||
let (returnClause, misplacedSpecifiers) = parseReturnClause(handle) | ||
addMissingEffectsSpecifiers(atDecl: true, from: misplacedSpecifiers, asyncOrReasync: &asyncOrReasync, throwsOrRethrows: &throwsOrRethrows) | ||
output = returnClause | ||
} else { | ||
output = nil | ||
} | ||
|
||
let specifiersAfterReturn = parseAllEffectsSpecifiers() | ||
addMissingEffectsSpecifiers(atDecl: true, from: specifiersAfterReturn, asyncOrReasync: &asyncOrReasync, throwsOrRethrows: &throwsOrRethrows) | ||
|
||
return RawFunctionSignatureSyntax( | ||
input: input, | ||
asyncOrReasyncKeyword: async, | ||
throwsOrRethrowsKeyword: throwsKeyword, | ||
asyncOrReasyncKeyword: asyncOrReasync, | ||
throwsOrRethrowsKeyword: throwsOrRethrows, | ||
RawUnexpectedNodesSyntax(specifiersAfterThrows, arena: self.arena), | ||
output: output, | ||
RawUnexpectedNodesSyntax(specifiersAfterReturn, arena: self.arena), | ||
arena: self.arena) | ||
} | ||
} | ||
|
@@ -1601,9 +1587,11 @@ extension Parser { | |
|
||
let indices = self.parseParameterClause(for: .indices) | ||
|
||
let unexpectedBeforeArrow = RawUnexpectedNodesSyntax(parseAllEffectsSpecifiers(), arena: self.arena) | ||
|
||
let result: RawReturnClauseSyntax | ||
if self.at(.arrow) { | ||
result = self.parseFunctionReturnClause().returnClause | ||
if let handle = self.canRecoverTo(.arrow) { | ||
result = self.parseReturnClause(handle).returnClause | ||
} else { | ||
result = RawReturnClauseSyntax( | ||
arrow: RawTokenSyntax(missing: .arrow, arena: self.arena), | ||
|
@@ -1635,6 +1623,7 @@ extension Parser { | |
subscriptKeyword: subscriptKeyword, | ||
genericParameterClause: genericParameterClause, | ||
indices: indices, | ||
unexpectedBeforeArrow, | ||
result: result, | ||
genericWhereClause: genericWhereClause, | ||
accessor: accessor, | ||
|
@@ -1767,41 +1756,6 @@ extension Parser { | |
attributes: attrs, modifier: modifier, kind: kind, token: introducer) | ||
} | ||
|
||
@_spi(RawSyntax) | ||
public mutating func parseEffectsSpecifier() -> RawTokenSyntax? { | ||
// 'async' | ||
if let async = self.consumeIfContextualKeyword("async") { | ||
return async | ||
} | ||
|
||
// 'reasync' | ||
if let reasync = self.consumeIfContextualKeyword("reasync") { | ||
return reasync | ||
} | ||
|
||
// 'throws'/'rethrows' | ||
if let throwsRethrows = self.consume(ifAny: [.throwsKeyword, .rethrowsKeyword]) { | ||
return throwsRethrows | ||
} | ||
|
||
// diagnose 'throw'/'try'. | ||
if let throwTry = self.consume(ifAny: [.throwKeyword, .tryKeyword], where: { !$0.isAtStartOfLine }) { | ||
return throwTry | ||
} | ||
|
||
return nil | ||
} | ||
|
||
@_spi(RawSyntax) | ||
public mutating func parseEffectsSpecifiers() -> [RawTokenSyntax] { | ||
var specifiers = [RawTokenSyntax]() | ||
var loopProgress = LoopProgressCondition() | ||
while let specifier = self.parseEffectsSpecifier(), loopProgress.evaluate(currentToken) { | ||
specifiers.append(specifier) | ||
} | ||
return specifiers | ||
} | ||
|
||
/// Parse the body of a variable declaration. This can include explicit | ||
/// getters, setters, and observers, or the body of a computed property. | ||
/// | ||
|
@@ -1884,14 +1838,15 @@ extension Parser { | |
|
||
// Next, parse effects specifiers. While it's only valid to have them | ||
// on 'get' accessors, we also emit diagnostics if they show up on others. | ||
let asyncKeyword: RawTokenSyntax? | ||
let throwsKeyword: RawTokenSyntax? | ||
if self.at(anyIn: EffectsSpecifier.self) != nil { | ||
asyncKeyword = self.parseEffectsSpecifier() | ||
throwsKeyword = self.parseEffectsSpecifier() | ||
var asyncOrReasync: RawTokenSyntax? = nil | ||
var throwsOrRethrows: RawTokenSyntax? = nil | ||
ahoppen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let unexpectedAfterThrows: [SpecifierWithToken] | ||
if introducer.kind == .get { | ||
(asyncOrReasync, throwsOrRethrows, unexpectedAfterThrows) = parseEffectsSpecifiers(atDecl: true) | ||
} else { | ||
asyncKeyword = nil | ||
throwsKeyword = nil | ||
asyncOrReasync = nil | ||
throwsOrRethrows = nil | ||
unexpectedAfterThrows = parseAllEffectsSpecifiers() | ||
} | ||
|
||
let body = self.parseOptionalCodeBlock() | ||
|
@@ -1901,8 +1856,9 @@ extension Parser { | |
modifier: introducer.modifier, | ||
accessorKind: introducer.token, | ||
parameter: parameter, | ||
asyncKeyword: asyncKeyword, | ||
throwsKeyword: throwsKeyword, | ||
asyncKeyword: asyncOrReasync, | ||
throwsKeyword: throwsOrRethrows, | ||
RawUnexpectedNodesSyntax(unexpectedAfterThrows, arena: self.arena), | ||
body: body, | ||
arena: self.arena)) | ||
} | ||
|
@@ -2233,3 +2189,59 @@ extension Parser { | |
} | ||
} | ||
} | ||
|
||
extension Parser { | ||
mutating func parseAllEffectsSpecifiers() -> [SpecifierWithToken] { | ||
var specifiers = [SpecifierWithToken]() | ||
var loopProgress = LoopProgressCondition() | ||
while let (specifier, handle) = self.canRecoverTo(anyIn: EffectsSpecifier.self), loopProgress.evaluate(currentToken) { | ||
let (unexpected, token) = self.eatKeepUnexpected(handle) | ||
specifiers.append(contentsOf: unexpected.map { SpecifierWithToken(specifier: nil, token: $0) }) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don’t really like how we are considering unexpected tokens as What do you think about changing struct ParsedEffectsSpecifier {
let specifier: EffectsSpecifier?
let unexpected: RawUnexpectedNodesSyntax?
let token: RawTokenSyntax
} That way you also don’t need to modify There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Much better name, but I'm not sure I understand the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can add struct ParsedEffectsSpecifier {
let specifier: EffectsSpecifier?
let unexpected: [RawTokenSyntax]
let token: RawTokenSyntax
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, makes sense. |
||
specifiers.append(SpecifierWithToken(specifier: specifier, token: token)) | ||
} | ||
return specifiers | ||
} | ||
|
||
func addMissingEffectsSpecifiers(atDecl: Bool, from unexpectedSpecifiers: [SpecifierWithToken], asyncOrReasync: inout RawTokenSyntax?, throwsOrRethrows: inout RawTokenSyntax?) { | ||
for unexpected in unexpectedSpecifiers { | ||
guard asyncOrReasync == nil || throwsOrRethrows == nil else { | ||
break | ||
} | ||
|
||
if asyncOrReasync == nil, unexpected.specifier == .asyncContextualKeyword || (atDecl && unexpected.specifier == .reasyncContextualKeyword) { | ||
asyncOrReasync = RawTokenSyntax(missing: unexpected.token.tokenKind, text: unexpected.token.tokenText, arena: self.arena) | ||
} | ||
|
||
if throwsOrRethrows == nil, unexpected.specifier == .throwKeyword || (atDecl && unexpected.specifier == .rethrowsKeyword) { | ||
throwsOrRethrows = RawTokenSyntax(missing: unexpected.token.tokenKind, arena: self.arena) | ||
} | ||
} | ||
} | ||
|
||
mutating func parseEffectsSpecifiers(atDecl: Bool) -> (asyncOrReasync: RawTokenSyntax?, | ||
throwsOrRethrows: RawTokenSyntax?, | ||
specifiersAfterThrows: [SpecifierWithToken]){ | ||
var initialSpecifiers = parseAllEffectsSpecifiers() | ||
|
||
var asyncOrReasync: RawTokenSyntax? | ||
var throwsOrRethrows: RawTokenSyntax? | ||
if !initialSpecifiers.isEmpty { | ||
let first = initialSpecifiers.removeFirst() | ||
ahoppen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if first.specifier == .asyncContextualKeyword || atDecl && first.specifier == .reasyncContextualKeyword { | ||
asyncOrReasync = first.token | ||
|
||
if !initialSpecifiers.isEmpty { | ||
let second = initialSpecifiers.removeFirst() | ||
ahoppen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if second.specifier == .throwsKeyword || atDecl && second.specifier == .rethrowsKeyword { | ||
throwsOrRethrows = second.token | ||
} | ||
} | ||
} else if first.specifier == .throwsKeyword || atDecl && first.specifier == .rethrowsKeyword { | ||
throwsOrRethrows = first.token | ||
} | ||
addMissingEffectsSpecifiers(atDecl: atDecl, from: initialSpecifiers, asyncOrReasync: &asyncOrReasync, throwsOrRethrows: &throwsOrRethrows) | ||
} | ||
|
||
return (asyncOrReasync, throwsOrRethrows, initialSpecifiers) | ||
} | ||
} |
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 are dropping the effect specifiers here, right?
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.
Weird that I can't see this in the review. But no, they're added to the return clause itself. They're only returned to be able to make
async/throws
missing when necessary. This is one of the many reasons I'm not a huge fan of this patch (though this particular returning of unexpected nodes was already here).It really seems like we should just recover + place tokens in unexpected + have ASTGen handle these diagnostics. Though one issue there is that they wouldn't be parsed as contextual keywords if we just did regular recovery vs looked for the effects specifically.
@unknown
is sort of an example of this, though it currently allows any attribute as the@unknown
attribute and parses the rest as unexpected (ie. just recovers to the case/default).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, never mind. I missed that
throws
also gets added to the unexpected tokens of thereturnClause
.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.
Yeah, but it's super easy to miss. Hence my problem with doing it this way :)