How does Swift's @_semantics attribute make compile time checks and how to make one of my own?

4 weeks ago 29
ARTICLE AD BOX

These identifiers for @_semantics are all hardcoded into the compiler. You can see a list of them in SemanticAttrs.def.

These constants are then checked in various places. For example, there are os_log-related semantics that require the argument be a string interpolation. These are checked in ConstantnessSemaDiagonostics.cpp. See the calls to hasSemanticsAttr.

So to create your own @_semantics, you need to fork the compiler.

If you just want to do some custom compile-time checks, I'd suggest writing a macro. Suppose you have a function doSomething and you want to enforce the rule that callers must pass an integer literal, you would rename it to __doSomething so that it doesn't show up in documentation and callers don't accidentally call it. Then, write a macro #doSomething that expands to a call to __doSomething. In the macro implementation, check if the argument is an integer literal and emit diagnostics as you wish.

// ------------- Macro Declaration ------------- func __doSomething(_ i: Int) { // do something here... } @freestanding(expression) public macro doSomething(_ i: Int) = #externalMacro(module: "...", type: "DoSomethingMacro") // ------------- Macro Implementation ------------- enum DoSomethingMacro: ExpressionMacro { static func expansion(of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext) throws -> ExprSyntax { guard let firstArg = node.arguments.first else { throw MacroExpansionErrorMessage("Argument not found!") } if !firstArg.is(IntegerLiteralExprSyntax.self) { context.diagnose(Diagnostic( node: firstArg, message: MacroExpansionErrorMessage("Argument must be integer literal") )) } return "__doSomething(\(node.arguments.first))" } } // ------------- Caller ------------- let foo = 10 #doSomething(foo) // error here!

Of course, callers can still call __doSomething without using the macro, if they really wanted. Alas, such is the nature of macros.


You might notice that the list in SemanticAttrs.def does not include swiftui.requires_constant_range. This is because the Swift toolchain built into Xcode is not exactly the same as the one that is on the GitHub repo. You can try compiling this code with both Xcode and an open source version of Swift (installed by swiftly for example),

@_semantics("swiftui.requires_constant_range") func f(_ range: Range<Int>) {} let foo = 10 f(0..<foo)

Xcode will give you a warning, but the open source version will not.

Read Entire Article