String interpolation is used everywhere in Swift — of course we would want to use it in the Regex
world. Let's consider the following snippet:
@RegexComponentBuilder
func makeSimpleXMLTagRegex(name: String) -> some RegexComponent<Substring> {
"<\(name)>"
}
It works as expected: for any tag name passed in, the function will return a new string with the correct format. And since the return type is some RegexComponent<Substring>
, the compiler can ensure it's only used by Regex, even though the underlying type is String
.
Now we want to make it more generic by allowing to match the tag name with Regex. With one simple change, we get the following:
@RegexComponentBuilder
func makeSimpleXMLTagRegex(name: some RegexComponent<Substring>) -> some RegexComponent<Substring> {
"<\(name)>"
}
It seems to work. The snippet compiles without breaking the API surface, because String
is a RegexComponent<Substring>
itself.
"<td>".wholeMatch(of: makeSimpleXMLTagRegex(name: #/td|tr/#)) // nil
So why? What happened?
If we break down the function, we find that it returns a String
for every output. If name
is String
or Substring
, it's interpolated like elsewhere. But if we pass in a Regex
or other RegexComponent
, we will get something like:
"<Regex<Substring>(program: _StringProcessing.Regex<Swift.Substring>.Program)>"
which basically cannot match anything, far from our expectation.
The fix is easy. We can break it down into pieces (much like how AttributedString
works under the hood), and let name
be freestanding instead of interpolated.
@RegexComponentBuilder
func makeSimpleXMLTagRegex(name: some RegexComponent<Substring>) -> some RegexComponent<Substring> {
"<"
name
">"
}
The fact is worrying, however, that we can easily get a wrong version which is simpler, looks natural, and, most importantly, doesn't trigger any warnings or errors. It even works for some cases, which means such error-prone code can possibly survive unit testing.
What can we do? I feels like interpolating RegexComponent
into a String
is a bad pattern that the compiler should warn about, but this may require going through Evolution.