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

Trait argument from associated type has conflicting impl #99940

Open
conradludgate opened this issue Jul 30, 2022 · 8 comments
Open

Trait argument from associated type has conflicting impl #99940

conradludgate opened this issue Jul 30, 2022 · 8 comments
Labels
A-associated-items Area: Associated items (types, constants & functions) A-coherence Area: Coherence C-bug Category: This is a bug. T-types Relevant to the types team, which will review and decide on the PR/issue.

Comments

@conradludgate
Copy link
Contributor

conradludgate commented Jul 30, 2022

I tried this code:

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=5a6bc60775d41ce5ef3ff9656d489c51

#![feature(try_trait_v2)]
struct Flip<T>(T);
impl<T> std::ops::FromResidual<Flip<T>> for Option<T>
{
    fn from_residual(residual: Flip<T>) -> Self {
        Some(residual.0)
    }
}

I expected to see this happen: Code compiles successfully

Instead, this happened:

error[[E0119]](https://doc.rust-lang.org/nightly/error-index.html#E0119): conflicting implementations of trait `std::ops::FromResidual<Flip<_>>` for type `std::option::Option<_>`
 --> src/lib.rs:3:1
  |
3 | impl<T> std::ops::FromResidual<Flip<T>> for Option<T>
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: conflicting implementation in crate `core`:
          - impl<T> FromResidual for Option<T>;

As you you see, the stdlib has

impl<T> FromResidual for Option<T> {}

Which makes use of a default type arg on FromResidual - <Self as Try>::Residual. For Option<T>, that is Option<Infallible>.

So apparently, FromResidual<Option<Infallible>> conflicts with FromResidual<Flip<T>>.

Playing devil's advocate, we could assume that FromResidual is being conservative with its conflicts because Try::Residual could change, but it could never change to a type I own.

Meta

rustc --version --verbose:

rustc 1.64.0-nightly (3924dac7b 2022-07-29)
binary: rustc
commit-hash: 3924dac7bb29bc8eb348059c901e8f912399c857
commit-date: 2022-07-29
host: aarch64-apple-darwin
release: 1.64.0-nightly
LLVM version: 14.0.6
@conradludgate conradludgate added the C-bug Category: This is a bug. label Jul 30, 2022
@conradludgate
Copy link
Contributor Author

I want this to focus away from FromResidual and more towards the type conflicts. FromResidual is just the first trait I found with this behaviour. I believe this would affect other traits too.

Also note, this requires a crate boundary. core already implements both

impl<T> ops::FromResidual for Option<T> {}
impl<T> ops::FromResidual<ops::Yeet<()>> for Option<T> {}

which is exactly what I'm trying to accomplish - just that it's conflicting in my crate

@conradludgate conradludgate changed the title Default trait argument through 2nd trait has conflicting impl Trait argument from associated type has conflicting impl Jul 30, 2022
togetherwithasteria added a commit to waylovely-project/robusta that referenced this issue Jul 30, 2022
This involves many hacky things including implementing some of the traits to the JOption enum instead!!

That's because there are some issues in the compiler like this one! rust-lang/rust#99940

Signed-off-by: Nefo Fortressia <[email protected]>
@conradludgate
Copy link
Contributor Author

It was suggested that this might be due to the generic Flip<T>, but I get the same error when non-generic:

#![feature(try_trait_v2)]
struct Flip;
impl<T> std::ops::FromResidual<Flip> for Option<T>
{
    fn from_residual(residual: Flip) -> Self {
        None
    }
}

@lcnr lcnr added the T-types Relevant to the types team, which will review and decide on the PR/issue. label Aug 1, 2022
@lcnr
Copy link
Contributor

lcnr commented Aug 1, 2022

minimization

// crate `dep`
pub trait Assoc {
    type Ty;
}
impl Assoc for () {
    type Ty = ();
}

pub trait Trait {}
impl Trait for <() as Assoc>::Ty {} // err
// impl Trait for () {} // ok

// local crate
struct LocalTy;
impl dep::Trait for LocalTy {}

results in

error[E0119]: conflicting implementations of trait `Trait<()>` for type `()`
  --> dep/src/lib.rs:10:1
   |
9  | impl Trait<<() as Assoc>::Ty> for () {} // err
   | ------------------------------------ first implementation here
10 | impl Trait<()> for () {}
   | ^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `()`

this is a language/compiler bug. we're missing a normalization during coherence1, cc @rust-lang/types

Footnotes

  1. ofc it's more complicated than this, see the next comment

@lcnr
Copy link
Contributor

lcnr commented Aug 1, 2022

normalizing the trait refs during coherence we end up with the obligation <() as Assoc>::Ty == LocalTy which should result in an error, proving that these two impls cannot overlap. To check that this obligation holds, we have to normalize <() as Assoc>::Ty.

For this we try to get the impl candidate for (): Assoc.

let impl_source = match selcx.select(&trait_obligation) {

This should succeed, but instead gets converted to ambiguity as the is_knowable check fails:

if let Some(conflict) = self.is_knowable(stack) {

We currently always convert trait obligations to ambiguity if either an upstream or downstream crate may implement that trait ref in the future. Most of the time, that is fine as coherence only checks whether some obligation definitely does not hold. So if some obligations get downgraded from success to ambiguity, this doesn't matter.

The only time where that matters is if this obligation succeeding may cause another obligation to fail. This is the case when checking projection predicates.

I think the correct fix here is to move the intercrate field into the InferCtxt. While that isn't perfect, as ideally the inference context shouldn't need to know about trait solving, there isn't any place which only sometimes wants to use intercrate mode, so it being part of the InferCtxt is a lot safer than trying to thread it into every fulfillment context. This is the same reasoning as in #99501

@lcnr
Copy link
Contributor

lcnr commented Aug 1, 2022

going to look into this at some point after rustconf

@rustbot claim

@lcnr
Copy link
Contributor

lcnr commented Aug 1, 2022

another case where normalization being ambiguous prevents a later error is

// dep
pub trait Assoc {
    type Ty;
}
impl Assoc for () {
    type Ty = ();
}

// local crate
trait Trait {}

impl Trait for <() as dep::Assoc>::Ty {} // err

trait Bar {}
impl<T: Bar> Trait for T {} // this errors, but we know that `(): Bar` does not hold.

unlike the first case, this one is order dependent as T: Bar remains ambiguous until we've normalized () as dep::Assoc>::Ty. Afaik to correctly fix this we have to stop using predicate_may_hold but instead have to use a proper fulfillment context

.find(|o| !selcx.predicate_may_hold_fatal(o));

@b-naber
Copy link
Contributor

b-naber commented Sep 7, 2022

normalizing the trait refs during coherence we end up with the obligation <() as Assoc>::Ty == LocalTy which should result in an error, proving that these two impls cannot overlap. To check that this obligation holds, we have to normalize <() as Assoc>::Ty.

For this we try to get the impl candidate for (): Assoc.

let impl_source = match selcx.select(&trait_obligation) {

This should succeed, but instead gets converted to ambiguity as the is_knowable check fails:

if let Some(conflict) = self.is_knowable(stack) {

We currently always convert trait obligations to ambiguity if either an upstream or downstream crate may implement that trait ref in the future. Most of the time, that is fine as coherence only checks whether some obligation definitely does not hold. So if some obligations get downgraded from success to ambiguity, this doesn't matter.

The only time where that matters is if this obligation succeeding may cause another obligation to fail. This is the case when checking projection predicates.

I think the correct fix here is to move the intercrate field into the InferCtxt. While that isn't perfect, as ideally the inference context shouldn't need to know about trait solving, there isn't any place which only sometimes wants to use intercrate mode, so it being part of the InferCtxt is a lot safer than trying to thread it into every fulfillment context. This is the same reasoning as in #99501

Not sure how similar this is to your approach, but I previously tried to fix this by changing the intercrate flag and got errors during a bors run, which I wasn't able to solve: #86360

@lcnr lcnr removed their assignment Aug 1, 2023
@lcnr lcnr added A-coherence Area: Coherence A-associated-items Area: Associated items (types, constants & functions) labels May 12, 2024
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Aug 14, 2024
…=scottmcm

Explicitly specify type parameter on FromResidual for Option and ControlFlow.

~~Remove type parameter default `R = <Self as Try>::Residual` from `FromResidual`~~ _Specify default type parameter on `FromResidual` impls in the stdlib_ to work around rust-lang#99940 / rust-lang#87350 ~~as mentioned in rust-lang#84277 (comment).

This does not completely fix the issue, but works around it for `Option` and `ControlFlow` specifically (`Result` does not have the issue since it already did not use the default parameter of `FromResidual`).

~~(Does this need an ACP or similar?)~~ ~~This probably needs at least an FCP since it changes the API described in [the RFC](rust-lang/rfcs#3058). Not sure if T-lang, T-libs-api, T-libs, or some combination (The tracking issue is tagged T-lang, T-libs-api).~~ This probably doesn't need T-lang input, since it is not changing the API of `FromResidual` from the RFC? Maybe needs T-libs-api FCP?
jieyouxu added a commit to jieyouxu/rust that referenced this issue Aug 14, 2024
…=scottmcm

Explicitly specify type parameter on FromResidual for Option and ControlFlow.

~~Remove type parameter default `R = <Self as Try>::Residual` from `FromResidual`~~ _Specify default type parameter on `FromResidual` impls in the stdlib_ to work around rust-lang#99940 / rust-lang#87350 ~~as mentioned in rust-lang#84277 (comment).

This does not completely fix the issue, but works around it for `Option` and `ControlFlow` specifically (`Result` does not have the issue since it already did not use the default parameter of `FromResidual`).

~~(Does this need an ACP or similar?)~~ ~~This probably needs at least an FCP since it changes the API described in [the RFC](rust-lang/rfcs#3058). Not sure if T-lang, T-libs-api, T-libs, or some combination (The tracking issue is tagged T-lang, T-libs-api).~~ This probably doesn't need T-lang input, since it is not changing the API of `FromResidual` from the RFC? Maybe needs T-libs-api FCP?
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Aug 14, 2024
Rollup merge of rust-lang#128954 - zachs18:fromresidual-no-default, r=scottmcm

Explicitly specify type parameter on FromResidual for Option and ControlFlow.

~~Remove type parameter default `R = <Self as Try>::Residual` from `FromResidual`~~ _Specify default type parameter on `FromResidual` impls in the stdlib_ to work around rust-lang#99940 / rust-lang#87350 ~~as mentioned in rust-lang#84277 (comment).

This does not completely fix the issue, but works around it for `Option` and `ControlFlow` specifically (`Result` does not have the issue since it already did not use the default parameter of `FromResidual`).

~~(Does this need an ACP or similar?)~~ ~~This probably needs at least an FCP since it changes the API described in [the RFC](rust-lang/rfcs#3058). Not sure if T-lang, T-libs-api, T-libs, or some combination (The tracking issue is tagged T-lang, T-libs-api).~~ This probably doesn't need T-lang input, since it is not changing the API of `FromResidual` from the RFC? Maybe needs T-libs-api FCP?
@arpj-rebola
Copy link

I just inadvertently fell into this issue: forum post

My use case is generic methods so that the caller can (parametrically) decide the level of error reporting it wants from the callee. Without more general FromResidual implemented for the usual Try types, the implementation is forced to have the same level of error reporting in any closures it is passed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-associated-items Area: Associated items (types, constants & functions) A-coherence Area: Coherence C-bug Category: This is a bug. T-types Relevant to the types team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

4 participants