Rollup merge of #128110 - veera-sivarajan:bugfix-80173, r=cjgillot

Suggest Replacing Comma with Semicolon in Incorrect Repeat Expressions

Fixes #80173

This PR detects typos in repeat expressions like `["_", 10]` and `vec![String::new(), 10]` and suggests replacing comma with semicolon.

Also, improves code in other place by adding doc comments and making use of a helper function to check if a type implements `Clone`.

References:
1. For `vec![T; N]`: https://doc.rust-lang.org/std/macro.vec.html
2. For `[T; N]`: https://doc.rust-lang.org/std/primitive.array.html
This commit is contained in:
Matthias Krüger 2025-01-09 06:02:39 +01:00 committed by GitHub
commit e4e2d9ceb8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 324 additions and 11 deletions

View file

@ -7,7 +7,7 @@ use rustc_ast::token::CommentKind;
use rustc_ast::util::parser::{AssocOp, ExprPrecedence}; use rustc_ast::util::parser::{AssocOp, ExprPrecedence};
use rustc_ast::{ use rustc_ast::{
self as ast, AttrId, AttrStyle, DelimArgs, FloatTy, InlineAsmOptions, InlineAsmTemplatePiece, self as ast, AttrId, AttrStyle, DelimArgs, FloatTy, InlineAsmOptions, InlineAsmTemplatePiece,
IntTy, Label, LitKind, MetaItemInner, MetaItemLit, TraitObjectSyntax, UintTy, IntTy, Label, LitIntType, LitKind, MetaItemInner, MetaItemLit, TraitObjectSyntax, UintTy,
}; };
pub use rustc_ast::{ pub use rustc_ast::{
BinOp, BinOpKind, BindingMode, BorrowKind, BoundConstness, BoundPolarity, ByRef, CaptureBy, BinOp, BinOpKind, BindingMode, BorrowKind, BoundConstness, BoundPolarity, ByRef, CaptureBy,
@ -2094,6 +2094,18 @@ impl Expr<'_> {
} }
} }
/// Check if expression is an integer literal that can be used
/// where `usize` is expected.
pub fn is_size_lit(&self) -> bool {
matches!(
self.kind,
ExprKind::Lit(Lit {
node: LitKind::Int(_, LitIntType::Unsuffixed | LitIntType::Unsigned(UintTy::Usize)),
..
})
)
}
/// If `Self.kind` is `ExprKind::DropTemps(expr)`, drill down until we get a non-`DropTemps` /// If `Self.kind` is `ExprKind::DropTemps(expr)`, drill down until we get a non-`DropTemps`
/// `Expr`. This is used in suggestions to ignore this `ExprKind` as it is semantically /// `Expr`. This is used in suggestions to ignore this `ExprKind` as it is semantically
/// silent, only signaling the ownership system. By doing this, suggestions that check the /// silent, only signaling the ownership system. By doing this, suggestions that check the

View file

@ -165,6 +165,8 @@ hir_typeck_remove_semi_for_coerce_ret = the `match` arms can conform to this ret
hir_typeck_remove_semi_for_coerce_semi = the `match` is a statement because of this semicolon, consider removing it hir_typeck_remove_semi_for_coerce_semi = the `match` is a statement because of this semicolon, consider removing it
hir_typeck_remove_semi_for_coerce_suggestion = remove this semicolon hir_typeck_remove_semi_for_coerce_suggestion = remove this semicolon
hir_typeck_replace_comma_with_semicolon = replace the comma with a semicolon to create {$descr}
hir_typeck_return_stmt_outside_of_fn_body = hir_typeck_return_stmt_outside_of_fn_body =
{$statement_kind} statement outside of function body {$statement_kind} statement outside of function body
.encl_body_label = the {$statement_kind} is part of this body... .encl_body_label = the {$statement_kind} is part of this body...

View file

@ -30,7 +30,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
if expr_ty == expected { if expr_ty == expected {
return; return;
} }
self.annotate_alternative_method_deref(err, expr, error); self.annotate_alternative_method_deref(err, expr, error);
self.explain_self_literal(err, expr, expected, expr_ty); self.explain_self_literal(err, expr, expected, expr_ty);
@ -39,6 +38,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|| self.suggest_missing_unwrap_expect(err, expr, expected, expr_ty) || self.suggest_missing_unwrap_expect(err, expr, expected, expr_ty)
|| self.suggest_remove_last_method_call(err, expr, expected) || self.suggest_remove_last_method_call(err, expr, expected)
|| self.suggest_associated_const(err, expr, expected) || self.suggest_associated_const(err, expr, expected)
|| self.suggest_semicolon_in_repeat_expr(err, expr, expr_ty)
|| self.suggest_deref_ref_or_into(err, expr, expected, expr_ty, expected_ty_expr) || self.suggest_deref_ref_or_into(err, expr, expected, expr_ty, expected_ty_expr)
|| self.suggest_option_to_bool(err, expr, expr_ty, expected) || self.suggest_option_to_bool(err, expr, expr_ty, expected)
|| self.suggest_compatible_variants(err, expr, expected, expr_ty) || self.suggest_compatible_variants(err, expr, expected, expr_ty)

View file

@ -846,3 +846,16 @@ pub(crate) struct PassFnItemToVariadicFunction {
pub sugg_span: Span, pub sugg_span: Span,
pub replace: String, pub replace: String,
} }
#[derive(Subdiagnostic)]
#[suggestion(
hir_typeck_replace_comma_with_semicolon,
applicability = "machine-applicable",
style = "verbose",
code = "; "
)]
pub(crate) struct ReplaceCommaWithSemicolon {
#[primary_span]
pub comma_span: Span,
pub descr: &'static str,
}

View file

@ -1320,14 +1320,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let span = expr.span.shrink_to_hi(); let span = expr.span.shrink_to_hi();
let subdiag = if self.type_is_copy_modulo_regions(self.param_env, ty) { let subdiag = if self.type_is_copy_modulo_regions(self.param_env, ty) {
errors::OptionResultRefMismatch::Copied { span, def_path } errors::OptionResultRefMismatch::Copied { span, def_path }
} else if let Some(clone_did) = self.tcx.lang_items().clone_trait() } else if self.type_is_clone_modulo_regions(self.param_env, ty) {
&& rustc_trait_selection::traits::type_known_to_meet_bound_modulo_regions(
self,
self.param_env,
ty,
clone_did,
)
{
errors::OptionResultRefMismatch::Cloned { span, def_path } errors::OptionResultRefMismatch::Cloned { span, def_path }
} else { } else {
return false; return false;
@ -2182,6 +2175,87 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
} }
} }
/// Suggest replacing comma with semicolon in incorrect repeat expressions
/// like `["_", 10]` or `vec![String::new(), 10]`.
pub(crate) fn suggest_semicolon_in_repeat_expr(
&self,
err: &mut Diag<'_>,
expr: &hir::Expr<'_>,
expr_ty: Ty<'tcx>,
) -> bool {
// Check if `expr` is contained in array of two elements
if let hir::Node::Expr(array_expr) = self.tcx.parent_hir_node(expr.hir_id)
&& let hir::ExprKind::Array(elements) = array_expr.kind
&& let [first, second] = &elements[..]
&& second.hir_id == expr.hir_id
{
// Span between the two elements of the array
let comma_span = first.span.between(second.span);
// Check if `expr` is a constant value of type `usize`.
// This can only detect const variable declarations and
// calls to const functions.
// Checking this here instead of rustc_hir::hir because
// this check needs access to `self.tcx` but rustc_hir
// has no access to `TyCtxt`.
let expr_is_const_usize = expr_ty.is_usize()
&& match expr.kind {
ExprKind::Path(QPath::Resolved(
None,
Path { res: Res::Def(DefKind::Const, _), .. },
)) => true,
ExprKind::Call(
Expr {
kind:
ExprKind::Path(QPath::Resolved(
None,
Path { res: Res::Def(DefKind::Fn, fn_def_id), .. },
)),
..
},
_,
) => self.tcx.is_const_fn(*fn_def_id),
_ => false,
};
// Type of the first element is guaranteed to be checked
// when execution reaches here because `mismatched types`
// error occurs only when type of second element of array
// is not the same as type of first element.
let first_ty = self.typeck_results.borrow().expr_ty(first);
// `array_expr` is from a macro `vec!["a", 10]` if
// 1. array expression's span is imported from a macro
// 2. first element of array implements `Clone` trait
// 3. second element is an integer literal or is an expression of `usize` like type
if self.tcx.sess.source_map().is_imported(array_expr.span)
&& self.type_is_clone_modulo_regions(self.param_env, first_ty)
&& (expr.is_size_lit() || expr_ty.is_usize_like())
{
err.subdiagnostic(errors::ReplaceCommaWithSemicolon {
comma_span,
descr: "a vector",
});
return true;
}
// `array_expr` is from an array `["a", 10]` if
// 1. first element of array implements `Copy` trait
// 2. second element is an integer literal or is a const value of type `usize`
if self.type_is_copy_modulo_regions(self.param_env, first_ty)
&& (expr.is_size_lit() || expr_is_const_usize)
{
err.subdiagnostic(errors::ReplaceCommaWithSemicolon {
comma_span,
descr: "an array",
});
return true;
}
}
false
}
/// If the expected type is an enum (Issue #55250) with any variants whose /// If the expected type is an enum (Issue #55250) with any variants whose
/// sole field is of the found type, suggest such variants. (Issue #42764) /// sole field is of the found type, suggest such variants. (Issue #42764)
pub(crate) fn suggest_compatible_variants( pub(crate) fn suggest_compatible_variants(

View file

@ -27,7 +27,7 @@ use crate::infer::canonical::Canonical;
use crate::ty::InferTy::*; use crate::ty::InferTy::*;
use crate::ty::{ use crate::ty::{
self, AdtDef, BoundRegionKind, Discr, GenericArg, GenericArgs, GenericArgsRef, List, ParamEnv, self, AdtDef, BoundRegionKind, Discr, GenericArg, GenericArgs, GenericArgsRef, List, ParamEnv,
Region, Ty, TyCtxt, TypeFlags, TypeSuperVisitable, TypeVisitable, TypeVisitor, Region, Ty, TyCtxt, TypeFlags, TypeSuperVisitable, TypeVisitable, TypeVisitor, UintTy,
}; };
// Re-export and re-parameterize some `I = TyCtxt<'tcx>` types here // Re-export and re-parameterize some `I = TyCtxt<'tcx>` types here
@ -1017,6 +1017,18 @@ impl<'tcx> Ty<'tcx> {
} }
} }
/// Check if type is an `usize`.
#[inline]
pub fn is_usize(self) -> bool {
matches!(self.kind(), Uint(UintTy::Usize))
}
/// Check if type is an `usize` or an integral type variable.
#[inline]
pub fn is_usize_like(self) -> bool {
matches!(self.kind(), Uint(UintTy::Usize) | Infer(IntVar(_)))
}
#[inline] #[inline]
pub fn is_never(self) -> bool { pub fn is_never(self) -> bool {
matches!(self.kind(), Never) matches!(self.kind(), Never)

View file

@ -47,6 +47,12 @@ impl<'tcx> InferCtxt<'tcx> {
traits::type_known_to_meet_bound_modulo_regions(self, param_env, ty, copy_def_id) traits::type_known_to_meet_bound_modulo_regions(self, param_env, ty, copy_def_id)
} }
fn type_is_clone_modulo_regions(&self, param_env: ty::ParamEnv<'tcx>, ty: Ty<'tcx>) -> bool {
let ty = self.resolve_vars_if_possible(ty);
let clone_def_id = self.tcx.require_lang_item(LangItem::Clone, None);
traits::type_known_to_meet_bound_modulo_regions(self, param_env, ty, clone_def_id)
}
fn type_is_sized_modulo_regions(&self, param_env: ty::ParamEnv<'tcx>, ty: Ty<'tcx>) -> bool { fn type_is_sized_modulo_regions(&self, param_env: ty::ParamEnv<'tcx>, ty: Ty<'tcx>) -> bool {
let lang_item = self.tcx.require_lang_item(LangItem::Sized, None); let lang_item = self.tcx.require_lang_item(LangItem::Sized, None);
traits::type_known_to_meet_bound_modulo_regions(self, param_env, ty, lang_item) traits::type_known_to_meet_bound_modulo_regions(self, param_env, ty, lang_item)

View file

@ -0,0 +1,70 @@
#[derive(Copy, Clone)]
struct Type;
struct NewType;
const fn get_size() -> usize {
10
}
fn get_dyn_size() -> usize {
10
}
fn main() {
let a = ["a", 10];
//~^ ERROR mismatched types
//~| HELP replace the comma with a semicolon to create an array
const size_b: usize = 20;
let b = [Type, size_b];
//~^ ERROR mismatched types
//~| HELP replace the comma with a semicolon to create an array
let size_c: usize = 13;
let c = [Type, size_c];
//~^ ERROR mismatched types
const size_d: bool = true;
let d = [Type, size_d];
//~^ ERROR mismatched types
let e = [String::new(), 10];
//~^ ERROR mismatched types
//~| HELP try using a conversion method
let f = ["f", get_size()];
//~^ ERROR mismatched types
//~| HELP replace the comma with a semicolon to create an array
let m = ["m", get_dyn_size()];
//~^ ERROR mismatched types
// is_vec, is_clone, is_usize_like
let g = vec![String::new(), 10];
//~^ ERROR mismatched types
//~| HELP replace the comma with a semicolon to create a vector
let dyn_size = 10;
let h = vec![Type, dyn_size];
//~^ ERROR mismatched types
//~| HELP replace the comma with a semicolon to create a vector
let i = vec![Type, get_dyn_size()];
//~^ ERROR mismatched types
//~| HELP replace the comma with a semicolon to create a vector
let k = vec!['c', 10];
//~^ ERROR mismatched types
//~| HELP replace the comma with a semicolon to create a vector
let j = vec![Type, 10_u8];
//~^ ERROR mismatched types
let l = vec![NewType, 10];
//~^ ERROR mismatched types
let byte_size: u8 = 10;
let h = vec![Type, byte_size];
//~^ ERROR mismatched types
}

View file

@ -0,0 +1,124 @@
error[E0308]: mismatched types
--> $DIR/typo-in-repeat-expr-issue-80173.rs:15:19
|
LL | let a = ["a", 10];
| ^^ expected `&str`, found integer
|
help: replace the comma with a semicolon to create an array
|
LL | let a = ["a"; 10];
| ~
error[E0308]: mismatched types
--> $DIR/typo-in-repeat-expr-issue-80173.rs:20:20
|
LL | let b = [Type, size_b];
| ^^^^^^ expected `Type`, found `usize`
|
help: replace the comma with a semicolon to create an array
|
LL | let b = [Type; size_b];
| ~
error[E0308]: mismatched types
--> $DIR/typo-in-repeat-expr-issue-80173.rs:25:20
|
LL | let c = [Type, size_c];
| ^^^^^^ expected `Type`, found `usize`
error[E0308]: mismatched types
--> $DIR/typo-in-repeat-expr-issue-80173.rs:29:20
|
LL | let d = [Type, size_d];
| ^^^^^^ expected `Type`, found `bool`
error[E0308]: mismatched types
--> $DIR/typo-in-repeat-expr-issue-80173.rs:32:29
|
LL | let e = [String::new(), 10];
| ^^- help: try using a conversion method: `.to_string()`
| |
| expected `String`, found integer
error[E0308]: mismatched types
--> $DIR/typo-in-repeat-expr-issue-80173.rs:36:19
|
LL | let f = ["f", get_size()];
| ^^^^^^^^^^ expected `&str`, found `usize`
|
help: replace the comma with a semicolon to create an array
|
LL | let f = ["f"; get_size()];
| ~
error[E0308]: mismatched types
--> $DIR/typo-in-repeat-expr-issue-80173.rs:40:19
|
LL | let m = ["m", get_dyn_size()];
| ^^^^^^^^^^^^^^ expected `&str`, found `usize`
error[E0308]: mismatched types
--> $DIR/typo-in-repeat-expr-issue-80173.rs:44:33
|
LL | let g = vec![String::new(), 10];
| ^^ expected `String`, found integer
|
help: replace the comma with a semicolon to create a vector
|
LL | let g = vec![String::new(); 10];
| ~
error[E0308]: mismatched types
--> $DIR/typo-in-repeat-expr-issue-80173.rs:49:24
|
LL | let h = vec![Type, dyn_size];
| ^^^^^^^^ expected `Type`, found integer
|
help: replace the comma with a semicolon to create a vector
|
LL | let h = vec![Type; dyn_size];
| ~
error[E0308]: mismatched types
--> $DIR/typo-in-repeat-expr-issue-80173.rs:53:24
|
LL | let i = vec![Type, get_dyn_size()];
| ^^^^^^^^^^^^^^ expected `Type`, found `usize`
|
help: replace the comma with a semicolon to create a vector
|
LL | let i = vec![Type; get_dyn_size()];
| ~
error[E0308]: mismatched types
--> $DIR/typo-in-repeat-expr-issue-80173.rs:57:23
|
LL | let k = vec!['c', 10];
| ^^ expected `char`, found `u8`
|
help: replace the comma with a semicolon to create a vector
|
LL | let k = vec!['c'; 10];
| ~
error[E0308]: mismatched types
--> $DIR/typo-in-repeat-expr-issue-80173.rs:61:24
|
LL | let j = vec![Type, 10_u8];
| ^^^^^ expected `Type`, found `u8`
error[E0308]: mismatched types
--> $DIR/typo-in-repeat-expr-issue-80173.rs:64:27
|
LL | let l = vec![NewType, 10];
| ^^ expected `NewType`, found integer
error[E0308]: mismatched types
--> $DIR/typo-in-repeat-expr-issue-80173.rs:68:24
|
LL | let h = vec![Type, byte_size];
| ^^^^^^^^^ expected `Type`, found `u8`
error: aborting due to 14 previous errors
For more information about this error, try `rustc --explain E0308`.