//! Type inference, i.e. the process of walking through the code and determining //! the type of each expression and pattern. //! //! For type inference, compare the implementations in rustc (the various //! check_* methods in librustc_typeck/check/mod.rs are a good entry point) and //! IntelliJ-Rust (org.rust.lang.core.types.infer). Our entry point for //! inference here is the `infer` function, which infers the types of all //! expressions in a given function. //! //! During inference, types (i.e. the `Ty` struct) can contain type 'variables' //! which represent currently unknown types; as we walk through the expressions, //! we might determine that certain variables need to be equal to each other, or //! to certain types. To record this, we use the union-find implementation from //! the `ena` crate, which is extracted from rustc. use std::ops::Index; use std::sync::Arc; use chalk_ir::{cast::Cast, DebruijnIndex, Mutability}; use hir_def::{ body::Body, data::{ConstData, FunctionData, StaticData}, expr::{ArithOp, BinaryOp, BindingAnnotation, ExprId, PatId}, lang_item::LangItemTarget, path::{path, Path}, resolver::{HasResolver, Resolver, TypeNs}, type_ref::TypeRef, AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, FunctionId, HasModule, Lookup, TraitId, TypeAliasId, VariantId, }; use hir_expand::{diagnostics::DiagnosticSink, name::name}; use la_arena::ArenaMap; use rustc_hash::FxHashMap; use stdx::impl_from; use syntax::SmolStr; use super::{DomainGoal, InEnvironment, ProjectionTy, TraitEnvironment, TraitRef, Ty}; use crate::{ db::HirDatabase, fold_tys, infer::diagnostics::InferenceDiagnostic, lower::ImplTraitLoweringMode, to_assoc_type_id, AliasEq, AliasTy, Interner, TyBuilder, TyExt, TyKind, }; // This lint has a false positive here. See the link below for details. // // https://github.com/rust-lang/rust/issues/57411 #[allow(unreachable_pub)] pub use unify::could_unify; pub(crate) use unify::unify; mod unify; mod path; mod expr; mod pat; mod coerce; /// The entry point of type inference. pub(crate) fn infer_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc { let _p = profile::span("infer_query"); let resolver = def.resolver(db.upcast()); let mut ctx = InferenceContext::new(db, def, resolver); match def { DefWithBodyId::ConstId(c) => ctx.collect_const(&db.const_data(c)), DefWithBodyId::FunctionId(f) => ctx.collect_fn(&db.function_data(f)), DefWithBodyId::StaticId(s) => ctx.collect_static(&db.static_data(s)), } ctx.infer_body(); Arc::new(ctx.resolve_all()) } #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] enum ExprOrPatId { ExprId(ExprId), PatId(PatId), } impl_from!(ExprId, PatId for ExprOrPatId); /// Binding modes inferred for patterns. /// https://doc.rust-lang.org/reference/patterns.html#binding-modes #[derive(Copy, Clone, Debug, Eq, PartialEq)] enum BindingMode { Move, Ref(Mutability), } impl BindingMode { fn convert(annotation: BindingAnnotation) -> BindingMode { match annotation { BindingAnnotation::Unannotated | BindingAnnotation::Mutable => BindingMode::Move, BindingAnnotation::Ref => BindingMode::Ref(Mutability::Not), BindingAnnotation::RefMut => BindingMode::Ref(Mutability::Mut), } } } impl Default for BindingMode { fn default() -> Self { BindingMode::Move } } #[derive(Debug)] pub(crate) struct InferOk { // obligations } #[derive(Debug)] pub(crate) struct TypeError; pub(crate) type InferResult = Result; /// A mismatch between an expected and an inferred type. #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub struct TypeMismatch { pub expected: Ty, pub actual: Ty, } #[derive(Clone, PartialEq, Eq, Debug)] struct InternedStandardTypes { unknown: Ty, } impl Default for InternedStandardTypes { fn default() -> Self { InternedStandardTypes { unknown: TyKind::Error.intern(&Interner) } } } /// The result of type inference: A mapping from expressions and patterns to types. #[derive(Clone, PartialEq, Eq, Debug, Default)] pub struct InferenceResult { /// For each method call expr, records the function it resolves to. method_resolutions: FxHashMap, /// For each field access expr, records the field it resolves to. field_resolutions: FxHashMap, /// For each struct literal or pattern, records the variant it resolves to. variant_resolutions: FxHashMap, /// For each associated item record what it resolves to assoc_resolutions: FxHashMap, diagnostics: Vec, pub type_of_expr: ArenaMap, /// For each pattern record the type it resolves to. /// /// **Note**: When a pattern type is resolved it may still contain /// unresolved or missing subpatterns or subpatterns of mismatched types. pub type_of_pat: ArenaMap, type_mismatches: FxHashMap, /// Interned Unknown to return references to. standard_types: InternedStandardTypes, } impl InferenceResult { pub fn method_resolution(&self, expr: ExprId) -> Option { self.method_resolutions.get(&expr).copied() } pub fn field_resolution(&self, expr: ExprId) -> Option { self.field_resolutions.get(&expr).copied() } pub fn variant_resolution_for_expr(&self, id: ExprId) -> Option { self.variant_resolutions.get(&id.into()).copied() } pub fn variant_resolution_for_pat(&self, id: PatId) -> Option { self.variant_resolutions.get(&id.into()).copied() } pub fn assoc_resolutions_for_expr(&self, id: ExprId) -> Option { self.assoc_resolutions.get(&id.into()).copied() } pub fn assoc_resolutions_for_pat(&self, id: PatId) -> Option { self.assoc_resolutions.get(&id.into()).copied() } pub fn type_mismatch_for_expr(&self, expr: ExprId) -> Option<&TypeMismatch> { self.type_mismatches.get(&expr.into()) } pub fn type_mismatch_for_pat(&self, pat: PatId) -> Option<&TypeMismatch> { self.type_mismatches.get(&pat.into()) } pub fn expr_type_mismatches(&self) -> impl Iterator { self.type_mismatches.iter().filter_map(|(expr_or_pat, mismatch)| match *expr_or_pat { ExprOrPatId::ExprId(expr) => Some((expr, mismatch)), _ => None, }) } pub fn pat_type_mismatches(&self) -> impl Iterator { self.type_mismatches.iter().filter_map(|(expr_or_pat, mismatch)| match *expr_or_pat { ExprOrPatId::PatId(pat) => Some((pat, mismatch)), _ => None, }) } pub fn add_diagnostics( &self, db: &dyn HirDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink, ) { self.diagnostics.iter().for_each(|it| it.add_to(db, owner, sink)) } } impl Index for InferenceResult { type Output = Ty; fn index(&self, expr: ExprId) -> &Ty { self.type_of_expr.get(expr).unwrap_or(&self.standard_types.unknown) } } impl Index for InferenceResult { type Output = Ty; fn index(&self, pat: PatId) -> &Ty { self.type_of_pat.get(pat).unwrap_or(&self.standard_types.unknown) } } /// The inference context contains all information needed during type inference. #[derive(Clone, Debug)] struct InferenceContext<'a> { db: &'a dyn HirDatabase, owner: DefWithBodyId, body: Arc, resolver: Resolver, table: unify::InferenceTable<'a>, trait_env: Arc, result: InferenceResult, /// The return type of the function being inferred, or the closure if we're /// currently within one. /// /// We might consider using a nested inference context for checking /// closures, but currently this is the only field that will change there, /// so it doesn't make sense. return_ty: Ty, diverges: Diverges, breakables: Vec, } #[derive(Clone, Debug)] struct BreakableContext { may_break: bool, break_ty: Ty, label: Option, } fn find_breakable<'c>( ctxs: &'c mut [BreakableContext], label: Option<&name::Name>, ) -> Option<&'c mut BreakableContext> { match label { Some(_) => ctxs.iter_mut().rev().find(|ctx| ctx.label.as_ref() == label), None => ctxs.last_mut(), } } impl<'a> InferenceContext<'a> { fn new(db: &'a dyn HirDatabase, owner: DefWithBodyId, resolver: Resolver) -> Self { let krate = owner.module(db.upcast()).krate(); let trait_env = owner .as_generic_def_id() .map_or_else(|| Arc::new(TraitEnvironment::empty(krate)), |d| db.trait_environment(d)); InferenceContext { result: InferenceResult::default(), table: unify::InferenceTable::new(db, trait_env.clone()), trait_env, return_ty: TyKind::Error.intern(&Interner), // set in collect_fn_signature db, owner, body: db.body(owner), resolver, diverges: Diverges::Maybe, breakables: Vec::new(), } } fn err_ty(&self) -> Ty { TyKind::Error.intern(&Interner) } fn resolve_all(mut self) -> InferenceResult { // FIXME resolve obligations as well (use Guidance if necessary) self.table.resolve_obligations_as_possible(); // make sure diverging type variables are marked as such self.table.propagate_diverging_flag(); let mut result = std::mem::take(&mut self.result); for ty in result.type_of_expr.values_mut() { let resolved = self.table.resolve_ty_completely(ty.clone()); *ty = resolved; } for ty in result.type_of_pat.values_mut() { let resolved = self.table.resolve_ty_completely(ty.clone()); *ty = resolved; } result } fn write_expr_ty(&mut self, expr: ExprId, ty: Ty) { self.result.type_of_expr.insert(expr, ty); } fn write_method_resolution(&mut self, expr: ExprId, func: FunctionId) { self.result.method_resolutions.insert(expr, func); } fn write_field_resolution(&mut self, expr: ExprId, field: FieldId) { self.result.field_resolutions.insert(expr, field); } fn write_variant_resolution(&mut self, id: ExprOrPatId, variant: VariantId) { self.result.variant_resolutions.insert(id, variant); } fn write_assoc_resolution(&mut self, id: ExprOrPatId, item: AssocItemId) { self.result.assoc_resolutions.insert(id, item); } fn write_pat_ty(&mut self, pat: PatId, ty: Ty) { self.result.type_of_pat.insert(pat, ty); } fn push_diagnostic(&mut self, diagnostic: InferenceDiagnostic) { self.result.diagnostics.push(diagnostic); } fn make_ty_with_mode( &mut self, type_ref: &TypeRef, impl_trait_mode: ImplTraitLoweringMode, ) -> Ty { // FIXME use right resolver for block let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver) .with_impl_trait_mode(impl_trait_mode); let ty = ctx.lower_ty(type_ref); let ty = self.insert_type_vars(ty); self.normalize_associated_types_in(ty) } fn make_ty(&mut self, type_ref: &TypeRef) -> Ty { self.make_ty_with_mode(type_ref, ImplTraitLoweringMode::Disallowed) } /// Replaces Ty::Unknown by a new type var, so we can maybe still infer it. fn insert_type_vars_shallow(&mut self, ty: Ty) -> Ty { match ty.kind(&Interner) { TyKind::Error => self.table.new_type_var(), _ => ty, } } fn insert_type_vars(&mut self, ty: Ty) -> Ty { fold_tys(ty, |ty, _| self.insert_type_vars_shallow(ty), DebruijnIndex::INNERMOST) } fn resolve_obligations_as_possible(&mut self) { self.table.resolve_obligations_as_possible(); } fn push_obligation(&mut self, o: DomainGoal) { self.table.register_obligation(o.cast(&Interner)); } fn unify(&mut self, ty1: &Ty, ty2: &Ty) -> bool { // TODO handle expectations properly if ty2.is_unknown() { return true; } self.table.unify(ty1, ty2) } fn unify_inner(&mut self, ty1: &Ty, ty2: &Ty) -> InferResult { self.table.unify_inner(ty1, ty2) } // FIXME get rid of this, instead resolve shallowly where necessary /// Resolves the type as far as currently possible, replacing type variables /// by their known types. All types returned by the infer_* functions should /// be resolved as far as possible, i.e. contain no type variables with /// known type. fn resolve_ty_as_possible(&mut self, ty: Ty) -> Ty { self.resolve_obligations_as_possible(); self.table.resolve_ty_as_possible(ty) } fn resolve_ty_shallow(&mut self, ty: &Ty) -> Ty { self.table.resolve_ty_shallow(ty) } fn resolve_associated_type(&mut self, inner_ty: Ty, assoc_ty: Option) -> Ty { self.resolve_associated_type_with_params(inner_ty, assoc_ty, &[]) } fn resolve_associated_type_with_params( &mut self, inner_ty: Ty, assoc_ty: Option, params: &[Ty], ) -> Ty { match assoc_ty { Some(res_assoc_ty) => { let trait_ = match res_assoc_ty.lookup(self.db.upcast()).container { hir_def::AssocContainerId::TraitId(trait_) => trait_, _ => panic!("resolve_associated_type called with non-associated type"), }; let ty = self.table.new_type_var(); let trait_ref = TyBuilder::trait_ref(self.db, trait_) .push(inner_ty) .fill(params.iter().cloned()) .build(); let alias_eq = AliasEq { alias: AliasTy::Projection(ProjectionTy { associated_ty_id: to_assoc_type_id(res_assoc_ty), substitution: trait_ref.substitution.clone(), }), ty: ty.clone(), }; self.push_obligation(trait_ref.cast(&Interner)); self.push_obligation(alias_eq.cast(&Interner)); self.resolve_ty_as_possible(ty) } None => self.err_ty(), } } /// Recurses through the given type, normalizing associated types mentioned /// in it by replacing them by type variables and registering obligations to /// resolve later. This should be done once for every type we get from some /// type annotation (e.g. from a let type annotation, field type or function /// call). `make_ty` handles this already, but e.g. for field types we need /// to do it as well. fn normalize_associated_types_in(&mut self, ty: Ty) -> Ty { self.table.normalize_associated_types_in(ty) } fn resolve_variant(&mut self, path: Option<&Path>) -> (Ty, Option) { let path = match path { Some(path) => path, None => return (self.err_ty(), None), }; let resolver = &self.resolver; let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver); // FIXME: this should resolve assoc items as well, see this example: // https://play.rust-lang.org/?gist=087992e9e22495446c01c0d4e2d69521 let (resolution, unresolved) = match resolver.resolve_path_in_type_ns(self.db.upcast(), path.mod_path()) { Some(it) => it, None => return (self.err_ty(), None), }; return match resolution { TypeNs::AdtId(AdtId::StructId(strukt)) => { let substs = ctx.substs_from_path(path, strukt.into(), true); let ty = self.db.ty(strukt.into()); let ty = self.insert_type_vars(ty.substitute(&Interner, &substs)); forbid_unresolved_segments((ty, Some(strukt.into())), unresolved) } TypeNs::AdtId(AdtId::UnionId(u)) => { let substs = ctx.substs_from_path(path, u.into(), true); let ty = self.db.ty(u.into()); let ty = self.insert_type_vars(ty.substitute(&Interner, &substs)); forbid_unresolved_segments((ty, Some(u.into())), unresolved) } TypeNs::EnumVariantId(var) => { let substs = ctx.substs_from_path(path, var.into(), true); let ty = self.db.ty(var.parent.into()); let ty = self.insert_type_vars(ty.substitute(&Interner, &substs)); forbid_unresolved_segments((ty, Some(var.into())), unresolved) } TypeNs::SelfType(impl_id) => { let generics = crate::utils::generics(self.db.upcast(), impl_id.into()); let substs = generics.type_params_subst(self.db); let ty = self.db.impl_self_ty(impl_id).substitute(&Interner, &substs); self.resolve_variant_on_alias(ty, unresolved, path) } TypeNs::TypeAliasId(it) => { let ty = TyBuilder::def_ty(self.db, it.into()) .fill(std::iter::repeat_with(|| self.table.new_type_var())) .build(); self.resolve_variant_on_alias(ty, unresolved, path) } TypeNs::AdtSelfType(_) => { // FIXME this could happen in array size expressions, once we're checking them (self.err_ty(), None) } TypeNs::GenericParam(_) => { // FIXME potentially resolve assoc type (self.err_ty(), None) } TypeNs::AdtId(AdtId::EnumId(_)) | TypeNs::BuiltinType(_) | TypeNs::TraitId(_) => { // FIXME diagnostic (self.err_ty(), None) } }; fn forbid_unresolved_segments( result: (Ty, Option), unresolved: Option, ) -> (Ty, Option) { if unresolved.is_none() { result } else { // FIXME diagnostic (TyKind::Error.intern(&Interner), None) } } } fn resolve_variant_on_alias( &mut self, ty: Ty, unresolved: Option, path: &Path, ) -> (Ty, Option) { match unresolved { None => { let variant = ty.as_adt().and_then(|(adt_id, _)| match adt_id { AdtId::StructId(s) => Some(VariantId::StructId(s)), AdtId::UnionId(u) => Some(VariantId::UnionId(u)), AdtId::EnumId(_) => { // FIXME Error E0071, expected struct, variant or union type, found enum `Foo` None } }); (ty, variant) } Some(1) => { let segment = path.mod_path().segments().last().unwrap(); // this could be an enum variant or associated type if let Some((AdtId::EnumId(enum_id), _)) = ty.as_adt() { let enum_data = self.db.enum_data(enum_id); if let Some(local_id) = enum_data.variant(segment) { let variant = EnumVariantId { parent: enum_id, local_id }; return (ty, Some(variant.into())); } } // FIXME potentially resolve assoc type (self.err_ty(), None) } Some(_) => { // FIXME diagnostic (self.err_ty(), None) } } } fn collect_const(&mut self, data: &ConstData) { self.return_ty = self.make_ty(&data.type_ref); } fn collect_static(&mut self, data: &StaticData) { self.return_ty = self.make_ty(&data.type_ref); } fn collect_fn(&mut self, data: &FunctionData) { let body = Arc::clone(&self.body); // avoid borrow checker problem let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver) .with_impl_trait_mode(ImplTraitLoweringMode::Param); let param_tys = data.params.iter().map(|type_ref| ctx.lower_ty(type_ref)).collect::>(); for (ty, pat) in param_tys.into_iter().zip(body.params.iter()) { let ty = self.insert_type_vars(ty); let ty = self.normalize_associated_types_in(ty); self.infer_pat(*pat, &ty, BindingMode::default()); } let return_ty = self.make_ty_with_mode(&data.ret_type, ImplTraitLoweringMode::Disallowed); // FIXME implement RPIT self.return_ty = return_ty; } fn infer_body(&mut self) { self.infer_expr_coerce(self.body.body_expr, &Expectation::has_type(self.return_ty.clone())); } fn resolve_lang_item(&self, name: &str) -> Option { let krate = self.resolver.krate()?; let name = SmolStr::new_inline(name); self.db.lang_item(krate, name) } fn resolve_into_iter_item(&self) -> Option { let path = path![core::iter::IntoIterator]; let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?; self.db.trait_data(trait_).associated_type_by_name(&name![Item]) } fn resolve_ops_try_ok(&self) -> Option { let path = path![core::ops::Try]; let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?; self.db.trait_data(trait_).associated_type_by_name(&name![Ok]) } fn resolve_ops_neg_output(&self) -> Option { let trait_ = self.resolve_lang_item("neg")?.as_trait()?; self.db.trait_data(trait_).associated_type_by_name(&name![Output]) } fn resolve_ops_not_output(&self) -> Option { let trait_ = self.resolve_lang_item("not")?.as_trait()?; self.db.trait_data(trait_).associated_type_by_name(&name![Output]) } fn resolve_future_future_output(&self) -> Option { let trait_ = self.resolve_lang_item("future_trait")?.as_trait()?; self.db.trait_data(trait_).associated_type_by_name(&name![Output]) } fn resolve_binary_op_output(&self, bop: &BinaryOp) -> Option { let lang_item = match bop { BinaryOp::ArithOp(aop) => match aop { ArithOp::Add => "add", ArithOp::Sub => "sub", ArithOp::Mul => "mul", ArithOp::Div => "div", ArithOp::Shl => "shl", ArithOp::Shr => "shr", ArithOp::Rem => "rem", ArithOp::BitXor => "bitxor", ArithOp::BitOr => "bitor", ArithOp::BitAnd => "bitand", }, _ => return None, }; let trait_ = self.resolve_lang_item(lang_item)?.as_trait(); self.db.trait_data(trait_?).associated_type_by_name(&name![Output]) } fn resolve_boxed_box(&self) -> Option { let struct_ = self.resolve_lang_item("owned_box")?.as_struct()?; Some(struct_.into()) } fn resolve_range_full(&self) -> Option { let path = path![core::ops::RangeFull]; let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?; Some(struct_.into()) } fn resolve_range(&self) -> Option { let path = path![core::ops::Range]; let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?; Some(struct_.into()) } fn resolve_range_inclusive(&self) -> Option { let path = path![core::ops::RangeInclusive]; let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?; Some(struct_.into()) } fn resolve_range_from(&self) -> Option { let path = path![core::ops::RangeFrom]; let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?; Some(struct_.into()) } fn resolve_range_to(&self) -> Option { let path = path![core::ops::RangeTo]; let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?; Some(struct_.into()) } fn resolve_range_to_inclusive(&self) -> Option { let path = path![core::ops::RangeToInclusive]; let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?; Some(struct_.into()) } fn resolve_ops_index(&self) -> Option { self.resolve_lang_item("index")?.as_trait() } fn resolve_ops_index_output(&self) -> Option { let trait_ = self.resolve_ops_index()?; self.db.trait_data(trait_).associated_type_by_name(&name![Output]) } } /// When inferring an expression, we propagate downward whatever type hint we /// are able in the form of an `Expectation`. #[derive(Clone, PartialEq, Eq, Debug)] struct Expectation { ty: Ty, /// See the `rvalue_hint` method. rvalue_hint: bool, } impl Expectation { /// The expectation that the type of the expression needs to equal the given /// type. fn has_type(ty: Ty) -> Self { Expectation { ty, rvalue_hint: false } } /// The following explanation is copied straight from rustc: /// Provides an expectation for an rvalue expression given an *optional* /// hint, which is not required for type safety (the resulting type might /// be checked higher up, as is the case with `&expr` and `box expr`), but /// is useful in determining the concrete type. /// /// The primary use case is where the expected type is a fat pointer, /// like `&[isize]`. For example, consider the following statement: /// /// let x: &[isize] = &[1, 2, 3]; /// /// In this case, the expected type for the `&[1, 2, 3]` expression is /// `&[isize]`. If however we were to say that `[1, 2, 3]` has the /// expectation `ExpectHasType([isize])`, that would be too strong -- /// `[1, 2, 3]` does not have the type `[isize]` but rather `[isize; 3]`. /// It is only the `&[1, 2, 3]` expression as a whole that can be coerced /// to the type `&[isize]`. Therefore, we propagate this more limited hint, /// which still is useful, because it informs integer literals and the like. /// See the test case `test/ui/coerce-expect-unsized.rs` and #20169 /// for examples of where this comes up,. fn rvalue_hint(ty: Ty) -> Self { Expectation { ty, rvalue_hint: true } } /// This expresses no expectation on the type. fn none() -> Self { Expectation { // FIXME ty: TyKind::Error.intern(&Interner), rvalue_hint: false, } } fn coercion_target(&self) -> Ty { if self.rvalue_hint { // FIXME TyKind::Error.intern(&Interner) } else { self.ty.clone() } } } #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] enum Diverges { Maybe, Always, } impl Diverges { fn is_always(self) -> bool { self == Diverges::Always } } impl std::ops::BitAnd for Diverges { type Output = Self; fn bitand(self, other: Self) -> Self { std::cmp::min(self, other) } } impl std::ops::BitOr for Diverges { type Output = Self; fn bitor(self, other: Self) -> Self { std::cmp::max(self, other) } } impl std::ops::BitAndAssign for Diverges { fn bitand_assign(&mut self, other: Self) { *self = *self & other; } } impl std::ops::BitOrAssign for Diverges { fn bitor_assign(&mut self, other: Self) { *self = *self | other; } } mod diagnostics { use hir_def::{expr::ExprId, DefWithBodyId}; use hir_expand::diagnostics::DiagnosticSink; use crate::{ db::HirDatabase, diagnostics::{BreakOutsideOfLoop, NoSuchField}, }; #[derive(Debug, PartialEq, Eq, Clone)] pub(super) enum InferenceDiagnostic { NoSuchField { expr: ExprId }, BreakOutsideOfLoop { expr: ExprId }, } impl InferenceDiagnostic { pub(super) fn add_to( &self, db: &dyn HirDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink, ) { match self { InferenceDiagnostic::NoSuchField { expr } => { let (_, source_map) = db.body_with_source_map(owner); let field = source_map.field_syntax(*expr); sink.push(NoSuchField { file: field.file_id, field: field.value }) } InferenceDiagnostic::BreakOutsideOfLoop { expr } => { let (_, source_map) = db.body_with_source_map(owner); let ptr = source_map .expr_syntax(*expr) .expect("break outside of loop in synthetic syntax"); sink.push(BreakOutsideOfLoop { file: ptr.file_id, expr: ptr.value }) } } } } }