Skip to content

Commit 31e581e

Browse files
Wrap dyn type with parentheses in suggestion
1 parent 453ceaf commit 31e581e

File tree

10 files changed

+286
-62
lines changed

10 files changed

+286
-62
lines changed

compiler/rustc_hir/src/hir.rs

+41-5
Original file line numberDiff line numberDiff line change
@@ -644,13 +644,49 @@ impl<'hir> Generics<'hir> {
644644
})
645645
}
646646

647-
pub fn bounds_span_for_suggestions(&self, param_def_id: LocalDefId) -> Option<Span> {
647+
/// Returns a suggestable empty span right after the "final" bound of the generic parameter.
648+
///
649+
/// If that bound needs to be wrapped in parentheses to avoid ambiguity with
650+
/// subsequent bounds, it also returns an empty span for an open parenthesis
651+
/// as the second component.
652+
///
653+
/// E.g., adding `+ 'static` after `Fn() -> dyn Future<Output = ()>` or
654+
/// `Fn() -> &'static dyn Debug` requires parentheses:
655+
/// `Fn() -> (dyn Future<Output = ()>) + 'static` and
656+
/// `Fn() -> &'static (dyn Debug) + 'static`, respectively.
657+
pub fn bounds_span_for_suggestions(
658+
&self,
659+
param_def_id: LocalDefId,
660+
) -> Option<(Span, Option<Span>)> {
648661
self.bounds_for_param(param_def_id).flat_map(|bp| bp.bounds.iter().rev()).find_map(
649662
|bound| {
650-
// We include bounds that come from a `#[derive(_)]` but point at the user's code,
651-
// as we use this method to get a span appropriate for suggestions.
652-
let bs = bound.span();
653-
bs.can_be_used_for_suggestions().then(|| bs.shrink_to_hi())
663+
let span_for_parentheses = if let Some(trait_ref) = bound.trait_ref()
664+
&& let [.., segment] = trait_ref.path.segments
665+
&& segment.args().parenthesized == GenericArgsParentheses::ParenSugar
666+
&& let [binding] = segment.args().bindings
667+
&& let TypeBindingKind::Equality { term: Term::Ty(ret_ty) } = binding.kind
668+
&& let ret_ty = ret_ty.peel_refs()
669+
&& let TyKind::TraitObject(
670+
_,
671+
_,
672+
TraitObjectSyntax::Dyn | TraitObjectSyntax::DynStar,
673+
) = ret_ty.kind
674+
&& ret_ty.span.can_be_used_for_suggestions()
675+
{
676+
Some(ret_ty.span)
677+
} else {
678+
None
679+
};
680+
681+
span_for_parentheses.map_or_else(
682+
|| {
683+
// We include bounds that come from a `#[derive(_)]` but point at the user's code,
684+
// as we use this method to get a span appropriate for suggestions.
685+
let bs = bound.span();
686+
bs.can_be_used_for_suggestions().then(|| (bs.shrink_to_hi(), None))
687+
},
688+
|span| Some((span.shrink_to_hi(), Some(span.shrink_to_lo()))),
689+
)
654690
},
655691
)
656692
}

compiler/rustc_hir_typeck/src/method/suggest.rs

+41-24
Original file line numberDiff line numberDiff line change
@@ -3291,14 +3291,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
32913291
param.name.ident(),
32923292
));
32933293
let bounds_span = hir_generics.bounds_span_for_suggestions(def_id);
3294-
if rcvr_ty.is_ref() && param.is_impl_trait() && bounds_span.is_some() {
3294+
if rcvr_ty.is_ref()
3295+
&& param.is_impl_trait()
3296+
&& let Some((bounds_span, _)) = bounds_span
3297+
{
32953298
err.multipart_suggestions(
32963299
msg,
32973300
candidates.iter().map(|t| {
32983301
vec![
32993302
(param.span.shrink_to_lo(), "(".to_string()),
33003303
(
3301-
bounds_span.unwrap(),
3304+
bounds_span,
33023305
format!(" + {})", self.tcx.def_path_str(t.def_id)),
33033306
),
33043307
]
@@ -3308,32 +3311,46 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
33083311
return;
33093312
}
33103313

3311-
let (sp, introducer) = if let Some(span) = bounds_span {
3312-
(span, Introducer::Plus)
3313-
} else if let Some(colon_span) = param.colon_span {
3314-
(colon_span.shrink_to_hi(), Introducer::Nothing)
3315-
} else if param.is_impl_trait() {
3316-
(param.span.shrink_to_hi(), Introducer::Plus)
3317-
} else {
3318-
(param.span.shrink_to_hi(), Introducer::Colon)
3319-
};
3314+
let (sp, introducer, open_paren_sp) =
3315+
if let Some((span, open_paren_sp)) = bounds_span {
3316+
(span, Introducer::Plus, open_paren_sp)
3317+
} else if let Some(colon_span) = param.colon_span {
3318+
(colon_span.shrink_to_hi(), Introducer::Nothing, None)
3319+
} else if param.is_impl_trait() {
3320+
(param.span.shrink_to_hi(), Introducer::Plus, None)
3321+
} else {
3322+
(param.span.shrink_to_hi(), Introducer::Colon, None)
3323+
};
33203324

3321-
err.span_suggestions(
3322-
sp,
3325+
let all_suggs = candidates.iter().map(|cand| {
3326+
let suggestion = format!(
3327+
"{} {}",
3328+
match introducer {
3329+
Introducer::Plus => " +",
3330+
Introducer::Colon => ":",
3331+
Introducer::Nothing => "",
3332+
},
3333+
self.tcx.def_path_str(cand.def_id)
3334+
);
3335+
3336+
let mut suggs = vec![];
3337+
3338+
if let Some(open_paren_sp) = open_paren_sp {
3339+
suggs.push((open_paren_sp, "(".to_string()));
3340+
suggs.push((sp, format!("){suggestion}")));
3341+
} else {
3342+
suggs.push((sp, suggestion));
3343+
}
3344+
3345+
suggs
3346+
});
3347+
3348+
err.multipart_suggestions(
33233349
msg,
3324-
candidates.iter().map(|t| {
3325-
format!(
3326-
"{} {}",
3327-
match introducer {
3328-
Introducer::Plus => " +",
3329-
Introducer::Colon => ":",
3330-
Introducer::Nothing => "",
3331-
},
3332-
self.tcx.def_path_str(t.def_id)
3333-
)
3334-
}),
3350+
all_suggs,
33353351
Applicability::MaybeIncorrect,
33363352
);
3353+
33373354
return;
33383355
}
33393356
Node::Item(hir::Item {

compiler/rustc_infer/src/infer/error_reporting/mod.rs

+11-5
Original file line numberDiff line numberDiff line change
@@ -2369,7 +2369,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
23692369
generic_param_scope = self.tcx.local_parent(generic_param_scope);
23702370
}
23712371

2372-
// type_param_sugg_span is (span, has_bounds)
2372+
// type_param_sugg_span is (span, has_bounds, needs_parentheses)
23732373
let (type_scope, type_param_sugg_span) = match bound_kind {
23742374
GenericKind::Param(ref param) => {
23752375
let generics = self.tcx.generics_of(generic_param_scope);
@@ -2380,10 +2380,10 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
23802380
// instead we suggest `T: 'a + 'b` in that case.
23812381
let hir_generics = self.tcx.hir().get_generics(scope).unwrap();
23822382
let sugg_span = match hir_generics.bounds_span_for_suggestions(def_id) {
2383-
Some(span) => Some((span, true)),
2383+
Some((span, open_paren_sp)) => Some((span, true, open_paren_sp)),
23842384
// If `param` corresponds to `Self`, no usable suggestion span.
23852385
None if generics.has_self && param.index == 0 => None,
2386-
None => Some((self.tcx.def_span(def_id).shrink_to_hi(), false)),
2386+
None => Some((self.tcx.def_span(def_id).shrink_to_hi(), false, None)),
23872387
};
23882388
(scope, sugg_span)
23892389
}
@@ -2406,12 +2406,18 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
24062406
let mut suggs = vec![];
24072407
let lt_name = self.suggest_name_region(sub, &mut suggs);
24082408

2409-
if let Some((sp, has_lifetimes)) = type_param_sugg_span
2409+
if let Some((sp, has_lifetimes, open_paren_sp)) = type_param_sugg_span
24102410
&& suggestion_scope == type_scope
24112411
{
24122412
let suggestion =
24132413
if has_lifetimes { format!(" + {lt_name}") } else { format!(": {lt_name}") };
2414-
suggs.push((sp, suggestion))
2414+
2415+
if let Some(open_paren_sp) = open_paren_sp {
2416+
suggs.push((open_paren_sp, "(".to_string()));
2417+
suggs.push((sp, format!("){suggestion}")));
2418+
} else {
2419+
suggs.push((sp, suggestion))
2420+
}
24152421
} else if let GenericKind::Alias(ref p) = bound_kind
24162422
&& let ty::Projection = p.kind(self.tcx)
24172423
&& let DefKind::AssocTy = self.tcx.def_kind(p.def_id)

compiler/rustc_middle/src/ty/diagnostics.rs

+22-17
Original file line numberDiff line numberDiff line change
@@ -279,24 +279,29 @@ pub fn suggest_constraining_type_params<'a>(
279279
constraint.sort();
280280
constraint.dedup();
281281
let constraint = constraint.join(" + ");
282-
let mut suggest_restrict = |span, bound_list_non_empty| {
283-
suggestions.push((
284-
span,
285-
if span_to_replace.is_some() {
286-
constraint.clone()
287-
} else if constraint.starts_with('<') {
288-
constraint.to_string()
289-
} else if bound_list_non_empty {
290-
format!(" + {constraint}")
291-
} else {
292-
format!(" {constraint}")
293-
},
294-
SuggestChangingConstraintsMessage::RestrictBoundFurther,
295-
))
282+
let mut suggest_restrict = |span, bound_list_non_empty, open_paren_sp| {
283+
let suggestion = if span_to_replace.is_some() {
284+
constraint.clone()
285+
} else if constraint.starts_with('<') {
286+
constraint.to_string()
287+
} else if bound_list_non_empty {
288+
format!(" + {constraint}")
289+
} else {
290+
format!(" {constraint}")
291+
};
292+
293+
use SuggestChangingConstraintsMessage::RestrictBoundFurther;
294+
295+
if let Some(open_paren_sp) = open_paren_sp {
296+
suggestions.push((open_paren_sp, "(".to_string(), RestrictBoundFurther));
297+
suggestions.push((span, format!("){suggestion}"), RestrictBoundFurther));
298+
} else {
299+
suggestions.push((span, suggestion, RestrictBoundFurther));
300+
}
296301
};
297302

298303
if let Some(span) = span_to_replace {
299-
suggest_restrict(span, true);
304+
suggest_restrict(span, true, None);
300305
continue;
301306
}
302307

@@ -327,8 +332,8 @@ pub fn suggest_constraining_type_params<'a>(
327332
// --
328333
// |
329334
// replace with: `T: Bar +`
330-
if let Some(span) = generics.bounds_span_for_suggestions(param.def_id) {
331-
suggest_restrict(span, true);
335+
if let Some((span, open_paren_sp)) = generics.bounds_span_for_suggestions(param.def_id) {
336+
suggest_restrict(span, true, open_paren_sp);
332337
continue;
333338
}
334339

compiler/rustc_trait_selection/src/traits/error_reporting/type_err_ctxt_ext.rs

+19-8
Original file line numberDiff line numberDiff line change
@@ -2938,17 +2938,28 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
29382938
}
29392939
_ => {}
29402940
};
2941+
29412942
// Didn't add an indirection suggestion, so add a general suggestion to relax `Sized`.
2942-
let (span, separator) = if let Some(s) = generics.bounds_span_for_suggestions(param.def_id)
2943-
{
2944-
(s, " +")
2943+
let (span, separator, open_paren_sp) =
2944+
if let Some((s, open_paren_sp)) = generics.bounds_span_for_suggestions(param.def_id) {
2945+
(s, " +", open_paren_sp)
2946+
} else {
2947+
(param.name.ident().span.shrink_to_hi(), ":", None)
2948+
};
2949+
2950+
let mut suggs = vec![];
2951+
let suggestion = format!("{separator} ?Sized");
2952+
2953+
if let Some(open_paren_sp) = open_paren_sp {
2954+
suggs.push((open_paren_sp, "(".to_string()));
2955+
suggs.push((span, format!("){suggestion}")));
29452956
} else {
2946-
(param.name.ident().span.shrink_to_hi(), ":")
2947-
};
2948-
err.span_suggestion_verbose(
2949-
span,
2957+
suggs.push((span, suggestion));
2958+
}
2959+
2960+
err.multipart_suggestion_verbose(
29502961
"consider relaxing the implicit `Sized` restriction",
2951-
format!("{separator} ?Sized"),
2962+
suggs,
29522963
Applicability::MachineApplicable,
29532964
);
29542965
}

tests/ui/suggestions/impl-trait-with-missing-trait-bounds-in-arg.fixed

+12-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,18 @@ impl Foo for S {}
1212
impl Bar for S {}
1313

1414
fn test(foo: impl Foo + Bar) {
15-
foo.hello(); //~ ERROR E0599
15+
foo.hello(); //~ ERROR no method named `hello` found
16+
}
17+
18+
trait Trait {
19+
fn method(&self) {}
20+
}
21+
22+
impl Trait for fn() {}
23+
24+
#[allow(dead_code)]
25+
fn test2(f: impl Fn() -> (dyn std::fmt::Debug) + Trait) {
26+
f.method(); //~ ERROR no method named `method` found
1627
}
1728

1829
fn main() {

tests/ui/suggestions/impl-trait-with-missing-trait-bounds-in-arg.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,18 @@ impl Foo for S {}
1212
impl Bar for S {}
1313

1414
fn test(foo: impl Foo) {
15-
foo.hello(); //~ ERROR E0599
15+
foo.hello(); //~ ERROR no method named `hello` found
16+
}
17+
18+
trait Trait {
19+
fn method(&self) {}
20+
}
21+
22+
impl Trait for fn() {}
23+
24+
#[allow(dead_code)]
25+
fn test2(f: impl Fn() -> dyn std::fmt::Debug) {
26+
f.method(); //~ ERROR no method named `method` found
1627
}
1728

1829
fn main() {

tests/ui/suggestions/impl-trait-with-missing-trait-bounds-in-arg.stderr

+15-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,20 @@ help: the following trait defines an item `hello`, perhaps you need to restrict
1212
LL | fn test(foo: impl Foo + Bar) {
1313
| +++++
1414

15-
error: aborting due to 1 previous error
15+
error[E0599]: no method named `method` found for type parameter `impl Fn() -> dyn std::fmt::Debug` in the current scope
16+
--> $DIR/impl-trait-with-missing-trait-bounds-in-arg.rs:26:7
17+
|
18+
LL | fn test2(f: impl Fn() -> dyn std::fmt::Debug) {
19+
| -------------------------------- method `method` not found for this type parameter
20+
LL | f.method();
21+
| ^^^^^^ method not found in `impl Fn() -> dyn std::fmt::Debug`
22+
|
23+
= help: items from traits can only be used if the type parameter is bounded by the trait
24+
help: the following trait defines an item `method`, perhaps you need to restrict type parameter `impl Fn() -> dyn std::fmt::Debug` with it:
25+
|
26+
LL | fn test2(f: impl Fn() -> (dyn std::fmt::Debug) + Trait) {
27+
| + +++++++++
28+
29+
error: aborting due to 2 previous errors
1630

1731
For more information about this error, try `rustc --explain E0599`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#![feature(dyn_star)] //~ WARNING the feature `dyn_star` is incomplete
2+
3+
use std::future::Future;
4+
5+
pub fn dyn_func<T>(
6+
executor: impl FnOnce(T) -> dyn Future<Output = ()>,
7+
) -> Box<dyn FnOnce(T) -> dyn Future<Output = ()>> {
8+
Box::new(executor) //~ ERROR may not live long enough
9+
}
10+
11+
pub fn dyn_star_func<T>(
12+
executor: impl FnOnce(T) -> dyn* Future<Output = ()>,
13+
) -> Box<dyn FnOnce(T) -> dyn* Future<Output = ()>> {
14+
Box::new(executor) //~ ERROR may not live long enough
15+
}
16+
17+
trait Trait {
18+
fn method(&self) {}
19+
}
20+
21+
impl Trait for fn() {}
22+
23+
pub fn in_ty_param<T: Fn() -> dyn std::fmt::Debug> (t: T) {
24+
t.method();
25+
//~^ ERROR no method named `method` found for type parameter `T`
26+
}
27+
28+
fn with_sized<T: Fn() -> &'static (dyn std::fmt::Debug) + ?Sized>() {
29+
without_sized::<T>();
30+
//~^ ERROR the size for values of type `T` cannot be known at compilation time
31+
}
32+
33+
fn without_sized<T: Fn() -> &'static dyn std::fmt::Debug>() {}
34+
35+
fn main() {}

0 commit comments

Comments
 (0)