URL macro

I've played around with the sample repo and built a simple macro to create compile time checked URLs.

Motivation

This is similar in spirit to compile time checked regular expressions. For a literal Regex the compiler makes sure that the regex has no syntactic errors by parsing it at compile time. In case of a syntax error the compiler emits an error (and does not produce any code).

When the regex parses without error a non-optional, non-throwing initializer is inserted. During runtime the Regex has to be parsed again to create the actual instance but it is known to not throw an error.

This behavior, where the compiler checks the validity of an initializer's arguments would be helpful for many other types, for example a SemVer type that checks for a valid semantic version.

In these cases macros seem like a good workaround (until we get statically checked arguments).

So the macro would take the provided string literal and, during compile time, check if it's valid.

let url = #URL("https://swift.org/") // fine: url is a non-optional URL
let bad = #URL("not a url") // emits error

This went very well and I'm happy with the result. Declaring and implementing the macro was straight forward and the emitted diagnostics are as expected. There's even unit test support which is nice and helpful in an environment with limited debug tooling.

With one detail I could need some help, though:

The macro implementation needs to access the string value of the passed literal. It is of type StringSegmentSyntax which can be converted to a String using segment.content.text. The result is the verbatim source text between the double quotes from the original string literal. In most cases this is fine.

But when there are escaped characters in the literal, like in "https://swift\u{2e}org/", the macro fails. (\u{2e} is the escaped ASCII '.' character.) With my limited experience in SwiftSyntax I have not managed to get an unescaped string from the StringSegmentSyntax.

15 Likes

Hey @nikolai.ruhe,

Are you still seeing this problem? I only just saw the post.

So if I understand you correctly, you would like to have a utility to which you can pass a string literal that might contain unicode escape sequences and have those replaced by the characters they represent? If this is the case, then the answer is that SwiftSyntax does not provide such a utility yet. If you would be interested in writing one and opening a pull request to GitHub - apple/swift-syntax: A set of Swift libraries for parsing, inspecting, generating, and transforming Swift source code. that would be a very welcome addition.

2 Likes

[...] that would be a very welcome addition.

Hi @ahoppen,

Thanks for the suggestion :slight_smile: I have looked into SwiftSyntax a bit and came up with this: StringLiteralExprSyntax/contentValue.

Since I'm not familiar with the architecture of SwiftSyntax there might be issues with placing and usage of code. It would be great if you could have a look before I make the PR on the SwiftSyntax repo.

1 Like

I just skimmed over your PR and it looks very good to me. Thank you :pray: Clever to re-use the lexer to get the escaped values. I’ll probably have a few comments but it’s easier to post them after you open a PR to the apple/swift-syntax repository. That way we’ll have all the conversation in a single place.

1 Like