Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir/src/source_analyzer.rs')
| -rw-r--r-- | crates/hir/src/source_analyzer.rs | 198 |
1 files changed, 162 insertions, 36 deletions
diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index 6ba7a42c19..c6f2d151f5 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -17,7 +17,7 @@ use hir_def::{ path::Path, scope::{ExprScopes, ScopeId}, }, - hir::{BindingId, Expr, ExprId, ExprOrPatId, Pat}, + hir::{BindingId, Expr, ExprId, ExprOrPatId, Pat, PatId}, lang_item::LangItems, nameres::MacroSubNs, resolver::{HasResolver, Resolver, TypeNs, ValueNs, resolver_for_scope}, @@ -44,6 +44,7 @@ use hir_ty::{ }; use intern::sym; use itertools::Itertools; +use rustc_hash::FxHashSet; use rustc_type_ir::{ AliasTyKind, inherent::{AdtDef, IntoKind, Ty as _}, @@ -531,18 +532,12 @@ impl<'db> SourceAnalyzer<'db> { db: &'db dyn HirDatabase, range_pat: &ast::RangePat, ) -> Option<StructId> { - let path: ModPath = match (range_pat.op_kind()?, range_pat.start(), range_pat.end()) { - (RangeOp::Exclusive, None, Some(_)) => path![core::ops::RangeTo], - (RangeOp::Exclusive, Some(_), None) => path![core::ops::RangeFrom], - (RangeOp::Exclusive, Some(_), Some(_)) => path![core::ops::Range], - (RangeOp::Inclusive, None, Some(_)) => path![core::ops::RangeToInclusive], - (RangeOp::Inclusive, Some(_), Some(_)) => path![core::ops::RangeInclusive], - - (RangeOp::Exclusive, None, None) => return None, - (RangeOp::Inclusive, None, None) => return None, - (RangeOp::Inclusive, Some(_), None) => return None, - }; - self.resolver.resolve_known_struct(db, &path) + self.resolve_range_struct( + db, + range_pat.op_kind()?, + range_pat.start().is_some(), + range_pat.end().is_some(), + ) } pub(crate) fn resolve_range_expr( @@ -550,19 +545,59 @@ impl<'db> SourceAnalyzer<'db> { db: &'db dyn HirDatabase, range_expr: &ast::RangeExpr, ) -> Option<StructId> { - let path: ModPath = match (range_expr.op_kind()?, range_expr.start(), range_expr.end()) { - (RangeOp::Exclusive, None, None) => path![core::ops::RangeFull], - (RangeOp::Exclusive, None, Some(_)) => path![core::ops::RangeTo], - (RangeOp::Exclusive, Some(_), None) => path![core::ops::RangeFrom], - (RangeOp::Exclusive, Some(_), Some(_)) => path![core::ops::Range], - (RangeOp::Inclusive, None, Some(_)) => path![core::ops::RangeToInclusive], - (RangeOp::Inclusive, Some(_), Some(_)) => path![core::ops::RangeInclusive], + self.resolve_range_struct( + db, + range_expr.op_kind()?, + range_expr.start().is_some(), + range_expr.end().is_some(), + ) + } + fn resolve_range_struct( + &self, + db: &'db dyn HirDatabase, + op_kind: RangeOp, + has_start: bool, + has_end: bool, + ) -> Option<StructId> { + let has_new_range = + self.resolver.top_level_def_map().is_unstable_feature_enabled(&sym::new_range); + let lang_items = self.lang_items(db); + match (op_kind, has_start, has_end) { + (RangeOp::Exclusive, false, false) => lang_items.RangeFull, + (RangeOp::Exclusive, false, true) => lang_items.RangeTo, + (RangeOp::Exclusive, true, false) => { + if has_new_range { + lang_items.RangeFromCopy + } else { + lang_items.RangeFrom + } + } + (RangeOp::Exclusive, true, true) => { + if has_new_range { + lang_items.RangeCopy + } else { + lang_items.Range + } + } + (RangeOp::Inclusive, false, true) => { + if has_new_range { + lang_items.RangeToInclusiveCopy + } else { + lang_items.RangeToInclusive + } + } + (RangeOp::Inclusive, true, true) => { + if has_new_range { + lang_items.RangeInclusiveCopy + } else { + lang_items.RangeInclusiveStruct + } + } // [E0586] inclusive ranges must be bounded at the end - (RangeOp::Inclusive, None, None) => return None, - (RangeOp::Inclusive, Some(_), None) => return None, - }; - self.resolver.resolve_known_struct(db, &path) + (RangeOp::Inclusive, false, false) => None, + (RangeOp::Inclusive, true, false) => None, + } } pub(crate) fn resolve_await_to_poll( @@ -1241,21 +1276,31 @@ impl<'db> SourceAnalyzer<'db> { let body = self.store()?; let infer = self.infer()?; - let expr_id = self.expr_id(literal.clone().into())?; - let substs = infer.expr_or_pat_ty(expr_id).as_adt()?.1; - - let (variant, missing_fields, _exhaustive) = match expr_id { - ExprOrPatId::ExprId(expr_id) => { - record_literal_missing_fields(db, infer, expr_id, &body[expr_id])? - } - ExprOrPatId::PatId(pat_id) => { - record_pattern_missing_fields(db, infer, pat_id, &body[pat_id])? - } - }; + let expr_id = self.expr_id(literal.clone().into())?.as_expr()?; + let substs = infer.expr_ty(expr_id).as_adt()?.1; + let (variant, missing_fields) = + record_literal_missing_fields(db, infer, expr_id, &body[expr_id])?; let res = self.missing_fields(db, substs, variant, missing_fields); Some(res) } + pub(crate) fn record_literal_matched_fields( + &self, + db: &'db dyn HirDatabase, + literal: &ast::RecordExpr, + ) -> Option<Vec<(Field, Type<'db>)>> { + let body = self.store()?; + let infer = self.infer()?; + + let expr_id = self.expr_id(literal.clone().into())?.as_expr()?; + let substs = infer.expr_ty(expr_id).as_adt()?.1; + let (variant, matched_fields) = + record_literal_matched_fields(db, infer, expr_id, &body[expr_id])?; + + let res = self.missing_fields(db, substs, variant, matched_fields); + Some(res) + } + pub(crate) fn record_pattern_missing_fields( &self, db: &'db dyn HirDatabase, @@ -1267,12 +1312,29 @@ impl<'db> SourceAnalyzer<'db> { let pat_id = self.pat_id(&pattern.clone().into())?.as_pat()?; let substs = infer.pat_ty(pat_id).as_adt()?.1; - let (variant, missing_fields, _exhaustive) = + let (variant, missing_fields) = record_pattern_missing_fields(db, infer, pat_id, &body[pat_id])?; let res = self.missing_fields(db, substs, variant, missing_fields); Some(res) } + pub(crate) fn record_pattern_matched_fields( + &self, + db: &'db dyn HirDatabase, + pattern: &ast::RecordPat, + ) -> Option<Vec<(Field, Type<'db>)>> { + let body = self.store()?; + let infer = self.infer()?; + + let pat_id = self.pat_id(&pattern.clone().into())?.as_pat()?; + let substs = infer.pat_ty(pat_id).as_adt()?.1; + + let (variant, matched_fields) = + record_pattern_matched_fields(db, infer, pat_id, &body[pat_id])?; + let res = self.missing_fields(db, substs, variant, matched_fields); + Some(res) + } + fn missing_fields( &self, db: &'db dyn HirDatabase, @@ -1810,3 +1872,67 @@ pub(crate) fn name_hygiene(db: &dyn HirDatabase, name: InFile<&SyntaxNode>) -> H let ctx = span_map.span_at(name.value.text_range().start()).ctx; HygieneId::new(ctx.opaque_and_semiopaque(db)) } + +fn record_literal_matched_fields( + db: &dyn HirDatabase, + infer: &InferenceResult, + id: ExprId, + expr: &Expr, +) -> Option<(VariantId, Vec<LocalFieldId>)> { + let (fields, _spread) = match expr { + Expr::RecordLit { fields, spread, .. } => (fields, spread), + _ => return None, + }; + + let variant_def = infer.variant_resolution_for_expr(id)?; + if let VariantId::UnionId(_) = variant_def { + return None; + } + + let variant_data = variant_def.fields(db); + + let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect(); + // suggest fields if: + // - not in code + let matched_fields: Vec<LocalFieldId> = variant_data + .fields() + .iter() + .filter_map(|(f, d)| (!specified_fields.contains(&d.name)).then_some(f)) + .collect(); + if matched_fields.is_empty() { + return None; + } + Some((variant_def, matched_fields)) +} + +fn record_pattern_matched_fields( + db: &dyn HirDatabase, + infer: &InferenceResult, + id: PatId, + pat: &Pat, +) -> Option<(VariantId, Vec<LocalFieldId>)> { + let (fields, _ellipsis) = match pat { + Pat::Record { path: _, args, ellipsis } => (args, *ellipsis), + _ => return None, + }; + + let variant_def = infer.variant_resolution_for_pat(id)?; + if let VariantId::UnionId(_) = variant_def { + return None; + } + + let variant_data = variant_def.fields(db); + + let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect(); + // suggest fields if: + // - not in code + let matched_fields: Vec<LocalFieldId> = variant_data + .fields() + .iter() + .filter_map(|(f, d)| if !specified_fields.contains(&d.name) { Some(f) } else { None }) + .collect(); + if matched_fields.is_empty() { + return None; + } + Some((variant_def, matched_fields)) +} |