Skip to content

Commit 111c7a3

Browse files
committed
[NFC] Generate code for experimental nodes better
Initializers for nodes with experimental node children need to be marked `@_spi`. This PR: • Adds that attribute. • Generates an alternative which *doesn’t* use SPI as part of the compatibility layer. • As a side effect, adds a `Child.Refactoring.introduced` case that can be used to generate compatibility `unexpected` properties. No functional change in this commit, but it will affect the code generation in the next one.
1 parent c951dd6 commit 111c7a3

File tree

6 files changed

+309
-109
lines changed

6 files changed

+309
-109
lines changed

CodeGeneration/Sources/SyntaxSupport/Child.swift

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,19 @@ public class Child: NodeChoiceConvertible {
140140
}
141141
}
142142

143+
/// Should this child be hidden?
144+
///
145+
/// A hidden child is one that is not accessible in any way at a specific point in the history, but still needs to be
146+
/// (default) initialized. As always, its `newestChildPath` indicates the current way to access it.
147+
///
148+
/// Hidden children are used for `Refactoring.introduced` and for the implicit changeset that creates
149+
/// non-experimental APIs that ignore experimental children.
150+
public let isHidden: Bool
151+
152+
/// True if this child was created by a `childHistory` change set. Such children
153+
/// are part of the compatibility layer and are therefore deprecated.
154+
public var isHistorical: Bool
155+
143156
/// A name of this child as an identifier.
144157
public var identifier: TokenSyntax {
145158
return .identifier(lowercaseFirstWord(name: name))
@@ -161,8 +174,8 @@ public class Child: NodeChoiceConvertible {
161174
return "\(raw: newestName.withFirstCharacterUppercased)Options"
162175
}
163176

164-
/// If this child is deprecated, describes the sequence of accesses necessary
165-
/// to reach the equivalent value using non-deprecated children; if the child
177+
/// If this child is part of a compatibility layer, describes the sequence of accesses necessary
178+
/// to reach the equivalent value using non-compatibility-layer children; if the child
166179
/// is not deprecated, this array is empty.
167180
///
168181
/// Think of the elements of this array like components in a key path:
@@ -199,12 +212,6 @@ public class Child: NodeChoiceConvertible {
199212
/// of the child. That information is not directly available anywhere.
200213
public let newestChildPath: [Child]
201214

202-
/// True if this child was created by a `Child.Refactoring`. Such children
203-
/// are part of the compatibility layer and are therefore deprecated.
204-
public var isHistorical: Bool {
205-
!newestChildPath.isEmpty
206-
}
207-
208215
/// Replaces the nodes in `newerChildPath` with their own `newerChildPath`s,
209216
/// if any, to form a child path enitrely of non-historical nodes.
210217
static private func makeNewestChildPath(from newerChildPath: [Child]) -> [Child] {
@@ -214,7 +221,7 @@ public class Child: NodeChoiceConvertible {
214221
var workStack = Array(newerChildPath.reversed())
215222

216223
while let elem = workStack.popLast() {
217-
if elem.isHistorical {
224+
if !elem.newestChildPath.isEmpty {
218225
// There's an even newer version. Start working on that.
219226
workStack.append(contentsOf: elem.newestChildPath.reversed())
220227
} else {
@@ -308,7 +315,8 @@ public class Child: NodeChoiceConvertible {
308315
documentation: String? = nil,
309316
isOptional: Bool = false,
310317
providesDefaultInitialization: Bool = true,
311-
newerChildPath: [Child] = []
318+
newerChildPath: [Child] = [],
319+
isHistorical: Bool = false
312320
) {
313321
precondition(name.first?.isLowercase ?? true, "The first letter of a child’s name should be lowercase")
314322
self.name = name
@@ -320,11 +328,18 @@ public class Child: NodeChoiceConvertible {
320328
self.documentationAbstract = String(documentation?.split(whereSeparator: \.isNewline).first ?? "")
321329
self.isOptional = isOptional
322330
self.providesDefaultInitialization = providesDefaultInitialization
331+
self.isHidden = false
332+
self.isHistorical = isHistorical
323333
}
324334

325335
/// Create a node that is a copy of the last node in `newerChildPath`, but
326336
/// with modifications.
327-
init(renamingTo replacementName: String? = nil, newerChildPath: [Child]) {
337+
init(
338+
renamingTo replacementName: String? = nil,
339+
makingHistorical: Bool = false,
340+
makingHidden: Bool = false,
341+
newerChildPath: [Child]
342+
) {
328343
let other = newerChildPath.last!
329344

330345
self.name = replacementName ?? other.name
@@ -336,6 +351,8 @@ public class Child: NodeChoiceConvertible {
336351
self.documentationAbstract = other.documentationAbstract
337352
self.isOptional = other.isOptional
338353
self.providesDefaultInitialization = other.providesDefaultInitialization
354+
self.isHidden = makingHidden || other.isHidden
355+
self.isHistorical = makingHistorical || other.isHistorical
339356
}
340357

341358
/// Create a child for the unexpected nodes between two children (either or
@@ -361,7 +378,8 @@ public class Child: NodeChoiceConvertible {
361378
documentation: nil,
362379
isOptional: true,
363380
providesDefaultInitialization: true,
364-
newerChildPath: newerChildPath
381+
newerChildPath: newerChildPath,
382+
isHistorical: (earlier?.isHistorical ?? false) || (later?.isHistorical ?? false)
365383
)
366384
}
367385
}
@@ -417,5 +435,8 @@ extension Child {
417435
/// point in the past, so deprecated aliases that flatten the other node's
418436
/// children into this node should be provided.
419437
case extracted
438+
439+
/// A new child was added (and it's important to preserve the names around it).
440+
case introduced
420441
}
421442
}

0 commit comments

Comments
 (0)