Skip to content

Commit 6b4d2ac

Browse files
committed
Wrap multi-bound impl/dyn after prefix type constructors
`&impl A + B`, `&mut impl A + B`, `*const impl A + B`, `*mut impl A + B`, `&dyn A + B` and similar shapes parse as the ambiguous `(&impl A) + B` and are rejected by the parser, so diagnostics that surface them produce non-compilable Rust. `refining_impl_trait` machine-applicable suggestions are the user-visible failure. In the `ty::Ref` and `ty::RawPtr` arms of `pretty_print_type`, wrap the inner type in parens when it would print with `+`-joined bounds. For `&mut`, emit the mutability prefix before the parens so the output is `&mut (impl A + B)`, not the ill-formed `&(mut impl A + B)`. `ty::Dynamic` already self-wraps under an explicit region, so that case is skipped. `opaque_has_multiple_bounds` mirrors `pretty_print_opaque_impl_type`'s sizedness handling, so `?Sized` and the synthetic `Sized` suffix are counted correctly. `LegacySymbolMangler` and `TypeNamePrinter` override `add_disambiguating_parens_in_prefix_position` to `false`, keeping mangled symbols and `std::any::type_name` output byte-identical.
1 parent 3bf5c6d commit 6b4d2ac

17 files changed

Lines changed: 619 additions & 25 deletions

File tree

compiler/rustc_const_eval/src/util/type_name.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,12 @@ impl<'tcx> PrettyPrinter<'tcx> for TypeNamePrinter<'tcx> {
203203
// `std::any::type_name` should never print verbose type names
204204
false
205205
}
206+
207+
// `type_name` output is user-observable; keep it byte-stable here even
208+
// though the docs permit drift.
209+
fn add_disambiguating_parens_in_prefix_position(&self) -> bool {
210+
false
211+
}
206212
}
207213

208214
impl Write for TypeNamePrinter<'_> {

compiler/rustc_middle/src/ty/print/pretty.rs

Lines changed: 128 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,15 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write {
352352
/// will always be printed.)
353353
fn should_print_optional_region(&self, region: ty::Region<'tcx>) -> bool;
354354

355+
/// Whether `pretty_print_type` should wrap a multi-bound `impl` / `dyn`
356+
/// inner in parens after a prefix type constructor (`&`, `&mut`, `*const`,
357+
/// `*mut`), so the output is `&(impl A + B)` rather than the parser-
358+
/// ambiguous `&impl A + B`. Byte-stable printers (mangling, `type_name`)
359+
/// override this to `false`.
360+
fn add_disambiguating_parens_in_prefix_position(&self) -> bool {
361+
true
362+
}
363+
355364
fn reset_type_limit(&mut self) {}
356365

357366
// Defaults (should not be overridden):
@@ -723,15 +732,17 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write {
723732
}
724733
ty::RawPtr(ty, mutbl) => {
725734
write!(self, "*{} ", mutbl.ptr_str())?;
726-
ty.print(self)?;
735+
self.print_inner_with_disambiguating_parens(ty)?;
727736
}
728737
ty::Ref(r, ty, mutbl) => {
729738
write!(self, "&")?;
730739
if self.should_print_optional_region(r) {
731740
r.print(self)?;
732741
write!(self, " ")?;
733742
}
734-
ty::TypeAndMut { ty, mutbl }.print(self)?;
743+
// `&mut (impl A + B)`, not `&(mut impl A + B)`: emit `mut ` before the parens.
744+
write!(self, "{}", mutbl.prefix_str())?;
745+
self.print_inner_with_disambiguating_parens(ty)?;
735746
}
736747
ty::Never => write!(self, "!")?,
737748
ty::Tuple(tys) => {
@@ -1071,6 +1082,121 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write {
10711082
Ok(())
10721083
}
10731084

1085+
/// Prints `ty` after a prefix type constructor, wrapping in parens iff
1086+
/// [`Self::inner_needs_disambiguating_parens`] returns `true`.
1087+
fn print_inner_with_disambiguating_parens(&mut self, ty: Ty<'tcx>) -> Result<(), PrintError> {
1088+
let need_paren = self.inner_needs_disambiguating_parens(ty);
1089+
if need_paren {
1090+
write!(self, "(")?;
1091+
}
1092+
ty.print(self)?;
1093+
if need_paren {
1094+
write!(self, ")")?;
1095+
}
1096+
Ok(())
1097+
}
1098+
1099+
/// Whether `ty`, sitting right after a prefix type constructor, would
1100+
/// print with a top-level `+`. Without the wrap, `&impl A + B` parses as
1101+
/// the ambiguous `(&impl A) + B`.
1102+
fn inner_needs_disambiguating_parens(&self, ty: Ty<'tcx>) -> bool {
1103+
if !self.add_disambiguating_parens_in_prefix_position() || self.should_print_verbose() {
1104+
return false;
1105+
}
1106+
match ty.kind() {
1107+
// RPITIT trait-side appears as `Projection`, not `Opaque`.
1108+
ty::Alias(ty::AliasTy { kind: ty::Opaque { def_id }, args, .. }) => {
1109+
self.opaque_has_multiple_bounds(*def_id, args)
1110+
}
1111+
ty::Alias(ty::AliasTy { kind: ty::Projection { def_id }, args, .. })
1112+
if self.tcx().is_impl_trait_in_trait(*def_id) =>
1113+
{
1114+
self.opaque_has_multiple_bounds(*def_id, args)
1115+
}
1116+
ty::Dynamic(predicates, region) => {
1117+
if self.should_print_optional_region(*region) {
1118+
// `ty::Dynamic` self-wraps when it has an explicit region.
1119+
false
1120+
} else {
1121+
// Projections inline into the principal as `<Item = X>`;
1122+
// only principal/auto traits produce a top-level `+`.
1123+
predicates
1124+
.iter()
1125+
.filter(|pred| {
1126+
matches!(
1127+
pred.skip_binder(),
1128+
ty::ExistentialPredicate::Trait(_)
1129+
| ty::ExistentialPredicate::AutoTrait(_)
1130+
)
1131+
})
1132+
.count()
1133+
> 1
1134+
}
1135+
}
1136+
_ => false,
1137+
}
1138+
}
1139+
1140+
/// Whether `Alias(Opaque)` would print with more than one top-level
1141+
/// `+`-joined component. Must stay in sync with
1142+
/// `pretty_print_opaque_impl_type`'s sized-bound handling. `?Sized` and
1143+
/// the synthetic `Sized` / `?Sized` / `MetaSized` / `PointeeSized` suffix
1144+
/// each contribute one top-level joinable component. Regressions land in
1145+
/// `tests/ui/impl-trait/in-trait/refine-rustfix-parens.rs`.
1146+
fn opaque_has_multiple_bounds(&self, def_id: DefId, args: ty::GenericArgsRef<'tcx>) -> bool {
1147+
let tcx = self.tcx();
1148+
let bounds = tcx.explicit_item_bounds(def_id);
1149+
1150+
// Mirror `pretty_print_opaque_impl_type`'s sized-bound handling:
1151+
// positive `Sized` and `MetaSized` are absorbed into the synthetic
1152+
// suffix below; negative `Sized` (`?Sized`) falls through and is
1153+
// printed inline.
1154+
let mut trait_emits = 0usize;
1155+
let mut lifetimes_count = 0usize;
1156+
let mut has_sized_bound = false;
1157+
let mut has_negative_sized_bound = false;
1158+
let mut has_meta_sized_bound = false;
1159+
1160+
for (predicate, _) in
1161+
bounds.iter_instantiated_copied(tcx, args).map(Unnormalized::skip_norm_wip)
1162+
{
1163+
match predicate.kind().skip_binder() {
1164+
ty::ClauseKind::Trait(pred) => match tcx.as_lang_item(pred.def_id()) {
1165+
Some(LangItem::Sized) => match pred.polarity {
1166+
ty::PredicatePolarity::Positive => {
1167+
has_sized_bound = true;
1168+
}
1169+
ty::PredicatePolarity::Negative => {
1170+
has_negative_sized_bound = true;
1171+
trait_emits += 1;
1172+
}
1173+
},
1174+
Some(LangItem::MetaSized) => {
1175+
has_meta_sized_bound = true;
1176+
}
1177+
Some(LangItem::PointeeSized) => {}
1178+
_ => trait_emits += 1,
1179+
},
1180+
ty::ClauseKind::TypeOutlives(_) => lifetimes_count += 1,
1181+
_ => {}
1182+
}
1183+
}
1184+
1185+
// The synthetic suffix in `pretty_print_opaque_impl_type` emits at most
1186+
// one extra bound (`Sized` / `?Sized` / `MetaSized` / `PointeeSized`).
1187+
let using_sized_hierarchy = tcx.features().sized_hierarchy();
1188+
let add_sized = has_sized_bound && (trait_emits == 0 || has_negative_sized_bound);
1189+
let add_maybe_sized =
1190+
has_meta_sized_bound && !has_negative_sized_bound && !using_sized_hierarchy;
1191+
let has_pointee_sized =
1192+
!has_sized_bound && !has_meta_sized_bound && !has_negative_sized_bound;
1193+
let synthetic = add_sized
1194+
|| add_maybe_sized
1195+
|| (using_sized_hierarchy && (has_meta_sized_bound || has_pointee_sized));
1196+
1197+
trait_emits + lifetimes_count + usize::from(synthetic) > 1
1198+
}
1199+
10741200
fn pretty_print_opaque_impl_type(
10751201
&mut self,
10761202
def_id: DefId,

compiler/rustc_symbol_mangling/src/legacy.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,12 @@ impl<'tcx> PrettyPrinter<'tcx> for LegacySymbolMangler<'tcx> {
477477
false
478478
}
479479

480+
// Mangled symbols are ABI-stable; don't pick up the default printer's
481+
// parser-disambiguating parens.
482+
fn add_disambiguating_parens_in_prefix_position(&self) -> bool {
483+
false
484+
}
485+
480486
// Identical to `PrettyPrinter::comma_sep` except there is no space after each comma.
481487
fn comma_sep<T>(&mut self, mut elems: impl Iterator<Item = T>) -> Result<(), PrintError>
482488
where

src/tools/miri/tests/fail/dyn-upcast-nop-wrong-trait.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: Undefined Behavior: constructing invalid value of type *const dyn std::fmt::Debug + std::marker::Send + std::marker::Sync: wrong trait in wide pointer vtable: expected `std::fmt::Debug + std::marker::Send + std::marker::Sync`, but encountered `std::fmt::Display`
1+
error: Undefined Behavior: constructing invalid value of type *const (dyn std::fmt::Debug + std::marker::Send + std::marker::Sync): wrong trait in wide pointer vtable: expected `std::fmt::Debug + std::marker::Send + std::marker::Sync`, but encountered `std::fmt::Display`
22
--> tests/fail/dyn-upcast-nop-wrong-trait.rs:LL:CC
33
|
44
LL | let ptr: *const (dyn fmt::Debug + Send + Sync) = unsafe { std::mem::transmute(ptr) };

src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ LL | extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
1313
at RUSTLIB/std/src/sys/backtrace.rs:LL:CC
1414
2: std::rt::lang_start::<()>::{closure#0}
1515
at RUSTLIB/std/src/rt.rs:LL:CC
16-
3: std::ops::function::impls::<impl std::ops::FnOnce<()> for &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>::call_once
16+
3: std::ops::function::impls::<impl std::ops::FnOnce<()> for &(dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe)>::call_once
1717
at RUSTLIB/core/src/ops/function.rs:LL:CC
18-
4: std::panicking::catch_unwind::do_call::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>
18+
4: std::panicking::catch_unwind::do_call::<&(dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe), i32>
1919
at RUSTLIB/std/src/panicking.rs:LL:CC
20-
5: std::panicking::catch_unwind::<i32, &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>
20+
5: std::panicking::catch_unwind::<i32, &(dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe)>
2121
at RUSTLIB/std/src/panicking.rs:LL:CC
22-
6: std::panic::catch_unwind::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>
22+
6: std::panic::catch_unwind::<&(dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe), i32>
2323
at RUSTLIB/std/src/panic.rs:LL:CC
2424
7: std::rt::lang_start_internal::{closure#0}
2525
at RUSTLIB/std/src/rt.rs:LL:CC

src/tools/miri/tests/pass/backtrace/backtrace-global-alloc.stderr

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
at RUSTLIB/std/src/sys/backtrace.rs:LL:CC
77
3: std::rt::lang_start::<()>::{closure#0}
88
at RUSTLIB/std/src/rt.rs:LL:CC
9-
4: std::ops::function::impls::<impl std::ops::FnOnce<()> for &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>::call_once
9+
4: std::ops::function::impls::<impl std::ops::FnOnce<()> for &(dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe)>::call_once
1010
at RUSTLIB/core/src/ops/function.rs:LL:CC
11-
5: std::panicking::catch_unwind::do_call::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>
11+
5: std::panicking::catch_unwind::do_call::<&(dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe), i32>
1212
at RUSTLIB/std/src/panicking.rs:LL:CC
13-
6: std::panicking::catch_unwind::<i32, &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>
13+
6: std::panicking::catch_unwind::<i32, &(dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe)>
1414
at RUSTLIB/std/src/panicking.rs:LL:CC
15-
7: std::panic::catch_unwind::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>
15+
7: std::panic::catch_unwind::<&(dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe), i32>
1616
at RUSTLIB/std/src/panic.rs:LL:CC
1717
8: std::rt::lang_start_internal::{closure#0}
1818
at RUSTLIB/std/src/rt.rs:LL:CC

src/tools/miri/tests/pass/backtrace/backtrace-std.stderr

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@
1414
at RUSTLIB/std/src/sys/backtrace.rs:LL:CC
1515
7: std::rt::lang_start::<()>::{closure#0}
1616
at RUSTLIB/std/src/rt.rs:LL:CC
17-
8: std::ops::function::impls::<impl std::ops::FnOnce<()> for &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>::call_once
17+
8: std::ops::function::impls::<impl std::ops::FnOnce<()> for &(dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe)>::call_once
1818
at RUSTLIB/core/src/ops/function.rs:LL:CC
19-
9: std::panicking::catch_unwind::do_call::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>
19+
9: std::panicking::catch_unwind::do_call::<&(dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe), i32>
2020
at RUSTLIB/std/src/panicking.rs:LL:CC
21-
10: std::panicking::catch_unwind::<i32, &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>
21+
10: std::panicking::catch_unwind::<i32, &(dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe)>
2222
at RUSTLIB/std/src/panicking.rs:LL:CC
23-
11: std::panic::catch_unwind::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>
23+
11: std::panic::catch_unwind::<&(dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe), i32>
2424
at RUSTLIB/std/src/panic.rs:LL:CC
2525
12: std::rt::lang_start_internal::{closure#0}
2626
at RUSTLIB/std/src/rt.rs:LL:CC

tests/ui/argument-suggestions/display-is-suggestable.stderr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ error[E0061]: this function takes 1 argument but 0 arguments were supplied
22
--> $DIR/display-is-suggestable.rs:6:5
33
|
44
LL | foo();
5-
| ^^^-- argument #1 of type `&dyn std::fmt::Display + Send` is missing
5+
| ^^^-- argument #1 of type `&(dyn std::fmt::Display + Send)` is missing
66
|
77
note: function defined here
88
--> $DIR/display-is-suggestable.rs:3:4
@@ -11,8 +11,8 @@ LL | fn foo(x: &(dyn Display + Send)) {}
1111
| ^^^ ------------------------
1212
help: provide the argument
1313
|
14-
LL | foo(/* &dyn std::fmt::Display + Send */);
15-
| +++++++++++++++++++++++++++++++++++
14+
LL | foo(/* &(dyn std::fmt::Display + Send) */);
15+
| +++++++++++++++++++++++++++++++++++++
1616

1717
error: aborting due to 1 previous error
1818

tests/ui/cast/casts-differing-anon.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error[E0606]: casting `*mut impl Debug + ?Sized` as `*mut impl Debug + ?Sized` is invalid
1+
error[E0606]: casting `*mut (impl Debug + ?Sized)` as `*mut (impl Debug + ?Sized)` is invalid
22
--> $DIR/casts-differing-anon.rs:21:13
33
|
44
LL | b_raw = f_raw as *mut _;

0 commit comments

Comments
 (0)