diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index ede48f7eed4512ac4b6248eebc7a259b259603be..f5b2f1bb5b17844a6f89e1fd700b129deddd26af 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -12,6 +12,7 @@ use rustc_middle::ty; use rustc_resolve::ParentScope; use rustc_session::lint; +use rustc_span::hygiene::MacroKind; use rustc_span::symbol::Ident; use rustc_span::symbol::Symbol; use rustc_span::DUMMY_SP; @@ -122,6 +123,42 @@ fn variant_field( } } + /// Resolves a string as a macro. + fn macro_resolve(&self, path_str: &str, parent_id: Option) -> Option { + let cx = self.cx; + let path = ast::Path::from_ident(Ident::from_str(path_str)); + cx.enter_resolver(|resolver| { + if let Ok((Some(ext), res)) = resolver.resolve_macro_path( + &path, + None, + &ParentScope::module(resolver.graph_root()), + false, + false, + ) { + if let SyntaxExtensionKind::LegacyBang { .. } = ext.kind { + return Some(res.map_id(|_| panic!("unexpected id"))); + } + } + if let Some(res) = resolver.all_macros().get(&Symbol::intern(path_str)) { + return Some(res.map_id(|_| panic!("unexpected id"))); + } + if let Some(module_id) = parent_id.or(self.mod_ids.last().cloned()) { + let module_id = cx.tcx.hir().local_def_id(module_id); + if let Ok((_, res)) = + resolver.resolve_str_path_error(DUMMY_SP, path_str, MacroNS, module_id) + { + // don't resolve builtins like `#[derive]` + if let Res::Def(..) = res { + let res = res.map_id(|_| panic!("unexpected node_id")); + return Some(res); + } + } + } else { + debug!("attempting to resolve item without parent module: {}", path_str); + } + None + }) + } /// Resolves a string as a path within a particular namespace. Also returns an optional /// URL fragment in the case of variants and methods. fn resolve( @@ -371,6 +408,22 @@ fn resolve( } } +/// Check for resolve collisions between a trait and its derive +/// +/// These are common and we should just resolve to the trait in that case +fn is_derive_trait_collision(ns: &PerNS>) -> bool { + if let PerNS { + type_ns: Some((Res::Def(DefKind::Trait, _), _)), + macro_ns: Some((Res::Def(DefKind::Macro(MacroKind::Derive), _), _)), + .. + } = *ns + { + true + } else { + false + } +} + impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> { fn fold_item(&mut self, mut item: Item) -> Option { let item_hir_id = if item.is_mod() { @@ -532,6 +585,9 @@ fn fold_item(&mut self, mut item: Item) -> Option { } else if link.starts_with("macro@") { kind = Some(MacroNS); link.trim_start_matches("macro@") + } else if link.starts_with("derive@") { + kind = Some(MacroNS); + link.trim_start_matches("derive@") } else if link.ends_with('!') { kind = Some(MacroNS); link.trim_end_matches('!') @@ -614,8 +670,9 @@ fn fold_item(&mut self, mut item: Item) -> Option { } None => { // Try everything! - let candidates = PerNS { - macro_ns: macro_resolve(cx, path_str) + let mut candidates = PerNS { + macro_ns: self + .macro_resolve(path_str, base_node) .map(|res| (res, extra_fragment.clone())), type_ns: match self.resolve( path_str, @@ -668,10 +725,16 @@ fn fold_item(&mut self, mut item: Item) -> Option { continue; } - let is_unambiguous = candidates.clone().present_items().count() == 1; - if is_unambiguous { + let len = candidates.clone().present_items().count(); + + if len == 1 { candidates.present_items().next().unwrap() + } else if len == 2 && is_derive_trait_collision(&candidates) { + candidates.type_ns.unwrap() } else { + if is_derive_trait_collision(&candidates) { + candidates.macro_ns = None; + } ambiguity_error( cx, &item, @@ -684,7 +747,7 @@ fn fold_item(&mut self, mut item: Item) -> Option { } } Some(MacroNS) => { - if let Some(res) = macro_resolve(cx, path_str) { + if let Some(res) = self.macro_resolve(path_str, base_node) { (res, extra_fragment) } else { resolution_failure(cx, &item, path_str, &dox, link_range); @@ -727,28 +790,6 @@ fn fold_crate(&mut self, mut c: Crate) -> Crate { } } -/// Resolves a string as a macro. -fn macro_resolve(cx: &DocContext<'_>, path_str: &str) -> Option { - let path = ast::Path::from_ident(Ident::from_str(path_str)); - cx.enter_resolver(|resolver| { - if let Ok((Some(ext), res)) = resolver.resolve_macro_path( - &path, - None, - &ParentScope::module(resolver.graph_root()), - false, - false, - ) { - if let SyntaxExtensionKind::LegacyBang { .. } = ext.kind { - return Some(res.map_id(|_| panic!("unexpected id"))); - } - } - if let Some(res) = resolver.all_macros().get(&Symbol::intern(path_str)) { - return Some(res.map_id(|_| panic!("unexpected id"))); - } - None - }) -} - fn build_diagnostic( cx: &DocContext<'_>, item: &Item, @@ -916,7 +957,7 @@ fn ambiguity_error( Res::Def(DefKind::AssocFn | DefKind::Fn, _) => { ("add parentheses", format!("{}()", path_str)) } - Res::Def(DefKind::Macro(..), _) => { + Res::Def(DefKind::Macro(MacroKind::Bang), _) => { ("add an exclamation mark", format!("{}!", path_str)) } _ => { @@ -930,6 +971,9 @@ fn ambiguity_error( (Res::Def(DefKind::Mod, _), _) => "module", (_, TypeNS) => "type", (_, ValueNS) => "value", + (Res::Def(DefKind::Macro(MacroKind::Derive), _), MacroNS) => { + "derive" + } (_, MacroNS) => "macro", }; diff --git a/src/test/rustdoc/auxiliary/intra-link-proc-macro-macro.rs b/src/test/rustdoc/auxiliary/intra-link-proc-macro-macro.rs new file mode 100644 index 0000000000000000000000000000000000000000..04a431d99026e8f7c4478af244d3042042450c0e --- /dev/null +++ b/src/test/rustdoc/auxiliary/intra-link-proc-macro-macro.rs @@ -0,0 +1,35 @@ +// force-host +// no-prefer-dynamic +// compile-flags: --crate-type proc-macro + +#![crate_type="proc-macro"] +#![crate_name="intra_link_proc_macro_macro"] + +extern crate proc_macro; + +use proc_macro::TokenStream; + +#[proc_macro_derive(DeriveA)] +pub fn a_derive(input: TokenStream) -> TokenStream { + input +} + +#[proc_macro_derive(DeriveB)] +pub fn b_derive(input: TokenStream) -> TokenStream { + input +} + +#[proc_macro_derive(DeriveTrait)] +pub fn trait_derive(input: TokenStream) -> TokenStream { + input +} + +#[proc_macro_attribute] +pub fn attr_a(input: TokenStream, _args: TokenStream) -> TokenStream { + input +} + +#[proc_macro_attribute] +pub fn attr_b(input: TokenStream, _args: TokenStream) -> TokenStream { + input +} diff --git a/src/test/rustdoc/intra-link-proc-macro.rs b/src/test/rustdoc/intra-link-proc-macro.rs new file mode 100644 index 0000000000000000000000000000000000000000..7b6ea5d60f8530d0045f3904be1b296205c9624b --- /dev/null +++ b/src/test/rustdoc/intra-link-proc-macro.rs @@ -0,0 +1,27 @@ +// aux-build:intra-link-proc-macro-macro.rs +// build-aux-docs +#![deny(intra_doc_link_resolution_failure)] + +extern crate intra_link_proc_macro_macro; + + +pub use intra_link_proc_macro_macro::{DeriveA, attr_a}; +use intra_link_proc_macro_macro::{DeriveB, attr_b}; + +// @has intra_link_proc_macro/struct.Foo.html +// @has - '//a/@href' '../intra_link_proc_macro/derive.DeriveA.html' +// @has - '//a/@href' '../intra_link_proc_macro/attr.attr_a.html' +// @has - '//a/@href' '../intra_link_proc_macro/trait.DeriveTrait.html' +// @has - '//a/@href' '../intra_link_proc_macro_macro/derive.DeriveB.html' +// @has - '//a/@href' '../intra_link_proc_macro_macro/attr.attr_b.html' +/// Link to [DeriveA], [attr_a], [DeriveB], [attr_b], [DeriveTrait] +pub struct Foo; + +// @has intra_link_proc_macro/struct.Bar.html +// @has - '//a/@href' '../intra_link_proc_macro/derive.DeriveA.html' +// @has - '//a/@href' '../intra_link_proc_macro/attr.attr_a.html' +/// Link to [deriveA](derive@DeriveA) [attr](macro@attr_a) +pub struct Bar; + +// this should not cause ambiguity errors +pub trait DeriveTrait {}