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
19 changes: 19 additions & 0 deletions CodeGeneration/Sources/SyntaxSupport/Child.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ public class Child {
/// Whether this child is optional and can be `nil`.
public let isOptional: Bool

/// The experimental feature the child represents, or `nil` if this isn't
/// for an experimental feature.
public let experimentalFeature: ExperimentalFeature?

/// A name of this child that can be shown in diagnostics.
///
/// This is used to e.g. describe the child if all of its tokens are missing in the source file.
Expand Down Expand Up @@ -110,6 +114,10 @@ public class Child {
/// The first line of the child's documentation
public let documentationAbstract: String

/// If `true`, this is for an experimental language feature, and any public
/// API generated should be SPI.
public var isExperimental: Bool { experimentalFeature != nil }

public var syntaxNodeKind: SyntaxNodeKind {
switch kind {
case .node(kind: let kind):
Expand Down Expand Up @@ -218,6 +226,15 @@ public class Child {
}
}

/// The attributes that should be printed on any API for the this child.
///
/// This is typically used to mark APIs as SPI when the keyword is part of
/// an experimental language feature.
public var apiAttributes: AttributeListSyntax {
guard isExperimental else { return "" }
return AttributeListSyntax("@_spi(ExperimentalLanguageFeatures)").with(\.trailingTrivia, .newline)
}

/// If a classification is passed, it specifies the color identifiers in
/// that subtree should inherit for syntax coloring. Must be a member of
/// ``SyntaxClassification``.
Expand All @@ -227,6 +244,7 @@ public class Child {
name: String,
deprecatedName: String? = nil,
kind: ChildKind,
experimentalFeature: ExperimentalFeature? = nil,
nameForDiagnostics: String? = nil,
documentation: String? = nil,
isOptional: Bool = false
Expand All @@ -236,6 +254,7 @@ public class Child {
self.name = name
self.deprecatedName = deprecatedName
self.kind = kind
self.experimentalFeature = experimentalFeature
self.nameForDiagnostics = nameForDiagnostics
self.documentationSummary = SwiftSyntax.Trivia.docCommentTrivia(from: documentation)
self.documentationAbstract = String(documentation?.split(whereSeparator: \.isNewline).first ?? "")
Expand Down
49 changes: 49 additions & 0 deletions CodeGeneration/Sources/SyntaxSupport/CommonNodes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,34 @@ public let COMMON_NODES: [Node] = [
]
),

Node(
kind: .thrownTypeClause,
Copy link
Member

Choose a reason for hiding this comment

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

You should mark this node as experimental.

base: .syntax,
Copy link
Member

Choose a reason for hiding this comment

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

Could you add a doc comments for this node and its children?

nameForDiagnostics: "thrown type clause",
documentation: "The specific error type that a function can throw.",
traits: [
"Parenthesized"
],
children: [
Child(
name: "leftParen",
kind: .token(choices: [.token(.leftParen)]),
documentation: "The '(' to open the thrown type clause."
),
Child(
name: "type",
kind: .node(kind: .type),
nameForDiagnostics: "thrown type",
documentation: "The thrown error type."
),
Child(
name: "rightParen",
kind: .token(choices: [.token(.rightParen)]),
documentation: "The ')' to closure the thrown type clause."
),
]
),

Node(
kind: .accessorEffectSpecifiers,
base: .syntax,
Expand All @@ -99,6 +127,13 @@ public let COMMON_NODES: [Node] = [
documentation: "The `throws` keyword.",
isOptional: true
),
Child(
name: "thrownError",
kind: .node(kind: .thrownTypeClause),
experimentalFeature: .typedThrows,
documentation: "The specific error type thrown by this accessor.",
isOptional: true
),
]
),

Expand All @@ -122,6 +157,13 @@ public let COMMON_NODES: [Node] = [
documentation: "The `throws` or `rethrows` keyword.",
isOptional: true
),
Child(
name: "thrownError",
kind: .node(kind: .thrownTypeClause),
experimentalFeature: .typedThrows,
documentation: "The specific error type thrown by this function.",
isOptional: true
),
]
),

Expand Down Expand Up @@ -324,6 +366,13 @@ public let COMMON_NODES: [Node] = [
kind: .token(choices: [.keyword(.throws)]),
isOptional: true
),
Child(
name: "thrownError",
kind: .node(kind: .thrownTypeClause),
experimentalFeature: .typedThrows,
documentation: "The specific error type thrown by this function type.",
isOptional: true
),
]
),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import SwiftSyntax
public enum ExperimentalFeature: String, CaseIterable {
case referenceBindings
case thenStatements
case typedThrows

/// The name of the feature, which is used in the doc comment.
public var featureName: String {
Expand All @@ -23,6 +24,8 @@ public enum ExperimentalFeature: String, CaseIterable {
return "reference bindings"
case .thenStatements:
return "'then' statements"
case .typedThrows:
return "typed throws"
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ public enum SyntaxNodeKind: String, CaseIterable {
case switchExpr
case ternaryExpr
case thenStmt
case thrownTypeClause
case throwStmt
case tryExpr
case tupleExpr
Expand Down
10 changes: 9 additions & 1 deletion CodeGeneration/Sources/SyntaxSupport/Traits.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,15 @@ public let TRAITS: [Trait] = [
Child(name: "asyncSpecifier", kind: .token(choices: [.keyword(.async), .keyword(.reasync)]), isOptional: true),
Child(name: "unexpectedBetweenAsyncSpecifierAndThrowsSpecifier", kind: .node(kind: .unexpectedNodes), isOptional: true),
Child(name: "throwsSpecifier", kind: .token(choices: [.keyword(.throws), .keyword(.rethrows)]), isOptional: true),
Child(name: "unexpectedAfterThrowsSpecifier", kind: .node(kind: .unexpectedNodes), isOptional: true),
Child(name: "unexpectedBetweenThrowsSpecifierAndThrownError", kind: .node(kind: .unexpectedNodes), isOptional: true),
Child(
name: "thrownError",
kind: .node(kind: .thrownTypeClause),
experimentalFeature: .typedThrows,
documentation: "The specific thrown error type.",
isOptional: true
),
Child(name: "unexpectedAfterThrownError", kind: .node(kind: .unexpectedNodes), isOptional: true),
]
),
Trait(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func syntaxNode(nodesStartingWith: [Character]) -> SourceFileSyntax {
try! VariableDeclSyntax(
"""
\(child.documentation)
public var \(child.varOrCaseName.backtickedIfNeeded): \(type)
\(child.apiAttributes)public var \(child.varOrCaseName.backtickedIfNeeded): \(type)
"""
) {
AccessorDeclSyntax(accessorSpecifier: .keyword(.get)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ let syntaxTraitsFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
DeclSyntax(
"""
\(child.documentation)
var \(child.varOrCaseName): \(child.syntaxNodeKind.syntaxType)\(questionMark) { get set }
\(child.apiAttributes)var \(child.varOrCaseName): \(child.syntaxNodeKind.syntaxType)\(questionMark) { get set }
"""
)
}
Expand Down
3 changes: 3 additions & 0 deletions Release Notes/510.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@

## API-Incompatible Changes

- Effect specifiers:
- Description: The `unexpectedAfterThrowsSpecifier` node of the various effect specifiers has been removed.
- Pull request: https://github.com/apple/swift-syntax/pull/2219

## Template

Expand Down
66 changes: 55 additions & 11 deletions Sources/SwiftParser/Specifiers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,13 @@ public enum EffectSpecifier: TokenSpecSet {
case .throwsSpecifier(let underlyingKind): return underlyingKind.spec
}
}

var isThrowsSpecifier: Bool {
switch self {
case .asyncSpecifier: return false
case .throwsSpecifier: return true
}
}
}

// MARK: - EffectSpecifiersTrait
Expand All @@ -153,10 +160,12 @@ protocol RawMisplacedEffectSpecifiersTrait {

var asyncSpecifier: RawTokenSyntax? { get }
var throwsSpecifier: RawTokenSyntax? { get }
var thrownError: RawThrownTypeClauseSyntax? { get }

init(
asyncSpecifier: RawTokenSyntax?,
throwsSpecifier: RawTokenSyntax?,
thrownError: RawThrownTypeClauseSyntax?,
arena: __shared SyntaxArena
)

Expand All @@ -166,14 +175,17 @@ protocol RawMisplacedEffectSpecifiersTrait {
protocol RawEffectSpecifiersTrait: RawMisplacedEffectSpecifiersTrait {
var unexpectedBeforeAsyncSpecifier: RawUnexpectedNodesSyntax? { get }
var unexpectedBetweenAsyncSpecifierAndThrowsSpecifier: RawUnexpectedNodesSyntax? { get }
var unexpectedAfterThrowsSpecifier: RawUnexpectedNodesSyntax? { get }

var unexpectedBetweenThrowsSpecifierAndThrownError: RawUnexpectedNodesSyntax? { get }
var thrownError: RawThrownTypeClauseSyntax? { get }
var unexpectedAfterThrownError: RawUnexpectedNodesSyntax? { get }
init(
_ unexpectedBeforeAsyncSpecifier: RawUnexpectedNodesSyntax?,
asyncSpecifier: RawTokenSyntax?,
_ unexpectedBetweenAsyncSpecifierAndThrowsSpecifier: RawUnexpectedNodesSyntax?,
throwsSpecifier: RawTokenSyntax?,
_ unexpectedAfterThrowsSpecifier: RawUnexpectedNodesSyntax?,
_ unexpectedBetweenThrowsSpecifierAndThrownError: RawUnexpectedNodesSyntax?,
thrownError: RawThrownTypeClauseSyntax?,
_ unexpectedAfterThrownError: RawUnexpectedNodesSyntax?,
arena: __shared SyntaxArena
)
}
Expand All @@ -182,6 +194,7 @@ extension RawEffectSpecifiersTrait {
init(
asyncSpecifier: RawTokenSyntax?,
throwsSpecifier: RawTokenSyntax?,
thrownError: RawThrownTypeClauseSyntax?,
arena: __shared SyntaxArena
) {
self.init(
Expand All @@ -190,6 +203,8 @@ extension RawEffectSpecifiersTrait {
nil,
throwsSpecifier: throwsSpecifier,
nil,
thrownError: thrownError,
nil,
arena: arena
)
}
Expand All @@ -200,7 +215,9 @@ extension RawEffectSpecifiersTrait {
asyncSpecifier: self.asyncSpecifier ?? misplacedAsyncKeyword,
self.unexpectedBetweenAsyncSpecifierAndThrowsSpecifier,
throwsSpecifier: self.throwsSpecifier ?? misplacedThrowsKeyword,
self.unexpectedAfterThrowsSpecifier,
self.unexpectedBetweenThrowsSpecifierAndThrownError,
thrownError: thrownError,
self.unexpectedAfterThrownError,
arena: arena
)
}
Expand Down Expand Up @@ -521,10 +538,12 @@ extension RawDeinitializerEffectSpecifiersSyntax: RawMisplacedEffectSpecifiersTr
}

var throwsSpecifier: RawTokenSyntax? { nil }
var thrownError: RawThrownTypeClauseSyntax? { nil }

init(
asyncSpecifier: RawTokenSyntax?,
throwsSpecifier: RawTokenSyntax?,
thrownError: RawThrownTypeClauseSyntax?,
arena: __shared SwiftSyntax.SyntaxArena
) {
// `throwsSpecifier` should never be present because `parseMisplacedEffectSpecifiers()` only creates missing tokens
Expand Down Expand Up @@ -577,12 +596,28 @@ extension TokenConsumer {
// MARK: - Parsing effect specifiers

extension Parser {
private mutating func parseThrownTypeClause() -> RawThrownTypeClauseSyntax {
let (unexpectedBeforeLeftParen, leftParen) = self.expect(.leftParen)
let type = self.parseType()
let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen)
return RawThrownTypeClauseSyntax(
unexpectedBeforeLeftParen,
leftParen: leftParen,
type: type,
unexpectedBeforeRightParen,
rightParen: rightParen,
arena: self.arena
)
}

private mutating func parseEffectSpecifiers<S: RawEffectSpecifiersTrait>(_: S.Type) -> S? {
var unexpectedBeforeAsync: [RawSyntax] = []
var asyncKeyword: RawTokenSyntax? = nil
var unexpectedBeforeThrows: [RawSyntax] = []
var throwsKeyword: RawTokenSyntax?
var unexpectedAfterThrows: [RawSyntax] = []
var thrownError: RawThrownTypeClauseSyntax?
var unexpectedAfterThrownError: [RawSyntax] = []

while let misspelledAsync = self.consume(ifAnyIn: S.MisspelledAsyncTokenKinds.self) {
unexpectedBeforeAsync.append(RawSyntax(misspelledAsync))
if asyncKeyword == nil {
Expand Down Expand Up @@ -617,26 +652,32 @@ extension Parser {
let (unexpected, throwsKw) = self.eat(handle)
unexpectedBeforeThrows.append(contentsOf: unexpected?.elements ?? [])
throwsKeyword = throwsKw

if self.at(.leftParen) && experimentalFeatures.contains(.typedThrows) {
thrownError = parseThrownTypeClause()
}
}

var unexpectedAfterThrowsLoopProgress = LoopProgressCondition()
while self.hasProgressed(&unexpectedAfterThrowsLoopProgress) {
var unexpectedAfterThrownErrorLoopProgress = LoopProgressCondition()
while self.hasProgressed(&unexpectedAfterThrownErrorLoopProgress) {
if let (_, handle, _) = self.at(anyIn: S.MisspelledAsyncTokenKinds.self, or: S.CorrectAsyncTokenKinds.self) {
let misspelledAsync = self.eat(handle)
unexpectedAfterThrows.append(RawSyntax(misspelledAsync))
unexpectedAfterThrownError.append(RawSyntax(misspelledAsync))
if asyncKeyword == nil {
// Handle `async` after `throws`
asyncKeyword = missingToken(.keyword(.async))
}
} else if let (_, handle, _) = self.at(anyIn: S.MisspelledThrowsTokenKinds.self, or: S.CorrectThrowsTokenKinds.self) {
let misspelledThrows = self.eat(handle)
unexpectedAfterThrows.append(RawSyntax(misspelledThrows))
unexpectedAfterThrownError.append(RawSyntax(misspelledThrows))
} else {
break
}
}

if unexpectedBeforeAsync.isEmpty && asyncKeyword == nil && unexpectedBeforeThrows.isEmpty && throwsKeyword == nil && unexpectedAfterThrows.isEmpty {
if unexpectedBeforeAsync.isEmpty && asyncKeyword == nil && unexpectedBeforeThrows.isEmpty && throwsKeyword == nil && thrownError == nil
&& unexpectedAfterThrownError.isEmpty
{
return nil
}

Expand All @@ -645,7 +686,9 @@ extension Parser {
asyncSpecifier: asyncKeyword,
RawUnexpectedNodesSyntax(unexpectedBeforeThrows, arena: self.arena),
throwsSpecifier: throwsKeyword,
RawUnexpectedNodesSyntax(unexpectedAfterThrows, arena: self.arena),
nil,
thrownError: thrownError,
RawUnexpectedNodesSyntax(unexpectedAfterThrownError, arena: self.arena),
arena: self.arena
)
}
Expand Down Expand Up @@ -749,6 +792,7 @@ extension Parser {
effectSpecifiers = S(
asyncSpecifier: synthesizedAsync,
throwsSpecifier: synthesizedThrows,
thrownError: nil,
arena: self.arena
)
}
Expand Down
9 changes: 8 additions & 1 deletion Sources/SwiftParser/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -800,11 +800,18 @@ extension Parser.Lookahead {
return true
}

if self.at(anyIn: EffectSpecifier.self) != nil {
if let effect = self.at(anyIn: EffectSpecifier.self) {
if self.peek().rawTokenKind == .arrow {
return true
}

if effect.spec.isThrowsSpecifier && self.peek().rawTokenKind == .leftParen {
var backtrack = self.lookahead()
backtrack.consumeAnyToken()
backtrack.skipSingle()
return backtrack.atFunctionTypeArrow()
}
Comment on lines +808 to +813
Copy link
Member

Choose a reason for hiding this comment

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

I think this doesn’t handle async throws(MyError) because the current token is async here, which means that we end up in the if below, which just consumes throws but not MyError.

Copy link
Member Author

Choose a reason for hiding this comment

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

The consumeAnyToken() consumes the throws, and the skipSingle consumes a parenthesized expression.

Copy link
Member

Choose a reason for hiding this comment

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

No, it doesn’t. The following test fails to parse

assertParse(
  "[() async throws(MyError) -> Void]()"
)


if peek(isAtAnyIn: EffectSpecifier.self) != nil {
var backtrack = self.lookahead()
backtrack.consumeAnyToken()
Expand Down
Loading