diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/struct_instantiation.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/struct_instantiation.rs index 0a4491a0342..a6b331768ec 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/struct_instantiation.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/struct_instantiation.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeSet; + use itertools::Itertools; use sway_error::{ error::{CompileError, StructFieldUsageContext}, @@ -140,6 +142,18 @@ pub(crate) fn struct_instantiation( }); } + // Check that there are no duplicate fields. + let mut seen_fields: BTreeSet = BTreeSet::new(); + for field in fields.iter() { + if let Some(duplicate) = seen_fields.get(&field.name) { + handler.emit_err(CompileError::StructFieldDuplicated { + field_name: field.name.clone(), + duplicate: duplicate.clone(), + }); + } + seen_fields.insert(field.name.clone()); + } + // Check that there are no extra fields. for field in fields.iter() { if !struct_fields.iter().any(|x| x.name == field.name) { diff --git a/sway-error/src/error.rs b/sway-error/src/error.rs index f0bb9c26a84..0e625e02d1e 100644 --- a/sway-error/src/error.rs +++ b/sway-error/src/error.rs @@ -368,6 +368,8 @@ pub enum CompileError { struct_is_empty: bool, usage_context: StructFieldUsageContext, }, + #[error("Field \"{field_name}\" has multiple definitions.")] + StructFieldDuplicated { field_name: Ident, duplicate: Ident }, #[error("No method named \"{method_name}\" found for type \"{type_name}\".")] MethodNotFound { method_name: Ident, @@ -1071,6 +1073,7 @@ impl Spanned for CompileError { StructCannotBeInstantiated { span, .. } => span.clone(), StructFieldIsPrivate { field_name, .. } => field_name.span(), StructFieldDoesNotExist { field_name, .. } => field_name.span(), + StructFieldDuplicated { field_name, .. } => field_name.span(), MethodNotFound { span, .. } => span.clone(), ModuleNotFound { span, .. } => span.clone(), TupleElementAccessOnNonTuple { span, .. } => span.clone(), @@ -2061,6 +2064,24 @@ impl ToDiagnostic for CompileError { }, help: vec![], }, + StructFieldDuplicated { field_name, duplicate } => Diagnostic { + reason: Some(Reason::new(code(1), "Struct field has multiple definitions".to_string())), + issue: Issue::error( + source_engine, + field_name.span(), + format!("Field \"{field_name}\" has multiple definitions.") + ), + hints: { + vec![ + Hint::info( + source_engine, + duplicate.span(), + "Field definition duplicated here.".into(), + ) + ] + }, + help: vec![], + }, NotIndexable { actually, span } => Diagnostic { reason: Some(Reason::new(code(1), "Type is not indexable".to_string())), issue: Issue::error( diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/duplicate_struct_field/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/duplicate_struct_field/Forc.lock new file mode 100644 index 00000000000..7edc5e026e0 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/duplicate_struct_field/Forc.lock @@ -0,0 +1,8 @@ +[[package]] +name = "core" +source = "path+from-root-62266B0554A16849" + +[[package]] +name = "duplicate_struct_field" +source = "member" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/duplicate_struct_field/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/duplicate_struct_field/Forc.toml new file mode 100644 index 00000000000..9d8c9ab5a22 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/duplicate_struct_field/Forc.toml @@ -0,0 +1,9 @@ +[project] +name = "duplicate_struct_field" +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +implicit-std = false + +[dependencies] +core = { path = "../../../../../../sway-lib-core" } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/duplicate_struct_field/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_fail/duplicate_struct_field/json_abi_oracle.json new file mode 100644 index 00000000000..c0ff35e67b8 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/duplicate_struct_field/json_abi_oracle.json @@ -0,0 +1,35 @@ +{ + "configurables": [], + "functions": [ + { + "attributes": null, + "inputs": [], + "name": "bar", + "output": { + "name": "", + "type": 0, + "typeArguments": null + } + }, + { + "attributes": null, + "inputs": [], + "name": "baz", + "output": { + "name": "", + "type": 0, + "typeArguments": null + } + } + ], + "loggedTypes": [], + "messagesTypes": [], + "types": [ + { + "components": [], + "type": "()", + "typeId": 0, + "typeParameters": null + } + ] +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/duplicate_struct_field/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/duplicate_struct_field/src/main.sw new file mode 100644 index 00000000000..9692b706999 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/duplicate_struct_field/src/main.sw @@ -0,0 +1,12 @@ +script; + +struct S { + x: u64, +} + +pub fn main() { + let _ = S{ + x:1, + x: "a", + }; +} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/duplicate_struct_field/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/duplicate_struct_field/test.toml new file mode 100644 index 00000000000..b86975dfc6f --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/duplicate_struct_field/test.toml @@ -0,0 +1,4 @@ +category = "fail" + +# check: $()Struct field has multiple definitions +# check: $()Field "x" has multiple definitions.