PureXML/Sources/XPath/CoreFunctions.swift
Mihaela Mihaljevic 36b2a73637
chore(concurrency): justify every @unchecked Sendable / nonisolated(unsafe) (#387)
The rules-swift concurrency rule's acceptance check requires every `@unchecked
Sendable` and `nonisolated(unsafe)` to carry an adjacent comment explaining why
the race is acceptable; an unjustified one is a violation. An audit found six
escape hatches whose comment explained the type but not the race:

- The four XPath `FunctionTable` statics (`CoreFunctions`, `StringFunctions`,
  `NodeFunctions`, `Evaluator.library`): each is an immutable, build-once lookup
  table never mutated after initialization, so concurrent reads race on nothing;
  `FunctionTable` is un-`Sendable` only because it stores closures.
- `Validation.Validator`: immutable after `init` (every stored validation list is
  a `let`); the erased `AnyValidation` closures are simply not provably `Sendable`.
- The test `ResultBox`: written once by the worker thread before its semaphore
  signals and read only after the wait, so the write happens-before the read.

Comments only; no behaviour change. `LoaderBox` and `ElementStorage` were already
justified. swiftformat/swiftlint --strict and the full suite stay green.
2026-06-29 15:12:02 +02:00

34 lines
1.7 KiB
Swift

extension PureXML.XPath {
/// The core XPath functions tied to the four-type model and predicates:
/// `last`, `position`, `count`, `not`, `true`, `false`, `boolean`, `number`,
/// and `string`. The broader library (string, node, and number functions)
/// extends this table.
enum CoreFunctions {
// nonisolated(unsafe): an immutable, build-once table never mutated after
// initialization, so concurrent reads are safe; `FunctionTable` is only
// un-`Sendable` because it stores closures.
nonisolated(unsafe) static let table = FunctionTable([
"last": { _, context in .number(Double(context.size)) },
"position": { _, context in .number(Double(context.position)) },
"count": { arguments, _ in
guard let nodes = arguments.first?.nodes else {
throw PureXML.XPath.QueryError.invalidArguments("count() requires a node-set")
}
return .number(Double(nodes.count))
},
"not": { arguments, _ in .boolean(!(arguments.first?.boolean ?? false)) },
"true": { _, _ in .boolean(true) },
"false": { _, _ in .boolean(false) },
"boolean": { arguments, _ in .boolean(arguments.first?.boolean ?? false) },
"number": { arguments, context in
if let first = arguments.first { return .number(first.number) }
return .number(PureXML.XPath.Value.parseNumber(context.node.stringValue))
},
"string": { arguments, context in
if let first = arguments.first { return .string(first.string) }
return .string(context.node.stringValue)
},
])
}
}