Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat[lang]: allow hex literals for unsigned integer types #4380

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion tests/functional/codegen/types/numbers/test_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from tests.utils import ZERO_ADDRESS, decimal_to_int
from vyper.compiler import compile_code
from vyper.exceptions import TypeMismatch
from vyper.utils import MemoryPositions
from vyper.utils import MemoryPositions, hex_to_int


def search_for_sublist(ir, sublist):
Expand Down Expand Up @@ -197,6 +197,23 @@ def test() -> Bytes[100]:
assert c.test() == test_str


def test_constant_hex_int(get_contract):
test_value = "0xfa"
code = f"""
X: constant(uint8) = {test_value}

@external
def test() -> uint8:
y: uint8 = X

return y
"""

c = get_contract(code)

assert c.test() == hex_to_int(test_value)


def test_constant_folds(experimental_codegen):
some_prime = 10013677
code = f"""
Expand Down
15 changes: 13 additions & 2 deletions tests/functional/codegen/types/numbers/test_unsigned_ints.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,8 @@


@pytest.mark.parametrize("typ", types)
def test_uint_literal(get_contract, typ):
@pytest.mark.parametrize("is_hex_int", [True, False])
def test_uint_literal(get_contract, typ, is_hex_int):
lo, hi = typ.ast_bounds

good_cases = [0, 1, 2, 3, hi // 2 - 1, hi // 2, hi // 2 + 1, hi - 1, hi]
Expand All @@ -222,11 +223,21 @@
return o
"""

def _to_hex_int(v):

Check notice

Code scanning / CodeQL

Unused local variable Note test

Variable _to_hex_int is not used.
n_nibbles = typ.bits // 4
return "0x" + hex(v)[2:].rjust(n_nibbles, "0")

for val in good_cases:
c = get_contract(code_template.format(typ=typ, val=val))
input_val = val
if is_hex_int:
n_nibbles = typ.bits // 4
input_val = "0x" + hex(val)[2:].rjust(n_nibbles, "0")
c = get_contract(code_template.format(typ=typ, val=input_val))
assert c.test() == val

for val in bad_cases:
if is_hex_int:
return
exc = (
TypeMismatch
if SizeLimits.MIN_INT256 <= val <= SizeLimits.MAX_UINT256
Expand Down
8 changes: 8 additions & 0 deletions vyper/ast/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
SizeLimits,
annotate_source_code,
evm_div,
hex_to_int,
quantize,
sha256sum,
)
Expand Down Expand Up @@ -883,6 +884,13 @@ def bytes_value(self):
"""
return bytes.fromhex(self.value.removeprefix("0x"))

@property
def int_value(self):
"""
This value as integer
"""
return hex_to_int(self.value)


class Str(Constant):
__slots__ = ()
Expand Down
2 changes: 2 additions & 0 deletions vyper/ast/nodes.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ class Decimal(Num): ...
class Hex(Num):
@property
def n_bytes(self): ...
@property
def int_value(self): ...

class Str(Constant): ...
class Bytes(Constant): ...
Expand Down
3 changes: 2 additions & 1 deletion vyper/builtins/_signatures.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ def modifiability(self):

# helper function to deal with TYPE_Ts
def _validate_single(self, arg: vy_ast.VyperNode, expected_type: VyperType) -> None:
if TYPE_T.any().compare_type(expected_type):
constant_node = arg if isinstance(arg, vy_ast.Constant) else None
if TYPE_T.any().compare_type(expected_type, constant_node):
# try to parse the type - call type_from_annotation
# for its side effects (will throw if is not a type)
type_from_annotation(arg)
Expand Down
8 changes: 8 additions & 0 deletions vyper/codegen/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
is_array_like,
is_bytes_m_type,
is_flag_type,
is_integer_type,
is_numeric_type,
is_tuple_like,
make_setter,
Expand Down Expand Up @@ -133,6 +134,13 @@ def parse_Hex(self):

return IRnode.from_list(val, typ=t)

elif is_integer_type(t):
n_bits = n_bytes * 8
assert t.bits <= n_bits

val = self.expr.int_value
return IRnode.from_list(val, typ=t)

# String literals
def parse_Str(self):
bytez, bytez_length = string_to_bytes(self.expr.value)
Expand Down
3 changes: 2 additions & 1 deletion vyper/semantics/analysis/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,8 @@ def validate_expected_type(node, expected_type):
return
else:
for given, expected in itertools.product(given_types, expected_type):
if expected.compare_type(given):
constant_node = node if isinstance(node, vy_ast.Constant) else None
if expected.compare_type(given, constant_node):
return

# validation failed, prepare a meaningful error message
Expand Down
6 changes: 4 additions & 2 deletions vyper/semantics/types/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def __repr__(self):
def __init__(self, type_):
self.type_ = type_

def compare_type(self, other):
def compare_type(self, other, is_constant):
if isinstance(other, self.type_):
return True
# compare two GenericTypeAcceptors -- they are the same if the base
Expand Down Expand Up @@ -290,7 +290,9 @@ def validate_literal(self, node: vy_ast.Constant) -> None:
def validate_index_type(self, node: vy_ast.Subscript) -> None:
raise StructureException(f"Not an indexable type: '{self}'", node)

def compare_type(self, other: "VyperType") -> bool:
def compare_type(
self, other: "VyperType", constant_node: Optional[vy_ast.Constant] = None
) -> bool:
"""
Compare this type object against another type object.

Expand Down
2 changes: 1 addition & 1 deletion vyper/semantics/types/bytestrings.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def set_min_length(self, min_length):
raise CompilerPanic("Cannot reduce the min_length of ArrayValueType")
self._min_length = min_length

def compare_type(self, other):
def compare_type(self, other, is_constant):
if not super().compare_type(other):
return False

Expand Down
21 changes: 17 additions & 4 deletions vyper/semantics/types/primitives.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from decimal import Decimal
from functools import cached_property
from typing import Any, Tuple, Union
from typing import Any, Optional, Tuple, Union

from vyper import ast as vy_ast
from vyper.abi_types import ABI_Address, ABI_Bool, ABI_BytesM, ABI_GIntM, ABIType
Expand Down Expand Up @@ -100,7 +100,9 @@ def validate_literal(self, node: vy_ast.Constant) -> None:
if nibbles not in (nibbles.lower(), nibbles.upper()):
raise InvalidLiteral(f"Cannot mix uppercase and lowercase for {self} literal", node)

def compare_type(self, other: VyperType) -> bool:
def compare_type(
self, other: VyperType, constant_node: Optional[vy_ast.Constant] = None
) -> bool:
if not super().compare_type(other):
return False
assert isinstance(other, BytesM_T)
Expand Down Expand Up @@ -291,7 +293,18 @@ def all(cls) -> Tuple["IntegerT", ...]:
def abi_type(self) -> ABIType:
return ABI_GIntM(self.bits, self.is_signed)

def compare_type(self, other: VyperType) -> bool:
def compare_type(
self, other: VyperType, constant_node: Optional[vy_ast.Constant] = None
) -> bool:
# handle hex integers
if (
not self.is_signed
and isinstance(other, BytesM_T)
and isinstance(constant_node, vy_ast.Hex)
):
lo, hi = self.ast_bounds
return lo <= constant_node.int_value <= hi

# this function is performance sensitive
# originally:
# if not super().compare_type(other):
Expand Down Expand Up @@ -414,6 +427,6 @@ def validate_literal(self, node: vy_ast.Constant) -> None:
class SelfT(AddressT):
_id = "self"

def compare_type(self, other):
def compare_type(self, other, is_constant):
# compares true to AddressT
return isinstance(other, type(self)) or isinstance(self, type(other))
8 changes: 4 additions & 4 deletions vyper/semantics/types/subscriptable.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def __repr__(self):
return f"HashMap[{self.key_type}, {self.value_type}]"

# TODO not sure this is used?
def compare_type(self, other):
def compare_type(self, other, is_constant):
return (
super().compare_type(other)
and self.key_type == other.key_type
Expand Down Expand Up @@ -196,7 +196,7 @@ def subtype(self):
def get_subscripted_type(self, node):
return self.value_type

def compare_type(self, other):
def compare_type(self, other, is_constant):
if not isinstance(self, type(other)):
return False
if self.length != other.length:
Expand Down Expand Up @@ -273,7 +273,7 @@ def size_in_bytes(self):
# one length word + size of the array items
return 32 + self.value_type.size_in_bytes * self.length

def compare_type(self, other):
def compare_type(self, other, is_constant):
# TODO allow static array to be assigned to dyn array?
# if not isinstance(other, (DArrayT, SArrayT)):
if not isinstance(self, type(other)):
Expand Down Expand Up @@ -384,7 +384,7 @@ def get_subscripted_type(self, node):
node = node.reduced()
return self.member_types[node.value]

def compare_type(self, other):
def compare_type(self, other, is_constant):
if not isinstance(self, type(other)):
return False
if self.length != other.length:
Expand Down
2 changes: 1 addition & 1 deletion vyper/semantics/types/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def __init__(self, members=None):
def __eq__(self, other):
return self is other

def compare_type(self, other):
def compare_type(self, other, is_constant):
# object exact comparison is a bit tricky here since we have
# to be careful to construct any given user type exactly
# only one time. however, the alternative requires reasoning
Expand Down
Loading