diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 9dbd7d7b260..ad774f98602 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -19,14 +19,12 @@ pub use self::FunctionRetTy::*; pub use self::Visibility::{Public, Inherited}; use rustc_target::spec::abi::Abi; -use syntax; -use syntax::ast::{self, AttrStyle, Name, NodeId, Ident}; +use syntax::ast::{self, AttrStyle, Ident}; use syntax::attr; use syntax::codemap::{dummy_spanned, Spanned}; -use syntax::feature_gate::UnstableFeatures; use syntax::ptr::P; use syntax::symbol::keywords::{self, Keyword}; -use syntax::symbol::{Symbol, InternedString}; +use syntax::symbol::InternedString; use syntax_pos::{self, DUMMY_SP, Pos, FileName}; use rustc::mir::interpret::ConstValue; @@ -38,14 +36,12 @@ use rustc::mir::interpret::GlobalId; use rustc::hir::{self, GenericArg, HirVec}; use rustc::hir::def::{self, Def, CtorKind}; use rustc::hir::def_id::{CrateNum, DefId, CRATE_DEF_INDEX, LOCAL_CRATE}; -use rustc::hir::map::Node; use rustc::ty::subst::Substs; use rustc::ty::{self, TyCtxt, Region, RegionVid, Ty, AdtKind}; use rustc::middle::stability; use rustc::util::nodemap::{FxHashMap, FxHashSet}; use rustc_typeck::hir_ty_to_ty; use rustc::infer::region_constraints::{RegionConstraintData, Constraint}; -use rustc::lint as lint; use std::collections::hash_map::Entry; use std::fmt; @@ -59,14 +55,12 @@ use std::str::FromStr; use std::cell::RefCell; use std::sync::Arc; use std::u32; -use std::ops::Range; use core::{self, DocContext}; use doctree; use visit_ast; use html::render::{cache, ExternalLocation}; use html::item_type::ItemType; -use html::markdown::markdown_links; pub mod inline; pub mod cfg; @@ -580,32 +574,7 @@ impl Clean for doctree::Module { // maintain a stack of mod ids, for doc comment path resolution // but we also need to resolve the module's own docs based on whether its docs were written // inside or outside the module, so check for that - let attrs = if self.attrs.iter() - .filter(|a| a.check_name("doc")) - .next() - .map_or(true, |a| a.style == AttrStyle::Inner) { - // inner doc comment, use the module's own scope for resolution - if self.id != NodeId::new(0) { - *cx.current_item_name.borrow_mut() = Some(cx.tcx.hir.name(self.id)); - } else { - *cx.current_item_name.borrow_mut() = None; - } - cx.mod_ids.borrow_mut().push(self.id); - self.attrs.clean(cx) - } else { - // outer doc comment, use its parent's scope - match cx.mod_ids.borrow().last() { - Some(parent) if *parent != NodeId::new(0) => { - *cx.current_item_name.borrow_mut() = Some(cx.tcx.hir.name(*parent)); - } - _ => { - *cx.current_item_name.borrow_mut() = None; - } - } - let attrs = self.attrs.clean(cx); - cx.mod_ids.borrow_mut().push(self.id); - attrs - }; + let attrs = self.attrs.clean(cx); let mut items: Vec = vec![]; items.extend(self.extern_crates.iter().map(|x| x.clean(cx))); @@ -624,8 +593,6 @@ impl Clean for doctree::Module { items.extend(self.impls.iter().flat_map(|x| x.clean(cx))); items.extend(self.macros.iter().map(|x| x.clean(cx))); - cx.mod_ids.borrow_mut().pop(); - // determine if we should display the inner contents or // the outer `mod` item for the source code. let whence = { @@ -785,6 +752,7 @@ pub struct Attributes { pub span: Option, /// map from Rust paths to resolved defs and potential URL fragments pub links: Vec<(String, Option, Option)>, + pub inner_docs: bool, } impl Attributes { @@ -929,12 +897,18 @@ impl Attributes { } } + let inner_docs = attrs.iter() + .filter(|a| a.check_name("doc")) + .next() + .map_or(true, |a| a.style == AttrStyle::Inner); + Attributes { doc_strings, other_attrs, cfg: if cfg == Cfg::True { None } else { Some(Arc::new(cfg)) }, span: sp, links: vec![], + inner_docs, } } @@ -1027,487 +1001,9 @@ impl AttributesExt for Attributes { } } -/// Given a def, returns its name and disambiguator -/// for a value namespace -/// -/// Returns None for things which cannot be ambiguous since -/// they exist in both namespaces (structs and modules) -fn value_ns_kind(def: Def, path_str: &str) -> Option<(&'static str, String)> { - match def { - // structs, variants, and mods exist in both namespaces. skip them - Def::StructCtor(..) | Def::Mod(..) | Def::Variant(..) | Def::VariantCtor(..) => None, - Def::Fn(..) - => Some(("function", format!("{}()", path_str))), - Def::Method(..) - => Some(("method", format!("{}()", path_str))), - Def::Const(..) - => Some(("const", format!("const@{}", path_str))), - Def::Static(..) - => Some(("static", format!("static@{}", path_str))), - _ => Some(("value", format!("value@{}", path_str))), - } -} - -/// Given a def, returns its name, the article to be used, and a disambiguator -/// for the type namespace -fn type_ns_kind(def: Def, path_str: &str) -> (&'static str, &'static str, String) { - let (kind, article) = match def { - // we can still have non-tuple structs - Def::Struct(..) => ("struct", "a"), - Def::Enum(..) => ("enum", "an"), - Def::Trait(..) => ("trait", "a"), - Def::Union(..) => ("union", "a"), - _ => ("type", "a"), - }; - (kind, article, format!("{}@{}", kind, path_str)) -} - -fn span_of_attrs(attrs: &Attributes) -> syntax_pos::Span { - if attrs.doc_strings.is_empty() { - return DUMMY_SP; - } - let start = attrs.doc_strings[0].span(); - let end = attrs.doc_strings.last().expect("No doc strings provided").span(); - start.to(end) -} - -fn ambiguity_error(cx: &DocContext, attrs: &Attributes, - path_str: &str, - article1: &str, kind1: &str, disambig1: &str, - article2: &str, kind2: &str, disambig2: &str) { - let sp = span_of_attrs(attrs); - cx.sess() - .struct_span_warn(sp, - &format!("`{}` is both {} {} and {} {}", - path_str, article1, kind1, - article2, kind2)) - .help(&format!("try `{}` if you want to select the {}, \ - or `{}` if you want to \ - select the {}", - disambig1, kind1, disambig2, - kind2)) - .emit(); -} - -/// Given an enum variant's def, return the def of its enum and the associated fragment -fn handle_variant(cx: &DocContext, def: Def) -> Result<(Def, Option), ()> { - use rustc::ty::DefIdTree; - - let parent = if let Some(parent) = cx.tcx.parent(def.def_id()) { - parent - } else { - return Err(()) - }; - let parent_def = Def::Enum(parent); - let variant = cx.tcx.expect_variant_def(def); - Ok((parent_def, Some(format!("{}.v", variant.name)))) -} - -const PRIMITIVES: &[(&str, Def)] = &[ - ("u8", Def::PrimTy(hir::PrimTy::TyUint(syntax::ast::UintTy::U8))), - ("u16", Def::PrimTy(hir::PrimTy::TyUint(syntax::ast::UintTy::U16))), - ("u32", Def::PrimTy(hir::PrimTy::TyUint(syntax::ast::UintTy::U32))), - ("u64", Def::PrimTy(hir::PrimTy::TyUint(syntax::ast::UintTy::U64))), - ("u128", Def::PrimTy(hir::PrimTy::TyUint(syntax::ast::UintTy::U128))), - ("usize", Def::PrimTy(hir::PrimTy::TyUint(syntax::ast::UintTy::Usize))), - ("i8", Def::PrimTy(hir::PrimTy::TyInt(syntax::ast::IntTy::I8))), - ("i16", Def::PrimTy(hir::PrimTy::TyInt(syntax::ast::IntTy::I16))), - ("i32", Def::PrimTy(hir::PrimTy::TyInt(syntax::ast::IntTy::I32))), - ("i64", Def::PrimTy(hir::PrimTy::TyInt(syntax::ast::IntTy::I64))), - ("i128", Def::PrimTy(hir::PrimTy::TyInt(syntax::ast::IntTy::I128))), - ("isize", Def::PrimTy(hir::PrimTy::TyInt(syntax::ast::IntTy::Isize))), - ("f32", Def::PrimTy(hir::PrimTy::TyFloat(syntax::ast::FloatTy::F32))), - ("f64", Def::PrimTy(hir::PrimTy::TyFloat(syntax::ast::FloatTy::F64))), - ("str", Def::PrimTy(hir::PrimTy::TyStr)), - ("bool", Def::PrimTy(hir::PrimTy::TyBool)), - ("char", Def::PrimTy(hir::PrimTy::TyChar)), -]; - -fn is_primitive(path_str: &str, is_val: bool) -> Option { - if is_val { - None - } else { - PRIMITIVES.iter().find(|x| x.0 == path_str).map(|x| x.1) - } -} - -/// Resolve a given string as a path, along with whether or not it is -/// in the value namespace. Also returns an optional URL fragment in the case -/// of variants and methods -fn resolve(cx: &DocContext, path_str: &str, is_val: bool) -> Result<(Def, Option), ()> { - // In case we're in a module, try to resolve the relative - // path - if let Some(id) = cx.mod_ids.borrow().last() { - let result = cx.resolver.borrow_mut() - .with_scope(*id, - |resolver| { - resolver.resolve_str_path_error(DUMMY_SP, - &path_str, is_val) - }); - - if let Ok(result) = result { - // In case this is a trait item, skip the - // early return and try looking for the trait - let value = match result.def { - Def::Method(_) | Def::AssociatedConst(_) => true, - Def::AssociatedTy(_) => false, - Def::Variant(_) => return handle_variant(cx, result.def), - // not a trait item, just return what we found - _ => return Ok((result.def, None)) - }; - - if value != is_val { - return Err(()) - } - } else if let Some(prim) = is_primitive(path_str, is_val) { - return Ok((prim, Some(path_str.to_owned()))) - } else { - // If resolution failed, it may still be a method - // because methods are not handled by the resolver - // If so, bail when we're not looking for a value - if !is_val { - return Err(()) - } - } - - // Try looking for methods and associated items - let mut split = path_str.rsplitn(2, "::"); - let item_name = if let Some(first) = split.next() { - first - } else { - return Err(()) - }; - - let mut path = if let Some(second) = split.next() { - second.to_owned() - } else { - return Err(()) - }; - - if path == "self" || path == "Self" { - if let Some(name) = *cx.current_item_name.borrow() { - path = name.to_string(); - } - } - - let ty = cx.resolver.borrow_mut() - .with_scope(*id, - |resolver| { - resolver.resolve_str_path_error(DUMMY_SP, &path, false) - })?; - match ty.def { - Def::Struct(did) | Def::Union(did) | Def::Enum(did) | Def::TyAlias(did) => { - let item = cx.tcx.inherent_impls(did) - .iter() - .flat_map(|imp| cx.tcx.associated_items(*imp)) - .find(|item| item.ident.name == item_name); - if let Some(item) = item { - let out = match item.kind { - ty::AssociatedKind::Method if is_val => "method", - ty::AssociatedKind::Const if is_val => "associatedconstant", - _ => return Err(()) - }; - Ok((ty.def, Some(format!("{}.{}", out, item_name)))) - } else { - match cx.tcx.type_of(did).sty { - ty::TyAdt(def, _) => { - if let Some(item) = if def.is_enum() { - def.all_fields().find(|item| item.ident.name == item_name) - } else { - def.non_enum_variant() - .fields - .iter() - .find(|item| item.ident.name == item_name) - } { - Ok((ty.def, - Some(format!("{}.{}", - if def.is_enum() { - "variant" - } else { - "structfield" - }, - item.ident)))) - } else { - Err(()) - } - } - _ => Err(()), - } - } - } - Def::Trait(did) => { - let item = cx.tcx.associated_item_def_ids(did).iter() - .map(|item| cx.tcx.associated_item(*item)) - .find(|item| item.ident.name == item_name); - if let Some(item) = item { - let kind = match item.kind { - ty::AssociatedKind::Const if is_val => "associatedconstant", - ty::AssociatedKind::Type if !is_val => "associatedtype", - ty::AssociatedKind::Method if is_val => { - if item.defaultness.has_value() { - "method" - } else { - "tymethod" - } - } - _ => return Err(()) - }; - - Ok((ty.def, Some(format!("{}.{}", kind, item_name)))) - } else { - Err(()) - } - } - _ => Err(()) - } - } else { - Err(()) - } -} - -/// Resolve a string as a macro -fn macro_resolve(cx: &DocContext, path_str: &str) -> Option { - use syntax::ext::base::{MacroKind, SyntaxExtension}; - use syntax::ext::hygiene::Mark; - let segment = ast::PathSegment::from_ident(Ident::from_str(path_str)); - let path = ast::Path { segments: vec![segment], span: DUMMY_SP }; - let mut resolver = cx.resolver.borrow_mut(); - let mark = Mark::root(); - let res = resolver - .resolve_macro_to_def_inner(mark, &path, MacroKind::Bang, false); - if let Ok(def) = res { - if let SyntaxExtension::DeclMacro { .. } = *resolver.get_macro(def) { - return Some(def); - } - } - if let Some(def) = resolver.all_macros.get(&Symbol::intern(path_str)) { - return Some(*def); - } - None -} - -#[derive(Debug)] -enum PathKind { - /// can be either value or type, not a macro - Unknown, - /// macro - Macro, - /// values, functions, consts, statics, everything in the value namespace - Value, - /// types, traits, everything in the type namespace - Type, -} - -fn resolution_failure( - cx: &DocContext, - attrs: &Attributes, - path_str: &str, - dox: &str, - link_range: Option>, -) { - let sp = span_of_attrs(attrs); - let msg = format!("`[{}]` cannot be resolved, ignoring it...", path_str); - - let code_dox = sp.to_src(cx); - - let doc_comment_padding = 3; - let mut diag = if let Some(link_range) = link_range { - // blah blah blah\nblah\nblah [blah] blah blah\nblah blah - // ^ ~~~~~~ - // | link_range - // last_new_line_offset - - let mut diag; - if dox.lines().count() == code_dox.lines().count() { - let line_offset = dox[..link_range.start].lines().count(); - // The span starts in the `///`, so we don't have to account for the leading whitespace - let code_dox_len = if line_offset <= 1 { - doc_comment_padding - } else { - // The first `///` - doc_comment_padding + - // Each subsequent leading whitespace and `///` - code_dox.lines().skip(1).take(line_offset - 1).fold(0, |sum, line| { - sum + doc_comment_padding + line.len() - line.trim().len() - }) - }; - - // Extract the specific span - let sp = sp.from_inner_byte_pos( - link_range.start + code_dox_len, - link_range.end + code_dox_len, - ); - - diag = cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE, - NodeId::new(0), - sp, - &msg); - diag.span_label(sp, "cannot be resolved, ignoring"); - } else { - diag = cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE, - NodeId::new(0), - sp, - &msg); - - let last_new_line_offset = dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1); - let line = dox[last_new_line_offset..].lines().next().unwrap_or(""); - - // Print the line containing the `link_range` and manually mark it with '^'s - diag.note(&format!( - "the link appears in this line:\n\n{line}\n\ - {indicator: for [ast::Attribute] { fn clean(&self, cx: &DocContext) -> Attributes { - let mut attrs = Attributes::from_ast(cx.sess().diagnostic(), self); - - if UnstableFeatures::from_environment().is_nightly_build() { - let dox = attrs.collapsed_doc_value().unwrap_or_else(String::new); - for (ori_link, link_range) in markdown_links(&dox) { - // bail early for real links - if ori_link.contains('/') { - continue; - } - let link = ori_link.replace("`", ""); - let (def, fragment) = { - let mut kind = PathKind::Unknown; - let path_str = if let Some(prefix) = - ["struct@", "enum@", "type@", - "trait@", "union@"].iter() - .find(|p| link.starts_with(**p)) { - kind = PathKind::Type; - link.trim_left_matches(prefix) - } else if let Some(prefix) = - ["const@", "static@", - "value@", "function@", "mod@", - "fn@", "module@", "method@"] - .iter().find(|p| link.starts_with(**p)) { - kind = PathKind::Value; - link.trim_left_matches(prefix) - } else if link.ends_with("()") { - kind = PathKind::Value; - link.trim_right_matches("()") - } else if link.starts_with("macro@") { - kind = PathKind::Macro; - link.trim_left_matches("macro@") - } else if link.ends_with('!') { - kind = PathKind::Macro; - link.trim_right_matches('!') - } else { - &link[..] - }.trim(); - - if path_str.contains(|ch: char| !(ch.is_alphanumeric() || - ch == ':' || ch == '_')) { - continue; - } - - match kind { - PathKind::Value => { - if let Ok(def) = resolve(cx, path_str, true) { - def - } else { - resolution_failure(cx, &attrs, path_str, &dox, link_range); - // this could just be a normal link or a broken link - // we could potentially check if something is - // "intra-doc-link-like" and warn in that case - continue; - } - } - PathKind::Type => { - if let Ok(def) = resolve(cx, path_str, false) { - def - } else { - resolution_failure(cx, &attrs, path_str, &dox, link_range); - // this could just be a normal link - continue; - } - } - PathKind::Unknown => { - // try everything! - if let Some(macro_def) = macro_resolve(cx, path_str) { - if let Ok(type_def) = resolve(cx, path_str, false) { - let (type_kind, article, type_disambig) - = type_ns_kind(type_def.0, path_str); - ambiguity_error(cx, &attrs, path_str, - article, type_kind, &type_disambig, - "a", "macro", &format!("macro@{}", path_str)); - continue; - } else if let Ok(value_def) = resolve(cx, path_str, true) { - let (value_kind, value_disambig) - = value_ns_kind(value_def.0, path_str) - .expect("struct and mod cases should have been \ - caught in previous branch"); - ambiguity_error(cx, &attrs, path_str, - "a", value_kind, &value_disambig, - "a", "macro", &format!("macro@{}", path_str)); - } - (macro_def, None) - } else if let Ok(type_def) = resolve(cx, path_str, false) { - // It is imperative we search for not-a-value first - // Otherwise we will find struct ctors for when we are looking - // for structs, and the link won't work. - // if there is something in both namespaces - if let Ok(value_def) = resolve(cx, path_str, true) { - let kind = value_ns_kind(value_def.0, path_str); - if let Some((value_kind, value_disambig)) = kind { - let (type_kind, article, type_disambig) - = type_ns_kind(type_def.0, path_str); - ambiguity_error(cx, &attrs, path_str, - article, type_kind, &type_disambig, - "a", value_kind, &value_disambig); - continue; - } - } - type_def - } else if let Ok(value_def) = resolve(cx, path_str, true) { - value_def - } else { - resolution_failure(cx, &attrs, path_str, &dox, link_range); - // this could just be a normal link - continue; - } - } - PathKind::Macro => { - if let Some(def) = macro_resolve(cx, path_str) { - (def, None) - } else { - resolution_failure(cx, &attrs, path_str, &dox, link_range); - continue - } - } - } - }; - - if let Def::PrimTy(_) = def { - attrs.links.push((ori_link, None, fragment)); - } else { - let id = register_def(cx, def); - attrs.links.push((ori_link, Some(id), fragment)); - } - } - - cx.sess().abort_if_errors(); - } - - attrs + Attributes::from_ast(cx.sess().diagnostic(), self) } } @@ -2165,7 +1661,6 @@ impl Clean for doctree::Function { (self.generics.clean(cx), (&self.decl, self.body).clean(cx)) }); - *cx.current_item_name.borrow_mut() = Some(self.name); Item { name: Some(self.name.clean(cx)), attrs: self.attrs.clean(cx), @@ -2340,7 +1835,6 @@ pub struct Trait { impl Clean for doctree::Trait { fn clean(&self, cx: &DocContext) -> Item { - *cx.current_item_name.borrow_mut() = Some(self.name); let attrs = self.attrs.clean(cx); let is_spotlight = attrs.has_doc_flag("spotlight"); Item { @@ -2412,7 +1906,6 @@ impl Clean for hir::TraitItem { AssociatedTypeItem(bounds.clean(cx), default.clean(cx)) } }; - *cx.current_item_name.borrow_mut() = Some(self.ident.name); Item { name: Some(self.ident.name.clean(cx)), attrs: self.attrs.clean(cx), @@ -2445,7 +1938,6 @@ impl Clean for hir::ImplItem { generics: Generics::default(), }, true), }; - *cx.current_item_name.borrow_mut() = Some(self.ident.name); Item { name: Some(self.ident.name.clean(cx)), source: self.span.clean(cx), @@ -3239,7 +2731,6 @@ impl<'tcx> Clean for Ty<'tcx> { impl Clean for hir::StructField { fn clean(&self, cx: &DocContext) -> Item { - *cx.current_item_name.borrow_mut() = Some(self.ident.name); Item { name: Some(self.ident.name).clean(cx), attrs: self.attrs.clean(cx), @@ -3319,7 +2810,6 @@ impl Clean> for doctree::Struct { let mut ret = get_auto_traits_with_node_id(cx, self.id, name.clone()); ret.extend(get_blanket_impls_with_node_id(cx, self.id, name.clone())); - *cx.current_item_name.borrow_mut() = Some(self.name); ret.push(Item { name: Some(name), attrs: self.attrs.clean(cx), @@ -3346,7 +2836,6 @@ impl Clean> for doctree::Union { let mut ret = get_auto_traits_with_node_id(cx, self.id, name.clone()); ret.extend(get_blanket_impls_with_node_id(cx, self.id, name.clone())); - *cx.current_item_name.borrow_mut() = Some(self.name); ret.push(Item { name: Some(name), attrs: self.attrs.clean(cx), @@ -3400,7 +2889,6 @@ impl Clean> for doctree::Enum { let mut ret = get_auto_traits_with_node_id(cx, self.id, name.clone()); ret.extend(get_blanket_impls_with_node_id(cx, self.id, name.clone())); - *cx.current_item_name.borrow_mut() = Some(self.name); ret.push(Item { name: Some(name), attrs: self.attrs.clean(cx), @@ -3427,7 +2915,6 @@ pub struct Variant { impl Clean for doctree::Variant { fn clean(&self, cx: &DocContext) -> Item { - *cx.current_item_name.borrow_mut() = Some(self.name); Item { name: Some(self.name.clean(cx)), attrs: self.attrs.clean(cx), @@ -3708,7 +3195,6 @@ pub struct Typedef { impl Clean for doctree::Typedef { fn clean(&self, cx: &DocContext) -> Item { - *cx.current_item_name.borrow_mut() = Some(self.name); Item { name: Some(self.name.clean(cx)), attrs: self.attrs.clean(cx), @@ -3784,7 +3270,6 @@ pub struct Static { impl Clean for doctree::Static { fn clean(&self, cx: &DocContext) -> Item { debug!("cleaning static {}: {:?}", self.name.clean(cx), self); - *cx.current_item_name.borrow_mut() = Some(self.name); Item { name: Some(self.name.clean(cx)), attrs: self.attrs.clean(cx), @@ -3810,7 +3295,6 @@ pub struct Constant { impl Clean for doctree::Constant { fn clean(&self, cx: &DocContext) -> Item { - *cx.current_item_name.borrow_mut() = Some(self.name); Item { name: Some(self.name.clean(cx)), attrs: self.attrs.clean(cx), @@ -3892,23 +3376,6 @@ pub fn get_blanket_impls_with_def_id(cx: &DocContext, id: DefId) -> Vec { finder.get_with_def_id(id) } -fn get_name_if_possible(cx: &DocContext, node: NodeId) -> Option { - match cx.tcx.hir.get(node) { - Node::NodeItem(_) | - Node::NodeForeignItem(_) | - Node::NodeImplItem(_) | - Node::NodeTraitItem(_) | - Node::NodeVariant(_) | - Node::NodeField(_) | - Node::NodeLifetime(_) | - Node::NodeGenericParam(_) | - Node::NodeBinding(&hir::Pat { node: hir::PatKind::Binding(_,_,_,_), .. }) | - Node::NodeStructCtor(_) => {} - _ => return None, - } - Some(cx.tcx.hir.name(node)) -} - impl Clean> for doctree::Impl { fn clean(&self, cx: &DocContext) -> Vec { let mut ret = Vec::new(); @@ -3928,7 +3395,6 @@ impl Clean> for doctree::Impl { .collect() }).unwrap_or(FxHashSet()); - *cx.current_item_name.borrow_mut() = get_name_if_possible(cx, self.for_.id); ret.push(Item { name: None, attrs: self.attrs.clean(cx), @@ -4016,7 +3482,6 @@ fn build_deref_target_impls(cx: &DocContext, impl Clean for doctree::ExternCrate { fn clean(&self, cx: &DocContext) -> Item { - *cx.current_item_name.borrow_mut() = Some(self.name); Item { name: None, attrs: self.attrs.clean(cx), @@ -4064,7 +3529,6 @@ impl Clean> for doctree::Import { Import::Simple(name.clean(cx), resolve_use_source(cx, path)) }; - *cx.current_item_name.borrow_mut() = Some(self.name); vec![Item { name: None, attrs: self.attrs.clean(cx), @@ -4134,7 +3598,6 @@ impl Clean for hir::ForeignItem { } }; - *cx.current_item_name.borrow_mut() = Some(self.name); Item { name: Some(self.name.clean(cx)), attrs: self.attrs.clean(cx), @@ -4150,7 +3613,7 @@ impl Clean for hir::ForeignItem { // Utilities -trait ToSource { +pub trait ToSource { fn to_src(&self, cx: &DocContext) -> String; } @@ -4260,7 +3723,7 @@ fn resolve_type(cx: &DocContext, ResolvedPath { path: path, typarams: None, did: did, is_generic: is_generic } } -fn register_def(cx: &DocContext, def: Def) -> DefId { +pub fn register_def(cx: &DocContext, def: Def) -> DefId { debug!("register_def({:?})", def); let (did, kind) = match def { @@ -4311,7 +3774,6 @@ pub struct Macro { impl Clean for doctree::Macro { fn clean(&self, cx: &DocContext) -> Item { let name = self.name.clean(cx); - *cx.current_item_name.borrow_mut() = None; Item { name: Some(name.clone()), attrs: self.attrs.clean(cx), diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index 88faea9514f..86e5bbeab70 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -26,7 +26,7 @@ use rustc_metadata::creader::CrateLoader; use rustc_metadata::cstore::CStore; use rustc_target::spec::TargetTriple; -use syntax::ast::{self, Ident, Name, NodeId}; +use syntax::ast::{self, Ident}; use syntax::codemap; use syntax::edition::Edition; use syntax::feature_gate::UnstableFeatures; @@ -45,8 +45,9 @@ use std::path::PathBuf; use visit_ast::RustdocVisitor; use clean; -use clean::{get_path_for_type, Clean, MAX_DEF_ID}; +use clean::{get_path_for_type, Clean, MAX_DEF_ID, AttributesExt}; use html::render::RenderInfo; +use passes; pub use rustc::session::config::{Input, Options, CodegenOptions}; pub use rustc::session::search_paths::SearchPaths; @@ -57,7 +58,6 @@ pub struct DocContext<'a, 'tcx: 'a, 'rcx: 'a, 'cstore: 'rcx> { pub tcx: TyCtxt<'a, 'tcx, 'tcx>, pub resolver: &'a RefCell>, /// The stack of module NodeIds up till this point - pub mod_ids: RefCell>, pub crate_name: Option, pub cstore: Rc, pub populated_all_crate_impls: Cell, @@ -87,7 +87,6 @@ pub struct DocContext<'a, 'tcx: 'a, 'rcx: 'a, 'cstore: 'rcx> { pub all_fake_def_ids: RefCell>, /// Maps (type_id, trait_id) -> auto trait impl pub generated_synthetics: RefCell>, - pub current_item_name: RefCell>, pub all_traits: Vec, } @@ -322,7 +321,10 @@ pub fn run_core(search_paths: SearchPaths, error_format: ErrorOutputType, cmd_lints: Vec<(String, lint::Level)>, lint_cap: Option, - describe_lints: bool) -> (clean::Crate, RenderInfo) + describe_lints: bool, + mut manual_passes: Vec, + mut default_passes: passes::DefaultPassOption) + -> (clean::Crate, RenderInfo, Vec) { // Parse, resolve, and typecheck the given crate. @@ -517,23 +519,86 @@ pub fn run_core(search_paths: SearchPaths, ty_substs: Default::default(), lt_substs: Default::default(), impl_trait_bounds: Default::default(), - mod_ids: Default::default(), send_trait: send_trait, fake_def_ids: RefCell::new(FxHashMap()), all_fake_def_ids: RefCell::new(FxHashSet()), generated_synthetics: RefCell::new(FxHashSet()), - current_item_name: RefCell::new(None), all_traits: tcx.all_traits(LOCAL_CRATE).to_vec(), }; debug!("crate: {:?}", tcx.hir.krate()); - let krate = { + let mut krate = { let mut v = RustdocVisitor::new(&ctxt); v.visit(tcx.hir.krate()); v.clean(&ctxt) }; - (krate, ctxt.renderinfo.into_inner()) + fn report_deprecated_attr(name: &str, diag: &errors::Handler) { + let mut msg = diag.struct_warn(&format!("the `#![doc({})]` attribute is \ + considered deprecated", name)); + msg.warn("please see https://github.com/rust-lang/rust/issues/44136"); + + if name == "no_default_passes" { + msg.help("you may want to use `#![doc(document_private_items)]`"); + } + + msg.emit(); + } + + // Process all of the crate attributes, extracting plugin metadata along + // with the passes which we are supposed to run. + for attr in krate.module.as_ref().unwrap().attrs.lists("doc") { + let diag = ctxt.sess().diagnostic(); + + let name = attr.name().map(|s| s.as_str()); + let name = name.as_ref().map(|s| &s[..]); + if attr.is_word() { + if name == Some("no_default_passes") { + report_deprecated_attr("no_default_passes", diag); + if default_passes == passes::DefaultPassOption::Default { + default_passes = passes::DefaultPassOption::None; + } + } + } else if let Some(value) = attr.value_str() { + let sink = match name { + Some("passes") => { + report_deprecated_attr("passes = \"...\"", diag); + &mut manual_passes + }, + Some("plugins") => { + report_deprecated_attr("plugins = \"...\"", diag); + eprintln!("WARNING: #![doc(plugins = \"...\")] no longer functions; \ + see CVE-2018-1000622"); + continue + }, + _ => continue, + }; + for p in value.as_str().split_whitespace() { + sink.push(p.to_string()); + } + } + + if attr.is_word() && name == Some("document_private_items") { + if default_passes == passes::DefaultPassOption::Default { + default_passes = passes::DefaultPassOption::Private; + } + } + } + + let mut passes: Vec = + passes::defaults(default_passes).iter().map(|p| p.to_string()).collect(); + passes.extend(manual_passes); + + for pass in &passes { + // the "unknown pass" error will be reported when late passes are run + if let Some(pass) = passes::find_pass(pass).and_then(|p| p.early_fn()) { + krate = pass(krate, &ctxt); + } + } + + ctxt.sess().abort_if_errors(); + + (krate, ctxt.renderinfo.into_inner(), passes) }), &sess) }) } diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index dda97cfdb2c..bd7f7386fd1 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -26,6 +26,7 @@ #![feature(entry_and_modify)] #![feature(ptr_offset_from)] #![feature(crate_visibility_modifier)] +#![feature(const_fn)] #![recursion_limit="256"] @@ -96,8 +97,6 @@ mod visit_lib; mod test; mod theme; -use clean::AttributesExt; - struct Output { krate: clean::Crate, renderinfo: html::render::RenderInfo, @@ -367,8 +366,8 @@ fn main_args(args: &[String]) -> isize { if matches.opt_strs("passes") == ["list"] { println!("Available passes for running rustdoc:"); - for &(name, _, description) in passes::PASSES { - println!("{:>20} - {}", name, description); + for pass in passes::PASSES { + println!("{:>20} - {}", pass.name(), pass.description()); } println!("\nDefault passes for rustdoc:"); for &name in passes::DEFAULT_PASSES { @@ -630,7 +629,7 @@ fn rust_input(cratefile: PathBuf, where R: 'static + Send, F: 'static + Send + FnOnce(Output) -> R { - let mut default_passes = if matches.opt_present("no-defaults") { + let default_passes = if matches.opt_present("no-defaults") { passes::DefaultPassOption::None } else if matches.opt_present("document-private-items") { passes::DefaultPassOption::Private @@ -638,8 +637,8 @@ where R: 'static + Send, passes::DefaultPassOption::Default }; - let mut manual_passes = matches.opt_strs("passes"); - let mut plugins = matches.opt_strs("plugins"); + let manual_passes = matches.opt_strs("passes"); + let plugins = matches.opt_strs("plugins"); // First, parse the crate and extract all relevant information. let mut paths = SearchPaths::new(); @@ -673,11 +672,11 @@ where R: 'static + Send, let result = rustc_driver::monitor(move || syntax::with_globals(move || { use rustc::session::config::Input; - let (mut krate, renderinfo) = + let (mut krate, renderinfo, passes) = core::run_core(paths, cfgs, externs, Input::File(cratefile), triple, maybe_sysroot, display_warnings, crate_name.clone(), force_unstable_if_unmarked, edition, cg, error_format, - lint_opts, lint_cap, describe_lints); + lint_opts, lint_cap, describe_lints, manual_passes, default_passes); info!("finished with rustc"); @@ -687,58 +686,6 @@ where R: 'static + Send, krate.version = crate_version; - let diag = core::new_handler(error_format, None); - - fn report_deprecated_attr(name: &str, diag: &errors::Handler) { - let mut msg = diag.struct_warn(&format!("the `#![doc({})]` attribute is \ - considered deprecated", name)); - msg.warn("please see https://github.com/rust-lang/rust/issues/44136"); - - if name == "no_default_passes" { - msg.help("you may want to use `#![doc(document_private_items)]`"); - } - - msg.emit(); - } - - // Process all of the crate attributes, extracting plugin metadata along - // with the passes which we are supposed to run. - for attr in krate.module.as_ref().unwrap().attrs.lists("doc") { - let name = attr.name().map(|s| s.as_str()); - let name = name.as_ref().map(|s| &s[..]); - if attr.is_word() { - if name == Some("no_default_passes") { - report_deprecated_attr("no_default_passes", &diag); - if default_passes == passes::DefaultPassOption::Default { - default_passes = passes::DefaultPassOption::None; - } - } - } else if let Some(value) = attr.value_str() { - let sink = match name { - Some("passes") => { - report_deprecated_attr("passes = \"...\"", &diag); - &mut manual_passes - }, - Some("plugins") => { - report_deprecated_attr("plugins = \"...\"", &diag); - &mut plugins - }, - _ => continue, - }; - sink.extend(value.as_str().split_whitespace().map(|p| p.to_string())); - } - - if attr.is_word() && name == Some("document_private_items") { - if default_passes == passes::DefaultPassOption::Default { - default_passes = passes::DefaultPassOption::Private; - } - } - } - - let mut passes: Vec = - passes::defaults(default_passes).iter().map(|p| p.to_string()).collect(); - passes.extend(manual_passes); - if !plugins.is_empty() { eprintln!("WARNING: --plugins no longer functions; see CVE-2018-1000622"); } @@ -751,8 +698,13 @@ where R: 'static + Send, for pass in &passes { // determine if we know about this pass - let pass = match passes::PASSES.iter().find(|(p, ..)| p == pass) { - Some(pass) => pass.1, + let pass = match passes::find_pass(pass) { + Some(pass) => if let Some(pass) = pass.late_fn() { + pass + } else { + // not a late pass, but still valid so don't report the error + continue + } None => { error!("unknown pass {}, skipping", *pass); diff --git a/src/librustdoc/passes/collapse_docs.rs b/src/librustdoc/passes/collapse_docs.rs index 6f70fcf1099..33d052775ba 100644 --- a/src/librustdoc/passes/collapse_docs.rs +++ b/src/librustdoc/passes/collapse_docs.rs @@ -11,8 +11,13 @@ use clean::{self, DocFragment, Item}; use fold; use fold::DocFolder; +use passes::Pass; use std::mem::replace; +pub const COLLAPSE_DOCS: Pass = + Pass::late("collapse-docs", collapse_docs, + "concatenates all document attributes into one document attribute"); + #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum DocFragmentKind { Sugared, diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs new file mode 100644 index 00000000000..28d504c2ee0 --- /dev/null +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -0,0 +1,597 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use clean::*; + +use rustc::lint as lint; +use rustc::hir; +use rustc::hir::def::Def; +use rustc::ty; +use syntax; +use syntax::ast::{self, Ident, NodeId}; +use syntax::feature_gate::UnstableFeatures; +use syntax::symbol::Symbol; +use syntax_pos::{self, DUMMY_SP}; + +use std::ops::Range; + +use core::DocContext; +use fold::DocFolder; +use html::markdown::markdown_links; +use passes::Pass; + +pub const COLLECT_INTRA_DOC_LINKS: Pass = + Pass::early("collect-intra-doc-links", collect_intra_doc_links, + "reads a crate's documentation to resolve intra-doc-links"); + +pub fn collect_intra_doc_links(krate: Crate, cx: &DocContext) -> Crate { + if !UnstableFeatures::from_environment().is_nightly_build() { + krate + } else { + let mut coll = LinkCollector::new(cx); + + coll.fold_crate(krate) + } +} + +#[derive(Debug)] +enum PathKind { + /// can be either value or type, not a macro + Unknown, + /// macro + Macro, + /// values, functions, consts, statics, everything in the value namespace + Value, + /// types, traits, everything in the type namespace + Type, +} + +struct LinkCollector<'a, 'tcx: 'a, 'rcx: 'a, 'cstore: 'rcx> { + cx: &'a DocContext<'a, 'tcx, 'rcx, 'cstore>, + mod_ids: Vec, +} + +impl<'a, 'tcx, 'rcx, 'cstore> LinkCollector<'a, 'tcx, 'rcx, 'cstore> { + fn new(cx: &'a DocContext<'a, 'tcx, 'rcx, 'cstore>) -> Self { + LinkCollector { + cx, + mod_ids: Vec::new(), + } + } + + /// Resolve a given string as a path, along with whether or not it is + /// in the value namespace. Also returns an optional URL fragment in the case + /// of variants and methods + fn resolve(&self, path_str: &str, is_val: bool, current_item: &Option) + -> Result<(Def, Option), ()> + { + let cx = self.cx; + + // In case we're in a module, try to resolve the relative + // path + if let Some(id) = self.mod_ids.last() { + let result = cx.resolver.borrow_mut() + .with_scope(*id, + |resolver| { + resolver.resolve_str_path_error(DUMMY_SP, + &path_str, is_val) + }); + + if let Ok(result) = result { + // In case this is a trait item, skip the + // early return and try looking for the trait + let value = match result.def { + Def::Method(_) | Def::AssociatedConst(_) => true, + Def::AssociatedTy(_) => false, + Def::Variant(_) => return handle_variant(cx, result.def), + // not a trait item, just return what we found + _ => return Ok((result.def, None)) + }; + + if value != is_val { + return Err(()) + } + } else if let Some(prim) = is_primitive(path_str, is_val) { + return Ok((prim, Some(path_str.to_owned()))) + } else { + // If resolution failed, it may still be a method + // because methods are not handled by the resolver + // If so, bail when we're not looking for a value + if !is_val { + return Err(()) + } + } + + // Try looking for methods and associated items + let mut split = path_str.rsplitn(2, "::"); + let item_name = if let Some(first) = split.next() { + first + } else { + return Err(()) + }; + + let mut path = if let Some(second) = split.next() { + second.to_owned() + } else { + return Err(()) + }; + + if path == "self" || path == "Self" { + if let Some(name) = current_item.as_ref() { + path = name.clone(); + } + } + + let ty = cx.resolver.borrow_mut() + .with_scope(*id, + |resolver| { + resolver.resolve_str_path_error(DUMMY_SP, &path, false) + })?; + match ty.def { + Def::Struct(did) | Def::Union(did) | Def::Enum(did) | Def::TyAlias(did) => { + let item = cx.tcx.inherent_impls(did) + .iter() + .flat_map(|imp| cx.tcx.associated_items(*imp)) + .find(|item| item.ident.name == item_name); + if let Some(item) = item { + let out = match item.kind { + ty::AssociatedKind::Method if is_val => "method", + ty::AssociatedKind::Const if is_val => "associatedconstant", + _ => return Err(()) + }; + Ok((ty.def, Some(format!("{}.{}", out, item_name)))) + } else { + match cx.tcx.type_of(did).sty { + ty::TyAdt(def, _) => { + if let Some(item) = if def.is_enum() { + def.all_fields().find(|item| item.ident.name == item_name) + } else { + def.non_enum_variant() + .fields + .iter() + .find(|item| item.ident.name == item_name) + } { + Ok((ty.def, + Some(format!("{}.{}", + if def.is_enum() { + "variant" + } else { + "structfield" + }, + item.ident)))) + } else { + Err(()) + } + } + _ => Err(()), + } + } + } + Def::Trait(did) => { + let item = cx.tcx.associated_item_def_ids(did).iter() + .map(|item| cx.tcx.associated_item(*item)) + .find(|item| item.ident.name == item_name); + if let Some(item) = item { + let kind = match item.kind { + ty::AssociatedKind::Const if is_val => "associatedconstant", + ty::AssociatedKind::Type if !is_val => "associatedtype", + ty::AssociatedKind::Method if is_val => { + if item.defaultness.has_value() { + "method" + } else { + "tymethod" + } + } + _ => return Err(()) + }; + + Ok((ty.def, Some(format!("{}.{}", kind, item_name)))) + } else { + Err(()) + } + } + _ => Err(()) + } + } else { + Err(()) + } + } +} + +impl<'a, 'tcx, 'rcx, 'cstore> DocFolder for LinkCollector<'a, 'tcx, 'rcx, 'cstore> { + fn fold_item(&mut self, mut item: Item) -> Option { + let item_node_id = if item.is_mod() { + if let Some(id) = self.cx.tcx.hir.as_local_node_id(item.def_id) { + Some(id) + } else { + debug!("attempting to fold on a non-local item: {:?}", item); + return self.fold_item_recur(item); + } + } else { + None + }; + + let current_item = match item.inner { + ModuleItem(..) => { + if item.attrs.inner_docs { + if item_node_id.unwrap() != NodeId::new(0) { + item.name.clone() + } else { + None + } + } else { + match self.mod_ids.last() { + Some(parent) if *parent != NodeId::new(0) => { + //FIXME: can we pull the parent module's name from elsewhere? + Some(self.cx.tcx.hir.name(*parent).to_string()) + } + _ => None, + } + } + } + ImplItem(Impl { ref for_, .. }) => { + for_.def_id().map(|did| self.cx.tcx.item_name(did).to_string()) + } + // we don't display docs on `extern crate` items anyway, so don't process them + ExternCrateItem(..) => return self.fold_item_recur(item), + ImportItem(Import::Simple(ref name, ..)) => Some(name.clone()), + MacroItem(..) => None, + _ => item.name.clone(), + }; + + if item.is_mod() && item.attrs.inner_docs { + self.mod_ids.push(item_node_id.unwrap()); + } + + let cx = self.cx; + let dox = item.attrs.collapsed_doc_value().unwrap_or_else(String::new); + + for (ori_link, link_range) in markdown_links(&dox) { + // bail early for real links + if ori_link.contains('/') { + continue; + } + let link = ori_link.replace("`", ""); + let (def, fragment) = { + let mut kind = PathKind::Unknown; + let path_str = if let Some(prefix) = + ["struct@", "enum@", "type@", + "trait@", "union@"].iter() + .find(|p| link.starts_with(**p)) { + kind = PathKind::Type; + link.trim_left_matches(prefix) + } else if let Some(prefix) = + ["const@", "static@", + "value@", "function@", "mod@", + "fn@", "module@", "method@"] + .iter().find(|p| link.starts_with(**p)) { + kind = PathKind::Value; + link.trim_left_matches(prefix) + } else if link.ends_with("()") { + kind = PathKind::Value; + link.trim_right_matches("()") + } else if link.starts_with("macro@") { + kind = PathKind::Macro; + link.trim_left_matches("macro@") + } else if link.ends_with('!') { + kind = PathKind::Macro; + link.trim_right_matches('!') + } else { + &link[..] + }.trim(); + + if path_str.contains(|ch: char| !(ch.is_alphanumeric() || + ch == ':' || ch == '_')) { + continue; + } + + match kind { + PathKind::Value => { + if let Ok(def) = self.resolve(path_str, true, ¤t_item) { + def + } else { + resolution_failure(cx, &item.attrs, path_str, &dox, link_range); + // this could just be a normal link or a broken link + // we could potentially check if something is + // "intra-doc-link-like" and warn in that case + continue; + } + } + PathKind::Type => { + if let Ok(def) = self.resolve(path_str, false, ¤t_item) { + def + } else { + resolution_failure(cx, &item.attrs, path_str, &dox, link_range); + // this could just be a normal link + continue; + } + } + PathKind::Unknown => { + // try everything! + if let Some(macro_def) = macro_resolve(cx, path_str) { + if let Ok(type_def) = self.resolve(path_str, false, ¤t_item) { + let (type_kind, article, type_disambig) + = type_ns_kind(type_def.0, path_str); + ambiguity_error(cx, &item.attrs, path_str, + article, type_kind, &type_disambig, + "a", "macro", &format!("macro@{}", path_str)); + continue; + } else if let Ok(value_def) = self.resolve(path_str, + true, + ¤t_item) { + let (value_kind, value_disambig) + = value_ns_kind(value_def.0, path_str) + .expect("struct and mod cases should have been \ + caught in previous branch"); + ambiguity_error(cx, &item.attrs, path_str, + "a", value_kind, &value_disambig, + "a", "macro", &format!("macro@{}", path_str)); + } + (macro_def, None) + } else if let Ok(type_def) = self.resolve(path_str, false, ¤t_item) { + // It is imperative we search for not-a-value first + // Otherwise we will find struct ctors for when we are looking + // for structs, and the link won't work. + // if there is something in both namespaces + if let Ok(value_def) = self.resolve(path_str, true, ¤t_item) { + let kind = value_ns_kind(value_def.0, path_str); + if let Some((value_kind, value_disambig)) = kind { + let (type_kind, article, type_disambig) + = type_ns_kind(type_def.0, path_str); + ambiguity_error(cx, &item.attrs, path_str, + article, type_kind, &type_disambig, + "a", value_kind, &value_disambig); + continue; + } + } + type_def + } else if let Ok(value_def) = self.resolve(path_str, true, ¤t_item) { + value_def + } else { + resolution_failure(cx, &item.attrs, path_str, &dox, link_range); + // this could just be a normal link + continue; + } + } + PathKind::Macro => { + if let Some(def) = macro_resolve(cx, path_str) { + (def, None) + } else { + resolution_failure(cx, &item.attrs, path_str, &dox, link_range); + continue + } + } + } + }; + + if let Def::PrimTy(_) = def { + item.attrs.links.push((ori_link, None, fragment)); + } else { + let id = register_def(cx, def); + item.attrs.links.push((ori_link, Some(id), fragment)); + } + } + + if item.is_mod() && !item.attrs.inner_docs { + self.mod_ids.push(item_node_id.unwrap()); + } + + if item.is_mod() { + let ret = self.fold_item_recur(item); + + self.mod_ids.pop(); + + ret + } else { + self.fold_item_recur(item) + } + } +} + +/// Resolve a string as a macro +fn macro_resolve(cx: &DocContext, path_str: &str) -> Option { + use syntax::ext::base::{MacroKind, SyntaxExtension}; + use syntax::ext::hygiene::Mark; + let segment = ast::PathSegment::from_ident(Ident::from_str(path_str)); + let path = ast::Path { segments: vec![segment], span: DUMMY_SP }; + let mut resolver = cx.resolver.borrow_mut(); + let mark = Mark::root(); + let res = resolver + .resolve_macro_to_def_inner(mark, &path, MacroKind::Bang, false); + if let Ok(def) = res { + if let SyntaxExtension::DeclMacro { .. } = *resolver.get_macro(def) { + return Some(def); + } + } + if let Some(def) = resolver.all_macros.get(&Symbol::intern(path_str)) { + return Some(*def); + } + None +} + +fn span_of_attrs(attrs: &Attributes) -> syntax_pos::Span { + if attrs.doc_strings.is_empty() { + return DUMMY_SP; + } + let start = attrs.doc_strings[0].span(); + let end = attrs.doc_strings.last().expect("No doc strings provided").span(); + start.to(end) +} + +fn resolution_failure( + cx: &DocContext, + attrs: &Attributes, + path_str: &str, + dox: &str, + link_range: Option>, +) { + let sp = span_of_attrs(attrs); + let msg = format!("`[{}]` cannot be resolved, ignoring it...", path_str); + + let code_dox = sp.to_src(cx); + + let doc_comment_padding = 3; + let mut diag = if let Some(link_range) = link_range { + // blah blah blah\nblah\nblah [blah] blah blah\nblah blah + // ^ ~~~~~~ + // | link_range + // last_new_line_offset + + let mut diag; + if dox.lines().count() == code_dox.lines().count() { + let line_offset = dox[..link_range.start].lines().count(); + // The span starts in the `///`, so we don't have to account for the leading whitespace + let code_dox_len = if line_offset <= 1 { + doc_comment_padding + } else { + // The first `///` + doc_comment_padding + + // Each subsequent leading whitespace and `///` + code_dox.lines().skip(1).take(line_offset - 1).fold(0, |sum, line| { + sum + doc_comment_padding + line.len() - line.trim().len() + }) + }; + + // Extract the specific span + let sp = sp.from_inner_byte_pos( + link_range.start + code_dox_len, + link_range.end + code_dox_len, + ); + + diag = cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE, + NodeId::new(0), + sp, + &msg); + diag.span_label(sp, "cannot be resolved, ignoring"); + } else { + diag = cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE, + NodeId::new(0), + sp, + &msg); + + let last_new_line_offset = dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1); + let line = dox[last_new_line_offset..].lines().next().unwrap_or(""); + + // Print the line containing the `link_range` and manually mark it with '^'s + diag.note(&format!( + "the link appears in this line:\n\n{line}\n\ + {indicator: Option<(&'static str, String)> { + match def { + // structs, variants, and mods exist in both namespaces. skip them + Def::StructCtor(..) | Def::Mod(..) | Def::Variant(..) | Def::VariantCtor(..) => None, + Def::Fn(..) + => Some(("function", format!("{}()", path_str))), + Def::Method(..) + => Some(("method", format!("{}()", path_str))), + Def::Const(..) + => Some(("const", format!("const@{}", path_str))), + Def::Static(..) + => Some(("static", format!("static@{}", path_str))), + _ => Some(("value", format!("value@{}", path_str))), + } +} + +/// Given a def, returns its name, the article to be used, and a disambiguator +/// for the type namespace +fn type_ns_kind(def: Def, path_str: &str) -> (&'static str, &'static str, String) { + let (kind, article) = match def { + // we can still have non-tuple structs + Def::Struct(..) => ("struct", "a"), + Def::Enum(..) => ("enum", "an"), + Def::Trait(..) => ("trait", "a"), + Def::Union(..) => ("union", "a"), + _ => ("type", "a"), + }; + (kind, article, format!("{}@{}", kind, path_str)) +} + +/// Given an enum variant's def, return the def of its enum and the associated fragment +fn handle_variant(cx: &DocContext, def: Def) -> Result<(Def, Option), ()> { + use rustc::ty::DefIdTree; + + let parent = if let Some(parent) = cx.tcx.parent(def.def_id()) { + parent + } else { + return Err(()) + }; + let parent_def = Def::Enum(parent); + let variant = cx.tcx.expect_variant_def(def); + Ok((parent_def, Some(format!("{}.v", variant.name)))) +} + +const PRIMITIVES: &[(&str, Def)] = &[ + ("u8", Def::PrimTy(hir::PrimTy::TyUint(syntax::ast::UintTy::U8))), + ("u16", Def::PrimTy(hir::PrimTy::TyUint(syntax::ast::UintTy::U16))), + ("u32", Def::PrimTy(hir::PrimTy::TyUint(syntax::ast::UintTy::U32))), + ("u64", Def::PrimTy(hir::PrimTy::TyUint(syntax::ast::UintTy::U64))), + ("u128", Def::PrimTy(hir::PrimTy::TyUint(syntax::ast::UintTy::U128))), + ("usize", Def::PrimTy(hir::PrimTy::TyUint(syntax::ast::UintTy::Usize))), + ("i8", Def::PrimTy(hir::PrimTy::TyInt(syntax::ast::IntTy::I8))), + ("i16", Def::PrimTy(hir::PrimTy::TyInt(syntax::ast::IntTy::I16))), + ("i32", Def::PrimTy(hir::PrimTy::TyInt(syntax::ast::IntTy::I32))), + ("i64", Def::PrimTy(hir::PrimTy::TyInt(syntax::ast::IntTy::I64))), + ("i128", Def::PrimTy(hir::PrimTy::TyInt(syntax::ast::IntTy::I128))), + ("isize", Def::PrimTy(hir::PrimTy::TyInt(syntax::ast::IntTy::Isize))), + ("f32", Def::PrimTy(hir::PrimTy::TyFloat(syntax::ast::FloatTy::F32))), + ("f64", Def::PrimTy(hir::PrimTy::TyFloat(syntax::ast::FloatTy::F64))), + ("str", Def::PrimTy(hir::PrimTy::TyStr)), + ("bool", Def::PrimTy(hir::PrimTy::TyBool)), + ("char", Def::PrimTy(hir::PrimTy::TyChar)), +]; + +fn is_primitive(path_str: &str, is_val: bool) -> Option { + if is_val { + None + } else { + PRIMITIVES.iter().find(|x| x.0 == path_str).map(|x| x.1) + } +} diff --git a/src/librustdoc/passes/mod.rs b/src/librustdoc/passes/mod.rs index aa4acaf75bf..16251877bb1 100644 --- a/src/librustdoc/passes/mod.rs +++ b/src/librustdoc/passes/mod.rs @@ -8,88 +8,153 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +//! Contains information about "passes", used to modify crate information during the documentation +//! process. + use rustc::hir::def_id::DefId; use rustc::middle::privacy::AccessLevels; use rustc::util::nodemap::DefIdSet; use std::mem; +use std::fmt; use clean::{self, GetDefId, Item}; +use core::DocContext; use fold; use fold::StripItem; mod collapse_docs; -pub use self::collapse_docs::collapse_docs; +pub use self::collapse_docs::COLLAPSE_DOCS; mod strip_hidden; -pub use self::strip_hidden::strip_hidden; +pub use self::strip_hidden::STRIP_HIDDEN; mod strip_private; -pub use self::strip_private::strip_private; +pub use self::strip_private::STRIP_PRIVATE; mod strip_priv_imports; -pub use self::strip_priv_imports::strip_priv_imports; +pub use self::strip_priv_imports::STRIP_PRIV_IMPORTS; mod unindent_comments; -pub use self::unindent_comments::unindent_comments; +pub use self::unindent_comments::UNINDENT_COMMENTS; mod propagate_doc_cfg; -pub use self::propagate_doc_cfg::propagate_doc_cfg; +pub use self::propagate_doc_cfg::PROPAGATE_DOC_CFG; -type Pass = ( - &'static str, // name - fn(clean::Crate) -> clean::Crate, // fn - &'static str, -); // description +mod collect_intra_doc_links; +pub use self::collect_intra_doc_links::COLLECT_INTRA_DOC_LINKS; +/// Represents a single pass. +#[derive(Copy, Clone)] +pub enum Pass { + /// An "early pass" is run in the compiler context, and can gather information about types and + /// traits and the like. + EarlyPass { + name: &'static str, + pass: fn(clean::Crate, &DocContext) -> clean::Crate, + description: &'static str, + }, + /// A "late pass" is run between crate cleaning and page generation. + LatePass { + name: &'static str, + pass: fn(clean::Crate) -> clean::Crate, + description: &'static str, + }, +} + +impl fmt::Debug for Pass { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut dbg = match *self { + Pass::EarlyPass { .. } => f.debug_struct("EarlyPass"), + Pass::LatePass { .. } => f.debug_struct("LatePass"), + }; + + dbg.field("name", &self.name()) + .field("pass", &"...") + .field("description", &self.description()) + .finish() + } +} + +impl Pass { + /// Constructs a new early pass. + pub const fn early(name: &'static str, + pass: fn(clean::Crate, &DocContext) -> clean::Crate, + description: &'static str) -> Pass { + Pass::EarlyPass { name, pass, description } + } + + /// Constructs a new late pass. + pub const fn late(name: &'static str, + pass: fn(clean::Crate) -> clean::Crate, + description: &'static str) -> Pass { + Pass::LatePass { name, pass, description } + } + + /// Returns the name of this pass. + pub fn name(self) -> &'static str { + match self { + Pass::EarlyPass { name, .. } | + Pass::LatePass { name, .. } => name, + } + } + + /// Returns the description of this pass. + pub fn description(self) -> &'static str { + match self { + Pass::EarlyPass { description, .. } | + Pass::LatePass { description, .. } => description, + } + } + + /// If this pass is an early pass, returns the pointer to its function. + pub fn early_fn(self) -> Option clean::Crate> { + match self { + Pass::EarlyPass { pass, .. } => Some(pass), + _ => None, + } + } + + /// If this pass is a late pass, returns the pointer to its function. + pub fn late_fn(self) -> Option clean::Crate> { + match self { + Pass::LatePass { pass, .. } => Some(pass), + _ => None, + } + } +} + +/// The full list of passes. pub const PASSES: &'static [Pass] = &[ - ( - "strip-hidden", - strip_hidden, - "strips all doc(hidden) items from the output", - ), - ( - "unindent-comments", - unindent_comments, - "removes excess indentation on comments in order for markdown to like it", - ), - ( - "collapse-docs", - collapse_docs, - "concatenates all document attributes into one document attribute", - ), - ( - "strip-private", - strip_private, - "strips all private items from a crate which cannot be seen externally, \ - implies strip-priv-imports", - ), - ( - "strip-priv-imports", - strip_priv_imports, - "strips all private import statements (`use`, `extern crate`) from a crate", - ), - ( - "propagate-doc-cfg", - propagate_doc_cfg, - "propagates `#[doc(cfg(...))]` to child items", - ), + STRIP_HIDDEN, + UNINDENT_COMMENTS, + COLLAPSE_DOCS, + STRIP_PRIVATE, + STRIP_PRIV_IMPORTS, + PROPAGATE_DOC_CFG, + COLLECT_INTRA_DOC_LINKS, ]; +/// The list of passes run by default. pub const DEFAULT_PASSES: &'static [&'static str] = &[ "strip-hidden", "strip-private", + "collect-intra-doc-links", "collapse-docs", "unindent-comments", "propagate-doc-cfg", ]; +/// The list of default passes run with `--document-private-items` is passed to rustdoc. pub const DEFAULT_PRIVATE_PASSES: &'static [&'static str] = &[ "strip-priv-imports", + "collect-intra-doc-links", "collapse-docs", "unindent-comments", "propagate-doc-cfg", ]; +/// A shorthand way to refer to which set of passes to use, based on the presence of +/// `--no-defaults` or `--document-private-items`. #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum DefaultPassOption { Default, @@ -97,6 +162,7 @@ pub enum DefaultPassOption { None, } +/// Returns the given default set of passes. pub fn defaults(default_set: DefaultPassOption) -> &'static [&'static str] { match default_set { DefaultPassOption::Default => DEFAULT_PASSES, @@ -105,6 +171,11 @@ pub fn defaults(default_set: DefaultPassOption) -> &'static [&'static str] { } } +/// If the given name matches a known pass, returns its information. +pub fn find_pass(pass_name: &str) -> Option { + PASSES.iter().find(|p| p.name() == pass_name).cloned() +} + struct Stripper<'a> { retained: &'a mut DefIdSet, access_levels: &'a AccessLevels, diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs index 572a8d3f470..69093846302 100644 --- a/src/librustdoc/passes/propagate_doc_cfg.rs +++ b/src/librustdoc/passes/propagate_doc_cfg.rs @@ -13,6 +13,11 @@ use std::sync::Arc; use clean::{Crate, Item}; use clean::cfg::Cfg; use fold::DocFolder; +use passes::Pass; + +pub const PROPAGATE_DOC_CFG: Pass = + Pass::late("propagate-doc-cfg", propagate_doc_cfg, + "propagates `#[doc(cfg(...))]` to child items"); pub fn propagate_doc_cfg(cr: Crate) -> Crate { CfgPropagator { parent_cfg: None }.fold_crate(cr) diff --git a/src/librustdoc/passes/strip_hidden.rs b/src/librustdoc/passes/strip_hidden.rs index 279c9603703..cc0b6fb6d67 100644 --- a/src/librustdoc/passes/strip_hidden.rs +++ b/src/librustdoc/passes/strip_hidden.rs @@ -13,13 +13,18 @@ use std::mem; use clean::{self, AttributesExt, NestedAttributesExt}; use clean::Item; +use core::DocContext; use fold; use fold::DocFolder; use fold::StripItem; -use passes::ImplStripper; +use passes::{ImplStripper, Pass}; + +pub const STRIP_HIDDEN: Pass = + Pass::early("strip-hidden", strip_hidden, + "strips all doc(hidden) items from the output"); /// Strip items marked `#[doc(hidden)]` -pub fn strip_hidden(krate: clean::Crate) -> clean::Crate { +pub fn strip_hidden(krate: clean::Crate, _: &DocContext) -> clean::Crate { let mut retained = DefIdSet(); // strip all #[doc(hidden)] items diff --git a/src/librustdoc/passes/strip_priv_imports.rs b/src/librustdoc/passes/strip_priv_imports.rs index c4640839923..f01c333d742 100644 --- a/src/librustdoc/passes/strip_priv_imports.rs +++ b/src/librustdoc/passes/strip_priv_imports.rs @@ -9,9 +9,13 @@ // except according to those terms. use clean; +use core::DocContext; use fold::DocFolder; -use passes::ImportStripper; +use passes::{ImportStripper, Pass}; -pub fn strip_priv_imports(krate: clean::Crate) -> clean::Crate { +pub const STRIP_PRIV_IMPORTS: Pass = Pass::early("strip-priv-imports", strip_priv_imports, + "strips all private import statements (`use`, `extern crate`) from a crate"); + +pub fn strip_priv_imports(krate: clean::Crate, _: &DocContext) -> clean::Crate { ImportStripper.fold_crate(krate) } diff --git a/src/librustdoc/passes/strip_private.rs b/src/librustdoc/passes/strip_private.rs index 45f706590e3..3b17a768ffd 100644 --- a/src/librustdoc/passes/strip_private.rs +++ b/src/librustdoc/passes/strip_private.rs @@ -11,12 +11,18 @@ use rustc::util::nodemap::DefIdSet; use clean; +use core::DocContext; use fold::DocFolder; -use passes::{ImplStripper, ImportStripper, Stripper}; +use passes::{ImplStripper, ImportStripper, Stripper, Pass}; + +pub const STRIP_PRIVATE: Pass = + Pass::early("strip-private", strip_private, + "strips all private items from a crate which cannot be seen externally, \ + implies strip-priv-imports"); /// Strip private items from the point of view of a crate or externally from a /// crate, specified by the `xcrate` flag. -pub fn strip_private(mut krate: clean::Crate) -> clean::Crate { +pub fn strip_private(mut krate: clean::Crate, _: &DocContext) -> clean::Crate { // This stripper collects all *retained* nodes. let mut retained = DefIdSet(); let access_levels = krate.access_levels.clone(); diff --git a/src/librustdoc/passes/unindent_comments.rs b/src/librustdoc/passes/unindent_comments.rs index 2510ec011b6..6d875c107c8 100644 --- a/src/librustdoc/passes/unindent_comments.rs +++ b/src/librustdoc/passes/unindent_comments.rs @@ -14,6 +14,11 @@ use std::usize; use clean::{self, DocFragment, Item}; use fold::{self, DocFolder}; +use passes::Pass; + +pub const UNINDENT_COMMENTS: Pass = + Pass::late("unindent-comments", unindent_comments, + "removes excess indentation on comments in order for markdown to like it"); pub fn unindent_comments(krate: clean::Crate) -> clean::Crate { CommentCleaner.fold_crate(krate) diff --git a/src/test/run-make-fulldeps/exit-code/lint-failure.rs b/src/test/run-make-fulldeps/exit-code/lint-failure.rs index 3bf40b753c1..910abfd5d7e 100644 --- a/src/test/run-make-fulldeps/exit-code/lint-failure.rs +++ b/src/test/run-make-fulldeps/exit-code/lint-failure.rs @@ -11,6 +11,6 @@ #![deny(intra_doc_link_resolution_failure)] /// [intradoc::failure] -fn main() { +pub fn main() { println!("Hello, world!"); } diff --git a/src/test/rustdoc/auxiliary/intra-link-extern-crate.rs b/src/test/rustdoc/auxiliary/intra-link-extern-crate.rs new file mode 100644 index 00000000000..e4a194466cc --- /dev/null +++ b/src/test/rustdoc/auxiliary/intra-link-extern-crate.rs @@ -0,0 +1,13 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![crate_name="inner"] + +//! ooh, i'm a rebel just for [kicks] diff --git a/src/test/rustdoc/intra-link-extern-crate.rs b/src/test/rustdoc/intra-link-extern-crate.rs new file mode 100644 index 00000000000..5666f3bd2f2 --- /dev/null +++ b/src/test/rustdoc/intra-link-extern-crate.rs @@ -0,0 +1,19 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:intra-link-extern-crate.rs + +// When loading `extern crate` statements, we would pull in their docs at the same time, even +// though they would never actually get displayed. This tripped intra-doc-link resolution failures, +// for items that aren't under our control, and not actually getting documented! + +#![deny(intra_doc_link_resolution_failure)] + +extern crate inner; diff --git a/src/test/rustdoc/intra-link-private.rs b/src/test/rustdoc/intra-link-private.rs new file mode 100644 index 00000000000..dbdfbc4e5ad --- /dev/null +++ b/src/test/rustdoc/intra-link-private.rs @@ -0,0 +1,18 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Rustdoc would previously report resolution failures on items that weren't in the public docs. +// These failures were legitimate, but not truly relevant - the docs in question couldn't be +// checked for accuracy anyway. + +#![deny(intra_doc_link_resolution_failure)] + +/// ooh, i'm a [rebel] just for kicks +struct SomeStruct;