Skip to content

implied bounds from associated types may not actually get implied pt 2. #98543

Description

@lcnr

revives #91068 which has been fixed by only considering implied bounds from projections if they don't normalize while typechecking the function itself

// We only add implied bounds for the normalized type as the unnormalized
// type may not actually get checked by the caller.
//
// Can otherwise be unsound, see #91068.
let TypeOpOutput { output: norm_ty, constraints: constraints1, .. } = self
.param_env
.and(type_op::normalize::Normalize::new(ty))
.fully_perform(self.infcx)
.unwrap_or_else(|_| {
self.infcx
.tcx
.sess
.delay_span_bug(DUMMY_SP, &format!("failed to normalize {:?}", ty));
TypeOpOutput {
output: self.infcx.tcx.ty_error(),
constraints: None,
error_info: None,
}
});
// Note: we need this in examples like
// ```
// trait Foo {
// type Bar;
// fn foo(&self) -> &Self::Bar;
// }
// impl Foo for () {
// type Bar = ();
// fn foo(&self) ->&() {}
// }
// ```
// Both &Self::Bar and &() are WF
let constraints_implied = self.add_implied_bounds(norm_ty);
normalized_inputs_and_output.push(norm_ty);
constraints1.into_iter().chain(constraints_implied)

This does not mean that the projection won't normalize when getting called:

trait Trait {
    type Type;
}

impl<T> Trait for T {
    type Type = ();
}

fn f<'a, 'b>(s: &'b str, _: <&'a &'b () as Trait>::Type) -> &'a str
where
    &'a &'b (): Trait, // <- adding this bound is the change from #91068 
{
    s
}

fn main() {
    let x = String::from("Hello World!");
    let y = f(&x, ());
    drop(x);
    println!("{}", y);
}

The added bound prevents <&'a &'b () as Trait>::Type from getting normalized while borrowchecking f, as we prefer param candidates over impl candidates. When calling f, we don't have the &'a &'b (): Trait in our param_env, so we can now freely use the impl candidate to normalize the projection. The caller therefore doesn't have to prove that &'a &'b () is well formed, causing unsoundness.

I am a bit surprised that the caller doesn't have to prove that the &'a &'b (): Trait obligation is well formed, which would cause this example to not be unsound. It doesn't remove the general unsoundness here though. Here's an alternative test where that isn't enough:

trait Trait {
    type Type;
}

impl<T> Trait for T {
    type Type = ();
}

fn f<'a, 'b>(s: &'b str, _: <&'a &'b () as Trait>::Type) -> &'a str
where
    for<'what, 'ever> &'what &'ever (): Trait,
{
    s
}

fn main() {
    let x = String::from("Hello World!");
    let y = f(&x, ());
    drop(x);
    println!("{}", y);
}

Activity

  1. added
    C-bugCategory: This is a bug.
    I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness
    I-types-nominatedNominated for discussion during a types team meeting.
    T-typesRelevant to the types team, which will review and decide on the PR/issue.
    on Jun 26, 2022
  2. added
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Jun 26, 2022
  3. added
    A-associated-itemsArea: Associated items (types, constants & functions)
    A-implied-boundsArea: Implied bounds / inferred outlives-bounds
    on Jun 26, 2022
  4. steffahn commented on Jun 27, 2022

    @steffahn
    Member

    Unlike #91068, these code examples compile “successfully” all the way since Rust 1.7.0.

  5. apiraino commented on Jun 27, 2022

    @apiraino
    Contributor

    WG-prioritization assigning priority (Zulip discussion).

    @rustbot label -I-prioritize +P-high

  6. added
    P-highHigh priority
    and removed
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Jun 27, 2022
  7. lcnr commented on Jun 27, 2022

    @lcnr
    ContributorAuthor

    while this currently does not work, we should also add a test for something like this. If we get stronger implied bounds, simply requiring the caller to prove wf for all projection components won't be enough anymore

    #![feature(generic_associated_types)]
    
    trait Trait<'a> {
        type Type
        where
            Self: 'a;
    }
    
    impl<'a, T> Trait<'a> for T {
        type Type = ()
        where
            Self: 'a; // once this bound gets implied, we have UB again
    }
    
    fn f<'a, 'b>(s: &'b str, _: <&'b () as Trait<'a>>::Type) -> &'a str
    where
        &'b (): Trait<'a>, // <- adding this bound is the change from #91068 
    {
        s
    }
    
    fn main() {
        let x = String::from("Hello World!");
        let y = f(&x, ());
        drop(x);
        println!("{}", y);
    }
    trait Trait<'a>: 'a {
        type Type;
    }
    
    impl<'a, T> Trait<'a> for T
    where
        T: 'a // if this bound get's implied we would probably get ub here again
    {
        type Type = ();
    }
    
    fn f<'a, 'b>(s: &'b str, _: <&'b () as Trait<'a>>::Type) -> &'a str
    where
        &'b (): Trait<'a>, // <- adding this bound is the change from #91068 
    {
        s
    }
    
    fn main() {
        let x = String::from("Hello World!");
        let y = f(&x, ());
        drop(x);
        println!("{}", y);
    }
  8. jackh726 commented on Jul 29, 2022

    @jackh726
    Member

    There's a PR open that fixes this.

  9. removed
    I-types-nominatedNominated for discussion during a types team meeting.
    on Jul 29, 2022
  10. added a commit that references this issue on Aug 9, 2022
  11. lcnr commented on Aug 19, 2022

    @lcnr
    ContributorAuthor

    fixed by #99217

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

    A-associated-itemsArea: Associated items (types, constants & functions)A-implied-boundsArea: Implied bounds / inferred outlives-boundsC-bugCategory: This is a bug.I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessP-highHigh priorityT-typesRelevant to the types team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions