Skip to content

Commit b2fa77a

Browse files
committed
Add FCW for unsuffixed float literal f32 fallback
1 parent f5e00ec commit b2fa77a

File tree

16 files changed

+269
-15
lines changed

16 files changed

+269
-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
@@ -1035,3 +1035,11 @@ pub(crate) struct NakedFunctionsMustNakedAsm {
10351035
#[label]
10361036
pub span: Span,
10371037
}
1038+
1039+
#[derive(LintDiagnostic)]
1040+
#[diag(hir_typeck_float_literal_f32_fallback)]
1041+
pub(crate) struct FloatLiteralF32Fallback {
1042+
pub literal: String,
1043+
#[suggestion(code = "{literal}_f32", applicability = "machine-applicable")]
1044+
pub span: Option<Span>,
1045+
}

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::{self, FloatVid, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable};
1515
use rustc_session::lint;
1616
use rustc_span::def_id::LocalDefId;
@@ -192,6 +192,20 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
192192
.iter()
193193
.flat_map(|ty| ty.float_vid())
194194
.filter(|vid| roots.contains(&self.root_float_var(*vid)))
195+
.inspect(|vid| {
196+
let span = self.float_var_origin(*vid);
197+
// Show the entire literal in the suggestion to make it clearer.
198+
let literal = self.tcx.sess.source_map().span_to_snippet(span).ok();
199+
self.tcx.emit_node_span_lint(
200+
FLOAT_LITERAL_F32_FALLBACK,
201+
CRATE_HIR_ID,
202+
span,
203+
errors::FloatLiteralF32Fallback {
204+
span: literal.as_ref().map(|_| span),
205+
literal: literal.unwrap_or_default(),
206+
},
207+
);
208+
})
195209
.collect();
196210
debug!("calculate_fallback_to_f32: fallback_to_f32={:?}", fallback_to_f32);
197211
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
@@ -1674,7 +1674,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
16741674
ty::Float(_) => Some(ty),
16751675
_ => None,
16761676
});
1677-
opt_ty.unwrap_or_else(|| self.next_float_var())
1677+
opt_ty.unwrap_or_else(|| self.next_float_var(lit.span))
16781678
}
16791679
ast::LitKind::Bool(_) => tcx.types.bool,
16801680
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.
@@ -797,9 +810,11 @@ impl<'tcx> InferCtxt<'tcx> {
797810
Ty::new_int_var(self.tcx, next_int_var_id)
798811
}
799812

800-
pub fn next_float_var(&self) -> Ty<'tcx> {
801-
let next_float_var_id =
802-
self.inner.borrow_mut().float_unification_table().new_key(ty::FloatVarValue::Unknown);
813+
pub fn next_float_var(&self, span: Span) -> Ty<'tcx> {
814+
let mut inner = self.inner.borrow_mut();
815+
let next_float_var_id = inner.float_unification_table().new_key(ty::FloatVarValue::Unknown);
816+
let span_index = inner.float_origin_span_storage.push(span);
817+
debug_assert_eq!(next_float_var_id, span_index);
803818
Ty::new_float_var(self.tcx, next_float_var_id)
804819
}
805820

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
@@ -5111,3 +5111,52 @@ declare_lint! {
51115111
report_in_deps: true,
51125112
};
51135113
}
5114+
5115+
declare_lint! {
5116+
/// The `float_literal_f32_fallback` lint detects situations where the type of an unsuffixed
5117+
/// float literal falls back to `f32` instead of `f64` to avoid a compilation error. This occurs
5118+
/// when there is a trait bound `f32: From<T>` (or equivalent, such as `T: Into<f32>`) and the
5119+
/// literal is inferred to have the same type as `T`.
5120+
///
5121+
/// ### Example
5122+
///
5123+
/// ```rust
5124+
/// fn foo(x: impl Into<f32>) -> f32 {
5125+
/// x.into()
5126+
/// }
5127+
///
5128+
/// fn main() {
5129+
/// dbg!(foo(2.5));
5130+
/// }
5131+
/// ```
5132+
///
5133+
/// {{produces}}
5134+
///
5135+
/// ### Explanation
5136+
///
5137+
/// Rust allows traits that are only implemented for a single floating point type to guide type
5138+
/// inferrence for floating point literals. This used to apply in the case of `f32: From<T>`
5139+
/// (where `T` was inferred to be the same type as a floating point literal), as the only
5140+
/// floating point type impl was `f32: From<f32>`. However, as Rust is in the process of adding
5141+
/// support for `f16`, there are now two implementations for floating point types:
5142+
/// `f32: From<f16>` and `f32: From<f32>`. This means that the trait bound `f32: From<T>` can no
5143+
/// longer guide inferrence for the type of the floating point literal. The default fallback for
5144+
/// unsuffixed floating point literals is `f64`. As `f32` does not implement `From<f64>`,
5145+
/// falling back to `f64` would cause a compilation error; therefore, the float type fallback
5146+
/// has been tempoarily adjusted to fallback to `f32` in this scenario.
5147+
///
5148+
/// The lint will automatically provide a machine-applicable suggestion to add a `_f32` suffix
5149+
/// to the literal, which will fix the problem.
5150+
///
5151+
/// This is a [future-incompatible] lint to transition this to a hard error in the future. See
5152+
/// [issue #FIXME] for more details.
5153+
///
5154+
/// [issue #FIXME]: https://github.com/rust-lang/rust/issues/FIXME
5155+
pub FLOAT_LITERAL_F32_FALLBACK,
5156+
Warn,
5157+
"detects unsuffixed floating point literals whose type fallback to `f32`",
5158+
@future_incompatible = FutureIncompatibleInfo {
5159+
reason: FutureIncompatibilityReason::FutureReleaseError,
5160+
reference: "issue #FIXME <https://github.com/rust-lang/rust/issues/FIXME>",
5161+
};
5162+
}
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+
}
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:9: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:12: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:15: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:19: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+
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+
}
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:9: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:12: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:15: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:19: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)