Hi all!
I'd like to start a real discussion on a topic that's already been brought up several times (see below). It's come to my attention that implementing it might cause issues with the ABI (as it proposes changes to the reflection API), so it might be better for us to discuss it before it's too late (if it isn't already!).
I haven't given this that much thought so far, so any and all comments and suggestions are much appreciated.
Introduce custom attributes
- Proposal: SE-NNNN
- Author: Vini Vendramini
- Review Manager: TBD
- Status: Pitch only
Introduction
Swift currently supports marking declarations with attributes, such as @objc
and @deprecated
. The compiler uses these attributes to change the way it interprets the corresponding code. However, there is no support for declaring and using new attributes to suit the needs of a project or library. This proposal aims to change that.
Previous discussions on the subject:
Motivation
Adding custom attributes to Swift would enable programmers to explore and use new methods of meta-programming. For instance, someone writing a UI library might want their users to mark properties that can be animated, while someone writing a linter might allow users to mark declarations that the linter should ignore. This proposal would allow for such use cases and many others that haven't yet been thought of.
Proposed solution
This proposal includes adding the following to the language and the compiler:
- A syntax for declaring new attributes, including possibly associated values and default values.
- The ability to use these new attributes to annotate declarations.
- Support for inspecting attributes in runtime using the reflection API.
Detailed design
Custom attributes would be declared using the following syntax:
<accessLevel> attribute[(availableAtRuntime)] @<attributeName>[(<associatedValues>)]
For example:
// Defines an attribute that's public and available at runtime (i.e. should be accessible from the reflection APIs) called animatable
public attribute(availableAtRuntime) @animatable
// Usage
import MyUIFramework
struct Circle {
@animatable var radius: Double
}
// Defines an attribute that's internal but not available at runtime (i.e. only shows up in the source code) called ignoreRules, which contains associated values
internal attribute @ignoreRules(_ rules: [String], printWarnings: Bool = false)
// Usage
import LinterAttributes
@ignoreRules(["cyclomatic_complexity", "line_count"])
func a() {
// ...
}
Runtime Access
Certain uses of custom attributes might need runtime access to work. This access will be provided as an addition the reflection API, as in the following example:
class A {
@animatable var x: Int = 0
}
let mirror = Mirror(reflecting: A())
for child in mirror.childrenWithAttributes {
print("Property name:", child.label) // "x"
print("Property value:", child.value) // 0
print("Property attributes:", child.attributes) // (name: "animatable", associatedValues: [String: Any]())
}
Namespacing
Name conflicts can be solved with simple namespacing when needed:
import UIFrameworkA
import UIFrameworkB
struct Circle {
@UIFrameworkA.animatable var radius: Double
@UIFrameworkB.animatable var color: Color
}
Annotating types
The Swift compiler supports annotations for types, as it enables the compiler to provide extra guarantees and features regarding those types. However, it isn't clear at the moment how custom attributes for types would be useful, and therefore it is out of the scope of this proposal.
Source compatibility
This proposal is purely additive.
Effect on ABI stability
This feature would change existing reflection APIs, but it is not clear in which ways. Help in this topic would be appreciated.
Effect on API resilience
Also unknown.
Alternatives considered
There's been discussion before on emulating some of this behavior using protocols, an approach that's more limited but currently feasible. No other alternatives were considered.