Skip to content

Commit e10262c

Browse files
Implement refinement lint for RPITIT
1 parent b0d4553 commit e10262c

26 files changed

+483
-21
lines changed

compiler/rustc_errors/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ use rustc_fluent_macro::fluent_messages;
4444
pub use rustc_lint_defs::{pluralize, Applicability};
4545
use rustc_span::source_map::SourceMap;
4646
pub use rustc_span::ErrorGuaranteed;
47-
use rustc_span::{Loc, Span};
47+
use rustc_span::{Loc, Span, DUMMY_SP};
4848

4949
use std::borrow::Cow;
5050
use std::error::Report;
@@ -1754,7 +1754,7 @@ impl DelayedDiagnostic {
17541754
BacktraceStatus::Captured => {
17551755
let inner = &self.inner;
17561756
self.inner.subdiagnostic(DelayedAtWithNewline {
1757-
span: inner.span.primary_span().unwrap(),
1757+
span: inner.span.primary_span().unwrap_or(DUMMY_SP),
17581758
emitted_at: inner.emitted_at.clone(),
17591759
note: self.note,
17601760
});
@@ -1764,7 +1764,7 @@ impl DelayedDiagnostic {
17641764
_ => {
17651765
let inner = &self.inner;
17661766
self.inner.subdiagnostic(DelayedAtWithoutNewline {
1767-
span: inner.span.primary_span().unwrap(),
1767+
span: inner.span.primary_span().unwrap_or(DUMMY_SP),
17681768
emitted_at: inner.emitted_at.clone(),
17691769
note: self.note,
17701770
});

compiler/rustc_hir_analysis/messages.ftl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,11 @@ hir_analysis_return_type_notation_on_non_rpitit =
222222
.note = function returns `{$ty}`, which is not compatible with associated type return bounds
223223
.label = this function must be `async` or return `impl Trait`
224224
225+
hir_analysis_rpitit_refined = impl trait in impl method signature does not match trait method signature
226+
.suggestion = replace the return type so that it matches the trait
227+
.label = return type from trait method defined here
228+
.unmatched_bound_label = this bound is stronger than that defined on the trait
229+
225230
hir_analysis_self_in_impl_self =
226231
`Self` is not valid in the self type of an impl block
227232
.note = replace `Self` with a different type

compiler/rustc_hir_analysis/src/check/compare_impl_item.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ use rustc_trait_selection::traits::{
2828
use std::borrow::Cow;
2929
use std::iter;
3030

31+
mod refine;
32+
3133
/// Checks that a method from an impl conforms to the signature of
3234
/// the same method as declared in the trait.
3335
///
@@ -53,6 +55,12 @@ pub(super) fn compare_impl_method<'tcx>(
5355
impl_trait_ref,
5456
CheckImpliedWfMode::Check,
5557
)?;
58+
refine::check_refining_return_position_impl_trait_in_trait(
59+
tcx,
60+
impl_m,
61+
trait_m,
62+
impl_trait_ref,
63+
);
5664
};
5765
}
5866

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
use rustc_data_structures::fx::FxIndexSet;
2+
use rustc_hir as hir;
3+
use rustc_hir::def_id::DefId;
4+
use rustc_infer::infer::{outlives::env::OutlivesEnvironment, TyCtxtInferExt};
5+
use rustc_lint_defs::builtin::REFINING_IMPL_TRAIT;
6+
use rustc_middle::traits::{ObligationCause, Reveal};
7+
use rustc_middle::ty::{
8+
self, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitor,
9+
};
10+
use rustc_span::{Span, DUMMY_SP};
11+
use rustc_trait_selection::traits::{
12+
elaborate, normalize_param_env_or_error, outlives_bounds::InferCtxtExt, ObligationCtxt,
13+
};
14+
use std::ops::ControlFlow;
15+
16+
/// Check that an implementation does not refine an RPITIT from a trait method signature.
17+
pub(super) fn check_refining_return_position_impl_trait_in_trait<'tcx>(
18+
tcx: TyCtxt<'tcx>,
19+
impl_m: ty::AssocItem,
20+
trait_m: ty::AssocItem,
21+
impl_trait_ref: ty::TraitRef<'tcx>,
22+
) {
23+
if !tcx.impl_method_has_trait_impl_trait_tys(impl_m.def_id) {
24+
return;
25+
}
26+
27+
let impl_def_id = impl_m.container_id(tcx);
28+
let impl_m_args = ty::GenericArgs::identity_for_item(tcx, impl_m.def_id);
29+
let trait_m_to_impl_m_args = impl_m_args.rebase_onto(tcx, impl_def_id, impl_trait_ref.args);
30+
let bound_trait_m_sig = tcx.fn_sig(trait_m.def_id).instantiate(tcx, trait_m_to_impl_m_args);
31+
let trait_m_sig = tcx.liberate_late_bound_regions(impl_m.def_id, bound_trait_m_sig);
32+
33+
let Ok(hidden_tys) = tcx.collect_return_position_impl_trait_in_trait_tys(impl_m.def_id) else {
34+
// Error already emitted, no need to delay another.
35+
return;
36+
};
37+
38+
let mut collector = ImplTraitInTraitCollector { tcx, types: FxIndexSet::default() };
39+
trait_m_sig.visit_with(&mut collector);
40+
41+
// Bound that we find on RPITITs in the trait signature.
42+
let mut trait_bounds = vec![];
43+
// Bounds that we find on the RPITITs in the impl signature.
44+
let mut impl_bounds = vec![];
45+
46+
for trait_projection in collector.types.into_iter().rev() {
47+
let impl_opaque_args = trait_projection.args.rebase_onto(tcx, trait_m.def_id, impl_m_args);
48+
let hidden_ty = hidden_tys[&trait_projection.def_id].instantiate(tcx, impl_opaque_args);
49+
50+
// If the hidden type is not an opaque, then we have "refined" the trait signature.
51+
let ty::Alias(ty::Opaque, impl_opaque) = *hidden_ty.kind() else {
52+
report_mismatched_rpitit_signature(
53+
tcx,
54+
trait_m_sig,
55+
trait_m.def_id,
56+
impl_m.def_id,
57+
None,
58+
);
59+
return;
60+
};
61+
62+
// This opaque also needs to be from the impl method -- otherwise,
63+
// it's a refinement to a TAIT.
64+
if !tcx.hir().get_if_local(impl_opaque.def_id).map_or(false, |node| {
65+
matches!(
66+
node.expect_item().expect_opaque_ty().origin,
67+
hir::OpaqueTyOrigin::AsyncFn(def_id) | hir::OpaqueTyOrigin::FnReturn(def_id)
68+
if def_id == impl_m.def_id.expect_local()
69+
)
70+
}) {
71+
report_mismatched_rpitit_signature(
72+
tcx,
73+
trait_m_sig,
74+
trait_m.def_id,
75+
impl_m.def_id,
76+
None,
77+
);
78+
return;
79+
}
80+
81+
trait_bounds.extend(
82+
tcx.item_bounds(trait_projection.def_id).iter_instantiated(tcx, trait_projection.args),
83+
);
84+
impl_bounds.extend(elaborate(
85+
tcx,
86+
tcx.explicit_item_bounds(impl_opaque.def_id)
87+
.iter_instantiated_copied(tcx, impl_opaque.args),
88+
));
89+
}
90+
91+
let hybrid_preds = tcx
92+
.predicates_of(impl_def_id)
93+
.instantiate_identity(tcx)
94+
.into_iter()
95+
.chain(tcx.predicates_of(trait_m.def_id).instantiate_own(tcx, trait_m_to_impl_m_args))
96+
.map(|(clause, _)| clause);
97+
let param_env = ty::ParamEnv::new(tcx.mk_clauses_from_iter(hybrid_preds), Reveal::UserFacing);
98+
let param_env = normalize_param_env_or_error(tcx, param_env, ObligationCause::dummy());
99+
100+
let ref infcx = tcx.infer_ctxt().build();
101+
let ocx = ObligationCtxt::new(infcx);
102+
103+
// Normalize the bounds. This has two purposes:
104+
//
105+
// 1. Project the RPITIT projections from the trait to the opaques on the impl,
106+
// which means that they don't need to be mapped manually.
107+
//
108+
// 2. Project any other projections that show up in the bound. That makes sure that
109+
// we don't consider `tests/ui/async-await/in-trait/async-associated-types.rs`
110+
// to be refining.
111+
let (trait_bounds, impl_bounds) =
112+
ocx.normalize(&ObligationCause::dummy(), param_env, (trait_bounds, impl_bounds));
113+
114+
// Since we've normalized things, we need to resolve regions, since we'll
115+
// possibly have introduced region vars during projection. We don't expect
116+
// this resolution to have incurred any region errors -- but if we do, then
117+
// just delay a bug.
118+
let mut implied_wf_types = FxIndexSet::default();
119+
implied_wf_types.extend(trait_m_sig.inputs_and_output);
120+
implied_wf_types.extend(ocx.normalize(
121+
&ObligationCause::dummy(),
122+
param_env,
123+
trait_m_sig.inputs_and_output,
124+
));
125+
if !ocx.select_all_or_error().is_empty() {
126+
tcx.sess.delay_span_bug(
127+
DUMMY_SP,
128+
"encountered errors when checking RPITIT refinement (selection)",
129+
);
130+
return;
131+
}
132+
let outlives_env = OutlivesEnvironment::with_bounds(
133+
param_env,
134+
infcx.implied_bounds_tys(param_env, impl_m.def_id.expect_local(), implied_wf_types),
135+
);
136+
let errors = infcx.resolve_regions(&outlives_env);
137+
if !errors.is_empty() {
138+
tcx.sess.delay_span_bug(
139+
DUMMY_SP,
140+
"encountered errors when checking RPITIT refinement (regions)",
141+
);
142+
return;
143+
}
144+
// Resolve any lifetime variables that may have been introduced during normalization.
145+
let Ok((trait_bounds, impl_bounds)) = infcx.fully_resolve((trait_bounds, impl_bounds)) else {
146+
tcx.sess.delay_span_bug(
147+
DUMMY_SP,
148+
"encountered errors when checking RPITIT refinement (resolution)",
149+
);
150+
return;
151+
};
152+
153+
// For quicker lookup, use an `IndexSet`
154+
// (we don't use one earlier because it's not foldable..)
155+
let trait_bounds = FxIndexSet::from_iter(trait_bounds);
156+
157+
// Find any clauses that are present in the impl's RPITITs that are not
158+
// present in the trait's RPITITs. This will trigger on trivial predicates,
159+
// too, since we *do not* use the trait solver to prove that the RPITIT's
160+
// bounds are not stronger -- we're doing a simple, syntactic compatibility
161+
// check between bounds. This is strictly forwards compatible, though.
162+
for (clause, span) in impl_bounds {
163+
if !trait_bounds.contains(&clause) {
164+
report_mismatched_rpitit_signature(
165+
tcx,
166+
trait_m_sig,
167+
trait_m.def_id,
168+
impl_m.def_id,
169+
Some(span),
170+
);
171+
return;
172+
}
173+
}
174+
}
175+
176+
struct ImplTraitInTraitCollector<'tcx> {
177+
tcx: TyCtxt<'tcx>,
178+
types: FxIndexSet<ty::AliasTy<'tcx>>,
179+
}
180+
181+
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for ImplTraitInTraitCollector<'tcx> {
182+
type BreakTy = !;
183+
184+
fn visit_ty(&mut self, ty: Ty<'tcx>) -> std::ops::ControlFlow<Self::BreakTy> {
185+
if let ty::Alias(ty::Projection, proj) = *ty.kind()
186+
&& self.tcx.is_impl_trait_in_trait(proj.def_id)
187+
{
188+
if self.types.insert(proj) {
189+
for (pred, _) in self
190+
.tcx
191+
.explicit_item_bounds(proj.def_id)
192+
.iter_instantiated_copied(self.tcx, proj.args)
193+
{
194+
pred.visit_with(self)?;
195+
}
196+
}
197+
ControlFlow::Continue(())
198+
} else {
199+
ty.super_visit_with(self)
200+
}
201+
}
202+
}
203+
204+
fn report_mismatched_rpitit_signature<'tcx>(
205+
tcx: TyCtxt<'tcx>,
206+
trait_m_sig: ty::FnSig<'tcx>,
207+
trait_m_def_id: DefId,
208+
impl_m_def_id: DefId,
209+
unmatched_bound: Option<Span>,
210+
) {
211+
let mapping = std::iter::zip(
212+
tcx.fn_sig(trait_m_def_id).skip_binder().bound_vars(),
213+
tcx.fn_sig(impl_m_def_id).skip_binder().bound_vars(),
214+
)
215+
.filter_map(|(impl_bv, trait_bv)| {
216+
if let ty::BoundVariableKind::Region(impl_bv) = impl_bv
217+
&& let ty::BoundVariableKind::Region(trait_bv) = trait_bv
218+
{
219+
Some((impl_bv, trait_bv))
220+
} else {
221+
None
222+
}
223+
})
224+
.collect();
225+
226+
let mut return_ty =
227+
trait_m_sig.output().fold_with(&mut super::RemapLateBound { tcx, mapping: &mapping });
228+
229+
if tcx.asyncness(impl_m_def_id).is_async() && tcx.asyncness(trait_m_def_id).is_async() {
230+
let ty::Alias(ty::Projection, future_ty) = return_ty.kind() else {
231+
bug!();
232+
};
233+
let Some(future_output_ty) = tcx
234+
.explicit_item_bounds(future_ty.def_id)
235+
.iter_instantiated_copied(tcx, future_ty.args)
236+
.find_map(|(clause, _)| match clause.kind().no_bound_vars()? {
237+
ty::ClauseKind::Projection(proj) => proj.term.ty(),
238+
_ => None,
239+
})
240+
else {
241+
bug!()
242+
};
243+
return_ty = future_output_ty;
244+
}
245+
246+
let (span, impl_return_span, pre, post) =
247+
match tcx.hir().get_by_def_id(impl_m_def_id.expect_local()).fn_decl().unwrap().output {
248+
hir::FnRetTy::DefaultReturn(span) => (tcx.def_span(impl_m_def_id), span, "-> ", " "),
249+
hir::FnRetTy::Return(ty) => (ty.span, ty.span, "", ""),
250+
};
251+
let trait_return_span =
252+
tcx.hir().get_if_local(trait_m_def_id).map(|node| match node.fn_decl().unwrap().output {
253+
hir::FnRetTy::DefaultReturn(_) => tcx.def_span(trait_m_def_id),
254+
hir::FnRetTy::Return(ty) => ty.span,
255+
});
256+
257+
let span = unmatched_bound.unwrap_or(span);
258+
tcx.emit_spanned_lint(
259+
REFINING_IMPL_TRAIT,
260+
tcx.local_def_id_to_hir_id(impl_m_def_id.expect_local()),
261+
span,
262+
crate::errors::ReturnPositionImplTraitInTraitRefined {
263+
impl_return_span,
264+
trait_return_span,
265+
pre,
266+
post,
267+
return_ty,
268+
unmatched_bound,
269+
},
270+
);
271+
}

compiler/rustc_hir_analysis/src/errors.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,21 @@ pub struct UnusedAssociatedTypeBounds {
919919
pub span: Span,
920920
}
921921

922+
#[derive(LintDiagnostic)]
923+
#[diag(hir_analysis_rpitit_refined)]
924+
pub(crate) struct ReturnPositionImplTraitInTraitRefined<'tcx> {
925+
#[suggestion(applicability = "maybe-incorrect", code = "{pre}{return_ty}{post}")]
926+
pub impl_return_span: Span,
927+
#[label]
928+
pub trait_return_span: Option<Span>,
929+
#[label(hir_analysis_unmatched_bound_label)]
930+
pub unmatched_bound: Option<Span>,
931+
932+
pub pre: &'static str,
933+
pub post: &'static str,
934+
pub return_ty: Ty<'tcx>,
935+
}
936+
922937
#[derive(Diagnostic)]
923938
#[diag(hir_analysis_assoc_bound_on_const)]
924939
#[note]

0 commit comments

Comments
 (0)