Do not project when there are unconstrained impl params

This commit is contained in:
Michael Goulet 2025-01-03 05:01:14 +00:00
parent ab3924b298
commit 2d602ea793
26 changed files with 214 additions and 283 deletions

View File

@ -57,22 +57,24 @@ pub(crate) fn check_impl_wf(
tcx: TyCtxt<'_>,
impl_def_id: LocalDefId,
) -> Result<(), ErrorGuaranteed> {
let min_specialization = tcx.features().min_specialization();
let mut res = Ok(());
debug_assert_matches!(tcx.def_kind(impl_def_id), DefKind::Impl { .. });
res = res.and(enforce_impl_params_are_constrained(tcx, impl_def_id));
if min_specialization {
// Check that the args are constrained. We queryfied the check for ty/const params
// since unconstrained type/const params cause ICEs in projection, so we want to
// detect those specifically and project those to `TyKind::Error`.
let mut res = tcx.ensure().enforce_impl_non_lifetime_params_are_constrained(impl_def_id);
res = res.and(enforce_impl_lifetime_params_are_constrained(tcx, impl_def_id));
if tcx.features().min_specialization() {
res = res.and(check_min_specialization(tcx, impl_def_id));
}
res
}
fn enforce_impl_params_are_constrained(
pub(crate) fn enforce_impl_lifetime_params_are_constrained(
tcx: TyCtxt<'_>,
impl_def_id: LocalDefId,
) -> Result<(), ErrorGuaranteed> {
// Every lifetime used in an associated type must be constrained.
let impl_self_ty = tcx.type_of(impl_def_id).instantiate_identity();
if impl_self_ty.references_error() {
// 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(
// Compilation must continue in order for other important diagnostics to keep showing up.
return Ok(());
}
let impl_generics = tcx.generics_of(impl_def_id);
let impl_predicates = tcx.predicates_of(impl_def_id);
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(
})
.collect();
let mut res = Ok(());
for param in &impl_generics.own_params {
match param.kind {
ty::GenericParamDefKind::Lifetime => {
let param_lt = cgp::Parameter::from(param.to_early_bound_region_data());
if lifetimes_in_associated_types.contains(&param_lt) // (*)
&& !input_parameters.contains(&param_lt)
{
let mut diag = tcx.dcx().create_err(UnconstrainedGenericParameter {
span: tcx.def_span(param.def_id),
param_name: param.name,
param_def_kind: tcx.def_descr(param.def_id),
const_param_note: false,
const_param_note2: false,
});
diag.code(E0207);
res = Err(diag.emit());
}
// (*) This is a horrible concession to reality. I think it'd be
// better to just ban unconstrained lifetimes outright, but in
// practice people do non-hygienic macros like:
//
// ```
// macro_rules! __impl_slice_eq1 {
// ($Lhs: ty, $Rhs: ty, $Bound: ident) => {
// impl<'a, 'b, A: $Bound, B> PartialEq<$Rhs> for $Lhs where A: PartialEq<B> {
// ....
// }
// }
// }
// ```
//
// In a concession to backwards compatibility, we continue to
// permit those, so long as the lifetimes aren't used in
// associated types. I believe this is sound, because lifetimes
// used elsewhere are not projected back out.
}
ty::GenericParamDefKind::Type { .. } | ty::GenericParamDefKind::Const { .. } => {
// Enforced in `enforce_impl_non_lifetime_params_are_constrained`.
}
}
}
res
}
pub(crate) fn enforce_impl_non_lifetime_params_are_constrained(
tcx: TyCtxt<'_>,
impl_def_id: LocalDefId,
) -> Result<(), ErrorGuaranteed> {
let impl_self_ty = tcx.type_of(impl_def_id).instantiate_identity();
if impl_self_ty.references_error() {
// Don't complain about unconstrained type params when self ty isn't known due to errors.
// (#36836)
tcx.dcx().span_delayed_bug(
tcx.def_span(impl_def_id),
format!(
"potentially unconstrained type parameters weren't evaluated: {impl_self_ty:?}",
),
);
// This is super fishy, but our current `rustc_hir_analysis::check_crate` pipeline depends on
// `type_of` having been called much earlier, and thus this value being read from cache.
// Compilation must continue in order for other important diagnostics to keep showing up.
return Ok(());
}
let impl_generics = tcx.generics_of(impl_def_id);
let impl_predicates = tcx.predicates_of(impl_def_id);
let impl_trait_ref = tcx.impl_trait_ref(impl_def_id).map(ty::EarlyBinder::instantiate_identity);
impl_trait_ref.error_reported()?;
let mut input_parameters = cgp::parameters_for_impl(tcx, impl_self_ty, impl_trait_ref);
cgp::identify_constrained_generic_params(
tcx,
impl_predicates,
impl_trait_ref,
&mut input_parameters,
);
let mut res = Ok(());
for param in &impl_generics.own_params {
let err = match param.kind {
@ -129,15 +210,14 @@ fn enforce_impl_params_are_constrained(
let param_ty = ty::ParamTy::for_def(param);
!input_parameters.contains(&cgp::Parameter::from(param_ty))
}
ty::GenericParamDefKind::Lifetime => {
let param_lt = cgp::Parameter::from(param.to_early_bound_region_data());
lifetimes_in_associated_types.contains(&param_lt) && // (*)
!input_parameters.contains(&param_lt)
}
ty::GenericParamDefKind::Const { .. } => {
let param_ct = ty::ParamConst::for_def(param);
!input_parameters.contains(&cgp::Parameter::from(param_ct))
}
ty::GenericParamDefKind::Lifetime => {
// Enforced in `enforce_impl_type_params_are_constrained`.
false
}
};
if err {
let const_param_note = matches!(param.kind, ty::GenericParamDefKind::Const { .. });
@ -153,23 +233,4 @@ fn enforce_impl_params_are_constrained(
}
}
res
// (*) This is a horrible concession to reality. I think it'd be
// better to just ban unconstrained lifetimes outright, but in
// practice people do non-hygienic macros like:
//
// ```
// macro_rules! __impl_slice_eq1 {
// ($Lhs: ty, $Rhs: ty, $Bound: ident) => {
// impl<'a, 'b, A: $Bound, B> PartialEq<$Rhs> for $Lhs where A: PartialEq<B> {
// ....
// }
// }
// }
// ```
//
// In a concession to backwards compatibility, we continue to
// permit those, so long as the lifetimes aren't used in
// associated types. I believe this is sound, because lifetimes
// used elsewhere are not projected back out.
}

View File

@ -128,6 +128,8 @@ pub fn provide(providers: &mut Providers) {
hir_wf_check::provide(providers);
*providers = Providers {
inherit_sig_for_delegation_item: delegation::inherit_sig_for_delegation_item,
enforce_impl_non_lifetime_params_are_constrained:
impl_wf_check::enforce_impl_non_lifetime_params_are_constrained,
..*providers
};
}

View File

@ -1719,6 +1719,11 @@ rustc_queries! {
ensure_forwards_result_if_red
}
query enforce_impl_non_lifetime_params_are_constrained(key: LocalDefId) -> Result<(), ErrorGuaranteed> {
desc { |tcx| "checking that `{}`'s generics are constrained by the impl header", tcx.def_path_str(key) }
ensure_forwards_result_if_red
}
// The `DefId`s of all non-generic functions and statics in the given crate
// that can be reached from outside the crate.
//

View File

@ -950,39 +950,45 @@ fn assemble_candidates_from_impls<'cx, 'tcx>(
//
// NOTE: This should be kept in sync with the similar code in
// `rustc_ty_utils::instance::resolve_associated_item()`.
let node_item = specialization_graph::assoc_def(
match specialization_graph::assoc_def(
selcx.tcx(),
impl_data.impl_def_id,
obligation.predicate.def_id,
)
.map_err(|ErrorGuaranteed { .. }| ())?;
if node_item.is_final() {
// Non-specializable items are always projectable.
true
} else {
// Only reveal a specializable default if we're past type-checking
// and the obligation is monomorphic, otherwise passes such as
// transmute checking and polymorphic MIR optimizations could
// get a result which isn't correct for all monomorphizations.
match selcx.infcx.typing_mode() {
TypingMode::Coherence
| TypingMode::Analysis { .. }
| TypingMode::PostBorrowckAnalysis { .. } => {
debug!(
assoc_ty = ?selcx.tcx().def_path_str(node_item.item.def_id),
?obligation.predicate,
"assemble_candidates_from_impls: not eligible due to default",
);
false
}
TypingMode::PostAnalysis => {
// NOTE(eddyb) inference variables can resolve to parameters, so
// assume `poly_trait_ref` isn't monomorphic, if it contains any.
let poly_trait_ref = selcx.infcx.resolve_vars_if_possible(trait_ref);
!poly_trait_ref.still_further_specializable()
) {
Ok(node_item) => {
if node_item.is_final() {
// Non-specializable items are always projectable.
true
} else {
// Only reveal a specializable default if we're past type-checking
// and the obligation is monomorphic, otherwise passes such as
// transmute checking and polymorphic MIR optimizations could
// get a result which isn't correct for all monomorphizations.
match selcx.infcx.typing_mode() {
TypingMode::Coherence
| TypingMode::Analysis { .. }
| TypingMode::PostBorrowckAnalysis { .. } => {
debug!(
assoc_ty = ?selcx.tcx().def_path_str(node_item.item.def_id),
?obligation.predicate,
"not eligible due to default",
);
false
}
TypingMode::PostAnalysis => {
// NOTE(eddyb) inference variables can resolve to parameters, so
// assume `poly_trait_ref` isn't monomorphic, if it contains any.
let poly_trait_ref =
selcx.infcx.resolve_vars_if_possible(trait_ref);
!poly_trait_ref.still_further_specializable()
}
}
}
}
// Always project `ErrorGuaranteed`, since this will just help
// us propagate `TyKind::Error` around which suppresses ICEs
// and spurious, unrelated inference errors.
Err(ErrorGuaranteed { .. }) => true,
}
}
ImplSource::Builtin(BuiltinImplSource::Misc, _) => {
@ -2014,7 +2020,6 @@ fn confirm_impl_candidate<'cx, 'tcx>(
Ok(assoc_ty) => assoc_ty,
Err(guar) => return Progress::error(tcx, guar),
};
if !assoc_ty.item.defaultness(tcx).has_value() {
// This means that the impl is missing a definition for the
// associated type. This error will be reported by the type

View File

@ -376,6 +376,12 @@ pub(crate) fn assoc_def(
// If there is no such item in that impl, this function will fail with a
// cycle error if the specialization graph is currently being built.
if let Some(&impl_item_id) = tcx.impl_item_implementor_ids(impl_def_id).get(&assoc_def_id) {
// Ensure that the impl is constrained, otherwise projection may give us
// bad unconstrained infer vars.
if let Some(impl_def_id) = impl_def_id.as_local() {
tcx.ensure().enforce_impl_non_lifetime_params_are_constrained(impl_def_id)?;
}
let item = tcx.associated_item(impl_item_id);
let impl_node = Node::Impl(impl_def_id);
return Ok(LeafDef {
@ -391,6 +397,14 @@ pub(crate) fn assoc_def(
let ancestors = trait_def.ancestors(tcx, impl_def_id)?;
if let Some(assoc_item) = ancestors.leaf_def(tcx, assoc_def_id) {
// Ensure that the impl is constrained, otherwise projection may give us
// bad unconstrained infer vars.
if assoc_item.item.container == ty::AssocItemContainer::Impl
&& let Some(impl_def_id) = assoc_item.item.container_id(tcx).as_local()
{
tcx.ensure().enforce_impl_non_lifetime_params_are_constrained(impl_def_id)?;
}
Ok(assoc_item)
} else {
// This is saying that neither the trait nor

View File

@ -1,23 +0,0 @@
//@ known-bug: #123141
trait Trait {
fn next(self) -> Self::Item;
type Item;
}
struct Foo<T: ?Sized>(T);
impl<T: ?Sized, U> Trait for Foo<U> {
type Item = Foo<T>;
fn next(self) -> Self::Item {
loop {}
}
}
fn opaque() -> impl Trait {
Foo::<_>(10_u32)
}
fn main() {
opaque().next();
}

View File

@ -1,22 +0,0 @@
//@ known-bug: rust-lang/rust#125874
pub trait A {}
pub trait Mirror {
type Assoc: ?Sized;
}
impl<T: ?Sized> Mirror for dyn A {
type Assoc = T;
}
struct Bar {
foo: <dyn A + 'static as Mirror>::Assoc,
}
pub fn main() {
let strct = Bar { foo: 3 };
match strct {
Bar { foo: 1, .. } => {}
_ => (),
};
}

View File

@ -1,11 +0,0 @@
//@ known-bug: rust-lang/rust#126942
struct Thing;
pub trait Every {
type Assoc;
}
impl<T: ?Sized> Every for Thing {
type Assoc = T;
}
static I: <Thing as Every>::Assoc = 3;

View File

@ -1,12 +0,0 @@
//@ known-bug: #127804
struct Thing;
pub trait Every {
type Assoc;
}
impl<T: ?Sized> Every for Thing {
type Assoc = T;
}
fn foo(_: <Thing as Every>::Assoc) {}

View File

@ -1,13 +0,0 @@
//@ known-bug: #130967
trait Producer {
type Produced;
fn make_one() -> Self::Produced;
}
impl<E: ?Sized> Producer for () {
type Produced = Option<E>;
fn make_one() -> Self::Produced {
loop {}
}
}

View File

@ -22,73 +22,7 @@ help: consider adding an explicit lifetime bound
LL | type Output<'a> = FooRef<'a, U> where Self: 'a, U: 'a;
| +++++++
error[E0309]: the parameter type `T` may not live long enough
--> $DIR/issue-87735.rs:31:15
|
LL | impl<'b, T, U> AsRef2 for Foo<T>
| -- the parameter type `T` must be valid for the lifetime `'b` as defined here...
...
LL | T: AsRef2<Output<'b> = &'b [U]>,
| ^^^^^^^^^^^^^^^^^^^^ ...so that the type `T` will meet its required lifetime bounds...
|
note: ...that is required by this bound
--> $DIR/issue-87735.rs:7:31
|
LL | type Output<'a> where Self: 'a;
| ^^
help: consider adding an explicit lifetime bound
|
LL | T: AsRef2<Output<'b> = &'b [U]> + 'b,
| ++++
error[E0309]: the parameter type `T` may not live long enough
--> $DIR/issue-87735.rs:36:31
|
LL | impl<'b, T, U> AsRef2 for Foo<T>
| -- the parameter type `T` must be valid for the lifetime `'b` as defined here...
...
LL | fn as_ref2<'a>(&'a self) -> Self::Output<'a> {
| ^^^^^^^^^^^^^^^^ ...so that the type `T` will meet its required lifetime bounds...
|
note: ...that is required by this bound
--> $DIR/issue-87735.rs:7:31
|
LL | type Output<'a> where Self: 'a;
| ^^
help: consider adding an explicit lifetime bound
|
LL | T: AsRef2<Output<'b> = &'b [U]> + 'b,
| ++++
error: lifetime may not live long enough
--> $DIR/issue-87735.rs:37:5
|
LL | impl<'b, T, U> AsRef2 for Foo<T>
| -- lifetime `'b` defined here
...
LL | fn as_ref2<'a>(&'a self) -> Self::Output<'a> {
| -- lifetime `'a` defined here
LL | FooRef(self.0.as_ref2())
| ^^^^^^^^^^^^^^^^^^^^^^^^ method was supposed to return data with lifetime `'a` but it is returning data with lifetime `'b`
|
= help: consider adding the following bound: `'b: 'a`
error: lifetime may not live long enough
--> $DIR/issue-87735.rs:37:12
|
LL | impl<'b, T, U> AsRef2 for Foo<T>
| -- lifetime `'b` defined here
...
LL | fn as_ref2<'a>(&'a self) -> Self::Output<'a> {
| -- lifetime `'a` defined here
LL | FooRef(self.0.as_ref2())
| ^^^^^^^^^^^^^^^^ argument requires that `'a` must outlive `'b`
|
= help: consider adding the following bound: `'a: 'b`
help: `'b` and `'a` must be the same: replace one with the other
error: aborting due to 6 previous errors
error: aborting due to 2 previous errors
Some errors have detailed explanations: E0207, E0309.
For more information about an error, try `rustc --explain E0207`.

View File

@ -13,7 +13,6 @@ impl<T: ?Sized> Mirror for () {
pub trait First {
async fn first() -> <() as Mirror>::Assoc;
//~^ ERROR type annotations needed
}
impl First for () {

View File

@ -4,13 +4,6 @@ error[E0207]: the type parameter `T` is not constrained by the impl trait, self
LL | impl<T: ?Sized> Mirror for () {
| ^ unconstrained type parameter
error[E0282]: type annotations needed
--> $DIR/refine-resolution-errors.rs:15:5
|
LL | async fn first() -> <() as Mirror>::Assoc;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type
error: aborting due to 1 previous error
error: aborting due to 2 previous errors
Some errors have detailed explanations: E0207, E0282.
For more information about an error, try `rustc --explain E0207`.
For more information about this error, try `rustc --explain E0207`.

View File

@ -9,8 +9,6 @@ impl<T> X for () {
//~^ ERROR `T` is not constrained by the impl trait, self type, or predicates
type I = impl Sized;
fn f() -> Self::I {}
//~^ ERROR type annotations needed
//~| ERROR type annotations needed
}
fn main() {}

View File

@ -4,19 +4,6 @@ error[E0207]: the type parameter `T` is not constrained by the impl trait, self
LL | impl<T> X for () {
| ^ unconstrained type parameter
error[E0282]: type annotations needed
--> $DIR/issue-87340.rs:11:23
|
LL | fn f() -> Self::I {}
| ^^ cannot infer type for type parameter `T`
error: aborting due to 1 previous error
error[E0282]: type annotations needed
--> $DIR/issue-87340.rs:11:15
|
LL | fn f() -> Self::I {}
| ^^^^^^^ cannot infer type for type parameter `T`
error: aborting due to 3 previous errors
Some errors have detailed explanations: E0207, E0282.
For more information about an error, try `rustc --explain E0207`.
For more information about this error, try `rustc --explain E0207`.

View File

@ -7,6 +7,12 @@ LL | impl<T> Foo<T> for [isize; 0] {
LL | impl<T, U> Foo<T> for U {
| ^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `[isize; 0]`
error[E0207]: the type parameter `U` is not constrained by the impl trait, self type, or predicates
--> $DIR/impl-unused-tps.rs:32:9
|
LL | impl<T, U> Bar for T {
| ^ unconstrained type parameter
error[E0119]: conflicting implementations of trait `Bar`
--> $DIR/impl-unused-tps.rs:40:1
|
@ -46,12 +52,6 @@ error[E0207]: the type parameter `U` is not constrained by the impl trait, self
LL | impl<T, U> Foo<T> for [isize; 1] {
| ^ unconstrained type parameter
error[E0207]: the type parameter `U` is not constrained by the impl trait, self type, or predicates
--> $DIR/impl-unused-tps.rs:32:9
|
LL | impl<T, U> Bar for T {
| ^ unconstrained type parameter
error[E0207]: the type parameter `U` is not constrained by the impl trait, self type, or predicates
--> $DIR/impl-unused-tps.rs:40:9
|

View File

@ -0,0 +1,17 @@
// Make sure we don't ICE in `normalize_erasing_regions` when normalizing
// an associated type in an impl with unconstrained non-lifetime params.
// (This time in a function signature)
struct Thing;
pub trait Every {
type Assoc;
}
impl<T: ?Sized> Every for Thing {
//~^ ERROR the type parameter `T` is not constrained
type Assoc = T;
}
fn foo(_: <Thing as Every>::Assoc) {}
fn main() {}

View File

@ -0,0 +1,9 @@
error[E0207]: the type parameter `T` is not constrained by the impl trait, self type, or predicates
--> $DIR/unconstrained-projection-normalization-2.rs:10:6
|
LL | impl<T: ?Sized> Every for Thing {
| ^ unconstrained type parameter
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0207`.

View File

@ -0,0 +1,16 @@
// Make sure we don't ICE in `normalize_erasing_regions` when normalizing
// an associated type in an impl with unconstrained non-lifetime params.
struct Thing;
pub trait Every {
type Assoc;
}
impl<T: ?Sized> Every for Thing {
//~^ ERROR the type parameter `T` is not constrained
type Assoc = T;
}
static I: <Thing as Every>::Assoc = 3;
fn main() {}

View File

@ -0,0 +1,9 @@
error[E0207]: the type parameter `T` is not constrained by the impl trait, self type, or predicates
--> $DIR/unconstrained-projection-normalization.rs:9:6
|
LL | impl<T: ?Sized> Every for Thing {
| ^ unconstrained type parameter
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0207`.

View File

@ -42,7 +42,6 @@ impl<T: MyFrom<Phantom2<DummyT<U>>>, U> MyIndex<DummyT<T>> for Scope<U> {
//~^ ERROR the type parameter `T` is not constrained by the impl
type O = T;
fn my_index(self) -> Self::O {
//~^ ERROR item does not constrain
MyFrom::my_from(self.0).ok().unwrap()
}
}

View File

@ -17,19 +17,6 @@ note: this opaque type is in the signature
LL | type DummyT<T> = impl F;
| ^^^^^^
error: item does not constrain `DummyT::{opaque#0}`, but has it in its signature
--> $DIR/ice-failed-to-resolve-instance-for-110696.rs:44:8
|
LL | fn my_index(self) -> Self::O {
| ^^^^^^^^
|
= note: consider moving the opaque type's declaration and defining uses into a separate module
note: this opaque type is in the signature
--> $DIR/ice-failed-to-resolve-instance-for-110696.rs:20:18
|
LL | type DummyT<T> = impl F;
| ^^^^^^
error: aborting due to 3 previous errors
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0207`.

View File

@ -12,8 +12,6 @@ impl<T> X for () {
//~^ ERROR the type parameter `T` is not constrained
type I = impl Sized;
fn f() -> Self::I {}
//~^ ERROR type annotations needed
//~| ERROR type annotations needed
}
fn main() {}

View File

@ -4,19 +4,6 @@ error[E0207]: the type parameter `T` is not constrained by the impl trait, self
LL | impl<T> X for () {
| ^ unconstrained type parameter
error[E0282]: type annotations needed
--> $DIR/impl-with-unconstrained-param.rs:14:23
|
LL | fn f() -> Self::I {}
| ^^ cannot infer type for type parameter `T`
error: aborting due to 1 previous error
error[E0282]: type annotations needed
--> $DIR/impl-with-unconstrained-param.rs:14:15
|
LL | fn f() -> Self::I {}
| ^^^^^^^ cannot infer type for type parameter `T`
error: aborting due to 3 previous errors
Some errors have detailed explanations: E0207, E0282.
For more information about an error, try `rustc --explain E0207`.
For more information about this error, try `rustc --explain E0207`.

View File

@ -14,7 +14,6 @@ impl<T> Allocator for DefaultAllocator {
type A = impl Fn(<DefaultAllocator as Allocator>::Buffer);
fn foo() -> A {
//~^ ERROR: type annotations needed
|_| ()
}

View File

@ -4,13 +4,6 @@ error[E0207]: the type parameter `T` is not constrained by the impl trait, self
LL | impl<T> Allocator for DefaultAllocator {
| ^ unconstrained type parameter
error[E0282]: type annotations needed
--> $DIR/issue-74244.rs:16:13
|
LL | fn foo() -> A {
| ^ cannot infer type for type parameter `T`
error: aborting due to 1 previous error
error: aborting due to 2 previous errors
Some errors have detailed explanations: E0207, E0282.
For more information about an error, try `rustc --explain E0207`.
For more information about this error, try `rustc --explain E0207`.