Skip to content

Commit 1f95c91

Browse files
committed
Auto merge of rust-lang#79613 - GuillaumeGomez:doc-keyword-checks, r=oli-obk
Add checks for #[doc(keyword = "...")] attribute The goal here is to extend check for `#[doc(keyword = "...")]`. cc `@jyn514` r? `@oli-obk`
2 parents 2203527 + 15f9453 commit 1f95c91

File tree

10 files changed

+194
-106
lines changed

10 files changed

+194
-106
lines changed

Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -4011,6 +4011,7 @@ dependencies = [
40114011
"rustc_errors",
40124012
"rustc_hir",
40134013
"rustc_index",
4014+
"rustc_lexer",
40144015
"rustc_middle",
40154016
"rustc_serialize",
40164017
"rustc_session",

compiler/rustc_passes/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ rustc_ast = { path = "../rustc_ast" }
1818
rustc_serialize = { path = "../rustc_serialize" }
1919
rustc_span = { path = "../rustc_span" }
2020
rustc_trait_selection = { path = "../rustc_trait_selection" }
21+
rustc_lexer = { path = "../rustc_lexer" }

compiler/rustc_passes/src/check_attr.rs

+139-79
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ impl CheckAttrVisitor<'tcx> {
7878
} else if self.tcx.sess.check_name(attr, sym::track_caller) {
7979
self.check_track_caller(&attr.span, attrs, span, target)
8080
} else if self.tcx.sess.check_name(attr, sym::doc) {
81-
self.check_doc_alias(attr, hir_id, target)
81+
self.check_doc_attrs(attr, hir_id, target)
8282
} else if self.tcx.sess.check_name(attr, sym::no_link) {
8383
self.check_no_link(&attr, span, target)
8484
} else if self.tcx.sess.check_name(attr, sym::export_name) {
@@ -287,99 +287,159 @@ impl CheckAttrVisitor<'tcx> {
287287
}
288288
}
289289

290-
fn doc_alias_str_error(&self, meta: &NestedMetaItem) {
290+
fn doc_attr_str_error(&self, meta: &NestedMetaItem, attr_name: &str) {
291291
self.tcx
292292
.sess
293293
.struct_span_err(
294294
meta.span(),
295-
"doc alias attribute expects a string: #[doc(alias = \"0\")]",
295+
&format!("doc {0} attribute expects a string: #[doc({0} = \"a\")]", attr_name),
296296
)
297297
.emit();
298298
}
299299

300-
fn check_doc_alias(&self, attr: &Attribute, hir_id: HirId, target: Target) -> bool {
300+
fn check_doc_alias(&self, meta: &NestedMetaItem, hir_id: HirId, target: Target) -> bool {
301+
let doc_alias = meta.value_str().map(|s| s.to_string()).unwrap_or_else(String::new);
302+
if doc_alias.is_empty() {
303+
self.doc_attr_str_error(meta, "alias");
304+
return false;
305+
}
306+
if let Some(c) =
307+
doc_alias.chars().find(|&c| c == '"' || c == '\'' || (c.is_whitespace() && c != ' '))
308+
{
309+
self.tcx
310+
.sess
311+
.struct_span_err(
312+
meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
313+
&format!("{:?} character isn't allowed in `#[doc(alias = \"...\")]`", c,),
314+
)
315+
.emit();
316+
return false;
317+
}
318+
if doc_alias.starts_with(' ') || doc_alias.ends_with(' ') {
319+
self.tcx
320+
.sess
321+
.struct_span_err(
322+
meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
323+
"`#[doc(alias = \"...\")]` cannot start or end with ' '",
324+
)
325+
.emit();
326+
return false;
327+
}
328+
if let Some(err) = match target {
329+
Target::Impl => Some("implementation block"),
330+
Target::ForeignMod => Some("extern block"),
331+
Target::AssocTy => {
332+
let parent_hir_id = self.tcx.hir().get_parent_item(hir_id);
333+
let containing_item = self.tcx.hir().expect_item(parent_hir_id);
334+
if Target::from_item(containing_item) == Target::Impl {
335+
Some("type alias in implementation block")
336+
} else {
337+
None
338+
}
339+
}
340+
Target::AssocConst => {
341+
let parent_hir_id = self.tcx.hir().get_parent_item(hir_id);
342+
let containing_item = self.tcx.hir().expect_item(parent_hir_id);
343+
// We can't link to trait impl's consts.
344+
let err = "associated constant in trait implementation block";
345+
match containing_item.kind {
346+
ItemKind::Impl { of_trait: Some(_), .. } => Some(err),
347+
_ => None,
348+
}
349+
}
350+
_ => None,
351+
} {
352+
self.tcx
353+
.sess
354+
.struct_span_err(
355+
meta.span(),
356+
&format!("`#[doc(alias = \"...\")]` isn't allowed on {}", err),
357+
)
358+
.emit();
359+
return false;
360+
}
361+
true
362+
}
363+
364+
fn check_doc_keyword(&self, meta: &NestedMetaItem, hir_id: HirId) -> bool {
365+
let doc_keyword = meta.value_str().map(|s| s.to_string()).unwrap_or_else(String::new);
366+
if doc_keyword.is_empty() {
367+
self.doc_attr_str_error(meta, "keyword");
368+
return false;
369+
}
370+
match self.tcx.hir().expect_item(hir_id).kind {
371+
ItemKind::Mod(ref module) => {
372+
if !module.item_ids.is_empty() {
373+
self.tcx
374+
.sess
375+
.struct_span_err(
376+
meta.span(),
377+
"`#[doc(keyword = \"...\")]` can only be used on empty modules",
378+
)
379+
.emit();
380+
return false;
381+
}
382+
}
383+
_ => {
384+
self.tcx
385+
.sess
386+
.struct_span_err(
387+
meta.span(),
388+
"`#[doc(keyword = \"...\")]` can only be used on modules",
389+
)
390+
.emit();
391+
return false;
392+
}
393+
}
394+
if !rustc_lexer::is_ident(&doc_keyword) {
395+
self.tcx
396+
.sess
397+
.struct_span_err(
398+
meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
399+
&format!("`{}` is not a valid identifier", doc_keyword),
400+
)
401+
.emit();
402+
return false;
403+
}
404+
true
405+
}
406+
407+
fn check_attr_crate_level(
408+
&self,
409+
meta: &NestedMetaItem,
410+
hir_id: HirId,
411+
attr_name: &str,
412+
) -> bool {
413+
if CRATE_HIR_ID == hir_id {
414+
self.tcx
415+
.sess
416+
.struct_span_err(
417+
meta.span(),
418+
&format!(
419+
"`#![doc({} = \"...\")]` isn't allowed as a crate level attribute",
420+
attr_name,
421+
),
422+
)
423+
.emit();
424+
return false;
425+
}
426+
true
427+
}
428+
429+
fn check_doc_attrs(&self, attr: &Attribute, hir_id: HirId, target: Target) -> bool {
301430
if let Some(mi) = attr.meta() {
302431
if let Some(list) = mi.meta_item_list() {
303432
for meta in list {
304433
if meta.has_name(sym::alias) {
305-
if !meta.is_value_str() {
306-
self.doc_alias_str_error(meta);
307-
return false;
308-
}
309-
let doc_alias =
310-
meta.value_str().map(|s| s.to_string()).unwrap_or_else(String::new);
311-
if doc_alias.is_empty() {
312-
self.doc_alias_str_error(meta);
313-
return false;
314-
}
315-
if let Some(c) = doc_alias
316-
.chars()
317-
.find(|&c| c == '"' || c == '\'' || (c.is_whitespace() && c != ' '))
434+
if !self.check_attr_crate_level(meta, hir_id, "alias")
435+
|| !self.check_doc_alias(meta, hir_id, target)
318436
{
319-
self.tcx
320-
.sess
321-
.struct_span_err(
322-
meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
323-
&format!(
324-
"{:?} character isn't allowed in `#[doc(alias = \"...\")]`",
325-
c,
326-
),
327-
)
328-
.emit();
329437
return false;
330438
}
331-
if doc_alias.starts_with(' ') || doc_alias.ends_with(' ') {
332-
self.tcx
333-
.sess
334-
.struct_span_err(
335-
meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
336-
"`#[doc(alias = \"...\")]` cannot start or end with ' '",
337-
)
338-
.emit();
339-
return false;
340-
}
341-
if let Some(err) = match target {
342-
Target::Impl => Some("implementation block"),
343-
Target::ForeignMod => Some("extern block"),
344-
Target::AssocTy => {
345-
let parent_hir_id = self.tcx.hir().get_parent_item(hir_id);
346-
let containing_item = self.tcx.hir().expect_item(parent_hir_id);
347-
if Target::from_item(containing_item) == Target::Impl {
348-
Some("type alias in implementation block")
349-
} else {
350-
None
351-
}
352-
}
353-
Target::AssocConst => {
354-
let parent_hir_id = self.tcx.hir().get_parent_item(hir_id);
355-
let containing_item = self.tcx.hir().expect_item(parent_hir_id);
356-
// We can't link to trait impl's consts.
357-
let err = "associated constant in trait implementation block";
358-
match containing_item.kind {
359-
ItemKind::Impl { of_trait: Some(_), .. } => Some(err),
360-
_ => None,
361-
}
362-
}
363-
_ => None,
364-
} {
365-
self.tcx
366-
.sess
367-
.struct_span_err(
368-
meta.span(),
369-
&format!("`#[doc(alias = \"...\")]` isn't allowed on {}", err),
370-
)
371-
.emit();
372-
return false;
373-
}
374-
if CRATE_HIR_ID == hir_id {
375-
self.tcx
376-
.sess
377-
.struct_span_err(
378-
meta.span(),
379-
"`#![doc(alias = \"...\")]` isn't allowed as a crate \
380-
level attribute",
381-
)
382-
.emit();
439+
} else if meta.has_name(sym::keyword) {
440+
if !self.check_attr_crate_level(meta, hir_id, "keyword")
441+
|| !self.check_doc_keyword(meta, hir_id)
442+
{
383443
return false;
384444
}
385445
}

src/librustdoc/clean/mod.rs

+1-16
Original file line numberDiff line numberDiff line change
@@ -162,29 +162,14 @@ impl Clean<ExternalCrate> for CrateNum {
162162
.collect()
163163
};
164164

165-
let get_span =
166-
|attr: &ast::NestedMetaItem| Some(attr.meta_item()?.name_value_literal()?.span);
167-
168165
let as_keyword = |res: Res| {
169166
if let Res::Def(DefKind::Mod, def_id) = res {
170167
let attrs = cx.tcx.get_attrs(def_id).clean(cx);
171168
let mut keyword = None;
172169
for attr in attrs.lists(sym::doc) {
173170
if attr.has_name(sym::keyword) {
174171
if let Some(v) = attr.value_str() {
175-
let k = v.to_string();
176-
if !rustc_lexer::is_ident(&k) {
177-
let sp = get_span(&attr).unwrap_or_else(|| attr.span());
178-
cx.tcx
179-
.sess
180-
.struct_span_err(
181-
sp,
182-
&format!("`{}` is not a valid identifier", v),
183-
)
184-
.emit();
185-
} else {
186-
keyword = Some(k);
187-
}
172+
keyword = Some(v.to_string());
188173
break;
189174
}
190175
}

src/test/rustdoc-ui/check-doc-alias-attr.stderr

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
error: doc alias attribute expects a string: #[doc(alias = "0")]
1+
error: doc alias attribute expects a string: #[doc(alias = "a")]
22
--> $DIR/check-doc-alias-attr.rs:6:7
33
|
44
LL | #[doc(alias)]
55
| ^^^^^
66

7-
error: doc alias attribute expects a string: #[doc(alias = "0")]
7+
error: doc alias attribute expects a string: #[doc(alias = "a")]
88
--> $DIR/check-doc-alias-attr.rs:7:7
99
|
1010
LL | #[doc(alias = 0)]
1111
| ^^^^^^^^^
1212

13-
error: doc alias attribute expects a string: #[doc(alias = "0")]
13+
error: doc alias attribute expects a string: #[doc(alias = "a")]
1414
--> $DIR/check-doc-alias-attr.rs:8:7
1515
|
1616
LL | #[doc(alias("bar"))]

src/test/ui/check-doc-alias-attr.stderr

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
error: doc alias attribute expects a string: #[doc(alias = "0")]
1+
error: doc alias attribute expects a string: #[doc(alias = "a")]
22
--> $DIR/check-doc-alias-attr.rs:7:7
33
|
44
LL | #[doc(alias)]
55
| ^^^^^
66

7-
error: doc alias attribute expects a string: #[doc(alias = "0")]
7+
error: doc alias attribute expects a string: #[doc(alias = "a")]
88
--> $DIR/check-doc-alias-attr.rs:8:7
99
|
1010
LL | #[doc(alias = 0)]
1111
| ^^^^^^^^^
1212

13-
error: doc alias attribute expects a string: #[doc(alias = "0")]
13+
error: doc alias attribute expects a string: #[doc(alias = "a")]
1414
--> $DIR/check-doc-alias-attr.rs:9:7
1515
|
1616
LL | #[doc(alias("bar"))]

src/test/ui/doc-alias-crate-level.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@
44

55
#![crate_type = "lib"]
66

7-
#![doc(alias = "shouldn't work!")] //~ ERROR
7+
#![doc(alias = "not working!")] //~ ERROR
8+
9+
#[doc(alias = "shouldn't work!")] //~ ERROR
10+
pub struct Foo;
+10-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
error: '\'' character isn't allowed in `#[doc(alias = "...")]`
2-
--> $DIR/doc-alias-crate-level.rs:7:16
2+
--> $DIR/doc-alias-crate-level.rs:9:15
33
|
4-
LL | #![doc(alias = "shouldn't work!")]
5-
| ^^^^^^^^^^^^^^^^^
4+
LL | #[doc(alias = "shouldn't work!")]
5+
| ^^^^^^^^^^^^^^^^^
66

7-
error: aborting due to previous error
7+
error: `#![doc(alias = "...")]` isn't allowed as a crate level attribute
8+
--> $DIR/doc-alias-crate-level.rs:7:8
9+
|
10+
LL | #![doc(alias = "not working!")]
11+
| ^^^^^^^^^^^^^^^^^^^^^^
12+
13+
error: aborting due to 2 previous errors
814

src/test/ui/doc_keyword.rs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#![crate_type = "lib"]
2+
#![feature(doc_keyword)]
3+
4+
#![doc(keyword = "hello")] //~ ERROR
5+
6+
#[doc(keyword = "hell")] //~ ERROR
7+
mod foo {
8+
fn hell() {}
9+
}
10+
11+
#[doc(keyword = "hall")] //~ ERROR
12+
fn foo() {}

src/test/ui/doc_keyword.stderr

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: `#[doc(keyword = "...")]` can only be used on empty modules
2+
--> $DIR/doc_keyword.rs:6:7
3+
|
4+
LL | #[doc(keyword = "hell")]
5+
| ^^^^^^^^^^^^^^^^
6+
7+
error: `#[doc(keyword = "...")]` can only be used on modules
8+
--> $DIR/doc_keyword.rs:11:7
9+
|
10+
LL | #[doc(keyword = "hall")]
11+
| ^^^^^^^^^^^^^^^^
12+
13+
error: `#![doc(keyword = "...")]` isn't allowed as a crate level attribute
14+
--> $DIR/doc_keyword.rs:4:8
15+
|
16+
LL | #![doc(keyword = "hello")]
17+
| ^^^^^^^^^^^^^^^^^
18+
19+
error: aborting due to 3 previous errors
20+

0 commit comments

Comments
 (0)