Skip to content

Improve diagnostics of #available and #unavailable #1125

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 6 commits into from
Jan 12, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public let AVAILABILITY_NODES: [Node] = [
element: "AvailabilityArgument"),

Node(name: "AvailabilityArgument",
nameForDiagnostics: "'@available' argument",
nameForDiagnostics: "availability argument",
description: "A single argument to an `@available` argument like `*`, `iOS 10.1`,or `message: \"This has been deprecated\"`.",
kind: "Syntax",
children: [
Expand Down Expand Up @@ -50,7 +50,7 @@ public let AVAILABILITY_NODES: [Node] = [
]),

Node(name: "AvailabilityLabeledArgument",
nameForDiagnostics: "'@available' argument",
nameForDiagnostics: "availability argument",
description: "A argument to an `@available` attribute that consists of a label anda value, e.g. `message: \"This has been deprecated\"`.",
kind: "Syntax",
children: [
Expand Down Expand Up @@ -81,7 +81,7 @@ public let AVAILABILITY_NODES: [Node] = [
]),

Node(name: "AvailabilityVersionRestriction",
nameForDiagnostics: "'@available' argument",
nameForDiagnostics: "availability argument",
description: "An argument to `@available` that restricts the availability on acertain platform to a version, e.g. `iOS 10` or `swift 3.4`.",
kind: "Syntax",
children: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,8 +405,6 @@ public let STMT_NODES: [Node] = [
kind: "Expr"),
Child(name: "Availability",
kind: "AvailabilityCondition"),
Child(name: "Unavailability",
kind: "UnavailabilityCondition"),
Child(name: "MatchingPattern",
kind: "MatchingPatternCondition"),
Child(name: "OptionalBinding",
Expand All @@ -423,13 +421,14 @@ public let STMT_NODES: [Node] = [
]),

Node(name: "AvailabilityCondition",
nameForDiagnostics: "'#availabile' condition",
nameForDiagnostics: "availability condition",
kind: "Syntax",
children: [
Child(name: "PoundAvailableKeyword",
kind: "PoundAvailableToken",
Child(name: "AvailabilityKeyword",
kind: "Token",
tokenChoices: [
"PoundAvailable"
"PoundAvailable",
"PoundUnavailable"
]),
Child(name: "LeftParen",
kind: "LeftParenToken",
Expand Down Expand Up @@ -490,30 +489,6 @@ public let STMT_NODES: [Node] = [
isOptional: true)
]),

Node(name: "UnavailabilityCondition",
nameForDiagnostics: "'#unavailable' condition",
kind: "Syntax",
children: [
Child(name: "PoundUnavailableKeyword",
kind: "PoundUnavailableToken",
tokenChoices: [
"PoundUnavailable"
]),
Child(name: "LeftParen",
kind: "LeftParenToken",
tokenChoices: [
"LeftParen"
]),
Child(name: "AvailabilitySpec",
kind: "AvailabilitySpecList",
collectionElementName: "AvailabilityArgument"),
Child(name: "RightParen",
kind: "RightParenToken",
tokenChoices: [
"RightParen"
])
]),

Node(name: "HasSymbolCondition",
nameForDiagnostics: "'#_hasSymbol' condition",
kind: "Syntax",
Expand Down
14 changes: 5 additions & 9 deletions Sources/SwiftParser/Attributes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,8 @@ extension Parser {

let argument: RawAttributeSyntax.Argument
do {
if self.peek().rawTokenKind == .integerLiteral {
argument = .availability(self.parseAvailabilitySpecList(from: .available))
} else if self.peek().rawTokenKind == .floatingLiteral {
argument = .availability(self.parseAvailabilitySpecList(from: .available))
if self.peek().rawTokenKind == .integerLiteral || self.peek().rawTokenKind == .floatingLiteral {
argument = .availability(self.parseAvailabilitySpecList())
} else {
argument = .availability(self.parseExtendedAvailabilitySpecList())
}
Expand Down Expand Up @@ -181,10 +179,8 @@ extension Parser {

let argument: RawAttributeSyntax.Argument
do {
if self.peek().rawTokenKind == .integerLiteral {
argument = .availability(self.parseAvailabilitySpecList(from: .available))
} else if self.peek().rawTokenKind == .floatingLiteral {
argument = .availability(self.parseAvailabilitySpecList(from: .available))
if self.peek().rawTokenKind == .integerLiteral || self.peek().rawTokenKind == .floatingLiteral {
argument = .availability(self.parseAvailabilitySpecList())
} else {
argument = .availability(self.parseExtendedAvailabilitySpecList())
}
Expand Down Expand Up @@ -641,7 +637,7 @@ extension Parser {
case (.availability, let handle)?:
let ident = self.eat(handle)
let (unexpectedBeforeColon, colon) = self.expect(.colon)
let availability = self.parseAvailabilitySpecList(from: .available)
let availability = self.parseAvailabilitySpecList()
let (unexpectedBeforeSemi, semi) = self.expect(.semicolon)
elements.append(
.availabilityEntry(
Expand Down
58 changes: 29 additions & 29 deletions Sources/SwiftParser/Availability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,37 @@
@_spi(RawSyntax) import SwiftSyntax

extension Parser {
enum AvailabilitySpecSource {
case available
case unavailable
case macro
}

/// Parse a list of availability arguments.
///
/// Grammar
/// =======
///
/// availability-arguments → availability-argument | availability-argument , availability-arguments
mutating func parseAvailabilitySpecList(
from source: AvailabilitySpecSource
) -> RawAvailabilitySpecListSyntax {
mutating func parseAvailabilitySpecList() -> RawAvailabilitySpecListSyntax {
var elements = [RawAvailabilityArgumentSyntax]()
do {
var keepGoing: RawTokenSyntax? = nil
var availablityArgumentProgress = LoopProgressCondition()
repeat {
let entry: RawAvailabilityArgumentSyntax.Entry
switch source {
case .available where self.at(.identifier),
.unavailable where self.at(.identifier):
if self.at(.identifier) {
entry = .availabilityVersionRestriction(self.parseAvailabilityMacro())
default:
} else {
entry = self.parseAvailabilitySpec()
}

let unexpectedBeforeKeepGoing: RawUnexpectedNodesSyntax?
keepGoing = self.consume(if: .comma)
if keepGoing == nil, let orOperator = self.consumeIfContextualPunctuator("||") {
unexpectedBeforeKeepGoing = RawUnexpectedNodesSyntax([orOperator], arena: self.arena)
keepGoing = missingToken(.comma)
} else {
unexpectedBeforeKeepGoing = nil
}
elements.append(
RawAvailabilityArgumentSyntax(
entry: entry,
unexpectedBeforeKeepGoing,
trailingComma: keepGoing,
arena: self.arena
)
Expand Down Expand Up @@ -256,9 +254,10 @@ extension Parser {
/// platform-name → tvOS
mutating func parsePlatformVersionConstraintSpec() -> RawAvailabilityVersionRestrictionSyntax {
// Register the platform name as a keyword token.
let plaform = self.consumeAnyToken()
let (unexpectedBeforePlatform, plaform) = self.expect(.identifier)
let version = self.parseVersionTuple()
return RawAvailabilityVersionRestrictionSyntax(
unexpectedBeforePlatform,
platform: plaform,
version: version,
arena: self.arena
Expand Down Expand Up @@ -301,28 +300,29 @@ extension Parser {
/// platform-version → decimal-digits '.' decimal-digits
/// platform-version → decimal-digits '.' decimal-digits '.' decimal-digits
mutating func parseVersionTuple() -> RawVersionTupleSyntax {
if let major = self.consume(if: .integerLiteral) {
return RawVersionTupleSyntax(
majorMinor: major,
patchPeriod: nil,
patchVersion: nil,
arena: self.arena
)
}

let majorMinor = self.consumeAnyToken()
let period = self.consume(if: .period)

let (unexpectedBeforeMajorMinor, majorMinor) = self.expectAny([.integerLiteral, .floatingLiteral], default: .integerLiteral)
let patchPeriod: RawTokenSyntax?
let unexpectedBeforePatch: RawUnexpectedNodesSyntax?
let patch: RawTokenSyntax?
if period != nil {
patch = self.consumeAnyToken()
if majorMinor.tokenKind == .floatingLiteral {
patchPeriod = self.consume(if: .period)
if patchPeriod != nil {
(unexpectedBeforePatch, patch) = self.expect(.integerLiteral)
} else {
unexpectedBeforePatch = nil
patch = nil
}
} else {
patchPeriod = nil
unexpectedBeforePatch = nil
patch = nil
}

return RawVersionTupleSyntax(
unexpectedBeforeMajorMinor,
majorMinor: majorMinor,
patchPeriod: period,
patchPeriod: patchPeriod,
unexpectedBeforePatch,
patchVersion: patch,
arena: self.arena
)
Expand Down
12 changes: 10 additions & 2 deletions Sources/SwiftParser/Expressions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1209,12 +1209,20 @@ extension Parser {
arena: self.arena
)
)

case (.pound, _)?:
return RawExprSyntax(
self.parseMacroExpansionExpr(pattern: pattern, flavor: flavor)
)

case (.poundAvailableKeyword, _)?, (.poundUnavailableKeyword, _)?:
let poundAvailable = self.parsePoundAvailableConditionElement()
return RawExprSyntax(
RawIdentifierExprSyntax(
RawUnexpectedNodesSyntax([poundAvailable], arena: self.arena),
identifier: missingToken(.identifier),
declNameArguments: nil,
arena: self.arena
)
)
case (.leftBrace, _)?: // expr-closure
return RawExprSyntax(self.parseClosureExpression())
case (.period, let handle)?: // .foo
Expand Down
6 changes: 6 additions & 0 deletions Sources/SwiftParser/RawTokenKindSubset.swift
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,8 @@ enum PrimaryExpressionStart: RawTokenKindSubset {
case nilKeyword
case period
case pound
case poundAvailableKeyword // For recovery
case poundUnavailableKeyword // For recovery
case regexLiteral
case selfKeyword
case stringLiteral
Expand All @@ -716,6 +718,8 @@ enum PrimaryExpressionStart: RawTokenKindSubset {
case RawTokenKindMatch(.nil): self = .nilKeyword
case RawTokenKindMatch(.period): self = .period
case RawTokenKindMatch(.pound): self = .pound
case RawTokenKindMatch(.poundAvailableKeyword): self = .poundAvailableKeyword
case RawTokenKindMatch(.poundUnavailableKeyword): self = .poundUnavailableKeyword
case RawTokenKindMatch(.regexLiteral): self = .regexLiteral
case RawTokenKindMatch(.self): self = .selfKeyword
case RawTokenKindMatch(.stringLiteral): self = .stringLiteral
Expand All @@ -742,6 +746,8 @@ enum PrimaryExpressionStart: RawTokenKindSubset {
case .nilKeyword: return .keyword(.nil)
case .period: return .period
case .pound: return .pound
case .poundAvailableKeyword: return .poundAvailableKeyword
case .poundUnavailableKeyword: return .poundUnavailableKeyword
case .regexLiteral: return .regexLiteral
case .selfKeyword: return .keyword(.self)
case .stringLiteral: return .stringLiteral
Expand Down
48 changes: 19 additions & 29 deletions Sources/SwiftParser/Statements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -212,10 +212,18 @@ extension Parser {
var loopProgress = LoopProgressCondition()
repeat {
let condition = self.parseConditionElement()
let unexpectedBeforeKeepGoing: RawUnexpectedNodesSyntax?
keepGoing = self.consume(if: .comma)
if keepGoing == nil, let andOperator = self.consumeIfContextualPunctuator("&&") {
unexpectedBeforeKeepGoing = RawUnexpectedNodesSyntax([andOperator], arena: self.arena)
keepGoing = missingToken(.comma)
} else {
unexpectedBeforeKeepGoing = nil
}
elements.append(
RawConditionElementSyntax(
condition: condition,
unexpectedBeforeKeepGoing,
trailingComma: keepGoing,
arena: self.arena
)
Expand Down Expand Up @@ -348,39 +356,21 @@ extension Parser {
@_spi(RawSyntax)
public mutating func parsePoundAvailableConditionElement() -> RawConditionElementSyntax.Condition {
assert(self.at(any: [.poundAvailableKeyword, .poundUnavailableKeyword]))
let kind: AvailabilitySpecSource = self.at(.poundAvailableKeyword) ? .available : .unavailable
let keyword = self.consumeAnyToken()
let (unexpectedBeforeLParen, lparen) = self.expect(.leftParen)
let spec = self.parseAvailabilitySpecList(from: kind)
let spec = self.parseAvailabilitySpecList()
let (unexpectedBeforeRParen, rparen) = self.expect(.rightParen)
switch kind {
case .available:
return .availability(
RawAvailabilityConditionSyntax(
poundAvailableKeyword: keyword,
unexpectedBeforeLParen,
leftParen: lparen,
availabilitySpec: spec,
unexpectedBeforeRParen,
rightParen: rparen,
arena: self.arena
)
)
case .unavailable:
return .unavailability(
RawUnavailabilityConditionSyntax(
poundUnavailableKeyword: keyword,
unexpectedBeforeLParen,
leftParen: lparen,
availabilitySpec: spec,
unexpectedBeforeRParen,
rightParen: rparen,
arena: self.arena
)
return .availability(
RawAvailabilityConditionSyntax(
availabilityKeyword: keyword,
unexpectedBeforeLParen,
leftParen: lparen,
availabilitySpec: spec,
unexpectedBeforeRParen,
rightParen: rparen,
arena: self.arena
)
case .macro:
fatalError("Macros are not allowed in this position!")
}
)
}

/// Parse a `#_hasSymbol` condition.
Expand Down
Loading