Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refining_impl_trait: Decide whether explicit opt-in is needed #121718

Open
tmandry opened this issue Feb 28, 2024 · 11 comments
Open

refining_impl_trait: Decide whether explicit opt-in is needed #121718

tmandry opened this issue Feb 28, 2024 · 11 comments
Labels
C-discussion Category: Discussion or questions that doesn't represent real issues. F-refine `#![feature(refine)]`; RFC #3245 T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@tmandry
Copy link
Member

tmandry commented Feb 28, 2024

What is refining_impl_trait?

The refining_impl_trait lints detect usages of return-position impl traits in trait signatures which are refined by implementations, meaning the implementation adds information about the return type that is not present in the trait.

Example

pub trait AsDisplay {
    fn as_display(&self) -> impl Display;
}

impl<'s> AsDisplay for &'s str {
    fn as_display(&self) -> Self {
        *self
    }
}

fn main() {
    // users can observe that the return type of
    // `<&str as AsDisplay>::as_display()` is `&str`.
    let x: &str = "".as_display();
}

The above code compiles, but produces a warning:

warning: impl trait in impl method signature does not match trait method signature
 --> src/main.rs:8:29
  |
4 |     fn as_display(&self) -> impl Display;
  |                             ------------ return type from trait method defined here
...
8 |     fn as_display(&self) -> Self {
  |                             ^^^^
  |
  = note: add `#[allow(refining_impl_trait)]` if it is intended for this to be part of the public API of this crate
  = note: `#[warn(refining_impl_trait)]` on by default
help: replace the return type so that it matches the trait
  |
8 |     fn as_display(&self) -> impl std::fmt::Display {
  |                             ~~~~~~~~~~~~~~~~~~~~~~

Explanation

Callers of methods for types where the implementation is known are able to observe the types written in the impl signature. This may be intended behavior, but may also lead to implementation details being revealed unintentionally. In particular, it may pose a semver hazard for authors of libraries who do not wish to make stronger guarantees about the types than what is written in the trait signature.

Future possibilities

Add an explicit #[refine] attribute. This would make it easier to opt in to refining on a per-method basis and make the refine check feel more like a builtin feature of the language.

Make refining_impl_trait error-by-default in a future edition. This would mean we are committed to explicit refinement for impl Traits. Since this would happen over an edition, we could choose to go even further and make it a hard error so you could not blanket-allow it in your crate.

Keep all refining_impl_trait warn-by-default.

Make refining_impl_trait_internal allow-by-default, but keep refining_impl_trait_reachable warn-by-default. This would mean that we think semver hazards for publicly-reachable traits in a crate are important enough to lint against. But for uses internal to a crate, opting in is unnecessary overhead for the user.

Make all refining_impl_trait allow-by-default. This would mean that we think most uses of refinement for return-position impl Trait are deliberate and unlikely to cause semver hazards or other unexpected behavior.

Discussion

The language team is collecting feedback about this lint. We would like to know

  • Whether users expect refinement to require an explicit opt-in.
  • Whether users see value in the refinement check to prevent accidental leakage of implementation details.
  • Whether users expect refinement to work the same between publicly-reachable impls and internal-only impls.
  • Whether users would like to see a #[refine] attribute in the future.
@tmandry tmandry added T-lang Relevant to the language team, which will review and decide on the PR/issue. C-discussion Category: Discussion or questions that doesn't represent real issues. F-refine `#![feature(refine)]`; RFC #3245 labels Feb 28, 2024
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Feb 28, 2024
@jieyouxu jieyouxu removed the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Feb 28, 2024
fmease added a commit to fmease/rust that referenced this issue Mar 16, 2024
…-errors

Split refining_impl_trait lint into _reachable, _internal variants

As discussed in rust-lang#119535 (comment):

> We discussed this today in triage and developed a consensus to:
>
> * Add a separate lint against impls that refine a return type defined with RPITIT even when the trait is not crate public.
> * Place that in a lint group along with the analogous crate public lint.
> * Create an issue to solicit feedback on these lints (or perhaps two separate ones).
> * Have the warnings displayed with each lint reference this issue in a similar manner to how we do that today with the required `Self: '0'` bound on GATs.
> * Make a note to review this feedback on 2-3 release cycles.

This points users to rust-lang#121718 to leave feedback.
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Mar 17, 2024
Rollup merge of rust-lang#121720 - tmandry:split-refining, r=compiler-errors

Split refining_impl_trait lint into _reachable, _internal variants

As discussed in rust-lang#119535 (comment):

> We discussed this today in triage and developed a consensus to:
>
> * Add a separate lint against impls that refine a return type defined with RPITIT even when the trait is not crate public.
> * Place that in a lint group along with the analogous crate public lint.
> * Create an issue to solicit feedback on these lints (or perhaps two separate ones).
> * Have the warnings displayed with each lint reference this issue in a similar manner to how we do that today with the required `Self: '0'` bound on GATs.
> * Make a note to review this feedback on 2-3 release cycles.

This points users to rust-lang#121718 to leave feedback.
@schuelermine
Copy link
Contributor

User feedback:
Given my experience in other languages I expected refinement to be available by default and not linted against.
I don’t care how it works, I just want it to exist (so #[refine] is fine by me).

@dhardy
Copy link
Contributor

dhardy commented Apr 24, 2024

Personally I like #[refine], but...

... is it refinement when the impl is on a private type, not in any way observable outside the current module? In this particular case I don't see any need to warn (assuming the lint remains just a warning).

@rdrpenguin04
Copy link

I don't really care; I'd prefer not needing to opt-in, but I'm fine either way.

@thynson
Copy link

thynson commented Jun 18, 2024

Make refining_impl_trait_internal allow-by-default, but keep refining_impl_trait_reachable warn-by-default.

Prefered in this way with #[refine] introduced.

@jean-pierreBoth
Copy link

#[allow(refining_impl_trait)] is ok for me

facebook-github-bot pushed a commit to facebook/buck2 that referenced this issue Jul 11, 2024
Summary:
Release notes: https://blog.rust-lang.org/2024/06/13/Rust-1.79.0.html

Stabilized features:

- `absolute_path`
- `associated_type_bounds`
- `inline_const`
- `pointer_is_aligned`
- `slice_ptr_len`

This release raises a new warning when a trait impl contains a signature that is more refined than the corresponding signature in the trait, impacting `buck2_query`.

```lang=text
warning: impl trait in impl method signature does not match trait method signature
  --> fbcode/buck2/app/buck2_query/src/query/environment/tests.rs:73:30
   |
73 |     fn deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::Key> + Send + 'a> {
   |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
  ::: fbcode/buck2/app/buck2_query/src/query/environment.rs:98:30
   |
98 |     fn deps<'a>(&'a self) -> impl Iterator<Item = &'a Self::Key> + Send + 'a;
   |                              ----------------------------------------------- return type from trait method defined here
   |
   = note: add `#[allow(refining_impl_trait)]` if it is intended for this to be part of the public API of this crate
   = note: we are soliciting feedback, see issue #121718 <rust-lang/rust#121718> for more information
   = note: `#[warn(refining_impl_trait_internal)]` on by default
help: replace the return type so that it matches the trait
   |
73 |     fn deps<'a>(&'a self) -> impl Iterator<Item = &'a <Self as node::LabeledNode>::Key> + std::marker::Send + 'a {
   |                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```

It also contains changes to the `dead_code` lint, which are addressed in {D59622902} and {D59623034}.

Reviewed By: zertosh

Differential Revision: D58982552

fbshipit-source-id: a17eaf91d07234209fddf5cbdebce4424dda57ae
facebook-github-bot pushed a commit to facebookincubator/buck2-change-detector that referenced this issue Jul 11, 2024
Summary:
Release notes: https://blog.rust-lang.org/2024/06/13/Rust-1.79.0.html

Stabilized features:

- `absolute_path`
- `associated_type_bounds`
- `inline_const`
- `pointer_is_aligned`
- `slice_ptr_len`

This release raises a new warning when a trait impl contains a signature that is more refined than the corresponding signature in the trait, impacting `buck2_query`.

```lang=text
warning: impl trait in impl method signature does not match trait method signature
  --> fbcode/buck2/app/buck2_query/src/query/environment/tests.rs:73:30
   |
73 |     fn deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::Key> + Send + 'a> {
   |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
  ::: fbcode/buck2/app/buck2_query/src/query/environment.rs:98:30
   |
98 |     fn deps<'a>(&'a self) -> impl Iterator<Item = &'a Self::Key> + Send + 'a;
   |                              ----------------------------------------------- return type from trait method defined here
   |
   = note: add `#[allow(refining_impl_trait)]` if it is intended for this to be part of the public API of this crate
   = note: we are soliciting feedback, see issue #121718 <rust-lang/rust#121718> for more information
   = note: `#[warn(refining_impl_trait_internal)]` on by default
help: replace the return type so that it matches the trait
   |
73 |     fn deps<'a>(&'a self) -> impl Iterator<Item = &'a <Self as node::LabeledNode>::Key> + std::marker::Send + 'a {
   |                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```

It also contains changes to the `dead_code` lint, which are addressed in {D59622902} and {D59623034}.

Reviewed By: zertosh

Differential Revision: D58982552

fbshipit-source-id: a17eaf91d07234209fddf5cbdebce4424dda57ae
facebook-github-bot pushed a commit to facebook/ocamlrep that referenced this issue Jul 11, 2024
Summary:
Release notes: https://blog.rust-lang.org/2024/06/13/Rust-1.79.0.html

Stabilized features:

- `absolute_path`
- `associated_type_bounds`
- `inline_const`
- `pointer_is_aligned`
- `slice_ptr_len`

This release raises a new warning when a trait impl contains a signature that is more refined than the corresponding signature in the trait, impacting `buck2_query`.

```lang=text
warning: impl trait in impl method signature does not match trait method signature
  --> fbcode/buck2/app/buck2_query/src/query/environment/tests.rs:73:30
   |
73 |     fn deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::Key> + Send + 'a> {
   |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
  ::: fbcode/buck2/app/buck2_query/src/query/environment.rs:98:30
   |
98 |     fn deps<'a>(&'a self) -> impl Iterator<Item = &'a Self::Key> + Send + 'a;
   |                              ----------------------------------------------- return type from trait method defined here
   |
   = note: add `#[allow(refining_impl_trait)]` if it is intended for this to be part of the public API of this crate
   = note: we are soliciting feedback, see issue #121718 <rust-lang/rust#121718> for more information
   = note: `#[warn(refining_impl_trait_internal)]` on by default
help: replace the return type so that it matches the trait
   |
73 |     fn deps<'a>(&'a self) -> impl Iterator<Item = &'a <Self as node::LabeledNode>::Key> + std::marker::Send + 'a {
   |                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```

It also contains changes to the `dead_code` lint, which are addressed in {D59622902} and {D59623034}.

Reviewed By: zertosh

Differential Revision: D58982552

fbshipit-source-id: a17eaf91d07234209fddf5cbdebce4424dda57ae
facebook-github-bot pushed a commit to facebook/starlark-rust that referenced this issue Jul 11, 2024
Summary:
Release notes: https://blog.rust-lang.org/2024/06/13/Rust-1.79.0.html

Stabilized features:

- `absolute_path`
- `associated_type_bounds`
- `inline_const`
- `pointer_is_aligned`
- `slice_ptr_len`

This release raises a new warning when a trait impl contains a signature that is more refined than the corresponding signature in the trait, impacting `buck2_query`.

```lang=text
warning: impl trait in impl method signature does not match trait method signature
  --> fbcode/buck2/app/buck2_query/src/query/environment/tests.rs:73:30
   |
73 |     fn deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::Key> + Send + 'a> {
   |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
  ::: fbcode/buck2/app/buck2_query/src/query/environment.rs:98:30
   |
98 |     fn deps<'a>(&'a self) -> impl Iterator<Item = &'a Self::Key> + Send + 'a;
   |                              ----------------------------------------------- return type from trait method defined here
   |
   = note: add `#[allow(refining_impl_trait)]` if it is intended for this to be part of the public API of this crate
   = note: we are soliciting feedback, see issue #121718 <rust-lang/rust#121718> for more information
   = note: `#[warn(refining_impl_trait_internal)]` on by default
help: replace the return type so that it matches the trait
   |
73 |     fn deps<'a>(&'a self) -> impl Iterator<Item = &'a <Self as node::LabeledNode>::Key> + std::marker::Send + 'a {
   |                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```

It also contains changes to the `dead_code` lint, which are addressed in {D59622902} and {D59623034}.

Reviewed By: zertosh

Differential Revision: D58982552

fbshipit-source-id: a17eaf91d07234209fddf5cbdebce4424dda57ae
facebook-github-bot pushed a commit to facebookincubator/reindeer that referenced this issue Jul 11, 2024
Summary:
Release notes: https://blog.rust-lang.org/2024/06/13/Rust-1.79.0.html

Stabilized features:

- `absolute_path`
- `associated_type_bounds`
- `inline_const`
- `pointer_is_aligned`
- `slice_ptr_len`

This release raises a new warning when a trait impl contains a signature that is more refined than the corresponding signature in the trait, impacting `buck2_query`.

```lang=text
warning: impl trait in impl method signature does not match trait method signature
  --> fbcode/buck2/app/buck2_query/src/query/environment/tests.rs:73:30
   |
73 |     fn deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::Key> + Send + 'a> {
   |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
  ::: fbcode/buck2/app/buck2_query/src/query/environment.rs:98:30
   |
98 |     fn deps<'a>(&'a self) -> impl Iterator<Item = &'a Self::Key> + Send + 'a;
   |                              ----------------------------------------------- return type from trait method defined here
   |
   = note: add `#[allow(refining_impl_trait)]` if it is intended for this to be part of the public API of this crate
   = note: we are soliciting feedback, see issue #121718 <rust-lang/rust#121718> for more information
   = note: `#[warn(refining_impl_trait_internal)]` on by default
help: replace the return type so that it matches the trait
   |
73 |     fn deps<'a>(&'a self) -> impl Iterator<Item = &'a <Self as node::LabeledNode>::Key> + std::marker::Send + 'a {
   |                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```

It also contains changes to the `dead_code` lint, which are addressed in {D59622902} and {D59623034}.

Reviewed By: zertosh

Differential Revision: D58982552

fbshipit-source-id: a17eaf91d07234209fddf5cbdebce4424dda57ae
facebook-github-bot pushed a commit to facebookincubator/gazebo that referenced this issue Jul 11, 2024
Summary:
Release notes: https://blog.rust-lang.org/2024/06/13/Rust-1.79.0.html

Stabilized features:

- `absolute_path`
- `associated_type_bounds`
- `inline_const`
- `pointer_is_aligned`
- `slice_ptr_len`

This release raises a new warning when a trait impl contains a signature that is more refined than the corresponding signature in the trait, impacting `buck2_query`.

```lang=text
warning: impl trait in impl method signature does not match trait method signature
  --> fbcode/buck2/app/buck2_query/src/query/environment/tests.rs:73:30
   |
73 |     fn deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::Key> + Send + 'a> {
   |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
  ::: fbcode/buck2/app/buck2_query/src/query/environment.rs:98:30
   |
98 |     fn deps<'a>(&'a self) -> impl Iterator<Item = &'a Self::Key> + Send + 'a;
   |                              ----------------------------------------------- return type from trait method defined here
   |
   = note: add `#[allow(refining_impl_trait)]` if it is intended for this to be part of the public API of this crate
   = note: we are soliciting feedback, see issue #121718 <rust-lang/rust#121718> for more information
   = note: `#[warn(refining_impl_trait_internal)]` on by default
help: replace the return type so that it matches the trait
   |
73 |     fn deps<'a>(&'a self) -> impl Iterator<Item = &'a <Self as node::LabeledNode>::Key> + std::marker::Send + 'a {
   |                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```

It also contains changes to the `dead_code` lint, which are addressed in {D59622902} and {D59623034}.

Reviewed By: zertosh

Differential Revision: D58982552

fbshipit-source-id: a17eaf91d07234209fddf5cbdebce4424dda57ae
@JarredAllen
Copy link
Contributor

(not sure if this is the right thread for this) I just hit a funky case that caused this lint where I didn't expect it, in code like this:

trait DoSomething {
    fn do_something() -> impl Future;
}

struct SomethingDoer;
impl DoSomething for SomethingDoer {
    async fn do_something() {}
}

If I specify the output type of do_something (e.g. fn do_something() -> impl Future<Output = ()>), then the async fn implementation for the trait is allowed (I guess the implementation here desugars to -> impl Future<Output = ()>), but then the trait has to impose a return type on the do_something() method.

Generally, I'm fine with whatever #[refine]/#[allow(..)] combination is deemed desirable, but I'd expect the code snippet I put here to work without requiring any annotations (either through refining or through changing the desugaring for async fn in this case, though it's probably too late to change the desugaring).

@maurer
Copy link
Contributor

maurer commented Aug 12, 2024

Ran into this dealing with impl Trait as an unconstrained part of a trait function signature.

Simplified case

Because the Ok branch is never constructed, the Rust compiler doesn't know what type to pick for that branch. Refining the type gives Rust an option. This isn't quite the same as what's being described, as we don't actually necessarily want users to have access to this type in the signature, we just want to give Rust a candidate for the existential so that it can finish compilation.

This can be worked around by making an explicit internal copy of the function, but this doesn't seem great.

@JarredAllen
Copy link
Contributor

Ran into this dealing with impl Trait as an unconstrained part of a trait function signature.

Simplified case

Because the Ok branch is never constructed, the Rust compiler doesn't know what type to pick for that branch. Refining the type gives Rust an option. This isn't quite the same as what's being described, as we don't actually necessarily want users to have access to this type in the signature, we just want to give Rust a candidate for the existential so that it can finish compilation.

This can be worked around by making an explicit internal copy of the function, but this doesn't seem great.

You can also get around it by annotating the None with an explicit type for the generic: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=a46cbfd8d7abda90d9862dc413bacbb9

impl P for str {
    fn try_iter(&self) -> Option<impl Iterator<Item = u32>> {
        None::<std::array::IntoIter<u32, 0>>
    }
}

Still not perfect that you have to annotate the None with an explicit type even though it's never used, but that lets you avoid refining without writing another function.

@obi1kenobi
Copy link
Member

Generic bounds applied to trait methods also have some unexpected quirky behavior under refinement — I opened #129251 to avoid cluttering this issue with a tangent.

@noakodaren
Copy link

I think it should warn for both public and internal impls; leaking internal implementation details to oneself should also be a conscious decision. It would be nice if it would become a hard error, but it is probably not worth it. In any case, #[refine] would be nice.

@j-a-m-l
Copy link

j-a-m-l commented Oct 8, 2024

For me refinements are useful, and I expect that crates that use my library could avoid unnecessary conversions.
This is an example of my current use case:

pub trait Whatever {
    fn id(&self) -> impl ToString;
}

pub struct InternalObjectString<'a> {
    id: &'a str,
}

impl<'a> Whatever for InternalObjectString<'a> {
    fn id(&self) -> String {
        self.id.to_string()
    }
}

pub struct InternalObjectInt {
    id: u32
}

impl<'a> Whatever for InternalObjectInt {
    fn id(&self) -> u64 {
        self.id as u64
    }
}

// In other crate
use whatever::Whatever;

struct ExternalCrateObject {
    id: u32,
}

impl Whatever for ExternalCrateObject {
    fn id(&self) -> u32 {
        self.id
    }
}

In my case, types that implement Whatever could be used by the user to unify different implementations or they could be used alone, so it is not necessary to transform them to the same type always.
For instance, they may be interested only in InternalObjectInt or they may need to implement their own version (ExternalCrateObject).

I also prefer that the type that implements the trait could be stricter and more explicit about the types of its methods.

So:

  • Refinements should be allowed in the public API, using an attribute or whatever.
  • Annotating them for the internal API maybe is unnecessary and adds more overhead, but it could be useful for the public API and the compiler could detect those leaks.

About introducing #[refine]:
Using a new attribute such as #[refine] would be nice, but using #[allow(refining_impl_trait)] is fine.

I'd introduce it only if it is necessary for the internal API too, or if it can be used in more contexts. Some ideas:

  • Using it to limit the feature to the public API #[refine(pub)] or internal API #[refinement(crate)] in case that it must be set explicitly.
  • Exposing the public API as an specific type:
pub trait Whatever {
    #[refine(String)]
    fn id(&self) -> impl ToString;
}
  • Using #[refine] at other levels, such evaluating the return at compile time when possible:
trait Number {
    #[refine(return < 0)]
    const fn negative(&self) -> impl Into<i32> {
        -1
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-discussion Category: Discussion or questions that doesn't represent real issues. F-refine `#![feature(refine)]`; RFC #3245 T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests