In Swift 6.4 or later, a RegexBuilder pattern can hang when an unbounded quantifier repeats a body that can match the empty string, where that body begins with NegativeLookahead.
I've opened a corresponding issue and PR to resolve the issue in swift-experimental-string-processing. See below for a reproduction and a workaround.
The regression affects apps running on OS 27 built with Xcode 27, which includes Swift 6.4. Running apps built with Xcode 27 on OS 26 or earlier demonstrates the expected behavior.
Links:
- Issue: https://github.com/swiftlang/swift-experimental-string-processing/issues/865
- PR: https://github.com/swiftlang/swift-experimental-string-processing/pull/866
- FB23419149 and FB23179771
- https://forums.swift.org/t/regexbuilder-infinite-loop-when-nullable-capture-starts-with-negativelookahead/87713
Reproduction
In the reducer below, matching "A" repeatedly invokes the capture transform with an empty substring without advancing through the input.
import RegexBuilder
let regex = Regex {
ZeroOrMore {
Capture {
NegativeLookahead { "a" }
ZeroOrMore(.digit)
} transform: { String($0) } // invoked repeatedly with ""
}
}
_ = "A".matches(of: regex) // never returns
Reduced string form:
_ = try! Regex(#"(?:(?!a)\d*)*"#).firstMatch(in: "A") // never returns
The issue is in the same forward-progress class as PR #851, which skips a nullable quantification's child subtree. Lookaround groups need the same treatment.
The regression first appears in Swift 6.4-dev toolchains. I observed the issue in code running on iOS 27 beta 1 (24A5355q), then traced the regression to PR #849 in swift-experimental-string-processing.
Workaround
In the meantime, wrap the capture contents in Optionally { }:
import RegexBuilder
let digits = Regex {
NegativeLookahead { "a" }
ZeroOrMore(.digit)
}
let regex = Regex {
ZeroOrMore {
Capture {
Optionally {
digits
}
} transform: { String($0) }
}
}
_ = "A".matches(of: regex)