Skip to content

Commit

Permalink
Add support for numeric invert operator
Browse files Browse the repository at this point in the history
Fixes #521
  • Loading branch information
cburgdorf committed Aug 31, 2021
1 parent f1b8f9c commit 9e39c21
Show file tree
Hide file tree
Showing 23 changed files with 145 additions and 11 deletions.
10 changes: 8 additions & 2 deletions crates/analyzer/src/traversal/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -666,8 +666,14 @@ fn expr_unary_operation(
))
}
UnaryOperator::Invert => {
scope.not_yet_implemented("unary invert", exp.span);
Ok(ExpressionAttributes::new(Type::unit(), Location::Value))
if !matches!(operand_attributes.typ, Type::Base(Base::Numeric(_))) {
emit_err(scope, "a numeric type")
}

Ok(ExpressionAttributes::new(
operand_attributes.typ,
Location::Value,
))
}
};
}
Expand Down
1 change: 1 addition & 0 deletions crates/analyzer/tests/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ test_stmt! { undefined_type, "let x: foobar = 10" }
test_stmt! { unexpected_return, "return 1" }
test_stmt! { revert_reason_not_struct, "revert 1" }
test_stmt! { invalid_ascii, "String<2>(\"ä\")" }
test_stmt! { invert_non_numeric, "~true" }

test_file! { bad_tuple_attr1 }
test_file! { bad_tuple_attr2 }
Expand Down
12 changes: 12 additions & 0 deletions crates/analyzer/tests/snapshots/errors__invert_non_numeric.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
source: crates/analyzer/tests/errors.rs
expression: "error_string(\"[snippet]\", &src)"

---
error: cannot apply unary operator `~` to type `bool`
┌─ [snippet]:3:4
3~true
^^^^ this has type `bool`; expected a numeric type


3 changes: 3 additions & 0 deletions crates/test-files/fixtures/features/return_invert_i128.fe
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
contract Foo:
pub fn bar(x: i128) -> i128:
return ~x
3 changes: 3 additions & 0 deletions crates/test-files/fixtures/features/return_invert_i16.fe
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
contract Foo:
pub fn bar(x: i16) -> i16:
return ~x
3 changes: 3 additions & 0 deletions crates/test-files/fixtures/features/return_invert_i256.fe
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
contract Foo:
pub fn bar(x: i256) -> i256:
return ~x
3 changes: 3 additions & 0 deletions crates/test-files/fixtures/features/return_invert_i32.fe
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
contract Foo:
pub fn bar(x: i32) -> i32:
return ~x
3 changes: 3 additions & 0 deletions crates/test-files/fixtures/features/return_invert_i64.fe
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
contract Foo:
pub fn bar(x: i64) -> i64:
return ~x
3 changes: 3 additions & 0 deletions crates/test-files/fixtures/features/return_invert_i8.fe
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
contract Foo:
pub fn bar(x: i8) -> i8:
return ~x
3 changes: 3 additions & 0 deletions crates/test-files/fixtures/features/return_invert_u128.fe
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
contract Foo:
pub fn bar(x: u128) -> u128:
return ~x
3 changes: 3 additions & 0 deletions crates/test-files/fixtures/features/return_invert_u16.fe
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
contract Foo:
pub fn bar(x: u16) -> u16:
return ~x
3 changes: 3 additions & 0 deletions crates/test-files/fixtures/features/return_invert_u256.fe
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
contract Foo:
pub fn bar(x: u256) -> u256:
return ~x
3 changes: 3 additions & 0 deletions crates/test-files/fixtures/features/return_invert_u32.fe
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
contract Foo:
pub fn bar(x: u32) -> u32:
return ~x
3 changes: 3 additions & 0 deletions crates/test-files/fixtures/features/return_invert_u64.fe
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
contract Foo:
pub fn bar(x: u64) -> u64:
return ~x
3 changes: 3 additions & 0 deletions crates/test-files/fixtures/features/return_invert_u8.fe
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
contract Foo:
pub fn bar(x: u8) -> u8:
return ~x
13 changes: 13 additions & 0 deletions crates/tests/src/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,19 @@ fn test_assert() {
case("return_msg_sig.fe", &[], uint_token(4273672062)),
case("return_sum_list_expression_1.fe", &[], uint_token(210)),
case("return_sum_list_expression_2.fe", &[], uint_token(210)),
// unary invert
case("return_invert_i256.fe", &[int_token(1)], int_token(-2)),
case("return_invert_i128.fe", &[int_token(1)], int_token(-2)),
case("return_invert_i64.fe", &[int_token(4000000000)], int_token(-4000000001)),
case("return_invert_i32.fe", &[int_token(30000)], int_token(-30001)),
case("return_invert_i16.fe", &[int_token(2000)], int_token(-2001)),
case("return_invert_i8.fe", &[int_token(1)], int_token(-2)),
case("return_invert_u256.fe", &[uint_token(1)], uint_token_from_dec_str("115792089237316195423570985008687907853269984665640564039457584007913129639934")),
case("return_invert_u128.fe", &[uint_token(1)], uint_token_from_dec_str("340282366920938463463374607431768211454")),
case("return_invert_u64.fe", &[uint_token(1)], uint_token_from_dec_str("18446744073709551614")),
case("return_invert_u32.fe", &[uint_token(1)], uint_token(4294967294)),
case("return_invert_u16.fe", &[uint_token(1)], uint_token(65534)),
case("return_invert_u8.fe", &[uint_token(1)], uint_token(254)),
// binary operators
case("return_addition_u256.fe", &[uint_token(42), uint_token(42)], uint_token(84)),
case("return_addition_i256.fe", &[int_token(-42), int_token(-42)], int_token(-84)),
Expand Down
14 changes: 12 additions & 2 deletions crates/yulgen/src/mappers/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::context::FnContext;
use crate::names;
use crate::operations::{
abi as abi_operations, contracts as contract_operations, data as data_operations,
structs as struct_operations,
math as math_operations, structs as struct_operations,
};
use crate::types::AsAbiType;
use crate::types::EvmSized;
Expand Down Expand Up @@ -272,13 +272,23 @@ pub fn expr_unary_operation(context: &mut FnContext, exp: &Node<fe::Expr>) -> yu
if let fe::Expr::UnaryOperation { op, operand } = &exp.kind {
let yul_operand = expr(context, operand);

let typ = &context
.expression_attributes(operand)
.expect("Missing `operand` expression in context")
.typ;

return match &op.kind {
fe::UnaryOperator::USub => {
let zero = literal_expression! {0};
expression! { sub([zero], [yul_operand]) }
}
fe::UnaryOperator::Not => expression! { iszero([yul_operand]) },
_ => todo!(),
fe::UnaryOperator::Invert => match typ {
Type::Base(Base::Numeric(integer)) => {
math_operations::adjust_numeric_size(integer, expression! { not([yul_operand])})
}
_ => unreachable!(),
},
};
}

Expand Down
6 changes: 6 additions & 0 deletions crates/yulgen/src/names/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ pub fn checked_sub(size: &Integer) -> yul::Identifier {
identifier! {(format!("checked_sub_{}", size.to_lowercase()))}
}

/// Generate a function name to adjust the size of the integer
pub fn adjust_numeric_size(size: &Integer) -> yul::Identifier {
let size: &str = size.into();
identifier! {(format!("adjust_numeric_{}", size.to_lowercase()))}
}

/// Generate a safe function name for a user defined function
pub fn func_name(name: &str) -> yul::Identifier {
identifier! { (format!("$${}", name)) }
Expand Down
13 changes: 13 additions & 0 deletions crates/yulgen/src/operations/math.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use fe_analyzer::namespace::types::Integer;
use yultsur::*;

use crate::names;

/// Loads a value of the given type from storage.
pub fn adjust_numeric_size(integer: &Integer, value: yul::Expression) -> yul::Expression {
if integer.size() < 32 {
expression! { [names::adjust_numeric_size(integer)]([value]) }
} else {
value
}
}
1 change: 1 addition & 0 deletions crates/yulgen/src/operations/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod abi;
pub mod contracts;
pub mod data;
pub mod math;
pub mod revert;
pub mod structs;
42 changes: 42 additions & 0 deletions crates/yulgen/src/runtime/functions/math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,22 @@ pub fn checked_sub_fns() -> Vec<yul::Statement> {
]
}

/// Return a vector of runtime functions to adjust numeric sizes
pub fn adjust_numeric_size_fns() -> Vec<yul::Statement> {
vec![
adjust_numeric_unsigned(Integer::U128),
adjust_numeric_unsigned(Integer::U64),
adjust_numeric_unsigned(Integer::U32),
adjust_numeric_unsigned(Integer::U16),
adjust_numeric_unsigned(Integer::U8),
adjust_numeric_signed(Integer::I128, 15),
adjust_numeric_signed(Integer::I64, 7),
adjust_numeric_signed(Integer::I32, 3),
adjust_numeric_signed(Integer::I16, 1),
adjust_numeric_signed(Integer::I8, 0),
]
}

// Return all math runtime functions
pub fn all() -> Vec<yul::Statement> {
[
Expand All @@ -106,6 +122,7 @@ pub fn all() -> Vec<yul::Statement> {
checked_mod_fns(),
checked_mul_fns(),
checked_sub_fns(),
adjust_numeric_size_fns(),
]
.concat()
}
Expand Down Expand Up @@ -287,6 +304,31 @@ fn _checked_exp_unsigned(size: Integer) -> yul::Statement {
}
}

fn adjust_numeric_unsigned(size: Integer) -> yul::Statement {
if size.is_signed() {
panic!("Expected unsigned integer")
}
let max_value = get_max(size);
let fn_name = names::adjust_numeric_size(&size);
function_definition! {
function [fn_name](value) -> cleaned {
(cleaned := and(value, [max_value]))
}
}
}

fn adjust_numeric_signed(size: Integer, base: usize) -> yul::Statement {
if !size.is_signed() {
panic!("Expected signed integer")
}
let fn_name = names::adjust_numeric_size(&size);
function_definition! {
function [fn_name](value) -> cleaned {
(cleaned := signextend([literal_expression! { (base) }], value))
}
}
}

fn checked_exp_helper() -> yul::Statement {
// TODO: Refactor once https://github.com/ethereum/fe/issues/314 is resolved
yul::Statement::FunctionDefinition(yul::FunctionDefinition {
Expand Down
7 changes: 0 additions & 7 deletions docs/src/spec/expr_unary_operators.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,6 @@
The unary operators are used to negate expressions. The unary `-` (minus) operator yields the negation of its numeric argument. The unary `~` (invert) operator yields the *bitwise* inversion of its integer argument. The unary `not` operator yields the inversion of its boolean argument.

<div class="warning">

Warning:
The unary NOT operator (`~`) isn't yet implemented ([See GitHub issue](https://github.com/ethereum/fe/issues/521))
</div>


Example:

```
Expand Down
1 change: 1 addition & 0 deletions newsfragments/526.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implemented support for numeric unary invert operator (`~`)

0 comments on commit 9e39c21

Please sign in to comment.