diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index c144f0b7dbc5b..75c708e33929d 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -443,6 +443,9 @@ language_item_table! { FieldBase, sym::field_base, field_base, Target::AssocTy, GenericRequirement::Exact(0); FieldType, sym::field_type, field_type, Target::AssocTy, GenericRequirement::Exact(0); FieldOffset, sym::field_offset, field_offset, Target::AssocConst, GenericRequirement::Exact(0); + + // Used to fallback `{float}` to `f32` when `f32: From<{float}>` + From, sym::From, from_trait, Target::Trait, GenericRequirement::Exact(1); } /// The requirement imposed on the generics of a lang item diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index 0d49e06240532..29fc6729e4b3e 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -346,7 +346,7 @@ impl<'a, 'tcx> FnCtxt<'a, '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, None), ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_) => { bug!("unexpected fresh ty outside of the trait solver") } diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs index 6eef156846972..b55e7933cc1e9 100644 --- a/compiler/rustc_hir_typeck/src/errors.rs +++ b/compiler/rustc_hir_typeck/src/errors.rs @@ -1384,3 +1384,15 @@ pub(crate) struct ProjectOnNonPinProjectType { )] pub sugg_span: Option, } + +#[derive(Diagnostic)] +#[diag("falling back to `f32` as the trait bound `f32: From` 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, +} diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index e21cadcf3ffe6..084079561a6d3 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -349,7 +349,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let tcx = self.tcx; match expr.kind { - ExprKind::Lit(ref lit) => self.check_expr_lit(lit, expected), + ExprKind::Lit(ref lit) => self.check_expr_lit(lit, expr.hir_id, expected), ExprKind::Binary(op, lhs, rhs) => self.check_expr_binop(expr, op, lhs, rhs, expected), ExprKind::Assign(lhs, rhs, span) => { self.check_expr_assign(expr, expected, lhs, rhs, span) diff --git a/compiler/rustc_hir_typeck/src/fallback.rs b/compiler/rustc_hir_typeck/src/fallback.rs index 5aadf37720d0c..84b09d4ab477b 100644 --- a/compiler/rustc_hir_typeck/src/fallback.rs +++ b/compiler/rustc_hir_typeck/src/fallback.rs @@ -5,13 +5,13 @@ use rustc_data_structures::fx::FxHashSet; 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_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable}; +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; use rustc_span::{DUMMY_SP, Span}; @@ -55,6 +55,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { let (diverging_fallback, diverging_fallback_ty) = self.calculate_diverging_fallback(&unresolved_variables); + let fallback_to_f32 = self.calculate_fallback_to_f32(&unresolved_variables); // We do fallback in two passes, to try to generate // better error messages. @@ -62,8 +63,12 @@ impl<'tcx> FnCtxt<'_, 'tcx> { let mut fallback_occurred = false; for ty in unresolved_variables { debug!("unsolved_variable = {:?}", ty); - fallback_occurred |= - self.fallback_if_possible(ty, &diverging_fallback, diverging_fallback_ty); + fallback_occurred |= self.fallback_if_possible( + ty, + &diverging_fallback, + diverging_fallback_ty, + &fallback_to_f32, + ); } fallback_occurred @@ -73,7 +78,8 @@ impl<'tcx> FnCtxt<'_, 'tcx> { /// /// - Unconstrained ints are replaced with `i32`. /// - /// - Unconstrained floats are replaced with `f64`. + /// - Unconstrained floats are replaced with `f64`, except when there is a trait predicate + /// `f32: From<{float}>`, in which case `f32` is used as the fallback instead. /// /// - Non-numerics may get replaced with `()` or `!`, depending on how they /// were categorized by [`Self::calculate_diverging_fallback`], crate's @@ -89,6 +95,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { ty: Ty<'tcx>, diverging_fallback: &UnordSet>, diverging_fallback_ty: Ty<'tcx>, + fallback_to_f32: &UnordSet, ) -> bool { // Careful: we do NOT shallow-resolve `ty`. We know that `ty` // is an unsolved variable, and we determine its fallback @@ -111,6 +118,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { let fallback = match ty.kind() { _ if let Some(e) = self.tainted_by_errors() => Ty::new_error(self.tcx, e), ty::Infer(ty::IntVar(_)) => self.tcx.types.i32, + ty::Infer(ty::FloatVar(vid)) if fallback_to_f32.contains(vid) => self.tcx.types.f32, ty::Infer(ty::FloatVar(_)) => self.tcx.types.f64, _ if diverging_fallback.contains(&ty) => { self.diverging_fallback_has_occurred.set(true); @@ -125,6 +133,52 @@ impl<'tcx> FnCtxt<'_, 'tcx> { true } + /// Existing code relies on `f32: From` (usually written as `T: Into`) resolving `T` to + /// `f32` when the type of `T` is inferred from an unsuffixed float literal. Using the default + /// fallback of `f64`, this would break when adding `impl From for f32`, as there are now + /// two float type which could be `T`, meaning that the fallback of `f64` would be used and + /// compilation error would occur as `f32` does not implement `From`. To avoid breaking + /// existing code, we instead fallback `T` to `f32` when there is a trait predicate + /// `f32: From`. This means code like the following will continue to compile: + /// + /// ```rust + /// fn foo>(_: T) {} + /// + /// foo(1.0); + /// ``` + fn calculate_fallback_to_f32(&self, unresolved_variables: &[Ty<'tcx>]) -> UnordSet { + let roots: UnordSet = self.from_float_for_f32_root_vids(); + if roots.is_empty() { + // Most functions have no `f32: From<{float}>` predicates, so short-circuit and return + // an empty set when this is the case. + return UnordSet::new(); + } + // Calculate all the unresolved variables that need to fallback to `f32` here. This ensures + // we don't need to find root variables in `fallback_if_possible`: see the comment at the + // top of that function for details. + let fallback_to_f32 = unresolved_variables + .iter() + .flat_map(|ty| ty.float_vid()) + .filter(|vid| roots.contains(&self.root_float_var(*vid))) + .inspect(|vid| { + let origin = 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(origin.span).ok(); + self.tcx.emit_node_span_lint( + FLOAT_LITERAL_F32_FALLBACK, + origin.lint_id.unwrap_or(CRATE_HIR_ID), + origin.span, + errors::FloatLiteralF32Fallback { + span: literal.as_ref().map(|_| origin.span), + literal: literal.unwrap_or_default(), + }, + ); + }) + .collect(); + debug!("calculate_fallback_to_f32: fallback_to_f32={:?}", fallback_to_f32); + fallback_to_f32 + } + fn calculate_diverging_fallback( &self, unresolved_variables: &[Ty<'tcx>], @@ -362,6 +416,11 @@ impl<'tcx> FnCtxt<'_, 'tcx> { Some(self.root_var(self.shallow_resolve(ty).ty_vid()?)) } + /// If `ty` is an unresolved float type variable, returns its root vid. + pub(crate) fn root_float_vid(&self, ty: Ty<'tcx>) -> Option { + Some(self.root_float_var(self.shallow_resolve(ty).float_vid()?)) + } + /// Given a set of diverging vids and coercions, walk the HIR to gather a /// set of suggestions which can be applied to preserve fallback to unit. fn try_to_suggest_annotations( diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index bb31bcbf70f1b..e37cab6671584 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -718,6 +718,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pub(in super::super) fn check_expr_lit( &self, lit: &hir::Lit, + lint_id: HirId, expected: Expectation<'tcx>, ) -> Ty<'tcx> { let tcx = self.tcx; @@ -765,7 +766,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { 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, Some(lint_id))) } ast::LitKind::Bool(_) => tcx.types.bool, ast::LitKind::CStr(_, _) => Ty::new_imm_ref( diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs index 1ab7ac4c2e361..652f0498b5841 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs @@ -1,5 +1,7 @@ //! A utility module to inspect currently ambiguous obligations in the current context. +use rustc_data_structures::unord::UnordSet; +use rustc_hir::def_id::DefId; use rustc_infer::traits::{self, ObligationCause, PredicateObligations}; use rustc_middle::ty::{self, Ty, TypeVisitableExt}; use rustc_span::Span; @@ -96,6 +98,69 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }); obligations_for_self_ty } + + /// Only needed for the `From<{float}>` for `f32` type fallback. + #[instrument(skip(self), level = "debug")] + pub(crate) fn from_float_for_f32_root_vids(&self) -> UnordSet { + if self.next_trait_solver() { + self.from_float_for_f32_root_vids_next() + } else { + let Some(from_trait) = self.tcx.lang_items().from_trait() else { + return UnordSet::new(); + }; + self.fulfillment_cx + .borrow_mut() + .pending_obligations() + .into_iter() + .filter_map(|obligation| { + self.predicate_from_float_for_f32_root_vid(from_trait, obligation.predicate) + }) + .collect() + } + } + + fn predicate_from_float_for_f32_root_vid( + &self, + from_trait: DefId, + predicate: ty::Predicate<'tcx>, + ) -> Option { + // The predicates we are looking for look like + // `TraitPredicate(>, polarity:Positive)`. + // They will have no bound variables. + match predicate.kind().no_bound_vars() { + Some(ty::PredicateKind::Clause(ty::ClauseKind::Trait(ty::TraitPredicate { + polarity: ty::PredicatePolarity::Positive, + trait_ref, + }))) if trait_ref.def_id == from_trait + && self.shallow_resolve(trait_ref.self_ty()).kind() + == &ty::Float(ty::FloatTy::F32) => + { + self.root_float_vid(trait_ref.args.type_at(1)) + } + _ => None, + } + } + + fn from_float_for_f32_root_vids_next(&self) -> UnordSet { + let Some(from_trait) = self.tcx.lang_items().from_trait() else { + return UnordSet::new(); + }; + let obligations = self.fulfillment_cx.borrow().pending_obligations(); + debug!(?obligations); + let mut vids = UnordSet::new(); + for obligation in obligations { + let mut visitor = FindFromFloatForF32RootVids { + fcx: self, + from_trait, + vids: &mut vids, + span: obligation.cause.span, + }; + + let goal = obligation.as_goal(); + self.visit_proof_tree(goal, &mut visitor); + } + vids + } } struct NestedObligationsForSelfTy<'a, 'tcx> { @@ -105,7 +170,7 @@ struct NestedObligationsForSelfTy<'a, 'tcx> { obligations_for_self_ty: &'a mut PredicateObligations<'tcx>, } -impl<'a, 'tcx> ProofTreeVisitor<'tcx> for NestedObligationsForSelfTy<'a, 'tcx> { +impl<'tcx> ProofTreeVisitor<'tcx> for NestedObligationsForSelfTy<'_, 'tcx> { fn span(&self) -> Span { self.root_cause.span } @@ -144,3 +209,37 @@ impl<'a, 'tcx> ProofTreeVisitor<'tcx> for NestedObligationsForSelfTy<'a, 'tcx> { } } } + +struct FindFromFloatForF32RootVids<'a, 'tcx> { + fcx: &'a FnCtxt<'a, 'tcx>, + from_trait: DefId, + vids: &'a mut UnordSet, + span: Span, +} + +impl<'tcx> ProofTreeVisitor<'tcx> for FindFromFloatForF32RootVids<'_, 'tcx> { + fn span(&self) -> 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 occurrences in practice (a.k.a. `Into`). + InspectConfig { max_depth: 3 } + } + + fn visit_goal(&mut self, inspect_goal: &InspectGoal<'_, 'tcx>) { + if let Some(vid) = self + .fcx + .predicate_from_float_for_f32_root_vid(self.from_trait, inspect_goal.goal().predicate) + { + self.vids.insert(vid); + } else if let Some(candidate) = inspect_goal.unique_applicable_candidate() { + let start_len = self.vids.len(); + let _ = candidate.goal().infcx().commit_if_ok(|_| { + candidate.visit_nested_no_probe(self); + if self.vids.len() > start_len { Ok(()) } else { Err(()) } + }); + } + } +} diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs index 26f7d1ccffc93..198c177e6d447 100644 --- a/compiler/rustc_hir_typeck/src/pat.rs +++ b/compiler/rustc_hir_typeck/src/pat.rs @@ -917,7 +917,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn check_pat_expr_unadjusted(&self, lt: &'tcx hir::PatExpr<'tcx>) -> Ty<'tcx> { let ty = match <.kind { rustc_hir::PatExprKind::Lit { lit, negated } => { - let ty = self.check_expr_lit(lit, Expectation::NoExpectation); + let ty = self.check_expr_lit(lit, lt.hir_id, Expectation::NoExpectation); if *negated { self.register_bound( ty, diff --git a/compiler/rustc_infer/src/infer/canonical/mod.rs b/compiler/rustc_infer/src/infer/canonical/mod.rs index c6826d774216f..55c78036c6d17 100644 --- a/compiler/rustc_infer/src/infer/canonical/mod.rs +++ b/compiler/rustc_infer/src/infer/canonical/mod.rs @@ -107,7 +107,10 @@ impl<'tcx> InferCtxt<'tcx> { CanonicalVarKind::Int => self.next_int_var().into(), - CanonicalVarKind::Float => self.next_float_var().into(), + CanonicalVarKind::Float => { + // There is no HirId available to pass as a lint_id. + self.next_float_var(span, None).into() + } CanonicalVarKind::PlaceholderTy(ty::PlaceholderType { universe, bound, .. }) => { let universe_mapped = universe_map(universe); diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index a38d4e819e298..d27a0a77f4302 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -16,8 +16,9 @@ use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_data_structures::undo_log::{Rollback, UndoLogs}; use rustc_data_structures::unify as ut; use rustc_errors::{DiagCtxtHandle, ErrorGuaranteed}; -use rustc_hir as hir; use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::{self as hir, HirId}; +use rustc_index::IndexVec; use rustc_macros::extension; pub use rustc_macros::{TypeFoldable, TypeVisitable}; use rustc_middle::bug; @@ -38,6 +39,7 @@ use tracing::{debug, instrument}; use type_variable::TypeVariableOrigin; use crate::infer::snapshot::undo_log::UndoLog; +use crate::infer::type_variable::FloatVariableOrigin; use crate::infer::unify_key::{ConstVariableOrigin, ConstVariableValue, ConstVidKey}; use crate::traits::{ self, ObligationCause, ObligationInspector, PredicateObligation, PredicateObligations, @@ -108,6 +110,11 @@ pub struct InferCtxtInner<'tcx> { /// Map from floating variable to the kind of float it represents. float_unification_storage: ut::UnificationTableStorage, + /// Map from floating variable to the origin span it came from, and the HirId that should be + /// used to lint at that location. This is only used for the FCW for the fallback to `f32`, + /// so can be removed once the `f32` fallback is removed. + float_origin_origin_storage: IndexVec, + /// Tracks the set of region variables and the constraints between them. /// /// This is initially `Some(_)` but when @@ -161,6 +168,7 @@ impl<'tcx> InferCtxtInner<'tcx> { const_unification_storage: Default::default(), int_unification_storage: Default::default(), float_unification_storage: Default::default(), + float_origin_origin_storage: Default::default(), region_constraint_storage: Some(Default::default()), region_obligations: Default::default(), region_assumptions: Default::default(), @@ -644,6 +652,13 @@ impl<'tcx> InferCtxt<'tcx> { 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) -> FloatVariableOrigin { + self.inner.borrow_mut().float_origin_origin_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 +836,12 @@ impl<'tcx> InferCtxt<'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, lint_id: Option) -> Ty<'tcx> { + let mut inner = self.inner.borrow_mut(); + let next_float_var_id = inner.float_unification_table().new_key(ty::FloatVarValue::Unknown); + let origin = FloatVariableOrigin { span, lint_id }; + let span_index = inner.float_origin_origin_storage.push(origin); + debug_assert_eq!(next_float_var_id, span_index); Ty::new_float_var(self.tcx, next_float_var_id) } @@ -1166,6 +1184,10 @@ impl<'tcx> InferCtxt<'tcx> { self.inner.borrow_mut().type_variables().sub_unification_table_root_var(var) } + pub fn root_float_var(&self, var: ty::FloatVid) -> ty::FloatVid { + self.inner.borrow_mut().float_unification_table().find(var) + } + pub fn root_const_var(&self, var: ty::ConstVid) -> ty::ConstVid { self.inner.borrow_mut().const_unification_table().find(var).vid } diff --git a/compiler/rustc_infer/src/infer/snapshot/fudge.rs b/compiler/rustc_infer/src/infer/snapshot/fudge.rs index 6709c822dc7b1..2ce98b7541afa 100644 --- a/compiler/rustc_infer/src/infer/snapshot/fudge.rs +++ b/compiler/rustc_infer/src/infer/snapshot/fudge.rs @@ -10,9 +10,11 @@ use tracing::instrument; use ut::UnifyKey; use super::VariableLengths; -use crate::infer::type_variable::TypeVariableOrigin; +use crate::infer::type_variable::{FloatVariableOrigin, 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 +27,14 @@ where 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, Vec) { + let range = vars_since_snapshot(&inner.float_unification_table(), snapshot_var_len); + (range.clone(), range.map(|index| inner.float_origin_origin_storage[index]).collect()) +} + fn const_vars_since_snapshot<'tcx>( table: &mut UnificationTable<'_, 'tcx, ConstVidKey<'tcx>>, snapshot_var_len: usize, @@ -130,7 +140,7 @@ struct SnapshotVarData<'tcx> { region_vars: (Range, Vec>), type_vars: (Range, Vec), int_vars: Range, - float_vars: Range, + float_vars: (Range, Vec), const_vars: (Range, Vec), } @@ -143,8 +153,7 @@ impl<'tcx> SnapshotVarData<'tcx> { 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 +167,7 @@ impl<'tcx> SnapshotVarData<'tcx> { 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 +212,11 @@ impl<'a, 'tcx> TypeFolder> for InferenceFudger<'a, '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 FloatVariableOrigin { span, lint_id } = + self.snapshot_vars.float_vars.1[idx]; + self.infcx.next_float_var(span, lint_id) } else { ty } diff --git a/compiler/rustc_infer/src/infer/type_variable.rs b/compiler/rustc_infer/src/infer/type_variable.rs index 65f77fe8e25f3..4c56bf0923c39 100644 --- a/compiler/rustc_infer/src/infer/type_variable.rs +++ b/compiler/rustc_infer/src/infer/type_variable.rs @@ -4,6 +4,7 @@ use std::ops::Range; use rustc_data_structures::undo_log::Rollback; use rustc_data_structures::{snapshot_vec as sv, unify as ut}; +use rustc_hir::HirId; use rustc_hir::def_id::DefId; use rustc_index::IndexVec; use rustc_middle::bug; @@ -99,6 +100,16 @@ pub struct TypeVariableOrigin { pub param_def_id: Option, } +#[derive(Copy, Clone, Debug)] +pub struct FloatVariableOrigin { + pub span: Span, + + /// `HirId` to lint at for this float variable, if any. + /// + /// This should only be used for diagnostics. + pub lint_id: Option, +} + #[derive(Clone)] pub(crate) struct TypeVariableData { origin: TypeVariableOrigin, diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 8af8f40d69f56..3462ab2ee7275 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -47,6 +47,7 @@ declare_lint_pass! { EXPLICIT_BUILTIN_CFGS_IN_FLAGS, EXPORTED_PRIVATE_DEPENDENCIES, FFI_UNWIND_CALLS, + FLOAT_LITERAL_F32_FALLBACK, FORBIDDEN_LINT_GROUPS, FUNCTION_ITEM_REFERENCES, FUZZY_PROVENANCE_CASTS, @@ -5643,3 +5644,53 @@ declare_lint! { "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` (or equivalent, such as `T: Into`) and the + /// literal is inferred to have the same type as `T`. + /// + /// ### Example + /// + /// ```rust + /// fn foo(x: impl Into) -> 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 + /// inference for floating point literals. This used to apply in the case of `f32: From` + /// (where `T` was inferred to be the same type as a floating point literal), as the only + /// floating point type impl was `f32: From`. However, as Rust is in the process of adding + /// support for `f16`, there are now two implementations for floating point types: + /// `f32: From` and `f32: From`. This means that the trait bound `f32: From` 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`, + /// 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 #154024] for more details. + /// + /// [issue #154024]: https://github.com/rust-lang/rust/issues/154024 + /// [future-incompatible]: ../index.md#future-incompatible-lints + pub FLOAT_LITERAL_F32_FALLBACK, + Warn, + "detects unsuffixed floating point literals whose type fallback to `f32`", + @future_incompatible = FutureIncompatibleInfo { + reason: fcw!(FutureReleaseError #154024), + report_in_deps: false, + }; +} diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs index 9164f7b57e648..c781d129a160b 100644 --- a/compiler/rustc_middle/src/ty/sty.rs +++ b/compiler/rustc_middle/src/ty/sty.rs @@ -1180,6 +1180,14 @@ impl<'tcx> Ty<'tcx> { } } + #[inline] + pub fn float_vid(self) -> Option { + match self.kind() { + &Infer(FloatVar(vid)) => Some(vid), + _ => None, + } + } + #[inline] pub fn is_ty_or_numeric_infer(self) -> bool { matches!(self.kind(), Infer(_)) diff --git a/library/core/src/convert/mod.rs b/library/core/src/convert/mod.rs index ef4ab15f93c0b..4a4c7ee388f9e 100644 --- a/library/core/src/convert/mod.rs +++ b/library/core/src/convert/mod.rs @@ -577,6 +577,7 @@ pub const trait Into: Sized { /// [`from`]: From::from /// [book]: ../../book/ch09-00-error-handling.html #[rustc_diagnostic_item = "From"] +#[lang = "From"] #[stable(feature = "rust1", since = "1.0.0")] #[rustc_on_unimplemented(on( all(Self = "&str", T = "alloc::string::String"), diff --git a/library/core/src/convert/num.rs b/library/core/src/convert/num.rs index 673245056e79b..a5c9eaf9035a0 100644 --- a/library/core/src/convert/num.rs +++ b/library/core/src/convert/num.rs @@ -172,8 +172,14 @@ impl_from!(u32 => f128, #[stable(feature = "lossless_float_conv", since = "1.6.0 impl_from!(u64 => f128, #[unstable(feature = "f128", issue = "116909")], #[unstable_feature_bound(f128)]); // float -> float -// FIXME(f16,f128): adding additional `From<{float}>` impls to `f32` breaks inference. See -// + +// FIXME(f16): adding the additional `From<{float}>` impl to `f32` would break inference in cases +// like `f32::from(1.0)`. The type checker has a custom workaround to keep that and similar code +// compiling even with the second `From<16> for f32` instance. We keep this instance unstable for +// now so that we can later remove the workaround. +// +// See also . +impl_from!(f16 => f32, #[unstable(feature = "f32_from_f16", issue = "154005")], #[unstable_feature_bound(f32_from_f16)]); impl_from!(f16 => f64, #[stable(feature = "lossless_float_conv", since = "1.6.0")]); impl_from!(f16 => f128, #[stable(feature = "lossless_float_conv", since = "1.6.0")]); impl_from!(f32 => f64, #[stable(feature = "lossless_float_conv", since = "1.6.0")]); diff --git a/tests/ui/feature-gates/feature-gate-f16.e2015.stderr b/tests/ui/feature-gates/feature-gate-f16.e2015.stderr index 5d1ca8f6d047a..b53f12af48fc8 100644 --- a/tests/ui/feature-gates/feature-gate-f16.e2015.stderr +++ b/tests/ui/feature-gates/feature-gate-f16.e2015.stderr @@ -19,7 +19,7 @@ LL | let a: f16 = 100.0; = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: the type `f16` is unstable - --> $DIR/feature-gate-f16.rs:16:11 + --> $DIR/feature-gate-f16.rs:19:11 | LL | fn foo(a: f16) {} | ^^^ @@ -29,7 +29,7 @@ LL | fn foo(a: f16) {} = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: the type `f16` is unstable - --> $DIR/feature-gate-f16.rs:19:8 + --> $DIR/feature-gate-f16.rs:22:8 | LL | a: f16, | ^^^ @@ -58,6 +58,27 @@ LL | let c = 0f16; = help: add `#![feature(f16)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error: aborting due to 6 previous errors +error[E0658]: the type `f16` is unstable + --> $DIR/feature-gate-f16.rs:13:18 + | +LL | let d: f32 = 1.0f16.into(); + | ^^^^^^ + | + = note: see issue #116909 for more information + = help: add `#![feature(f16)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: use of unstable library feature `f32_from_f16` + --> $DIR/feature-gate-f16.rs:13:25 + | +LL | let d: f32 = 1.0f16.into(); + | ^^^^ + | + = help: add `#![feature(f32_from_f16)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + = note: required for `f32` to implement `From` + = note: required for `f16` to implement `Into` + +error: aborting due to 8 previous errors For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/feature-gates/feature-gate-f16.e2018.stderr b/tests/ui/feature-gates/feature-gate-f16.e2018.stderr index 5d1ca8f6d047a..b53f12af48fc8 100644 --- a/tests/ui/feature-gates/feature-gate-f16.e2018.stderr +++ b/tests/ui/feature-gates/feature-gate-f16.e2018.stderr @@ -19,7 +19,7 @@ LL | let a: f16 = 100.0; = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: the type `f16` is unstable - --> $DIR/feature-gate-f16.rs:16:11 + --> $DIR/feature-gate-f16.rs:19:11 | LL | fn foo(a: f16) {} | ^^^ @@ -29,7 +29,7 @@ LL | fn foo(a: f16) {} = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: the type `f16` is unstable - --> $DIR/feature-gate-f16.rs:19:8 + --> $DIR/feature-gate-f16.rs:22:8 | LL | a: f16, | ^^^ @@ -58,6 +58,27 @@ LL | let c = 0f16; = help: add `#![feature(f16)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error: aborting due to 6 previous errors +error[E0658]: the type `f16` is unstable + --> $DIR/feature-gate-f16.rs:13:18 + | +LL | let d: f32 = 1.0f16.into(); + | ^^^^^^ + | + = note: see issue #116909 for more information + = help: add `#![feature(f16)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: use of unstable library feature `f32_from_f16` + --> $DIR/feature-gate-f16.rs:13:25 + | +LL | let d: f32 = 1.0f16.into(); + | ^^^^ + | + = help: add `#![feature(f32_from_f16)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + = note: required for `f32` to implement `From` + = note: required for `f16` to implement `Into` + +error: aborting due to 8 previous errors For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/feature-gates/feature-gate-f16.rs b/tests/ui/feature-gates/feature-gate-f16.rs index f748c603efe92..6babc6c5c03ce 100644 --- a/tests/ui/feature-gates/feature-gate-f16.rs +++ b/tests/ui/feature-gates/feature-gate-f16.rs @@ -10,6 +10,9 @@ pub fn main() { let a: f16 = 100.0; //~ ERROR the type `f16` is unstable let b = 0.0f16; //~ ERROR the type `f16` is unstable let c = 0f16; //~ ERROR the type `f16` is unstable + let d: f32 = 1.0f16.into(); + //~^ ERROR the type `f16` is unstable + //~| ERROR use of unstable library feature `f32_from_f16` foo(1.23); } diff --git a/tests/ui/feature-gates/feature-gate-f32_from_f16.rs b/tests/ui/feature-gates/feature-gate-f32_from_f16.rs new file mode 100644 index 0000000000000..5a7381b85b0f4 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-f32_from_f16.rs @@ -0,0 +1,7 @@ +#![feature(f16)] +#![allow(unused)] + +pub fn main() { + let _: f32 = 1.0f16.into(); + //~^ ERROR use of unstable library feature `f32_from_f16` +} diff --git a/tests/ui/feature-gates/feature-gate-f32_from_f16.stderr b/tests/ui/feature-gates/feature-gate-f32_from_f16.stderr new file mode 100644 index 0000000000000..ce3e8ffb3b0dc --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-f32_from_f16.stderr @@ -0,0 +1,14 @@ +error[E0658]: use of unstable library feature `f32_from_f16` + --> $DIR/feature-gate-f32_from_f16.rs:5:25 + | +LL | let _: f32 = 1.0f16.into(); + | ^^^^ + | + = help: add `#![feature(f32_from_f16)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + = note: required for `f32` to implement `From` + = note: required for `f16` to implement `Into` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/float/f16-into-f32.rs b/tests/ui/float/f16-into-f32.rs new file mode 100644 index 0000000000000..ad428edec7273 --- /dev/null +++ b/tests/ui/float/f16-into-f32.rs @@ -0,0 +1,17 @@ +//@ build-pass +#![feature(f16, f32_from_f16)] +#![allow(unused)] + +// Check that float conversions work, specifically a {float} literal that normally would fall back +// to an f64 but due to the Into bound here falls back to f32. Also test that the lint is emitted in +// the correct location, and can be `expect`ed or `allow`ed. +fn convert(x: impl Into) -> f32 { + x.into() +} + +pub fn main() { + let _ = convert(1.0f32); + let _ = convert(1.0f16); + #[expect(float_literal_f32_fallback)] + let _ = convert(1.0); +} diff --git a/tests/ui/float/f32-into-f32.next-solver.fixed b/tests/ui/float/f32-into-f32.next-solver.fixed new file mode 100644 index 0000000000000..a8d56f4428b91 --- /dev/null +++ b/tests/ui/float/f32-into-f32.next-solver.fixed @@ -0,0 +1,23 @@ +//@ revisions: old-solver next-solver +//@[next-solver] compile-flags: -Znext-solver +//@ run-pass +//@ run-rustfix + +fn foo(_: impl Into) {} + +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); +} diff --git a/tests/ui/float/f32-into-f32.next-solver.stderr b/tests/ui/float/f32-into-f32.next-solver.stderr new file mode 100644 index 0000000000000..fc88f9d2c7f3c --- /dev/null +++ b/tests/ui/float/f32-into-f32.next-solver.stderr @@ -0,0 +1,39 @@ +warning: falling back to `f32` as the trait bound `f32: From` 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 #154024 + = note: `#[warn(float_literal_f32_fallback)]` (part of `#[warn(future_incompatible)]`) on by default + +warning: falling back to `f32` as the trait bound `f32: From` 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 #154024 + +warning: falling back to `f32` as the trait bound `f32: From` 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 #154024 + +warning: falling back to `f32` as the trait bound `f32: From` 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 #154024 + +warning: 4 warnings emitted + diff --git a/tests/ui/float/f32-into-f32.old-solver.fixed b/tests/ui/float/f32-into-f32.old-solver.fixed new file mode 100644 index 0000000000000..a8d56f4428b91 --- /dev/null +++ b/tests/ui/float/f32-into-f32.old-solver.fixed @@ -0,0 +1,23 @@ +//@ revisions: old-solver next-solver +//@[next-solver] compile-flags: -Znext-solver +//@ run-pass +//@ run-rustfix + +fn foo(_: impl Into) {} + +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); +} diff --git a/tests/ui/float/f32-into-f32.old-solver.stderr b/tests/ui/float/f32-into-f32.old-solver.stderr new file mode 100644 index 0000000000000..fc88f9d2c7f3c --- /dev/null +++ b/tests/ui/float/f32-into-f32.old-solver.stderr @@ -0,0 +1,39 @@ +warning: falling back to `f32` as the trait bound `f32: From` 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 #154024 + = note: `#[warn(float_literal_f32_fallback)]` (part of `#[warn(future_incompatible)]`) on by default + +warning: falling back to `f32` as the trait bound `f32: From` 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 #154024 + +warning: falling back to `f32` as the trait bound `f32: From` 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 #154024 + +warning: falling back to `f32` as the trait bound `f32: From` 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 #154024 + +warning: 4 warnings emitted + diff --git a/tests/ui/float/f32-into-f32.rs b/tests/ui/float/f32-into-f32.rs new file mode 100644 index 0000000000000..b55023453b7e0 --- /dev/null +++ b/tests/ui/float/f32-into-f32.rs @@ -0,0 +1,23 @@ +//@ revisions: old-solver next-solver +//@[next-solver] compile-flags: -Znext-solver +//@ run-pass +//@ run-rustfix + +fn foo(_: impl Into) {} + +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); +} diff --git a/tests/ui/float/trait-f16-or-f32.rs b/tests/ui/float/trait-f16-or-f32.rs new file mode 100644 index 0000000000000..72f0a4fbde477 --- /dev/null +++ b/tests/ui/float/trait-f16-or-f32.rs @@ -0,0 +1,13 @@ +//@ check-fail + +#![feature(f16)] + +trait Trait {} +impl Trait for f16 {} +impl Trait for f32 {} + +fn foo(_: impl Trait) {} + +fn main() { + foo(1.0); //~ ERROR the trait bound `f64: Trait` is not satisfied +} diff --git a/tests/ui/float/trait-f16-or-f32.stderr b/tests/ui/float/trait-f16-or-f32.stderr new file mode 100644 index 0000000000000..0117d787bc615 --- /dev/null +++ b/tests/ui/float/trait-f16-or-f32.stderr @@ -0,0 +1,24 @@ +error[E0277]: the trait bound `f64: Trait` is not satisfied + --> $DIR/trait-f16-or-f32.rs:12:9 + | +LL | foo(1.0); + | --- ^^^ the trait `Trait` is not implemented for `f64` + | | + | required by a bound introduced by this call + | +help: the following other types implement trait `Trait` + --> $DIR/trait-f16-or-f32.rs:6:1 + | +LL | impl Trait for f16 {} + | ^^^^^^^^^^^^^^^^^^ `f16` +LL | impl Trait for f32 {} + | ^^^^^^^^^^^^^^^^^^ `f32` +note: required by a bound in `foo` + --> $DIR/trait-f16-or-f32.rs:9:16 + | +LL | fn foo(_: impl Trait) {} + | ^^^^^ required by this bound in `foo` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/inference/untyped-primitives.rs b/tests/ui/inference/untyped-primitives.rs index 8515ca79903fc..d04ccb4ff7bbd 100644 --- a/tests/ui/inference/untyped-primitives.rs +++ b/tests/ui/inference/untyped-primitives.rs @@ -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); } diff --git a/tests/ui/inference/untyped-primitives.stderr b/tests/ui/inference/untyped-primitives.stderr new file mode 100644 index 0000000000000..8b0fb15caa8f9 --- /dev/null +++ b/tests/ui/inference/untyped-primitives.stderr @@ -0,0 +1,12 @@ +warning: falling back to `f32` as the trait bound `f32: From` 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 #154024 + = note: `#[warn(float_literal_f32_fallback)]` (part of `#[warn(future_incompatible)]`) on by default + +warning: 1 warning emitted +