Skip to content

Commit

Permalink
feat(fv): Add the implication operator
Browse files Browse the repository at this point in the history
For writing more readable proofs we need the implication operator.
With this commit we now add it. The user can write x ==> y where x and y
are boolean statements.

We are transforming the implication operator into !x | y which is
logically equivalent to x ==> y.

The implication operator can be used both in attributes and function's
bodies.

Added tests which showcase the new feature.
  • Loading branch information
Aristotelis2002 committed Dec 20, 2024
1 parent a8f08d3 commit a73cd6a
Show file tree
Hide file tree
Showing 16 changed files with 93 additions and 9 deletions.
1 change: 1 addition & 0 deletions compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,7 @@ fn convert_operator(op: BinaryOpKind) -> BinaryOp {
BinaryOpKind::Xor => BinaryOp::Xor,
BinaryOpKind::ShiftLeft => BinaryOp::Shl,
BinaryOpKind::ShiftRight => BinaryOp::Shr,
BinaryOpKind::Implication => unreachable!("No implication token should have remained."),
}
}

Expand Down
4 changes: 4 additions & 0 deletions compiler/noirc_frontend/src/ast/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ pub enum BinaryOpKind {
ShiftRight,
ShiftLeft,
Modulo,
Implication,
}

impl BinaryOpKind {
Expand Down Expand Up @@ -374,6 +375,7 @@ impl BinaryOpKind {
BinaryOpKind::ShiftRight => ">>",
BinaryOpKind::ShiftLeft => "<<",
BinaryOpKind::Modulo => "%",
BinaryOpKind::Implication => "==>",
}
}

Expand All @@ -395,6 +397,7 @@ impl BinaryOpKind {
BinaryOpKind::ShiftLeft => Token::ShiftLeft,
BinaryOpKind::ShiftRight => Token::ShiftRight,
BinaryOpKind::Modulo => Token::Percent,
BinaryOpKind::Implication => Token::Implication,
}
}
}
Expand Down Expand Up @@ -770,6 +773,7 @@ impl Display for BinaryOpKind {
BinaryOpKind::ShiftLeft => write!(f, "<<"),
BinaryOpKind::ShiftRight => write!(f, ">>"),
BinaryOpKind::Modulo => write!(f, "%"),
BinaryOpKind::Implication => write!(f, "==>"),
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion compiler/noirc_frontend/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,8 @@ impl UnresolvedTypeExpression {
| BinaryOpKind::Or
| BinaryOpKind::Xor
| BinaryOpKind::ShiftRight
| BinaryOpKind::ShiftLeft => {
| BinaryOpKind::ShiftLeft
| BinaryOpKind::Implication => {
unreachable!("impossible via `operator_allowed` check")
}
};
Expand Down
3 changes: 3 additions & 0 deletions compiler/noirc_frontend/src/elaborator/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1873,6 +1873,9 @@ fn try_eval_array_length_id_with_fuel(
BinaryOpKind::ShiftRight => Ok(lhs >> rhs),
BinaryOpKind::ShiftLeft => Ok(lhs << rhs),
BinaryOpKind::Modulo => Ok(lhs % rhs),
BinaryOpKind::Implication => {
unreachable!("No implication token shoud have remained.")
}
}
}
HirExpression::Cast(cast) => {
Expand Down
1 change: 1 addition & 0 deletions compiler/noirc_frontend/src/hir/comptime/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ impl<'interner> TokenPrettyPrinter<'interner> {
Token::EOF => Ok(()),
Token::Requires => write!(f, "{token}"),
Token::Ensures => write!(f, "{token}"),
Token::Implication => write!(f, " {token} ")
}
}

Expand Down
3 changes: 3 additions & 0 deletions compiler/noirc_frontend/src/hir/comptime/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1102,6 +1102,9 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
(Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs % rhs)),
(lhs, rhs) => make_error(self, lhs, rhs, "%"),
},
BinaryOpKind::Implication => {
unreachable!("All implications must have been transformed to !lsh | rhs")
}
}
}

Expand Down
11 changes: 10 additions & 1 deletion compiler/noirc_frontend/src/lexer/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,16 @@ impl<'a> Lexer<'a> {
}
}
Token::Bang => self.single_double_peek_token('=', prev_token, Token::NotEqual),
Token::Assign => self.single_double_peek_token('=', prev_token, Token::Equal),
Token::Assign => {
if self.peek_char_is('=') && self.peek2_char_is('>') {
let start = self.position;
self.next_char();
self.next_char();
Ok(Token::Implication.into_span(start, start + 2))
} else {
self.single_double_peek_token('=', prev_token, Token::Equal)
}
}
Token::Minus => self.single_double_peek_token('>', prev_token, Token::Arrow),
Token::Colon => self.single_double_peek_token(':', prev_token, Token::DoubleColon),
Token::Slash => {
Expand Down
7 changes: 7 additions & 0 deletions compiler/noirc_frontend/src/lexer/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ pub enum BorrowedToken<'input> {
Requires,
/// #[ensures
Ensures,
/// ==>
Implication,

#[allow(clippy::upper_case_acronyms)]
EOF,
Expand Down Expand Up @@ -236,6 +238,8 @@ pub enum Token {
Requires,
/// #[ensures
Ensures,
/// ==>
Implication,

#[allow(clippy::upper_case_acronyms)]
EOF,
Expand Down Expand Up @@ -317,6 +321,7 @@ pub fn token_to_borrowed_token(token: &Token) -> BorrowedToken<'_> {
Token::UnquoteMarker(id) => BorrowedToken::UnquoteMarker(*id),
Token::Requires => BorrowedToken::Requires,
Token::Ensures => BorrowedToken::Ensures,
Token::Implication => BorrowedToken::Implication,
}
}

Expand Down Expand Up @@ -450,6 +455,7 @@ impl fmt::Display for Token {
Token::UnquoteMarker(_) => write!(f, "(UnquoteMarker)"),
Token::Requires => write!(f, "#[requires"),
Token::Ensures => write!(f, "#[ensures"),
Token::Implication => write!(f, "==>"),
}
}
}
Expand Down Expand Up @@ -564,6 +570,7 @@ impl Token {
Token::Greater => Greater,
Token::GreaterEqual => GreaterEqual,
Token::Percent => Modulo,
Token::Implication => Implication,
_ => return None,
};
Some(Spanned::from(span, binary_op))
Expand Down
3 changes: 3 additions & 0 deletions compiler/noirc_frontend/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ impl SortedModule {

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
pub enum Precedence {
Implication,
Lowest,
Or,
And,
Expand All @@ -473,6 +474,7 @@ impl Precedence {
let precedence = match tok {
Token::Equal => Precedence::Lowest,
Token::NotEqual => Precedence::Lowest,
Token::Implication => Precedence::Implication,
Token::Pipe => Precedence::Or,
Token::Ampersand => Precedence::And,
Token::Caret => Precedence::Xor,
Expand All @@ -498,6 +500,7 @@ impl Precedence {
fn next(self) -> Self {
use Precedence::*;
match self {
Implication => Lowest,
Lowest => Or,
Or => Xor,
Xor => And,
Expand Down
22 changes: 15 additions & 7 deletions compiler/noirc_frontend/src/parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,7 @@ use super::{
};
use super::{spanned, Item, TopLevelStatement};
use crate::ast::{
BinaryOp, BinaryOpKind, BlockExpression, Documented, ForLoopStatement, ForRange,
GenericTypeArgs, Ident, IfExpression, InfixExpression, LValue, Literal, ModuleDeclaration,
NoirTypeAlias, Param, Path, Pattern, Recoverable, Statement, TypeImpl, UnaryRhsMemberAccess,
UnaryRhsMethodCall, UseTree, UseTreeKind, Visibility,
BinaryOp, BinaryOpKind, BlockExpression, Documented, ForLoopStatement, ForRange, GenericTypeArgs, Ident, IfExpression, InfixExpression, LValue, Literal, ModuleDeclaration, NoirTypeAlias, Param, Path, Pattern, PrefixExpression, Recoverable, Statement, TypeImpl, UnaryOp, UnaryRhsMemberAccess, UnaryRhsMethodCall, UseTree, UseTreeKind, Visibility
};
use crate::ast::{
Expression, ExpressionKind, LetStatement, StatementKind, UnresolvedType, UnresolvedTypeData,
Expand Down Expand Up @@ -731,7 +728,7 @@ fn call_data() -> impl NoirParser<Visibility> {
pub fn expression() -> impl ExprParser {
recursive(|expr| {
expression_with_precedence(
Precedence::Lowest,
Precedence::Implication,
expr.clone(),
expression_no_constructors(expr.clone()),
statement(expr.clone(), expression_no_constructors(expr)),
Expand All @@ -748,7 +745,7 @@ where
{
recursive(|expr_no_constructors| {
expression_with_precedence(
Precedence::Lowest,
Precedence::Implication,
expr_parser.clone(),
expr_no_constructors.clone(),
statement(expr_parser, expr_no_constructors),
Expand Down Expand Up @@ -823,7 +820,18 @@ where

fn create_infix_expression(lhs: Expression, (operator, rhs): (BinaryOp, Expression)) -> Expression {
let span = lhs.span.merge(rhs.span);
let infix = Box::new(InfixExpression { lhs, operator, rhs });

// If we have an implication (lhs ==> rhs) we want to transform it to (!lhs | rhs)
let infix = if operator.contents == BinaryOpKind::Implication {
let new_lhs = Expression::new(
ExpressionKind::Prefix(Box::new(PrefixExpression { operator: UnaryOp::Not, rhs: lhs.clone() })),
lhs.span,
);
let new_operator = Spanned::from(operator.span(), BinaryOpKind::Or);
Box::new(InfixExpression { lhs: new_lhs, operator: new_operator, rhs })
} else {
Box::new(InfixExpression { lhs, operator, rhs })
};

Expression { span, kind: ExpressionKind::Infix(infix) }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "implication_operator"
type = "bin"
authors = [""]
compiler_version = ">=0.35.0"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// This test is expected to fail because if y is true, main returns x, not 0.
#[ensures((y ==> result == 0))]
fn main(x: u32, y: bool) -> pub u32 {
if y {
x
} else {
0
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "implication_operator"
type = "bin"
authors = [""]
compiler_version = ">=0.35.0"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#[ensures((y ==> result == x) & (!y ==> result == 0))]
fn main(x: u32, y: bool) -> pub u32 {
if y {
x
} else {
0
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "implication_operator"
type = "bin"
authors = [""]
compiler_version = ">=0.35.0"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// This test shows that you can use the implication operator not
// only in attributes but also in the function's body.
#[requires(y ==> x > 5)]
fn main(x: u32, y: bool) {
assert(y ==> x > 5);
}

0 comments on commit a73cd6a

Please sign in to comment.