]> git.lizzy.rs Git - rust.git/commitdiff
turn intra-doc-link collection into an early pass
authorQuietMisdreavus <grey@quietmisdreavus.net>
Sat, 28 Jul 2018 02:19:36 +0000 (21:19 -0500)
committerQuietMisdreavus <grey@quietmisdreavus.net>
Sun, 5 Aug 2018 03:20:45 +0000 (22:20 -0500)
src/librustdoc/clean/mod.rs
src/librustdoc/core.rs
src/librustdoc/passes/collect_intra_doc_links.rs [new file with mode: 0644]
src/librustdoc/passes/mod.rs

index 9dbd7d7b2607d45f010cbb1758bf07027a58d9c1..ad774f9860264b1f035c250779ae9afb3f1fdbbe 100644 (file)
 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;
 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;
 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 @@ fn clean(&self, cx: &DocContext) -> Item {
         // 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<Item> = vec![];
         items.extend(self.extern_crates.iter().map(|x| x.clean(cx)));
@@ -624,8 +593,6 @@ fn clean(&self, cx: &DocContext) -> Item {
         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<syntax_pos::Span>,
     /// map from Rust paths to resolved defs and potential URL fragments
     pub links: Vec<(String, Option<DefId>, Option<String>)>,
+    pub inner_docs: bool,
 }
 
 impl Attributes {
@@ -929,12 +897,18 @@ pub fn from_ast(diagnostic: &::errors::Handler,
             }
         }
 
+        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 @@ fn lists<'a>(&'a self, name: &'a str) -> ListAttributesIter<'a> {
     }
 }
 
-/// 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<String>), ()> {
-    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<Def> {
-    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<String>), ()> {
-    // 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<Def> {
-    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<Range<usize>>,
-) {
-    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: <before$}{indicator:^<found$}",
-                line=line,
-                indicator="",
-                before=link_range.start - last_new_line_offset,
-                found=link_range.len(),
-            ));
-        }
-        diag
-    } else {
-        cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
-                                     NodeId::new(0),
-                                     sp,
-                                     &msg)
-    };
-    diag.help("to escape `[` and `]` characters, just add '\\' before them like \
-               `\\[` or `\\]`");
-    diag.emit();
-}
-
 impl Clean<Attributes> 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 @@ fn clean(&self, cx: &DocContext) -> Item {
             (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<Item> 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 @@ fn clean(&self, cx: &DocContext) -> Item {
                 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 @@ fn clean(&self, cx: &DocContext) -> Item {
                 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 @@ fn clean(&self, cx: &DocContext) -> Type {
 
 impl Clean<Item> 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 @@ fn clean(&self, cx: &DocContext) -> Vec<Item> {
         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 @@ fn clean(&self, cx: &DocContext) -> Vec<Item> {
         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 @@ fn clean(&self, cx: &DocContext) -> Vec<Item> {
         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<Item> 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<Item> 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<Item> 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<Item> 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<Item> {
     finder.get_with_def_id(id)
 }
 
-fn get_name_if_possible(cx: &DocContext, node: NodeId) -> Option<Name> {
-    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<Vec<Item>> for doctree::Impl {
     fn clean(&self, cx: &DocContext) -> Vec<Item> {
         let mut ret = Vec::new();
@@ -3928,7 +3395,6 @@ fn clean(&self, cx: &DocContext) -> Vec<Item> {
                   .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<Item> 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 @@ fn clean(&self, cx: &DocContext) -> Vec<Item> {
             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 @@ 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),
@@ -4150,7 +3613,7 @@ fn clean(&self, cx: &DocContext) -> Item {
 
 // 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<Item> 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),
index f6310417dacc0b55bee21627a65f5983dfbe1b9e..3e5f16817a8d033cb684c25b5a4dc0e9f344387e 100644 (file)
@@ -26,7 +26,7 @@
 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;
@@ -58,7 +58,6 @@ pub struct DocContext<'a, 'tcx: 'a, 'rcx: 'a, 'cstore: 'rcx> {
     pub tcx: TyCtxt<'a, 'tcx, 'tcx>,
     pub resolver: &'a RefCell<resolve::Resolver<'rcx, 'cstore>>,
     /// The stack of module NodeIds up till this point
-    pub mod_ids: RefCell<Vec<NodeId>>,
     pub crate_name: Option<String>,
     pub cstore: Rc<CStore>,
     pub populated_all_crate_impls: Cell<bool>,
@@ -88,7 +87,6 @@ pub struct DocContext<'a, 'tcx: 'a, 'rcx: 'a, 'cstore: 'rcx> {
     pub all_fake_def_ids: RefCell<FxHashSet<DefId>>,
     /// Maps (type_id, trait_id) -> auto trait impl
     pub generated_synthetics: RefCell<FxHashSet<(DefId, DefId)>>,
-    pub current_item_name: RefCell<Option<Name>>,
     pub all_traits: Vec<DefId>,
 }
 
@@ -325,7 +323,8 @@ pub fn run_core(search_paths: SearchPaths,
                 lint_cap: Option<lint::Level>,
                 describe_lints: bool,
                 mut manual_passes: Vec<String>,
-                mut default_passes: passes::DefaultPassOption) -> (clean::Crate, RenderInfo, Vec<String>)
+                mut default_passes: passes::DefaultPassOption)
+    -> (clean::Crate, RenderInfo, Vec<String>)
 {
     // Parse, resolve, and typecheck the given crate.
 
@@ -520,12 +519,10 @@ 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());
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
new file mode 100644 (file)
index 0000000..981832a
--- /dev/null
@@ -0,0 +1,598 @@
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<NodeId>,
+}
+
+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<String>)
+        -> Result<(Def, Option<String>), ()>
+    {
+        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<Item> {
+        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())
+            }
+            ExternCrateItem(ref name, ..) => Some(name.clone()),
+            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, &current_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, &current_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, &current_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,
+                                                                       &current_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, &current_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, &current_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, &current_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));
+            }
+        }
+
+        cx.sess().abort_if_errors();
+
+        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<Def> {
+    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<Range<usize>>,
+) {
+    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: <before$}{indicator:^<found$}",
+                line=line,
+                indicator="",
+                before=link_range.start - last_new_line_offset,
+                found=link_range.len(),
+            ));
+        }
+        diag
+    } else {
+        cx.tcx.struct_span_lint_node(lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE,
+                                     NodeId::new(0),
+                                     sp,
+                                     &msg)
+    };
+    diag.help("to escape `[` and `]` characters, just add '\\' before them like \
+               `\\[` or `\\]`");
+    diag.emit();
+}
+
+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 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))
+}
+
+/// 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<String>), ()> {
+    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<Def> {
+    if is_val {
+        None
+    } else {
+        PRIMITIVES.iter().find(|x| x.0 == path_str).map(|x| x.1)
+    }
+}
index 329e489f7b7899ff2f724731f65a244488cb480d..16251877bb10637faa7a993d5a012ea8f4e08ca4 100644 (file)
@@ -40,6 +40,9 @@
 mod propagate_doc_cfg;
 pub use self::propagate_doc_cfg::PROPAGATE_DOC_CFG;
 
+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 {
@@ -128,12 +131,14 @@ pub fn late_fn(self) -> Option<fn(clean::Crate) -> clean::Crate> {
     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",
@@ -142,6 +147,7 @@ pub fn late_fn(self) -> Option<fn(clean::Crate) -> clean::Crate> {
 /// 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",