fix recursive circuit member function namespace bug

This commit is contained in:
collin 2020-07-03 22:44:09 -07:00
parent 3f10bcfe82
commit 84f634559c
6 changed files with 134 additions and 82 deletions

152
README.md
View File

@ -130,14 +130,14 @@ function main() -> group {
### Operator Assignment Statements ### Operator Assignment Statements
```rust ```rust
function main() -> u32 { function main() -> u32 {
let mut a = 10; let mut a = 10;
a += 5; a += 5;
a -= 10; a -= 10;
a *= 5; a *= 5;
a /= 5; a /= 5;
a **= 2; a **= 2;
return a return a
} }
``` ```
@ -173,11 +173,11 @@ function main() -> u32[2] {
### Multidimensional Arrays ### Multidimensional Arrays
```rust ```rust
function main() -> u32[3][2] { function main() -> u32[3][2] {
let m = [[0u32, 0u32], [0u32, 0u32]]; let m = [[0u32, 0u32], [0u32, 0u32]];
let m: u32[3][2] = [[0; 3]; 2]; let m: u32[3][2] = [[0; 3]; 2];
return m return m
} }
``` ```
@ -193,8 +193,8 @@ In the underlying circuit, this is a single bit multiplexer.
```rust ```rust
function main() -> u32 { function main() -> u32 {
let y = if 3==3 ? 1 : 5; let y = if 3==3 ? 1 : 5;
return y return y
} }
``` ```
@ -204,45 +204,47 @@ Since `first` and `second` are one or more statements, they resolve to separate
In the underlying circuit this can be thought of as a demultiplexer. In the underlying circuit this can be thought of as a demultiplexer.
```rust ```rust
function main(a: bool, b: bool) -> u32 { function main(a: bool, b: bool) -> u32 {
let mut res = 0u32; let mut res = 0u32;
if a {
res = 1; if a {
} else if b { res = 1;
res = 2; } else if b {
} else { res = 2;
res = 3; } else {
} res = 3;
return res }
return res
} }
``` ```
### For loop ### For loop
```rust ```rust
function main() -> fe { function main() -> fe {
let mut a = 1field; let mut a = 1field;
for i in 0..4 { for i in 0..4 {
a = a + 1; a = a + 1;
} }
return a return a
} }
``` ```
## Functions ## Functions
```rust ```rust
function test1(a : u32) -> u32 { function test1(a : u32) -> u32 {
return a + 1 return a + 1
} }
function test2(b: fe) -> field { function test2(b: fe) -> field {
return b * 2field return b * 2field
} }
function test3(c: bool) -> bool { function test3(c: bool) -> bool {
return c && true return c && true
} }
function main() -> u32 { function main() -> u32 {
return test1(5) return test1(5)
} }
``` ```
@ -250,13 +252,13 @@ function main() -> u32 {
### Function Scope ### Function Scope
```rust ```rust
function foo() -> field { function foo() -> field {
// return myGlobal <- not allowed // return myGlobal <- not allowed
return 42field return 42field
} }
function main() -> field { function main() -> field {
let myGlobal = 42field; let myGlobal = 42field;
return foo() return foo()
} }
``` ```
@ -279,7 +281,7 @@ Main function inputs are allocated private variables in the program's constraint
`a` is implicitly private. `a` is implicitly private.
```rust ```rust
function main(a: field) -> field { function main(a: field) -> field {
return a return a
} }
``` ```
Normal function inputs are passed by value. Normal function inputs are passed by value.
@ -299,8 +301,8 @@ function main() -> u32 {
## Circuits ## Circuits
Circuits in Leo are similar to classes in object oriented langauges. Circuits are defined above functions in a Leo program. Circuits can have one or more members. Circuits in Leo are similar to classes in object oriented langauges. Circuits are defined above functions in a Leo program. Circuits can have one or more members.
#### Circuit member values
Members can be defined as fields which hold primitive values Members can be defined as fields which hold primitive values.
```rust ```rust
circuit Point { circuit Point {
x: u32 x: u32
@ -312,51 +314,73 @@ function main() -> u32 {
} }
``` ```
#### Circuit member functions
Members can also be defined as functions. Members can also be defined as functions.
```rust ```rust
circuit Circ { circuit Foo {
function echo(x: u32) -> u32 { function echo(x: u32) -> u32 {
return x return x
} }
} }
function main() -> u32 { function main() -> u32 {
let c = Circ { }; let c = Foo { };
return c.echo(1u32) return c.echo(1u32)
} }
``` ```
#### Circuit member static functions
Circuit functions can be made static, enabling them to be called without instantiation. Circuit functions can be made static, enabling them to be called without instantiation.
```rust ```rust
circuit Circ { circuit Foo {
static function echo(x: u32) -> u32 { static function echo(x: u32) -> u32 {
return x return x
} }
} }
function main() -> u32 { function main() -> u32 {
return Circ::echo(1u32) return Foo::echo(1u32)
} }
``` ```
The `Self` keyword is supported in circuit functions. #### `Self` and `self`
The `Self` keyword references the circuit definition.
```rust ```rust
circuit Circ { circuit Foo {
b: bool b: bool
static function new() -> Self { static function new() -> Self { // Self resolves to Foo
return Self { b: true } return Self { b: true }
} }
} }
function main() -> Circ { function main() -> bool {
let c = Circ::new(); let c = Foo::new();
return c.b return c.b
} }
``` ```
The `self` keyword references the circuit's members.
```rust
circuit Foo {
b: bool
function bar() -> bool {
return self.b
}
}
function main() -> bool {
let c = Foo { b: true };
return c.b
}
```
## Imports ## Imports
Leo supports importing functions and circuits by name into the current file with the following syntax: Leo supports importing functions
}
} and circuits by name into the current file with the following syntax:
```rust ```rust
import [package].[name]; import [package].[name];
@ -472,11 +496,11 @@ This will enforce that the two values are equal in the constraint system.
```rust ```rust
function main() { function main() {
assert_eq!(45, 45); assert_eq!(45, 45);
assert_eq!(2fe, 2fe); assert_eq!(2fe, 2fe);
assert_eq!(true, true); assert_eq!(true, true);
} }
``` ```
@ -490,15 +514,15 @@ function main(a: u32) -> u32 {
} }
test function expect_pass() { test function expect_pass() {
let a = 1u32; let a = 1u32;
let res = main(a); let res = main(a);
assert_eq!(res, 1u32); assert_eq!(res, 1u32);
} }
test function expect_fail() { test function expect_fail() {
assert_eq!(1u8, 0u8); assert_eq!(1u8, 0u8);
} }
``` ```

View File

@ -704,9 +704,11 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
// access a circuit member using the `self` keyword // access a circuit member using the `self` keyword
if let Expression::Identifier(ref identifier) = *circuit_identifier { if let Expression::Identifier(ref identifier) = *circuit_identifier {
if identifier.is_self() { if identifier.is_self() {
let self_keyword = new_scope(function_scope, SELF_KEYWORD.to_string()); let self_file_scope = new_scope(file_scope.clone(), identifier.name.to_string());
let self_function_scope = new_scope(self_file_scope.clone(), identifier.name.to_string());
let member_value = let member_value =
self.evaluate_identifier(file_scope, self_keyword, &vec![], circuit_member.clone())?; self.evaluate_identifier(self_file_scope, self_function_scope, &vec![], circuit_member.clone())?;
return Ok(member_value); return Ok(member_value);
} }
@ -730,20 +732,13 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
Some(member) => { Some(member) => {
match &member.1 { match &member.1 {
ConstrainedValue::Function(ref _circuit_identifier, ref _function) => { ConstrainedValue::Function(ref _circuit_identifier, ref _function) => {
// Pass static circuit fields into function call by value // Pass circuit members into function call by value
for stored_member in members { for stored_member in members {
match &stored_member.1 { let circuit_scope = new_scope(file_scope.clone(), circuit_name.to_string());
ConstrainedValue::Function(_, _) => {} let self_keyword = new_scope(circuit_scope, SELF_KEYWORD.to_string());
ConstrainedValue::Static(_) => {} let field = new_scope(self_keyword, stored_member.0.to_string());
_ => {
let circuit_scope = new_scope(file_scope.clone(), circuit_name.to_string());
let function_scope = new_scope(circuit_scope, member.0.to_string());
let self_keyword = new_scope(function_scope, SELF_KEYWORD.to_string());
let field = new_scope(self_keyword, stored_member.0.to_string());
self.store(field, stored_member.1.clone()); self.store(field, stored_member.1.clone());
}
}
} }
} }
ConstrainedValue::Static(value) => { ConstrainedValue::Static(value) => {
@ -831,7 +826,7 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
*function.clone(), *function.clone(),
)?; )?;
let (outer_scope, function_call) = function_value.extract_function(file_scope, span.clone())?; let (outer_scope, function_call) = function_value.extract_function(file_scope.clone(), span.clone())?;
let name_unique = format!( let name_unique = format!(
"function call {} {}:{}", "function call {} {}:{}",

View File

@ -14,6 +14,10 @@ pub fn new_scope(outer: String, inner: String) -> String {
format!("{}_{}", outer, inner) format!("{}_{}", outer, inner)
} }
pub fn is_in_scope(current_scope: &String, desired_scope: &String) -> bool {
current_scope.ends_with(desired_scope)
}
impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> { impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {

View File

@ -3,6 +3,7 @@
use crate::{ use crate::{
constraints::boolean::{allocate_bool, new_bool_constant}, constraints::boolean::{allocate_bool, new_bool_constant},
errors::{ExpressionError, FieldError, ValueError}, errors::{ExpressionError, FieldError, ValueError},
is_in_scope,
new_scope, new_scope,
FieldType, FieldType,
GroupType, GroupType,
@ -122,8 +123,11 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedValue<F, G> {
ConstrainedValue::Function(circuit_identifier, function) => { ConstrainedValue::Function(circuit_identifier, function) => {
let mut outer_scope = scope.clone(); let mut outer_scope = scope.clone();
// If this is a circuit function, evaluate inside the circuit scope // If this is a circuit function, evaluate inside the circuit scope
if circuit_identifier.is_some() { if let Some(identifier) = circuit_identifier {
outer_scope = new_scope(scope, circuit_identifier.unwrap().to_string()); // avoid creating recursive scope
if !is_in_scope(&scope, &identifier.name.to_string()) {
outer_scope = new_scope(scope, identifier.name.to_string());
}
} }
Ok((outer_scope, function.clone())) Ok((outer_scope, function.clone()))

View File

@ -0,0 +1,17 @@
circuit Foo {
x: u32,
function add_x(y: u32) -> u32 {
return self.x + y
}
function call_add_x(y: u32) -> u32 {
return self.add_x(y)
}
}
function main() -> u32 {
let f = Foo { x: 1u32 };
return f.call_add_x(1u32)
}

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
get_error, get_error,
get_output, get_output,
integers::u32::output_one, integers::u32::{output_number, output_one},
parse_program, parse_program,
EdwardsConstrainedValue, EdwardsConstrainedValue,
EdwardsTestCompiler, EdwardsTestCompiler,
@ -138,6 +138,14 @@ fn test_member_function_invalid() {
expect_fail(program); expect_fail(program);
} }
#[test]
fn test_member_function_nested() {
let bytes = include_bytes!("member_function_nested.leo");
let program = parse_program(bytes).unwrap();
output_number(program, 2u32);
}
#[test] #[test]
fn test_member_static_function() { fn test_member_static_function() {
let bytes = include_bytes!("member_static_function.leo"); let bytes = include_bytes!("member_static_function.leo");