Skip to content

Commit 08764ad

Browse files
committed
Auto merge of #77087 - estebank:issue-45817, r=matthewjasper
Provide structured suggestions when finding structs when expecting a trait When finding an ADT in a trait object definition provide some solutions. Fix #45817. Given `<Param as Trait>::Assoc: Ty` suggest `Param: Trait<Assoc = Ty>`. Fix #75829.
2 parents b1af43b + 4ae8f6e commit 08764ad

File tree

7 files changed

+525
-24
lines changed

7 files changed

+525
-24
lines changed

compiler/rustc_ast/src/ast.rs

+10
Original file line numberDiff line numberDiff line change
@@ -1884,6 +1884,16 @@ impl Clone for Ty {
18841884
}
18851885
}
18861886

1887+
impl Ty {
1888+
pub fn peel_refs(&self) -> &Self {
1889+
let mut final_ty = self;
1890+
while let TyKind::Rptr(_, MutTy { ty, .. }) = &final_ty.kind {
1891+
final_ty = &ty;
1892+
}
1893+
final_ty
1894+
}
1895+
}
1896+
18871897
#[derive(Clone, Encodable, Decodable, Debug)]
18881898
pub struct BareFnTy {
18891899
pub unsafety: Unsafe,

compiler/rustc_resolve/src/late.rs

+20
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,13 @@ struct DiagnosticMetadata<'ast> {
384384

385385
/// Used to detect possible `if let` written without `let` and to provide structured suggestion.
386386
in_if_condition: Option<&'ast Expr>,
387+
388+
/// If we are currently in a trait object definition. Used to point at the bounds when
389+
/// encountering a struct or enum.
390+
current_trait_object: Option<&'ast [ast::GenericBound]>,
391+
392+
/// Given `where <T as Bar>::Baz: String`, suggest `where T: Bar<Baz = String>`.
393+
current_where_predicate: Option<&'ast WherePredicate>,
387394
}
388395

389396
struct LateResolutionVisitor<'a, 'b, 'ast> {
@@ -453,6 +460,7 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
453460
self.diagnostic_metadata.current_let_binding = original;
454461
}
455462
fn visit_ty(&mut self, ty: &'ast Ty) {
463+
let prev = self.diagnostic_metadata.current_trait_object;
456464
match ty.kind {
457465
TyKind::Path(ref qself, ref path) => {
458466
self.smart_resolve_path(ty.id, qself.as_ref(), path, PathSource::Type);
@@ -464,9 +472,13 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
464472
.map_or(Res::Err, |d| d.res());
465473
self.r.record_partial_res(ty.id, PartialRes::new(res));
466474
}
475+
TyKind::TraitObject(ref bounds, ..) => {
476+
self.diagnostic_metadata.current_trait_object = Some(&bounds[..]);
477+
}
467478
_ => (),
468479
}
469480
visit::walk_ty(self, ty);
481+
self.diagnostic_metadata.current_trait_object = prev;
470482
}
471483
fn visit_poly_trait_ref(&mut self, tref: &'ast PolyTraitRef, m: &'ast TraitBoundModifier) {
472484
self.smart_resolve_path(
@@ -660,6 +672,14 @@ impl<'a: 'ast, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
660672
}
661673
self.diagnostic_metadata.currently_processing_generics = prev;
662674
}
675+
676+
fn visit_where_predicate(&mut self, p: &'ast WherePredicate) {
677+
debug!("visit_where_predicate {:?}", p);
678+
let previous_value =
679+
replace(&mut self.diagnostic_metadata.current_where_predicate, Some(p));
680+
visit::walk_where_predicate(self, p);
681+
self.diagnostic_metadata.current_where_predicate = previous_value;
682+
}
663683
}
664684

665685
impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {

compiler/rustc_resolve/src/late/diagnostics.rs

+203-16
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
use crate::diagnostics::{ImportSuggestion, LabelSuggestion, TypoSuggestion};
22
use crate::late::lifetimes::{ElisionFailureInfo, LifetimeContext};
3-
use crate::late::{LateResolutionVisitor, RibKind};
3+
use crate::late::{AliasPossibility, LateResolutionVisitor, RibKind};
44
use crate::path_names_to_string;
55
use crate::{CrateLint, Module, ModuleKind, ModuleOrUniformRoot};
66
use crate::{PathResult, PathSource, Segment};
77

88
use rustc_ast::util::lev_distance::find_best_match_for_name;
99
use rustc_ast::visit::FnKind;
1010
use rustc_ast::{self as ast, Expr, ExprKind, Item, ItemKind, NodeId, Path, Ty, TyKind};
11+
use rustc_ast_pretty::pprust::path_segment_to_string;
1112
use rustc_data_structures::fx::FxHashSet;
1213
use rustc_errors::{pluralize, struct_span_err, Applicability, DiagnosticBuilder};
1314
use rustc_hir as hir;
@@ -19,7 +20,7 @@ use rustc_session::config::nightly_options;
1920
use rustc_session::parse::feature_err;
2021
use rustc_span::hygiene::MacroKind;
2122
use rustc_span::symbol::{kw, sym, Ident, Symbol};
22-
use rustc_span::{BytePos, Span, DUMMY_SP};
23+
use rustc_span::{BytePos, MultiSpan, Span, DUMMY_SP};
2324

2425
use tracing::debug;
2526

@@ -439,27 +440,213 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
439440
}
440441
}
441442

442-
if !self.type_ascription_suggestion(&mut err, base_span)
443-
&& !self.r.add_typo_suggestion(&mut err, typo_sugg, ident_span)
444-
{
445-
// Fallback label.
446-
err.span_label(base_span, fallback_label);
447-
448-
match self.diagnostic_metadata.current_let_binding {
449-
Some((pat_sp, Some(ty_sp), None)) if ty_sp.contains(base_span) && could_be_expr => {
450-
err.span_suggestion_short(
451-
pat_sp.between(ty_sp),
452-
"use `=` if you meant to assign",
453-
" = ".to_string(),
454-
Applicability::MaybeIncorrect,
443+
if !self.type_ascription_suggestion(&mut err, base_span) {
444+
let mut fallback = false;
445+
if let (
446+
PathSource::Trait(AliasPossibility::Maybe),
447+
Some(Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Union, _)),
448+
) = (source, res)
449+
{
450+
if let Some(bounds @ [_, .., _]) = self.diagnostic_metadata.current_trait_object {
451+
fallback = true;
452+
let spans: Vec<Span> = bounds
453+
.iter()
454+
.map(|bound| bound.span())
455+
.filter(|&sp| sp != base_span)
456+
.collect();
457+
458+
let start_span = bounds.iter().map(|bound| bound.span()).next().unwrap();
459+
// `end_span` is the end of the poly trait ref (Foo + 'baz + Bar><)
460+
let end_span = bounds.iter().map(|bound| bound.span()).last().unwrap();
461+
// `last_bound_span` is the last bound of the poly trait ref (Foo + >'baz< + Bar)
462+
let last_bound_span = spans.last().cloned().unwrap();
463+
let mut multi_span: MultiSpan = spans.clone().into();
464+
for sp in spans {
465+
let msg = if sp == last_bound_span {
466+
format!(
467+
"...because of {} bound{}",
468+
if bounds.len() <= 2 { "this" } else { "these" },
469+
if bounds.len() <= 2 { "" } else { "s" },
470+
)
471+
} else {
472+
String::new()
473+
};
474+
multi_span.push_span_label(sp, msg);
475+
}
476+
multi_span.push_span_label(
477+
base_span,
478+
"expected this type to be a trait...".to_string(),
455479
);
480+
err.span_help(
481+
multi_span,
482+
"`+` is used to constrain a \"trait object\" type with lifetimes or \
483+
auto-traits; structs and enums can't be bound in that way",
484+
);
485+
if bounds.iter().all(|bound| match bound {
486+
ast::GenericBound::Outlives(_) => true,
487+
ast::GenericBound::Trait(tr, _) => tr.span == base_span,
488+
}) {
489+
let mut sugg = vec![];
490+
if base_span != start_span {
491+
sugg.push((start_span.until(base_span), String::new()));
492+
}
493+
if base_span != end_span {
494+
sugg.push((base_span.shrink_to_hi().to(end_span), String::new()));
495+
}
496+
497+
err.multipart_suggestion(
498+
"if you meant to use a type and not a trait here, remove the bounds",
499+
sugg,
500+
Applicability::MaybeIncorrect,
501+
);
502+
}
456503
}
457-
_ => {}
504+
}
505+
506+
fallback |= self.restrict_assoc_type_in_where_clause(span, &mut err);
507+
508+
if !self.r.add_typo_suggestion(&mut err, typo_sugg, ident_span) {
509+
fallback = true;
510+
match self.diagnostic_metadata.current_let_binding {
511+
Some((pat_sp, Some(ty_sp), None))
512+
if ty_sp.contains(base_span) && could_be_expr =>
513+
{
514+
err.span_suggestion_short(
515+
pat_sp.between(ty_sp),
516+
"use `=` if you meant to assign",
517+
" = ".to_string(),
518+
Applicability::MaybeIncorrect,
519+
);
520+
}
521+
_ => {}
522+
}
523+
}
524+
if fallback {
525+
// Fallback label.
526+
err.span_label(base_span, fallback_label);
458527
}
459528
}
460529
(err, candidates)
461530
}
462531

532+
/// Given `where <T as Bar>::Baz: String`, suggest `where T: Bar<Baz = String>`.
533+
fn restrict_assoc_type_in_where_clause(
534+
&mut self,
535+
span: Span,
536+
err: &mut DiagnosticBuilder<'_>,
537+
) -> bool {
538+
// Detect that we are actually in a `where` predicate.
539+
let (bounded_ty, bounds, where_span) =
540+
if let Some(ast::WherePredicate::BoundPredicate(ast::WhereBoundPredicate {
541+
bounded_ty,
542+
bound_generic_params,
543+
bounds,
544+
span,
545+
})) = self.diagnostic_metadata.current_where_predicate
546+
{
547+
if !bound_generic_params.is_empty() {
548+
return false;
549+
}
550+
(bounded_ty, bounds, span)
551+
} else {
552+
return false;
553+
};
554+
555+
// Confirm that the target is an associated type.
556+
let (ty, position, path) = if let ast::TyKind::Path(
557+
Some(ast::QSelf { ty, position, .. }),
558+
path,
559+
) = &bounded_ty.kind
560+
{
561+
// use this to verify that ident is a type param.
562+
let partial_res = if let Ok(Some(partial_res)) = self.resolve_qpath_anywhere(
563+
bounded_ty.id,
564+
None,
565+
&Segment::from_path(path),
566+
Namespace::TypeNS,
567+
span,
568+
true,
569+
CrateLint::No,
570+
) {
571+
partial_res
572+
} else {
573+
return false;
574+
};
575+
if !(matches!(
576+
partial_res.base_res(),
577+
hir::def::Res::Def(hir::def::DefKind::AssocTy, _)
578+
) && partial_res.unresolved_segments() == 0)
579+
{
580+
return false;
581+
}
582+
(ty, position, path)
583+
} else {
584+
return false;
585+
};
586+
587+
if let ast::TyKind::Path(None, type_param_path) = &ty.peel_refs().kind {
588+
// Confirm that the `SelfTy` is a type parameter.
589+
let partial_res = if let Ok(Some(partial_res)) = self.resolve_qpath_anywhere(
590+
bounded_ty.id,
591+
None,
592+
&Segment::from_path(type_param_path),
593+
Namespace::TypeNS,
594+
span,
595+
true,
596+
CrateLint::No,
597+
) {
598+
partial_res
599+
} else {
600+
return false;
601+
};
602+
if !(matches!(
603+
partial_res.base_res(),
604+
hir::def::Res::Def(hir::def::DefKind::TyParam, _)
605+
) && partial_res.unresolved_segments() == 0)
606+
{
607+
return false;
608+
}
609+
if let (
610+
[ast::PathSegment { ident: constrain_ident, args: None, .. }],
611+
[ast::GenericBound::Trait(poly_trait_ref, ast::TraitBoundModifier::None)],
612+
) = (&type_param_path.segments[..], &bounds[..])
613+
{
614+
if let [ast::PathSegment { ident, args: None, .. }] =
615+
&poly_trait_ref.trait_ref.path.segments[..]
616+
{
617+
if ident.span == span {
618+
err.span_suggestion_verbose(
619+
*where_span,
620+
&format!("constrain the associated type to `{}`", ident),
621+
format!(
622+
"{}: {}<{} = {}>",
623+
self.r
624+
.session
625+
.source_map()
626+
.span_to_snippet(ty.span) // Account for `<&'a T as Foo>::Bar`.
627+
.unwrap_or_else(|_| constrain_ident.to_string()),
628+
path.segments[..*position]
629+
.iter()
630+
.map(|segment| path_segment_to_string(segment))
631+
.collect::<Vec<_>>()
632+
.join("::"),
633+
path.segments[*position..]
634+
.iter()
635+
.map(|segment| path_segment_to_string(segment))
636+
.collect::<Vec<_>>()
637+
.join("::"),
638+
ident,
639+
),
640+
Applicability::MaybeIncorrect,
641+
);
642+
}
643+
return true;
644+
}
645+
}
646+
}
647+
false
648+
}
649+
463650
/// Check if the source is call expression and the first argument is `self`. If true,
464651
/// return the span of whole call and the span for all arguments expect the first one (`self`).
465652
fn call_has_self_arg(&self, source: PathSource<'_>) -> Option<(Span, Option<Span>)> {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
trait Bar {
2+
type Baz;
3+
}
4+
5+
struct Foo<T> where T: Bar, <T as Bar>::Baz: String { //~ ERROR expected trait, found struct
6+
t: T,
7+
}
8+
9+
struct Qux<'a, T> where T: Bar, <&'a T as Bar>::Baz: String { //~ ERROR expected trait, found struct
10+
t: &'a T,
11+
}
12+
13+
fn foo<T: Bar>(_: T) where <T as Bar>::Baz: String { //~ ERROR expected trait, found struct
14+
}
15+
16+
fn qux<'a, T: Bar>(_: &'a T) where <&'a T as Bar>::Baz: String { //~ ERROR expected trait, found
17+
}
18+
19+
fn main() {}

0 commit comments

Comments
 (0)