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