Skip to content

RegexBuilder w/ custom component "fails to cast," crashes #805

Open
@benpious

Description

@benpious

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions