Skip to content

Commit e57533f

Browse files
authored
[move-compiler] Add positional struct fields (#14073)
## Description Adds support for positional struct fields in Move 2024. This affects the parser, expansion, and naming (and a minor update is needed in `to_bytecode`). ## Parser * Updated to support positional struct declarations * Updated dotted field accesses to support valid struct indices * Updated binding parsing to support positional struct destructuring ## Expansion * Added support for conversion of a `Call` to `Unpack` if it's in an `LValue` position (similar to what we do in expansion for `Pack` to `Unpack` in LValue position). ## Naming * Positional fields are removed at this point * `Call`s are disambiguated into `Packs` or remain as `Call`s based on the name references an in-scope struct type. * We enforce that named packs/unpacks and positional packs/unpacks correspond to a named/positional struct declaration. ## Test Plan Added a number of tests for each phase. --- If your changes are not user-facing and not a breaking change, you can skip the following section. Otherwise, please indicate what changed, and then add to the Release Notes section as highlighted during the release process. ### Type of Change (Check all that apply) - [ ] protocol change - [X] user-visible impact - [ ] breaking change for a client SDKs - [ ] breaking change for FNs (FN binary must upgrade) - [ ] breaking change for validators or node operators (must upgrade binaries) - [ ] breaking change for on-chain data layout - [ ] necessitate either a data wipe or data migration ### Release notes Adds positional struct declaration support to Move 2024.alpha. When using the 2024 edition you'll be able to declare structs with positional fields (e.g., `public struct Positional(u64, bool)`), access positional struct fields via their index (e.g., `x.0` `,x.1`) and unpack/pack them positionally (e.g., `let Positional(value, is_tf) = x;`, `let x = Positional(0, true)`).
1 parent fb3d21d commit e57533f

File tree

69 files changed

+1547
-119
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+1547
-119
lines changed

move-compiler/src/diagnostics/codes.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ codes!(
220220
UnboundField: { msg: "unbound field", severity: BlockingError },
221221
ReservedName: { msg: "invalid use of reserved name", severity: BlockingError },
222222
UnboundMacro: { msg: "unbound macro", severity: BlockingError },
223+
PositionalCallMismatch: { msg: "positional call mismatch", severity: NonblockingError },
223224
],
224225
// errors for typing rules. mostly typing/translate
225226
TypeSafety: [

move-compiler/src/editions/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub enum FeatureGate {
3131
PostFixAbilities,
3232
StructTypeVisibility,
3333
DotCall,
34+
PositionalFields,
3435
}
3536

3637
#[derive(PartialEq, Eq, Clone, Copy, Debug, PartialOrd, Ord, Default)]
@@ -97,6 +98,7 @@ const E2024_ALPHA_FEATURES: &[FeatureGate] = &[
9798
FeatureGate::PostFixAbilities,
9899
FeatureGate::StructTypeVisibility,
99100
FeatureGate::DotCall,
101+
FeatureGate::PositionalFields,
100102
];
101103

102104
impl Edition {
@@ -169,6 +171,7 @@ impl FeatureGate {
169171
FeatureGate::PostFixAbilities => "Postfix abilities are",
170172
FeatureGate::StructTypeVisibility => "Struct visibility modifiers are",
171173
FeatureGate::DotCall => "Method syntax is",
174+
FeatureGate::PositionalFields => "Positional fields are",
172175
}
173176
}
174177
}

move-compiler/src/expansion/ast.rs

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,8 @@ pub struct StructDefinition {
210210

211211
#[derive(Debug, Clone, PartialEq)]
212212
pub enum StructFields {
213-
Defined(Fields<Type>),
213+
Positional(Vec<Type>),
214+
Named(Fields<Type>),
214215
Native(Loc),
215216
}
216217

@@ -403,10 +404,16 @@ pub type Type = Spanned<Type_>;
403404
// Expressions
404405
//**************************************************************************************************
405406

407+
#[derive(Debug, Clone, PartialEq)]
408+
pub enum FieldBindings {
409+
Named(Fields<LValue>),
410+
Positional(Vec<LValue>),
411+
}
412+
406413
#[derive(Debug, Clone, PartialEq)]
407414
pub enum LValue_ {
408415
Var(ModuleAccess, Option<Vec<Type>>),
409-
Unpack(ModuleAccess, Option<Vec<Type>>, Fields<LValue>),
416+
Unpack(ModuleAccess, Option<Vec<Type>>, FieldBindings),
410417
}
411418
pub type LValue = Spanned<LValue_>;
412419
pub type LValueList_ = Vec<LValue>;
@@ -1188,15 +1195,23 @@ impl AstDebug for (StructName, &StructDefinition) {
11881195
w.write(&format!("struct#{index} {name}"));
11891196
type_parameters.ast_debug(w);
11901197
ability_modifiers_ast_debug(w, abilities);
1191-
if let StructFields::Defined(fields) = fields {
1192-
w.block(|w| {
1198+
match fields {
1199+
StructFields::Named(fields) => w.block(|w| {
11931200
w.list(fields, ",", |w, (_, f, idx_st)| {
11941201
let (idx, st) = idx_st;
11951202
w.write(&format!("{}#{}: ", idx, f));
11961203
st.ast_debug(w);
11971204
true
11981205
});
1199-
})
1206+
}),
1207+
StructFields::Positional(fields) => w.block(|w| {
1208+
w.list(fields.iter().enumerate(), ",", |w, (idx, ty)| {
1209+
w.write(&format!("{idx}#pos{idx}: "));
1210+
ty.ast_debug(w);
1211+
true
1212+
});
1213+
}),
1214+
StructFields::Native(_) => (),
12001215
}
12011216
}
12021217
}
@@ -1863,20 +1878,14 @@ impl AstDebug for LValue_ {
18631878
w.write(">");
18641879
}
18651880
}
1866-
L::Unpack(ma, tys_opt, fields) => {
1881+
L::Unpack(ma, tys_opt, field_binds) => {
18671882
ma.ast_debug(w);
18681883
if let Some(ss) = tys_opt {
18691884
w.write("<");
18701885
ss.ast_debug(w);
18711886
w.write(">");
18721887
}
1873-
w.write("{");
1874-
w.comma(fields, |w, (_, f, idx_b)| {
1875-
let (idx, b) = idx_b;
1876-
w.write(&format!("{}#{}: ", idx, f));
1877-
b.ast_debug(w);
1878-
});
1879-
w.write("}");
1888+
field_binds.ast_debug(w);
18801889
}
18811890
}
18821891
}
@@ -1912,3 +1921,27 @@ impl AstDebug for Vec<Vec<Exp>> {
19121921
}
19131922
}
19141923
}
1924+
1925+
impl AstDebug for FieldBindings {
1926+
fn ast_debug(&self, w: &mut AstWriter) {
1927+
match self {
1928+
FieldBindings::Named(fields) => {
1929+
w.write("{");
1930+
w.comma(fields, |w, (_, f, idx_b)| {
1931+
let (idx, b) = idx_b;
1932+
w.write(&format!("{}#{}: ", idx, f));
1933+
b.ast_debug(w);
1934+
});
1935+
w.write("}");
1936+
}
1937+
FieldBindings::Positional(vals) => {
1938+
w.write("(");
1939+
w.comma(vals.iter().enumerate(), |w, (idx, lval)| {
1940+
w.write(&format!("{idx}: "));
1941+
lval.ast_debug(w);
1942+
});
1943+
w.write(")");
1944+
}
1945+
}
1946+
}
1947+
}

move-compiler/src/expansion/translate.rs

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ use crate::{
1212
byte_string, hex_string,
1313
},
1414
parser::ast::{
15-
self as P, Ability, ConstantName, Field, FunctionName, ModuleName, StructName, Var,
15+
self as P, Ability, ConstantName, Field, FieldBindings, FunctionName, ModuleName,
16+
StructName, Var,
1617
},
1718
shared::{known_attributes::AttributePosition, unique_map::UniqueMap, *},
1819
FullyCompiledProgram,
@@ -1461,6 +1462,10 @@ fn struct_fields(
14611462
) -> E::StructFields {
14621463
let pfields_vec = match pfields {
14631464
P::StructFields::Native(loc) => return E::StructFields::Native(loc),
1465+
P::StructFields::Positional(tys) => {
1466+
let field_tys = tys.into_iter().map(|fty| type_(context, fty)).collect();
1467+
return E::StructFields::Positional(field_tys);
1468+
}
14641469
P::StructFields::Defined(v) => v,
14651470
};
14661471
let mut field_map = UniqueMap::new();
@@ -1480,7 +1485,7 @@ fn struct_fields(
14801485
));
14811486
}
14821487
}
1483-
E::StructFields::Defined(field_map)
1488+
E::StructFields::Named(field_map)
14841489
}
14851490

14861491
//**************************************************************************************************
@@ -2278,7 +2283,7 @@ fn exp_(context: &mut Context, sp!(loc, pe_): P::Exp) -> E::Exp {
22782283
.into_iter()
22792284
.map(|(f, pe)| (f, exp_(context, pe)))
22802285
.collect();
2281-
let efields = fields(context, loc, "construction", "argument", efields_vec);
2286+
let efields = named_fields(context, loc, "construction", "argument", efields_vec);
22822287
match en_opt {
22832288
Some(en) => EE::Pack(en, tys_opt, efields),
22842289
None => {
@@ -2562,7 +2567,7 @@ fn num_too_big_error(loc: Loc, type_description: &'static str) -> Diagnostic {
25622567
// Fields
25632568
//**************************************************************************************************
25642569

2565-
fn fields<T>(
2570+
fn named_fields<T>(
25662571
context: &mut Context,
25672572
loc: Loc,
25682573
case: &str,
@@ -2621,11 +2626,24 @@ fn bind(context: &mut Context, sp!(loc, pb_): P::Bind) -> Option<E::LValue> {
26212626
PB::Unpack(ptn, ptys_opt, pfields) => {
26222627
let tn = name_access_chain(context, Access::ApplyNamed, *ptn)?;
26232628
let tys_opt = optional_types(context, ptys_opt);
2624-
let vfields: Option<Vec<(Field, E::LValue)>> = pfields
2625-
.into_iter()
2626-
.map(|(f, pb)| Some((f, bind(context, pb)?)))
2627-
.collect();
2628-
let fields = fields(context, loc, "deconstruction binding", "binding", vfields?);
2629+
let fields = match pfields {
2630+
FieldBindings::Named(named_bindings) => {
2631+
let vfields: Option<Vec<(Field, E::LValue)>> = named_bindings
2632+
.into_iter()
2633+
.map(|(f, pb)| Some((f, bind(context, pb)?)))
2634+
.collect();
2635+
let fields =
2636+
named_fields(context, loc, "deconstruction binding", "binding", vfields?);
2637+
E::FieldBindings::Named(fields)
2638+
}
2639+
FieldBindings::Positional(positional_bindings) => {
2640+
let fields: Option<Vec<E::LValue>> = positional_bindings
2641+
.into_iter()
2642+
.map(|b| bind(context, b))
2643+
.collect();
2644+
E::FieldBindings::Positional(fields?)
2645+
}
2646+
};
26292647
EL::Unpack(tn, tys_opt, fields)
26302648
}
26312649
};
@@ -2723,7 +2741,16 @@ fn assign(context: &mut Context, sp!(loc, e_): P::Exp) -> Option<E::LValue> {
27232741
let en = name_access_chain(context, Access::ApplyNamed, pn)?;
27242742
let tys_opt = optional_types(context, ptys_opt);
27252743
let efields = assign_unpack_fields(context, loc, pfields)?;
2726-
EL::Unpack(en, tys_opt, efields)
2744+
EL::Unpack(en, tys_opt, E::FieldBindings::Named(efields))
2745+
}
2746+
PE::Call(pn, false, ptys_opt, sp!(_, exprs)) => {
2747+
context
2748+
.env
2749+
.check_feature(FeatureGate::PositionalFields, context.current_package, loc);
2750+
let en = name_access_chain(context, Access::ApplyNamed, pn)?;
2751+
let tys_opt = optional_types(context, ptys_opt);
2752+
let pfields: Option<_> = exprs.into_iter().map(|e| assign(context, e)).collect();
2753+
EL::Unpack(en, tys_opt, E::FieldBindings::Positional(pfields?))
27272754
}
27282755
_ => {
27292756
context.env.add_diag(diag!(
@@ -2749,7 +2776,7 @@ fn assign_unpack_fields(
27492776
.into_iter()
27502777
.map(|(f, e)| Some((f, assign(context, e)?)))
27512778
.collect::<Option<_>>()?;
2752-
Some(fields(
2779+
Some(named_fields(
27532780
context,
27542781
loc,
27552782
"deconstructing assignment",
@@ -2924,9 +2951,14 @@ fn unbound_names_bind(unbound: &mut BTreeSet<Name>, sp!(_, l_): &E::LValue) {
29242951
EL::Var(sp!(_, E::ModuleAccess_::ModuleAccess(..)), _) => {
29252952
// Qualified vars are not considered in unbound set.
29262953
}
2927-
EL::Unpack(_, _, efields) => efields
2928-
.iter()
2929-
.for_each(|(_, _, (_, l))| unbound_names_bind(unbound, l)),
2954+
EL::Unpack(_, _, efields) => match efields {
2955+
E::FieldBindings::Named(efields) => efields
2956+
.iter()
2957+
.for_each(|(_, _, (_, l))| unbound_names_bind(unbound, l)),
2958+
E::FieldBindings::Positional(lvals) => {
2959+
lvals.iter().for_each(|l| unbound_names_bind(unbound, l))
2960+
}
2961+
},
29302962
}
29312963
}
29322964

@@ -2945,9 +2977,14 @@ fn unbound_names_assign(unbound: &mut BTreeSet<Name>, sp!(_, l_): &E::LValue) {
29452977
EL::Var(sp!(_, E::ModuleAccess_::ModuleAccess(..)), _) => {
29462978
// Qualified vars are not considered in unbound set.
29472979
}
2948-
EL::Unpack(_, _, efields) => efields
2949-
.iter()
2950-
.for_each(|(_, _, (_, l))| unbound_names_assign(unbound, l)),
2980+
EL::Unpack(_, _, efields) => match efields {
2981+
E::FieldBindings::Named(efields) => efields
2982+
.iter()
2983+
.for_each(|(_, _, (_, l))| unbound_names_assign(unbound, l)),
2984+
E::FieldBindings::Positional(lvals) => {
2985+
lvals.iter().for_each(|l| unbound_names_assign(unbound, l))
2986+
}
2987+
},
29512988
}
29522989
}
29532990

0 commit comments

Comments
 (0)