Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/hir-ty/src/infer.rs6
-rw-r--r--crates/hir-ty/src/infer/expr.rs5
-rw-r--r--crates/hir/src/diagnostics.rs18
-rw-r--r--crates/ide-diagnostics/src/handlers/duplicate_field.rs84
-rw-r--r--crates/ide-diagnostics/src/lib.rs2
5 files changed, 113 insertions, 2 deletions
diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs
index c61badf179..d28ee4ab44 100644
--- a/crates/hir-ty/src/infer.rs
+++ b/crates/hir-ty/src/infer.rs
@@ -330,6 +330,12 @@ pub enum InferenceDiagnostic {
#[type_visitable(ignore)]
has_rest: bool,
},
+ DuplicateField {
+ #[type_visitable(ignore)]
+ field: ExprOrPatId,
+ #[type_visitable(ignore)]
+ variant: VariantId,
+ },
PrivateField {
#[type_visitable(ignore)]
expr: ExprId,
diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs
index 680800244b..e07a1b2ba4 100644
--- a/crates/hir-ty/src/infer/expr.rs
+++ b/crates/hir-ty/src/infer/expr.rs
@@ -1073,7 +1073,10 @@ impl<'db> InferenceContext<'_, 'db> {
variant_field_tys[i].get().instantiate(interner, args)
} else {
if let Some(field_idx) = seen_fields.get(&name) {
- // FIXME: Emit an error: duplicate field.
+ self.push_diagnostic(InferenceDiagnostic::DuplicateField {
+ field: field.expr.into(),
+ variant,
+ });
variant_field_tys[*field_idx].get().instantiate(interner, args)
} else {
self.push_diagnostic(InferenceDiagnostic::NoSuchField {
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index 401bbd4a59..b34c7b20c3 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -31,7 +31,7 @@ use syntax::{
};
use triomphe::Arc;
-use crate::{AssocItem, Field, Function, GenericDef, Local, Trait, Type};
+use crate::{AssocItem, Field, Function, GenericDef, Local, Trait, Type, Variant};
pub use hir_def::VariantId;
pub use hir_ty::{
@@ -128,6 +128,7 @@ diagnostics![AnyDiagnostic<'db> ->
NonExhaustiveRecordExpr,
NoSuchField,
MismatchedArrayPatLen,
+ DuplicateField,
PatternArgInExternFn,
PrivateAssocItem,
PrivateField,
@@ -268,6 +269,12 @@ pub struct NoSuchField {
}
#[derive(Debug)]
+pub struct DuplicateField {
+ pub field: InFile<AstPtr<Either<ast::RecordExprField, ast::RecordPatField>>>,
+ pub variant: Variant,
+}
+
+#[derive(Debug)]
pub struct PrivateAssocItem {
pub expr_or_pat: InFile<ExprOrPatPtr>,
pub item: AssocItem,
@@ -764,6 +771,15 @@ impl<'db> AnyDiagnostic<'db> {
let pat = pat_syntax(pat)?.map(Into::into);
MismatchedArrayPatLen { pat, expected, found, has_rest }.into()
}
+ &InferenceDiagnostic::DuplicateField { field: expr, variant } => {
+ let expr_or_pat = match expr {
+ ExprOrPatId::ExprId(expr) => {
+ source_map.field_syntax(expr).map(AstPtr::wrap_left)
+ }
+ ExprOrPatId::PatId(pat) => source_map.pat_field_syntax(pat),
+ };
+ DuplicateField { field: expr_or_pat, variant: variant.into() }.into()
+ }
&InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => {
MismatchedArgCount { call_expr: expr_syntax(call_expr)?, expected, found }.into()
}
diff --git a/crates/ide-diagnostics/src/handlers/duplicate_field.rs b/crates/ide-diagnostics/src/handlers/duplicate_field.rs
new file mode 100644
index 0000000000..13d594066d
--- /dev/null
+++ b/crates/ide-diagnostics/src/handlers/duplicate_field.rs
@@ -0,0 +1,84 @@
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
+
+// Diagnostic: duplicate-field
+//
+// This diagnostic is triggered when a record expression or pattern specifies
+// the same field more than once.
+pub(crate) fn duplicate_field(
+ ctx: &DiagnosticsContext<'_, '_>,
+ d: &hir::DuplicateField,
+) -> Diagnostic {
+ Diagnostic::new_with_syntax_node_ptr(
+ ctx,
+ DiagnosticCode::RustcHardError("E0062"),
+ "field specified more than once",
+ d.field.map(Into::into),
+ )
+ .stable()
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::check_diagnostics;
+
+ #[test]
+ fn duplicate_field_in_struct_literal() {
+ check_diagnostics(
+ r#"
+struct S { foo: i32, bar: i32 }
+fn main() {
+ let _ = S {
+ foo: 1,
+ bar: 2,
+ foo: 3,
+ //^^^^^^ error: field specified more than once
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn duplicate_field_in_enum_variant_literal() {
+ check_diagnostics(
+ r#"
+enum E { V { foo: i32 } }
+fn main() {
+ let _ = E::V {
+ foo: 1,
+ foo: 2,
+ //^^^^^^ error: field specified more than once
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_duplicate_when_each_field_specified_once() {
+ check_diagnostics(
+ r#"
+struct S { foo: i32, bar: i32 }
+fn main() {
+ let _ = S { foo: 1, bar: 2 };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_duplicate_for_unknown_field_falls_through_to_no_such_field() {
+ check_diagnostics(
+ r#"
+struct S { foo: i32 }
+fn main() {
+ let _ = S {
+ foo: 1,
+ bar: 2,
+ //^^^^^^ 💡 error: no such field
+ };
+}
+"#,
+ );
+ }
+}
diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs
index 5882a8fdc2..a490d48fed 100644
--- a/crates/ide-diagnostics/src/lib.rs
+++ b/crates/ide-diagnostics/src/lib.rs
@@ -32,6 +32,7 @@ mod handlers {
pub(crate) mod await_outside_of_async;
pub(crate) mod bad_rtn;
pub(crate) mod break_outside_of_loop;
+ pub(crate) mod duplicate_field;
pub(crate) mod elided_lifetimes_in_path;
pub(crate) mod expected_function;
pub(crate) mod generic_args_prohibited;
@@ -442,6 +443,7 @@ pub fn semantic_diagnostics(
handlers::non_exhaustive_record_expr::non_exhaustive_record_expr(&ctx, &d)
}
AnyDiagnostic::NoSuchField(d) => handlers::no_such_field::no_such_field(&ctx, &d),
+ AnyDiagnostic::DuplicateField(d) => handlers::duplicate_field::duplicate_field(&ctx, &d),
AnyDiagnostic::PrivateAssocItem(d) => handlers::private_assoc_item::private_assoc_item(&ctx, &d),
AnyDiagnostic::PrivateField(d) => handlers::private_field::private_field(&ctx, &d),
AnyDiagnostic::ReplaceFilterMapNextWithFindMap(d) => handlers::replace_filter_map_next_with_find_map::replace_filter_map_next_with_find_map(&ctx, &d),