Rollup merge of #135733 - frank-king:feature/pin-self-receiver, r=oli-obk,traviscross

Implement `&pin const self` and `&pin mut self` sugars

This PR implements part of #130494.

It introduces the sugars `&pin const self` and `&pin mut self` for `self: Pin<&Self>` and `self: Pin<&mut Self>`.
This commit is contained in:
Michael Goulet 2025-03-06 12:22:08 -05:00 committed by GitHub
commit 91826b687c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 308 additions and 76 deletions

View File

@ -2641,6 +2641,8 @@ pub enum SelfKind {
Value(Mutability),
/// `&'lt self`, `&'lt mut self`
Region(Option<Lifetime>, Mutability),
/// `&'lt pin const self`, `&'lt pin mut self`
Pinned(Option<Lifetime>, Mutability),
/// `self: TYPE`, `mut self: TYPE`
Explicit(P<Ty>, Mutability),
}
@ -2650,6 +2652,8 @@ impl SelfKind {
match self {
SelfKind::Region(None, mutbl) => mutbl.ref_prefix_str().to_string(),
SelfKind::Region(Some(lt), mutbl) => format!("&{lt} {}", mutbl.prefix_str()),
SelfKind::Pinned(None, mutbl) => format!("&pin {}", mutbl.ptr_str()),
SelfKind::Pinned(Some(lt), mutbl) => format!("&{lt} pin {}", mutbl.ptr_str()),
SelfKind::Value(_) | SelfKind::Explicit(_, _) => {
unreachable!("if we had an explicit self, we wouldn't be here")
}
@ -2666,11 +2670,13 @@ impl Param {
if ident.name == kw::SelfLower {
return match self.ty.kind {
TyKind::ImplicitSelf => Some(respan(self.pat.span, SelfKind::Value(mutbl))),
TyKind::Ref(lt, MutTy { ref ty, mutbl })
| TyKind::PinnedRef(lt, MutTy { ref ty, mutbl })
TyKind::Ref(lt, MutTy { ref ty, mutbl }) if ty.kind.is_implicit_self() => {
Some(respan(self.pat.span, SelfKind::Region(lt, mutbl)))
}
TyKind::PinnedRef(lt, MutTy { ref ty, mutbl })
if ty.kind.is_implicit_self() =>
{
Some(respan(self.pat.span, SelfKind::Region(lt, mutbl)))
Some(respan(self.pat.span, SelfKind::Pinned(lt, mutbl)))
}
_ => Some(respan(
self.pat.span.to(self.ty.span),
@ -2712,6 +2718,15 @@ impl Param {
tokens: None,
}),
),
SelfKind::Pinned(lt, mutbl) => (
mutbl,
P(Ty {
id: DUMMY_NODE_ID,
kind: TyKind::PinnedRef(lt, MutTy { ty: infer_ty, mutbl }),
span,
tokens: None,
}),
),
};
Param {
attrs,

View File

@ -1783,6 +1783,13 @@ impl<'a> State<'a> {
self.print_mutability(*m, false);
self.word("self")
}
SelfKind::Pinned(lt, m) => {
self.word("&");
self.print_opt_lifetime(lt);
self.word("pin ");
self.print_mutability(*m, true);
self.word("self")
}
SelfKind::Explicit(typ, m) => {
self.print_mutability(*m, false);
self.word("self");

View File

@ -2954,14 +2954,27 @@ impl<'a> Parser<'a> {
}
_ => unreachable!(),
};
// is lifetime `n` tokens ahead?
let is_lifetime = |this: &Self, n| this.look_ahead(n, |t| t.is_lifetime());
// Is `self` `n` tokens ahead?
let is_isolated_self = |this: &Self, n| {
this.is_keyword_ahead(n, &[kw::SelfLower])
&& this.look_ahead(n + 1, |t| t != &token::PathSep)
};
// Is `pin const self` `n` tokens ahead?
let is_isolated_pin_const_self = |this: &Self, n| {
this.look_ahead(n, |token| token.is_ident_named(sym::pin))
&& this.is_keyword_ahead(n + 1, &[kw::Const])
&& is_isolated_self(this, n + 2)
};
// Is `mut self` `n` tokens ahead?
let is_isolated_mut_self =
|this: &Self, n| this.is_keyword_ahead(n, &[kw::Mut]) && is_isolated_self(this, n + 1);
// Is `pin mut self` `n` tokens ahead?
let is_isolated_pin_mut_self = |this: &Self, n| {
this.look_ahead(n, |token| token.is_ident_named(sym::pin))
&& is_isolated_mut_self(this, n + 1)
};
// Parse `self` or `self: TYPE`. We already know the current token is `self`.
let parse_self_possibly_typed = |this: &mut Self, m| {
let eself_ident = expect_self_ident(this);
@ -3012,26 +3025,35 @@ impl<'a> Parser<'a> {
let eself_lo = self.token.span;
let (eself, eself_ident, eself_hi) = match self.token.uninterpolate().kind {
token::And => {
let eself = if is_isolated_self(self, 1) {
// `&self`
self.bump();
SelfKind::Region(None, Mutability::Not)
} else if is_isolated_mut_self(self, 1) {
// `&mut self`
self.bump();
self.bump();
SelfKind::Region(None, Mutability::Mut)
} else if self.look_ahead(1, |t| t.is_lifetime()) && is_isolated_self(self, 2) {
// `&'lt self`
self.bump();
let lt = self.expect_lifetime();
SelfKind::Region(Some(lt), Mutability::Not)
} else if self.look_ahead(1, |t| t.is_lifetime()) && is_isolated_mut_self(self, 2) {
// `&'lt mut self`
self.bump();
let lt = self.expect_lifetime();
self.bump();
SelfKind::Region(Some(lt), Mutability::Mut)
let has_lifetime = is_lifetime(self, 1);
let skip_lifetime_count = has_lifetime as usize;
let eself = if is_isolated_self(self, skip_lifetime_count + 1) {
// `&{'lt} self`
self.bump(); // &
let lifetime = has_lifetime.then(|| self.expect_lifetime());
SelfKind::Region(lifetime, Mutability::Not)
} else if is_isolated_mut_self(self, skip_lifetime_count + 1) {
// `&{'lt} mut self`
self.bump(); // &
let lifetime = has_lifetime.then(|| self.expect_lifetime());
self.bump(); // mut
SelfKind::Region(lifetime, Mutability::Mut)
} else if is_isolated_pin_const_self(self, skip_lifetime_count + 1) {
// `&{'lt} pin const self`
self.bump(); // &
let lifetime = has_lifetime.then(|| self.expect_lifetime());
self.psess.gated_spans.gate(sym::pin_ergonomics, self.token.span);
self.bump(); // pin
self.bump(); // const
SelfKind::Pinned(lifetime, Mutability::Not)
} else if is_isolated_pin_mut_self(self, skip_lifetime_count + 1) {
// `&{'lt} pin mut self`
self.bump(); // &
let lifetime = has_lifetime.then(|| self.expect_lifetime());
self.psess.gated_spans.gate(sym::pin_ergonomics, self.token.span);
self.bump(); // pin
self.bump(); // mut
SelfKind::Pinned(lifetime, Mutability::Mut)
} else {
// `&not_self`
return Ok(None);

View File

@ -2359,6 +2359,21 @@ impl Rewrite for ast::Param {
}
}
fn rewrite_opt_lifetime(
context: &RewriteContext<'_>,
lifetime: Option<ast::Lifetime>,
) -> RewriteResult {
let Some(l) = lifetime else {
return Ok(String::new());
};
let mut result = l.rewrite_result(
context,
Shape::legacy(context.config.max_width(), Indent::empty()),
)?;
result.push(' ');
Ok(result)
}
fn rewrite_explicit_self(
context: &RewriteContext<'_>,
explicit_self: &ast::ExplicitSelf,
@ -2367,58 +2382,34 @@ fn rewrite_explicit_self(
shape: Shape,
has_multiple_attr_lines: bool,
) -> RewriteResult {
match explicit_self.node {
let self_str = match explicit_self.node {
ast::SelfKind::Region(lt, m) => {
let mut_str = format_mutability(m);
match lt {
Some(ref l) => {
let lifetime_str = l.rewrite_result(
context,
Shape::legacy(context.config.max_width(), Indent::empty()),
)?;
Ok(combine_strs_with_missing_comments(
context,
param_attrs,
&format!("&{lifetime_str} {mut_str}self"),
span,
shape,
!has_multiple_attr_lines,
)?)
}
None => Ok(combine_strs_with_missing_comments(
context,
param_attrs,
&format!("&{mut_str}self"),
span,
shape,
!has_multiple_attr_lines,
)?),
}
let lifetime_str = rewrite_opt_lifetime(context, lt)?;
format!("&{lifetime_str}{mut_str}self")
}
ast::SelfKind::Pinned(lt, m) => {
let mut_str = m.ptr_str();
let lifetime_str = rewrite_opt_lifetime(context, lt)?;
format!("&{lifetime_str}pin {mut_str} self")
}
ast::SelfKind::Explicit(ref ty, mutability) => {
let type_str = ty.rewrite_result(
context,
Shape::legacy(context.config.max_width(), Indent::empty()),
)?;
Ok(combine_strs_with_missing_comments(
context,
param_attrs,
&format!("{}self: {}", format_mutability(mutability), type_str),
span,
shape,
!has_multiple_attr_lines,
)?)
format!("{}self: {}", format_mutability(mutability), type_str)
}
ast::SelfKind::Value(mutability) => Ok(combine_strs_with_missing_comments(
context,
param_attrs,
&format!("{}self", format_mutability(mutability)),
span,
shape,
!has_multiple_attr_lines,
)?),
}
ast::SelfKind::Value(mutability) => format!("{}self", format_mutability(mutability)),
};
Ok(combine_strs_with_missing_comments(
context,
param_attrs,
&self_str,
span,
shape,
!has_multiple_attr_lines,
)?)
}
pub(crate) fn span_lo_for_param(param: &ast::Param) -> BytePos {

View File

@ -8,3 +8,13 @@ fn g<'a>(x: & 'a pin const i32) {}
fn h<'a>(x: & 'a pin
mut i32) {}
fn i(x: &pin mut i32) {}
struct Foo;
impl Foo {
fn f(&pin const self) {}
fn g<'a>(& 'a pin const self) {}
fn h<'a>(& 'a pin
mut self) {}
fn i(&pin mut self) {}
}

View File

@ -7,3 +7,12 @@ fn f(x: &pin const i32) {}
fn g<'a>(x: &'a pin const i32) {}
fn h<'a>(x: &'a pin mut i32) {}
fn i(x: &pin mut i32) {}
struct Foo;
impl Foo {
fn f(&pin const self) {}
fn g<'a>(&'a pin const self) {}
fn h<'a>(&'a pin mut self) {}
fn i(&pin mut self) {}
}

View File

@ -0,0 +1,24 @@
//@ pp-exact
#![feature(pin_ergonomics)]
#![allow(dead_code, incomplete_features)]
struct Foo;
impl Foo {
fn baz(&pin mut self) {}
fn baz_const(&pin const self) {}
fn baz_lt<'a>(&'a pin mut self) {}
fn baz_const_lt(&'_ pin const self) {}
}
fn foo(_: &pin mut Foo) {}
fn foo_lt<'a>(_: &'a pin mut Foo) {}
fn foo_const(_: &pin const Foo) {}
fn foo_const_lt(_: &'_ pin const Foo) {}
fn main() {}

View File

@ -0,0 +1,46 @@
//@ check-pass
#![feature(pin_ergonomics)]
#![allow(dead_code, incomplete_features)]
// Makes sure we can handle `&pin mut self` and `&pin const self` as sugar for
// `self: Pin<&mut Self>` and `self: Pin<&Self>`.
use std::pin::Pin;
struct Foo;
impl Foo {
fn baz(&pin mut self) {}
fn baz_const(&pin const self) {}
fn baz_lt<'a>(&'a pin mut self) {}
fn baz_const_lt(&'_ pin const self) {}
}
fn foo(_: &pin mut Foo) {}
fn foo_const(_: &pin const Foo) {}
fn bar(x: &pin mut Foo) {
// For the calls below to work we need to automatically reborrow,
// as if the user had written `foo(x.as_mut())`.
foo(x);
foo(x);
Foo::baz(x);
Foo::baz(x);
// make sure we can reborrow &mut as &.
foo_const(x);
Foo::baz_const(x);
let x: &pin const _ = Pin::new(&Foo);
foo_const(x); // make sure reborrowing from & to & works.
foo_const(x);
}
fn main() {}

View File

@ -7,9 +7,13 @@ struct Foo;
impl Foo {
fn foo(self: Pin<&mut Self>) {
}
fn foo_sugar(&pin mut self) {} //~ ERROR pinned reference syntax is experimental
fn foo_sugar_const(&pin const self) {} //~ ERROR pinned reference syntax is experimental
}
fn foo(x: Pin<&mut Foo>) {
fn foo(mut x: Pin<&mut Foo>) {
Foo::foo_sugar(x.as_mut());
Foo::foo_sugar_const(x.as_ref());
let _y: &pin mut Foo = x; //~ ERROR pinned reference syntax is experimental
}
@ -27,4 +31,38 @@ fn baz(mut x: Pin<&mut Foo>) {
fn baz_sugar(_: &pin const Foo) {} //~ ERROR pinned reference syntax is experimental
#[cfg(any())]
mod not_compiled {
use std::pin::Pin;
struct Foo;
impl Foo {
fn foo(self: Pin<&mut Self>) {
}
fn foo_sugar(&pin mut self) {} //~ ERROR pinned reference syntax is experimental
fn foo_sugar_const(&pin const self) {} //~ ERROR pinned reference syntax is experimental
}
fn foo(mut x: Pin<&mut Foo>) {
Foo::foo_sugar(x.as_mut());
Foo::foo_sugar_const(x.as_ref());
let _y: &pin mut Foo = x; //~ ERROR pinned reference syntax is experimental
}
fn foo_sugar(_: &pin mut Foo) {} //~ ERROR pinned reference syntax is experimental
fn bar(x: Pin<&mut Foo>) {
foo(x);
foo(x);
}
fn baz(mut x: Pin<&mut Foo>) {
x.foo();
x.foo();
}
fn baz_sugar(_: &pin const Foo) {} //~ ERROR pinned reference syntax is experimental
}
fn main() {}

View File

@ -1,5 +1,25 @@
error[E0658]: pinned reference syntax is experimental
--> $DIR/feature-gate-pin_ergonomics.rs:13:14
--> $DIR/feature-gate-pin_ergonomics.rs:10:19
|
LL | fn foo_sugar(&pin mut self) {}
| ^^^
|
= note: see issue #130494 <https://github.com/rust-lang/rust/issues/130494> for more information
= help: add `#![feature(pin_ergonomics)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
error[E0658]: pinned reference syntax is experimental
--> $DIR/feature-gate-pin_ergonomics.rs:11:25
|
LL | fn foo_sugar_const(&pin const self) {}
| ^^^
|
= note: see issue #130494 <https://github.com/rust-lang/rust/issues/130494> for more information
= help: add `#![feature(pin_ergonomics)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
error[E0658]: pinned reference syntax is experimental
--> $DIR/feature-gate-pin_ergonomics.rs:17:14
|
LL | let _y: &pin mut Foo = x;
| ^^^
@ -9,7 +29,7 @@ LL | let _y: &pin mut Foo = x;
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
error[E0658]: pinned reference syntax is experimental
--> $DIR/feature-gate-pin_ergonomics.rs:16:18
--> $DIR/feature-gate-pin_ergonomics.rs:20:18
|
LL | fn foo_sugar(_: &pin mut Foo) {}
| ^^^
@ -19,7 +39,7 @@ LL | fn foo_sugar(_: &pin mut Foo) {}
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
error[E0658]: pinned reference syntax is experimental
--> $DIR/feature-gate-pin_ergonomics.rs:28:18
--> $DIR/feature-gate-pin_ergonomics.rs:32:18
|
LL | fn baz_sugar(_: &pin const Foo) {}
| ^^^
@ -28,8 +48,58 @@ LL | fn baz_sugar(_: &pin const Foo) {}
= help: add `#![feature(pin_ergonomics)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
error[E0658]: pinned reference syntax is experimental
--> $DIR/feature-gate-pin_ergonomics.rs:43:23
|
LL | fn foo_sugar(&pin mut self) {}
| ^^^
|
= note: see issue #130494 <https://github.com/rust-lang/rust/issues/130494> for more information
= help: add `#![feature(pin_ergonomics)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
error[E0658]: pinned reference syntax is experimental
--> $DIR/feature-gate-pin_ergonomics.rs:44:29
|
LL | fn foo_sugar_const(&pin const self) {}
| ^^^
|
= note: see issue #130494 <https://github.com/rust-lang/rust/issues/130494> for more information
= help: add `#![feature(pin_ergonomics)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
error[E0658]: pinned reference syntax is experimental
--> $DIR/feature-gate-pin_ergonomics.rs:50:18
|
LL | let _y: &pin mut Foo = x;
| ^^^
|
= note: see issue #130494 <https://github.com/rust-lang/rust/issues/130494> for more information
= help: add `#![feature(pin_ergonomics)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
error[E0658]: pinned reference syntax is experimental
--> $DIR/feature-gate-pin_ergonomics.rs:53:22
|
LL | fn foo_sugar(_: &pin mut Foo) {}
| ^^^
|
= note: see issue #130494 <https://github.com/rust-lang/rust/issues/130494> for more information
= help: add `#![feature(pin_ergonomics)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
error[E0658]: pinned reference syntax is experimental
--> $DIR/feature-gate-pin_ergonomics.rs:65:22
|
LL | fn baz_sugar(_: &pin const Foo) {}
| ^^^
|
= note: see issue #130494 <https://github.com/rust-lang/rust/issues/130494> for more information
= help: add `#![feature(pin_ergonomics)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
error[E0382]: use of moved value: `x`
--> $DIR/feature-gate-pin_ergonomics.rs:20:9
--> $DIR/feature-gate-pin_ergonomics.rs:24:9
|
LL | fn bar(x: Pin<&mut Foo>) {
| - move occurs because `x` has type `Pin<&mut Foo>`, which does not implement the `Copy` trait
@ -39,15 +109,15 @@ LL | foo(x);
| ^ value used here after move
|
note: consider changing this parameter type in function `foo` to borrow instead if owning the value isn't necessary
--> $DIR/feature-gate-pin_ergonomics.rs:12:11
--> $DIR/feature-gate-pin_ergonomics.rs:14:15
|
LL | fn foo(x: Pin<&mut Foo>) {
| --- ^^^^^^^^^^^^^ this parameter takes ownership of the value
LL | fn foo(mut x: Pin<&mut Foo>) {
| --- ^^^^^^^^^^^^^ this parameter takes ownership of the value
| |
| in this function
error[E0382]: use of moved value: `x`
--> $DIR/feature-gate-pin_ergonomics.rs:25:5
--> $DIR/feature-gate-pin_ergonomics.rs:29:5
|
LL | fn baz(mut x: Pin<&mut Foo>) {
| ----- move occurs because `x` has type `Pin<&mut Foo>`, which does not implement the `Copy` trait
@ -66,7 +136,7 @@ help: consider reborrowing the `Pin` instead of moving it
LL | x.as_mut().foo();
| +++++++++
error: aborting due to 5 previous errors
error: aborting due to 12 previous errors
Some errors have detailed explanations: E0382, E0658.
For more information about an error, try `rustc --explain E0382`.