|
| 1 | +//===----------------------------------------------------------------------===// |
| 2 | +// |
| 3 | +// This source file is part of the Swift.org open source project |
| 4 | +// |
| 5 | +// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors |
| 6 | +// Licensed under Apache License v2.0 with Runtime Library Exception |
| 7 | +// |
| 8 | +// See https://swift.org/LICENSE.txt for license information |
| 9 | +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| 10 | +// |
| 11 | +//===----------------------------------------------------------------------===// |
| 12 | + |
| 13 | +/// Wrapper around the syntax kind strings to provide functionality specific to |
| 14 | +/// SwiftSyntaxBuilder. In particular, this includes the functionality to create |
| 15 | +/// the `*Buildable`, `ExpressibleAs*` and `*Syntax` Swift types from the syntax |
| 16 | +/// kind. |
| 17 | +struct SyntaxBuildableType: Hashable { |
| 18 | + let syntaxKind: String |
| 19 | + let tokenKind: String? |
| 20 | + let isOptional: Bool |
| 21 | + |
| 22 | + /// If optional `?`, otherwise the empty string. |
| 23 | + var optionalQuestionMark: String { |
| 24 | + isOptional ? "?" : "" |
| 25 | + } |
| 26 | + |
| 27 | + /// Whether this is a token. |
| 28 | + var isToken: Bool { |
| 29 | + syntaxKind == "Token" |
| 30 | + } |
| 31 | + |
| 32 | + /// This type with `isOptional` set to false. |
| 33 | + var nonOptional: Self { |
| 34 | + tokenKind.map { Self(syntaxKind: $0) } ?? Self(syntaxKind: syntaxKind) |
| 35 | + } |
| 36 | + |
| 37 | + /// The token if this is a token. |
| 38 | + var token: Token? { |
| 39 | + tokenKind.flatMap { SYNTAX_TOKEN_MAP[$0] } |
| 40 | + } |
| 41 | + |
| 42 | + /// If the type has a default value (because it is optional or a token |
| 43 | + /// with fixed test), return an expression of the form ` = defaultValue` |
| 44 | + /// that can be used as the default value for a function parameter. |
| 45 | + /// Otherwise, return the empty string. |
| 46 | + var defaultInitialization: String { |
| 47 | + if isOptional { |
| 48 | + return " = nil" |
| 49 | + } else if isToken { |
| 50 | + if let token = token, token.text != nil { |
| 51 | + return " = TokenSyntax.`\(lowercaseFirstWord(name: token.name))`" |
| 52 | + } else if tokenKind == "EOFToken" { |
| 53 | + return " = TokenSyntax.eof" |
| 54 | + } |
| 55 | + } |
| 56 | + return "" |
| 57 | + } |
| 58 | + |
| 59 | + /// Whether the type is a syntax collection. |
| 60 | + var isSyntaxCollection: Bool { |
| 61 | + syntaxKind == "SyntaxCollection" |
| 62 | + || (baseType.map(\.isSyntaxCollection) ?? false) |
| 63 | + } |
| 64 | + |
| 65 | + /// The raw base name of this kind. Used for the `build*` methods in the |
| 66 | + /// defined in the buildable types. |
| 67 | + var baseName: String { |
| 68 | + syntaxKind |
| 69 | + } |
| 70 | + |
| 71 | + /// Return the name of the `Buildable` type that is the main entry point for building |
| 72 | + /// SwiftSyntax trees using `SwiftSyntaxBuilder`. |
| 73 | + /// |
| 74 | + /// These names look as follows: |
| 75 | + /// - For nodes: The node name, e.g. `IdentifierExpr` (these are implemented as structs) |
| 76 | + /// - For base kinds: `<BaseKind>Buildable`, e.g. `ExprBuildable` (these are implemented as protocols) |
| 77 | + /// - For token: `TokenSyntax` (tokens don't have a dedicated type in SwiftSyntaxBuilder) |
| 78 | + /// If the type is optional, this terminates with a '?'. |
| 79 | + var buildable: String { |
| 80 | + if isToken { |
| 81 | + // Tokens don't have a dedicated buildable type. |
| 82 | + return "TokenSyntax\(optionalQuestionMark)" |
| 83 | + } else if SYNTAX_BASE_KINDS.contains(syntaxKind) { |
| 84 | + return "\(syntaxKind)Buildable\(optionalQuestionMark)" |
| 85 | + } else { |
| 86 | + return "\(syntaxKind)\(optionalQuestionMark)" |
| 87 | + } |
| 88 | + } |
| 89 | + |
| 90 | + /// Whether parameters of this type should be initializable by a result builder. |
| 91 | + /// Used for certain syntax collections and block-like structures (e.g. `CodeBlock`, |
| 92 | + /// `MemberDeclList`). |
| 93 | + var isBuilderInitializable: Bool { |
| 94 | + BUILDER_INITIALIZABLE_TYPES.keys.contains(nonOptional.buildable) |
| 95 | + } |
| 96 | + |
| 97 | + /// A type suitable for initializing this type through a result builder (e.g. |
| 98 | + /// returns `CodeBlockItemList` for `CodeBlock`) and otherwise itself. |
| 99 | + var builderInitializableType: Self { |
| 100 | + let buildable = nonOptional.buildable |
| 101 | + return Self( |
| 102 | + syntaxKind: BUILDER_INITIALIZABLE_TYPES[buildable].flatMap { $0 } ?? buildable, |
| 103 | + isOptional: isOptional |
| 104 | + ) |
| 105 | + } |
| 106 | + |
| 107 | + /// The type from `buildable()` without any question marks attached. |
| 108 | + /// This is used for the `create*` methods defined in the `ExpressibleAs*` protocols. |
| 109 | + var buildableBaseName: String { |
| 110 | + nonOptional.buildable |
| 111 | + } |
| 112 | + |
| 113 | + /// The `ExpressibleAs*` Swift type for this syntax kind. Tokens don't |
| 114 | + /// have an `ExpressibleAs*` type, so for those this method just returns |
| 115 | + /// `TokenSyntax`. If the type is optional, this terminates with a `?`. |
| 116 | + var expressibleAs: String { |
| 117 | + if isToken { |
| 118 | + // Tokens don't have a dedicated ExpressibleAs type. |
| 119 | + return buildable |
| 120 | + } else { |
| 121 | + return "ExpressibleAs\(buildable)" |
| 122 | + } |
| 123 | + } |
| 124 | + |
| 125 | + /// The corresponding `*Syntax` type defined in the `SwiftSyntax` module, |
| 126 | + /// which will eventually get built from `SwiftSyntaxBuilder`. If the type |
| 127 | + /// is optional, this terminates with a `?`. |
| 128 | + var syntax: String { |
| 129 | + if syntaxKind == "Syntax" { |
| 130 | + return "Syntax\(optionalQuestionMark)" |
| 131 | + } else { |
| 132 | + return "\(syntaxKind)Syntax\(optionalQuestionMark)" |
| 133 | + } |
| 134 | + } |
| 135 | + |
| 136 | + /// Assuming that this is a collection type, the type of the result builder |
| 137 | + /// that can be used to build the collection. |
| 138 | + var resultBuilder: String { |
| 139 | + "\(syntaxKind)Builder\(optionalQuestionMark)" |
| 140 | + } |
| 141 | + |
| 142 | + /// The collection types in which this type occurs as an element. |
| 143 | + /// We automatically make the `ExpressibleAs*` protocols conform to the |
| 144 | + /// `ExpressibleAs*` protocol of the collection they occur in. |
| 145 | + var elementInCollections: [Self] { |
| 146 | + SYNTAX_NODES |
| 147 | + .filter { $0.isSyntaxCollection && $0.collectionElementType == self } |
| 148 | + .map { $0.type } |
| 149 | + } |
| 150 | + |
| 151 | + /// Whether this type has the `WithTrailingComma` trait. |
| 152 | + var hasWithTrailingCommaTrait: Bool { |
| 153 | + SYNTAX_NODES.contains { $0.type == self && $0.traits.contains("WithTrailingComma") } |
| 154 | + } |
| 155 | + |
| 156 | + /// Types that take a single non-optional parameter of this types |
| 157 | + /// and to which this type is thus convertible. We automatically |
| 158 | + /// make the `ExpressibleAs*` of this type conform to the `ExpressibleAs*` |
| 159 | + /// protocol of the convertible types. |
| 160 | + var convertibleToTypes: [Self] { |
| 161 | + (SYNTAX_BUILDABLE_EXPRESSIBLE_AS_CONFORMANCES[buildable] ?? []) |
| 162 | + .map { Self(syntaxKind: $0) } |
| 163 | + } |
| 164 | + |
| 165 | + /// If this type is not a base kind, its base type (see `SyntaxBuildableNode.base_type()`), |
| 166 | + /// otherwise `nil`. |
| 167 | + var baseType: Self? { |
| 168 | + if !SYNTAX_BASE_KINDS.contains(syntaxKind) && !isToken { |
| 169 | + return Node.from(type: self).baseType |
| 170 | + } else { |
| 171 | + return nil |
| 172 | + } |
| 173 | + } |
| 174 | + |
| 175 | + /// The types to which this `ExpressibleAs*` type conforms to via |
| 176 | + /// automatically generated conformances. |
| 177 | + var generatedExpressibleAsConformances: [Self] { |
| 178 | + var conformances = elementInCollections + convertibleToTypes |
| 179 | + if let baseType = baseType, baseType.baseName != "SyntaxCollection" { |
| 180 | + conformances.append(baseType) |
| 181 | + } |
| 182 | + return conformances |
| 183 | + } |
| 184 | + |
| 185 | + /// The types to which this `ExpressibleAs*` type conforms to via |
| 186 | + /// automatically generated conformances, including transitive conformances. |
| 187 | + var transitiveExpressibleAsConformances: [Self] { |
| 188 | + generatedExpressibleAsConformances.flatMap { conformance in |
| 189 | + [conformance] + conformance.transitiveExpressibleAsConformances |
| 190 | + } |
| 191 | + } |
| 192 | + |
| 193 | + /// The types to which this `ExpressibleAs*` type implicitly conforms |
| 194 | + /// to via transitive conformances. These conformances don't need to be |
| 195 | + /// spelled out explicitly in the source code. |
| 196 | + var impliedExpressibleAsConformances: [Self] { |
| 197 | + generatedExpressibleAsConformances.flatMap { conformance in |
| 198 | + conformance.transitiveExpressibleAsConformances |
| 199 | + } |
| 200 | + } |
| 201 | + |
| 202 | + init(syntaxKind: String, isOptional: Bool = false) { |
| 203 | + self.isOptional = isOptional |
| 204 | + if syntaxKind.hasSuffix("Token") { |
| 205 | + // There are different token kinds but all of them are represented by `Token` in the Swift source (see `kind_to_type` in `gyb_syntax_support`). |
| 206 | + self.syntaxKind = "Token" |
| 207 | + self.tokenKind = syntaxKind |
| 208 | + } else { |
| 209 | + self.syntaxKind = syntaxKind |
| 210 | + self.tokenKind = nil |
| 211 | + } |
| 212 | + } |
| 213 | + |
| 214 | + /// Generate an expression that converts a variable named `varName` |
| 215 | + /// which is of `expressibleAs` type to an object of type `buildable`. |
| 216 | + func generateExprConvertParamTypeToStorageType(varName: String) -> String { |
| 217 | + if isToken { |
| 218 | + return varName |
| 219 | + } else { |
| 220 | + return "\(varName)\(optionalQuestionMark).create\(buildableBaseName)()" |
| 221 | + } |
| 222 | + } |
| 223 | +} |
0 commit comments