diff --git a/compiler/ast/src/types/type_.rs b/compiler/ast/src/types/type_.rs index 941949a35e..d42d2406e4 100644 --- a/compiler/ast/src/types/type_.rs +++ b/compiler/ast/src/types/type_.rs @@ -111,7 +111,7 @@ impl fmt::Display for Type { Type::I32 => write!(f, "i32"), Type::I64 => write!(f, "i64"), Type::I128 => write!(f, "i128"), - Type::Identifier(ref variable) => write!(f, "circuit {}", variable), + Type::Identifier(ref variable) => write!(f, "{}", variable), Type::Scalar => write!(f, "scalar"), Type::String => write!(f, "string"), Type::U8 => write!(f, "u8"), diff --git a/compiler/passes/src/code_generation/visit_program.rs b/compiler/passes/src/code_generation/visit_program.rs index 88d2d7d462..7c1d35a919 100644 --- a/compiler/passes/src/code_generation/visit_program.rs +++ b/compiler/passes/src/code_generation/visit_program.rs @@ -149,7 +149,7 @@ impl<'a> CodeGenerator<'a> { writeln!( output_string, " {} as {}.private;", // todo: CAUTION private record variables only. - name, type_, + name, type_.to_string().to_lowercase(), ) .expect("failed to write to string"); } diff --git a/compiler/passes/src/type_checking/check_program.rs b/compiler/passes/src/type_checking/check_program.rs index 342da11195..da1636252c 100644 --- a/compiler/passes/src/type_checking/check_program.rs +++ b/compiler/passes/src/type_checking/check_program.rs @@ -121,6 +121,21 @@ impl<'a> ProgramVisitor<'a> for TypeChecker<'a> { }; check_has_field(sym::owner, Type::Address); check_has_field(sym::gates, Type::U64); + + // Check that the record does not contain another record. + for member in input.members.iter() { + if let CircuitMember::CircuitVariable(_, Type::Identifier(identifier)) = member { + if let Some(circuit) = self.symbol_table.borrow().lookup_circuit(identifier.name) { + if circuit.is_record { + self.emit_err(TypeCheckerError::record_cannot_contain_record( + input.identifier.name, + identifier.name, + input.span(), + )); + } + } + } + } } // Ensure there are no tuple typed members. diff --git a/errors/src/errors/type_checker/type_checker_error.rs b/errors/src/errors/type_checker/type_checker_error.rs index 0e93e66c4b..be93d9dff3 100644 --- a/errors/src/errors/type_checker/type_checker_error.rs +++ b/errors/src/errors/type_checker/type_checker_error.rs @@ -288,4 +288,11 @@ create_messages!( msg: format!("Helper functions cannot have modes associated with their inputs."), help: Some("Consider removing the mode or adding a `@program` annotation to the function.".to_string()), } + + @formatted + record_cannot_contain_record { + args: (parent_record: impl Display, child_record: impl Display), + msg: format!("A record cannot contain another record."), + help: Some(format!("Remove the record `{child_record} from `{parent_record}`.")), + } ); diff --git a/tests/compiler/records/nested_record.leo b/tests/compiler/records/nested_record.leo new file mode 100644 index 0000000000..3eda140179 --- /dev/null +++ b/tests/compiler/records/nested_record.leo @@ -0,0 +1,35 @@ +/* +namespace: Compile +expectation: Pass +*/ + +circuit Amount { + amount: u64, + amt: u64, +} + +record Token { + // The token owner. + owner: address, + // The Aleo balance (in gates). + gates: u64, + // The token amount. + amount: Amount, +} + +@program +function mint(r0: address, r1: u64) -> Token { + return Token { + owner: r0, + gates: 0u64, + amount: Amount { amount: r1, amt: r1 }, + }; +} + +@program +function main(x: address) -> u64 { + const c: u64 = 1u64; + let t: Token = Token { owner: x, gates: 0u64, amount: Amount { amount: c, amt: c } }; + + return t.gates; +} \ No newline at end of file diff --git a/tests/compiler/records/nested_record_fail.leo b/tests/compiler/records/nested_record_fail.leo new file mode 100644 index 0000000000..09fa94bae0 --- /dev/null +++ b/tests/compiler/records/nested_record_fail.leo @@ -0,0 +1,22 @@ +/* +namespace: Compile +expectation: Fail +*/ + +record Foo { + // The token owner. + owner: address, + // The Aleo balance (in gates). + gates: u64, + // The token amount. + amount: u64, +} + +record Token { + // The token owner. + owner: address, + // The Aleo balance (in gates). + gates: u64, + // The token amount. + foo: Foo, +} \ No newline at end of file diff --git a/tests/expectations/compiler/records/nested_record.out b/tests/expectations/compiler/records/nested_record.out new file mode 100644 index 0000000000..39211e314d --- /dev/null +++ b/tests/expectations/compiler/records/nested_record.out @@ -0,0 +1,9 @@ +--- +namespace: Compile +expectation: Pass +outputs: + - output: + - initial_input_ast: no input + initial_ast: e781700aae47b3e3e6fabae4bc8620588ce4a91c8ed500cd1bfb0005ba92f9a0 + unrolled_ast: e781700aae47b3e3e6fabae4bc8620588ce4a91c8ed500cd1bfb0005ba92f9a0 + ssa_ast: fdd24be71c71d34367fdd00465fa80391d89776c49e62ad13bea07385dfeb8f8 diff --git a/tests/expectations/compiler/records/nested_record_fail.out b/tests/expectations/compiler/records/nested_record_fail.out new file mode 100644 index 0000000000..e2bee6da1f --- /dev/null +++ b/tests/expectations/compiler/records/nested_record_fail.out @@ -0,0 +1,5 @@ +--- +namespace: Compile +expectation: Fail +outputs: + - "Error [ETYC0372030]: A record cannot contain another record.\n --> compiler-test:12:1\n |\n 12 | record Token {\n 13 | // The token owner.\n 14 | owner: address,\n 15 | // The Aleo balance (in gates).\n 16 | gates: u64,\n 17 | // The token amount.\n 18 | foo: Foo,\n 19 | }\n | ^\n |\n = Remove the record `Foo from `Token`.\n"