Skip to content

Commit

Permalink
feat: struct destructuring (#2243)
Browse files Browse the repository at this point in the history
* test: struct destructuring

* feat: add print statements for debugging

* debugging: add print

* Add Forc.lock.

* Add descriptions.

* fix: remove println! statements

* chore: update to latest version

* feat: add not working Pattern:Struct branch to match

* fix: identation in oracle json

* feat: create pattern if field has none

* refactor: remove debug prints

* fix: warnings

* test: type annotations for destructure

* fix: handle type annotations

* chore: run formatter

* fix: remove test cargo toml

* fix: destructure test case

* test: nested struct destructuring

* docs: add struct destructuring

* refactor: fix formatting

* fix: formatting

* refactor examples

* fix: formatting

* fix: remove mention of deleted file

* test: nested tuple in struct and vis versa

* docs: add nested tuple in struct and vis versa to examples

* fix: formatting

* fix: formatting

* fix: formatting

* fix: formatting

Co-authored-by: emilyaherbert <emily.herbert@fuel.sh>
  • Loading branch information
matt-user and emilyaherbert authored Jul 8, 2022
1 parent fa003ff commit aadca68
Show file tree
Hide file tree
Showing 16 changed files with 319 additions and 10 deletions.
2 changes: 2 additions & 0 deletions docs/src/basics/structs_tuples_and_enums.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand Down
16 changes: 16 additions & 0 deletions examples/structs/src/data_structures.sw
Original file line number Diff line number Diff line change
Expand Up @@ -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]))),
}
61 changes: 60 additions & 1 deletion examples/structs/src/main.sw
Original file line number Diff line number Diff line change
@@ -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`
Expand Down Expand Up @@ -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;
}
3 changes: 3 additions & 0 deletions sway-core/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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_";

Expand Down
117 changes: 109 additions & 8 deletions sway-core/src/convert_parse_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2531,25 +2531,112 @@ 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!(
"{}{}",
crate::constants::TUPLE_NAME_PREFIX,
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();
Expand All @@ -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,
Expand All @@ -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,
Expand Down
1 change: 0 additions & 1 deletion sway-parse/src/item/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,6 @@ mod tests {
match Item::parse(&mut parser) {
Ok(item) => item,
Err(_) => {
//println!("Tokens: {:?}", token_stream);
panic!("Parse error: {:?}", errors);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[[package]]
name = 'core'
source = 'path+from-root-F93ECA3248F311E3'
dependencies = []

[[package]]
name = 'struct_destructuring'
source = 'root'
dependencies = ['core']
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
entry = "main.sw"
license = "Apache-2.0"
name = "struct_destructuring"

[dependencies]
core = { path = "../../../../../../../sway-lib-core" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[
{
"inputs": [],
"name": "main",
"outputs": [
{
"components": null,
"name": "",
"type": "u64",
"typeArguments": null
}
],
"type": "function"
}
]
Original file line number Diff line number Diff line change
@@ -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]) ) ),
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
category = "run"
expected_result = { action = "return", value = 0 }
validate_abi = true
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[[package]]
name = 'core'
source = 'path+from-root-F93ECA3248F311E3'
dependencies = []

[[package]]
name = 'struct_destructuring'
source = 'root'
dependencies = ['core']
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
entry = "main.sw"
license = "Apache-2.0"
name = "struct_destructuring"

[dependencies]
core = { path = "../../../../../../../sway-lib-core" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[
{
"inputs": [],
"name": "main",
"outputs": [
{
"components": null,
"name": "",
"type": "u64",
"typeArguments": null
}
],
"type": "function"
}
]
Loading

0 comments on commit aadca68

Please sign in to comment.