Skip to content

Commit fdcd5dc

Browse files
authored
Add support for dynamic records (#29232)
* Add support for dynamic records This change adds the `dyn record` primitive type to expose the new dynamic record functionality. One can cast any record to a `dyn record`: ```leo record Token { owner: address, balance: u64 } fn foo(owner: address, balance: u64) -> dyn record { let t = Token { owner, balance }; let dt = t as dyn record; return dt; } ``` One can take the value of a field from a `dyn record`, provided it is cast to a type and exists at runtime: ```leo fn foo(dt: dyn record) -> u64 { let balance: u64 = dt.balance; return balance; } ``` * Add tests
1 parent ee07305 commit fdcd5dc

Some content is hidden

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

41 files changed

+483
-6
lines changed

crates/abi-types/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ pub enum Plaintext {
200200
pub enum FunctionInput {
201201
Plaintext(Plaintext),
202202
Record(RecordRef),
203+
DynamicRecord,
203204
}
204205

205206
/// Valid types for function outputs. Aleo: `transition` outputs.
@@ -209,6 +210,7 @@ pub enum FunctionOutput {
209210
Record(RecordRef),
210211
/// Aleo `future` - the handle for an on-chain finalization.
211212
Final,
213+
DynamicRecord,
212214
}
213215

214216
/// Primitive types that map directly to Aleo literal types.

crates/abi/src/aleo.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ fn convert_output(output: &ast::Output, record_names: &HashSet<Symbol>) -> abi::
9494
}
9595

9696
fn convert_function_input(ty: &ast::Type, record_names: &HashSet<Symbol>) -> abi::FunctionInput {
97+
if let ast::Type::DynRecord = ty {
98+
return abi::FunctionInput::DynamicRecord;
99+
}
97100
if let ast::Type::Composite(comp_ty) = ty {
98101
let name = comp_ty.path.identifier().name;
99102
if record_names.contains(&name) {
@@ -109,6 +112,7 @@ fn convert_function_input(ty: &ast::Type, record_names: &HashSet<Symbol>) -> abi
109112
fn convert_function_output(ty: &ast::Type, record_names: &HashSet<Symbol>) -> abi::FunctionOutput {
110113
match ty {
111114
ast::Type::Future(_) => abi::FunctionOutput::Final,
115+
ast::Type::DynRecord => abi::FunctionOutput::DynamicRecord,
112116
ast::Type::Composite(comp_ty) => {
113117
let name = comp_ty.path.identifier().name;
114118
if record_names.contains(&name) {

crates/abi/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,9 @@ fn convert_plaintext(ty: &ast::Type) -> abi::Plaintext {
194194
}
195195

196196
fn convert_function_input(ty: &ast::Type, ctx: &Ctx) -> abi::FunctionInput {
197+
if let ast::Type::DynRecord = ty {
198+
return abi::FunctionInput::DynamicRecord;
199+
}
197200
if let ast::Type::Composite(comp_ty) = ty
198201
&& is_record(comp_ty, ctx)
199202
{
@@ -208,6 +211,7 @@ fn convert_function_input(ty: &ast::Type, ctx: &Ctx) -> abi::FunctionInput {
208211
fn convert_function_output(ty: &ast::Type, ctx: &Ctx) -> abi::FunctionOutput {
209212
match ty {
210213
ast::Type::Future(_) => abi::FunctionOutput::Final,
214+
ast::Type::DynRecord => abi::FunctionOutput::DynamicRecord,
211215
ast::Type::Composite(comp_ty) if is_record(comp_ty, ctx) => abi::FunctionOutput::Record(abi::RecordRef {
212216
path: comp_ty.path.segments_iter().map(|s| s.to_string()).collect(),
213217
program: comp_ty.path.program().map(|s| s.to_string()),
@@ -358,6 +362,7 @@ fn collect_from_function_input(ty: &abi::FunctionInput, program_name: &str, used
358362
used.insert(rec_ref.path.clone());
359363
}
360364
}
365+
abi::FunctionInput::DynamicRecord => {}
361366
}
362367
}
363368

@@ -370,6 +375,7 @@ fn collect_from_function_output(ty: &abi::FunctionOutput, program_name: &str, us
370375
}
371376
}
372377
abi::FunctionOutput::Final => {}
378+
abi::FunctionOutput::DynamicRecord => {}
373379
}
374380
}
375381

crates/errors/src/errors/type_checker/type_checker_error.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1438,4 +1438,18 @@ create_messages!(
14381438
msg: format!("Dynamic calls can only be made from an entry point, but found one in {context}."),
14391439
help: None,
14401440
}
1441+
1442+
@formatted
1443+
dyn_record_field_requires_type {
1444+
args: (field: impl Display),
1445+
msg: format!("Accessing field `{field}` on a `dyn record` requires a type annotation."),
1446+
help: Some(format!("Use `let x: <type> = r.{field};` or `r.{field} as <type>`.")),
1447+
}
1448+
1449+
@formatted
1450+
cannot_cast_to_dyn_record {
1451+
args: (type_: impl Display),
1452+
msg: format!("Cannot cast `{type_}` to `dyn record`: only concrete record types can be cast to `dyn record`."),
1453+
help: None,
1454+
}
14411455
);

crates/parser-rowan/src/lexer.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ fn ident_to_kind(s: &str) -> SyntaxKind {
292292
"signature" => KW_SIGNATURE,
293293
"string" => KW_STRING,
294294
"record" => KW_RECORD,
295+
"dyn" => KW_DYN,
295296
"identifier" => KW_IDENTIFIER,
296297
"i8" => KW_I8,
297298
"i16" => KW_I16,

crates/parser-rowan/src/parser/types.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ impl Parser<'_, '_> {
5454
KW_SIGNATURE,
5555
KW_STRING,
5656
KW_IDENTIFIER,
57+
KW_DYN,
5758
KW_I8,
5859
KW_I16,
5960
KW_I32,
@@ -129,7 +130,12 @@ impl Parser<'_, '_> {
129130
// isn't captured inside the TYPE_PRIMITIVE.
130131
self.skip_trivia();
131132
let m = self.start();
133+
let kind = self.current();
132134
self.bump_raw();
135+
// `dyn record` is a two-token primitive type.
136+
if kind == KW_DYN {
137+
self.expect(KW_RECORD);
138+
}
133139
Some(m.complete(self, TYPE_PRIMITIVE))
134140
}
135141

crates/parser-rowan/src/syntax_kind.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ define_syntax_kinds! {
127127
KW_STRING,
128128
/// `record`
129129
KW_RECORD,
130+
/// `dyn`
131+
KW_DYN,
130132
/// `identifier`
131133
KW_IDENTIFIER,
132134
/// `Final`
@@ -596,6 +598,7 @@ impl SyntaxKind {
596598
| KW_SIGNATURE
597599
| KW_STRING
598600
| KW_RECORD
601+
| KW_DYN
599602
| KW_IDENTIFIER
600603
| KW_FINAL_UPPER
601604
| KW_I8
@@ -651,6 +654,7 @@ impl SyntaxKind {
651654
| KW_SCALAR
652655
| KW_SIGNATURE
653656
| KW_STRING
657+
| KW_DYN
654658
| KW_IDENTIFIER
655659
| KW_FINAL_UPPER
656660
| KW_I8
@@ -853,6 +857,7 @@ impl SyntaxKind {
853857
KW_SIGNATURE => "'signature'",
854858
KW_STRING => "'string'",
855859
KW_RECORD => "'record'",
860+
KW_DYN => "'dyn'",
856861
KW_IDENTIFIER => "'identifier'",
857862
KW_FINAL_UPPER => "'Final'",
858863
KW_I8 => "'i8'",

crates/parser/src/rowan.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2935,6 +2935,7 @@ fn keyword_to_primitive_type(kind: SyntaxKind) -> Option<leo_ast::Type> {
29352935
KW_SCALAR => leo_ast::Type::Scalar,
29362936
KW_SIGNATURE => leo_ast::Type::Signature,
29372937
KW_STRING => leo_ast::Type::String,
2938+
KW_DYN => leo_ast::Type::DynRecord,
29382939
KW_IDENTIFIER => leo_ast::Type::Identifier,
29392940
KW_U8 => leo_ast::Type::Integer(leo_ast::IntegerType::U8),
29402941
KW_U16 => leo_ast::Type::Integer(leo_ast::IntegerType::U16),

crates/passes/src/code_generation/expression.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
use super::*;
1818

19+
use leo_span::sym;
20+
1921
use leo_ast::{
2022
ArrayAccess,
2123
ArrayExpression,
@@ -69,7 +71,10 @@ impl CodeGeneratingVisitor<'_> {
6971

7072
match input {
7173
Expression::ArrayAccess(expr) => (Some(self.visit_array_access(expr)), vec![]),
72-
Expression::MemberAccess(expr) => (Some(self.visit_member_access(expr)), vec![]),
74+
Expression::MemberAccess(expr) => {
75+
let (expr, stmts) = self.visit_member_access(expr);
76+
(Some(expr), stmts)
77+
}
7378
Expression::Path(expr) => (Some(self.visit_path(expr)), vec![]),
7479
Expression::Literal(expr) => (Some(self.visit_value(expr)), vec![]),
7580

@@ -405,11 +410,28 @@ impl CodeGeneratingVisitor<'_> {
405410
AleoExpr::ArrayAccess(Box::new(array_operand), Box::new(index_operand))
406411
}
407412

408-
fn visit_member_access(&mut self, input: &MemberAccess) -> AleoExpr {
409-
let (inner_expr, _) = self.visit_expression(&input.inner);
413+
fn visit_member_access(&mut self, input: &MemberAccess) -> (AleoExpr, Vec<AleoStmt>) {
414+
let (inner_expr, mut instructions) = self.visit_expression(&input.inner);
410415
let inner_expr = inner_expr.expect("Trying to access a member of an empty expression.");
411416

412-
AleoExpr::MemberAccess(Box::new(inner_expr), input.name.to_string())
417+
// Check if the inner expression is a dyn record.
418+
let inner_type = self.state.type_table.get(&input.inner.id());
419+
if matches!(inner_type, Some(Type::DynRecord)) && input.name.name != sym::owner {
420+
// Non-owner field access on dyn record: emit get.record.dynamic.
421+
let result_type = self.state.type_table.get(&input.id).expect("Type should be resolved.");
422+
let dest_reg = self.next_register();
423+
let aleo_type = self.visit_type(&result_type);
424+
instructions.push(AleoStmt::GetRecordDynamic(
425+
inner_expr,
426+
input.name.to_string(),
427+
dest_reg.clone(),
428+
aleo_type,
429+
));
430+
return (AleoExpr::Reg(dest_reg), instructions);
431+
}
432+
433+
// Regular member access (or .owner on dyn record).
434+
(AleoExpr::MemberAccess(Box::new(inner_expr), input.name.to_string()), instructions)
413435
}
414436

415437
fn visit_repeat(&mut self, input: &RepeatExpression) -> (AleoExpr, Vec<AleoStmt>) {

crates/passes/src/code_generation/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,7 @@ pub enum AleoStmt {
421421
Vec<AleoReg>,
422422
Vec<(AleoType, Option<AleoVisibility>)>,
423423
),
424+
GetRecordDynamic(AleoExpr, String, AleoReg, AleoType),
424425
Async(String, Vec<AleoExpr>, Vec<AleoReg>),
425426
BranchEq(AleoExpr, AleoExpr, String),
426427
Position(String),
@@ -468,6 +469,9 @@ impl Display for AleoStmt {
468469
Self::Cast(operands, dest, type_) => {
469470
writeln!(f, " cast {operands} into {dest} as {type_};")
470471
}
472+
Self::GetRecordDynamic(src, field, dest, type_) => {
473+
writeln!(f, " get.record.dynamic {src}.{field} into {dest} as {type_};")
474+
}
471475
Self::Abs(op, dest) => writeln!(f, " abs {op} into {dest};"),
472476
Self::AbsW(op, dest) => writeln!(f, " abs.w {op} into {dest};"),
473477
Self::Double(op, dest) => writeln!(f, " double {op} into {dest};"),

0 commit comments

Comments
 (0)