Skip to content

Commit 952ab57

Browse files
committed
More accurate suggestions when writing wrong style of enum variant literal
``` 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 */); | ~~~~~~~~~~~~~~~~; | ```
1 parent df6fb97 commit 952ab57

19 files changed

+940
-432
lines changed

compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs

+60-6
Original file line numberDiff line numberDiff line change
@@ -1087,20 +1087,74 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
10871087
);
10881088

10891089
let adt_def = qself_ty.ty_adt_def().expect("enum is not an ADT");
1090-
if let Some(suggested_name) = find_best_match_for_name(
1090+
if let Some(variant_name) = find_best_match_for_name(
10911091
&adt_def
10921092
.variants()
10931093
.iter()
10941094
.map(|variant| variant.name)
10951095
.collect::<Vec<Symbol>>(),
10961096
assoc_ident.name,
10971097
None,
1098-
) {
1099-
err.span_suggestion(
1100-
assoc_ident.span,
1098+
) && let Some(variant) =
1099+
adt_def.variants().iter().find(|s| s.name == variant_name)
1100+
{
1101+
let mut suggestion = vec![(assoc_ident.span, variant_name.to_string())];
1102+
if let hir::Node::Stmt(hir::Stmt {
1103+
kind: hir::StmtKind::Semi(ref expr),
1104+
..
1105+
})
1106+
| hir::Node::Expr(ref expr) = tcx.parent_hir_node(hir_ref_id)
1107+
&& let hir::ExprKind::Struct(..) = expr.kind
1108+
{
1109+
match variant.ctor {
1110+
None => {
1111+
// struct
1112+
suggestion = vec![(
1113+
assoc_ident.span.with_hi(expr.span.hi()),
1114+
if variant.fields.is_empty() {
1115+
format!("{variant_name} {{}}")
1116+
} else {
1117+
format!(
1118+
"{variant_name} {{ {} }}",
1119+
variant
1120+
.fields
1121+
.iter()
1122+
.map(|f| format!("{}: /* value */", f.name))
1123+
.collect::<Vec<_>>()
1124+
.join(", ")
1125+
)
1126+
},
1127+
)];
1128+
}
1129+
Some((hir::def::CtorKind::Fn, def_id)) => {
1130+
// tuple
1131+
let fn_sig = tcx.fn_sig(def_id).instantiate_identity();
1132+
let inputs = fn_sig.inputs().skip_binder();
1133+
suggestion = vec![(
1134+
assoc_ident.span.with_hi(expr.span.hi()),
1135+
format!(
1136+
"{variant_name}({})",
1137+
inputs
1138+
.iter()
1139+
.map(|i| format!("/* {i} */"))
1140+
.collect::<Vec<_>>()
1141+
.join(", ")
1142+
),
1143+
)];
1144+
}
1145+
Some((hir::def::CtorKind::Const, _)) => {
1146+
// unit
1147+
suggestion = vec![(
1148+
assoc_ident.span.with_hi(expr.span.hi()),
1149+
variant_name.to_string(),
1150+
)];
1151+
}
1152+
}
1153+
}
1154+
err.multipart_suggestion_verbose(
11011155
"there is a variant with a similar name",
1102-
suggested_name,
1103-
Applicability::MaybeIncorrect,
1156+
suggestion,
1157+
Applicability::HasPlaceholders,
11041158
);
11051159
} else {
11061160
err.span_label(

compiler/rustc_hir_typeck/src/expr.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
519519
Ty::new_error(tcx, e)
520520
}
521521
Res::Def(DefKind::Variant, _) => {
522-
let e = report_unexpected_variant_res(tcx, res, qpath, expr.span, E0533, "value");
522+
let e = report_unexpected_variant_res(
523+
tcx,
524+
res,
525+
Some(expr),
526+
qpath,
527+
expr.span,
528+
E0533,
529+
"value",
530+
);
523531
Ty::new_error(tcx, e)
524532
}
525533
_ => {

compiler/rustc_hir_typeck/src/lib.rs

+58-2
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ use crate::expectation::Expectation;
5353
use crate::fn_ctxt::LoweredTy;
5454
use crate::gather_locals::GatherLocalsVisitor;
5555
use rustc_data_structures::unord::UnordSet;
56-
use rustc_errors::{codes::*, struct_span_code_err, ErrorGuaranteed};
56+
use rustc_errors::{codes::*, struct_span_code_err, Applicability, ErrorGuaranteed};
5757
use rustc_hir as hir;
5858
use rustc_hir::def::{DefKind, Res};
5959
use rustc_hir::intravisit::Visitor;
@@ -346,6 +346,7 @@ impl<'tcx> EnclosingBreakables<'tcx> {
346346
fn report_unexpected_variant_res(
347347
tcx: TyCtxt<'_>,
348348
res: Res,
349+
expr: Option<&hir::Expr<'_>>,
349350
qpath: &hir::QPath<'_>,
350351
span: Span,
351352
err_code: ErrCode,
@@ -356,7 +357,7 @@ fn report_unexpected_variant_res(
356357
_ => res.descr(),
357358
};
358359
let path_str = rustc_hir_pretty::qpath_to_string(&tcx, qpath);
359-
let err = tcx
360+
let mut err = tcx
360361
.dcx()
361362
.struct_span_err(span, format!("expected {expected}, found {res_descr} `{path_str}`"))
362363
.with_code(err_code);
@@ -366,6 +367,61 @@ fn report_unexpected_variant_res(
366367
err.with_span_label(span, "`fn` calls are not allowed in patterns")
367368
.with_help(format!("for more information, visit {patterns_url}"))
368369
}
370+
Res::Def(DefKind::Variant, _) if let Some(expr) = expr => {
371+
err.span_label(span, format!("not a {expected}"));
372+
let variant = tcx.expect_variant_res(res);
373+
let sugg = if variant.fields.is_empty() {
374+
" {}".to_string()
375+
} else {
376+
format!(
377+
" {{ {} }}",
378+
variant
379+
.fields
380+
.iter()
381+
.map(|f| format!("{}: /* value */", f.name))
382+
.collect::<Vec<_>>()
383+
.join(", ")
384+
)
385+
};
386+
let descr = "you might have meant to create a new value of the struct";
387+
let mut suggestion = vec![];
388+
match tcx.parent_hir_node(expr.hir_id) {
389+
hir::Node::Expr(hir::Expr {
390+
kind: hir::ExprKind::Call(..),
391+
span: call_span,
392+
..
393+
}) => {
394+
suggestion.push((span.shrink_to_hi().with_hi(call_span.hi()), sugg));
395+
}
396+
hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Binary(..), hir_id, .. }) => {
397+
suggestion.push((expr.span.shrink_to_lo(), "(".to_string()));
398+
if let hir::Node::Expr(drop_temps) = tcx.parent_hir_node(*hir_id)
399+
&& let hir::ExprKind::DropTemps(_) = drop_temps.kind
400+
&& let hir::Node::Expr(parent) = tcx.parent_hir_node(drop_temps.hir_id)
401+
&& let hir::ExprKind::If(condition, block, None) = parent.kind
402+
&& condition.hir_id == drop_temps.hir_id
403+
&& let hir::ExprKind::Block(block, _) = block.kind
404+
&& block.stmts.is_empty()
405+
&& let Some(expr) = block.expr
406+
&& let hir::ExprKind::Path(..) = expr.kind
407+
{
408+
// Special case: you can incorrectly write an equality condition:
409+
// if foo == Struct { field } { /* if body */ }
410+
// which should have been written
411+
// if foo == (Struct { field }) { /* if body */ }
412+
suggestion.push((block.span.shrink_to_hi(), ")".to_string()));
413+
} else {
414+
suggestion.push((span.shrink_to_hi().with_hi(expr.span.hi()), sugg));
415+
}
416+
}
417+
_ => {
418+
suggestion.push((span.shrink_to_hi(), sugg));
419+
}
420+
}
421+
422+
err.multipart_suggestion_verbose(descr, suggestion, Applicability::MaybeIncorrect);
423+
err
424+
}
369425
_ => err.with_span_label(span, format!("not a {expected}")),
370426
}
371427
.emit()

compiler/rustc_hir_typeck/src/method/suggest.rs

+116-5
Original file line numberDiff line numberDiff line change
@@ -1596,16 +1596,127 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
15961596
// that had unsatisfied trait bounds
15971597
if unsatisfied_predicates.is_empty() && rcvr_ty.is_enum() {
15981598
let adt_def = rcvr_ty.ty_adt_def().expect("enum is not an ADT");
1599-
if let Some(suggestion) = edit_distance::find_best_match_for_name(
1599+
if let Some(var_name) = edit_distance::find_best_match_for_name(
16001600
&adt_def.variants().iter().map(|s| s.name).collect::<Vec<_>>(),
16011601
item_name.name,
16021602
None,
1603-
) {
1604-
err.span_suggestion(
1605-
span,
1603+
) && let Some(variant) = adt_def.variants().iter().find(|s| s.name == var_name)
1604+
{
1605+
let mut suggestion = vec![(span, var_name.to_string())];
1606+
if let SelfSource::QPath(ty) = source
1607+
&& let hir::Node::Expr(ref path_expr) = self.tcx.parent_hir_node(ty.hir_id)
1608+
&& let hir::ExprKind::Path(_) = path_expr.kind
1609+
&& let hir::Node::Stmt(hir::Stmt {
1610+
kind: hir::StmtKind::Semi(ref parent), ..
1611+
})
1612+
| hir::Node::Expr(ref parent) = self.tcx.parent_hir_node(path_expr.hir_id)
1613+
{
1614+
let replacement_span =
1615+
if let hir::ExprKind::Call(..) | hir::ExprKind::Struct(..) = parent.kind {
1616+
// We want to replace the parts that need to go, like `()` and `{}`.
1617+
span.with_hi(parent.span.hi())
1618+
} else {
1619+
span
1620+
};
1621+
match (variant.ctor, parent.kind) {
1622+
(None, hir::ExprKind::Struct(..)) => {
1623+
// We want a struct and we have a struct. We won't suggest changing
1624+
// the fields (at least for now).
1625+
suggestion = vec![(span, var_name.to_string())];
1626+
}
1627+
(None, _) => {
1628+
// struct
1629+
suggestion = vec![(
1630+
replacement_span,
1631+
if variant.fields.is_empty() {
1632+
format!("{var_name} {{}}")
1633+
} else {
1634+
format!(
1635+
"{var_name} {{ {} }}",
1636+
variant
1637+
.fields
1638+
.iter()
1639+
.map(|f| format!("{}: /* value */", f.name))
1640+
.collect::<Vec<_>>()
1641+
.join(", ")
1642+
)
1643+
},
1644+
)];
1645+
}
1646+
(Some((hir::def::CtorKind::Const, _)), _) => {
1647+
// unit, remove the `()`.
1648+
suggestion = vec![(replacement_span, var_name.to_string())];
1649+
}
1650+
(
1651+
Some((hir::def::CtorKind::Fn, def_id)),
1652+
hir::ExprKind::Call(rcvr, args),
1653+
) => {
1654+
let fn_sig = self.tcx.fn_sig(def_id).instantiate_identity();
1655+
let inputs = fn_sig.inputs().skip_binder();
1656+
// FIXME: reuse the logic for "change args" suggestion to account for types
1657+
// involved and detect things like substitution.
1658+
match (inputs, args) {
1659+
(inputs, []) => {
1660+
// Add arguments.
1661+
suggestion.push((
1662+
rcvr.span.shrink_to_hi().with_hi(parent.span.hi()),
1663+
format!(
1664+
"({})",
1665+
inputs
1666+
.iter()
1667+
.map(|i| format!("/* {i} */"))
1668+
.collect::<Vec<String>>()
1669+
.join(", ")
1670+
),
1671+
));
1672+
}
1673+
(_, [arg]) if inputs.len() != args.len() => {
1674+
// Replace arguments.
1675+
suggestion.push((
1676+
arg.span,
1677+
inputs
1678+
.iter()
1679+
.map(|i| format!("/* {i} */"))
1680+
.collect::<Vec<String>>()
1681+
.join(", "),
1682+
));
1683+
}
1684+
(_, [arg_start, .., arg_end]) if inputs.len() != args.len() => {
1685+
// Replace arguments.
1686+
suggestion.push((
1687+
arg_start.span.to(arg_end.span),
1688+
inputs
1689+
.iter()
1690+
.map(|i| format!("/* {i} */"))
1691+
.collect::<Vec<String>>()
1692+
.join(", "),
1693+
));
1694+
}
1695+
// Argument count is the same, keep as is.
1696+
_ => {}
1697+
}
1698+
}
1699+
(Some((hir::def::CtorKind::Fn, def_id)), _) => {
1700+
let fn_sig = self.tcx.fn_sig(def_id).instantiate_identity();
1701+
let inputs = fn_sig.inputs().skip_binder();
1702+
suggestion = vec![(
1703+
replacement_span,
1704+
format!(
1705+
"{var_name}({})",
1706+
inputs
1707+
.iter()
1708+
.map(|i| format!("/* {i} */"))
1709+
.collect::<Vec<String>>()
1710+
.join(", ")
1711+
),
1712+
)];
1713+
}
1714+
}
1715+
}
1716+
err.multipart_suggestion_verbose(
16061717
"there is a variant with a similar name",
16071718
suggestion,
1608-
Applicability::MaybeIncorrect,
1719+
Applicability::HasPlaceholders,
16091720
);
16101721
}
16111722
}

compiler/rustc_hir_typeck/src/pat.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -1023,7 +1023,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
10231023
}
10241024
Res::Def(DefKind::AssocFn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::Variant, _) => {
10251025
let expected = "unit struct, unit variant or constant";
1026-
let e = report_unexpected_variant_res(tcx, res, qpath, pat.span, E0533, expected);
1026+
let e =
1027+
report_unexpected_variant_res(tcx, res, None, qpath, pat.span, E0533, expected);
10271028
return Ty::new_error(tcx, e);
10281029
}
10291030
Res::SelfCtor(def_id) => {
@@ -1036,6 +1037,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
10361037
let e = report_unexpected_variant_res(
10371038
tcx,
10381039
res,
1040+
None,
10391041
qpath,
10401042
pat.span,
10411043
E0533,
@@ -1189,7 +1191,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
11891191
};
11901192
let report_unexpected_res = |res: Res| {
11911193
let expected = "tuple struct or tuple variant";
1192-
let e = report_unexpected_variant_res(tcx, res, qpath, pat.span, E0164, expected);
1194+
let e = report_unexpected_variant_res(tcx, res, None, qpath, pat.span, E0164, expected);
11931195
on_error(e);
11941196
e
11951197
};

0 commit comments

Comments
 (0)