Skip to content

Commit 6e6d23c

Browse files
committed
Add FCW for unsuffixed float literal f32 fallback
1 parent 17abf3a commit 6e6d23c

File tree

14 files changed

+205
-15
lines changed

14 files changed

+205
-15
lines changed

compiler/rustc_hir_typeck/messages.ftl

Lines changed: 4 additions & 0 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -983,3 +983,11 @@ pub(crate) struct RegisterTypeUnstable<'a> {
983983
pub span: Span,
984984
pub ty: Ty<'a>,
985985
}
986+
987+
#[derive(LintDiagnostic)]
988+
#[diag(hir_typeck_float_literal_f32_fallback)]
989+
pub(crate) struct FloatLiteralF32Fallback {
990+
pub literal: String,
991+
#[suggestion(code = "{literal}_f32", applicability = "machine-applicable")]
992+
pub span: Option<Span>,
993+
}

compiler/rustc_hir_typeck/src/fallback.rs

Lines changed: 16 additions & 2 deletions
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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1607,7 +1607,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
16071607
ty::Float(_) => Some(ty),
16081608
_ => None,
16091609
});
1610-
opt_ty.unwrap_or_else(|| self.next_float_var())
1610+
opt_ty.unwrap_or_else(|| self.next_float_var(lit.span))
16111611
}
16121612
ast::LitKind::Bool(_) => tcx.types.bool,
16131613
ast::LitKind::CStr(_, _) => Ty::new_imm_ref(

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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 18 additions & 3 deletions
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;
@@ -110,6 +111,10 @@ pub struct InferCtxtInner<'tcx> {
110111
/// Map from floating variable to the kind of float it represents.
111112
float_unification_storage: ut::UnificationTableStorage<ty::FloatVid>,
112113

114+
/// Map from floating variable to the origin span it came from. This is only used for the FCW
115+
/// for the fallback to `f32`, so can be removed once the `f32` fallback is removed.
116+
float_origin_span_storage: IndexVec<FloatVid, Span>,
117+
113118
/// Tracks the set of region variables and the constraints between them.
114119
///
115120
/// This is initially `Some(_)` but when
@@ -166,6 +171,7 @@ impl<'tcx> InferCtxtInner<'tcx> {
166171
const_unification_storage: Default::default(),
167172
int_unification_storage: Default::default(),
168173
float_unification_storage: Default::default(),
174+
float_origin_span_storage: Default::default(),
169175
region_constraint_storage: Some(Default::default()),
170176
region_obligations: vec![],
171177
opaque_type_storage: Default::default(),
@@ -625,6 +631,13 @@ impl<'tcx> InferCtxt<'tcx> {
625631
self.inner.borrow_mut().type_variables().var_origin(vid)
626632
}
627633

634+
/// Returns the origin of the float type variable identified by `vid`.
635+
///
636+
/// No attempt is made to resolve `vid` to its root variable.
637+
pub fn float_var_origin(&self, vid: FloatVid) -> Span {
638+
self.inner.borrow_mut().float_origin_span_storage[vid]
639+
}
640+
628641
/// Returns the origin of the const variable identified by `vid`
629642
// FIXME: We should store origins separately from the unification table
630643
// so this doesn't need to be optional.
@@ -810,9 +823,11 @@ impl<'tcx> InferCtxt<'tcx> {
810823
Ty::new_int_var(self.tcx, next_int_var_id)
811824
}
812825

813-
pub fn next_float_var(&self) -> Ty<'tcx> {
814-
let next_float_var_id =
815-
self.inner.borrow_mut().float_unification_table().new_key(ty::FloatVarValue::Unknown);
826+
pub fn next_float_var(&self, span: Span) -> Ty<'tcx> {
827+
let mut inner = self.inner.borrow_mut();
828+
let next_float_var_id = inner.float_unification_table().new_key(ty::FloatVarValue::Unknown);
829+
let span_index = inner.float_origin_span_storage.push(span);
830+
debug_assert_eq!(next_float_var_id, span_index);
816831
Ty::new_float_var(self.tcx, next_float_var_id)
817832
}
818833

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

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

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

1619
fn vars_since_snapshot<'tcx, T>(
1720
table: &UnificationTable<'_, 'tcx, T>,
@@ -24,6 +27,14 @@ where
2427
T::from_index(snapshot_var_len as u32)..T::from_index(table.len() as u32)
2528
}
2629

30+
fn float_vars_since_snapshot(
31+
inner: &mut InferCtxtInner<'_>,
32+
snapshot_var_len: usize,
33+
) -> (Range<FloatVid>, Vec<Span>) {
34+
let range = vars_since_snapshot(&inner.float_unification_table(), snapshot_var_len);
35+
(range.clone(), range.map(|index| inner.float_origin_span_storage[index]).collect())
36+
}
37+
2738
fn const_vars_since_snapshot<'tcx>(
2839
table: &mut UnificationTable<'_, 'tcx, ConstVidKey<'tcx>>,
2940
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

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5067,3 +5067,52 @@ declare_lint! {
50675067
reference: "issue #138762 <https://github.com/rust-lang/rust/issues/138762>",
50685068
};
50695069
}
5070+
5071+
declare_lint! {
5072+
/// The `float_literal_f32_fallback` lint detects situations where the type of an unsuffixed
5073+
/// float literal falls back to `f32` instead of `f64` to avoid a compilation error. This occurs
5074+
/// when there is a trait bound `f32: From<T>` (or equivalent, such as `T: Into<f32>`) and the
5075+
/// literal is inferred to have the same type as `T`.
5076+
///
5077+
/// ### Example
5078+
///
5079+
/// ```rust
5080+
/// fn foo(x: impl Into<f32>) -> f32 {
5081+
/// x.into()
5082+
/// }
5083+
///
5084+
/// fn main() {
5085+
/// dbg!(foo(2.5));
5086+
/// }
5087+
/// ```
5088+
///
5089+
/// {{produces}}
5090+
///
5091+
/// ### Explanation
5092+
///
5093+
/// Rust allows traits that are only implemented for a single floating point type to guide type
5094+
/// inferrence for floating point literals. This used to apply in the case of `f32: From<T>`
5095+
/// (where `T` was inferred to be the same type as a floating point literal), as the only
5096+
/// floating point type impl was `f32: From<f32>`. However, as Rust is in the process of adding
5097+
/// support for `f16`, there are now two implementations for floating point types:
5098+
/// `f32: From<f16>` and `f32: From<f32>`. This means that the trait bound `f32: From<T>` can no
5099+
/// longer guide inferrence for the type of the floating point literal. The default fallback for
5100+
/// unsuffixed floating point literals is `f64`. As `f32` does not implement `From<f64>`,
5101+
/// falling back to `f64` would cause a compilation error; therefore, the float type fallback
5102+
/// has been tempoarily adjusted to fallback to `f32` in this scenario.
5103+
///
5104+
/// The lint will automatically provide a machine-applicable suggestion to add a `_f32` suffix
5105+
/// to the literal, which will fix the problem.
5106+
///
5107+
/// This is a [future-incompatible] lint to transition this to a hard error in the future. See
5108+
/// [issue #FIXME] for more details.
5109+
///
5110+
/// [issue #FIXME]: https://github.com/rust-lang/rust/issues/FIXME
5111+
pub FLOAT_LITERAL_F32_FALLBACK,
5112+
Warn,
5113+
"detects unsuffixed floating point literals whose type fallback to `f32`",
5114+
@future_incompatible = FutureIncompatibleInfo {
5115+
reason: FutureIncompatibilityReason::FutureReleaseErrorDontReportInDeps,
5116+
reference: "issue #FIXME <https://github.com/rust-lang/rust/issues/FIXME>",
5117+
};
5118+
}

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

Lines changed: 21 additions & 0 deletions
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

Lines changed: 14 additions & 0 deletions
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

Lines changed: 39 additions & 0 deletions
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+

tests/ui/inference/untyped-primitives.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@
55

66
fn main() {
77
let x = f32::from(3.14);
8+
//~^ WARN falling back to `f32`
9+
//~| WARN this was previously accepted
810
let y = f64::from(3.14);
911
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
warning: falling back to `f32` as the trait bound `f32: From<f64>` is not satisfied
2+
--> $DIR/untyped-primitives.rs:7:23
3+
|
4+
LL | let x = f32::from(3.14);
5+
| ^^^^ help: explicitly specify the type as `f32`: `3.14_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: 1 warning emitted
12+

0 commit comments

Comments
 (0)