From 23e6fb77a2bd47118da2474a8b6d2bbe165d714f Mon Sep 17 00:00:00 2001 From: Yukang Date: Thu, 23 Jul 2026 05:40:10 +0800 Subject: [PATCH 1/4] Allow unary operand types to be inferred later --- compiler/rustc_hir_typeck/src/expr.rs | 49 +++++++++++-------- ...late-raw-pointer-inference-issue-106138.rs | 14 ++++++ ...-raw-pointer-inference-issue-106138.stderr | 17 +++++++ ...associated-output-mismatch-issue-106138.rs | 42 ++++++++++++++++ ...ciated-output-mismatch-issue-106138.stderr | 42 ++++++++++++++++ ...-op-late-operand-inference-issue-106138.rs | 49 +++++++++++++++++++ 6 files changed, 193 insertions(+), 20 deletions(-) create mode 100644 tests/ui/inference/unary-deref-late-raw-pointer-inference-issue-106138.rs create mode 100644 tests/ui/inference/unary-deref-late-raw-pointer-inference-issue-106138.stderr create mode 100644 tests/ui/inference/unary-op-associated-output-mismatch-issue-106138.rs create mode 100644 tests/ui/inference/unary-op-associated-output-mismatch-issue-106138.stderr create mode 100644 tests/ui/inference/unary-op-late-operand-inference-issue-106138.rs diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 06dbd06eb191b..814d671b28509 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -421,33 +421,42 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return Ty::new_error(tcx, guar); } - let oprnd_t = self.structurally_resolve_type(expr.span, oprnd_t); + // `Not` and `Neg` can be selected using an unresolved operand type, + // later constraints may still determine that type, + // so we call `resolve_vars_with_obligations` for them. match unop { - hir::UnOp::Deref => self.lookup_derefing(expr, oprnd, oprnd_t).unwrap_or_else(|| { - let mut err = - self.dcx().create_err(CantDereference { span: expr.span, ty: oprnd_t }); - let sp = tcx.sess.source_map().start_point(expr.span).with_parent(None); - if let Some(sp) = tcx.sess.psess.ambiguous_block_expr_parse.borrow().get(&sp) { - err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp)); - } - // The operand may be an uncalled function, in which case it is its return type - // the user meant to dereference. Only suggest the call when that return type is - // itself dereferenceable, mirroring the checks `lookup_derefing` just failed. - self.suggest_fn_call(&mut err, oprnd, oprnd_t, |output| { - output.builtin_deref(true).is_some() - || self.tcx.lang_items().deref_trait().is_some_and(|deref_trait| { - self.type_implements_trait(deref_trait, [output], self.param_env) - .may_apply() - }) - }); - Ty::new_error(tcx, err.emit()) - }), + hir::UnOp::Deref => { + // Dereferencing must distinguish builtin pointers from + // overloaded `Deref`, so it still requires a structurally resolved type. + let oprnd_t = self.structurally_resolve_type(expr.span, oprnd_t); + self.lookup_derefing(expr, oprnd, oprnd_t).unwrap_or_else(|| { + let mut err = + self.dcx().create_err(CantDereference { span: expr.span, ty: oprnd_t }); + let sp = tcx.sess.source_map().start_point(expr.span).with_parent(None); + if let Some(sp) = tcx.sess.psess.ambiguous_block_expr_parse.borrow().get(&sp) { + err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp)); + } + // The operand may be an uncalled function, in which case it is its return type + // the user meant to dereference. Only suggest the call when that return type is + // itself dereferenceable, mirroring the checks `lookup_derefing` just failed. + self.suggest_fn_call(&mut err, oprnd, oprnd_t, |output| { + output.builtin_deref(true).is_some() + || self.tcx.lang_items().deref_trait().is_some_and(|deref_trait| { + self.type_implements_trait(deref_trait, [output], self.param_env) + .may_apply() + }) + }); + Ty::new_error(tcx, err.emit()) + }) + } hir::UnOp::Not => { + let oprnd_t = self.resolve_vars_with_obligations(oprnd_t); let result = self.check_user_unop(expr, oprnd_t, unop, expected_inner); // If it's builtin, we can reuse the type, this helps inference. if oprnd_t.is_integral() || *oprnd_t.kind() == ty::Bool { oprnd_t } else { result } } hir::UnOp::Neg => { + let oprnd_t = self.resolve_vars_with_obligations(oprnd_t); let result = self.check_user_unop(expr, oprnd_t, unop, expected_inner); // If it's builtin, we can reuse the type, this helps inference. if oprnd_t.is_numeric() { oprnd_t } else { result } diff --git a/tests/ui/inference/unary-deref-late-raw-pointer-inference-issue-106138.rs b/tests/ui/inference/unary-deref-late-raw-pointer-inference-issue-106138.rs new file mode 100644 index 0000000000000..c2d67e02ed520 --- /dev/null +++ b/tests/ui/inference/unary-deref-late-raw-pointer-inference-issue-106138.rs @@ -0,0 +1,14 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/106138. +//! An unresolved dereference must not treat a raw pointer as an overloaded `Deref`. + +fn make() -> T { + loop {} +} + +fn main() { + let pointer = make(); + //~^ ERROR type annotations needed + let value = unsafe { *pointer }; + let _: *const u8 = pointer; + let _: u8 = value; +} diff --git a/tests/ui/inference/unary-deref-late-raw-pointer-inference-issue-106138.stderr b/tests/ui/inference/unary-deref-late-raw-pointer-inference-issue-106138.stderr new file mode 100644 index 0000000000000..f6a2a772ffe54 --- /dev/null +++ b/tests/ui/inference/unary-deref-late-raw-pointer-inference-issue-106138.stderr @@ -0,0 +1,17 @@ +error[E0282]: type annotations needed + --> $DIR/unary-deref-late-raw-pointer-inference-issue-106138.rs:9:9 + | +LL | let pointer = make(); + | ^^^^^^^ +LL | +LL | let value = unsafe { *pointer }; + | -------- type must be known at this point + | +help: consider giving `pointer` an explicit type + | +LL | let pointer: /* Type */ = make(); + | ++++++++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0282`. diff --git a/tests/ui/inference/unary-op-associated-output-mismatch-issue-106138.rs b/tests/ui/inference/unary-op-associated-output-mismatch-issue-106138.rs new file mode 100644 index 0000000000000..b30535115602e --- /dev/null +++ b/tests/ui/inference/unary-op-associated-output-mismatch-issue-106138.rs @@ -0,0 +1,42 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/106138. +//! From comment https://github.com/rust-lang/rust/pull/107567#discussion_r1093589448 +//! Unary operator inference must not assume that the associated output type equals `Self`. + +#[derive(Copy, Clone, Default)] +struct A; + +struct B; + +impl std::ops::Not for A { + type Output = B; + + fn not(self) -> B { + B + } +} + +#[derive(Copy, Clone)] +struct NoNot; + +fn make() -> T { + loop {} +} + +fn resolved_without_impl() { + let value = make(); + let _ = !value; + //~^ ERROR the trait bound `NoNot: Not` is not satisfied + let _: NoNot = value; +} + +fn unconstrained() { + let value = make(); + //~^ ERROR type annotations needed + let _ = !value; +} + +fn main() { + let x = Default::default(); + //~^ ERROR cannot call associated function on trait + let _: A = !x; +} diff --git a/tests/ui/inference/unary-op-associated-output-mismatch-issue-106138.stderr b/tests/ui/inference/unary-op-associated-output-mismatch-issue-106138.stderr new file mode 100644 index 0000000000000..a1772e57f9420 --- /dev/null +++ b/tests/ui/inference/unary-op-associated-output-mismatch-issue-106138.stderr @@ -0,0 +1,42 @@ +error[E0277]: the trait bound `NoNot: Not` is not satisfied + --> $DIR/unary-op-associated-output-mismatch-issue-106138.rs:27:13 + | +LL | let _ = !value; + | ^^^^^^ unsatisfied trait bound + | +help: the trait `Not` is not implemented for `NoNot` + --> $DIR/unary-op-associated-output-mismatch-issue-106138.rs:19:1 + | +LL | struct NoNot; + | ^^^^^^^^^^^^ + +error[E0284]: type annotations needed + --> $DIR/unary-op-associated-output-mismatch-issue-106138.rs:33:9 + | +LL | let value = make(); + | ^^^^^ +LL | +LL | let _ = !value; + | ------ type must be known at this point + | + = note: cannot satisfy `<_ as Not>::Output == _` +help: consider giving `value` an explicit type + | +LL | let value: /* Type */ = make(); + | ++++++++++++ + +error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type + --> $DIR/unary-op-associated-output-mismatch-issue-106138.rs:39:13 + | +LL | let x = Default::default(); + | ^^^^^^^^^^^^^^^^^^ cannot call associated function of trait + | +help: use a fully-qualified path to a specific available implementation + | +LL | let x = ::default(); + | +++++++++++++++++++ + + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0277, E0284, E0790. +For more information about an error, try `rustc --explain E0277`. diff --git a/tests/ui/inference/unary-op-late-operand-inference-issue-106138.rs b/tests/ui/inference/unary-op-late-operand-inference-issue-106138.rs new file mode 100644 index 0000000000000..549c7a91d6f00 --- /dev/null +++ b/tests/ui/inference/unary-op-late-operand-inference-issue-106138.rs @@ -0,0 +1,49 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/106138. +//! Unary operators should allow their operand types to be inferred by later constraints. + +//@ check-pass + +use std::ops::Not; + +fn not_index(x: &Vec) { + let closure = |i, a: &Vec| !a[i]; + let _ = closure(0, x); +} + +fn neg_index(x: &Vec) { + let closure = |i, a: &Vec| -a[i]; + let _ = closure(0, x); +} + +#[derive(Copy, Clone, Default)] +struct Input; + +struct Output; + +impl Not for Input { + type Output = Output; + + fn not(self) -> Self::Output { + Output + } +} + +fn output_differs_from_operand() { + let input = Default::default(); + let output = !input; + let _: Input = input; + let _: Output = output; +} + +fn output_expectation_differs_from_operand() { + let input = Default::default(); + let _: Output = !input; + let _: Input = input; +} + +fn main() { + not_index(&vec![true]); + neg_index(&vec![1]); + output_differs_from_operand(); + output_expectation_differs_from_operand(); +} From d73573109ea0b1e2c77b2d97633227a578df975c Mon Sep 17 00:00:00 2001 From: Yukang Date: Mon, 24 Aug 2026 11:27:06 +0800 Subject: [PATCH 2/4] bless mismatch for unary-op-associated-output-mismatch-issue-106138 --- .../unary-op-associated-output-mismatch-issue-106138.stderr | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ui/inference/unary-op-associated-output-mismatch-issue-106138.stderr b/tests/ui/inference/unary-op-associated-output-mismatch-issue-106138.stderr index a1772e57f9420..30318239390e5 100644 --- a/tests/ui/inference/unary-op-associated-output-mismatch-issue-106138.stderr +++ b/tests/ui/inference/unary-op-associated-output-mismatch-issue-106138.stderr @@ -20,6 +20,7 @@ LL | let _ = !value; | ------ type must be known at this point | = note: cannot satisfy `<_ as Not>::Output == _` + = note: the type must also implement `Not` help: consider giving `value` an explicit type | LL | let value: /* Type */ = make(); From 64e2aef68b620df82407784f69ada352d344a438 Mon Sep 17 00:00:00 2001 From: Yukang Date: Mon, 24 Aug 2026 11:45:57 +0800 Subject: [PATCH 3/4] add regression test for issue 26830 --- ...-neg-late-operand-inference-issue-26830.rs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/ui/inference/unary-neg-late-operand-inference-issue-26830.rs diff --git a/tests/ui/inference/unary-neg-late-operand-inference-issue-26830.rs b/tests/ui/inference/unary-neg-late-operand-inference-issue-26830.rs new file mode 100644 index 0000000000000..9f2ad98e5d6b4 --- /dev/null +++ b/tests/ui/inference/unary-neg-late-operand-inference-issue-26830.rs @@ -0,0 +1,27 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/26830. +//! Unary negation should allow later constraints to determine its operand type. + +//@ check-pass + +fn make() -> T { + loop {} +} + +fn constrained_by_value() { + let input = make(); + let output = -input; + let _: i32 = input; + let _: i32 = output; +} + +fn constrained_by_borrow() { + let input = make(); + let output = -input; + let _: &i32 = &input; + let _: i32 = output; +} + +fn main() { + constrained_by_value(); + constrained_by_borrow(); +} From 108faae5d816e5224aacf40fa6fd52531d163351 Mon Sep 17 00:00:00 2001 From: Yukang Date: Wed, 2 Sep 2026 18:13:26 +0800 Subject: [PATCH 4/4] fix comment for check_expr_unop with Deref Co-authored-by: lcnr --- compiler/rustc_hir_typeck/src/expr.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 814d671b28509..ee78e4e2cbce8 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -421,13 +421,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return Ty::new_error(tcx, guar); } - // `Not` and `Neg` can be selected using an unresolved operand type, - // later constraints may still determine that type, - // so we call `resolve_vars_with_obligations` for them. + // Resolve what we can for `Not` and `Neg`, but allow later constraints + // to determine an unresolved operand type, + // so we call `deeply_resolve_ignoring_regions_with_obligations` for them. match unop { hir::UnOp::Deref => { // Dereferencing must distinguish builtin pointers from // overloaded `Deref`, so it still requires a structurally resolved type. + // + // This is necessary as raw pointers do not implement `Deref`. let oprnd_t = self.structurally_resolve_type(expr.span, oprnd_t); self.lookup_derefing(expr, oprnd, oprnd_t).unwrap_or_else(|| { let mut err = @@ -450,13 +452,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }) } hir::UnOp::Not => { - let oprnd_t = self.resolve_vars_with_obligations(oprnd_t); + let oprnd_t = self.deeply_resolve_ignoring_regions_with_obligations(oprnd_t); let result = self.check_user_unop(expr, oprnd_t, unop, expected_inner); // If it's builtin, we can reuse the type, this helps inference. if oprnd_t.is_integral() || *oprnd_t.kind() == ty::Bool { oprnd_t } else { result } } hir::UnOp::Neg => { - let oprnd_t = self.resolve_vars_with_obligations(oprnd_t); + let oprnd_t = self.deeply_resolve_ignoring_regions_with_obligations(oprnd_t); let result = self.check_user_unop(expr, oprnd_t, unop, expected_inner); // If it's builtin, we can reuse the type, this helps inference. if oprnd_t.is_numeric() { oprnd_t } else { result }