Skip to content

Regression in Error conversion from Infallible #66757

Closed

Description

@alexcrichton

The following code:

use std::convert::{TryFrom, Infallible};

struct E;

impl From<Infallible> for E {
    fn from(_: Infallible) -> E {
        E
    }
}

fn foo() -> Result<(), E> {
    u32::try_from(1u32)?;
    Ok(())
}

compiles on stable but fails to compile on nightly with:

error[E0277]: `?` couldn't convert the error to `E`
  --> src/lib.rs:13:24
   |
13 |     u32::try_from(1u32)?;
   |                        ^ the trait `std::convert::From<()>` is not implemented for `E`
   |
   = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
   = help: the following implementations were found:
             <E as std::convert::From<!>>
   = note: required by `std::convert::From::from`

This was discovered through bytecodealliance/wasmtime#633

Activity

  1. alexcrichton commented on Nov 25, 2019

    @alexcrichton
    MemberAuthor

    Bisection reveals #66607 as the cause, likely #65355

  2. added
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    T-libs-api[DEPRECATED; DO NOT USE]
    on Nov 25, 2019
  3. SimonSapin commented on Nov 25, 2019

    @SimonSapin
    Contributor

    Reduced:

    struct E;
    
    impl From<!> for E {
        fn from(_: !) -> E {
            E
        }
    }
    
    #[allow(unreachable_code)]
    fn foo(never: !) {
        <E as From<!>>::from(never);  // Ok
        <E as From<_>>::from(never);  // Inference fails here
    }

    (Playground)

  4. Centril commented on Nov 25, 2019

    @Centril
    Contributor

    (Adding #![feature(never_type_fallback)] resolves the issue.)

  5. SimonSapin commented on Nov 25, 2019

    @SimonSapin
    Contributor

    The above went through this doc-comment of the desugaring of foo?, which wasn’t easy to find:

    /// Desugar `ExprKind::Try` from: `<expr>?` into:
    /// ```rust
    /// match Try::into_result(<expr>) {
    /// Ok(val) => #[allow(unreachable_code)] val,
    /// Err(err) => #[allow(unreachable_code)]
    /// // If there is an enclosing `try {...}`:
    /// break 'catch_target Try::from_error(From::from(err)),
    /// // Otherwise:
    /// return Try::from_error(From::from(err)),
    /// }
    /// ```

  6. SimonSapin commented on Nov 25, 2019

    @SimonSapin
    Contributor

    (Adding #![feature(never_type_fallback)] resolves the issue.)

    That explains where the () comes from, thanks. But it’s not clear to me why fallback should happen in the first place in this code.

  7. Centril commented on Nov 25, 2019

    @Centril
    Contributor

    Unclear to me also; cc @nikomatsakis and @arielb1 -- also cc #65992

  8. acfoltzer commented on Nov 26, 2019

    @acfoltzer

    The same result also occurs when the desired error type is transitively From<!>, which seems to come up pretty commonly in the wild since std errors like TryFromIntError are From<!>:

    use std::convert::{TryFrom};
    
    struct E;
    
    impl From<!> for E {
        fn from(_: !) -> E {
            E
        }
    }
    
    struct F;
    
    impl From<E> for F {
        fn from(_: E) -> F {
            F
        }
    }
    
    fn foo() -> Result<(), F> {
        u32::try_from(1u32)?;
        Ok(())
    }
    error[E0277]: `?` couldn't convert the error to `F`
      --> src/lib.rs:20:24
       |
    20 |     u32::try_from(1u32)?;
       |                        ^ the trait `std::convert::From<()>` is not implemented for `F`
       |
       = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
       = help: the following implementations were found:
                 <F as std::convert::From<E>>
       = note: required by `std::convert::From::from`
    
  9. SimonSapin commented on Nov 26, 2019

    @SimonSapin
    Contributor

    @acfoltzer Your example legitimately does not compile, although the error message is wrong in Nightly because of this bug. From conversions have never worked transitively. The impl From<!> for E does not affect your example, you get the same results without it. The ! here comes from <u32 as TryFrom<u32>>::Error

  10. acfoltzer commented on Nov 26, 2019

    @acfoltzer

    @SimonSapin Hmm, that wasn't the change I was expecting would make the example compile, but the fact that it doesn't change the behavior is a surprise.

    What I thought was the telling change is whether it compiles after removing the impl From<E> for F rather than impl From<!> for E. But it looks like that happens regardless of the From<!>:

    use std::convert::TryFrom;
    
    struct E;
    
    struct F;
    
    impl From<E> for F {
        fn from(_: E) -> F {
            F
        }
    }
    
    fn foo() -> Result<(), F> {
        u32::try_from(1u32)?;
        Ok(())
    }
    error[E0277]: `?` couldn't convert the error to `F`
      --> src/lib.rs:14:24
       |
    14 |     u32::try_from(1u32)?;
       |                        ^ the trait `std::convert::From<()>` is not implemented for `F`
       |
       = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
       = help: the following implementations were found:
                 <F as std::convert::From<E>>
       = note: required by `std::convert::From::from`
    

    vs the following, which compiles fine:

    use std::convert::TryFrom;
    
    struct F;
    
    fn foo() -> Result<(), F> {
        u32::try_from(1u32)?;
        Ok(())
    }

    Maybe this is a distinct issue?

  11. SimonSapin commented on Nov 26, 2019

    @SimonSapin
    Contributor

    Oooh that’s interesting! Adapting this to the reduced failure from #66757 (comment): removing the impl From<!> for E makes that code compile:

    pub struct E;
    
    #[allow(unreachable_code)]
    pub fn foo(never: !) {
        <E as From<_>>::from(never);
    }

    (Playground)

    I think what happens in the code that compiles is that the only From<_> trait implemented by E is From<E>, the identity conversion. So the call is <E as From<E>>::from which expects an argument of type E. It works out because ! auto-coerces to E (or anything).

    If we add impl From<!> for E (back), now E as From<_> becomes ambiguous. We have a type variable, not fully resolved in type inference, which is then unified with the argument expression passed to from(). That argument has type !, so the type variable is unified with the type !. This unification apparently happens before any coercion has a chance to happen.

    Presumably, the compiler then ends up here somehow:

    #[inline]
    pub fn mk_diverging_default(self) -> Ty<'tcx> {
    if self.features().never_type_fallback {
    self.types.never
    } else {
    self.types.unit
    }
    }

  12. dtolnay commented on Nov 27, 2019

    @dtolnay
    Member

    heim-rs/heim#182 is hitting a variation of this which minimizes to:

    fn from(e: std::convert::Infallible) -> Box<dyn std::error::Error> {
        Box::new(e)
    }

    Fine on stable/beta, fails on nightly with:

    error[E0277]: the trait bound `(): std::error::Error` is not satisfied
     --> src/main.rs:2:5
      |
    2 |     Box::new(e)
      |     ^^^^^^^^^^^ the trait `std::error::Error` is not implemented for `()`
      |
      = note: required for the cast to the object type `dyn std::error::Error`
  13. 17 remaining items

  14. nikomatsakis commented on Dec 2, 2019

    @nikomatsakis
    Contributor

    I agree with @dtolnay as well -- I think a revert is probably the most prudent step right now.

  15. nikomatsakis commented on Dec 2, 2019

    @nikomatsakis
    Contributor

    @SimonSapin in your example, the problem has to do with when fallback occurs. When you see x.bar, fallback has not yet occurred, and so the type is not (yet) known. Field access (and the . operator in general) requires that the type be known, so you get an error.

    However, code like this does compile, and it shows that x is getting a type of either u32 or () -- we know it's not u32, so it must be (). Interestingly, if you enable fallback-to-!, the code no longer builds. (playground):

    #![allow(unreachable_code)]
    
    // Uncomment and you will get an error:
    // #![feature(never_type_fallback)]
    
    fn foo(never: !) {
        let x: _ = never;
        bar(x);
    }
    
    fn bar<T: Bar>(t: T) { }
    trait Bar { }
    impl Bar for u32 { }
    impl Bar for () { }
    
    fn main() { }

    Trait resolution can wait until the end, after fallback has occurred.

  16. nikomatsakis commented on Dec 5, 2019

    @nikomatsakis
    Contributor

    We discussed in today's lang-team meeting and decided that the most prudent step is to revert. @Centril asked if someone else could prepare the PR -- any volunteers?

    We would like after that to pursue the mitigations a bit more actively, since it seems better to have a uniform fallback to ! than to introduce the "split fallback" that I proposed in my previous comments. @llogiq you had previously expressed an interest, not sure if others are interested, but we may want to up the priority on that work (I can try to dedicate a bit more time to getting it started, too).

  17. llogiq commented on Dec 6, 2019

    @llogiq
    Contributor

    @nikomatsakis Sorry, I was swamped with other work. Also some pointers to the infer functions I need to use – how do I find out if a type comes from fallback? Or how do I set the fallback? – would be really helpful.

  18. nikomatsakis commented on Dec 6, 2019

    @nikomatsakis
    Contributor

    @llogiq Not a problem at all! Happy to discuss further the fallback question on Zulip.

  19. pnkfelix commented on Dec 12, 2019

    @pnkfelix
    Contributor

    visiting for T-compiler triage. I think we can remove the nomination label; the immediate fire is being put out via PR #67224

  20. added a commit that references this issue on Dec 12, 2019
  21. added a commit that references this issue on Dec 14, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P-highHigh priorityT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-langRelevant to the language teamT-libs-api[DEPRECATED; DO NOT USE]regression-from-stable-to-nightlyPerformance or correctness regression from stable to nightly.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions