swiftlang / swiftlang/swift-experimental-string-processing
RegexBuilder w/ custom component "fails to cast," crashes
Nobody has claimed this yet.
- Dominant language
- Swift
- Stars
- 308
- Forks
- 52
- Avg merge
- 12h 45m
- Merged PRs (30d)
- 2
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
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reproducing the sample with Swift 5.7, then trace how CustomConsumingRegexComponent.consuming propagates its output through the nested RegexBuilder captures used by matches(of:). Add regression coverage for this composition and verify that the compiled regex no longer crashes at runtime and returns the expected output.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- swift
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100