From 91293ea4d44f662516ae14b0c91204b3d080fb7c Mon Sep 17 00:00:00 2001 From: Shoyu Vanilla Date: Fri, 11 Oct 2024 02:25:15 +0900 Subject: [PATCH] Do not consider match/let/ref of place that evaluates to ! to diverge, disallow coercions from them too --- crates/hir-ty/src/infer.rs | 3 +- crates/hir-ty/src/infer/cast.rs | 15 +- crates/hir-ty/src/infer/closure.rs | 3 +- crates/hir-ty/src/infer/coerce.rs | 49 +++- crates/hir-ty/src/infer/expr.rs | 404 +++++++++++++++++++++----- crates/hir-ty/src/infer/mutability.rs | 13 +- crates/hir-ty/src/infer/pat.rs | 6 +- crates/hir-ty/src/tests/never_type.rs | 246 ++++++++++++++++ 8 files changed, 637 insertions(+), 102 deletions(-) diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 8bc3c50725..88334b492d 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -57,7 +57,7 @@ use crate::{ db::HirDatabase, fold_tys, generics::Generics, - infer::{coerce::CoerceMany, unify::InferenceTable}, + infer::{coerce::CoerceMany, expr::ExprIsRead, unify::InferenceTable}, lower::ImplTraitLoweringMode, mir::MirSpan, to_assoc_type_id, @@ -1154,6 +1154,7 @@ impl<'a> InferenceContext<'a> { _ = self.infer_expr_coerce( self.body.body_expr, &Expectation::has_type(self.return_ty.clone()), + ExprIsRead::Yes, ) } } diff --git a/crates/hir-ty/src/infer/cast.rs b/crates/hir-ty/src/infer/cast.rs index 5d5bff0864..21d0be6ed5 100644 --- a/crates/hir-ty/src/infer/cast.rs +++ b/crates/hir-ty/src/infer/cast.rs @@ -5,8 +5,9 @@ use hir_def::{hir::ExprId, AdtId}; use stdx::never; use crate::{ - infer::unify::InferenceTable, Adjustment, Binders, DynTy, InferenceDiagnostic, Interner, - PlaceholderIndex, QuantifiedWhereClauses, Ty, TyExt, TyKind, TypeFlags, WhereClause, + infer::{coerce::CoerceNever, unify::InferenceTable}, + Adjustment, Binders, DynTy, InferenceDiagnostic, Interner, PlaceholderIndex, + QuantifiedWhereClauses, Ty, TyExt, TyKind, TypeFlags, WhereClause, }; #[derive(Debug)] @@ -128,7 +129,7 @@ impl CastCheck { return Ok(()); } - if let Ok((adj, _)) = table.coerce(&self.expr_ty, &self.cast_ty) { + if let Ok((adj, _)) = table.coerce(&self.expr_ty, &self.cast_ty, CoerceNever::Yes) { apply_adjustments(self.source_expr, adj); set_coercion_cast(self.source_expr); return Ok(()); @@ -154,7 +155,8 @@ impl CastCheck { let sig = self.expr_ty.callable_sig(table.db).expect("FnDef had no sig"); let sig = table.normalize_associated_types_in(sig); let fn_ptr = TyKind::Function(sig.to_fn_ptr()).intern(Interner); - if let Ok((adj, _)) = table.coerce(&self.expr_ty, &fn_ptr) { + if let Ok((adj, _)) = table.coerce(&self.expr_ty, &fn_ptr, CoerceNever::Yes) + { apply_adjustments(self.source_expr, adj); } else { return Err(CastError::IllegalCast); @@ -241,7 +243,8 @@ impl CastCheck { if let TyKind::Array(ety, _) = t_expr.kind(Interner) { // Coerce to a raw pointer so that we generate RawPtr in MIR. let array_ptr_type = TyKind::Raw(m_expr, t_expr.clone()).intern(Interner); - if let Ok((adj, _)) = table.coerce(&self.expr_ty, &array_ptr_type) { + if let Ok((adj, _)) = table.coerce(&self.expr_ty, &array_ptr_type, CoerceNever::Yes) + { apply_adjustments(self.source_expr, adj); } else { never!( @@ -253,7 +256,7 @@ impl CastCheck { // This is a less strict condition than rustc's `demand_eqtype`, // but false negative is better than false positive - if table.coerce(ety, t_cast).is_ok() { + if table.coerce(ety, t_cast, CoerceNever::Yes).is_ok() { return Ok(()); } } diff --git a/crates/hir-ty/src/infer/closure.rs b/crates/hir-ty/src/infer/closure.rs index 5cad08b939..e9825cf099 100644 --- a/crates/hir-ty/src/infer/closure.rs +++ b/crates/hir-ty/src/infer/closure.rs @@ -29,6 +29,7 @@ use crate::{ db::{HirDatabase, InternedClosure}, error_lifetime, from_chalk_trait_id, from_placeholder_idx, generics::Generics, + infer::coerce::CoerceNever, make_binders, mir::{BorrowKind, MirSpan, MutBorrowKind, ProjectionElem}, to_chalk_trait_id, @@ -65,7 +66,7 @@ impl InferenceContext<'_> { } // Deduction from where-clauses in scope, as well as fn-pointer coercion are handled here. - let _ = self.coerce(Some(closure_expr), closure_ty, &expected_ty); + let _ = self.coerce(Some(closure_expr), closure_ty, &expected_ty, CoerceNever::Yes); // Coroutines are not Fn* so return early. if matches!(closure_ty.kind(Interner), TyKind::Coroutine(..)) { diff --git a/crates/hir-ty/src/infer/coerce.rs b/crates/hir-ty/src/infer/coerce.rs index 7e758c0b51..366c3cb0f1 100644 --- a/crates/hir-ty/src/infer/coerce.rs +++ b/crates/hir-ty/src/infer/coerce.rs @@ -139,8 +139,8 @@ impl CoerceMany { }; if let Some(sig) = sig { let target_ty = TyKind::Function(sig.to_fn_ptr()).intern(Interner); - let result1 = ctx.table.coerce_inner(self.merged_ty(), &target_ty); - let result2 = ctx.table.coerce_inner(expr_ty.clone(), &target_ty); + let result1 = ctx.table.coerce_inner(self.merged_ty(), &target_ty, CoerceNever::Yes); + let result2 = ctx.table.coerce_inner(expr_ty.clone(), &target_ty, CoerceNever::Yes); if let (Ok(result1), Ok(result2)) = (result1, result2) { ctx.table.register_infer_ok(InferOk { value: (), goals: result1.goals }); for &e in &self.expressions { @@ -159,9 +159,9 @@ impl CoerceMany { // type is a type variable and the new one is `!`, trying it the other // way around first would mean we make the type variable `!`, instead of // just marking it as possibly diverging. - if let Ok(res) = ctx.coerce(expr, &expr_ty, &self.merged_ty()) { + if let Ok(res) = ctx.coerce(expr, &expr_ty, &self.merged_ty(), CoerceNever::Yes) { self.final_ty = Some(res); - } else if let Ok(res) = ctx.coerce(expr, &self.merged_ty(), &expr_ty) { + } else if let Ok(res) = ctx.coerce(expr, &self.merged_ty(), &expr_ty, CoerceNever::Yes) { self.final_ty = Some(res); } else { match cause { @@ -197,7 +197,7 @@ pub(crate) fn coerce( let vars = table.fresh_subst(tys.binders.as_slice(Interner)); let ty1_with_vars = vars.apply(tys.value.0.clone(), Interner); let ty2_with_vars = vars.apply(tys.value.1.clone(), Interner); - let (adjustments, ty) = table.coerce(&ty1_with_vars, &ty2_with_vars)?; + let (adjustments, ty) = table.coerce(&ty1_with_vars, &ty2_with_vars, CoerceNever::Yes)?; // default any type vars that weren't unified back to their original bound vars // (kind of hacky) let find_var = |iv| { @@ -219,6 +219,12 @@ pub(crate) fn coerce( Ok((adjustments, table.resolve_with_fallback(ty, &fallback))) } +#[derive(Clone, Copy, PartialEq, Eq)] +pub(crate) enum CoerceNever { + Yes, + No, +} + impl InferenceContext<'_> { /// Unify two types, but may coerce the first one to the second one /// using "implicit coercion rules" if needed. @@ -227,10 +233,16 @@ impl InferenceContext<'_> { expr: Option, from_ty: &Ty, to_ty: &Ty, + // [Comment from rustc](https://github.com/rust-lang/rust/blob/4cc494bbfe9911d24f3ee521f98d5c6bb7e3ffe8/compiler/rustc_hir_typeck/src/coercion.rs#L85-L89) + // Whether we allow `NeverToAny` coercions. This is unsound if we're + // coercing a place expression without it counting as a read in the MIR. + // This is a side-effect of HIR not really having a great distinction + // between places and values. + coerce_never: CoerceNever, ) -> Result { let from_ty = self.resolve_ty_shallow(from_ty); let to_ty = self.resolve_ty_shallow(to_ty); - let (adjustments, ty) = self.table.coerce(&from_ty, &to_ty)?; + let (adjustments, ty) = self.table.coerce(&from_ty, &to_ty, coerce_never)?; if let Some(expr) = expr { self.write_expr_adj(expr, adjustments); } @@ -245,10 +257,11 @@ impl InferenceTable<'_> { &mut self, from_ty: &Ty, to_ty: &Ty, + coerce_never: CoerceNever, ) -> Result<(Vec, Ty), TypeError> { let from_ty = self.resolve_ty_shallow(from_ty); let to_ty = self.resolve_ty_shallow(to_ty); - match self.coerce_inner(from_ty, &to_ty) { + match self.coerce_inner(from_ty, &to_ty, coerce_never) { Ok(InferOk { value: (adjustments, ty), goals }) => { self.register_infer_ok(InferOk { value: (), goals }); Ok((adjustments, ty)) @@ -260,19 +273,23 @@ impl InferenceTable<'_> { } } - fn coerce_inner(&mut self, from_ty: Ty, to_ty: &Ty) -> CoerceResult { + fn coerce_inner(&mut self, from_ty: Ty, to_ty: &Ty, coerce_never: CoerceNever) -> CoerceResult { if from_ty.is_never() { - // Subtle: If we are coercing from `!` to `?T`, where `?T` is an unbound - // type variable, we want `?T` to fallback to `!` if not - // otherwise constrained. An example where this arises: - // - // let _: Option = Some({ return; }); - // - // here, we would coerce from `!` to `?T`. if let TyKind::InferenceVar(tv, TyVariableKind::General) = to_ty.kind(Interner) { self.set_diverging(*tv, true); } - return success(simple(Adjust::NeverToAny)(to_ty.clone()), to_ty.clone(), vec![]); + if coerce_never == CoerceNever::Yes { + // Subtle: If we are coercing from `!` to `?T`, where `?T` is an unbound + // type variable, we want `?T` to fallback to `!` if not + // otherwise constrained. An example where this arises: + // + // let _: Option = Some({ return; }); + // + // here, we would coerce from `!` to `?T`. + return success(simple(Adjust::NeverToAny)(to_ty.clone()), to_ty.clone(), vec![]); + } else { + return self.unify_and(&from_ty, to_ty, identity); + } } // If we are coercing into a TAIT, coerce into its proxy inference var, instead. diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index a04e7b17ae..657e4d7796 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -10,10 +10,11 @@ use either::Either; use hir_def::{ hir::{ ArithOp, Array, AsmOperand, AsmOptions, BinaryOp, ClosureKind, Expr, ExprId, LabelId, - Literal, Statement, UnaryOp, + Literal, Pat, PatId, Statement, UnaryOp, }, lang_item::{LangItem, LangItemTarget}, path::{GenericArg, GenericArgs, Path}, + resolver::ValueNs, BlockId, FieldId, GenericDefId, GenericParamId, ItemContainerId, Lookup, TupleFieldId, TupleId, }; use hir_expand::name::Name; @@ -28,7 +29,7 @@ use crate::{ error_lifetime, generics::{generics, Generics}, infer::{ - coerce::{CoerceMany, CoercionCause}, + coerce::{CoerceMany, CoerceNever, CoercionCause}, find_continuable, pat::contains_explicit_ref_binding, BreakableKind, @@ -52,9 +53,20 @@ use super::{ Expectation, InferenceContext, InferenceDiagnostic, TypeMismatch, }; +#[derive(Clone, Copy, PartialEq, Eq)] +pub(crate) enum ExprIsRead { + Yes, + No, +} + impl InferenceContext<'_> { - pub(crate) fn infer_expr(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { - let ty = self.infer_expr_inner(tgt_expr, expected); + pub(crate) fn infer_expr( + &mut self, + tgt_expr: ExprId, + expected: &Expectation, + is_read: ExprIsRead, + ) -> Ty { + let ty = self.infer_expr_inner(tgt_expr, expected, is_read); if let Some(expected_ty) = expected.only_has_type(&mut self.table) { let could_unify = self.unify(&ty, &expected_ty); if !could_unify { @@ -67,16 +79,26 @@ impl InferenceContext<'_> { ty } - pub(crate) fn infer_expr_no_expect(&mut self, tgt_expr: ExprId) -> Ty { - self.infer_expr_inner(tgt_expr, &Expectation::None) + pub(crate) fn infer_expr_no_expect(&mut self, tgt_expr: ExprId, is_read: ExprIsRead) -> Ty { + self.infer_expr_inner(tgt_expr, &Expectation::None, is_read) } /// Infer type of expression with possibly implicit coerce to the expected type. /// Return the type after possible coercion. - pub(super) fn infer_expr_coerce(&mut self, expr: ExprId, expected: &Expectation) -> Ty { - let ty = self.infer_expr_inner(expr, expected); + pub(super) fn infer_expr_coerce( + &mut self, + expr: ExprId, + expected: &Expectation, + is_read: ExprIsRead, + ) -> Ty { + let ty = self.infer_expr_inner(expr, expected, is_read); if let Some(target) = expected.only_has_type(&mut self.table) { - match self.coerce(Some(expr), &ty, &target) { + let coerce_never = if self.expr_guaranteed_to_constitute_read_for_never(expr, is_read) { + CoerceNever::Yes + } else { + CoerceNever::No + }; + match self.coerce(Some(expr), &ty, &target, coerce_never) { Ok(res) => res, Err(_) => { self.result.type_mismatches.insert( @@ -91,8 +113,137 @@ impl InferenceContext<'_> { } } - fn infer_expr_coerce_never(&mut self, expr: ExprId, expected: &Expectation) -> Ty { - let ty = self.infer_expr_inner(expr, expected); + /// Whether this expression constitutes a read of value of the type that + /// it evaluates to. + /// + /// This is used to determine if we should consider the block to diverge + /// if the expression evaluates to `!`, and if we should insert a `NeverToAny` + /// coercion for values of type `!`. + /// + /// This function generally returns `false` if the expression is a place + /// expression and the *parent* expression is the scrutinee of a match or + /// the pointee of an `&` addr-of expression, since both of those parent + /// expressions take a *place* and not a value. + pub(super) fn expr_guaranteed_to_constitute_read_for_never( + &mut self, + expr: ExprId, + is_read: ExprIsRead, + ) -> bool { + // rustc does the place expr check first, but since we are feeding + // readness of the `expr` as a given value, we just can short-circuit + // the place expr check if it's true(see codes and comments below) + if is_read == ExprIsRead::Yes { + return true; + } + + // We only care about place exprs. Anything else returns an immediate + // which would constitute a read. We don't care about distinguishing + // "syntactic" place exprs since if the base of a field projection is + // not a place then it would've been UB to read from it anyways since + // that constitutes a read. + if !self.is_syntactic_place_expr(expr) { + return true; + } + + // rustc queries parent hir node of `expr` here and determine whether + // the current `expr` is read of value per its parent. + // But since we don't have hir node, we cannot follow such "bottom-up" + // method. + // So, we pass down such readness from the parent expression through the + // recursive `infer_expr*` calls in a "top-down" manner. + is_read == ExprIsRead::Yes + } + + /// Whether this pattern constitutes a read of value of the scrutinee that + /// it is matching against. This is used to determine whether we should + /// perform `NeverToAny` coercions. + fn pat_guaranteed_to_constitute_read_for_never(&self, pat: PatId) -> bool { + match &self.body[pat] { + // Does not constitute a read. + Pat::Wild => false, + + // This is unnecessarily restrictive when the pattern that doesn't + // constitute a read is unreachable. + // + // For example `match *never_ptr { value => {}, _ => {} }` or + // `match *never_ptr { _ if false => {}, value => {} }`. + // + // It is however fine to be restrictive here; only returning `true` + // can lead to unsoundness. + Pat::Or(subpats) => { + subpats.iter().all(|pat| self.pat_guaranteed_to_constitute_read_for_never(*pat)) + } + + // All of these constitute a read, or match on something that isn't `!`, + // which would require a `NeverToAny` coercion. + Pat::Bind { .. } + | Pat::TupleStruct { .. } + | Pat::Path(_) + | Pat::Tuple { .. } + | Pat::Box { .. } + | Pat::Ref { .. } + | Pat::Lit(_) + | Pat::Range { .. } + | Pat::Slice { .. } + | Pat::ConstBlock(_) + | Pat::Record { .. } + | Pat::Missing => true, + } + } + + fn is_syntactic_place_expr(&self, expr: ExprId) -> bool { + match &self.body[expr] { + // Lang item paths cannot currently be local variables or statics. + Expr::Path(Path::LangItem(_, _)) => false, + Expr::Path(Path::Normal { type_anchor: Some(_), .. }) => false, + Expr::Path(path) => self + .resolver + .resolve_path_in_value_ns_fully(self.db.upcast(), path) + .map_or(true, |res| matches!(res, ValueNs::LocalBinding(_) | ValueNs::StaticId(_))), + Expr::Underscore => true, + Expr::UnaryOp { op: UnaryOp::Deref, .. } => true, + Expr::Field { .. } | Expr::Index { .. } => true, + Expr::Call { .. } + | Expr::MethodCall { .. } + | Expr::Tuple { .. } + | Expr::If { .. } + | Expr::Match { .. } + | Expr::Closure { .. } + | Expr::Block { .. } + | Expr::Array(..) + | Expr::Break { .. } + | Expr::Continue { .. } + | Expr::Return { .. } + | Expr::Become { .. } + | Expr::Let { .. } + | Expr::Loop { .. } + | Expr::InlineAsm(..) + | Expr::OffsetOf(..) + | Expr::Literal(..) + | Expr::Const(..) + | Expr::UnaryOp { .. } + | Expr::BinaryOp { .. } + | Expr::Yield { .. } + | Expr::Cast { .. } + | Expr::Async { .. } + | Expr::Unsafe { .. } + | Expr::Await { .. } + | Expr::Ref { .. } + | Expr::Range { .. } + | Expr::Box { .. } + | Expr::RecordLit { .. } + | Expr::Yeet { .. } + | Expr::Missing => false, + } + } + + fn infer_expr_coerce_never( + &mut self, + expr: ExprId, + expected: &Expectation, + is_read: ExprIsRead, + ) -> Ty { + let ty = self.infer_expr_inner(expr, expected, is_read); // While we don't allow *arbitrary* coercions here, we *do* allow // coercions from `!` to `expected`. if ty.is_never() { @@ -105,7 +256,7 @@ impl InferenceContext<'_> { } if let Some(target) = expected.only_has_type(&mut self.table) { - self.coerce(Some(expr), &ty, &target) + self.coerce(Some(expr), &ty, &target, CoerceNever::Yes) .expect("never-to-any coercion should always succeed") } else { ty @@ -124,7 +275,12 @@ impl InferenceContext<'_> { } } - fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { + fn infer_expr_inner( + &mut self, + tgt_expr: ExprId, + expected: &Expectation, + is_read: ExprIsRead, + ) -> Ty { self.db.unwind_if_cancelled(); let ty = match &self.body[tgt_expr] { @@ -134,17 +290,18 @@ impl InferenceContext<'_> { self.infer_expr_coerce_never( condition, &Expectation::HasType(self.result.standard_types.bool_.clone()), + ExprIsRead::Yes, ); let condition_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); - let then_ty = self.infer_expr_inner(then_branch, expected); + let then_ty = self.infer_expr_inner(then_branch, expected, ExprIsRead::Yes); let then_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); let mut coerce = CoerceMany::new(expected.coercion_target_type(&mut self.table)); coerce.coerce(self, Some(then_branch), &then_ty, CoercionCause::Expr(then_branch)); match else_branch { Some(else_branch) => { - let else_ty = self.infer_expr_inner(else_branch, expected); + let else_ty = self.infer_expr_inner(else_branch, expected, ExprIsRead::Yes); let else_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); coerce.coerce( self, @@ -163,7 +320,12 @@ impl InferenceContext<'_> { coerce.complete(self) } &Expr::Let { pat, expr } => { - let input_ty = self.infer_expr(expr, &Expectation::none()); + let child_is_read = if self.pat_guaranteed_to_constitute_read_for_never(pat) { + ExprIsRead::Yes + } else { + ExprIsRead::No + }; + let input_ty = self.infer_expr(expr, &Expectation::none(), child_is_read); self.infer_top_pat(pat, &input_ty); self.result.standard_types.bool_.clone() } @@ -176,7 +338,7 @@ impl InferenceContext<'_> { Expr::Const(id) => { self.with_breakable_ctx(BreakableKind::Border, None, None, |this| { let loc = this.db.lookup_intern_anonymous_const(*id); - this.infer_expr(loc.root, expected) + this.infer_expr(loc.root, expected, ExprIsRead::Yes) }) .1 } @@ -189,7 +351,11 @@ impl InferenceContext<'_> { let ty = self.table.new_type_var(); let (breaks, ()) = self.with_breakable_ctx(BreakableKind::Loop, Some(ty), label, |this| { - this.infer_expr(body, &Expectation::HasType(TyBuilder::unit())); + this.infer_expr( + body, + &Expectation::HasType(TyBuilder::unit()), + ExprIsRead::Yes, + ); }); match breaks { @@ -312,7 +478,7 @@ impl InferenceContext<'_> { ty } Expr::Call { callee, args, .. } => { - let callee_ty = self.infer_expr(*callee, &Expectation::none()); + let callee_ty = self.infer_expr(*callee, &Expectation::none(), ExprIsRead::Yes); let mut derefs = Autoderef::new(&mut self.table, callee_ty.clone(), false); let (res, derefed_callee) = loop { let Some((callee_deref_ty, _)) = derefs.next() else { @@ -393,7 +559,12 @@ impl InferenceContext<'_> { expected, ), Expr::Match { expr, arms } => { - let input_ty = self.infer_expr(*expr, &Expectation::none()); + let scrutinee_is_read = arms + .iter() + .all(|arm| self.pat_guaranteed_to_constitute_read_for_never(arm.pat)); + let scrutinee_is_read = + if scrutinee_is_read { ExprIsRead::Yes } else { ExprIsRead::No }; + let input_ty = self.infer_expr(*expr, &Expectation::none(), scrutinee_is_read); if arms.is_empty() { self.diverges = Diverges::Always; @@ -423,11 +594,12 @@ impl InferenceContext<'_> { self.infer_expr_coerce_never( guard_expr, &Expectation::HasType(self.result.standard_types.bool_.clone()), + ExprIsRead::Yes, ); } self.diverges = Diverges::Maybe; - let arm_ty = self.infer_expr_inner(arm.expr, &expected); + let arm_ty = self.infer_expr_inner(arm.expr, &expected, ExprIsRead::Yes); all_arms_diverge &= self.diverges; coerce.coerce(self, Some(arm.expr), &arm_ty, CoercionCause::Expr(arm.expr)); } @@ -480,7 +652,11 @@ impl InferenceContext<'_> { }, None => self.err_ty(), }; - self.infer_expr_inner(expr, &Expectation::HasType(opt_coerce_to)) + self.infer_expr_inner( + expr, + &Expectation::HasType(opt_coerce_to), + ExprIsRead::Yes, + ) } else { TyBuilder::unit() }; @@ -517,10 +693,14 @@ impl InferenceContext<'_> { Expr::Yield { expr } => { if let Some((resume_ty, yield_ty)) = self.resume_yield_tys.clone() { if let Some(expr) = expr { - self.infer_expr_coerce(*expr, &Expectation::has_type(yield_ty)); + self.infer_expr_coerce( + *expr, + &Expectation::has_type(yield_ty), + ExprIsRead::Yes, + ); } else { let unit = self.result.standard_types.unit.clone(); - let _ = self.coerce(Some(tgt_expr), &unit, &yield_ty); + let _ = self.coerce(Some(tgt_expr), &unit, &yield_ty, CoerceNever::Yes); } resume_ty } else { @@ -530,7 +710,7 @@ impl InferenceContext<'_> { } Expr::Yeet { expr } => { if let &Some(expr) = expr { - self.infer_expr_no_expect(expr); + self.infer_expr_no_expect(expr, ExprIsRead::Yes); } self.result.standard_types.never.clone() } @@ -589,28 +769,37 @@ impl InferenceContext<'_> { // Field type might have some unknown types // FIXME: we may want to emit a single type variable for all instance of type fields? let field_ty = self.insert_type_vars(field_ty); - self.infer_expr_coerce(field.expr, &Expectation::has_type(field_ty)); + self.infer_expr_coerce( + field.expr, + &Expectation::has_type(field_ty), + ExprIsRead::Yes, + ); } } None => { for field in fields.iter() { - self.infer_expr_coerce(field.expr, &Expectation::None); + // Field projections don't constitute reads. + self.infer_expr_coerce(field.expr, &Expectation::None, ExprIsRead::No); } } } if let Some(expr) = spread { - self.infer_expr(*expr, &Expectation::has_type(ty.clone())); + self.infer_expr(*expr, &Expectation::has_type(ty.clone()), ExprIsRead::Yes); } ty } Expr::Field { expr, name } => self.infer_field_access(tgt_expr, *expr, name, expected), Expr::Await { expr } => { - let inner_ty = self.infer_expr_inner(*expr, &Expectation::none()); + let inner_ty = self.infer_expr_inner(*expr, &Expectation::none(), ExprIsRead::Yes); self.resolve_associated_type(inner_ty, self.resolve_future_future_output()) } Expr::Cast { expr, type_ref } => { let cast_ty = self.make_ty(type_ref); - let expr_ty = self.infer_expr(*expr, &Expectation::Castable(cast_ty.clone())); + let expr_ty = self.infer_expr( + *expr, + &Expectation::Castable(cast_ty.clone()), + ExprIsRead::Yes, + ); self.deferred_cast_checks.push(CastCheck::new( tgt_expr, *expr, @@ -638,7 +827,7 @@ impl InferenceContext<'_> { } else { Expectation::none() }; - let inner_ty = self.infer_expr_inner(*expr, &expectation); + let inner_ty = self.infer_expr_inner(*expr, &expectation, ExprIsRead::Yes); match rawness { Rawness::RawPtr => TyKind::Raw(mutability, inner_ty), Rawness::Ref => { @@ -650,7 +839,7 @@ impl InferenceContext<'_> { } &Expr::Box { expr } => self.infer_expr_box(expr, expected), Expr::UnaryOp { expr, op } => { - let inner_ty = self.infer_expr_inner(*expr, &Expectation::none()); + let inner_ty = self.infer_expr_inner(*expr, &Expectation::none(), ExprIsRead::Yes); let inner_ty = self.resolve_ty_shallow(&inner_ty); // FIXME: Note down method resolution her match op { @@ -720,19 +909,32 @@ impl InferenceContext<'_> { // cannot happen in destructuring assignments because of how // they are desugared. if is_ordinary { - let lhs_ty = self.infer_expr(lhs, &Expectation::none()); - self.infer_expr_coerce(*rhs, &Expectation::has_type(lhs_ty)); + // LHS of assignment doesn't constitute reads. + let lhs_ty = self.infer_expr(lhs, &Expectation::none(), ExprIsRead::No); + self.infer_expr_coerce( + *rhs, + &Expectation::has_type(lhs_ty), + ExprIsRead::No, + ); } else { - let rhs_ty = self.infer_expr(*rhs, &Expectation::none()); + let rhs_ty = self.infer_expr(*rhs, &Expectation::none(), ExprIsRead::Yes); self.infer_assignee_expr(lhs, &rhs_ty); } self.result.standard_types.unit.clone() } Some(BinaryOp::LogicOp(_)) => { let bool_ty = self.result.standard_types.bool_.clone(); - self.infer_expr_coerce(*lhs, &Expectation::HasType(bool_ty.clone())); + self.infer_expr_coerce( + *lhs, + &Expectation::HasType(bool_ty.clone()), + ExprIsRead::Yes, + ); let lhs_diverges = self.diverges; - self.infer_expr_coerce(*rhs, &Expectation::HasType(bool_ty.clone())); + self.infer_expr_coerce( + *rhs, + &Expectation::HasType(bool_ty.clone()), + ExprIsRead::Yes, + ); // Depending on the LHS' value, the RHS can never execute. self.diverges = lhs_diverges; bool_ty @@ -741,11 +943,12 @@ impl InferenceContext<'_> { _ => self.err_ty(), }, Expr::Range { lhs, rhs, range_type } => { - let lhs_ty = lhs.map(|e| self.infer_expr_inner(e, &Expectation::none())); + let lhs_ty = + lhs.map(|e| self.infer_expr_inner(e, &Expectation::none(), ExprIsRead::Yes)); let rhs_expect = lhs_ty .as_ref() .map_or_else(Expectation::none, |ty| Expectation::has_type(ty.clone())); - let rhs_ty = rhs.map(|e| self.infer_expr(e, &rhs_expect)); + let rhs_ty = rhs.map(|e| self.infer_expr(e, &rhs_expect, ExprIsRead::Yes)); match (range_type, lhs_ty, rhs_ty) { (RangeOp::Exclusive, None, None) => match self.resolve_range_full() { Some(adt) => TyBuilder::adt(self.db, adt).build(), @@ -779,8 +982,8 @@ impl InferenceContext<'_> { } } Expr::Index { base, index, is_assignee_expr } => { - let base_ty = self.infer_expr_inner(*base, &Expectation::none()); - let index_ty = self.infer_expr(*index, &Expectation::none()); + let base_ty = self.infer_expr_inner(*base, &Expectation::none(), ExprIsRead::Yes); + let index_ty = self.infer_expr(*index, &Expectation::none(), ExprIsRead::Yes); if let Some(index_trait) = self.resolve_lang_trait(LangItem::Index) { let canonicalized = self.canonicalize(base_ty.clone()); @@ -851,7 +1054,11 @@ impl InferenceContext<'_> { }; for (expr, ty) in exprs.iter().zip(tys.iter_mut()) { - *ty = self.infer_expr_coerce(*expr, &Expectation::has_type(ty.clone())); + *ty = self.infer_expr_coerce( + *expr, + &Expectation::has_type(ty.clone()), + ExprIsRead::Yes, + ); } TyKind::Tuple(tys.len(), Substitution::from_iter(Interner, tys)).intern(Interner) @@ -958,7 +1165,7 @@ impl InferenceContext<'_> { Expr::OffsetOf(_) => TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(Interner), Expr::InlineAsm(asm) => { let mut check_expr_asm_operand = |expr, is_input: bool| { - let ty = self.infer_expr_no_expect(expr); + let ty = self.infer_expr_no_expect(expr, ExprIsRead::Yes); // If this is an input value, we require its type to be fully resolved // at this point. This allows us to provide helpful coercions which help @@ -975,11 +1182,11 @@ impl InferenceContext<'_> { CallableSig::from_def(self.db, *def, parameters).to_fn_ptr(), ) .intern(Interner); - _ = self.coerce(Some(expr), &ty, &fnptr_ty); + _ = self.coerce(Some(expr), &ty, &fnptr_ty, CoerceNever::Yes); } TyKind::Ref(mutbl, _, base_ty) => { let ptr_ty = TyKind::Raw(*mutbl, base_ty.clone()).intern(Interner); - _ = self.coerce(Some(expr), &ty, &ptr_ty); + _ = self.coerce(Some(expr), &ty, &ptr_ty, CoerceNever::Yes); } _ => {} } @@ -1016,7 +1223,9 @@ impl InferenceContext<'_> { // use a new type variable if we got unknown here let ty = self.insert_type_vars_shallow(ty); self.write_expr_ty(tgt_expr, ty.clone()); - if self.resolve_ty_shallow(&ty).is_never() { + if self.resolve_ty_shallow(&ty).is_never() + && self.expr_guaranteed_to_constitute_read_for_never(tgt_expr, is_read) + { // Any expression that produces a value of type `!` must have diverged self.diverges = Diverges::Always; } @@ -1041,7 +1250,7 @@ impl InferenceContext<'_> { let (_, inner_ty) = self.with_breakable_ctx(BreakableKind::Border, None, None, |this| { let ty = this.infer_block(tgt_expr, *id, statements, *tail, None, expected); if let Some(target) = expected.only_has_type(&mut this.table) { - match this.coerce(Some(tgt_expr), &ty, &target) { + match this.coerce(Some(tgt_expr), &ty, &target, CoerceNever::Yes) { Ok(res) => res, Err(_) => { this.result.type_mismatches.insert( @@ -1153,7 +1362,7 @@ impl InferenceContext<'_> { Array::ElementList { elements, .. } => { let mut coerce = CoerceMany::new(elem_ty); for &expr in elements.iter() { - let cur_elem_ty = self.infer_expr_inner(expr, &expected); + let cur_elem_ty = self.infer_expr_inner(expr, &expected, ExprIsRead::Yes); coerce.coerce(self, Some(expr), &cur_elem_ty, CoercionCause::Expr(expr)); } ( @@ -1162,13 +1371,17 @@ impl InferenceContext<'_> { ) } &Array::Repeat { initializer, repeat } => { - self.infer_expr_coerce(initializer, &Expectation::has_type(elem_ty.clone())); + self.infer_expr_coerce( + initializer, + &Expectation::has_type(elem_ty.clone()), + ExprIsRead::Yes, + ); let usize = TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(Interner); match self.body[repeat] { Expr::Underscore => { self.write_expr_ty(repeat, usize); } - _ => _ = self.infer_expr(repeat, &Expectation::HasType(usize)), + _ => _ = self.infer_expr(repeat, &Expectation::HasType(usize), ExprIsRead::Yes), } ( @@ -1193,7 +1406,8 @@ impl InferenceContext<'_> { .as_mut() .expect("infer_return called outside function body") .expected_ty(); - let return_expr_ty = self.infer_expr_inner(expr, &Expectation::HasType(ret_ty)); + let return_expr_ty = + self.infer_expr_inner(expr, &Expectation::HasType(ret_ty), ExprIsRead::Yes); let mut coerce_many = self.return_coercion.take().unwrap(); coerce_many.coerce(self, Some(expr), &return_expr_ty, CoercionCause::Expr(expr)); self.return_coercion = Some(coerce_many); @@ -1213,7 +1427,7 @@ impl InferenceContext<'_> { None => { // FIXME: diagnose return outside of function if let Some(expr) = expr { - self.infer_expr_no_expect(expr); + self.infer_expr_no_expect(expr, ExprIsRead::Yes); } } } @@ -1225,8 +1439,11 @@ impl InferenceContext<'_> { Some(return_coercion) => { let ret_ty = return_coercion.expected_ty(); - let call_expr_ty = - self.infer_expr_inner(expr, &Expectation::HasType(ret_ty.clone())); + let call_expr_ty = self.infer_expr_inner( + expr, + &Expectation::HasType(ret_ty.clone()), + ExprIsRead::Yes, + ); // NB: this should *not* coerce. // tail calls don't support any coercions except lifetimes ones (like `&'static u8 -> &'a u8`). @@ -1234,7 +1451,7 @@ impl InferenceContext<'_> { } None => { // FIXME: diagnose `become` outside of functions - self.infer_expr_no_expect(expr); + self.infer_expr_no_expect(expr, ExprIsRead::Yes); } } @@ -1255,7 +1472,7 @@ impl InferenceContext<'_> { }) .unwrap_or_else(Expectation::none); - let inner_ty = self.infer_expr_inner(inner_expr, &inner_exp); + let inner_ty = self.infer_expr_inner(inner_expr, &inner_exp, ExprIsRead::Yes); TyBuilder::adt(self.db, box_id) .push(inner_ty) .fill_with_defaults(self.db, || self.table.new_type_var()) @@ -1333,12 +1550,13 @@ impl InferenceContext<'_> { Expr::Underscore => rhs_ty.clone(), _ => { // `lhs` is a place expression, a unit struct, or an enum variant. - let lhs_ty = self.infer_expr_inner(lhs, &Expectation::none()); + // LHS of assignment doesn't constitute reads. + let lhs_ty = self.infer_expr_inner(lhs, &Expectation::none(), ExprIsRead::No); // This is the only branch where this function may coerce any type. // We are returning early to avoid the unifiability check below. let lhs_ty = self.insert_type_vars_shallow(lhs_ty); - let ty = match self.coerce(None, &rhs_ty, &lhs_ty) { + let ty = match self.coerce(None, &rhs_ty, &lhs_ty, CoerceNever::Yes) { Ok(ty) => ty, Err(_) => { self.result.type_mismatches.insert( @@ -1373,7 +1591,12 @@ impl InferenceContext<'_> { tgt_expr: ExprId, ) -> Ty { let lhs_expectation = Expectation::none(); - let lhs_ty = self.infer_expr(lhs, &lhs_expectation); + let is_read = if matches!(op, BinaryOp::Assignment { .. }) { + ExprIsRead::Yes + } else { + ExprIsRead::No + }; + let lhs_ty = self.infer_expr(lhs, &lhs_expectation, is_read); let rhs_ty = self.table.new_type_var(); let trait_func = lang_items_for_bin_op(op).and_then(|(name, lang_item)| { @@ -1396,7 +1619,7 @@ impl InferenceContext<'_> { self.err_ty() }; - self.infer_expr_coerce(rhs, &Expectation::has_type(rhs_ty)); + self.infer_expr_coerce(rhs, &Expectation::has_type(rhs_ty), ExprIsRead::Yes); return ret_ty; } @@ -1415,7 +1638,7 @@ impl InferenceContext<'_> { let method_ty = self.db.value_ty(func.into()).unwrap().substitute(Interner, &subst); self.register_obligations_for_call(&method_ty); - self.infer_expr_coerce(rhs, &Expectation::has_type(rhs_ty.clone())); + self.infer_expr_coerce(rhs, &Expectation::has_type(rhs_ty.clone()), ExprIsRead::Yes); let ret_ty = match method_ty.callable_sig(self.db) { Some(sig) => { @@ -1487,12 +1710,25 @@ impl InferenceContext<'_> { .unwrap_or_else(|| this.table.new_type_var()); let ty = if let Some(expr) = initializer { + // If we have a subpattern that performs a read, we want to consider this + // to diverge for compatibility to support something like `let x: () = *never_ptr;`. + let target_is_read = + if this.pat_guaranteed_to_constitute_read_for_never(*pat) { + ExprIsRead::Yes + } else { + ExprIsRead::No + }; let ty = if contains_explicit_ref_binding(this.body, *pat) { - this.infer_expr(*expr, &Expectation::has_type(decl_ty.clone())) + this.infer_expr( + *expr, + &Expectation::has_type(decl_ty.clone()), + target_is_read, + ) } else { this.infer_expr_coerce( *expr, &Expectation::has_type(decl_ty.clone()), + target_is_read, ) }; if type_ref.is_some() { @@ -1512,17 +1748,19 @@ impl InferenceContext<'_> { this.infer_expr_coerce( *expr, &Expectation::HasType(this.result.standard_types.never.clone()), + ExprIsRead::Yes, ); this.diverges = previous_diverges; } } &Statement::Expr { expr, has_semi } => { if has_semi { - this.infer_expr(expr, &Expectation::none()); + this.infer_expr(expr, &Expectation::none(), ExprIsRead::Yes); } else { this.infer_expr_coerce( expr, &Expectation::HasType(this.result.standard_types.unit.clone()), + ExprIsRead::Yes, ); } } @@ -1532,7 +1770,7 @@ impl InferenceContext<'_> { // FIXME: This should make use of the breakable CoerceMany if let Some(expr) = tail { - this.infer_expr_coerce(expr, expected) + this.infer_expr_coerce(expr, expected, ExprIsRead::Yes) } else { // Citing rustc: if there is no explicit tail expression, // that is typically equivalent to a tail expression @@ -1545,8 +1783,20 @@ impl InferenceContext<'_> { // we don't even make an attempt at coercion this.table.new_maybe_never_var() } else if let Some(t) = expected.only_has_type(&mut this.table) { + let coerce_never = if this + .expr_guaranteed_to_constitute_read_for_never(expr, ExprIsRead::Yes) + { + CoerceNever::Yes + } else { + CoerceNever::No + }; if this - .coerce(Some(expr), &this.result.standard_types.unit.clone(), &t) + .coerce( + Some(expr), + &this.result.standard_types.unit.clone(), + &t, + coerce_never, + ) .is_err() { this.result.type_mismatches.insert( @@ -1658,7 +1908,8 @@ impl InferenceContext<'_> { name: &Name, expected: &Expectation, ) -> Ty { - let receiver_ty = self.infer_expr_inner(receiver, &Expectation::none()); + // Field projections don't constitute reads. + let receiver_ty = self.infer_expr_inner(receiver, &Expectation::none(), ExprIsRead::No); if name.is_missing() { // Bail out early, don't even try to look up field. Also, we don't issue an unresolved @@ -1730,7 +1981,7 @@ impl InferenceContext<'_> { generic_args: Option<&GenericArgs>, expected: &Expectation, ) -> Ty { - let receiver_ty = self.infer_expr_inner(receiver, &Expectation::none()); + let receiver_ty = self.infer_expr_inner(receiver, &Expectation::none(), ExprIsRead::Yes); let canonicalized_receiver = self.canonicalize(receiver_ty.clone()); let resolved = method_resolution::lookup_method( @@ -1917,7 +2168,7 @@ impl InferenceContext<'_> { let expected_ty = self.normalize_associated_types_in(expected_ty); let expected = Expectation::rvalue_hint(self, expected_ty); // infer with the expected type we have... - let ty = self.infer_expr_inner(arg, &expected); + let ty = self.infer_expr_inner(arg, &expected, ExprIsRead::Yes); // then coerce to either the expected type or just the formal parameter type let coercion_target = if let Some(ty) = expected.only_has_type(&mut self.table) { @@ -1931,7 +2182,20 @@ impl InferenceContext<'_> { // The function signature may contain some unknown types, so we need to insert // type vars here to avoid type mismatch false positive. let coercion_target = self.insert_type_vars(coercion_target); - if self.coerce(Some(arg), &ty, &coercion_target).is_err() && !arg_count_mismatch { + + // Any expression that produces a value of type `!` must have diverged, + // unless it's a place expression that isn't being read from, in which case + // diverging would be unsound since we may never actually read the `!`. + // e.g. `let _ = *never_ptr;` with `never_ptr: *const !`. + let coerce_never = + if self.expr_guaranteed_to_constitute_read_for_never(arg, ExprIsRead::Yes) { + CoerceNever::Yes + } else { + CoerceNever::No + }; + if self.coerce(Some(arg), &ty, &coercion_target, coerce_never).is_err() + && !arg_count_mismatch + { self.result.type_mismatches.insert( arg.into(), TypeMismatch { expected: coercion_target, actual: ty.clone() }, @@ -2106,7 +2370,7 @@ impl InferenceContext<'_> { } let _ty = arg.data(Interner).ty.clone(); let expected = Expectation::none(); // FIXME use actual const ty, when that is lowered correctly - self.infer_expr(args[arg_idx as usize], &expected); + self.infer_expr(args[arg_idx as usize], &expected, ExprIsRead::Yes); // FIXME: evaluate and unify with the const } let mut indices = legacy_const_generics_indices.as_ref().clone(); diff --git a/crates/hir-ty/src/infer/mutability.rs b/crates/hir-ty/src/infer/mutability.rs index 8dcaa9c581..6a0daee6ea 100644 --- a/crates/hir-ty/src/infer/mutability.rs +++ b/crates/hir-ty/src/infer/mutability.rs @@ -12,12 +12,11 @@ use hir_expand::name::Name; use intern::sym; use crate::{ - infer::Expectation, lower::lower_to_chalk_mutability, Adjust, Adjustment, AutoBorrow, Interner, - OverloadedDeref, TyBuilder, TyKind, + infer::{expr::ExprIsRead, Expectation, InferenceContext}, + lower::lower_to_chalk_mutability, + Adjust, Adjustment, AutoBorrow, Interner, OverloadedDeref, TyBuilder, TyKind, }; -use super::InferenceContext; - impl InferenceContext<'_> { pub(crate) fn infer_mut_body(&mut self) { self.infer_mut_expr(self.body.body_expr, Mutability::Not); @@ -164,7 +163,11 @@ impl InferenceContext<'_> { if let Some(ty) = self.result.type_of_expr.get(index) { ty.clone() } else { - self.infer_expr(index, &Expectation::none()) + self.infer_expr( + index, + &Expectation::none(), + ExprIsRead::Yes, + ) }; let trait_ref = TyBuilder::trait_ref(self.db, index_trait) .push(base_ty) diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs index 50542b2acd..fee6755408 100644 --- a/crates/hir-ty/src/infer/pat.rs +++ b/crates/hir-ty/src/infer/pat.rs @@ -12,7 +12,7 @@ use stdx::TupleExt; use crate::{ consteval::{try_const_usize, usize_const}, - infer::{BindingMode, Expectation, InferenceContext, TypeMismatch}, + infer::{expr::ExprIsRead, BindingMode, Expectation, InferenceContext, TypeMismatch}, lower::lower_to_chalk_mutability, primitive::UintTy, static_lifetime, InferenceDiagnostic, Interner, Mutability, Scalar, Substitution, Ty, @@ -361,7 +361,7 @@ impl InferenceContext<'_> { None => self.err_ty(), }, Pat::ConstBlock(expr) => { - self.infer_expr(*expr, &Expectation::has_type(expected.clone())) + self.infer_expr(*expr, &Expectation::has_type(expected.clone()), ExprIsRead::Yes) } Pat::Missing => self.err_ty(), }; @@ -497,7 +497,7 @@ impl InferenceContext<'_> { } } - self.infer_expr(expr, &Expectation::has_type(expected.clone())) + self.infer_expr(expr, &Expectation::has_type(expected.clone()), ExprIsRead::Yes) } fn is_non_ref_pat(&mut self, body: &hir_def::body::Body, pat: PatId) -> bool { diff --git a/crates/hir-ty/src/tests/never_type.rs b/crates/hir-ty/src/tests/never_type.rs index 5c63cd00f9..1ca4c9b2ad 100644 --- a/crates/hir-ty/src/tests/never_type.rs +++ b/crates/hir-ty/src/tests/never_type.rs @@ -539,3 +539,249 @@ fn test() { "#, ); } + +#[test] +fn diverging_place_match1() { + check_infer_with_mismatches( + r#" +//- minicore: sized +fn not_a_read() -> ! { + unsafe { + let x: *const ! = 0 as _; + let _: ! = *x; + } +} +"#, + expect![[r#" + 21..100 '{ ... } }': ! + 27..98 'unsafe... }': ! + 48..49 'x': *const ! + 62..63 '0': i32 + 62..68 '0 as _': *const ! + 82..83 '_': ! + 89..91 '*x': ! + 90..91 'x': *const ! + 27..98: expected !, got () + "#]], + ) +} + +#[test] +fn diverging_place_match2() { + check_infer_with_mismatches( + r#" +//- minicore: sized +fn not_a_read_implicit() -> ! { + unsafe { + let x: *const ! = 0 as _; + let _ = *x; + } +} +"#, + expect![[r#" + 30..106 '{ ... } }': ! + 36..104 'unsafe... }': ! + 57..58 'x': *const ! + 71..72 '0': i32 + 71..77 '0 as _': *const ! + 91..92 '_': ! + 95..97 '*x': ! + 96..97 'x': *const ! + 36..104: expected !, got () + "#]], + ) +} + +#[test] +fn diverging_place_match3() { + check_infer_with_mismatches( + r#" +//- minicore: sized +fn not_a_read_guide_coercion() -> ! { + unsafe { + let x: *const ! = 0 as _; + let _: () = *x; + } +} +"#, + expect![[r#" + 36..116 '{ ... } }': ! + 42..114 'unsafe... }': ! + 63..64 'x': *const ! + 77..78 '0': i32 + 77..83 '0 as _': *const ! + 97..98 '_': () + 105..107 '*x': ! + 106..107 'x': *const ! + 42..114: expected !, got () + 105..107: expected (), got ! + "#]], + ) +} + +#[test] +fn diverging_place_match4() { + check_infer_with_mismatches( + r#" +//- minicore: sized +fn empty_match() -> ! { + unsafe { + let x: *const ! = 0 as _; + match *x { _ => {} }; + } +} +"#, + expect![[r#" + 22..108 '{ ... } }': ! + 28..106 'unsafe... }': ! + 49..50 'x': *const ! + 63..64 '0': i32 + 63..69 '0 as _': *const ! + 79..99 'match ...> {} }': () + 85..87 '*x': ! + 86..87 'x': *const ! + 90..91 '_': ! + 95..97 '{}': () + 28..106: expected !, got () + "#]], + ) +} + +#[test] +fn diverging_place_match5() { + check_infer_with_mismatches( + r#" +//- minicore: sized +fn field_projection() -> ! { + unsafe { + let x: *const (!, ()) = 0 as _; + let _ = (*x).0; + } +} +"#, + expect![[r#" + 27..113 '{ ... } }': ! + 33..111 'unsafe... }': ! + 54..55 'x': *const (!, ()) + 74..75 '0': i32 + 74..80 '0 as _': *const (!, ()) + 94..95 '_': ! + 98..104 '(*x).0': ! + 99..101 '*x': (!, ()) + 100..101 'x': *const (!, ()) + 33..111: expected !, got () + "#]], + ) +} + +#[test] +fn diverging_place_match6() { + check_infer_with_mismatches( + r#" +//- minicore: sized +fn covered_arm() -> ! { + unsafe { + let x: *const ! = 0 as _; + let (_ | 1i32) = *x; + } +} +"#, + expect![[r#" + 22..107 '{ ... } }': ! + 28..105 'unsafe... }': ! + 49..50 'x': *const ! + 63..64 '0': i32 + 63..69 '0 as _': *const ! + 84..85 '_': ! + 84..92 '_ | 1i32': ! + 88..92 '1i32': i32 + 88..92 '1i32': i32 + 96..98 '*x': ! + 97..98 'x': *const ! + 28..105: expected !, got () + 88..92: expected !, got i32 + "#]], + ) +} + +#[test] +fn diverging_place_match7() { + check_infer_with_mismatches( + r#" +//- minicore: sized +fn uncovered_arm() -> ! { + unsafe { + let x: *const ! = 0 as _; + let (1i32 | _) = *x; + } +} +"#, + expect![[r#" + 24..109 '{ ... } }': ! + 30..107 'unsafe... }': ! + 51..52 'x': *const ! + 65..66 '0': i32 + 65..71 '0 as _': *const ! + 86..90 '1i32': i32 + 86..90 '1i32': i32 + 86..94 '1i32 | _': ! + 93..94 '_': ! + 98..100 '*x': ! + 99..100 'x': *const ! + 30..107: expected !, got () + 86..90: expected !, got i32 + "#]], + ) +} + +#[test] +fn diverging_place_match8() { + check_infer_with_mismatches( + r#" +//- minicore: sized +fn coerce_ref_binding() -> ! { + unsafe { + let x: *const ! = 0 as _; + let ref _x: () = *x; + } +} +"#, + expect![[r#" + 29..114 '{ ... } }': ! + 35..112 'unsafe... }': ! + 56..57 'x': *const ! + 70..71 '0': i32 + 70..76 '0 as _': *const ! + 90..96 'ref _x': &'? () + 103..105 '*x': ! + 104..105 'x': *const ! + 103..105: expected (), got ! + "#]], + ) +} + +#[test] +fn never_place_isnt_diverging() { + check_infer_with_mismatches( + r#" +//- minicore: sized +fn make_up_a_pointer() -> *const T { + unsafe { + let x: *const ! = 0 as _; + &raw const *x + } +} +"#, + expect![[r#" + 38..116 '{ ... } }': *const T + 44..114 'unsafe... }': *const T + 65..66 'x': *const ! + 79..80 '0': i32 + 79..85 '0 as _': *const ! + 95..108 '&raw const *x': *const ! + 106..108 '*x': ! + 107..108 'x': *const ! + 95..108: expected *const T, got *const ! + "#]], + ) +}