diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index f2682e00430d0..a74e5e378177b 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -198,6 +198,9 @@ pub struct RenderOptions { pub generate_search_filter: bool, /// Option (disabled by default) to generate files used by RLS and some other tools. pub generate_redirect_pages: bool, + /// Option (disabled by default) to generate file with names that won't crate conflicts + /// on case insensitive file systems. + pub case_insensitive: bool, } impl Options { @@ -431,6 +434,7 @@ impl Options { let show_coverage = matches.opt_present("show-coverage"); let document_private = matches.opt_present("document-private-items"); + let case_insensitive = matches.opt_present("case-insensitive"); let default_passes = if matches.opt_present("no-defaults") { passes::DefaultPassOption::None @@ -508,6 +512,7 @@ impl Options { markdown_playground_url, generate_search_filter, generate_redirect_pages, + case_insensitive, } }) } diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs index 866d8fe682a7b..399ece8dd8838 100644 --- a/src/librustdoc/html/render.rs +++ b/src/librustdoc/html/render.rs @@ -150,6 +150,10 @@ struct SharedContext { pub generate_search_filter: bool, /// Option disabled by default to generate files used by RLS and some other tools. pub generate_redirect_pages: bool, + /// If true, it'll lower all type names and handle conflicts. + pub case_insensitive: bool, + /// The type name and its url. + pub types_urls: RefCell>, } impl SharedContext { @@ -162,9 +166,7 @@ impl SharedContext { Ok(()) } -} -impl SharedContext { /// Returns `true` if the `collapse-docs` pass was run on this crate. pub fn was_collapsed(&self) -> bool { self.passes.contains("collapse-docs") @@ -172,7 +174,7 @@ impl SharedContext { /// Based on whether the `collapse-docs` pass was run, return either the `doc_value` or the /// `collapsed_doc_value` of the given item. - pub fn maybe_collapsed_doc_value<'a>(&self, item: &'a clean::Item) -> Option> { + pub fn maybe_collapsed_doc_value<'b>(&self, item: &'b clean::Item) -> Option> { if self.was_collapsed() { item.collapsed_doc_value().map(|s| s.into()) } else { @@ -410,19 +412,23 @@ struct IndexItem { parent: Option, parent_idx: Option, search_type: Option, + alternative_path: Option, } impl ToJson for IndexItem { fn to_json(&self) -> Json { assert_eq!(self.parent.is_some(), self.parent_idx.is_some()); - let mut data = Vec::with_capacity(6); + let mut data = Vec::with_capacity(7); data.push((self.ty as usize).to_json()); data.push(self.name.to_json()); data.push(self.path.to_json()); data.push(self.desc.to_json()); data.push(self.parent_idx.to_json()); data.push(self.search_type.to_json()); + if let Some(ref alternative_path) = self.alternative_path { + data.push(alternative_path.to_json()); + } Json::Array(data) } @@ -527,6 +533,7 @@ pub fn run(mut krate: clean::Crate, static_root_path, generate_search_filter, generate_redirect_pages, + case_insensitive, .. } = options; @@ -557,6 +564,8 @@ pub fn run(mut krate: clean::Crate, static_root_path, generate_search_filter, generate_redirect_pages, + types_urls: RefCell::new(Default::default()), + case_insensitive, }; // If user passed in `--playground-url` arg, we fill in crate name here @@ -593,12 +602,13 @@ pub fn run(mut krate: clean::Crate, } } } - let dst = output; - try_err!(fs::create_dir_all(&dst), &dst); - krate = render_sources(&dst, &mut scx, krate)?; - let cx = Context { + try_err!(fs::create_dir_all(&output), &output); + krate = { + render_sources(&output, &mut scx, krate)? + }; + let mut cx = Context { current: Vec::new(), - dst, + dst: output, render_redirect_pages: false, codes: ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build()), id_map: Rc::new(RefCell::new(id_map)), @@ -688,8 +698,26 @@ pub fn run(mut krate: clean::Crate, } } + // Generate alternative URLs if the `case-insensitive` option is enabled. + cx.generate_urls(&krate)?; + if !cx.shared.types_urls.borrow().is_empty() { + let types_urls = cx.shared.types_urls.borrow(); + let s_path = cx.dst.display().to_string(); + + for item in cache.search_index.iter_mut() { + let path = format!("{}/{}.{}.html", + cx.dst.join(item.path.split("::").collect::>().join("/")) + .display(), + item.ty, + item.name); + item.alternative_path = match types_urls.get(&path).map(|(x, _)| x) { + Some(x) => url_to_path(x, &s_path), + None => None, + }; + } + } // Build our search index - let index = build_index(&krate, &mut cache); + let index = build_index(&krate, &mut cache, &*cx.shared.types_urls.borrow(), &cx.dst); // Freeze the cache now that the index has been built. Put an Arc into TLS // for future parallelization opportunities @@ -703,8 +731,34 @@ pub fn run(mut krate: clean::Crate, cx.krate(krate) } +fn url_to_path(url: &str, start_path: &str) -> Option { + if let Some(s) = url.splitn(2, start_path).skip(1).next() { + let mut s = s.to_owned(); + if s.starts_with(::std::path::MAIN_SEPARATOR) { + s.remove(0); + } + let mut s = s.split(std::path::MAIN_SEPARATOR).collect::>(); + if s.is_empty() { + None + } else { + let pos = s.len() - 1; + s[pos] = s[pos].split('.').skip(1).next().unwrap(); + Some(s.join("::")) + } + } else { + None + } +} + /// Builds the search index from the collected metadata -fn build_index(krate: &clean::Crate, cache: &mut Cache) -> String { +fn build_index( + krate: &clean::Crate, + cache: &mut Cache, + types_urls: &FxHashMap, + output_path: &Path, +) -> String { + let s_path = output_path.display().to_string(); + let mut nodeid_to_pathid = FxHashMap::default(); let mut crate_items = Vec::with_capacity(cache.search_index.len()); let mut crate_paths = Vec::::new(); @@ -716,7 +770,11 @@ fn build_index(krate: &clean::Crate, cache: &mut Cache) -> String { // Attach all orphan items to the type's definition if the type // has since been learned. for &(did, ref item) in orphan_impl_items { - if let Some(&(ref fqp, _)) = paths.get(&did) { + if let Some(&(ref fqp, typ)) = paths.get(&did) { + let path = format!("{}/{}.{}.html", + output_path.join(fqp[..fqp.len() - 1].join("/")).display(), + typ, + fqp[fqp.len() - 1]); search_index.push(IndexItem { ty: item.type_(), name: item.name.clone().unwrap(), @@ -725,6 +783,10 @@ fn build_index(krate: &clean::Crate, cache: &mut Cache) -> String { parent: Some(did), parent_idx: None, search_type: get_index_search_type(&item), + alternative_path: match types_urls.get(&path).map(|(x, _)| x) { + Some(x) => url_to_path(x, &s_path), + None => None, + }, }); } } @@ -1254,8 +1316,11 @@ themePicker.onblur = handleThemeButtonsBlur; Ok(()) } -fn render_sources(dst: &Path, scx: &mut SharedContext, - krate: clean::Crate) -> Result { +fn render_sources<'a>( + dst: &Path, + scx: &'a mut SharedContext, + krate: clean::Crate, +) -> Result { info!("emitting source files"); let dst = dst.join("src").join(&krate.name); try_err!(fs::create_dir_all(&dst), &dst); @@ -1596,6 +1661,7 @@ impl DocFolder for Cache { parent, parent_idx: None, search_type: get_index_search_type(&item), + alternative_path: None, }); } } @@ -1790,6 +1856,7 @@ impl<'a> Cache { parent: None, parent_idx: None, search_type: get_index_search_type(&item), + alternative_path: None, }); } } @@ -1873,27 +1940,25 @@ impl AllTypes { } } - fn append(&mut self, item_name: String, item_type: &ItemType) { - let mut url: Vec<_> = item_name.split("::").skip(1).collect(); - if let Some(name) = url.pop() { - let new_url = format!("{}/{}.{}.html", url.join("/"), item_type, name); - url.push(name); - let name = url.join("::"); - match *item_type { - ItemType::Struct => self.structs.insert(ItemEntry::new(new_url, name)), - ItemType::Enum => self.enums.insert(ItemEntry::new(new_url, name)), - ItemType::Union => self.unions.insert(ItemEntry::new(new_url, name)), - ItemType::Primitive => self.primitives.insert(ItemEntry::new(new_url, name)), - ItemType::Trait => self.traits.insert(ItemEntry::new(new_url, name)), - ItemType::Macro => self.macros.insert(ItemEntry::new(new_url, name)), - ItemType::Function => self.functions.insert(ItemEntry::new(new_url, name)), - ItemType::Typedef => self.typedefs.insert(ItemEntry::new(new_url, name)), - ItemType::Existential => self.existentials.insert(ItemEntry::new(new_url, name)), - ItemType::Static => self.statics.insert(ItemEntry::new(new_url, name)), - ItemType::Constant => self.constants.insert(ItemEntry::new(new_url, name)), - ItemType::ProcAttribute => self.attributes.insert(ItemEntry::new(new_url, name)), - ItemType::ProcDerive => self.derives.insert(ItemEntry::new(new_url, name)), - ItemType::TraitAlias => self.trait_aliases.insert(ItemEntry::new(new_url, name)), + fn append(&mut self, item_path: String, url: String, item_type: ItemType) { + let full_path: Vec<_> = item_path.split("::").skip(1).collect(); + if !full_path.is_empty() { + let name = full_path.join("::"); + match item_type { + ItemType::Struct => self.structs.insert(ItemEntry::new(url, name)), + ItemType::Enum => self.enums.insert(ItemEntry::new(url, name)), + ItemType::Union => self.unions.insert(ItemEntry::new(url, name)), + ItemType::Primitive => self.primitives.insert(ItemEntry::new(url, name)), + ItemType::Trait => self.traits.insert(ItemEntry::new(url, name)), + ItemType::Macro => self.macros.insert(ItemEntry::new(url, name)), + ItemType::Function => self.functions.insert(ItemEntry::new(url, name)), + ItemType::Typedef => self.typedefs.insert(ItemEntry::new(url, name)), + ItemType::Existential => self.existentials.insert(ItemEntry::new(url, name)), + ItemType::Static => self.statics.insert(ItemEntry::new(url, name)), + ItemType::Constant => self.constants.insert(ItemEntry::new(url, name)), + ItemType::ProcAttribute => self.attributes.insert(ItemEntry::new(url, name)), + ItemType::ProcDerive => self.derives.insert(ItemEntry::new(url, name)), + ItemType::TraitAlias => self.trait_aliases.insert(ItemEntry::new(url, name)), _ => true, }; } @@ -1995,6 +2060,27 @@ impl<'a> fmt::Display for Settings<'a> { } } +fn get_real_path( + types_urls: &mut FxHashMap, + full_name: String, + is_mod: bool, +) -> String { + let mut real_path = full_name.to_lowercase(); + if is_mod && real_path.ends_with('/') { + real_path.pop(); + } + let mut inc = 1; + while match types_urls.get(&real_path) { + Some((n, _)) => n != &full_name, + None => false, + } { + real_path = format!("{}{}", real_path, inc); + inc += 1; + } + types_urls.insert(real_path.clone(), (full_name, is_mod)); + real_path +} + impl Context { fn derive_id(&self, id: String) -> String { let mut map = self.id_map.borrow_mut(); @@ -2007,21 +2093,84 @@ impl Context { "../".repeat(self.current.len()) } + fn generate_urls( + &mut self, + krate: &clean::Crate, + ) -> Result<(), Error> { + /*if !self.shared.case_insensitive { + return Ok(()); + }*/ + let mut item = match krate.module { + Some(ref i) => i.clone(), + None => return Ok(()), + }; + item.name = Some(krate.name.clone()); + + { + let mut all = AllTypes::new(); + let mut work = vec![(self.clone(), item)]; + + while let Some((mut cx, item)) = work.pop() { + cx.item(item, &mut all, |icx, item, check_path| { + if item.name.is_some() && check_path { + let mut types_urls = icx.shared.types_urls.borrow_mut(); + let name = icx.dst.join(item_path_without_extension(&item)); + get_real_path(&mut *types_urls, name.display().to_string(), + item.is_mod()); + } + work.push((icx.clone(), item)); + }, false)? + } + } + let mut types_urls = self.shared.types_urls.borrow_mut(); + *types_urls = types_urls.iter() + .map(|(k, (v, is_mod))| { + let add = if *is_mod { + "/index.html" + } else { + ".html" + }; + (format!("{}{}", v, add), + (format!("{}{}", k, add), *is_mod)) + }) + .filter(|(k, (v, _))| k != v) + .collect::>(); + + Ok(()) + } + /// Recurse in the directory structure and change the "root path" to make /// sure it always points to the top (relatively). - fn recurse(&mut self, s: String, f: F) -> T where - F: FnOnce(&mut Context) -> T, + fn recurse(&mut self, item: clean::Item, render: bool, f: F) -> T where + F: FnOnce(&mut Context, clean::Item) -> T, { - if s.is_empty() { - panic!("Unexpected empty destination: {:?}", self.current); - } let prev = self.dst.clone(); + let name = item.name.as_ref().expect("no name for module").to_owned(); + let s = if !render { + let name = self.dst.join(&name).display().to_string(); + let mut types_urls = self.shared.types_urls.borrow_mut(); + Path::new(&get_real_path(&mut *types_urls, name, true)) + .file_name() + .expect("no file name found") + .to_str() + .expect("conversion to str failed") + .to_owned() + } else { + Path::new(&item_path(&self.shared, &self.dst, item.type_(), &name)) + .parent() + .expect("no parent found") + .file_name() + .expect("no file name found") + .to_str() + .expect("conversion to str failed") + .to_owned() + }; self.dst.push(&s); self.current.push(s); info!("Recursing into {}", self.dst.display()); - let ret = f(self); + let ret = f(self, item); info!("Recursed; leaving {}", self.dst.display()); @@ -2041,8 +2190,7 @@ impl Context { Some(i) => i, None => return Ok(()), }; - let final_file = self.dst.join(&krate.name) - .join("all.html"); + let final_file = self.dst.join(&krate.name).join("all.html"); let settings_file = self.dst.join("settings.html"); let crate_name = krate.name.clone(); @@ -2051,13 +2199,14 @@ impl Context { let mut all = AllTypes::new(); { - // Render the crate documentation + // Generate URLs if needed. let mut work = vec![(self.clone(), item)]; + // Render the crate documentation. while let Some((mut cx, item)) = work.pop() { - cx.item(item, &mut all, |cx, item| { - work.push((cx.clone(), item)) - })? + cx.item(item, &mut all, |icx, item, _| { + work.push((icx.clone(), item)) + }, true)? } } @@ -2115,6 +2264,107 @@ impl Context { Ok(()) } + /// Non-parallelized version of rendering an item. This will take the input + /// item, render its contents, and then invoke the specified closure with + /// all sub-items which need to be rendered. + /// + /// The rendering driver uses this closure to queue up more work. + fn item(&mut self, item: clean::Item, all: &mut AllTypes, mut f: F, + render: bool) -> Result<(), Error> + where F: FnMut(&mut Context, clean::Item, bool), + { + // Stripped modules survive the rustdoc passes (i.e., `strip-private`) + // if they contain impls for public types. These modules can also + // contain items such as publicly re-exported structures. + // + // External crates will provide links to these structures, so + // these modules are recursed into, but not rendered normally + // (a flag on the context). + if !self.render_redirect_pages { + self.render_redirect_pages = item.is_stripped(); + } + + if item.is_mod() { + // modules are special because they add a namespace. We also need to + // recurse into the items of the module as well. + self.recurse(item, render, |this, item| { + let path = Path::new(&this.dst); + if render { + let mut buf = Vec::new(); + this.render_item(&mut buf, &item, false).expect("failed to render..."); + // buf will be empty if the module is stripped and there is no redirect for it + if !buf.is_empty() { + try_err!(this.shared.ensure_dir(path), path); + let dst = Path::new(&this.dst).join("index.html"); + try_err!(fs::write(&dst, buf), &dst); + } + } + let m = match item.inner { + clean::StrippedItem(box clean::ModuleItem(m)) | + clean::ModuleItem(m) => m, + _ => unreachable!() + }; + if render { + // Render sidebar-items.js used throughout this module. + if !this.render_redirect_pages { + // FIXME: generate correct url for sidebar items. + let items = this.build_sidebar_items(&m); + let js_dst = path.join("sidebar-items.js"); + let mut js_out = BufWriter::new(try_err!(File::create(&js_dst), &js_dst)); + try_err!(write!(&mut js_out, "initSidebarItems({});", + as_json(&items)), &js_dst); + } + } + + for item in m.items { + let check_path = !item.is_mod(); + f(this, item, check_path); + } + + Ok(()) + })?; + } else if item.name.is_some() && render { + let mut buf = Vec::new(); + self.render_item(&mut buf, &item, true).expect("failed to render 2"); + // buf will be empty if the item is stripped and there is no redirect for it + if !buf.is_empty() { + let name = item.name.as_ref().expect("name is unavailable 2"); + let item_type = item.type_(); + let file_name = item_path(&self.shared, &self.dst, item_type, name); + let path = Path::new(&file_name).parent().expect("no parent for item..."); + try_err!(self.shared.ensure_dir(path), path); + try_err!(fs::write(&file_name, buf), Path::new(&file_name)); + + if !self.render_redirect_pages { + let full_name = full_path(self, &item); + all.append(full_name, file_name.clone(), item_type); + } + if self.shared.generate_redirect_pages { + // Redirect from a sane URL using the namespace to Rustdoc's + // URL for the page. + let redir_name = format!("{}.{}.html", name, item_type.name_space()); + let redir_dst = self.dst.join(redir_name); + if let Ok(redirect_out) = OpenOptions::new().create_new(true) + .write(true) + .open(&redir_dst) { + let mut redirect_out = BufWriter::new(redirect_out); + try_err!(layout::redirect(&mut redirect_out, &file_name), &redir_dst); + } + } + // If the item is a macro, redirect from the old macro URL (with !) + // to the new one (without). + if item_type == ItemType::Macro { + let redir_name = format!("{}.{}!.html", item_type, name); + let redir_dst = self.dst.join(redir_name); + let redirect_out = try_err!(File::create(&redir_dst), &redir_dst); + let mut redirect_out = BufWriter::new(redirect_out); + try_err!(layout::redirect(&mut redirect_out, &file_name), &redir_dst); + } + } + } + Ok(()) + } + fn render_item(&self, writer: &mut dyn io::Write, it: &clean::Item, @@ -2136,7 +2386,7 @@ impl Context { if !title.is_empty() { title.push_str("::"); } - title.push_str(it.name.as_ref().unwrap()); + title.push_str(it.name.as_ref().expect("unavailable name")); } title.push_str(" - Rust"); let tyname = it.type_().css_class(); @@ -2145,7 +2395,7 @@ impl Context { self.shared.layout.krate) } else { format!("API documentation for the Rust `{}` {} in crate `{}`.", - it.name.as_ref().unwrap(), tyname, self.shared.layout.krate) + it.name.as_ref().expect("unavailable name"), tyname, self.shared.layout.krate) }; let keywords = make_item_keywords(it); let page = layout::Page { @@ -2167,8 +2417,8 @@ impl Context { if !self.render_redirect_pages { layout::render(writer, &self.shared.layout, &page, - &Sidebar{ cx: self, item: it }, - &Item{ cx: self, item: it }, + &Sidebar { cx: self, item: it }, + &Item { cx: self, item: it }, self.shared.css_file_extension.is_some(), &self.shared.themes, self.shared.generate_search_filter)?; @@ -2179,111 +2429,14 @@ impl Context { url.push_str(name); url.push_str("/"); } - url.push_str(&item_path(ty, names.last().unwrap())); + url.push_str(&format!("{}.{}.html", + ty, &names.last().expect("name is empty"))); layout::redirect(writer, &url)?; } } Ok(()) } - /// Non-parallelized version of rendering an item. This will take the input - /// item, render its contents, and then invoke the specified closure with - /// all sub-items which need to be rendered. - /// - /// The rendering driver uses this closure to queue up more work. - fn item(&mut self, item: clean::Item, all: &mut AllTypes, mut f: F) -> Result<(), Error> - where F: FnMut(&mut Context, clean::Item), - { - // Stripped modules survive the rustdoc passes (i.e., `strip-private`) - // if they contain impls for public types. These modules can also - // contain items such as publicly re-exported structures. - // - // External crates will provide links to these structures, so - // these modules are recursed into, but not rendered normally - // (a flag on the context). - if !self.render_redirect_pages { - self.render_redirect_pages = item.is_stripped(); - } - - if item.is_mod() { - // modules are special because they add a namespace. We also need to - // recurse into the items of the module as well. - let name = item.name.as_ref().unwrap().to_string(); - let mut item = Some(item); - self.recurse(name, |this| { - let item = item.take().unwrap(); - - let mut buf = Vec::new(); - this.render_item(&mut buf, &item, false).unwrap(); - // buf will be empty if the module is stripped and there is no redirect for it - if !buf.is_empty() { - try_err!(this.shared.ensure_dir(&this.dst), &this.dst); - let joint_dst = this.dst.join("index.html"); - try_err!(fs::write(&joint_dst, buf), &joint_dst); - } - - let m = match item.inner { - clean::StrippedItem(box clean::ModuleItem(m)) | - clean::ModuleItem(m) => m, - _ => unreachable!() - }; - - // Render sidebar-items.js used throughout this module. - if !this.render_redirect_pages { - let items = this.build_sidebar_items(&m); - let js_dst = this.dst.join("sidebar-items.js"); - let mut js_out = BufWriter::new(try_err!(File::create(&js_dst), &js_dst)); - try_err!(write!(&mut js_out, "initSidebarItems({});", - as_json(&items)), &js_dst); - } - - for item in m.items { - f(this, item); - } - - Ok(()) - })?; - } else if item.name.is_some() { - let mut buf = Vec::new(); - self.render_item(&mut buf, &item, true).unwrap(); - // buf will be empty if the item is stripped and there is no redirect for it - if !buf.is_empty() { - let name = item.name.as_ref().unwrap(); - let item_type = item.type_(); - let file_name = &item_path(item_type, name); - try_err!(self.shared.ensure_dir(&self.dst), &self.dst); - let joint_dst = self.dst.join(file_name); - try_err!(fs::write(&joint_dst, buf), &joint_dst); - - if !self.render_redirect_pages { - all.append(full_path(self, &item), &item_type); - } - if self.shared.generate_redirect_pages { - // Redirect from a sane URL using the namespace to Rustdoc's - // URL for the page. - let redir_name = format!("{}.{}.html", name, item_type.name_space()); - let redir_dst = self.dst.join(redir_name); - if let Ok(redirect_out) = OpenOptions::new().create_new(true) - .write(true) - .open(&redir_dst) { - let mut redirect_out = BufWriter::new(redirect_out); - try_err!(layout::redirect(&mut redirect_out, file_name), &redir_dst); - } - } - // If the item is a macro, redirect from the old macro URL (with !) - // to the new one (without). - if item_type == ItemType::Macro { - let redir_name = format!("{}.{}!.html", item_type, name); - let redir_dst = self.dst.join(redir_name); - let redirect_out = try_err!(File::create(&redir_dst), &redir_dst); - let mut redirect_out = BufWriter::new(redirect_out); - try_err!(layout::redirect(&mut redirect_out, file_name), &redir_dst); - } - } - } - Ok(()) - } - fn build_sidebar_items(&self, m: &clean::Module) -> BTreeMap> { // BTreeMap instead of HashMap to get a sorted output let mut map: BTreeMap<_, Vec<_>> = BTreeMap::new(); @@ -2452,14 +2605,15 @@ impl<'a> fmt::Display for Item<'a> { } } write!(fmt, "{}", - self.item.type_(), self.item.name.as_ref().unwrap())?; + self.item.type_(), self.item.name.as_ref().expect("name is unavailable 4"))?; write!(fmt, "")?; // in-band match self.item.inner { clean::ModuleItem(ref m) => item_module(fmt, self.cx, self.item, &m.items), - clean::FunctionItem(ref f) | clean::ForeignFunctionItem(ref f) => + clean::FunctionItem(ref f) | + clean::ForeignFunctionItem(ref f) => item_function(fmt, self.cx, self.item, f), clean::TraitItem(ref t) => item_trait(fmt, self.cx, self.item, t), clean::StructItem(ref s) => item_struct(fmt, self.cx, self.item, s), @@ -2469,7 +2623,8 @@ impl<'a> fmt::Display for Item<'a> { clean::MacroItem(ref m) => item_macro(fmt, self.cx, self.item, m), clean::ProcMacroItem(ref m) => item_proc_macro(fmt, self.cx, self.item, m), clean::PrimitiveItem(ref p) => item_primitive(fmt, self.cx, self.item, p), - clean::StaticItem(ref i) | clean::ForeignStaticItem(ref i) => + clean::StaticItem(ref i) | + clean::ForeignStaticItem(ref i) => item_static(fmt, self.cx, self.item, i), clean::ConstantItem(ref c) => item_constant(fmt, self.cx, self.item, c), clean::ForeignTypeItem => item_foreign_type(fmt, self.cx, self.item), @@ -2484,17 +2639,34 @@ impl<'a> fmt::Display for Item<'a> { } } -fn item_path(ty: ItemType, name: &str) -> String { +fn item_path_without_extension(item: &clean::Item) -> String { + let ty = item.type_(); + let name = item.name.as_ref().expect("name is unavailable 5"); match ty { - ItemType::Module => format!("{}index.html", SlashChecker(name)), - _ => format!("{}.{}.html", ty.css_class(), name), + ItemType::Module => format!("{}", SlashChecker(name)), + _ => format!("{}.{}", ty.css_class(), name), + } +} + +fn item_path(scx: &SharedContext, path: &Path, ty: ItemType, name: &str) -> String { + let x = match ty { + ItemType::Module => path.join(format!("{}/index.html", name)), + _ => path.join(format!("{}.{}.html", ty.css_class(), name)), + }.display().to_string(); + let types_urls = scx.types_urls.borrow(); + if !types_urls.is_empty() { + types_urls.get(&x) + .map(|(x, _)| x.to_owned()) + .unwrap_or_else(|| x) + } else { + x } } fn full_path(cx: &Context, item: &clean::Item) -> String { let mut s = cx.current.join("::"); s.push_str("::"); - s.push_str(item.name.as_ref().unwrap()); + s.push_str(item.name.as_ref().expect("name is unavailable 6")); s } @@ -2533,13 +2705,14 @@ fn document(w: &mut fmt::Formatter<'_>, cx: &Context, item: &clean::Item) -> fmt } /// Render md_text as markdown. -fn render_markdown(w: &mut fmt::Formatter<'_>, - cx: &Context, - md_text: &str, - links: Vec<(String, String)>, - prefix: &str, - is_hidden: bool) - -> fmt::Result { +fn render_markdown( + w: &mut fmt::Formatter<'_>, + cx: &Context, + md_text: &str, + links: Vec<(String, String)>, + prefix: &str, + is_hidden: bool, +) -> fmt::Result { let mut ids = cx.id_map.borrow_mut(); write!(w, "
{}{}
", if is_hidden { " hidden" } else { "" }, @@ -2745,7 +2918,7 @@ fn item_module(w: &mut fmt::Formatter<'_>, cx: &Context, write!(w, "")?; } curty = myty; - let (short, name) = item_ty_to_strs(&myty.unwrap()); + let (short, name) = item_ty_to_strs(&myty.expect("no myty")); write!(w, "

\ {name}

\n", id = cx.derive_id(short.to_owned()), name = name)?; @@ -2801,14 +2974,17 @@ fn item_module(w: &mut fmt::Formatter<'_>, cx: &Context, title='{title}'>{name}{unsafety_flag}\ \ ", - name = *myitem.name.as_ref().unwrap(), + name = *myitem.name.as_ref().expect("no myitem name"), stab_tags = stability_tags(myitem), docs = MarkdownSummaryLine(doc_value, &myitem.links()), class = myitem.type_(), add = add, stab = stab.unwrap_or_else(|| String::new()), unsafety_flag = unsafety_flag, - href = item_path(myitem.type_(), myitem.name.as_ref().unwrap()), + href = item_path(&cx.shared, + &cx.dst, + myitem.type_(), + myitem.name.as_ref().expect("no myitem name 2")), title = [full_path(cx, myitem), myitem.type_().to_string()] .iter() .filter_map(|s| if !s.is_empty() { @@ -2976,7 +3152,7 @@ fn item_constant(w: &mut fmt::Formatter<'_>, cx: &Context, it: &clean::Item, write!(w, "{vis}const \ {name}: {typ}", vis = VisSpace(&it.visibility), - name = it.name.as_ref().unwrap(), + name = it.name.as_ref().expect("name unavailable 7"), typ = c.type_)?; document(w, cx, it) } @@ -2989,7 +3165,7 @@ fn item_static(w: &mut fmt::Formatter<'_>, cx: &Context, it: &clean::Item, {name}: {typ}", vis = VisSpace(&it.visibility), mutability = MutableSpace(s.mutability), - name = it.name.as_ref().unwrap(), + name = it.name.as_ref().expect("name unavailable 8"), typ = s.type_)?; document(w, cx, it) } @@ -3003,7 +3179,7 @@ fn item_function(w: &mut fmt::Formatter<'_>, cx: &Context, it: &clean::Item, UnsafetySpace(f.header.unsafety), AsyncSpace(f.header.asyncness), AbiSpace(f.header.abi), - it.name.as_ref().unwrap(), + it.name.as_ref().expect("name unavailable 9"), f.generics ).len(); write!(w, "{}
", render_spotlight_traits(it)?)?;
@@ -3016,7 +3192,7 @@ fn item_function(w: &mut fmt::Formatter<'_>, cx: &Context, it: &clean::Item,
            unsafety = UnsafetySpace(f.header.unsafety),
            asyncness = AsyncSpace(f.header.asyncness),
            abi = AbiSpace(f.header.abi),
-           name = it.name.as_ref().unwrap(),
+           name = it.name.as_ref().expect("name unavailable 10"),
            generics = f.generics,
            where_clause = WhereClause { gens: &f.generics, indent: 0, end_newline: true },
            decl = Function {
@@ -3049,7 +3225,7 @@ fn render_impls(cx: &Context, w: &mut fmt::Formatter<'_>,
                 traits: &[&&Impl],
                 containing_item: &clean::Item) -> fmt::Result {
     for i in traits {
-        let did = i.trait_did().unwrap();
+        let did = i.trait_did().expect("cannot get trait def id");
         let assoc_link = AssocItemLink::GotoSource(did, &i.inner_impl().provided_trait_methods);
         render_impl(w, cx, i, assoc_link,
                     RenderMode::Normal, containing_item.stable_since(), true, None)?;
@@ -3101,7 +3277,7 @@ fn item_trait(
                VisSpace(&it.visibility),
                UnsafetySpace(t.unsafety),
                if t.is_auto { "auto " } else { "" },
-               it.name.as_ref().unwrap(),
+               it.name.as_ref().expect("name unavailable 10"),
                t.generics,
                bounds)?;
 
@@ -3185,7 +3361,7 @@ fn item_trait(
 
     fn trait_item(w: &mut fmt::Formatter<'_>, cx: &Context, m: &clean::Item, t: &clean::Item)
                   -> fmt::Result {
-        let name = m.name.as_ref().unwrap();
+        let name = m.name.as_ref().expect("name unavailable 11");
         let item_type = m.type_();
         let id = cx.derive_id(format!("{}.{}", item_type, name));
         let ns_id = cx.derive_id(format!("{}.{}", name, item_type.name_space()));
@@ -3336,14 +3512,14 @@ fn item_trait(
                path[..path.len() - 1].join("/")
            },
            ty = it.type_().css_class(),
-           name = *it.name.as_ref().unwrap())?;
+           name = *it.name.as_ref().expect("name unavailable 12"))?;
     Ok(())
 }
 
 fn naive_assoc_href(it: &clean::Item, link: AssocItemLink<'_>) -> String {
     use crate::html::item_type::ItemType::*;
 
-    let name = it.name.as_ref().unwrap();
+    let name = it.name.as_ref().expect("name unavailable 13");
     let ty = match it.type_() {
         Typedef | AssociatedType => AssociatedType,
         s@_ => s,
@@ -3367,7 +3543,7 @@ fn assoc_const(w: &mut fmt::Formatter<'_>,
     write!(w, "{}const {}: {}",
            VisSpace(&it.visibility),
            naive_assoc_href(it, link),
-           it.name.as_ref().unwrap(),
+           it.name.as_ref().expect("name unavailable 14"),
            ty)?;
     Ok(())
 }
@@ -3378,7 +3554,7 @@ fn assoc_type(w: &mut W, it: &clean::Item,
                              link: AssocItemLink<'_>) -> fmt::Result {
     write!(w, "type {}",
            naive_assoc_href(it, link),
-           it.name.as_ref().unwrap())?;
+           it.name.as_ref().expect("name unavailable 15"))?;
     if !bounds.is_empty() {
         write!(w, ": {}", GenericBounds(bounds))?
     }
@@ -3419,7 +3595,7 @@ fn render_assoc_item(w: &mut fmt::Formatter<'_>,
               link: AssocItemLink<'_>,
               parent: ItemType)
               -> fmt::Result {
-        let name = meth.name.as_ref().unwrap();
+        let name = meth.name.as_ref().expect("name unavailable 16");
         let anchor = format!("#{}.{}", meth.type_(), name);
         let href = match link {
             AssocItemLink::Anchor(Some(ref id)) => format!("#{}", id),
@@ -3526,9 +3702,10 @@ fn item_struct(w: &mut fmt::Formatter<'_>, cx: &Context, it: &clean::Item,
             for (field, ty) in fields {
                 let id = cx.derive_id(format!("{}.{}",
                                            ItemType::StructField,
-                                           field.name.as_ref().unwrap()));
+                                           field.name.as_ref().expect("field name unavailable")));
                 let ns_id = cx.derive_id(format!("{}.{}",
-                                              field.name.as_ref().unwrap(),
+                                              field.name.as_ref()
+                                                        .expect("field name unavailable 2"),
                                               ItemType::StructField.name_space()));
                 write!(w, "\
                            \
@@ -3537,7 +3714,7 @@ fn item_struct(w: &mut fmt::Formatter<'_>, cx: &Context, it: &clean::Item,
                        item_type = ItemType::StructField,
                        id = id,
                        ns_id = ns_id,
-                       name = field.name.as_ref().unwrap(),
+                       name = field.name.as_ref().expect("field name unavailable 3"),
                        ty = ty)?;
                 document(w, cx, field)?;
             }
@@ -3547,7 +3724,7 @@ fn item_struct(w: &mut fmt::Formatter<'_>, cx: &Context, it: &clean::Item,
 }
 
 fn item_union(w: &mut fmt::Formatter<'_>, cx: &Context, it: &clean::Item,
-               s: &clean::Union) -> fmt::Result {
+              s: &clean::Union) -> fmt::Result {
     wrap_into_docblock(w, |w| {
         write!(w, "
")?;
         render_attributes(w, it)?;
@@ -3598,7 +3775,7 @@ fn item_enum(w: &mut fmt::Formatter<'_>, cx: &Context, it: &clean::Item,
         render_attributes(w, it)?;
         write!(w, "{}enum {}{}{}",
                VisSpace(&it.visibility),
-               it.name.as_ref().unwrap(),
+               it.name.as_ref().expect("enum name unavailable"),
                e.generics,
                WhereClause { gens: &e.generics, indent: 0, end_newline: true })?;
         if e.variants.is_empty() && !e.variants_stripped {
@@ -3607,7 +3784,7 @@ fn item_enum(w: &mut fmt::Formatter<'_>, cx: &Context, it: &clean::Item,
             write!(w, " {{\n")?;
             for v in &e.variants {
                 write!(w, "    ")?;
-                let name = v.name.as_ref().unwrap();
+                let name = v.name.as_ref().expect("variant name unavailable");
                 match v.inner {
                     clean::VariantItem(ref var) => {
                         match var.kind {
@@ -3655,16 +3832,17 @@ fn item_enum(w: &mut fmt::Formatter<'_>, cx: &Context, it: &clean::Item,
         for variant in &e.variants {
             let id = cx.derive_id(format!("{}.{}",
                                        ItemType::Variant,
-                                       variant.name.as_ref().unwrap()));
+                                       variant.name.as_ref().expect("variant name unavailable 2")));
             let ns_id = cx.derive_id(format!("{}.{}",
-                                          variant.name.as_ref().unwrap(),
+                                          variant.name.as_ref()
+                                                      .expect("variant name unavailable 3"),
                                           ItemType::Variant.name_space()));
             write!(w, "\
                        \
                        {name}",
                    id = id,
                    ns_id = ns_id,
-                   name = variant.name.as_ref().unwrap())?;
+                   name = variant.name.as_ref().expect("variant name unavailable 4"))?;
             if let clean::VariantItem(ref var) = variant.inner {
                 if let clean::VariantKind::Tuple(ref tys) = var.kind {
                     write!(w, "(")?;
@@ -3686,21 +3864,29 @@ fn item_enum(w: &mut fmt::Formatter<'_>, cx: &Context, it: &clean::Item,
             }) = variant.inner {
                 let variant_id = cx.derive_id(format!("{}.{}.fields",
                                                    ItemType::Variant,
-                                                   variant.name.as_ref().unwrap()));
+                                                   variant.name
+                                                          .as_ref()
+                                                          .expect("variant name unavailable 5")));
                 write!(w, "",
                        id = variant_id)?;
                 write!(w, "

Fields of {name}

", - name = variant.name.as_ref().unwrap())?; + name = variant.name.as_ref().expect("variant name unavailable 6"))?; for field in &s.fields { use crate::clean::StructFieldItem; if let StructFieldItem(ref ty) = field.inner { let id = cx.derive_id(format!("variant.{}.field.{}", - variant.name.as_ref().unwrap(), - field.name.as_ref().unwrap())); + variant.name + .as_ref() + .expect("variant name unavailable 7"), + field.name.as_ref() + .expect("field name unavailable"))); let ns_id = cx.derive_id(format!("{}.{}.{}.{}", - variant.name.as_ref().unwrap(), + variant.name + .as_ref() + .expect("variant name unavailable 8"), ItemType::Variant.name_space(), - field.name.as_ref().unwrap(), + field.name.as_ref() + .expect("field name unavailable 2"), ItemType::StructField.name_space())); write!(w, "\ \ @@ -3708,7 +3894,7 @@ fn item_enum(w: &mut fmt::Formatter<'_>, cx: &Context, it: &clean::Item, ", id = id, ns_id = ns_id, - f = field.name.as_ref().unwrap(), + f = field.name.as_ref().expect("field name unavailable 3"), t = *ty)?; document(w, cx, field)?; } @@ -3762,7 +3948,7 @@ fn render_attributes(w: &mut fmt::Formatter<'_>, it: &clean::Item) -> fmt::Resul if !ATTRIBUTE_WHITELIST.contains(&attr.name_or_empty().get()) { continue; } - if let Some(s) = render_attribute(&attr.meta().unwrap()) { + if let Some(s) = render_attribute(&attr.meta().expect("failed to render attribute")) { attrs.push_str(&format!("#[{}]\n", s)); } } @@ -3780,8 +3966,8 @@ fn render_struct(w: &mut fmt::Formatter<'_>, it: &clean::Item, structhead: bool) -> fmt::Result { write!(w, "{}{}{}", VisSpace(&it.visibility), - if structhead {"struct "} else {""}, - it.name.as_ref().unwrap())?; + if structhead { "struct " } else { "" }, + it.name.as_ref().expect("name unavailable 20"))?; if let Some(g) = g { write!(w, "{}", g)? } @@ -3797,18 +3983,18 @@ fn render_struct(w: &mut fmt::Formatter<'_>, it: &clean::Item, write!(w, "\n{} {}{}: {},", tab, VisSpace(&field.visibility), - field.name.as_ref().unwrap(), + field.name.as_ref().expect("field name unavailable 5"), *ty)?; has_visible_fields = true; } } if has_visible_fields { - if it.has_stripped_fields().unwrap() { + if it.has_stripped_fields().expect("failed to check stripped fields") { write!(w, "\n{} // some fields omitted", tab)?; } write!(w, "\n{}", tab)?; - } else if it.has_stripped_fields().unwrap() { + } else if it.has_stripped_fields().expect("failed to check stripped fields 2") { // If there are no visible fields we can just display // `{ /* fields omitted */ }` to save space. write!(w, " /* fields omitted */ ")?; @@ -3856,7 +4042,7 @@ fn render_union(w: &mut fmt::Formatter<'_>, it: &clean::Item, write!(w, "{}{}{}", VisSpace(&it.visibility), if structhead {"union "} else {""}, - it.name.as_ref().unwrap())?; + it.name.as_ref().expect("name unavailable 21"))?; if let Some(g) = g { write!(w, "{}", g)?; write!(w, "{}", WhereClause { gens: g, indent: 0, end_newline: true })?; @@ -3867,13 +4053,13 @@ fn render_union(w: &mut fmt::Formatter<'_>, it: &clean::Item, if let clean::StructFieldItem(ref ty) = field.inner { write!(w, " {}{}: {},\n{}", VisSpace(&field.visibility), - field.name.as_ref().unwrap(), + field.name.as_ref().expect("field name unavailable 6"), *ty, tab)?; } } - if it.has_stripped_fields().unwrap() { + if it.has_stripped_fields().expect("failed to check stripped fields 3") { write!(w, " // some fields omitted\n{}", tab)?; } write!(w, "}}")?; @@ -3906,11 +4092,13 @@ enum RenderMode { ForDeref { mut_: bool }, } -fn render_assoc_items(w: &mut fmt::Formatter<'_>, - cx: &Context, - containing_item: &clean::Item, - it: DefId, - what: AssocItemRender<'_>) -> fmt::Result { +fn render_assoc_items( + w: &mut fmt::Formatter<'_>, + cx: &Context, + containing_item: &clean::Item, + it: DefId, + what: AssocItemRender<'_> +) -> fmt::Result { let c = cache(); let v = match c.impls.get(&it) { Some(v) => v, @@ -4370,7 +4558,11 @@ fn item_typedef(w: &mut fmt::Formatter<'_>, cx: &Context, it: &clean::Item, render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) } -fn item_foreign_type(w: &mut fmt::Formatter<'_>, cx: &Context, it: &clean::Item) -> fmt::Result { +fn item_foreign_type( + w: &mut fmt::Formatter<'_>, + cx: &Context, + it: &clean::Item, +) -> fmt::Result { writeln!(w, "
extern {{")?;
     render_attributes(w, it)?;
     write!(
@@ -4980,8 +5172,12 @@ fn item_macro(w: &mut fmt::Formatter<'_>, cx: &Context, it: &clean::Item,
     document(w, cx, it)
 }
 
-fn item_proc_macro(w: &mut fmt::Formatter<'_>, cx: &Context, it: &clean::Item, m: &clean::ProcMacro)
-    -> fmt::Result
+fn item_proc_macro(
+    w: &mut fmt::Formatter<'_>,
+    cx: &Context,
+    it: &clean::Item,
+    m: &clean::ProcMacro,
+) -> fmt::Result
 {
     let name = it.name.as_ref().expect("proc-macros always have names");
     match m.kind {
diff --git a/src/librustdoc/html/static/main.js b/src/librustdoc/html/static/main.js
index aad7eb627bfe2..dfaf38f011c1f 100644
--- a/src/librustdoc/html/static/main.js
+++ b/src/librustdoc/html/static/main.js
@@ -486,7 +486,7 @@ if (!DOMTokenList.prototype.remove) {
                             var res = buildHrefAndPath(obj);
                             obj.displayPath = pathSplitter(res[0]);
                             obj.fullPath = obj.displayPath + obj.name;
-                            // To be sure than it some items aren't considered as duplicate.
+                            // To be sure that some items aren't considered as duplicate.
                             obj.fullPath += "|" + obj.ty;
                             obj.href = res[1];
                             out.push(obj);
@@ -1269,14 +1269,15 @@ if (!DOMTokenList.prototype.remove) {
             var href;
             var type = itemTypes[item.ty];
             var name = item.name;
+            var path = item.alternative_path || item.path;
 
             if (type === "mod") {
-                displayPath = item.path + "::";
-                href = rootPath + item.path.replace(/::/g, "/") + "/" +
+                displayPath = path + "::";
+                href = rootPath + path.replace(/::/g, "/") + "/" +
                        name + "/index.html";
             } else if (type === "primitive" || type === "keyword") {
                 displayPath = "";
-                href = rootPath + item.path.replace(/::/g, "/") +
+                href = rootPath + path.replace(/::/g, "/") +
                        "/" + type + "." + name + ".html";
             } else if (type === "externcrate") {
                 displayPath = "";
@@ -1288,15 +1289,15 @@ if (!DOMTokenList.prototype.remove) {
                 if (parentType === "primitive") {
                     displayPath = myparent.name + "::";
                 } else {
-                    displayPath = item.path + "::" + myparent.name + "::";
+                    displayPath = path + "::" + myparent.name + "::";
                 }
-                href = rootPath + item.path.replace(/::/g, "/") +
+                href = rootPath + path.replace(/::/g, "/") +
                        "/" + parentType +
                        "." + myparent.name +
                        ".html" + anchor;
             } else {
-                displayPath = item.path + "::";
-                href = rootPath + item.path.replace(/::/g, "/") +
+                displayPath = path + "::";
+                href = rootPath + path.replace(/::/g, "/") +
                        "/" + type + "." + name + ".html";
             }
             return [displayPath, href];
@@ -1593,7 +1594,8 @@ if (!DOMTokenList.prototype.remove) {
                     var rawRow = items[i];
                     var row = {crate: crate, ty: rawRow[0], name: rawRow[1],
                                path: rawRow[2] || lastPath, desc: rawRow[3],
-                               parent: paths[rawRow[4]], type: rawRow[5]};
+                               parent: paths[rawRow[4]], type: rawRow[5],
+                               alternative_path: rawRow[6]};
                     searchIndex.push(row);
                     if (typeof row.name === "string") {
                         var word = row.name.toLowerCase();
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index 2ebb465d53dbe..3f3f24cadbf25 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -355,6 +355,11 @@ fn opts() -> Vec {
                       "show-coverage",
                       "calculate percentage of public items with documentation")
         }),
+        unstable("case-insensitive", |o| {
+            o.optflag("",
+                      "case-insensitive",
+                      "Generate files with taking into account case insensitive file system")
+        }),
     ]
 }
 
{stab_tags}{docs}