diff options
| author | Alex Crichton <alex@alexcrichton.com> | 2015-04-07 00:16:35 -0700 |
|---|---|---|
| committer | Alex Crichton <alex@alexcrichton.com> | 2015-04-07 17:54:34 -0700 |
| commit | 75ef0832ae00477f837a73356ea7f12f64134c7c (patch) | |
| tree | 589e3b6fb2f10c5d5905650bbd936b8fd249a04f | |
| parent | 11f26f999581e639233e64f4f1fbc210a2bb856a (diff) | |
| download | rust-75ef0832ae00477f837a73356ea7f12f64134c7c.tar.gz rust-75ef0832ae00477f837a73356ea7f12f64134c7c.zip | |
rustdoc: Improve handling inlined associated types
* All bounds are now discovered through the trait to be inlined. * The `?Sized` bound now renders correctly for inlined associated types. * All `QPath`s (`<A as B>::C`) instances are rendered as `A::C` where `C` is a hyperlink to the trait `B`. This should improve at least how the docs look at least. * Supertrait bounds are now separated and display as the source lists them. Closes #20727 Closes #21145
| -rw-r--r-- | src/librustc/metadata/encoder.rs | 3 | ||||
| -rw-r--r-- | src/librustdoc/clean/inline.rs | 52 | ||||
| -rw-r--r-- | src/librustdoc/clean/mod.rs | 176 | ||||
| -rw-r--r-- | src/librustdoc/html/format.rs | 35 | ||||
| -rw-r--r-- | src/test/auxiliary/issue-20727.rs | 38 | ||||
| -rw-r--r-- | src/test/rustdoc/issue-20727-2.rs | 32 | ||||
| -rw-r--r-- | src/test/rustdoc/issue-20727-3.rs | 33 | ||||
| -rw-r--r-- | src/test/rustdoc/issue-20727-4.rs | 49 | ||||
| -rw-r--r-- | src/test/rustdoc/issue-20727.rs | 33 |
9 files changed, 382 insertions, 69 deletions
diff --git a/src/librustc/metadata/encoder.rs b/src/librustc/metadata/encoder.rs index b9559abbdf4..f9d3d707bb5 100644 --- a/src/librustc/metadata/encoder.rs +++ b/src/librustc/metadata/encoder.rs @@ -1264,7 +1264,8 @@ fn encode_info_for_item(ecx: &EncodeContext, encode_paren_sugar(rbml_w, trait_def.paren_sugar); encode_defaulted(rbml_w, ty::trait_has_default_impl(tcx, def_id)); encode_associated_type_names(rbml_w, &trait_def.associated_type_names); - encode_generics(rbml_w, ecx, &trait_def.generics, &trait_predicates, tag_item_generics); + encode_generics(rbml_w, ecx, &trait_def.generics, &trait_predicates, + tag_item_generics); encode_predicates(rbml_w, ecx, &ty::lookup_super_predicates(tcx, def_id), tag_item_super_predicates); encode_trait_ref(rbml_w, ecx, &*trait_def.trait_ref, tag_item_trait_ref); diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index e4b2e82b21b..24184bae95b 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -150,11 +150,14 @@ pub fn build_external_trait(cx: &DocContext, tcx: &ty::ctxt, let def = ty::lookup_trait_def(tcx, did); let trait_items = ty::trait_items(tcx, did).clean(cx); let predicates = ty::lookup_predicates(tcx, did); + let generics = (&def.generics, &predicates, subst::TypeSpace).clean(cx); + let generics = filter_non_trait_generics(did, generics); + let (generics, supertrait_bounds) = separate_supertrait_bounds(generics); clean::Trait { unsafety: def.unsafety, - generics: (&def.generics, &predicates, subst::TypeSpace).clean(cx), + generics: generics, items: trait_items, - bounds: vec![], // supertraits can be found in the list of predicates + bounds: supertrait_bounds, } } @@ -447,3 +450,48 @@ fn build_static(cx: &DocContext, tcx: &ty::ctxt, expr: "\n\n\n".to_string(), // trigger the "[definition]" links } } + +/// A trait's generics clause actually contains all of the predicates for all of +/// its associated types as well. We specifically move these clauses to the +/// associated types instead when displaying, so when we're genering the +/// generics for the trait itself we need to be sure to remove them. +/// +/// The inverse of this filtering logic can be found in the `Clean` +/// implementation for `AssociatedType` +fn filter_non_trait_generics(trait_did: ast::DefId, mut g: clean::Generics) + -> clean::Generics { + g.where_predicates.retain(|pred| { + match *pred { + clean::WherePredicate::BoundPredicate { + ty: clean::QPath { + self_type: box clean::Generic(ref s), + trait_: box clean::ResolvedPath { did, .. }, + name: ref _name, + }, .. + } => *s != "Self" || did != trait_did, + _ => true, + } + }); + return g; +} + +/// Supertrait bounds for a trait are also listed in the generics coming from +/// the metadata for a crate, so we want to separate those out and create a new +/// list of explicit supertrait bounds to render nicely. +fn separate_supertrait_bounds(mut g: clean::Generics) + -> (clean::Generics, Vec<clean::TyParamBound>) { + let mut ty_bounds = Vec::new(); + g.where_predicates.retain(|pred| { + match *pred { + clean::WherePredicate::BoundPredicate { + ty: clean::Generic(ref s), + ref bounds + } if *s == "Self" => { + ty_bounds.extend(bounds.iter().cloned()); + false + } + _ => true, + } + }); + (g, ty_bounds) +} diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 630dc18dca7..65778114fe0 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -498,6 +498,35 @@ pub enum TyParamBound { TraitBound(PolyTrait, ast::TraitBoundModifier) } +impl TyParamBound { + fn maybe_sized(cx: &DocContext) -> TyParamBound { + use syntax::ast::TraitBoundModifier as TBM; + let mut sized_bound = ty::BuiltinBound::BoundSized.clean(cx); + if let TyParamBound::TraitBound(_, ref mut tbm) = sized_bound { + *tbm = TBM::Maybe + }; + sized_bound + } + + fn is_sized_bound(&self, cx: &DocContext) -> bool { + use syntax::ast::TraitBoundModifier as TBM; + if let Some(tcx) = cx.tcx_opt() { + let sized_did = match tcx.lang_items.sized_trait() { + Some(did) => did, + None => return false + }; + if let TyParamBound::TraitBound(PolyTrait { + trait_: Type::ResolvedPath { did, .. }, .. + }, TBM::None) = *self { + if did == sized_did { + return true + } + } + } + false + } +} + impl Clean<TyParamBound> for ast::TyParamBound { fn clean(&self, cx: &DocContext) -> TyParamBound { match *self { @@ -835,7 +864,9 @@ impl<'tcx> Clean<Type> for ty::ProjectionTy<'tcx> { fn clean(&self, cx: &DocContext) -> Type { let trait_ = match self.trait_ref.clean(cx) { TyParamBound::TraitBound(t, _) => t.trait_, - TyParamBound::RegionBound(_) => panic!("cleaning a trait got a region??"), + TyParamBound::RegionBound(_) => { + panic!("cleaning a trait got a region") + } }; Type::QPath { name: self.item_name.clean(cx), @@ -868,28 +899,8 @@ impl<'a, 'tcx> Clean<Generics> for (&'a ty::Generics<'tcx>, subst::ParamSpace) { fn clean(&self, cx: &DocContext) -> Generics { use std::collections::HashSet; - use syntax::ast::TraitBoundModifier as TBM; use self::WherePredicate as WP; - fn has_sized_bound(bounds: &[TyParamBound], cx: &DocContext) -> bool { - if let Some(tcx) = cx.tcx_opt() { - let sized_did = match tcx.lang_items.sized_trait() { - Some(did) => did, - None => return false - }; - for bound in bounds { - if let TyParamBound::TraitBound(PolyTrait { - trait_: Type::ResolvedPath { did, .. }, .. - }, TBM::None) = *bound { - if did == sized_did { - return true - } - } - } - } - false - } - let (gens, preds, space) = *self; // Bounds in the type_params and lifetimes fields are repeated in the @@ -904,34 +915,38 @@ impl<'a, 'tcx> Clean<Generics> for (&'a ty::Generics<'tcx>, srp.clean(cx) }).collect::<Vec<_>>(); - let where_predicates = preds.predicates.get_slice(space) - .to_vec().clean(cx); + let mut where_predicates = preds.predicates.get_slice(space) + .to_vec().clean(cx); - // Type parameters have a Sized bound by default unless removed with + // Type parameters and have a Sized bound by default unless removed with // ?Sized. Scan through the predicates and mark any type parameter with // a Sized bound, removing the bounds as we find them. + // + // Note that associated types also have a sized bound by default, but we + // don't actually konw the set of associated types right here so that's + // handled in cleaning associated types let mut sized_params = HashSet::new(); - let mut where_predicates = where_predicates.into_iter().filter_map(|pred| { - if let WP::BoundPredicate { ty: Type::Generic(ref g), ref bounds } = pred { - if has_sized_bound(&**bounds, cx) { - sized_params.insert(g.clone()); - return None + where_predicates.retain(|pred| { + match *pred { + WP::BoundPredicate { ty: Generic(ref g), ref bounds } => { + if bounds.iter().any(|b| b.is_sized_bound(cx)) { + sized_params.insert(g.clone()); + false + } else { + true + } } + _ => true, } - Some(pred) - }).collect::<Vec<_>>(); + }); - // Finally, run through the type parameters again and insert a ?Sized + // Run through the type parameters again and insert a ?Sized // unbound for any we didn't find to be Sized. for tp in &stripped_typarams { if !sized_params.contains(&tp.name) { - let mut sized_bound = ty::BuiltinBound::BoundSized.clean(cx); - if let TyParamBound::TraitBound(_, ref mut tbm) = sized_bound { - *tbm = TBM::Maybe - }; where_predicates.push(WP::BoundPredicate { ty: Type::Generic(tp.name.clone()), - bounds: vec![sized_bound] + bounds: vec![TyParamBound::maybe_sized(cx)], }) } } @@ -1597,17 +1612,7 @@ impl<'tcx> Clean<Type> for ty::Ty<'tcx> { } ty::ty_tup(ref t) => Tuple(t.clean(cx)), - ty::ty_projection(ref data) => { - let trait_ref = match data.trait_ref.clean(cx) { - TyParamBound::TraitBound(t, _) => t.trait_, - TyParamBound::RegionBound(_) => panic!("cleaning a trait got a region??"), - }; - Type::QPath { - name: data.item_name.clean(cx), - self_type: box data.trait_ref.self_ty().clean(cx), - trait_: box trait_ref, - } - } + ty::ty_projection(ref data) => data.clean(cx), ty::ty_param(ref p) => Generic(token::get_name(p.name).to_string()), @@ -1881,6 +1886,22 @@ pub struct Path { pub segments: Vec<PathSegment>, } +impl Path { + pub fn singleton(name: String) -> Path { + Path { + global: false, + segments: vec![PathSegment { + name: name, + params: PathParameters::AngleBracketed { + lifetimes: Vec::new(), + types: Vec::new(), + bindings: Vec::new() + } + }] + } + } +} + impl Clean<Path> for ast::Path { fn clean(&self, cx: &DocContext) -> Path { Path { @@ -2516,21 +2537,66 @@ impl Clean<Stability> for attr::Stability { impl Clean<Item> for ty::AssociatedType { fn clean(&self, cx: &DocContext) -> Item { + // When loading a cross-crate associated type, the bounds for this type + // are actually located on the trait/impl itself, so we need to load + // all of the generics from there and then look for bounds that are + // applied to this associated type in question. + let predicates = ty::lookup_predicates(cx.tcx(), self.container.id()); + let generics = match self.container { + ty::TraitContainer(did) => { + let def = ty::lookup_trait_def(cx.tcx(), did); + (&def.generics, &predicates, subst::TypeSpace).clean(cx) + } + ty::ImplContainer(did) => { + let ty = ty::lookup_item_type(cx.tcx(), did); + (&ty.generics, &predicates, subst::TypeSpace).clean(cx) + } + }; + let my_name = self.name.clean(cx); + let mut bounds = generics.where_predicates.iter().filter_map(|pred| { + let (name, self_type, trait_, bounds) = match *pred { + WherePredicate::BoundPredicate { + ty: QPath { ref name, ref self_type, ref trait_ }, + ref bounds + } => (name, self_type, trait_, bounds), + _ => return None, + }; + if *name != my_name { return None } + match **trait_ { + ResolvedPath { did, .. } if did == self.container.id() => {} + _ => return None, + } + match **self_type { + Generic(ref s) if *s == "Self" => {} + _ => return None, + } + Some(bounds) + }).flat_map(|i| i.iter().cloned()).collect::<Vec<_>>(); + + // Our Sized/?Sized bound didn't get handled when creating the generics + // because we didn't actually get our whole set of bounds until just now + // (some of them may have come from the trait). If we do have a sized + // bound, we remove it, and if we don't then we add the `?Sized` bound + // at the end. + match bounds.iter().position(|b| b.is_sized_bound(cx)) { + Some(i) => { bounds.remove(i); } + None => bounds.push(TyParamBound::maybe_sized(cx)), + } + Item { source: DUMMY_SP.clean(cx), name: Some(self.name.clean(cx)), - attrs: Vec::new(), - // FIXME(#20727): bounds are missing and need to be filled in from the - // predicates on the trait itself - inner: AssociatedTypeItem(vec![], None), - visibility: None, + attrs: inline::load_attrs(cx, cx.tcx(), self.def_id), + inner: AssociatedTypeItem(bounds, None), + visibility: self.vis.clean(cx), def_id: self.def_id, - stability: None, + stability: stability::lookup(cx.tcx(), self.def_id).clean(cx), } } } -impl<'a> Clean<Typedef> for (ty::TypeScheme<'a>, ty::GenericPredicates<'a>, ParamSpace) { +impl<'a> Clean<Typedef> for (ty::TypeScheme<'a>, ty::GenericPredicates<'a>, + ParamSpace) { fn clean(&self, cx: &DocContext) -> Typedef { let (ref ty_scheme, ref predicates, ps) = *self; Typedef { diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 365e34476aa..ae9d9761001 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -502,6 +502,29 @@ impl fmt::Display for clean::Type { } Ok(()) } + // It's pretty unsightly to look at `<A as B>::C` in output, and + // we've got hyperlinking on our side, so try to avoid longer + // notation as much as possible by making `C` a hyperlink to trait + // `B` to disambiguate. + // + // FIXME: this is still a lossy conversion and there should probably + // be a better way of representing this in general? Most of + // the ugliness comes from inlining across crates where + // everything comes in as a fully resolved QPath (hard to + // look at). + clean::QPath { + ref name, + ref self_type, + trait_: box clean::ResolvedPath { did, ref typarams, .. }, + } => { + try!(write!(f, "{}::", self_type)); + let path = clean::Path::singleton(name.clone()); + try!(resolved_path(f, did, &path, false)); + + // FIXME: `typarams` are not rendered, and this seems bad? + drop(typarams); + Ok(()) + } clean::QPath { ref name, ref self_type, ref trait_ } => { write!(f, "<{} as {}>::{}", self_type, trait_, name) } @@ -636,17 +659,7 @@ impl fmt::Display for clean::ViewListIdent { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.source { Some(did) => { - let path = clean::Path { - global: false, - segments: vec!(clean::PathSegment { - name: self.name.clone(), - params: clean::PathParameters::AngleBracketed { - lifetimes: Vec::new(), - types: Vec::new(), - bindings: Vec::new() - } - }) - }; + let path = clean::Path::singleton(self.name.clone()); resolved_path(f, did, &path, false) } _ => write!(f, "{}", self.name), diff --git a/src/test/auxiliary/issue-20727.rs b/src/test/auxiliary/issue-20727.rs new file mode 100644 index 00000000000..aea8b429d9f --- /dev/null +++ b/src/test/auxiliary/issue-20727.rs @@ -0,0 +1,38 @@ +// Copyright 2015 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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +pub trait Deref { + type Target: ?Sized; + + fn deref<'a>(&'a self) -> &'a Self::Target; +} + +pub trait Add<RHS = Self> { + type Output; + + fn add(self, rhs: RHS) -> Self::Output; +} + + +pub trait Bar {} +pub trait Deref2 { + type Target: Bar; + + fn deref(&self) -> Self::Target; +} + +pub trait Index<Idx: ?Sized> { + type Output: ?Sized; + fn index(&self, index: Idx) -> &Self::Output; +} + +pub trait IndexMut<Idx: ?Sized>: Index<Idx> { + fn index_mut(&mut self, index: Idx) -> &mut Self::Output; +} diff --git a/src/test/rustdoc/issue-20727-2.rs b/src/test/rustdoc/issue-20727-2.rs new file mode 100644 index 00000000000..e0122d66de1 --- /dev/null +++ b/src/test/rustdoc/issue-20727-2.rs @@ -0,0 +1,32 @@ +// Copyright 2015 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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:issue-20727.rs + +extern crate issue_20727; + +// @has issue_20727_2/trait.Add.html +pub trait Add<RHS = Self> { + // @has - '//*[@class="rust trait"]' 'trait Add<RHS = Self> {' + // @has - '//*[@class="rust trait"]' 'type Output;' + type Output; + + // @has - '//*[@class="rust trait"]' 'fn add(self, rhs: RHS) -> Self::Output;' + fn add(self, rhs: RHS) -> Self::Output; +} + +// @has issue_20727_2/reexport/trait.Add.html +pub mod reexport { + // @has - '//*[@class="rust trait"]' 'trait Add<RHS = Self> {' + // @has - '//*[@class="rust trait"]' 'type Output;' + // @has - '//*[@class="rust trait"]' 'fn add(self, rhs: RHS) -> Self::Output;' + pub use issue_20727::Add; +} + diff --git a/src/test/rustdoc/issue-20727-3.rs b/src/test/rustdoc/issue-20727-3.rs new file mode 100644 index 00000000000..8338239a29d --- /dev/null +++ b/src/test/rustdoc/issue-20727-3.rs @@ -0,0 +1,33 @@ +// Copyright 2015 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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:issue-20727.rs + +extern crate issue_20727; + +pub trait Bar {} + +// @has issue_20727_3/trait.Deref2.html +pub trait Deref2 { + // @has - '//*[@class="rust trait"]' 'trait Deref2 {' + // @has - '//*[@class="rust trait"]' 'type Target: Bar;' + type Target: Bar; + + // @has - '//*[@class="rust trait"]' 'fn deref(&self) -> Self::Target;' + fn deref(&self) -> Self::Target; +} + +// @has issue_20727_3/reexport/trait.Deref2.html +pub mod reexport { + // @has - '//*[@class="rust trait"]' 'trait Deref2 {' + // @has - '//*[@class="rust trait"]' 'type Target: Bar;' + // @has - '//*[@class="rust trait"]' 'fn deref(&self) -> Self::Target;' + pub use issue_20727::Deref2; +} diff --git a/src/test/rustdoc/issue-20727-4.rs b/src/test/rustdoc/issue-20727-4.rs new file mode 100644 index 00000000000..ed361ed990a --- /dev/null +++ b/src/test/rustdoc/issue-20727-4.rs @@ -0,0 +1,49 @@ +// Copyright 2015 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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:issue-20727.rs + +extern crate issue_20727; + +// @has issue_20727_4/trait.Index.html +pub trait Index<Idx: ?Sized> { + // @has - '//*[@class="rust trait"]' 'trait Index<Idx: ?Sized> {' + // @has - '//*[@class="rust trait"]' 'type Output: ?Sized' + type Output: ?Sized; + + // @has - '//*[@class="rust trait"]' \ + // 'fn index(&self, index: Idx) -> &Self::Output' + fn index(&self, index: Idx) -> &Self::Output; +} + +// @has issue_20727_4/trait.IndexMut.html +pub trait IndexMut<Idx: ?Sized>: Index<Idx> { + // @has - '//*[@class="rust trait"]' \ + // 'trait IndexMut<Idx: ?Sized>: Index<Idx> {' + // @has - '//*[@class="rust trait"]' \ + // 'fn index_mut(&mut self, index: Idx) -> &mut Self::Output;' + fn index_mut(&mut self, index: Idx) -> &mut Self::Output; +} + +pub mod reexport { + // @has issue_20727_4/reexport/trait.Index.html + // @has - '//*[@class="rust trait"]' 'trait Index<Idx> where Idx: ?Sized {' + // @has - '//*[@class="rust trait"]' 'type Output: ?Sized' + // @has - '//*[@class="rust trait"]' \ + // 'fn index(&self, index: Idx) -> &Self::Output' + pub use issue_20727::Index; + + // @has issue_20727_4/reexport/trait.IndexMut.html + // @has - '//*[@class="rust trait"]' \ + // 'trait IndexMut<Idx>: Index<Idx> where Idx: ?Sized {' + // @has - '//*[@class="rust trait"]' \ + // 'fn index_mut(&mut self, index: Idx) -> &mut Self::Output;' + pub use issue_20727::IndexMut; +} diff --git a/src/test/rustdoc/issue-20727.rs b/src/test/rustdoc/issue-20727.rs new file mode 100644 index 00000000000..9903ad34b4c --- /dev/null +++ b/src/test/rustdoc/issue-20727.rs @@ -0,0 +1,33 @@ +// Copyright 2015 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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:issue-20727.rs + +extern crate issue_20727; + +// @has issue_20727/trait.Deref.html +pub trait Deref { + // @has - '//*[@class="rust trait"]' 'trait Deref {' + // @has - '//*[@class="rust trait"]' 'type Target: ?Sized;' + type Target: ?Sized; + + // @has - '//*[@class="rust trait"]' \ + // "fn deref<'a>(&'a self) -> &'a Self::Target;" + fn deref<'a>(&'a self) -> &'a Self::Target; +} + +// @has issue_20727/reexport/trait.Deref.html +pub mod reexport { + // @has - '//*[@class="rust trait"]' 'trait Deref {' + // @has - '//*[@class="rust trait"]' 'type Target: ?Sized;' + // @has - '//*[@class="rust trait"]' \ + // "fn deref(&'a self) -> &'a Self::Target;" + pub use issue_20727::Deref; +} |
