Pitch: Introduce custom attributes


(Brent Royal-Gordon) #21

I think you could at least have every attribute be declared in the language (presumably in Policy.swift in the standard library), but treat certain attributes specially. (Step zero for this is probably something like "open Attr.def, write attribute @<name> { _builtinAttrID: <id> } for each entry, and make the compiler parse that". Then you gradually lean on _builtinAttrID less and less, but you might never get to not leaning on it at all.)


(Rodrigo Cardoso Buske) #22

I strongly disagree with your proposed syntax, I can see your intention, having @ kind of be the function, but that looks pretty bad to me, without mentioning it adds unnecessary parenthesis.

I like the idea of having #available use this. But again I disagree with your syntax.
Having it be called if would require another syntax for checking inside implementations (or a pretty weird call). where it currently is the same, such as

if #available(iOS 8.1, *) {

} else {

}

Also, would prefer to avoid String based API's whenever possible, and again less parenthesis (although the composition would be good). Something like:

@available(.iOS(version: .greaterThanOrEqualTo(8.1)))
class Foo {
    // implementation that can use iOS 8.0+ APIs
}

But a problem that brings, as seen by the "inside implementation" example is that it would require the custom attributes to be able to have a return type too, Bool in this case, which could be interesting, but opens another can of worms.

Just for completion, would like to add that I really liked your examples for "deriving" implementations, apart (again) from the specific syntax >.>


(Vinicius Vendramini) #23

Thanks everybody for the feedback. Here's a second version of the pitch.

I've come to agree with the sentiment that including custom attributes that are available at runtime might be a bit much. Therefore, I'll try to make a first proposal only for compile-time attribtues. I don't really understand the steps in the Swift compiler, but it seems to me that attributes should at least be available in libSyntax and SourceKit so that tools can officially deal with them. Any clarifications here are appreciated.

(As an aside, I've never done this before. Would it be better if I hosted the pitch in github or in a gist somewhere as it evolves?)

Introduce custom attributes

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 linter might allow users to mark declarations that the linter should ignore, while someone writing a JSON parser might let users mark their properties directly with the right coding keys. This proposal would allow for such use cases and many others that haven't yet been thought of.

Proposed solution

This document proposes adding the following to the language and the compiler:

  • A syntax for declaring new attributes, including:
    • A list of the types of declarations on which they can be used.
    • Associated values and default values (optionally).
  • The ability to use these new attributes to annotate declarations.

It deliberately does not include support for inspecting the attributes in runtime (see Alternatives Considered).

Detailed design

Syntax

Custom attributes would be declared using the following syntax:

<accessLevel> attribute[(listOfSupportedDeclarations)] @<attributeName>[(<associatedValues>)]

And used as in the examples:

// Defines a public attribute called codingKey
internal attribute(.variables) @codingKey(_ key: String = "key")

// Usage
import JSONFramework

class NetworkHandler {
	@codingKey("url_path") var url: URL
}
// Defines an internal attribute called ignoreRules
internal attribute(.functions, .variables, .classes) @ignoreRules(_ rules: [String], printWarnings: Bool = false)

// Usage
import LinterAttributes

@ignoreRules(["cyclomatic_complexity", "line_count"])
func a() {
	// ...
}

Usage

The declared attributes should be made public by both libSyntax and SourceKit, allowing developers to use them to access the attribute information.

Namespacing

Name conflicts can be solved with simple namespacing when needed:

import JSONFrameworkA
import JSONFrameworkB

struct Circle {
	@JSONFrameworkA.codingKey("circle_radius") var radius: Double
	@JSONFrameworkB.codingKey("color_name") 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

No effect.

Effect on API resilience

No effect.

Alternatives considered

Currently available workarounds

There's been discussion before on emulating some of this behavior using protocols, an approach that's more limited but currently feasible.

Runtime support

An initial version of this proposal suggested that attributes should be made available at runtime through the reflection API. This idea was positively received in the discussions but was ultimately left for a future proposal, which makes this proposal smaller and allows time to improve the reflection APIs before runtime attributes are considered.


(Dante Broggi) #24

May I suggest an alternate syntax:

// Defines an internal attribute called ignoreRules
internal attribute ignoreRules(_ rules: [String], printWarnings: Bool = false): FuncAttribute, VarAttribute, ClassAttribute

Explanation for changes:

  • We know it is an attribute from attribute, we don't need @ here.
  • The name is more important than what it applies to, put the name first.
  • Since the supported declarations were pushed to last,
    and are somewhat protocol-like, enhance the similarity.

(Tanner) #25

I think that's a good first step.

The pitch looks great, I like everything in there :+1: Just think the actual syntax could be tweaked a tiny bit.

I like these changes a lot :+1:

Maybe instead of looking like protocol conformance, we could make these look like the get / set modifiers in protocol properties? We could also use keyword names so there are no extra protocols to memorize.

// Defines an internal attribute called ignoreRules
internal attribute ignoreRules(_ rules: [String]) { func var class }