Skip to content

Commit 31c5cff

Browse files
committed
rustdoc: take a more conservative approach when eliding generic args
1 parent f360c7c commit 31c5cff

File tree

2 files changed

+49
-67
lines changed

2 files changed

+49
-67
lines changed

Diff for: src/librustdoc/clean/utils.rs

+41-59
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

@@ -141,14 +127,7 @@ pub(crate) fn ty_args_to_args<'tcx>(
141127
let default =
142128
ty_args.map_bound(|args| default.instantiate(cx.tcx, args).expect_const());
143129

144-
if can_elide_generic_arg(
145-
cx.tcx,
146-
&cause,
147-
param_env,
148-
ty_args.rebind(ct),
149-
default,
150-
params[index].def_id,
151-
) {
130+
if can_elide_generic_arg(ty_args.rebind(ct), default) {
152131
return None;
153132
}
154133

@@ -165,50 +144,53 @@ pub(crate) fn ty_args_to_args<'tcx>(
165144
}
166145

167146
/// Check if the generic argument `actual` coincides with the `default` and can therefore be elided.
168-
fn can_elide_generic_arg<'tcx, T: ToTrace<'tcx> + TypeVisitable<TyCtxt<'tcx>>>(
169-
tcx: TyCtxt<'tcx>,
170-
cause: &ObligationCause<'tcx>,
171-
param_env: ty::ParamEnv<'tcx>,
172-
actual: ty::Binder<'tcx, T>,
173-
default: ty::Binder<'tcx, T>,
174-
did: DefId,
175-
) -> bool {
176-
// The operations below are only correct if we don't have any inference variables.
177-
debug_assert!(!actual.has_infer());
178-
debug_assert!(!default.has_infer());
147+
///
148+
/// This uses a very conservative approach for performance and correctness reasons, meaning for
149+
/// several classes of terms it claims that they cannot be elided even if they theoretically could.
150+
/// This is absolutely fine since this concerns mostly edge cases.
151+
fn can_elide_generic_arg<'tcx, Term>(
152+
actual: ty::Binder<'tcx, Term>,
153+
default: ty::Binder<'tcx, Term>,
154+
) -> bool
155+
where
156+
Term: Eq + TypeVisitable<TyCtxt<'tcx>>,
157+
{
158+
// In practice, we shouldn't have any inference variables at this point. However to be safe, we
159+
// bail out if we do happen to stumble upon them. For performance reasons, we don't want to
160+
// construct an `InferCtxt` here to properly handle them.
161+
if actual.has_infer() || default.has_infer() {
162+
return false;
163+
}
179164

180-
// Since we don't properly keep track of bound variables, don't attempt to make
181-
// any sense out of escaping bound variables (we just don't have enough context).
165+
// Since we don't properly keep track of bound variables in rustdoc (yet), we don't attempt to
166+
// make any sense out of escaping bound variables. We simply don't have enough context and it
167+
// would be incorrect to try to do so anyway.
182168
if actual.has_escaping_bound_vars() || default.has_escaping_bound_vars() {
183169
return false;
184170
}
185171

186-
// If the arguments contain projections or (non-escaping) late-bound regions, we have to examine
187-
// them more closely and can't take the fast path.
188-
// Having projections means that there's potential to be further normalized thereby revealing if
189-
// they are equal after all. Regarding late-bound regions, they can be liberated allowing us to
190-
// consider more types to be equal by ignoring the names of binders.
191-
if !actual.has_late_bound_regions()
192-
&& !actual.has_projections()
193-
&& !default.has_late_bound_regions()
194-
&& !default.has_projections()
172+
// Theoretically we could now check if either term contains (non-escaping) late-bound regions or
173+
// projections, relate the two using an `InferCtxt` and check if the resulting obligations hold
174+
// since having projections means that the terms can potentially be further normalized thereby
175+
// revealing if they are equal after all. Regarding late-bound regions, they would need to be
176+
// liberated allowing us to consider more types to be equal by ignoring the names of binders
177+
// (e.g., `for<'a> ...` and `for<'b> ...`).
178+
//
179+
// However, we are mostly interested in eliding generic args that were originally elided by the
180+
// user and later filled in by the compiler (i.e., re-eliding) compared to eliding arbitrary
181+
// generic arguments if they happen to coincide with the default ignoring the fact we can't
182+
// possibly distinguish these two cases. Therefore and for performance reasons, we just bail out
183+
// instead.
184+
if actual.has_late_bound_regions()
185+
|| actual.has_projections()
186+
|| default.has_late_bound_regions()
187+
|| default.has_projections()
195188
{
196-
// Check the memory addresses of the interned arguments for equality.
197-
return actual.skip_binder() == default.skip_binder();
189+
return false;
198190
}
199191

200-
let actual = tcx.liberate_late_bound_regions(did, actual);
201-
let default = tcx.liberate_late_bound_regions(did, default);
202-
203-
let infcx = tcx.infer_ctxt().build();
204-
let ocx = ObligationCtxt::new(&infcx);
205-
206-
let actual = ocx.normalize(cause, param_env, actual);
207-
let default = ocx.normalize(cause, param_env, default);
208-
209-
ocx.eq(cause, param_env, actual, default).is_ok()
210-
&& ocx.select_all_or_error().is_empty()
211-
&& infcx.resolve_regions(&OutlivesEnvironment::new(param_env)).is_empty()
192+
// Check the memory addresses of the interned arguments for equality.
193+
actual.skip_binder() == default.skip_binder()
212194
}
213195

214196
fn external_generic_args<'tcx>(

Diff for: tests/rustdoc/inline_cross/default-generic-args.rs

+8-8
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
@@ -54,13 +53,13 @@ pub use default_generic_args::R1;
5453
pub use default_generic_args::R2;
5554

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

6261
// @has user/type.H1.html
63-
// Check that we don't conflate distinct universially quantified regions (#1):
62+
// FIXME: Update this: Check that we don't conflate distinct universially quantified regions (#1):
6463
// FIXME: Ideally we would also print the *binders* here.
6564
// @has - '//*[@class="rust item-decl"]//code' "fn(_: fn(_: Re<'a, &'b ()>))"
6665
pub use default_generic_args::H1;
@@ -71,11 +70,12 @@ pub use default_generic_args::H1;
7170
pub use default_generic_args::H2;
7271

7372
// @has user/type.P0.html
74-
// @has - '//*[@class="rust item-decl"]//code' "Proj<()>"
73+
// FIXME: Add comment here.
74+
// @has - '//*[@class="rust item-decl"]//code' "Proj<(), <() as Basis>::Assoc>"
7575
pub use default_generic_args::P0;
7676

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

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

0 commit comments

Comments
 (0)