(I sort of answered my own question while writing this up, consider this a pre-pre-rfc train of thought.)
Tracking issue: rust#44690
Reference: https://doc.rust-lang.org/reference/attributes.html (out of date)
With rfc#2539 merged, we have I’ve seen a semi-formal definition of what the current meta syntax is (pest syntax):
attribute = { "#" ~ "[" ~ meta ~ "]" }
meta = { meta_kv | meta_list | meta_word }
meta_word = { Ident }
// Ident := identifier
meta_list = { meta_word ~ "(" ~ TokenStream ~ ")" }
// TokenStream := sequence of tokens where delimiters match
meta_kv = { meta_word ~ "=" ~ TokenTree }
// TokenTree := identifer | literal | punctuation mark | delimited TokenStream
This is the #![feature(unrestricted_attribute_tokens)]
grammar, not the currently stable one. Currently stable is closer to:
attribute = { "#" ~ "[" ~ meta ~ "]" }
meta = { meta_kv | meta_list | meta_word }
meta_word = { Path }
// Path := non generic path
meta_list = { meta_word ~ "(" ~ CommaSeparatedList(meta)? ~ ")" }
meta_kv = { meta_word ~ "=" ~ Literal }
// Literal := (raw) (byte) string literal | (byte) char literal | unsuffixed number literal
(Yes, #[namespaced(hey::jude = 1968)]
is valid. This is likely due to (a side effect of?) scoped lints.)
The tracking issue is focused on attributes for tools (e.g. #[rustfmt::skip]
), so the remaining ungating of attribute syntax locked behind unrestricted_attribute_tokens
probably deserves to be pulled out into a new tracking issue.
Recently when writing derives I’ve been running into a large number of cases where #[namespace(key = path::to::thing)]
would be very useful. I personally would even prefer #[namespace(key = (path::to::thing))]
over the current #[namespace(key = "path::to::thing")
or #[namespace(key(path::to::thing))]
.
Maintaining a consistent syntax for attributes even in the face of attribute proc macros and derives is probably desirable. Has there been an RFC for the desired end-state of attribute syntax? If there hasn’t been, I could and would be interested in writing an RFC for committing attributes to the following syntax:
attribute = { "#" ~ "[" ~ meta ~ "]" }
meta = { meta_kv | meta_list | meta_word }
meta_kv = { meta_word ~ "=" ~ (LiteralExpression | PathExpression) }
// *Expression := <https://doc.rust-lang.org/reference/expressions.html>
meta_list = { meta_word ~ "(" ~ CommaSeparatedList(meta)? ~ ")" }
meta_word = { Path }
// Path := non generic path
(This is… actually a very small expansion I now realize.) The intent of restricting the expressions allowed in “value” position is to prevent inviting writing code within attributes. Ideally we’d allow “small snippets” of code to be written, but still disincentivize writing large chunks of code.
At the very least, allowing (non-generic) paths in the value position of key-value pairs should be fairly uncontroversial and benefit existing macros. They can be written with ()
instead of =
anyway, and existing derives use = "path"
.
A full list of code-in-attribute-string-literal used by serde:
-
#[serde(bound(serialize = "T: MyTrait"))]
– a fullwhere
clause- requires
,
, so needs to be bracketed somehow
- requires
-
#[serde(default = "path")]
– a path
Side note observation: #[why = ,]
is valid with #![feature(unrestricted_attribute_tokens)]
, which should probably not be allowed, because it’s useless and #[a = ,, b = ,, c = ,,]
just looks silly. The feature gate also currently does not effect the $:meta
macro matcher, so that would need to be fixed before stabilization.