Skip to content

[CodeGeneration] Unify handling for node choices #2827

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
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
8 changes: 1 addition & 7 deletions CodeGeneration/Sources/SyntaxSupport/Child.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public enum ChildKind {

/// A child of a node, that may be declared optional or a token with a
/// restricted subset of acceptable kinds or texts.
public class Child: IdentifierConvertible {
public class Child: NodeChoiceConvertible {
/// The name of the child.
///
/// The first character of the name is always uppercase.
Expand All @@ -97,8 +97,6 @@ public class Child: IdentifierConvertible {
/// 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.
Expand Down Expand Up @@ -129,10 +127,6 @@ public class Child: IdentifierConvertible {
/// 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
20 changes: 6 additions & 14 deletions CodeGeneration/Sources/SyntaxSupport/Node.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import SwiftSyntax
/// but fixed types.
/// - Collection nodes contains an arbitrary number of children but all those
/// children are of the same type.
public class Node: IdentifierConvertible {
public class Node: NodeChoiceConvertible {
fileprivate enum Data {
case layout(children: [Child], traits: [String])
case collection(choices: [SyntaxNodeKind])
Expand All @@ -40,8 +40,6 @@ public class Node: IdentifierConvertible {
/// The kind of node’s supertype. This kind must have `isBase == true`
public let base: SyntaxNodeKind

/// The experimental feature the node is part of, or `nil` if this isn't
/// for an experimental feature.
public let experimentalFeature: ExperimentalFeature?

/// When the node name is printed for diagnostics, this name is used.
Expand All @@ -57,9 +55,9 @@ public class Node: IdentifierConvertible {
/// function that should be invoked to create this node.
public let parserFunction: TokenSyntax?

/// 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 {
self.kind
}

/// A name for this node as an identifier.
public var identifier: TokenSyntax {
Expand Down Expand Up @@ -112,14 +110,8 @@ public class Node: IdentifierConvertible {
return attrList.with(\.trailingTrivia, attrList.isEmpty ? [] : .newline)
}

/// The documentation note to print for an experimental feature.
public var experimentalDocNote: SwiftSyntax.Trivia {
let comment = experimentalFeature.map {
"""
- Experiment: Requires experimental feature `\($0.token)`.
"""
}
return SwiftSyntax.Trivia.docCommentTrivia(from: comment)
public var apiAttributes: AttributeListSyntax {
self.apiAttributes()
}

/// Construct the specification for a layout syntax node.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import SwiftSyntax

/// Instances of a conforming type should provide necessary information for generating code of a node choice.
public protocol NodeChoiceConvertible: IdentifierConvertible {
/// A docc comment describing the syntax node convertible, including the trivia provided when
/// initializing the syntax node convertible, and the list of possible token choices inferred automatically.
var documentation: SwiftSyntax.Trivia { get }

/// The experimental feature the syntax node convertible represents, or `nil` if this isn't
/// for an experimental feature.
var experimentalFeature: ExperimentalFeature? { get }

/// The attributes that should be printed on any API for the syntax node convertible.
///
/// This is typically used to mark APIs as SPI when the keyword is part of
/// an experimental language feature.
var apiAttributes: AttributeListSyntax { get }

/// The kind of the syntax node convertible.
var syntaxNodeKind: SyntaxNodeKind { get }
}

public extension NodeChoiceConvertible {
/// The documentation note to print for an experimental feature.
var experimentalDocNote: SwiftSyntax.Trivia {
guard let experimentalFeature else {
return []
}
return .docCommentTrivia(from: "- Note: Requires experimental feature `\(experimentalFeature.token)`.")
}

/// If `true`, this is for an experimental language feature, and any public
/// API generated should be SPI.
var isExperimental: Bool {
self.experimentalFeature != nil
}
}
43 changes: 43 additions & 0 deletions CodeGeneration/Sources/SyntaxSupport/RawSyntaxNodeKind.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import SwiftSyntax

/// A wrapper of ``SyntaxNodeKind`` providing syntax type information for the raw side.
public struct RawSyntaxNodeKind: TypeConvertible {
public var syntaxNodeKind: SyntaxNodeKind

public var isBase: Bool {
self.syntaxNodeKind.isBase
}

public var syntaxType: TypeSyntax {
"Raw\(self.syntaxNodeKind.syntaxType)"
}

public var protocolType: TypeSyntax {
switch self {
case .syntax, .syntaxCollection:
return "RawSyntaxNodeProtocol"
default:
return "\(self.syntaxType)NodeProtocol"
}
}

public var isAvailableInDocc: Bool {
false
}

public static func ~= (lhs: SyntaxNodeKind, rhs: Self) -> Bool {
lhs == rhs.syntaxNodeKind
}
}
39 changes: 5 additions & 34 deletions CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import SwiftSyntaxBuilder
///
/// Using the cases of this enum, children of syntax nodes can refer the syntax
/// node that defines their layout.
public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible {
public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible, TypeConvertible {
// Please keep this list sorted alphabetically

case _canImportExpr
Expand Down Expand Up @@ -327,7 +327,6 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible {
}
}

/// Whether this is one of the syntax base nodes.
public var isBase: Bool {
switch self {
case .decl, .expr, .pattern, .stmt, .syntax, .syntaxCollection, .type:
Expand All @@ -337,12 +336,10 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible {
}
}

/// A name for this node as an identifier.
public var identifier: TokenSyntax {
return .identifier(rawValue)
}

/// The type name of this node in the SwiftSyntax module.
public var syntaxType: TypeSyntax {
switch self {
case .syntax:
Expand All @@ -354,7 +351,6 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible {
}
}

/// Whether the node is public API and not underscored/deprecated and can thus be referenced in docc links.
public var isAvailableInDocc: Bool {
if let node = SYNTAX_NODE_MAP[self], node.isExperimental {
return false
Expand All @@ -365,39 +361,10 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible {
}
}

/// If this node is non-experimental a docc link wrapped in two backticks.
///
/// For experimental nodes, the node's type name in code font.
public var doccLink: String {
if isAvailableInDocc {
return "``\(syntaxType)``"
} else {
return "`\(syntaxType)`"
}
}

/// For base nodes, the name of the corresponding protocol to which all the
/// concrete nodes that have this base kind, conform.
public var protocolType: TypeSyntax {
return "\(syntaxType)Protocol"
}

/// The name of this node at the `RawSyntax` level.
public var rawType: TypeSyntax {
return "Raw\(syntaxType)"
}

/// For base nodes, the name of the corresponding raw protocol to which all the
/// concrete raw nodes that have this base kind, conform.
public var rawProtocolType: TypeSyntax {
switch self {
case .syntax, .syntaxCollection:
return "RawSyntaxNodeProtocol"
default:
return "Raw\(raw: rawValue.withFirstCharacterUppercased)SyntaxNodeProtocol"
}
}

/// For base node types, generates the name of the protocol to which all
/// concrete leaf nodes that derive from this base kind should conform.
///
Expand Down Expand Up @@ -507,4 +474,8 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible {
AttributeSyntax(#"@available(*, deprecated, renamed: "\#(syntaxType)")"#)
}
}

public var raw: RawSyntaxNodeKind {
RawSyntaxNodeKind(syntaxNodeKind: self)
}
}
50 changes: 50 additions & 0 deletions CodeGeneration/Sources/SyntaxSupport/TypeConvertible.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import SwiftSyntax

/// Instances of a conforming type should provide syntax type information to be used in code generation.
public protocol TypeConvertible {
/// Whether this is one of the syntax base nodes.
var isBase: Bool {
get
}

/// The type name of this node in the SwiftSyntax module.
var syntaxType: TypeSyntax {
get
}

/// For base nodes, the name of the corresponding protocol to which all the
/// concrete nodes that have this base kind, conform.
var protocolType: TypeSyntax {
get
}

/// Whether the node is public API and not underscored/deprecated and can thus be referenced in docc links.
var isAvailableInDocc: Bool {
get
}
}

public extension TypeConvertible {
/// If this node is non-experimental a docc link wrapped in two backticks.
///
/// For experimental nodes, the node's type name in code font.
var doccLink: String {
if self.isAvailableInDocc {
return "``\(self.syntaxType)``"
} else {
return "`\(self.syntaxType)`"
}
}
}
Loading