Skip to content

Commit 3254088

Browse files
committed
Auto merge of rust-lang#129392 - compiler-errors:raw-ref-op-doesnt-diverge-but-more, r=<try>
[EXPERIMENT] Do not consider match/let/raw-ref of deref that evalautes to ! to diverge This is the more involved version of rust-lang#129371. It's pretty ugly rn. r? `@ghost`
2 parents 0f6e1ae + 1ee0400 commit 3254088

File tree

9 files changed

+163
-14
lines changed

9 files changed

+163
-14
lines changed

compiler/rustc_hir_typeck/src/coercion.rs

+14-7
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ struct Coerce<'a, 'tcx> {
8181
/// See #47489 and #48598
8282
/// See docs on the "AllowTwoPhase" type for a more detailed discussion
8383
allow_two_phase: AllowTwoPhase,
84+
coerce_never: bool,
8485
}
8586

8687
impl<'a, 'tcx> Deref for Coerce<'a, 'tcx> {
@@ -124,8 +125,9 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
124125
fcx: &'f FnCtxt<'f, 'tcx>,
125126
cause: ObligationCause<'tcx>,
126127
allow_two_phase: AllowTwoPhase,
128+
coerce_never: bool,
127129
) -> Self {
128-
Coerce { fcx, cause, allow_two_phase, use_lub: false }
130+
Coerce { fcx, cause, allow_two_phase, use_lub: false, coerce_never }
129131
}
130132

131133
fn unify(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> InferResult<'tcx, Ty<'tcx>> {
@@ -176,7 +178,11 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
176178

177179
// Coercing from `!` to any type is allowed:
178180
if a.is_never() {
179-
return success(simple(Adjust::NeverToAny)(b), b, vec![]);
181+
if self.coerce_never {
182+
return success(simple(Adjust::NeverToAny)(b), b, vec![]);
183+
} else {
184+
return self.unify_and(a, b, identity);
185+
}
180186
}
181187

182188
// Coercing *from* an unresolved inference variable means that
@@ -978,7 +984,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
978984
/// The expressions *must not* have any preexisting adjustments.
979985
pub fn coerce(
980986
&self,
981-
expr: &hir::Expr<'_>,
987+
expr: &'tcx hir::Expr<'tcx>,
982988
expr_ty: Ty<'tcx>,
983989
mut target: Ty<'tcx>,
984990
allow_two_phase: AllowTwoPhase,
@@ -995,7 +1001,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
9951001

9961002
let cause =
9971003
cause.unwrap_or_else(|| self.cause(expr.span, ObligationCauseCode::ExprAssignable));
998-
let coerce = Coerce::new(self, cause, allow_two_phase);
1004+
let coerce =
1005+
Coerce::new(self, cause, allow_two_phase, self.expr_is_rvalue_for_divergence(expr));
9991006
let ok = self.commit_if_ok(|_| coerce.coerce(source, target))?;
10001007

10011008
let (adjustments, _) = self.register_infer_ok_obligations(ok);
@@ -1018,7 +1025,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
10181025

10191026
let cause = self.cause(DUMMY_SP, ObligationCauseCode::ExprAssignable);
10201027
// We don't ever need two-phase here since we throw out the result of the coercion
1021-
let coerce = Coerce::new(self, cause, AllowTwoPhase::No);
1028+
let coerce = Coerce::new(self, cause, AllowTwoPhase::No, true);
10221029
self.probe(|_| {
10231030
let Ok(ok) = coerce.coerce(source, target) else {
10241031
return false;
@@ -1035,7 +1042,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
10351042
pub fn deref_steps(&self, expr_ty: Ty<'tcx>, target: Ty<'tcx>) -> Option<usize> {
10361043
let cause = self.cause(DUMMY_SP, ObligationCauseCode::ExprAssignable);
10371044
// We don't ever need two-phase here since we throw out the result of the coercion
1038-
let coerce = Coerce::new(self, cause, AllowTwoPhase::No);
1045+
let coerce = Coerce::new(self, cause, AllowTwoPhase::No, true);
10391046
coerce
10401047
.autoderef(DUMMY_SP, expr_ty)
10411048
.find_map(|(ty, steps)| self.probe(|_| coerce.unify(ty, target)).ok().map(|_| steps))
@@ -1192,7 +1199,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
11921199
// probably aren't processing function arguments here and even if we were,
11931200
// they're going to get autorefed again anyway and we can apply 2-phase borrows
11941201
// at that time.
1195-
let mut coerce = Coerce::new(self, cause.clone(), AllowTwoPhase::No);
1202+
let mut coerce = Coerce::new(self, cause.clone(), AllowTwoPhase::No, true);
11961203
coerce.use_lub = true;
11971204

11981205
// First try to coerce the new expression to the type of the previous ones,

compiler/rustc_hir_typeck/src/expr.rs

+68-2
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
237237
_ => self.warn_if_unreachable(expr.hir_id, expr.span, "expression"),
238238
}
239239

240-
// Any expression that produces a value of type `!` must have diverged
241-
if ty.is_never() {
240+
// Any expression that produces a value of type `!` must have diverged,
241+
// unless it's the place of a raw ref expr, or a scrutinee of a match.
242+
if ty.is_never() && self.expr_is_rvalue_for_divergence(expr) {
242243
self.diverges.set(self.diverges.get() | Diverges::always(expr.span));
243244
}
244245

@@ -256,6 +257,71 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
256257
ty
257258
}
258259

260+
// FIXME: built-in indexing should be supported here.
261+
/// THIS IS SUBTLE BUT I DONT WANT TO EXPLAIN IT YET.
262+
pub(super) fn expr_is_rvalue_for_divergence(&self, expr: &'tcx hir::Expr<'tcx>) -> bool {
263+
match expr.kind {
264+
ExprKind::Path(QPath::Resolved(
265+
_,
266+
hir::Path {
267+
res: Res::Local(..) | Res::Def(DefKind::Static { .. }, _) | Res::Err,
268+
..
269+
},
270+
))
271+
| ExprKind::Unary(hir::UnOp::Deref, _)
272+
| ExprKind::Field(..)
273+
| ExprKind::Index(..) => {
274+
// All places.
275+
}
276+
277+
_ => return true,
278+
}
279+
280+
// If this expression has any adjustments, they may constitute reads.
281+
if !self.typeck_results.borrow().expr_adjustments(expr).is_empty() {
282+
return true;
283+
}
284+
285+
fn pat_does_read(pat: &hir::Pat<'_>) -> bool {
286+
let mut does_read = false;
287+
pat.walk(|pat| {
288+
if matches!(
289+
pat.kind,
290+
hir::PatKind::Wild | hir::PatKind::Never | hir::PatKind::Or(_)
291+
) {
292+
true
293+
} else {
294+
does_read = true;
295+
false
296+
}
297+
});
298+
does_read
299+
}
300+
301+
match self.tcx.parent_hir_node(expr.hir_id) {
302+
hir::Node::Expr(hir::Expr {
303+
kind: hir::ExprKind::AddrOf(hir::BorrowKind::Raw, ..),
304+
..
305+
}) => false,
306+
hir::Node::Expr(hir::Expr {
307+
kind: hir::ExprKind::Assign(target, _, _) | hir::ExprKind::Field(target, _),
308+
..
309+
}) if expr.hir_id == target.hir_id => false,
310+
hir::Node::LetStmt(hir::LetStmt { init: Some(target), pat, .. })
311+
| hir::Node::Expr(hir::Expr {
312+
kind: hir::ExprKind::Let(hir::LetExpr { init: target, pat, .. }),
313+
..
314+
}) if expr.hir_id == target.hir_id && !pat_does_read(*pat) => false,
315+
hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Match(target, arms, _), .. })
316+
if expr.hir_id == target.hir_id
317+
&& !arms.iter().any(|arm| pat_does_read(arm.pat)) =>
318+
{
319+
false
320+
}
321+
_ => true,
322+
}
323+
}
324+
259325
#[instrument(skip(self, expr), level = "debug")]
260326
fn check_expr_kind(
261327
&self,

compiler/rustc_hir_typeck/src/pat.rs

+9
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use rustc_trait_selection::traits::{ObligationCause, ObligationCauseCode};
2727
use ty::VariantDef;
2828

2929
use super::report_unexpected_variant_res;
30+
use crate::diverges::Diverges;
3031
use crate::gather_locals::DeclOrigin;
3132
use crate::{errors, FnCtxt, LoweredTy};
3233

@@ -276,6 +277,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
276277
}
277278
};
278279

280+
// All other patterns constitute a read, which causes us to diverge
281+
// if the type is never.
282+
if !matches!(pat.kind, PatKind::Wild | PatKind::Never | PatKind::Or(_)) {
283+
if ty.is_never() {
284+
self.diverges.set(self.diverges.get() | Diverges::always(pat.span));
285+
}
286+
}
287+
279288
self.write_ty(pat.hir_id, ty);
280289

281290
// (note_1): In most of the cases where (note_1) is referenced
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#![feature(never_type)]
2+
3+
fn make_up_a_value<T>() -> T {
4+
unsafe {
5+
//~^ ERROR mismatched types
6+
let x: *const ! = 0 as _;
7+
let _: ! = *x;
8+
// Since `*x` "diverges" in HIR, but doesn't count as a read in MIR, this
9+
// is unsound since we act as if it diverges but it doesn't.
10+
}
11+
}
12+
13+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/diverging-place-match.rs:4:5
3+
|
4+
LL | fn make_up_a_value<T>() -> T {
5+
| - expected this type parameter
6+
LL | / unsafe {
7+
LL | |
8+
LL | | let x: *const ! = 0 as _;
9+
LL | | let _: ! = *x;
10+
LL | | // Since `*x` "diverges" in HIR, but doesn't count as a read in MIR, this
11+
LL | | // is unsound since we act as if it diverges but it doesn't.
12+
LL | | }
13+
| |_____^ expected type parameter `T`, found `()`
14+
|
15+
= note: expected type parameter `T`
16+
found unit type `()`
17+
18+
error: aborting due to 1 previous error
19+
20+
For more information about this error, try `rustc --explain E0308`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#![feature(never_type)]
2+
3+
fn make_up_a_value<T>() -> T {
4+
unsafe {
5+
//~^ ERROR mismatched types
6+
let x: *const ! = 0 as _;
7+
&raw const *x;
8+
// Since `*x` is `!`, HIR typeck used to think that it diverges
9+
// and allowed the block to coerce to any value, leading to UB.
10+
}
11+
}
12+
13+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/never-place-isnt-diverging.rs:4:5
3+
|
4+
LL | fn make_up_a_value<T>() -> T {
5+
| - expected this type parameter
6+
LL | / unsafe {
7+
LL | |
8+
LL | | let x: *const ! = 0 as _;
9+
LL | | &raw const *x;
10+
LL | | // Since `*x` is `!`, HIR typeck used to think that it diverges
11+
LL | | // and allowed the block to coerce to any value, leading to UB.
12+
LL | | }
13+
| |_____^ expected type parameter `T`, found `()`
14+
|
15+
= note: expected type parameter `T`
16+
found unit type `()`
17+
18+
error: aborting due to 1 previous error
19+
20+
For more information about this error, try `rustc --explain E0308`.

tests/ui/reachable/expr_assign.stderr

+5-4
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@ LL | #![deny(unreachable_code)]
1414
| ^^^^^^^^^^^^^^^^
1515

1616
error: unreachable expression
17-
--> $DIR/expr_assign.rs:20:14
17+
--> $DIR/expr_assign.rs:20:9
1818
|
1919
LL | *p = return;
20-
| -- ^^^^^^ unreachable expression
21-
| |
22-
| any code following this expression is unreachable
20+
| ^^^^^------
21+
| | |
22+
| | any code following this expression is unreachable
23+
| unreachable expression
2324

2425
error: unreachable expression
2526
--> $DIR/expr_assign.rs:26:15

tests/ui/reachable/unwarned-match-on-never.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ error: unreachable expression
22
--> $DIR/unwarned-match-on-never.rs:10:5
33
|
44
LL | match x {}
5-
| - any code following this expression is unreachable
5+
| ---------- any code following this expression is unreachable
66
LL | // But matches in unreachable code are warned.
77
LL | match x {}
88
| ^^^^^^^^^^ unreachable expression

0 commit comments

Comments
 (0)