From bbbefa3edc48cb1afd57f1d99bf418731be1ef8f Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 6 Mar 2021 21:59:01 +0100 Subject: [PATCH 1/3] Allow doc alias attributes to use both list and value --- compiler/rustc_passes/src/check_attr.rs | 93 ++++++++++++++++++++++-- src/doc/rustdoc/src/advanced-features.md | 7 ++ src/librustdoc/clean/types.rs | 16 +++- 3 files changed, 107 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 6cc649c1180c5..0257a63e50fab 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -390,10 +390,25 @@ impl CheckAttrVisitor<'tcx> { .emit(); } - fn check_doc_alias(&self, meta: &NestedMetaItem, hir_id: HirId, target: Target) -> bool { - let doc_alias = meta.value_str().map(|s| s.to_string()).unwrap_or_else(String::new); + fn check_doc_alias_value( + &self, + meta: &NestedMetaItem, + doc_alias: &str, + hir_id: HirId, + target: Target, + is_list: bool, + ) -> bool { if doc_alias.is_empty() { - self.doc_attr_str_error(meta, "alias"); + self.tcx + .sess + .struct_span_err( + meta.name_value_literal_span().unwrap_or_else(|| meta.span()), + &format!( + "`#[doc(alias{})]` attribute cannot have empty value", + if is_list { "(\"...\")" } else { " = \"...\"" }, + ), + ) + .emit(); return false; } if let Some(c) = @@ -403,7 +418,11 @@ impl CheckAttrVisitor<'tcx> { .sess .struct_span_err( meta.name_value_literal_span().unwrap_or_else(|| meta.span()), - &format!("{:?} character isn't allowed in `#[doc(alias = \"...\")]`", c), + &format!( + "{:?} character isn't allowed in `#[doc(alias{})]`", + c, + if is_list { "(\"...\")" } else { " = \"...\"" }, + ), ) .emit(); return false; @@ -413,7 +432,10 @@ impl CheckAttrVisitor<'tcx> { .sess .struct_span_err( meta.name_value_literal_span().unwrap_or_else(|| meta.span()), - "`#[doc(alias = \"...\")]` cannot start or end with ' '", + &format!( + "`#[doc(alias{})]` cannot start or end with ' '", + if is_list { "(\"...\")" } else { " = \"...\"" }, + ), ) .emit(); return false; @@ -446,7 +468,11 @@ impl CheckAttrVisitor<'tcx> { .sess .struct_span_err( meta.span(), - &format!("`#[doc(alias = \"...\")]` isn't allowed on {}", err), + &format!( + "`#[doc(alias{})]` isn't allowed on {}", + if is_list { "(\"...\")" } else { " = \"...\"" }, + err, + ), ) .emit(); return false; @@ -457,7 +483,10 @@ impl CheckAttrVisitor<'tcx> { .sess .struct_span_err( meta.span(), - &format!("`#[doc(alias = \"...\")]` is the same as the item's name"), + &format!( + "`#[doc(alias{})]` is the same as the item's name", + if is_list { "(\"...\")" } else { " = \"...\"" }, + ), ) .emit(); return false; @@ -465,6 +494,56 @@ impl CheckAttrVisitor<'tcx> { true } + fn check_doc_alias(&self, meta: &NestedMetaItem, hir_id: HirId, target: Target) -> bool { + if let Some(values) = meta.meta_item_list() { + let mut errors = 0; + for v in values { + match v.literal() { + Some(l) => match l.kind { + LitKind::Str(s, _) => { + if !self.check_doc_alias_value(v, &s.as_str(), hir_id, target, true) { + errors += 1; + } + } + _ => { + self.tcx + .sess + .struct_span_err( + v.span(), + "`#[doc(alias(\"a\")]` expects string literals", + ) + .emit(); + errors += 1; + } + }, + None => { + self.tcx + .sess + .struct_span_err( + v.span(), + "`#[doc(alias(\"a\")]` expects string literals", + ) + .emit(); + errors += 1; + } + } + } + errors == 0 + } else if let Some(doc_alias) = meta.value_str().map(|s| s.to_string()) { + self.check_doc_alias_value(meta, &doc_alias, hir_id, target, false) + } else { + self.tcx + .sess + .struct_span_err( + meta.span(), + "doc alias attribute expects a string `#[doc(alias = \"a\")]` or a list of \ + strings: `#[doc(alias(\"a\", \"b\")]`", + ) + .emit(); + false + } + } + fn check_doc_keyword(&self, meta: &NestedMetaItem, hir_id: HirId) -> bool { let doc_keyword = meta.value_str().map(|s| s.to_string()).unwrap_or_else(String::new); if doc_keyword.is_empty() { diff --git a/src/doc/rustdoc/src/advanced-features.md b/src/doc/rustdoc/src/advanced-features.md index abdc2e4025d0e..6147bd0a97a96 100644 --- a/src/doc/rustdoc/src/advanced-features.md +++ b/src/doc/rustdoc/src/advanced-features.md @@ -81,3 +81,10 @@ Then, when looking for it through the `rustdoc` search, if you enter "x" or "big", search will show the `BigX` struct first. There are some limitations on the doc alias names though: you can't use `"` or whitespace. + +You can add multiple aliases at the same time by using a list: + +```rust,no_run +#[doc(alias("x", "big"))] +pub struct BigX; +``` diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index a94ee918c2467..b67af48451038 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -914,8 +914,20 @@ impl Attributes { self.other_attrs .lists(sym::doc) .filter(|a| a.has_name(sym::alias)) - .filter_map(|a| a.value_str().map(|s| s.to_string())) - .filter(|v| !v.is_empty()) + .map(|a| { + if let Some(values) = a.meta_item_list() { + values + .iter() + .map(|l| match l.literal().unwrap().kind { + ast::LitKind::Str(s, _) => s.as_str().to_string(), + _ => unreachable!(), + }) + .collect::>() + } else { + vec![a.value_str().map(|s| s.to_string()).unwrap()] + } + }) + .flatten() .collect::>() } } From 2069d3e13b5254e64c75548e88a549122b118e91 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 6 Mar 2021 21:59:26 +0100 Subject: [PATCH 2/3] Update doc alias ui tests --- src/test/rustdoc-ui/check-doc-alias-attr.rs | 14 +++- .../rustdoc-ui/check-doc-alias-attr.stderr | 72 ++++++++++++++++--- src/test/ui/rustdoc/check-doc-alias-attr.rs | 14 +++- .../ui/rustdoc/check-doc-alias-attr.stderr | 72 ++++++++++++++++--- 4 files changed, 148 insertions(+), 24 deletions(-) diff --git a/src/test/rustdoc-ui/check-doc-alias-attr.rs b/src/test/rustdoc-ui/check-doc-alias-attr.rs index 912e35f916545..719b98604c497 100644 --- a/src/test/rustdoc-ui/check-doc-alias-attr.rs +++ b/src/test/rustdoc-ui/check-doc-alias-attr.rs @@ -1,11 +1,11 @@ #![crate_type = "lib"] #[doc(alias = "foo")] // ok! +#[doc(alias("bar", "baz"))] // ok! pub struct Bar; #[doc(alias)] //~ ERROR #[doc(alias = 0)] //~ ERROR -#[doc(alias("bar"))] //~ ERROR #[doc(alias = "\"")] //~ ERROR #[doc(alias = "\n")] //~ ERROR #[doc(alias = " @@ -13,4 +13,16 @@ pub struct Bar; #[doc(alias = "\t")] //~ ERROR #[doc(alias = " hello")] //~ ERROR #[doc(alias = "hello ")] //~ ERROR +#[doc(alias = "")] //~ ERROR pub struct Foo; + +#[doc(alias(0))] //~ ERROR +#[doc(alias("\""))] //~ ERROR +#[doc(alias("\n"))] //~ ERROR +#[doc(alias(" +"))] //~^ ERROR +#[doc(alias("\t"))] //~ ERROR +#[doc(alias(" hello"))] //~ ERROR +#[doc(alias("hello "))] //~ ERROR +#[doc(alias(""))] //~ ERROR +pub struct Foo2; diff --git a/src/test/rustdoc-ui/check-doc-alias-attr.stderr b/src/test/rustdoc-ui/check-doc-alias-attr.stderr index 1c7fc83bb8dea..f99d69dc101b6 100644 --- a/src/test/rustdoc-ui/check-doc-alias-attr.stderr +++ b/src/test/rustdoc-ui/check-doc-alias-attr.stderr @@ -1,21 +1,15 @@ -error: doc alias attribute expects a string: #[doc(alias = "a")] - --> $DIR/check-doc-alias-attr.rs:6:7 +error: doc alias attribute expects a string `#[doc(alias = "a")]` or a list of strings `#[doc(alias("a", "b"))]` + --> $DIR/check-doc-alias-attr.rs:7:7 | LL | #[doc(alias)] | ^^^^^ -error: doc alias attribute expects a string: #[doc(alias = "a")] - --> $DIR/check-doc-alias-attr.rs:7:7 +error: doc alias attribute expects a string `#[doc(alias = "a")]` or a list of strings `#[doc(alias("a", "b"))]` + --> $DIR/check-doc-alias-attr.rs:8:7 | LL | #[doc(alias = 0)] | ^^^^^^^^^ -error: doc alias attribute expects a string: #[doc(alias = "a")] - --> $DIR/check-doc-alias-attr.rs:8:7 - | -LL | #[doc(alias("bar"))] - | ^^^^^^^^^^^^ - error: '\"' character isn't allowed in `#[doc(alias = "...")]` --> $DIR/check-doc-alias-attr.rs:9:15 | @@ -54,5 +48,61 @@ error: `#[doc(alias = "...")]` cannot start or end with ' ' LL | #[doc(alias = "hello ")] | ^^^^^^^^ -error: aborting due to 9 previous errors +error: `#[doc(alias = "...")]` attribute cannot have empty value + --> $DIR/check-doc-alias-attr.rs:16:15 + | +LL | #[doc(alias = "")] + | ^^ + +error: `#[doc(alias("a"))]` expects string literals + --> $DIR/check-doc-alias-attr.rs:19:13 + | +LL | #[doc(alias(0))] + | ^ + +error: '\"' character isn't allowed in `#[doc(alias("..."))]` + --> $DIR/check-doc-alias-attr.rs:20:13 + | +LL | #[doc(alias("\""))] + | ^^^^ + +error: '\n' character isn't allowed in `#[doc(alias("..."))]` + --> $DIR/check-doc-alias-attr.rs:21:13 + | +LL | #[doc(alias("\n"))] + | ^^^^ + +error: '\n' character isn't allowed in `#[doc(alias("..."))]` + --> $DIR/check-doc-alias-attr.rs:22:13 + | +LL | #[doc(alias(" + | _____________^ +LL | | "))] + | |_^ + +error: '\t' character isn't allowed in `#[doc(alias("..."))]` + --> $DIR/check-doc-alias-attr.rs:24:13 + | +LL | #[doc(alias("\t"))] + | ^^^^ + +error: `#[doc(alias("..."))]` cannot start or end with ' ' + --> $DIR/check-doc-alias-attr.rs:25:13 + | +LL | #[doc(alias(" hello"))] + | ^^^^^^^^ + +error: `#[doc(alias("..."))]` cannot start or end with ' ' + --> $DIR/check-doc-alias-attr.rs:26:13 + | +LL | #[doc(alias("hello "))] + | ^^^^^^^^ + +error: `#[doc(alias("..."))]` attribute cannot have empty value + --> $DIR/check-doc-alias-attr.rs:27:13 + | +LL | #[doc(alias(""))] + | ^^ + +error: aborting due to 17 previous errors diff --git a/src/test/ui/rustdoc/check-doc-alias-attr.rs b/src/test/ui/rustdoc/check-doc-alias-attr.rs index 912e35f916545..719b98604c497 100644 --- a/src/test/ui/rustdoc/check-doc-alias-attr.rs +++ b/src/test/ui/rustdoc/check-doc-alias-attr.rs @@ -1,11 +1,11 @@ #![crate_type = "lib"] #[doc(alias = "foo")] // ok! +#[doc(alias("bar", "baz"))] // ok! pub struct Bar; #[doc(alias)] //~ ERROR #[doc(alias = 0)] //~ ERROR -#[doc(alias("bar"))] //~ ERROR #[doc(alias = "\"")] //~ ERROR #[doc(alias = "\n")] //~ ERROR #[doc(alias = " @@ -13,4 +13,16 @@ pub struct Bar; #[doc(alias = "\t")] //~ ERROR #[doc(alias = " hello")] //~ ERROR #[doc(alias = "hello ")] //~ ERROR +#[doc(alias = "")] //~ ERROR pub struct Foo; + +#[doc(alias(0))] //~ ERROR +#[doc(alias("\""))] //~ ERROR +#[doc(alias("\n"))] //~ ERROR +#[doc(alias(" +"))] //~^ ERROR +#[doc(alias("\t"))] //~ ERROR +#[doc(alias(" hello"))] //~ ERROR +#[doc(alias("hello "))] //~ ERROR +#[doc(alias(""))] //~ ERROR +pub struct Foo2; diff --git a/src/test/ui/rustdoc/check-doc-alias-attr.stderr b/src/test/ui/rustdoc/check-doc-alias-attr.stderr index 1c7fc83bb8dea..f99d69dc101b6 100644 --- a/src/test/ui/rustdoc/check-doc-alias-attr.stderr +++ b/src/test/ui/rustdoc/check-doc-alias-attr.stderr @@ -1,21 +1,15 @@ -error: doc alias attribute expects a string: #[doc(alias = "a")] - --> $DIR/check-doc-alias-attr.rs:6:7 +error: doc alias attribute expects a string `#[doc(alias = "a")]` or a list of strings `#[doc(alias("a", "b"))]` + --> $DIR/check-doc-alias-attr.rs:7:7 | LL | #[doc(alias)] | ^^^^^ -error: doc alias attribute expects a string: #[doc(alias = "a")] - --> $DIR/check-doc-alias-attr.rs:7:7 +error: doc alias attribute expects a string `#[doc(alias = "a")]` or a list of strings `#[doc(alias("a", "b"))]` + --> $DIR/check-doc-alias-attr.rs:8:7 | LL | #[doc(alias = 0)] | ^^^^^^^^^ -error: doc alias attribute expects a string: #[doc(alias = "a")] - --> $DIR/check-doc-alias-attr.rs:8:7 - | -LL | #[doc(alias("bar"))] - | ^^^^^^^^^^^^ - error: '\"' character isn't allowed in `#[doc(alias = "...")]` --> $DIR/check-doc-alias-attr.rs:9:15 | @@ -54,5 +48,61 @@ error: `#[doc(alias = "...")]` cannot start or end with ' ' LL | #[doc(alias = "hello ")] | ^^^^^^^^ -error: aborting due to 9 previous errors +error: `#[doc(alias = "...")]` attribute cannot have empty value + --> $DIR/check-doc-alias-attr.rs:16:15 + | +LL | #[doc(alias = "")] + | ^^ + +error: `#[doc(alias("a"))]` expects string literals + --> $DIR/check-doc-alias-attr.rs:19:13 + | +LL | #[doc(alias(0))] + | ^ + +error: '\"' character isn't allowed in `#[doc(alias("..."))]` + --> $DIR/check-doc-alias-attr.rs:20:13 + | +LL | #[doc(alias("\""))] + | ^^^^ + +error: '\n' character isn't allowed in `#[doc(alias("..."))]` + --> $DIR/check-doc-alias-attr.rs:21:13 + | +LL | #[doc(alias("\n"))] + | ^^^^ + +error: '\n' character isn't allowed in `#[doc(alias("..."))]` + --> $DIR/check-doc-alias-attr.rs:22:13 + | +LL | #[doc(alias(" + | _____________^ +LL | | "))] + | |_^ + +error: '\t' character isn't allowed in `#[doc(alias("..."))]` + --> $DIR/check-doc-alias-attr.rs:24:13 + | +LL | #[doc(alias("\t"))] + | ^^^^ + +error: `#[doc(alias("..."))]` cannot start or end with ' ' + --> $DIR/check-doc-alias-attr.rs:25:13 + | +LL | #[doc(alias(" hello"))] + | ^^^^^^^^ + +error: `#[doc(alias("..."))]` cannot start or end with ' ' + --> $DIR/check-doc-alias-attr.rs:26:13 + | +LL | #[doc(alias("hello "))] + | ^^^^^^^^ + +error: `#[doc(alias("..."))]` attribute cannot have empty value + --> $DIR/check-doc-alias-attr.rs:27:13 + | +LL | #[doc(alias(""))] + | ^^ + +error: aborting due to 17 previous errors From 1d26e6b632c78dedff2dd19d93d0687b2c97717d Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 8 Mar 2021 17:30:22 +0100 Subject: [PATCH 3/3] Improve code by removing similar function calls and using loops instead for collecting iterators --- compiler/rustc_passes/src/check_attr.rs | 92 +++++++++---------------- src/librustdoc/clean/types.rs | 33 +++++---- 2 files changed, 49 insertions(+), 76 deletions(-) diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 0257a63e50fab..82562c3a92dbe 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -398,47 +398,42 @@ impl CheckAttrVisitor<'tcx> { target: Target, is_list: bool, ) -> bool { + let tcx = self.tcx; + let err_fn = move |span: Span, msg: &str| { + tcx.sess.span_err( + span, + &format!( + "`#[doc(alias{})]` {}", + if is_list { "(\"...\")" } else { " = \"...\"" }, + msg, + ), + ); + false + }; if doc_alias.is_empty() { - self.tcx - .sess - .struct_span_err( - meta.name_value_literal_span().unwrap_or_else(|| meta.span()), - &format!( - "`#[doc(alias{})]` attribute cannot have empty value", - if is_list { "(\"...\")" } else { " = \"...\"" }, - ), - ) - .emit(); - return false; + return err_fn( + meta.name_value_literal_span().unwrap_or_else(|| meta.span()), + "attribute cannot have empty value", + ); } if let Some(c) = doc_alias.chars().find(|&c| c == '"' || c == '\'' || (c.is_whitespace() && c != ' ')) { - self.tcx - .sess - .struct_span_err( - meta.name_value_literal_span().unwrap_or_else(|| meta.span()), - &format!( - "{:?} character isn't allowed in `#[doc(alias{})]`", - c, - if is_list { "(\"...\")" } else { " = \"...\"" }, - ), - ) - .emit(); + self.tcx.sess.span_err( + meta.name_value_literal_span().unwrap_or_else(|| meta.span()), + &format!( + "{:?} character isn't allowed in `#[doc(alias{})]`", + c, + if is_list { "(\"...\")" } else { " = \"...\"" }, + ), + ); return false; } if doc_alias.starts_with(' ') || doc_alias.ends_with(' ') { - self.tcx - .sess - .struct_span_err( - meta.name_value_literal_span().unwrap_or_else(|| meta.span()), - &format!( - "`#[doc(alias{})]` cannot start or end with ' '", - if is_list { "(\"...\")" } else { " = \"...\"" }, - ), - ) - .emit(); - return false; + return err_fn( + meta.name_value_literal_span().unwrap_or_else(|| meta.span()), + "cannot start or end with ' '", + ); } if let Some(err) = match target { Target::Impl => Some("implementation block"), @@ -464,32 +459,11 @@ impl CheckAttrVisitor<'tcx> { } _ => None, } { - self.tcx - .sess - .struct_span_err( - meta.span(), - &format!( - "`#[doc(alias{})]` isn't allowed on {}", - if is_list { "(\"...\")" } else { " = \"...\"" }, - err, - ), - ) - .emit(); - return false; + return err_fn(meta.span(), &format!("isn't allowed on {}", err)); } let item_name = self.tcx.hir().name(hir_id); if &*item_name.as_str() == doc_alias { - self.tcx - .sess - .struct_span_err( - meta.span(), - &format!( - "`#[doc(alias{})]` is the same as the item's name", - if is_list { "(\"...\")" } else { " = \"...\"" }, - ), - ) - .emit(); - return false; + return err_fn(meta.span(), "is the same as the item's name"); } true } @@ -510,7 +484,7 @@ impl CheckAttrVisitor<'tcx> { .sess .struct_span_err( v.span(), - "`#[doc(alias(\"a\")]` expects string literals", + "`#[doc(alias(\"a\"))]` expects string literals", ) .emit(); errors += 1; @@ -521,7 +495,7 @@ impl CheckAttrVisitor<'tcx> { .sess .struct_span_err( v.span(), - "`#[doc(alias(\"a\")]` expects string literals", + "`#[doc(alias(\"a\"))]` expects string literals", ) .emit(); errors += 1; @@ -537,7 +511,7 @@ impl CheckAttrVisitor<'tcx> { .struct_span_err( meta.span(), "doc alias attribute expects a string `#[doc(alias = \"a\")]` or a list of \ - strings: `#[doc(alias(\"a\", \"b\")]`", + strings `#[doc(alias(\"a\", \"b\"))]`", ) .emit(); false diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index b67af48451038..7608b485c5b80 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -911,24 +911,23 @@ impl Attributes { } crate fn get_doc_aliases(&self) -> FxHashSet { - self.other_attrs - .lists(sym::doc) - .filter(|a| a.has_name(sym::alias)) - .map(|a| { - if let Some(values) = a.meta_item_list() { - values - .iter() - .map(|l| match l.literal().unwrap().kind { - ast::LitKind::Str(s, _) => s.as_str().to_string(), - _ => unreachable!(), - }) - .collect::>() - } else { - vec![a.value_str().map(|s| s.to_string()).unwrap()] + let mut aliases = FxHashSet::default(); + + for attr in self.other_attrs.lists(sym::doc).filter(|a| a.has_name(sym::alias)) { + if let Some(values) = attr.meta_item_list() { + for l in values { + match l.literal().unwrap().kind { + ast::LitKind::Str(s, _) => { + aliases.insert(s.as_str().to_string()); + } + _ => unreachable!(), + } } - }) - .flatten() - .collect::>() + } else { + aliases.insert(attr.value_str().map(|s| s.to_string()).unwrap()); + } + } + aliases } }