Add redundant type annotations lint

This commit is contained in:
Alessio Cosenza 2023-03-29 00:49:43 +02:00
parent ae880e41a8
commit 01b1057258
No known key found for this signature in database
GPG key ID: 785AB79082BB23C2
6 changed files with 443 additions and 0 deletions

View file

@ -5110,6 +5110,7 @@ Released 2018-09-13
[`redundant_pub_crate`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pub_crate
[`redundant_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_slicing
[`redundant_static_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes
[`redundant_type_annotations`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_type_annotations
[`ref_binding_to_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_binding_to_reference
[`ref_in_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_in_deref
[`ref_option_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option_ref

View file

@ -543,6 +543,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::redundant_slicing::DEREF_BY_SLICING_INFO,
crate::redundant_slicing::REDUNDANT_SLICING_INFO,
crate::redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES_INFO,
crate::redundant_type_annotations::REDUNDANT_TYPE_ANNOTATIONS_INFO,
crate::ref_option_ref::REF_OPTION_REF_INFO,
crate::ref_patterns::REF_PATTERNS_INFO,
crate::reference::DEREF_ADDROF_INFO,

View file

@ -267,6 +267,7 @@ mod redundant_field_names;
mod redundant_pub_crate;
mod redundant_slicing;
mod redundant_static_lifetimes;
mod redundant_type_annotations;
mod ref_option_ref;
mod ref_patterns;
mod reference;
@ -1012,6 +1013,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_early_pass(|| Box::new(needless_else::NeedlessElse));
store.register_late_pass(|_| Box::new(missing_fields_in_debug::MissingFieldsInDebug));
store.register_late_pass(|_| Box::new(endian_bytes::EndianBytes));
store.register_late_pass(|_| Box::new(redundant_type_annotations::RedundantTypeAnnotations));
// add lints here, do not remove this comment, it's used in `new_lint`
}

View file

@ -0,0 +1,185 @@
use clippy_utils::diagnostics::span_lint;
use rustc_ast::LitKind;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::Ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// Warns about needless / redundant type annotations.
///
/// ### Why is this bad?
/// Code without type annotations is shorter and in most cases
/// more idiomatic and easier to modify.
///
/// ### Example
/// ```rust
/// let foo: String = String::new();
/// ```
/// Use instead:
/// ```rust
/// let foo = String::new();
/// ```
#[clippy::version = "1.70.0"]
pub REDUNDANT_TYPE_ANNOTATIONS,
pedantic,
"warns about needless / redundant type annotations."
}
declare_lint_pass!(RedundantTypeAnnotations => [REDUNDANT_TYPE_ANNOTATIONS]);
fn is_same_type<'tcx>(cx: &LateContext<'tcx>, ty_resolved_path: hir::def::Res, func_return_type: Ty<'tcx>) -> bool {
// type annotation is primitive
if let hir::def::Res::PrimTy(primty) = ty_resolved_path
&& func_return_type.is_primitive()
&& let Some(func_return_type_sym) = func_return_type.primitive_symbol()
{
return primty.name() == func_return_type_sym;
}
// type annotation is any other non generic type
if let hir::def::Res::Def(_, defid) = ty_resolved_path
&& let Some(annotation_ty) = cx.tcx.type_of(defid).no_bound_vars()
{
return annotation_ty == func_return_type;
}
false
}
fn func_hir_id_to_func_ty<'tcx>(cx: &LateContext<'tcx>, hir_id: hir::hir_id::HirId) -> Option<Ty<'tcx>> {
if let Some((defkind, func_defid)) = cx.typeck_results().type_dependent_def(hir_id)
&& defkind == hir::def::DefKind::AssocFn
&& let Some(init_ty) = cx.tcx.type_of(func_defid).no_bound_vars()
{
Some(init_ty)
} else {
None
}
}
fn func_ty_to_return_type<'tcx>(cx: &LateContext<'tcx>, func_ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
if func_ty.is_fn()
&& let Some(return_type) = func_ty.fn_sig(cx.tcx).output().no_bound_vars()
{
Some(return_type)
} else {
None
}
}
/// Extracts the fn Ty, e.g. `fn() -> std::string::String {f}`
fn extract_fn_ty<'tcx>(
cx: &LateContext<'tcx>,
call: &hir::Expr<'tcx>,
func_return_path: &hir::QPath<'tcx>,
) -> Option<Ty<'tcx>> {
match func_return_path {
// let a: String = f(); where f: fn f() -> String
hir::QPath::Resolved(_, resolved_path) => {
if let hir::def::Res::Def(_, defid) = resolved_path.res
&& let Some(middle_ty_init) = cx.tcx.type_of(defid).no_bound_vars()
{
Some(middle_ty_init)
} else {
None
}
},
// Associated functions like
// let a: String = String::new();
// let a: String = String::get_string();
hir::QPath::TypeRelative(..) => func_hir_id_to_func_ty(cx, call.hir_id),
hir::QPath::LangItem(..) => None,
}
}
fn is_redundant_in_func_call<'tcx>(
cx: &LateContext<'tcx>,
ty_resolved_path: hir::def::Res,
call: &hir::Expr<'tcx>,
) -> bool {
if let hir::ExprKind::Path(init_path) = &call.kind {
let func_type = extract_fn_ty(cx, call, init_path);
if let Some(func_type) = func_type
&& let Some(init_return_type) = func_ty_to_return_type(cx, func_type)
{
return is_same_type(cx, ty_resolved_path, init_return_type);
}
}
false
}
impl LateLintPass<'_> for RedundantTypeAnnotations {
fn check_local<'tcx>(&mut self, cx: &LateContext<'tcx>, local: &'tcx rustc_hir::Local<'tcx>) {
// type annotation part
if !local.span.from_expansion()
&& let Some(ty) = &local.ty
// initialization part
&& let Some(init) = local.init
{
match &init.kind {
// When the initialization is a call to a function
hir::ExprKind::Call(init_call, _) => {
if let hir::TyKind::Path(ty_path) = &ty.kind
&& let hir::QPath::Resolved(_, resolved_path_ty) = ty_path
&& is_redundant_in_func_call(cx, resolved_path_ty.res, init_call) {
span_lint(cx, REDUNDANT_TYPE_ANNOTATIONS, local.span, "redundant type annotation");
}
},
hir::ExprKind::MethodCall(_, _, _, _) => {
if let hir::TyKind::Path(ty_path) = &ty.kind
&& let hir::QPath::Resolved(_, resolved_path_ty) = ty_path
&& let Some(func_ty) = func_hir_id_to_func_ty(cx, init.hir_id)
&& let Some(return_type) = func_ty_to_return_type(cx, func_ty)
&& is_same_type(cx, resolved_path_ty.res, return_type)
{
span_lint(cx, REDUNDANT_TYPE_ANNOTATIONS, local.span, "redundant type annotation");
}
},
// When the initialization is a path for example u32::MAX
hir::ExprKind::Path(init_path) => {
// TODO: check for non primty
if let hir::TyKind::Path(ty_path) = &ty.kind
&& let hir::QPath::Resolved(_, resolved_path_ty) = ty_path
&& let hir::def::Res::PrimTy(primty) = resolved_path_ty.res
&& let hir::QPath::TypeRelative(init_ty, _) = init_path
&& let hir::TyKind::Path(init_ty_path) = &init_ty.kind
&& let hir::QPath::Resolved(_, resolved_init_ty_path) = init_ty_path
&& let hir::def::Res::PrimTy(primty_init) = resolved_init_ty_path.res
&& primty == primty_init
{
span_lint(cx, REDUNDANT_TYPE_ANNOTATIONS, local.span, "redundant type annotation");
}
},
hir::ExprKind::Lit(init_lit) => {
match init_lit.node {
// In these cases the annotation is redundant
LitKind::Str(..)
| LitKind::ByteStr(..)
| LitKind::Byte(..)
| LitKind::Char(..)
| LitKind::Bool(..)
| LitKind::CStr(..) => {
span_lint(cx, REDUNDANT_TYPE_ANNOTATIONS, local.span, "redundant type annotation");
},
LitKind::Int(..) | LitKind::Float(..) => {
// If the initialization value is a suffixed literal we lint
if init_lit.node.is_suffixed() {
span_lint(cx, REDUNDANT_TYPE_ANNOTATIONS, local.span, "redundant type annotation");
}
},
LitKind::Err => (),
}
}
_ => ()
}
};
}
}

View file

@ -0,0 +1,160 @@
#![allow(unused)]
#![warn(clippy::redundant_type_annotations)]
#[derive(Debug, Default)]
struct Cake<T> {
_data: T,
}
fn make_something<T: Default>() -> T {
T::default()
}
fn make_cake<T: Default>() -> Cake<T> {
Cake::<T>::default()
}
fn plus_one<T: std::ops::Add<u8, Output = T>>(val: T) -> T {
val + 1
}
struct Pie {
inner: u32,
}
enum Pizza {
One,
Two,
}
fn return_a_string() -> String {
String::new()
}
fn return_a_struct() -> Pie {
Pie { inner: 5 }
}
fn return_an_enum() -> Pizza {
Pizza::One
}
fn return_an_int() -> u32 {
5
}
impl Pie {
fn return_an_int(&self) -> u32 {
self.inner
}
fn associated_return_an_int() -> u32 {
5
}
fn new() -> Self {
Self { inner: 5 }
}
fn associated_return_a_string() -> String {
String::from("")
}
fn test_method_call(&self) {
let v: u32 = self.return_an_int(); // Should lint
}
}
fn test_generics() {
// The type annotation is needed to determine T
let _c: Cake<i32> = make_something();
// The type annotation is needed to determine the topic
let _c: Cake<u8> = make_cake();
// This should lint (doesn't)
let _c: Cake<u8> = make_cake::<u8>();
// This should lint (doesn't)
let _c: u8 = make_something::<u8>();
// This should lint (doesn't)
let _c: u8 = plus_one(5_u8);
// Annotation needed otherwise T is i32
let _c: u8 = plus_one(5);
}
fn test_non_locals() {
// This shouldn't lint
fn _arg(x: u32) -> u32 {
x
}
// This could lint, but probably shouldn't
let _closure_arg = |x: u32| x;
}
fn test_complex_types() {
// Shouldn't lint, since the literal will be i32 otherwise
let _u8: u8 = 128;
// Should lint (doesn't)
let _tuple_i32: (i32, i32) = (12, 13);
// Shouldn't lint, since the tuple will be i32 otherwise
let _tuple_u32: (u32, u32) = (1, 2);
// Should lint, since the type is determined by the init value (doesn't)
let _tuple_u32: (u32, u32) = (3_u32, 4_u32);
// Should lint (doesn't)
let _array: [i32; 3] = [5, 6, 7];
// Shouldn't lint
let _array: [u32; 2] = [8, 9];
}
fn test_functions() {
// Everything here should lint
let _return: String = return_a_string();
let _return: Pie = return_a_struct();
let _return: Pizza = return_an_enum();
let _return: u32 = return_an_int();
let _return: String = String::new();
let new_pie: Pie = Pie::new();
let _return: u32 = new_pie.return_an_int();
let _return: String = String::from("test");
let _return: u32 = Pie::associated_return_an_int();
let _return: String = Pie::associated_return_a_string();
}
fn test_simple_types() {
let _var: u32 = u32::MAX;
// Should lint
let _var: u32 = 5_u32;
// Should lint
let _var: &str = "test";
// Should lint
let _var: &[u8] = b"test";
// Should lint
let _var: bool = false;
}
fn main() {}
// TODO: test refs

View file

@ -0,0 +1,94 @@
error: redundant type annotation
--> $DIR/redundant_type_annotations.rs:64:9
|
LL | let v: u32 = self.return_an_int(); // Should lint
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::redundant-type-annotations` implied by `-D warnings`
error: redundant type annotation
--> $DIR/redundant_type_annotations.rs:121:5
|
LL | let _return: String = return_a_string();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: redundant type annotation
--> $DIR/redundant_type_annotations.rs:123:5
|
LL | let _return: Pie = return_a_struct();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: redundant type annotation
--> $DIR/redundant_type_annotations.rs:125:5
|
LL | let _return: Pizza = return_an_enum();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: redundant type annotation
--> $DIR/redundant_type_annotations.rs:127:5
|
LL | let _return: u32 = return_an_int();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: redundant type annotation
--> $DIR/redundant_type_annotations.rs:129:5
|
LL | let _return: String = String::new();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: redundant type annotation
--> $DIR/redundant_type_annotations.rs:131:5
|
LL | let new_pie: Pie = Pie::new();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: redundant type annotation
--> $DIR/redundant_type_annotations.rs:133:5
|
LL | let _return: u32 = new_pie.return_an_int();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: redundant type annotation
--> $DIR/redundant_type_annotations.rs:137:5
|
LL | let _return: u32 = Pie::associated_return_an_int();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: redundant type annotation
--> $DIR/redundant_type_annotations.rs:139:5
|
LL | let _return: String = Pie::associated_return_a_string();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: redundant type annotation
--> $DIR/redundant_type_annotations.rs:143:5
|
LL | let _var: u32 = u32::MAX;
| ^^^^^^^^^^^^^^^^^^^^^^^^^
error: redundant type annotation
--> $DIR/redundant_type_annotations.rs:146:5
|
LL | let _var: u32 = 5_u32;
| ^^^^^^^^^^^^^^^^^^^^^^
error: redundant type annotation
--> $DIR/redundant_type_annotations.rs:149:5
|
LL | let _var: &str = "test";
| ^^^^^^^^^^^^^^^^^^^^^^^^
error: redundant type annotation
--> $DIR/redundant_type_annotations.rs:152:5
|
LL | let _var: &[u8] = b"test";
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
error: redundant type annotation
--> $DIR/redundant_type_annotations.rs:155:5
|
LL | let _var: bool = false;
| ^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 15 previous errors