Coverage Report

Created: 2025-07-04 06:57

/src/swift-protobuf/Sources/SwiftProtobuf/NameMap.swift
Line
Count
Source (jump to first uncovered line)
1
// Sources/SwiftProtobuf/NameMap.swift - Bidirectional number/name mapping
2
//
3
// Copyright (c) 2014 - 2016 Apple Inc. and the project authors
4
// Licensed under Apache License v2.0 with Runtime Library Exception
5
//
6
// See LICENSE.txt for license information:
7
// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
8
//
9
// -----------------------------------------------------------------------------
10
11
/// TODO: Right now, only the NameMap and the NameDescription enum
12
/// (which are directly used by the generated code) are public.
13
/// This means that code outside the library has no way to actually
14
/// use this data.  We should develop and publicize a suitable API
15
/// for that purpose.  (Which might be the same as the internal API.)
16
17
/// This must produce exactly the same outputs as the corresponding
18
/// code in the protoc-gen-swift code generator. Changing it will
19
/// break compatibility of the library with older generated code.
20
///
21
/// It does not necessarily need to match protoc's JSON field naming
22
/// logic, however.
23
1.15k
private func toJSONFieldName(_ s: UnsafeBufferPointer<UInt8>) -> String {
24
1.15k
    var result = String.UnicodeScalarView()
25
1.15k
    var capitalizeNext = false
26
18.8k
    for c in s {
27
18.8k
        if c == UInt8(ascii: "_") {
28
2.01k
            capitalizeNext = true
29
18.8k
        } else if capitalizeNext {
30
2.01k
            result.append(Unicode.Scalar(c).uppercasedAssumingASCII)
31
2.01k
            capitalizeNext = false
32
18.8k
        } else {
33
16.8k
            result.append(Unicode.Scalar(c))
34
18.8k
        }
35
18.8k
    }
36
1.15k
    return String(result)
37
1.15k
}
38
#if !REMOVE_LEGACY_NAMEMAP_INITIALIZERS
39
0
private func toJSONFieldName(_ s: StaticString) -> String {
40
0
    guard s.hasPointerRepresentation else {
41
0
        // If it's a single code point, it wouldn't be changed by the above algorithm.
42
0
        // Return it as-is.
43
0
        return s.description
44
0
    }
45
0
    return toJSONFieldName(UnsafeBufferPointer(start: s.utf8Start, count: s.utf8CodeUnitCount))
46
0
}
47
#endif  // !REMOVE_LEGACY_NAMEMAP_INITIALIZERS
48
49
/// Allocate static memory buffers to intern UTF-8
50
/// string data.  Track the buffers and release all of those buffers
51
/// in case we ever get deallocated.
52
private class InternPool {
53
640
    private var interned = [UnsafeRawBufferPointer]()
54
55
1.16k
    func intern(utf8: String.UTF8View) -> UnsafeRawBufferPointer {
56
1.16k
        let mutable = UnsafeMutableRawBufferPointer.allocate(
57
1.16k
            byteCount: utf8.count,
58
1.16k
            alignment: MemoryLayout<UInt8>.alignment
59
1.16k
        )
60
1.16k
        mutable.copyBytes(from: utf8)
61
1.16k
        let immutable = UnsafeRawBufferPointer(mutable)
62
1.16k
        interned.append(immutable)
63
1.16k
        return immutable
64
1.16k
    }
65
66
0
    func intern(utf8Ptr: UnsafeBufferPointer<UInt8>) -> UnsafeRawBufferPointer {
67
0
        let mutable = UnsafeMutableRawBufferPointer.allocate(
68
0
            byteCount: utf8Ptr.count,
69
0
            alignment: MemoryLayout<UInt8>.alignment
70
0
        )
71
0
        mutable.copyBytes(from: utf8Ptr)
72
0
        let immutable = UnsafeRawBufferPointer(mutable)
73
0
        interned.append(immutable)
74
0
        return immutable
75
0
    }
76
77
0
    deinit {
78
0
        for buff in interned {
79
0
            buff.deallocate()
80
0
        }
81
0
    }
82
}
83
84
/// Instructions used in bytecode streams that define proto name mappings.
85
///
86
/// Since field and enum case names are encoded in numeric order, field and case number operands in
87
/// the bytecode are stored as adjacent differences. Most messages/enums use densely packed
88
/// numbers, so we've optimized the opcodes for that; each instruction that takes a single
89
/// field/case number has two forms: one that assumes the next number is +1 from the previous
90
/// number, and a second form that takes an arbitrary delta from the previous number.
91
///
92
/// This has package visibility so that it is also visible to the generator.
93
package enum ProtoNameInstruction: UInt64, CaseIterable {
94
    /// The proto (text format) name and the JSON name are the same string.
95
    ///
96
    /// ## Operands
97
    /// * (Delta only) An integer representing the (delta from the previous) field or enum case
98
    ///   number.
99
    /// * A string containing the single text format and JSON name.
100
    case sameNext = 1
101
    case sameDelta = 2
102
103
    /// The JSON name can be computed from the proto string.
104
    ///
105
    /// ## Operands
106
    /// * (Delta only) An integer representing the (delta from the previous) field or enum case
107
    ///   number.
108
    /// * A string containing the single text format name, from which the JSON name will be
109
    ///   dynamically computed.
110
    case standardNext = 3
111
    case standardDelta = 4
112
113
    /// The JSON and text format names are just different.
114
    ///
115
    /// ## Operands
116
    /// * (Delta only) An integer representing the (delta from the previous) field or enum case
117
    ///   number.
118
    /// * A string containing the text format name.
119
    /// * A string containing the JSON name.
120
    case uniqueNext = 5
121
    case uniqueDelta = 6
122
123
    /// Used for group fields only to represent the message type name of a group.
124
    ///
125
    /// ## Operands
126
    /// * (Delta only) An integer representing the (delta from the previous) field number.
127
    /// * A string containing the (UpperCamelCase by convention) message type name, from which the
128
    ///   text format and JSON names can be derived (lowercase).
129
    case groupNext = 7
130
    case groupDelta = 8
131
132
    /// Used for enum cases only to represent a value's primary proto name (the first defined case)
133
    /// and its aliases. The JSON and text format names for enums are always the same.
134
    ///
135
    /// ## Operands
136
    /// * (Delta only) An integer representing the (delta from the previous) enum case number.
137
    /// * An integer `aliasCount` representing the number of aliases.
138
    /// * A string containing the text format/JSON name (the first defined case with this number).
139
    /// * `aliasCount` strings containing other text format/JSON names that are aliases.
140
    case aliasNext = 9
141
    case aliasDelta = 10
142
143
    /// Represents a reserved name in a proto message.
144
    ///
145
    /// ## Operands
146
    /// * The name of a reserved field.
147
    case reservedName = 11
148
149
    /// Represents a range of reserved field numbers or enum case numbers in a proto message.
150
    ///
151
    /// ## Operands
152
    /// * An integer representing the lower bound (inclusive) of the reserved number range.
153
    /// * An integer representing the delta between the upper bound (exclusive) and the lower bound
154
    ///   of the reserved number range.
155
    case reservedNumbers = 12
156
157
    /// Indicates whether the opcode represents an instruction that has an explicit delta encoded
158
    /// as its first operand.
159
689
    var hasExplicitDelta: Bool {
160
689
        switch self {
161
689
        case .sameDelta, .standardDelta, .uniqueDelta, .groupDelta, .aliasDelta: return true
162
689
        default: return false
163
689
        }
164
689
    }
165
}
166
167
/// An immutable bidirectional mapping between field/enum-case names
168
/// and numbers, used to record field names for text-based
169
/// serialization (JSON and text).  These maps are lazily instantiated
170
/// for each message as needed, so there is no run-time overhead for
171
/// users who do not use text-based serialization formats.
172
public struct _NameMap: ExpressibleByDictionaryLiteral {
173
174
    /// An immutable interned string container.  The `utf8Start` pointer
175
    /// is guaranteed valid for the lifetime of the `NameMap` that you
176
    /// fetched it from.  Since `NameMap`s are only instantiated as
177
    /// immutable static values, that should be the lifetime of the
178
    /// program.
179
    ///
180
    /// Internally, this uses `StaticString` (which refers to a fixed
181
    /// block of UTF-8 data) where possible.  In cases where the string
182
    /// has to be computed, it caches the UTF-8 bytes in an
183
    /// unmovable and immutable heap area.
184
    package struct Name: Hashable, CustomStringConvertible {
185
        #if !REMOVE_LEGACY_NAMEMAP_INITIALIZERS
186
        // This should not be used outside of this file, as it requires
187
        // coordinating the lifecycle with the lifecycle of the pool
188
        // where the raw UTF8 gets interned.
189
0
        fileprivate init(staticString: StaticString, pool: InternPool) {
190
0
            if staticString.hasPointerRepresentation {
191
0
                self.utf8Buffer = UnsafeRawBufferPointer(
192
0
                    start: staticString.utf8Start,
193
0
                    count: staticString.utf8CodeUnitCount
194
0
                )
195
0
            } else {
196
0
                self.utf8Buffer = staticString.withUTF8Buffer { pool.intern(utf8Ptr: $0) }
197
0
            }
198
0
        }
199
        #endif  // !REMOVE_LEGACY_NAMEMAP_INITIALIZERS
200
201
        // This should not be used outside of this file, as it requires
202
        // coordinating the lifecycle with the lifecycle of the pool
203
        // where the raw UTF8 gets interned.
204
1.16k
        fileprivate init(string: String, pool: InternPool) {
205
1.16k
            let utf8 = string.utf8
206
1.16k
            self.utf8Buffer = pool.intern(utf8: utf8)
207
1.16k
        }
208
209
        // This is for building a transient `Name` object sufficient for lookup purposes.
210
        // It MUST NOT be exposed outside of this file.
211
30.4M
        fileprivate init(transientUtf8Buffer: UnsafeRawBufferPointer) {
212
30.4M
            self.utf8Buffer = transientUtf8Buffer
213
30.4M
        }
214
215
        // This is for building a `Name` object from a slice of a bytecode `StaticString`.
216
        // It MUST NOT be exposed outside of this file.
217
1.37k
        fileprivate init(bytecodeUTF8Buffer: UnsafeBufferPointer<UInt8>) {
218
1.37k
            self.utf8Buffer = UnsafeRawBufferPointer(bytecodeUTF8Buffer)
219
1.37k
        }
220
221
        internal let utf8Buffer: UnsafeRawBufferPointer
222
223
124k
        public var description: String {
224
124k
            String(decoding: self.utf8Buffer, as: UTF8.self)
225
124k
        }
226
227
17.2M
        public func hash(into hasher: inout Hasher) {
228
974M
            for byte in utf8Buffer {
229
974M
                hasher.combine(byte)
230
974M
            }
231
17.2M
        }
232
233
12.8M
        public static func == (lhs: Name, rhs: Name) -> Bool {
234
12.8M
            if lhs.utf8Buffer.count != rhs.utf8Buffer.count {
235
9.65M
                return false
236
9.65M
            }
237
3.16M
            return lhs.utf8Buffer.elementsEqual(rhs.utf8Buffer)
238
12.8M
        }
239
    }
240
241
    /// The JSON and proto names for a particular field, enum case, or extension.
242
    internal struct Names {
243
        private(set) var json: Name?
244
        private(set) var proto: Name
245
    }
246
247
    #if !REMOVE_LEGACY_NAMEMAP_INITIALIZERS
248
249
    /// A description of the names for a particular field or enum case.
250
    /// The different forms here let us minimize the amount of string
251
    /// data that we store in the binary.
252
    ///
253
    /// These are only used in the generated code to initialize a NameMap.
254
    public enum NameDescription {
255
256
        /// The proto (text format) name and the JSON name are the same string.
257
        case same(proto: StaticString)
258
259
        /// The JSON name can be computed from the proto string
260
        case standard(proto: StaticString)
261
262
        /// The JSON and text format names are just different.
263
        case unique(proto: StaticString, json: StaticString)
264
265
        /// Used for enum cases only to represent a value's primary proto name (the
266
        /// first defined case) and its aliases. The JSON and text format names for
267
        /// enums are always the same.
268
        case aliased(proto: StaticString, aliases: [StaticString])
269
    }
270
271
    #endif  // !REMOVE_LEGACY_NAMEMAP_INITIALIZERS
272
273
640
    private var internPool = InternPool()
274
275
    /// The mapping from field/enum-case numbers to names.
276
640
    private var numberToNameMap: [Int: Names] = [:]
277
278
    /// The mapping from proto/text names to field/enum-case numbers.
279
640
    private var protoToNumberMap: [Name: Int] = [:]
280
281
    /// The mapping from JSON names to field/enum-case numbers.
282
    /// Note that this also contains all of the proto/text names,
283
    /// as required by Google's spec for protobuf JSON.
284
640
    private var jsonToNumberMap: [Name: Int] = [:]
285
286
    /// The reserved names in for this object. Currently only used for Message to
287
    /// support TextFormat's requirement to skip these names in all cases.
288
640
    private var reservedNames: [String] = []
289
290
    /// The reserved numbers in for this object. Currently only used for Message to
291
    /// support TextFormat's requirement to skip these numbers in all cases.
292
640
    private var reservedRanges: [Range<Int32>] = []
293
294
    /// Creates a new empty field/enum-case name/number mapping.
295
20
    public init() {}
296
297
    #if REMOVE_LEGACY_NAMEMAP_INITIALIZERS
298
299
    // Provide a dummy for ExpressibleByDictionaryLiteral conformance.
300
    public init(dictionaryLiteral elements: (Int, Int)...) {
301
        fatalError("Support compiled out removed")
302
    }
303
304
    #else  // !REMOVE_LEGACY_NAMEMAP_INITIALIZERS
305
306
    /// Build the bidirectional maps between numbers and proto/JSON names.
307
    public init(
308
        reservedNames: [String],
309
        reservedRanges: [Range<Int32>],
310
        numberNameMappings: KeyValuePairs<Int, NameDescription>
311
0
    ) {
312
0
        self.reservedNames = reservedNames
313
0
        self.reservedRanges = reservedRanges
314
0
315
0
        initHelper(numberNameMappings)
316
0
    }
317
318
    /// Build the bidirectional maps between numbers and proto/JSON names.
319
0
    public init(dictionaryLiteral elements: (Int, NameDescription)...) {
320
0
        initHelper(elements)
321
0
    }
322
323
    /// Helper to share the building of mappings between the two initializers.
324
    private mutating func initHelper<Pairs: Collection>(
325
        _ elements: Pairs
326
0
    ) where Pairs.Element == (key: Int, value: NameDescription) {
327
0
        for (number, description) in elements {
328
0
            switch description {
329
0
330
0
            case .same(proto: let p):
331
0
                let protoName = Name(staticString: p, pool: internPool)
332
0
                let names = Names(json: protoName, proto: protoName)
333
0
                numberToNameMap[number] = names
334
0
                protoToNumberMap[protoName] = number
335
0
                jsonToNumberMap[protoName] = number
336
0
337
0
            case .standard(proto: let p):
338
0
                let protoName = Name(staticString: p, pool: internPool)
339
0
                let jsonString = toJSONFieldName(p)
340
0
                let jsonName = Name(string: jsonString, pool: internPool)
341
0
                let names = Names(json: jsonName, proto: protoName)
342
0
                numberToNameMap[number] = names
343
0
                protoToNumberMap[protoName] = number
344
0
                jsonToNumberMap[protoName] = number
345
0
                jsonToNumberMap[jsonName] = number
346
0
347
0
            case .unique(proto: let p, json: let j):
348
0
                let jsonName = Name(staticString: j, pool: internPool)
349
0
                let protoName = Name(staticString: p, pool: internPool)
350
0
                let names = Names(json: jsonName, proto: protoName)
351
0
                numberToNameMap[number] = names
352
0
                protoToNumberMap[protoName] = number
353
0
                jsonToNumberMap[protoName] = number
354
0
                jsonToNumberMap[jsonName] = number
355
0
356
0
            case .aliased(proto: let p, let aliases):
357
0
                let protoName = Name(staticString: p, pool: internPool)
358
0
                let names = Names(json: protoName, proto: protoName)
359
0
                numberToNameMap[number] = names
360
0
                protoToNumberMap[protoName] = number
361
0
                jsonToNumberMap[protoName] = number
362
0
                for alias in aliases {
363
0
                    let protoName = Name(staticString: alias, pool: internPool)
364
0
                    protoToNumberMap[protoName] = number
365
0
                    jsonToNumberMap[protoName] = number
366
0
                }
367
0
            }
368
0
        }
369
0
    }
370
371
    #endif  // !REMOVE_LEGACY_NAMEMAP_INITIALIZERS
372
373
120
    public init(bytecode: StaticString) {
374
120
        var previousNumber = 0
375
1.37k
        BytecodeInterpreter<ProtoNameInstruction>(program: bytecode).execute { instruction, reader in
376
1.37k
            func nextNumber() -> Int {
377
1.37k
                let next: Int
378
1.37k
                if instruction.hasExplicitDelta {
379
74
                    next = previousNumber + Int(reader.nextInt32())
380
1.37k
                } else {
381
1.30k
                    next = previousNumber + 1
382
1.37k
                }
383
1.37k
                previousNumber = next
384
1.37k
                return next
385
1.37k
            }
386
1.37k
387
1.37k
            switch instruction {
388
1.37k
            case .sameNext, .sameDelta:
389
210
                let number = nextNumber()
390
210
                let protoName = Name(bytecodeUTF8Buffer: reader.nextNullTerminatedString())
391
210
                numberToNameMap[number] = Names(json: protoName, proto: protoName)
392
210
                protoToNumberMap[protoName] = number
393
210
                jsonToNumberMap[protoName] = number
394
1.37k
395
1.37k
            case .standardNext, .standardDelta:
396
1.15k
                let number = nextNumber()
397
1.15k
                let protoNameBuffer = reader.nextNullTerminatedString()
398
1.15k
                let protoName = Name(bytecodeUTF8Buffer: protoNameBuffer)
399
1.15k
                let jsonString = toJSONFieldName(protoNameBuffer)
400
1.15k
                let jsonName = Name(string: jsonString, pool: internPool)
401
1.15k
                numberToNameMap[number] = Names(json: jsonName, proto: protoName)
402
1.15k
                protoToNumberMap[protoName] = number
403
1.15k
                jsonToNumberMap[protoName] = number
404
1.15k
                jsonToNumberMap[jsonName] = number
405
1.37k
406
1.37k
            case .uniqueNext, .uniqueDelta:
407
0
                let number = nextNumber()
408
0
                let protoName = Name(bytecodeUTF8Buffer: reader.nextNullTerminatedString())
409
0
                let jsonName = Name(bytecodeUTF8Buffer: reader.nextNullTerminatedString())
410
0
                numberToNameMap[number] = Names(json: jsonName, proto: protoName)
411
0
                protoToNumberMap[protoName] = number
412
0
                jsonToNumberMap[protoName] = number
413
0
                jsonToNumberMap[jsonName] = number
414
1.37k
415
1.37k
            case .groupNext, .groupDelta:
416
12
                let number = nextNumber()
417
12
                let protoNameBuffer = reader.nextNullTerminatedString()
418
12
                let protoName = Name(bytecodeUTF8Buffer: protoNameBuffer)
419
12
                protoToNumberMap[protoName] = number
420
12
                jsonToNumberMap[protoName] = number
421
12
422
12
                let lowercaseName: Name
423
12
                let hasUppercase = protoNameBuffer.contains { (UInt8(ascii: "A")...UInt8(ascii: "Z")).contains($0) }
424
12
                if hasUppercase {
425
12
                    lowercaseName = Name(
426
12
                        string: String(decoding: protoNameBuffer, as: UTF8.self).lowercased(),
427
12
                        pool: internPool
428
12
                    )
429
12
                    protoToNumberMap[lowercaseName] = number
430
12
                    jsonToNumberMap[lowercaseName] = number
431
12
                } else {
432
0
                    // No need to convert and intern a separate copy of the string
433
0
                    // if it would be identical.
434
0
                    lowercaseName = protoName
435
12
                }
436
12
                numberToNameMap[number] = Names(json: lowercaseName, proto: protoName)
437
1.37k
438
1.37k
            case .aliasNext, .aliasDelta:
439
0
                let number = nextNumber()
440
0
                let protoName = Name(bytecodeUTF8Buffer: reader.nextNullTerminatedString())
441
0
                numberToNameMap[number] = Names(json: protoName, proto: protoName)
442
0
                protoToNumberMap[protoName] = number
443
0
                jsonToNumberMap[protoName] = number
444
0
                for alias in reader.nextNullTerminatedStringArray() {
445
0
                    let protoName = Name(bytecodeUTF8Buffer: alias)
446
0
                    protoToNumberMap[protoName] = number
447
0
                    jsonToNumberMap[protoName] = number
448
1.37k
                }
449
1.37k
450
1.37k
            case .reservedName:
451
0
                let name = String(decoding: reader.nextNullTerminatedString(), as: UTF8.self)
452
0
                reservedNames.append(name)
453
1.37k
454
1.37k
            case .reservedNumbers:
455
0
                let lowerBound = reader.nextInt32()
456
0
                let upperBound = lowerBound + reader.nextInt32()
457
0
                reservedRanges.append(lowerBound..<upperBound)
458
1.37k
            }
459
1.37k
        }
460
120
    }
461
462
    /// Returns the name bundle for the field/enum-case with the given number, or
463
    /// `nil` if there is no match.
464
186M
    internal func names(for number: Int) -> Names? {
465
186M
        numberToNameMap[number]
466
186M
    }
467
468
    /// Returns the field/enum-case number that has the given JSON name,
469
    /// or `nil` if there is no match.
470
    ///
471
    /// This is used by the Text format parser to look up field or enum
472
    /// names using a direct reference to the un-decoded UTF8 bytes.
473
145k
    internal func number(forProtoName raw: UnsafeRawBufferPointer) -> Int? {
474
145k
        let n = Name(transientUtf8Buffer: raw)
475
145k
        return protoToNumberMap[n]
476
145k
    }
477
478
    /// Returns the field/enum-case number that has the given JSON name,
479
    /// or `nil` if there is no match.
480
    ///
481
    /// This accepts a regular `String` and is used in JSON parsing
482
    /// only when a field name or enum name was decoded from a string
483
    /// containing backslash escapes.
484
    ///
485
    /// JSON parsing must interpret *both* the JSON name of the
486
    /// field/enum-case provided by the descriptor *as well as* its
487
    /// original proto/text name.
488
121k
    internal func number(forJSONName name: String) -> Int? {
489
121k
        let utf8 = Array(name.utf8)
490
127k
        return utf8.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) in
491
127k
            let n = Name(transientUtf8Buffer: buffer)
492
127k
            return jsonToNumberMap[n]
493
127k
        }
494
121k
    }
495
496
    /// Returns the field/enum-case number that has the given JSON name,
497
    /// or `nil` if there is no match.
498
    ///
499
    /// This is used by the JSON parser when a field name or enum name
500
    /// required no special processing.  As a result, we can avoid
501
    /// copying the name and look up the number using a direct reference
502
    /// to the un-decoded UTF8 bytes.
503
7.22M
    internal func number(forJSONName raw: UnsafeRawBufferPointer) -> Int? {
504
7.22M
        let n = Name(transientUtf8Buffer: raw)
505
7.22M
        return jsonToNumberMap[n]
506
7.22M
    }
507
508
    /// Returns all proto names
509
0
    internal var names: [Name] {
510
0
        numberToNameMap.map(\.value.proto)
511
0
    }
512
513
    /// Returns if the given name was reserved.
514
409
    internal func isReserved(name: UnsafeRawBufferPointer) -> Bool {
515
409
        guard !reservedNames.isEmpty,
516
409
            let baseAddress = name.baseAddress,
517
409
            let s = utf8ToString(bytes: baseAddress, count: name.count)
518
409
        else {
519
409
            return false
520
409
        }
521
0
        return reservedNames.contains(s)
522
409
    }
523
524
    /// Returns if the given number was reserved.
525
234
    internal func isReserved(number: Int32) -> Bool {
526
234
        for range in reservedRanges {
527
0
            if range.contains(number) {
528
0
                return true
529
0
            }
530
234
        }
531
234
        return false
532
234
    }
533
}
534
535
// The `_NameMap` (and supporting types) are only mutated during their initial
536
// creation, then for the lifetime of the a process they are constant. Swift
537
// 5.10 flags the generated `_protobuf_nameMap` usages as a problem
538
// (https://github.com/apple/swift-protobuf/issues/1560) so this silences those
539
// warnings since the usage has been deemed safe.
540
//
541
// https://github.com/apple/swift-protobuf/issues/1561 is also opened to revisit
542
// the `_NameMap` generally as it dates back to the days before Swift perferred
543
// the UTF-8 internal encoding.
544
extension _NameMap: Sendable {}
545
extension _NameMap.Name: @unchecked Sendable {}
546
extension InternPool: @unchecked Sendable {}