Skip to content

Commit 7aad5c0

Browse files
beetreesfolkertdev
authored andcommitted
Add FCW for unsuffixed float literal f32 fallback
1 parent f354fa8 commit 7aad5c0

16 files changed

Lines changed: 270 additions & 16 deletions

File tree

compiler/rustc_hir_typeck/src/demand.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
346346
match infer {
347347
ty::TyVar(_) => self.next_ty_var(DUMMY_SP),
348348
ty::IntVar(_) => self.next_int_var(),
349-
ty::FloatVar(_) => self.next_float_var(),
349+
ty::FloatVar(_) => self.next_float_var(DUMMY_SP),
350350
ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_) => {
351351
bug!("unexpected fresh ty outside of the trait solver")
352352
}

compiler/rustc_hir_typeck/src/errors.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1384,3 +1384,15 @@ pub(crate) struct ProjectOnNonPinProjectType {
13841384
)]
13851385
pub sugg_span: Option<Span>,
13861386
}
1387+
1388+
#[derive(Diagnostic)]
1389+
#[diag("falling back to `f32` as the trait bound `f32: From<f64>` is not satisfied")]
1390+
pub(crate) struct FloatLiteralF32Fallback {
1391+
pub literal: String,
1392+
#[suggestion(
1393+
"explicitly specify the type as `f32`",
1394+
code = "{literal}_f32",
1395+
applicability = "machine-applicable"
1396+
)]
1397+
pub span: Option<Span>,
1398+
}

compiler/rustc_hir_typeck/src/fallback.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ use rustc_data_structures::fx::FxHashSet;
55
use rustc_data_structures::graph;
66
use rustc_data_structures::graph::vec_graph::VecGraph;
77
use rustc_data_structures::unord::{UnordMap, UnordSet};
8-
use rustc_hir as hir;
9-
use rustc_hir::HirId;
108
use rustc_hir::attrs::DivergingFallbackBehavior;
119
use rustc_hir::def::{DefKind, Res};
1210
use rustc_hir::def_id::DefId;
1311
use rustc_hir::intravisit::{InferKind, Visitor};
12+
use rustc_hir::{self as hir, CRATE_HIR_ID, HirId};
13+
use rustc_lint::builtin::FLOAT_LITERAL_F32_FALLBACK;
1414
use rustc_middle::ty::{self, FloatVid, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable};
1515
use rustc_session::lint;
1616
use rustc_span::def_id::LocalDefId;
@@ -160,6 +160,20 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
160160
.iter()
161161
.flat_map(|ty| ty.float_vid())
162162
.filter(|vid| roots.contains(&self.root_float_var(*vid)))
163+
.inspect(|vid| {
164+
let span = self.float_var_origin(*vid);
165+
// Show the entire literal in the suggestion to make it clearer.
166+
let literal = self.tcx.sess.source_map().span_to_snippet(span).ok();
167+
self.tcx.emit_node_span_lint(
168+
FLOAT_LITERAL_F32_FALLBACK,
169+
CRATE_HIR_ID,
170+
span,
171+
errors::FloatLiteralF32Fallback {
172+
span: literal.as_ref().map(|_| span),
173+
literal: literal.unwrap_or_default(),
174+
},
175+
);
176+
})
163177
.collect();
164178
debug!("calculate_fallback_to_f32: fallback_to_f32={:?}", fallback_to_f32);
165179
fallback_to_f32

compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -765,7 +765,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
765765
ty::Float(_) => Some(ty),
766766
_ => None,
767767
});
768-
opt_ty.unwrap_or_else(|| self.next_float_var())
768+
opt_ty.unwrap_or_else(|| self.next_float_var(lit.span))
769769
}
770770
ast::LitKind::Bool(_) => tcx.types.bool,
771771
ast::LitKind::CStr(_, _) => Ty::new_imm_ref(

compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ impl<'tcx> ProofTreeVisitor<'tcx> for FindFromFloatForF32RootVids<'_, 'tcx> {
224224

225225
fn config(&self) -> InspectConfig {
226226
// Avoid hang from exponentially growing proof trees (see `cycle-modulo-ambig-aliases.rs`).
227-
// 3 is more than enough for all occurences in practice (a.k.a. `Into`).
227+
// 3 is more than enough for all occurrences in practice (a.k.a. `Into`).
228228
InspectConfig { max_depth: 3 }
229229
}
230230

compiler/rustc_infer/src/infer/canonical/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ impl<'tcx> InferCtxt<'tcx> {
107107

108108
CanonicalVarKind::Int => self.next_int_var().into(),
109109

110-
CanonicalVarKind::Float => self.next_float_var().into(),
110+
CanonicalVarKind::Float => self.next_float_var(span).into(),
111111

112112
CanonicalVarKind::PlaceholderTy(ty::PlaceholderType { universe, bound, .. }) => {
113113
let universe_mapped = universe_map(universe);

compiler/rustc_infer/src/infer/mod.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use rustc_data_structures::unify as ut;
1818
use rustc_errors::{DiagCtxtHandle, ErrorGuaranteed};
1919
use rustc_hir as hir;
2020
use rustc_hir::def_id::{DefId, LocalDefId};
21+
use rustc_index::IndexVec;
2122
use rustc_macros::extension;
2223
pub use rustc_macros::{TypeFoldable, TypeVisitable};
2324
use rustc_middle::bug;
@@ -108,6 +109,10 @@ pub struct InferCtxtInner<'tcx> {
108109
/// Map from floating variable to the kind of float it represents.
109110
float_unification_storage: ut::UnificationTableStorage<ty::FloatVid>,
110111

112+
/// Map from floating variable to the origin span it came from. This is only used for the FCW
113+
/// for the fallback to `f32`, so can be removed once the `f32` fallback is removed.
114+
float_origin_span_storage: IndexVec<FloatVid, Span>,
115+
111116
/// Tracks the set of region variables and the constraints between them.
112117
///
113118
/// This is initially `Some(_)` but when
@@ -161,6 +166,7 @@ impl<'tcx> InferCtxtInner<'tcx> {
161166
const_unification_storage: Default::default(),
162167
int_unification_storage: Default::default(),
163168
float_unification_storage: Default::default(),
169+
float_origin_span_storage: Default::default(),
164170
region_constraint_storage: Some(Default::default()),
165171
region_obligations: Default::default(),
166172
region_assumptions: Default::default(),
@@ -644,6 +650,13 @@ impl<'tcx> InferCtxt<'tcx> {
644650
self.inner.borrow_mut().type_variables().var_origin(vid)
645651
}
646652

653+
/// Returns the origin of the float type variable identified by `vid`.
654+
///
655+
/// No attempt is made to resolve `vid` to its root variable.
656+
pub fn float_var_origin(&self, vid: FloatVid) -> Span {
657+
self.inner.borrow_mut().float_origin_span_storage[vid]
658+
}
659+
647660
/// Returns the origin of the const variable identified by `vid`
648661
// FIXME: We should store origins separately from the unification table
649662
// so this doesn't need to be optional.
@@ -821,9 +834,11 @@ impl<'tcx> InferCtxt<'tcx> {
821834
Ty::new_int_var(self.tcx, next_int_var_id)
822835
}
823836

824-
pub fn next_float_var(&self) -> Ty<'tcx> {
825-
let next_float_var_id =
826-
self.inner.borrow_mut().float_unification_table().new_key(ty::FloatVarValue::Unknown);
837+
pub fn next_float_var(&self, span: Span) -> Ty<'tcx> {
838+
let mut inner = self.inner.borrow_mut();
839+
let next_float_var_id = inner.float_unification_table().new_key(ty::FloatVarValue::Unknown);
840+
let span_index = inner.float_origin_span_storage.push(span);
841+
debug_assert_eq!(next_float_var_id, span_index);
827842
Ty::new_float_var(self.tcx, next_float_var_id)
828843
}
829844

compiler/rustc_infer/src/infer/snapshot/fudge.rs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@ use rustc_middle::ty::{
66
self, ConstVid, FloatVid, IntVid, RegionVid, Ty, TyCtxt, TyVid, TypeFoldable, TypeFolder,
77
TypeSuperFoldable, TypeVisitableExt,
88
};
9+
use rustc_span::Span;
910
use tracing::instrument;
1011
use ut::UnifyKey;
1112

1213
use super::VariableLengths;
1314
use crate::infer::type_variable::TypeVariableOrigin;
1415
use crate::infer::unify_key::{ConstVariableValue, ConstVidKey};
15-
use crate::infer::{ConstVariableOrigin, InferCtxt, RegionVariableOrigin, UnificationTable};
16+
use crate::infer::{
17+
ConstVariableOrigin, InferCtxt, InferCtxtInner, RegionVariableOrigin, UnificationTable,
18+
};
1619

1720
fn vars_since_snapshot<'tcx, T>(
1821
table: &UnificationTable<'_, 'tcx, T>,
@@ -25,6 +28,14 @@ where
2528
T::from_index(snapshot_var_len as u32)..T::from_index(table.len() as u32)
2629
}
2730

31+
fn float_vars_since_snapshot(
32+
inner: &mut InferCtxtInner<'_>,
33+
snapshot_var_len: usize,
34+
) -> (Range<FloatVid>, Vec<Span>) {
35+
let range = vars_since_snapshot(&inner.float_unification_table(), snapshot_var_len);
36+
(range.clone(), range.map(|index| inner.float_origin_span_storage[index]).collect())
37+
}
38+
2839
fn const_vars_since_snapshot<'tcx>(
2940
table: &mut UnificationTable<'_, 'tcx, ConstVidKey<'tcx>>,
3041
snapshot_var_len: usize,
@@ -130,7 +141,7 @@ struct SnapshotVarData<'tcx> {
130141
region_vars: (Range<RegionVid>, Vec<RegionVariableOrigin<'tcx>>),
131142
type_vars: (Range<TyVid>, Vec<TypeVariableOrigin>),
132143
int_vars: Range<IntVid>,
133-
float_vars: Range<FloatVid>,
144+
float_vars: (Range<FloatVid>, Vec<Span>),
134145
const_vars: (Range<ConstVid>, Vec<ConstVariableOrigin>),
135146
}
136147

@@ -143,8 +154,7 @@ impl<'tcx> SnapshotVarData<'tcx> {
143154
let type_vars = inner.type_variables().vars_since_snapshot(vars_pre_snapshot.type_var_len);
144155
let int_vars =
145156
vars_since_snapshot(&inner.int_unification_table(), vars_pre_snapshot.int_var_len);
146-
let float_vars =
147-
vars_since_snapshot(&inner.float_unification_table(), vars_pre_snapshot.float_var_len);
157+
let float_vars = float_vars_since_snapshot(&mut inner, vars_pre_snapshot.float_var_len);
148158

149159
let const_vars = const_vars_since_snapshot(
150160
&mut inner.const_unification_table(),
@@ -158,7 +168,7 @@ impl<'tcx> SnapshotVarData<'tcx> {
158168
region_vars.0.is_empty()
159169
&& type_vars.0.is_empty()
160170
&& int_vars.is_empty()
161-
&& float_vars.is_empty()
171+
&& float_vars.0.is_empty()
162172
&& const_vars.0.is_empty()
163173
}
164174
}
@@ -203,8 +213,10 @@ impl<'a, 'tcx> TypeFolder<TyCtxt<'tcx>> for InferenceFudger<'a, 'tcx> {
203213
}
204214
}
205215
ty::FloatVar(vid) => {
206-
if self.snapshot_vars.float_vars.contains(&vid) {
207-
self.infcx.next_float_var()
216+
if self.snapshot_vars.float_vars.0.contains(&vid) {
217+
let idx = vid.as_usize() - self.snapshot_vars.float_vars.0.start.as_usize();
218+
let span = self.snapshot_vars.float_vars.1[idx];
219+
self.infcx.next_float_var(span)
208220
} else {
209221
ty
210222
}

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5643,3 +5643,52 @@ declare_lint! {
56435643
"detects uses of deprecated LLVM intrinsics",
56445644
@feature_gate = link_llvm_intrinsics;
56455645
}
5646+
5647+
declare_lint! {
5648+
/// The `float_literal_f32_fallback` lint detects situations where the type of an unsuffixed
5649+
/// float literal falls back to `f32` instead of `f64` to avoid a compilation error. This occurs
5650+
/// when there is a trait bound `f32: From<T>` (or equivalent, such as `T: Into<f32>`) and the
5651+
/// literal is inferred to have the same type as `T`.
5652+
///
5653+
/// ### Example
5654+
///
5655+
/// ```rust
5656+
/// fn foo(x: impl Into<f32>) -> f32 {
5657+
/// x.into()
5658+
/// }
5659+
///
5660+
/// fn main() {
5661+
/// dbg!(foo(2.5));
5662+
/// }
5663+
/// ```
5664+
///
5665+
/// {{produces}}
5666+
///
5667+
/// ### Explanation
5668+
///
5669+
/// Rust allows traits that are only implemented for a single floating point type to guide type
5670+
/// inferrence for floating point literals. This used to apply in the case of `f32: From<T>`
5671+
/// (where `T` was inferred to be the same type as a floating point literal), as the only
5672+
/// floating point type impl was `f32: From<f32>`. However, as Rust is in the process of adding
5673+
/// support for `f16`, there are now two implementations for floating point types:
5674+
/// `f32: From<f16>` and `f32: From<f32>`. This means that the trait bound `f32: From<T>` can no
5675+
/// longer guide inference for the type of the floating point literal. The default fallback for
5676+
/// unsuffixed floating point literals is `f64`. As `f32` does not implement `From<f64>`,
5677+
/// falling back to `f64` would cause a compilation error; therefore, the float type fallback
5678+
/// has been tempoarily adjusted to fallback to `f32` in this scenario.
5679+
///
5680+
/// The lint will automatically provide a machine-applicable suggestion to add a `_f32` suffix
5681+
/// to the literal, which will fix the problem.
5682+
///
5683+
/// This is a [future-incompatible] lint to transition this to a hard error in the future. See
5684+
/// [issue #FIXME] for more details.
5685+
///
5686+
/// [issue #FIXME]: https://github.com/rust-lang/rust/issues/FIXME
5687+
pub FLOAT_LITERAL_F32_FALLBACK,
5688+
Warn,
5689+
"detects unsuffixed floating point literals whose type fallback to `f32`",
5690+
@future_incompatible = FutureIncompatibleInfo {
5691+
reason: fcw!(FutureReleaseError #0),
5692+
report_in_deps: false,
5693+
};
5694+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//@ revisions: old-solver next-solver
2+
//@[next-solver] compile-flags: -Znext-solver
3+
//@ run-pass
4+
//@ run-rustfix
5+
6+
fn foo(_: impl Into<f32>) {}
7+
8+
fn main() {
9+
foo(1.0_f32);
10+
//~^ WARN falling back to `f32`
11+
//~| WARN this was previously accepted
12+
foo(-(2.5_f32));
13+
//~^ WARN falling back to `f32`
14+
//~| WARN this was previously accepted
15+
foo(1e5_f32);
16+
//~^ WARN falling back to `f32`
17+
//~| WARN this was previously accepted
18+
foo(4f32); // no warning
19+
let x = -4.0_f32;
20+
//~^ WARN falling back to `f32`
21+
//~| WARN this was previously accepted
22+
foo(x);
23+
}

0 commit comments

Comments
 (0)