Description
Describe the bug
It's possible to write a RegexBuilder (provided below) using the DSL and a CustomConsumingRegexComponent
which Swift type-checks and compiles, but then crashes at runtime, in the internals of the regex library itself, claiming that it can't cast the output of a regex to the expected value. I may have missed something in the documentation, but my understanding was that one of the big selling points of the RegexBuilder
library is that this is impossible.
As you'll see below, the custom component I've written (ZeroOrMoreOf
) is a fairly trivial one: it takes a RegexComponent as input, and matches as many times as possible, eventually producing an Array of the output of the regex it was given.
Steps To Reproduce
You should see this behavior when you compile the following code with Swift 5.7 and run it:
struct ZeroOrMoreOf<Part>: CustomConsumingRegexComponent where Part: RegexComponent {
init(@RegexComponentBuilder input: () -> Part) {
self.part = input()
}
func consuming(
_ input: String,
startingAt index: String.Index,
in bounds: Range<String.Index>
) throws -> (upperBound: String.Index, output: RegexOutput)? {
var output: [Part.RegexOutput] = []
var bounds = bounds
var upperBound = index
if let match = input[bounds].firstMatch(of: part) {
output.append(match.output)
bounds = match.range
upperBound = match.range.upperBound
}
if output.count > 0 {
return (upperBound, output)
} else {
throw Error("No matches found")
}
}
let part: Part
typealias RegexOutput = [Part.RegexOutput]
}
@RegexComponentBuilder
func whiteSpace() -> some RegexComponent<Substring> {
ZeroOrMore {
ChoiceOf {
CharacterClass.whitespace
CharacterClass.verticalWhitespace
}
}
}
struct Output {
let a: Substring
let b: Substring
}
let r = Regex {
ZeroOrMoreOf {
ChoiceOf {
ZeroOrMoreOf {
Capture {
Capture {
CharacterClass.word
}
Capture {
CharacterClass.word
}
} transform: {
Output(a: $0.1, b: $0.2)
}
}
whiteSpace()
}
}
}
let m = "tt".matches(of: r)
for o in m {
print(o.output)
}
Expected behavior
A regex written with the DSL that compiles, and does not use force casts, should not crash in the manner that this Regex crashes: inside the library, seemingly complaining that it doesn't have the type that it says it does.
Actual behavior: I get the following crash:
Could not cast value of type 'Swift.Substring' (0x1b8cdc268) to '(Swift.Substring, Swift.Substring, Swift.Substring)' (0x1b6f797c0).
2022-10-29 17:08:23.778967-0700 xctest[33061:1058964] Could not cast value of type 'Swift.Substring' (0x1b8cdc268) to '(Swift.Substring, Swift.Substring, Swift.Substring)' (0x1b6f797c0).
Could not cast value of type 'Swift.Substring' (0x1b8cdc268) to '(Swift.Substring, Swift.Substring, Swift.Substring)' (0x1b6f797c0).
Environment (please fill out the following information)
- OS: iOS 16
- Xcode Version/Tag/Branch: Xcode 14.0 (14A309), presumably Swift 5.7