Add FCW for unsuffixed float literal f32 fallback

This commit is contained in:
beetrees
2025-03-31 23:18:09 +01:00
committed by Folkert de Vries
parent f354fa86b3
commit 7aad5c0784
16 changed files with 270 additions and 16 deletions
+1 -1
View File
@@ -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")
}
+12
View File
@@ -1384,3 +1384,15 @@ pub(crate) struct ProjectOnNonPinProjectType {
)]
pub sugg_span: Option<Span>,
}
#[derive(Diagnostic)]
#[diag("falling back to `f32` as the trait bound `f32: From<f64>` 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<Span>,
}
+16 -2
View File
@@ -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
@@ -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(
@@ -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 }
}
@@ -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);
+18 -3
View File
@@ -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<ty::FloatVid>,
/// 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<FloatVid, Span>,
/// 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)
}
@@ -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<FloatVid>, Vec<Span>) {
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<RegionVid>, Vec<RegionVariableOrigin<'tcx>>),
type_vars: (Range<TyVid>, Vec<TypeVariableOrigin>),
int_vars: Range<IntVid>,
float_vars: Range<FloatVid>,
float_vars: (Range<FloatVid>, Vec<Span>),
const_vars: (Range<ConstVid>, Vec<ConstVariableOrigin>),
}
@@ -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
}
+49
View File
@@ -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<T>` (or equivalent, such as `T: Into<f32>`) and the
/// literal is inferred to have the same type as `T`.
///
/// ### Example
///
/// ```rust
/// fn foo(x: impl Into<f32>) -> 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<T>`
/// (where `T` was inferred to be the same type as a floating point literal), as the only
/// floating point type impl was `f32: From<f32>`. However, as Rust is in the process of adding
/// support for `f16`, there are now two implementations for floating point types:
/// `f32: From<f16>` and `f32: From<f32>`. This means that the trait bound `f32: From<T>` 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<f64>`,
/// 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,
};
}
@@ -0,0 +1,23 @@
//@ revisions: old-solver next-solver
//@[next-solver] compile-flags: -Znext-solver
//@ run-pass
//@ run-rustfix
fn foo(_: impl Into<f32>) {}
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);
}
@@ -0,0 +1,39 @@
warning: falling back to `f32` as the trait bound `f32: From<f64>` 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 <https://github.com/rust-lang/rust/issues/0>
= note: `#[warn(float_literal_f32_fallback)]` on by default
warning: falling back to `f32` as the trait bound `f32: From<f64>` 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 <https://github.com/rust-lang/rust/issues/0>
warning: falling back to `f32` as the trait bound `f32: From<f64>` 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 <https://github.com/rust-lang/rust/issues/0>
warning: falling back to `f32` as the trait bound `f32: From<f64>` 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 <https://github.com/rust-lang/rust/issues/0>
warning: 4 warnings emitted
@@ -0,0 +1,23 @@
//@ revisions: old-solver next-solver
//@[next-solver] compile-flags: -Znext-solver
//@ run-pass
//@ run-rustfix
fn foo(_: impl Into<f32>) {}
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);
}
@@ -0,0 +1,39 @@
warning: falling back to `f32` as the trait bound `f32: From<f64>` 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 <https://github.com/rust-lang/rust/issues/0>
= note: `#[warn(float_literal_f32_fallback)]` on by default
warning: falling back to `f32` as the trait bound `f32: From<f64>` 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 <https://github.com/rust-lang/rust/issues/0>
warning: falling back to `f32` as the trait bound `f32: From<f64>` 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 <https://github.com/rust-lang/rust/issues/0>
warning: falling back to `f32` as the trait bound `f32: From<f64>` 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 <https://github.com/rust-lang/rust/issues/0>
warning: 4 warnings emitted
+14
View File
@@ -1,9 +1,23 @@
//@ revisions: old-solver next-solver
//@[next-solver] compile-flags: -Znext-solver
//@ run-pass
//@ run-rustfix
fn foo(_: impl Into<f32>) {}
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);
}
+2
View File
@@ -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);
}
@@ -0,0 +1,12 @@
warning: falling back to `f32` as the trait bound `f32: From<f64>` 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 <https://github.com/rust-lang/rust/issues/FIXME>
= note: `#[warn(float_literal_f32_fallback)]` on by default
warning: 1 warning emitted