Implement default field values .. syntax
- Added `RecordSpread` enum to distinguish between no spread, field defaults, and spread expressions - Updated `FieldData` to include `default_value` field - Modified record literal lowering to handle default field values - Updated diagnostics to check for missing fields considering defaults - Added methods to get matched fields for records for completions - Enhanced hover support for struct rest patterns
This commit is contained in:
parent
d7136caaf1
commit
b75c58d4b3
19 changed files with 355 additions and 75 deletions
|
|
@ -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, .. } => {
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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, "}}");
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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, .. };
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 += ", ";
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue