Skip to content
Merged
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
9 changes: 9 additions & 0 deletions guppylang/cfg/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,15 @@ def visit_GeneratorExp(self, node: ast.GeneratorExp) -> DesugaredGeneratorExpr:
def visit_Call(self, node: ast.Call) -> ast.AST:
return is_comptime_expression(node) or self.generic_visit(node)

def visit_UnaryOp(self, node: ast.UnaryOp) -> ast.AST:
# Desugar negated numeric constants into constants
match node.op, node.operand:
case ast.USub(), ast.Constant(value=float(v) | int(v)) as const:
const.value = -v
return with_loc(node, const)
case _:
return self.generic_visit(node)

def generic_visit(self, node: ast.AST) -> ast.AST:
# Short-circuit expressions must be built using the `BranchBuilder`. However, we
# can turn them into regular expressions by assigning True/False to a temporary
Expand Down
19 changes: 19 additions & 0 deletions guppylang/checker/errors/type_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,3 +323,22 @@ class TupleIndexOutOfBoundsError(Error):
)
index: int
size: int


@dataclass(frozen=True)
class IntOverflowError(Error):
title: ClassVar[str] = "Integer {over_under}flow"
span_label: ClassVar[str] = (
"Value does not fit into a {bits}-bit {signed_unsigned} integer"
)
signed: bool
bits: int
is_underflow: bool

@property
def over_under(self) -> str:
return "under" if self.is_underflow else "over"

@property
def signed_unsigned(self) -> str:
return "signed" if self.signed else "unsigned"
18 changes: 17 additions & 1 deletion guppylang/checker/expr_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
BinaryOperatorNotDefinedError,
ConstMismatchError,
IllegalConstant,
IntOverflowError,
ModuleMemberNotFoundError,
NonLinearInstantiateError,
NotCallableError,
Expand Down Expand Up @@ -1336,9 +1337,11 @@ def python_value_to_guppy_type(
return string_type()
# Only resolve `int` to `nat` if the user specifically asked for it
case int(n) if type_hint == nat_type() and n >= 0:
_int_bounds_check(n, node, signed=False)
return nat_type()
# Otherwise, default to `int` for consistency with Python
case int():
case int(n):
_int_bounds_check(n, node, signed=True)
return int_type()
case float():
return float_type()
Expand All @@ -1361,6 +1364,19 @@ def python_value_to_guppy_type(
return None


def _int_bounds_check(value: int, node: AstNode, signed: bool) -> None:
bit_width = 1 << NumericType.INT_WIDTH
if signed:
max_v = (1 << (bit_width - 1)) - 1
min_v = -(1 << (bit_width - 1))
else:
max_v = (1 << bit_width) - 1
min_v = 0
if value < min_v or value > max_v:
err = IntOverflowError(node, signed, bit_width, value < min_v)
raise GuppyTypeError(err)


def _python_list_to_guppy_type(
vs: list[Any], node: ast.AST, globals: Globals, type_hint: Type | None
) -> OpaqueType | None:
Expand Down
25 changes: 13 additions & 12 deletions guppylang/compiler/expr_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
CompiledCallableDef,
CompiledValueDef,
)
from guppylang.error import GuppyComptimeError, GuppyError, InternalGuppyError
from guppylang.error import GuppyError, InternalGuppyError
from guppylang.nodes import (
BarrierExpr,
DesugaredArrayComp,
Expand All @@ -58,7 +58,10 @@
TupleAccessAndDrop,
TypeApply,
)
from guppylang.std._internal.compiler.arithmetic import convert_ifromusize
from guppylang.std._internal.compiler.arithmetic import (
UnsignedIntVal,
convert_ifromusize,
)
from guppylang.std._internal.compiler.array import (
array_convert_from_std_array,
array_convert_to_std_array,
Expand Down Expand Up @@ -785,16 +788,14 @@ def python_value_to_hugr(v: Any, exp_ty: Type) -> hv.Value | None:
case str():
return hugr.std.prelude.StringVal(v)
case int():
bit_width = 1 << NumericType.INT_WIDTH
max_v = (1 << (bit_width - 1)) - 1
min_v = -(1 << (bit_width - 1))
if v < min_v or v > max_v:
msg = (
f"Integer value {v} is out of bounds for {bit_width}"
"-bit signed integer."
)
raise GuppyComptimeError(msg)
return hugr.std.int.IntVal(v, width=NumericType.INT_WIDTH)
assert isinstance(exp_ty, NumericType)
match exp_ty.kind:
case NumericType.Kind.Nat:
return UnsignedIntVal(v, width=NumericType.INT_WIDTH)
case NumericType.Kind.Int:
return hugr.std.int.IntVal(v, width=NumericType.INT_WIDTH)
case _:
raise InternalGuppyError("Unexpected numeric type")
case float():
return hugr.std.float.FloatVal(v)
case tuple(elts):
Expand Down
27 changes: 26 additions & 1 deletion guppylang/std/_internal/compiler/arithmetic.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"""Native arithmetic operations from the HUGR std, and compilers for non native ones."""

from collections.abc import Sequence
from dataclasses import dataclass

import hugr.std.int
from hugr import ops
from hugr import model, ops, val
from hugr import tys as ht
from hugr.std.int import int_t

Expand All @@ -12,6 +13,30 @@

INT_T = int_t(NumericType.INT_WIDTH)


@dataclass
class UnsignedIntVal(val.ExtensionValue): # TODO: Upstream this to hugr-py?
"""Custom value for an unsigned integer."""

v: int
width: int

def __post_init__(self) -> None:
assert self.v >= 0

def to_value(self) -> val.Extension:
payload = {"log_width": self.width, "value": self.v}
return val.Extension("ConstInt", typ=int_t(self.width), val=payload)

def __str__(self) -> str:
return f"{self.v}"

def to_model(self) -> model.Term:
return model.Apply(
"arithmetic.int.const", [model.Literal(self.width), model.Literal(self.v)]
)


# ------------------------------------------------------
# --------- std.arithmetic.int operations --------------
# ------------------------------------------------------
Expand Down
9 changes: 5 additions & 4 deletions guppylang/tys/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,11 @@ def arg_from_ast(
# Integer literals are turned into nat args since these are the only ones we support
# right now.
# TODO: Once we also have int args etc, we need proper inference logic here
if isinstance(node, ast.Constant) and isinstance(node.value, int):
# Fun fact: int ast.Constant values are never negative since e.g. `-5` is a
# `ast.UnaryOp` negation of a `ast.Constant(5)`
assert node.value >= 0
if (
isinstance(node, ast.Constant)
and isinstance(node.value, int)
and node.value >= 0
):
nat_ty = NumericType(NumericType.Kind.Nat)
return ConstArg(ConstValue(nat_ty, node.value))

Expand Down
2 changes: 1 addition & 1 deletion tests/error/tracing_errors/bad_int1.err
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ Traceback (most recent call last):
guppy.compile(test)
File "$FILE", line 7, in test
return x + (1 << 63)
guppylang.error.GuppyComptimeError: Integer value 9223372036854775808 is out of bounds for 64-bit signed integer.
guppylang.error.GuppyComptimeError: Integer overflow: Value does not fit into a 64-bit signed integer
2 changes: 1 addition & 1 deletion tests/error/tracing_errors/bad_int2.err
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ Traceback (most recent call last):
guppy.compile(test)
File "$FILE", line 7, in test
return x + (-(1 << 63) - 1)
guppylang.error.GuppyComptimeError: Integer value -9223372036854775809 is out of bounds for 64-bit signed integer.
guppylang.error.GuppyComptimeError: Integer underflow: Value does not fit into a 64-bit signed integer
8 changes: 8 additions & 0 deletions tests/error/type_errors/int_overflow.err
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Error: Integer overflow (at $FILE:6:11)
|
4 | @guppy
5 | def foo() -> int:
6 | return 9_223_372_036_854_775_808
| ^^^^^^^^^^^^^^^^^^^^^^^^^ Value does not fit into a 64-bit signed integer

Guppy compilation failed due to 1 previous error
9 changes: 9 additions & 0 deletions tests/error/type_errors/int_overflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from guppylang.decorator import guppy


@guppy
def foo() -> int:
return 9_223_372_036_854_775_808


guppy.compile(foo)
8 changes: 8 additions & 0 deletions tests/error/type_errors/int_underflow.err
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Error: Integer underflow (at $FILE:6:11)
|
4 | @guppy
5 | def foo() -> int:
6 | return -9_223_372_036_854_775_809
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ Value does not fit into a 64-bit signed integer

Guppy compilation failed due to 1 previous error
9 changes: 9 additions & 0 deletions tests/error/type_errors/int_underflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from guppylang.decorator import guppy


@guppy
def foo() -> int:
return -9_223_372_036_854_775_809


guppy.compile(foo)
8 changes: 8 additions & 0 deletions tests/error/type_errors/nat_overflow.err
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Error: Integer overflow (at $FILE:7:11)
|
5 | @guppy
6 | def foo() -> nat:
7 | return 18_446_744_073_709_551_616
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ Value does not fit into a 64-bit unsigned integer

Guppy compilation failed due to 1 previous error
10 changes: 10 additions & 0 deletions tests/error/type_errors/nat_overflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from guppylang.decorator import guppy
from guppylang.std.num import nat


@guppy
def foo() -> nat:
return 18_446_744_073_709_551_616


guppy.compile(foo)
8 changes: 8 additions & 0 deletions tests/error/type_errors/negative_nat_literal.err
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Error: Type mismatch (at $FILE:7:11)
|
5 | @guppy
6 | def foo() -> nat:
7 | return -1
| ^^ Expected return value of type `nat`, got `int`

Guppy compilation failed due to 1 previous error
10 changes: 10 additions & 0 deletions tests/error/type_errors/negative_nat_literal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from guppylang.decorator import guppy
from guppylang.std.num import nat


@guppy
def foo() -> nat:
return -1


guppy.compile(foo)
17 changes: 17 additions & 0 deletions tests/integration/test_arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,23 @@ def const() -> nat:
validate(const)


def test_int_bounds(run_int_fn):
@guppy
def main() -> int:
return 9_223_372_036_854_775_807 + -9_223_372_036_854_775_808

run_int_fn(main, -1)


def test_nat_bounds(run_int_fn):
@guppy
def main() -> nat:
x: nat = 18_446_744_073_709_551_614
return x - x

run_int_fn(main, 0)


def test_aug_assign(run_int_fn):
@guppy
def add(x: int) -> int:
Expand Down
Loading