Skip to content

Commit 7107e29

Browse files
committed
Fix output type mismatch with RegexBuilder (swiftlang#626)
Some regex literals (and presumably other `Regex` instances) lose their output type information when used in a RegexBuilder closure due to the way the concatenating builder calls are overloaded. In particular, any output type with labeled tuples or where the sum of tuple components in the accumulated and new output types is greater than 10 will be ignored. Regex internals don't make this distinction, however, so there ends up being a mismatch between what a `Regex.Match` instance tries to produce and the output type of the outermost regex. For example, this code results in a crash, because `regex` is a `Regex<Substring>` but the match tries to produce a `(Substring, number: Substring)`: let regex = Regex { ZeroOrMore(.whitespace) /:(?<number>\d+):/ ZeroOrMore(.whitespace) } let match = try regex.wholeMatch(in: " :21: ") print(match!.output) To fix this, we add a new `ignoreCapturesInTypedOutput` DSLTree node to mark situations where the output type is discarded. This status is propagated through the capture list into the match's storage, which lets us produce the correct output type. Note that we can't just drop the capture groups when building the compiled program because (1) different parts of the regex might reference the capture group and (2) all capture groups are available if a developer converts the output to `AnyRegexOutput`. let anyOutput = AnyRegexOutput(match) // anyOutput[1] == "21" // anyOutput["number"] == Optional("21") Fixes swiftlang#625. rdar://104823356 Note: Linux seems to crash on different tests when the two customTest overloads have `internal` visibility or are called. Switching one of the functions to be generic over a RegexComponent works around the issue.
1 parent 6a7a17d commit 7107e29

19 files changed

+605
-239
lines changed

Package.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ let availabilityDefinition = PackageDescription.SwiftSetting.unsafeFlags([
88
"-define-availability",
99
"-Xfrontend",
1010
"SwiftStdlib 5.7:macOS 9999, iOS 9999, watchOS 9999, tvOS 9999",
11+
"-Xfrontend",
12+
"-define-availability",
13+
"-Xfrontend",
14+
"SwiftStdlib 5.8:macOS 9999, iOS 9999, watchOS 9999, tvOS 9999",
1115
])
1216

1317
/// Swift settings for building a private stdlib-like module that is to be used
@@ -87,7 +91,7 @@ let package = Package(
8791
name: "RegexBuilderTests",
8892
dependencies: ["_StringProcessing", "RegexBuilder", "TestSupport"],
8993
swiftSettings: [
90-
.unsafeFlags(["-Xfrontend", "-disable-availability-checking"])
94+
availabilityDefinition
9195
]),
9296
.testTarget(
9397
name: "DocumentationTests",

Sources/RegexBuilder/DSL.swift

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,3 +508,65 @@ extension Regex.Match {
508508
internal func makeFactory() -> _RegexFactory {
509509
_RegexFactory()
510510
}
511+
512+
/// These are special `accumulate` methods that wrap one or both components in
513+
/// a node that indicates that that their output types shouldn't be included in
514+
/// the resulting strongly-typed output type. This is required from a
515+
/// `buildPartialBlock` call where a component's output type is either ignored
516+
/// or not included in the resulting type. For example:
517+
///
518+
/// static func buildPartialBlock<W0, W1, C1, R0: RegexComponent, R1: RegexComponent>(
519+
/// accumulated: R0, next: R1
520+
/// ) -> Regex<(Substring, C1)> where R0.RegexOutput == W0, R1.RegexOutput == (W1, C1)
521+
///
522+
/// In this `buildPartialBlock` overload, `W0` isn't included in the
523+
/// resulting output type, even though it can match any output type, including
524+
/// a tuple. When `W0` matches a tuple type that doesn't match another overload
525+
/// (because of arity or labels) we need this "ignoring" variant so that we
526+
/// don't have a type mismatch when we ultimately cast the type-erased output
527+
/// to the expected type.
528+
@available(SwiftStdlib 5.7, *)
529+
extension _RegexFactory {
530+
/// Concatenates the `left` and `right` component, wrapping `right` to
531+
/// indicate that its output type shouldn't be included in the resulting
532+
/// strongly-typed output type.
533+
@_alwaysEmitIntoClient
534+
internal func accumulate<Output>(
535+
_ left: some RegexComponent,
536+
ignoringOutputTypeOf right: some RegexComponent
537+
) -> Regex<Output> {
538+
if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) {
539+
return accumulate(left, ignoreCapturesInTypedOutput(right))
540+
}
541+
return accumulate(left, right)
542+
}
543+
544+
/// Concatenates the `left` and `right` component, wrapping `left` to
545+
/// indicate that its output type shouldn't be included in the resulting
546+
/// strongly-typed output type.
547+
@_alwaysEmitIntoClient
548+
internal func accumulate<Output>(
549+
ignoringOutputTypeOf left: some RegexComponent,
550+
_ right: some RegexComponent
551+
) -> Regex<Output> {
552+
if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) {
553+
return accumulate(ignoreCapturesInTypedOutput(left), right)
554+
}
555+
return accumulate(left, right)
556+
}
557+
558+
/// Concatenates the `left` and `right` component, wrapping both sides to
559+
/// indicate that their output types shouldn't be included in the resulting
560+
/// strongly-typed output type.
561+
@_alwaysEmitIntoClient
562+
internal func accumulate<Output>(
563+
ignoringOutputTypeOf left: some RegexComponent,
564+
andAlso right: some RegexComponent
565+
) -> Regex<Output> {
566+
if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) {
567+
return accumulate(
568+
ignoreCapturesInTypedOutput(left), ignoreCapturesInTypedOutput(right))
569+
}
570+
return accumulate(left, right)
571+
}
572+
}

Sources/RegexBuilder/Variadics.swift

Lines changed: 22 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2021-2022 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2021-2023 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -20,7 +20,7 @@ extension RegexComponentBuilder {
2020
accumulated: R0, next: R1
2121
) -> Regex<(Substring, C1)> where R0.RegexOutput == W0, R1.RegexOutput == (W1, C1) {
2222
let factory = makeFactory()
23-
return factory.accumulate(accumulated, next)
23+
return factory.accumulate(ignoringOutputTypeOf: accumulated, next)
2424
}
2525
}
2626
@available(SwiftStdlib 5.7, *)
@@ -30,7 +30,7 @@ extension RegexComponentBuilder {
3030
accumulated: R0, next: R1
3131
) -> Regex<(Substring, C1, C2)> where R0.RegexOutput == W0, R1.RegexOutput == (W1, C1, C2) {
3232
let factory = makeFactory()
33-
return factory.accumulate(accumulated, next)
33+
return factory.accumulate(ignoringOutputTypeOf: accumulated, next)
3434
}
3535
}
3636
@available(SwiftStdlib 5.7, *)
@@ -40,7 +40,7 @@ extension RegexComponentBuilder {
4040
accumulated: R0, next: R1
4141
) -> Regex<(Substring, C1, C2, C3)> where R0.RegexOutput == W0, R1.RegexOutput == (W1, C1, C2, C3) {
4242
let factory = makeFactory()
43-
return factory.accumulate(accumulated, next)
43+
return factory.accumulate(ignoringOutputTypeOf: accumulated, next)
4444
}
4545
}
4646
@available(SwiftStdlib 5.7, *)
@@ -50,7 +50,7 @@ extension RegexComponentBuilder {
5050
accumulated: R0, next: R1
5151
) -> Regex<(Substring, C1, C2, C3, C4)> where R0.RegexOutput == W0, R1.RegexOutput == (W1, C1, C2, C3, C4) {
5252
let factory = makeFactory()
53-
return factory.accumulate(accumulated, next)
53+
return factory.accumulate(ignoringOutputTypeOf: accumulated, next)
5454
}
5555
}
5656
@available(SwiftStdlib 5.7, *)
@@ -60,7 +60,7 @@ extension RegexComponentBuilder {
6060
accumulated: R0, next: R1
6161
) -> Regex<(Substring, C1, C2, C3, C4, C5)> where R0.RegexOutput == W0, R1.RegexOutput == (W1, C1, C2, C3, C4, C5) {
6262
let factory = makeFactory()
63-
return factory.accumulate(accumulated, next)
63+
return factory.accumulate(ignoringOutputTypeOf: accumulated, next)
6464
}
6565
}
6666
@available(SwiftStdlib 5.7, *)
@@ -70,7 +70,7 @@ extension RegexComponentBuilder {
7070
accumulated: R0, next: R1
7171
) -> Regex<(Substring, C1, C2, C3, C4, C5, C6)> where R0.RegexOutput == W0, R1.RegexOutput == (W1, C1, C2, C3, C4, C5, C6) {
7272
let factory = makeFactory()
73-
return factory.accumulate(accumulated, next)
73+
return factory.accumulate(ignoringOutputTypeOf: accumulated, next)
7474
}
7575
}
7676
@available(SwiftStdlib 5.7, *)
@@ -80,7 +80,7 @@ extension RegexComponentBuilder {
8080
accumulated: R0, next: R1
8181
) -> Regex<(Substring, C1, C2, C3, C4, C5, C6, C7)> where R0.RegexOutput == W0, R1.RegexOutput == (W1, C1, C2, C3, C4, C5, C6, C7) {
8282
let factory = makeFactory()
83-
return factory.accumulate(accumulated, next)
83+
return factory.accumulate(ignoringOutputTypeOf: accumulated, next)
8484
}
8585
}
8686
@available(SwiftStdlib 5.7, *)
@@ -90,7 +90,7 @@ extension RegexComponentBuilder {
9090
accumulated: R0, next: R1
9191
) -> Regex<(Substring, C1, C2, C3, C4, C5, C6, C7, C8)> where R0.RegexOutput == W0, R1.RegexOutput == (W1, C1, C2, C3, C4, C5, C6, C7, C8) {
9292
let factory = makeFactory()
93-
return factory.accumulate(accumulated, next)
93+
return factory.accumulate(ignoringOutputTypeOf: accumulated, next)
9494
}
9595
}
9696
@available(SwiftStdlib 5.7, *)
@@ -100,7 +100,7 @@ extension RegexComponentBuilder {
100100
accumulated: R0, next: R1
101101
) -> Regex<(Substring, C1, C2, C3, C4, C5, C6, C7, C8, C9)> where R0.RegexOutput == W0, R1.RegexOutput == (W1, C1, C2, C3, C4, C5, C6, C7, C8, C9) {
102102
let factory = makeFactory()
103-
return factory.accumulate(accumulated, next)
103+
return factory.accumulate(ignoringOutputTypeOf: accumulated, next)
104104
}
105105
}
106106
@available(SwiftStdlib 5.7, *)
@@ -110,7 +110,7 @@ extension RegexComponentBuilder {
110110
accumulated: R0, next: R1
111111
) -> Regex<(Substring, C1, C2, C3, C4, C5, C6, C7, C8, C9, C10)> where R0.RegexOutput == W0, R1.RegexOutput == (W1, C1, C2, C3, C4, C5, C6, C7, C8, C9, C10) {
112112
let factory = makeFactory()
113-
return factory.accumulate(accumulated, next)
113+
return factory.accumulate(ignoringOutputTypeOf: accumulated, next)
114114
}
115115
}
116116
@available(SwiftStdlib 5.7, *)
@@ -565,123 +565,112 @@ extension RegexComponentBuilder {
565565
}
566566
@available(SwiftStdlib 5.7, *)
567567
extension RegexComponentBuilder {
568-
@available(SwiftStdlib 5.7, *)
569568
@_alwaysEmitIntoClient
570569
public static func buildPartialBlock<W0, R0: RegexComponent, R1: RegexComponent>(
571570
accumulated: R0, next: R1
572571
) -> Regex<Substring> where R0.RegexOutput == W0 {
573572
let factory = makeFactory()
574-
return factory.accumulate(accumulated, next)
573+
return factory.accumulate(ignoringOutputTypeOf: accumulated, andAlso: next)
575574
}
576575
}
577576
@available(SwiftStdlib 5.7, *)
578577
extension RegexComponentBuilder {
579-
@available(SwiftStdlib 5.7, *)
580578
@_alwaysEmitIntoClient
581579
public static func buildPartialBlock<W0, C0, R0: RegexComponent, R1: RegexComponent>(
582580
accumulated: R0, next: R1
583581
) -> Regex<(Substring, C0)> where R0.RegexOutput == (W0, C0) {
584582
let factory = makeFactory()
585-
return factory.accumulate(accumulated, next)
583+
return factory.accumulate(accumulated, ignoringOutputTypeOf: next)
586584
}
587585
}
588586
@available(SwiftStdlib 5.7, *)
589587
extension RegexComponentBuilder {
590-
@available(SwiftStdlib 5.7, *)
591588
@_alwaysEmitIntoClient
592589
public static func buildPartialBlock<W0, C0, C1, R0: RegexComponent, R1: RegexComponent>(
593590
accumulated: R0, next: R1
594591
) -> Regex<(Substring, C0, C1)> where R0.RegexOutput == (W0, C0, C1) {
595592
let factory = makeFactory()
596-
return factory.accumulate(accumulated, next)
593+
return factory.accumulate(accumulated, ignoringOutputTypeOf: next)
597594
}
598595
}
599596
@available(SwiftStdlib 5.7, *)
600597
extension RegexComponentBuilder {
601-
@available(SwiftStdlib 5.7, *)
602598
@_alwaysEmitIntoClient
603599
public static func buildPartialBlock<W0, C0, C1, C2, R0: RegexComponent, R1: RegexComponent>(
604600
accumulated: R0, next: R1
605601
) -> Regex<(Substring, C0, C1, C2)> where R0.RegexOutput == (W0, C0, C1, C2) {
606602
let factory = makeFactory()
607-
return factory.accumulate(accumulated, next)
603+
return factory.accumulate(accumulated, ignoringOutputTypeOf: next)
608604
}
609605
}
610606
@available(SwiftStdlib 5.7, *)
611607
extension RegexComponentBuilder {
612-
@available(SwiftStdlib 5.7, *)
613608
@_alwaysEmitIntoClient
614609
public static func buildPartialBlock<W0, C0, C1, C2, C3, R0: RegexComponent, R1: RegexComponent>(
615610
accumulated: R0, next: R1
616611
) -> Regex<(Substring, C0, C1, C2, C3)> where R0.RegexOutput == (W0, C0, C1, C2, C3) {
617612
let factory = makeFactory()
618-
return factory.accumulate(accumulated, next)
613+
return factory.accumulate(accumulated, ignoringOutputTypeOf: next)
619614
}
620615
}
621616
@available(SwiftStdlib 5.7, *)
622617
extension RegexComponentBuilder {
623-
@available(SwiftStdlib 5.7, *)
624618
@_alwaysEmitIntoClient
625619
public static func buildPartialBlock<W0, C0, C1, C2, C3, C4, R0: RegexComponent, R1: RegexComponent>(
626620
accumulated: R0, next: R1
627621
) -> Regex<(Substring, C0, C1, C2, C3, C4)> where R0.RegexOutput == (W0, C0, C1, C2, C3, C4) {
628622
let factory = makeFactory()
629-
return factory.accumulate(accumulated, next)
623+
return factory.accumulate(accumulated, ignoringOutputTypeOf: next)
630624
}
631625
}
632626
@available(SwiftStdlib 5.7, *)
633627
extension RegexComponentBuilder {
634-
@available(SwiftStdlib 5.7, *)
635628
@_alwaysEmitIntoClient
636629
public static func buildPartialBlock<W0, C0, C1, C2, C3, C4, C5, R0: RegexComponent, R1: RegexComponent>(
637630
accumulated: R0, next: R1
638631
) -> Regex<(Substring, C0, C1, C2, C3, C4, C5)> where R0.RegexOutput == (W0, C0, C1, C2, C3, C4, C5) {
639632
let factory = makeFactory()
640-
return factory.accumulate(accumulated, next)
633+
return factory.accumulate(accumulated, ignoringOutputTypeOf: next)
641634
}
642635
}
643636
@available(SwiftStdlib 5.7, *)
644637
extension RegexComponentBuilder {
645-
@available(SwiftStdlib 5.7, *)
646638
@_alwaysEmitIntoClient
647639
public static func buildPartialBlock<W0, C0, C1, C2, C3, C4, C5, C6, R0: RegexComponent, R1: RegexComponent>(
648640
accumulated: R0, next: R1
649641
) -> Regex<(Substring, C0, C1, C2, C3, C4, C5, C6)> where R0.RegexOutput == (W0, C0, C1, C2, C3, C4, C5, C6) {
650642
let factory = makeFactory()
651-
return factory.accumulate(accumulated, next)
643+
return factory.accumulate(accumulated, ignoringOutputTypeOf: next)
652644
}
653645
}
654646
@available(SwiftStdlib 5.7, *)
655647
extension RegexComponentBuilder {
656-
@available(SwiftStdlib 5.7, *)
657648
@_alwaysEmitIntoClient
658649
public static func buildPartialBlock<W0, C0, C1, C2, C3, C4, C5, C6, C7, R0: RegexComponent, R1: RegexComponent>(
659650
accumulated: R0, next: R1
660651
) -> Regex<(Substring, C0, C1, C2, C3, C4, C5, C6, C7)> where R0.RegexOutput == (W0, C0, C1, C2, C3, C4, C5, C6, C7) {
661652
let factory = makeFactory()
662-
return factory.accumulate(accumulated, next)
653+
return factory.accumulate(accumulated, ignoringOutputTypeOf: next)
663654
}
664655
}
665656
@available(SwiftStdlib 5.7, *)
666657
extension RegexComponentBuilder {
667-
@available(SwiftStdlib 5.7, *)
668658
@_alwaysEmitIntoClient
669659
public static func buildPartialBlock<W0, C0, C1, C2, C3, C4, C5, C6, C7, C8, R0: RegexComponent, R1: RegexComponent>(
670660
accumulated: R0, next: R1
671661
) -> Regex<(Substring, C0, C1, C2, C3, C4, C5, C6, C7, C8)> where R0.RegexOutput == (W0, C0, C1, C2, C3, C4, C5, C6, C7, C8) {
672662
let factory = makeFactory()
673-
return factory.accumulate(accumulated, next)
663+
return factory.accumulate(accumulated, ignoringOutputTypeOf: next)
674664
}
675665
}
676666
@available(SwiftStdlib 5.7, *)
677667
extension RegexComponentBuilder {
678-
@available(SwiftStdlib 5.7, *)
679668
@_alwaysEmitIntoClient
680669
public static func buildPartialBlock<W0, C0, C1, C2, C3, C4, C5, C6, C7, C8, C9, R0: RegexComponent, R1: RegexComponent>(
681670
accumulated: R0, next: R1
682671
) -> Regex<(Substring, C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)> where R0.RegexOutput == (W0, C0, C1, C2, C3, C4, C5, C6, C7, C8, C9) {
683672
let factory = makeFactory()
684-
return factory.accumulate(accumulated, next)
673+
return factory.accumulate(accumulated, ignoringOutputTypeOf: next)
685674
}
686675
}
687676

@@ -6884,7 +6873,3 @@ extension TryCapture {
68846873
self.init(factory.captureOptional(componentBuilder(), reference._raw, transform))
68856874
}
68866875
}
6887-
6888-
6889-
6890-
// END AUTO-GENERATED CONTENT

Sources/VariadicsGenerator/VariadicsGenerator.swift

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ struct VariadicsGenerator: ParsableCommand {
132132
//
133133
// This source file is part of the Swift.org open source project
134134
//
135-
// Copyright (c) 2021-2022 Apple Inc. and the Swift project authors
135+
// Copyright (c) 2021-2023 Apple Inc. and the Swift project authors
136136
// Licensed under Apache License v2.0 with Runtime Library Exception
137137
//
138138
// See https://swift.org/LICENSE.txt for license information
@@ -262,7 +262,20 @@ struct VariadicsGenerator: ParsableCommand {
262262
accumulated: R0, next: R1
263263
) -> \(regexTypeName)<\(matchType)> \(whereClause) {
264264
let factory = makeFactory()
265+
266+
""")
267+
if leftArity == 0 {
268+
output("""
269+
return factory.accumulate(ignoringOutputTypeOf: accumulated, next)
270+
271+
""")
272+
} else {
273+
output("""
265274
return factory.accumulate(accumulated, next)
275+
276+
""")
277+
}
278+
output("""
266279
}
267280
}
268281
@@ -274,7 +287,6 @@ struct VariadicsGenerator: ParsableCommand {
274287
output("""
275288
\(defaultAvailableAttr)
276289
extension \(concatBuilderName) {
277-
\(defaultAvailableAttr)
278290
@_alwaysEmitIntoClient
279291
public static func buildPartialBlock<W0
280292
""")
@@ -308,7 +320,20 @@ struct VariadicsGenerator: ParsableCommand {
308320
output("""
309321
{
310322
let factory = makeFactory()
311-
return factory.accumulate(accumulated, next)
323+
324+
""")
325+
if leftArity == 0 {
326+
output("""
327+
return factory.accumulate(ignoringOutputTypeOf: accumulated, andAlso: next)
328+
329+
""")
330+
} else {
331+
output("""
332+
return factory.accumulate(accumulated, ignoringOutputTypeOf: next)
333+
334+
""")
335+
}
336+
output("""
312337
}
313338
}
314339

0 commit comments

Comments
 (0)