Skip to content

Commit bd378ed

Browse files
committed
Require #[pin_v2] for explicit pin-projection patterns
The structural-pinning check only ran for the implicit projection (match ergonomics, gated on `default_binding_modes`). Explicit `&pin mut` / `ref pin` patterns went through `check_pat_ref` and the tuple-struct/struct paths without any check, so they could pin-project through a type that never opted into structural pinning and hand out a `Pin<&mut Field>` for it, breaking the `Pin` guarantee. Move the check to the destructuring site (`check_pin_projection`, called from `check_pat_struct` and `check_pat_tuple_struct`) so one check covers both the implicit and explicit projections. Refs #157634
1 parent beae781 commit bd378ed

7 files changed

Lines changed: 231 additions & 15 deletions

File tree

compiler/rustc_hir_typeck/src/pat.rs

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -544,21 +544,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
544544
{
545545
debug!("scrutinee ty {expected:?} is a pinned reference, inserting pin deref");
546546

547-
// if the inner_ty is an ADT, make sure that it can be structurally pinned
548-
// (i.e., it is `#[pin_v2]`).
549-
if let Some(adt) = inner_ty.ty_adt_def()
550-
&& !adt.is_pin_project()
551-
&& !adt.is_pin()
552-
{
553-
let def_span: Option<Span> = self.tcx.hir_span_if_local(adt.did());
554-
let sugg_span = def_span.map(|span| span.shrink_to_lo());
555-
self.dcx().emit_err(crate::errors::ProjectOnNonPinProjectType {
556-
span: pat.span,
557-
def_span,
558-
sugg_span,
559-
});
560-
}
561-
562547
// Use the old pat info to keep `current_depth` to its old value.
563548
let new_pat_info =
564549
self.adjust_pat_info(Pinnedness::Pinned, inner_mutability, old_pat_info);
@@ -1523,6 +1508,34 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
15231508
Ok(ResolvedPat { ty: pat_ty, kind: ResolvedPatKind::Struct { variant } })
15241509
}
15251510

1511+
/// Reject pin-projection through a type that isn't structurally pinnable.
1512+
///
1513+
/// Destructuring an ADT underneath a `&pin` reference projects its fields as pinned references.
1514+
/// This is only sound if the type opted into structural pinning with `#[pin_v2]`; otherwise it
1515+
/// would let safe code form a `Pin<&mut Field>` for a type that should never be pinned, breaking
1516+
/// the `Pin` guarantee (see #157634).
1517+
///
1518+
/// This covers both explicit (`&pin mut`/`&pin const`) and implicit (match-ergonomics)
1519+
/// projection. `max_pinnedness` is only set for `&pin mut`, so the implicit shared (`&pin
1520+
/// const`) case is instead recognized through its pinned binding mode, hence both are checked.
1521+
fn check_pin_projection(&self, pat: &'tcx Pat<'tcx>, pat_ty: Ty<'tcx>, pat_info: PatInfo<'tcx>) {
1522+
let through_pin = pat_info.max_pinnedness == PinnednessCap::Pinned
1523+
|| matches!(pat_info.binding_mode, ByRef::Yes(Pinnedness::Pinned, _));
1524+
if through_pin
1525+
&& let Some(adt) = pat_ty.ty_adt_def()
1526+
&& !adt.is_pin_project()
1527+
&& !adt.is_pin()
1528+
{
1529+
let def_span: Option<Span> = self.tcx.hir_span_if_local(adt.did());
1530+
let sugg_span = def_span.map(|span| span.shrink_to_lo());
1531+
self.dcx().emit_err(crate::errors::ProjectOnNonPinProjectType {
1532+
span: pat.span,
1533+
def_span,
1534+
sugg_span,
1535+
});
1536+
}
1537+
}
1538+
15261539
fn check_pat_struct(
15271540
&self,
15281541
pat: &'tcx Pat<'tcx>,
@@ -1533,6 +1546,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
15331546
expected: Ty<'tcx>,
15341547
pat_info: PatInfo<'tcx>,
15351548
) -> Ty<'tcx> {
1549+
self.check_pin_projection(pat, pat_ty, pat_info);
1550+
15361551
// Type-check the path.
15371552
let had_err = self.demand_eqtype_pat(pat.span, expected, pat_ty, &pat_info.top_info);
15381553

@@ -1791,6 +1806,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
17911806
expected: Ty<'tcx>,
17921807
pat_info: PatInfo<'tcx>,
17931808
) -> Ty<'tcx> {
1809+
self.check_pin_projection(pat, pat_ty, pat_info);
1810+
17941811
let tcx = self.tcx;
17951812
let on_error = |e| {
17961813
for pat in subpats {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// A plain, non-`#[pin_v2]` type defined in another crate, so it has no local span in the
2+
// downstream crate that projects through it.
3+
pub struct Foreign<T>(pub T);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//@ edition:2024
2+
//@ aux-build:non_pin_project.rs
3+
#![feature(pin_ergonomics)]
4+
#![allow(incomplete_features)]
5+
6+
// Regression test for #157634, exercising the diagnostic good-practice raised in the #157542
7+
// review: the projection error must be emitted even when the projected-through type comes from
8+
// another crate and therefore has no local span. `ProjectOnNonPinProjectType` carries its
9+
// `def_span`/`sugg_span` as `Option<Span>`, so for a foreign type the "type defined here" note
10+
// and the `#[pin_v2]` suggestion are dropped rather than suppressing the error itself.
11+
12+
extern crate non_pin_project;
13+
14+
use non_pin_project::Foreign;
15+
use std::pin::Pin;
16+
17+
fn project<T>(p: Pin<&mut Foreign<T>>) {
18+
let &pin mut Foreign(ref pin mut _x) = p;
19+
//~^ ERROR cannot project on type that is not `#[pin_v2]`
20+
}
21+
22+
fn main() {}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
error: cannot project on type that is not `#[pin_v2]`
2+
--> $DIR/pin-pattern-foreign-non-pin-project.rs:18:18
3+
|
4+
LL | let &pin mut Foreign(ref pin mut _x) = p;
5+
| ^^^^^^^^^^^^^^^^^^^^^^^
6+
7+
error: aborting due to 1 previous error
8+
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//@ edition:2024
2+
#![feature(pin_ergonomics)]
3+
#![allow(incomplete_features)]
4+
5+
// Regression test for #157634.
6+
//
7+
// The implicit pin-projection (via match ergonomics) is only allowed on `#[pin_v2]` types, but
8+
// the explicit `&pin mut` / `ref pin` pattern forms used to project through *any* type. That is
9+
// unsound: it lets safe code form a `Pin<&mut Field>` for a type that never opted into structural
10+
// pinning, breaking the `Pin` guarantee. Check that the explicit forms are now gated the same way
11+
// as the implicit one.
12+
13+
use std::pin::Pin;
14+
15+
struct NotPinProject<T>(T);
16+
17+
struct NotPinProjectStruct<T> {
18+
x: T,
19+
}
20+
21+
enum NotPinProjectEnum<T> {
22+
Tuple(T),
23+
Struct { x: T },
24+
}
25+
26+
fn tuple_struct<T>(p: Pin<&mut NotPinProject<T>>) {
27+
let &pin mut NotPinProject(ref pin mut _x) = p;
28+
//~^ ERROR cannot project on type that is not `#[pin_v2]`
29+
}
30+
31+
fn struct_field<T>(p: Pin<&mut NotPinProjectStruct<T>>) {
32+
let &pin mut NotPinProjectStruct { x: ref pin mut _x } = p;
33+
//~^ ERROR cannot project on type that is not `#[pin_v2]`
34+
}
35+
36+
fn shared<T>(p: Pin<&NotPinProject<T>>) {
37+
let &pin const NotPinProject(ref pin const _x) = p;
38+
//~^ ERROR cannot project on type that is not `#[pin_v2]`
39+
}
40+
41+
fn enum_tuple<T>(p: Pin<&mut NotPinProjectEnum<T>>) {
42+
if let &pin mut NotPinProjectEnum::Tuple(ref pin mut _x) = p {}
43+
//~^ ERROR cannot project on type that is not `#[pin_v2]`
44+
}
45+
46+
fn enum_struct<T>(p: Pin<&mut NotPinProjectEnum<T>>) {
47+
if let &pin mut NotPinProjectEnum::Struct { x: ref pin mut _x } = p {}
48+
//~^ ERROR cannot project on type that is not `#[pin_v2]`
49+
}
50+
51+
// The exact shape from the issue: `Thing` unconditionally implements `Unpin`, so it must not be
52+
// possible to project a pinned reference to one of its fields.
53+
struct Thing<T>(T);
54+
impl<T> Unpin for Thing<T> {}
55+
56+
fn issue_157634<T>(pinned_thing: Pin<&mut Thing<Option<T>>>) {
57+
let &pin mut Thing(ref pin mut _pinned_option) = pinned_thing;
58+
//~^ ERROR cannot project on type that is not `#[pin_v2]`
59+
}
60+
61+
fn main() {}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
error: cannot project on type that is not `#[pin_v2]`
2+
--> $DIR/pin-pattern-non-pin-project.rs:27:18
3+
|
4+
LL | let &pin mut NotPinProject(ref pin mut _x) = p;
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
note: type defined here
8+
--> $DIR/pin-pattern-non-pin-project.rs:15:1
9+
|
10+
LL | struct NotPinProject<T>(T);
11+
| ^^^^^^^^^^^^^^^^^^^^^^^
12+
help: add `#[pin_v2]` here
13+
|
14+
LL + #[pin_v2]
15+
LL | struct NotPinProject<T>(T);
16+
|
17+
18+
error: cannot project on type that is not `#[pin_v2]`
19+
--> $DIR/pin-pattern-non-pin-project.rs:32:18
20+
|
21+
LL | let &pin mut NotPinProjectStruct { x: ref pin mut _x } = p;
22+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
23+
|
24+
note: type defined here
25+
--> $DIR/pin-pattern-non-pin-project.rs:17:1
26+
|
27+
LL | struct NotPinProjectStruct<T> {
28+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
29+
help: add `#[pin_v2]` here
30+
|
31+
LL + #[pin_v2]
32+
LL | struct NotPinProjectStruct<T> {
33+
|
34+
35+
error: cannot project on type that is not `#[pin_v2]`
36+
--> $DIR/pin-pattern-non-pin-project.rs:37:20
37+
|
38+
LL | let &pin const NotPinProject(ref pin const _x) = p;
39+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
40+
|
41+
note: type defined here
42+
--> $DIR/pin-pattern-non-pin-project.rs:15:1
43+
|
44+
LL | struct NotPinProject<T>(T);
45+
| ^^^^^^^^^^^^^^^^^^^^^^^
46+
help: add `#[pin_v2]` here
47+
|
48+
LL + #[pin_v2]
49+
LL | struct NotPinProject<T>(T);
50+
|
51+
52+
error: cannot project on type that is not `#[pin_v2]`
53+
--> $DIR/pin-pattern-non-pin-project.rs:42:21
54+
|
55+
LL | if let &pin mut NotPinProjectEnum::Tuple(ref pin mut _x) = p {}
56+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
57+
|
58+
note: type defined here
59+
--> $DIR/pin-pattern-non-pin-project.rs:21:1
60+
|
61+
LL | enum NotPinProjectEnum<T> {
62+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
63+
help: add `#[pin_v2]` here
64+
|
65+
LL + #[pin_v2]
66+
LL | enum NotPinProjectEnum<T> {
67+
|
68+
69+
error: cannot project on type that is not `#[pin_v2]`
70+
--> $DIR/pin-pattern-non-pin-project.rs:47:21
71+
|
72+
LL | if let &pin mut NotPinProjectEnum::Struct { x: ref pin mut _x } = p {}
73+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
74+
|
75+
note: type defined here
76+
--> $DIR/pin-pattern-non-pin-project.rs:21:1
77+
|
78+
LL | enum NotPinProjectEnum<T> {
79+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
80+
help: add `#[pin_v2]` here
81+
|
82+
LL + #[pin_v2]
83+
LL | enum NotPinProjectEnum<T> {
84+
|
85+
86+
error: cannot project on type that is not `#[pin_v2]`
87+
--> $DIR/pin-pattern-non-pin-project.rs:57:18
88+
|
89+
LL | let &pin mut Thing(ref pin mut _pinned_option) = pinned_thing;
90+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
91+
|
92+
note: type defined here
93+
--> $DIR/pin-pattern-non-pin-project.rs:53:1
94+
|
95+
LL | struct Thing<T>(T);
96+
| ^^^^^^^^^^^^^^^
97+
help: add `#[pin_v2]` here
98+
|
99+
LL + #[pin_v2]
100+
LL | struct Thing<T>(T);
101+
|
102+
103+
error: aborting due to 6 previous errors
104+

tests/ui/pin-ergonomics/user-type-projection.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// Historically, this could occur when the code handling those projections did not know
1010
// about `&pin` patterns, and incorrectly treated them as plain `&`/`&mut` patterns instead.
1111

12+
#[pin_v2]
1213
struct Data {
1314
x: u32
1415
}

0 commit comments

Comments
 (0)