@@ -3,7 +3,7 @@ use clean::AttributesExt;
3
3
use std:: cmp:: Ordering ;
4
4
use std:: fmt;
5
5
6
- use rustc_data_structures:: fx:: FxHashMap ;
6
+ use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
7
7
use rustc_hir as hir;
8
8
use rustc_hir:: def:: CtorKind ;
9
9
use rustc_hir:: def_id:: DefId ;
@@ -790,16 +790,18 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra
790
790
render_assoc_items ( w, cx, it, it. item_id . expect_def_id ( ) , AssocItemRender :: All ) ;
791
791
792
792
let cache = cx. cache ( ) ;
793
+ let mut extern_crates = FxHashSet :: default ( ) ;
793
794
if let Some ( implementors) = cache. implementors . get ( & it. item_id . expect_def_id ( ) ) {
794
795
// The DefId is for the first Type found with that name. The bool is
795
796
// if any Types with the same name but different DefId have been found.
796
797
let mut implementor_dups: FxHashMap < Symbol , ( DefId , bool ) > = FxHashMap :: default ( ) ;
797
798
for implementor in implementors {
798
- match implementor. inner_impl ( ) . for_ {
799
- clean:: Type :: Path { ref path }
800
- | clean:: BorrowedRef { type_ : box clean:: Type :: Path { ref path } , .. }
801
- if !path. is_assoc_ty ( ) =>
802
- {
799
+ if let Some ( did) = implementor. inner_impl ( ) . for_ . without_borrowed_ref ( ) . def_id ( cx. cache ( ) ) &&
800
+ !did. is_local ( ) {
801
+ extern_crates. insert ( did. krate ) ;
802
+ }
803
+ match implementor. inner_impl ( ) . for_ . without_borrowed_ref ( ) {
804
+ clean:: Type :: Path { ref path } if !path. is_assoc_ty ( ) => {
803
805
let did = path. def_id ( ) ;
804
806
let & mut ( prev_did, ref mut has_duplicates) =
805
807
implementor_dups. entry ( path. last ( ) ) . or_insert ( ( did, false ) ) ;
@@ -898,20 +900,96 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra
898
900
}
899
901
}
900
902
903
+ // Include implementors in crates that depend on the current crate.
904
+ //
905
+ // This is complicated by the way rustdoc is invoked, which is basically
906
+ // the same way rustc is invoked: it gets called, one at a time, for each
907
+ // crate. When building the rustdocs for the current crate, rustdoc can
908
+ // see crate metadata for its dependencies, but cannot see metadata for its
909
+ // dependents.
910
+ //
911
+ // To make this work, we generate a "hook" at this stage, and our
912
+ // dependents can "plug in" to it when they build. For simplicity's sake,
913
+ // it's [JSONP]: a JavaScript file with the data we need (and can parse),
914
+ // surrounded by a tiny wrapper that the Rust side ignores, but allows the
915
+ // JavaScript side to include without having to worry about Same Origin
916
+ // Policy. The code for *that* is in `write_shared.rs`.
917
+ //
918
+ // This is further complicated by `#[doc(inline)]`. We want all copies
919
+ // of an inlined trait to reference the same JS file, to address complex
920
+ // dependency graphs like this one (lower crates depend on higher crates):
921
+ //
922
+ // ```text
923
+ // --------------------------------------------
924
+ // | crate A: trait Foo |
925
+ // --------------------------------------------
926
+ // | |
927
+ // -------------------------------- |
928
+ // | crate B: impl A::Foo for Bar | |
929
+ // -------------------------------- |
930
+ // | |
931
+ // ---------------------------------------------
932
+ // | crate C: #[doc(inline)] use A::Foo as Baz |
933
+ // | impl Baz for Quux |
934
+ // ---------------------------------------------
935
+ // ```
936
+ //
937
+ // Basically, we want `C::Baz` and `A::Foo` to show the same set of
938
+ // impls, which is easier if they both treat `/implementors/A/trait.Foo.js`
939
+ // as the Single Source of Truth.
940
+ //
941
+ // We also want the `impl Baz for Quux` to be written to
942
+ // `trait.Foo.js`. However, when we generate plain HTML for `C::Baz`,
943
+ // we're going to want to generate plain HTML for `impl Baz for Quux` too,
944
+ // because that'll load faster, and it's better for SEO. And we don't want
945
+ // the same impl to show up twice on the same page.
946
+ //
947
+ // To make this work, the implementors JS file has a structure kinda
948
+ // like this:
949
+ //
950
+ // ```js
951
+ // JSONP({
952
+ // "B": {"impl A::Foo for Bar"},
953
+ // "C": {"impl Baz for Quux"},
954
+ // });
955
+ // ```
956
+ //
957
+ // First of all, this means we can rebuild a crate, and it'll replace its own
958
+ // data if something changes. That is, `rustdoc` is idempotent. The other
959
+ // advantage is that we can list the crates that get included in the HTML,
960
+ // and ignore them when doing the JavaScript-based part of rendering.
961
+ // So C's HTML will have something like this:
962
+ //
963
+ // ```html
964
+ // <script type="text/javascript" src="/implementors/A/trait.Foo.js"
965
+ // data-ignore-extern-crates="A,B" async></script>
966
+ // ```
967
+ //
968
+ // And, when the JS runs, anything in data-ignore-extern-crates is known
969
+ // to already be in the HTML, and will be ignored.
970
+ //
971
+ // [JSONP]: https://en.wikipedia.org/wiki/JSONP
901
972
let mut js_src_path: UrlPartsBuilder = std:: iter:: repeat ( ".." )
902
973
. take ( cx. current . len ( ) )
903
974
. chain ( std:: iter:: once ( "implementors" ) )
904
975
. collect ( ) ;
905
- if it. item_id . is_local ( ) {
906
- js_src_path. extend ( cx. current . iter ( ) . copied ( ) ) ;
976
+ if let Some ( did) = it. item_id . as_def_id ( ) &&
977
+ let get_extern = { || cache. external_paths . get ( & did) . map ( |s| s. 0 . clone ( ) ) } &&
978
+ let Some ( fqp) = cache. exact_paths . get ( & did) . cloned ( ) . or_else ( get_extern) {
979
+ js_src_path. extend ( fqp[ ..fqp. len ( ) - 1 ] . iter ( ) . copied ( ) ) ;
980
+ js_src_path. push_fmt ( format_args ! ( "{}.{}.js" , it. type_( ) , fqp. last( ) . unwrap( ) ) ) ;
907
981
} else {
908
- let ( ref path , _ ) = cache . external_paths [ & it . item_id . expect_def_id ( ) ] ;
909
- js_src_path. extend ( path [ ..path . len ( ) - 1 ] . iter ( ) . copied ( ) ) ;
982
+ js_src_path . extend ( cx . current . iter ( ) . copied ( ) ) ;
983
+ js_src_path. push_fmt ( format_args ! ( "{}.{}.js" , it . type_ ( ) , it . name . unwrap ( ) ) ) ;
910
984
}
911
- js_src_path. push_fmt ( format_args ! ( "{}.{}.js" , it. type_( ) , it. name. unwrap( ) ) ) ;
985
+ let extern_crates = extern_crates
986
+ . into_iter ( )
987
+ . map ( |cnum| cx. shared . tcx . crate_name ( cnum) . to_string ( ) )
988
+ . collect :: < Vec < _ > > ( )
989
+ . join ( "," ) ;
912
990
write ! (
913
991
w,
914
- "<script type=\" text/javascript\" src=\" {src}\" async></script>" ,
992
+ "<script type=\" text/javascript\" src=\" {src}\" data-ignore-extern-crates= \" {extern_crates} \" async></script>" ,
915
993
src = js_src_path. finish( ) ,
916
994
) ;
917
995
}
0 commit comments