Swift: type erased Regex builder

78 views Asked by At

I have the following function that uses Swift 5.7's Regex builder:

static func numberStartingWith6(strictLength: Bool) -> some RegexComponent  {
        let myRegex = Regex {
            
            Optionally {
                "6"
                
                if strictLength {
                    Repeat(CharacterClass.digit, count: 9)
                }
                else {
                    Repeat(0...8) {
                        CharacterClass.digit
                    }
                }
            
            }
        }
        
       

but i get the following syntax error at the Optionally type:

Type '() -> ()' cannot conform to 'RegexComponent'

I tried to figure out the underlying type of the myRegex variable in the function above and it was: Regex<Regex<Optionally<Substring>. RegexOutput>. RegexOutput>

How to declare the correct return type in the function's signature so that I don't care about the regex builder's underlying type (i.e. type erasure).

1

There are 1 answers

0
Sweeper On BEST ANSWER

RegexComponentBuilder doesn't support if statements. There is no buildEither or buildIf methods.

The error is rather confusing. Since you used an if statement in the closure, the compiler no longer thinks this is a @RegexComponentBuilder closure, and tries to match the other initialiser overloads. It found this overload that takes a RegexComponent, and says the closure (of type () -> ()) can't be converted to that.

if statements are not supported possibly because if the branches capture different things, the Output type of the resulting regex is going to be very complicated.

For example, if you have

if x {
    // here you capture an Int, a Substring, and a Double
} else if y {
    // here you capture an Double, a Float, and a Date
} else {
    // here you capture a Decimal, a Boolean and a Substring

Then the Output type of the regex would be

(Substring, Int?, Substring?, Double?, Double?, Float?, Date?, Decimal?, Boolean?, Substring?)

or

(Substring, (Int, Substring, Double)?, (Double, Float, Date)?, (Decimal, Boolean, Substring)?)

The amount of buildEither and buildIf overloads needed for this is rather unmanageable.

You can write a buildEither yourself if you only capture the same types in each branch:

// this won't work if you capture different types in each branch of an if
extension RegexComponentBuilder {
    static func buildEither<T>(first component: Regex<T>) -> Regex<T> {
        buildPartialBlock(first: component)
    }
    
    public static func buildEither<T>(second component: Regex<T>) -> Regex<T> {
        buildPartialBlock(first: component)
    }
}

That said, your code does not need an if statement. Just do:

Repeat(strictLength ? 9...9 : 0...8) {
    CharacterClass.digit
}

Or if the regex component in each branch is a different type, you can just declare a let:

Regex {
    
    Optionally {
        "6"
        let temp: any RegexComponent<Substring /* or any other output you want*/> = {
            if strictLength {
                return ChoiceOf {
                    "a"
                    CharacterClass.digit
                }
            } else {
                return Optionally {
                    "b"
                }
            }
        }()
        
        // then use temp somewhere...
        temp
        
    }
}