Skip to content

Commit 3656fd8

Browse files
committed
Start of line in negative lookahead optimization fix
Don't use the content of negative lookaheads when determining whether a start-of-line assertion can require that match has to start at the beginning of the subject. Fixes swiftlang/swift#81789. rdar://152119639
1 parent a1a5191 commit 3656fd8

File tree

2 files changed

+22
-1
lines changed

2 files changed

+22
-1
lines changed

Sources/_StringProcessing/Regex/DSLTree.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,10 @@ extension DSLTree.Node {
791791

792792
// Groups (and other parent nodes) defer to the child.
793793
case .nonCapturingGroup(let kind, let child):
794+
// Don't let a negative lookahead affect this - need to continue to next sibling
795+
if kind.isNegativeLookahead {
796+
return nil
797+
}
794798
options.beginScope()
795799
defer { options.endScope() }
796800
if case .changeMatchingOptions(let sequence) = kind.ast {
@@ -902,6 +906,10 @@ extension DSLTree {
902906
public static var negativeLookahead: Self {
903907
.init(ast: .negativeLookahead)
904908
}
909+
910+
internal var isNegativeLookahead: Bool {
911+
self.ast == .negativeLookahead
912+
}
905913
}
906914

907915
@_spi(RegexBuilder)

Tests/RegexTests/MatchTests.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func _firstMatch(
4646
) throws -> (String, [String?])? {
4747
var regex = try Regex(regexStr, syntax: syntax).matchingSemantics(semanticLevel)
4848
let result = try regex.firstMatch(in: input)
49-
49+
5050
func validateSubstring(_ substringInput: Substring) throws {
5151
// Sometimes the characters we add to a substring merge with existing
5252
// string members. This messes up cross-validation, so skip the test.
@@ -1629,6 +1629,14 @@ extension RegexTests {
16291629
// engines generally enforce that lookbehinds are fixed width
16301630
firstMatchTest(
16311631
#"\d{3}(?<!USD\d{3})"#, input: "Price: JYP100", match: "100", xfail: true)
1632+
1633+
// Assertions inside negative lookahead
1634+
firstMatchTest(
1635+
#"(?!\b)(With)"#, input: "dispatchWithName", match: "With")
1636+
firstMatchTest(
1637+
#"(?!^)(With)"#, input: "dispatchWithName", match: "With")
1638+
firstMatchTest(
1639+
#"(?!\s)^dispatch"#, input: "dispatchWithName", match: "dispatch")
16321640
}
16331641

16341642
func testMatchAnchors() throws {
@@ -2875,6 +2883,11 @@ extension RegexTests {
28752883
("\r\n", "\r\n")
28762884
)
28772885
}
2886+
2887+
func testIssue81789() throws {
2888+
let matches = "dispatchWithName".matches(of: #/(?!^)(With(?!No)|For|In|At|To)(?=[A-Z])/#)
2889+
XCTAssert(matches[0].output == ("With", "With"))
2890+
}
28782891

28792892
func testNSRECompatibility() throws {
28802893
// NSRE-compatibility includes scalar matching, so `[\r\n]` should match

0 commit comments

Comments
 (0)