From 7aad5c0784e8b1a0aceaa65dec56677a7f6f3735 Mon Sep 17 00:00:00 2001 From: beetrees Date: Mon, 31 Mar 2025 23:18:09 +0100 Subject: [PATCH] Add FCW for unsuffixed float literal `f32` fallback --- compiler/rustc_hir_typeck/src/demand.rs | 2 +- compiler/rustc_hir_typeck/src/errors.rs | 12 +++++ compiler/rustc_hir_typeck/src/fallback.rs | 18 ++++++- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 2 +- .../src/fn_ctxt/inspect_obligations.rs | 2 +- .../rustc_infer/src/infer/canonical/mod.rs | 2 +- compiler/rustc_infer/src/infer/mod.rs | 21 ++++++-- .../rustc_infer/src/infer/snapshot/fudge.rs | 26 +++++++--- compiler/rustc_lint_defs/src/builtin.rs | 49 +++++++++++++++++++ tests/ui/float/f32-into-f32.next-solver.fixed | 23 +++++++++ .../ui/float/f32-into-f32.next-solver.stderr | 39 +++++++++++++++ tests/ui/float/f32-into-f32.old-solver.fixed | 23 +++++++++ tests/ui/float/f32-into-f32.old-solver.stderr | 39 +++++++++++++++ tests/ui/float/f32-into-f32.rs | 14 ++++++ tests/ui/inference/untyped-primitives.rs | 2 + tests/ui/inference/untyped-primitives.stderr | 12 +++++ 16 files changed, 270 insertions(+), 16 deletions(-) create mode 100644 tests/ui/float/f32-into-f32.next-solver.fixed create mode 100644 tests/ui/float/f32-into-f32.next-solver.stderr create mode 100644 tests/ui/float/f32-into-f32.old-solver.fixed create mode 100644 tests/ui/float/f32-into-f32.old-solver.stderr create mode 100644 tests/ui/inference/untyped-primitives.stderr diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index 0d49e0624053..6316e6b9d592 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -346,7 +346,7 @@ fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) { match infer { ty::TyVar(_) => self.next_ty_var(DUMMY_SP), ty::IntVar(_) => self.next_int_var(), - ty::FloatVar(_) => self.next_float_var(), + ty::FloatVar(_) => self.next_float_var(DUMMY_SP), ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_) => { bug!("unexpected fresh ty outside of the trait solver") } diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs index 6eef15684697..b55e7933cc1e 100644 --- a/compiler/rustc_hir_typeck/src/errors.rs +++ b/compiler/rustc_hir_typeck/src/errors.rs @@ -1384,3 +1384,15 @@ pub(crate) struct ProjectOnNonPinProjectType { )] pub sugg_span: Option, } + +#[derive(Diagnostic)] +#[diag("falling back to `f32` as the trait bound `f32: From` is not satisfied")] +pub(crate) struct FloatLiteralF32Fallback { + pub literal: String, + #[suggestion( + "explicitly specify the type as `f32`", + code = "{literal}_f32", + applicability = "machine-applicable" + )] + pub span: Option, +} diff --git a/compiler/rustc_hir_typeck/src/fallback.rs b/compiler/rustc_hir_typeck/src/fallback.rs index 704abb9c39d9..cebf10e382ac 100644 --- a/compiler/rustc_hir_typeck/src/fallback.rs +++ b/compiler/rustc_hir_typeck/src/fallback.rs @@ -5,12 +5,12 @@ use rustc_data_structures::graph; use rustc_data_structures::graph::vec_graph::VecGraph; use rustc_data_structures::unord::{UnordMap, UnordSet}; -use rustc_hir as hir; -use rustc_hir::HirId; use rustc_hir::attrs::DivergingFallbackBehavior; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefId; use rustc_hir::intravisit::{InferKind, Visitor}; +use rustc_hir::{self as hir, CRATE_HIR_ID, HirId}; +use rustc_lint::builtin::FLOAT_LITERAL_F32_FALLBACK; use rustc_middle::ty::{self, FloatVid, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable}; use rustc_session::lint; use rustc_span::def_id::LocalDefId; @@ -160,6 +160,20 @@ fn calculate_fallback_to_f32(&self, unresolved_variables: &[Ty<'tcx>]) -> UnordS .iter() .flat_map(|ty| ty.float_vid()) .filter(|vid| roots.contains(&self.root_float_var(*vid))) + .inspect(|vid| { + let span = self.float_var_origin(*vid); + // Show the entire literal in the suggestion to make it clearer. + let literal = self.tcx.sess.source_map().span_to_snippet(span).ok(); + self.tcx.emit_node_span_lint( + FLOAT_LITERAL_F32_FALLBACK, + CRATE_HIR_ID, + span, + errors::FloatLiteralF32Fallback { + span: literal.as_ref().map(|_| span), + literal: literal.unwrap_or_default(), + }, + ); + }) .collect(); debug!("calculate_fallback_to_f32: fallback_to_f32={:?}", fallback_to_f32); fallback_to_f32 diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index bb31bcbf70f1..0752ecb151f6 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -765,7 +765,7 @@ pub(in super::super) fn check_expr_lit( ty::Float(_) => Some(ty), _ => None, }); - opt_ty.unwrap_or_else(|| self.next_float_var()) + opt_ty.unwrap_or_else(|| self.next_float_var(lit.span)) } ast::LitKind::Bool(_) => tcx.types.bool, ast::LitKind::CStr(_, _) => Ty::new_imm_ref( diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs index dcaace299ada..652f0498b584 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs @@ -224,7 +224,7 @@ fn span(&self) -> Span { fn config(&self) -> InspectConfig { // Avoid hang from exponentially growing proof trees (see `cycle-modulo-ambig-aliases.rs`). - // 3 is more than enough for all occurences in practice (a.k.a. `Into`). + // 3 is more than enough for all occurrences in practice (a.k.a. `Into`). InspectConfig { max_depth: 3 } } diff --git a/compiler/rustc_infer/src/infer/canonical/mod.rs b/compiler/rustc_infer/src/infer/canonical/mod.rs index c6826d774216..321f2e43deef 100644 --- a/compiler/rustc_infer/src/infer/canonical/mod.rs +++ b/compiler/rustc_infer/src/infer/canonical/mod.rs @@ -107,7 +107,7 @@ pub fn instantiate_canonical_var( CanonicalVarKind::Int => self.next_int_var().into(), - CanonicalVarKind::Float => self.next_float_var().into(), + CanonicalVarKind::Float => self.next_float_var(span).into(), CanonicalVarKind::PlaceholderTy(ty::PlaceholderType { universe, bound, .. }) => { let universe_mapped = universe_map(universe); diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index 0e2934760f4a..5e28fb8765cd 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -18,6 +18,7 @@ use rustc_errors::{DiagCtxtHandle, ErrorGuaranteed}; use rustc_hir as hir; use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_index::IndexVec; use rustc_macros::extension; pub use rustc_macros::{TypeFoldable, TypeVisitable}; use rustc_middle::bug; @@ -108,6 +109,10 @@ pub struct InferCtxtInner<'tcx> { /// Map from floating variable to the kind of float it represents. float_unification_storage: ut::UnificationTableStorage, + /// Map from floating variable to the origin span it came from. This is only used for the FCW + /// for the fallback to `f32`, so can be removed once the `f32` fallback is removed. + float_origin_span_storage: IndexVec, + /// Tracks the set of region variables and the constraints between them. /// /// This is initially `Some(_)` but when @@ -161,6 +166,7 @@ fn new() -> InferCtxtInner<'tcx> { const_unification_storage: Default::default(), int_unification_storage: Default::default(), float_unification_storage: Default::default(), + float_origin_span_storage: Default::default(), region_constraint_storage: Some(Default::default()), region_obligations: Default::default(), region_assumptions: Default::default(), @@ -644,6 +650,13 @@ pub fn type_var_origin(&self, vid: TyVid) -> TypeVariableOrigin { self.inner.borrow_mut().type_variables().var_origin(vid) } + /// Returns the origin of the float type variable identified by `vid`. + /// + /// No attempt is made to resolve `vid` to its root variable. + pub fn float_var_origin(&self, vid: FloatVid) -> Span { + self.inner.borrow_mut().float_origin_span_storage[vid] + } + /// Returns the origin of the const variable identified by `vid` // FIXME: We should store origins separately from the unification table // so this doesn't need to be optional. @@ -821,9 +834,11 @@ pub fn next_int_var(&self) -> Ty<'tcx> { Ty::new_int_var(self.tcx, next_int_var_id) } - pub fn next_float_var(&self) -> Ty<'tcx> { - let next_float_var_id = - self.inner.borrow_mut().float_unification_table().new_key(ty::FloatVarValue::Unknown); + pub fn next_float_var(&self, span: Span) -> Ty<'tcx> { + let mut inner = self.inner.borrow_mut(); + let next_float_var_id = inner.float_unification_table().new_key(ty::FloatVarValue::Unknown); + let span_index = inner.float_origin_span_storage.push(span); + debug_assert_eq!(next_float_var_id, span_index); Ty::new_float_var(self.tcx, next_float_var_id) } diff --git a/compiler/rustc_infer/src/infer/snapshot/fudge.rs b/compiler/rustc_infer/src/infer/snapshot/fudge.rs index 6709c822dc7b..48af711c3177 100644 --- a/compiler/rustc_infer/src/infer/snapshot/fudge.rs +++ b/compiler/rustc_infer/src/infer/snapshot/fudge.rs @@ -6,13 +6,16 @@ self, ConstVid, FloatVid, IntVid, RegionVid, Ty, TyCtxt, TyVid, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitableExt, }; +use rustc_span::Span; use tracing::instrument; use ut::UnifyKey; use super::VariableLengths; use crate::infer::type_variable::TypeVariableOrigin; use crate::infer::unify_key::{ConstVariableValue, ConstVidKey}; -use crate::infer::{ConstVariableOrigin, InferCtxt, RegionVariableOrigin, UnificationTable}; +use crate::infer::{ + ConstVariableOrigin, InferCtxt, InferCtxtInner, RegionVariableOrigin, UnificationTable, +}; fn vars_since_snapshot<'tcx, T>( table: &UnificationTable<'_, 'tcx, T>, @@ -25,6 +28,14 @@ fn vars_since_snapshot<'tcx, T>( T::from_index(snapshot_var_len as u32)..T::from_index(table.len() as u32) } +fn float_vars_since_snapshot( + inner: &mut InferCtxtInner<'_>, + snapshot_var_len: usize, +) -> (Range, Vec) { + let range = vars_since_snapshot(&inner.float_unification_table(), snapshot_var_len); + (range.clone(), range.map(|index| inner.float_origin_span_storage[index]).collect()) +} + fn const_vars_since_snapshot<'tcx>( table: &mut UnificationTable<'_, 'tcx, ConstVidKey<'tcx>>, snapshot_var_len: usize, @@ -130,7 +141,7 @@ struct SnapshotVarData<'tcx> { region_vars: (Range, Vec>), type_vars: (Range, Vec), int_vars: Range, - float_vars: Range, + float_vars: (Range, Vec), const_vars: (Range, Vec), } @@ -143,8 +154,7 @@ fn new(infcx: &InferCtxt<'tcx>, vars_pre_snapshot: VariableLengths) -> SnapshotV let type_vars = inner.type_variables().vars_since_snapshot(vars_pre_snapshot.type_var_len); let int_vars = vars_since_snapshot(&inner.int_unification_table(), vars_pre_snapshot.int_var_len); - let float_vars = - vars_since_snapshot(&inner.float_unification_table(), vars_pre_snapshot.float_var_len); + let float_vars = float_vars_since_snapshot(&mut inner, vars_pre_snapshot.float_var_len); let const_vars = const_vars_since_snapshot( &mut inner.const_unification_table(), @@ -158,7 +168,7 @@ fn is_empty(&self) -> bool { region_vars.0.is_empty() && type_vars.0.is_empty() && int_vars.is_empty() - && float_vars.is_empty() + && float_vars.0.is_empty() && const_vars.0.is_empty() } } @@ -203,8 +213,10 @@ fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> { } } ty::FloatVar(vid) => { - if self.snapshot_vars.float_vars.contains(&vid) { - self.infcx.next_float_var() + if self.snapshot_vars.float_vars.0.contains(&vid) { + let idx = vid.as_usize() - self.snapshot_vars.float_vars.0.start.as_usize(); + let span = self.snapshot_vars.float_vars.1[idx]; + self.infcx.next_float_var(span) } else { ty } diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 8af8f40d69f5..d7ca52c590f1 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -5643,3 +5643,52 @@ "detects uses of deprecated LLVM intrinsics", @feature_gate = link_llvm_intrinsics; } + +declare_lint! { + /// The `float_literal_f32_fallback` lint detects situations where the type of an unsuffixed + /// float literal falls back to `f32` instead of `f64` to avoid a compilation error. This occurs + /// when there is a trait bound `f32: From` (or equivalent, such as `T: Into`) and the + /// literal is inferred to have the same type as `T`. + /// + /// ### Example + /// + /// ```rust + /// fn foo(x: impl Into) -> f32 { + /// x.into() + /// } + /// + /// fn main() { + /// dbg!(foo(2.5)); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Rust allows traits that are only implemented for a single floating point type to guide type + /// inferrence for floating point literals. This used to apply in the case of `f32: From` + /// (where `T` was inferred to be the same type as a floating point literal), as the only + /// floating point type impl was `f32: From`. However, as Rust is in the process of adding + /// support for `f16`, there are now two implementations for floating point types: + /// `f32: From` and `f32: From`. This means that the trait bound `f32: From` can no + /// longer guide inference for the type of the floating point literal. The default fallback for + /// unsuffixed floating point literals is `f64`. As `f32` does not implement `From`, + /// falling back to `f64` would cause a compilation error; therefore, the float type fallback + /// has been tempoarily adjusted to fallback to `f32` in this scenario. + /// + /// The lint will automatically provide a machine-applicable suggestion to add a `_f32` suffix + /// to the literal, which will fix the problem. + /// + /// This is a [future-incompatible] lint to transition this to a hard error in the future. See + /// [issue #FIXME] for more details. + /// + /// [issue #FIXME]: https://github.com/rust-lang/rust/issues/FIXME + pub FLOAT_LITERAL_F32_FALLBACK, + Warn, + "detects unsuffixed floating point literals whose type fallback to `f32`", + @future_incompatible = FutureIncompatibleInfo { + reason: fcw!(FutureReleaseError #0), + report_in_deps: false, + }; +} diff --git a/tests/ui/float/f32-into-f32.next-solver.fixed b/tests/ui/float/f32-into-f32.next-solver.fixed new file mode 100644 index 000000000000..a8d56f4428b9 --- /dev/null +++ b/tests/ui/float/f32-into-f32.next-solver.fixed @@ -0,0 +1,23 @@ +//@ revisions: old-solver next-solver +//@[next-solver] compile-flags: -Znext-solver +//@ run-pass +//@ run-rustfix + +fn foo(_: impl Into) {} + +fn main() { + foo(1.0_f32); + //~^ WARN falling back to `f32` + //~| WARN this was previously accepted + foo(-(2.5_f32)); + //~^ WARN falling back to `f32` + //~| WARN this was previously accepted + foo(1e5_f32); + //~^ WARN falling back to `f32` + //~| WARN this was previously accepted + foo(4f32); // no warning + let x = -4.0_f32; + //~^ WARN falling back to `f32` + //~| WARN this was previously accepted + foo(x); +} diff --git a/tests/ui/float/f32-into-f32.next-solver.stderr b/tests/ui/float/f32-into-f32.next-solver.stderr new file mode 100644 index 000000000000..ea71e3652384 --- /dev/null +++ b/tests/ui/float/f32-into-f32.next-solver.stderr @@ -0,0 +1,39 @@ +warning: falling back to `f32` as the trait bound `f32: From` is not satisfied + --> $DIR/f32-into-f32.rs:9:9 + | +LL | foo(1.0); + | ^^^ help: explicitly specify the type as `f32`: `1.0_f32` + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #0 + = note: `#[warn(float_literal_f32_fallback)]` on by default + +warning: falling back to `f32` as the trait bound `f32: From` is not satisfied + --> $DIR/f32-into-f32.rs:12:11 + | +LL | foo(-(2.5)); + | ^^^ help: explicitly specify the type as `f32`: `2.5_f32` + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #0 + +warning: falling back to `f32` as the trait bound `f32: From` is not satisfied + --> $DIR/f32-into-f32.rs:15:9 + | +LL | foo(1e5); + | ^^^ help: explicitly specify the type as `f32`: `1e5_f32` + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #0 + +warning: falling back to `f32` as the trait bound `f32: From` is not satisfied + --> $DIR/f32-into-f32.rs:19:14 + | +LL | let x = -4.0; + | ^^^ help: explicitly specify the type as `f32`: `4.0_f32` + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #0 + +warning: 4 warnings emitted + diff --git a/tests/ui/float/f32-into-f32.old-solver.fixed b/tests/ui/float/f32-into-f32.old-solver.fixed new file mode 100644 index 000000000000..a8d56f4428b9 --- /dev/null +++ b/tests/ui/float/f32-into-f32.old-solver.fixed @@ -0,0 +1,23 @@ +//@ revisions: old-solver next-solver +//@[next-solver] compile-flags: -Znext-solver +//@ run-pass +//@ run-rustfix + +fn foo(_: impl Into) {} + +fn main() { + foo(1.0_f32); + //~^ WARN falling back to `f32` + //~| WARN this was previously accepted + foo(-(2.5_f32)); + //~^ WARN falling back to `f32` + //~| WARN this was previously accepted + foo(1e5_f32); + //~^ WARN falling back to `f32` + //~| WARN this was previously accepted + foo(4f32); // no warning + let x = -4.0_f32; + //~^ WARN falling back to `f32` + //~| WARN this was previously accepted + foo(x); +} diff --git a/tests/ui/float/f32-into-f32.old-solver.stderr b/tests/ui/float/f32-into-f32.old-solver.stderr new file mode 100644 index 000000000000..ea71e3652384 --- /dev/null +++ b/tests/ui/float/f32-into-f32.old-solver.stderr @@ -0,0 +1,39 @@ +warning: falling back to `f32` as the trait bound `f32: From` is not satisfied + --> $DIR/f32-into-f32.rs:9:9 + | +LL | foo(1.0); + | ^^^ help: explicitly specify the type as `f32`: `1.0_f32` + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #0 + = note: `#[warn(float_literal_f32_fallback)]` on by default + +warning: falling back to `f32` as the trait bound `f32: From` is not satisfied + --> $DIR/f32-into-f32.rs:12:11 + | +LL | foo(-(2.5)); + | ^^^ help: explicitly specify the type as `f32`: `2.5_f32` + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #0 + +warning: falling back to `f32` as the trait bound `f32: From` is not satisfied + --> $DIR/f32-into-f32.rs:15:9 + | +LL | foo(1e5); + | ^^^ help: explicitly specify the type as `f32`: `1e5_f32` + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #0 + +warning: falling back to `f32` as the trait bound `f32: From` is not satisfied + --> $DIR/f32-into-f32.rs:19:14 + | +LL | let x = -4.0; + | ^^^ help: explicitly specify the type as `f32`: `4.0_f32` + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #0 + +warning: 4 warnings emitted + diff --git a/tests/ui/float/f32-into-f32.rs b/tests/ui/float/f32-into-f32.rs index 1b3f0926bdde..b55023453b7e 100644 --- a/tests/ui/float/f32-into-f32.rs +++ b/tests/ui/float/f32-into-f32.rs @@ -1,9 +1,23 @@ //@ revisions: old-solver next-solver //@[next-solver] compile-flags: -Znext-solver //@ run-pass +//@ run-rustfix fn foo(_: impl Into) {} fn main() { foo(1.0); + //~^ WARN falling back to `f32` + //~| WARN this was previously accepted + foo(-(2.5)); + //~^ WARN falling back to `f32` + //~| WARN this was previously accepted + foo(1e5); + //~^ WARN falling back to `f32` + //~| WARN this was previously accepted + foo(4f32); // no warning + let x = -4.0; + //~^ WARN falling back to `f32` + //~| WARN this was previously accepted + foo(x); } diff --git a/tests/ui/inference/untyped-primitives.rs b/tests/ui/inference/untyped-primitives.rs index 8515ca79903f..d04ccb4ff7bb 100644 --- a/tests/ui/inference/untyped-primitives.rs +++ b/tests/ui/inference/untyped-primitives.rs @@ -5,5 +5,7 @@ fn main() { let x = f32::from(3.14); + //~^ WARN falling back to `f32` + //~| WARN this was previously accepted let y = f64::from(3.14); } diff --git a/tests/ui/inference/untyped-primitives.stderr b/tests/ui/inference/untyped-primitives.stderr new file mode 100644 index 000000000000..48d62963221c --- /dev/null +++ b/tests/ui/inference/untyped-primitives.stderr @@ -0,0 +1,12 @@ +warning: falling back to `f32` as the trait bound `f32: From` is not satisfied + --> $DIR/untyped-primitives.rs:7:23 + | +LL | let x = f32::from(3.14); + | ^^^^ help: explicitly specify the type as `f32`: `3.14_f32` + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #FIXME + = note: `#[warn(float_literal_f32_fallback)]` on by default + +warning: 1 warning emitted +