Skip to content

Commit

Permalink
More accurate suggestions when writing wrong style of enum variant li…
Browse files Browse the repository at this point in the history
…teral

```
error[E0533]: expected value, found struct variant `E::Empty3`
  --> $DIR/empty-struct-braces-expr.rs:18:14
   |
LL |     let e3 = E::Empty3;
   |              ^^^^^^^^^ not a value
   |
help: you might have meant to create a new value of the struct
   |
LL |     let e3 = E::Empty3 {};
   |                        ++
```
```
error[E0533]: expected value, found struct variant `E::V`
  --> $DIR/struct-literal-variant-in-if.rs:10:13
   |
LL |     if x == E::V { field } {}
   |             ^^^^ not a value
   |
help: you might have meant to create a new value of the struct
   |
LL |     if x == (E::V { field }) {}
   |             +              +
```
```
error[E0618]: expected function, found enum variant `Enum::Unit`
  --> $DIR/suggestion-highlights.rs:15:5
   |
LL |     Unit,
   |     ---- enum variant `Enum::Unit` defined here
...
LL |     Enum::Unit();
   |     ^^^^^^^^^^--
   |     |
   |     call expression requires function
   |
help: `Enum::Unit` is a unit enum variant, and does not take parentheses to be constructed
   |
LL -     Enum::Unit();
LL +     Enum::Unit;
   |
```
```
error[E0599]: no variant or associated item named `tuple` found for enum `Enum` in the current scope
  --> $DIR/suggestion-highlights.rs:36:11
   |
LL | enum Enum {
   | --------- variant or associated item `tuple` not found for this enum
...
LL |     Enum::tuple;
   |           ^^^^^ variant or associated item not found in `Enum`
   |
help: there is a variant with a similar name
   |
LL |     Enum::Tuple(/* i32 */);
   |           ~~~~~~~~~~~~~~~~;
   |
```
  • Loading branch information
estebank committed Jul 18, 2024
1 parent 757073a commit d1f5e32
Show file tree
Hide file tree
Showing 19 changed files with 940 additions and 432 deletions.
66 changes: 60 additions & 6 deletions compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1087,20 +1087,74 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
);

let adt_def = qself_ty.ty_adt_def().expect("enum is not an ADT");
if let Some(suggested_name) = find_best_match_for_name(
if let Some(variant_name) = find_best_match_for_name(
&adt_def
.variants()
.iter()
.map(|variant| variant.name)
.collect::<Vec<Symbol>>(),
assoc_ident.name,
None,
) {
err.span_suggestion(
assoc_ident.span,
) && let Some(variant) =
adt_def.variants().iter().find(|s| s.name == variant_name)
{
let mut suggestion = vec![(assoc_ident.span, variant_name.to_string())];
if let hir::Node::Stmt(hir::Stmt {
kind: hir::StmtKind::Semi(ref expr),
..
})
| hir::Node::Expr(ref expr) = tcx.parent_hir_node(hir_ref_id)
&& let hir::ExprKind::Struct(..) = expr.kind
{
match variant.ctor {
None => {
// struct
suggestion = vec![(
assoc_ident.span.with_hi(expr.span.hi()),
if variant.fields.is_empty() {
format!("{variant_name} {{}}")
} else {
format!(
"{variant_name} {{ {} }}",
variant
.fields
.iter()
.map(|f| format!("{}: /* value */", f.name))
.collect::<Vec<_>>()
.join(", ")
)
},
)];
}
Some((hir::def::CtorKind::Fn, def_id)) => {
// tuple
let fn_sig = tcx.fn_sig(def_id).instantiate_identity();
let inputs = fn_sig.inputs().skip_binder();
suggestion = vec![(
assoc_ident.span.with_hi(expr.span.hi()),
format!(
"{variant_name}({})",
inputs
.iter()
.map(|i| format!("/* {i} */"))
.collect::<Vec<_>>()
.join(", ")
),
)];
}
Some((hir::def::CtorKind::Const, _)) => {
// unit
suggestion = vec![(
assoc_ident.span.with_hi(expr.span.hi()),
variant_name.to_string(),
)];
}
}
}
err.multipart_suggestion_verbose(
"there is a variant with a similar name",
suggested_name,
Applicability::MaybeIncorrect,
suggestion,
Applicability::HasPlaceholders,
);
} else {
err.span_label(
Expand Down
10 changes: 9 additions & 1 deletion compiler/rustc_hir_typeck/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Ty::new_error(tcx, e)
}
Res::Def(DefKind::Variant, _) => {
let e = report_unexpected_variant_res(tcx, res, qpath, expr.span, E0533, "value");
let e = report_unexpected_variant_res(
tcx,
res,
Some(expr),
qpath,
expr.span,
E0533,
"value",
);
Ty::new_error(tcx, e)
}
_ => {
Expand Down
60 changes: 58 additions & 2 deletions compiler/rustc_hir_typeck/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ use crate::expectation::Expectation;
use crate::fn_ctxt::LoweredTy;
use crate::gather_locals::GatherLocalsVisitor;
use rustc_data_structures::unord::UnordSet;
use rustc_errors::{codes::*, struct_span_code_err, ErrorGuaranteed};
use rustc_errors::{codes::*, struct_span_code_err, Applicability, ErrorGuaranteed};
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::intravisit::Visitor;
Expand Down Expand Up @@ -346,6 +346,7 @@ impl<'tcx> EnclosingBreakables<'tcx> {
fn report_unexpected_variant_res(
tcx: TyCtxt<'_>,
res: Res,
expr: Option<&hir::Expr<'_>>,
qpath: &hir::QPath<'_>,
span: Span,
err_code: ErrCode,
Expand All @@ -356,7 +357,7 @@ fn report_unexpected_variant_res(
_ => res.descr(),
};
let path_str = rustc_hir_pretty::qpath_to_string(&tcx, qpath);
let err = tcx
let mut err = tcx
.dcx()
.struct_span_err(span, format!("expected {expected}, found {res_descr} `{path_str}`"))
.with_code(err_code);
Expand All @@ -366,6 +367,61 @@ fn report_unexpected_variant_res(
err.with_span_label(span, "`fn` calls are not allowed in patterns")
.with_help(format!("for more information, visit {patterns_url}"))
}
Res::Def(DefKind::Variant, _) if let Some(expr) = expr => {
err.span_label(span, format!("not a {expected}"));
let variant = tcx.expect_variant_res(res);
let sugg = if variant.fields.is_empty() {
" {}".to_string()
} else {
format!(
" {{ {} }}",
variant
.fields
.iter()
.map(|f| format!("{}: /* value */", f.name))
.collect::<Vec<_>>()
.join(", ")
)
};
let descr = "you might have meant to create a new value of the struct";
let mut suggestion = vec![];
match tcx.parent_hir_node(expr.hir_id) {
hir::Node::Expr(hir::Expr {
kind: hir::ExprKind::Call(..),
span: call_span,
..
}) => {
suggestion.push((span.shrink_to_hi().with_hi(call_span.hi()), sugg));
}
hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Binary(..), hir_id, .. }) => {
suggestion.push((expr.span.shrink_to_lo(), "(".to_string()));
if let hir::Node::Expr(drop_temps) = tcx.parent_hir_node(*hir_id)
&& let hir::ExprKind::DropTemps(_) = drop_temps.kind
&& let hir::Node::Expr(parent) = tcx.parent_hir_node(drop_temps.hir_id)
&& let hir::ExprKind::If(condition, block, None) = parent.kind
&& condition.hir_id == drop_temps.hir_id
&& let hir::ExprKind::Block(block, _) = block.kind
&& block.stmts.is_empty()
&& let Some(expr) = block.expr
&& let hir::ExprKind::Path(..) = expr.kind
{
// Special case: you can incorrectly write an equality condition:
// if foo == Struct { field } { /* if body */ }
// which should have been written
// if foo == (Struct { field }) { /* if body */ }
suggestion.push((block.span.shrink_to_hi(), ")".to_string()));
} else {
suggestion.push((span.shrink_to_hi().with_hi(expr.span.hi()), sugg));
}
}
_ => {
suggestion.push((span.shrink_to_hi(), sugg));
}
}

err.multipart_suggestion_verbose(descr, suggestion, Applicability::MaybeIncorrect);
err
}
_ => err.with_span_label(span, format!("not a {expected}")),
}
.emit()
Expand Down
121 changes: 116 additions & 5 deletions compiler/rustc_hir_typeck/src/method/suggest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1596,16 +1596,127 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// that had unsatisfied trait bounds
if unsatisfied_predicates.is_empty() && rcvr_ty.is_enum() {
let adt_def = rcvr_ty.ty_adt_def().expect("enum is not an ADT");
if let Some(suggestion) = edit_distance::find_best_match_for_name(
if let Some(var_name) = edit_distance::find_best_match_for_name(
&adt_def.variants().iter().map(|s| s.name).collect::<Vec<_>>(),
item_name.name,
None,
) {
err.span_suggestion(
span,
) && let Some(variant) = adt_def.variants().iter().find(|s| s.name == var_name)
{
let mut suggestion = vec![(span, var_name.to_string())];
if let SelfSource::QPath(ty) = source
&& let hir::Node::Expr(ref path_expr) = self.tcx.parent_hir_node(ty.hir_id)
&& let hir::ExprKind::Path(_) = path_expr.kind
&& let hir::Node::Stmt(hir::Stmt {
kind: hir::StmtKind::Semi(ref parent), ..
})
| hir::Node::Expr(ref parent) = self.tcx.parent_hir_node(path_expr.hir_id)
{
let replacement_span =
if let hir::ExprKind::Call(..) | hir::ExprKind::Struct(..) = parent.kind {
// We want to replace the parts that need to go, like `()` and `{}`.
span.with_hi(parent.span.hi())
} else {
span
};
match (variant.ctor, parent.kind) {
(None, hir::ExprKind::Struct(..)) => {
// We want a struct and we have a struct. We won't suggest changing
// the fields (at least for now).
suggestion = vec![(span, var_name.to_string())];
}
(None, _) => {
// struct
suggestion = vec![(
replacement_span,
if variant.fields.is_empty() {
format!("{var_name} {{}}")
} else {
format!(
"{var_name} {{ {} }}",
variant
.fields
.iter()
.map(|f| format!("{}: /* value */", f.name))
.collect::<Vec<_>>()
.join(", ")
)
},
)];
}
(Some((hir::def::CtorKind::Const, _)), _) => {
// unit, remove the `()`.
suggestion = vec![(replacement_span, var_name.to_string())];
}
(
Some((hir::def::CtorKind::Fn, def_id)),
hir::ExprKind::Call(rcvr, args),
) => {
let fn_sig = self.tcx.fn_sig(def_id).instantiate_identity();
let inputs = fn_sig.inputs().skip_binder();
// FIXME: reuse the logic for "change args" suggestion to account for types
// involved and detect things like substitution.
match (inputs, args) {
(inputs, []) => {
// Add arguments.
suggestion.push((
rcvr.span.shrink_to_hi().with_hi(parent.span.hi()),
format!(
"({})",
inputs
.iter()
.map(|i| format!("/* {i} */"))
.collect::<Vec<String>>()
.join(", ")
),
));
}
(_, [arg]) if inputs.len() != args.len() => {
// Replace arguments.
suggestion.push((
arg.span,
inputs
.iter()
.map(|i| format!("/* {i} */"))
.collect::<Vec<String>>()
.join(", "),
));
}
(_, [arg_start, .., arg_end]) if inputs.len() != args.len() => {
// Replace arguments.
suggestion.push((
arg_start.span.to(arg_end.span),
inputs
.iter()
.map(|i| format!("/* {i} */"))
.collect::<Vec<String>>()
.join(", "),
));
}
// Argument count is the same, keep as is.
_ => {}
}
}
(Some((hir::def::CtorKind::Fn, def_id)), _) => {
let fn_sig = self.tcx.fn_sig(def_id).instantiate_identity();
let inputs = fn_sig.inputs().skip_binder();
suggestion = vec![(
replacement_span,
format!(
"{var_name}({})",
inputs
.iter()
.map(|i| format!("/* {i} */"))
.collect::<Vec<String>>()
.join(", ")
),
)];
}
}
}
err.multipart_suggestion_verbose(
"there is a variant with a similar name",
suggestion,
Applicability::MaybeIncorrect,
Applicability::HasPlaceholders,
);
}
}
Expand Down
6 changes: 4 additions & 2 deletions compiler/rustc_hir_typeck/src/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1023,7 +1023,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
Res::Def(DefKind::AssocFn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::Variant, _) => {
let expected = "unit struct, unit variant or constant";
let e = report_unexpected_variant_res(tcx, res, qpath, pat.span, E0533, expected);
let e =
report_unexpected_variant_res(tcx, res, None, qpath, pat.span, E0533, expected);
return Ty::new_error(tcx, e);
}
Res::SelfCtor(def_id) => {
Expand All @@ -1036,6 +1037,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let e = report_unexpected_variant_res(
tcx,
res,
None,
qpath,
pat.span,
E0533,
Expand Down Expand Up @@ -1189,7 +1191,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
};
let report_unexpected_res = |res: Res| {
let expected = "tuple struct or tuple variant";
let e = report_unexpected_variant_res(tcx, res, qpath, pat.span, E0164, expected);
let e = report_unexpected_variant_res(tcx, res, None, qpath, pat.span, E0164, expected);
on_error(e);
e
};
Expand Down
Loading

0 comments on commit d1f5e32

Please sign in to comment.