Skip to content

Suggest replacing . with :: in more error diagnostics. #136344

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 64 additions & 24 deletions compiler/rustc_resolve/src/late/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use rustc_ast::ptr::P;
use rustc_ast::visit::{FnCtxt, FnKind, LifetimeCtxt, Visitor, walk_ty};
use rustc_ast::{
self as ast, AssocItemKind, DUMMY_NODE_ID, Expr, ExprKind, GenericParam, GenericParamKind,
Item, ItemKind, MethodCall, NodeId, Path, Ty, TyKind,
Item, ItemKind, MethodCall, NodeId, Path, PathSegment, Ty, TyKind,
};
use rustc_ast_pretty::pprust::where_bound_predicate_to_string;
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
Expand Down Expand Up @@ -1529,7 +1529,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
Applicability::MaybeIncorrect,
);
true
} else if kind == DefKind::Struct
} else if matches!(kind, DefKind::Struct | DefKind::TyAlias)
&& let Some(lhs_source_span) = lhs_span.find_ancestor_inside(expr.span)
&& let Ok(snippet) = this.r.tcx.sess.source_map().span_to_snippet(lhs_source_span)
{
Expand Down Expand Up @@ -1566,7 +1566,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
}
};

let mut bad_struct_syntax_suggestion = |this: &mut Self, def_id: DefId| {
let bad_struct_syntax_suggestion = |this: &mut Self, err: &mut Diag<'_>, def_id: DefId| {
let (followed_by_brace, closing_brace) = this.followed_by_brace(span);

match source {
Expand Down Expand Up @@ -1740,12 +1740,10 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
}
}
(
Res::Def(kind @ (DefKind::Mod | DefKind::Trait), _),
Res::Def(kind @ (DefKind::Mod | DefKind::Trait | DefKind::TyAlias), _),
PathSource::Expr(Some(parent)),
) => {
if !path_sep(self, err, parent, kind) {
return false;
}
) if path_sep(self, err, parent, kind) => {
return true;
}
(
Res::Def(DefKind::Enum, def_id),
Expand Down Expand Up @@ -1777,13 +1775,13 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
let (ctor_def, ctor_vis, fields) = if let Some(struct_ctor) = struct_ctor {
if let PathSource::Expr(Some(parent)) = source {
if let ExprKind::Field(..) | ExprKind::MethodCall(..) = parent.kind {
bad_struct_syntax_suggestion(self, def_id);
bad_struct_syntax_suggestion(self, err, def_id);
return true;
}
}
struct_ctor
} else {
bad_struct_syntax_suggestion(self, def_id);
bad_struct_syntax_suggestion(self, err, def_id);
return true;
};

Expand Down Expand Up @@ -1861,7 +1859,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
err.span_label(span, "constructor is not visible here due to private fields");
}
(Res::Def(DefKind::Union | DefKind::Variant, def_id), _) if ns == ValueNS => {
bad_struct_syntax_suggestion(self, def_id);
bad_struct_syntax_suggestion(self, err, def_id);
}
(Res::Def(DefKind::Ctor(_, CtorKind::Const), def_id), _) if ns == ValueNS => {
match source {
Expand Down Expand Up @@ -2471,31 +2469,73 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
def_id: DefId,
span: Span,
) {
let Some(variants) = self.collect_enum_ctors(def_id) else {
let Some(variant_ctors) = self.collect_enum_ctors(def_id) else {
err.note("you might have meant to use one of the enum's variants");
return;
};

let suggest_only_tuple_variants =
matches!(source, PathSource::TupleStruct(..)) || source.is_call();
if suggest_only_tuple_variants {
// If the expression is a field-access or method-call, try to find a variant with the field/method name
// that could have been intended, and suggest replacing the `.` with `::`.
// Otherwise, suggest adding `::VariantName` after the enum;
// and if the expression is call-like, only suggest tuple variants.
let (suggest_path_sep_dot_span, suggest_only_tuple_variants) = match source {
// `Type(a, b)` in a pattern, only suggest adding a tuple variant after `Type`.
PathSource::TupleStruct(..) => (None, true),
PathSource::Expr(Some(expr)) => match &expr.kind {
// `Type(a, b)`, only suggest adding a tuple variant after `Type`.
ExprKind::Call(..) => (None, true),
// `Type.Foo(a, b)`, suggest replacing `.` -> `::` if variant `Foo` exists and is a tuple variant,
// otherwise suggest adding a variant after `Type`.
ExprKind::MethodCall(box MethodCall {
receiver,
span,
seg: PathSegment { ident, .. },
..
}) => {
let dot_span = receiver.span.between(*span);
let found_tuple_variant = variant_ctors.iter().any(|(path, _, ctor_kind)| {
*ctor_kind == CtorKind::Fn
&& path.segments.last().is_some_and(|seg| seg.ident == *ident)
});
(found_tuple_variant.then_some(dot_span), false)
}
// `Type.Foo`, suggest replacing `.` -> `::` if variant `Foo` exists and is a unit or tuple variant,
// otherwise suggest adding a variant after `Type`.
ExprKind::Field(base, ident) => {
let dot_span = base.span.between(ident.span);
let found_tuple_or_unit_variant = variant_ctors.iter().any(|(path, ..)| {
path.segments.last().is_some_and(|seg| seg.ident == *ident)
});
(found_tuple_or_unit_variant.then_some(dot_span), false)
}
_ => (None, false),
},
_ => (None, false),
};

if let Some(dot_span) = suggest_path_sep_dot_span {
err.span_suggestion_verbose(
dot_span,
"use the path separator to refer to a variant",
"::",
Applicability::MaybeIncorrect,
);
} else if suggest_only_tuple_variants {
// Suggest only tuple variants regardless of whether they have fields and do not
// suggest path with added parentheses.
let mut suggestable_variants = variants
let mut suggestable_variants = variant_ctors
.iter()
.filter(|(.., kind)| *kind == CtorKind::Fn)
.map(|(variant, ..)| path_names_to_string(variant))
.collect::<Vec<_>>();
suggestable_variants.sort();

let non_suggestable_variant_count = variants.len() - suggestable_variants.len();
let non_suggestable_variant_count = variant_ctors.len() - suggestable_variants.len();

let source_msg = if source.is_call() {
"to construct"
} else if matches!(source, PathSource::TupleStruct(..)) {
let source_msg = if matches!(source, PathSource::TupleStruct(..)) {
"to match against"
} else {
unreachable!()
"to construct"
};

if !suggestable_variants.is_empty() {
Expand All @@ -2514,7 +2554,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
}

// If the enum has no tuple variants..
if non_suggestable_variant_count == variants.len() {
if non_suggestable_variant_count == variant_ctors.len() {
err.help(format!("the enum has no tuple variants {source_msg}"));
}

Expand All @@ -2537,7 +2577,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
}
};

let mut suggestable_variants = variants
let mut suggestable_variants = variant_ctors
.iter()
.filter(|(_, def_id, kind)| !needs_placeholder(*def_id, *kind))
.map(|(variant, _, kind)| (path_names_to_string(variant), kind))
Expand All @@ -2564,7 +2604,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
);
}

let mut suggestable_variants_with_placeholders = variants
let mut suggestable_variants_with_placeholders = variant_ctors
.iter()
.filter(|(_, def_id, kind)| needs_placeholder(*def_id, *kind))
.map(|(variant, _, kind)| (path_names_to_string(variant), kind))
Expand Down
1 change: 0 additions & 1 deletion src/tools/tidy/src/issues.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3619,7 +3619,6 @@ ui/resolve/issue-21221-1.rs
ui/resolve/issue-21221-2.rs
ui/resolve/issue-21221-3.rs
ui/resolve/issue-21221-4.rs
ui/resolve/issue-22692.rs
ui/resolve/issue-2330.rs
ui/resolve/issue-23305.rs
ui/resolve/issue-2356.rs
Expand Down
123 changes: 123 additions & 0 deletions tests/ui/resolve/dot-notation-type-namespace-suggest-path-sep.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// see also https://github.com/rust-lang/rust/issues/22692

type Alias = Vec<u32>;

mod foo {
fn bar() {}
}

fn main() {
let _ = String.new();
//~^ ERROR expected value, found struct `String`
//~| HELP use the path separator

let _ = String.default;
//~^ ERROR expected value, found struct `String`
//~| HELP use the path separator

let _ = Vec::<()>.with_capacity(1);
//~^ ERROR expected value, found struct `Vec`
//~| HELP use the path separator

let _ = Alias.new();
//~^ ERROR expected value, found type alias `Alias`
//~| HELP use the path separator

let _ = Alias.default;
//~^ ERROR expected value, found type alias `Alias`
//~| HELP use the path separator

let _ = foo.bar;
//~^ ERROR expected value, found module `foo`
//~| HELP use the path separator
}

macro_rules! Type {
() => {
::std::cell::Cell
//~^ ERROR expected value, found struct `std::cell::Cell`
//~| ERROR expected value, found struct `std::cell::Cell`
//~| ERROR expected value, found struct `std::cell::Cell`
};
(alias) => {
Alias
//~^ ERROR expected value, found type alias `Alias`
//~| ERROR expected value, found type alias `Alias`
//~| ERROR expected value, found type alias `Alias`
};
}

macro_rules! create {
(type method) => {
Vec.new()
//~^ ERROR expected value, found struct `Vec`
//~| HELP use the path separator
};
(type field) => {
Vec.new
//~^ ERROR expected value, found struct `Vec`
//~| HELP use the path separator
};
(macro method) => {
Type!().new(0)
//~^ HELP use the path separator
};
(macro method alias) => {
Type!(alias).new(0)
//~^ HELP use the path separator
};
}

macro_rules! check_ty {
($Ty:ident) => {
$Ty.foo
//~^ ERROR expected value, found type alias `Alias`
//~| HELP use the path separator
};
}
macro_rules! check_ident {
($Ident:ident) => {
Alias.$Ident
//~^ ERROR expected value, found type alias `Alias`
//~| HELP use the path separator
};
}
macro_rules! check_ty_ident {
($Ty:ident, $Ident:ident) => {
$Ty.$Ident
//~^ ERROR expected value, found type alias `Alias`
//~| HELP use the path separator
};
}

fn interaction_with_macros() {
//
// Verify that we do not only suggest to replace `.` with `::` if the receiver is a
// macro call but that we also correctly suggest to surround it with angle brackets.
//

Type!().get();
//~^ HELP use the path separator

Type! {}.get;
//~^ HELP use the path separator

Type!(alias).get();
//~^ HELP use the path separator

Type! {alias}.get;
//~^ HELP use the path separator

//
// Ensure that the suggestion is shown for expressions inside of macro definitions.
//

let _ = create!(type method);
let _ = create!(type field);
let _ = create!(macro method);
let _ = create!(macro method alias);

let _ = check_ty!(Alias);
let _ = check_ident!(foo);
let _ = check_ty_ident!(Alias, foo);
}
Loading
Loading