Repository: xdslproject/xdsl
Pull request #4351: core: Refactor parser and printer for complex types
Author: login: erick-xanadu
Labels: name: core
description: xDSL core (ir, textual format, ...)
Created: 2025-05-09T00:06:45Z | Merged: 2025-05-13T07:08:33Z

These are just the unproblematic parts.



Diff:
diff --git a/tests/test_parser.py b/tests/test_parser.py
index d77446c222..2f70d146d4 100644
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -739,6 +739,43 @@ def test_parse_int(
         )
 
 
+@pytest.mark.parametrize("nonnumeric", ["--", "+", "a", "{", "(1.0, 1.0)"])
+def test_parse_optional_int_or_float_nonnumeric(nonnumeric: str):
+    parser = Parser(Context(), nonnumeric)
+    assert parser._parse_optional_int_or_float() is None
+
+
+@pytest.mark.parametrize(
+    "numeric,is_int", [("1", True), ("1.0", False), ("-1", True), ("-1.0", False)]
+)
+def test_parse_optional_int_or_float_numeric(numeric: str, is_int: bool):
+    parser = Parser(Context(), numeric)
+    value_span = parser._parse_optional_int_or_float()
+    assert value_span is not None
+    value, span = value_span
+    caster = int if is_int else float
+    assert value == caster(numeric)
+    assert span.text == numeric
+
+
+@pytest.mark.parametrize("nonnumeric", ["--", "+", "a", "{", "(1.0, 1.0)"])
+def test_parse_int_or_float_nonnumeric(nonnumeric: str):
+    parser = Parser(Context(), nonnumeric)
+    with pytest.raises(ParseError):
+        parser._parse_int_or_float()
+
+
+@pytest.mark.parametrize("numeric,is_int", [("1", True), ("1.0", False)])
+def test_parse_int_or_float_numeric(numeric: str, is_int: bool):
+    parser = Parser(Context(), numeric)
+    value_span = parser._parse_int_or_float()
+    assert value_span is not None
+    value, span = value_span
+    caster = int if is_int else float
+    assert value == caster(numeric)
+    assert span.text == numeric
+
+
 @pytest.mark.parametrize(
     "text, allow_boolean, allow_negative",
     [
@@ -947,6 +984,36 @@ def test_parse_str_enum(keyword: str, expected: MyEnum | None):
         assert parser.parse_str_enum(MyEnum) == expected
 
 
+@pytest.mark.parametrize("value", ["(1., 2)", "(1, 2.)"])
+def test_parse_optional_complex_error(value: str):
+    parser = Parser(Context(), value)
+    with pytest.raises(
+        ParseError,
+        match=re.escape("Complex value must be either (float, float) or (int, int)"),
+    ):
+        parser._parse_optional_complex()
+
+
+@pytest.mark.parametrize("noncomplex", ["1", "-1", "A", "{"])
+def test_parse_optional_complex_noncomplex(noncomplex: str):
+    parser = Parser(Context(), noncomplex)
+    assert parser._parse_optional_complex() is None
+
+
+@pytest.mark.parametrize(
+    "toks, pyval", [("(-1., 2.)", (-1.0, 2.0)), ("(1, 2)", (1, 2))]
+)
+def test_parse_optional_complex_success(
+    toks: str, pyval: tuple[int, int] | tuple[float, float]
+):
+    parser = Parser(Context(), toks)
+    value_and_span = parser._parse_optional_complex()
+    assert value_and_span is not None
+    value, span = value_and_span
+    assert value == pyval
+    assert span.text == toks
+
+
 class MySingletonEnum(StrEnum):
     A = "a"
 
diff --git a/tests/test_printer.py b/tests/test_printer.py
index 1757b76d73..d708d7d65a 100644
--- a/tests/test_printer.py
+++ b/tests/test_printer.py
@@ -11,6 +11,7 @@
 from xdsl.dialects.builtin import (
     AnyFloat,
     Builtin,
+    ComplexType,
     FloatAttr,
     FunctionType,
     IntAttr,
@@ -784,6 +785,62 @@ def _test_float_print(expected: str, value: float, type: AnyFloat):
     _test_float_print("-3.14285707", -22 / 7, f32)
 
 
+@pytest.mark.parametrize(
+    "expected, value",
+    [
+        ("(-3.000000e+00,-3.000000e+00)", (-3.0, -3.0)),
+        ("(3.000000e+00,3.000000e+00)", (3.0, 3.0)),
+    ],
+)
+def test_complex_float(expected: str, value: tuple[float, float]):
+    printer = Printer()
+    io = StringIO()
+    printer.stream = io
+    type = ComplexType(f32)
+    printer.print_complex_float(value, type)
+    assert io.getvalue() == expected
+
+
+@pytest.mark.parametrize(
+    "expected, value",
+    [
+        ("(-3,-3)", (-3, -3)),
+        ("(3,3)", (3, 3)),
+    ],
+)
+def test_complex_int(expected: str, value: tuple[int, int]):
+    printer = Printer()
+    io = StringIO()
+    printer.stream = io
+    type = ComplexType(i32)
+    printer.print_complex_int(value, type)
+    assert io.getvalue() == expected
+
+
+@pytest.mark.parametrize(
+    "expected, value, is_int",
+    [
+        ("(-3,-3)", (-3, -3), True),
+        ("(3,3)", (3, 3), True),
+        ("(-3.000000e+00,-3.000000e+00)", (-3.0, -3.0), False),
+        ("(3.000000e+00,3.000000e+00)", (3.0, 3.0), False),
+    ],
+)
+def test_complex(
+    expected: str, value: tuple[int, int] | tuple[float, float], is_int: bool
+):
+    printer = Printer()
+    io = StringIO()
+    printer.stream = io
+    if is_int:
+        type = ComplexType(i32)
+        printer.print_complex(value, type)
+    else:
+        type = ComplexType(f32)
+        printer.print_complex(value, type)
+    assert io.getvalue() == expected
+
+
 def test_float_attr():
     printer = Printer()
 
diff --git a/xdsl/parser/attribute_parser.py b/xdsl/parser/attribute_parser.py
index 5e4385f105..a051ed0bfc 100644
--- a/xdsl/parser/attribute_parser.py
+++ b/xdsl/parser/attribute_parser.py
@@ -988,6 +988,67 @@ def to_type(self, parser: AttrParser, type: AnyFloat | IntegerType | IndexType):
                         parser, allow_negative=True, allow_booleans=False
                     )
 
+    def _parse_optional_int_or_float(
+        self,
+    ) -> tuple[int, Span] | tuple[float, Span] | None:
+        """
+        May rollback if the token after `-` is not either an integer
+        or float literal.
+        """
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if self._current_token.kind == MLIRTokenKind.INTEGER_LIT:
+            token = self._consume_token(MLIRTokenKind.INTEGER_LIT)
+            value = token.kind.get_int_value(token.span)
+        elif self._current_token.kind == MLIRTokenKind.FLOAT_LIT:
+            token = self._consume_token(MLIRTokenKind.FLOAT_LIT)
+            value = token.kind.get_float_value(token.span)
+        else:
+            self._resume_from(pos)
+            return None
+
+        if is_negative:
+            span = Span(minus_token.span.start, token.span.end, token.span.input)
+            value = -value
+        else:
+            span = token.span
+
+        return value, span
+
+    def _parse_optional_complex(
+        self,
+    ) -> tuple[tuple[float, float] | tuple[int, int], Span] | None:
+        if self._current_token.kind != MLIRTokenKind.L_PAREN:
+            return None
+
+        token = self._consume_token(MLIRTokenKind.L_PAREN)
+        start = token.span.start
+        input = token.span.input
+        real, _ = self._parse_int_or_float()
+        self.parse_punctuation(",")
+        imag, _ = self._parse_int_or_float()
+        real_ty = type(real)
+        imag_ty = type(imag)
+        if real_ty != imag_ty:
+            self.raise_error(
+                "Complex value must be either (float, float) or (int, int)"
+            )
+        token = self._consume_token(MLIRTokenKind.R_PAREN)
+        end = token.span.end
+        value = (real, imag)
+        span = Span(start, end, input)
+        return value, span
+
+    def _parse_int_or_float(self) -> tuple[int, Span] | tuple[float, Span]:
+        retval = self._parse_optional_int_or_float()
+        if retval is None:
+            self.raise_error("either an int or float must be present")
+        return retval
+
     def _parse_tensor_literal_element(self) -> _TensorLiteralElement:
         """
         Parse a tensor literal element, which can be a boolean, an integer
@@ -1001,28 +1062,11 @@ def _parse_tensor_literal_element(self) -> _TensorLiteralElement:
             token = self._consume_token(MLIRTokenKind.BARE_IDENT)
             return self._TensorLiteralElement(False, False, token.span)
 
-        # checking for negation
-        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
-
-        # Integer and float case
-        if self._current_token.kind == MLIRTokenKind.FLOAT_LIT:
-            token = self._consume_token(MLIRTokenKind.FLOAT_LIT)
-            value = token.kind.get_float_value(token.span)
-        elif self._current_token.kind == MLIRTokenKind.INTEGER_LIT:
-            token = self._consume_token(MLIRTokenKind.INTEGER_LIT)
-            value = token.kind.get_int_value(token.span)
-        else:
-            self.raise_error("Expected either a float, integer, or complex literal")
-
-        if minus_token is None:
-            is_negative = False
-            span = token.span
-        else:
-            is_negative = True
-            value = -value
-            span = Span(minus_token.span.start, token.span.end, token.span.input)
+        if scalar_span := self._parse_optional_int_or_float():
+            value, span = scalar_span
+            return self._TensorLiteralElement(value < 0, value, span)
 
-        return self._TensorLiteralElement(is_negative, value, span)
+        self.raise_error("Expected either a float, integer, or complex literal")
 
     def _parse_tensor_literal(
         self,
diff --git a/xdsl/printer.py b/xdsl/printer.py
index 9e88a6da73..0c0adfc4c7 100644
--- a/xdsl/printer.py
+++ b/xdsl/printer.py
@@ -19,6 +19,7 @@
     BFloat16Type,
     BoolAttr,
     BytesAttr,
+    ComplexElementT,
     ComplexType,
     DenseArrayBase,
     DenseResourceAttr,
@@ -341,6 +342,40 @@ def print_bytes_literal(self, bytestring: bytes):
     def print_float_attr(self, attribute: FloatAttr):
         self.print_float(attribute.value.data, attribute.type)
 
+    def print_complex_float(
+        self, value: tuple[float, float], type: ComplexType[ComplexElementT]
+    ):
+        assert isinstance(type.element_type, AnyFloat)
+        self.print_string("(")
+        real, imag = value[0], value[1]
+        self.print_float(real, type.element_type)
+        self.print_string(",")
+        self.print_float(imag, type.element_type)
+        self.print_string(")")
+
+    def print_complex_int(
+        self, value: tuple[int, int], type: ComplexType[ComplexElementT]
+    ):
+        assert isinstance(type.element_type, IntegerType)
+        self.print_string("(")
+        real, imag = value[0], value[1]
+        self.print_string(str(real))
+        self.print_string(",")
+        self.print_string(str(imag))
+        self.print_string(")")
+
+    def print_complex(
+        self,
+        value: tuple[float, float] | tuple[int, int],
+        type: ComplexType[ComplexElementT],
+    ):
+        if isinstance(type.element_type, IntegerType):
+            assert isa(value, tuple[int, int])
+            self.print_complex_int(value, type)
+        else:
+            assert isa(value, tuple[float, float])
+            self.print_complex_float(value, type)
+
     def print_float(self, value: float, type: AnyFloat):
         if math.isnan(value) or math.isinf(value):
             if isinstance(type, Float16Type):


Reviews:
user: login: superlopuh
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T07:23:35Z
user: login: superlopuh
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T07:25:39Z
user: login: erick-xanadu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T11:42:30Z
user: login: erick-xanadu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T11:44:11Z
user: login: superlopuh
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T12:17:25Z
user: login: math-fehr
body: That looks okay to me, can you just add some documentation on the parsing functions, and change `parse_optional_int_or_float` so it doesn't rollback?
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-09T14:50:23Z
user: login: erick-xanadu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T15:32:06Z
user: login: erick-xanadu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T15:40:53Z
user: login: superlopuh
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-12T21:04:28Z
user: login: superlopuh
body: LGTM!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-12T21:06:53Z
user: login: math-fehr
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-12T21:24:13Z
user: login: erick-xanadu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T21:46:33Z
user: login: erick-xanadu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T21:48:18Z
user: login: math-fehr
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-12T22:05:00Z
user: login: math-fehr
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-12T22:05:25Z
user: login: erick-xanadu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T22:46:41Z
user: login: erick-xanadu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T22:46:50Z
user: login: erick-xanadu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T22:47:05Z

Review comments:
diff_hunk: @@ -340,6 +341,31 @@ def print_bytes_literal(self, bytestring: bytes):
     def print_float_attr(self, attribute: FloatAttr):
         self.print_float(attribute.value.data, attribute.type)
 
+    def print_complex_float(self, value: complex, type: ComplexType):
+        assert isinstance(type.element_type, AnyFloat)
+        self.print_string("(")
+        real, imag = value.real, value.imag
+        self.print_float(real, type.element_type)
+        self.print_string(",")
+        self.print_float(imag, type.element_type)
+        self.print_string(")")
+
+    def print_complex_int(self, value: tuple[int, int], type: ComplexType):
+        assert isinstance(type.element_type, IntegerType)
+        self.print_string("(")
+        real, imag = value[0], value[1]
+        self.print_string(str(real))
+        self.print_string(",")
+        self.print_string(str(imag))
+        self.print_string(")")
+
+    def print_complex(self, value: complex | tuple[int, int], type: ComplexType):
+        if isinstance(value, complex):
+            self.print_complex_float(value, type)
+        else:
+            assert isa(value, tuple[int, int])
+            self.print_complex_int(value, type)
+
path: xdsl/printer.py
user: login: superlopuh
body: let's add `print_complex_attr` before adding reusable helpers?
created_at: 2025-05-09T07:23:35Z
author_association: MEMBER
diff_hunk: @@ -989,6 +989,82 @@ def to_type(self, parser: AttrParser, type: AnyFloat | IntegerType | IndexType):
                         parser, allow_negative=True, allow_booleans=False
                     )
 
+    def _parse_optional_int(self) -> tuple[int, Span] | None:
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.INTEGER_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.INTEGER_LIT)
+        value = token.kind.get_int_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
+
+    def _parse_optional_float(self) -> tuple[float, Span] | None:
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.FLOAT_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.FLOAT_LIT)
+        value = token.kind.get_float_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
+
+    def _parse_optional_int_or_float(
+        self,
+    ) -> tuple[int, Span] | tuple[float, Span] | None:
+        if retval := self._parse_optional_int():
+            return retval
+        return self._parse_optional_float()
+
+    def _parse_optional_complex(self) -> tuple[complex | tuple[int, int], Span] | None:
+        if self._current_token.kind != MLIRTokenKind.L_PAREN:
+            return None
+
+        token = self._consume_token(MLIRTokenKind.L_PAREN)
+        start = token.span.start
+        input = token.span.input
+        real, _ = self._parse_int_or_float()
+        self.parse_punctuation(",")
+        imag, _ = self._parse_int_or_float()
+        real_ty = type(real)
+        imag_ty = type(imag)
+        assert real_ty == imag_ty, (
+            "Complex type must be either (float, float) or (int, int)"
+        )
path: xdsl/parser/attribute_parser.py
user: login: superlopuh
body: this should be a parsing error IMO, would be good to add a test for this
created_at: 2025-05-09T07:25:39Z
author_association: MEMBER
diff_hunk: @@ -989,6 +989,82 @@ def to_type(self, parser: AttrParser, type: AnyFloat | IntegerType | IndexType):
                         parser, allow_negative=True, allow_booleans=False
                     )
 
+    def _parse_optional_int(self) -> tuple[int, Span] | None:
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.INTEGER_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.INTEGER_LIT)
+        value = token.kind.get_int_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
+
+    def _parse_optional_float(self) -> tuple[float, Span] | None:
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.FLOAT_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.FLOAT_LIT)
+        value = token.kind.get_float_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
+
+    def _parse_optional_int_or_float(
+        self,
+    ) -> tuple[int, Span] | tuple[float, Span] | None:
+        if retval := self._parse_optional_int():
+            return retval
+        return self._parse_optional_float()
+
+    def _parse_optional_complex(self) -> tuple[complex | tuple[int, int], Span] | None:
+        if self._current_token.kind != MLIRTokenKind.L_PAREN:
+            return None
+
+        token = self._consume_token(MLIRTokenKind.L_PAREN)
+        start = token.span.start
+        input = token.span.input
+        real, _ = self._parse_int_or_float()
+        self.parse_punctuation(",")
+        imag, _ = self._parse_int_or_float()
+        real_ty = type(real)
+        imag_ty = type(imag)
+        assert real_ty == imag_ty, (
+            "Complex type must be either (float, float) or (int, int)"
+        )
path: xdsl/parser/attribute_parser.py
user: login: erick-xanadu
body: https://github.com/xdslproject/xdsl/pull/4351/commits/2dd518ddd07963138c1f694fe2a3f1e4825745f3
created_at: 2025-05-09T11:42:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -340,6 +341,31 @@ def print_bytes_literal(self, bytestring: bytes):
     def print_float_attr(self, attribute: FloatAttr):
         self.print_float(attribute.value.data, attribute.type)
 
+    def print_complex_float(self, value: complex, type: ComplexType):
+        assert isinstance(type.element_type, AnyFloat)
+        self.print_string("(")
+        real, imag = value.real, value.imag
+        self.print_float(real, type.element_type)
+        self.print_string(",")
+        self.print_float(imag, type.element_type)
+        self.print_string(")")
+
+    def print_complex_int(self, value: tuple[int, int], type: ComplexType):
+        assert isinstance(type.element_type, IntegerType)
+        self.print_string("(")
+        real, imag = value[0], value[1]
+        self.print_string(str(real))
+        self.print_string(",")
+        self.print_string(str(imag))
+        self.print_string(")")
+
+    def print_complex(self, value: complex | tuple[int, int], type: ComplexType):
+        if isinstance(value, complex):
+            self.print_complex_float(value, type)
+        else:
+            assert isa(value, tuple[int, int])
+            self.print_complex_int(value, type)
+
path: xdsl/printer.py
user: login: erick-xanadu
body: Do you want me to add a ComplexAttr?
created_at: 2025-05-09T11:44:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -340,6 +341,31 @@ def print_bytes_literal(self, bytestring: bytes):
     def print_float_attr(self, attribute: FloatAttr):
         self.print_float(attribute.value.data, attribute.type)
 
+    def print_complex_float(self, value: complex, type: ComplexType):
+        assert isinstance(type.element_type, AnyFloat)
+        self.print_string("(")
+        real, imag = value.real, value.imag
+        self.print_float(real, type.element_type)
+        self.print_string(",")
+        self.print_float(imag, type.element_type)
+        self.print_string(")")
+
+    def print_complex_int(self, value: tuple[int, int], type: ComplexType):
+        assert isinstance(type.element_type, IntegerType)
+        self.print_string("(")
+        real, imag = value[0], value[1]
+        self.print_string(str(real))
+        self.print_string(",")
+        self.print_string(str(imag))
+        self.print_string(")")
+
+    def print_complex(self, value: complex | tuple[int, int], type: ComplexType):
+        if isinstance(value, complex):
+            self.print_complex_float(value, type)
+        else:
+            assert isa(value, tuple[int, int])
+            self.print_complex_int(value, type)
+
path: xdsl/printer.py
user: login: superlopuh
body: Oh, I misunderstood, I thought we had one already.
created_at: 2025-05-09T12:17:25Z
author_association: MEMBER
diff_hunk: @@ -989,6 +989,83 @@ def to_type(self, parser: AttrParser, type: AnyFloat | IntegerType | IndexType):
                         parser, allow_negative=True, allow_booleans=False
                     )
 
+    def _parse_optional_int(self) -> tuple[int, Span] | None:
path: xdsl/parser/attribute_parser.py
user: login: math-fehr
body: Can you document the function to mention what it is parsing (especially the `-` part)?
Also, can you mention that this will do a lookup when parsing (i.e., it might parse `-` then rollback if the next token is not an integer).
Same for the functions below
created_at: 2025-05-09T14:42:50Z
author_association: COLLABORATOR
diff_hunk: @@ -989,6 +989,83 @@ def to_type(self, parser: AttrParser, type: AnyFloat | IntegerType | IndexType):
                         parser, allow_negative=True, allow_booleans=False
                     )
 
+    def _parse_optional_int(self) -> tuple[int, Span] | None:
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.INTEGER_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.INTEGER_LIT)
+        value = token.kind.get_int_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
+
+    def _parse_optional_float(self) -> tuple[float, Span] | None:
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.FLOAT_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.FLOAT_LIT)
+        value = token.kind.get_float_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
+
+    def _parse_optional_int_or_float(
+        self,
+    ) -> tuple[int, Span] | tuple[float, Span] | None:
+        if retval := self._parse_optional_int():
+            return retval
+        return self._parse_optional_float()
path: xdsl/parser/attribute_parser.py
user: login: math-fehr
body: Can you keep the old logic for that function?
With this current code, parsing `-0.2` will look like this:
* Parse `-`
* Find that the next token is not an integer, rollback
* Parse `-`
* Got a float, returns it

Compared to previously (in parse_tensor_literal_element), which would:
* Parse `-`
* Get the next token
* If it is not an integer, check if it's a float

This is quite more efficient, especially since the parser is already quite slow, and I believe this will slow down significantly the parsing of a simple float dense array.
created_at: 2025-05-09T14:50:00Z
author_association: COLLABORATOR
diff_hunk: @@ -989,6 +989,83 @@ def to_type(self, parser: AttrParser, type: AnyFloat | IntegerType | IndexType):
                         parser, allow_negative=True, allow_booleans=False
                     )
 
+    def _parse_optional_int(self) -> tuple[int, Span] | None:
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.INTEGER_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.INTEGER_LIT)
+        value = token.kind.get_int_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
+
+    def _parse_optional_float(self) -> tuple[float, Span] | None:
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.FLOAT_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.FLOAT_LIT)
+        value = token.kind.get_float_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
+
+    def _parse_optional_int_or_float(
+        self,
+    ) -> tuple[int, Span] | tuple[float, Span] | None:
+        if retval := self._parse_optional_int():
+            return retval
+        return self._parse_optional_float()
path: xdsl/parser/attribute_parser.py
user: login: erick-xanadu
body: https://github.com/xdslproject/xdsl/pull/4351/commits/3d9f78e02a8fd400a20c57f16542ae94d49fdc7d Thanks for the review! Let me know if there are more concerns.
created_at: 2025-05-09T15:32:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -989,6 +989,83 @@ def to_type(self, parser: AttrParser, type: AnyFloat | IntegerType | IndexType):
                         parser, allow_negative=True, allow_booleans=False
                     )
 
+    def _parse_optional_int(self) -> tuple[int, Span] | None:
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.INTEGER_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.INTEGER_LIT)
+        value = token.kind.get_int_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
+
+    def _parse_optional_float(self) -> tuple[float, Span] | None:
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.FLOAT_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.FLOAT_LIT)
+        value = token.kind.get_float_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
+
+    def _parse_optional_int_or_float(
+        self,
+    ) -> tuple[int, Span] | tuple[float, Span] | None:
+        if retval := self._parse_optional_int():
+            return retval
+        return self._parse_optional_float()
path: xdsl/parser/attribute_parser.py
user: login: erick-xanadu
body: Just as a note, I left the implementation of `parse_optional_int_or_float` to be able to rollback. We could guarantee that we are parsing the correct type if we were able to look ahead a couple of tokens and look for the type of the tensor and then just repeatedly call `parse_int`, `parse_float`, `parse_complex` respectively. But I think that would need a rework on the parser that is beyond the scope of this work.
created_at: 2025-05-09T15:40:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -988,6 +988,120 @@ def to_type(self, parser: AttrParser, type: AnyFloat | IntegerType | IndexType):
                         parser, allow_negative=True, allow_booleans=False
                     )
 
+    def _parse_optional_int(self) -> tuple[int, Span] | None:
+        """Parses an optional int. Includes parsing the minus token.
path: xdsl/parser/attribute_parser.py
user: login: superlopuh
body: We usually start the docs on newlines, this is more of a nit, but feels like it would be good to stay consistent:
```suggestion
        """
        Parses an optional int. Includes parsing the minus token.
```
created_at: 2025-05-12T21:04:28Z
author_association: MEMBER
diff_hunk: @@ -988,6 +988,120 @@ def to_type(self, parser: AttrParser, type: AnyFloat | IntegerType | IndexType):
                         parser, allow_negative=True, allow_booleans=False
                     )
 
+    def _parse_optional_int(self) -> tuple[int, Span] | None:
+        """Parses an optional int. Includes parsing the minus token.
+
+        May rollback if the token after `-` is not a integer literal.
+        """
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.INTEGER_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.INTEGER_LIT)
+        value = token.kind.get_int_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
+
+    def _parse_optional_float(self) -> tuple[float, Span] | None:
+        """Parses an optional float. Includes parsing the minus token.
+
+        May rollback if the token after `-` is not a float literal.
+        """
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.FLOAT_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.FLOAT_LIT)
+        value = token.kind.get_float_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
path: xdsl/parser/attribute_parser.py
user: login: math-fehr
body: Is there a reason you are keeping these functions, despite not using them anywhere (for int and float parsing?).
I would personally remove them if you don't have a use for them.
created_at: 2025-05-12T21:20:47Z
author_association: COLLABORATOR
diff_hunk: @@ -988,6 +988,120 @@ def to_type(self, parser: AttrParser, type: AnyFloat | IntegerType | IndexType):
                         parser, allow_negative=True, allow_booleans=False
                     )
 
+    def _parse_optional_int(self) -> tuple[int, Span] | None:
+        """Parses an optional int. Includes parsing the minus token.
+
+        May rollback if the token after `-` is not a integer literal.
+        """
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.INTEGER_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.INTEGER_LIT)
+        value = token.kind.get_int_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
+
+    def _parse_optional_float(self) -> tuple[float, Span] | None:
+        """Parses an optional float. Includes parsing the minus token.
+
+        May rollback if the token after `-` is not a float literal.
+        """
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.FLOAT_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.FLOAT_LIT)
+        value = token.kind.get_float_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
+
+    def _parse_optional_int_or_float(
+        self,
+    ) -> tuple[int, Span] | tuple[float, Span] | None:
+        """This version does not call self._parse_optional_int()
+        and self._parse_optional_float() for efficiency. It inlines
+        both and hoists parsing the minus_token.
+
+        May rollback if the token after `-` is not either an integer
+        or float literal.
+        """
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if self._current_token.kind == MLIRTokenKind.INTEGER_LIT:
+            token = self._consume_token(MLIRTokenKind.INTEGER_LIT)
+            value = token.kind.get_int_value(token.span)
+        elif self._current_token.kind == MLIRTokenKind.FLOAT_LIT:
+            token = self._consume_token(MLIRTokenKind.FLOAT_LIT)
+            value = token.kind.get_float_value(token.span)
+        else:
+            self._resume_from(pos)
+            return None
+
+        if is_negative:
+            span = Span(minus_token.span.start, token.span.end, token.span.input)
+            value = -value
+        else:
+            span = token.span
+
+        return value, span
path: xdsl/parser/attribute_parser.py
user: login: math-fehr
body: Now that I'm thinking about it, is it possible that this function is exactly `parser.parse_number`?
I'm sorry I completely missed this before, and I think the reason the tensor element wasn't using it is probably because we missed it (or it was in the middle of a refactor).
But that would actually simplify the PR a lot if these are the same. Also, can you move this function to `BaseParser` if they are different? I think that is a better place than in `AttrParser`, which is supposed to be for parsing attributes.

created_at: 2025-05-12T21:24:11Z
author_association: COLLABORATOR
diff_hunk: @@ -988,6 +988,120 @@ def to_type(self, parser: AttrParser, type: AnyFloat | IntegerType | IndexType):
                         parser, allow_negative=True, allow_booleans=False
                     )
 
+    def _parse_optional_int(self) -> tuple[int, Span] | None:
+        """Parses an optional int. Includes parsing the minus token.
+
+        May rollback if the token after `-` is not a integer literal.
+        """
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.INTEGER_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.INTEGER_LIT)
+        value = token.kind.get_int_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
+
+    def _parse_optional_float(self) -> tuple[float, Span] | None:
+        """Parses an optional float. Includes parsing the minus token.
+
+        May rollback if the token after `-` is not a float literal.
+        """
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.FLOAT_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.FLOAT_LIT)
+        value = token.kind.get_float_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
path: xdsl/parser/attribute_parser.py
user: login: erick-xanadu
body: Thanks! I must have just been coding automatically without thinking. 
created_at: 2025-05-12T21:46:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -988,6 +988,120 @@ def to_type(self, parser: AttrParser, type: AnyFloat | IntegerType | IndexType):
                         parser, allow_negative=True, allow_booleans=False
                     )
 
+    def _parse_optional_int(self) -> tuple[int, Span] | None:
+        """Parses an optional int. Includes parsing the minus token.
+
+        May rollback if the token after `-` is not a integer literal.
+        """
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.INTEGER_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.INTEGER_LIT)
+        value = token.kind.get_int_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
+
+    def _parse_optional_float(self) -> tuple[float, Span] | None:
+        """Parses an optional float. Includes parsing the minus token.
+
+        May rollback if the token after `-` is not a float literal.
+        """
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.FLOAT_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.FLOAT_LIT)
+        value = token.kind.get_float_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
+
+    def _parse_optional_int_or_float(
+        self,
+    ) -> tuple[int, Span] | tuple[float, Span] | None:
+        """This version does not call self._parse_optional_int()
+        and self._parse_optional_float() for efficiency. It inlines
+        both and hoists parsing the minus_token.
+
+        May rollback if the token after `-` is not either an integer
+        or float literal.
+        """
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if self._current_token.kind == MLIRTokenKind.INTEGER_LIT:
+            token = self._consume_token(MLIRTokenKind.INTEGER_LIT)
+            value = token.kind.get_int_value(token.span)
+        elif self._current_token.kind == MLIRTokenKind.FLOAT_LIT:
+            token = self._consume_token(MLIRTokenKind.FLOAT_LIT)
+            value = token.kind.get_float_value(token.span)
+        else:
+            self._resume_from(pos)
+            return None
+
+        if is_negative:
+            span = Span(minus_token.span.start, token.span.end, token.span.input)
+            value = -value
+        else:
+            span = token.span
+
+        return value, span
path: xdsl/parser/attribute_parser.py
user: login: erick-xanadu
body: These are slightly different in that they also return a span. The `_TensorLiteralElement` constructor takes in a span. `parse_number` does not return a span. I'm not familiar with this part of the code base, so happy to move it wherever you want. Would you like for `parse_number` to return a span? Or would you like for this function to get added to `BaseParser`? 
created_at: 2025-05-12T21:48:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -988,6 +988,120 @@ def to_type(self, parser: AttrParser, type: AnyFloat | IntegerType | IndexType):
                         parser, allow_negative=True, allow_booleans=False
                     )
 
+    def _parse_optional_int(self) -> tuple[int, Span] | None:
+        """Parses an optional int. Includes parsing the minus token.
+
+        May rollback if the token after `-` is not a integer literal.
+        """
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.INTEGER_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.INTEGER_LIT)
+        value = token.kind.get_int_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
+
+    def _parse_optional_float(self) -> tuple[float, Span] | None:
+        """Parses an optional float. Includes parsing the minus token.
+
+        May rollback if the token after `-` is not a float literal.
+        """
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.FLOAT_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.FLOAT_LIT)
+        value = token.kind.get_float_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
+
+    def _parse_optional_int_or_float(
+        self,
+    ) -> tuple[int, Span] | tuple[float, Span] | None:
+        """This version does not call self._parse_optional_int()
+        and self._parse_optional_float() for efficiency. It inlines
+        both and hoists parsing the minus_token.
+
+        May rollback if the token after `-` is not either an integer
+        or float literal.
+        """
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if self._current_token.kind == MLIRTokenKind.INTEGER_LIT:
+            token = self._consume_token(MLIRTokenKind.INTEGER_LIT)
+            value = token.kind.get_int_value(token.span)
+        elif self._current_token.kind == MLIRTokenKind.FLOAT_LIT:
+            token = self._consume_token(MLIRTokenKind.FLOAT_LIT)
+            value = token.kind.get_float_value(token.span)
+        else:
+            self._resume_from(pos)
+            return None
+
+        if is_negative:
+            span = Span(minus_token.span.start, token.span.end, token.span.input)
+            value = -value
+        else:
+            span = token.span
+
+        return value, span
path: xdsl/parser/attribute_parser.py
user: login: math-fehr
body: Oh I see, good point!
I would say actually let's keep it here, since it's only used in attribute parsers for now.
One day I'll figure out a nice interface to not duplicate everything, but that's fine for this one I would say!
created_at: 2025-05-12T22:05:00Z
author_association: COLLABORATOR
diff_hunk: @@ -988,6 +988,120 @@ def to_type(self, parser: AttrParser, type: AnyFloat | IntegerType | IndexType):
                         parser, allow_negative=True, allow_booleans=False
                     )
 
+    def _parse_optional_int(self) -> tuple[int, Span] | None:
+        """Parses an optional int. Includes parsing the minus token.
+
+        May rollback if the token after `-` is not a integer literal.
+        """
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.INTEGER_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.INTEGER_LIT)
+        value = token.kind.get_int_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
+
+    def _parse_optional_float(self) -> tuple[float, Span] | None:
+        """Parses an optional float. Includes parsing the minus token.
+
+        May rollback if the token after `-` is not a float literal.
+        """
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.FLOAT_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.FLOAT_LIT)
+        value = token.kind.get_float_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
+
+    def _parse_optional_int_or_float(
+        self,
+    ) -> tuple[int, Span] | tuple[float, Span] | None:
+        """This version does not call self._parse_optional_int()
+        and self._parse_optional_float() for efficiency. It inlines
+        both and hoists parsing the minus_token.
+
+        May rollback if the token after `-` is not either an integer
+        or float literal.
+        """
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if self._current_token.kind == MLIRTokenKind.INTEGER_LIT:
+            token = self._consume_token(MLIRTokenKind.INTEGER_LIT)
+            value = token.kind.get_int_value(token.span)
+        elif self._current_token.kind == MLIRTokenKind.FLOAT_LIT:
+            token = self._consume_token(MLIRTokenKind.FLOAT_LIT)
+            value = token.kind.get_float_value(token.span)
+        else:
+            self._resume_from(pos)
+            return None
+
+        if is_negative:
+            span = Span(minus_token.span.start, token.span.end, token.span.input)
+            value = -value
+        else:
+            span = token.span
+
+        return value, span
path: xdsl/parser/attribute_parser.py
user: login: math-fehr
body: Oh, just don't forget to update the description so it doesn't reference the functions you should remove!
created_at: 2025-05-12T22:05:25Z
author_association: COLLABORATOR
diff_hunk: @@ -988,6 +988,120 @@ def to_type(self, parser: AttrParser, type: AnyFloat | IntegerType | IndexType):
                         parser, allow_negative=True, allow_booleans=False
                     )
 
+    def _parse_optional_int(self) -> tuple[int, Span] | None:
+        """Parses an optional int. Includes parsing the minus token.
+
+        May rollback if the token after `-` is not a integer literal.
+        """
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.INTEGER_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.INTEGER_LIT)
+        value = token.kind.get_int_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
+
+    def _parse_optional_float(self) -> tuple[float, Span] | None:
+        """Parses an optional float. Includes parsing the minus token.
+
+        May rollback if the token after `-` is not a float literal.
+        """
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.FLOAT_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.FLOAT_LIT)
+        value = token.kind.get_float_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
+
+    def _parse_optional_int_or_float(
+        self,
+    ) -> tuple[int, Span] | tuple[float, Span] | None:
+        """This version does not call self._parse_optional_int()
+        and self._parse_optional_float() for efficiency. It inlines
+        both and hoists parsing the minus_token.
+
+        May rollback if the token after `-` is not either an integer
+        or float literal.
+        """
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if self._current_token.kind == MLIRTokenKind.INTEGER_LIT:
+            token = self._consume_token(MLIRTokenKind.INTEGER_LIT)
+            value = token.kind.get_int_value(token.span)
+        elif self._current_token.kind == MLIRTokenKind.FLOAT_LIT:
+            token = self._consume_token(MLIRTokenKind.FLOAT_LIT)
+            value = token.kind.get_float_value(token.span)
+        else:
+            self._resume_from(pos)
+            return None
+
+        if is_negative:
+            span = Span(minus_token.span.start, token.span.end, token.span.input)
+            value = -value
+        else:
+            span = token.span
+
+        return value, span
path: xdsl/parser/attribute_parser.py
user: login: erick-xanadu
body: Updated the description [here](https://github.com/xdslproject/xdsl/pull/4351/commits/da4ad24a9c36572a59f83050eb0eab973312bb75)
created_at: 2025-05-12T22:46:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -988,6 +988,120 @@ def to_type(self, parser: AttrParser, type: AnyFloat | IntegerType | IndexType):
                         parser, allow_negative=True, allow_booleans=False
                     )
 
+    def _parse_optional_int(self) -> tuple[int, Span] | None:
+        """Parses an optional int. Includes parsing the minus token.
+
+        May rollback if the token after `-` is not a integer literal.
+        """
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.INTEGER_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.INTEGER_LIT)
+        value = token.kind.get_int_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
+
+    def _parse_optional_float(self) -> tuple[float, Span] | None:
+        """Parses an optional float. Includes parsing the minus token.
+
+        May rollback if the token after `-` is not a float literal.
+        """
+        pos = self._current_token.span.start
+
+        # checking for negation
+        minus_token = self._parse_optional_token(MLIRTokenKind.MINUS)
+        is_negative = minus_token is not None
+
+        if not self._current_token.kind == MLIRTokenKind.FLOAT_LIT:
+            self._resume_from(pos)
+            return None
+
+        token = self._consume_token(MLIRTokenKind.FLOAT_LIT)
+        value = token.kind.get_float_value(token.span)
+        span = (
+            Span(minus_token.span.start, token.span.end, token.span.input)
+            if is_negative
+            else token.span
+        )
+        value = -value if is_negative else value
+        return value, span
path: xdsl/parser/attribute_parser.py
user: login: erick-xanadu
body: Removed them [here](https://github.com/xdslproject/xdsl/pull/4351/commits/da4ad24a9c36572a59f83050eb0eab973312bb75).
created_at: 2025-05-12T22:46:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -988,6 +988,120 @@ def to_type(self, parser: AttrParser, type: AnyFloat | IntegerType | IndexType):
                         parser, allow_negative=True, allow_booleans=False
                     )
 
+    def _parse_optional_int(self) -> tuple[int, Span] | None:
+        """Parses an optional int. Includes parsing the minus token.
path: xdsl/parser/attribute_parser.py
user: login: erick-xanadu
body: This function was removed [here](https://github.com/xdslproject/xdsl/pull/4351/commits/da4ad24a9c36572a59f83050eb0eab973312bb75)
created_at: 2025-05-12T22:47:05Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-05-09T00:14:02Z
body: ## [Codecov](https://app.codecov.io/gh/xdslproject/xdsl/pull/4351?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=xdslproject) Report
:x: Patch coverage is `96.96970%` with `2 lines` in your changes missing coverage. Please review.
:white_check_mark: Project coverage is 89.14%. Comparing base ([`f6a870e`](https://app.codecov.io/gh/xdslproject/xdsl/commit/f6a870ef1ff429bc0563a079ccd6439a115259b5?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=xdslproject)) to head ([`da4ad24`](https://app.codecov.io/gh/xdslproject/xdsl/commit/da4ad24a9c36572a59f83050eb0eab973312bb75?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=xdslproject)).
:warning: Report is 559 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/xdslproject/xdsl/pull/4351?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=xdslproject) | Patch % | Lines |
|---|---|---|
| [xdsl/parser/attribute\_parser.py](https://app.codecov.io/gh/xdslproject/xdsl/pull/4351?src=pr&el=tree&filepath=xdsl%2Fparser%2Fattribute_parser.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=xdslproject#diff-eGRzbC9wYXJzZXIvYXR0cmlidXRlX3BhcnNlci5weQ==) | 95.45% | [1 Missing and 1 partial :warning: ](https://app.codecov.io/gh/xdslproject/xdsl/pull/4351?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=xdslproject) |

<details><summary>Additional details and impacted files</summary>



```diff
@@            Coverage Diff             @@
##             main    #4351      +/-   ##
==========================================
+ Coverage   89.12%   89.14%   +0.01%     
==========================================
  Files         335      335              
  Lines       45404    45460      +56     
  Branches     5584     5588       +4     
==========================================
+ Hits        40466    40524      +58     
+ Misses       3540     3537       -3     
- Partials     1398     1399       +1     
```
</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/xdslproject/xdsl/pull/4351?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=xdslproject).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=xdslproject).
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: superlopuh
created_at: 2025-05-10T08:18:30Z
body: Since we already merged the `tuple[float, float]` version to main, could you please switch this PR also? Also, could you please bring the test coverage of this bit of code to 100%? I think it's worth it for core code like printing and parsing of builtins.
author_association: MEMBER
user: login: erick-xanadu
created_at: 2025-05-10T14:55:20Z
body: I'm still missing a little bit of work here, but I'll address it later today. No need to merge until this is done. Thanks!
author_association: CONTRIBUTOR
user: login: superlopuh
created_at: 2025-05-11T07:03:40Z
body: Looking great, thank you!
author_association: MEMBER
user: login: erick-xanadu
created_at: 2025-05-12T15:48:45Z
body: I'm having some issues with pyright again. From CI. 

> home/runner/work/xdsl/xdsl/tests/test_printer.py:798:40 - error: Argument of type "ComplexType[Float32Type]" cannot be assigned to parameter "type" of type "ComplexType[AnyFloat | IntegerType]" in function "print_complex_float"

Even when narrowing the type:

```
@@ -341,7 +341,7 @@ class Printer(BasePrinter):
     def print_float_attr(self, attribute: FloatAttr):
         self.print_float(attribute.value.data, attribute.type)
 
-    def print_complex_float(self, value: tuple[float, float], type: ComplexType):
+    def print_complex_float(self, value: tuple[float, float], type: ComplexType[AnyFloat]):
         assert isinstance(type.element_type, AnyFloat)
         self.print_string("(")
         real, imag = value[0], value[1]
```

I'll stop working on this for the time being while I work on some other stuff. Thanks for the help so far!
author_association: CONTRIBUTOR
user: login: superlopuh
created_at: 2025-05-12T21:07:11Z
body: CC @math-fehr 
author_association: MEMBER