From da7ff48b1135ed85cbfc115331a5b7b69c90b7fe Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu Date: Wed, 10 Aug 2022 14:56:41 -0700 Subject: [PATCH 1/4] Add check for nested records; fix codegen for circuit nested in record --- compiler/ast/src/types/type_.rs | 2 +- .../src/code_generation/visit_program.rs | 2 +- .../passes/src/type_checking/check_program.rs | 15 ++++++++ .../errors/type_checker/type_checker_error.rs | 7 ++++ tests/compiler/records/nested_record.leo | 35 +++++++++++++++++++ tests/compiler/records/nested_record_fail.leo | 22 ++++++++++++ .../compiler/records/nested_record.out | 9 +++++ .../compiler/records/nested_record_fail.out | 5 +++ 8 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 tests/compiler/records/nested_record.leo create mode 100644 tests/compiler/records/nested_record_fail.leo create mode 100644 tests/expectations/compiler/records/nested_record.out create mode 100644 tests/expectations/compiler/records/nested_record_fail.out 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" From 8caafb78d5165f91df4f891f495fd46f83d6a538 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu Date: Thu, 11 Aug 2022 00:55:46 -0700 Subject: [PATCH 2/4] Fmt --- compiler/passes/src/code_generation/visit_program.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/passes/src/code_generation/visit_program.rs b/compiler/passes/src/code_generation/visit_program.rs index 7c1d35a919..1e40f416df 100644 --- a/compiler/passes/src/code_generation/visit_program.rs +++ b/compiler/passes/src/code_generation/visit_program.rs @@ -149,7 +149,8 @@ impl<'a> CodeGenerator<'a> { writeln!( output_string, " {} as {}.private;", // todo: CAUTION private record variables only. - name, type_.to_string().to_lowercase(), + name, + type_.to_string().to_lowercase(), ) .expect("failed to write to string"); } From 64cecda8417cbee1692c77df8d18b1cbba4a3884 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu Date: Thu, 11 Aug 2022 08:55:07 -0700 Subject: [PATCH 3/4] Add more test cases checking for member records --- .../circuit_contains_record_fail.leo} | 10 +++----- .../compiler/records/nested_record_1_fail.leo | 16 ++++++++++++ .../compiler/records/nested_record_2_fail.leo | 17 +++++++++++++ .../compiler/records/nested_record_3_fail.leo | 21 ++++++++++++++++ .../compiler/records/nested_record_4_fail.leo | 25 +++++++++++++++++++ .../circuits/circuit_contains_record_fail.out | 5 ++++ .../compiler/records/nested_record_1_fail.out | 5 ++++ .../compiler/records/nested_record_2_fail.out | 5 ++++ .../compiler/records/nested_record_3_fail.out | 5 ++++ .../compiler/records/nested_record_4_fail.out | 5 ++++ .../compiler/records/nested_record_fail.out | 2 +- 11 files changed, 108 insertions(+), 8 deletions(-) rename tests/compiler/{records/nested_record_fail.leo => circuits/circuit_contains_record_fail.leo} (63%) create mode 100644 tests/compiler/records/nested_record_1_fail.leo create mode 100644 tests/compiler/records/nested_record_2_fail.leo create mode 100644 tests/compiler/records/nested_record_3_fail.leo create mode 100644 tests/compiler/records/nested_record_4_fail.leo create mode 100644 tests/expectations/compiler/circuits/circuit_contains_record_fail.out create mode 100644 tests/expectations/compiler/records/nested_record_1_fail.out create mode 100644 tests/expectations/compiler/records/nested_record_2_fail.out create mode 100644 tests/expectations/compiler/records/nested_record_3_fail.out create mode 100644 tests/expectations/compiler/records/nested_record_4_fail.out diff --git a/tests/compiler/records/nested_record_fail.leo b/tests/compiler/circuits/circuit_contains_record_fail.leo similarity index 63% rename from tests/compiler/records/nested_record_fail.leo rename to tests/compiler/circuits/circuit_contains_record_fail.leo index 09fa94bae0..bc5d58b545 100644 --- a/tests/compiler/records/nested_record_fail.leo +++ b/tests/compiler/circuits/circuit_contains_record_fail.leo @@ -3,13 +3,9 @@ namespace: Compile expectation: Fail */ -record Foo { - // The token owner. - owner: address, - // The Aleo balance (in gates). - gates: u64, +circuit Foo { // The token amount. - amount: u64, + token: Token, } record Token { @@ -19,4 +15,4 @@ record Token { gates: u64, // The token amount. foo: Foo, -} \ No newline at end of file +} diff --git a/tests/compiler/records/nested_record_1_fail.leo b/tests/compiler/records/nested_record_1_fail.leo new file mode 100644 index 0000000000..fa0f4d97d9 --- /dev/null +++ b/tests/compiler/records/nested_record_1_fail.leo @@ -0,0 +1,16 @@ +/* +namespace: Compile +expectation: Fail +*/ + +record Foo { + owner: address, + gates: u64, + amount: u64, +} + +record Token { + owner: address, + gates: u64, + foo: Foo, +} diff --git a/tests/compiler/records/nested_record_2_fail.leo b/tests/compiler/records/nested_record_2_fail.leo new file mode 100644 index 0000000000..fd421bf7bf --- /dev/null +++ b/tests/compiler/records/nested_record_2_fail.leo @@ -0,0 +1,17 @@ +/* +namespace: Compile +expectation: Fail +*/ + + +record Token2 { + owner: address, + gates: u64, + foo: (Foo, Foo), +} + +record Foo { + owner: address, + gates: u64, + amount: u64, +} diff --git a/tests/compiler/records/nested_record_3_fail.leo b/tests/compiler/records/nested_record_3_fail.leo new file mode 100644 index 0000000000..1446830a7d --- /dev/null +++ b/tests/compiler/records/nested_record_3_fail.leo @@ -0,0 +1,21 @@ +/* +namespace: Compile +expectation: Fail +*/ + +record Token { + owner: address, + gates: u64, + bar: Bar, +} + + +circuit Bar { + bar: Foo, +} + +record Foo { + owner: address, + gates: u64, + amount: u64, +} diff --git a/tests/compiler/records/nested_record_4_fail.leo b/tests/compiler/records/nested_record_4_fail.leo new file mode 100644 index 0000000000..535cdbbc5e --- /dev/null +++ b/tests/compiler/records/nested_record_4_fail.leo @@ -0,0 +1,25 @@ +/* +namespace: Compile +expectation: Fail +*/ + +record Token3 { + owner: address, + gates: u64, + bar: (Bar, Bar), +} + +circuit Bar { + bar: (Token, Token), +} + +record Token { + owner: address, + gates: u64, + amount: u64, +} + + + + + diff --git a/tests/expectations/compiler/circuits/circuit_contains_record_fail.out b/tests/expectations/compiler/circuits/circuit_contains_record_fail.out new file mode 100644 index 0000000000..872dc2d75a --- /dev/null +++ b/tests/expectations/compiler/circuits/circuit_contains_record_fail.out @@ -0,0 +1,5 @@ +--- +namespace: Compile +expectation: Fail +outputs: + - "Error [ETYC0372030]: A circuit or record cannot contain another record.\n --> compiler-test:5:5\n |\n 5 | token: Token,\n | ^^^^^\n |\n = Remove the record `Token` from `Foo`.\n" diff --git a/tests/expectations/compiler/records/nested_record_1_fail.out b/tests/expectations/compiler/records/nested_record_1_fail.out new file mode 100644 index 0000000000..af467f7ddb --- /dev/null +++ b/tests/expectations/compiler/records/nested_record_1_fail.out @@ -0,0 +1,5 @@ +--- +namespace: Compile +expectation: Fail +outputs: + - "Error [ETYC0372030]: A circuit or record cannot contain another record.\n --> compiler-test:12:5\n |\n 12 | foo: Foo,\n | ^^^\n |\n = Remove the record `Foo` from `Token`.\n" diff --git a/tests/expectations/compiler/records/nested_record_2_fail.out b/tests/expectations/compiler/records/nested_record_2_fail.out new file mode 100644 index 0000000000..6ea3c2ee89 --- /dev/null +++ b/tests/expectations/compiler/records/nested_record_2_fail.out @@ -0,0 +1,5 @@ +--- +namespace: Compile +expectation: Fail +outputs: + - "Error [ETYC0372025]: Tuples are only allowed as function return types.\n --> compiler-test:7:5\n |\n 7 | foo: (Foo, Foo),\n | ^^^\nError [ETYC0372030]: A circuit or record cannot contain another record.\n --> compiler-test:7:5\n |\n 7 | foo: (Foo, Foo),\n | ^^^\n |\n = Remove the record `Foo` from `Token2`.\nError [ETYC0372030]: A circuit or record cannot contain another record.\n --> compiler-test:7:5\n |\n 7 | foo: (Foo, Foo),\n | ^^^\n |\n = Remove the record `Foo` from `Token2`.\n" diff --git a/tests/expectations/compiler/records/nested_record_3_fail.out b/tests/expectations/compiler/records/nested_record_3_fail.out new file mode 100644 index 0000000000..76a19ae055 --- /dev/null +++ b/tests/expectations/compiler/records/nested_record_3_fail.out @@ -0,0 +1,5 @@ +--- +namespace: Compile +expectation: Fail +outputs: + - "Error [ETYC0372030]: A circuit or record cannot contain another record.\n --> compiler-test:11:5\n |\n 11 | bar: Foo,\n | ^^^\n |\n = Remove the record `Foo` from `Bar`.\n" diff --git a/tests/expectations/compiler/records/nested_record_4_fail.out b/tests/expectations/compiler/records/nested_record_4_fail.out new file mode 100644 index 0000000000..622fe2891f --- /dev/null +++ b/tests/expectations/compiler/records/nested_record_4_fail.out @@ -0,0 +1,5 @@ +--- +namespace: Compile +expectation: Fail +outputs: + - "Error [ETYC0372025]: Tuples are only allowed as function return types.\n --> compiler-test:6:5\n |\n 6 | bar: (Bar, Bar),\n | ^^^\nError [ETYC0372025]: Tuples are only allowed as function return types.\n --> compiler-test:10:5\n |\n 10 | bar: (Token, Token),\n | ^^^\nError [ETYC0372030]: A circuit or record cannot contain another record.\n --> compiler-test:10:5\n |\n 10 | bar: (Token, Token),\n | ^^^\n |\n = Remove the record `Token` from `Bar`.\nError [ETYC0372030]: A circuit or record cannot contain another record.\n --> compiler-test:10:5\n |\n 10 | bar: (Token, Token),\n | ^^^\n |\n = Remove the record `Token` from `Bar`.\n" diff --git a/tests/expectations/compiler/records/nested_record_fail.out b/tests/expectations/compiler/records/nested_record_fail.out index e2bee6da1f..b3b2f4ef67 100644 --- a/tests/expectations/compiler/records/nested_record_fail.out +++ b/tests/expectations/compiler/records/nested_record_fail.out @@ -2,4 +2,4 @@ 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" + - "Error [EPAR0370005]: expected 'function', 'circuit', 'test' -- found 'interface'\n --> compiler-test:21:1\n |\n 21 | interface Bar {\n | ^^^^^^^^^" From f6ba7067e1812eb5427fb936b3c1ae7170e6cbf2 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu Date: Thu, 11 Aug 2022 08:55:28 -0700 Subject: [PATCH 4/4] Fix tyc for member records --- .../passes/src/type_checking/check_program.rs | 21 ++++------------ compiler/passes/src/type_checking/checker.rs | 25 +++++++++++++++++++ .../errors/type_checker/type_checker_error.rs | 8 +++--- 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/compiler/passes/src/type_checking/check_program.rs b/compiler/passes/src/type_checking/check_program.rs index da1636252c..351edae244 100644 --- a/compiler/passes/src/type_checking/check_program.rs +++ b/compiler/passes/src/type_checking/check_program.rs @@ -24,6 +24,8 @@ use leo_span::sym; use std::cell::RefCell; use std::collections::HashSet; +// TODO: Generally, cleanup tyc logic. + impl<'a> ProgramVisitor<'a> for TypeChecker<'a> { fn visit_function(&mut self, input: &'a Function) { // Check that the function's annotations are valid. @@ -121,26 +123,13 @@ 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. for CircuitMember::CircuitVariable(v, type_) in input.members.iter() { + // Ensure there are no tuple typed members. self.assert_not_tuple(v.span, type_); + // Ensure that there are no record members. + self.assert_member_is_not_record(v.span, input.identifier.name, type_); } } } diff --git a/compiler/passes/src/type_checking/checker.rs b/compiler/passes/src/type_checking/checker.rs index ae9b71be6e..f014a49aa4 100644 --- a/compiler/passes/src/type_checking/checker.rs +++ b/compiler/passes/src/type_checking/checker.rs @@ -317,6 +317,31 @@ impl<'a> TypeChecker<'a> { self.emit_err(TypeCheckerError::tuple_not_allowed(span)) } } + + /// Emits an error if the circuit member is a record type. + pub(crate) fn assert_member_is_not_record(&self, span: Span, parent: Symbol, type_: &Type) { + match type_ { + Type::Identifier(identifier) + if self + .symbol_table + .borrow() + .lookup_circuit(identifier.name) + .map_or(false, |circuit| circuit.is_record) => + { + self.emit_err(TypeCheckerError::circuit_or_record_cannot_contain_record( + parent, + identifier.name, + span, + )) + } + Type::Tuple(tuple_type) => { + for type_ in tuple_type.iter() { + self.assert_member_is_not_record(span, parent, type_) + } + } + _ => {} // Do nothing. + } + } } fn types_to_string(types: &[Type]) -> String { diff --git a/errors/src/errors/type_checker/type_checker_error.rs b/errors/src/errors/type_checker/type_checker_error.rs index be93d9dff3..4e72f8f4d4 100644 --- a/errors/src/errors/type_checker/type_checker_error.rs +++ b/errors/src/errors/type_checker/type_checker_error.rs @@ -290,9 +290,9 @@ create_messages!( } @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}`.")), + circuit_or_record_cannot_contain_record { + args: (parent: impl Display, child: impl Display), + msg: format!("A circuit or record cannot contain another record."), + help: Some(format!("Remove the record `{child}` from `{parent}`.")), } );