diff --git a/Sources/_StringProcessing/Regex/AnyRegexOutput.swift b/Sources/_StringProcessing/Regex/AnyRegexOutput.swift index 0a76f2d86..ae8193804 100644 --- a/Sources/_StringProcessing/Regex/AnyRegexOutput.swift +++ b/Sources/_StringProcessing/Regex/AnyRegexOutput.swift @@ -381,7 +381,12 @@ extension AnyRegexOutput.ElementRepresentation { } var type: Any.Type { - content?.value.map { Swift.type(of: $0) } - ?? TypeConstruction.optionalType(of: Substring.self, depth: optionalDepth) + func wrapIfNecessary(_: U.Type) -> Any.Type { + TypeConstruction.optionalType(of: U.self, depth: optionalDepth) + } + + return content?.value.map { + _openExistential(Swift.type(of: $0), do: wrapIfNecessary) + } ?? TypeConstruction.optionalType(of: Substring.self, depth: optionalDepth) } } diff --git a/Tests/RegexBuilderTests/RegexDSLTests.swift b/Tests/RegexBuilderTests/RegexDSLTests.swift index c6f5fdaf5..3611ee2c0 100644 --- a/Tests/RegexBuilderTests/RegexDSLTests.swift +++ b/Tests/RegexBuilderTests/RegexDSLTests.swift @@ -1962,6 +1962,59 @@ extension RegexDSLTests { XCTAssertNotNil(clip.contains(pattern)) XCTAssertNotNil(clip2.contains(pattern)) } + + func testIssue83022() throws { + // Original report from https://github.com/swiftlang/swift/issues/83022 + // rdar://155710126 + let mixedNumberRegex = Regex { + // whole number + Optionally { + Capture { + OneOrMore(.digit) + } transform: { Int($0)! } + OneOrMore { " " } + } + // numerator + Capture { + OneOrMore(.digit) + } transform: { Int($0)! } + "/" + // denominator (modified to test for double optional) + Capture { + OneOrMore(.digit) + } transform: { Optional.some(Int($0)) } + } + + do { + let match = try XCTUnwrap(mixedNumberRegex.wholeMatch(in: "1 3/4")) + XCTAssertEqual(match.1, 1) + XCTAssertEqual(match.2, 3) + XCTAssertEqual(match.3, 4) + + let erasedMatch = Regex.Match(match) + XCTAssert(erasedMatch.output[0].type == Substring.self) + XCTAssert(erasedMatch.output[1].type == Int?.self) + XCTAssert(erasedMatch.output[2].type == Int.self) + XCTAssert(erasedMatch.output[3].type == Int??.self) + } + + do { + let match = try XCTUnwrap(mixedNumberRegex.wholeMatch(in: "3/4")) + XCTAssertNil(match.1) + XCTAssertEqual(match.2, 3) + XCTAssertEqual(match.3, 4) + + let erasedMatch = Regex.Match(match) + XCTAssert(erasedMatch.output[0].type == Substring.self) + XCTAssert(erasedMatch.output[2].type == Int.self) + XCTAssert(erasedMatch.output[3].type == Int??.self) + + XCTExpectFailure { + // `nil` value is interpreted as `Substring?` instead of `Int?` + XCTAssert(erasedMatch.output[1].type == Int?.self) + } + } + } } extension Unicode.Scalar {