Skip to content

Commit 4a4dd8e

Browse files
Auto merge of #143629 - oli-obk:limited-nonzero-coercion, r=<try>
Limited nonzero coercion coercing `NonZero<T>` to `T` if all the types are available at the cast site. This is fairly limited, as (as the tests show), this does not allow "reborrowing" `&NonZero<T>` as `&T`. It also fails quickly if the types aren't fully clear at the coercion site, but only resolved later. Also cannot aid in trait selection or similar, even if there is clearly only one option. Related: * #143594 * rust-lang/rfcs#3786 cc `@traviscross`
2 parents 040e2f8 + e341923 commit 4a4dd8e

27 files changed

+663
-17
lines changed

compiler/rustc_hir/src/lang_items.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,8 @@ language_item_table! {
426426
String, sym::String, string, Target::Struct, GenericRequirement::None;
427427
CStr, sym::CStr, c_str, Target::Struct, GenericRequirement::None;
428428

429+
NonZero, sym::NonZero, non_zero, Target::Struct, GenericRequirement::Exact(1);
430+
429431
// Experimental lang items for implementing contract pre- and post-condition checking.
430432
ContractBuildCheckEnsures, sym::contract_build_check_ensures, contract_build_check_ensures_fn, Target::Fn, GenericRequirement::None;
431433
ContractCheckRequires, sym::contract_check_requires, contract_check_requires_fn, Target::Fn, GenericRequirement::None;

compiler/rustc_hir_typeck/src/coercion.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,9 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
263263
// It cannot convert closures that require unsafe.
264264
self.coerce_closure_to_fn(a, closure_def_id_a, args_a, b)
265265
}
266+
ty::Adt(adt, args) if self.tcx.is_lang_item(adt.did(), hir::LangItem::NonZero) => {
267+
self.coerce_non_zero(a, args.type_at(0), b)
268+
}
266269
_ => {
267270
// Otherwise, just use unification rules.
268271
self.unify(a, b)
@@ -827,6 +830,19 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
827830
self.unify_and(a, b, [], Adjust::ReborrowPin(mut_b))
828831
}
829832

833+
fn coerce_non_zero(
834+
&self,
835+
non_zero: Ty<'tcx>,
836+
wrapped_ty: Ty<'tcx>,
837+
target_ty: Ty<'tcx>,
838+
) -> CoerceResult<'tcx> {
839+
if target_ty.is_ty_var() {
840+
return self.unify(non_zero, target_ty);
841+
}
842+
self.commit_if_ok(|_| self.unify_and(wrapped_ty, target_ty, [], Adjust::NonZeroIntoInner))
843+
.or_else(|_| self.unify(non_zero, target_ty))
844+
}
845+
830846
fn coerce_from_safe_fn(
831847
&self,
832848
fn_ty_a: ty::PolyFnSig<'tcx>,

compiler/rustc_hir_typeck/src/expr_use_visitor.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,8 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
854854
};
855855
self.delegate.borrow_mut().borrow(&place_with_id, place_with_id.hir_id, bk);
856856
}
857+
858+
adjustment::Adjust::NonZeroIntoInner => {}
857859
}
858860
place_with_id = self.cat_expr_adjusted(expr, place_with_id, adjustment)?;
859861
}
@@ -1336,6 +1338,7 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
13361338
adjustment::Adjust::NeverToAny
13371339
| adjustment::Adjust::Pointer(_)
13381340
| adjustment::Adjust::Borrow(_)
1341+
| adjustment::Adjust::NonZeroIntoInner
13391342
| adjustment::Adjust::ReborrowPin(..) => {
13401343
// Result is an rvalue.
13411344
Ok(self.cat_rvalue(expr.hir_id, target))

compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
290290
// FIXME(const_trait_impl): We could enforce these; they correspond to
291291
// `&mut T: DerefMut` tho, so it's kinda moot.
292292
}
293-
Adjust::Borrow(_) => {
293+
Adjust::NonZeroIntoInner | Adjust::Borrow(_) => {
294294
// No effects to enforce here.
295295
}
296296
}

compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2487,7 +2487,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
24872487
_ => return false,
24882488
};
24892489

2490-
if !self.tcx.is_diagnostic_item(sym::NonZero, adt.did()) {
2490+
if !self.tcx.is_lang_item(adt.did(), LangItem::NonZero) {
24912491
return false;
24922492
}
24932493

compiler/rustc_lint/src/autorefs.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ fn has_implicit_borrow(Adjustment { kind, .. }: &Adjustment<'_>) -> Option<(Muta
171171
| Adjust::Pointer(..)
172172
| Adjust::ReborrowPin(..)
173173
| Adjust::Deref(None)
174+
| Adjust::NonZeroIntoInner
174175
| Adjust::Borrow(AutoBorrow::RawPtr(..)) => None,
175176
}
176177
}

compiler/rustc_middle/src/ty/adjustment.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ pub enum Adjust {
105105

106106
/// Take a pinned reference and reborrow as a `Pin<&mut T>` or `Pin<&T>`.
107107
ReborrowPin(hir::Mutability),
108+
109+
/// Turn a `NonZero<T>` into `T`
110+
NonZeroIntoInner,
108111
}
109112

110113
/// An overloaded autoderef step, representing a `Deref(Mut)::deref(_mut)`

compiler/rustc_mir_build/src/thir/cx/expr.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,34 @@ impl<'tcx> ThirBuildCx<'tcx> {
239239
debug!(?kind);
240240
kind
241241
}
242+
Adjust::NonZeroIntoInner => {
243+
// These only happen on `NonZero`
244+
let ty::Adt(adt, args) = expr.ty.kind() else {
245+
span_bug!(span, "nonzero adjustment not based on adt: {expr:#?}")
246+
};
247+
// Find the right field type
248+
let ty = self
249+
.tcx
250+
.type_of(adt.variant(FIRST_VARIANT).fields[FieldIdx::ZERO].did)
251+
.instantiate(self.tcx, args);
252+
// Get rid of the `ZeroablePrimitive` projection
253+
let ty = self.tcx.normalize_erasing_regions(self.typing_env, ty);
254+
let lhs = self.thir.exprs.push(expr);
255+
ExprKind::Field {
256+
lhs: self.thir.exprs.push(Expr {
257+
span,
258+
temp_lifetime,
259+
ty,
260+
kind: ExprKind::Field {
261+
lhs,
262+
variant_index: FIRST_VARIANT,
263+
name: FieldIdx::ZERO,
264+
},
265+
}),
266+
variant_index: FIRST_VARIANT,
267+
name: FieldIdx::ZERO,
268+
}
269+
}
242270
};
243271

244272
Expr { temp_lifetime, ty: adjustment.target, span, kind }

library/core/src/num/nonzero.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ impl_zeroable_primitive!(
123123
#[stable(feature = "generic_nonzero", since = "1.79.0")]
124124
#[repr(transparent)]
125125
#[rustc_nonnull_optimization_guaranteed]
126-
#[rustc_diagnostic_item = "NonZero"]
126+
#[lang = "NonZero"]
127127
pub struct NonZero<T: ZeroablePrimitive>(T::NonZeroInner);
128128

129129
macro_rules! impl_nonzero_fmt {

src/tools/clippy/clippy_lints/src/methods/useless_nonzero_new_unchecked.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
22
use clippy_utils::is_inside_always_const_context;
33
use clippy_utils::msrvs::{self, Msrv};
44
use clippy_utils::source::snippet_with_applicability;
5-
use clippy_utils::ty::is_type_diagnostic_item;
5+
use clippy_utils::ty::is_type_lang_item;
66
use rustc_errors::Applicability;
7-
use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, Node, QPath, UnsafeSource};
7+
use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, LangItem, Node, QPath, UnsafeSource};
88
use rustc_lint::LateContext;
99
use rustc_span::sym;
1010

@@ -15,7 +15,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, func: &Expr<'
1515
&& segment.ident.name == sym::new_unchecked
1616
&& let [init_arg] = args
1717
&& is_inside_always_const_context(cx.tcx, expr.hir_id)
18-
&& is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::NonZero)
18+
&& is_type_lang_item(cx, cx.typeck_results().node_type(ty.hir_id), LangItem::NonZero)
1919
&& msrv.meets(cx, msrvs::CONST_UNWRAP)
2020
{
2121
let mut app = Applicability::MachineApplicable;

src/tools/clippy/clippy_lints/src/non_zero_suggestions.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use clippy_utils::source::snippet;
33
use clippy_utils::sym;
44
use rustc_ast::ast::BinOpKind;
55
use rustc_errors::Applicability;
6-
use rustc_hir::{Expr, ExprKind};
6+
use rustc_hir::{Expr, ExprKind, LangItem};
77
use rustc_lint::{LateContext, LateLintPass};
88
use rustc_middle::ty::{self, Ty};
99
use rustc_session::declare_lint_pass;
@@ -81,7 +81,7 @@ fn check_non_zero_conversion(cx: &LateContext<'_>, expr: &Expr<'_>, applicabilit
8181
// Check if the receiver type is a NonZero type
8282
if let ty::Adt(adt_def, _) = receiver_ty.kind()
8383
&& adt_def.is_struct()
84-
&& cx.tcx.get_diagnostic_name(adt_def.did()) == Some(sym::NonZero)
84+
&& cx.tcx.is_lang_item(adt_def.did(), LangItem::NonZero)
8585
&& let Some(target_non_zero_type) = get_target_non_zero_type(target_ty)
8686
{
8787
let arg_snippet = get_arg_snippet(cx, arg, rcv_path);

src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use clippy_utils::diagnostics::span_lint;
55
use clippy_utils::ty::is_type_diagnostic_item;
66
use clippy_utils::{expr_or_init, is_from_proc_macro, is_lint_allowed, peel_hir_expr_refs, peel_hir_expr_unary};
77
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
8+
use rustc_hir::LangItem;
89
use rustc_lint::{LateContext, LateLintPass};
910
use rustc_middle::ty::{self, Ty};
1011
use rustc_session::impl_lint_pass;
@@ -102,7 +103,7 @@ impl ArithmeticSideEffects {
102103

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

105-
if !tcx.is_diagnostic_item(sym::NonZero, adt.did()) {
106+
if !tcx.is_lang_item(adt.did(), LangItem::NonZero) {
106107
return false;
107108
}
108109

src/tools/clippy/clippy_lints/src/operators/integer_division.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
2-
use clippy_utils::ty::is_type_diagnostic_item;
3-
use rustc_hir as hir;
2+
use clippy_utils::ty::is_type_lang_item;
3+
use rustc_hir::{self as hir, LangItem};
44
use rustc_lint::LateContext;
5-
use rustc_span::symbol::sym;
65

76
use super::INTEGER_DIVISION;
87

@@ -16,7 +15,7 @@ pub(crate) fn check<'tcx>(
1615
if op == hir::BinOpKind::Div
1716
&& cx.typeck_results().expr_ty(left).is_integral()
1817
&& let right_ty = cx.typeck_results().expr_ty(right)
19-
&& (right_ty.is_integral() || is_type_diagnostic_item(cx, right_ty, sym::NonZero))
18+
&& (right_ty.is_integral() || is_type_lang_item(cx, right_ty, LangItem::NonZero))
2019
{
2120
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
2221
span_lint_and_then(cx, INTEGER_DIVISION, expr.span, "integer division", |diag| {

src/tools/clippy/clippy_lints/src/transmute/transmute_int_to_non_zero.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use super::TRANSMUTE_INT_TO_NON_ZERO;
22
use clippy_utils::diagnostics::span_lint_and_then;
33
use clippy_utils::sugg;
44
use rustc_errors::Applicability;
5-
use rustc_hir::Expr;
5+
use rustc_hir::{Expr, LangItem};
66
use rustc_lint::LateContext;
77
use rustc_middle::ty::{self, Ty};
88
use rustc_span::symbol::sym;
@@ -22,7 +22,7 @@ pub(super) fn check<'tcx>(
2222
return false;
2323
};
2424

25-
if !tcx.is_diagnostic_item(sym::NonZero, adt.did()) {
25+
if !tcx.is_lang_item(adt.did(), LangItem::NonZero) {
2626
return false;
2727
}
2828

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#![allow(overflowing_literals)]
2+
3+
fn main() {
4+
let x: std::num::NonZero<i8> = -128;
5+
//~^ ERROR mismatched types
6+
//~| HELP consider calling `NonZero::new`
7+
assert_eq!(x.get(), -128_i8);
8+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/non_zero_assigned_lit.rs:4:36
3+
|
4+
LL | let x: std::num::NonZero<i8> = -128;
5+
| --------------------- ^^^^ expected `NonZero<i8>`, found integer
6+
| |
7+
| expected due to this
8+
|
9+
= note: expected struct `NonZero<i8>`
10+
found type `{integer}`
11+
help: consider calling `NonZero::new`
12+
|
13+
LL | let x: std::num::NonZero<i8> = NonZero::new(-128).unwrap();
14+
| +++++++++++++ ++++++++++
15+
16+
error: aborting due to 1 previous error
17+
18+
For more information about this error, try `rustc --explain E0308`.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#![allow(overflowing_literals)]
2+
3+
fn main() {
4+
let _: std::num::NonZero<u8> = 256;
5+
//~^ ERROR mismatched types
6+
//~| HELP consider calling `NonZero::new`
7+
8+
let _: std::num::NonZero<i8> = -128;
9+
//~^ ERROR mismatched types
10+
//~| HELP consider calling `NonZero::new`
11+
let _: std::num::NonZero<i8> = -129;
12+
//~^ ERROR mismatched types
13+
//~| HELP consider calling `NonZero::new`
14+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/non_zero_assigned_oflo_lit.rs:4:36
3+
|
4+
LL | let _: std::num::NonZero<u8> = 256;
5+
| --------------------- ^^^ expected `NonZero<u8>`, found integer
6+
| |
7+
| expected due to this
8+
|
9+
= note: expected struct `NonZero<u8>`
10+
found type `{integer}`
11+
help: consider calling `NonZero::new`
12+
|
13+
LL | let _: std::num::NonZero<u8> = NonZero::new(256).unwrap();
14+
| +++++++++++++ ++++++++++
15+
16+
error[E0308]: mismatched types
17+
--> $DIR/non_zero_assigned_oflo_lit.rs:8:36
18+
|
19+
LL | let _: std::num::NonZero<i8> = -128;
20+
| --------------------- ^^^^ expected `NonZero<i8>`, found integer
21+
| |
22+
| expected due to this
23+
|
24+
= note: expected struct `NonZero<i8>`
25+
found type `{integer}`
26+
help: consider calling `NonZero::new`
27+
|
28+
LL | let _: std::num::NonZero<i8> = NonZero::new(-128).unwrap();
29+
| +++++++++++++ ++++++++++
30+
31+
error[E0308]: mismatched types
32+
--> $DIR/non_zero_assigned_oflo_lit.rs:11:36
33+
|
34+
LL | let _: std::num::NonZero<i8> = -129;
35+
| --------------------- ^^^^ expected `NonZero<i8>`, found integer
36+
| |
37+
| expected due to this
38+
|
39+
= note: expected struct `NonZero<i8>`
40+
found type `{integer}`
41+
help: consider calling `NonZero::new`
42+
|
43+
LL | let _: std::num::NonZero<i8> = NonZero::new(-129).unwrap();
44+
| +++++++++++++ ++++++++++
45+
46+
error: aborting due to 3 previous errors
47+
48+
For more information about this error, try `rustc --explain E0308`.

tests/ui/mismatched_types/non_zero_assigned_something.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,47 @@
11
fn main() {
2+
let _: std::num::NonZero<u64> = 0;
3+
//~^ ERROR mismatched types
4+
//~| HELP consider calling `NonZero::new`
5+
6+
let _: Option<std::num::NonZero<u64>> = 0;
7+
//~^ ERROR mismatched types
8+
//~| HELP consider calling `NonZero::new`
9+
210
let _: std::num::NonZero<u64> = 1;
311
//~^ ERROR mismatched types
412
//~| HELP consider calling `NonZero::new`
13+
let _: std::num::NonZero<u64> = 1u8;
14+
//~^ ERROR mismatched types
15+
let _: std::num::NonZero<u64> = 1u64;
16+
//~^ ERROR mismatched types
17+
//~| HELP consider calling `NonZero::new`
18+
19+
let _: std::num::NonZero<u8> = 255;
20+
//~^ ERROR mismatched types
21+
//~| HELP consider calling `NonZero::new`
22+
let _: std::num::NonZero<u8> = 256;
23+
//~^ ERROR mismatched types
24+
//~| HELP consider calling `NonZero::new`
25+
26+
let _: std::num::NonZero<u8> = -10;
27+
//~^ ERROR mismatched types
28+
//~| HELP consider calling `NonZero::new`
29+
30+
let _: std::num::NonZero<i8> = -128;
31+
//~^ ERROR mismatched types
32+
//~| HELP consider calling `NonZero::new`
33+
let _: std::num::NonZero<i8> = -129;
34+
//~^ ERROR mismatched types
35+
//~| HELP consider calling `NonZero::new`
36+
let _: std::num::NonZero<i8> = -1;
37+
//~^ ERROR mismatched types
38+
//~| HELP consider calling `NonZero::new`
39+
let _: std::num::NonZero<i8> = 0;
40+
//~^ ERROR mismatched types
41+
//~| HELP consider calling `NonZero::new`
42+
let _: std::num::NonZero<i8> = 1;
43+
//~^ ERROR mismatched types
44+
//~| HELP consider calling `NonZero::new`
545

646
let _: Option<std::num::NonZero<u64>> = 1;
747
//~^ ERROR mismatched types

0 commit comments

Comments
 (0)