Merge pull request #348 from AleoHQ/feature/mutable-self-circuit-values

Feature/mutable self circuit values
This commit is contained in:
Howard Wu 2020-09-10 16:20:16 -07:00 committed by GitHub
commit 89dbe6bcf5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 348 additions and 49 deletions

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::{access::AssigneeAccess, ast::Rule, common::Identifier, SpanDef};
use crate::{access::AssigneeAccess, ast::Rule, common::KeywordOrIdentifier, SpanDef};
use pest::Span;
use pest_ast::FromPest;
@ -24,7 +24,7 @@ use std::fmt;
#[derive(Clone, Debug, FromPest, PartialEq, Serialize)]
#[pest_ast(rule(Rule::assignee))]
pub struct Assignee<'ast> {
pub identifier: Identifier<'ast>,
pub name: KeywordOrIdentifier<'ast>,
pub accesses: Vec<AssigneeAccess<'ast>>,
#[pest_ast(outer())]
#[serde(with = "SpanDef")]
@ -33,7 +33,7 @@ pub struct Assignee<'ast> {
impl<'ast> fmt::Display for Assignee<'ast> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.identifier)?;
write!(f, "{}", self.name)?;
for (i, access) in self.accesses.iter().enumerate() {
write!(f, "{}", access)?;
if i < self.accesses.len() - 1 {

View File

@ -0,0 +1,43 @@
// Copyright (C) 2019-2020 Aleo Systems Inc.
// This file is part of the Leo library.
// The Leo library is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// The Leo library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::{
ast::Rule,
common::{Identifier, SelfKeyword},
functions::InputKeyword,
};
use pest_ast::FromPest;
use serde::Serialize;
use std::fmt;
#[derive(Clone, Debug, FromPest, PartialEq, Serialize)]
#[pest_ast(rule(Rule::keyword_or_identifier))]
pub enum KeywordOrIdentifier<'ast> {
SelfKeyword(SelfKeyword<'ast>),
Input(InputKeyword<'ast>),
Identifier(Identifier<'ast>),
}
impl<'ast> fmt::Display for KeywordOrIdentifier<'ast> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
KeywordOrIdentifier::SelfKeyword(self_keyword) => write!(f, "{}", self_keyword),
KeywordOrIdentifier::Input(input_keyword) => write!(f, "{}", input_keyword),
KeywordOrIdentifier::Identifier(identifier) => write!(f, "{}", identifier),
}
}
}

View File

@ -26,6 +26,9 @@ pub use eoi::*;
pub mod identifier;
pub use identifier::*;
pub mod keyword_or_identifier;
pub use keyword_or_identifier::*;
pub mod line_end;
pub use line_end::*;

View File

@ -22,6 +22,7 @@ use crate::{
use pest::Span;
use pest_ast::FromPest;
use serde::Serialize;
use std::fmt;
#[derive(Clone, Debug, FromPest, PartialEq, Serialize)]
#[pest_ast(rule(Rule::self_keyword))]
@ -32,3 +33,9 @@ pub struct SelfKeyword<'ast> {
#[serde(with = "SpanDef")]
pub span: Span<'ast>,
}
impl<'ast> fmt::Display for SelfKeyword<'ast> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.keyword)
}
}

View File

@ -14,13 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::{
access::Access,
ast::Rule,
common::{Identifier, SelfKeyword},
functions::InputKeyword,
SpanDef,
};
use crate::{access::Access, ast::Rule, common::KeywordOrIdentifier, SpanDef};
use pest::Span;
use pest_ast::FromPest;
@ -35,11 +29,3 @@ pub struct PostfixExpression<'ast> {
#[serde(with = "SpanDef")]
pub span: Span<'ast>,
}
#[derive(Clone, Debug, FromPest, PartialEq, Serialize)]
#[pest_ast(rule(Rule::keyword_or_identifier))]
pub enum KeywordOrIdentifier<'ast> {
SelfKeyword(SelfKeyword<'ast>),
Input(InputKeyword<'ast>),
Identifier(Identifier<'ast>),
}

View File

@ -22,6 +22,7 @@ use crate::{
use pest::Span;
use pest_ast::FromPest;
use serde::Serialize;
use std::fmt;
#[derive(Clone, Debug, FromPest, PartialEq, Serialize)]
#[pest_ast(rule(Rule::input_keyword))]
@ -32,3 +33,9 @@ pub struct InputKeyword<'ast> {
#[serde(with = "SpanDef")]
pub span: Span<'ast>,
}
impl<'ast> fmt::Display for InputKeyword<'ast> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.keyword)
}
}

View File

@ -1,7 +1,7 @@
/// Common
// Declared in common/assignee.rs
assignee = { identifier ~ access_assignee* }
assignee = { keyword_or_identifier ~ access_assignee* }
// Declared in files/file.rs
file = { SOI ~ NEWLINE* ~ definition* ~ NEWLINE* ~ EOI }

View File

@ -16,7 +16,7 @@
//! Enforce a function call expression in a compiled Leo program.
use crate::{errors::ExpressionError, program::ConstrainedProgram, value::ConstrainedValue, GroupType};
use crate::{errors::ExpressionError, new_scope, program::ConstrainedProgram, value::ConstrainedValue, GroupType};
use leo_typed::{Expression, Span, Type};
use snarkos_models::{
@ -35,13 +35,32 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
arguments: Vec<Expression>,
span: Span,
) -> Result<ConstrainedValue<F, G>, ExpressionError> {
let function_value = self.enforce_expression(
cs,
file_scope.clone(),
function_scope.clone(),
expected_type,
*function.clone(),
)?;
let (declared_circuit_reference, function_value) = match *function.clone() {
Expression::CircuitMemberAccess(circuit_identifier, circuit_member, span) => {
// Call a circuit function that can mutate self.
// Save a reference to the circuit we are mutating.
let circuit_id_string = format!("{}", circuit_identifier);
let declared_circuit_reference = new_scope(function_scope.clone(), circuit_id_string);
(
declared_circuit_reference,
self.enforce_circuit_access(
cs,
file_scope.clone(),
function_scope.clone(),
expected_type,
circuit_identifier,
circuit_member,
span,
)?,
)
}
function => (
function_scope.clone(),
self.enforce_expression(cs, file_scope.clone(), function_scope.clone(), expected_type, function)?,
),
};
let (outer_scope, function_call) = function_value.extract_function(file_scope.clone(), span.clone())?;
@ -58,6 +77,7 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
function_scope,
function_call,
arguments,
declared_circuit_reference,
)
.map_err(|error| ExpressionError::from(Box::new(error)))
}

View File

@ -47,6 +47,7 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
caller_scope: String,
function: Function,
input: Vec<Expression>,
declared_circuit_reference: String,
) -> Result<ConstrainedValue<F, G>, FunctionError> {
let function_name = new_scope(scope.clone(), function.get_name());
@ -103,6 +104,7 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
None,
statement.clone(),
function.returns.clone(),
declared_circuit_reference.clone(),
)?;
results.append(&mut result);

View File

@ -77,7 +77,7 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
}
let span = function.span.clone();
let result_value = self.enforce_function(cs, scope, function_name, function, input_variables)?;
let result_value = self.enforce_function(cs, scope, function_name, function, input_variables, "".to_owned())?;
let output_bytes = OutputBytes::new_from_constrained_value(registers, result_value, span)?;
Ok(output_bytes)

View File

@ -19,6 +19,7 @@
use crate::{
assignee::resolve_assignee,
errors::StatementError,
new_scope,
program::ConstrainedProgram,
value::ConstrainedValue,
GroupType,
@ -39,6 +40,7 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
cs: &mut CS,
file_scope: String,
function_scope: String,
declared_circuit_reference: String,
indicator: Option<Boolean>,
assignee: Assignee,
expression: Expression,
@ -79,8 +81,34 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
span,
),
Assignee::Tuple(_tuple, index) => self.assign_tuple(cs, indicator, variable_name, index, new_value, span),
Assignee::CircuitField(_assignee, object_name) => {
self.mutute_circuit_variable(cs, indicator, variable_name, object_name, new_value, span)
Assignee::CircuitField(assignee, circuit_variable) => {
// Mutate a circuit variable using the self keyword.
if let Assignee::Identifier(circuit_name) = *assignee {
if circuit_name.is_self() {
let self_circuit_variable_name = new_scope(circuit_name.name, circuit_variable.name.clone());
let self_variable_name = new_scope(file_scope, self_circuit_variable_name);
let value = self.mutate_circuit_variable(
cs,
indicator,
declared_circuit_reference,
circuit_variable,
new_value,
span,
)?;
self.store(self_variable_name, value);
} else {
let _value = self.mutate_circuit_variable(
cs,
indicator,
variable_name,
circuit_variable,
new_value,
span,
)?;
}
}
Ok(())
}
}
}

View File

@ -28,7 +28,7 @@ use snarkos_models::{
};
impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
pub fn mutute_circuit_variable<CS: ConstraintSystem<F>>(
pub fn mutate_circuit_variable<CS: ConstraintSystem<F>>(
&mut self,
cs: &mut CS,
indicator: Option<Boolean>,
@ -36,7 +36,7 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
variable_name: Identifier,
mut new_value: ConstrainedValue<F, G>,
span: Span,
) -> Result<(), StatementError> {
) -> Result<ConstrainedValue<F, G>, StatementError> {
let condition = indicator.unwrap_or(Boolean::Constant(true));
// Get the mutable circuit by name
@ -48,19 +48,25 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
match matched_variable {
Some(member) => match &member.1 {
ConstrainedValue::Function(_circuit_identifier, function) => {
return Err(StatementError::immutable_circuit_function(
// Throw an error if we try to mutate a circuit function
Err(StatementError::immutable_circuit_function(
function.identifier.to_string(),
span,
));
))
}
ConstrainedValue::Static(_value) => {
return Err(StatementError::immutable_circuit_function("static".into(), span));
ConstrainedValue::Static(_circuit_function) => {
// Throw an error if we try to mutate a static circuit function
Err(StatementError::immutable_circuit_function("static".into(), span))
}
ConstrainedValue::Mutable(value) => {
// Mutate the circuit variable's value in place
// Check that the new value type == old value type
new_value.resolve_type(Some(value.to_type(span.clone())?), span.clone())?;
// Conditionally select the value if this branch is executed.
let name_unique = format!("select {} {}:{}", new_value, span.line, span.start);
let selected_value = ConstrainedValue::conditionally_select(
let mut selected_value = ConstrainedValue::conditionally_select(
cs.ns(|| name_unique),
&condition,
&new_value,
@ -70,23 +76,29 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
StatementError::select_fail(new_value.to_string(), member.1.to_string(), span)
})?;
// Make sure the new value is still mutable
selected_value = ConstrainedValue::Mutable(Box::new(selected_value));
member.1 = selected_value.to_owned();
Ok(selected_value.to_owned())
}
_ => {
return Err(StatementError::immutable_circuit_variable(variable_name.name, span));
// Throw an error if we try to mutate an immutable circuit variable
Err(StatementError::immutable_circuit_variable(variable_name.name, span))
}
},
None => {
return Err(StatementError::undefined_circuit_variable(
// Throw an error if the circuit variable does not exist in the circuit
Err(StatementError::undefined_circuit_variable(
variable_name.to_string(),
span,
));
))
}
}
}
_ => return Err(StatementError::undefined_circuit(variable_name.to_string(), span)),
// Throw an error if the circuit definition does not exist in the file
_ => Err(StatementError::undefined_circuit(variable_name.to_string(), span)),
}
Ok(())
}
}

View File

@ -44,6 +44,7 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
indicator.clone(),
statement.clone(),
return_type.clone(),
"".to_owned(),
)?;
results.append(&mut value);

View File

@ -38,6 +38,7 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
indicator: Option<Boolean>,
statement: Statement,
return_type: Option<Type>,
declared_circuit_reference: String,
) -> Result<Vec<(Option<Boolean>, ConstrainedValue<F, G>)>, StatementError> {
let mut results = vec![];
@ -62,7 +63,16 @@ impl<F: Field + PrimeField, G: GroupType<F>> ConstrainedProgram<F, G> {
)?;
}
Statement::Assign(variable, expression, span) => {
self.enforce_assign_statement(cs, file_scope, function_scope, indicator, variable, expression, span)?;
self.enforce_assign_statement(
cs,
file_scope,
function_scope,
declared_circuit_reference,
indicator,
variable,
expression,
span,
)?;
}
Statement::Conditional(statement, span) => {
let mut result = self.enforce_conditional_statement(

View File

@ -134,6 +134,71 @@ fn test_member_static_function_undefined() {
expect_fail(program)
}
// Mutability
#[test]
fn test_mutate_function_fail() {
let bytes = include_bytes!("mut_function_fail.leo");
let program = parse_program(bytes).unwrap();
expect_compiler_error(program);
}
#[test]
fn test_mutate_self_variable() {
let bytes = include_bytes!("mut_self_variable.leo");
let program = parse_program(bytes).unwrap();
assert_satisfied(program);
}
#[test]
fn test_mutate_self_variable_fail() {
let bytes = include_bytes!("mut_self_variable_fail.leo");
let program = parse_program(bytes).unwrap();
expect_compiler_error(program);
}
#[test]
fn test_mutate_self_function_fail() {
let bytes = include_bytes!("mut_self_function_fail.leo");
let program = parse_program(bytes).unwrap();
expect_compiler_error(program);
}
#[test]
fn test_mutate_self_static_function_fail() {
let bytes = include_bytes!("mut_self_static_function_fail.leo");
let program = parse_program(bytes).unwrap();
expect_compiler_error(program);
}
#[test]
fn test_mutate_static_function_fail() {
let bytes = include_bytes!("mut_static_function_fail.leo");
let program = parse_program(bytes).unwrap();
expect_compiler_error(program);
}
#[test]
fn test_mutate_variable() {
let bytes = include_bytes!("mut_variable.leo");
let program = parse_program(bytes).unwrap();
assert_satisfied(program);
}
#[test]
fn test_mutate_variable_fail() {
let bytes = include_bytes!("mut_variable_fail.leo");
let program = parse_program(bytes).unwrap();
expect_compiler_error(program);
}
// Self
#[test]
fn test_self_member_pass() {

View File

@ -0,0 +1,9 @@
circuit Foo {
function bar() {}
}
function main() {
let mut f = Foo { a: 0u8 };
f.bar = 1u8;
}

View File

@ -0,0 +1,15 @@
circuit Foo {
a: u8,
function bar() {}
function set_a(new: u8) {
self.bar = new;
}
}
function main() {
let mut f = Foo { a: 0u8 };
f.set_a(1u8);
}

View File

@ -0,0 +1,15 @@
circuit Foo {
a: u8,
static function bar() {}
function set_a(new: u8) {
self.bar = new;
}
}
function main() {
let mut f = Foo { a: 0u8 };
f.set_a(1u8);
}

View File

@ -0,0 +1,22 @@
circuit Foo {
mut a: u8,
function set_a(new: u8) {
self.a = new;
console.assert(self.a == new);
}
}
function main() {
let mut f = Foo { a: 0u8 };
console.assert(f.a == 0u8);
f.set_a(1u8);
console.assert(f.a == 1u8);
f.set_a(2u8);
console.assert(f.a == 2u8);
}

View File

@ -0,0 +1,13 @@
circuit Foo {
a: u8,
function set_a(new: u8) {
self.a = new;
}
}
function main() {
let mut f = Foo { a: 0u8 };
f.set_a(1u8);
}

View File

@ -0,0 +1,9 @@
circuit Foo {
static function bar() {}
}
function main() {
let mut f = Foo { a: 0u8 };
f.bar = 1u8;
}

View File

@ -0,0 +1,17 @@
circuit Foo {
mut a: u8,
}
function main() {
let mut f = Foo { a: 0u8 };
console.assert(f.a == 0u8);
f.a = 1u8;
console.assert(f.a == 1u8);
f.a = 2u8;
console.assert(f.a == 2u8);
}

View File

@ -0,0 +1,9 @@
circuit Foo {
a: u8,
}
function main() {
let mut f = Foo { a: 0u8 };
f.a = 1u8;
}

View File

@ -17,7 +17,7 @@
use crate::{Expression, Identifier, RangeOrExpression};
use leo_ast::{
access::AssigneeAccess as AstAssigneeAccess,
common::{Assignee as AstAssignee, Identifier as AstIdentifier},
common::{Assignee as AstAssignee, Identifier as AstIdentifier, KeywordOrIdentifier},
};
use serde::{Deserialize, Serialize};
@ -38,9 +38,15 @@ impl<'ast> From<AstIdentifier<'ast>> for Assignee {
}
}
impl<'ast> From<KeywordOrIdentifier<'ast>> for Assignee {
fn from(name: KeywordOrIdentifier<'ast>) -> Self {
Assignee::Identifier(Identifier::from(name))
}
}
impl<'ast> From<AstAssignee<'ast>> for Assignee {
fn from(assignee: AstAssignee<'ast>) -> Self {
let variable = Assignee::from(assignee.identifier);
let variable = Assignee::from(assignee.name);
// We start with the id, and we fold the array of accesses by wrapping the current value
assignee

View File

@ -23,8 +23,8 @@ use leo_ast::{
use leo_input::common::Identifier as InputAstIdentifier;
use leo_ast::{
common::SelfKeyword,
expressions::{CircuitName, KeywordOrIdentifier},
common::{KeywordOrIdentifier, SelfKeyword},
expressions::CircuitName,
functions::InputKeyword,
types::SelfType,
};

View File

@ -362,7 +362,7 @@ impl<'ast> From<AstExpression<'ast>> for Expression {
// Assignee -> Expression for operator assign statements
impl<'ast> From<Assignee<'ast>> for Expression {
fn from(assignee: Assignee<'ast>) -> Self {
let variable = Expression::Identifier(Identifier::from(assignee.identifier));
let variable = Expression::Identifier(Identifier::from(assignee.name));
// we start with the id, and we fold the array of accesses by wrapping the current value
assignee