From 33d6acee91fa0fc86f769ed03b45f519d00b6ac4 Mon Sep 17 00:00:00 2001 From: Adrian Taylor Date: Thu, 19 Dec 2024 16:47:28 +0000 Subject: [PATCH 1/3] Arbitrary self types v2: unstable doc updates. Document these two feature flags. --- .../arbitrary-self-types-pointers.md | 57 +++++++ .../language-features/arbitrary-self-types.md | 154 ++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 src/doc/unstable-book/src/language-features/arbitrary-self-types-pointers.md create mode 100644 src/doc/unstable-book/src/language-features/arbitrary-self-types.md diff --git a/src/doc/unstable-book/src/language-features/arbitrary-self-types-pointers.md b/src/doc/unstable-book/src/language-features/arbitrary-self-types-pointers.md new file mode 100644 index 0000000000000..f73bcaffa8003 --- /dev/null +++ b/src/doc/unstable-book/src/language-features/arbitrary-self-types-pointers.md @@ -0,0 +1,57 @@ +# `arbitrary_self_types_pointers` + +The tracking issue for this feature is: [#44874] + +[#38788]: https://github.com/rust-lang/rust/issues/44874 + +------------------------ + +This extends the [arbitrary self types] feature to allow methods to +receive `self` by pointer. For example: + +```rust +#![feature(arbitrary_self_types_pointers)] + +struct A; + +impl A { + fn m(self: *const Self) {} +} + +fn main() { + let a = A; + let a_ptr: *const A = &a as *const A; + a_ptr.m(); +} +``` + +In general this is not advised: it's thought to be better practice to wrap +raw pointers in a newtype wrapper which implements the `core::ops::Receiver` +trait, then you need "only" the `arbitrary_self_types` feature. For example: + +```rust +#![feature(arbitrary_self_types)] +#![allow(dead_code)] + +struct A; + +impl A { + fn m(self: Wrapper) {} // can extract the pointer and do + // what it needs +} + +struct Wrapper(*const T); + +impl core::ops::Receiver for Wrapper { + type Target = T; +} + +fn main() { + let a = A; + let a_ptr: *const A = &a as *const A; + let a_wrapper = Wrapper(a_ptr); + a_wrapper.m(); +} +``` + +[arbitrary self types]: arbitrary-self-types.md diff --git a/src/doc/unstable-book/src/language-features/arbitrary-self-types.md b/src/doc/unstable-book/src/language-features/arbitrary-self-types.md new file mode 100644 index 0000000000000..2f8b52d404393 --- /dev/null +++ b/src/doc/unstable-book/src/language-features/arbitrary-self-types.md @@ -0,0 +1,154 @@ +# `arbitrary_self_types` + +The tracking issue for this feature is: [#44874] + +[#38788]: https://github.com/rust-lang/rust/issues/44874 + +------------------------ + +Allows any type implementing `core::ops::Receiver` to be used as the type +of `self` in a method belonging to `T`. + +For example, + +```rust +#![feature(arbitrary_self_types)] + +struct A; + +impl A { + fn f(self: SmartPtr) -> i32 { 1 } // note self type +} + +struct SmartPtr(T); + +impl core::ops::Receiver for SmartPtr { + type Target = T; +} + +fn main() { + let smart_ptr = SmartPtr(A); + assert_eq!(smart_ptr.f(), 1); +} +``` + +The `Receiver` trait has a blanket implementation for all `T: Deref`, so in fact +things like this work too: + +```rust +#![feature(arbitrary_self_types)] + +use std::rc::Rc; + +struct A; + +impl A { + fn f(self: Rc) -> i32 { 1 } // Rc implements Deref +} + +fn main() { + let smart_ptr = Rc::new(A); + assert_eq!(smart_ptr.f(), 1); +} +``` + +Interestingly, that works even without the `arbitrary_self_types` feature +- but that's because certain types are _effectively_ hard coded, including +`Rc`. ("Hard coding" isn't quite true; they use a lang-item called +`LegacyReceiver` to denote their special-ness in this way). With the +`arbitrary_self_types` feature, their special-ness goes away, and custom +smart pointers can achieve the same. + +## Changes to method lookup + +Method lookup previously used to work by stepping through the `Deref` +chain then using the resulting list of steps in two different ways: + +* To identify types that might contribute methods via their `impl` + blocks (inherent methods) or via traits +* To identify the types that the method receiver (`a` in the above + examples) can be converted to. + +With this feature, these lists are created by instead stepping through +the `Receiver` chain. However, a note is kept about whether the type +can be reached also via the `Deref` chain. + +The full chain (via `Receiver` hops) is used for the first purpose +(identifying relevant `impl` blocks and traits); whereas the shorter +list (reachable via `Deref`) is used for the second purpose. That's +because, to convert the method target (`a` in `a.b()`) to the self +type, Rust may need to be able to use `Deref::deref`. Type conversions, +then, can only proceed as far as the end of the `Deref` chain whereas +the longer `Receiver` chain can be used to explore more places where +useful methods might reside. + +## Types suitable for use as smart pointers + +This feature allows the creation of customised smart pointers - for example +your own equivalent to `Rc` or `Box` with whatever capabilities you like. +Those smart pointers can either implement `Deref` (if it's safe to +create a reference to the referent) or `Receiver` (if it isn't). + +Either way, smart pointer types should mostly _avoid having methods_. +Calling methods on a smart pointer leads to ambiguity about whether you're +aiming for a method on the pointer, or on the referent. + +Best practice is therefore to put smart pointer functionality into +associated functions instead - that's what's done in all the smart pointer +types within Rust's standard library which implement `Receiver`. + +If you choose to add any methods to your smart pointer type, your users +may run into errors from deshadowing, as described in the next section. + +## Avoiding shadowing + +With or without this feature, Rust emits an error if it finds two method +candidates, like this: + +```rust,compile_fail +use std::pin::Pin; +use std::pin::pin; + +struct A; + +impl A { + fn get_ref(self: Pin<&A>) {} +} + +fn main() { + let pinned_a: Pin<&A> = pin!(A).as_ref(); + let pinned_a: Pin<&A> = pinned_a.as_ref(); + pinned_a.get_ref(); // error[E0034]: multiple applicable items in scope +} +``` + +(this is why Rust's smart pointers are mostly carefully designed to avoid +having methods at all, and shouldn't add new methods in future.) + +With `arbitrary_self_types`, we take care to spot some other kinds of +conflict: + +```rust,compile_fail +#![feature(arbitrary_self_types)] + +use std::pin::Pin; +use std::pin::pin; + +struct A; + +impl A { + fn get_ref(self: &Pin<&A>) {} // note &Pin +} + +fn main() { + let pinned_a: Pin<&mut A> = pin!(A); + let pinned_a: Pin<&A> = pinned_a.as_ref(); + pinned_a.get_ref(); +} +``` + +This is to guard against the case where an inner (referent) type has a +method of a given name, taking the smart pointer by reference, and then +the smart pointer implementer adds a similar method taking self by value. +As noted in the previous section, the safe option is simply +not to add methods to smart pointers, and then these errors can't occur. From a6a707169d42e3ef2ca8ef243a6023a2c7df988b Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Tue, 24 Dec 2024 19:08:09 +0000 Subject: [PATCH 2/3] Consider arm to diverge if guard diverges --- compiler/rustc_hir_typeck/src/_match.rs | 12 ++++++++++-- tests/ui/reachable/expr_match.rs | 8 ++++++-- tests/ui/reachable/expr_match.stderr | 24 +++++++++++++++++++++++- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/_match.rs b/compiler/rustc_hir_typeck/src/_match.rs index 243313ee8767a..87300f5bb83c6 100644 --- a/compiler/rustc_hir_typeck/src/_match.rs +++ b/compiler/rustc_hir_typeck/src/_match.rs @@ -77,12 +77,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let mut prior_non_diverging_arms = vec![]; // Used only for diagnostics. let mut prior_arm = None; for arm in arms { + self.diverges.set(Diverges::Maybe); + if let Some(e) = &arm.guard { - self.diverges.set(Diverges::Maybe); self.check_expr_has_type_or_error(e, tcx.types.bool, |_| {}); + + // FIXME: If this is the first arm and the pattern is irrefutable, + // e.g. `_` or `x`, and the guard diverges, then the whole match + // may also be considered to diverge. We should warn on all subsequent + // arms, too, just like we do for diverging scrutinees above. } - self.diverges.set(Diverges::Maybe); + // N.B. We don't reset diverges here b/c we want to warn in the arm + // if the guard diverges, like: `x if { loop {} } => f()`, and we + // also want to consider the arm to diverge itself. let arm_ty = self.check_expr_with_expectation(arm.body, expected); all_arms_diverge &= self.diverges.get(); diff --git a/tests/ui/reachable/expr_match.rs b/tests/ui/reachable/expr_match.rs index 2fd26b54e15ee..1bae061c98421 100644 --- a/tests/ui/reachable/expr_match.rs +++ b/tests/ui/reachable/expr_match.rs @@ -21,9 +21,13 @@ fn d() { } fn e() { - // Here the compiler fails to figure out that the `println` is dead. - match () { () if return => (), () => return } + match () { + () if return => (), + //~^ ERROR unreachable expression + () => return, + } println!("I am dead"); + //~^ ERROR unreachable statement } fn f() { diff --git a/tests/ui/reachable/expr_match.stderr b/tests/ui/reachable/expr_match.stderr index d15208609cff8..ae202a6e0c34a 100644 --- a/tests/ui/reachable/expr_match.stderr +++ b/tests/ui/reachable/expr_match.stderr @@ -23,5 +23,27 @@ LL | println!("I am dead"); | = note: this error originates in the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) -error: aborting due to 2 previous errors +error: unreachable expression + --> $DIR/expr_match.rs:25:25 + | +LL | () if return => (), + | ------ ^^ unreachable expression + | | + | any code following this expression is unreachable + +error: unreachable statement + --> $DIR/expr_match.rs:29:5 + | +LL | / match () { +LL | | () if return => (), +LL | | +LL | | () => return, +LL | | } + | |_____- any code following this `match` expression is unreachable, as all arms diverge +LL | println!("I am dead"); + | ^^^^^^^^^^^^^^^^^^^^^ unreachable statement + | + = note: this error originates in the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 4 previous errors From 9bcd1dee9507fe68a31842b7676b9631d69781fa Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 25 Dec 2024 01:08:59 +0000 Subject: [PATCH 3/3] Actually print all the relevant parts of a coroutine in verbose mode --- compiler/rustc_middle/src/ty/print/pretty.rs | 6 ++++++ tests/ui/async-await/async-closures/def-path.stderr | 4 ++-- tests/ui/coroutine/print/coroutine-print-verbose-2.stderr | 4 ++-- tests/ui/coroutine/print/coroutine-print-verbose-3.stderr | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs index 8e89331ef03c3..9fe1caa4b58f5 100644 --- a/compiler/rustc_middle/src/ty/print/pretty.rs +++ b/compiler/rustc_middle/src/ty/print/pretty.rs @@ -841,6 +841,12 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write { p!( " upvar_tys=", print(args.as_coroutine().tupled_upvars_ty()), + " resume_ty=", + print(args.as_coroutine().resume_ty()), + " yield_ty=", + print(args.as_coroutine().yield_ty()), + " return_ty=", + print(args.as_coroutine().return_ty()), " witness=", print(args.as_coroutine().witness()) ); diff --git a/tests/ui/async-await/async-closures/def-path.stderr b/tests/ui/async-await/async-closures/def-path.stderr index cf25b2d2d2328..13ebaf67e54ad 100644 --- a/tests/ui/async-await/async-closures/def-path.stderr +++ b/tests/ui/async-await/async-closures/def-path.stderr @@ -5,11 +5,11 @@ LL | let x = async || {}; | -- the expected `async` closure body LL | LL | let () = x(); - | ^^ --- this expression has type `{static main::{closure#0}::{closure#0} upvar_tys=?16t witness=?6t}` + | ^^ --- this expression has type `{static main::{closure#0}::{closure#0} upvar_tys=?16t resume_ty=ResumeTy yield_ty=() return_ty=() witness=?6t}` | | | expected `async` closure body, found `()` | - = note: expected `async` closure body `{static main::{closure#0}::{closure#0} upvar_tys=?16t witness=?6t}` + = note: expected `async` closure body `{static main::{closure#0}::{closure#0} upvar_tys=?16t resume_ty=ResumeTy yield_ty=() return_ty=() witness=?6t}` found unit type `()` error: aborting due to 1 previous error diff --git a/tests/ui/coroutine/print/coroutine-print-verbose-2.stderr b/tests/ui/coroutine/print/coroutine-print-verbose-2.stderr index 2ab9d35f05ab9..8877d45dddad2 100644 --- a/tests/ui/coroutine/print/coroutine-print-verbose-2.stderr +++ b/tests/ui/coroutine/print/coroutine-print-verbose-2.stderr @@ -9,7 +9,7 @@ LL | | drop(a); LL | | }); | |______^ coroutine is not `Sync` | - = help: within `{main::{closure#0} upvar_tys=() witness={main::{closure#0}}}`, the trait `Sync` is not implemented for `NotSync` + = help: within `{main::{closure#0} upvar_tys=() resume_ty=() yield_ty=() return_ty=() witness={main::{closure#0}}}`, the trait `Sync` is not implemented for `NotSync` note: coroutine is not `Sync` as this value is used across a yield --> $DIR/coroutine-print-verbose-2.rs:20:9 | @@ -34,7 +34,7 @@ LL | | drop(a); LL | | }); | |______^ coroutine is not `Send` | - = help: within `{main::{closure#1} upvar_tys=() witness={main::{closure#1}}}`, the trait `Send` is not implemented for `NotSend` + = help: within `{main::{closure#1} upvar_tys=() resume_ty=() yield_ty=() return_ty=() witness={main::{closure#1}}}`, the trait `Send` is not implemented for `NotSend` note: coroutine is not `Send` as this value is used across a yield --> $DIR/coroutine-print-verbose-2.rs:27:9 | diff --git a/tests/ui/coroutine/print/coroutine-print-verbose-3.stderr b/tests/ui/coroutine/print/coroutine-print-verbose-3.stderr index dce45aeae56a9..2f9f20cf1ffbf 100644 --- a/tests/ui/coroutine/print/coroutine-print-verbose-3.stderr +++ b/tests/ui/coroutine/print/coroutine-print-verbose-3.stderr @@ -11,7 +11,7 @@ LL | | }; | |_____^ expected `()`, found coroutine | = note: expected unit type `()` - found coroutine `{main::{closure#0} upvar_tys=?4t witness=?6t}` + found coroutine `{main::{closure#0} upvar_tys=?4t resume_ty=() yield_ty=i32 return_ty=&'?1 str witness=?6t}` error: aborting due to 1 previous error