diff options
Diffstat (limited to 'compiler')
| -rw-r--r-- | compiler/rustc_hir_typeck/messages.ftl | 3 | ||||
| -rw-r--r-- | compiler/rustc_hir_typeck/src/errors.rs | 5 | ||||
| -rw-r--r-- | compiler/rustc_hir_typeck/src/fallback.rs | 50 | ||||
| -rw-r--r-- | compiler/rustc_lint_defs/src/builtin.rs | 54 |
4 files changed, 112 insertions, 0 deletions
diff --git a/compiler/rustc_hir_typeck/messages.ftl b/compiler/rustc_hir_typeck/messages.ftl index 6f499947d5c..3ab319a037b 100644 --- a/compiler/rustc_hir_typeck/messages.ftl +++ b/compiler/rustc_hir_typeck/messages.ftl @@ -44,6 +44,9 @@ hir_typeck_convert_using_method = try using `{$sugg}` to convert `{$found}` to ` hir_typeck_ctor_is_private = tuple struct constructor `{$def}` is private +hir_typeck_dependency_on_unit_never_type_fallback = this function depends on never type fallback being `()` + .help = specify the types explicitly + hir_typeck_deref_is_empty = this expression `Deref`s to `{$deref_ty}` which implements `is_empty` hir_typeck_expected_default_return_type = expected `()` because of default return type diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs index 3f12f252654..6dd34024fd1 100644 --- a/compiler/rustc_hir_typeck/src/errors.rs +++ b/compiler/rustc_hir_typeck/src/errors.rs @@ -183,6 +183,11 @@ pub enum NeverTypeFallbackFlowingIntoUnsafe { Deref, } +#[derive(LintDiagnostic)] +#[help] +#[diag(hir_typeck_dependency_on_unit_never_type_fallback)] +pub struct DependencyOnUnitNeverTypeFallback {} + #[derive(Subdiagnostic)] #[multipart_suggestion( hir_typeck_add_missing_parentheses_in_range, diff --git a/compiler/rustc_hir_typeck/src/fallback.rs b/compiler/rustc_hir_typeck/src/fallback.rs index 5ba67c52ef2..b7937f458b3 100644 --- a/compiler/rustc_hir_typeck/src/fallback.rs +++ b/compiler/rustc_hir_typeck/src/fallback.rs @@ -14,6 +14,7 @@ use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable}; use rustc_session::lint; use rustc_span::DUMMY_SP; use rustc_span::{def_id::LocalDefId, Span}; +use rustc_trait_selection::traits::{ObligationCause, ObligationCtxt}; #[derive(Copy, Clone)] pub enum DivergingFallbackBehavior { @@ -344,6 +345,9 @@ impl<'tcx> FnCtxt<'_, 'tcx> { // `!`. let mut diverging_fallback = UnordMap::with_capacity(diverging_vids.len()); let unsafe_infer_vars = OnceCell::new(); + + self.lint_obligations_broken_by_never_type_fallback_change(behavior, &diverging_vids); + for &diverging_vid in &diverging_vids { let diverging_ty = Ty::new_var(self.tcx, diverging_vid); let root_vid = self.root_var(diverging_vid); @@ -468,6 +472,52 @@ impl<'tcx> FnCtxt<'_, 'tcx> { } } + fn lint_obligations_broken_by_never_type_fallback_change( + &self, + behavior: DivergingFallbackBehavior, + diverging_vids: &[ty::TyVid], + ) { + let DivergingFallbackBehavior::ToUnit = behavior else { return }; + + // Fallback happens if and only if there are diverging variables + if diverging_vids.is_empty() { + return; + } + + // Returns errors which happen if fallback is set to `fallback` + let remaining_errors_if_fallback_to = |fallback| { + self.probe(|_| { + let obligations = self.fulfillment_cx.borrow().pending_obligations(); + let ocx = ObligationCtxt::new(&self.infcx); + ocx.register_obligations(obligations.iter().cloned()); + + for &diverging_vid in diverging_vids { + let diverging_ty = Ty::new_var(self.tcx, diverging_vid); + + ocx.eq(&ObligationCause::dummy(), self.param_env, diverging_ty, fallback) + .expect("expected diverging var to be unconstrained"); + } + + ocx.select_where_possible() + }) + }; + + // If we have no errors with `fallback = ()`, but *do* have errors with `fallback = !`, + // then this code will be broken by the never type fallback change.qba + let unit_errors = remaining_errors_if_fallback_to(self.tcx.types.unit); + if unit_errors.is_empty() + && let never_errors = remaining_errors_if_fallback_to(self.tcx.types.never) + && !never_errors.is_empty() + { + self.tcx.emit_node_span_lint( + lint::builtin::DEPENDENCY_ON_UNIT_NEVER_TYPE_FALLBACK, + self.tcx.local_def_id_to_hir_id(self.body_id), + self.tcx.def_span(self.body_id), + errors::DependencyOnUnitNeverTypeFallback {}, + ) + } + } + /// Returns a graph whose nodes are (unresolved) inference variables and where /// an edge `?A -> ?B` indicates that the variable `?A` is coerced to `?B`. fn create_coercion_graph(&self) -> VecGraph<ty::TyVid, true> { diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 45051394ffc..a12c76037e7 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -34,6 +34,7 @@ declare_lint_pass! { CONST_EVALUATABLE_UNCHECKED, CONST_ITEM_MUTATION, DEAD_CODE, + DEPENDENCY_ON_UNIT_NEVER_TYPE_FALLBACK, DEPRECATED, DEPRECATED_CFG_ATTR_CRATE_TYPE_NAME, DEPRECATED_IN_FUTURE, @@ -4200,6 +4201,59 @@ declare_lint! { } declare_lint! { + /// The `dependency_on_unit_never_type_fallback` lint detects cases where code compiles with + /// [never type fallback] being [`()`], but will stop compiling with fallback being [`!`]. + /// + /// [never type fallback]: https://doc.rust-lang.org/nightly/core/primitive.never.html#never-type-fallback + /// [`!`]: https://doc.rust-lang.org/core/primitive.never.html + /// [`()`]: https://doc.rust-lang.org/core/primitive.unit.html + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(dependency_on_unit_never_type_fallback)] + /// fn main() { + /// if true { + /// // return has type `!` which, is some cases, causes never type fallback + /// return + /// } else { + /// // the type produced by this call is not specified explicitly, + /// // so it will be inferred from the previous branch + /// Default::default() + /// }; + /// // depending on the fallback, this may compile (because `()` implements `Default`), + /// // or it may not (because `!` does not implement `Default`) + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Due to historic reasons never type fallback was `()`, meaning that `!` got spontaneously + /// coerced to `()`. There are plans to change that, but they may make the code such as above + /// not compile. Instead of depending on the fallback, you should specify the type explicitly: + /// ``` + /// if true { + /// return + /// } else { + /// // type is explicitly specified, fallback can't hurt us no more + /// <() as Default>::default() + /// }; + /// ``` + /// + /// See [Tracking Issue for making `!` fall back to `!`](https://github.com/rust-lang/rust/issues/123748). + pub DEPENDENCY_ON_UNIT_NEVER_TYPE_FALLBACK, + Warn, + "never type fallback affecting unsafe function calls", + @future_incompatible = FutureIncompatibleInfo { + reason: FutureIncompatibilityReason::FutureReleaseErrorDontReportInDeps, + reference: "issue #123748 <https://github.com/rust-lang/rust/issues/123748>", + }; + report_in_external_macro +} + +declare_lint! { /// The `byte_slice_in_packed_struct_with_derive` lint detects cases where a byte slice field /// (`[u8]`) or string slice field (`str`) is used in a `packed` struct that derives one or /// more built-in traits. |
