Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-ty/src/infer/expr.rs')
| -rw-r--r-- | crates/hir-ty/src/infer/expr.rs | 1029 |
1 files changed, 361 insertions, 668 deletions
diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index b7ab109b3b..7487660a76 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -3,19 +3,18 @@ use std::{iter::repeat_with, mem}; use either::Either; -use hir_def::hir::ClosureKind; use hir_def::{ - BlockId, FieldId, GenericDefId, GenericParamId, ItemContainerId, Lookup, TupleFieldId, TupleId, - expr_store::path::{GenericArg as HirGenericArg, GenericArgs as HirGenericArgs, Path}, + BlockId, FieldId, GenericDefId, ItemContainerId, Lookup, TupleFieldId, TupleId, + expr_store::path::{GenericArgs as HirGenericArgs, Path}, hir::{ - ArithOp, Array, AsmOperand, AsmOptions, BinaryOp, Expr, ExprId, ExprOrPatId, LabelId, - Literal, Pat, PatId, Statement, UnaryOp, generics::GenericParamDataRef, + Array, AsmOperand, AsmOptions, BinaryOp, BindingAnnotation, Expr, ExprId, ExprOrPatId, + LabelId, Literal, Pat, PatId, Statement, UnaryOp, }, lang_item::{LangItem, LangItemTarget}, resolver::ValueNs, }; +use hir_def::{FunctionId, hir::ClosureKind}; use hir_expand::name::Name; -use intern::sym; use rustc_ast_ir::Mutability; use rustc_type_ir::{ CoroutineArgs, CoroutineArgsParts, InferTy, Interner, @@ -25,32 +24,25 @@ use syntax::ast::RangeOp; use tracing::debug; use crate::{ - Adjust, Adjustment, AutoBorrow, CallableDefId, DeclContext, DeclOrigin, - IncorrectGenericsLenKind, Rawness, TraitEnvironment, - autoderef::overloaded_deref_ty, + Adjust, Adjustment, CallableDefId, DeclContext, DeclOrigin, Rawness, TraitEnvironment, + autoderef::InferenceContextAutoderef, consteval, db::InternedCoroutine, generics::generics, infer::{ - AllowTwoPhase, BreakableKind, - coerce::{CoerceMany, CoerceNever}, - find_continuable, + AllowTwoPhase, BreakableKind, coerce::CoerceMany, find_continuable, pat::contains_explicit_ref_binding, }, - lang_items::lang_items_for_bin_op, - lower::{ - LifetimeElisionKind, lower_mutability, - path::{GenericArgsLowerer, TypeLikeConst, substs_from_args_and_bindings}, - }, - method_resolution::{self, VisibleFromModule}, + lower::{GenericPredicates, lower_mutability}, + method_resolution::{self, CandidateId, MethodCallee, MethodError}, next_solver::{ - Const, DbInterner, ErrorGuaranteed, GenericArg, GenericArgs, TraitRef, Ty, TyKind, - TypeError, + ErrorGuaranteed, FnSig, GenericArgs, TraitRef, Ty, TyKind, TypeError, infer::{ - InferOk, + BoundRegionConversionTime, InferOk, traits::{Obligation, ObligationCause}, }, obligation_ctxt::ObligationCtxt, + util::clauses_as_obligations, }, traits::FnTrait, }; @@ -103,12 +95,7 @@ impl<'db> InferenceContext<'_, 'db> { ) -> Ty<'db> { let ty = self.infer_expr_inner(expr, expected, is_read); if let Some(target) = expected.only_has_type(&mut self.table) { - let coerce_never = if self.expr_guaranteed_to_constitute_read_for_never(expr, is_read) { - CoerceNever::Yes - } else { - CoerceNever::No - }; - match self.coerce(expr.into(), ty, target, AllowTwoPhase::No, coerce_never) { + match self.coerce(expr.into(), ty, target, AllowTwoPhase::No, is_read) { Ok(res) => res, Err(_) => { self.result @@ -203,6 +190,23 @@ impl<'db> InferenceContext<'_, 'db> { } } + /// Checks if the pattern contains any `ref` or `ref mut` bindings, and if + /// yes whether it contains mutable or just immutables ones. + // + // FIXME(tschottdorf): this is problematic as the HIR is being scraped, but + // ref bindings are be implicit after #42640 (default match binding modes). See issue #44848. + fn contains_explicit_ref_binding(&self, pat: PatId) -> bool { + if let Pat::Bind { id, .. } = self.body[pat] + && matches!(self.body[id].mode, BindingAnnotation::Ref | BindingAnnotation::RefMut) + { + return true; + } + + let mut result = false; + self.body.walk_pats_shallow(pat, |pat| result |= self.contains_explicit_ref_binding(pat)); + result + } + fn is_syntactic_place_expr(&self, expr: ExprId) -> bool { match &self.body[expr] { // Lang item paths cannot currently be local variables or statics. @@ -250,6 +254,15 @@ impl<'db> InferenceContext<'_, 'db> { } } + #[expect(clippy::needless_return)] + pub(crate) fn check_lhs_assignable(&self, lhs: ExprId) { + if self.is_syntactic_place_expr(lhs) { + return; + } + + // FIXME: Emit diagnostic. + } + fn infer_expr_coerce_never( &mut self, expr: ExprId, @@ -269,7 +282,7 @@ impl<'db> InferenceContext<'_, 'db> { } if let Some(target) = expected.only_has_type(&mut self.table) { - self.coerce(expr.into(), ty, target, AllowTwoPhase::No, CoerceNever::Yes) + self.coerce(expr.into(), ty, target, AllowTwoPhase::No, ExprIsRead::Yes) .expect("never-to-any coercion should always succeed") } else { ty @@ -320,16 +333,28 @@ impl<'db> InferenceContext<'_, 'db> { expected.coercion_target_type(&mut self.table), &coercion_sites, ); - coerce.coerce(self, &ObligationCause::new(), then_branch, then_ty); + coerce.coerce(self, &ObligationCause::new(), then_branch, then_ty, ExprIsRead::Yes); match else_branch { Some(else_branch) => { 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, &ObligationCause::new(), else_branch, else_ty); + coerce.coerce( + self, + &ObligationCause::new(), + else_branch, + else_ty, + ExprIsRead::Yes, + ); self.diverges = condition_diverges | then_diverges & else_diverges; } None => { - coerce.coerce_forced_unit(self, tgt_expr, &ObligationCause::new(), true); + coerce.coerce_forced_unit( + self, + tgt_expr, + &ObligationCause::new(), + true, + ExprIsRead::Yes, + ); self.diverges = condition_diverges; } } @@ -407,12 +432,20 @@ impl<'db> InferenceContext<'_, 'db> { expected, ), Expr::Match { expr, arms } => { - let scrutinee_is_read = arms - .iter() - .all(|arm| self.pat_guaranteed_to_constitute_read_for_never(arm.pat)); + let mut scrutinee_is_read = true; + let mut contains_ref_bindings = false; + for arm in arms { + scrutinee_is_read &= self.pat_guaranteed_to_constitute_read_for_never(arm.pat); + contains_ref_bindings |= self.contains_explicit_ref_binding(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); + let input_ty = self.demand_scrutinee_type( + *expr, + contains_ref_bindings, + arms.is_empty(), + scrutinee_is_read, + ); if arms.is_empty() { self.diverges = Diverges::Always; @@ -421,7 +454,6 @@ impl<'db> InferenceContext<'_, 'db> { let matchee_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); let mut all_arms_diverge = Diverges::Always; for arm in arms.iter() { - let input_ty = self.table.structurally_resolve_type(input_ty); self.infer_top_pat(arm.pat, input_ty, None); } @@ -447,7 +479,13 @@ impl<'db> InferenceContext<'_, 'db> { let arm_ty = self.infer_expr_inner(arm.expr, &expected, ExprIsRead::Yes); all_arms_diverge &= self.diverges; - coerce.coerce(self, &ObligationCause::new(), arm.expr, arm_ty); + coerce.coerce( + self, + &ObligationCause::new(), + arm.expr, + arm_ty, + ExprIsRead::Yes, + ); } self.diverges = matchee_diverges | all_arms_diverge; @@ -499,6 +537,7 @@ impl<'db> InferenceContext<'_, 'db> { &ObligationCause::new(), expr.unwrap_or(tgt_expr), val_ty, + ExprIsRead::Yes, ); // Avoiding borrowck @@ -536,7 +575,7 @@ impl<'db> InferenceContext<'_, 'db> { unit, yield_ty, AllowTwoPhase::No, - CoerceNever::Yes, + ExprIsRead::Yes, ); } resume_ty @@ -662,70 +701,13 @@ impl<'db> InferenceContext<'_, 'db> { } } &Expr::Box { expr } => self.infer_expr_box(expr, expected), - Expr::UnaryOp { expr, op } => { - let inner_ty = self.infer_expr_inner(*expr, &Expectation::none(), ExprIsRead::Yes); - let inner_ty = self.table.try_structurally_resolve_type(inner_ty); - // FIXME: Note down method resolution her - match op { - UnaryOp::Deref => { - if let Some(deref_trait) = self.resolve_lang_trait(LangItem::Deref) - && let Some(deref_fn) = deref_trait - .trait_items(self.db) - .method_by_name(&Name::new_symbol_root(sym::deref)) - { - // FIXME: this is wrong in multiple ways, subst is empty, and we emit it even for builtin deref (note that - // the mutability is not wrong, and will be fixed in `self.infer_mut`). - self.write_method_resolution(tgt_expr, deref_fn, self.types.empty_args); - } - if let Some(derefed) = inner_ty.builtin_deref(self.db, true) { - self.table.try_structurally_resolve_type(derefed) - } else { - let infer_ok = overloaded_deref_ty(&self.table, inner_ty); - match infer_ok { - Some(infer_ok) => self.table.register_infer_ok(infer_ok), - None => self.err_ty(), - } - } - } - UnaryOp::Neg => { - match inner_ty.kind() { - // Fast path for builtins - TyKind::Int(_) - | TyKind::Uint(_) - | TyKind::Float(_) - | TyKind::Infer(InferTy::IntVar(_) | InferTy::FloatVar(_)) => inner_ty, - // Otherwise we resolve via the std::ops::Neg trait - _ => self - .resolve_associated_type(inner_ty, self.resolve_ops_neg_output()), - } - } - UnaryOp::Not => { - match inner_ty.kind() { - // Fast path for builtins - TyKind::Bool - | TyKind::Int(_) - | TyKind::Uint(_) - | TyKind::Float(_) - | TyKind::Infer(InferTy::IntVar(_) | InferTy::FloatVar(_)) => inner_ty, - // Otherwise we resolve via the std::ops::Not trait - _ => self - .resolve_associated_type(inner_ty, self.resolve_ops_not_output()), - } - } - } - } + Expr::UnaryOp { expr, op } => self.infer_unop_expr(*op, *expr, expected, tgt_expr), Expr::BinaryOp { lhs, rhs, op } => match op { - Some(BinaryOp::LogicOp(_)) => { - let bool_ty = self.types.bool; - self.infer_expr_coerce(*lhs, &Expectation::HasType(bool_ty), ExprIsRead::Yes); - let lhs_diverges = self.diverges; - self.infer_expr_coerce(*rhs, &Expectation::HasType(bool_ty), ExprIsRead::Yes); - // Depending on the LHS' value, the RHS can never execute. - self.diverges = lhs_diverges; - bool_ty + Some(BinaryOp::Assignment { op: Some(op) }) => { + self.infer_assign_op_expr(tgt_expr, *op, *lhs, *rhs) } - Some(op) => self.infer_overloadable_binop(*lhs, *op, *rhs, tgt_expr), - _ => self.err_ty(), + Some(op) => self.infer_binop_expr(tgt_expr, *op, *lhs, *rhs), + None => self.err_ty(), }, &Expr::Assignment { target, value } => { // In ordinary (non-destructuring) assignments, the type of @@ -790,8 +772,7 @@ impl<'db> InferenceContext<'_, 'db> { Expr::Range { lhs, rhs, range_type } => { 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)); + let rhs_expect = lhs_ty.map_or_else(Expectation::none, Expectation::has_type); let rhs_ty = rhs.map(|e| self.infer_expr(e, &rhs_expect, ExprIsRead::Yes)); let single_arg_adt = |adt, ty: Ty<'db>| { Ty::new_adt( @@ -833,55 +814,39 @@ impl<'db> InferenceContext<'_, 'db> { } } Expr::Index { base, index } => { - 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); - let receiver_adjustments = method_resolution::resolve_indexing_op( - &mut self.table, - canonicalized, - index_trait, - ); - let (self_ty, mut adj) = receiver_adjustments - .map_or((self.err_ty(), Vec::new()), |adj| { - adj.apply(&mut self.table, base_ty) - }); - - // mutability will be fixed up in `InferenceContext::infer_mut`; - adj.push(Adjustment::borrow( - self.interner(), - Mutability::Not, - self_ty, - self.table.next_region_var(), - )); - self.write_expr_adj(*base, adj.into_boxed_slice()); - if let Some(func) = index_trait - .trait_items(self.db) - .method_by_name(&Name::new_symbol_root(sym::index)) - { - let subst = GenericArgs::new_from_iter( - self.interner(), - [self_ty.into(), index_ty.into()], + let base_t = self.infer_expr_no_expect(*base, ExprIsRead::Yes); + let idx_t = self.infer_expr_no_expect(*index, ExprIsRead::Yes); + + let base_t = self.table.structurally_resolve_type(base_t); + match self.lookup_indexing(tgt_expr, *base, base_t, idx_t) { + Some((trait_index_ty, trait_element_ty)) => { + // two-phase not needed because index_ty is never mutable + self.demand_coerce( + *index, + idx_t, + trait_index_ty, + AllowTwoPhase::No, + ExprIsRead::Yes, ); - self.write_method_resolution(tgt_expr, func, subst); + self.table.select_obligations_where_possible(); + trait_element_ty } - let assoc = self.resolve_ops_index_output(); - self.resolve_associated_type_with_params(self_ty, assoc, &[index_ty.into()]) - } else { - self.err_ty() + // FIXME: Report an error. + None => self.types.error, } } Expr::Tuple { exprs, .. } => { - let mut tys = - match expected.only_has_type(&mut self.table).as_ref().map(|t| t.kind()) { - Some(TyKind::Tuple(substs)) => substs - .iter() - .chain(repeat_with(|| self.table.next_ty_var())) - .take(exprs.len()) - .collect::<Vec<_>>(), - _ => (0..exprs.len()).map(|_| self.table.next_ty_var()).collect(), - }; + let mut tys = match expected + .only_has_type(&mut self.table) + .map(|t| self.table.try_structurally_resolve_type(t).kind()) + { + Some(TyKind::Tuple(substs)) => substs + .iter() + .chain(repeat_with(|| self.table.next_ty_var())) + .take(exprs.len()) + .collect::<Vec<_>>(), + _ => (0..exprs.len()).map(|_| self.table.next_ty_var()).collect(), + }; for (expr, ty) in exprs.iter().zip(tys.iter_mut()) { *ty = @@ -1014,7 +979,7 @@ impl<'db> InferenceContext<'_, 'db> { ty, fnptr_ty, AllowTwoPhase::No, - CoerceNever::Yes, + ExprIsRead::Yes, ); } TyKind::Ref(_, base_ty, mutbl) => { @@ -1024,7 +989,7 @@ impl<'db> InferenceContext<'_, 'db> { ty, ptr_ty, AllowTwoPhase::No, - CoerceNever::Yes, + ExprIsRead::Yes, ); } _ => {} @@ -1073,6 +1038,77 @@ impl<'db> InferenceContext<'_, 'db> { ty } + fn demand_scrutinee_type( + &mut self, + scrut: ExprId, + contains_ref_bindings: bool, + no_arms: bool, + scrutinee_is_read: ExprIsRead, + ) -> Ty<'db> { + // Not entirely obvious: if matches may create ref bindings, we want to + // use the *precise* type of the scrutinee, *not* some supertype, as + // the "scrutinee type" (issue #23116). + // + // arielb1 [writes here in this comment thread][c] that there + // is certainly *some* potential danger, e.g., for an example + // like: + // + // [c]: https://github.com/rust-lang/rust/pull/43399#discussion_r130223956 + // + // ``` + // let Foo(x) = f()[0]; + // ``` + // + // Then if the pattern matches by reference, we want to match + // `f()[0]` as a lexpr, so we can't allow it to be + // coerced. But if the pattern matches by value, `f()[0]` is + // still syntactically a lexpr, but we *do* want to allow + // coercions. + // + // However, *likely* we are ok with allowing coercions to + // happen if there are no explicit ref mut patterns - all + // implicit ref mut patterns must occur behind a reference, so + // they will have the "correct" variance and lifetime. + // + // This does mean that the following pattern would be legal: + // + // ``` + // struct Foo(Bar); + // struct Bar(u32); + // impl Deref for Foo { + // type Target = Bar; + // fn deref(&self) -> &Bar { &self.0 } + // } + // impl DerefMut for Foo { + // fn deref_mut(&mut self) -> &mut Bar { &mut self.0 } + // } + // fn foo(x: &mut Foo) { + // { + // let Bar(z): &mut Bar = x; + // *z = 42; + // } + // assert_eq!(foo.0.0, 42); + // } + // ``` + // + // FIXME(tschottdorf): don't call contains_explicit_ref_binding, which + // is problematic as the HIR is being scraped, but ref bindings may be + // implicit after #42640. We need to make sure that pat_adjustments + // (once introduced) is populated by the time we get here. + // + // See #44848. + if contains_ref_bindings || no_arms { + self.infer_expr_no_expect(scrut, scrutinee_is_read) + } else { + // ...but otherwise we want to use any supertype of the + // scrutinee. This is sort of a workaround, see note (*) in + // `check_pat` for some details. + let scrut_ty = self.table.next_ty_var(); + self.infer_expr_coerce_never(scrut, &Expectation::HasType(scrut_ty), scrutinee_is_read); + scrut_ty + } + } + fn infer_expr_path(&mut self, path: &Path, id: ExprOrPatId, scope_id: ExprId) -> Ty<'db> { let g = self.resolver.update_to_inner_scope(self.db, self.owner, scope_id); let ty = match self.infer_path(path, id) { @@ -1089,6 +1125,47 @@ impl<'db> InferenceContext<'_, 'db> { ty } + fn infer_unop_expr( + &mut self, + unop: UnaryOp, + oprnd: ExprId, + expected: &Expectation<'db>, + expr: ExprId, + ) -> Ty<'db> { + let expected_inner = match unop { + UnaryOp::Not | UnaryOp::Neg => expected, + UnaryOp::Deref => &Expectation::None, + }; + let mut oprnd_t = self.infer_expr_inner(oprnd, expected_inner, ExprIsRead::Yes); + + oprnd_t = self.table.structurally_resolve_type(oprnd_t); + match unop { + UnaryOp::Deref => { + if let Some(ty) = self.lookup_derefing(expr, oprnd, oprnd_t) { + oprnd_t = ty; + } else { + // FIXME: Report an error. + oprnd_t = self.types.error; + } + } + UnaryOp::Not => { + let result = self.infer_user_unop(expr, oprnd_t, unop); + // If it's builtin, we can reuse the type, this helps inference. + if !(oprnd_t.is_integral() || oprnd_t.kind() == TyKind::Bool) { + oprnd_t = result; + } + } + UnaryOp::Neg => { + let result = self.infer_user_unop(expr, oprnd_t, unop); + // If it's builtin, we can reuse the type, this helps inference. + if !oprnd_t.is_numeric() { + oprnd_t = result; + } + } + } + oprnd_t + } + fn infer_async_block( &mut self, tgt_expr: ExprId, @@ -1106,8 +1183,7 @@ impl<'db> InferenceContext<'_, 'db> { 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(tgt_expr.into(), ty, target, AllowTwoPhase::No, CoerceNever::Yes) - { + match this.coerce(tgt_expr.into(), ty, target, AllowTwoPhase::No, ExprIsRead::Yes) { Ok(res) => res, Err(_) => { this.result @@ -1220,7 +1296,10 @@ impl<'db> InferenceContext<'_, 'db> { } fn infer_expr_array(&mut self, array: &Array, expected: &Expectation<'db>) -> Ty<'db> { - let elem_ty = match expected.to_option(&mut self.table).as_ref().map(|t| t.kind()) { + let elem_ty = match expected + .to_option(&mut self.table) + .map(|t| self.table.try_structurally_resolve_type(t).kind()) + { Some(TyKind::Array(st, _) | TyKind::Slice(st)) => st, _ => self.table.next_ty_var(), }; @@ -1236,7 +1315,13 @@ impl<'db> InferenceContext<'_, 'db> { let mut coerce = CoerceMany::with_coercion_sites(elem_ty, elements); for &expr in elements.iter() { let cur_elem_ty = self.infer_expr_inner(expr, &expected, ExprIsRead::Yes); - coerce.coerce(self, &ObligationCause::new(), expr, cur_elem_ty); + coerce.coerce( + self, + &ObligationCause::new(), + expr, + cur_elem_ty, + ExprIsRead::Yes, + ); } ( coerce.complete(self), @@ -1250,14 +1335,18 @@ impl<'db> InferenceContext<'_, 'db> { ExprIsRead::Yes, ); let usize = self.types.usize; - match self.body[repeat] { + let len = match self.body[repeat] { Expr::Underscore => { self.write_expr_ty(repeat, usize); + self.table.next_const_var() } - _ => _ = self.infer_expr(repeat, &Expectation::HasType(usize), ExprIsRead::Yes), - } + _ => { + self.infer_expr(repeat, &Expectation::HasType(usize), ExprIsRead::Yes); + consteval::eval_to_const(repeat, self) + } + }; - (elem_ty, consteval::eval_to_const(repeat, self)) + (elem_ty, len) } }; // Try to evaluate unevaluated constant, and insert variable if is not possible. @@ -1274,7 +1363,7 @@ impl<'db> InferenceContext<'_, 'db> { 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, &ObligationCause::new(), expr, return_expr_ty); + coerce_many.coerce(self, &ObligationCause::new(), expr, return_expr_ty, ExprIsRead::Yes); self.return_coercion = Some(coerce_many); } @@ -1285,7 +1374,13 @@ impl<'db> InferenceContext<'_, 'db> { self.infer_return(expr); } else { let mut coerce = self.return_coercion.take().unwrap(); - coerce.coerce_forced_unit(self, ret, &ObligationCause::new(), true); + coerce.coerce_forced_unit( + self, + ret, + &ObligationCause::new(), + true, + ExprIsRead::Yes, + ); self.return_coercion = Some(coerce); } } @@ -1350,103 +1445,6 @@ impl<'db> InferenceContext<'_, 'db> { } } - fn infer_overloadable_binop( - &mut self, - lhs: ExprId, - op: BinaryOp, - rhs: ExprId, - tgt_expr: ExprId, - ) -> Ty<'db> { - let lhs_expectation = Expectation::none(); - 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.next_ty_var(); - - let trait_func = lang_items_for_bin_op(op).and_then(|(name, lang_item)| { - let trait_id = self.resolve_lang_item(lang_item)?.as_trait()?; - let func = trait_id.trait_items(self.db).method_by_name(&name)?; - Some((trait_id, func)) - }); - let func = match trait_func { - Some((_, it)) => it, - None => { - // HACK: `rhs_ty` is a general inference variable with no clue at all at this - // point. Passing `lhs_ty` as both operands just to check if `lhs_ty` is a builtin - // type applicable to `op`. - let ret_ty = if self.is_builtin_binop(lhs_ty, lhs_ty, op) { - // Assume both operands are builtin so we can continue inference. No guarantee - // on the correctness, rustc would complain as necessary lang items don't seem - // to exist anyway. - self.enforce_builtin_binop_types(lhs_ty, rhs_ty, op) - } else { - self.err_ty() - }; - - self.infer_expr_coerce(rhs, &Expectation::has_type(rhs_ty), ExprIsRead::Yes); - - return ret_ty; - } - }; - - // HACK: We can use this substitution for the function because the function itself doesn't - // have its own generic parameters. - let args = GenericArgs::new_from_iter(self.interner(), [lhs_ty.into(), rhs_ty.into()]); - - self.write_method_resolution(tgt_expr, func, args); - - let method_ty = self.db.value_ty(func.into()).unwrap().instantiate(self.interner(), args); - self.register_obligations_for_call(method_ty); - - self.infer_expr_coerce(rhs, &Expectation::has_type(rhs_ty), ExprIsRead::Yes); - - let ret_ty = match method_ty.callable_sig(self.interner()) { - Some(sig) => { - let sig = sig.skip_binder(); - let p_left = sig.inputs_and_output.as_slice()[0]; - if matches!(op, BinaryOp::CmpOp(..) | BinaryOp::Assignment { .. }) - && let TyKind::Ref(lt, _, mtbl) = p_left.kind() - { - self.write_expr_adj( - lhs, - Box::new([Adjustment { - kind: Adjust::Borrow(AutoBorrow::Ref(lt, mtbl)), - target: p_left, - }]), - ); - } - let p_right = sig.inputs_and_output.as_slice()[1]; - if matches!(op, BinaryOp::CmpOp(..)) - && let TyKind::Ref(lt, _, mtbl) = p_right.kind() - { - self.write_expr_adj( - rhs, - Box::new([Adjustment { - kind: Adjust::Borrow(AutoBorrow::Ref(lt, mtbl)), - target: p_right, - }]), - ); - } - sig.output() - } - None => self.err_ty(), - }; - - let ret_ty = self.process_remote_user_written_ty(ret_ty); - - if self.is_builtin_binop(lhs_ty, rhs_ty, op) { - // use knowledge of built-in binary ops, which can sometimes help inference - let builtin_ret = self.enforce_builtin_binop_types(lhs_ty, rhs_ty, op); - self.unify(builtin_ret, ret_ty); - builtin_ret - } else { - ret_ty - } - } - fn infer_block( &mut self, expr: ExprId, @@ -1548,20 +1546,13 @@ impl<'db> InferenceContext<'_, 'db> { // 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( expr.into(), this.types.unit, t, AllowTwoPhase::No, - coerce_never, + ExprIsRead::Yes, ) .is_err() { @@ -1591,7 +1582,7 @@ impl<'db> InferenceContext<'_, 'db> { name: &Name, ) -> Option<(Ty<'db>, Either<FieldId, TupleFieldId>, Vec<Adjustment<'db>>, bool)> { let interner = self.interner(); - let mut autoderef = self.table.autoderef(receiver_ty); + let mut autoderef = self.table.autoderef_with_tracking(receiver_ty); let mut private_field = None; let res = autoderef.by_ref().find_map(|(derefed_ty, _)| { let (field_id, parameters) = match derefed_ty.kind() { @@ -1640,14 +1631,16 @@ impl<'db> InferenceContext<'_, 'db> { Some(match res { Some((field_id, ty)) => { - let adjustments = autoderef.adjust_steps(); + let adjustments = + self.table.register_infer_ok(autoderef.adjust_steps_as_infer_ok()); let ty = self.process_remote_user_written_ty(ty); (ty, field_id, adjustments, true) } None => { let (field_id, subst) = private_field?; - let adjustments = autoderef.adjust_steps(); + let adjustments = + self.table.register_infer_ok(autoderef.adjust_steps_as_infer_ok()); let ty = self.db.field_types(field_id.parent)[field_id.local_id] .instantiate(self.interner(), subst); let ty = self.process_remote_user_written_ty(ty); @@ -1666,6 +1659,7 @@ impl<'db> InferenceContext<'_, 'db> { ) -> Ty<'db> { // Field projections don't constitute reads. let receiver_ty = self.infer_expr_inner(receiver, &Expectation::none(), ExprIsRead::No); + let receiver_ty = self.table.structurally_resolve_type(receiver_ty); if name.is_missing() { // Bail out early, don't even try to look up field. Also, we don't issue an unresolved @@ -1689,46 +1683,39 @@ impl<'db> InferenceContext<'_, 'db> { None => { // no field found, lets attempt to resolve it like a function so that IDE things // work out while people are typing - let canonicalized_receiver = self.canonicalize(receiver_ty); - let resolved = method_resolution::lookup_method( - &canonicalized_receiver, - &mut self.table, - Self::get_traits_in_scope(&self.resolver, &self.traits_in_scope) - .as_ref() - .left_or_else(|&it| it), - VisibleFromModule::Filter(self.resolver.module()), - name, + let resolved = self.lookup_method_including_private( + receiver_ty, + name.clone(), + None, + receiver, + tgt_expr, ); self.push_diagnostic(InferenceDiagnostic::UnresolvedField { expr: tgt_expr, receiver: receiver_ty, name: name.clone(), - method_with_same_name_exists: resolved.is_some(), + method_with_same_name_exists: resolved.is_ok(), }); match resolved { - Some((adjust, func, _)) => { - let (ty, adjustments) = adjust.apply(&mut self.table, receiver_ty); - let args = self.substs_for_method_call(tgt_expr, func.into(), None); - self.write_expr_adj(receiver, adjustments.into_boxed_slice()); - self.write_method_resolution(tgt_expr, func, args); - - self.check_method_call( - tgt_expr, - &[], - self.db - .value_ty(func.into()) - .unwrap() - .instantiate(self.interner(), args), - ty, - expected, - ) + Ok((func, _is_visible)) => { + self.check_method_call(tgt_expr, &[], func.sig, receiver_ty, expected) } - None => self.err_ty(), + Err(_) => self.err_ty(), } } } } + fn instantiate_erroneous_method(&mut self, def_id: FunctionId) -> MethodCallee<'db> { + // FIXME: Using fresh infer vars for the method args isn't optimal, + // we can do better by going thorough the full probe/confirm machinery. + let args = self.table.fresh_args_for_item(def_id.into()); + let sig = self.db.callable_item_signature(def_id.into()).instantiate(self.interner(), args); + let sig = + self.infcx().instantiate_binder_with_fresh_vars(BoundRegionConversionTime::FnCall, sig); + MethodCallee { def_id, args, sig } + } + fn infer_call( &mut self, tgt_expr: ExprId, @@ -1737,13 +1724,14 @@ impl<'db> InferenceContext<'_, 'db> { expected: &Expectation<'db>, ) -> Ty<'db> { let callee_ty = self.infer_expr(callee, &Expectation::none(), ExprIsRead::Yes); + let callee_ty = self.table.try_structurally_resolve_type(callee_ty); let interner = self.interner(); - let mut derefs = self.table.autoderef(callee_ty); + let mut derefs = InferenceContextAutoderef::new_from_inference_context(self, callee_ty); let (res, derefed_callee) = loop { let Some((callee_deref_ty, _)) = derefs.next() else { break (None, callee_ty); }; - if let Some(res) = derefs.table.callable_sig(callee_deref_ty, args.len()) { + if let Some(res) = derefs.ctx().table.callable_sig(callee_deref_ty, args.len()) { break (Some(res), callee_deref_ty); } }; @@ -1753,7 +1741,8 @@ impl<'db> InferenceContext<'_, 'db> { || res.is_none(); let (param_tys, ret_ty) = match res { Some((func, params, ret_ty)) => { - let mut adjustments = derefs.adjust_steps(); + let infer_ok = derefs.adjust_steps_as_infer_ok(); + let mut adjustments = self.table.register_infer_ok(infer_ok); if let Some(fn_x) = func { self.write_fn_trait_method_resolution( fn_x, @@ -1819,7 +1808,7 @@ impl<'db> InferenceContext<'_, 'db> { indices_to_skip, is_varargs, ); - self.table.normalize_associated_types_in(ret_ty) + ret_ty } fn infer_method_call( @@ -1834,56 +1823,26 @@ impl<'db> InferenceContext<'_, 'db> { let receiver_ty = self.infer_expr_inner(receiver, &Expectation::none(), ExprIsRead::Yes); let receiver_ty = self.table.try_structurally_resolve_type(receiver_ty); - if matches!(receiver_ty.kind(), TyKind::Error(_) | TyKind::Infer(InferTy::TyVar(_))) { - // Don't probe on error type, or on a fully unresolved infer var. - // FIXME: Emit an error if we're probing on an infer var (type annotations needed). - for &arg in args { - // Make sure we infer and record the arguments. - self.infer_expr_no_expect(arg, ExprIsRead::Yes); - } - return receiver_ty; - } - - let canonicalized_receiver = self.canonicalize(receiver_ty); - - let resolved = method_resolution::lookup_method( - &canonicalized_receiver, - &mut self.table, - Self::get_traits_in_scope(&self.resolver, &self.traits_in_scope) - .as_ref() - .left_or_else(|&it| it), - VisibleFromModule::Filter(self.resolver.module()), - method_name, + let resolved = self.lookup_method_including_private( + receiver_ty, + method_name.clone(), + generic_args, + receiver, + tgt_expr, ); match resolved { - Some((adjust, func, visible)) => { + Ok((func, visible)) => { if !visible { self.push_diagnostic(InferenceDiagnostic::PrivateAssocItem { id: tgt_expr.into(), - item: func.into(), + item: func.def_id.into(), }) } - - let (ty, adjustments) = adjust.apply(&mut self.table, receiver_ty); - self.write_expr_adj(receiver, adjustments.into_boxed_slice()); - - let gen_args = self.substs_for_method_call(tgt_expr, func.into(), generic_args); - self.write_method_resolution(tgt_expr, func, gen_args); - let interner = DbInterner::new_with(self.db, None, None); - self.check_method_call( - tgt_expr, - args, - self.db - .value_ty(func.into()) - .expect("we have a function def") - .instantiate(interner, gen_args), - ty, - expected, - ) + self.check_method_call(tgt_expr, args, func.sig, receiver_ty, expected) } // Failed to resolve, report diagnostic and try to resolve as call to field access or // assoc function - None => { + Err(_) => { let field_with_same_name_exists = match self.lookup_field(receiver_ty, method_name) { Some((ty, field_id, adjustments, _public)) => { @@ -1894,65 +1853,65 @@ impl<'db> InferenceContext<'_, 'db> { None => None, }; - let assoc_func_with_same_name = method_resolution::iterate_method_candidates( - &canonicalized_receiver, - &mut self.table, - Self::get_traits_in_scope(&self.resolver, &self.traits_in_scope) - .as_ref() - .left_or_else(|&it| it), - VisibleFromModule::Filter(self.resolver.module()), - Some(method_name), - method_resolution::LookupMode::Path, - |_ty, item, visible| match item { - hir_def::AssocItemId::FunctionId(function_id) if visible => { - Some(function_id) - } - _ => None, - }, - ); + let assoc_func_with_same_name = self.with_method_resolution(|ctx| { + if !matches!( + receiver_ty.kind(), + TyKind::Infer(InferTy::TyVar(_)) | TyKind::Error(_) + ) { + ctx.probe_for_name( + method_resolution::Mode::Path, + method_name.clone(), + receiver_ty, + ) + } else { + Err(MethodError::ErrorReported) + } + }); + let assoc_func_with_same_name = match assoc_func_with_same_name { + Ok(method_resolution::Pick { + item: CandidateId::FunctionId(def_id), .. + }) + | Err(MethodError::PrivateMatch(method_resolution::Pick { + item: CandidateId::FunctionId(def_id), + .. + })) => Some(self.instantiate_erroneous_method(def_id)), + _ => None, + }; self.push_diagnostic(InferenceDiagnostic::UnresolvedMethodCall { expr: tgt_expr, receiver: receiver_ty, name: method_name.clone(), field_with_same_name: field_with_same_name_exists, - assoc_func_with_same_name, + assoc_func_with_same_name: assoc_func_with_same_name.map(|it| it.def_id), }); let recovered = match assoc_func_with_same_name { - Some(f) => { - let args = self.substs_for_method_call(tgt_expr, f.into(), generic_args); - let interner = DbInterner::new_with(self.db, None, None); - let f = self - .db - .value_ty(f.into()) - .expect("we have a function def") - .instantiate(interner, args); - let sig = f.callable_sig(self.interner()).expect("we have a function def"); - Some((f, sig, true)) - } + Some(it) => Some(( + Ty::new_fn_def( + self.interner(), + CallableDefId::FunctionId(it.def_id).into(), + it.args, + ), + it.sig, + true, + )), None => field_with_same_name_exists.and_then(|field_ty| { let callable_sig = field_ty.callable_sig(self.interner())?; - Some((field_ty, callable_sig, false)) + Some((field_ty, callable_sig.skip_binder(), false)) }), }; match recovered { - Some((callee_ty, sig, strip_first)) => { - let sig = sig.skip_binder(); - self.check_call( - tgt_expr, - args, - callee_ty, - sig.inputs_and_output - .inputs() - .get(strip_first as usize..) - .unwrap_or(&[]), - sig.output(), - &[], - true, - expected, - ) - } + Some((callee_ty, sig, strip_first)) => self.check_call( + tgt_expr, + args, + callee_ty, + sig.inputs_and_output.inputs().get(strip_first as usize..).unwrap_or(&[]), + sig.output(), + &[], + true, + expected, + ), None => { for &arg in args.iter() { self.infer_expr_no_expect(arg, ExprIsRead::Yes); @@ -1968,38 +1927,20 @@ impl<'db> InferenceContext<'_, 'db> { &mut self, tgt_expr: ExprId, args: &[ExprId], - method_ty: Ty<'db>, + sig: FnSig<'db>, receiver_ty: Ty<'db>, expected: &Expectation<'db>, ) -> Ty<'db> { - self.register_obligations_for_call(method_ty); - let ((formal_receiver_ty, param_tys), ret_ty, is_varargs) = - match method_ty.callable_sig(self.interner()) { - Some(sig) => { - let sig = sig.skip_binder(); - ( - if !sig.inputs_and_output.inputs().is_empty() { - ( - sig.inputs_and_output.as_slice()[0], - sig.inputs_and_output.inputs()[1..].to_vec(), - ) - } else { - (self.types.error, Vec::new()) - }, - sig.output(), - sig.c_variadic, - ) - } - None => { - let formal_receiver_ty = self.table.next_ty_var(); - let ret_ty = self.table.next_ty_var(); - ((formal_receiver_ty, Vec::new()), ret_ty, true) - } - }; + let (formal_receiver_ty, param_tys) = if !sig.inputs_and_output.inputs().is_empty() { + (sig.inputs_and_output.as_slice()[0], &sig.inputs_and_output.inputs()[1..]) + } else { + (self.types.error, &[] as _) + }; + let ret_ty = sig.output(); self.table.unify(formal_receiver_ty, receiver_ty); - self.check_call_arguments(tgt_expr, ¶m_tys, ret_ty, expected, args, &[], is_varargs); - self.table.normalize_associated_types_in(ret_ty) + self.check_call_arguments(tgt_expr, param_tys, ret_ty, expected, args, &[], sig.c_variadic); + ret_ty } /// Generic function that factors out common logic from function calls, @@ -2110,20 +2051,13 @@ impl<'db> InferenceContext<'_, 'db> { // fulfillment error to be more accurate. let coerced_ty = this.table.resolve_vars_with_obligations(coerced_ty); - let coerce_never = if this - .expr_guaranteed_to_constitute_read_for_never(provided_arg, ExprIsRead::Yes) - { - CoerceNever::Yes - } else { - CoerceNever::No - }; let coerce_error = this .coerce( provided_arg.into(), checked_ty, coerced_ty, AllowTwoPhase::Yes, - coerce_never, + ExprIsRead::Yes, ) .err(); if coerce_error.is_some() { @@ -2204,144 +2138,19 @@ impl<'db> InferenceContext<'_, 'db> { if !args_count_matches {} } - fn substs_for_method_call( - &mut self, - expr: ExprId, - def: GenericDefId, - generic_args: Option<&HirGenericArgs>, - ) -> GenericArgs<'db> { - struct LowererCtx<'a, 'b, 'db> { - ctx: &'a mut InferenceContext<'b, 'db>, - expr: ExprId, - } - - impl<'db> GenericArgsLowerer<'db> for LowererCtx<'_, '_, 'db> { - fn report_len_mismatch( - &mut self, - def: GenericDefId, - provided_count: u32, - expected_count: u32, - kind: IncorrectGenericsLenKind, - ) { - self.ctx.push_diagnostic(InferenceDiagnostic::MethodCallIncorrectGenericsLen { - expr: self.expr, - provided_count, - expected_count, - kind, - def, - }); - } - - fn report_arg_mismatch( - &mut self, - param_id: GenericParamId, - arg_idx: u32, - has_self_arg: bool, - ) { - self.ctx.push_diagnostic(InferenceDiagnostic::MethodCallIncorrectGenericsOrder { - expr: self.expr, - param_id, - arg_idx, - has_self_arg, - }); - } - - fn provided_kind( - &mut self, - param_id: GenericParamId, - param: GenericParamDataRef<'_>, - arg: &HirGenericArg, - ) -> GenericArg<'db> { - match (param, arg) { - ( - GenericParamDataRef::LifetimeParamData(_), - HirGenericArg::Lifetime(lifetime), - ) => self.ctx.make_body_lifetime(*lifetime).into(), - (GenericParamDataRef::TypeParamData(_), HirGenericArg::Type(type_ref)) => { - self.ctx.make_body_ty(*type_ref).into() - } - (GenericParamDataRef::ConstParamData(_), HirGenericArg::Const(konst)) => { - let GenericParamId::ConstParamId(const_id) = param_id else { - unreachable!("non-const param ID for const param"); - }; - let const_ty = self.ctx.db.const_param_ty_ns(const_id); - self.ctx.make_body_const(*konst, const_ty).into() - } - _ => unreachable!("unmatching param kinds were passed to `provided_kind()`"), - } - } - - fn provided_type_like_const( - &mut self, - const_ty: Ty<'db>, - arg: TypeLikeConst<'_>, - ) -> Const<'db> { - match arg { - TypeLikeConst::Path(path) => self.ctx.make_path_as_body_const(path, const_ty), - TypeLikeConst::Infer => self.ctx.table.next_const_var(), - } - } - - fn inferred_kind( - &mut self, - _def: GenericDefId, - param_id: GenericParamId, - _param: GenericParamDataRef<'_>, - _infer_args: bool, - _preceding_args: &[GenericArg<'db>], - ) -> GenericArg<'db> { - // Always create an inference var, even when `infer_args == false`. This helps with diagnostics, - // and I think it's also required in the presence of `impl Trait` (that must be inferred). - self.ctx.table.next_var_for_param(param_id) - } - - fn parent_arg(&mut self, param_id: GenericParamId) -> GenericArg<'db> { - self.ctx.table.next_var_for_param(param_id) - } - - fn report_elided_lifetimes_in_path( - &mut self, - _def: GenericDefId, - _expected_count: u32, - _hard_error: bool, - ) { - unreachable!("we set `LifetimeElisionKind::Infer`") - } - - fn report_elision_failure(&mut self, _def: GenericDefId, _expected_count: u32) { - unreachable!("we set `LifetimeElisionKind::Infer`") - } - - fn report_missing_lifetime(&mut self, _def: GenericDefId, _expected_count: u32) { - unreachable!("we set `LifetimeElisionKind::Infer`") - } - } - - substs_from_args_and_bindings( - self.db, - self.body, - generic_args, - def, - true, - LifetimeElisionKind::Infer, - false, - None, - &mut LowererCtx { ctx: self, expr }, - ) - } - fn register_obligations_for_call(&mut self, callable_ty: Ty<'db>) { let callable_ty = self.table.try_structurally_resolve_type(callable_ty); if let TyKind::FnDef(fn_def, parameters) = callable_ty.kind() { - let generic_predicates = - self.db.generic_predicates(GenericDefId::from_callable(self.db, fn_def.0)); - if let Some(predicates) = generic_predicates.instantiate(self.interner(), parameters) { - let interner = self.interner(); - let param_env = self.table.trait_env.env; - self.table.register_predicates(predicates.map(|predicate| { - Obligation::new(interner, ObligationCause::new(), param_env, predicate) - })); - } + let generic_predicates = GenericPredicates::query_all( + self.db, + GenericDefId::from_callable(self.db, fn_def.0), + ); + let param_env = self.table.trait_env.env; + self.table.register_predicates(clauses_as_obligations( + generic_predicates.iter_instantiated_copied(self.interner(), parameters.as_slice()), + ObligationCause::new(), + param_env, + )); // add obligation for trait implementation, if this is a trait method match fn_def.0 { CallableDefId::FunctionId(f) => { @@ -2410,122 +2219,6 @@ impl<'db> InferenceContext<'_, 'db> { indices } - /// Dereferences a single level of immutable referencing. - fn deref_ty_if_possible(&mut self, ty: Ty<'db>) -> Ty<'db> { - let ty = self.table.try_structurally_resolve_type(ty); - match ty.kind() { - TyKind::Ref(_, inner, Mutability::Not) => { - self.table.try_structurally_resolve_type(inner) - } - _ => ty, - } - } - - /// Enforces expectations on lhs type and rhs type depending on the operator and returns the - /// output type of the binary op. - fn enforce_builtin_binop_types(&mut self, lhs: Ty<'db>, rhs: Ty<'db>, op: BinaryOp) -> Ty<'db> { - // Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work (See rust-lang/rust#57447). - let lhs = self.deref_ty_if_possible(lhs); - let rhs = self.deref_ty_if_possible(rhs); - - let (op, is_assign) = match op { - BinaryOp::Assignment { op: Some(inner) } => (BinaryOp::ArithOp(inner), true), - _ => (op, false), - }; - - let output_ty = match op { - BinaryOp::LogicOp(_) => { - let bool_ = self.types.bool; - self.unify(lhs, bool_); - self.unify(rhs, bool_); - bool_ - } - - BinaryOp::ArithOp(ArithOp::Shl | ArithOp::Shr) => { - // result type is same as LHS always - lhs - } - - BinaryOp::ArithOp(_) => { - // LHS, RHS, and result will have the same type - self.unify(lhs, rhs); - lhs - } - - BinaryOp::CmpOp(_) => { - // LHS and RHS will have the same type - self.unify(lhs, rhs); - self.types.bool - } - - BinaryOp::Assignment { op: None } => { - stdx::never!("Simple assignment operator is not binary op."); - lhs - } - - BinaryOp::Assignment { .. } => unreachable!("handled above"), - }; - - if is_assign { self.types.unit } else { output_ty } - } - - fn is_builtin_binop(&mut self, lhs: Ty<'db>, rhs: Ty<'db>, op: BinaryOp) -> bool { - // Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work (See rust-lang/rust#57447). - let lhs = self.deref_ty_if_possible(lhs); - let rhs = self.deref_ty_if_possible(rhs); - - let op = match op { - BinaryOp::Assignment { op: Some(inner) } => BinaryOp::ArithOp(inner), - _ => op, - }; - - match op { - BinaryOp::LogicOp(_) => true, - - BinaryOp::ArithOp(ArithOp::Shl | ArithOp::Shr) => { - lhs.is_integral() && rhs.is_integral() - } - - BinaryOp::ArithOp( - ArithOp::Add | ArithOp::Sub | ArithOp::Mul | ArithOp::Div | ArithOp::Rem, - ) => { - lhs.is_integral() && rhs.is_integral() - || lhs.is_floating_point() && rhs.is_floating_point() - } - - BinaryOp::ArithOp(ArithOp::BitAnd | ArithOp::BitOr | ArithOp::BitXor) => { - lhs.is_integral() && rhs.is_integral() - || lhs.is_floating_point() && rhs.is_floating_point() - || matches!((lhs.kind(), rhs.kind()), (TyKind::Bool, TyKind::Bool)) - } - - BinaryOp::CmpOp(_) => { - let is_scalar = |kind| { - matches!( - kind, - TyKind::Bool - | TyKind::Char - | TyKind::Int(_) - | TyKind::Uint(_) - | TyKind::Float(_) - | TyKind::FnDef(..) - | TyKind::FnPtr(..) - | TyKind::RawPtr(..) - | TyKind::Infer(InferTy::IntVar(_) | InferTy::FloatVar(_)) - ) - }; - is_scalar(lhs.kind()) && is_scalar(rhs.kind()) - } - - BinaryOp::Assignment { op: None } => { - stdx::never!("Simple assignment operator is not binary op."); - false - } - - BinaryOp::Assignment { .. } => unreachable!("handled above"), - } - } - pub(super) fn with_breakable_ctx<T>( &mut self, kind: BreakableKind, |