@@ -18,8 +18,9 @@ public struct CompatibilityLayer {
18
18
/// Deprecated members that the compatibility layer needs for each trait.
19
19
public var deprecatedMembersByTrait : [ String : DeprecatedMemberInfo ] = [ : ]
20
20
21
- /// Cache for `replacementChildren(for:by:)`. Ensures that we don't create two different replacement children even
22
- /// if we refactor the same child twice, so we can reliably equate and hash `Child` objects by object identity.
21
+ /// Cache for `replacementChildren(for:by:historical:)`. Ensures that we don't create two different replacement
22
+ /// children even if we refactor the same child twice, so we can reliably equate and hash `Child` objects by
23
+ /// object identity.
23
24
private var cachedReplacementChildren : [ Child : [ Child ] ] = [ : ]
24
25
25
26
/// Returns the deprecated members that the compatibility layer needs for `node`.
@@ -46,13 +47,23 @@ public struct CompatibilityLayer {
46
47
47
48
/// Returns the child or children that would have existed in place of this
48
49
/// child before this refactoring was applied.
49
- private mutating func replacementChildren( for newerChild: Child , by refactoring: Child . Refactoring ) -> [ Child ] {
50
+ ///
51
+ /// - Parameters:
52
+ /// - newerChild: The child which is being replaced.
53
+ /// - refactoring: The refactoring which created that child and must be
54
+ /// reversed.
55
+ fileprivate mutating func replacementChildren(
56
+ for newerChild: Child ,
57
+ by refactoring: Child . Refactoring ,
58
+ historical: Bool
59
+ ) -> [ Child ] {
50
60
func make( ) -> [ Child ] {
51
61
switch refactoring {
52
62
case . renamed( from: let deprecatedName) :
53
63
return [
54
64
Child (
55
65
renamingTo: deprecatedName,
66
+ makingHistorical: historical,
56
67
newerChildPath: [ newerChild]
57
68
)
58
69
]
@@ -72,8 +83,20 @@ public struct CompatibilityLayer {
72
83
}
73
84
74
85
return newerGrandchildren. map { newerGrandchild in
75
- Child ( newerChildPath: [ newerChild, newerGrandchild] )
86
+ Child (
87
+ makingHistorical: historical,
88
+ newerChildPath: [ newerChild, newerGrandchild]
89
+ )
76
90
}
91
+
92
+ case . introduced:
93
+ return [
94
+ Child (
95
+ makingHistorical: historical,
96
+ makingHidden: true ,
97
+ newerChildPath: [ newerChild]
98
+ )
99
+ ]
77
100
}
78
101
}
79
102
@@ -100,6 +123,7 @@ public struct CompatibilityLayer {
100
123
deprecatedMembersByNode [ node. syntaxNodeKind] = result
101
124
}
102
125
126
+ /// Compute and cache compatibility layer information for the given children.
103
127
private mutating func computeMembers( for trait: Trait ) {
104
128
guard deprecatedMembersByTrait [ trait. traitName] == nil else {
105
129
return
@@ -115,58 +139,146 @@ public struct CompatibilityLayer {
115
139
deprecatedMembersByTrait [ trait. traitName] = result
116
140
}
117
141
118
- /// Compute and cache compatibility layer information for the given children.
142
+ /// Compute compatibility layer information for the given children.
119
143
private mutating func computeMembersFor(
120
144
typeName: String ,
121
145
initialChildren: [ Child ] ,
122
146
history: Child . History ,
123
147
areRequirements: Bool
124
148
) -> DeprecatedMemberInfo {
125
- // The results that will ultimately be saved into the DeprecatedMemberInfo.
149
+ var builder = DeprecatedMemberInfo . Builder (
150
+ typeName: typeName,
151
+ children: initialChildren,
152
+ areRequirements: areRequirements
153
+ )
154
+
155
+ // If any of the children are experimental, apply an initial change set that hides them, ensuring that we generate
156
+ // APIs which aren't experimental.
157
+ let experimentalChildren = initialChildren. filter { $0. isExperimental && !$0. isUnexpectedNodes }
158
+ if !experimentalChildren. isEmpty {
159
+ let syntheticChangeSet = experimentalChildren. map { ( $0. name, Child . Refactoring. introduced) }
160
+ builder. applyChangeSet ( syntheticChangeSet, for: & self , historical: false )
161
+ }
162
+
163
+ // Apply changes in the history
164
+ for changeSet in history {
165
+ builder. applyChangeSet ( changeSet, for: & self , historical: true )
166
+ }
167
+
168
+ return builder. make ( )
169
+ }
170
+ }
171
+
172
+ /// Describes the deprecated members of a given type that the compatibility layer ought to provide.
173
+ public struct DeprecatedMemberInfo {
174
+ /// Properties that are needed in the compatibility layer, in the order they ought to appear in the generated file.
175
+ public var vars : [ Child ] = [ ]
176
+
177
+ /// Initializer signatures that are needed in the compatibility layer, in the order they ought to appear in the generated file.
178
+ public var inits : [ InitSignature ] = [ ]
179
+
180
+ /// Is there anything whatsoever that we ought to generate?
181
+ public var isEmpty : Bool {
182
+ return vars. isEmpty && inits. isEmpty
183
+ }
184
+
185
+ fileprivate struct Builder {
186
+ /// Properties that are needed in the compatibility layer, in the order they ought to appear in the generated file.
187
+ /// This becomes a property of the `DeprecatedMemberInfo`.
126
188
var vars : [ Child ] = [ ]
127
- var initSignatures : [ InitSignature ] = [ ]
128
189
129
- // Temporary working state for the loop .
130
- var children = initialChildren
131
- var knownVars = Set ( children )
190
+ /// Initializer signatures that are needed in the compatibility layer, in the order they ought to appear in the generated file .
191
+ /// This becomes a property of the `DeprecatedMemberInfo`.
192
+ var inits : [ InitSignature ] = [ ]
132
193
133
- func firstIndexOfChild( named targetName: String ) -> Int {
134
- guard let i = children. firstIndex ( where: { $0. name == targetName } ) else {
135
- fatalError (
136
- " couldn't find ' \( targetName) ' in current children of \( typeName) : \( String ( reflecting: children. map ( \. name) ) ) "
137
- )
138
- }
139
- return i
194
+ /// Name of the type we're generating a compatibility layer for.
195
+ private let typeName : String
196
+
197
+ /// Are we building a compatibility layer for requirements of a trait? Traits don't have unexpected children or
198
+ /// initializers.
199
+ private let areRequirements : Bool
200
+
201
+ /// The current set of children after applying all of the change sets ever passed to `applyChangeSet(_:for:historical:)`.
202
+ /// This is working state.
203
+ private var children : [ Child ]
204
+
205
+ /// The set of all children that have ever been added to `vars`, plus the ones that were originally present.
206
+ /// Used to ensure duplicates aren't added to `vars`. This is working state.
207
+ private var knownVars : Set < Child >
208
+
209
+ /// Creates a builder with no deprecated members, but ready to start adding change sets.
210
+ init ( typeName: String , children: [ Child ] , areRequirements: Bool ) {
211
+ self . typeName = typeName
212
+ self . areRequirements = areRequirements
213
+
214
+ self . children = children
215
+ self . knownVars = Set ( children)
140
216
}
141
217
142
- for changeSet in history {
218
+ /// Creates a `DeprecatedMemberInfo` from all the change sets that have been passed to
219
+ /// `applyChangeSet(_:for:historical:)`.
220
+ func make( ) -> DeprecatedMemberInfo {
221
+ return DeprecatedMemberInfo ( vars: vars, inits: inits)
222
+ }
223
+
224
+ /// Generate the new `vars` and `inits` that are required to maintain compatibility with `changeSet`.
225
+ ///
226
+ /// - Parameters:
227
+ /// - changeSet: The changes to apply. This type is basically a generic form of `Child.ChangeSet`.
228
+ /// - compatibilityLayer: The compatibility layer that these children will ultimately belong to.
229
+ /// - historical: Should the children created by this change set be marked historical (and thus be deprecated)?
230
+ mutating func applyChangeSet(
231
+ _ changeSet: some RandomAccessCollection < ( key: String , value: Child . Refactoring ) > ,
232
+ for compatibilityLayer: inout CompatibilityLayer ,
233
+ historical: Bool
234
+ ) {
143
235
var unexpectedChildrenWithNewNames : Set < Child > = [ ]
144
236
145
237
// First pass: Apply the changes explicitly specified in the change set.
146
238
for (currentName, refactoring) in changeSet {
147
239
let i = firstIndexOfChild ( named: currentName)
148
240
149
- let replacementChildren = replacementChildren ( for: children [ i] , by: refactoring)
241
+ let replacementChildren = compatibilityLayer. replacementChildren (
242
+ for: children [ i] ,
243
+ by: refactoring,
244
+ historical: historical
245
+ )
150
246
children. replaceSubrange ( i... i, with: replacementChildren)
151
247
152
248
if !areRequirements {
249
+ func isDifferent( _ newChild: Child ) -> Bool {
250
+ newChild. isHidden || currentName != newChild. name
251
+ }
252
+
153
253
// Mark adjacent unexpected node children whose names have changed too.
154
- if currentName ! = replacementChildren. first? . name {
254
+ if let firstNewChild = replacementChildren. first, isDifferent ( firstNewChild ) {
155
255
unexpectedChildrenWithNewNames. insert ( children [ i - 1 ] )
156
256
}
157
- if currentName ! = replacementChildren. last? . name {
257
+ if let lastNewChild = replacementChildren. last, isDifferent ( lastNewChild ) {
158
258
unexpectedChildrenWithNewNames. insert ( children [ i + replacementChildren. count] )
159
259
}
160
260
}
161
261
}
162
262
163
263
// Second pass: Update unexpected node children adjacent to those changes whose names have probably changed.
164
- for unexpectedChild in unexpectedChildrenWithNewNames {
264
+ for unexpectedChild in unexpectedChildrenWithNewNames where !unexpectedChild . isHidden {
165
265
precondition ( unexpectedChild. isUnexpectedNodes)
166
266
let i = firstIndexOfChild ( named: unexpectedChild. name)
167
267
168
- let earlier = children [ checked: i - 1 ]
169
- let later = children [ checked: i + 1 ]
268
+ guard i == 0 || !children[ i - 1 ] . isHidden else {
269
+ // Special case: `unexpectedChild` follows a hidden child and should be hidden too.
270
+ children [ i] = Child ( makingHistorical: historical, makingHidden: true , newerChildPath: [ unexpectedChild] )
271
+ continue
272
+ }
273
+
274
+ // Find nearest expected, non-hidden node before `unexpectedChild`
275
+ let allEarlier = children. prefix ( through: max ( i - 1 , children. startIndex) )
276
+ let earlier = allEarlier. last { !$0. isHidden && !$0. isUnexpectedNodes }
277
+
278
+ // Find nearest expected, non-hidden node after `unexpectedChild`
279
+ let allLater = children. suffix ( from: min ( i + 1 , children. endIndex) )
280
+ let later = allLater. first { !$0. isHidden && !$0. isUnexpectedNodes }
281
+
170
282
precondition ( !( earlier? . isUnexpectedNodes ?? false ) && !( later? . isUnexpectedNodes ?? false ) )
171
283
172
284
let newChild = Child ( forUnexpectedBetween: earlier, and: later, newerChildPath: [ unexpectedChild] )
@@ -176,33 +288,23 @@ public struct CompatibilityLayer {
176
288
children [ i] = newChild
177
289
}
178
290
179
- // Third pass: Append newly-created children to vars. We do this now so that changes from the first two passes are properly interleaved, preserving source order.
180
- vars += children. filter { knownVars. insert ( $0) . inserted }
291
+ // Third pass: Append newly-created children to vars. We do this now so that changes from the first two passes
292
+ // are properly interleaved, preserving source order.
293
+ self . vars += children. filter { !$0. isHidden && knownVars. insert ( $0) . inserted }
181
294
182
295
// We don't create compatibility layers for protocol requirement inits.
183
296
if !areRequirements {
184
- initSignatures . append ( InitSignature ( children: children) )
297
+ self . inits . append ( InitSignature ( children: children) )
185
298
}
186
299
}
187
300
188
- return DeprecatedMemberInfo ( vars: vars, inits: initSignatures)
189
- }
190
- }
191
-
192
- /// Describes the deprecated members of a given type that the compatibility layer ought to provide.
193
- public struct DeprecatedMemberInfo {
194
- /// Properties that are needed in the compatibility layer, in the order they ought to appear in the generated file.
195
- public var vars : [ Child ] = [ ]
196
-
197
- /// Initializer signatures that are needed in the compatibility layer, in the order they ought to appear in the generated file.
198
- public var inits : [ InitSignature ] = [ ]
199
- }
200
-
201
- extension Array {
202
- /// Returns `nil` if `i` is out of bounds, or the indicated element otherwise.
203
- fileprivate subscript( checked i: Index ) -> Element ? {
204
- get {
205
- return indices. contains ( i) ? self [ i] : nil
301
+ private func firstIndexOfChild( named targetName: String ) -> Int {
302
+ guard let i = children. firstIndex ( where: { $0. name == targetName } ) else {
303
+ fatalError (
304
+ " couldn't find ' \( targetName) ' in current children of \( typeName) : \( String ( reflecting: children. map ( \. name) ) ) "
305
+ )
306
+ }
307
+ return i
206
308
}
207
309
}
208
310
}
0 commit comments