diff --git a/crates/hir-def/src/expr_store.rs b/crates/hir-def/src/expr_store.rs index 10cd460d1d36..edbfd42d1314 100644 --- a/crates/hir-def/src/expr_store.rs +++ b/crates/hir-def/src/expr_store.rs @@ -32,7 +32,7 @@ use crate::{ expr_store::path::Path, hir::{ Array, AsmOperand, Binding, BindingId, Expr, ExprId, ExprOrPatId, Label, LabelId, Pat, - PatId, RecordFieldPat, Statement, + PatId, RecordFieldPat, RecordSpread, Statement, }, nameres::{DefMap, block_def_map}, type_ref::{LifetimeRef, LifetimeRefId, PathId, TypeRef, TypeRefId}, @@ -575,8 +575,8 @@ impl ExpressionStore { for field in fields.iter() { f(field.expr); } - if let &Some(expr) = spread { - f(expr); + if let RecordSpread::Expr(expr) = spread { + f(*expr); } } Expr::Closure { body, .. } => { @@ -706,8 +706,8 @@ impl ExpressionStore { for field in fields.iter() { f(field.expr); } - if let &Some(expr) = spread { - f(expr); + if let RecordSpread::Expr(expr) = spread { + f(*expr); } } Expr::Closure { body, .. } => { diff --git a/crates/hir-def/src/expr_store/lower.rs b/crates/hir-def/src/expr_store/lower.rs index d3774ded39dc..8353dc67bfc1 100644 --- a/crates/hir-def/src/expr_store/lower.rs +++ b/crates/hir-def/src/expr_store/lower.rs @@ -47,7 +47,7 @@ use crate::{ hir::{ Array, Binding, BindingAnnotation, BindingId, BindingProblems, CaptureBy, ClosureKind, Expr, ExprId, Item, Label, LabelId, Literal, MatchArm, Movability, OffsetOf, Pat, PatId, - RecordFieldPat, RecordLitField, Statement, generics::GenericParams, + RecordFieldPat, RecordLitField, RecordSpread, Statement, generics::GenericParams, }, item_scope::BuiltinShadowMode, item_tree::FieldsShape, @@ -1232,10 +1232,16 @@ impl<'db> ExprCollector<'db> { Some(RecordLitField { name, expr }) }) .collect(); - let spread = nfl.spread().map(|s| self.collect_expr(s)); + let spread_expr = nfl.spread().map(|s| self.collect_expr(s)); + let has_spread_syntax = nfl.dotdot_token().is_some(); + let spread = match (spread_expr, has_spread_syntax) { + (None, false) => RecordSpread::None, + (None, true) => RecordSpread::FieldDefaults, + (Some(expr), _) => RecordSpread::Expr(expr), + }; Expr::RecordLit { path, fields, spread } } else { - Expr::RecordLit { path, fields: Box::default(), spread: None } + Expr::RecordLit { path, fields: Box::default(), spread: RecordSpread::None } }; self.alloc_expr(record_lit, syntax_ptr) @@ -1961,7 +1967,7 @@ impl<'db> ExprCollector<'db> { } } - fn collect_expr_opt(&mut self, expr: Option) -> ExprId { + pub fn collect_expr_opt(&mut self, expr: Option) -> ExprId { match expr { Some(expr) => self.collect_expr(expr), None => self.missing_expr(), diff --git a/crates/hir-def/src/expr_store/lower/format_args.rs b/crates/hir-def/src/expr_store/lower/format_args.rs index 7ef84f27f664..51616afb3892 100644 --- a/crates/hir-def/src/expr_store/lower/format_args.rs +++ b/crates/hir-def/src/expr_store/lower/format_args.rs @@ -10,7 +10,8 @@ use crate::{ builtin_type::BuiltinUint, expr_store::{HygieneId, lower::ExprCollector, path::Path}, hir::{ - Array, BindingAnnotation, Expr, ExprId, Literal, Pat, RecordLitField, Statement, + Array, BindingAnnotation, Expr, ExprId, Literal, Pat, RecordLitField, RecordSpread, + Statement, format_args::{ self, FormatAlignment, FormatArgs, FormatArgsPiece, FormatArgument, FormatArgumentKind, FormatArgumentsCollector, FormatCount, FormatDebugHex, FormatOptions, @@ -869,7 +870,7 @@ impl<'db> ExprCollector<'db> { self.alloc_expr_desugared(Expr::RecordLit { path: self.lang_path(lang_items.FormatPlaceholder).map(Box::new), fields: Box::new([position, flags, precision, width]), - spread: None, + spread: RecordSpread::None, }) } else { let format_placeholder_new = diff --git a/crates/hir-def/src/expr_store/pretty.rs b/crates/hir-def/src/expr_store/pretty.rs index f5ef8e1a3595..35f3cd114e36 100644 --- a/crates/hir-def/src/expr_store/pretty.rs +++ b/crates/hir-def/src/expr_store/pretty.rs @@ -16,7 +16,8 @@ use crate::{ attrs::AttrFlags, expr_store::path::{GenericArg, GenericArgs}, hir::{ - Array, BindingAnnotation, CaptureBy, ClosureKind, Literal, Movability, Statement, + Array, BindingAnnotation, CaptureBy, ClosureKind, Literal, Movability, RecordSpread, + Statement, generics::{GenericParams, WherePredicate}, }, lang_item::LangItemTarget, @@ -139,7 +140,7 @@ pub fn print_variant_body_hir(db: &dyn DefDatabase, owner: VariantId, edition: E } for (_, data) in fields.fields().iter() { - let FieldData { name, type_ref, visibility, is_unsafe } = data; + let FieldData { name, type_ref, visibility, is_unsafe, default_value: _ } = data; match visibility { crate::item_tree::RawVisibility::Module(interned, _visibility_explicitness) => { w!(p, "pub(in {})", interned.display(db, p.edition)) @@ -679,10 +680,17 @@ impl Printer<'_> { p.print_expr(field.expr); wln!(p, ","); } - if let Some(spread) = spread { - w!(p, ".."); - p.print_expr(*spread); - wln!(p); + match spread { + RecordSpread::None => {} + RecordSpread::FieldDefaults => { + w!(p, ".."); + wln!(p); + } + RecordSpread::Expr(spread_expr) => { + w!(p, ".."); + p.print_expr(*spread_expr); + wln!(p); + } } }); w!(self, "}}"); diff --git a/crates/hir-def/src/hir.rs b/crates/hir-def/src/hir.rs index 53be0de7d9c3..7781a8fe54ee 100644 --- a/crates/hir-def/src/hir.rs +++ b/crates/hir-def/src/hir.rs @@ -187,6 +187,13 @@ impl From for Literal { } } +#[derive(Debug, Clone, Eq, PartialEq, Copy)] +pub enum RecordSpread { + None, + FieldDefaults, + Expr(ExprId), +} + #[derive(Debug, Clone, Eq, PartialEq)] pub enum Expr { /// This is produced if the syntax tree does not have a required expression piece. @@ -259,7 +266,7 @@ pub enum Expr { RecordLit { path: Option>, fields: Box<[RecordLitField]>, - spread: Option, + spread: RecordSpread, }, Field { expr: ExprId, diff --git a/crates/hir-def/src/signatures.rs b/crates/hir-def/src/signatures.rs index 0dd88edbfb08..37c8f762fe5d 100644 --- a/crates/hir-def/src/signatures.rs +++ b/crates/hir-def/src/signatures.rs @@ -12,7 +12,7 @@ use intern::{Symbol, sym}; use la_arena::{Arena, Idx}; use rustc_abi::{IntegerType, ReprOptions}; use syntax::{ - NodeOrToken, SyntaxNodePtr, T, + AstNode, NodeOrToken, SyntaxNodePtr, T, ast::{self, HasGenericParams, HasName, HasVisibility, IsString}, }; use thin_vec::ThinVec; @@ -754,6 +754,7 @@ pub struct FieldData { pub type_ref: TypeRefId, pub visibility: RawVisibility, pub is_unsafe: bool, + pub default_value: Option, } pub type LocalFieldId = Idx; @@ -903,7 +904,14 @@ fn lower_fields( .filter_map(NodeOrToken::into_token) .any(|token| token.kind() == T![unsafe]); let name = field_name(idx, &field); - arena.alloc(FieldData { name, type_ref, visibility, is_unsafe }); + + // Check if field has default value (only for record fields) + let default_value = ast::RecordField::cast(field.syntax().clone()) + .and_then(|rf| rf.eq_token().is_some().then_some(rf.expr())) + .flatten() + .map(|expr| col.collect_expr_opt(Some(expr))); + + arena.alloc(FieldData { name, type_ref, visibility, is_unsafe, default_value }); idx += 1; } Err(cfg) => { diff --git a/crates/hir-ty/src/diagnostics/expr.rs b/crates/hir-ty/src/diagnostics/expr.rs index dd1fc3b36ef8..4e1bb6f4c533 100644 --- a/crates/hir-ty/src/diagnostics/expr.rs +++ b/crates/hir-ty/src/diagnostics/expr.rs @@ -41,7 +41,7 @@ use crate::{ pub(crate) use hir_def::{ LocalFieldId, VariantId, expr_store::Body, - hir::{Expr, ExprId, MatchArm, Pat, PatId, Statement}, + hir::{Expr, ExprId, MatchArm, Pat, PatId, RecordSpread, Statement}, }; pub enum BodyValidationDiagnostic { @@ -123,7 +123,7 @@ impl<'db> ExprValidator<'db> { } for (id, expr) in body.exprs() { - if let Some((variant, missed_fields, true)) = + if let Some((variant, missed_fields)) = record_literal_missing_fields(db, self.infer, id, expr) { self.diagnostics.push(BodyValidationDiagnostic::RecordMissingFields { @@ -154,7 +154,7 @@ impl<'db> ExprValidator<'db> { } for (id, pat) in body.pats() { - if let Some((variant, missed_fields, true)) = + if let Some((variant, missed_fields)) = record_pattern_missing_fields(db, self.infer, id, pat) { self.diagnostics.push(BodyValidationDiagnostic::RecordMissingFields { @@ -557,9 +557,9 @@ pub fn record_literal_missing_fields( infer: &InferenceResult, id: ExprId, expr: &Expr, -) -> Option<(VariantId, Vec, /*exhaustive*/ bool)> { - let (fields, exhaustive) = match expr { - Expr::RecordLit { fields, spread, .. } => (fields, spread.is_none()), +) -> Option<(VariantId, Vec)> { + let (fields, spread) = match expr { + Expr::RecordLit { fields, spread, .. } => (fields, spread), _ => return None, }; @@ -571,15 +571,28 @@ pub fn record_literal_missing_fields( let variant_data = variant_def.fields(db); let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect(); + // don't show missing fields if: + // - has ..expr + // - or has default value + .. + // - or already in code let missed_fields: Vec = variant_data .fields() .iter() - .filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) }) + .filter_map(|(f, d)| { + if specified_fields.contains(&d.name) + || matches!(spread, RecordSpread::Expr(_)) + || (d.default_value.is_some() && matches!(spread, RecordSpread::FieldDefaults)) + { + None + } else { + Some(f) + } + }) .collect(); if missed_fields.is_empty() { return None; } - Some((variant_def, missed_fields, exhaustive)) + Some((variant_def, missed_fields)) } pub fn record_pattern_missing_fields( @@ -587,9 +600,9 @@ pub fn record_pattern_missing_fields( infer: &InferenceResult, id: PatId, pat: &Pat, -) -> Option<(VariantId, Vec, /*exhaustive*/ bool)> { - let (fields, exhaustive) = match pat { - Pat::Record { path: _, args, ellipsis } => (args, !ellipsis), +) -> Option<(VariantId, Vec)> { + let (fields, ellipsis) = match pat { + Pat::Record { path: _, args, ellipsis } => (args, *ellipsis), _ => return None, }; @@ -601,15 +614,22 @@ pub fn record_pattern_missing_fields( let variant_data = variant_def.fields(db); let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect(); + // don't show missing fields if: + // - in code + // - or has .. let missed_fields: Vec = variant_data .fields() .iter() - .filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) }) + .filter_map( + |(f, d)| { + if specified_fields.contains(&d.name) || ellipsis { None } else { Some(f) } + }, + ) .collect(); if missed_fields.is_empty() { return None; } - Some((variant_def, missed_fields, exhaustive)) + Some((variant_def, missed_fields)) } fn types_of_subpatterns_do_match(pat: PatId, body: &Body, infer: &InferenceResult) -> bool { diff --git a/crates/hir-ty/src/infer/closure/analysis.rs b/crates/hir-ty/src/infer/closure/analysis.rs index b25901cc3b99..5a3eba1a71ae 100644 --- a/crates/hir-ty/src/infer/closure/analysis.rs +++ b/crates/hir-ty/src/infer/closure/analysis.rs @@ -8,7 +8,7 @@ use hir_def::{ expr_store::path::Path, hir::{ Array, AsmOperand, BinaryOp, BindingId, CaptureBy, Expr, ExprId, ExprOrPatId, Pat, PatId, - Statement, UnaryOp, + RecordSpread, Statement, UnaryOp, }, item_tree::FieldsShape, resolver::ValueNs, @@ -627,7 +627,7 @@ impl<'db> InferenceContext<'_, 'db> { self.consume_expr(expr); } Expr::RecordLit { fields, spread, .. } => { - if let &Some(expr) = spread { + if let RecordSpread::Expr(expr) = *spread { self.consume_expr(expr); } self.consume_exprs(fields.iter().map(|it| it.expr)); diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 226e9f5cd667..3dc963c4d2cb 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -8,7 +8,7 @@ use hir_def::{ expr_store::path::{GenericArgs as HirGenericArgs, Path}, hir::{ Array, AsmOperand, AsmOptions, BinaryOp, BindingAnnotation, Expr, ExprId, ExprOrPatId, - LabelId, Literal, Pat, PatId, Statement, UnaryOp, + LabelId, Literal, Pat, PatId, RecordSpread, Statement, UnaryOp, }, resolver::ValueNs, }; @@ -657,8 +657,8 @@ impl<'db> InferenceContext<'_, 'db> { } } } - if let Some(expr) = spread { - self.infer_expr(*expr, &Expectation::has_type(ty), ExprIsRead::Yes); + if let RecordSpread::Expr(expr) = *spread { + self.infer_expr(expr, &Expectation::has_type(ty), ExprIsRead::Yes); } ty } diff --git a/crates/hir-ty/src/infer/mutability.rs b/crates/hir-ty/src/infer/mutability.rs index 729ed214daea..45fa141b6d3d 100644 --- a/crates/hir-ty/src/infer/mutability.rs +++ b/crates/hir-ty/src/infer/mutability.rs @@ -2,7 +2,8 @@ //! between `Deref` and `DerefMut` or `Index` and `IndexMut` or similar. use hir_def::hir::{ - Array, AsmOperand, BinaryOp, BindingAnnotation, Expr, ExprId, Pat, PatId, Statement, UnaryOp, + Array, AsmOperand, BinaryOp, BindingAnnotation, Expr, ExprId, Pat, PatId, RecordSpread, + Statement, UnaryOp, }; use rustc_ast_ir::Mutability; @@ -132,8 +133,11 @@ impl<'db> InferenceContext<'_, 'db> { Expr::Become { expr } => { self.infer_mut_expr(*expr, Mutability::Not); } - Expr::RecordLit { path: _, fields, spread } => { - self.infer_mut_not_expr_iter(fields.iter().map(|it| it.expr).chain(*spread)) + Expr::RecordLit { path: _, fields, spread, .. } => { + self.infer_mut_not_expr_iter(fields.iter().map(|it| it.expr)); + if let RecordSpread::Expr(expr) = *spread { + self.infer_mut_expr(expr, Mutability::Not); + } } &Expr::Index { base, index } => { if mutability == Mutability::Mut { diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs index 1579f00e9266..199db7a3e718 100644 --- a/crates/hir-ty/src/mir/lower.rs +++ b/crates/hir-ty/src/mir/lower.rs @@ -9,7 +9,7 @@ use hir_def::{ expr_store::{Body, ExpressionStore, HygieneId, path::Path}, hir::{ ArithOp, Array, BinaryOp, BindingAnnotation, BindingId, ExprId, LabelId, Literal, MatchArm, - Pat, PatId, RecordFieldPat, RecordLitField, + Pat, PatId, RecordFieldPat, RecordLitField, RecordSpread, }, item_tree::FieldsShape, lang_item::LangItems, @@ -867,16 +867,17 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { } Expr::Become { .. } => not_supported!("tail-calls"), Expr::Yield { .. } => not_supported!("yield"), - Expr::RecordLit { fields, path, spread } => { - let spread_place = match spread { - &Some(it) => { + Expr::RecordLit { fields, path, spread, .. } => { + let spread_place = match *spread { + RecordSpread::Expr(it) => { let Some((p, c)) = self.lower_expr_as_place(current, it, true)? else { return Ok(None); }; current = c; Some(p) } - None => None, + RecordSpread::None => None, + RecordSpread::FieldDefaults => not_supported!("empty record spread"), }; let variant_id = self.infer.variant_resolution_for_expr(expr_id).ok_or_else(|| match path { diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index f4c42537de93..48b874d14b70 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -1981,6 +1981,15 @@ impl<'db> SemanticsImpl<'db> { .unwrap_or_default() } + pub fn record_literal_matched_fields( + &self, + literal: &ast::RecordExpr, + ) -> Vec<(Field, Type<'db>)> { + self.analyze(literal.syntax()) + .and_then(|it| it.record_literal_matched_fields(self.db, literal)) + .unwrap_or_default() + } + pub fn record_pattern_missing_fields( &self, pattern: &ast::RecordPat, @@ -1990,6 +1999,15 @@ impl<'db> SemanticsImpl<'db> { .unwrap_or_default() } + pub fn record_pattern_matched_fields( + &self, + pattern: &ast::RecordPat, + ) -> Vec<(Field, Type<'db>)> { + self.analyze(pattern.syntax()) + .and_then(|it| it.record_pattern_matched_fields(self.db, pattern)) + .unwrap_or_default() + } + fn with_ctx) -> T, T>(&self, f: F) -> T { let mut ctx = SourceToDefCtx { db: self.db, cache: &mut self.s2d_cache.borrow_mut() }; f(&mut ctx) diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index bf123e13f94d..85d3484312d9 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 _}, @@ -1241,21 +1242,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)>> { + 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 +1278,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)>> { + 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 +1838,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_semitransparent(db)) } + +fn record_literal_matched_fields( + db: &dyn HirDatabase, + infer: &InferenceResult, + id: ExprId, + expr: &Expr, +) -> Option<(VariantId, Vec)> { + 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 = 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)> { + 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 = 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)) +} diff --git a/crates/ide-assists/src/handlers/expand_rest_pattern.rs b/crates/ide-assists/src/handlers/expand_rest_pattern.rs index b746099e7279..867ac4851864 100644 --- a/crates/ide-assists/src/handlers/expand_rest_pattern.rs +++ b/crates/ide-assists/src/handlers/expand_rest_pattern.rs @@ -33,8 +33,8 @@ fn expand_record_rest_pattern( record_pat: ast::RecordPat, rest_pat: ast::RestPat, ) -> Option<()> { - let missing_fields = ctx.sema.record_pattern_missing_fields(&record_pat); - if missing_fields.is_empty() { + let matched_fields = ctx.sema.record_pattern_matched_fields(&record_pat); + if matched_fields.is_empty() { cov_mark::hit!(no_missing_fields); return None; } @@ -53,7 +53,7 @@ fn expand_record_rest_pattern( |builder| { let make = SyntaxFactory::with_mappings(); let mut editor = builder.make_editor(rest_pat.syntax()); - let new_fields = old_field_list.fields().chain(missing_fields.iter().map(|(f, _)| { + let new_fields = old_field_list.fields().chain(matched_fields.iter().map(|(f, _)| { make.record_pat_field_shorthand( make.ident_pat( false, diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index 77734c5d6f98..8c532e0f4d04 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -340,7 +340,7 @@ pub(crate) fn complete_expr_path( let missing_fields = ctx.sema.record_literal_missing_fields(record_expr); if !missing_fields.is_empty() { - add_default_update(acc, ctx, ty); + add_default_update(acc, ctx, ty.as_ref()); } } }; diff --git a/crates/ide-completion/src/completions/record.rs b/crates/ide-completion/src/completions/record.rs index c5bfdcb8b734..12c564af5cba 100644 --- a/crates/ide-completion/src/completions/record.rs +++ b/crates/ide-completion/src/completions/record.rs @@ -36,7 +36,7 @@ pub(crate) fn complete_record_pattern_fields( true => return, } } - _ => ctx.sema.record_pattern_missing_fields(record_pat), + _ => ctx.sema.record_pattern_matched_fields(record_pat), }; complete_fields(acc, ctx, missing_fields); } @@ -69,14 +69,14 @@ pub(crate) fn complete_record_expr_fields( } } _ => { - let missing_fields = ctx.sema.record_literal_missing_fields(record_expr); + let suggest_fields = ctx.sema.record_literal_matched_fields(record_expr); let update_exists = record_expr .record_expr_field_list() .is_some_and(|list| list.dotdot_token().is_some()); - if !missing_fields.is_empty() && !update_exists { + if !suggest_fields.is_empty() && !update_exists { cov_mark::hit!(functional_update_field); - add_default_update(acc, ctx, ty); + add_default_update(acc, ctx, ty.as_ref()); } if dot_prefix { cov_mark::hit!(functional_update_one_dot); @@ -90,7 +90,7 @@ pub(crate) fn complete_record_expr_fields( item.add_to(acc, ctx.db); return; } - missing_fields + suggest_fields } }; complete_fields(acc, ctx, missing_fields); @@ -99,11 +99,11 @@ pub(crate) fn complete_record_expr_fields( pub(crate) fn add_default_update( acc: &mut Completions, ctx: &CompletionContext<'_>, - ty: Option>, + ty: Option<&hir::TypeInfo<'_>>, ) { let default_trait = ctx.famous_defs().core_default_Default(); let impls_default_trait = default_trait - .zip(ty.as_ref()) + .zip(ty) .is_some_and(|(default_trait, ty)| ty.original.impls_trait(ctx.db, default_trait, &[])); if impls_default_trait { // FIXME: This should make use of scope_def like completions so we get all the other goodies diff --git a/crates/ide-completion/src/tests/record.rs b/crates/ide-completion/src/tests/record.rs index d9be6556fa5b..045b2d03b051 100644 --- a/crates/ide-completion/src/tests/record.rs +++ b/crates/ide-completion/src/tests/record.rs @@ -286,6 +286,24 @@ fn main() { ); } +#[test] +fn functional_update_fields_completion() { + // Complete fields before functional update `..` + check( + r#" +struct Point { x: i32 = 0, y: i32 = 0 } + +fn main() { + let p = Point { $0, .. }; +} +"#, + expect![[r#" + fd x i32 + fd y i32 + "#]], + ); +} + #[test] fn empty_union_literal() { check( @@ -302,7 +320,27 @@ fn foo() { fd bar f32 fd foo u32 "#]], - ) + ); +} + +#[test] +fn record_pattern_field_with_rest_pat() { + // When .. is present, complete all unspecified fields (even those with default values) + check( + r#" +struct UserInfo { id: i32, age: f32, email: u64 } + +fn foo(u1: UserInfo) { + let UserInfo { id, $0, .. } = u1; +} +"#, + expect![[r#" + fd age f32 + fd email u64 + kw mut + kw ref + "#]], + ); } #[test] diff --git a/crates/ide-diagnostics/src/handlers/missing_fields.rs b/crates/ide-diagnostics/src/handlers/missing_fields.rs index 2a251382d465..d5f25dfaf208 100644 --- a/crates/ide-diagnostics/src/handlers/missing_fields.rs +++ b/crates/ide-diagnostics/src/handlers/missing_fields.rs @@ -857,4 +857,81 @@ pub struct Claims { "#, ); } + + #[test] + fn test_default_field_values_basic() { + // This should work without errors - only field 'b' is required + check_diagnostics( + r#" +#![feature(default_field_values)] +struct Struct { + a: usize = 0, + b: usize, +} + +fn main() { + Struct { b: 1, .. }; +} +"#, + ); + } + + #[test] + fn test_default_field_values_missing_field_error() { + // This should report a missing field error because email is required + check_diagnostics( + r#" +#![feature(default_field_values)] +struct UserInfo { + id: i32, + age: f32 = 1.0, + email: String, +} + +fn main() { + UserInfo { id: 20, .. }; +// ^^^^^^^^💡 error: missing structure fields: +// |- email +} +"#, + ); + } + + #[test] + fn test_default_field_values_requires_spread_syntax() { + // without `..` should report missing fields + check_diagnostics( + r#" +#![feature(default_field_values)] +struct Point { + x: i32 = 0, + y: i32 = 0, +} + +fn main() { + Point { x: 0 }; +// ^^^^^💡 error: missing structure fields: +// |- y +} +"#, + ); + } + + #[test] + fn test_default_field_values_pattern_matching() { + check_diagnostics( + r#" +#![feature(default_field_values)] +struct Point { + x: i32 = 0, + y: i32 = 0, + z: i32, +} + +fn main() { + let Point { x, .. } = Point { z: 5, .. }; +} +"#, + ); + } } diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs index feac5fff84a7..15ea92d1c6ec 100644 --- a/crates/ide/src/hover/render.rs +++ b/crates/ide/src/hover/render.rs @@ -272,9 +272,9 @@ pub(super) fn struct_rest_pat( edition: Edition, display_target: DisplayTarget, ) -> HoverResult { - let missing_fields = sema.record_pattern_missing_fields(pattern); + let matched_fields = sema.record_pattern_matched_fields(pattern); - // if there are no missing fields, the end result is a hover that shows ".." + // if there are no matched fields, the end result is a hover that shows ".." // should be left in to indicate that there are no more fields in the pattern // example, S {a: 1, b: 2, ..} when struct S {a: u32, b: u32} @@ -285,13 +285,13 @@ pub(super) fn struct_rest_pat( targets.push(item); } }; - for (_, t) in &missing_fields { + for (_, t) in &matched_fields { walk_and_push_ty(sema.db, t, &mut push_new_def); } res.markup = { let mut s = String::from(".., "); - for (f, _) in &missing_fields { + for (f, _) in &matched_fields { s += f.display(sema.db, display_target).to_string().as_ref(); s += ", "; }