diff --git a/docs/src/basics/structs_tuples_and_enums.md b/docs/src/basics/structs_tuples_and_enums.md index a9f898d8c7c..c46eb9896cc 100644 --- a/docs/src/basics/structs_tuples_and_enums.md +++ b/docs/src/basics/structs_tuples_and_enums.md @@ -26,6 +26,8 @@ There are three ways to instantiate the struct. > You can mix and match all 3 ways to instantiate the struct at the same time. > Moreover, the order of the fields does not matter when instantiating however we encourage declaring the fields in alphabetical order and instantiating them in the same alphabetical order +Furthermore, multiple variables can be extracted from a struct using the destructuring syntax. + ### Struct Memory Layout > **Note** diff --git a/examples/structs/src/data_structures.sw b/examples/structs/src/data_structures.sw index e1c29124e44..b59ad0c5e8b 100644 --- a/examples/structs/src/data_structures.sw +++ b/examples/structs/src/data_structures.sw @@ -5,3 +5,19 @@ pub struct Foo { bar: u64, baz: bool, } + +// Struct types for destructuring +pub struct Point { + x: u64, + y: u64, +} + +pub struct Line { + p1: Point, + p2: Point, +} + +pub struct TupleInStruct { + nested_tuple: (u64, + (u32, (bool, str[2]))), +} diff --git a/examples/structs/src/main.sw b/examples/structs/src/main.sw index f753b0dc9a6..a20b4ac670c 100644 --- a/examples/structs/src/main.sw +++ b/examples/structs/src/main.sw @@ -1,7 +1,7 @@ library utils; dep data_structures; -use data_structures::Foo; +use data_structures::{Foo, Line, Point, TupleInStruct}; fn hardcoded_instantiation() -> Foo { // Instantiate `foo` as `Foo` @@ -51,3 +51,62 @@ fn shorthand_instantiation() -> Foo { // Return the struct foo } + +fn struct_destructuring() { + let point1 = Point { + x: 0, + y: 0, + }; + // Destructure the values from the struct into variables + let Point { + x, y + } + = point1; + + let point2 = Point { + x: 1, + y: 1, + }; + // If you do not care about specific struct fields then use ".." at the end of your variable list + let Point { + x, .. + } + = point2; + + let line = Line { + p1: point1, + p2: point2, + }; + // Destructure the values from the nested structs into variables + let Line { + p1: Point { + x: x0, y: y0 + }, + p2: Point { + x: x1, y: y1 + } + } + = line; + // You may also destructure tuples nested in structs and structs nested in tuples + let tuple_in_struct = TupleInStruct { + nested_tuple: (42u64, + (42u32, (true, "ok"))), + }; + let TupleInStruct { + nested_tuple: (a, (b, (c, d))) + } + = tuple_in_struct; + + let struct_in_tuple = (Point { + x: 2, y: 4, + }, + Point { + x: 3, y: 6, + }); + let(Point { + x: x0, y: y0, + }, + Point { + x: x1, y: y1, + }) = struct_in_tuple; +} diff --git a/sway-core/src/constants.rs b/sway-core/src/constants.rs index 6b1d315e485..7a93d9f45b9 100644 --- a/sway-core/src/constants.rs +++ b/sway-core/src/constants.rs @@ -21,6 +21,9 @@ pub const DEFAULT_ENTRY_POINT_FN_NAME: &str = "main"; /// The default prefix for the compiler generated names of tuples pub const TUPLE_NAME_PREFIX: &str = "__tuple_"; +// The default prefix for the compiler generated names of struct fields +pub const DESTRUCTURE_PREFIX: &str = "__destructure_"; + /// The default prefix for the compiler generated names of match pub const MATCH_RETURN_VAR_NAME_PREFIX: &str = "__match_return_var_name_"; diff --git a/sway-core/src/convert_parse_tree.rs b/sway-core/src/convert_parse_tree.rs index a69c3dc482a..083ca5a8401 100644 --- a/sway-core/src/convert_parse_tree.rs +++ b/sway-core/src/convert_parse_tree.rs @@ -2531,15 +2531,99 @@ fn statement_let_to_ast_nodes( let error = ConvertParseTreeError::ConstructorPatternsNotSupportedHere { span }; return Err(ec.error(error)); } - Pattern::Struct { .. } => { - let error = ConvertParseTreeError::StructPatternsNotSupportedHere { span }; - return Err(ec.error(error)); + Pattern::Struct { fields, .. } => { + let mut ast_nodes = Vec::new(); + + // Generate a deterministic name for the destructured struct + // Because the parser is single threaded, the name generated below will be stable. + static COUNTER: AtomicUsize = AtomicUsize::new(0); + let destructured_name = format!( + "{}{}", + crate::constants::DESTRUCTURE_PREFIX, + COUNTER.load(Ordering::SeqCst) + ); + COUNTER.fetch_add(1, Ordering::SeqCst); + let destructure_name = Ident::new_with_override( + Box::leak(destructured_name.into_boxed_str()), + span.clone(), + ); + + // Parse the type ascription and the type ascription span. + // In the event that the user did not provide a type ascription, + // it is set to TypeInfo::Unknown and the span to None. + let (type_ascription, type_ascription_span) = match &ty_opt { + Some(ty) => { + let type_ascription_span = ty.span(); + let type_ascription = ty_to_type_info(ec, ty.clone())?; + (type_ascription, Some(type_ascription_span)) + } + None => (TypeInfo::Unknown, None), + }; + + // Save the destructure to the new name as a new variable declaration + let save_body_first = VariableDeclaration { + name: destructure_name.clone(), + type_ascription, + type_ascription_span, + body: expression, + is_mutable: false, + }; + ast_nodes.push(AstNode { + content: AstNodeContent::Declaration(Declaration::VariableDeclaration( + save_body_first, + )), + span: span.clone(), + }); + + // create a new variable expression that points to the new destructured struct name that we just created + let new_expr = Expression::VariableExpression { + name: destructure_name, + span: span.clone(), + }; + + // for all of the fields of the struct destructuring on the LHS, + // recursively create variable declarations + for pattern_struct_field in fields.into_inner().into_iter() { + let (field, recursive_pattern) = match pattern_struct_field { + PatternStructField::Field { + field_name, + pattern_opt, + } => { + let recursive_pattern = match pattern_opt { + Some((_colon_token, box_pattern)) => *box_pattern, + None => Pattern::Var { + mutable: None, + name: field_name.clone(), + }, + }; + (field_name, recursive_pattern) + } + PatternStructField::Rest { .. } => { + continue; + } + }; + + // recursively create variable declarations for the subpatterns on the LHS + // and add them to the ast nodes + ast_nodes.extend(unfold( + ec, + recursive_pattern, + None, + Expression::SubfieldExpression { + prefix: Box::new(new_expr.clone()), + span: span.clone(), + field_to_access: field, + }, + span.clone(), + )?); + } + ast_nodes } Pattern::Tuple(pat_tuple) => { let mut ast_nodes = Vec::new(); - // Generate a deterministic name for the tuple. Because the parser is single - // threaded, the name generated below will be stable. + // Generate a deterministic name for the tuple. + // Because the parser is single threaded, the name generated below will be stable. static COUNTER: AtomicUsize = AtomicUsize::new(0); let tuple_name = format!( "{}{}", @@ -2547,9 +2631,12 @@ fn statement_let_to_ast_nodes( COUNTER.load(Ordering::SeqCst) ); COUNTER.fetch_add(1, Ordering::SeqCst); - let name = + let tuple_name = Ident::new_with_override(Box::leak(tuple_name.into_boxed_str()), span.clone()); + // Parse the type ascription and the type ascription span. + // In the event that the user did not provide a type ascription, + // it is set to TypeInfo::Unknown and the span to None. let (type_ascription, type_ascription_span) = match &ty_opt { Some(ty) => { let type_ascription_span = ty.span(); @@ -2558,8 +2645,10 @@ fn statement_let_to_ast_nodes( } None => (TypeInfo::Unknown, None), }; + + // Save the tuple to the new name as a new variable declaration. let save_body_first = VariableDeclaration { - name: name.clone(), + name: tuple_name.clone(), type_ascription, type_ascription_span, body: expression, @@ -2571,19 +2660,31 @@ fn statement_let_to_ast_nodes( )), span: span.clone(), }); + + // create a variable expression that points to the new tuple name that we just created let new_expr = Expression::VariableExpression { - name, + name: tuple_name, span: span.clone(), }; + + // from the possible type annotation, if the annotation was a tuple annotation, + // extract the internal types of the annotation let tuple_tys_opt = match ty_opt { Some(Ty::Tuple(tys)) => Some(tys.into_inner().to_tys()), _ => None, }; + + // for all of the elements in the tuple destructuring on the LHS, + // recursively create variable declarations for (index, pattern) in pat_tuple.into_inner().into_iter().enumerate() { + // from the possible type annotation, grab the type at the index of the current element + // we are processing let ty_opt = match &tuple_tys_opt { Some(tys) => tys.get(index).cloned(), None => None, }; + // recursively create variable declarations for the subpatterns on the LHS + // and add them to the ast nodes ast_nodes.extend(unfold( ec, pattern, diff --git a/sway-parse/src/item/mod.rs b/sway-parse/src/item/mod.rs index 0d475cf2b72..e00c60fb6ee 100644 --- a/sway-parse/src/item/mod.rs +++ b/sway-parse/src/item/mod.rs @@ -296,7 +296,6 @@ mod tests { match Item::parse(&mut parser) { Ok(item) => item, Err(_) => { - //println!("Tokens: {:?}", token_stream); panic!("Parse error: {:?}", errors); } } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/nested_struct_destructuring/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/language/nested_struct_destructuring/Forc.lock new file mode 100644 index 00000000000..ef59a65c491 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/nested_struct_destructuring/Forc.lock @@ -0,0 +1,9 @@ +[[package]] +name = 'core' +source = 'path+from-root-F93ECA3248F311E3' +dependencies = [] + +[[package]] +name = 'struct_destructuring' +source = 'root' +dependencies = ['core'] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/nested_struct_destructuring/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/nested_struct_destructuring/Forc.toml new file mode 100644 index 00000000000..07a66019ad5 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/nested_struct_destructuring/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "struct_destructuring" + +[dependencies] +core = { path = "../../../../../../../sway-lib-core" } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/nested_struct_destructuring/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/nested_struct_destructuring/json_abi_oracle.json new file mode 100644 index 00000000000..4c13996a97e --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/nested_struct_destructuring/json_abi_oracle.json @@ -0,0 +1,15 @@ +[ + { + "inputs": [], + "name": "main", + "outputs": [ + { + "components": null, + "name": "", + "type": "u64", + "typeArguments": null + } + ], + "type": "function" + } +] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/nested_struct_destructuring/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/nested_struct_destructuring/src/main.sw new file mode 100644 index 00000000000..86c60b43999 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/nested_struct_destructuring/src/main.sw @@ -0,0 +1,33 @@ +script; + +// Tests nested struct destructuring + +fn main() -> u64 { + let tuple_in_struct = TupleInStruct { + nested_tuple: (42u64, (42u32, (true, "ok") ) ), + }; + let TupleInStruct { nested_tuple: (a, (b, (c, d) ) ) } = tuple_in_struct; + + let struct_in_tuple = (Point { x: 2, y: 4, }, Point { x: 3, y: 6 }); + let (Point { x: x0, y: y0 }, Point { x: x1, y: y1 }) = struct_in_tuple; + + let point1 = Point { x: 0, y: 0 }; + let point2 = Point { x: 1, y: 1 }; + let line = Line { p1: point1, p2: point2 }; + let Line { p1: Point { x: x2, y: y2 }, p2: Point { x: x3, y: y3} } = line; + x2 +} + +struct Point { + x: u64, + y: u64, +} + +struct Line { + p1: Point, + p2: Point, +} + +struct TupleInStruct { + nested_tuple: (u64, (u32, (bool, str[2]) ) ), +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/nested_struct_destructuring/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/nested_struct_destructuring/test.toml new file mode 100644 index 00000000000..8972919fe98 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/nested_struct_destructuring/test.toml @@ -0,0 +1,3 @@ +category = "run" +expected_result = { action = "return", value = 0 } +validate_abi = true \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/struct_destructuring/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/language/struct_destructuring/Forc.lock new file mode 100644 index 00000000000..ef59a65c491 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/struct_destructuring/Forc.lock @@ -0,0 +1,9 @@ +[[package]] +name = 'core' +source = 'path+from-root-F93ECA3248F311E3' +dependencies = [] + +[[package]] +name = 'struct_destructuring' +source = 'root' +dependencies = ['core'] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/struct_destructuring/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/struct_destructuring/Forc.toml new file mode 100644 index 00000000000..07a66019ad5 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/struct_destructuring/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "struct_destructuring" + +[dependencies] +core = { path = "../../../../../../../sway-lib-core" } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/struct_destructuring/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/struct_destructuring/json_abi_oracle.json new file mode 100644 index 00000000000..4c13996a97e --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/struct_destructuring/json_abi_oracle.json @@ -0,0 +1,15 @@ +[ + { + "inputs": [], + "name": "main", + "outputs": [ + { + "components": null, + "name": "", + "type": "u64", + "typeArguments": null + } + ], + "type": "function" + } +] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/struct_destructuring/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/struct_destructuring/src/main.sw new file mode 100644 index 00000000000..a8ad7974ec2 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/struct_destructuring/src/main.sw @@ -0,0 +1,26 @@ +script; + +// Tests struct destructuring + +fn gimme_a_struct() -> Dummy { + Dummy { value1: 1, value2: true } +} + +fn main() -> u64 { + let Dummy { value1, value2 } = gimme_a_struct(); + let Dummy { value1, value2 }: Dummy = gimme_a_struct(); + let data = Data { + value: 42, + }; + let Data { value }: Data = data; + return value; +} + +struct Data { + value: u64, +} + +struct Dummy { + value1: u64, + value2: bool, +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/struct_destructuring/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/struct_destructuring/test.toml new file mode 100644 index 00000000000..de877071cf9 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/struct_destructuring/test.toml @@ -0,0 +1,3 @@ +category = "run" +expected_result = { action = "return", value = 42 } +validate_abi = true \ No newline at end of file