diff --git a/src/librustc_lint/types.rs b/src/librustc_lint/types.rs index 5431318dff959..1c4bd0ff4c2ec 100644 --- a/src/librustc_lint/types.rs +++ b/src/librustc_lint/types.rs @@ -150,11 +150,23 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TypeLimits { // Detect literal value out of range [min, max] inclusive // avoiding use of -min to prevent overflow/panic - if (negative && v > max + 1) || - (!negative && v > max) { - cx.span_lint(OVERFLOWING_LITERALS, - e.span, - &format!("literal out of range for {:?}", t)); + if (negative && v > max + 1) || (!negative && v > max) { + if let Some(repr_str) = get_bin_hex_repr(cx, lit) { + report_bin_hex_error( + cx, + e, + ty::TyInt(t), + repr_str, + v, + negative, + ); + return; + } + cx.span_lint( + OVERFLOWING_LITERALS, + e.span, + &format!("literal out of range for {:?}", t), + ); return; } } @@ -191,9 +203,22 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TypeLimits { } } } - cx.span_lint(OVERFLOWING_LITERALS, - e.span, - &format!("literal out of range for {:?}", t)); + if let Some(repr_str) = get_bin_hex_repr(cx, lit) { + report_bin_hex_error( + cx, + e, + ty::TyUint(t), + repr_str, + lit_val, + false, + ); + return; + } + cx.span_lint( + OVERFLOWING_LITERALS, + e.span, + &format!("literal out of range for {:?}", t), + ); } } ty::TyFloat(t) => { @@ -338,6 +363,120 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TypeLimits { _ => false, } } + + fn get_bin_hex_repr(cx: &LateContext, lit: &ast::Lit) -> Option { + let src = cx.sess().codemap().span_to_snippet(lit.span).ok()?; + let firstch = src.chars().next()?; + + if firstch == '0' { + match src.chars().nth(1) { + Some('x') | Some('b') => return Some(src), + _ => return None, + } + } + + None + } + + // This function finds the next fitting type and generates a suggestion string. + // It searches for fitting types in the following way (`X < Y`): + // - `iX`: if literal fits in `uX` => `uX`, else => `iY` + // - `-iX` => `iY` + // - `uX` => `uY` + // + // No suggestion for: `isize`, `usize`. + fn get_type_suggestion<'a>( + t: &ty::TypeVariants, + val: u128, + negative: bool, + ) -> Option { + use syntax::ast::IntTy::*; + use syntax::ast::UintTy::*; + macro_rules! find_fit { + ($ty:expr, $val:expr, $negative:expr, + $($type:ident => [$($utypes:expr),*] => [$($itypes:expr),*]),+) => { + { + let _neg = if negative { 1 } else { 0 }; + match $ty { + $($type => { + $(if !negative && val <= uint_ty_range($utypes).1 { + return Some(format!("{:?}", $utypes)) + })* + $(if val <= int_ty_range($itypes).1 as u128 + _neg { + return Some(format!("{:?}", $itypes)) + })* + None + },)* + _ => None + } + } + } + } + match t { + &ty::TyInt(i) => find_fit!(i, val, negative, + I8 => [U8] => [I16, I32, I64, I128], + I16 => [U16] => [I32, I64, I128], + I32 => [U32] => [I64, I128], + I64 => [U64] => [I128], + I128 => [U128] => []), + &ty::TyUint(u) => find_fit!(u, val, negative, + U8 => [U8, U16, U32, U64, U128] => [], + U16 => [U16, U32, U64, U128] => [], + U32 => [U32, U64, U128] => [], + U64 => [U64, U128] => [], + U128 => [U128] => []), + _ => None, + } + } + + fn report_bin_hex_error( + cx: &LateContext, + expr: &hir::Expr, + ty: ty::TypeVariants, + repr_str: String, + val: u128, + negative: bool, + ) { + let (t, actually) = match ty { + ty::TyInt(t) => { + let bits = int_ty_bits(t, cx.sess().target.isize_ty); + let actually = (val << (128 - bits)) as i128 >> (128 - bits); + (format!("{:?}", t), actually.to_string()) + } + ty::TyUint(t) => { + let bits = uint_ty_bits(t, cx.sess().target.usize_ty); + let actually = (val << (128 - bits)) >> (128 - bits); + (format!("{:?}", t), actually.to_string()) + } + _ => bug!(), + }; + let mut err = cx.struct_span_lint( + OVERFLOWING_LITERALS, + expr.span, + &format!("literal out of range for {}", t), + ); + err.note(&format!( + "the literal `{}` (decimal `{}`) does not fit into \ + an `{}` and will become `{}{}`", + repr_str, val, t, actually, t + )); + if let Some(sugg_ty) = + get_type_suggestion(&cx.tables.node_id_to_type(expr.hir_id).sty, val, negative) + { + if let Some(pos) = repr_str.chars().position(|c| c == 'i' || c == 'u') { + let (sans_suffix, _) = repr_str.split_at(pos); + err.span_suggestion( + expr.span, + &format!("consider using `{}` instead", sugg_ty), + format!("{}{}", sans_suffix, sugg_ty), + ); + } else { + err.help(&format!("consider using `{}` instead", sugg_ty)); + } + } + + err.emit(); + } } } diff --git a/src/test/ui/lint/type-overflow.rs b/src/test/ui/lint/type-overflow.rs new file mode 100644 index 0000000000000..495989587e585 --- /dev/null +++ b/src/test/ui/lint/type-overflow.rs @@ -0,0 +1,33 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// must-compile-successfully + +#![feature(i128_type)] + +fn main() { + let error = 255i8; //~WARNING literal out of range for i8 + + let ok = 0b1000_0001; // should be ok -> i32 + let ok = 0b0111_1111i8; // should be ok -> 127i8 + + let fail = 0b1000_0001i8; //~WARNING literal out of range for i8 + + let fail = 0x8000_0000_0000_0000i64; //~WARNING literal out of range for i64 + + let fail = 0x1_FFFF_FFFFu32; //~WARNING literal out of range for u32 + + let fail: i128 = 0x8000_0000_0000_0000_0000_0000_0000_0000; + //~^ WARNING literal out of range for i128 + + let fail = 0x8FFF_FFFF_FFFF_FFFE; //~WARNING literal out of range for i32 + + let fail = -0b1111_1111i8; //~WARNING literal out of range for i8 +} diff --git a/src/test/ui/lint/type-overflow.stderr b/src/test/ui/lint/type-overflow.stderr new file mode 100644 index 0000000000000..d3fcb1335e209 --- /dev/null +++ b/src/test/ui/lint/type-overflow.stderr @@ -0,0 +1,58 @@ +warning: literal out of range for i8 + --> $DIR/type-overflow.rs:16:17 + | +LL | let error = 255i8; //~WARNING literal out of range for i8 + | ^^^^^ + | + = note: #[warn(overflowing_literals)] on by default + +warning: literal out of range for i8 + --> $DIR/type-overflow.rs:21:16 + | +LL | let fail = 0b1000_0001i8; //~WARNING literal out of range for i8 + | ^^^^^^^^^^^^^ help: consider using `u8` instead: `0b1000_0001u8` + | + = note: the literal `0b1000_0001i8` (decimal `129`) does not fit into an `i8` and will become `-127i8` + +warning: literal out of range for i64 + --> $DIR/type-overflow.rs:23:16 + | +LL | let fail = 0x8000_0000_0000_0000i64; //~WARNING literal out of range for i64 + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `u64` instead: `0x8000_0000_0000_0000u64` + | + = note: the literal `0x8000_0000_0000_0000i64` (decimal `9223372036854775808`) does not fit into an `i64` and will become `-9223372036854775808i64` + +warning: literal out of range for u32 + --> $DIR/type-overflow.rs:25:16 + | +LL | let fail = 0x1_FFFF_FFFFu32; //~WARNING literal out of range for u32 + | ^^^^^^^^^^^^^^^^ help: consider using `u64` instead: `0x1_FFFF_FFFFu64` + | + = note: the literal `0x1_FFFF_FFFFu32` (decimal `8589934591`) does not fit into an `u32` and will become `4294967295u32` + +warning: literal out of range for i128 + --> $DIR/type-overflow.rs:27:22 + | +LL | let fail: i128 = 0x8000_0000_0000_0000_0000_0000_0000_0000; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the literal `0x8000_0000_0000_0000_0000_0000_0000_0000` (decimal `170141183460469231731687303715884105728`) does not fit into an `i128` and will become `-170141183460469231731687303715884105728i128` + = help: consider using `u128` instead + +warning: literal out of range for i32 + --> $DIR/type-overflow.rs:30:16 + | +LL | let fail = 0x8FFF_FFFF_FFFF_FFFE; //~WARNING literal out of range for i32 + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: the literal `0x8FFF_FFFF_FFFF_FFFE` (decimal `10376293541461622782`) does not fit into an `i32` and will become `-2i32` + = help: consider using `i128` instead + +warning: literal out of range for i8 + --> $DIR/type-overflow.rs:32:17 + | +LL | let fail = -0b1111_1111i8; //~WARNING literal out of range for i8 + | ^^^^^^^^^^^^^ help: consider using `i16` instead: `0b1111_1111i16` + | + = note: the literal `0b1111_1111i8` (decimal `255`) does not fit into an `i8` and will become `-1i8` +