Options for non-string-quoted meta attributes
Currently, we only support #[deftly(something = "value"))].
This is rather poor, especially for macros that want to take as input Rust code snippets (#56) or attributes to pass through somewhere (#109 (closed)). There are a few difficulties with improving this, notably: (1) possible ambiguity between an expression in quotes and an unquoted expression whose value is a string (2) overlap between some of the existing proposed syntaxes and #[deftly(nest(thing))].
The root question is this: what is the syntax of #[deftly]? Currently it is a nested tree of identifiers, which may have parens and then a further nested tree, or which may have values specified with =.
CC @nickm
Options
Consider ${Xmeta(scope(node)) as SOMETHING}.
Some options I came up with (not necessarily mutually exclusive):
#[deftly(scope(node(CONTENT)))]
Ie, allow templates to reference the content (within parens) of a node in the tree, as arbitrary tokens. (This is what #56 is asking for.)
Advantages: similar to many other proc macros' input syntax.
Disadvantages: it is no longer clear whether a certain part of a #[deftly] is "structural" (ie, parsed in the uniform tree-like way by derive-deftly itself) or template-specific arbitrary Rust fragements to be passed through. This ambiguity makes the parsing and processing by derive-deftly much more complicated (especialiy, meta attribute use checking). It also makes things less clear for the human reader. You can't tell from just looking at it that node is structural and CONTENT is payload.
#[deftly(scope(node = CONTENT))]
Ie, just allow unquoted values. This would work right now, without any trouble, for as ident, as ty, as path.
For as expr we suffer from an ambiguity. Is #[deftly(scope(node = "42"))] the integer literal 42 or the string literal "42"?
For as token_stream and as items, this cannot work because both items and token_strema can contain a comma, but it's comma that we're using to separate one meta item from the next.
-
#[deftly(scope(node = { CONTENT }))]or#[deftly(scope(node { CONTENT }))]Ie, use
{ }for arbitrary content, with or without an equals. This would always work. But it is clumsy if the{ }aren't actually needed to delimitCONTENT. -
Mixture of
= CONTENTand= { CONTENT }.We could possibly allow some things to be bare and automatically strip one level of
{ }. (We do that already inside templates with names arguments to expansion keywords.) So#[deftly(scope(node = 42))]and#[deftly(scope(node = { 42 }))]would be equivalent (and indistinguishable by the template).Alternatively whether to strip
{ }could depend on theas. Soas token_streamwould always expect{ };as exprwould allow them but only because{ }can always surround a Rust expression;as viswould never allow them.This syntax would work reasonably well for attributes. You could say
as attrsand it would expectCONTENTto be a series of#[...](outer attributes; inner ones would presumably be forbidden).
Here are some options for how the template can control its input syntax:
-
Template-level option to change how
as exprbehaves. Or, a new name foras exprthat expects a Rust expression without surrounding quotes --- but what would we call it? -
Make derive-deftly 2.0 where
as exprexpects unquoted values. (There is a sophisticated semver scheme which can arrange that not all templates have to change at once. d-d 1.0 and 2.0 templates could both be applied to the same struct.) -
Allow
#[deftly(scope(node = (VALUE)))], perhaps only foras expr.Advantage: the expansions of expressions already have
( ), so this is somehow natural.Disadvantage: the nested parens become even more confusing; the "meaning" of the piece (and thus of the close paren) depends on the presence of the
=. We don't want= ( )for other things than expressions, probably, so it would be a different syntax just for expressions.
Discussion
I am quite keen that one should be able to look at a #[deftly] attribute and know which parts of it are directed at the template ("payload") and which parts are structural (part of the meta identifier node tree, as in ${Xmeta(scope(node)) as ...}.
I think this rules out option 1. I quite like the = notation. It is pleasingly clear and mirrors much other Rust attribute stuff.
The most awkward case is as expr where the best syntax, #[deftly(scope(node = VALUE))] already means something else.
I think I don't mind if the as ... influences the accepted input syntax, and the interpretation of VALUE. It more or less already does that.
Proposal
-
#[deftly(scope(node = NONEMPTHY RUST SNIPPET))]accepted forty,path,ident, andvis. -
#[deftly(scope(node = { RUST SNIPPET }accepted forty,path,ident,expr,items,vis,token_stream(ie everything exceptstr).For
expr, the{ }are part of the expansion (and replace the( )that d-d normally inserts). (This is normally not very visible since{ }are generally allowed around an expression.)For
itemsandtoken_streamthe{ }are necessary to delimit the content, and we must not make them part of the expansion.For
ty,path,ident, the{ }are never necessary, and cannot be part of the expansion, but are accepted for orthogonality.For
vis, the{ }would in theory not ever be strictly necessary, but a visibility can be empty and#[deftly(scope(node(vis = )))]is very weird. So we require{ }in that case,#[deftly(scope(node(vis = {})))]. -
Invent
as attrswhich accepts#[deftly(scope(node = { #[ATTR] #[ATTR] }))].In future it might accept
#[deftly(scope(node = #[ATTR], node = #[ATTR]))]. That would be compatible. -
Add a template option
unquoted_exprsor similar, which causes the whole template to accept#[deftly(scope(node = EXPR))]and therefore changes the existing syntax#[deftly(scope(node = "TEXT"))]to be a string literal"TEXT"rather than parsingTEXTas Rust code. -
Add a ticket with the Breaking label, asking that
unquoted_exprsbecome the default in 2.0.