Swift Conditional Compilation for Attributes

Apr 03, 2023#swift

Swift 5.8 introduces conditional attributes, using conditional directive hasAttribute(AttributeName), which help simplify code that needs to be conditionally compiled based on the presence or absence of certain attributes.

Now the #if checks can surround attributes on a declaration wherever they appear, eliminating the need to clone a declaration just to adopt a new attribute.

#if hasAttribute(preconcurrency)
@preconcurrency
#endif
protocol P: Sendable {
  func f()
  func g()
}

Before this feature, conditional compilation can be used to address this problem, but the result is verbose and unsatisfactory. Moreover, the availability of some attributes can depend not on compiler version, but on platform and configuration flags. Although these are small issues in isolation, they make adopting new attributes in existing code harder than it needs to be.

Using hasAttribute is relatively straightforward, but there are a few rules:

Language attributes only

The hasAttribute only considers attributes that are part of the language. While the built-in attribute that enables the feature will be recognized by hasAttribute (e.g., hasAttribute(propertyWrapper) will evaluate true), the custom attribute will not (e.g., hasAttribute(MyWrapper) will evaluate false).

An attribute provides additional information about the declaration or type. Some examples of language attributes are:

  • @available: indicates a declaration’s life cycle relative to certain Swift language versions or certain platforms and operating system versions.
  • @discardableResult: indicates that the compiler shouldn’t generate a warning if the return value of a function is unused.
  • @objc: exposes a declaration to Objective-C code.
  • @propertyWrapper: defines a custom attribute that can be applied to a property to modify its behavior.

Unknown attribute name can still be parsed

A conditionally-compiled branch based on #if hasAttribute(UnknownAttributeName) can still be parsed by an existing compiler, even though it will not be applied to the declaration because it isn’t understood:

#if hasAttribute(UnknownAttributeName)
@UnknownAttributeName
#endif
func f()