Skip to content

Commit 2f93a73

Browse files
committed
rustdoc: take a more conservative approach when eliding generic args
1 parent f2b66d0 commit 2f93a73

File tree

2 files changed

+42
-71
lines changed

2 files changed

+42
-71
lines changed

src/librustdoc/clean/utils.rs

Lines changed: 35 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,11 @@ use rustc_ast::tokenstream::TokenTree;
1414
use rustc_hir as hir;
1515
use rustc_hir::def::{DefKind, Res};
1616
use rustc_hir::def_id::{DefId, LocalDefId, LOCAL_CRATE};
17-
use rustc_infer::infer::at::ToTrace;
18-
use rustc_infer::infer::outlives::env::OutlivesEnvironment;
1917
use rustc_metadata::rendered_const;
2018
use rustc_middle::mir;
21-
use rustc_middle::traits::ObligationCause;
2219
use rustc_middle::ty::{self, GenericArgKind, GenericArgsRef, TyCtxt};
2320
use rustc_middle::ty::{TypeVisitable, TypeVisitableExt};
2421
use rustc_span::symbol::{kw, sym, Symbol};
25-
use rustc_trait_selection::infer::TyCtxtInferExt;
26-
use rustc_trait_selection::traits::ObligationCtxt;
2722
use std::fmt::Write as _;
2823
use std::mem;
2924
use std::sync::LazyLock as Lazy;
@@ -86,8 +81,6 @@ pub(crate) fn ty_args_to_args<'tcx>(
8681
has_self: bool,
8782
container: Option<DefId>,
8883
) -> Vec<GenericArg> {
89-
let param_env = ty::ParamEnv::empty();
90-
let cause = ObligationCause::dummy();
9184
let params = container.map(|container| &cx.tcx.generics_of(container).params);
9285
let mut elision_has_failed_once_before = false;
9386

@@ -107,14 +100,7 @@ pub(crate) fn ty_args_to_args<'tcx>(
107100
let default =
108101
ty_args.map_bound(|args| default.instantiate(cx.tcx, args).expect_ty());
109102

110-
if can_elide_generic_arg(
111-
cx.tcx,
112-
&cause,
113-
param_env,
114-
ty_args.rebind(ty),
115-
default,
116-
params[index].def_id,
117-
) {
103+
if can_elide_generic_arg(ty_args.rebind(ty), default) {
118104
return None;
119105
}
120106

@@ -150,14 +136,7 @@ pub(crate) fn ty_args_to_args<'tcx>(
150136
let default =
151137
ty_args.map_bound(|args| default.instantiate(cx.tcx, args).expect_const());
152138

153-
if can_elide_generic_arg(
154-
cx.tcx,
155-
&cause,
156-
param_env,
157-
ty_args.rebind(ct),
158-
default,
159-
params[index].def_id,
160-
) {
139+
if can_elide_generic_arg(ty_args.rebind(ct), default) {
161140
return None;
162141
}
163142

@@ -174,50 +153,44 @@ pub(crate) fn ty_args_to_args<'tcx>(
174153
}
175154

176155
/// Check if the generic argument `actual` coincides with the `default` and can therefore be elided.
177-
fn can_elide_generic_arg<'tcx, T: ToTrace<'tcx> + TypeVisitable<TyCtxt<'tcx>>>(
178-
tcx: TyCtxt<'tcx>,
179-
cause: &ObligationCause<'tcx>,
180-
param_env: ty::ParamEnv<'tcx>,
181-
actual: ty::Binder<'tcx, T>,
182-
default: ty::Binder<'tcx, T>,
183-
did: DefId,
184-
) -> bool {
185-
// The operations below are only correct if we don't have any inference variables.
186-
debug_assert!(!actual.has_infer());
187-
debug_assert!(!default.has_infer());
188-
189-
// Since we don't properly keep track of bound variables, don't attempt to make
190-
// any sense out of escaping bound variables (we just don't have enough context).
191-
if actual.has_escaping_bound_vars() || default.has_escaping_bound_vars() {
156+
///
157+
/// This uses a very conservative approach for performance and correctness reasons, meaning for
158+
/// several classes of terms it claims that they cannot be elided even if they theoretically could.
159+
/// This is absolutely fine since this concerns mostly edge cases.
160+
fn can_elide_generic_arg<'tcx, Term>(
161+
actual: ty::Binder<'tcx, Term>,
162+
default: ty::Binder<'tcx, Term>,
163+
) -> bool
164+
where
165+
Term: Eq + TypeVisitable<TyCtxt<'tcx>>,
166+
{
167+
// In practice, we shouldn't have any inference variables at this point. However to be safe, we
168+
// bail out if we do happen to stumble upon them. For performance reasons, we don't want to
169+
// construct an `InferCtxt` here to properly handle them.
170+
if actual.has_infer() || default.has_infer() {
192171
return false;
193172
}
194173

195-
// If the arguments contain projections or (non-escaping) late-bound regions, we have to examine
196-
// them more closely and can't take the fast path.
197-
// Having projections means that there's potential to be further normalized thereby revealing if
198-
// they are equal after all. Regarding late-bound regions, they can be liberated allowing us to
199-
// consider more types to be equal by ignoring the names of binders.
200-
if !actual.has_late_bound_regions()
201-
&& !actual.has_projections()
202-
&& !default.has_late_bound_regions()
203-
&& !default.has_projections()
204-
{
205-
// Check the memory addresses of the interned arguments for equality.
206-
return actual.skip_binder() == default.skip_binder();
174+
// Since we don't properly keep track of bound variables in rustdoc (yet), we don't attempt to
175+
// make any sense out of escaping bound variables. We simply don't have enough context and it
176+
// would be incorrect to try to do so anyway.
177+
if actual.has_escaping_bound_vars() || default.has_escaping_bound_vars() {
178+
return false;
207179
}
208180

209-
let actual = tcx.liberate_late_bound_regions(did, actual);
210-
let default = tcx.liberate_late_bound_regions(did, default);
211-
212-
let infcx = tcx.infer_ctxt().build();
213-
let ocx = ObligationCtxt::new(&infcx);
214-
215-
let actual = ocx.normalize(cause, param_env, actual);
216-
let default = ocx.normalize(cause, param_env, default);
217-
218-
ocx.eq(cause, param_env, actual, default).is_ok()
219-
&& ocx.select_all_or_error().is_empty()
220-
&& infcx.resolve_regions(&OutlivesEnvironment::new(param_env)).is_empty()
181+
// Theoretically we could now check if either term contains (non-escaping) late-bound regions or
182+
// projections, relate the two using an `InferCtxt` and check if the resulting obligations hold
183+
// since having projections means that the terms can potentially be further normalized thereby
184+
// revealing if they are equal after all. Regarding late-bound regions, they would need to be
185+
// liberated allowing us to consider more types to be equal by ignoring the names of binders
186+
// (e.g., `for<'a> ...` and `for<'b> ...`).
187+
//
188+
// However, we are mostly interested in eliding generic args that were originally elided by the
189+
// user and later filled in by the compiler (i.e., re-eliding) compared to eliding arbitrary
190+
// generic arguments if they happen to coincide with the default ignoring the fact we can't
191+
// possibly distinguish these two cases. Therefore and for performance reasons, we just xheck
192+
// the memory addresses of the interned arguments for equality.
193+
actual.skip_binder() == default.skip_binder()
221194
}
222195

223196
fn external_generic_args<'tcx>(

tests/rustdoc/inline_cross/default-generic-args.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,8 @@ pub use default_generic_args::C0;
3535
pub use default_generic_args::C1;
3636

3737
// @has user/type.C2.html
38-
// Test that we normalize constants in this case:
39-
// FIXME: Ideally, we would render `3` here instead of the def-path str of the normalized constant.
40-
// @has - '//*[@class="rust item-decl"]//code' "CtPair<default_generic_args::::C2::{constant#0}>"
38+
// FIXME: Add a comment here.
39+
// @has - '//*[@class="rust item-decl"]//code' "CtPair<default_generic_args::::C2::{constant#0}, 3>"
4140
pub use default_generic_args::C2;
4241

4342
// @has user/type.R0.html
@@ -55,27 +54,26 @@ pub use default_generic_args::R2;
5554

5655
// @has user/type.H0.html
5756
// Check that we handle higher-ranked regions correctly:
58-
// FIXME: Ideally we would also print the *binders* here.
59-
// @has - '//*[@class="rust item-decl"]//code' "fn(_: fn(_: Re<'a>))"
57+
// @has - '//*[@class="rust item-decl"]//code' "fn(_: for<'a> fn(_: Re<'a>))"
6058
pub use default_generic_args::H0;
6159

6260
// @has user/type.H1.html
6361
// Check that we don't conflate distinct universially quantified regions (#1):
64-
// FIXME: Ideally we would also print the *binders* here.
65-
// @has - '//*[@class="rust item-decl"]//code' "fn(_: fn(_: Re<'a, &'b ()>))"
62+
// @has - '//*[@class="rust item-decl"]//code' "for<'b> fn(_: for<'a> fn(_: Re<'a, &'b ()>))"
6663
pub use default_generic_args::H1;
6764

6865
// @has user/type.H2.html
6966
// Check that we don't conflate distinct universially quantified regions (#2):
70-
// @has - '//*[@class="rust item-decl"]//code' "fn(_: fn(_: Re<'a, &'b ()>))"
67+
// @has - '//*[@class="rust item-decl"]//code' "for<'a> fn(_: for<'b> fn(_: Re<'a, &'b ()>))"
7168
pub use default_generic_args::H2;
7269

7370
// @has user/type.P0.html
71+
// FIXME: Add comment here.
7472
// @has - '//*[@class="rust item-decl"]//code' "Proj<()>"
7573
pub use default_generic_args::P0;
7674

7775
// @has user/type.P1.html
78-
// @has - '//*[@class="rust item-decl"]//code' "Proj<()>"
76+
// @has - '//*[@class="rust item-decl"]//code' "Proj<(), bool>"
7977
pub use default_generic_args::P1;
8078

8179
// @has user/type.P2.html

0 commit comments

Comments
 (0)