Skip to content

Commit c1ed215

Browse files
committed
fix: incorrect suggestion for nonexistent enum variant and applying generic on variant of type-aliased enum
1 parent ec38671 commit c1ed215

14 files changed

+2415
-101
lines changed

compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs

Lines changed: 33 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1625,7 +1625,7 @@ pub(crate) fn fn_trait_to_string(
16251625
}
16261626
}
16271627

1628-
/// Used for generics args error extend.
1628+
/// Extra information for diagnostics of generics args error.
16291629
pub enum GenericsArgsErrExtend<'tcx> {
16301630
EnumVariant {
16311631
qself: &'tcx hir::Ty<'tcx>,
@@ -1652,87 +1652,65 @@ fn generics_args_err_extend<'a>(
16521652
) {
16531653
match err_extend {
16541654
GenericsArgsErrExtend::EnumVariant { qself, assoc_segment, adt_def } => {
1655-
err.note("enum variants can't have type parameters");
1655+
err.note("variants of type-aliased enum can't have type parameters");
16561656
let type_name = tcx.item_name(adt_def.did());
1657-
let msg = format!(
1658-
"you might have meant to specify type parameters on enum \
1659-
`{type_name}`"
1660-
);
1661-
let Some(args) = assoc_segment.args else {
1657+
let Some(variant_args) = assoc_segment.args else {
16621658
return;
16631659
};
16641660
// Get the span of the generics args *including* the leading `::`.
16651661
// We do so by stretching args.span_ext to the left by 2. Earlier
16661662
// it was done based on the end of assoc segment but that sometimes
16671663
// led to impossible spans and caused issues like #116473
1668-
let args_span = args.span_ext.with_lo(args.span_ext.lo() - BytePos(2));
1664+
let variant_args_span =
1665+
variant_args.span_ext.with_lo(variant_args.span_ext.lo() - BytePos(2));
16691666
if tcx.generics_of(adt_def.did()).is_empty() {
16701667
// FIXME(estebank): we could also verify that the arguments being
16711668
// work for the `enum`, instead of just looking if it takes *any*.
16721669
err.span_suggestion_verbose(
1673-
args_span,
1670+
variant_args_span,
16741671
format!("{type_name} doesn't have generic parameters"),
16751672
"",
16761673
Applicability::MachineApplicable,
16771674
);
16781675
return;
16791676
}
1680-
let Ok(snippet) = tcx.sess.source_map().span_to_snippet(args_span) else {
1677+
let msg = "you might have meant to specify type parameters on the enum";
1678+
let Ok(snippet) = tcx.sess.source_map().span_to_snippet(variant_args_span) else {
1679+
err.note(msg);
1680+
return;
1681+
};
1682+
// If the path segment already has type params, we want to overwrite them.
1683+
let hir::TyKind::Path(hir::QPath::Resolved(_, path)) = &qself.kind else {
1684+
err.note(msg);
1685+
return;
1686+
};
1687+
// The last element of `path.segments` is the previous to last element
1688+
// on the path and would normally be the enum alias itself.
1689+
let Some(hir::PathSegment {
1690+
ident,
1691+
args: enum_args,
1692+
res: Res::SelfTyAlias { .. } | Res::Def(DefKind::TyAlias, _),
1693+
..
1694+
}) = &path.segments.last()
1695+
else {
16811696
err.note(msg);
16821697
return;
16831698
};
1684-
let (qself_sugg_span, is_self) =
1685-
if let hir::TyKind::Path(hir::QPath::Resolved(_, path)) = &qself.kind {
1686-
// If the path segment already has type params, we want to overwrite
1687-
// them.
1688-
match &path.segments {
1689-
// `segment` is the previous to last element on the path,
1690-
// which would normally be the `enum` itself, while the last
1691-
// `_` `PathSegment` corresponds to the variant.
1692-
[
1693-
..,
1694-
hir::PathSegment {
1695-
ident, args, res: Res::Def(DefKind::Enum, _), ..
1696-
},
1697-
_,
1698-
] => (
1699-
// We need to include the `::` in `Type::Variant::<Args>`
1700-
// to point the span to `::<Args>`, not just `<Args>`.
1701-
ident
1702-
.span
1703-
.shrink_to_hi()
1704-
.to(args.map_or(ident.span.shrink_to_hi(), |a| a.span_ext)),
1705-
false,
1706-
),
1707-
[segment] => {
1708-
(
1709-
// We need to include the `::` in `Type::Variant::<Args>`
1710-
// to point the span to `::<Args>`, not just `<Args>`.
1711-
segment.ident.span.shrink_to_hi().to(segment
1712-
.args
1713-
.map_or(segment.ident.span.shrink_to_hi(), |a| a.span_ext)),
1714-
kw::SelfUpper == segment.ident.name,
1715-
)
1716-
}
1717-
_ => {
1718-
err.note(msg);
1719-
return;
1720-
}
1721-
}
1722-
} else {
1723-
err.note(msg);
1724-
return;
1725-
};
17261699
let suggestion = vec![
1727-
if is_self {
1700+
if path.segments.len() == 1 && kw::SelfUpper == ident.name {
17281701
// Account for people writing `Self::Variant::<Args>`, where
17291702
// `Self` is the enum, and suggest replacing `Self` with the
17301703
// appropriate type: `Type::<Args>::Variant`.
17311704
(qself.span, format!("{type_name}{snippet}"))
17321705
} else {
1733-
(qself_sugg_span, snippet)
1706+
// We need to include the `::` in `Type::<Args>::Variants`
1707+
// to point the span to `::<Args>`, not just `<Args>`.
1708+
let enum_args_span = ident.span.shrink_to_hi().to(enum_args
1709+
.and_then(|a| a.span_ext())
1710+
.unwrap_or(ident.span.shrink_to_hi()));
1711+
(enum_args_span, snippet)
17341712
},
1735-
(args_span, String::new()),
1713+
(variant_args_span, String::new()),
17361714
];
17371715
err.multipart_suggestion_verbose(msg, suggestion, Applicability::MaybeIncorrect);
17381716
}

compiler/rustc_hir_typeck/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -422,10 +422,10 @@ fn report_unexpected_variant_res(
422422
let mut suggestion = vec![];
423423
match tcx.parent_hir_node(expr.hir_id) {
424424
hir::Node::Expr(hir::Expr {
425-
kind: hir::ExprKind::Call(..),
425+
kind: hir::ExprKind::Call(callee, ..),
426426
span: call_span,
427427
..
428-
}) => {
428+
}) if callee.hir_id == expr.hir_id => {
429429
suggestion.push((span.shrink_to_hi().with_hi(call_span.hi()), sugg));
430430
}
431431
hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Binary(..), hir_id, .. }) => {

compiler/rustc_hir_typeck/src/method/suggest.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1592,13 +1592,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
15921592
&& let hir::Node::Stmt(&hir::Stmt { kind: hir::StmtKind::Semi(parent), .. })
15931593
| hir::Node::Expr(parent) = self.tcx.parent_hir_node(path_expr.hir_id)
15941594
{
1595-
let replacement_span =
1596-
if let hir::ExprKind::Call(..) | hir::ExprKind::Struct(..) = parent.kind {
1597-
// We want to replace the parts that need to go, like `()` and `{}`.
1598-
span.with_hi(parent.span.hi())
1599-
} else {
1600-
span
1601-
};
1595+
// We want to also replace variant constructor part like `()` and `{}`.
1596+
let replacement_span = match parent.kind {
1597+
hir::ExprKind::Call(callee, ..) if callee.hir_id == path_expr.hir_id => {
1598+
if span.hi() == callee.span.hi() {
1599+
span.with_hi(parent.span.hi())
1600+
} else {
1601+
// Bail if there are parens around the variant like `(A::B)()`
1602+
span
1603+
}
1604+
}
1605+
hir::ExprKind::Struct(..) => span.with_hi(parent.span.hi()),
1606+
_ => span,
1607+
};
16021608
match (variant.ctor, parent.kind) {
16031609
(None, hir::ExprKind::Struct(..)) => {
16041610
// We want a struct and we have a struct. We won't suggest changing
@@ -1631,7 +1637,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
16311637
(
16321638
Some((hir::def::CtorKind::Fn, def_id)),
16331639
hir::ExprKind::Call(rcvr, args),
1634-
) => {
1640+
) if rcvr.hir_id == path_expr.hir_id => {
16351641
let fn_sig = self.tcx.fn_sig(def_id).instantiate_identity();
16361642
let inputs = fn_sig.inputs().skip_binder();
16371643
// FIXME: reuse the logic for "change args" suggestion to account for types

tests/crashes/130395.rs

Lines changed: 0 additions & 10 deletions
This file was deleted.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Regression test for #146586
2+
3+
//@ only-linux
4+
//@ compile-flags: --error-format=human --color=always
5+
6+
enum Enum {
7+
Unit,
8+
Tuple(i32),
9+
Struct { x: i32 },
10+
}
11+
12+
fn foo(_: Enum) {}
13+
14+
fn main() {
15+
foo(Enum::Unit);
16+
foo(Enum::Tuple);
17+
foo(Enum::Struct); // Suggestion was malformed
18+
foo(Enum::Unit());
19+
foo(Enum::Tuple());
20+
foo(Enum::Struct());
21+
foo(Enum::Unit {});
22+
foo(Enum::Tuple {});
23+
foo(Enum::Struct {});
24+
foo(Enum::Unit(0));
25+
foo(Enum::Tuple(0));
26+
foo(Enum::Struct(0));
27+
foo(Enum::Unit { x: 0 });
28+
foo(Enum::Tuple { x: 0 });
29+
foo(Enum::Struct { x: 0 });
30+
foo(Enum::Unit(0, 0));
31+
foo(Enum::Tuple(0, 0));
32+
foo(Enum::Struct(0, 0));
33+
foo(Enum::Unit { x: 0, y: 0 });
34+
foo(Enum::Tuple { x: 0, y: 0 });
35+
foo(Enum::Struct { x: 0, y: 0 });
36+
foo(Enum::unit); // Suggestion was malformed
37+
foo(Enum::tuple); // Suggestion is enhanced
38+
foo(Enum::r#struct); // Suggestion was malformed
39+
foo(Enum::unit());
40+
foo(Enum::tuple());
41+
foo(Enum::r#struct());
42+
foo(Enum::unit {}); // Suggestion could be enhanced
43+
foo(Enum::tuple {}); // Suggestion could be enhanced
44+
foo(Enum::r#struct {}); // Suggestion could be enhanced
45+
foo(Enum::unit(0));
46+
foo(Enum::tuple(0));
47+
foo(Enum::r#struct(0));
48+
foo(Enum::unit { x: 0 }); // Suggestion could be enhanced
49+
foo(Enum::tuple { x: 0 }); // Suggestion could be enhanced
50+
foo(Enum::r#struct { x: 0 });
51+
foo(Enum::unit(0, 0));
52+
foo(Enum::tuple(0, 0));
53+
foo(Enum::r#struct(0, 0));
54+
foo(Enum::unit { x: 0, y: 0 }); // Suggestion could be enhanced
55+
foo(Enum::tuple { x: 0, y: 0 }); // Suggestion could be enhanced
56+
foo(Enum::r#struct { x: 0, y: 0 }); // Suggestion could be enhanced
57+
}

0 commit comments

Comments
 (0)