diff options
| author | Matthias Krüger <matthias.krueger@famsik.de> | 2025-02-19 21:16:07 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-02-19 21:16:07 +0100 |
| commit | 82279108e1ce9cd5ec1cfa927d60d7747352a326 (patch) | |
| tree | 95a3f5bb13667e5b0ceb60f2af34238738ecf45e /compiler/rustc_resolve/src | |
| parent | 659838ebfdddd0c7ed2e97487313581416f0675a (diff) | |
| parent | 2c3725021e1b976ecc30b902f74e0f867c634592 (diff) | |
| download | rust-82279108e1ce9cd5ec1cfa927d60d7747352a326.tar.gz rust-82279108e1ce9cd5ec1cfa927d60d7747352a326.zip | |
Rollup merge of #136344 - zachs18:dot_notation_more_defkinds_3, r=davidtwco
Suggest replacing `.` with `::` in more error diagnostics.
First commit makes the existing "help: use the path separator to refer to an item" also work when the base is a type alias, not just a trait/module/struct.
The existing unconditional `DefKind::Mod | DefKind::Trait` match arm is changed to a conditional `DefKind::Mod | DefKind::Trait | DefKind::TyAlias` arm that only matches if the `path_sep` suggestion-adding closure succeeds, so as not to stop the later `DefKind::TyAlias`-specific suggestions if the path-sep suggestion does not apply. This shouldn't change behavior for `Mod` or `Trait` (due to the default arm's `return false` etc).
This commit also updates `tests/ui/resolve/issue-22692.rs` to reflect this, and also renames it to something more meaningful.
This commit also makes the `bad_struct_syntax_suggestion` closure take `err` as a parameter instead of capturing it, since otherwise caused borrowing errors due to the change to using `path_sep` in a pattern guard.
<details> <summary> Type alias diagnostic example </summary>
```rust
type S = String;
fn main() {
let _ = S.new;
}
```
```diff
error[E0423]: expected value, found type alias `S`
--> diag7.rs:4:13
|
4 | let _ = S.new;
| ^
|
- = note: can't use a type alias as a constructor
+ help: use the path separator to refer to an item
+ |
+4 | let _ = S::new;
+ | ~~
```
</details>
Second commit adds some cases for `enum`s, where if there is a field/method expression where the field/method has the name of a unit/tuple variant, we assume the user intended to create that variant[^1] and suggest replacing the `.` from the field/method suggestion with a `::` path separator. If no such variant is found (or if the error is not a field/method expression), we give the existing suggestion that suggests adding `::TupleVariant(/* fields */)` after the enum.
<details> <summary> Enum diagnostic example </summary>
```rust
enum Foo {
A(u32),
B,
C { x: u32 },
}
fn main() {
let _ = Foo.A(42); // changed
let _ = Foo.B; // changed
let _ = Foo.D(42); // no change
let _ = Foo.D; // no change
let _ = Foo(42); // no change
}
```
```diff
error[E0423]: expected value, found enum `Foo`
--> diag8.rs:8:13
|
8 | let _ = Foo.A(42); // changed
| ^^^
|
note: the enum is defined here
--> diag8.rs:1:1
|
1 | / enum Foo {
2 | | A(u32),
3 | | B,
4 | | C { x: u32 },
5 | | }
| |_^
-help: you might have meant to use the following enum variant
- |
-8 | let _ = Foo::B.A(42); // changed
- | ~~~~~~
-help: alternatively, the following enum variant is available
+help: use the path separator to refer to a variant
|
-8 | let _ = (Foo::A(/* fields */)).A(42); // changed
- | ~~~~~~~~~~~~~~~~~~~~~~
+8 | let _ = Foo::A(42); // changed
+ | ~~
error[E0423]: expected value, found enum `Foo`
--> diag8.rs:9:13
|
9 | let _ = Foo.B; // changed
| ^^^
|
note: the enum is defined here
--> diag8.rs:1:1
|
1 | / enum Foo {
2 | | A(u32),
3 | | B,
4 | | C { x: u32 },
5 | | }
| |_^
-help: you might have meant to use the following enum variant
- |
-9 | let _ = Foo::B.B; // changed
- | ~~~~~~
-help: alternatively, the following enum variant is available
+help: use the path separator to refer to a variant
|
-9 | let _ = (Foo::A(/* fields */)).B; // changed
- | ~~~~~~~~~~~~~~~~~~~~~~
+9 | let _ = Foo::B; // changed
+ | ~~
error[E0423]: expected value, found enum `Foo`
--> diag8.rs:10:13
|
10 | let _ = Foo.D(42); // no change
| ^^^
|
note: the enum is defined here
--> diag8.rs:1:1
|
1 | / enum Foo {
2 | | A(u32),
3 | | B,
4 | | C { x: u32 },
5 | | }
| |_^
help: you might have meant to use the following enum variant
|
10 | let _ = Foo::B.D(42); // no change
| ~~~~~~
help: alternatively, the following enum variant is available
|
10 | let _ = (Foo::A(/* fields */)).D(42); // no change
| ~~~~~~~~~~~~~~~~~~~~~~
error[E0423]: expected value, found enum `Foo`
--> diag8.rs:11:13
|
11 | let _ = Foo.D; // no change
| ^^^
|
note: the enum is defined here
--> diag8.rs:1:1
|
1 | / enum Foo {
2 | | A(u32),
3 | | B,
4 | | C { x: u32 },
5 | | }
| |_^
help: you might have meant to use the following enum variant
|
11 | let _ = Foo::B.D; // no change
| ~~~~~~
help: alternatively, the following enum variant is available
|
11 | let _ = (Foo::A(/* fields */)).D; // no change
| ~~~~~~~~~~~~~~~~~~~~~~
error[E0423]: expected function, tuple struct or tuple variant, found enum `Foo`
--> diag8.rs:12:13
|
12 | let _ = Foo(42); // no change
| ^^^ help: try to construct one of the enum's variants: `Foo::A`
|
= help: you might have meant to construct the enum's non-tuple variant
note: the enum is defined here
--> diag8.rs:1:1
|
1 | / enum Foo {
2 | | A(u32),
3 | | B,
4 | | C { x: u32 },
5 | | }
| |_^
error: aborting due to 5 previous errors
```
</details>
[^1]: or if it's a field expression and a tuple variant, that they meant to refer the variant constructor.
Diffstat (limited to 'compiler/rustc_resolve/src')
| -rw-r--r-- | compiler/rustc_resolve/src/late/diagnostics.rs | 88 |
1 files changed, 64 insertions, 24 deletions
diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 0962865e7f1..b37c684a055 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -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}; @@ -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) { @@ -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 { @@ -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), @@ -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; }; @@ -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 { @@ -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() { @@ -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}")); } @@ -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)) @@ -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)) |
