Skip to content

Limited nonzero coercion #143629

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions compiler/rustc_hir/src/lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,8 @@ language_item_table! {
String, sym::String, string, Target::Struct, GenericRequirement::None;
CStr, sym::CStr, c_str, Target::Struct, GenericRequirement::None;

NonZero, sym::NonZero, non_zero, Target::Struct, GenericRequirement::Exact(1);

// Experimental lang items for implementing contract pre- and post-condition checking.
ContractBuildCheckEnsures, sym::contract_build_check_ensures, contract_build_check_ensures_fn, Target::Fn, GenericRequirement::None;
ContractCheckRequires, sym::contract_check_requires, contract_check_requires_fn, Target::Fn, GenericRequirement::None;
Expand Down
16 changes: 16 additions & 0 deletions compiler/rustc_hir_typeck/src/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,9 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
// It cannot convert closures that require unsafe.
self.coerce_closure_to_fn(a, closure_def_id_a, args_a, b)
}
ty::Adt(adt, args) if self.tcx.is_lang_item(adt.did(), hir::LangItem::NonZero) => {
self.coerce_non_zero(a, args.type_at(0), b)
}
_ => {
// Otherwise, just use unification rules.
self.unify(a, b)
Expand Down Expand Up @@ -827,6 +830,19 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
self.unify_and(a, b, [], Adjust::ReborrowPin(mut_b))
}

fn coerce_non_zero(
&self,
non_zero: Ty<'tcx>,
wrapped_ty: Ty<'tcx>,
target_ty: Ty<'tcx>,
) -> CoerceResult<'tcx> {
if target_ty.is_ty_var() {
return self.unify(non_zero, target_ty);
}
self.commit_if_ok(|_| self.unify_and(wrapped_ty, target_ty, [], Adjust::NonZeroIntoInner))
.or_else(|_| self.unify(non_zero, target_ty))
}

fn coerce_from_safe_fn(
&self,
fn_ty_a: ty::PolyFnSig<'tcx>,
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_hir_typeck/src/expr_use_visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,8 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
};
self.delegate.borrow_mut().borrow(&place_with_id, place_with_id.hir_id, bk);
}

adjustment::Adjust::NonZeroIntoInner => {}
}
place_with_id = self.cat_expr_adjusted(expr, place_with_id, adjustment)?;
}
Expand Down Expand Up @@ -1336,6 +1338,7 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
adjustment::Adjust::NeverToAny
| adjustment::Adjust::Pointer(_)
| adjustment::Adjust::Borrow(_)
| adjustment::Adjust::NonZeroIntoInner
| adjustment::Adjust::ReborrowPin(..) => {
// Result is an rvalue.
Ok(self.cat_rvalue(expr.hir_id, target))
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// FIXME(const_trait_impl): We could enforce these; they correspond to
// `&mut T: DerefMut` tho, so it's kinda moot.
}
Adjust::Borrow(_) => {
Adjust::NonZeroIntoInner | Adjust::Borrow(_) => {
// No effects to enforce here.
}
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2487,7 +2487,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
_ => return false,
};

if !self.tcx.is_diagnostic_item(sym::NonZero, adt.did()) {
if !self.tcx.is_lang_item(adt.did(), LangItem::NonZero) {
return false;
}

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_lint/src/autorefs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ fn has_implicit_borrow(Adjustment { kind, .. }: &Adjustment<'_>) -> Option<(Muta
| Adjust::Pointer(..)
| Adjust::ReborrowPin(..)
| Adjust::Deref(None)
| Adjust::NonZeroIntoInner
| Adjust::Borrow(AutoBorrow::RawPtr(..)) => None,
}
}
3 changes: 3 additions & 0 deletions compiler/rustc_middle/src/ty/adjustment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ pub enum Adjust {

/// Take a pinned reference and reborrow as a `Pin<&mut T>` or `Pin<&T>`.
ReborrowPin(hir::Mutability),

/// Turn a `NonZero<T>` into `T`
NonZeroIntoInner,
}

/// An overloaded autoderef step, representing a `Deref(Mut)::deref(_mut)`
Expand Down
28 changes: 28 additions & 0 deletions compiler/rustc_mir_build/src/thir/cx/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,34 @@ impl<'tcx> ThirBuildCx<'tcx> {
debug!(?kind);
kind
}
Adjust::NonZeroIntoInner => {
// These only happen on `NonZero`
let ty::Adt(adt, args) = expr.ty.kind() else {
span_bug!(span, "nonzero adjustment not based on adt: {expr:#?}")
};
// Find the right field type
let ty = self
.tcx
.type_of(adt.variant(FIRST_VARIANT).fields[FieldIdx::ZERO].did)
.instantiate(self.tcx, args);
// Get rid of the `ZeroablePrimitive` projection
let ty = self.tcx.normalize_erasing_regions(self.typing_env, ty);
let lhs = self.thir.exprs.push(expr);
ExprKind::Field {
lhs: self.thir.exprs.push(Expr {
span,
temp_lifetime,
ty,
kind: ExprKind::Field {
lhs,
variant_index: FIRST_VARIANT,
name: FieldIdx::ZERO,
},
}),
variant_index: FIRST_VARIANT,
name: FieldIdx::ZERO,
}
}
};

Expr { temp_lifetime, ty: adjustment.target, span, kind }
Expand Down
2 changes: 1 addition & 1 deletion library/core/src/num/nonzero.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ impl_zeroable_primitive!(
#[stable(feature = "generic_nonzero", since = "1.79.0")]
#[repr(transparent)]
#[rustc_nonnull_optimization_guaranteed]
#[rustc_diagnostic_item = "NonZero"]
#[lang = "NonZero"]
pub struct NonZero<T: ZeroablePrimitive>(T::NonZeroInner);

macro_rules! impl_nonzero_fmt {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::is_inside_always_const_context;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::ty::is_type_lang_item;
use rustc_errors::Applicability;
use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, Node, QPath, UnsafeSource};
use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, LangItem, Node, QPath, UnsafeSource};
use rustc_lint::LateContext;
use rustc_span::sym;

Expand All @@ -15,7 +15,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, func: &Expr<'
&& segment.ident.name == sym::new_unchecked
&& let [init_arg] = args
&& is_inside_always_const_context(cx.tcx, expr.hir_id)
&& is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::NonZero)
&& is_type_lang_item(cx, cx.typeck_results().node_type(ty.hir_id), LangItem::NonZero)
&& msrv.meets(cx, msrvs::CONST_UNWRAP)
{
let mut app = Applicability::MachineApplicable;
Expand Down
4 changes: 2 additions & 2 deletions src/tools/clippy/clippy_lints/src/non_zero_suggestions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use clippy_utils::source::snippet;
use clippy_utils::sym;
use rustc_ast::ast::BinOpKind;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_hir::{Expr, ExprKind, LangItem};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, Ty};
use rustc_session::declare_lint_pass;
Expand Down Expand Up @@ -81,7 +81,7 @@ fn check_non_zero_conversion(cx: &LateContext<'_>, expr: &Expr<'_>, applicabilit
// Check if the receiver type is a NonZero type
if let ty::Adt(adt_def, _) = receiver_ty.kind()
&& adt_def.is_struct()
&& cx.tcx.get_diagnostic_name(adt_def.did()) == Some(sym::NonZero)
&& cx.tcx.is_lang_item(adt_def.did(), LangItem::NonZero)
&& let Some(target_non_zero_type) = get_target_non_zero_type(target_ty)
{
let arg_snippet = get_arg_snippet(cx, arg, rcv_path);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use clippy_utils::diagnostics::span_lint;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{expr_or_init, is_from_proc_macro, is_lint_allowed, peel_hir_expr_refs, peel_hir_expr_unary};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir::LangItem;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, Ty};
use rustc_session::impl_lint_pass;
Expand Down Expand Up @@ -102,7 +103,7 @@ impl ArithmeticSideEffects {

let ty::Adt(adt, substs) = ty.kind() else { return false };

if !tcx.is_diagnostic_item(sym::NonZero, adt.did()) {
if !tcx.is_lang_item(adt.did(), LangItem::NonZero) {
return false;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_hir as hir;
use clippy_utils::ty::is_type_lang_item;
use rustc_hir::{self as hir, LangItem};
use rustc_lint::LateContext;
use rustc_span::symbol::sym;

use super::INTEGER_DIVISION;

Expand All @@ -16,7 +15,7 @@ pub(crate) fn check<'tcx>(
if op == hir::BinOpKind::Div
&& cx.typeck_results().expr_ty(left).is_integral()
&& let right_ty = cx.typeck_results().expr_ty(right)
&& (right_ty.is_integral() || is_type_diagnostic_item(cx, right_ty, sym::NonZero))
&& (right_ty.is_integral() || is_type_lang_item(cx, right_ty, LangItem::NonZero))
{
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(cx, INTEGER_DIVISION, expr.span, "integer division", |diag| {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::TRANSMUTE_INT_TO_NON_ZERO;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::sugg;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_hir::{Expr, LangItem};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
use rustc_span::symbol::sym;
Expand All @@ -22,7 +22,7 @@ pub(super) fn check<'tcx>(
return false;
};

if !tcx.is_diagnostic_item(sym::NonZero, adt.did()) {
if !tcx.is_lang_item(adt.did(), LangItem::NonZero) {
return false;
}

Expand Down
8 changes: 8 additions & 0 deletions tests/ui/mismatched_types/non_zero_assigned_lit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#![allow(overflowing_literals)]

fn main() {
let x: std::num::NonZero<i8> = -128;
//~^ ERROR mismatched types
//~| HELP consider calling `NonZero::new`
assert_eq!(x.get(), -128_i8);
}
18 changes: 18 additions & 0 deletions tests/ui/mismatched_types/non_zero_assigned_lit.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
error[E0308]: mismatched types
--> $DIR/non_zero_assigned_lit.rs:4:36
|
LL | let x: std::num::NonZero<i8> = -128;
| --------------------- ^^^^ expected `NonZero<i8>`, found integer
| |
| expected due to this
|
= note: expected struct `NonZero<i8>`
found type `{integer}`
help: consider calling `NonZero::new`
|
LL | let x: std::num::NonZero<i8> = NonZero::new(-128).unwrap();
| +++++++++++++ ++++++++++

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0308`.
14 changes: 14 additions & 0 deletions tests/ui/mismatched_types/non_zero_assigned_oflo_lit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#![allow(overflowing_literals)]

fn main() {
let _: std::num::NonZero<u8> = 256;
//~^ ERROR mismatched types
//~| HELP consider calling `NonZero::new`

let _: std::num::NonZero<i8> = -128;
//~^ ERROR mismatched types
//~| HELP consider calling `NonZero::new`
let _: std::num::NonZero<i8> = -129;
//~^ ERROR mismatched types
//~| HELP consider calling `NonZero::new`
}
48 changes: 48 additions & 0 deletions tests/ui/mismatched_types/non_zero_assigned_oflo_lit.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
error[E0308]: mismatched types
--> $DIR/non_zero_assigned_oflo_lit.rs:4:36
|
LL | let _: std::num::NonZero<u8> = 256;
| --------------------- ^^^ expected `NonZero<u8>`, found integer
| |
| expected due to this
|
= note: expected struct `NonZero<u8>`
found type `{integer}`
help: consider calling `NonZero::new`
|
LL | let _: std::num::NonZero<u8> = NonZero::new(256).unwrap();
| +++++++++++++ ++++++++++

error[E0308]: mismatched types
--> $DIR/non_zero_assigned_oflo_lit.rs:8:36
|
LL | let _: std::num::NonZero<i8> = -128;
| --------------------- ^^^^ expected `NonZero<i8>`, found integer
| |
| expected due to this
|
= note: expected struct `NonZero<i8>`
found type `{integer}`
help: consider calling `NonZero::new`
|
LL | let _: std::num::NonZero<i8> = NonZero::new(-128).unwrap();
| +++++++++++++ ++++++++++

error[E0308]: mismatched types
--> $DIR/non_zero_assigned_oflo_lit.rs:11:36
|
LL | let _: std::num::NonZero<i8> = -129;
| --------------------- ^^^^ expected `NonZero<i8>`, found integer
| |
| expected due to this
|
= note: expected struct `NonZero<i8>`
found type `{integer}`
help: consider calling `NonZero::new`
|
LL | let _: std::num::NonZero<i8> = NonZero::new(-129).unwrap();
| +++++++++++++ ++++++++++

error: aborting due to 3 previous errors

For more information about this error, try `rustc --explain E0308`.
40 changes: 40 additions & 0 deletions tests/ui/mismatched_types/non_zero_assigned_something.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,47 @@
fn main() {
let _: std::num::NonZero<u64> = 0;
//~^ ERROR mismatched types
//~| HELP consider calling `NonZero::new`

let _: Option<std::num::NonZero<u64>> = 0;
//~^ ERROR mismatched types
//~| HELP consider calling `NonZero::new`

let _: std::num::NonZero<u64> = 1;
//~^ ERROR mismatched types
//~| HELP consider calling `NonZero::new`
let _: std::num::NonZero<u64> = 1u8;
//~^ ERROR mismatched types
let _: std::num::NonZero<u64> = 1u64;
//~^ ERROR mismatched types
//~| HELP consider calling `NonZero::new`

let _: std::num::NonZero<u8> = 255;
//~^ ERROR mismatched types
//~| HELP consider calling `NonZero::new`
let _: std::num::NonZero<u8> = 256;
//~^ ERROR mismatched types
//~| HELP consider calling `NonZero::new`

let _: std::num::NonZero<u8> = -10;
//~^ ERROR mismatched types
//~| HELP consider calling `NonZero::new`

let _: std::num::NonZero<i8> = -128;
//~^ ERROR mismatched types
//~| HELP consider calling `NonZero::new`
let _: std::num::NonZero<i8> = -129;
//~^ ERROR mismatched types
//~| HELP consider calling `NonZero::new`
let _: std::num::NonZero<i8> = -1;
//~^ ERROR mismatched types
//~| HELP consider calling `NonZero::new`
let _: std::num::NonZero<i8> = 0;
//~^ ERROR mismatched types
//~| HELP consider calling `NonZero::new`
let _: std::num::NonZero<i8> = 1;
//~^ ERROR mismatched types
//~| HELP consider calling `NonZero::new`

let _: Option<std::num::NonZero<u64>> = 1;
//~^ ERROR mismatched types
Expand Down
Loading
Loading