Skip to content

Commit

Permalink
[CodeGeneration] improved creation of syntax nodes across raw and non…
Browse files Browse the repository at this point in the history
…-raw

- introduced `TypeConvertible`, `ParameterConvertible` and `SyntaxNodeConvertible`
- introduced raw representations of `SyntaxNodeKind`, `Node` and `Child` for raw
- removed `SyntaxBuildableType`
- fixed the typing of raw node choices
  • Loading branch information
AppAppWorks committed Sep 10, 2024
1 parent a7b91e6 commit 2d8a3cd
Show file tree
Hide file tree
Showing 53 changed files with 814 additions and 806 deletions.
88 changes: 85 additions & 3 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: NodeChoiceConvertible {
public class Child: SyntaxNodeConvertible, NodeChoiceConvertible, ParameterConvertible {
/// The name of the child.
///
/// The first character of the name is always uppercase.
Expand Down Expand Up @@ -227,9 +227,9 @@ public class Child: NodeChoiceConvertible {
case .nodeChoices(let choices):
return choices.isEmpty
case .node(let kind):
return kind.isBase
return kind.isBaseType
case .collection(kind: let kind, _, _, _):
return kind.isBase
return kind.isBaseType
case .token:
return false
}
Expand All @@ -244,6 +244,11 @@ public class Child: NodeChoiceConvertible {
return AttributeListSyntax("@_spi(ExperimentalLanguageFeatures)").with(\.trailingTrivia, .newline)
}

/// The ``Node`` representation of this child, if any.
public var node: Node? {
self.syntaxNodeKind.node
}

/// 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 Down Expand Up @@ -273,3 +278,80 @@ public class Child: NodeChoiceConvertible {
self.isOptional = isOptional
}
}

// MARK: SyntaxNodeConvertible
public extension Child {
var isNode: Bool {
switch self.kind {
case .node, .collection:
return true
default:
return false
}
}

var syntaxType: TypeSyntax {
switch self.kind {
case .node(let kind), .collection(let kind, _, _, _):
return kind.syntaxType
case .nodeChoices:
return self.syntaxChoicesType
case .token:
return "TokenSyntax"
}
}
}

// MARK: ParameterConvertible
extension Child {
public var parameterAnyType: TypeSyntax {
self.parameterType(specifier: "any")
}

public var parameterSomeType: TypeSyntax {
self.parameterType(specifier: "some")
}

func parameterType(
specifier: TokenSyntax,
protocolType: TypeSyntax? = nil,
syntaxType: TypeSyntax? = nil
) -> TypeSyntax {
let type: TypeSyntax
if self.isBaseNode {
type = "\(specifier) \(protocolType ?? self.protocolType)"
} else {
type = syntaxType ?? self.syntaxType
}
return self.isOptional ? type.optionalWrapped : type
}

func defaultValue(syntaxType: TypeSyntax) -> ExprSyntax? {
guard !self.isOptional else {
if self.isBaseNode {
return "\(syntaxType.optionalWrapped).none"
} else {
return "nil"
}
}
if case .collection(_, _, defaultsToEmpty: true, _) = self.kind {
return "[]"
}
guard let token else {
return self.isOptional ? "nil" : nil
}
guard token.text == nil else {
return ".\(token.identifier)Token()"
}
guard case .token(let choices, _, _) = self.kind,
case .keyword(let keyword) = choices.only
else {
return nil
}
return ".\(token.memberCallName)(.\(keyword.spec.memberCallName))"
}

public var defaultValue: ExprSyntax? {
self.defaultValue(syntaxType: self.syntaxType)
}
}
28 changes: 14 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: NodeChoiceConvertible {
public class Node: SyntaxNodeConvertible, NodeChoiceConvertible {
fileprivate enum Data {
case layout(children: [Child], traits: [String])
case collection(choices: [SyntaxNodeKind])
Expand All @@ -38,7 +38,7 @@ public class Node: NodeChoiceConvertible {
public let kind: SyntaxNodeKind

/// The kind of node’s supertype. This kind must have `isBase == true`
public var base: SyntaxNodeKind {
public var baseKind: SyntaxNodeKind {
self.kind.base
}

Expand All @@ -57,6 +57,10 @@ public class Node: NodeChoiceConvertible {
/// function that should be invoked to create this node.
public let parserFunction: TokenSyntax?

public let isOptional = false

public let isNode = true

public var syntaxNodeKind: SyntaxNodeKind {
self.kind
}
Expand All @@ -71,7 +75,7 @@ public class Node: NodeChoiceConvertible {
public var layoutNode: LayoutNode? {
switch data {
case .layout:
if kind.isBase {
if kind.isBaseType {
return nil
} else {
return LayoutNode(node: self)
Expand Down Expand Up @@ -127,7 +131,7 @@ public class Node: NodeChoiceConvertible {
children: [Child] = []
) {
precondition(kind.base != .syntaxCollection)
precondition(kind.base.isBase, "unknown base kind '\(kind.base)' for node '\(kind)'")
precondition(kind.base.isBaseType, "unknown base kind '\(kind.base)' for node '\(kind)'")

self.kind = kind
self.experimentalFeature = experimentalFeature
Expand Down Expand Up @@ -219,18 +223,14 @@ public class Node: NodeChoiceConvertible {

let list =
childIn
.map {
if let childName = $0.child?.identifier {
.map { (node, child) in
if let childName = child?.identifier {
// This will repeat the syntax type before and after the dot, which is
// a little unfortunate, but it's the only way I found to get docc to
// generate a fully-qualified type + member.
if $0.node.isAvailableInDocc {
return " - \($0.node.doccLink).``\($0.node.syntaxType)/\(childName)``"
} else {
return " - \($0.node.doccLink).`\($0.node.syntaxType)/\(childName)`"
}
return " - \(node.doccLink).\(node.doccLink(content: "\(node.syntaxType)/\(childName)"))"
} else {
return " - \($0.node.doccLink)"
return " - \(node.doccLink)"
}
}
.joined(separator: "\n")
Expand All @@ -252,8 +252,8 @@ public class Node: NodeChoiceConvertible {

let list =
SYNTAX_NODES
.filter { $0.base == self.kind && !$0.isExperimental && !$0.kind.isDeprecated }
.map { "- \($0.kind.doccLink)" }
.filter { $0.baseKind == self.kind && !$0.isExperimental && !$0.isDeprecated }
.map { "- \($0.doccLink)" }
.joined(separator: "\n")

guard !list.isEmpty else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import SwiftSyntax

/// Instances of a conforming type should provide necessary information for generating code of a node choice.
public protocol NodeChoiceConvertible: IdentifierConvertible {
associatedtype Kind: SyntaxNodeKindProtocol

/// 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 }
Expand All @@ -29,7 +31,7 @@ public protocol NodeChoiceConvertible: IdentifierConvertible {
var apiAttributes: AttributeListSyntax { get }

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

public extension NodeChoiceConvertible {
Expand Down
35 changes: 35 additions & 0 deletions CodeGeneration/Sources/SyntaxSupport/ParameterConvertible.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//===----------------------------------------------------------------------===//
//
// 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

/// Implementations should provide necessary information for generating code of a parameter.
public protocol ParameterConvertible {
/// The type that is used for parameters in SwiftSyntaxBuilder that take this
/// type of syntax node and expect an existential type if the parameter type is a protocol.
var parameterAnyType: TypeSyntax {
get
}

/// The type that is used for parameters in SwiftSyntaxBuilder that take this
/// type of syntax node and expect a generic type if the parameter type is a protocol.
var parameterSomeType: TypeSyntax {
get
}

/// If the type has a default value (because it is optional or a token
/// with fixed test), return an expression that can be used as the
/// default value for a function parameter. Otherwise, return `nil`.
var defaultValue: ExprSyntax? {
get
}
}
85 changes: 85 additions & 0 deletions CodeGeneration/Sources/SyntaxSupport/RawChild.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//===----------------------------------------------------------------------===//
//
// 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

public extension Child {
/// The raw representation of ``Child``.
struct Raw: SyntaxNodeConvertible, NodeChoiceConvertible, ParameterConvertible {
var child: Child

public var isOptional: Bool {
self.child.isOptional
}

public var isNode: Bool {
self.child.isNode
}

public var syntaxType: TypeSyntax {
switch self.child.kind {
case .node(let kind), .collection(let kind, _, _, _):
return kind.raw.syntaxType
case .nodeChoices:
return self.child.syntaxChoicesType
case .token:
return "RawTokenSyntax"
}
}

public var syntaxNodeKind: SyntaxNodeKind.Raw {
self.child.syntaxNodeKind.raw
}

public var documentation: SwiftSyntax.Trivia {
self.child.documentation
}

public var experimentalFeature: ExperimentalFeature? {
self.child.experimentalFeature
}

public var apiAttributes: AttributeListSyntax {
self.child.apiAttributes
}

public var identifier: TokenSyntax {
self.child.identifier
}

public var parameterAnyType: TypeSyntax {
self.child.parameterType(specifier: "any", protocolType: self.protocolType, syntaxType: self.syntaxType)
}

public var parameterSomeType: TypeSyntax {
if self.child.isBaseNode && !self.child.isOptional {
// we restrict the use of generic type to non-optional parameter types, otherwise call sites would no longer be
// able to just pass `nil` to this parameter without specializing `(some Raw<Kind>SyntaxNodeProtocol)?`
//
// we've opted out of providing a default value to the parameter (e.g. `RawExprSyntax?.none`) as a workaround,
// as passing an explicit `nil` would prompt developers to think clearly whether this parameter should be parsed
return "some \(self.protocolType)"
} else {
return self.actualType
}
}

public var defaultValue: ExprSyntax? {
self.child.defaultValue(syntaxType: self.syntaxType)
}
}

/// The raw representation of this child.
var raw: Raw {
Raw(child: self)
}
}
54 changes: 54 additions & 0 deletions CodeGeneration/Sources/SyntaxSupport/RawNode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//===----------------------------------------------------------------------===//
//
// 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

public extension Node {
/// The raw representation of ``Node``. The definition of a syntax node that, when generated, conforms to
/// `RawSyntaxNodeProtocol`.
struct Raw: SyntaxNodeConvertible, NodeChoiceConvertible {
var node: Node

public var isOptional: Bool {
self.node.isOptional
}

public var syntaxNodeKind: SyntaxNodeKind.Raw {
self.node.syntaxNodeKind.raw
}

public var isNode: Bool {
self.node.isNode
}

public var documentation: SwiftSyntax.Trivia {
self.node.documentation
}

public var experimentalFeature: ExperimentalFeature? {
self.node.experimentalFeature
}

public var apiAttributes: AttributeListSyntax {
self.node.apiAttributes
}

public var identifier: TokenSyntax {
self.node.identifier
}
}

/// The raw representation of this node.
var raw: Raw {
Raw(node: self)
}
}
Loading

0 comments on commit 2d8a3cd

Please sign in to comment.