Skip to content

Commit

Permalink
Rollup merge of #134603 - kpreid:pointerlike-err, r=estebank
Browse files Browse the repository at this point in the history
Explain why a type is not eligible for `impl PointerLike`.

The rules were baffling when I ran in to them trying to add some impls (to `std`, not my own code, as it happens), so I made the compiler explain them to me.

The logic of the successful cases is unchanged, but I did rearrange it to reverse the order of the primitive and `Adt` cases; this makes producing the errors easier. I'm still not very familiar with `rustc` internals, so let me know if there's a better way to do any of this.

This also adds test coverage for which impls are accepted or rejected, which I didn't see any of already.

The PR template tells me I should consider mentioning a tracking issue, but there isn't one for `pointer_like_trait`, so I'll mention `dyn_star`: #102425
  • Loading branch information
matthiaskrgr authored Dec 22, 2024
2 parents bcdde4e + 7b500d8 commit 7cf9156
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 31 deletions.
100 changes: 69 additions & 31 deletions compiler/rustc_hir_analysis/src/coherence/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -673,37 +673,6 @@ fn visit_implementation_of_pointer_like(checker: &Checker<'_>) -> Result<(), Err
let impl_span = tcx.def_span(checker.impl_def_id);
let self_ty = tcx.impl_trait_ref(checker.impl_def_id).unwrap().instantiate_identity().self_ty();

// If an ADT is repr(transparent)...
if let ty::Adt(def, args) = *self_ty.kind()
&& def.repr().transparent()
{
// FIXME(compiler-errors): This should and could be deduplicated into a query.
// Find the nontrivial field.
let adt_typing_env = ty::TypingEnv::non_body_analysis(tcx, def.did());
let nontrivial_field = def.all_fields().find(|field_def| {
let field_ty = tcx.type_of(field_def.did).instantiate_identity();
!tcx.layout_of(adt_typing_env.as_query_input(field_ty))
.is_ok_and(|layout| layout.layout.is_1zst())
});

if let Some(nontrivial_field) = nontrivial_field {
// Check that the nontrivial field implements `PointerLike`.
let nontrivial_field = nontrivial_field.ty(tcx, args);
let (infcx, param_env) = tcx.infer_ctxt().build_with_typing_env(typing_env);
let ocx = ObligationCtxt::new(&infcx);
ocx.register_bound(
ObligationCause::misc(impl_span, checker.impl_def_id),
param_env,
nontrivial_field,
tcx.lang_items().pointer_like().unwrap(),
);
// FIXME(dyn-star): We should regionck this implementation.
if ocx.select_all_or_error().is_empty() {
return Ok(());
}
}
}

let is_permitted_primitive = match *self_ty.kind() {
ty::Adt(def, _) => def.is_box(),
ty::Uint(..) | ty::Int(..) | ty::RawPtr(..) | ty::Ref(..) | ty::FnPtr(..) => true,
Expand All @@ -717,12 +686,81 @@ fn visit_implementation_of_pointer_like(checker: &Checker<'_>) -> Result<(), Err
return Ok(());
}

let why_disqualified = match *self_ty.kind() {
// If an ADT is repr(transparent)
ty::Adt(self_ty_def, args) => {
if self_ty_def.repr().transparent() {
// FIXME(compiler-errors): This should and could be deduplicated into a query.
// Find the nontrivial field.
let adt_typing_env = ty::TypingEnv::non_body_analysis(tcx, self_ty_def.did());
let nontrivial_field = self_ty_def.all_fields().find(|field_def| {
let field_ty = tcx.type_of(field_def.did).instantiate_identity();
!tcx.layout_of(adt_typing_env.as_query_input(field_ty))
.is_ok_and(|layout| layout.layout.is_1zst())
});

if let Some(nontrivial_field) = nontrivial_field {
// Check that the nontrivial field implements `PointerLike`.
let nontrivial_field_ty = nontrivial_field.ty(tcx, args);
let (infcx, param_env) = tcx.infer_ctxt().build_with_typing_env(typing_env);
let ocx = ObligationCtxt::new(&infcx);
ocx.register_bound(
ObligationCause::misc(impl_span, checker.impl_def_id),
param_env,
nontrivial_field_ty,
tcx.lang_items().pointer_like().unwrap(),
);
// FIXME(dyn-star): We should regionck this implementation.
if ocx.select_all_or_error().is_empty() {
return Ok(());
} else {
format!(
"the field `{field_name}` of {descr} `{self_ty}` \
does not implement `PointerLike`",
field_name = nontrivial_field.name,
descr = self_ty_def.descr()
)
}
} else {
format!(
"the {descr} `{self_ty}` is `repr(transparent)`, \
but does not have a non-trivial field (it is zero-sized)",
descr = self_ty_def.descr()
)
}
} else if self_ty_def.is_box() {
// If we got here, then the `layout.is_pointer_like()` check failed
// and this box is not a thin pointer.

String::from("boxes of dynamically-sized types are too large to be `PointerLike`")
} else {
format!(
"the {descr} `{self_ty}` is not `repr(transparent)`",
descr = self_ty_def.descr()
)
}
}
ty::Ref(..) => {
// If we got here, then the `layout.is_pointer_like()` check failed
// and this reference is not a thin pointer.
String::from("references to dynamically-sized types are too large to be `PointerLike`")
}
ty::Dynamic(..) | ty::Foreign(..) => {
String::from("types of dynamic or unknown size may not implement `PointerLike`")
}
_ => {
// This is a white lie; it is true everywhere outside the standard library.
format!("only user-defined sized types are eligible for `impl PointerLike`")
}
};

Err(tcx
.dcx()
.struct_span_err(
impl_span,
"implementation must be applied to type that has the same ABI as a pointer, \
or is `repr(transparent)` and whose field is `PointerLike`",
)
.with_note(why_disqualified)
.emit())
}
82 changes: 82 additions & 0 deletions tests/ui/dyn-star/pointer-like-impl-rules.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//@ check-fail

#![feature(extern_types)]
#![feature(pointer_like_trait)]

use std::marker::PointerLike;

struct NotReprTransparent;
impl PointerLike for NotReprTransparent {}
//~^ ERROR: implementation must be applied to type that
//~| NOTE: the struct `NotReprTransparent` is not `repr(transparent)`

#[repr(transparent)]
struct FieldIsPl(usize);
impl PointerLike for FieldIsPl {}

#[repr(transparent)]
struct FieldIsPlAndHasOtherField(usize, ());
impl PointerLike for FieldIsPlAndHasOtherField {}

#[repr(transparent)]
struct FieldIsNotPl(u8);
impl PointerLike for FieldIsNotPl {}
//~^ ERROR: implementation must be applied to type that
//~| NOTE: the field `0` of struct `FieldIsNotPl` does not implement `PointerLike`

#[repr(transparent)]
struct GenericFieldIsNotPl<T>(T);
impl<T> PointerLike for GenericFieldIsNotPl<T> {}
//~^ ERROR: implementation must be applied to type that
//~| NOTE: the field `0` of struct `GenericFieldIsNotPl<T>` does not implement `PointerLike`

#[repr(transparent)]
struct GenericFieldIsPl<T>(T);
impl<T: PointerLike> PointerLike for GenericFieldIsPl<T> {}

#[repr(transparent)]
struct IsZeroSized(());
impl PointerLike for IsZeroSized {}
//~^ ERROR: implementation must be applied to type that
//~| NOTE: the struct `IsZeroSized` is `repr(transparent)`, but does not have a non-trivial field

trait SomeTrait {}
impl PointerLike for dyn SomeTrait {}
//~^ ERROR: implementation must be applied to type that
//~| NOTE: types of dynamic or unknown size

extern "C" {
type ExternType;
}
impl PointerLike for ExternType {}
//~^ ERROR: implementation must be applied to type that
//~| NOTE: types of dynamic or unknown size

struct LocalSizedType(&'static str);
struct LocalUnsizedType(str);

// This is not a special error but a normal coherence error,
// which should still happen.
impl PointerLike for &LocalSizedType {}
//~^ ERROR: conflicting implementations of trait `PointerLike`
//~| NOTE: conflicting implementation in crate `core`

impl PointerLike for &LocalUnsizedType {}
//~^ ERROR: implementation must be applied to type that
//~| NOTE: references to dynamically-sized types are too large to be `PointerLike`

impl PointerLike for Box<LocalSizedType> {}
//~^ ERROR: conflicting implementations of trait `PointerLike`
//~| NOTE: conflicting implementation in crate `alloc`

impl PointerLike for Box<LocalUnsizedType> {}
//~^ ERROR: implementation must be applied to type that
//~| NOTE: boxes of dynamically-sized types are too large to be `PointerLike`

fn expects_pointer_like(x: impl PointerLike) {}

fn main() {
expects_pointer_like(FieldIsPl(1usize));
expects_pointer_like(FieldIsPlAndHasOtherField(1usize, ()));
expects_pointer_like(GenericFieldIsPl(1usize));
}
85 changes: 85 additions & 0 deletions tests/ui/dyn-star/pointer-like-impl-rules.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
error[E0119]: conflicting implementations of trait `PointerLike` for type `&LocalSizedType`
--> $DIR/pointer-like-impl-rules.rs:60:1
|
LL | impl PointerLike for &LocalSizedType {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: conflicting implementation in crate `core`:
- impl<T> PointerLike for &T;

error[E0119]: conflicting implementations of trait `PointerLike` for type `Box<LocalSizedType>`
--> $DIR/pointer-like-impl-rules.rs:68:1
|
LL | impl PointerLike for Box<LocalSizedType> {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: conflicting implementation in crate `alloc`:
- impl<T> PointerLike for Box<T>;

error: implementation must be applied to type that has the same ABI as a pointer, or is `repr(transparent)` and whose field is `PointerLike`
--> $DIR/pointer-like-impl-rules.rs:9:1
|
LL | impl PointerLike for NotReprTransparent {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: the struct `NotReprTransparent` is not `repr(transparent)`

error: implementation must be applied to type that has the same ABI as a pointer, or is `repr(transparent)` and whose field is `PointerLike`
--> $DIR/pointer-like-impl-rules.rs:23:1
|
LL | impl PointerLike for FieldIsNotPl {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: the field `0` of struct `FieldIsNotPl` does not implement `PointerLike`

error: implementation must be applied to type that has the same ABI as a pointer, or is `repr(transparent)` and whose field is `PointerLike`
--> $DIR/pointer-like-impl-rules.rs:29:1
|
LL | impl<T> PointerLike for GenericFieldIsNotPl<T> {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: the field `0` of struct `GenericFieldIsNotPl<T>` does not implement `PointerLike`

error: implementation must be applied to type that has the same ABI as a pointer, or is `repr(transparent)` and whose field is `PointerLike`
--> $DIR/pointer-like-impl-rules.rs:39:1
|
LL | impl PointerLike for IsZeroSized {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: the struct `IsZeroSized` is `repr(transparent)`, but does not have a non-trivial field (it is zero-sized)

error: implementation must be applied to type that has the same ABI as a pointer, or is `repr(transparent)` and whose field is `PointerLike`
--> $DIR/pointer-like-impl-rules.rs:44:1
|
LL | impl PointerLike for dyn SomeTrait {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: types of dynamic or unknown size may not implement `PointerLike`

error: implementation must be applied to type that has the same ABI as a pointer, or is `repr(transparent)` and whose field is `PointerLike`
--> $DIR/pointer-like-impl-rules.rs:51:1
|
LL | impl PointerLike for ExternType {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: types of dynamic or unknown size may not implement `PointerLike`

error: implementation must be applied to type that has the same ABI as a pointer, or is `repr(transparent)` and whose field is `PointerLike`
--> $DIR/pointer-like-impl-rules.rs:64:1
|
LL | impl PointerLike for &LocalUnsizedType {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: references to dynamically-sized types are too large to be `PointerLike`

error: implementation must be applied to type that has the same ABI as a pointer, or is `repr(transparent)` and whose field is `PointerLike`
--> $DIR/pointer-like-impl-rules.rs:72:1
|
LL | impl PointerLike for Box<LocalUnsizedType> {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: boxes of dynamically-sized types are too large to be `PointerLike`

error: aborting due to 10 previous errors

For more information about this error, try `rustc --explain E0119`.

0 comments on commit 7cf9156

Please sign in to comment.