Skip to content

Commit 39efb1c

Browse files
committed
Auto merge of #114941 - compiler-errors:inline-shadowed-by-dyn, r=lcnr
Don't resolve generic impls that may be shadowed by dyn built-in impls **NOTE:** This is a hack. This is not trying to be a general fix for the issue that we've allowed overlapping built-in trait object impls and user-written impls for quite a long time, and traits like `Any` rely on this (#57893) -- this PR specifically aims to mitigate a new unsoundness that is uncovered by the MIR inliner (#114928) that interacts with this pre-existing issue. Builtin `dyn Trait` impls may overlap with user-provided blanket impls (`impl<T: ?Sized> Trait for T`) in generic contexts. This leads to bugs when instances are resolved in polymorphic contexts, since we typically prefer object candidates over impl candidates. This PR implements a (hacky) heuristic to `resolve_associated_item` to account for that unfortunate hole in the type system -- we now bail with ambiguity if we try to resolve a non-rigid instance whose self type is not known to be sized. This makes sure we can still inline instances like `impl<T: Sized> Trait for T`, which can never overlap with `dyn Trait`'s built-in impl, but we avoid inlining an impl that may be shadowed by a `dyn Trait`. Fixes #114928
2 parents 19dd953 + a30ad3a commit 39efb1c

8 files changed

+177
-1
lines changed

Diff for: compiler/rustc_middle/src/ty/sty.rs

+27
Original file line numberDiff line numberDiff line change
@@ -2945,6 +2945,33 @@ impl<'tcx> Ty<'tcx> {
29452945
_ => false,
29462946
}
29472947
}
2948+
2949+
pub fn is_known_rigid(self) -> bool {
2950+
match self.kind() {
2951+
Bool
2952+
| Char
2953+
| Int(_)
2954+
| Uint(_)
2955+
| Float(_)
2956+
| Adt(_, _)
2957+
| Foreign(_)
2958+
| Str
2959+
| Array(_, _)
2960+
| Slice(_)
2961+
| RawPtr(_)
2962+
| Ref(_, _, _)
2963+
| FnDef(_, _)
2964+
| FnPtr(_)
2965+
| Dynamic(_, _, _)
2966+
| Closure(_, _)
2967+
| Generator(_, _, _)
2968+
| GeneratorWitness(_)
2969+
| GeneratorWitnessMIR(_, _)
2970+
| Never
2971+
| Tuple(_) => true,
2972+
Error(_) | Infer(_) | Alias(_, _) | Param(_) | Bound(_, _) | Placeholder(_) => false,
2973+
}
2974+
}
29482975
}
29492976

29502977
/// Extra information about why we ended up with a particular variance.

Diff for: compiler/rustc_ty_utils/src/instance.rs

+24-1
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,34 @@ fn resolve_associated_item<'tcx>(
141141
false
142142
}
143143
};
144-
145144
if !eligible {
146145
return Ok(None);
147146
}
148147

148+
// HACK: We may have overlapping `dyn Trait` built-in impls and
149+
// user-provided blanket impls. Detect that case here, and return
150+
// ambiguity.
151+
//
152+
// This should not affect totally monomorphized contexts, only
153+
// resolve calls that happen polymorphically, such as the mir-inliner
154+
// and const-prop (and also some lints).
155+
let self_ty = rcvr_args.type_at(0);
156+
if !self_ty.is_known_rigid() {
157+
let predicates = tcx
158+
.predicates_of(impl_data.impl_def_id)
159+
.instantiate(tcx, impl_data.args)
160+
.predicates;
161+
let sized_def_id = tcx.lang_items().sized_trait();
162+
// If we find a `Self: Sized` bound on the item, then we know
163+
// that `dyn Trait` can certainly never apply here.
164+
if !predicates.into_iter().filter_map(ty::Clause::as_trait_clause).any(|clause| {
165+
Some(clause.def_id()) == sized_def_id
166+
&& clause.skip_binder().self_ty() == self_ty
167+
}) {
168+
return Ok(None);
169+
}
170+
}
171+
149172
// Any final impl is required to define all associated items.
150173
if !leaf_def.item.defaultness(tcx).has_value() {
151174
let guard = tcx.sess.delay_span_bug(

Diff for: tests/mir-opt/dont_inline_type_id.call.Inline.diff

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
- // MIR for `call` before Inline
2+
+ // MIR for `call` after Inline
3+
4+
fn call(_1: &T) -> TypeId {
5+
debug s => _1;
6+
let mut _0: std::any::TypeId;
7+
let mut _2: &T;
8+
9+
bb0: {
10+
StorageLive(_2);
11+
_2 = &(*_1);
12+
_0 = <T as Any>::type_id(move _2) -> [return: bb1, unwind unreachable];
13+
}
14+
15+
bb1: {
16+
StorageDead(_2);
17+
return;
18+
}
19+
}
20+

Diff for: tests/mir-opt/dont_inline_type_id.rs

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// unit-test: Inline
2+
// compile-flags: --crate-type=lib -C panic=abort
3+
4+
use std::any::Any;
5+
use std::any::TypeId;
6+
7+
struct A<T: ?Sized + 'static> {
8+
a: i32,
9+
b: T,
10+
}
11+
12+
// EMIT_MIR dont_inline_type_id.call.Inline.diff
13+
pub fn call<T: ?Sized + 'static>(s: &T) -> TypeId {
14+
s.type_id()
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
- // MIR for `call` before Inline
2+
+ // MIR for `call` after Inline
3+
4+
fn call(_1: &T) -> i32 {
5+
debug s => _1;
6+
let mut _0: i32;
7+
let mut _2: &T;
8+
+ scope 1 (inlined <T as Foo>::bar) {
9+
+ debug self => _2;
10+
+ }
11+
12+
bb0: {
13+
StorageLive(_2);
14+
_2 = &(*_1);
15+
- _0 = <T as Foo>::bar(move _2) -> [return: bb1, unwind unreachable];
16+
- }
17+
-
18+
- bb1: {
19+
+ _0 = const 0_i32;
20+
StorageDead(_2);
21+
return;
22+
}
23+
}
24+

Diff for: tests/mir-opt/inline_generically_if_sized.rs

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// unit-test: Inline
2+
// compile-flags: --crate-type=lib -C panic=abort
3+
4+
trait Foo {
5+
fn bar(&self) -> i32;
6+
}
7+
8+
impl<T> Foo for T {
9+
fn bar(&self) -> i32 {
10+
0
11+
}
12+
}
13+
14+
// EMIT_MIR inline_generically_if_sized.call.Inline.diff
15+
pub fn call<T>(s: &T) -> i32 {
16+
s.bar()
17+
}
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// run-pass
2+
3+
#![feature(inline_const)]
4+
5+
// Makes sure we don't propagate generic instances of `Self: ?Sized` blanket impls.
6+
// This is relevant when we have an overlapping impl and builtin dyn instance.
7+
// See <https://github.com/rust-lang/rust/pull/114941> for more context.
8+
9+
trait Trait {
10+
fn foo(&self) -> &'static str;
11+
}
12+
13+
impl<T: ?Sized> Trait for T {
14+
fn foo(&self) -> &'static str {
15+
std::any::type_name::<T>()
16+
}
17+
}
18+
19+
fn bar<T: ?Sized>() -> fn(&T) -> &'static str {
20+
const { Trait::foo as fn(&T) -> &'static str }
21+
// If const prop were to propagate the instance
22+
}
23+
24+
fn main() {
25+
assert_eq!("i32", bar::<dyn Trait>()(&1i32));
26+
}
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// run-pass
2+
3+
// Makes sure we don't propagate generic instances of `Self: ?Sized` blanket impls.
4+
// This is relevant when we have an overlapping impl and builtin dyn instance.
5+
// See <https://github.com/rust-lang/rust/pull/114941> for more context.
6+
7+
trait Trait {
8+
fn foo(&self) -> &'static str;
9+
}
10+
11+
impl<T: ?Sized> Trait for T {
12+
fn foo(&self) -> &'static str {
13+
std::any::type_name::<T>()
14+
}
15+
}
16+
17+
const fn bar<T: ?Sized>() -> fn(&T) -> &'static str {
18+
Trait::foo
19+
// If const prop were to propagate the instance
20+
}
21+
22+
fn main() {
23+
assert_eq!("i32", bar::<dyn Trait>()(&1i32));
24+
}

0 commit comments

Comments
 (0)