Unnamed repository; edit this file 'description' to name the repository.
feat(diagnostics): record/union pattern diagnostics
AI-assisted:I utilized vscode's IDE assistant Copilot.
| -rw-r--r-- | crates/hir-ty/src/diagnostics/expr.rs | 11 | ||||
| -rw-r--r-- | crates/hir-ty/src/infer.rs | 16 | ||||
| -rw-r--r-- | crates/hir-ty/src/infer/pat.rs | 12 | ||||
| -rw-r--r-- | crates/hir/src/diagnostics.rs | 36 | ||||
| -rw-r--r-- | crates/ide-diagnostics/src/handlers/record_pat_missing_fields.rs | 43 | ||||
| -rw-r--r-- | crates/ide-diagnostics/src/handlers/union_pat_has_rest.rs | 41 | ||||
| -rw-r--r-- | crates/ide-diagnostics/src/handlers/union_pat_must_have_exactly_one_field.rs | 43 | ||||
| -rw-r--r-- | crates/ide-diagnostics/src/lib.rs | 12 |
8 files changed, 200 insertions, 14 deletions
diff --git a/crates/hir-ty/src/diagnostics/expr.rs b/crates/hir-ty/src/diagnostics/expr.rs index 760ebd27e0..e512915ccc 100644 --- a/crates/hir-ty/src/diagnostics/expr.rs +++ b/crates/hir-ty/src/diagnostics/expr.rs @@ -158,17 +158,6 @@ impl<'db> ExprValidator<'db> { } } - for (id, pat) in body.pats() { - if let Some((variant, missed_fields)) = - record_pattern_missing_fields(db, self.infer, id, pat) - { - self.diagnostics.push(BodyValidationDiagnostic::RecordMissingFields { - record: Either::Right(id), - variant, - missed_fields, - }); - } - } } fn validate_call( diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index c063d0211f..fc1e77efce 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -375,6 +375,22 @@ pub enum InferenceDiagnostic { #[type_visitable(ignore)] variant: VariantId, }, + UnionPatMustHaveExactlyOneField { + #[type_visitable(ignore)] + pat: PatId, + }, + UnionPatHasRest { + #[type_visitable(ignore)] + pat: PatId, + }, + RecordPatMissingFields { + #[type_visitable(ignore)] + pat: PatId, + #[type_visitable(ignore)] + variant: VariantId, + #[type_visitable(ignore)] + missing_fields: Vec<LocalFieldId>, + }, FunctionalRecordUpdateOnNonStruct { #[type_visitable(ignore)] base_expr: ExprId, diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs index f1af8a0b73..e876375cbc 100644 --- a/crates/hir-ty/src/infer/pat.rs +++ b/crates/hir-ty/src/infer/pat.rs @@ -1241,13 +1241,19 @@ https://doc.rust-lang.org/reference/types.html#trait-objects"; // Report an error if an incorrect number of fields was specified. if matches!(variant, VariantId::UnionId(_)) { if fields.len() != 1 { - // FIXME: Emit an error, unions can't have more than one field. + self.push_diagnostic(InferenceDiagnostic::UnionPatMustHaveExactlyOneField { pat }); } if has_rest_pat { - // FIXME: Emit an error, unions can't have a rest pat. + self.push_diagnostic(InferenceDiagnostic::UnionPatHasRest { pat }); } } else if !unmentioned_fields.is_empty() && !has_rest_pat { - // FIXME: Emit an error. + let missing_fields = + unmentioned_fields.iter().map(|(field_id, _)| *field_id).collect(); + self.push_diagnostic(InferenceDiagnostic::RecordPatMissingFields { + pat, + variant, + missing_fields, + }); } } diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 2d2883eb60..77f227b841 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -174,6 +174,9 @@ diagnostics![AnyDiagnostic<'db> -> ElidedLifetimesInPath, TypeMustBeKnown<'db>, UnionExprMustHaveExactlyOneField, + UnionPatMustHaveExactlyOneField, + UnionPatHasRest, + RecordPatMissingFields, UnimplementedTrait<'db>, ]; @@ -626,6 +629,22 @@ pub struct UnionExprMustHaveExactlyOneField { } #[derive(Debug)] +pub struct UnionPatMustHaveExactlyOneField { + pub pat: InFile<ExprOrPatPtr>, +} + +#[derive(Debug)] +pub struct UnionPatHasRest { + pub pat: InFile<ExprOrPatPtr>, +} + +#[derive(Debug)] +pub struct RecordPatMissingFields { + pub pat: InFile<ExprOrPatPtr>, + pub missed_fields: Vec<Name>, +} + +#[derive(Debug)] pub struct InvalidLhsOfAssignment { pub lhs: InFile<AstPtr<Either<ast::Expr, ast::Pat>>>, } @@ -942,6 +961,23 @@ impl<'db> AnyDiagnostic<'db> { let pat = pat_syntax(pat)?.map(Into::into); NonExhaustiveRecordPat { pat, variant: variant.into() }.into() } + &InferenceDiagnostic::UnionPatMustHaveExactlyOneField { pat } => { + let pat = pat_syntax(pat)?.map(Into::into); + UnionPatMustHaveExactlyOneField { pat }.into() + } + &InferenceDiagnostic::UnionPatHasRest { pat } => { + let pat = pat_syntax(pat)?.map(Into::into); + UnionPatHasRest { pat }.into() + } + InferenceDiagnostic::RecordPatMissingFields { pat, variant, missing_fields } => { + let pat = pat_syntax(*pat)?.map(Into::into); + let variant_data = variant.fields(db); + let missed_fields = missing_fields + .iter() + .map(|field_id| variant_data.fields()[*field_id].name.clone()) + .collect(); + RecordPatMissingFields { pat, missed_fields }.into() + } &InferenceDiagnostic::FunctionalRecordUpdateOnNonStruct { base_expr } => { FunctionalRecordUpdateOnNonStruct { base_expr: expr_syntax(base_expr)? }.into() } diff --git a/crates/ide-diagnostics/src/handlers/record_pat_missing_fields.rs b/crates/ide-diagnostics/src/handlers/record_pat_missing_fields.rs new file mode 100644 index 0000000000..5ef4b1b045 --- /dev/null +++ b/crates/ide-diagnostics/src/handlers/record_pat_missing_fields.rs @@ -0,0 +1,43 @@ +use stdx::format_to; + +use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext}; + +// Diagnostic: record-pat-missing-fields +// +// This diagnostic is triggered if a record pattern omits fields without `..`. +pub(crate) fn record_pat_missing_fields( + ctx: &DiagnosticsContext<'_, '_>, + d: &hir::RecordPatMissingFields, +) -> Diagnostic { + let mut message = String::from("missing structure fields:\n"); + for field in &d.missed_fields { + format_to!(message, "- {}\n", field.display(ctx.sema.db, ctx.edition)); + } + + Diagnostic::new_with_syntax_node_ptr( + ctx, + DiagnosticCode::RustcHardError("E0063"), + message, + d.pat.map(Into::into), + ) + .stable() +} + +#[cfg(test)] +mod tests { + use crate::tests::check_diagnostics; + + #[test] + fn record_pat_missing_fields() { + check_diagnostics( + r#" +struct S { foo: i32, bar: () } +fn baz(s: S) { + let S { foo: _ } = s; + //^ error: missing structure fields: + //| - bar +} +"#, + ); + } +} diff --git a/crates/ide-diagnostics/src/handlers/union_pat_has_rest.rs b/crates/ide-diagnostics/src/handlers/union_pat_has_rest.rs new file mode 100644 index 0000000000..03694f621c --- /dev/null +++ b/crates/ide-diagnostics/src/handlers/union_pat_has_rest.rs @@ -0,0 +1,41 @@ +use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext}; + +// Diagnostic: union-pat-has-rest +// +// A union pattern uses `..`. +pub(crate) fn union_pat_has_rest( + ctx: &DiagnosticsContext<'_, '_>, + d: &hir::UnionPatHasRest, +) -> Diagnostic { + Diagnostic::new_with_syntax_node_ptr( + ctx, + DiagnosticCode::RustcHardError("E0784"), + "union patterns cannot use `..`", + d.pat.map(Into::into), + ) + .stable() +} + +#[cfg(test)] +mod tests { + use crate::tests::check_diagnostics; + + #[test] + fn union_pat_has_rest() { + check_diagnostics( + r#" +union Bird { + pigeon: u8, + turtledove: u16, +} + +fn main(bird: Bird) { + unsafe { + let Bird { pigeon: 0, .. } = bird; + // ^^^^^^^^^^^^^^^^^^^^^ error: union patterns cannot use `..` + } +} +"#, + ); + } +} diff --git a/crates/ide-diagnostics/src/handlers/union_pat_must_have_exactly_one_field.rs b/crates/ide-diagnostics/src/handlers/union_pat_must_have_exactly_one_field.rs new file mode 100644 index 0000000000..f78514cd3c --- /dev/null +++ b/crates/ide-diagnostics/src/handlers/union_pat_must_have_exactly_one_field.rs @@ -0,0 +1,43 @@ +use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext}; + +// Diagnostic: union-pat-must-have-exactly-one-field +// +// A union pattern does not have exactly one field. +pub(crate) fn union_pat_must_have_exactly_one_field( + ctx: &DiagnosticsContext<'_, '_>, + d: &hir::UnionPatMustHaveExactlyOneField, +) -> Diagnostic { + Diagnostic::new_with_syntax_node_ptr( + ctx, + DiagnosticCode::RustcHardError("E0784"), + "union patterns should have exactly one field", + d.pat.map(Into::into), + ) + .stable() +} + +#[cfg(test)] +mod tests { + use crate::tests::check_diagnostics; + + #[test] + fn union_pat_must_have_exactly_one_field() { + check_diagnostics( + r#" +union Bird { + pigeon: u8, + turtledove: u16, +} + +fn main(bird: Bird) { + unsafe { + let Bird {} = bird; + // ^^^^^^^ error: union patterns should have exactly one field + let Bird { pigeon: 0, turtledove: 1 } = bird; + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: union patterns should have exactly one field + } +} +"#, + ); + } +} diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs index 18251bc8a2..d99f71db76 100644 --- a/crates/ide-diagnostics/src/lib.rs +++ b/crates/ide-diagnostics/src/lib.rs @@ -59,6 +59,7 @@ mod handlers { pub(crate) mod mismatched_arg_count; pub(crate) mod mismatched_array_pat_len; pub(crate) mod missing_fields; + pub(crate) mod record_pat_missing_fields; pub(crate) mod missing_lifetime; pub(crate) mod missing_match_arms; pub(crate) mod missing_unsafe; @@ -87,6 +88,8 @@ mod handlers { pub(crate) mod unimplemented_builtin_macro; pub(crate) mod unimplemented_trait; pub(crate) mod union_expr_must_have_exactly_one_field; + pub(crate) mod union_pat_has_rest; + pub(crate) mod union_pat_must_have_exactly_one_field; pub(crate) mod unreachable_label; pub(crate) mod unresolved_assoc_item; pub(crate) mod unresolved_extern_crate; @@ -472,6 +475,9 @@ pub fn semantic_diagnostics( AnyDiagnostic::MismatchedArgCount(d) => handlers::mismatched_arg_count::mismatched_arg_count(&ctx, &d), AnyDiagnostic::MismatchedArrayPatLen(d) => handlers::mismatched_array_pat_len::mismatched_array_pat_len(&ctx, &d), AnyDiagnostic::MissingFields(d) => handlers::missing_fields::missing_fields(&ctx, &d), + AnyDiagnostic::RecordPatMissingFields(d) => { + handlers::record_pat_missing_fields::record_pat_missing_fields(&ctx, &d) + } AnyDiagnostic::MissingMatchArms(d) => handlers::missing_match_arms::missing_match_arms(&ctx, &d), AnyDiagnostic::MissingUnsafe(d) => handlers::missing_unsafe::missing_unsafe(&ctx, &d), AnyDiagnostic::MovedOutOfRef(d) => handlers::moved_out_of_ref::moved_out_of_ref(&ctx, &d), @@ -544,6 +550,12 @@ pub fn semantic_diagnostics( AnyDiagnostic::TypeMustBeKnown(d) => handlers::type_must_be_known::type_must_be_known(&ctx, &d), AnyDiagnostic::PatternArgInExternFn(d) => handlers::pattern_arg_in_extern_fn::pattern_arg_in_extern_fn(&ctx, &d), AnyDiagnostic::UnionExprMustHaveExactlyOneField(d) => handlers::union_expr_must_have_exactly_one_field::union_expr_must_have_exactly_one_field(&ctx, &d), + AnyDiagnostic::UnionPatMustHaveExactlyOneField(d) => { + handlers::union_pat_must_have_exactly_one_field::union_pat_must_have_exactly_one_field(&ctx, &d) + } + AnyDiagnostic::UnionPatHasRest(d) => { + handlers::union_pat_has_rest::union_pat_has_rest(&ctx, &d) + } AnyDiagnostic::UnimplementedTrait(d) => handlers::unimplemented_trait::unimplemented_trait(&ctx, &d), AnyDiagnostic::FruInDestructuringAssignment(d) => handlers::fru_in_destructuring_assignment::fru_in_destructuring_assignment(&ctx, &d), AnyDiagnostic::ExplicitDropMethodUse(d) => handlers::explicit_drop_method_use::explicit_drop_method_use(&ctx, &d), |