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

TAIT coherence checks don't ensure composability of crates #130978

Open
steffahn opened this issue Sep 28, 2024 · 3 comments
Open

TAIT coherence checks don't ensure composability of crates #130978

steffahn opened this issue Sep 28, 2024 · 3 comments
Assignees
Labels
A-coherence Area: Coherence A-traits Area: Trait system C-bug Category: This is a bug. F-type_alias_impl_trait `#[feature(type_alias_impl_trait)]` requires-nightly This issue requires a nightly compiler in some way. T-types Relevant to the types team, which will review and decide on the PR/issue.

Comments

@steffahn
Copy link
Member

steffahn commented Sep 28, 2024

The coherence checks for trait implementations where the receiver type is an opaque type (defined with TAIT) are designed and/or implemented in a way that doesn’t uphold the principle of seamless composability of crates. It also doesn’t uphold current principles of what kind of trait implementation constitutes a breaking change.


Here’s a reproducing example (consisting of 3 crates):

crate A

[package]
name = "a"
edition = "2021"
pub trait MyFrom<T> {
    fn from(value: T) -> Self;
}

pub trait AsFoo {}

pub struct Foo;

impl AsFoo for Foo {}

crate B

[package]
name = "b"
edition = "2021"

[dependencies]
a = { path = "../a" }
use a::{AsFoo, MyFrom};

pub struct Wrapper<T>(pub T);

impl<T> MyFrom<T> for Wrapper<T> {
    fn from(value: T) -> Self {
        Wrapper(value)
    }
}

impl<T> AsFoo for Wrapper<T> {}

crate C

[package]
name = "c"
edition = "2021"

[dependencies]
a = { path = "../a" }
b = { path = "../b" }
#![feature(type_alias_impl_trait)]

use a::{AsFoo, Foo, MyFrom};
// use b; // <- uncomment for error

type Alias = impl AsFoo;

struct Local;

impl MyFrom<Local> for Alias {
    fn from(_: Local) -> Alias {
        Foo
    }
}

The above example involving 3 crates A, B, C; A is a dependency of B and C.

C compiles successfully with just A as a dependency.
If B is added as a dependency of C (and actually used, by uncommenting the use b;) then the following error appears:

error[E0119]: conflicting implementations of trait `MyFrom<Local>` for type `Wrapper<Local>`
  --> src/lib.rs:10:1
   |
10 | impl MyFrom<Local> for Alias {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: conflicting implementation in crate `b`:
           - impl<T> MyFrom<T> for Wrapper<T>;

The error also does make sense: If we further change crate C, so that the defining use of Alias produces not a Foo but a b::Wrapper<Local>, then the impls of MyFrom will become actually overlapping, even when the opaque type is treated transparently. As far as I can tell, it seems that the goal of the error was to be conservative and ensure that changing the actual concrete choice of type behind the opaque TAIT-type should not introduce any new overlap errors later.

As a consequence, in my opinion this means that without the crate B, there should probably also be some kind of error here.

Some more thoughts and observations:

  • the problematic impl<T> MyFrom<T> for Wrapper<T> is a blanket impl. Addition of such an impl is actually considered a breaking change in other kinds of situations
    • when such an impl is added for a type Wrapper<T> that already exists in previous versions, that’s considered technically breaking
    • however, if it’s introduced together with the type Wrapper<T>, that it okay; and it’s exactly this combination that “add trait B as dependency” achieves
  • the problematic impl<T> MyFrom<T> for Wrapper<T> produces the same error if crates A and B are combined into one. I split them up, because it makes an even stronger argument; nonetheless, common “what’s considered breaking” standards imply that crate A should of course also be allowed to simply add such a pair of struct Wrapper<T> + this impl of MyFrom, and this issue violates that principle, too.
  • I first noticed this issue in this code example, where some trait impls involving local traits and types, as well as From and AsRef<str> results in an error mention that surprisingly mentions random stuff such as gimli::common::DebugFrameOffset [presumably because it’s the first type the compiler finds that has some kind of From<T> for Self<T>-style implementation]. If nothing else that’s a surprising diagnostic, but unsurprisingly is just highlighted this more abstract&general issue with coherence-checks.
  • I have not tried reasoning more deeply about what kind of “orphan rules” a TAIT needs to fulfill to avoid this issue completely, but I wouldn’t be surprised if there are some straightforward ways of giving TAITs a more restrictive version of the orphan rules, which solve this issue.

@rustbot label +F-type_alias_impl_trait +A-coherence +A-traits +T-types

@steffahn steffahn added the C-bug Category: This is a bug. label Sep 28, 2024
@rustbot rustbot added needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. A-coherence Area: Coherence A-traits Area: Trait system F-type_alias_impl_trait `#[feature(type_alias_impl_trait)]` T-types Relevant to the types team, which will review and decide on the PR/issue. labels Sep 28, 2024
@fmease fmease removed the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Sep 28, 2024
@steffahn steffahn changed the title TAIT coherence checks don't ensure composibility of crates TAIT coherence checks don't ensure composability of crates Sep 28, 2024
@lcnr lcnr added I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness requires-nightly This issue requires a nightly compiler in some way. labels Oct 7, 2024
@lcnr
Copy link
Contributor

lcnr commented Oct 7, 2024

may be fixed by new solver, cc #99554

@lcnr
Copy link
Contributor

lcnr commented Oct 8, 2024

So this can actually not be unsound, it's only an issue of composability.

  • as long as C doesnt import B, the opaque type cannot refer to Wrapper
  • once C refers to B, we get an error

I believe this is a general issue with ambiguous aliases. Should affect specialization as well. We should always treat aliases which cannot be normalized as potentially uncovered ty params.

@lcnr lcnr removed the I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness label Oct 9, 2024
@lcnr
Copy link
Contributor

lcnr commented Oct 10, 2024

I believe that the fix for this is 8244d7a. However, this means that the coherence failure for the following now wants to talk about an "uncovered type parameter" even though there isn't any inference variable involved.

// crate a
trait Trait {}

// crate b
#![feature(type_alias_impl_trait)]
type Alias = impl Sized;
fn define() -> Alias { SomeForeignType }
impl a::Trait for Alias {}

This then ICEs here

debug_assert!(!collector.uncovered_params.is_empty());
and the diagnostics code generally doesn't handle this yet.

I would love for someone else to pick this up and handle the necessary diagnostics changes, though I may look into this myself in a few months once I've got the time

@fmease fmease self-assigned this Oct 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-coherence Area: Coherence A-traits Area: Trait system C-bug Category: This is a bug. F-type_alias_impl_trait `#[feature(type_alias_impl_trait)]` requires-nightly This issue requires a nightly compiler in some way. 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