Stabilize extended_key_value_attributes

# Stabilization report

 ## Summary

This stabilizes using macro expansion in key-value attributes, like so:

 ```rust
 #[doc = include_str!("my_doc.md")]
 struct S;

 #[path = concat!(env!("OUT_DIR"), "/generated.rs")]
 mod m;
 ```

See the changes to the reference for details on what macros are allowed;
see Petrochenkov's excellent blog post [on internals](https://internals.rust-lang.org/t/macro-expansion-points-in-attributes/11455)
for alternatives that were considered and rejected ("why accept no more
and no less?")

This has been available on nightly since 1.50 with no major issues.

 ## Notes

 ### Accepted syntax

The parser accepts arbitrary Rust expressions in this position, but any expression other than a macro invocation will ultimately lead to an error because it is not expected by the built-in expression forms (e.g., `#[doc]`).  Note that decorators and the like may be able to observe other expression forms.

 ### Expansion ordering

Expansion of macro expressions in "inert" attributes occurs after decorators have executed, analogously to macro expressions appearing in the function body or other parts of decorator input.

There is currently no way for decorators to accept macros in key-value position if macro expansion must be performed before the decorator executes (if the macro can simply be copied into the output for later expansion, that can work).

 ## Test cases

 - https://github.com/rust-lang/rust/blob/master/src/test/ui/attributes/key-value-expansion-on-mac.rs
 - https://github.com/rust-lang/rust/blob/master/src/test/rustdoc/external-doc.rs

The feature has also been dogfooded extensively in the compiler and
standard library:

- https://github.com/rust-lang/rust/pull/83329
- https://github.com/rust-lang/rust/pull/83230
- https://github.com/rust-lang/rust/pull/82641
- https://github.com/rust-lang/rust/pull/80534

 ## Implementation history

- Initial proposal: https://github.com/rust-lang/rust/issues/55414#issuecomment-554005412
- Experiment to see how much code it would break: https://github.com/rust-lang/rust/pull/67121
- Preliminary work to restrict expansion that would conflict with this
feature: https://github.com/rust-lang/rust/pull/77271
- Initial implementation: https://github.com/rust-lang/rust/pull/78837
- Fix for an ICE: https://github.com/rust-lang/rust/pull/80563

 ## Unresolved Questions

~~https://github.com/rust-lang/rust/pull/83366#issuecomment-805180738 listed some concerns, but they have been resolved as of this final report.~~

 ## Additional Information

 There are two workarounds that have a similar effect for `#[doc]`
attributes on nightly. One is to emulate this behavior by using a limited version of this feature that was stabilized for historical reasons:

```rust
macro_rules! forward_inner_docs {
    ($e:expr => $i:item) => {
        #[doc = $e]
        $i
    };
}

forward_inner_docs!(include_str!("lib.rs") => struct S {});
```

This also works for other attributes (like `#[path = concat!(...)]`).
The other is to use `doc(include)`:

```rust
 #![feature(external_doc)]
 #[doc(include = "lib.rs")]
 struct S {}
```

The first works, but is non-trivial for people to discover, and
difficult to read and maintain. The second is a strange special-case for
a particular use of the macro. This generalizes it to work for any use
case, not just including files.

I plan to remove `doc(include)` when this is stabilized. The
`forward_inner_docs` workaround will still compile without warnings, but
I expect it to be used less once it's no longer necessary.
This commit is contained in:
Joshua Nelson 2021-03-22 01:10:10 -04:00
parent 3e99439f4d
commit e48b6b4599
17 changed files with 21 additions and 76 deletions

View File

@ -712,10 +712,6 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session) {
gate_all!(const_trait_impl, "const trait impls are experimental"); gate_all!(const_trait_impl, "const trait impls are experimental");
gate_all!(half_open_range_patterns, "half-open range patterns are unstable"); gate_all!(half_open_range_patterns, "half-open range patterns are unstable");
gate_all!(inline_const, "inline-const is experimental"); gate_all!(inline_const, "inline-const is experimental");
gate_all!(
extended_key_value_attributes,
"arbitrary expressions in key-value attributes are unstable"
);
gate_all!( gate_all!(
const_generics_defaults, const_generics_defaults,
"default values for const generic parameters are experimental" "default values for const generic parameters are experimental"

View File

@ -8,7 +8,7 @@
#![feature(bool_to_option)] #![feature(bool_to_option)]
#![feature(const_cstr_unchecked)] #![feature(const_cstr_unchecked)]
#![feature(crate_visibility_modifier)] #![feature(crate_visibility_modifier)]
#![feature(extended_key_value_attributes)] #![cfg_attr(bootstrap, feature(extended_key_value_attributes))]
#![feature(extern_types)] #![feature(extern_types)]
#![feature(in_band_lifetimes)] #![feature(in_band_lifetimes)]
#![feature(iter_zip)] #![feature(iter_zip)]

View File

@ -5,7 +5,7 @@
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")] #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
#![feature(crate_visibility_modifier)] #![feature(crate_visibility_modifier)]
#![feature(backtrace)] #![feature(backtrace)]
#![feature(extended_key_value_attributes)] #![cfg_attr(bootstrap, feature(extended_key_value_attributes))]
#![feature(format_args_capture)] #![feature(format_args_capture)]
#![feature(iter_zip)] #![feature(iter_zip)]
#![feature(nll)] #![feature(nll)]

View File

@ -281,6 +281,8 @@ declare_features! (
(accepted, or_patterns, "1.53.0", Some(54883), None), (accepted, or_patterns, "1.53.0", Some(54883), None),
/// Allows defining identifiers beyond ASCII. /// Allows defining identifiers beyond ASCII.
(accepted, non_ascii_idents, "1.53.0", Some(55467), None), (accepted, non_ascii_idents, "1.53.0", Some(55467), None),
/// Allows arbitrary expressions in key-value attributes at parse time.
(accepted, extended_key_value_attributes, "1.54.0", Some(78835), None),
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// feature-group-end: accepted features // feature-group-end: accepted features

View File

@ -601,9 +601,6 @@ declare_features! (
/// Allows capturing disjoint fields in a closure/generator (RFC 2229). /// Allows capturing disjoint fields in a closure/generator (RFC 2229).
(active, capture_disjoint_fields, "1.49.0", Some(53488), None), (active, capture_disjoint_fields, "1.49.0", Some(53488), None),
/// Allows arbitrary expressions in key-value attributes at parse time.
(active, extended_key_value_attributes, "1.50.0", Some(78835), None),
/// Allows const generics to have default values (e.g. `struct Foo<const N: usize = 3>(...);`). /// Allows const generics to have default values (e.g. `struct Foo<const N: usize = 3>(...);`).
(active, const_generics_defaults, "1.51.0", Some(44580), None), (active, const_generics_defaults, "1.51.0", Some(44580), None),

View File

@ -4,7 +4,7 @@
#![feature(crate_visibility_modifier)] #![feature(crate_visibility_modifier)]
#![feature(const_panic)] #![feature(const_panic)]
#![feature(extended_key_value_attributes)] #![cfg_attr(bootstrap, feature(extended_key_value_attributes))]
#![feature(in_band_lifetimes)] #![feature(in_band_lifetimes)]
#![feature(once_cell)] #![feature(once_cell)]
#![cfg_attr(bootstrap, feature(or_patterns))] #![cfg_attr(bootstrap, feature(or_patterns))]

View File

@ -1065,24 +1065,11 @@ impl<'a> Parser<'a> {
} else if !delimited_only { } else if !delimited_only {
if self.eat(&token::Eq) { if self.eat(&token::Eq) {
let eq_span = self.prev_token.span; let eq_span = self.prev_token.span;
let mut is_interpolated_expr = false;
if let token::Interpolated(nt) = &self.token.kind {
if let token::NtExpr(..) = **nt {
is_interpolated_expr = true;
}
}
// Collect tokens because they are used during lowering to HIR. // Collect tokens because they are used during lowering to HIR.
let expr = self.parse_expr_force_collect()?; let expr = self.parse_expr_force_collect()?;
let span = expr.span; let span = expr.span;
match &expr.kind {
// Not gated to support things like `doc = $expr` that work on stable.
_ if is_interpolated_expr => {}
ExprKind::Lit(lit) if lit.kind.is_unsuffixed() => {}
_ => self.sess.gated_spans.gate(sym::extended_key_value_attributes, span),
}
let token_kind = token::Interpolated(Lrc::new(token::NtExpr(expr))); let token_kind = token::Interpolated(Lrc::new(token::NtExpr(expr)));
MacArgs::Eq(eq_span, Token::new(token_kind, span)) MacArgs::Eq(eq_span, Token::new(token_kind, span))
} else { } else {

View File

@ -113,7 +113,7 @@
#![cfg_attr(bootstrap, feature(doc_spotlight))] #![cfg_attr(bootstrap, feature(doc_spotlight))]
#![cfg_attr(not(bootstrap), feature(doc_notable_trait))] #![cfg_attr(not(bootstrap), feature(doc_notable_trait))]
#![feature(duration_consts_2)] #![feature(duration_consts_2)]
#![feature(extended_key_value_attributes)] #![cfg_attr(bootstrap, feature(extended_key_value_attributes))]
#![feature(extern_types)] #![feature(extern_types)]
#![feature(fundamental)] #![feature(fundamental)]
#![feature(intra_doc_pointers)] #![feature(intra_doc_pointers)]

View File

@ -268,7 +268,7 @@
#![feature(exact_size_is_empty)] #![feature(exact_size_is_empty)]
#![feature(exhaustive_patterns)] #![feature(exhaustive_patterns)]
#![feature(extend_one)] #![feature(extend_one)]
#![feature(extended_key_value_attributes)] #![cfg_attr(bootstrap, feature(extended_key_value_attributes))]
#![feature(fn_traits)] #![feature(fn_traits)]
#![feature(format_args_nl)] #![feature(format_args_nl)]
#![feature(gen_future)] #![feature(gen_future)]

View File

@ -0,0 +1,5 @@
# Rustdoc
This is documentation for rustdoc itself, written in mdbook format.
To build the book, use `x.py doc src/doc/rustdoc`.
To run doctests, use `x.py test src/doc/rustdoc`.

View File

@ -35,6 +35,13 @@ Which can feel more flexible. Note that this would generate this:
but given that docs are rendered via Markdown, it will remove these newlines. but given that docs are rendered via Markdown, it will remove these newlines.
Another use case is for including external files as documentation:
```rust,no_run
#[doc = include_str!("../README.md")]
# fn f() {}
```
The `doc` attribute has more options though! These don't involve the text of The `doc` attribute has more options though! These don't involve the text of
the output, but instead, various aspects of the presentation of the output. the output, but instead, various aspects of the presentation of the output.
We've split them into two kinds below: attributes that are useful at the We've split them into two kinds below: attributes that are useful at the

View File

@ -1,5 +1,4 @@
#![feature(external_doc)] #![feature(external_doc)]
#![feature(extended_key_value_attributes)]
// @has external_doc/struct.CanHasDocs.html // @has external_doc/struct.CanHasDocs.html
// @has - '//h1' 'External Docs' // @has - '//h1' 'External Docs'

View File

@ -1,4 +1,3 @@
#![feature(extended_key_value_attributes)]
#![feature(rustc_attrs)] #![feature(rustc_attrs)]
#[rustc_dummy = stringify!(a)] // OK #[rustc_dummy = stringify!(a)] // OK

View File

@ -1,5 +1,5 @@
error: unexpected token: `stringify!(b)` error: unexpected token: `stringify!(b)`
--> $DIR/key-value-expansion-on-mac.rs:12:17 --> $DIR/key-value-expansion-on-mac.rs:11:17
| |
LL | #[rustc_dummy = stringify!(b)] LL | #[rustc_dummy = stringify!(b)]
| ^^^^^^^^^^^^^ | ^^^^^^^^^^^^^

View File

@ -1,8 +0,0 @@
#[cfg(FALSE)]
#[attr = multi::segment::path] //~ ERROR arbitrary expressions in key-value attributes are unstable
#[attr = macro_call!()] //~ ERROR arbitrary expressions in key-value attributes are unstable
#[attr = 1 + 2] //~ ERROR arbitrary expressions in key-value attributes are unstable
#[attr = what?] //~ ERROR arbitrary expressions in key-value attributes are unstable
struct S;
fn main() {}

View File

@ -1,39 +0,0 @@
error[E0658]: arbitrary expressions in key-value attributes are unstable
--> $DIR/feature-gate-extended_key_value_attributes.rs:2:10
|
LL | #[attr = multi::segment::path]
| ^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #78835 <https://github.com/rust-lang/rust/issues/78835> for more information
= help: add `#![feature(extended_key_value_attributes)]` to the crate attributes to enable
error[E0658]: arbitrary expressions in key-value attributes are unstable
--> $DIR/feature-gate-extended_key_value_attributes.rs:3:10
|
LL | #[attr = macro_call!()]
| ^^^^^^^^^^^^^
|
= note: see issue #78835 <https://github.com/rust-lang/rust/issues/78835> for more information
= help: add `#![feature(extended_key_value_attributes)]` to the crate attributes to enable
error[E0658]: arbitrary expressions in key-value attributes are unstable
--> $DIR/feature-gate-extended_key_value_attributes.rs:4:10
|
LL | #[attr = 1 + 2]
| ^^^^^
|
= note: see issue #78835 <https://github.com/rust-lang/rust/issues/78835> for more information
= help: add `#![feature(extended_key_value_attributes)]` to the crate attributes to enable
error[E0658]: arbitrary expressions in key-value attributes are unstable
--> $DIR/feature-gate-extended_key_value_attributes.rs:5:10
|
LL | #[attr = what?]
| ^^^^^
|
= note: see issue #78835 <https://github.com/rust-lang/rust/issues/78835> for more information
= help: add `#![feature(extended_key_value_attributes)]` to the crate attributes to enable
error: aborting due to 4 previous errors
For more information about this error, try `rustc --explain E0658`.

View File

@ -1,4 +1,4 @@
#![feature(rustc_attrs, extended_key_value_attributes)] #![feature(rustc_attrs)]
#[rustc_dummy = 1usize] //~ ERROR: suffixed literals are not allowed in attributes #[rustc_dummy = 1usize] //~ ERROR: suffixed literals are not allowed in attributes
#[rustc_dummy = 1u8] //~ ERROR: suffixed literals are not allowed in attributes #[rustc_dummy = 1u8] //~ ERROR: suffixed literals are not allowed in attributes