Skip to content

Commit d470334

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; LL + Enum::Tuple(/* i32 */); | ```
1 parent bfbc036 commit d470334

18 files changed

+1979
-101
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_verbose(
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
@@ -517,7 +517,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
517517
Ty::new_error(tcx, e)
518518
}
519519
Res::Def(DefKind::Variant, _) => {
520-
let e = report_unexpected_variant_res(tcx, res, qpath, expr.span, E0533, "value");
520+
let e = report_unexpected_variant_res(
521+
tcx,
522+
res,
523+
Some(expr),
524+
qpath,
525+
expr.span,
526+
E0533,
527+
"value",
528+
);
521529
Ty::new_error(tcx, e)
522530
}
523531
_ => {

compiler/rustc_hir_typeck/src/lib.rs

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

tests/ui/empty/empty-struct-braces-expr.stderr

+12-1
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,23 @@ error[E0533]: expected value, found struct variant `E::Empty3`
7777
|
7878
LL | let e3 = E::Empty3;
7979
| ^^^^^^^^^ not a value
80+
|
81+
help: you might have meant to create a new value of the struct
82+
|
83+
LL | let e3 = E::Empty3 {};
84+
| ++
8085

8186
error[E0533]: expected value, found struct variant `E::Empty3`
8287
--> $DIR/empty-struct-braces-expr.rs:19:14
8388
|
8489
LL | let e3 = E::Empty3();
8590
| ^^^^^^^^^ not a value
91+
|
92+
help: you might have meant to create a new value of the struct
93+
|
94+
LL - let e3 = E::Empty3();
95+
LL + let e3 = E::Empty3 {};
96+
|
8697

8798
error[E0423]: expected function, tuple struct or tuple variant, found struct `XEmpty1`
8899
--> $DIR/empty-struct-braces-expr.rs:23:15
@@ -129,7 +140,7 @@ LL | let xe3 = XE::Empty3();
129140
help: there is a variant with a similar name
130141
|
131142
LL - let xe3 = XE::Empty3();
132-
LL + let xe3 = XE::XEmpty3();
143+
LL + let xe3 = XE::XEmpty3 {};
133144
|
134145

135146
error[E0599]: no variant named `Empty1` found for enum `empty_struct::XE`

tests/ui/enum/error-variant-with-turbofishes.stderr

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ error[E0533]: expected value, found struct variant `Struct<0>::Variant`
33
|
44
LL | let x = Struct::<0>::Variant;
55
| ^^^^^^^^^^^^^^^^^^^^ not a value
6+
|
7+
help: you might have meant to create a new value of the struct
8+
|
9+
LL | let x = Struct::<0>::Variant { x: /* value */ };
10+
| ++++++++++++++++++
611

712
error: aborting due to 1 previous error
813

0 commit comments

Comments
 (0)