Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 65 additions & 32 deletions Sources/SwiftParser/Attributes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -196,28 +196,35 @@ extension Parser {
atSign = atSign.tokenView.withTokenDiagnostic(tokenDiagnostic: diagnostic, arena: self.arena)
}
let attributeName = self.parseAttributeName()
let attributeNameHasTrailingSpace = attributeName.raw.trailingTriviaByteLength > 0

let shouldParseArgument: Bool
switch argumentMode {
case .required:
shouldParseArgument = true
case .customAttribute:
shouldParseArgument = self.withLookahead { $0.atAttributeOrSpecifierArgument() }
shouldParseArgument = self.withLookahead {
$0.atAttributeOrSpecifierArgument(lastTokenHadSpace: attributeNameHasTrailingSpace, forCustomAttribute: true)
}
case .optional:
shouldParseArgument = self.at(TokenSpec(.leftParen, allowAtStartOfLine: false))
shouldParseArgument = self.withLookahead {
$0.atAttributeOrSpecifierArgument(lastTokenHadSpace: attributeNameHasTrailingSpace, forCustomAttribute: false)
}
case .noArgument:
shouldParseArgument = false
}
if shouldParseArgument {
var (unexpectedBeforeLeftParen, leftParen) = self.expect(TokenSpec(.leftParen, allowAtStartOfLine: false))
if unexpectedBeforeLeftParen == nil
&& (attributeName.raw.trailingTriviaByteLength > 0 || leftParen.leadingTriviaByteLength > 0)
{

// Diagnose spaces between the name and the '('.
if unexpectedBeforeLeftParen == nil && (attributeNameHasTrailingSpace || leftParen.leadingTriviaByteLength > 0) {
let diagnostic = TokenDiagnostic(
self.swiftVersion < .v6 ? .extraneousLeadingWhitespaceWarning : .extraneousLeadingWhitespaceError,
byteOffset: 0
)
leftParen = leftParen.tokenView.withTokenDiagnostic(tokenDiagnostic: diagnostic, arena: self.arena)
}

let unexpectedBeforeArguments: RawUnexpectedNodesSyntax?
let argument: RawAttributeSyntax.Arguments
if let parseMissingArguments, leftParen.presence == .missing {
Expand Down Expand Up @@ -1074,44 +1081,70 @@ extension Parser {
// MARK: Lookahead

extension Parser.Lookahead {
mutating func atAttributeOrSpecifierArgument() -> Bool {
mutating func atAttributeOrSpecifierArgument(
lastTokenHadSpace: Bool,
forCustomAttribute: Bool = false
) -> Bool {
if !self.at(TokenSpec(.leftParen, allowAtStartOfLine: false)) {
return false
}

var lookahead = self.lookahead()
lookahead.skipSingle()

// If we have any keyword, identifier, or token that follows a function
// type's parameter list, this is a parameter list and not an attribute.
// Alternatively, we might have a token that illustrates we're not going to
// get anything following the attribute, which means the parentheses describe
// what follows the attribute.
switch lookahead.currentToken {
case TokenSpec(.arrow),
TokenSpec(.throw),
TokenSpec(.throws),
TokenSpec(.rethrows),
TokenSpec(.rightParen),
TokenSpec(.rightBrace),
TokenSpec(.rightSquare),
TokenSpec(.rightAngle):
return false
case _ where lookahead.at(.keyword(.async)):
return false
case _ where lookahead.at(.keyword(.reasync)):
return false
default:
return true
if self.swiftVersion >= .v6 {
if !lastTokenHadSpace && currentToken.leadingTriviaByteLength == 0 {
return true
}

return withLookahead({
$0.skipSingle()
return $0.at(.atSign) || $0.atStartOfDeclaration()
})
} else {
if !forCustomAttribute {
return true
}
var lookahead = self.lookahead()
lookahead.skipSingle()

// If we have any keyword, identifier, or token that follows a function
// type's parameter list, this is a parameter list and not an attribute.
// Alternatively, we might have a token that illustrates we're not going to
// get anything following the attribute, which means the parentheses describe
// what follows the attribute.
switch lookahead.currentToken {
case TokenSpec(.arrow),
TokenSpec(.throw),
TokenSpec(.throws),
TokenSpec(.rethrows),
TokenSpec(.rightParen),
TokenSpec(.rightBrace),
TokenSpec(.rightSquare),
TokenSpec(.rightAngle):
return false
case _ where lookahead.at(.keyword(.async)):
return false
case _ where lookahead.at(.keyword(.reasync)):
return false
default:
return true
}
}
}

mutating func canParseCustomAttribute() -> Bool {
guard self.canParseType() else {
guard
let numTypeTokens = self.withLookahead({ $0.canParseSimpleType() ? $0.tokensConsumed : nil }),
numTypeTokens >= 1
else {
return false
}
// Check if the last token had trailing white spaces.
for _ in 0..<numTypeTokens - 1 {
self.consumeAnyToken()
}
let hasSpace = self.currentToken.trailingTriviaByteLength > 0
self.consumeAnyToken()

if self.withLookahead({ $0.atAttributeOrSpecifierArgument() }) {
if self.atAttributeOrSpecifierArgument(lastTokenHadSpace: hasSpace, forCustomAttribute: true) {
self.skipSingle()
}

Expand Down
6 changes: 0 additions & 6 deletions Sources/SwiftParser/Expressions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,6 @@ extension TokenConsumer {
case nil:
break
}
if self.at(.atSign) || self.at(.keyword(.inout)) {
var lookahead = self.lookahead()
if lookahead.canParseType() {
return true
}
}
if self.at(.atSign) && self.peek(isAt: .stringQuote) {
// Invalid Objective-C-style string literal
return true
Expand Down
48 changes: 15 additions & 33 deletions Sources/SwiftParser/Lookahead.swift
Original file line number Diff line number Diff line change
Expand Up @@ -163,40 +163,31 @@ extension Parser.Lookahead {
// MARK: Skipping Tokens

extension Parser.Lookahead {
mutating func skipTypeAttribute() {
// These are keywords that we accept as attribute names.
guard self.at(.identifier) || self.at(.keyword(.in), .keyword(.inout)) else {
return
}
/// Skip *any* single attribute. I.e. a type attribute, a decl attribute, or
/// a custom attribute.
mutating func consumeAnyAttribute() {
self.eat(.atSign)

let nameHadSpace = self.currentToken.trailingTriviaByteLength > 0

// Determine which attribute it is.
if let (attr, handle) = self.at(anyIn: TypeAttribute.self) {
// Ok, it is a valid attribute, eat it, and then process it.
self.eat(handle)
switch attr {
case .convention, .isolated:
self.skipSingle()
case .convention, .isolated, .differentiable:
if self.atAttributeOrSpecifierArgument(lastTokenHadSpace: nameHadSpace) {
self.skipSingle()
}
default:
break
}
return
}

if let (_, handle) = self.at(anyIn: Parser.DeclarationAttributeWithSpecialSyntax.self) {
// This is a valid decl attribute so they should have put it on the decl
// instead of the type.
//
// Recover by eating @foo(...)
self.eat(handle)
if self.at(.leftParen) {
var lookahead = self.lookahead()
lookahead.skipSingle()
// If we found '->', or 'throws' after paren, it's likely a parameter
// of function type.
guard lookahead.at(.arrow) || lookahead.at(.keyword(.throws), .keyword(.rethrows), .keyword(.throw)) else {
self.skipSingle()
return
}
if self.atAttributeOrSpecifierArgument(lastTokenHadSpace: nameHadSpace) {
self.skipSingle()
}
return
}
Expand All @@ -212,18 +203,9 @@ extension Parser.Lookahead {
return false
}

while self.consume(if: .atSign) != nil {
// Consume qualified names that may or may not involve generic arguments.
repeat {
self.consume(if: .identifier, .keyword(.rethrows))
// We don't care whether this succeeds or fails to eat generic
// parameters.
_ = self.consumeGenericArguments()
} while self.consume(if: .period) != nil

if self.atAttributeOrSpecifierArgument() {
self.skipSingle()
}
var attributeProgress = LoopProgressCondition()
while self.at(.atSign), self.hasProgressed(&attributeProgress) {
self.consumeAnyAttribute()
}
return true
}
Expand Down
4 changes: 4 additions & 0 deletions Sources/SwiftParser/TopLevel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,10 @@ extension Parser {
return self.parseStatementItem()
} else if self.atStartOfDeclaration(isAtTopLevel: isAtTopLevel, allowInitDecl: allowInitDecl, allowRecovery: true) {
return .decl(self.parseDeclaration())
} else if self.at(.atSign), peek(isAt: .identifier) {
// Force parsing '@<identifier>' as a declaration, as there's no valid
// expression or statement starting with an attribute.
return .decl(self.parseDeclaration())
} else {
return .init(expr: RawMissingExprSyntax(arena: self.arena))
}
Expand Down
54 changes: 16 additions & 38 deletions Sources/SwiftParser/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,7 @@ extension Parser.Lookahead {

case .keyword(.dependsOn):
let canParseDependsOn = self.withLookahead({
let nameHadSpace = $0.currentToken.trailingTriviaByteLength > 0
// Consume 'dependsOn'
$0.consumeAnyToken()

Expand All @@ -798,7 +799,7 @@ extension Parser.Lookahead {
}

// `dependsOn` requires an argument list.
guard $0.atAttributeOrSpecifierArgument() else {
guard $0.atAttributeOrSpecifierArgument(lastTokenHadSpace: nameHadSpace) else {
return false
}

Expand All @@ -817,11 +818,7 @@ extension Parser.Lookahead {
}
}

var attributeProgress = LoopProgressCondition()
while self.at(.atSign), self.hasProgressed(&attributeProgress) {
self.consumeAnyToken()
self.skipTypeAttribute()
}
_ = self.consumeAttributeList()

return true
}
Expand Down Expand Up @@ -1186,7 +1183,9 @@ extension Parser {
// using `nonsisolated` without an argument is allowed in
// an inheritance clause.
// - The '(nonsending)' was omitted.
if !self.at(.leftParen) {
if !self.withLookahead({
$0.atAttributeOrSpecifierArgument(lastTokenHadSpace: nonisolatedKeyword.trailingTriviaByteLength > 0)
}) {
// `nonisolated P<...>` is allowed in an inheritance clause.
if withLookahead({ $0.canParseTypeIdentifier() }) {
let nonisolatedSpecifier = RawNonisolatedTypeSpecifierSyntax(
Expand Down Expand Up @@ -1214,16 +1213,18 @@ extension Parser {
)

return .nonisolatedTypeSpecifier(nonisolatedSpecifier)
}
} else {
let (unexpectedBeforeLeftParen, leftParen) = self.expect(.leftParen)
let (unexpectedBeforeModifier, modifier) = self.expect(.keyword(.nonsending))
let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen)

// Avoid being to greedy about `(` since this modifier should be associated with
// function types, it's possible that the argument is omitted and what follows
// is a function type i.e. `nonisolated () async -> Void`.
if self.at(.leftParen) && !withLookahead({ $0.atAttributeOrSpecifierArgument() }) {
let argument = RawNonisolatedSpecifierArgumentSyntax(
leftParen: missingToken(.leftParen),
nonsendingKeyword: missingToken(.keyword(.nonsending)),
rightParen: missingToken(.rightParen),
unexpectedBeforeLeftParen,
leftParen: leftParen,
unexpectedBeforeModifier,
nonsendingKeyword: modifier,
unexpectedBeforeRightParen,
rightParen: rightParen,
arena: self.arena
)

Expand All @@ -1233,31 +1234,8 @@ extension Parser {
argument: argument,
arena: self.arena
)

return .nonisolatedTypeSpecifier(nonisolatedSpecifier)
}

let (unexpectedBeforeLeftParen, leftParen) = self.expect(.leftParen)
let (unexpectedBeforeModifier, modifier) = self.expect(.keyword(.nonsending))
let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen)

let argument = RawNonisolatedSpecifierArgumentSyntax(
unexpectedBeforeLeftParen,
leftParen: leftParen,
unexpectedBeforeModifier,
nonsendingKeyword: modifier,
unexpectedBeforeRightParen,
rightParen: rightParen,
arena: self.arena
)

let nonisolatedSpecifier = RawNonisolatedTypeSpecifierSyntax(
unexpectedBeforeNonisolatedKeyword,
nonisolatedKeyword: nonisolatedKeyword,
argument: argument,
arena: self.arena
)
return .nonisolatedTypeSpecifier(nonisolatedSpecifier)
}

private mutating func parseSimpleTypeSpecifier(
Expand Down
Loading
Loading