Skip to content

Commit

Permalink
Unrolled build for rust-lang#136107
Browse files Browse the repository at this point in the history
Rollup merge of rust-lang#136107 - dingxiangfei2009:coerce-pointee-wellformed, r=compiler-errors

Introduce CoercePointeeWellformed for coherence checks at typeck stage

Fix rust-lang#135206

This is the first PR to introduce the "wellformedness" check for `derive(CoercePointee)`.

This patch introduces a new error code to cover all the prerequisites of the said macro. The checks that is enforced with this patch is whether the data is indeed `struct` and whether the layout is set to `repr(transparent)`.

A following series of patch will arrive later to address the following concern.
1. rust-lang#135217 so that we would only admit one single coercion on one type parameter, and leave the rest for future consideration in tandem of development of other coercion rules.
1. Enforcement of data field requirements.

**An open question** is whether there is a good schema to encode the `#[pointee]` as well, so that we could also check if the `#[pointee]` type parameter is indeed `?Sized`.

``@rustbot`` label F-derive_coerce_pointee
  • Loading branch information
rust-timer authored Feb 11, 2025
2 parents ffa9afe + 17026e2 commit 368a423
Show file tree
Hide file tree
Showing 15 changed files with 385 additions and 54 deletions.
74 changes: 55 additions & 19 deletions compiler/rustc_builtin_macros/src/deriving/coerce_pointee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ use ast::ptr::P;
use rustc_ast::mut_visit::MutVisitor;
use rustc_ast::visit::BoundKind;
use rustc_ast::{
self as ast, GenericArg, GenericBound, GenericParamKind, ItemKind, MetaItem,
self as ast, GenericArg, GenericBound, GenericParamKind, Generics, ItemKind, MetaItem,
TraitBoundModifiers, VariantData, WherePredicate,
};
use rustc_attr_parsing as attr;
use rustc_data_structures::flat_map_in_place::FlatMapInPlace;
use rustc_errors::E0802;
use rustc_expand::base::{Annotatable, ExtCtxt};
use rustc_macros::Diagnostic;
use rustc_span::{Ident, Span, Symbol, sym};
Expand All @@ -32,15 +32,6 @@ pub(crate) fn expand_deriving_coerce_pointee(
let (name_ident, generics) = if let Annotatable::Item(aitem) = item
&& let ItemKind::Struct(struct_data, g) = &aitem.kind
{
let is_transparent = aitem.attrs.iter().any(|attr| {
attr::find_repr_attrs(cx.sess, attr)
.into_iter()
.any(|r| matches!(r, attr::ReprTransparent))
});
if !is_transparent {
cx.dcx().emit_err(RequireTransparent { span });
return;
}
if !matches!(
struct_data,
VariantData::Struct { fields, recovered: _ } | VariantData::Tuple(fields, _)
Expand Down Expand Up @@ -88,8 +79,7 @@ pub(crate) fn expand_deriving_coerce_pointee(
} else {
let mut pointees = type_params
.iter()
.filter_map(|&(idx, span, is_pointee)| is_pointee.then_some((idx, span)))
.fuse();
.filter_map(|&(idx, span, is_pointee)| is_pointee.then_some((idx, span)));
match (pointees.next(), pointees.next()) {
(Some((idx, _span)), None) => idx,
(None, _) => {
Expand All @@ -110,6 +100,52 @@ pub(crate) fn expand_deriving_coerce_pointee(
// Declare helper function that adds implementation blocks.
// FIXME(dingxiangfei2009): Investigate the set of attributes on target struct to be propagated to impls
let attrs = thin_vec![cx.attr_word(sym::automatically_derived, span),];
// # Validity assertion which will be checked later in `rustc_hir_analysis::coherence::builtins`.
{
let trait_path =
cx.path_all(span, true, path!(span, core::marker::CoercePointeeValidated), vec![]);
let trait_ref = cx.trait_ref(trait_path);
push(Annotatable::Item(
cx.item(
span,
Ident::empty(),
attrs.clone(),
ast::ItemKind::Impl(Box::new(ast::Impl {
safety: ast::Safety::Default,
polarity: ast::ImplPolarity::Positive,
defaultness: ast::Defaultness::Final,
constness: ast::Const::No,
generics: Generics {
params: generics
.params
.iter()
.map(|p| match &p.kind {
GenericParamKind::Lifetime => {
cx.lifetime_param(p.span(), p.ident, p.bounds.clone())
}
GenericParamKind::Type { default: _ } => {
cx.typaram(p.span(), p.ident, p.bounds.clone(), None)
}
GenericParamKind::Const { ty, kw_span: _, default: _ } => cx
.const_param(
p.span(),
p.ident,
p.bounds.clone(),
ty.clone(),
None,
),
})
.collect(),
where_clause: generics.where_clause.clone(),
span: generics.span,
},
of_trait: Some(trait_ref),
self_ty: self_type.clone(),
items: ThinVec::new(),
})),
),
));
}
let mut add_impl_block = |generics, trait_symbol, trait_args| {
let mut parts = path!(span, core::ops);
parts.push(Ident::new(trait_symbol, span));
Expand Down Expand Up @@ -430,35 +466,35 @@ impl<'a, 'b> rustc_ast::visit::Visitor<'a> for AlwaysErrorOnGenericParam<'a, 'b>
}

#[derive(Diagnostic)]
#[diag(builtin_macros_coerce_pointee_requires_transparent)]
#[diag(builtin_macros_coerce_pointee_requires_transparent, code = E0802)]
struct RequireTransparent {
#[primary_span]
span: Span,
}

#[derive(Diagnostic)]
#[diag(builtin_macros_coerce_pointee_requires_one_field)]
#[diag(builtin_macros_coerce_pointee_requires_one_field, code = E0802)]
struct RequireOneField {
#[primary_span]
span: Span,
}

#[derive(Diagnostic)]
#[diag(builtin_macros_coerce_pointee_requires_one_generic)]
#[diag(builtin_macros_coerce_pointee_requires_one_generic, code = E0802)]
struct RequireOneGeneric {
#[primary_span]
span: Span,
}

#[derive(Diagnostic)]
#[diag(builtin_macros_coerce_pointee_requires_one_pointee)]
#[diag(builtin_macros_coerce_pointee_requires_one_pointee, code = E0802)]
struct RequireOnePointee {
#[primary_span]
span: Span,
}

#[derive(Diagnostic)]
#[diag(builtin_macros_coerce_pointee_too_many_pointees)]
#[diag(builtin_macros_coerce_pointee_too_many_pointees, code = E0802)]
struct TooManyPointees {
#[primary_span]
one: Span,
Expand All @@ -467,7 +503,7 @@ struct TooManyPointees {
}

#[derive(Diagnostic)]
#[diag(builtin_macros_coerce_pointee_requires_maybe_sized)]
#[diag(builtin_macros_coerce_pointee_requires_maybe_sized, code = E0802)]
struct RequiresMaybeSized {
#[primary_span]
span: Span,
Expand Down
94 changes: 94 additions & 0 deletions compiler/rustc_error_codes/src/error_codes/E0802.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
The target of `derive(CoercePointee)` macro has inadmissible specification for
a meaningful use.

Erroneous code examples:

The target data is not a `struct`.

```compile_fail,E0802
#![feature(coerce_pointee)]
use std::marker::CoercePointee;
#[derive(CoercePointee)]
enum NotStruct<'a, T: ?Sized> {
Variant(&'a T),
}
```

The target data has a layout that is not transparent, or `repr(transparent)`
in other words.

```compile_fail,E0802
#![feature(coerce_pointee)]
use std::marker::CoercePointee;
#[derive(CoercePointee)]
struct NotTransparent<'a, #[pointee] T: ?Sized> {
ptr: &'a T,
}
```

The target data has no data field.

```compile_fail,E0802
#![feature(coerce_pointee)]
use std::marker::CoercePointee;
#[derive(CoercePointee)]
#[repr(transparent)]
struct NoField<'a, #[pointee] T: ?Sized> {}
```

The target data is not generic over any data, or has no generic type parameter.

```compile_fail,E0802
#![feature(coerce_pointee)]
use std::marker::CoercePointee;
#[derive(CoercePointee)]
#[repr(transparent)]
struct NoGeneric<'a>(&'a u8);
```

The target data has multiple generic type parameters, but none is designated as
a pointee for coercion.

```compile_fail,E0802
#![feature(coerce_pointee)]
use std::marker::CoercePointee;
#[derive(CoercePointee)]
#[repr(transparent)]
struct AmbiguousPointee<'a, T1: ?Sized, T2: ?Sized> {
a: (&'a T1, &'a T2),
}
```

The target data has multiple generic type parameters that are designated as
pointees for coercion.

```compile_fail,E0802
#![feature(coerce_pointee)]
use std::marker::CoercePointee;
#[derive(CoercePointee)]
#[repr(transparent)]
struct TooManyPointees<
'a,
#[pointee] A: ?Sized,
#[pointee] B: ?Sized>
((&'a A, &'a B));
```

The type parameter that is designated as a pointee is not marked `?Sized`.

```compile_fail,E0802
#![feature(coerce_pointee)]
use std::marker::CoercePointee;
#[derive(CoercePointee)]
#[repr(transparent)]
struct NoMaybeSized<'a, #[pointee] T> {
ptr: &'a T,
}
```

In summary, the `CoercePointee` macro demands the type to be a `struct` that is
generic over at least one type or over more types, one of which is marked with
`#[pointee]`, and has at least one data field and adopts a `repr(transparent)`
layout.
The only generic type or the type marked with `#[pointee]` has to be also
marked as `?Sized`.
1 change: 1 addition & 0 deletions compiler/rustc_error_codes/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,7 @@ E0798: 0798,
E0799: 0799,
E0800: 0800,
E0801: 0801,
E0802: 0802,
);
)
}
Expand Down
39 changes: 38 additions & 1 deletion compiler/rustc_expand/src/build.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use rustc_ast::ptr::P;
use rustc_ast::util::literal;
use rustc_ast::{
self as ast, AttrVec, BlockCheckMode, Expr, LocalKind, MatchKind, PatKind, UnOp, attr, token,
self as ast, AnonConst, AttrVec, BlockCheckMode, Expr, LocalKind, MatchKind, PatKind, UnOp,
attr, token,
};
use rustc_span::source_map::Spanned;
use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym};
Expand Down Expand Up @@ -138,6 +139,42 @@ impl<'a> ExtCtxt<'a> {
}
}

pub fn lifetime_param(
&self,
span: Span,
ident: Ident,
bounds: ast::GenericBounds,
) -> ast::GenericParam {
ast::GenericParam {
id: ast::DUMMY_NODE_ID,
ident: ident.with_span_pos(span),
attrs: AttrVec::new(),
bounds,
is_placeholder: false,
kind: ast::GenericParamKind::Lifetime,
colon_span: None,
}
}

pub fn const_param(
&self,
span: Span,
ident: Ident,
bounds: ast::GenericBounds,
ty: P<ast::Ty>,
default: Option<AnonConst>,
) -> ast::GenericParam {
ast::GenericParam {
id: ast::DUMMY_NODE_ID,
ident: ident.with_span_pos(span),
attrs: AttrVec::new(),
bounds,
is_placeholder: false,
kind: ast::GenericParamKind::Const { ty, kw_span: DUMMY_SP, default },
colon_span: None,
}
}

pub fn trait_ref(&self, path: ast::Path) -> ast::TraitRef {
ast::TraitRef { path, ref_id: ast::DUMMY_NODE_ID }
}
Expand Down
6 changes: 6 additions & 0 deletions compiler/rustc_hir/src/lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,8 @@ language_item_table! {

PointerLike, sym::pointer_like, pointer_like, Target::Trait, GenericRequirement::Exact(0);

CoercePointeeValidated, sym::coerce_pointee_validated, coerce_pointee_validated_trait, Target::Trait, GenericRequirement::Exact(0);

ConstParamTy, sym::const_param_ty, const_param_ty_trait, Target::Trait, GenericRequirement::Exact(0);
UnsizedConstParamTy, sym::unsized_const_param_ty, unsized_const_param_ty_trait, Target::Trait, GenericRequirement::Exact(0);

Expand Down Expand Up @@ -429,9 +431,13 @@ language_item_table! {
ContractCheckRequires, sym::contract_check_requires, contract_check_requires_fn, Target::Fn, GenericRequirement::None;
}

/// The requirement imposed on the generics of a lang item
pub enum GenericRequirement {
/// No restriction on the generics
None,
/// A minimum number of generics that is demanded on a lang item
Minimum(usize),
/// The number of generics must match precisely as stipulated
Exact(usize),
}

Expand Down
10 changes: 10 additions & 0 deletions compiler/rustc_hir_analysis/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ hir_analysis_cmse_output_stack_spill =
.note1 = functions with the `"{$abi_name}"` ABI must pass their result via the available return registers
.note2 = the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size
hir_analysis_coerce_pointee_no_field = `CoercePointee` can only be derived on `struct`s with at least one field
hir_analysis_coerce_pointee_no_user_validity_assertion = asserting applicability of `derive(CoercePointee)` on a target data is forbidden
hir_analysis_coerce_pointee_not_concrete_ty = `derive(CoercePointee)` is only applicable to `struct`
hir_analysis_coerce_pointee_not_struct = `derive(CoercePointee)` is only applicable to `struct`, instead of `{$kind}`
hir_analysis_coerce_pointee_not_transparent = `derive(CoercePointee)` is only applicable to `struct` with `repr(transparent)` layout
hir_analysis_coerce_unsized_may = the trait `{$trait_name}` may only be implemented for a coercion between structures
hir_analysis_coerce_unsized_multi = implementing the trait `CoerceUnsized` requires multiple coercions
Expand Down
33 changes: 33 additions & 0 deletions compiler/rustc_hir_analysis/src/coherence/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ pub(super) fn check_trait<'tcx>(
checker
.check(lang_items.dispatch_from_dyn_trait(), visit_implementation_of_dispatch_from_dyn)?;
checker.check(lang_items.pointer_like(), visit_implementation_of_pointer_like)?;
checker.check(
lang_items.coerce_pointee_validated_trait(),
visit_implementation_of_coerce_pointee_validity,
)?;
Ok(())
}

Expand Down Expand Up @@ -783,3 +787,32 @@ fn visit_implementation_of_pointer_like(checker: &Checker<'_>) -> Result<(), Err
.with_note(why_disqualified)
.emit())
}

fn visit_implementation_of_coerce_pointee_validity(
checker: &Checker<'_>,
) -> Result<(), ErrorGuaranteed> {
let tcx = checker.tcx;
let self_ty = tcx.impl_trait_ref(checker.impl_def_id).unwrap().instantiate_identity().self_ty();
let span = tcx.def_span(checker.impl_def_id);
if !tcx.is_builtin_derived(checker.impl_def_id.into()) {
return Err(tcx.dcx().emit_err(errors::CoercePointeeNoUserValidityAssertion { span }));
}
let ty::Adt(def, _args) = self_ty.kind() else {
return Err(tcx.dcx().emit_err(errors::CoercePointeeNotConcreteType { span }));
};
let did = def.did();
// Now get a more precise span of the `struct`.
let span = tcx.def_span(did);
if !def.is_struct() {
return Err(tcx
.dcx()
.emit_err(errors::CoercePointeeNotStruct { span, kind: def.descr().into() }));
}
if !def.repr().transparent() {
return Err(tcx.dcx().emit_err(errors::CoercePointeeNotTransparent { span }));
}
if def.all_fields().next().is_none() {
return Err(tcx.dcx().emit_err(errors::CoercePointeeNoField { span }));
}
Ok(())
}
Loading

0 comments on commit 368a423

Please sign in to comment.