From 71b0fd21ea3b7f70ee65c5b21dd4e3132f596ffd Mon Sep 17 00:00:00 2001 From: dotacow Date: Sat, 23 May 2026 17:40:15 +0300 Subject: [PATCH 1/2] feat(diagnostics): record/union pattern diagnostics AI-assisted:I utilized vscode's IDE assistant Copilot. --- crates/hir-ty/src/diagnostics/expr.rs | 11 ----- crates/hir-ty/src/infer.rs | 16 +++++++ crates/hir-ty/src/infer/pat.rs | 12 ++++-- crates/hir/src/diagnostics.rs | 36 ++++++++++++++++ .../src/handlers/record_pat_missing_fields.rs | 43 +++++++++++++++++++ .../src/handlers/union_pat_has_rest.rs | 41 ++++++++++++++++++ .../union_pat_must_have_exactly_one_field.rs | 43 +++++++++++++++++++ crates/ide-diagnostics/src/lib.rs | 12 ++++++ 8 files changed, 200 insertions(+), 14 deletions(-) create mode 100644 crates/ide-diagnostics/src/handlers/record_pat_missing_fields.rs create mode 100644 crates/ide-diagnostics/src/handlers/union_pat_has_rest.rs create mode 100644 crates/ide-diagnostics/src/handlers/union_pat_must_have_exactly_one_field.rs diff --git a/crates/hir-ty/src/diagnostics/expr.rs b/crates/hir-ty/src/diagnostics/expr.rs index 760ebd27e057..e512915ccc2b 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 c063d0211f15..fc1e77efce1b 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, + }, 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 f1af8a0b73a5..e876375cbc80 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 2d2883eb60e1..77f227b841d7 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>, ]; @@ -625,6 +628,22 @@ pub struct UnionExprMustHaveExactlyOneField { pub expr: InFile, } +#[derive(Debug)] +pub struct UnionPatMustHaveExactlyOneField { + pub pat: InFile, +} + +#[derive(Debug)] +pub struct UnionPatHasRest { + pub pat: InFile, +} + +#[derive(Debug)] +pub struct RecordPatMissingFields { + pub pat: InFile, + pub missed_fields: Vec, +} + #[derive(Debug)] pub struct InvalidLhsOfAssignment { pub lhs: InFile>>, @@ -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 000000000000..5ef4b1b045e6 --- /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 000000000000..03694f621c9c --- /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 000000000000..f78514cd3c38 --- /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 18251bc8a2dd..d99f71db768c 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), From 63fa0445eb2d816afe6fae4b7fed62e4691f3570 Mon Sep 17 00:00:00 2001 From: dotacow Date: Sat, 23 May 2026 18:32:09 +0300 Subject: [PATCH 2/2] feat(ide-diagnostics): add diagnostics for invalid union patterns AI-assisted: I used an LLM to guide me through the debugging process. all code was reviewed and tested. --- crates/hir-ty/src/diagnostics/expr.rs | 11 +++++ crates/hir-ty/src/infer.rs | 8 ---- crates/hir-ty/src/infer/pat.rs | 8 +--- crates/hir/src/diagnostics.rs | 16 ------- .../src/handlers/record_pat_missing_fields.rs | 43 ------------------- .../src/handlers/union_pat_has_rest.rs | 2 +- .../union_pat_must_have_exactly_one_field.rs | 4 +- crates/ide-diagnostics/src/lib.rs | 4 -- 8 files changed, 15 insertions(+), 81 deletions(-) delete mode 100644 crates/ide-diagnostics/src/handlers/record_pat_missing_fields.rs diff --git a/crates/hir-ty/src/diagnostics/expr.rs b/crates/hir-ty/src/diagnostics/expr.rs index e512915ccc2b..760ebd27e057 100644 --- a/crates/hir-ty/src/diagnostics/expr.rs +++ b/crates/hir-ty/src/diagnostics/expr.rs @@ -158,6 +158,17 @@ 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 fc1e77efce1b..d9486fb0eb5d 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -383,14 +383,6 @@ pub enum InferenceDiagnostic { #[type_visitable(ignore)] pat: PatId, }, - RecordPatMissingFields { - #[type_visitable(ignore)] - pat: PatId, - #[type_visitable(ignore)] - variant: VariantId, - #[type_visitable(ignore)] - missing_fields: Vec, - }, 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 e876375cbc80..2583054fcdf6 100644 --- a/crates/hir-ty/src/infer/pat.rs +++ b/crates/hir-ty/src/infer/pat.rs @@ -1247,13 +1247,7 @@ https://doc.rust-lang.org/reference/types.html#trait-objects"; self.push_diagnostic(InferenceDiagnostic::UnionPatHasRest { pat }); } } else if !unmentioned_fields.is_empty() && !has_rest_pat { - let missing_fields = - unmentioned_fields.iter().map(|(field_id, _)| *field_id).collect(); - self.push_diagnostic(InferenceDiagnostic::RecordPatMissingFields { - pat, - variant, - missing_fields, - }); + // FIXME: Emit an error. } } diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 77f227b841d7..f21f90677285 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -176,7 +176,6 @@ diagnostics![AnyDiagnostic<'db> -> UnionExprMustHaveExactlyOneField, UnionPatMustHaveExactlyOneField, UnionPatHasRest, - RecordPatMissingFields, UnimplementedTrait<'db>, ]; @@ -638,12 +637,6 @@ pub struct UnionPatHasRest { pub pat: InFile, } -#[derive(Debug)] -pub struct RecordPatMissingFields { - pub pat: InFile, - pub missed_fields: Vec, -} - #[derive(Debug)] pub struct InvalidLhsOfAssignment { pub lhs: InFile>>, @@ -969,15 +962,6 @@ impl<'db> AnyDiagnostic<'db> { 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 deleted file mode 100644 index 5ef4b1b045e6..000000000000 --- a/crates/ide-diagnostics/src/handlers/record_pat_missing_fields.rs +++ /dev/null @@ -1,43 +0,0 @@ -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 index 03694f621c9c..933311871948 100644 --- a/crates/ide-diagnostics/src/handlers/union_pat_has_rest.rs +++ b/crates/ide-diagnostics/src/handlers/union_pat_has_rest.rs @@ -32,7 +32,7 @@ union Bird { fn main(bird: Bird) { unsafe { let Bird { pigeon: 0, .. } = bird; - // ^^^^^^^^^^^^^^^^^^^^^ error: union patterns cannot use `..` + //^^^^^^^^^^^^^^^^^^^^^^ 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 index f78514cd3c38..5c229211e1ff 100644 --- 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 @@ -32,9 +32,9 @@ union Bird { fn main(bird: Bird) { unsafe { let Bird {} = bird; - // ^^^^^^^ error: union patterns should have exactly one field + //^^^^^^^ error: union patterns should have exactly one field let Bird { pigeon: 0, turtledove: 1 } = bird; - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: union patterns should have exactly one field + //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 d99f71db768c..62b5106ada50 100644 --- a/crates/ide-diagnostics/src/lib.rs +++ b/crates/ide-diagnostics/src/lib.rs @@ -59,7 +59,6 @@ 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; @@ -475,9 +474,6 @@ 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),