Creating a Regex `ChoiceOf` from an array

I have a constant array like this, and I want to make a regex that matches any element in that list.

let digits: [Substring] = [
  "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"
]

I initially tried just ChoiceOf(digits), but that initializer doesn't exist. So I tried a manual for loop:

ChoiceOf {
  for digit in digits {
    digit
  }
}

And I got a whole bunch of confusing errors at once. One of them ("generic parameter 'Output' could not be inferred") makes sense, but the other two seem completely unrelated:

/main.swift:11:5: error: referencing initializer 'init(_:_:)' on 'RegexComponent' requires the types 'ChoiceOf<Output>' and 'CharacterClass' be equivalent
    ChoiceOf {
    ^
RegexBuilder.RegexComponent:2:11: note: where 'Self' = 'ChoiceOf<Output>'
extension RegexComponent where Self == CharacterClass {
          ^
/main.swift:11:14: error: trailing closure passed to parameter of type 'CharacterClass' that does not accept a closure
    ChoiceOf {
             ^
RegexBuilder.RegexComponent:3:12: note: 'init(_:_:)' declared here
    public init(_ first: CharacterClass, _ rest: CharacterClass...)

What is going on here? Why did it choose this completely unrelated initializer for a different type, and how do I make the ChoiceOf? In a case like this, is there a better option than just manually copying the array into the @AlternationBuilder closure?

The builder syntax for ChoiceOf doesn't include support for using arrays, likely because of the interaction with any captured types. The capture types from each of the sub-expressions in a ChoiceOf block are concatenated, which is possible with consecutive statements, but not from an array in the general case (since the output type would depend on the length of the array).

For this use case, where you aren't worried about capturing, and just have a series of simple regexes to alternate, we should be able to support that. I think we should also be able to have a version that returns a Regex<AnyRegexOutput>, since then the number and type of capture groups from the array is erased.

extension AlternationBuilder {
  public static func buildArray<R: RegexComponent>(_ components: [R]) 
    -> ChoiceOf<Substring> 
    where R.RegexOutput == Substring 

  @_disfavoredOverload
  public static func buildArray<R: RegexComponent>(_ components: [R]) 
    -> ChoiceOf<AnyRegexOutput>
}

Would you mind filing an issue here with that request?

(I'm not sure why the errors were so unhelpful – that would be a separate issue for the type checker in the apple/swift repo.)

2 Likes

Thanks, just submitted Creating a `ChoiceOf` from an array · Issue #701 · apple/swift-experimental-string-processing · GitHub

1 Like

Would it make sense to have the first overload work on any array of “all components have the same output”? Overload rules should already correctly prefer that over the other one only when it’s specifically available, even without the attribute.

1 Like