Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #21408 from kouhe3/default_field_values
default_field_values
Chayim Refael Friedman 3 months ago
parent a425c08 · parent 8c5a9eb · commit d7666fa
-rw-r--r--crates/hir-def/src/expr_store.rs10
-rw-r--r--crates/hir-def/src/expr_store/lower.rs14
-rw-r--r--crates/hir-def/src/expr_store/lower/format_args.rs5
-rw-r--r--crates/hir-def/src/expr_store/pretty.rs20
-rw-r--r--crates/hir-def/src/hir.rs9
-rw-r--r--crates/hir-def/src/signatures.rs12
-rw-r--r--crates/hir-ty/src/diagnostics/expr.rs46
-rw-r--r--crates/hir-ty/src/infer/closure/analysis.rs4
-rw-r--r--crates/hir-ty/src/infer/expr.rs6
-rw-r--r--crates/hir-ty/src/infer/mutability.rs10
-rw-r--r--crates/hir-ty/src/mir/lower.rs11
-rw-r--r--crates/hir/src/semantics.rs18
-rw-r--r--crates/hir/src/source_analyzer.rs118
-rw-r--r--crates/ide-assists/src/handlers/expand_rest_pattern.rs6
-rw-r--r--crates/ide-completion/src/completions/expr.rs2
-rw-r--r--crates/ide-completion/src/completions/record.rs14
-rw-r--r--crates/ide-completion/src/tests/record.rs40
-rw-r--r--crates/ide-diagnostics/src/handlers/missing_fields.rs77
-rw-r--r--crates/ide/src/hover/render.rs8
19 files changed, 355 insertions, 75 deletions
diff --git a/crates/hir-def/src/expr_store.rs b/crates/hir-def/src/expr_store.rs
index 10cd460d1d..edbfd42d13 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 7922261592..4fbf6d9517 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,
@@ -1266,10 +1266,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)
@@ -1995,7 +2001,7 @@ impl<'db> ExprCollector<'db> {
}
}
- fn collect_expr_opt(&mut self, expr: Option<ast::Expr>) -> ExprId {
+ pub fn collect_expr_opt(&mut self, expr: Option<ast::Expr>) -> 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 7ef84f27f6..51616afb38 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 f5ef8e1a35..35f3cd114e 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 53be0de7d9..7781a8fe54 100644
--- a/crates/hir-def/src/hir.rs
+++ b/crates/hir-def/src/hir.rs
@@ -187,6 +187,13 @@ impl From<ast::LiteralKind> 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<Box<Path>>,
fields: Box<[RecordLitField]>,
- spread: Option<ExprId>,
+ spread: RecordSpread,
},
Field {
expr: ExprId,
diff --git a/crates/hir-def/src/signatures.rs b/crates/hir-def/src/signatures.rs
index 0dd88edbfb..37c8f762fe 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<ExprId>,
}
pub type LocalFieldId = Idx<FieldData>;
@@ -903,7 +904,14 @@ fn lower_fields<Field: ast::HasAttrs + ast::HasVisibility>(
.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 dd1fc3b36e..4e1bb6f4c5 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<LocalFieldId>, /*exhaustive*/ bool)> {
- let (fields, exhaustive) = match expr {
- Expr::RecordLit { fields, spread, .. } => (fields, spread.is_none()),
+) -> Option<(VariantId, Vec<LocalFieldId>)> {
+ 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<LocalFieldId> = 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<LocalFieldId>, /*exhaustive*/ bool)> {
- let (fields, exhaustive) = match pat {
- Pat::Record { path: _, args, ellipsis } => (args, !ellipsis),
+) -> Option<(VariantId, Vec<LocalFieldId>)> {
+ 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<LocalFieldId> = 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 b25901cc3b..5a3eba1a71 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 c57d41cc5f..9f2d9d25b9 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,
- InlineAsmKind, LabelId, Literal, Pat, PatId, Statement, UnaryOp,
+ InlineAsmKind, 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 729ed214da..45fa141b6d 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 1579f00e92..199db7a3e7 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 98f5739600..4bc757da44 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -2003,6 +2003,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,
@@ -2012,6 +2021,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<F: FnOnce(&mut SourceToDefCtx<'_, '_>) -> 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 6ba7a42c19..4e85e299a9 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<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 +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<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 +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_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))
+}
diff --git a/crates/ide-assists/src/handlers/expand_rest_pattern.rs b/crates/ide-assists/src/handlers/expand_rest_pattern.rs
index b746099e72..867ac48518 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 77734c5d6f..8c532e0f4d 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 c5bfdcb8b7..12c564af5c 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<hir::TypeInfo<'_>>,
+ 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 d9be6556fa..045b2d03b0 100644
--- a/crates/ide-completion/src/tests/record.rs
+++ b/crates/ide-completion/src/tests/record.rs
@@ -287,6 +287,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(
r#"
@@ -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 2a251382d4..d5f25dfaf2 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 feac5fff84..15ea92d1c6 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 += ", ";
}