Skip to content

Commit a68b523

Browse files
committed
Auto merge of rust-lang#116471 - notriddle:notriddle/js-trait-alias, r=<try>
rustdoc: use JS to inline target type impl docs into alias Preview docs: - https://notriddle.com/rustdoc-html-demo-5/js-trait-alias/std/io/type.Result.html - https://notriddle.com/rustdoc-html-demo-5/js-trait-alias-compiler/rustc_middle/ty/type.PolyTraitRef.html *Review note: This is mostly just reverting rust-lang#115201. The last commit has the new work in it.* Fixes rust-lang#115718 This is an attempt to balance three problems, each of which would be violated by a simpler implementation: - A type alias should show all the `impl` blocks for the target type, and vice versa, if they're applicable. If nothing was done, and rustdoc continues to match them up in HIR, this would not work. - Copying the target type's docs into its aliases' HTML pages directly causes far too much redundant HTML text to be generated when a crate has large numbers of methods and large numbers of type aliases. - Using JavaScript exclusively for type alias impl docs would be a functional regression, and could make some docs very hard to find for non-JS readers. - Making sure that only applicable docs are show in the resulting page requires a type checkers. Do not reimplement the type checker in JavaScript. So, to make it work, rustdoc stashes these type-alias-inlined docs in a JSONP "database-lite". The file is generated in `write_shared.rs`, included in a `<script>` tag added in `print_item.rs`, and `main.js` takes care of patching the additional docs into the DOM. The format of `trait.impl` and `type.impl` JS files are superficially similar. Each line, except the JSONP wrapper itself, belongs to a crate, and they are otherwise separate (rustdoc should be idempotent). The "meat" of the file is HTML strings, so the frontend code is very simple. Links are relative to the doc root, though, so the frontend needs to fix that up, and inlined docs can reuse these files. However, there are a few differences, caused by the sophisticated features that type aliases have. Consider this crate graph: ```text --------------------------------- | crate A: struct Foo<T> | | type Bar = Foo<i32> | | impl X for Foo<i8> | | impl Y for Foo<i32> | --------------------------------- | ---------------------------------- | crate B: type Baz = A::Foo<i8> | | type Xyy = A::Foo<i8> | | impl Z for Xyy | ---------------------------------- ``` The type.impl/A/struct.Foo.js JS file has a structure kinda like this: ```js JSONP({ "A": [["impl Y for Foo<i32>", "Y", "A::Bar"]], "B": [["impl X for Foo<i8>", "X", "B::Baz", "B::Xyy"], ["impl Z for Xyy", "Z", "B::Baz"]], }); ``` When the type.impl file is loaded, only the current crate's docs are actually used. The main reason to bundle them together is that there's enough duplication in them for DEFLATE to remove the redundancy. The contents of a crate are a list of impl blocks, themselves represented as lists. The first item in the sublist is the HTML block, the second item is the name of the trait (which goes in the sidebar), and all others are the names of type aliases that successfully match. This way: - There's no need to generate these files for types that have no aliases in the current crate. If a dependent crate makes a type alias, it'll take care of generating its own docs. - There's no need to reimplement parts of the type checker in JavaScript. The Rust backend does the checking, and includes its results in the file. - Docs defined directly on the type alias are dropped directly in the HTML by `render_assoc_items`, and are accessible without JavaScript. The JSONP file will not list impl items that are known to be part of the main HTML file already. [JSONP]: https://en.wikipedia.org/wiki/JSONP
2 parents 48e2462 + 760c7f0 commit a68b523

37 files changed

+929
-159
lines changed

Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -4643,6 +4643,7 @@ dependencies = [
46434643
"arrayvec",
46444644
"askama",
46454645
"expect-test",
4646+
"indexmap 2.0.0",
46464647
"itertools",
46474648
"minifier",
46484649
"once_cell",

library/core/src/ptr/mut_ptr.rs

+9-9
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ impl<T: ?Sized> *mut T {
109109
/// with [`cast_mut`] on `*const T` and may have documentation value if used instead of implicit
110110
/// coercion.
111111
///
112-
/// [`cast_mut`]: pointer::cast_mut
112+
/// [`cast_mut`]: #method.cast_mut
113113
#[stable(feature = "ptr_const_cast", since = "1.65.0")]
114114
#[rustc_const_stable(feature = "ptr_const_cast", since = "1.65.0")]
115115
#[rustc_diagnostic_item = "ptr_cast_const"]
@@ -121,7 +121,7 @@ impl<T: ?Sized> *mut T {
121121
/// Casts a pointer to its raw bits.
122122
///
123123
/// This is equivalent to `as usize`, but is more specific to enhance readability.
124-
/// The inverse method is [`from_bits`](pointer#method.from_bits-1).
124+
/// The inverse method is [`from_bits`](#method.from_bits-1).
125125
///
126126
/// In particular, `*p as usize` and `p as usize` will both compile for
127127
/// pointers to numeric types but do very different things, so using this
@@ -157,7 +157,7 @@ impl<T: ?Sized> *mut T {
157157
/// Creates a pointer from its raw bits.
158158
///
159159
/// This is equivalent to `as *mut T`, but is more specific to enhance readability.
160-
/// The inverse method is [`to_bits`](pointer#method.to_bits-1).
160+
/// The inverse method is [`to_bits`](#method.to_bits-1).
161161
///
162162
/// # Examples
163163
///
@@ -307,7 +307,7 @@ impl<T: ?Sized> *mut T {
307307
///
308308
/// For the mutable counterpart see [`as_mut`].
309309
///
310-
/// [`as_uninit_ref`]: pointer#method.as_uninit_ref-1
310+
/// [`as_uninit_ref`]: #method.as_uninit_ref-1
311311
/// [`as_mut`]: #method.as_mut
312312
///
313313
/// # Safety
@@ -373,7 +373,7 @@ impl<T: ?Sized> *mut T {
373373
///
374374
/// For the mutable counterpart see [`as_uninit_mut`].
375375
///
376-
/// [`as_ref`]: pointer#method.as_ref-1
376+
/// [`as_ref`]: #method.as_ref-1
377377
/// [`as_uninit_mut`]: #method.as_uninit_mut
378378
///
379379
/// # Safety
@@ -628,7 +628,7 @@ impl<T: ?Sized> *mut T {
628628
/// For the shared counterpart see [`as_ref`].
629629
///
630630
/// [`as_uninit_mut`]: #method.as_uninit_mut
631-
/// [`as_ref`]: pointer#method.as_ref-1
631+
/// [`as_ref`]: #method.as_ref-1
632632
///
633633
/// # Safety
634634
///
@@ -693,7 +693,7 @@ impl<T: ?Sized> *mut T {
693693
/// For the shared counterpart see [`as_uninit_ref`].
694694
///
695695
/// [`as_mut`]: #method.as_mut
696-
/// [`as_uninit_ref`]: pointer#method.as_uninit_ref-1
696+
/// [`as_uninit_ref`]: #method.as_uninit_ref-1
697697
///
698698
/// # Safety
699699
///
@@ -792,7 +792,7 @@ impl<T: ?Sized> *mut T {
792792
///
793793
/// All of the following safety requirements are trivially satisfied for this usecase.
794794
///
795-
/// [`offset`]: pointer#method.offset-1
795+
/// [`offset`]: #method.offset-1
796796
///
797797
/// # Safety
798798
///
@@ -2081,7 +2081,7 @@ impl<T> *mut [T] {
20812081
///
20822082
/// For the mutable counterpart see [`as_uninit_slice_mut`].
20832083
///
2084-
/// [`as_ref`]: pointer#method.as_ref-1
2084+
/// [`as_ref`]: #method.as_ref-1
20852085
/// [`as_uninit_slice_mut`]: #method.as_uninit_slice_mut
20862086
///
20872087
/// # Safety

src/librustdoc/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ path = "lib.rs"
1010
arrayvec = { version = "0.7", default-features = false }
1111
askama = { version = "0.12", default-features = false, features = ["config"] }
1212
itertools = "0.10.1"
13+
indexmap = "2"
1314
minifier = "0.2.2"
1415
once_cell = "1.10.0"
1516
regex = "1"

src/librustdoc/clean/inline.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ use crate::clean::{
2626
use crate::core::DocContext;
2727
use crate::formats::item_type::ItemType;
2828

29+
use super::Item;
30+
2931
/// Attempt to inline a definition into this AST.
3032
///
3133
/// This function will fetch the definition specified, and if it is
@@ -83,7 +85,7 @@ pub(crate) fn try_inline(
8385
Res::Def(DefKind::TyAlias, did) => {
8486
record_extern_fqn(cx, did, ItemType::TypeAlias);
8587
build_impls(cx, did, attrs_without_docs, &mut ret);
86-
clean::TypeAliasItem(build_type_alias(cx, did))
88+
clean::TypeAliasItem(build_type_alias(cx, did, &mut ret))
8789
}
8890
Res::Def(DefKind::Enum, did) => {
8991
record_extern_fqn(cx, did, ItemType::Enum);
@@ -281,11 +283,15 @@ fn build_union(cx: &mut DocContext<'_>, did: DefId) -> clean::Union {
281283
clean::Union { generics, fields }
282284
}
283285

284-
fn build_type_alias(cx: &mut DocContext<'_>, did: DefId) -> Box<clean::TypeAlias> {
286+
fn build_type_alias(
287+
cx: &mut DocContext<'_>,
288+
did: DefId,
289+
ret: &mut Vec<Item>,
290+
) -> Box<clean::TypeAlias> {
285291
let predicates = cx.tcx.explicit_predicates_of(did);
286292
let ty = cx.tcx.type_of(did).instantiate_identity();
287293
let type_ = clean_middle_ty(ty::Binder::dummy(ty), cx, Some(did), None);
288-
let inner_type = clean_ty_alias_inner_type(ty, cx);
294+
let inner_type = clean_ty_alias_inner_type(ty, cx, ret);
289295

290296
Box::new(clean::TypeAlias {
291297
type_,

src/librustdoc/clean/mod.rs

+32-7
Original file line numberDiff line numberDiff line change
@@ -932,18 +932,27 @@ fn clean_ty_generics<'tcx>(
932932
fn clean_ty_alias_inner_type<'tcx>(
933933
ty: Ty<'tcx>,
934934
cx: &mut DocContext<'tcx>,
935+
ret: &mut Vec<Item>,
935936
) -> Option<TypeAliasInnerType> {
936937
let ty::Adt(adt_def, args) = ty.kind() else {
937938
return None;
938939
};
939940

941+
if !adt_def.did().is_local() {
942+
inline::build_impls(cx, adt_def.did(), None, ret);
943+
}
944+
940945
Some(if adt_def.is_enum() {
941946
let variants: rustc_index::IndexVec<_, _> = adt_def
942947
.variants()
943948
.iter()
944949
.map(|variant| clean_variant_def_with_args(variant, args, cx))
945950
.collect();
946951

952+
if !adt_def.did().is_local() {
953+
inline::record_extern_fqn(cx, adt_def.did(), ItemType::Enum);
954+
}
955+
947956
TypeAliasInnerType::Enum {
948957
variants,
949958
is_non_exhaustive: adt_def.is_variant_list_non_exhaustive(),
@@ -959,8 +968,14 @@ fn clean_ty_alias_inner_type<'tcx>(
959968
clean_variant_def_with_args(variant, args, cx).kind.inner_items().cloned().collect();
960969

961970
if adt_def.is_struct() {
971+
if !adt_def.did().is_local() {
972+
inline::record_extern_fqn(cx, adt_def.did(), ItemType::Struct);
973+
}
962974
TypeAliasInnerType::Struct { ctor_kind: variant.ctor_kind(), fields }
963975
} else {
976+
if !adt_def.did().is_local() {
977+
inline::record_extern_fqn(cx, adt_def.did(), ItemType::Union);
978+
}
964979
TypeAliasInnerType::Union { fields }
965980
}
966981
})
@@ -2734,14 +2749,24 @@ fn clean_maybe_renamed_item<'tcx>(
27342749
}
27352750

27362751
let ty = cx.tcx.type_of(def_id).instantiate_identity();
2737-
let inner_type = clean_ty_alias_inner_type(ty, cx);
27382752

2739-
TypeAliasItem(Box::new(TypeAlias {
2740-
generics,
2741-
inner_type,
2742-
type_: rustdoc_ty,
2743-
item_type: Some(type_),
2744-
}))
2753+
let mut ret = Vec::new();
2754+
let inner_type = clean_ty_alias_inner_type(ty, cx, &mut ret);
2755+
2756+
ret.push(generate_item_with_correct_attrs(
2757+
cx,
2758+
TypeAliasItem(Box::new(TypeAlias {
2759+
generics,
2760+
inner_type,
2761+
type_: rustdoc_ty,
2762+
item_type: Some(type_),
2763+
})),
2764+
item.owner_id.def_id.to_def_id(),
2765+
name,
2766+
import_id,
2767+
renamed,
2768+
));
2769+
return ret;
27452770
}
27462771
ItemKind::Enum(ref def, generics) => EnumItem(Enum {
27472772
variants: def.variants.iter().map(|v| clean_variant(v, cx)).collect(),

src/librustdoc/formats/cache.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ pub(crate) struct Cache {
5050
/// Unlike 'paths', this mapping ignores any renames that occur
5151
/// due to 'use' statements.
5252
///
53-
/// This map is used when writing out the special 'implementors'
54-
/// javascript file. By using the exact path that the type
53+
/// This map is used when writing out the `impl.trait` and `impl.type`
54+
/// javascript files. By using the exact path that the type
5555
/// is declared with, we ensure that each path will be identical
5656
/// to the path used if the corresponding type is inlined. By
5757
/// doing this, we can detect duplicate impls on a trait page, and only display

src/librustdoc/formats/item_type.rs

+3
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ impl ItemType {
180180
pub(crate) fn is_method(&self) -> bool {
181181
matches!(*self, ItemType::Method | ItemType::TyMethod)
182182
}
183+
pub(crate) fn is_adt(&self) -> bool {
184+
matches!(*self, ItemType::Struct | ItemType::Union | ItemType::Enum)
185+
}
183186
}
184187

185188
impl fmt::Display for ItemType {

src/librustdoc/html/render/context.rs

+2-51
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@ use std::sync::mpsc::{channel, Receiver};
77

88
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
99
use rustc_hir::def_id::{DefIdMap, LOCAL_CRATE};
10-
use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
1110
use rustc_middle::ty::TyCtxt;
1211
use rustc_session::Session;
13-
use rustc_span::def_id::DefId;
1412
use rustc_span::edition::Edition;
1513
use rustc_span::source_map::FileName;
1614
use rustc_span::{sym, Symbol};
@@ -24,13 +22,13 @@ use super::{
2422
sidebar::{sidebar_module_like, Sidebar},
2523
AllTypes, LinkFromSrc, StylePath,
2624
};
27-
use crate::clean::{self, types::ExternalLocation, ExternalCrate, TypeAliasItem};
25+
use crate::clean::{self, types::ExternalLocation, ExternalCrate};
2826
use crate::config::{ModuleSorting, RenderOptions};
2927
use crate::docfs::{DocFS, PathError};
3028
use crate::error::Error;
3129
use crate::formats::cache::Cache;
3230
use crate::formats::item_type::ItemType;
33-
use crate::formats::{self, FormatRenderer};
31+
use crate::formats::FormatRenderer;
3432
use crate::html::escape::Escape;
3533
use crate::html::format::{join_with_double_colon, Buffer};
3634
use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap};
@@ -149,53 +147,6 @@ impl SharedContext<'_> {
149147
pub(crate) fn edition(&self) -> Edition {
150148
self.tcx.sess.edition()
151149
}
152-
153-
/// Returns a list of impls on the given type, and, if it's a type alias,
154-
/// other types that it aliases.
155-
pub(crate) fn all_impls_for_item<'a>(
156-
&'a self,
157-
it: &clean::Item,
158-
did: DefId,
159-
) -> Vec<&'a formats::Impl> {
160-
let tcx = self.tcx;
161-
let cache = &self.cache;
162-
let mut saw_impls = FxHashSet::default();
163-
let mut v: Vec<&formats::Impl> = cache
164-
.impls
165-
.get(&did)
166-
.map(Vec::as_slice)
167-
.unwrap_or(&[])
168-
.iter()
169-
.filter(|i| saw_impls.insert(i.def_id()))
170-
.collect();
171-
if let TypeAliasItem(ait) = &*it.kind &&
172-
let aliased_clean_type = ait.item_type.as_ref().unwrap_or(&ait.type_) &&
173-
let Some(aliased_type_defid) = aliased_clean_type.def_id(cache) &&
174-
let Some(av) = cache.impls.get(&aliased_type_defid) &&
175-
let Some(alias_def_id) = it.item_id.as_def_id()
176-
{
177-
// This branch of the compiler compares types structually, but does
178-
// not check trait bounds. That's probably fine, since type aliases
179-
// don't normally constrain on them anyway.
180-
// https://github.com/rust-lang/rust/issues/21903
181-
//
182-
// FIXME(lazy_type_alias): Once the feature is complete or stable, rewrite this to use type unification.
183-
// Be aware of `tests/rustdoc/issue-112515-impl-ty-alias.rs` which might regress.
184-
let aliased_ty = tcx.type_of(alias_def_id).skip_binder();
185-
let reject_cx = DeepRejectCtxt {
186-
treat_obligation_params: TreatParams::AsCandidateKey,
187-
};
188-
v.extend(av.iter().filter(|impl_| {
189-
if let Some(impl_def_id) = impl_.impl_item.item_id.as_def_id() {
190-
reject_cx.types_may_unify(aliased_ty, tcx.type_of(impl_def_id).skip_binder())
191-
&& saw_impls.insert(impl_def_id)
192-
} else {
193-
false
194-
}
195-
}));
196-
}
197-
v
198-
}
199150
}
200151

201152
impl<'tcx> Context<'tcx> {

src/librustdoc/html/render/mod.rs

+9-16
Original file line numberDiff line numberDiff line change
@@ -1131,13 +1131,13 @@ pub(crate) fn render_all_impls(
11311131
fn render_assoc_items<'a, 'cx: 'a>(
11321132
cx: &'a mut Context<'cx>,
11331133
containing_item: &'a clean::Item,
1134-
did: DefId,
1134+
it: DefId,
11351135
what: AssocItemRender<'a>,
11361136
) -> impl fmt::Display + 'a + Captures<'cx> {
11371137
let mut derefs = DefIdSet::default();
1138-
derefs.insert(did);
1138+
derefs.insert(it);
11391139
display_fn(move |f| {
1140-
render_assoc_items_inner(f, cx, containing_item, did, what, &mut derefs);
1140+
render_assoc_items_inner(f, cx, containing_item, it, what, &mut derefs);
11411141
Ok(())
11421142
})
11431143
}
@@ -1146,17 +1146,15 @@ fn render_assoc_items_inner(
11461146
mut w: &mut dyn fmt::Write,
11471147
cx: &mut Context<'_>,
11481148
containing_item: &clean::Item,
1149-
did: DefId,
1149+
it: DefId,
11501150
what: AssocItemRender<'_>,
11511151
derefs: &mut DefIdSet,
11521152
) {
11531153
info!("Documenting associated items of {:?}", containing_item.name);
11541154
let shared = Rc::clone(&cx.shared);
1155-
let v = shared.all_impls_for_item(containing_item, did);
1156-
let v = v.as_slice();
1157-
let (non_trait, traits): (Vec<&Impl>, _) =
1158-
v.iter().partition(|i| i.inner_impl().trait_.is_none());
1159-
let mut saw_impls = FxHashSet::default();
1155+
let cache = &shared.cache;
1156+
let Some(v) = cache.impls.get(&it) else { return };
1157+
let (non_trait, traits): (Vec<_>, _) = v.iter().partition(|i| i.inner_impl().trait_.is_none());
11601158
if !non_trait.is_empty() {
11611159
let mut tmp_buf = Buffer::html();
11621160
let (render_mode, id, class_html) = match what {
@@ -1185,9 +1183,6 @@ fn render_assoc_items_inner(
11851183
};
11861184
let mut impls_buf = Buffer::html();
11871185
for i in &non_trait {
1188-
if !saw_impls.insert(i.def_id()) {
1189-
continue;
1190-
}
11911186
render_impl(
11921187
&mut impls_buf,
11931188
cx,
@@ -1233,10 +1228,8 @@ fn render_assoc_items_inner(
12331228

12341229
let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) =
12351230
traits.into_iter().partition(|t| t.inner_impl().kind.is_auto());
1236-
let (blanket_impl, concrete): (Vec<&Impl>, _) = concrete
1237-
.into_iter()
1238-
.filter(|t| saw_impls.insert(t.def_id()))
1239-
.partition(|t| t.inner_impl().kind.is_blanket());
1231+
let (blanket_impl, concrete): (Vec<&Impl>, _) =
1232+
concrete.into_iter().partition(|t| t.inner_impl().kind.is_blanket());
12401233

12411234
render_all_impls(w, cx, containing_item, &concrete, &synthetic, &blanket_impl);
12421235
}

0 commit comments

Comments
 (0)