use rustc_ast::ast::{Label, LitFloatType, LitIntType, LitKind}; use rustc_hir::{ self as hir, intravisit::{walk_expr, walk_stmt, walk_ty, FnKind, NestedVisitorMap, Visitor}, Body, BodyId, Expr, ExprKind, FnDecl, FnRetTy, Guard, HirId, Lit, Stmt, StmtKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::{ hir::map::Map, ty::{self, subst::GenericArgKind, FloatTy, IntTy, Ty, TyCtxt}, }; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::Span; use rustc_typeck::hir_ty_to_ty; use if_chain::if_chain; use crate::utils::span_lint_and_help; declare_clippy_lint! { /// **What it does:** Checks for usage of unconstrained numeric literals which may cause default numeric fallback in type /// inference. /// /// Default numeric fallback means that if numeric types have not yet been bound to concrete /// types at the end of type inference, then integer type is bound to `i32`, and similarly /// floating type is bound to `f64`. /// /// See [RFC0212](https://github.com/rust-lang/rfcs/blob/master/text/0212-restore-int-fallback.md) for more information about the fallback. /// /// **Why is this bad?** For those who are very careful about types, default numeric fallback /// can be a pitfall that cause unexpected runtime behavior. /// /// **Known problems:** None. /// /// **Example:** /// ```rust /// let i = 10; /// let f = 1.23; /// ``` /// /// Use instead: /// ```rust /// let i = 10i32; /// let f = 1.23f64; /// ``` pub DEFAULT_NUMERIC_FALLBACK, restriction, "usage of unconstrained numeric literals which may cause default numeric fallback." } declare_lint_pass!(DefaultNumericFallback => [DEFAULT_NUMERIC_FALLBACK]); /// Return the body that includes passed `hir_id` if exists. fn enclosing_body_opt(tcx: TyCtxt<'_>, hir_id: HirId) -> Option { let hir_map = tcx.hir(); let mut trace = vec![(hir_id)]; for (parent, _) in hir_map.parent_iter(hir_id) { trace.push(parent); if let Some(body) = hir_map.maybe_body_owned_by(parent) { if trace.iter().any(|hir_id| *hir_id == body.hir_id) { return Some(body); } } } None } fn ty_from_hir_ty<'tcx>(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'tcx>) -> Option> { if enclosing_body_opt(cx.tcx, hir_ty.hir_id).is_some() { cx.typeck_results().node_type_opt(hir_ty.hir_id) } else { Some(hir_ty_to_ty(cx.tcx, hir_ty)) } } impl LateLintPass<'_> for DefaultNumericFallback { fn check_fn( &mut self, cx: &LateContext<'tcx>, _: FnKind<'tcx>, fn_decl: &'tcx FnDecl<'_>, body: &'tcx Body<'_>, _: Span, _: HirId, ) { let ret_ty_bound = match fn_decl.output { FnRetTy::DefaultReturn(_) => None, FnRetTy::Return(ty) => Some(ty), } .and_then(|ty| { if is_infer_included(ty) { None } else { ty_from_hir_ty(cx, ty) } }); let mut visitor = NumericFallbackVisitor::new(ret_ty_bound, cx); visitor.visit_body(body); } } struct NumericFallbackVisitor<'a, 'tcx> { /// Stack manages type bound of exprs. The top element holds current expr type. ty_bounds: Vec>>, /// Ret type bound. ret_ty_bound: Option>, /// Break type bounds. break_ty_bounds: Vec<(Option