mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-28 14:03:35 +00:00
Merge #3905
3905: add ellipsis field to hir pat record r=matklad a=JoshMcguigan This PR corrects a `fixme`, adding an `ellipsis` field to the hir `Pat::Record` type. It will also be unlock some useful follow on work for #3894. Additionally it adds a diagnostic for missing fields in record patterns. ~~Marking as a draft because I don't have any tests, and a small amount of manual testing on my branch from #3894 suggests it might *not* be working. Any thoughts on how I can best test this, or else pointers on where I might be going wrong?~~ Co-authored-by: Josh Mcguigan <joshmcg88@gmail.com>
This commit is contained in:
commit
38e0d0f334
7 changed files with 159 additions and 57 deletions
|
@ -255,7 +255,7 @@ impl SourceAnalyzer {
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (variant, missing_fields) =
|
let (variant, missing_fields, _exhaustive) =
|
||||||
record_pattern_missing_fields(db, infer, pat_id, &body[pat_id])?;
|
record_pattern_missing_fields(db, infer, pat_id, &body[pat_id])?;
|
||||||
let res = self.missing_fields(db, krate, substs, variant, missing_fields);
|
let res = self.missing_fields(db, krate, substs, variant, missing_fields);
|
||||||
Some(res)
|
Some(res)
|
||||||
|
|
|
@ -668,7 +668,9 @@ impl ExprCollector<'_> {
|
||||||
});
|
});
|
||||||
fields.extend(iter);
|
fields.extend(iter);
|
||||||
|
|
||||||
Pat::Record { path, args: fields }
|
let ellipsis = record_field_pat_list.dotdot_token().is_some();
|
||||||
|
|
||||||
|
Pat::Record { path, args: fields, ellipsis }
|
||||||
}
|
}
|
||||||
ast::Pat::SlicePat(p) => {
|
ast::Pat::SlicePat(p) => {
|
||||||
let SlicePatComponents { prefix, slice, suffix } = p.components();
|
let SlicePatComponents { prefix, slice, suffix } = p.components();
|
||||||
|
|
|
@ -376,35 +376,14 @@ pub enum Pat {
|
||||||
Wild,
|
Wild,
|
||||||
Tuple(Vec<PatId>),
|
Tuple(Vec<PatId>),
|
||||||
Or(Vec<PatId>),
|
Or(Vec<PatId>),
|
||||||
Record {
|
Record { path: Option<Path>, args: Vec<RecordFieldPat>, ellipsis: bool },
|
||||||
path: Option<Path>,
|
Range { start: ExprId, end: ExprId },
|
||||||
args: Vec<RecordFieldPat>,
|
Slice { prefix: Vec<PatId>, slice: Option<PatId>, suffix: Vec<PatId> },
|
||||||
// FIXME: 'ellipsis' option
|
|
||||||
},
|
|
||||||
Range {
|
|
||||||
start: ExprId,
|
|
||||||
end: ExprId,
|
|
||||||
},
|
|
||||||
Slice {
|
|
||||||
prefix: Vec<PatId>,
|
|
||||||
slice: Option<PatId>,
|
|
||||||
suffix: Vec<PatId>,
|
|
||||||
},
|
|
||||||
Path(Path),
|
Path(Path),
|
||||||
Lit(ExprId),
|
Lit(ExprId),
|
||||||
Bind {
|
Bind { mode: BindingAnnotation, name: Name, subpat: Option<PatId> },
|
||||||
mode: BindingAnnotation,
|
TupleStruct { path: Option<Path>, args: Vec<PatId> },
|
||||||
name: Name,
|
Ref { pat: PatId, mutability: Mutability },
|
||||||
subpat: Option<PatId>,
|
|
||||||
},
|
|
||||||
TupleStruct {
|
|
||||||
path: Option<Path>,
|
|
||||||
args: Vec<PatId>,
|
|
||||||
},
|
|
||||||
Ref {
|
|
||||||
pat: PatId,
|
|
||||||
mutability: Mutability,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pat {
|
impl Pat {
|
||||||
|
|
|
@ -62,6 +62,29 @@ impl AstDiagnostic for MissingFields {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MissingPatFields {
|
||||||
|
pub file: HirFileId,
|
||||||
|
pub field_list: AstPtr<ast::RecordFieldPatList>,
|
||||||
|
pub missed_fields: Vec<Name>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Diagnostic for MissingPatFields {
|
||||||
|
fn message(&self) -> String {
|
||||||
|
let mut buf = String::from("Missing structure fields:\n");
|
||||||
|
for field in &self.missed_fields {
|
||||||
|
format_to!(buf, "- {}", field);
|
||||||
|
}
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
fn source(&self) -> InFile<SyntaxNodePtr> {
|
||||||
|
InFile { file_id: self.file, value: self.field_list.into() }
|
||||||
|
}
|
||||||
|
fn as_any(&self) -> &(dyn Any + Send + 'static) {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MissingMatchArms {
|
pub struct MissingMatchArms {
|
||||||
pub file: HirFileId,
|
pub file: HirFileId,
|
||||||
|
|
|
@ -9,7 +9,7 @@ use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
db::HirDatabase,
|
db::HirDatabase,
|
||||||
diagnostics::{MissingFields, MissingMatchArms, MissingOkInTailExpr},
|
diagnostics::{MissingFields, MissingMatchArms, MissingOkInTailExpr, MissingPatFields},
|
||||||
utils::variant_data,
|
utils::variant_data,
|
||||||
ApplicationTy, InferenceResult, Ty, TypeCtor,
|
ApplicationTy, InferenceResult, Ty, TypeCtor,
|
||||||
_match::{is_useful, MatchCheckCtx, Matrix, PatStack, Usefulness},
|
_match::{is_useful, MatchCheckCtx, Matrix, PatStack, Usefulness},
|
||||||
|
@ -49,6 +49,42 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
|
||||||
if let Some((variant_def, missed_fields, true)) =
|
if let Some((variant_def, missed_fields, true)) =
|
||||||
record_literal_missing_fields(db, &self.infer, id, expr)
|
record_literal_missing_fields(db, &self.infer, id, expr)
|
||||||
{
|
{
|
||||||
|
self.create_record_literal_missing_fields_diagnostic(
|
||||||
|
id,
|
||||||
|
db,
|
||||||
|
variant_def,
|
||||||
|
missed_fields,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Expr::Match { expr, arms } = expr {
|
||||||
|
self.validate_match(id, *expr, arms, db, self.infer.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (id, pat) in body.pats.iter() {
|
||||||
|
if let Some((variant_def, missed_fields, true)) =
|
||||||
|
record_pattern_missing_fields(db, &self.infer, id, pat)
|
||||||
|
{
|
||||||
|
self.create_record_pattern_missing_fields_diagnostic(
|
||||||
|
id,
|
||||||
|
db,
|
||||||
|
variant_def,
|
||||||
|
missed_fields,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let body_expr = &body[body.body_expr];
|
||||||
|
if let Expr::Block { tail: Some(t), .. } = body_expr {
|
||||||
|
self.validate_results_in_tail_expr(body.body_expr, *t, db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_record_literal_missing_fields_diagnostic(
|
||||||
|
&mut self,
|
||||||
|
id: ExprId,
|
||||||
|
db: &dyn HirDatabase,
|
||||||
|
variant_def: VariantId,
|
||||||
|
missed_fields: Vec<LocalStructFieldId>,
|
||||||
|
) {
|
||||||
// XXX: only look at source_map if we do have missing fields
|
// XXX: only look at source_map if we do have missing fields
|
||||||
let (_, source_map) = db.body_with_source_map(self.func.into());
|
let (_, source_map) = db.body_with_source_map(self.func.into());
|
||||||
|
|
||||||
|
@ -72,13 +108,35 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Expr::Match { expr, arms } = expr {
|
|
||||||
self.validate_match(id, *expr, arms, db, self.infer.clone());
|
fn create_record_pattern_missing_fields_diagnostic(
|
||||||
|
&mut self,
|
||||||
|
id: PatId,
|
||||||
|
db: &dyn HirDatabase,
|
||||||
|
variant_def: VariantId,
|
||||||
|
missed_fields: Vec<LocalStructFieldId>,
|
||||||
|
) {
|
||||||
|
// XXX: only look at source_map if we do have missing fields
|
||||||
|
let (_, source_map) = db.body_with_source_map(self.func.into());
|
||||||
|
|
||||||
|
if let Ok(source_ptr) = source_map.pat_syntax(id) {
|
||||||
|
if let Some(expr) = source_ptr.value.left() {
|
||||||
|
let root = source_ptr.file_syntax(db.upcast());
|
||||||
|
if let ast::Pat::RecordPat(record_pat) = expr.to_node(&root) {
|
||||||
|
if let Some(field_list) = record_pat.record_field_pat_list() {
|
||||||
|
let variant_data = variant_data(db.upcast(), variant_def);
|
||||||
|
let missed_fields = missed_fields
|
||||||
|
.into_iter()
|
||||||
|
.map(|idx| variant_data.fields()[idx].name.clone())
|
||||||
|
.collect();
|
||||||
|
self.sink.push(MissingPatFields {
|
||||||
|
file: source_ptr.file_id,
|
||||||
|
field_list: AstPtr::new(&field_list),
|
||||||
|
missed_fields,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let body_expr = &body[body.body_expr];
|
|
||||||
if let Expr::Block { tail: Some(t), .. } = body_expr {
|
|
||||||
self.validate_results_in_tail_expr(body.body_expr, *t, db);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,9 +290,9 @@ pub fn record_pattern_missing_fields(
|
||||||
infer: &InferenceResult,
|
infer: &InferenceResult,
|
||||||
id: PatId,
|
id: PatId,
|
||||||
pat: &Pat,
|
pat: &Pat,
|
||||||
) -> Option<(VariantId, Vec<LocalStructFieldId>)> {
|
) -> Option<(VariantId, Vec<LocalStructFieldId>, /*exhaustive*/ bool)> {
|
||||||
let fields = match pat {
|
let (fields, exhaustive) = match pat {
|
||||||
Pat::Record { path: _, args } => args,
|
Pat::Record { path: _, args, ellipsis } => (args, !ellipsis),
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -254,5 +312,5 @@ pub fn record_pattern_missing_fields(
|
||||||
if missed_fields.is_empty() {
|
if missed_fields.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
Some((variant_def, missed_fields))
|
Some((variant_def, missed_fields, exhaustive))
|
||||||
}
|
}
|
||||||
|
|
|
@ -158,7 +158,7 @@ impl<'a> InferenceContext<'a> {
|
||||||
Pat::TupleStruct { path: p, args: subpats } => {
|
Pat::TupleStruct { path: p, args: subpats } => {
|
||||||
self.infer_tuple_struct_pat(p.as_ref(), subpats, expected, default_bm, pat)
|
self.infer_tuple_struct_pat(p.as_ref(), subpats, expected, default_bm, pat)
|
||||||
}
|
}
|
||||||
Pat::Record { path: p, args: fields } => {
|
Pat::Record { path: p, args: fields, ellipsis: _ } => {
|
||||||
self.infer_record_pat(p.as_ref(), fields, expected, default_bm, pat)
|
self.infer_record_pat(p.as_ref(), fields, expected, default_bm, pat)
|
||||||
}
|
}
|
||||||
Pat::Path(path) => {
|
Pat::Path(path) => {
|
||||||
|
|
|
@ -409,3 +409,43 @@ fn no_such_field_with_feature_flag_diagnostics_on_struct_fields() {
|
||||||
|
|
||||||
assert_snapshot!(diagnostics, @r###""###);
|
assert_snapshot!(diagnostics, @r###""###);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_record_pat_field_diagnostic() {
|
||||||
|
let diagnostics = TestDB::with_files(
|
||||||
|
r"
|
||||||
|
//- /lib.rs
|
||||||
|
struct S { foo: i32, bar: () }
|
||||||
|
fn baz(s: S) {
|
||||||
|
let S { foo: _ } = s;
|
||||||
|
}
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.diagnostics()
|
||||||
|
.0;
|
||||||
|
|
||||||
|
assert_snapshot!(diagnostics, @r###"
|
||||||
|
"{ foo: _ }": Missing structure fields:
|
||||||
|
- bar
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() {
|
||||||
|
let diagnostics = TestDB::with_files(
|
||||||
|
r"
|
||||||
|
//- /lib.rs
|
||||||
|
struct S { foo: i32, bar: () }
|
||||||
|
fn baz(s: S) -> i32 {
|
||||||
|
match s {
|
||||||
|
S { foo, .. } => foo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.diagnostics()
|
||||||
|
.0;
|
||||||
|
|
||||||
|
assert_snapshot!(diagnostics, @"");
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue