Skip to content

Commit 2b78d92

Browse files
committed
Auto merge of #124336 - compiler-errors:super-outlives, r=lcnr
Enforce supertrait outlives obligations hold when confirming impl **TL;DR:** We elaborate super-predicates and apply any outlives obligations when proving an impl holds to fix a mismatch between implied bounds. Bugs in implied bounds (and implied well-formedness) occur whenever there is a mismatch between the assumptions that some code can assume to hold, and the obligations that a caller/user of that code must prove. If the former is stronger than the latter, then unsoundness occurs. Take a look at the example unsoundness: ```rust use std::fmt::Display; trait Static: 'static {} impl<T> Static for &'static T {} fn foo<S: Display>(x: S) -> Box<dyn Display> where &'static S: Static, { Box::new(x) } fn main() { let s = foo(&String::from("blah blah blah")); println!("{}", s); } ``` This specific example occurs because we elaborate obligations in `fn foo`: * `&'static S: Static` * `&'static S: 'static` <- super predicate * `S: 'static` <- elaborating outlives bounds However, when calling `foo`, we only need to prove the direct set of where clauses. So at the call site for some substitution `S = &'not_static str`, that means only proving `&'static &'not_static str: Static`. To prove this, we apply the impl, which itself holds trivially since it has no where clauses. This is the mismatch -- `foo` is allowed to assume that `S: 'static` via elaborating supertraits, but callers of `foo` never need to prove that `S: 'static`. There are several approaches to fixing this, all of which have problems due to current limitations in our type system: 1. proving the elaborated set of predicates always - This leads to issues since we don't have coinductive trait semantics, so we easily hit new cycles. * This would fix our issue, since callers of `foo` would have to both prove `&'static &'not_static str: Static` and its elaborated bounds, which would surface the problematic `'not_static: 'static` outlives obligation. * However, proving supertraits when proving impls leads to inductive cycles which can't be fixed until we get coinductive trait semantics. 2. Proving that an impl header is WF when applying that impl: * This would fix our issue, since when we try to prove `&'static &'not_static str: Static`, we'd need to prove `WF(&'static &'not_static str)`, which would surface the problematic `'not_static: 'static` outlives obligation. * However, this leads to issues since we don't have higher-ranked implied bounds. This breaks things when trying to apply impls to higher-ranked trait goals. To get around these limitations, we apply a subset of (1.), which is to elaborate the supertrait obligations of the impl but filter only the (region/type) outlives out of that set, since those can never participate in an inductive cycle. This is likely not sufficient to fix a pathological example of this issue, but it does clearly fill in a major gap that we're currently overlooking. This can also result in 'unintended' errors due to missing implied-bounds on binders. We did not encounter this in the crater run and don't expect people to rely on this code in practice: ```rust trait Outlives<'b>: 'b {} impl<'b, T> Outlives<'b> for &'b T {} fn foo<'b>() where // This bound will break due to this PR as we end up proving // `&'b &'!a (): 'b` without the implied `'!a: 'b` // bound. for<'a> &'b &'a (): Outlives<'b>, {} ``` Fixes #98117 --- Crater: #124336 (comment) Triaged: #124336 (comment) All of the fallout is due to generic const exprs, and can be ignored.
2 parents 83e9b93 + fa9ae7b commit 2b78d92

11 files changed

+132
-15
lines changed

Diff for: compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs

+11-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use rustc_type_ir::data_structures::HashMap;
77
use rustc_type_ir::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable};
88
use rustc_type_ir::inherent::*;
99
use rustc_type_ir::lang_items::TraitSolverLangItem;
10-
use rustc_type_ir::{self as ty, Interner, Upcast as _};
10+
use rustc_type_ir::{self as ty, elaborate, Interner, Upcast as _};
1111
use rustc_type_ir_macros::{TypeFoldable_Generic, TypeVisitable_Generic};
1212
use tracing::instrument;
1313

@@ -671,11 +671,19 @@ where
671671
{
672672
let cx = ecx.cx();
673673
let mut requirements = vec![];
674-
requirements.extend(
674+
// Elaborating all supertrait outlives obligations here is not soundness critical,
675+
// since if we just used the unelaborated set, then the transitive supertraits would
676+
// be reachable when proving the former. However, since we elaborate all supertrait
677+
// outlives obligations when confirming impls, we would end up with a different set
678+
// of outlives obligations here if we didn't do the same, leading to ambiguity.
679+
// FIXME(-Znext-solver=coinductive): Adding supertraits here can be removed once we
680+
// make impls coinductive always, since they'll always need to prove their supertraits.
681+
requirements.extend(elaborate::elaborate(
682+
cx,
675683
cx.explicit_super_predicates_of(trait_ref.def_id)
676684
.iter_instantiated(cx, trait_ref.args)
677685
.map(|(pred, _)| pred),
678-
);
686+
));
679687

680688
// FIXME(associated_const_equality): Also add associated consts to
681689
// the requirements here.

Diff for: compiler/rustc_next_trait_solver/src/solve/trait_goals.rs

+13
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,19 @@ where
8787
.map(|pred| goal.with(cx, pred));
8888
ecx.add_goals(GoalSource::ImplWhereBound, where_clause_bounds);
8989

90+
// We currently elaborate all supertrait outlives obligations from impls.
91+
// This can be removed when we actually do coinduction correctly, and prove
92+
// all supertrait obligations unconditionally.
93+
let goal_clause: I::Clause = goal.predicate.upcast(cx);
94+
for clause in elaborate::elaborate(cx, [goal_clause]) {
95+
if matches!(
96+
clause.kind().skip_binder(),
97+
ty::ClauseKind::TypeOutlives(..) | ty::ClauseKind::RegionOutlives(..)
98+
) {
99+
ecx.add_goal(GoalSource::Misc, goal.with(cx, clause));
100+
}
101+
}
102+
90103
ecx.evaluate_added_goals_and_make_canonical_response(maximal_certainty)
91104
})
92105
}

Diff for: compiler/rustc_trait_selection/src/traits/select/mod.rs

+33-2
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@ use std::fmt::{self, Display};
77
use std::ops::ControlFlow;
88
use std::{cmp, iter};
99

10+
use hir::def::DefKind;
1011
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
1112
use rustc_data_structures::stack::ensure_sufficient_stack;
1213
use rustc_errors::{Diag, EmissionGuarantee};
1314
use rustc_hir as hir;
1415
use rustc_hir::def_id::DefId;
1516
use rustc_hir::LangItem;
1617
use rustc_infer::infer::relate::TypeRelation;
17-
use rustc_infer::infer::BoundRegionConversionTime::HigherRankedType;
18-
use rustc_infer::infer::{BoundRegionConversionTime, DefineOpaqueTypes};
18+
use rustc_infer::infer::BoundRegionConversionTime::{self, HigherRankedType};
19+
use rustc_infer::infer::DefineOpaqueTypes;
20+
use rustc_infer::traits::util::elaborate;
1921
use rustc_infer::traits::TraitObligation;
2022
use rustc_middle::bug;
2123
use rustc_middle::dep_graph::{dep_kinds, DepNodeIndex};
@@ -2798,6 +2800,35 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
27982800
});
27992801
}
28002802

2803+
// Register any outlives obligations from the trait here, cc #124336.
2804+
if matches!(self.tcx().def_kind(def_id), DefKind::Impl { of_trait: true })
2805+
&& let Some(header) = self.tcx().impl_trait_header(def_id)
2806+
{
2807+
let trait_clause: ty::Clause<'tcx> =
2808+
header.trait_ref.instantiate(self.tcx(), args).upcast(self.tcx());
2809+
for clause in elaborate(self.tcx(), [trait_clause]) {
2810+
if matches!(
2811+
clause.kind().skip_binder(),
2812+
ty::ClauseKind::TypeOutlives(..) | ty::ClauseKind::RegionOutlives(..)
2813+
) {
2814+
let clause = normalize_with_depth_to(
2815+
self,
2816+
param_env,
2817+
cause.clone(),
2818+
recursion_depth,
2819+
clause,
2820+
&mut obligations,
2821+
);
2822+
obligations.push(Obligation {
2823+
cause: cause.clone(),
2824+
recursion_depth,
2825+
param_env,
2826+
predicate: clause.as_predicate(),
2827+
});
2828+
}
2829+
}
2830+
}
2831+
28012832
obligations
28022833
}
28032834
}

Diff for: compiler/rustc_type_ir/src/inherent.rs

+2
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,8 @@ pub trait Clause<I: Interner<Clause = Self>>:
451451
+ UpcastFrom<I, ty::Binder<I, ty::ClauseKind<I>>>
452452
+ UpcastFrom<I, ty::TraitRef<I>>
453453
+ UpcastFrom<I, ty::Binder<I, ty::TraitRef<I>>>
454+
+ UpcastFrom<I, ty::TraitPredicate<I>>
455+
+ UpcastFrom<I, ty::Binder<I, ty::TraitPredicate<I>>>
454456
+ UpcastFrom<I, ty::ProjectionPredicate<I>>
455457
+ UpcastFrom<I, ty::Binder<I, ty::ProjectionPredicate<I>>>
456458
+ IntoKind<Kind = ty::Binder<I, ty::ClauseKind<I>>>

Diff for: tests/ui/fn/implied-bounds-unnorm-associated-type-5.rs

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ trait Trait<'a>: 'a {
55
// if the `T: 'a` bound gets implied we would probably get ub here again
66
impl<'a, T> Trait<'a> for T {
77
//~^ ERROR the parameter type `T` may not live long enough
8+
//~| ERROR the parameter type `T` may not live long enough
89
type Type = ();
910
}
1011

Diff for: tests/ui/fn/implied-bounds-unnorm-associated-type-5.stderr

+15-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,21 @@ help: consider adding an explicit lifetime bound
1616
LL | impl<'a, T: 'a> Trait<'a> for T {
1717
| ++++
1818

19+
error[E0309]: the parameter type `T` may not live long enough
20+
--> $DIR/implied-bounds-unnorm-associated-type-5.rs:6:27
21+
|
22+
LL | impl<'a, T> Trait<'a> for T {
23+
| -- ^ ...so that the type `T` will meet its required lifetime bounds
24+
| |
25+
| the parameter type `T` must be valid for the lifetime `'a` as defined here...
26+
|
27+
help: consider adding an explicit lifetime bound
28+
|
29+
LL | impl<'a, T: 'a> Trait<'a> for T {
30+
| ++++
31+
1932
error[E0505]: cannot move out of `x` because it is borrowed
20-
--> $DIR/implied-bounds-unnorm-associated-type-5.rs:21:10
33+
--> $DIR/implied-bounds-unnorm-associated-type-5.rs:22:10
2134
|
2235
LL | let x = String::from("Hello World!");
2336
| - binding `x` declared here
@@ -33,7 +46,7 @@ help: consider cloning the value if the performance cost is acceptable
3346
LL | let y = f(&x.clone(), ());
3447
| ++++++++
3548

36-
error: aborting due to 2 previous errors
49+
error: aborting due to 3 previous errors
3750

3851
Some errors have detailed explanations: E0309, E0505.
3952
For more information about an error, try `rustc --explain E0309`.

Diff for: tests/ui/static/static-lifetime.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub trait Arbitrary: Sized + 'static {}
22

33
impl<'a, A: Clone> Arbitrary for ::std::borrow::Cow<'a, A> {} //~ ERROR lifetime bound
4+
//~^ ERROR cannot infer an appropriate lifetime for lifetime parameter `'a`
45

56
fn main() {
67
}

Diff for: tests/ui/static/static-lifetime.stderr

+28-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,32 @@ LL | impl<'a, A: Clone> Arbitrary for ::std::borrow::Cow<'a, A> {}
1111
| ^^
1212
= note: but lifetime parameter must outlive the static lifetime
1313

14-
error: aborting due to 1 previous error
14+
error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'a` due to conflicting requirements
15+
--> $DIR/static-lifetime.rs:3:34
16+
|
17+
LL | impl<'a, A: Clone> Arbitrary for ::std::borrow::Cow<'a, A> {}
18+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
19+
|
20+
note: first, the lifetime cannot outlive the lifetime `'a` as defined here...
21+
--> $DIR/static-lifetime.rs:3:6
22+
|
23+
LL | impl<'a, A: Clone> Arbitrary for ::std::borrow::Cow<'a, A> {}
24+
| ^^
25+
note: ...so that the types are compatible
26+
--> $DIR/static-lifetime.rs:3:34
27+
|
28+
LL | impl<'a, A: Clone> Arbitrary for ::std::borrow::Cow<'a, A> {}
29+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
30+
= note: expected `<Cow<'a, A> as Arbitrary>`
31+
found `<Cow<'_, A> as Arbitrary>`
32+
= note: but, the lifetime must be valid for the static lifetime...
33+
note: ...so that the declared lifetime parameter bounds are satisfied
34+
--> $DIR/static-lifetime.rs:3:34
35+
|
36+
LL | impl<'a, A: Clone> Arbitrary for ::std::borrow::Cow<'a, A> {}
37+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
38+
39+
error: aborting due to 2 previous errors
1540

16-
For more information about this error, try `rustc --explain E0478`.
41+
Some errors have detailed explanations: E0478, E0495.
42+
For more information about an error, try `rustc --explain E0478`.

Diff for: tests/ui/wf/wf-in-where-clause-static.current.stderr

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error[E0716]: temporary value dropped while borrowed
2+
--> $DIR/wf-in-where-clause-static.rs:18:18
3+
|
4+
LL | let s = foo(&String::from("blah blah blah"));
5+
| -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-- temporary value is freed at the end of this statement
6+
| | |
7+
| | creates a temporary value which is freed while still in use
8+
| argument requires that borrow lasts for `'static`
9+
10+
error: aborting due to 1 previous error
11+
12+
For more information about this error, try `rustc --explain E0716`.

Diff for: tests/ui/wf/wf-in-where-clause-static.next.stderr

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error[E0716]: temporary value dropped while borrowed
2+
--> $DIR/wf-in-where-clause-static.rs:18:18
3+
|
4+
LL | let s = foo(&String::from("blah blah blah"));
5+
| -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-- temporary value is freed at the end of this statement
6+
| | |
7+
| | creates a temporary value which is freed while still in use
8+
| argument requires that borrow lasts for `'static`
9+
10+
error: aborting due to 1 previous error
11+
12+
For more information about this error, try `rustc --explain E0716`.

Diff for: tests/ui/wf/wf-in-where-clause-static.rs

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
//@ check-pass
2-
//@ known-bug: #98117
3-
4-
// Should fail. Functions are responsible for checking the well-formedness of
5-
// their own where clauses, so this should fail and require an explicit bound
6-
// `T: 'static`.
1+
//@ revisions: current next
2+
//@ ignore-compare-mode-next-solver (explicit revisions)
3+
//@[next] compile-flags: -Znext-solver
74

85
use std::fmt::Display;
96

@@ -19,5 +16,6 @@ where
1916

2017
fn main() {
2118
let s = foo(&String::from("blah blah blah"));
19+
//~^ ERROR temporary value dropped while borrowed
2220
println!("{}", s);
2321
}

0 commit comments

Comments
 (0)