Skip to content

Commit 16025ae

Browse files
committed
Auto merge of rust-lang#135057 - compiler-errors:project-unconstrained, r=<try>
Project to `TyKind::Error` when there are unconstrained non-lifetime (ty/const) impl params It splits the `enforce_impl_params_are_constrained` function into lifetime/non-lifetime, and queryfies the latter. We can then use the result of the latter query (`Result<(), ErrorGuaranteed>`) to intercept projection and constrain the projected type to `TyKind::Error`, which ensures that we leak no ty or const vars to places that don't expect them, like `normalize_erasing_regions`. The reason we split `enforce_impl_params_are_constrained` into two parts is because we only error for *lifetimes* if the lifetime ends up showing up in any of the associated types of the impl (e.g. we allow `impl<'a> Foo { type Assoc = (); }`). However, in order to compute the `type_of` query for the anonymous associated type of an RPITIT, we need to do trait solving (in `query collect_return_position_impl_trait_in_trait_tys`). That would induce cycles. Luckily, it turns out for lifetimes we don't even care about if they're unconstrained, since they're erased in all contexts that we are trying to fix ICEs. So it's sufficient to keep this check separated out of the query. I think this is a bit less invasive of an approach compared to rust-lang#127973. The major difference between this PR and that PR is that we queryify the check instead of merging it into the `explicit_predicates_of` query, and we use the result to taint just projection goals, rather than trait goals too. This doesn't require a lot of new tracking in `ItemCtxt` and `GenericPredicates`, and it also seems to not require any other changes to typeck like that PR did. Fixes rust-lang#123141 Fixes rust-lang#125874 Fixes rust-lang#126942 Fixes rust-lang#127804 Fixes rust-lang#130967 r? oli-obk
2 parents 319f529 + 7ba5e3b commit 16025ae

32 files changed

+270
-304
lines changed

compiler/rustc_hir_analysis/src/impl_wf_check.rs

+92-31
Original file line numberDiff line numberDiff line change
@@ -57,22 +57,24 @@ pub(crate) fn check_impl_wf(
5757
tcx: TyCtxt<'_>,
5858
impl_def_id: LocalDefId,
5959
) -> Result<(), ErrorGuaranteed> {
60-
let min_specialization = tcx.features().min_specialization();
61-
let mut res = Ok(());
6260
debug_assert_matches!(tcx.def_kind(impl_def_id), DefKind::Impl { .. });
63-
res = res.and(enforce_impl_params_are_constrained(tcx, impl_def_id));
64-
if min_specialization {
61+
62+
// Check that the args are constrained. We queryfied the check for ty/const params
63+
// since unconstrained type/const params cause ICEs in projection, so we want to
64+
// detect those specifically and project those to `TyKind::Error`.
65+
let mut res = tcx.ensure().enforce_impl_non_lifetime_params_are_constrained(impl_def_id);
66+
res = res.and(enforce_impl_lifetime_params_are_constrained(tcx, impl_def_id));
67+
68+
if tcx.features().min_specialization() {
6569
res = res.and(check_min_specialization(tcx, impl_def_id));
6670
}
67-
6871
res
6972
}
7073

71-
fn enforce_impl_params_are_constrained(
74+
pub(crate) fn enforce_impl_lifetime_params_are_constrained(
7275
tcx: TyCtxt<'_>,
7376
impl_def_id: LocalDefId,
7477
) -> Result<(), ErrorGuaranteed> {
75-
// Every lifetime used in an associated type must be constrained.
7678
let impl_self_ty = tcx.type_of(impl_def_id).instantiate_identity();
7779
if impl_self_ty.references_error() {
7880
// Don't complain about unconstrained type params when self ty isn't known due to errors.
@@ -88,6 +90,7 @@ fn enforce_impl_params_are_constrained(
8890
// Compilation must continue in order for other important diagnostics to keep showing up.
8991
return Ok(());
9092
}
93+
9194
let impl_generics = tcx.generics_of(impl_def_id);
9295
let impl_predicates = tcx.predicates_of(impl_def_id);
9396
let impl_trait_ref = tcx.impl_trait_ref(impl_def_id).map(ty::EarlyBinder::instantiate_identity);
@@ -121,6 +124,84 @@ fn enforce_impl_params_are_constrained(
121124
})
122125
.collect();
123126

127+
let mut res = Ok(());
128+
for param in &impl_generics.own_params {
129+
match param.kind {
130+
ty::GenericParamDefKind::Lifetime => {
131+
let param_lt = cgp::Parameter::from(param.to_early_bound_region_data());
132+
if lifetimes_in_associated_types.contains(&param_lt) // (*)
133+
&& !input_parameters.contains(&param_lt)
134+
{
135+
let mut diag = tcx.dcx().create_err(UnconstrainedGenericParameter {
136+
span: tcx.def_span(param.def_id),
137+
param_name: param.name,
138+
param_def_kind: tcx.def_descr(param.def_id),
139+
const_param_note: false,
140+
const_param_note2: false,
141+
});
142+
diag.code(E0207);
143+
res = Err(diag.emit());
144+
}
145+
// (*) This is a horrible concession to reality. I think it'd be
146+
// better to just ban unconstrained lifetimes outright, but in
147+
// practice people do non-hygienic macros like:
148+
//
149+
// ```
150+
// macro_rules! __impl_slice_eq1 {
151+
// ($Lhs: ty, $Rhs: ty, $Bound: ident) => {
152+
// impl<'a, 'b, A: $Bound, B> PartialEq<$Rhs> for $Lhs where A: PartialEq<B> {
153+
// ....
154+
// }
155+
// }
156+
// }
157+
// ```
158+
//
159+
// In a concession to backwards compatibility, we continue to
160+
// permit those, so long as the lifetimes aren't used in
161+
// associated types. I believe this is sound, because lifetimes
162+
// used elsewhere are not projected back out.
163+
}
164+
ty::GenericParamDefKind::Type { .. } | ty::GenericParamDefKind::Const { .. } => {
165+
// Enforced in `enforce_impl_non_lifetime_params_are_constrained`.
166+
}
167+
}
168+
}
169+
res
170+
}
171+
172+
pub(crate) fn enforce_impl_non_lifetime_params_are_constrained(
173+
tcx: TyCtxt<'_>,
174+
impl_def_id: LocalDefId,
175+
) -> Result<(), ErrorGuaranteed> {
176+
let impl_self_ty = tcx.type_of(impl_def_id).instantiate_identity();
177+
if impl_self_ty.references_error() {
178+
// Don't complain about unconstrained type params when self ty isn't known due to errors.
179+
// (#36836)
180+
tcx.dcx().span_delayed_bug(
181+
tcx.def_span(impl_def_id),
182+
format!(
183+
"potentially unconstrained type parameters weren't evaluated: {impl_self_ty:?}",
184+
),
185+
);
186+
// This is super fishy, but our current `rustc_hir_analysis::check_crate` pipeline depends on
187+
// `type_of` having been called much earlier, and thus this value being read from cache.
188+
// Compilation must continue in order for other important diagnostics to keep showing up.
189+
return Ok(());
190+
}
191+
let impl_generics = tcx.generics_of(impl_def_id);
192+
let impl_predicates = tcx.predicates_of(impl_def_id);
193+
let impl_trait_ref = tcx.impl_trait_ref(impl_def_id).map(ty::EarlyBinder::instantiate_identity);
194+
195+
impl_trait_ref.error_reported()?;
196+
197+
let mut input_parameters = cgp::parameters_for_impl(tcx, impl_self_ty, impl_trait_ref);
198+
cgp::identify_constrained_generic_params(
199+
tcx,
200+
impl_predicates,
201+
impl_trait_ref,
202+
&mut input_parameters,
203+
);
204+
124205
let mut res = Ok(());
125206
for param in &impl_generics.own_params {
126207
let err = match param.kind {
@@ -129,15 +210,14 @@ fn enforce_impl_params_are_constrained(
129210
let param_ty = ty::ParamTy::for_def(param);
130211
!input_parameters.contains(&cgp::Parameter::from(param_ty))
131212
}
132-
ty::GenericParamDefKind::Lifetime => {
133-
let param_lt = cgp::Parameter::from(param.to_early_bound_region_data());
134-
lifetimes_in_associated_types.contains(&param_lt) && // (*)
135-
!input_parameters.contains(&param_lt)
136-
}
137213
ty::GenericParamDefKind::Const { .. } => {
138214
let param_ct = ty::ParamConst::for_def(param);
139215
!input_parameters.contains(&cgp::Parameter::from(param_ct))
140216
}
217+
ty::GenericParamDefKind::Lifetime => {
218+
// Enforced in `enforce_impl_type_params_are_constrained`.
219+
false
220+
}
141221
};
142222
if err {
143223
let const_param_note = matches!(param.kind, ty::GenericParamDefKind::Const { .. });
@@ -153,23 +233,4 @@ fn enforce_impl_params_are_constrained(
153233
}
154234
}
155235
res
156-
157-
// (*) This is a horrible concession to reality. I think it'd be
158-
// better to just ban unconstrained lifetimes outright, but in
159-
// practice people do non-hygienic macros like:
160-
//
161-
// ```
162-
// macro_rules! __impl_slice_eq1 {
163-
// ($Lhs: ty, $Rhs: ty, $Bound: ident) => {
164-
// impl<'a, 'b, A: $Bound, B> PartialEq<$Rhs> for $Lhs where A: PartialEq<B> {
165-
// ....
166-
// }
167-
// }
168-
// }
169-
// ```
170-
//
171-
// In a concession to backwards compatibility, we continue to
172-
// permit those, so long as the lifetimes aren't used in
173-
// associated types. I believe this is sound, because lifetimes
174-
// used elsewhere are not projected back out.
175236
}

compiler/rustc_hir_analysis/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ pub fn provide(providers: &mut Providers) {
128128
hir_wf_check::provide(providers);
129129
*providers = Providers {
130130
inherit_sig_for_delegation_item: delegation::inherit_sig_for_delegation_item,
131+
enforce_impl_non_lifetime_params_are_constrained:
132+
impl_wf_check::enforce_impl_non_lifetime_params_are_constrained,
131133
..*providers
132134
};
133135
}

compiler/rustc_middle/src/query/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -1719,6 +1719,12 @@ rustc_queries! {
17191719
ensure_forwards_result_if_red
17201720
}
17211721

1722+
query enforce_impl_non_lifetime_params_are_constrained(key: LocalDefId) -> Result<(), ErrorGuaranteed> {
1723+
desc { |tcx| "checking that `{}`'s generics are constrained by the impl header", tcx.def_path_str(key) }
1724+
cache_on_disk_if { true }
1725+
ensure_forwards_result_if_red
1726+
}
1727+
17221728
// The `DefId`s of all non-generic functions and statics in the given crate
17231729
// that can be reached from outside the crate.
17241730
//

compiler/rustc_next_trait_solver/src/delegate.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,10 @@ pub trait SolverDelegate: Deref<Target = <Self as SolverDelegate>::Infcx> + Size
9595
goal_trait_ref: ty::TraitRef<Self::Interner>,
9696
trait_assoc_def_id: <Self::Interner as Interner>::DefId,
9797
impl_def_id: <Self::Interner as Interner>::DefId,
98-
) -> Result<Option<<Self::Interner as Interner>::DefId>, NoSolution>;
98+
) -> Result<
99+
Option<<Self::Interner as Interner>::DefId>,
100+
<Self::Interner as Interner>::ErrorGuaranteed,
101+
>;
99102

100103
fn is_transmutable(
101104
&self,

compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -950,7 +950,7 @@ where
950950
goal_trait_ref: ty::TraitRef<I>,
951951
trait_assoc_def_id: I::DefId,
952952
impl_def_id: I::DefId,
953-
) -> Result<Option<I::DefId>, NoSolution> {
953+
) -> Result<Option<I::DefId>, I::ErrorGuaranteed> {
954954
self.delegate.fetch_eligible_assoc_item(goal_trait_ref, trait_assoc_def_id, impl_def_id)
955955
}
956956

compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs

+22-16
Original file line numberDiff line numberDiff line change
@@ -244,20 +244,7 @@ where
244244
.map(|pred| goal.with(cx, pred)),
245245
);
246246

247-
// In case the associated item is hidden due to specialization, we have to
248-
// return ambiguity this would otherwise be incomplete, resulting in
249-
// unsoundness during coherence (#105782).
250-
let Some(target_item_def_id) = ecx.fetch_eligible_assoc_item(
251-
goal_trait_ref,
252-
goal.predicate.def_id(),
253-
impl_def_id,
254-
)?
255-
else {
256-
return ecx.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS);
257-
};
258-
259-
let error_response = |ecx: &mut EvalCtxt<'_, D>, msg: &str| {
260-
let guar = cx.delay_bug(msg);
247+
let error_response = |ecx: &mut EvalCtxt<'_, D>, guar| {
261248
let error_term = match goal.predicate.alias.kind(cx) {
262249
ty::AliasTermKind::ProjectionTy => Ty::new_error(cx, guar).into(),
263250
ty::AliasTermKind::ProjectionConst => Const::new_error(cx, guar).into(),
@@ -267,8 +254,24 @@ where
267254
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
268255
};
269256

257+
// In case the associated item is hidden due to specialization, we have to
258+
// return ambiguity this would otherwise be incomplete, resulting in
259+
// unsoundness during coherence (#105782).
260+
let target_item_def_id = match ecx.fetch_eligible_assoc_item(
261+
goal_trait_ref,
262+
goal.predicate.def_id(),
263+
impl_def_id,
264+
) {
265+
Ok(Some(target_item_def_id)) => target_item_def_id,
266+
Ok(None) => {
267+
return ecx
268+
.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS);
269+
}
270+
Err(guar) => return error_response(ecx, guar),
271+
};
272+
270273
if !cx.has_item_definition(target_item_def_id) {
271-
return error_response(ecx, "missing item");
274+
return error_response(ecx, cx.delay_bug("missing item"));
272275
}
273276

274277
let target_container_def_id = cx.parent(target_item_def_id);
@@ -292,7 +295,10 @@ where
292295
)?;
293296

294297
if !cx.check_args_compatible(target_item_def_id, target_args) {
295-
return error_response(ecx, "associated item has mismatched arguments");
298+
return error_response(
299+
ecx,
300+
cx.delay_bug("associated item has mismatched arguments"),
301+
);
296302
}
297303

298304
// Finally we construct the actual value of the associated type.

compiler/rustc_trait_selection/src/solve/delegate.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,8 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate<
190190
goal_trait_ref: ty::TraitRef<'tcx>,
191191
trait_assoc_def_id: DefId,
192192
impl_def_id: DefId,
193-
) -> Result<Option<DefId>, NoSolution> {
194-
let node_item = specialization_graph::assoc_def(self.tcx, impl_def_id, trait_assoc_def_id)
195-
.map_err(|ErrorGuaranteed { .. }| NoSolution)?;
193+
) -> Result<Option<DefId>, ErrorGuaranteed> {
194+
let node_item = specialization_graph::assoc_def(self.tcx, impl_def_id, trait_assoc_def_id)?;
196195

197196
let eligible = if node_item.is_final() {
198197
// Non-specializable items are always projectable.

compiler/rustc_trait_selection/src/traits/project.rs

+34-29
Original file line numberDiff line numberDiff line change
@@ -950,39 +950,45 @@ fn assemble_candidates_from_impls<'cx, 'tcx>(
950950
//
951951
// NOTE: This should be kept in sync with the similar code in
952952
// `rustc_ty_utils::instance::resolve_associated_item()`.
953-
let node_item = specialization_graph::assoc_def(
953+
match specialization_graph::assoc_def(
954954
selcx.tcx(),
955955
impl_data.impl_def_id,
956956
obligation.predicate.def_id,
957-
)
958-
.map_err(|ErrorGuaranteed { .. }| ())?;
959-
960-
if node_item.is_final() {
961-
// Non-specializable items are always projectable.
962-
true
963-
} else {
964-
// Only reveal a specializable default if we're past type-checking
965-
// and the obligation is monomorphic, otherwise passes such as
966-
// transmute checking and polymorphic MIR optimizations could
967-
// get a result which isn't correct for all monomorphizations.
968-
match selcx.infcx.typing_mode() {
969-
TypingMode::Coherence
970-
| TypingMode::Analysis { .. }
971-
| TypingMode::PostBorrowckAnalysis { .. } => {
972-
debug!(
973-
assoc_ty = ?selcx.tcx().def_path_str(node_item.item.def_id),
974-
?obligation.predicate,
975-
"assemble_candidates_from_impls: not eligible due to default",
976-
);
977-
false
978-
}
979-
TypingMode::PostAnalysis => {
980-
// NOTE(eddyb) inference variables can resolve to parameters, so
981-
// assume `poly_trait_ref` isn't monomorphic, if it contains any.
982-
let poly_trait_ref = selcx.infcx.resolve_vars_if_possible(trait_ref);
983-
!poly_trait_ref.still_further_specializable()
957+
) {
958+
Ok(node_item) => {
959+
if node_item.is_final() {
960+
// Non-specializable items are always projectable.
961+
true
962+
} else {
963+
// Only reveal a specializable default if we're past type-checking
964+
// and the obligation is monomorphic, otherwise passes such as
965+
// transmute checking and polymorphic MIR optimizations could
966+
// get a result which isn't correct for all monomorphizations.
967+
match selcx.infcx.typing_mode() {
968+
TypingMode::Coherence
969+
| TypingMode::Analysis { .. }
970+
| TypingMode::PostBorrowckAnalysis { .. } => {
971+
debug!(
972+
assoc_ty = ?selcx.tcx().def_path_str(node_item.item.def_id),
973+
?obligation.predicate,
974+
"not eligible due to default",
975+
);
976+
false
977+
}
978+
TypingMode::PostAnalysis => {
979+
// NOTE(eddyb) inference variables can resolve to parameters, so
980+
// assume `poly_trait_ref` isn't monomorphic, if it contains any.
981+
let poly_trait_ref =
982+
selcx.infcx.resolve_vars_if_possible(trait_ref);
983+
!poly_trait_ref.still_further_specializable()
984+
}
985+
}
984986
}
985987
}
988+
// Always project `ErrorGuaranteed`, since this will just help
989+
// us propagate `TyKind::Error` around which suppresses ICEs
990+
// and spurious, unrelated inference errors.
991+
Err(ErrorGuaranteed { .. }) => true,
986992
}
987993
}
988994
ImplSource::Builtin(BuiltinImplSource::Misc, _) => {
@@ -2014,7 +2020,6 @@ fn confirm_impl_candidate<'cx, 'tcx>(
20142020
Ok(assoc_ty) => assoc_ty,
20152021
Err(guar) => return Progress::error(tcx, guar),
20162022
};
2017-
20182023
if !assoc_ty.item.defaultness(tcx).has_value() {
20192024
// This means that the impl is missing a definition for the
20202025
// associated type. This error will be reported by the type

compiler/rustc_trait_selection/src/traits/specialize/specialization_graph.rs

+14
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,12 @@ pub(crate) fn assoc_def(
376376
// If there is no such item in that impl, this function will fail with a
377377
// cycle error if the specialization graph is currently being built.
378378
if let Some(&impl_item_id) = tcx.impl_item_implementor_ids(impl_def_id).get(&assoc_def_id) {
379+
// Ensure that the impl is constrained, otherwise projection may give us
380+
// bad unconstrained infer vars.
381+
if let Some(impl_def_id) = impl_def_id.as_local() {
382+
tcx.ensure().enforce_impl_non_lifetime_params_are_constrained(impl_def_id)?;
383+
}
384+
379385
let item = tcx.associated_item(impl_item_id);
380386
let impl_node = Node::Impl(impl_def_id);
381387
return Ok(LeafDef {
@@ -391,6 +397,14 @@ pub(crate) fn assoc_def(
391397

392398
let ancestors = trait_def.ancestors(tcx, impl_def_id)?;
393399
if let Some(assoc_item) = ancestors.leaf_def(tcx, assoc_def_id) {
400+
// Ensure that the impl is constrained, otherwise projection may give us
401+
// bad unconstrained infer vars.
402+
if assoc_item.item.container == ty::AssocItemContainer::Impl
403+
&& let Some(impl_def_id) = assoc_item.item.container_id(tcx).as_local()
404+
{
405+
tcx.ensure().enforce_impl_non_lifetime_params_are_constrained(impl_def_id)?;
406+
}
407+
394408
Ok(assoc_item)
395409
} else {
396410
// This is saying that neither the trait nor

0 commit comments

Comments
 (0)