diff --git a/src/librustc/hir/lowering.rs b/src/librustc/hir/lowering.rs index 32b55a05124ac..a87f2747a57f5 100644 --- a/src/librustc/hir/lowering.rs +++ b/src/librustc/hir/lowering.rs @@ -151,6 +151,11 @@ pub trait Resolver { /// We must keep the set of definitions up to date as we add nodes that weren't in the AST. /// This should only return `None` during testing. fn definitions(&mut self) -> &mut Definitions; + + /// Given suffix ["b","c","d"], creates a HIR path for `[::crate_root]::b::c::d` and resolves + /// it based on `is_value`. + fn resolve_str_path(&mut self, span: Span, crate_root: Option<&str>, + components: &[&str], is_value: bool) -> hir::Path; } #[derive(Clone, Copy, Debug)] @@ -3625,16 +3630,7 @@ impl<'a> LoweringContext<'a> { /// `fld.cx.use_std`, and `::core::b::c::d` otherwise. /// The path is also resolved according to `is_value`. fn std_path(&mut self, span: Span, components: &[&str], is_value: bool) -> hir::Path { - let mut path = hir::Path { - span, - def: Def::Err, - segments: iter::once(keywords::CrateRoot.name()).chain({ - self.crate_root.into_iter().chain(components.iter().cloned()).map(Symbol::intern) - }).map(hir::PathSegment::from_name).collect(), - }; - - self.resolver.resolve_hir_path(&mut path, is_value); - path + self.resolver.resolve_str_path(span, self.crate_root, components, is_value) } fn signal_block_expr(&mut self, diff --git a/src/librustc_driver/driver.rs b/src/librustc_driver/driver.rs index 468a08b1fd9f3..b2897bd454839 100644 --- a/src/librustc_driver/driver.rs +++ b/src/librustc_driver/driver.rs @@ -28,7 +28,7 @@ use rustc::util::common::{ErrorReported, time}; use rustc_allocator as allocator; use rustc_borrowck as borrowck; use rustc_incremental; -use rustc_resolve::{MakeGlobMap, Resolver}; +use rustc_resolve::{MakeGlobMap, Resolver, ResolverArenas}; use rustc_metadata::creader::CrateLoader; use rustc_metadata::cstore::{self, CStore}; use rustc_trans_utils::trans_crate::TransCrate; @@ -139,6 +139,7 @@ pub fn compile_input(trans: Box, let crate_name = ::rustc_trans_utils::link::find_crate_name(Some(sess), &krate.attrs, input); + let ExpansionResult { expanded_crate, defs, analysis, resolutions, mut hir_forest } = { phase_2_configure_and_expand( sess, @@ -562,6 +563,12 @@ pub struct ExpansionResult { pub hir_forest: hir_map::Forest, } +pub struct InnerExpansionResult<'a> { + pub expanded_crate: ast::Crate, + pub resolver: Resolver<'a>, + pub hir_forest: hir_map::Forest, +} + /// Run the "early phases" of the compiler: initial `cfg` processing, /// loading compiler plugins (including those from `addl_plugins`), /// syntax expansion, secondary `cfg` expansion, synthesis of a test @@ -578,6 +585,55 @@ pub fn phase_2_configure_and_expand(sess: &Session, make_glob_map: MakeGlobMap, after_expand: F) -> Result + where F: FnOnce(&ast::Crate) -> CompileResult { + // Currently, we ignore the name resolution data structures for the purposes of dependency + // tracking. Instead we will run name resolution and include its output in the hash of each + // item, much like we do for macro expansion. In other words, the hash reflects not just + // its contents but the results of name resolution on those contents. Hopefully we'll push + // this back at some point. + let mut crate_loader = CrateLoader::new(sess, &cstore, &crate_name); + let resolver_arenas = Resolver::arenas(); + let result = phase_2_configure_and_expand_inner(sess, cstore, krate, registry, crate_name, + addl_plugins, make_glob_map, &resolver_arenas, + &mut crate_loader, after_expand); + match result { + Ok(InnerExpansionResult {expanded_crate, resolver, hir_forest}) => { + Ok(ExpansionResult { + expanded_crate, + defs: resolver.definitions, + hir_forest, + resolutions: Resolutions { + freevars: resolver.freevars, + export_map: resolver.export_map, + trait_map: resolver.trait_map, + maybe_unused_trait_imports: resolver.maybe_unused_trait_imports, + maybe_unused_extern_crates: resolver.maybe_unused_extern_crates, + }, + + analysis: ty::CrateAnalysis { + access_levels: Rc::new(AccessLevels::default()), + name: crate_name.to_string(), + glob_map: if resolver.make_glob_map { Some(resolver.glob_map) } else { None }, + }, + }) + } + Err(x) => Err(x) + } +} + +/// Same as phase_2_configure_and_expand, but doesn't let you keep the resolver +/// around +pub fn phase_2_configure_and_expand_inner<'a, F>(sess: &'a Session, + cstore: &'a CStore, + krate: ast::Crate, + registry: Option, + crate_name: &str, + addl_plugins: Option>, + make_glob_map: MakeGlobMap, + resolver_arenas: &'a ResolverArenas<'a>, + crate_loader: &'a mut CrateLoader, + after_expand: F) + -> Result, CompileIncomplete> where F: FnOnce(&ast::Crate) -> CompileResult, { let time_passes = sess.time_passes(); @@ -666,19 +722,12 @@ pub fn phase_2_configure_and_expand(sess: &Session, return Err(CompileIncomplete::Stopped); } - // Currently, we ignore the name resolution data structures for the purposes of dependency - // tracking. Instead we will run name resolution and include its output in the hash of each - // item, much like we do for macro expansion. In other words, the hash reflects not just - // its contents but the results of name resolution on those contents. Hopefully we'll push - // this back at some point. - let mut crate_loader = CrateLoader::new(sess, &cstore, crate_name); - let resolver_arenas = Resolver::arenas(); let mut resolver = Resolver::new(sess, cstore, &krate, crate_name, make_glob_map, - &mut crate_loader, + crate_loader, &resolver_arenas); resolver.whitelisted_legacy_custom_derives = whitelisted_legacy_custom_derives; syntax_ext::register_builtins(&mut resolver, syntax_exts, sess.features.borrow().quote); @@ -855,21 +904,9 @@ pub fn phase_2_configure_and_expand(sess: &Session, syntax::ext::hygiene::clear_markings(); } - Ok(ExpansionResult { + Ok(InnerExpansionResult { expanded_crate: krate, - defs: resolver.definitions, - analysis: ty::CrateAnalysis { - access_levels: Rc::new(AccessLevels::default()), - name: crate_name.to_string(), - glob_map: if resolver.make_glob_map { Some(resolver.glob_map) } else { None }, - }, - resolutions: Resolutions { - freevars: resolver.freevars, - export_map: resolver.export_map, - trait_map: resolver.trait_map, - maybe_unused_trait_imports: resolver.maybe_unused_trait_imports, - maybe_unused_extern_crates: resolver.maybe_unused_extern_crates, - }, + resolver, hir_forest, }) } diff --git a/src/librustc_resolve/lib.rs b/src/librustc_resolve/lib.rs index d9ae776a4d7b2..55c7e5f392416 100644 --- a/src/librustc_resolve/lib.rs +++ b/src/librustc_resolve/lib.rs @@ -67,6 +67,7 @@ use std::cell::{Cell, RefCell}; use std::cmp; use std::collections::BTreeSet; use std::fmt; +use std::iter; use std::mem::replace; use std::rc::Rc; @@ -1320,6 +1321,7 @@ pub struct Resolver<'a> { crate_loader: &'a mut CrateLoader, macro_names: FxHashSet, global_macros: FxHashMap>, + pub all_macros: FxHashMap, lexical_macro_resolutions: Vec<(Ident, &'a Cell>)>, macro_map: FxHashMap>, macro_defs: FxHashMap, @@ -1407,6 +1409,71 @@ impl<'a, 'b: 'a> ty::DefIdTree for &'a Resolver<'b> { impl<'a> hir::lowering::Resolver for Resolver<'a> { fn resolve_hir_path(&mut self, path: &mut hir::Path, is_value: bool) { + self.resolve_hir_path_cb(path, is_value, + |resolver, span, error| resolve_error(resolver, span, error)) + } + + fn resolve_str_path(&mut self, span: Span, crate_root: Option<&str>, + components: &[&str], is_value: bool) -> hir::Path { + let mut path = hir::Path { + span, + def: Def::Err, + segments: iter::once(keywords::CrateRoot.name()).chain({ + crate_root.into_iter().chain(components.iter().cloned()).map(Symbol::intern) + }).map(hir::PathSegment::from_name).collect(), + }; + + self.resolve_hir_path(&mut path, is_value); + path + } + + fn get_resolution(&mut self, id: NodeId) -> Option { + self.def_map.get(&id).cloned() + } + + fn definitions(&mut self) -> &mut Definitions { + &mut self.definitions + } +} + +impl<'a> Resolver<'a> { + /// Rustdoc uses this to resolve things in a recoverable way. ResolutionError<'a> + /// isn't something that can be returned because it can't be made to live that long, + /// and also it's a private type. Fortunately rustdoc doesn't need to know the error, + /// just that an error occured. + pub fn resolve_str_path_error(&mut self, span: Span, path_str: &str, is_value: bool) + -> Result { + use std::iter; + let mut errored = false; + + let mut path = if path_str.starts_with("::") { + hir::Path { + span, + def: Def::Err, + segments: iter::once(keywords::CrateRoot.name()).chain({ + path_str.split("::").skip(1).map(Symbol::intern) + }).map(hir::PathSegment::from_name).collect(), + } + } else { + hir::Path { + span, + def: Def::Err, + segments: path_str.split("::").map(Symbol::intern) + .map(hir::PathSegment::from_name).collect(), + } + }; + self.resolve_hir_path_cb(&mut path, is_value, |_, _, _| errored = true); + if errored || path.def == Def::Err { + Err(()) + } else { + Ok(path) + } + } + + /// resolve_hir_path, but takes a callback in case there was an error + fn resolve_hir_path_cb(&mut self, path: &mut hir::Path, is_value: bool, error_callback: F) + where F: for<'c, 'b> FnOnce(&'c mut Resolver, Span, ResolutionError<'b>) + { let namespace = if is_value { ValueNS } else { TypeNS }; let hir::Path { ref segments, span, ref mut def } = *path; let path: Vec = segments.iter() @@ -1418,24 +1485,16 @@ impl<'a> hir::lowering::Resolver for Resolver<'a> { *def = path_res.base_def(), PathResult::NonModule(..) => match self.resolve_path(&path, None, true, span) { PathResult::Failed(span, msg, _) => { - resolve_error(self, span, ResolutionError::FailedToResolve(&msg)); + error_callback(self, span, ResolutionError::FailedToResolve(&msg)); } _ => {} }, PathResult::Indeterminate => unreachable!(), PathResult::Failed(span, msg, _) => { - resolve_error(self, span, ResolutionError::FailedToResolve(&msg)); + error_callback(self, span, ResolutionError::FailedToResolve(&msg)); } } } - - fn get_resolution(&mut self, id: NodeId) -> Option { - self.def_map.get(&id).cloned() - } - - fn definitions(&mut self) -> &mut Definitions { - &mut self.definitions - } } impl<'a> Resolver<'a> { @@ -1538,6 +1597,7 @@ impl<'a> Resolver<'a> { crate_loader, macro_names: FxHashSet(), global_macros: FxHashMap(), + all_macros: FxHashMap(), lexical_macro_resolutions: Vec::new(), macro_map: FxHashMap(), macro_exports: Vec::new(), @@ -1833,8 +1893,8 @@ impl<'a> Resolver<'a> { // generate a fake "implementation scope" containing all the // implementations thus found, for compatibility with old resolve pass. - fn with_scope(&mut self, id: NodeId, f: F) - where F: FnOnce(&mut Resolver) + pub fn with_scope(&mut self, id: NodeId, f: F) -> T + where F: FnOnce(&mut Resolver) -> T { let id = self.definitions.local_def_id(id); let module = self.module_map.get(&id).cloned(); // clones a reference @@ -1845,13 +1905,14 @@ impl<'a> Resolver<'a> { self.ribs[TypeNS].push(Rib::new(ModuleRibKind(module))); self.finalize_current_module_macro_resolutions(); - f(self); + let ret = f(self); self.current_module = orig_module; self.ribs[ValueNS].pop(); self.ribs[TypeNS].pop(); + ret } else { - f(self); + f(self) } } diff --git a/src/librustc_resolve/macros.rs b/src/librustc_resolve/macros.rs index ceb39aea108c8..080ef3252a633 100644 --- a/src/librustc_resolve/macros.rs +++ b/src/librustc_resolve/macros.rs @@ -409,7 +409,7 @@ impl<'a> Resolver<'a> { def } - fn resolve_macro_to_def_inner(&mut self, scope: Mark, path: &ast::Path, + pub fn resolve_macro_to_def_inner(&mut self, scope: Mark, path: &ast::Path, kind: MacroKind, force: bool) -> Result { let ast::Path { ref segments, span } = *path; @@ -755,8 +755,9 @@ impl<'a> Resolver<'a> { *legacy_scope = LegacyScope::Binding(self.arenas.alloc_legacy_binding(LegacyBinding { parent: Cell::new(*legacy_scope), ident: ident, def_id: def_id, span: item.span, })); + let def = Def::Macro(def_id, MacroKind::Bang); + self.all_macros.insert(ident.name, def); if attr::contains_name(&item.attrs, "macro_export") { - let def = Def::Macro(def_id, MacroKind::Bang); self.macro_exports.push(Export { ident: ident.modern(), def: def, diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index b8c34d78d305e..e4e3cc2acd5ef 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -135,7 +135,11 @@ pub fn record_extern_fqn(cx: &DocContext, did: DefId, kind: clean::TypeKind) { None } }); - let fqn = once(crate_name).chain(relative).collect(); + let fqn = if let clean::TypeKind::Macro = kind { + vec![crate_name, relative.last().unwrap()] + } else { + once(crate_name).chain(relative).collect() + }; cx.renderinfo.borrow_mut().external_paths.insert(did, (fqn, kind)); } diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index cc75664cacbcc..fb1d21d6527af 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -20,9 +20,10 @@ pub use self::FunctionRetTy::*; pub use self::Visibility::*; use syntax::abi::Abi; -use syntax::ast; +use syntax::ast::{self, AttrStyle}; use syntax::attr; use syntax::codemap::Spanned; +use syntax::feature_gate::UnstableFeatures; use syntax::ptr::P; use syntax::symbol::keywords; use syntax_pos::{self, DUMMY_SP, Pos, FileName}; @@ -53,6 +54,7 @@ use core::DocContext; use doctree; use visit_ast; use html::item_type::ItemType; +use html::markdown::markdown_links; pub mod inline; pub mod cfg; @@ -124,7 +126,7 @@ pub struct Crate { pub masked_crates: FxHashSet, } -impl<'a, 'tcx> Clean for visit_ast::RustdocVisitor<'a, 'tcx> { +impl<'a, 'tcx, 'rcx> Clean for visit_ast::RustdocVisitor<'a, 'tcx, 'rcx> { fn clean(&self, cx: &DocContext) -> Crate { use ::visit_lib::LibEmbargoVisitor; @@ -305,6 +307,11 @@ impl Item { pub fn collapsed_doc_value(&self) -> Option { self.attrs.collapsed_doc_value() } + + pub fn links(&self) -> Vec<(String, String)> { + self.attrs.links() + } + pub fn is_crate(&self) -> bool { match self.inner { StrippedItem(box ModuleItem(Module { is_crate: true, ..})) | @@ -465,6 +472,23 @@ impl Clean for doctree::Module { "".to_string() }; + // 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 + cx.mod_ids.borrow_mut().push(self.id); + self.attrs.clean(cx) + } else { + // outer doc comment, use its parent's scope + let attrs = self.attrs.clean(cx); + cx.mod_ids.borrow_mut().push(self.id); + attrs + }; + let mut items: Vec = vec![]; items.extend(self.extern_crates.iter().map(|x| x.clean(cx))); items.extend(self.imports.iter().flat_map(|x| x.clean(cx))); @@ -481,6 +505,8 @@ 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 = { @@ -498,7 +524,7 @@ impl Clean for doctree::Module { Item { name: Some(name), - attrs: self.attrs.clean(cx), + attrs, source: whence.clean(cx), visibility: self.vis.clean(cx), stability: self.stab.clean(cx), @@ -633,6 +659,7 @@ pub struct Attributes { pub other_attrs: Vec, pub cfg: Option>, pub span: Option, + pub links: Vec<(String, DefId)>, } impl Attributes { @@ -762,11 +789,13 @@ impl Attributes { Some(attr.clone()) }) }).collect(); + Attributes { doc_strings, other_attrs, cfg: if cfg == Cfg::True { None } else { Some(Rc::new(cfg)) }, span: sp, + links: vec![], } } @@ -785,6 +814,20 @@ impl Attributes { None } } + + /// Get links as a vector + /// + /// Cache must be populated before call + pub fn links(&self) -> Vec<(String, String)> { + use html::format::href; + self.links.iter().filter_map(|&(ref s, did)| { + if let Some((href, ..)) = href(did) { + Some((s.clone(), href)) + } else { + None + } + }).collect() + } } impl AttributesExt for Attributes { @@ -793,9 +836,243 @@ 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 and mods exist in both namespaces. skip them + Def::StructCtor(..) | Def::Mod(..) => None, + Def::Variant(..) | Def::VariantCtor(..) + => Some(("variant", format!("{}()", path_str))), + 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 ambiguity_error(cx: &DocContext, attrs: &Attributes, + path_str: &str, + article1: &str, kind1: &str, disambig1: &str, + article2: &str, kind2: &str, disambig2: &str) { + let sp = attrs.doc_strings.first() + .map_or(DUMMY_SP, |a| a.span()); + cx.sess() + .struct_span_err(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(); +} + +/// Resolve a given string as a path, along with whether or not it is +/// in the value namespace +fn resolve(cx: &DocContext, path_str: &str, is_val: bool) -> Result { + // In case we're in a module, try to resolve the relative + // path + if let Some(id) = cx.mod_ids.borrow().last() { + cx.resolver.borrow_mut() + .with_scope(*id, |resolver| { + resolver.resolve_str_path_error(DUMMY_SP, + &path_str, is_val) + }) + } else { + // FIXME(Manishearth) this branch doesn't seem to ever be hit, really + cx.resolver.borrow_mut() + .resolve_str_path_error(DUMMY_SP, &path_str, is_val) + } +} + +/// Resolve a string as a macro +fn macro_resolve(cx: &DocContext, path_str: &str) -> Option { + use syntax::ext::base::MacroKind; + use syntax::ext::hygiene::Mark; + let segment = ast::PathSegment { + identifier: ast::Ident::from_str(path_str), + span: DUMMY_SP, + parameters: None, + }; + let path = ast::Path { + span: DUMMY_SP, + segments: vec![segment], + }; + + 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 { + Some(def) + } else if let Some(def) = resolver.all_macros.get(&path_str.into()) { + Some(*def) + } else { + None + } +} + +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 +} + impl Clean for [ast::Attribute] { fn clean(&self, cx: &DocContext) -> Attributes { - Attributes::from_ast(cx.sess().diagnostic(), self) + 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 link in markdown_links(&dox, cx.render_type) { + let def = { + 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@"] + .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(); + + // avoid resolving things (i.e. regular links) which aren't like paths + // FIXME(Manishearth) given that most links have slashes in them might be worth + // doing a check for slashes first + if path_str.contains(|ch: char| !(ch.is_alphanumeric() || + ch == ':' || ch == '_')) { + continue; + } + + + match kind { + PathKind::Value => { + if let Ok(path) = resolve(cx, path_str, true) { + path.def + } else { + // 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(path) = resolve(cx, path_str, false) { + path.def + } else { + // 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_path) = resolve(cx, path_str, false) { + let (type_kind, article, type_disambig) + = type_ns_kind(type_path.def, 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_path) = resolve(cx, path_str, true) { + let (value_kind, value_disambig) + = value_ns_kind(value_path.def, 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 + } else if let Ok(type_path) = 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_path) = resolve(cx, path_str, true) { + let kind = value_ns_kind(value_path.def, path_str); + if let Some((value_kind, value_disambig)) = kind { + let (type_kind, article, type_disambig) + = type_ns_kind(type_path.def, path_str); + ambiguity_error(cx, &attrs, path_str, + article, type_kind, &type_disambig, + "a", value_kind, &value_disambig); + continue; + } + } + type_path.def + } else if let Ok(value_path) = resolve(cx, path_str, true) { + value_path.def + } else { + // this could just be a normal link + continue; + } + } + PathKind::Macro => { + if let Some(def) = macro_resolve(cx, path_str) { + def + } else { + continue + } + } + } + }; + + + let id = register_def(cx, def); + attrs.links.push((link, id)); + } + + cx.sess().abort_if_errors(); + } + + attrs } } @@ -1853,6 +2130,7 @@ pub enum TypeKind { Variant, Typedef, Foreign, + Macro, } pub trait GetDefId { @@ -3154,6 +3432,7 @@ fn register_def(cx: &DocContext, def: Def) -> DefId { Def::TyForeign(i) => (i, TypeKind::Foreign), Def::Static(i, _) => (i, TypeKind::Static), Def::Variant(i) => (cx.tcx.parent_def_id(i).unwrap(), TypeKind::Enum), + Def::Macro(i, _) => (i, TypeKind::Macro), Def::SelfTy(Some(def_id), _) => (def_id, TypeKind::Trait), Def::SelfTy(_, Some(impl_def_id)) => { return impl_def_id diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index ef7d5b5ff84af..5fe4794389ff5 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -20,8 +20,10 @@ use rustc::lint; use rustc::util::nodemap::FxHashMap; use rustc_trans; use rustc_resolve as resolve; +use rustc_metadata::creader::CrateLoader; use rustc_metadata::cstore::CStore; +use syntax::ast::NodeId; use syntax::codemap; use syntax::feature_gate::UnstableFeatures; use errors; @@ -35,6 +37,7 @@ use std::path::PathBuf; use visit_ast::RustdocVisitor; use clean; use clean::Clean; +use html::markdown::RenderType; use html::render::RenderInfo; pub use rustc::session::config::Input; @@ -42,8 +45,11 @@ pub use rustc::session::search_paths::SearchPaths; pub type ExternalPaths = FxHashMap, clean::TypeKind)>; -pub struct DocContext<'a, 'tcx: 'a> { +pub struct DocContext<'a, 'tcx: 'a, 'rcx: 'a> { pub tcx: TyCtxt<'a, 'tcx, 'tcx>, + pub resolver: &'a RefCell>, + /// The stack of module NodeIds up till this point + pub mod_ids: RefCell>, pub populated_all_crate_impls: Cell, // Note that external items for which `doc(hidden)` applies to are shown as // non-reachable while local items aren't. This is because we're reusing @@ -54,6 +60,8 @@ pub struct DocContext<'a, 'tcx: 'a> { pub renderinfo: RefCell, /// Later on moved through `clean::Crate` into `html::render::CACHE_KEY` pub external_traits: RefCell>, + /// Which markdown renderer to use when extracting links. + pub render_type: RenderType, // The current set of type and lifetime substitutions, // for expanding type aliases at the HIR level: @@ -64,7 +72,7 @@ pub struct DocContext<'a, 'tcx: 'a> { pub lt_substs: RefCell>, } -impl<'a, 'tcx> DocContext<'a, 'tcx> { +impl<'a, 'tcx, 'rcx> DocContext<'a, 'tcx, 'rcx> { pub fn sess(&self) -> &session::Session { &self.tcx.sess } @@ -104,7 +112,8 @@ pub fn run_core(search_paths: SearchPaths, triple: Option, maybe_sysroot: Option, allow_warnings: bool, - force_unstable_if_unmarked: bool) -> (clean::Crate, RenderInfo) + force_unstable_if_unmarked: bool, + render_type: RenderType) -> (clean::Crate, RenderInfo) { // Parse, resolve, and typecheck the given crate. @@ -156,16 +165,40 @@ pub fn run_core(search_paths: SearchPaths, let name = ::rustc_trans_utils::link::find_crate_name(Some(&sess), &krate.attrs, &input); - let driver::ExpansionResult { defs, analysis, resolutions, mut hir_forest, .. } = { - let result = driver::phase_2_configure_and_expand(&sess, - &cstore, - krate, - None, - &name, - None, - resolve::MakeGlobMap::No, - |_| Ok(())); - abort_on_err(result, &sess) + let mut crate_loader = CrateLoader::new(&sess, &cstore, &name); + + let resolver_arenas = resolve::Resolver::arenas(); + let result = driver::phase_2_configure_and_expand_inner(&sess, + &cstore, + krate, + None, + &name, + None, + resolve::MakeGlobMap::No, + &resolver_arenas, + &mut crate_loader, + |_| Ok(())); + let driver::InnerExpansionResult { + mut hir_forest, + resolver, + .. + } = abort_on_err(result, &sess); + + // We need to hold on to the complete resolver, so we clone everything + // for the analysis passes to use. Suboptimal, but necessary in the + // current architecture. + let defs = resolver.definitions.clone(); + let resolutions = ty::Resolutions { + freevars: resolver.freevars.clone(), + export_map: resolver.export_map.clone(), + trait_map: resolver.trait_map.clone(), + maybe_unused_trait_imports: resolver.maybe_unused_trait_imports.clone(), + maybe_unused_extern_crates: resolver.maybe_unused_extern_crates.clone(), + }; + let analysis = ty::CrateAnalysis { + access_levels: Rc::new(AccessLevels::default()), + name: name.to_string(), + glob_map: if resolver.make_glob_map { Some(resolver.glob_map.clone()) } else { None }, }; let arenas = AllArenas::new(); @@ -176,6 +209,8 @@ pub fn run_core(search_paths: SearchPaths, &[], &sess); + let resolver = RefCell::new(resolver); + abort_on_err(driver::phase_3_run_analysis_passes(&*trans, control, &sess, @@ -203,12 +238,15 @@ pub fn run_core(search_paths: SearchPaths, let ctxt = DocContext { tcx, + resolver: &resolver, populated_all_crate_impls: Cell::new(false), access_levels: RefCell::new(access_levels), external_traits: Default::default(), renderinfo: Default::default(), + render_type, ty_substs: Default::default(), lt_substs: Default::default(), + mod_ids: Default::default(), }; debug!("crate: {:?}", tcx.hir.krate()); diff --git a/src/librustdoc/externalfiles.rs b/src/librustdoc/externalfiles.rs index f8320330ad265..f7d07af04ea98 100644 --- a/src/librustdoc/externalfiles.rs +++ b/src/librustdoc/externalfiles.rs @@ -37,7 +37,7 @@ impl ExternalHtml { ) .and_then(|(ih, bc)| load_external_files(md_before_content) - .map(|m_bc| (ih, format!("{}{}", bc, Markdown(&m_bc, render)))) + .map(|m_bc| (ih, format!("{}{}", bc, Markdown(&m_bc, &[], render)))) ) .and_then(|(ih, bc)| load_external_files(after_content) @@ -45,7 +45,7 @@ impl ExternalHtml { ) .and_then(|(ih, bc, ac)| load_external_files(md_after_content) - .map(|m_ac| (ih, bc, format!("{}{}", ac, Markdown(&m_ac, render)))) + .map(|m_ac| (ih, bc, format!("{}{}", ac, Markdown(&m_ac, &[], render)))) ) .map(|(ih, bc, ac)| ExternalHtml { diff --git a/src/librustdoc/html/item_type.rs b/src/librustdoc/html/item_type.rs index 81087cd412e2c..e9c6488c49c6c 100644 --- a/src/librustdoc/html/item_type.rs +++ b/src/librustdoc/html/item_type.rs @@ -102,6 +102,7 @@ impl From for ItemType { clean::TypeKind::Variant => ItemType::Variant, clean::TypeKind::Typedef => ItemType::Typedef, clean::TypeKind::Foreign => ItemType::ForeignType, + clean::TypeKind::Macro => ItemType::Macro, } } } diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index e66add20376fa..dce0c4b001a0d 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -56,15 +56,16 @@ pub enum RenderType { /// A unit struct which has the `fmt::Display` trait implemented. When /// formatted, this struct will emit the HTML corresponding to the rendered /// version of the contained markdown string. -// The second parameter is whether we need a shorter version or not. -pub struct Markdown<'a>(pub &'a str, pub RenderType); +/// The second parameter is a list of link replacements +// The third parameter is whether we need a shorter version or not. +pub struct Markdown<'a>(pub &'a str, pub &'a [(String, String)], pub RenderType); /// A unit struct like `Markdown`, that renders the markdown with a /// table of contents. pub struct MarkdownWithToc<'a>(pub &'a str, pub RenderType); /// A unit struct like `Markdown`, that renders the markdown escaping HTML tags. pub struct MarkdownHtml<'a>(pub &'a str, pub RenderType); /// A unit struct like `Markdown`, that renders only the first paragraph. -pub struct MarkdownSummaryLine<'a>(pub &'a str); +pub struct MarkdownSummaryLine<'a>(pub &'a str, pub &'a [(String, String)]); /// Controls whether a line will be hidden or shown in HTML output. /// @@ -247,6 +248,39 @@ impl<'a, I: Iterator>> Iterator for CodeBlocks<'a, I> { } } +/// Make headings links with anchor ids and build up TOC. +struct LinkReplacer<'a, 'b, I: Iterator>> { + inner: I, + links: &'b [(String, String)] +} + +impl<'a, 'b, I: Iterator>> LinkReplacer<'a, 'b, I> { + fn new(iter: I, links: &'b [(String, String)]) -> Self { + LinkReplacer { + inner: iter, + links + } + } +} + +impl<'a, 'b, I: Iterator>> Iterator for LinkReplacer<'a, 'b, I> { + type Item = Event<'a>; + + fn next(&mut self) -> Option { + let event = self.inner.next(); + if let Some(Event::Start(Tag::Link(dest, text))) = event { + if let Some(&(_, ref replace)) = self.links.into_iter().find(|link| &*link.0 == &*dest) + { + Some(Event::Start(Tag::Link(replace.to_owned().into(), text))) + } else { + Some(Event::Start(Tag::Link(dest, text))) + } + } else { + event + } + } +} + /// Make headings links with anchor ids and build up TOC. struct HeadingLinks<'a, 'b, I: Iterator>> { inner: I, @@ -527,6 +561,8 @@ struct MyOpaque { *const hoedown_buffer, *const hoedown_renderer_data, libc::size_t), toc_builder: Option, + links_out: Option>, + links_replace: Vec<(String, String)>, } extern { @@ -555,186 +591,293 @@ impl hoedown_buffer { } } -pub fn render(w: &mut fmt::Formatter, - s: &str, - print_toc: bool, - html_flags: libc::c_uint) -> fmt::Result { - extern fn block(ob: *mut hoedown_buffer, orig_text: *const hoedown_buffer, - lang: *const hoedown_buffer, data: *const hoedown_renderer_data, - line: libc::size_t) { - unsafe { - if orig_text.is_null() { return } - - let opaque = (*data).opaque as *mut hoedown_html_renderer_state; - let my_opaque: &MyOpaque = &*((*opaque).opaque as *const MyOpaque); - let text = (*orig_text).as_bytes(); - let origtext = str::from_utf8(text).unwrap(); - let origtext = origtext.trim_left(); - debug!("docblock: ==============\n{:?}\n=======", text); - let mut compile_fail = false; - let mut ignore = false; - - let rendered = if lang.is_null() || origtext.is_empty() { - false +extern fn hoedown_block(ob: *mut hoedown_buffer, orig_text: *const hoedown_buffer, + lang: *const hoedown_buffer, data: *const hoedown_renderer_data, + line: libc::size_t) { + unsafe { + if orig_text.is_null() { return } + + let opaque = (*data).opaque as *mut hoedown_html_renderer_state; + let my_opaque: &MyOpaque = &*((*opaque).opaque as *const MyOpaque); + let text = (*orig_text).as_bytes(); + let origtext = str::from_utf8(text).unwrap(); + let origtext = origtext.trim_left(); + debug!("docblock: ==============\n{:?}\n=======", text); + let mut compile_fail = false; + let mut ignore = false; + + let rendered = if lang.is_null() || origtext.is_empty() { + false + } else { + let rlang = (*lang).as_bytes(); + let rlang = str::from_utf8(rlang).unwrap(); + let parse_result = LangString::parse(rlang); + compile_fail = parse_result.compile_fail; + ignore = parse_result.ignore; + if !parse_result.rust { + (my_opaque.dfltblk)(ob, orig_text, lang, + opaque as *const hoedown_renderer_data, + line); + true } else { - let rlang = (*lang).as_bytes(); - let rlang = str::from_utf8(rlang).unwrap(); - let parse_result = LangString::parse(rlang); - compile_fail = parse_result.compile_fail; - ignore = parse_result.ignore; - if !parse_result.rust { - (my_opaque.dfltblk)(ob, orig_text, lang, - opaque as *const hoedown_renderer_data, - line); - true + false + } + }; + + let lines = origtext.lines().filter_map(|l| map_line(l).for_html()); + let text = lines.collect::>().join("\n"); + if rendered { return } + PLAYGROUND.with(|play| { + // insert newline to clearly separate it from the + // previous block so we can shorten the html output + let mut s = String::from("\n"); + let playground_button = play.borrow().as_ref().and_then(|&(ref krate, ref url)| { + if url.is_empty() { + return None; + } + let test = origtext.lines() + .map(|l| map_line(l).for_code()) + .collect::>().join("\n"); + let krate = krate.as_ref().map(|s| &**s); + let (test, _) = test::make_test(&test, krate, false, + &Default::default()); + let channel = if test.contains("#![feature(") { + "&version=nightly" } else { - false + "" + }; + // These characters don't need to be escaped in a URI. + // FIXME: use a library function for percent encoding. + fn dont_escape(c: u8) -> bool { + (b'a' <= c && c <= b'z') || + (b'A' <= c && c <= b'Z') || + (b'0' <= c && c <= b'9') || + c == b'-' || c == b'_' || c == b'.' || + c == b'~' || c == b'!' || c == b'\'' || + c == b'(' || c == b')' || c == b'*' } - }; - - let lines = origtext.lines().filter_map(|l| map_line(l).for_html()); - let text = lines.collect::>().join("\n"); - if rendered { return } - PLAYGROUND.with(|play| { - // insert newline to clearly separate it from the - // previous block so we can shorten the html output - let mut s = String::from("\n"); - let playground_button = play.borrow().as_ref().and_then(|&(ref krate, ref url)| { - if url.is_empty() { - return None; - } - let test = origtext.lines() - .map(|l| map_line(l).for_code()) - .collect::>().join("\n"); - let krate = krate.as_ref().map(|s| &**s); - let (test, _) = test::make_test(&test, krate, false, - &Default::default()); - let channel = if test.contains("#![feature(") { - "&version=nightly" + let mut test_escaped = String::new(); + for b in test.bytes() { + if dont_escape(b) { + test_escaped.push(char::from(b)); } else { - "" - }; - // These characters don't need to be escaped in a URI. - // FIXME: use a library function for percent encoding. - fn dont_escape(c: u8) -> bool { - (b'a' <= c && c <= b'z') || - (b'A' <= c && c <= b'Z') || - (b'0' <= c && c <= b'9') || - c == b'-' || c == b'_' || c == b'.' || - c == b'~' || c == b'!' || c == b'\'' || - c == b'(' || c == b')' || c == b'*' - } - let mut test_escaped = String::new(); - for b in test.bytes() { - if dont_escape(b) { - test_escaped.push(char::from(b)); - } else { - write!(test_escaped, "%{:02X}", b).unwrap(); - } + write!(test_escaped, "%{:02X}", b).unwrap(); } - Some(format!( - r#"Run"#, - url, test_escaped, channel - )) - }); - let tooltip = if ignore { - Some(("This example is not tested", "ignore")) - } else if compile_fail { - Some(("This example deliberately fails to compile", "compile_fail")) - } else { - None - }; - s.push_str(&highlight::render_with_highlighting( - &text, - Some(&format!("rust-example-rendered{}", - if ignore { " ignore" } - else if compile_fail { " compile_fail" } - else { "" })), - None, - playground_button.as_ref().map(String::as_str), - tooltip)); - hoedown_buffer_put(ob, s.as_ptr(), s.len()); - }) - } + } + Some(format!( + r#"Run"#, + url, test_escaped, channel + )) + }); + let tooltip = if ignore { + Some(("This example is not tested", "ignore")) + } else if compile_fail { + Some(("This example deliberately fails to compile", "compile_fail")) + } else { + None + }; + s.push_str(&highlight::render_with_highlighting( + &text, + Some(&format!("rust-example-rendered{}", + if ignore { " ignore" } + else if compile_fail { " compile_fail" } + else { "" })), + None, + playground_button.as_ref().map(String::as_str), + tooltip)); + hoedown_buffer_put(ob, s.as_ptr(), s.len()); + }) } +} - extern fn header(ob: *mut hoedown_buffer, text: *const hoedown_buffer, - level: libc::c_int, data: *const hoedown_renderer_data, - _: libc::size_t) { - // hoedown does this, we may as well too - unsafe { hoedown_buffer_put(ob, "\n".as_ptr(), 1); } +extern fn hoedown_header(ob: *mut hoedown_buffer, text: *const hoedown_buffer, + level: libc::c_int, data: *const hoedown_renderer_data, + _: libc::size_t) { + // hoedown does this, we may as well too + unsafe { hoedown_buffer_put(ob, "\n".as_ptr(), 1); } - // Extract the text provided - let s = if text.is_null() { - "".to_owned() - } else { - let s = unsafe { (*text).as_bytes() }; - str::from_utf8(&s).unwrap().to_owned() - }; + // Extract the text provided + let s = if text.is_null() { + "".to_owned() + } else { + let s = unsafe { (*text).as_bytes() }; + str::from_utf8(&s).unwrap().to_owned() + }; - // Discard '', '' tags and some escaped characters, - // transform the contents of the header into a hyphenated string - // without non-alphanumeric characters other than '-' and '_'. - // - // This is a terrible hack working around how hoedown gives us rendered - // html for text rather than the raw text. - let mut id = s.clone(); - let repl_sub = vec!["", "", "", "", - "", "", - "<", ">", "&", "'", """]; - for sub in repl_sub { - id = id.replace(sub, ""); - } - let id = id.chars().filter_map(|c| { - if c.is_alphanumeric() || c == '-' || c == '_' { - if c.is_ascii() { - Some(c.to_ascii_lowercase()) - } else { - Some(c) - } - } else if c.is_whitespace() && c.is_ascii() { - Some('-') + // Discard '', '' tags and some escaped characters, + // transform the contents of the header into a hyphenated string + // without non-alphanumeric characters other than '-' and '_'. + // + // This is a terrible hack working around how hoedown gives us rendered + // html for text rather than the raw text. + let mut id = s.clone(); + let repl_sub = vec!["", "", "", "", + "", "", + "<", ">", "&", "'", """]; + for sub in repl_sub { + id = id.replace(sub, ""); + } + let id = id.chars().filter_map(|c| { + if c.is_alphanumeric() || c == '-' || c == '_' { + if c.is_ascii() { + Some(c.to_ascii_lowercase()) } else { - None + Some(c) } - }).collect::(); + } else if c.is_whitespace() && c.is_ascii() { + Some('-') + } else { + None + } + }).collect::(); - let opaque = unsafe { (*data).opaque as *mut hoedown_html_renderer_state }; - let opaque = unsafe { &mut *((*opaque).opaque as *mut MyOpaque) }; + let opaque = unsafe { (*data).opaque as *mut hoedown_html_renderer_state }; + let opaque = unsafe { &mut *((*opaque).opaque as *mut MyOpaque) }; - let id = derive_id(id); + let id = derive_id(id); - let sec = opaque.toc_builder.as_mut().map_or("".to_owned(), |builder| { - format!("{} ", builder.push(level as u32, s.clone(), id.clone())) - }); + let sec = opaque.toc_builder.as_mut().map_or("".to_owned(), |builder| { + format!("{} ", builder.push(level as u32, s.clone(), id.clone())) + }); + + // Render the HTML + let text = format!("\ + {sec}{}", + s, lvl = level, id = id, sec = sec); - // Render the HTML - let text = format!("\ - {sec}{}", - s, lvl = level, id = id, sec = sec); + unsafe { hoedown_buffer_put(ob, text.as_ptr(), text.len()); } +} + +extern fn hoedown_codespan( + ob: *mut hoedown_buffer, + text: *const hoedown_buffer, + _: *const hoedown_renderer_data, + _: libc::size_t +) -> libc::c_int { + let content = if text.is_null() { + "".to_owned() + } else { + let bytes = unsafe { (*text).as_bytes() }; + let s = str::from_utf8(bytes).unwrap(); + collapse_whitespace(s) + }; - unsafe { hoedown_buffer_put(ob, text.as_ptr(), text.len()); } + let content = format!("{}", Escape(&content)); + unsafe { + hoedown_buffer_put(ob, content.as_ptr(), content.len()); } + // Return anything except 0, which would mean "also print the code span verbatim". + 1 +} - extern fn codespan( +pub fn render(w: &mut fmt::Formatter, + s: &str, + links: &[(String, String)], + print_toc: bool, + html_flags: libc::c_uint) -> fmt::Result { + // copied from pulldown-cmark (MIT license, Google) + // https://github.com/google/pulldown-cmark + // this is temporary till we remove the hoedown renderer + static HREF_SAFE: [u8; 128] = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, + ]; + + static HEX_CHARS: &'static [u8] = b"0123456789ABCDEF"; + + fn escape_href(ob: &mut String, s: &str) { + let mut mark = 0; + for i in 0..s.len() { + let c = s.as_bytes()[i]; + if c >= 0x80 || HREF_SAFE[c as usize] == 0 { + // character needing escape + + // write partial substring up to mark + if mark < i { + ob.push_str(&s[mark..i]); + } + match c { + b'&' => { + ob.push_str("&"); + }, + b'\'' => { + ob.push_str("'"); + }, + _ => { + let mut buf = [0u8; 3]; + buf[0] = b'%'; + buf[1] = HEX_CHARS[((c as usize) >> 4) & 0xF]; + buf[2] = HEX_CHARS[(c as usize) & 0xF]; + ob.push_str(str::from_utf8(&buf).unwrap()); + } + } + mark = i + 1; // all escaped characters are ASCII + } + } + ob.push_str(&s[mark..]); + } + // end code copied from pulldown-cmark + + extern fn hoedown_link( ob: *mut hoedown_buffer, - text: *const hoedown_buffer, - _: *const hoedown_renderer_data, - _: libc::size_t + content: *const hoedown_buffer, + link: *const hoedown_buffer, + title: *const hoedown_buffer, + data: *const hoedown_renderer_data, + _line: libc::size_t ) -> libc::c_int { - let content = if text.is_null() { - "".to_owned() + if link.is_null() { + return 0; + } + + let opaque = unsafe { (*data).opaque as *mut hoedown_html_renderer_state }; + let opaque = unsafe { &mut *((*opaque).opaque as *mut MyOpaque) }; + + let link = { + let s = unsafe { (*link).as_bytes() }; + str::from_utf8(s).unwrap().to_owned() + }; + + let link = if let Some(&(_, ref new_target)) = opaque.links_replace + .iter() + .find(|t| &*t.0 == &*link) { + new_target.to_owned() } else { - let bytes = unsafe { (*text).as_bytes() }; - let s = str::from_utf8(bytes).unwrap(); - collapse_whitespace(s) + link }; - let content = format!("{}", Escape(&content)); - unsafe { - hoedown_buffer_put(ob, content.as_ptr(), content.len()); - } - // Return anything except 0, which would mean "also print the code span verbatim". + let content = unsafe { + content.as_ref().map(|c| { + let s = c.as_bytes(); + str::from_utf8(s).unwrap().to_owned() + }) + }; + + let mut link_buf = String::new(); + escape_href(&mut link_buf, &link); + + let title = unsafe { + title.as_ref().map(|t| { + let s = t.as_bytes(); + str::from_utf8(s).unwrap().to_owned() + }) + }; + + let link_out = format!("{content}", + link = link_buf, + title = title.map_or(String::new(), + |t| format!(" title=\"{}\"", t)), + content = content.unwrap_or(String::new())); + + unsafe { hoedown_buffer_put(ob, link_out.as_ptr(), link_out.len()); } + + //return "anything but 0" to show we've written the link in 1 } @@ -743,13 +886,16 @@ pub fn render(w: &mut fmt::Formatter, let renderer = hoedown_html_renderer_new(html_flags, 0); let mut opaque = MyOpaque { dfltblk: (*renderer).blockcode.unwrap(), - toc_builder: if print_toc {Some(TocBuilder::new())} else {None} + toc_builder: if print_toc {Some(TocBuilder::new())} else {None}, + links_out: None, + links_replace: links.to_vec(), }; (*((*renderer).opaque as *mut hoedown_html_renderer_state)).opaque = &mut opaque as *mut _ as *mut libc::c_void; - (*renderer).blockcode = Some(block); - (*renderer).header = Some(header); - (*renderer).codespan = Some(codespan); + (*renderer).blockcode = Some(hoedown_block); + (*renderer).header = Some(hoedown_header); + (*renderer).codespan = Some(hoedown_codespan); + (*renderer).link = Some(hoedown_link); let document = hoedown_document_new(renderer, HOEDOWN_EXTENSIONS, 16); hoedown_document_render(document, ob, s.as_ptr(), @@ -993,12 +1139,12 @@ impl LangString { impl<'a> fmt::Display for Markdown<'a> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - let Markdown(md, render_type) = *self; + let Markdown(md, links, render_type) = *self; // This is actually common enough to special-case if md.is_empty() { return Ok(()) } if render_type == RenderType::Hoedown { - render(fmt, md, false, 0) + render(fmt, md, links, false, 0) } else { let mut opts = Options::empty(); opts.insert(OPTION_ENABLE_TABLES); @@ -1009,7 +1155,11 @@ impl<'a> fmt::Display for Markdown<'a> { let mut s = String::with_capacity(md.len() * 3 / 2); html::push_html(&mut s, - Footnotes::new(CodeBlocks::new(HeadingLinks::new(p, None)))); + Footnotes::new( + CodeBlocks::new( + LinkReplacer::new( + HeadingLinks::new(p, None), + links)))); fmt.write_str(&s) } @@ -1021,7 +1171,7 @@ impl<'a> fmt::Display for MarkdownWithToc<'a> { let MarkdownWithToc(md, render_type) = *self; if render_type == RenderType::Hoedown { - render(fmt, md, true, 0) + render(fmt, md, &[], true, 0) } else { let mut opts = Options::empty(); opts.insert(OPTION_ENABLE_TABLES); @@ -1050,7 +1200,7 @@ impl<'a> fmt::Display for MarkdownHtml<'a> { // This is actually common enough to special-case if md.is_empty() { return Ok(()) } if render_type == RenderType::Hoedown { - render(fmt, md, false, HOEDOWN_HTML_ESCAPE) + render(fmt, md, &[], false, HOEDOWN_HTML_ESCAPE) } else { let mut opts = Options::empty(); opts.insert(OPTION_ENABLE_TABLES); @@ -1076,7 +1226,7 @@ impl<'a> fmt::Display for MarkdownHtml<'a> { impl<'a> fmt::Display for MarkdownSummaryLine<'a> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - let MarkdownSummaryLine(md) = *self; + let MarkdownSummaryLine(md, links) = *self; // This is actually common enough to special-case if md.is_empty() { return Ok(()) } @@ -1084,7 +1234,7 @@ impl<'a> fmt::Display for MarkdownSummaryLine<'a> { let mut s = String::new(); - html::push_html(&mut s, SummaryLine::new(p)); + html::push_html(&mut s, LinkReplacer::new(SummaryLine::new(p), links)); fmt.write_str(&s) } @@ -1140,6 +1290,90 @@ pub fn plain_summary_line(md: &str) -> String { s } +pub fn markdown_links(md: &str, render_type: RenderType) -> Vec { + if md.is_empty() { + return vec![]; + } + + match render_type { + RenderType::Hoedown => { + extern fn hoedown_link( + _ob: *mut hoedown_buffer, + _content: *const hoedown_buffer, + link: *const hoedown_buffer, + _title: *const hoedown_buffer, + data: *const hoedown_renderer_data, + _line: libc::size_t + ) -> libc::c_int { + if link.is_null() { + return 0; + } + + let opaque = unsafe { (*data).opaque as *mut hoedown_html_renderer_state }; + let opaque = unsafe { &mut *((*opaque).opaque as *mut MyOpaque) }; + + if let Some(ref mut links) = opaque.links_out { + let s = unsafe { (*link).as_bytes() }; + let s = str::from_utf8(&s).unwrap().to_owned(); + + debug!("found link: {}", s); + + links.push(s); + } + + //returning 0 here means "emit the span verbatim", but we're not using the output + //anyway so we don't really care + 0 + } + + unsafe { + let ob = hoedown_buffer_new(DEF_OUNIT); + let renderer = hoedown_html_renderer_new(0, 0); + let mut opaque = MyOpaque { + dfltblk: (*renderer).blockcode.unwrap(), + toc_builder: None, + links_out: Some(vec![]), + links_replace: vec![], + }; + (*((*renderer).opaque as *mut hoedown_html_renderer_state)).opaque + = &mut opaque as *mut _ as *mut libc::c_void; + (*renderer).header = Some(hoedown_header); + (*renderer).codespan = Some(hoedown_codespan); + (*renderer).link = Some(hoedown_link); + + let document = hoedown_document_new(renderer, HOEDOWN_EXTENSIONS, 16); + hoedown_document_render(document, ob, md.as_ptr(), + md.len() as libc::size_t); + hoedown_document_free(document); + + hoedown_html_renderer_free(renderer); + hoedown_buffer_free(ob); + + opaque.links_out.unwrap() + } + } + RenderType::Pulldown => { + let mut opts = Options::empty(); + opts.insert(OPTION_ENABLE_TABLES); + opts.insert(OPTION_ENABLE_FOOTNOTES); + + let p = Parser::new_ext(md, opts); + + let iter = Footnotes::new(HeadingLinks::new(p, None)); + let mut links = vec![]; + + for ev in iter { + if let Event::Start(Tag::Link(dest, _)) = ev { + debug!("found link: {}", dest); + links.push(dest.into_owned()); + } + } + + links + } + } +} + #[cfg(test)] mod tests { use super::{LangString, Markdown, MarkdownHtml}; @@ -1191,14 +1425,14 @@ mod tests { #[test] fn issue_17736() { let markdown = "# title"; - format!("{}", Markdown(markdown, RenderType::Pulldown)); + format!("{}", Markdown(markdown, &[], RenderType::Pulldown)); reset_ids(true); } #[test] fn test_header() { fn t(input: &str, expect: &str) { - let output = format!("{}", Markdown(input, RenderType::Pulldown)); + let output = format!("{}", Markdown(input, &[], RenderType::Pulldown)); assert_eq!(output, expect, "original: {}", input); reset_ids(true); } @@ -1220,7 +1454,7 @@ mod tests { #[test] fn test_header_ids_multiple_blocks() { fn t(input: &str, expect: &str) { - let output = format!("{}", Markdown(input, RenderType::Pulldown)); + let output = format!("{}", Markdown(input, &[], RenderType::Pulldown)); assert_eq!(output, expect, "original: {}", input); } diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs index cfa09ea30a8be..b58a59f12173c 100644 --- a/src/librustdoc/html/render.rs +++ b/src/librustdoc/html/render.rs @@ -421,7 +421,7 @@ thread_local!(pub static CURRENT_LOCATION_KEY: RefCell> = thread_local!(pub static USED_ID_MAP: RefCell> = RefCell::new(init_ids())); -pub fn render_text String>(mut render: F) -> (String, String) { +pub fn render_text T>(mut render: F) -> (T, T) { // Save the state of USED_ID_MAP so it only gets updated once even // though we're rendering twice. let orig_used_id_map = USED_ID_MAP.with(|map| map.borrow().clone()); @@ -1284,7 +1284,7 @@ impl DocFolder for Cache { clean::FunctionItem(..) | clean::ModuleItem(..) | clean::ForeignFunctionItem(..) | clean::ForeignStaticItem(..) | clean::ConstantItem(..) | clean::StaticItem(..) | - clean::UnionItem(..) | clean::ForeignTypeItem + clean::UnionItem(..) | clean::ForeignTypeItem | clean::MacroItem(..) if !self.stripped_mod => { // Re-exported items mean that the same id can show up twice // in the rustdoc ast that we're looking at. We know, @@ -1861,12 +1861,14 @@ fn document(w: &mut fmt::Formatter, cx: &Context, item: &clean::Item) -> fmt::Re /// rendering between Pulldown and Hoedown. fn render_markdown(w: &mut fmt::Formatter, md_text: &str, + links: Vec<(String, String)>, span: Span, render_type: RenderType, prefix: &str, scx: &SharedContext) -> fmt::Result { - let (hoedown_output, pulldown_output) = render_text(|ty| format!("{}", Markdown(md_text, ty))); + let (hoedown_output, pulldown_output) = + render_text(|ty| format!("{}", Markdown(md_text, &links, ty))); let mut differences = html_diff::get_differences(&pulldown_output, &hoedown_output); differences.retain(|s| { match *s { @@ -1898,7 +1900,13 @@ fn document_short(w: &mut fmt::Formatter, item: &clean::Item, link: AssocItemLin } else { format!("{}", &plain_summary_line(Some(s))) }; - render_markdown(w, &markdown, item.source.clone(), cx.render_type, prefix, &cx.shared)?; + render_markdown(w, + &markdown, + item.links(), + item.source.clone(), + cx.render_type, + prefix, + &cx.shared)?; } else if !prefix.is_empty() { write!(w, "
{}
", prefix)?; } @@ -1924,7 +1932,13 @@ fn document_full(w: &mut fmt::Formatter, item: &clean::Item, cx: &Context, prefix: &str) -> fmt::Result { if let Some(s) = cx.shared.maybe_collapsed_doc_value(item) { debug!("Doc block: =====\n{}\n=====", s); - render_markdown(w, &*s, item.source.clone(), cx.render_type, prefix, &cx.shared)?; + render_markdown(w, + &*s, + item.links(), + item.source.clone(), + cx.render_type, + prefix, + &cx.shared)?; } else if !prefix.is_empty() { write!(w, "
{}
", prefix)?; } @@ -2146,10 +2160,10 @@ fn item_module(w: &mut fmt::Formatter, cx: &Context, stab_docs = stab_docs, docs = if cx.render_type == RenderType::Hoedown { format!("{}", - shorter(Some(&Markdown(doc_value, + shorter(Some(&Markdown(doc_value, &myitem.links(), RenderType::Hoedown).to_string()))) } else { - format!("{}", MarkdownSummaryLine(doc_value)) + format!("{}", MarkdownSummaryLine(doc_value, &myitem.links())) }, class = myitem.type_(), stab = myitem.stability_class().unwrap_or("".to_string()), @@ -3338,7 +3352,8 @@ fn render_impl(w: &mut fmt::Formatter, cx: &Context, i: &Impl, link: AssocItemLi write!(w, "")?; write!(w, "\n")?; if let Some(ref dox) = cx.shared.maybe_collapsed_doc_value(&i.impl_item) { - write!(w, "
{}
", Markdown(&*dox, cx.render_type))?; + write!(w, "
{}
", + Markdown(&*dox, &i.impl_item.links(), cx.render_type))?; } } diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 2e2dba7681cc3..6347c4a58dddd 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -503,6 +503,11 @@ where R: 'static + Send, F: 'static + Send + FnOnce(Output) -> R { let crate_name = matches.opt_str("crate-name"); let crate_version = matches.opt_str("crate-version"); let plugin_path = matches.opt_str("plugin-path"); + let render_type = if matches.opt_present("disable-commonmark") { + RenderType::Hoedown + } else { + RenderType::Pulldown + }; info!("starting to run rustc"); let display_warnings = matches.opt_present("display-warnings"); @@ -517,7 +522,7 @@ where R: 'static + Send, F: 'static + Send + FnOnce(Output) -> R { let (mut krate, renderinfo) = core::run_core(paths, cfgs, externs, Input::File(cratefile), triple, maybe_sysroot, - display_warnings, force_unstable_if_unmarked); + display_warnings, force_unstable_if_unmarked, render_type); info!("finished with rustc"); diff --git a/src/librustdoc/markdown.rs b/src/librustdoc/markdown.rs index af93505293cef..9af2ebf0661da 100644 --- a/src/librustdoc/markdown.rs +++ b/src/librustdoc/markdown.rs @@ -104,7 +104,7 @@ pub fn render(input: &Path, mut output: PathBuf, matches: &getopts::Matches, } else { // Save the state of USED_ID_MAP so it only gets updated once even // though we're rendering twice. - render_text(|ty| format!("{}", Markdown(text, ty))) + render_text(|ty| format!("{}", Markdown(text, &[], ty))) }; let mut differences = html_diff::get_differences(&pulldown_output, &hoedown_output); diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index 1cb52d735bb18..7b208465369fc 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -40,11 +40,11 @@ use doctree::*; // also, is there some reason that this doesn't use the 'visit' // framework from syntax? -pub struct RustdocVisitor<'a, 'tcx: 'a> { - cstore: &'tcx CrateStore, +pub struct RustdocVisitor<'a, 'tcx: 'a, 'rcx: 'a> { + cstore: &'a CrateStore, pub module: Module, pub attrs: hir::HirVec, - pub cx: &'a core::DocContext<'a, 'tcx>, + pub cx: &'a core::DocContext<'a, 'tcx, 'rcx>, view_item_stack: FxHashSet, inlining: bool, /// Is the current module and all of its parents public? @@ -52,9 +52,9 @@ pub struct RustdocVisitor<'a, 'tcx: 'a> { reexported_macros: FxHashSet, } -impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { - pub fn new(cstore: &'tcx CrateStore, - cx: &'a core::DocContext<'a, 'tcx>) -> RustdocVisitor<'a, 'tcx> { +impl<'a, 'tcx, 'rcx> RustdocVisitor<'a, 'tcx, 'rcx> { + pub fn new(cstore: &'a CrateStore, + cx: &'a core::DocContext<'a, 'tcx, 'rcx>) -> RustdocVisitor<'a, 'tcx, 'rcx> { // If the root is re-exported, terminate all recursion. let mut stack = FxHashSet(); stack.insert(ast::CRATE_NODE_ID); diff --git a/src/librustdoc/visit_lib.rs b/src/librustdoc/visit_lib.rs index 2fd47fa0a6d0a..15a8b58d0f6b9 100644 --- a/src/librustdoc/visit_lib.rs +++ b/src/librustdoc/visit_lib.rs @@ -22,8 +22,8 @@ use clean::{AttributesExt, NestedAttributesExt}; /// Similar to `librustc_privacy::EmbargoVisitor`, but also takes /// specific rustdoc annotations into account (i.e. `doc(hidden)`) -pub struct LibEmbargoVisitor<'a, 'b: 'a, 'tcx: 'b> { - cx: &'a ::core::DocContext<'b, 'tcx>, +pub struct LibEmbargoVisitor<'a, 'tcx: 'a, 'rcx: 'a> { + cx: &'a ::core::DocContext<'a, 'tcx, 'rcx>, // Accessibility levels for reachable nodes access_levels: RefMut<'a, AccessLevels>, // Previous accessibility level, None means unreachable @@ -32,8 +32,8 @@ pub struct LibEmbargoVisitor<'a, 'b: 'a, 'tcx: 'b> { visited_mods: FxHashSet, } -impl<'a, 'b, 'tcx> LibEmbargoVisitor<'a, 'b, 'tcx> { - pub fn new(cx: &'a ::core::DocContext<'b, 'tcx>) -> LibEmbargoVisitor<'a, 'b, 'tcx> { +impl<'a, 'tcx, 'rcx> LibEmbargoVisitor<'a, 'tcx, 'rcx> { + pub fn new(cx: &'a ::core::DocContext<'a, 'tcx, 'rcx>) -> LibEmbargoVisitor<'a, 'tcx, 'rcx> { LibEmbargoVisitor { cx, access_levels: cx.access_levels.borrow_mut(), diff --git a/src/test/rustdoc/intra-links.rs b/src/test/rustdoc/intra-links.rs new file mode 100644 index 0000000000000..aa6f553875441 --- /dev/null +++ b/src/test/rustdoc/intra-links.rs @@ -0,0 +1,60 @@ +// Copyright 2017 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. + +// @has intra_links/index.html +// @has - '//a/@href' '../intra_links/struct.ThisType.html' +// @has - '//a/@href' '../intra_links/enum.ThisEnum.html' +// @has - '//a/@href' '../intra_links/trait.ThisTrait.html' +// @has - '//a/@href' '../intra_links/type.ThisAlias.html' +// @has - '//a/@href' '../intra_links/union.ThisUnion.html' +// @has - '//a/@href' '../intra_links/fn.this_function.html' +// @has - '//a/@href' '../intra_links/constant.THIS_CONST.html' +// @has - '//a/@href' '../intra_links/static.THIS_STATIC.html' +// @has - '//a/@href' '../intra_links/macro.this_macro.html' +// @has - '//a/@href' '../intra_links/trait.SoAmbiguous.html' +// @has - '//a/@href' '../intra_links/fn.SoAmbiguous.html' +//! In this crate we would like to link to: +//! +//! * [`ThisType`](ThisType) +//! * [`ThisEnum`](ThisEnum) +//! * [`ThisTrait`](ThisTrait) +//! * [`ThisAlias`](ThisAlias) +//! * [`ThisUnion`](ThisUnion) +//! * [`this_function`](this_function()) +//! * [`THIS_CONST`](const@THIS_CONST) +//! * [`THIS_STATIC`](static@THIS_STATIC) +//! * [`this_macro`](this_macro!) +//! +//! In addition, there's some specifics we want to look at. There's [a trait called +//! SoAmbiguous][ambig-trait], but there's also [a function called SoAmbiguous][ambig-fn] too! +//! Whatever shall we do? +//! +//! [ambig-trait]: trait@SoAmbiguous +//! [ambig-fn]: SoAmbiguous() + +#[macro_export] +macro_rules! this_macro { + () => {}; +} + +pub struct ThisType; +pub enum ThisEnum { ThisVariant, } +pub trait ThisTrait {} +pub type ThisAlias = Result<(), ()>; +pub union ThisUnion { this_field: usize, } + +pub fn this_function() {} +pub const THIS_CONST: usize = 5usize; +pub static THIS_STATIC: usize = 5usize; + +pub trait SoAmbiguous {} + +#[allow(bad_style)] +pub fn SoAmbiguous() {} diff --git a/src/tools/error_index_generator/main.rs b/src/tools/error_index_generator/main.rs index aedae366c4119..8454e71fa3f9b 100644 --- a/src/tools/error_index_generator/main.rs +++ b/src/tools/error_index_generator/main.rs @@ -100,7 +100,7 @@ impl Formatter for HTMLFormatter { // Description rendered as markdown. match info.description { - Some(ref desc) => write!(output, "{}", Markdown(desc, RenderType::Hoedown))?, + Some(ref desc) => write!(output, "{}", Markdown(desc, &[], RenderType::Hoedown))?, None => write!(output, "

No description.

\n")?, }