Skip to content

Commit 7cf6e0c

Browse files
committed
Add FCW for unsuffixed float literal f32 fallback
1 parent c637b95 commit 7cf6e0c

File tree

12 files changed

+191
-15
lines changed

12 files changed

+191
-15
lines changed

compiler/rustc_hir_typeck/messages.ftl

+4
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ hir_typeck_field_multiply_specified_in_initializer =
9292
.label = used more than once
9393
.previous_use_label = first use of `{$ident}`
9494
95+
hir_typeck_float_literal_f32_fallback =
96+
falling back to `f32` as the trait bound `f32: From<f64>` is not satisfied
97+
.suggestion = explicitly specify the type as `f32`
98+
9599
hir_typeck_fn_item_to_variadic_function = can't pass a function item to a variadic function
96100
.suggestion = use a function pointer instead
97101
.help = a function item is zero-sized and needs to be cast into a function pointer to be used in FFI

compiler/rustc_hir_typeck/src/demand.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
342342
match infer {
343343
ty::TyVar(_) => self.next_ty_var(DUMMY_SP),
344344
ty::IntVar(_) => self.next_int_var(),
345-
ty::FloatVar(_) => self.next_float_var(),
345+
ty::FloatVar(_) => self.next_float_var(DUMMY_SP),
346346
ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_) => {
347347
bug!("unexpected fresh ty outside of the trait solver")
348348
}

compiler/rustc_hir_typeck/src/errors.rs

+8
Original file line numberDiff line numberDiff line change
@@ -979,3 +979,11 @@ pub(crate) enum SupertraitItemShadowee {
979979
traits: DiagSymbolList,
980980
},
981981
}
982+
983+
#[derive(LintDiagnostic)]
984+
#[diag(hir_typeck_float_literal_f32_fallback)]
985+
pub(crate) struct FloatLiteralF32Fallback {
986+
pub literal: String,
987+
#[suggestion(code = "{literal}_f32", applicability = "machine-applicable")]
988+
pub span: Option<Span>,
989+
}

compiler/rustc_hir_typeck/src/fallback.rs

+16-2
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ use rustc_data_structures::graph::iterate::DepthFirstSearch;
66
use rustc_data_structures::graph::vec_graph::VecGraph;
77
use rustc_data_structures::graph::{self};
88
use rustc_data_structures::unord::{UnordBag, UnordMap, UnordSet};
9-
use rustc_hir as hir;
10-
use rustc_hir::HirId;
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::{
1515
self, ClauseKind, FloatVid, PredicatePolarity, TraitPredicate, Ty, TyCtxt, TypeSuperVisitable,
1616
TypeVisitable,
@@ -221,6 +221,20 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
221221
.iter()
222222
.flat_map(|ty| ty.float_vid())
223223
.filter(|vid| roots.contains(&self.root_float_var(*vid)))
224+
.inspect(|vid| {
225+
let span = self.float_var_origin(*vid);
226+
// Show the entire literal in the suggestion to make it clearer.
227+
let literal = self.tcx.sess.source_map().span_to_snippet(span).ok();
228+
self.tcx.emit_node_span_lint(
229+
FLOAT_LITERAL_F32_FALLBACK,
230+
CRATE_HIR_ID,
231+
span,
232+
errors::FloatLiteralF32Fallback {
233+
span: literal.as_ref().map(|_| span),
234+
literal: literal.unwrap_or_default(),
235+
},
236+
);
237+
})
224238
.collect();
225239
debug!("calculate_fallback_to_f32: fallback_to_f32={:?}", fallback_to_f32);
226240
fallback_to_f32

compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1669,7 +1669,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
16691669
ty::Float(_) => Some(ty),
16701670
_ => None,
16711671
});
1672-
opt_ty.unwrap_or_else(|| self.next_float_var())
1672+
opt_ty.unwrap_or_else(|| self.next_float_var(lit.span))
16731673
}
16741674
ast::LitKind::Bool(_) => tcx.types.bool,
16751675
ast::LitKind::CStr(_, _) => Ty::new_imm_ref(

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ impl<'tcx> InferCtxt<'tcx> {
116116

117117
CanonicalTyVarKind::Int => self.next_int_var(),
118118

119-
CanonicalTyVarKind::Float => self.next_float_var(),
119+
CanonicalTyVarKind::Float => self.next_float_var(span),
120120
};
121121
ty.into()
122122
}

compiler/rustc_infer/src/infer/mod.rs

+18-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use rustc_data_structures::unify as ut;
2121
use rustc_errors::{DiagCtxtHandle, ErrorGuaranteed};
2222
use rustc_hir as hir;
2323
use rustc_hir::def_id::{DefId, LocalDefId};
24+
use rustc_index::IndexVec;
2425
use rustc_macros::extension;
2526
pub use rustc_macros::{TypeFoldable, TypeVisitable};
2627
use rustc_middle::bug;
@@ -109,6 +110,10 @@ pub struct InferCtxtInner<'tcx> {
109110
/// Map from floating variable to the kind of float it represents.
110111
float_unification_storage: ut::UnificationTableStorage<ty::FloatVid>,
111112

113+
/// Map from floating variable to the origin span it came from. This is only used for the FCW
114+
/// for the fallback to `f32`, so can be removed once the `f32` fallback is removed.
115+
float_origin_span_storage: IndexVec<FloatVid, Span>,
116+
112117
/// Tracks the set of region variables and the constraints between them.
113118
///
114119
/// This is initially `Some(_)` but when
@@ -165,6 +170,7 @@ impl<'tcx> InferCtxtInner<'tcx> {
165170
const_unification_storage: Default::default(),
166171
int_unification_storage: Default::default(),
167172
float_unification_storage: Default::default(),
173+
float_origin_span_storage: Default::default(),
168174
region_constraint_storage: Some(Default::default()),
169175
region_obligations: vec![],
170176
opaque_type_storage: Default::default(),
@@ -633,6 +639,13 @@ impl<'tcx> InferCtxt<'tcx> {
633639
self.inner.borrow_mut().type_variables().var_origin(vid)
634640
}
635641

642+
/// Returns the origin of the float type variable identified by `vid`.
643+
///
644+
/// No attempt is made to resolve `vid` to its root variable.
645+
pub fn float_var_origin(&self, vid: FloatVid) -> Span {
646+
self.inner.borrow_mut().float_origin_span_storage[vid]
647+
}
648+
636649
/// Returns the origin of the const variable identified by `vid`
637650
// FIXME: We should store origins separately from the unification table
638651
// so this doesn't need to be optional.
@@ -818,9 +831,11 @@ impl<'tcx> InferCtxt<'tcx> {
818831
Ty::new_int_var(self.tcx, next_int_var_id)
819832
}
820833

821-
pub fn next_float_var(&self) -> Ty<'tcx> {
822-
let next_float_var_id =
823-
self.inner.borrow_mut().float_unification_table().new_key(ty::FloatVarValue::Unknown);
834+
pub fn next_float_var(&self, span: Span) -> Ty<'tcx> {
835+
let mut inner = self.inner.borrow_mut();
836+
let next_float_var_id = inner.float_unification_table().new_key(ty::FloatVarValue::Unknown);
837+
let span_index = inner.float_origin_span_storage.push(span);
838+
debug_assert_eq!(next_float_var_id, span_index);
824839
Ty::new_float_var(self.tcx, next_float_var_id)
825840
}
826841

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

+19-7
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@ use rustc_middle::ty::{
55
self, ConstVid, FloatVid, IntVid, RegionVid, Ty, TyCtxt, TyVid, TypeFoldable, TypeFolder,
66
TypeSuperFoldable,
77
};
8+
use rustc_span::Span;
89
use rustc_type_ir::TypeVisitableExt;
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,
@@ -128,7 +139,7 @@ struct SnapshotVarData {
128139
region_vars: (Range<RegionVid>, Vec<RegionVariableOrigin>),
129140
type_vars: (Range<TyVid>, Vec<TypeVariableOrigin>),
130141
int_vars: Range<IntVid>,
131-
float_vars: Range<FloatVid>,
142+
float_vars: (Range<FloatVid>, Vec<Span>),
132143
const_vars: (Range<ConstVid>, Vec<ConstVariableOrigin>),
133144
}
134145

@@ -141,8 +152,7 @@ impl SnapshotVarData {
141152
let type_vars = inner.type_variables().vars_since_snapshot(vars_pre_snapshot.type_var_len);
142153
let int_vars =
143154
vars_since_snapshot(&inner.int_unification_table(), vars_pre_snapshot.int_var_len);
144-
let float_vars =
145-
vars_since_snapshot(&inner.float_unification_table(), vars_pre_snapshot.float_var_len);
155+
let float_vars = float_vars_since_snapshot(&mut inner, vars_pre_snapshot.float_var_len);
146156

147157
let const_vars = const_vars_since_snapshot(
148158
&mut inner.const_unification_table(),
@@ -156,7 +166,7 @@ impl SnapshotVarData {
156166
region_vars.0.is_empty()
157167
&& type_vars.0.is_empty()
158168
&& int_vars.is_empty()
159-
&& float_vars.is_empty()
169+
&& float_vars.0.is_empty()
160170
&& const_vars.0.is_empty()
161171
}
162172
}
@@ -201,8 +211,10 @@ impl<'a, 'tcx> TypeFolder<TyCtxt<'tcx>> for InferenceFudger<'a, 'tcx> {
201211
}
202212
}
203213
ty::FloatVar(vid) => {
204-
if self.snapshot_vars.float_vars.contains(&vid) {
205-
self.infcx.next_float_var()
214+
if self.snapshot_vars.float_vars.0.contains(&vid) {
215+
let idx = vid.as_usize() - self.snapshot_vars.float_vars.0.start.as_usize();
216+
let span = self.snapshot_vars.float_vars.1[idx];
217+
self.infcx.next_float_var(span)
206218
} else {
207219
ty
208220
}

compiler/rustc_lint_defs/src/builtin.rs

+49
Original file line numberDiff line numberDiff line change
@@ -5171,3 +5171,52 @@ declare_lint! {
51715171
reference: "issue #138762 <https://github.com/rust-lang/rust/issues/138762>",
51725172
};
51735173
}
5174+
5175+
declare_lint! {
5176+
/// The `float_literal_f32_fallback` lint detects situations where the type of an unsuffixed
5177+
/// float literal falls back to `f32` instead of `f64` to avoid a compilation error. This occurs
5178+
/// when there is a trait bound `f32: From<T>` (or equivalent, such as `T: Into<f32>`) and the
5179+
/// literal is inferred to have the same type as `T`.
5180+
///
5181+
/// ### Example
5182+
///
5183+
/// ```rust
5184+
/// fn foo(x: impl Into<f32>) -> f32 {
5185+
/// x.into()
5186+
/// }
5187+
///
5188+
/// fn main() {
5189+
/// dbg!(foo(2.5));
5190+
/// }
5191+
/// ```
5192+
///
5193+
/// {{produces}}
5194+
///
5195+
/// ### Explanation
5196+
///
5197+
/// Rust allows traits that are only implemented for a single floating point type to guide type
5198+
/// inferrence for floating point literals. This used to apply in the case of `f32: From<T>`
5199+
/// (where `T` was inferred to be the same type as a floating point literal), as the only
5200+
/// floating point type impl was `f32: From<f32>`. However, as Rust is in the process of adding
5201+
/// support for `f16`, there are now two implementations for floating point types:
5202+
/// `f32: From<f16>` and `f32: From<f32>`. This means that the trait bound `f32: From<T>` can no
5203+
/// longer guide inferrence for the type of the floating point literal. The default fallback for
5204+
/// unsuffixed floating point literals is `f64`. As `f32` does not implement `From<f64>`,
5205+
/// falling back to `f64` would cause a compilation error; therefore, the float type fallback
5206+
/// has been tempoarily adjusted to fallback to `f32` in this scenario.
5207+
///
5208+
/// The lint will automatically provide a machine-applicable suggestion to add a `_f32` suffix
5209+
/// to the literal, which will fix the problem.
5210+
///
5211+
/// This is a [future-incompatible] lint to transition this to a hard error in the future. See
5212+
/// [issue #FIXME] for more details.
5213+
///
5214+
/// [issue #FIXME]: https://github.com/rust-lang/rust/issues/FIXME
5215+
pub FLOAT_LITERAL_F32_FALLBACK,
5216+
Warn,
5217+
"detects unsuffixed floating point literals whose type fallback to `f32`",
5218+
@future_incompatible = FutureIncompatibleInfo {
5219+
reason: FutureIncompatibilityReason::FutureReleaseErrorDontReportInDeps,
5220+
reference: "issue #FIXME <https://github.com/rust-lang/rust/issues/FIXME>",
5221+
};
5222+
}

tests/ui/float/f32-into-f32.fixed

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//@ run-pass
2+
//@ run-rustfix
3+
4+
fn foo(_: impl Into<f32>) {}
5+
6+
fn main() {
7+
foo(1.0_f32);
8+
//~^ WARN falling back to `f32`
9+
//~| WARN this was previously accepted
10+
foo(-(2.5_f32));
11+
//~^ WARN falling back to `f32`
12+
//~| WARN this was previously accepted
13+
foo(1e5_f32);
14+
//~^ WARN falling back to `f32`
15+
//~| WARN this was previously accepted
16+
foo(4f32); // no warning
17+
let x = -4.0_f32;
18+
//~^ WARN falling back to `f32`
19+
//~| WARN this was previously accepted
20+
foo(x);
21+
}

tests/ui/float/f32-into-f32.rs

+14
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
11
//@ run-pass
2+
//@ run-rustfix
23

34
fn foo(_: impl Into<f32>) {}
45

56
fn main() {
67
foo(1.0);
8+
//~^ WARN falling back to `f32`
9+
//~| WARN this was previously accepted
10+
foo(-(2.5));
11+
//~^ WARN falling back to `f32`
12+
//~| WARN this was previously accepted
13+
foo(1e5);
14+
//~^ WARN falling back to `f32`
15+
//~| WARN this was previously accepted
16+
foo(4f32); // no warning
17+
let x = -4.0;
18+
//~^ WARN falling back to `f32`
19+
//~| WARN this was previously accepted
20+
foo(x);
721
}

tests/ui/float/f32-into-f32.stderr

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
warning: falling back to `f32` as the trait bound `f32: From<f64>` is not satisfied
2+
--> $DIR/f32-into-f32.rs:7:9
3+
|
4+
LL | foo(1.0);
5+
| ^^^ help: explicitly specify the type as `f32`: `1.0_f32`
6+
|
7+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
8+
= note: for more information, see issue #FIXME <https://github.com/rust-lang/rust/issues/FIXME>
9+
= note: `#[warn(float_literal_f32_fallback)]` on by default
10+
11+
warning: falling back to `f32` as the trait bound `f32: From<f64>` is not satisfied
12+
--> $DIR/f32-into-f32.rs:10:11
13+
|
14+
LL | foo(-(2.5));
15+
| ^^^ help: explicitly specify the type as `f32`: `2.5_f32`
16+
|
17+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
18+
= note: for more information, see issue #FIXME <https://github.com/rust-lang/rust/issues/FIXME>
19+
20+
warning: falling back to `f32` as the trait bound `f32: From<f64>` is not satisfied
21+
--> $DIR/f32-into-f32.rs:13:9
22+
|
23+
LL | foo(1e5);
24+
| ^^^ help: explicitly specify the type as `f32`: `1e5_f32`
25+
|
26+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
27+
= note: for more information, see issue #FIXME <https://github.com/rust-lang/rust/issues/FIXME>
28+
29+
warning: falling back to `f32` as the trait bound `f32: From<f64>` is not satisfied
30+
--> $DIR/f32-into-f32.rs:17:14
31+
|
32+
LL | let x = -4.0;
33+
| ^^^ help: explicitly specify the type as `f32`: `4.0_f32`
34+
|
35+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
36+
= note: for more information, see issue #FIXME <https://github.com/rust-lang/rust/issues/FIXME>
37+
38+
warning: 4 warnings emitted
39+

0 commit comments

Comments
 (0)