Skip to content

Index

irdl

I32: TypeAlias = IntegerType[Literal[32], Literal[Signedness.SIGNLESS]] module-attribute

IRDL = Dialect('irdl', [DialectOp, TypeOp, CPredOp, AttributeOp, BaseOp, ParametersOp, OperationOp, OperandsOp, ResultsOp, AttributesOp, RegionsOp, IsOp, ParametricOp, RegionOp, AnyOp, AnyOfOp, AllOfOp], [AttributeType, RegionType, VariadicityAttr, VariadicityArrayAttr]) module-attribute

ArrayAttr

Bases: _BuiltinGenericData[tuple[AttributeCovT, ...]], Iterable[AttributeCovT], Generic[AttributeCovT]

Source code in xdsl/dialects/builtin.py
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
@irdl_attr_definition
class ArrayAttr(
    _BuiltinGenericData[tuple[AttributeCovT, ...]],
    Iterable[AttributeCovT],
    Generic[AttributeCovT],
):
    name = "array"

    def __init__(self, param: Iterable[AttributeCovT]) -> None:
        super().__init__(tuple(param))

    def print_builtin(self, printer: Printer):
        with printer.in_square_brackets():
            printer.print_list(self.data, printer.print_attribute)

    @staticmethod
    @override
    def constr(
        constr: IRDLAttrConstraint[AttributeInvT]
        | RangeConstraint[AttributeInvT]
        | None = None,
    ) -> ArrayOfConstraint[AttributeInvT]:
        return ArrayOfConstraint[AttributeInvT](
            cast(
                AttrConstraint[AttributeInvT],
                TypeVarConstraint(AttributeCovT, AnyAttr()),
            )
            if constr is None
            else constr
        )

    def __len__(self):
        return len(self.data)

    def __iter__(self) -> Iterator[AttributeCovT]:
        return iter(self.data)

name = 'array' class-attribute instance-attribute

__init__(param: Iterable[AttributeCovT]) -> None

Source code in xdsl/dialects/builtin.py
187
188
def __init__(self, param: Iterable[AttributeCovT]) -> None:
    super().__init__(tuple(param))

print_builtin(printer: Printer)

Source code in xdsl/dialects/builtin.py
190
191
192
def print_builtin(self, printer: Printer):
    with printer.in_square_brackets():
        printer.print_list(self.data, printer.print_attribute)

constr(constr: IRDLAttrConstraint[AttributeInvT] | RangeConstraint[AttributeInvT] | None = None) -> ArrayOfConstraint[AttributeInvT] staticmethod

Source code in xdsl/dialects/builtin.py
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
@staticmethod
@override
def constr(
    constr: IRDLAttrConstraint[AttributeInvT]
    | RangeConstraint[AttributeInvT]
    | None = None,
) -> ArrayOfConstraint[AttributeInvT]:
    return ArrayOfConstraint[AttributeInvT](
        cast(
            AttrConstraint[AttributeInvT],
            TypeVarConstraint(AttributeCovT, AnyAttr()),
        )
        if constr is None
        else constr
    )

__len__()

Source code in xdsl/dialects/builtin.py
210
211
def __len__(self):
    return len(self.data)

__iter__() -> Iterator[AttributeCovT]

Source code in xdsl/dialects/builtin.py
213
214
def __iter__(self) -> Iterator[AttributeCovT]:
    return iter(self.data)

IntegerAttr

Bases: BuiltinAttribute, TypedAttribute, Generic[_IntegerAttrType]

Source code in xdsl/dialects/builtin.py
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
@irdl_attr_definition
class IntegerAttr(
    BuiltinAttribute,
    TypedAttribute,
    Generic[_IntegerAttrType],
):
    name = "integer"
    value: IntAttr
    type: _IntegerAttrType

    @overload
    def __init__(
        self,
        value: int | IntAttr,
        value_type: _IntegerAttrType,
        *,
        truncate_bits: bool = False,
    ) -> None: ...

    @overload
    def __init__(
        self: IntegerAttr[IntegerType[IntCovT, Literal[Signedness.SIGNLESS]]],
        value: int | IntAttr,
        value_type: IntCovT,
        *,
        truncate_bits: bool = False,
    ) -> None: ...

    def __init__(
        self,
        value: int | IntAttr,
        value_type: IntCovT | IntegerType[IntCovT] | IndexType,
        *,
        truncate_bits: bool = False,
    ) -> None:
        if isinstance(value_type, int):
            value_type = IntegerType(value_type)
        if not isinstance(value, int):
            value = value.data
        if not isinstance(value_type, IndexType):
            normalized_value = value_type.normalized_value(
                value, truncate_bits=truncate_bits
            )
            if normalized_value is not None:
                value = normalized_value
        super().__init__(IntAttr(value), value_type)

    @staticmethod
    def from_int_and_width(
        value: int, width: IntCovT
    ) -> IntegerAttr[IntegerType[IntCovT, Literal[Signedness.SIGNLESS]]]:
        return IntegerAttr(value, width)

    @staticmethod
    def from_index_int_value(value: int) -> IntegerAttr[IndexType]:
        return IntegerAttr(value, IndexType())

    @staticmethod
    def from_bool(value: bool) -> BoolAttr:
        return IntegerAttr(value, 1)

    def print_builtin(self, printer: Printer) -> None:
        ty = self.type
        printer.print_int(self.value.data, ty)
        if ty != i1:
            printer.print_string(" : ")
            printer.print_attribute(ty)

    def verify(self) -> None:
        if isinstance(int_type := self.type, IndexType):
            return

        int_type.verify_value(self.value.data)

    @staticmethod
    def parse_with_type(
        parser: AttrParser,
        type: Attribute,
    ) -> TypedAttribute:
        assert isa(type, IntegerType | IndexType)
        return IntegerAttr(parser.parse_integer(allow_boolean=(type == i1)), type)

    def print_without_type(self, printer: Printer):
        printer.print_int(self.value.data, self.type)

    def get_type(self) -> Attribute:
        return self.type

    @staticmethod
    def constr(
        type: IRDLAttrConstraint[_IntegerAttrType] = IntegerAttrTypeConstr,
        *,
        value: AttrConstraint | IntConstraint | None = None,
    ) -> AttrConstraint[IntegerAttr[_IntegerAttrType]]:
        if value is None and type == AnyAttr():
            return BaseAttr[IntegerAttr[_IntegerAttrType]](IntegerAttr)
        if isinstance(value, IntConstraint):
            value = IntAttrConstraint(value)
        return ParamAttrConstraint[IntegerAttr[_IntegerAttrType]](
            IntegerAttr,
            (
                value,
                type,
            ),
        )

    def __bool__(self) -> bool:
        """Returns True if value is non-zero."""
        return bool(self.value)

    @staticmethod
    def iter_unpack(
        type: _IntegerAttrTypeInvT, buffer: ReadableBuffer, /
    ) -> Iterator[IntegerAttr[_IntegerAttrTypeInvT]]:
        """
        Yields unpacked values one at a time, starting at the beginning of the buffer.
        """
        for value in type.iter_unpack(buffer):
            yield IntegerAttr(value, type)

    @staticmethod
    def unpack(
        type: _IntegerAttrTypeInvT, buffer: ReadableBuffer, num: int, /
    ) -> tuple[IntegerAttr[_IntegerAttrTypeInvT], ...]:
        """
        Unpack `num` values from the beginning of the buffer.
        """
        return tuple(IntegerAttr(value, type) for value in type.unpack(buffer, num))

name = 'integer' class-attribute instance-attribute

value: IntAttr instance-attribute

type: _IntegerAttrType instance-attribute

__init__(value: int | IntAttr, value_type: IntCovT | IntegerType[IntCovT] | IndexType, *, truncate_bits: bool = False) -> None

__init__(
    value: int | IntAttr,
    value_type: _IntegerAttrType,
    *,
    truncate_bits: bool = False,
) -> None
__init__(
    value: int | IntAttr,
    value_type: IntCovT,
    *,
    truncate_bits: bool = False,
) -> None
Source code in xdsl/dialects/builtin.py
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
def __init__(
    self,
    value: int | IntAttr,
    value_type: IntCovT | IntegerType[IntCovT] | IndexType,
    *,
    truncate_bits: bool = False,
) -> None:
    if isinstance(value_type, int):
        value_type = IntegerType(value_type)
    if not isinstance(value, int):
        value = value.data
    if not isinstance(value_type, IndexType):
        normalized_value = value_type.normalized_value(
            value, truncate_bits=truncate_bits
        )
        if normalized_value is not None:
            value = normalized_value
    super().__init__(IntAttr(value), value_type)

from_int_and_width(value: int, width: IntCovT) -> IntegerAttr[IntegerType[IntCovT, Literal[Signedness.SIGNLESS]]] staticmethod

Source code in xdsl/dialects/builtin.py
927
928
929
930
931
@staticmethod
def from_int_and_width(
    value: int, width: IntCovT
) -> IntegerAttr[IntegerType[IntCovT, Literal[Signedness.SIGNLESS]]]:
    return IntegerAttr(value, width)

from_index_int_value(value: int) -> IntegerAttr[IndexType] staticmethod

Source code in xdsl/dialects/builtin.py
933
934
935
@staticmethod
def from_index_int_value(value: int) -> IntegerAttr[IndexType]:
    return IntegerAttr(value, IndexType())

from_bool(value: bool) -> BoolAttr staticmethod

Source code in xdsl/dialects/builtin.py
937
938
939
@staticmethod
def from_bool(value: bool) -> BoolAttr:
    return IntegerAttr(value, 1)

print_builtin(printer: Printer) -> None

Source code in xdsl/dialects/builtin.py
941
942
943
944
945
946
def print_builtin(self, printer: Printer) -> None:
    ty = self.type
    printer.print_int(self.value.data, ty)
    if ty != i1:
        printer.print_string(" : ")
        printer.print_attribute(ty)

verify() -> None

Source code in xdsl/dialects/builtin.py
948
949
950
951
952
def verify(self) -> None:
    if isinstance(int_type := self.type, IndexType):
        return

    int_type.verify_value(self.value.data)

parse_with_type(parser: AttrParser, type: Attribute) -> TypedAttribute staticmethod

Source code in xdsl/dialects/builtin.py
954
955
956
957
958
959
960
@staticmethod
def parse_with_type(
    parser: AttrParser,
    type: Attribute,
) -> TypedAttribute:
    assert isa(type, IntegerType | IndexType)
    return IntegerAttr(parser.parse_integer(allow_boolean=(type == i1)), type)

print_without_type(printer: Printer)

Source code in xdsl/dialects/builtin.py
962
963
def print_without_type(self, printer: Printer):
    printer.print_int(self.value.data, self.type)

get_type() -> Attribute

Source code in xdsl/dialects/builtin.py
965
966
def get_type(self) -> Attribute:
    return self.type

constr(type: IRDLAttrConstraint[_IntegerAttrType] = IntegerAttrTypeConstr, *, value: AttrConstraint | IntConstraint | None = None) -> AttrConstraint[IntegerAttr[_IntegerAttrType]] staticmethod

Source code in xdsl/dialects/builtin.py
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
@staticmethod
def constr(
    type: IRDLAttrConstraint[_IntegerAttrType] = IntegerAttrTypeConstr,
    *,
    value: AttrConstraint | IntConstraint | None = None,
) -> AttrConstraint[IntegerAttr[_IntegerAttrType]]:
    if value is None and type == AnyAttr():
        return BaseAttr[IntegerAttr[_IntegerAttrType]](IntegerAttr)
    if isinstance(value, IntConstraint):
        value = IntAttrConstraint(value)
    return ParamAttrConstraint[IntegerAttr[_IntegerAttrType]](
        IntegerAttr,
        (
            value,
            type,
        ),
    )

__bool__() -> bool

Returns True if value is non-zero.

Source code in xdsl/dialects/builtin.py
986
987
988
def __bool__(self) -> bool:
    """Returns True if value is non-zero."""
    return bool(self.value)

iter_unpack(type: _IntegerAttrTypeInvT, buffer: ReadableBuffer) -> Iterator[IntegerAttr[_IntegerAttrTypeInvT]] staticmethod

Yields unpacked values one at a time, starting at the beginning of the buffer.

Source code in xdsl/dialects/builtin.py
990
991
992
993
994
995
996
997
998
@staticmethod
def iter_unpack(
    type: _IntegerAttrTypeInvT, buffer: ReadableBuffer, /
) -> Iterator[IntegerAttr[_IntegerAttrTypeInvT]]:
    """
    Yields unpacked values one at a time, starting at the beginning of the buffer.
    """
    for value in type.iter_unpack(buffer):
        yield IntegerAttr(value, type)

unpack(type: _IntegerAttrTypeInvT, buffer: ReadableBuffer, num: int) -> tuple[IntegerAttr[_IntegerAttrTypeInvT], ...] staticmethod

Unpack num values from the beginning of the buffer.

Source code in xdsl/dialects/builtin.py
1000
1001
1002
1003
1004
1005
1006
1007
@staticmethod
def unpack(
    type: _IntegerAttrTypeInvT, buffer: ReadableBuffer, num: int, /
) -> tuple[IntegerAttr[_IntegerAttrTypeInvT], ...]:
    """
    Unpack `num` values from the beginning of the buffer.
    """
    return tuple(IntegerAttr(value, type) for value in type.unpack(buffer, num))

StringAttr dataclass

Bases: _BuiltinData[str]

Source code in xdsl/dialects/builtin.py
269
270
271
272
273
274
@irdl_attr_definition
class StringAttr(_BuiltinData[str]):
    name = "string"

    def print_builtin(self, printer: Printer):
        printer.print_string_literal(self.data)

name = 'string' class-attribute instance-attribute

print_builtin(printer: Printer)

Source code in xdsl/dialects/builtin.py
273
274
def print_builtin(self, printer: Printer):
    printer.print_string_literal(self.data)

SymbolNameConstraint dataclass

Bases: AttrConstraint[StringAttr]

Constrain an attribute to be a StringAttr. This constraint has special assembly format support.

Source code in xdsl/dialects/builtin.py
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
@dataclass(frozen=True)
class SymbolNameConstraint(AttrConstraint[StringAttr]):
    """
    Constrain an attribute to be a StringAttr.
    This constraint has special assembly format support.
    """

    def verify(
        self,
        attr: Attribute,
        constraint_context: ConstraintContext,
    ) -> None:
        if not isinstance(attr, StringAttr):
            raise VerifyException(f"{attr} should be a string")

    def get_bases(self) -> set[type[Attribute]] | None:
        return {StringAttr}

    def mapping_type_vars(
        self, type_var_mapping: Mapping[TypeVar, AttrConstraint | IntConstraint]
    ) -> AttrConstraint[StringAttr]:
        return self

verify(attr: Attribute, constraint_context: ConstraintContext) -> None

Source code in xdsl/dialects/builtin.py
284
285
286
287
288
289
290
def verify(
    self,
    attr: Attribute,
    constraint_context: ConstraintContext,
) -> None:
    if not isinstance(attr, StringAttr):
        raise VerifyException(f"{attr} should be a string")

get_bases() -> set[type[Attribute]] | None

Source code in xdsl/dialects/builtin.py
292
293
def get_bases(self) -> set[type[Attribute]] | None:
    return {StringAttr}

mapping_type_vars(type_var_mapping: Mapping[TypeVar, AttrConstraint | IntConstraint]) -> AttrConstraint[StringAttr]

Source code in xdsl/dialects/builtin.py
295
296
297
298
def mapping_type_vars(
    self, type_var_mapping: Mapping[TypeVar, AttrConstraint | IntConstraint]
) -> AttrConstraint[StringAttr]:
    return self

__init__() -> None

SymbolRefAttr

Bases: ParametrizedAttribute, BuiltinAttribute

Source code in xdsl/dialects/builtin.py
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
@irdl_attr_definition
class SymbolRefAttr(ParametrizedAttribute, BuiltinAttribute):
    name = "symbol_ref"
    root_reference: StringAttr
    nested_references: ArrayAttr[StringAttr]

    def __init__(
        self,
        root: str | StringAttr,
        nested: Sequence[str] | Sequence[StringAttr] | ArrayAttr[StringAttr] = [],
    ) -> None:
        if isinstance(root, str):
            root = StringAttr(root)
        if not isinstance(nested, ArrayAttr):
            nested = ArrayAttr(
                [StringAttr(x) if isinstance(x, str) else x for x in nested]
            )
        super().__init__(root, nested)

    def string_value(self):
        root = self.root_reference.data
        for ref in self.nested_references.data:
            root += "." + ref.data
        return root

    def print_builtin(self, printer: Printer):
        printer.print_symbol_name(self.root_reference.data)
        for ref in self.nested_references.data:
            printer.print_string("::")
            printer.print_symbol_name(ref.data)

name = 'symbol_ref' class-attribute instance-attribute

root_reference: StringAttr instance-attribute

nested_references: ArrayAttr[StringAttr] instance-attribute

__init__(root: str | StringAttr, nested: Sequence[str] | Sequence[StringAttr] | ArrayAttr[StringAttr] = []) -> None

Source code in xdsl/dialects/builtin.py
315
316
317
318
319
320
321
322
323
324
325
326
def __init__(
    self,
    root: str | StringAttr,
    nested: Sequence[str] | Sequence[StringAttr] | ArrayAttr[StringAttr] = [],
) -> None:
    if isinstance(root, str):
        root = StringAttr(root)
    if not isinstance(nested, ArrayAttr):
        nested = ArrayAttr(
            [StringAttr(x) if isinstance(x, str) else x for x in nested]
        )
    super().__init__(root, nested)

string_value()

Source code in xdsl/dialects/builtin.py
328
329
330
331
332
def string_value(self):
    root = self.root_reference.data
    for ref in self.nested_references.data:
        root += "." + ref.data
    return root

print_builtin(printer: Printer)

Source code in xdsl/dialects/builtin.py
334
335
336
337
338
def print_builtin(self, printer: Printer):
    printer.print_symbol_name(self.root_reference.data)
    for ref in self.nested_references.data:
        printer.print_string("::")
        printer.print_symbol_name(ref.data)

UnitAttr dataclass

Bases: ParametrizedAttribute, BuiltinAttribute

Source code in xdsl/dialects/builtin.py
776
777
778
779
780
781
@irdl_attr_definition
class UnitAttr(ParametrizedAttribute, BuiltinAttribute):
    name = "unit"

    def print_builtin(self, printer: Printer) -> None:
        printer.print_string("unit")

name = 'unit' class-attribute instance-attribute

print_builtin(printer: Printer) -> None

Source code in xdsl/dialects/builtin.py
780
781
def print_builtin(self, printer: Printer) -> None:
    printer.print_string("unit")

Attribute dataclass

Bases: ABC

A compile-time value. Attributes are used to represent SSA variable types, and can be attached on operations to give extra information.

Source code in xdsl/ir/core.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
@dataclass(frozen=True)
class Attribute(ABC):
    """
    A compile-time value.
    Attributes are used to represent SSA variable types, and can be attached
    on operations to give extra information.
    """

    name: ClassVar[str] = field(init=False, repr=False)
    """The attribute name should be a static field in the attribute classes."""

    def __post_init__(self):
        self._verify()
        if not isinstance(self, Data | ParametrizedAttribute):
            raise TypeError("Attributes should only be Data or ParametrizedAttribute")

    def _verify(self):
        self.verify()

    def verify(self) -> None:
        """
        Check that the attribute parameters satisfy the expected invariants.
        Raise a VerifyException otherwise.
        """
        pass

    def __str__(self) -> str:
        from xdsl.printer import Printer

        res = StringIO()
        printer = Printer(stream=res)
        printer.print_attribute(self)
        return res.getvalue()

name: str = field(init=False, repr=False) class-attribute

The attribute name should be a static field in the attribute classes.

__post_init__()

Source code in xdsl/ir/core.py
115
116
117
118
def __post_init__(self):
    self._verify()
    if not isinstance(self, Data | ParametrizedAttribute):
        raise TypeError("Attributes should only be Data or ParametrizedAttribute")

verify() -> None

Check that the attribute parameters satisfy the expected invariants. Raise a VerifyException otherwise.

Source code in xdsl/ir/core.py
123
124
125
126
127
128
def verify(self) -> None:
    """
    Check that the attribute parameters satisfy the expected invariants.
    Raise a VerifyException otherwise.
    """
    pass

__str__() -> str

Source code in xdsl/ir/core.py
130
131
132
133
134
135
136
def __str__(self) -> str:
    from xdsl.printer import Printer

    res = StringIO()
    printer = Printer(stream=res)
    printer.print_attribute(self)
    return res.getvalue()

__init__() -> None

Block dataclass

Bases: _IRNode, IRWithUses, IRWithName

A sequence of operations

Source code in xdsl/ir/core.py
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
@dataclass(init=False, eq=False, unsafe_hash=False)
class Block(_IRNode, IRWithUses, IRWithName):
    """A sequence of operations"""

    _args: tuple[BlockArgument, ...]
    """The basic block arguments."""

    _first_op: Operation | None = field(repr=False)
    _last_op: Operation | None = field(repr=False)

    _next_block: Block | None = field(default=None, repr=False)
    _prev_block: Block | None = field(default=None, repr=False)

    parent: Region | None = field(default=None, repr=False)
    """Parent region containing the block."""

    @staticmethod
    def is_default_block_name(name: str) -> bool:
        """Check if a name matches the default block naming pattern (bb followed by digits)."""
        return name.startswith("bb") and name[2:].isdigit() and name[2:] != ""

    def __init__(
        self,
        ops: Iterable[Operation] = (),
        *,
        arg_types: Iterable[Attribute] = (),
    ):
        super().__init__()
        self._args = tuple(
            BlockArgument(arg_type, self, index)
            for index, arg_type in enumerate(arg_types)
        )
        self._first_op = None
        self._last_op = None

        self.add_ops(ops)

    @property
    def arg_types(self) -> Sequence[Attribute]:
        return tuple(arg.type for arg in self._args)

    @property
    def parent_node(self) -> IRNode | None:
        return self.parent

    @property
    def ops(self) -> BlockOps:
        """Returns a multi-pass Iterable of this block's operations."""
        return BlockOps(self)

    @property
    def next_block(self) -> Block | None:
        """The next block in the parent region"""
        return self._next_block

    @property
    def prev_block(self) -> Block | None:
        """The previous block in the parent region"""
        return self._prev_block

    def predecessors(self) -> tuple[Block, ...]:
        return tuple(
            p for use in self.uses if (p := use.operation.parent_block()) is not None
        )

    def parent_op(self) -> Operation | None:
        return self.parent.parent if self.parent else None

    def parent_region(self) -> Region | None:
        return self.parent

    def parent_block(self) -> Block | None:
        return self.parent.parent.parent if self.parent and self.parent.parent else None

    def __repr__(self) -> str:
        return f"<Block {id(self)}(_args={repr(self._args)}, num_ops={len(self.ops)})>"

    @property
    def args(self) -> tuple[BlockArgument, ...]:
        """Returns the block arguments."""
        return self._args

    def insert_arg(self, arg_type: Attribute, index: int) -> BlockArgument:
        """
        Insert a new argument with a given type to the arguments list at a specific index.
        Returns the new argument.
        """
        if index < 0 or index > len(self._args):
            raise ValueError("Unexpected index")
        new_arg = BlockArgument(arg_type, self, index)
        for arg in self._args[index:]:
            arg.index += 1
        self._args = tuple(chain(self._args[:index], [new_arg], self._args[index:]))
        return new_arg

    def erase_arg(self, arg: BlockArgument, safe_erase: bool = True) -> None:
        """
        Erase a block argument.
        If safe_erase is True, check that the block argument is not used.
        If safe_erase is False, replace the block argument uses with an ErasedSSAVAlue.
        """
        if arg.block is not self:
            raise ValueError("Attempting to delete an argument of the wrong block")
        for block_arg in self._args[arg.index + 1 :]:
            block_arg.index -= 1
        self._args = tuple(chain(self._args[: arg.index], self._args[arg.index + 1 :]))
        arg.erase(safe_erase=safe_erase)

    def _attach_op(self, operation: Operation) -> None:
        """Attach an operation to the block, and check that it has no parents."""
        if operation.parent:
            raise ValueError(
                "Can't add to a block an operation already attached to a block."
            )
        if operation.is_ancestor(self):
            raise ValueError(
                "Can't add an operation to a block contained in the operation."
            )
        operation.parent = self

    @property
    def is_empty(self) -> bool:
        """Returns `True` if there are no operations in this block."""
        return self._first_op is None

    @property
    def first_op(self) -> Operation | None:
        """The first operation in this block."""
        return self._first_op

    @property
    def last_op(self) -> Operation | None:
        """The last operation in this block."""
        return self._last_op

    def insert_op_after(self, new_op: Operation, existing_op: Operation) -> None:
        """
        Inserts `new_op` into this block, after `existing_op`.
        `new_op` should not be attached to a block.
        """
        if existing_op.parent is not self:
            raise ValueError(
                "Can't insert operation after operation not in this block."
            )

        self._attach_op(new_op)

        next_op = existing_op.next_op
        existing_op._insert_next_op(new_op)  # pyright: ignore[reportPrivateUsage]
        if next_op is None:
            # No `next_op`, means `prev_op` is the last op in the block.
            self._last_op = new_op

    def insert_op_before(self, new_op: Operation, existing_op: Operation) -> None:
        """
        Inserts `new_op` into this block, before `existing_op`.
        `new_op` should not be attached to a block.
        """
        if existing_op.parent is not self:
            raise ValueError(
                "Can't insert operation before operation not in current block"
            )

        self._attach_op(new_op)

        prev_op = existing_op.prev_op
        existing_op._insert_prev_op(new_op)  # pyright: ignore[reportPrivateUsage]
        if prev_op is None:
            # No `prev_op`, means `next_op` is the first op in the block.
            self._first_op = new_op

    def add_op(self, operation: Operation) -> None:
        """
        Add an operation at the end of the block.
        The operation should not be attached to another block already.
        """
        if self._last_op is None:
            self._attach_op(operation)
            self._first_op = operation
            self._last_op = operation
        else:
            self.insert_op_after(operation, self._last_op)

    def add_ops(self, ops: Iterable[Operation]) -> None:
        """
        Add operations at the end of the block.
        The operations should not be attached to another block.
        """
        for op in ops:
            self.add_op(op)

    def insert_ops_before(
        self, ops: Sequence[Operation], existing_op: Operation
    ) -> None:
        for op in ops:
            self.insert_op_before(op, existing_op)

    def insert_ops_after(
        self, ops: Sequence[Operation], existing_op: Operation
    ) -> None:
        for op in ops:
            self.insert_op_after(op, existing_op)

            existing_op = op

    def split_before(
        self,
        b_first: Operation,
        *,
        arg_types: Iterable[Attribute] = (),
    ) -> Block:
        """
        Split the block into two blocks before the specified operation.

        Note that all operations before the one given stay as part of the original basic
        block, and the rest of the operations in the original block are moved to the new
        block, including the old terminator.
        The original block is left without a terminator.
        The newly formed block is inserted into the parent region immediately after `self`
        and returned.
        """
        # Use `a` for new contents of `self`, and `b` for new block.
        if b_first.parent is not self:
            raise ValueError("Cannot split block on operation outside of the block.")

        parent = self.parent
        if parent is None:
            raise ValueError("Cannot split block with no parent.")

        first_of_self = self._first_op
        assert first_of_self is not None

        last_of_self = self._last_op
        assert last_of_self is not None

        a_last = b_first.prev_op
        b_last = last_of_self
        if a_last is None:
            # `before` is the first op in the Block, so all the ops move to the new block
            a_first = None
        else:
            a_first = first_of_self

        # Update first and last ops of self
        self._first_op = a_first
        self._last_op = a_last

        b = Block(arg_types=arg_types)
        a_index = parent.get_block_index(self)
        parent.insert_block(b, a_index + 1)

        b._first_op = b_first
        b._last_op = b_last

        # Update parent for moved ops
        b_iter: Operation | None = b_first
        while b_iter is not None:
            b_iter.parent = b
            b_iter = b_iter.next_op

        # Update next op for self.last
        if a_last is not None:
            a_last._next_op = None  # pyright: ignore[reportPrivateUsage]

        # Update previous op for b.first
        b_first._prev_op = None  # pyright: ignore[reportPrivateUsage]

        return b

    def get_operation_index(self, op: Operation) -> int:
        """Get the operation position in a block."""
        if op.parent is not self:
            raise ValueError("Operation is not a child of the block.")
        return next(idx for idx, block_op in enumerate(self.ops) if block_op is op)

    def detach_op(self, op: Operation) -> Operation:
        """
        Detach an operation from the block.
        Returns the detached operation.
        """
        if op.parent is not self:
            raise ValueError("Cannot detach operation from a different block.")
        op.parent = None

        prev_op = op.prev_op
        next_op = op.next_op

        if prev_op is not None:
            # detach op from linked list
            prev_op._next_op = next_op  # pyright: ignore[reportPrivateUsage]
            # detach linked list from op
            op._prev_op = None  # pyright: ignore[reportPrivateUsage]
        else:
            # reattach linked list if op is first op this block
            assert self._first_op is op
            self._first_op = next_op

        if next_op is not None:
            # detach op from linked list
            next_op._prev_op = prev_op  # pyright: ignore[reportPrivateUsage]
            # detach linked list from op
            op._next_op = None  # pyright: ignore[reportPrivateUsage]
        else:
            # reattach linked list if op is last op in this block
            assert self._last_op is op
            self._last_op = prev_op

        return op

    def erase_op(self, op: Operation, safe_erase: bool = True) -> None:
        """
        Erase an operation from the block.
        If safe_erase is True, check that the operation has no uses.
        """
        op = self.detach_op(op)
        op.erase(safe_erase=safe_erase)

    def walk(
        self, *, reverse: bool = False, region_first: bool = False
    ) -> Iterable[Operation]:
        """
        Iterate over all operations contained in the block.
        If region_first is set, then the operation regions are iterated before the
        operation. If reverse is set, then the region, block, and operation lists are
        iterated in reverse order.
        """
        for op in reversed(self.ops) if reverse else self.ops:
            yield from op.walk(reverse=reverse, region_first=region_first)

    def walk_blocks(self, *, reverse: bool = False) -> Iterator[Block]:
        """
        Iterate over all the blocks nested within this block, including self, in the
        order in which they are printed in the IR.
        Iterate in reverse order if reverse is True.
        """
        if not reverse:
            yield self
        for op in reversed(self.ops) if reverse else self.ops:
            yield from op.walk_blocks(reverse=reverse)
        if reverse:
            yield self

    def verify(self) -> None:
        for operation in self.ops:
            if operation.parent != self:
                raise ValueError(
                    "Parent pointer of operation does not refer to containing region"
                )
            operation.verify()

        if len(self.ops) == 0:
            if (region_parent := self.parent) is not None and (
                parent_op := region_parent.parent
            ) is not None:
                if len(region_parent.blocks) == 1 and not parent_op.has_trait(
                    NoTerminator
                ):
                    raise VerifyException(
                        f"Operation {parent_op.name} contains empty block in "
                        "single-block region that expects at least a terminator"
                    )

    def drop_all_references(self) -> None:
        """
        Drop all references to other operations.
        This function is called prior to deleting a block.
        """
        self.parent = None
        self._next_block = None
        self._prev_block = None
        for op in self.ops:
            op.drop_all_references()

    def find_ancestor_op_in_block(self, op: Operation) -> Operation | None:
        """
        Traverse up the operation hierarchy starting from op to find the ancestor
        operation that resides in the block.

        Returns None if no ancestor is found.
        """
        curr_op = op
        while curr_op.parent_block() != self:
            if (curr_op := curr_op.parent_op()) is None:
                return None

        return curr_op

    def erase(self, safe_erase: bool = True) -> None:
        """
        Erase the block, and remove all its references to other operations.
        If safe_erase is specified, check that no operation results are used outside
        the block.
        """
        assert self.parent is None, (
            "Blocks with parents should first be detached " + "before erasure."
        )
        self.drop_all_references()
        for op in self.ops:
            op.erase(safe_erase=safe_erase, drop_references=False)

    def is_structurally_equivalent(
        self,
        other: IRNode,
        context: dict[IRNode | SSAValue, IRNode | SSAValue] | None = None,
    ) -> bool:
        """
        Check if two blocks are structurally equivalent.
        The context is a mapping of IR nodes to IR nodes that are already known
        to be equivalent. This enables checking whether the use dependencies and
        successors are equivalent.
        """
        if context is None:
            context = {}
        if not isinstance(other, Block):
            return False
        if len(self.args) != len(other.args) or len(self.ops) != len(other.ops):
            return False
        for arg, other_arg in zip(self.args, other.args):
            if arg.type != other_arg.type:
                return False
            context[arg] = other_arg
        # Add self to the context so Operations can check for identical parents
        context[self] = other
        if not all(
            op.is_structurally_equivalent(other_op, context)
            for op, other_op in zip(self.ops, other.ops)
        ):
            return False

        return True

parent: Region | None = field(default=None, repr=False) class-attribute instance-attribute

Parent region containing the block.

arg_types: Sequence[Attribute] property

parent_node: IRNode | None property

ops: BlockOps property

Returns a multi-pass Iterable of this block's operations.

next_block: Block | None property

The next block in the parent region

prev_block: Block | None property

The previous block in the parent region

args: tuple[BlockArgument, ...] property

Returns the block arguments.

is_empty: bool property

Returns True if there are no operations in this block.

first_op: Operation | None property

The first operation in this block.

last_op: Operation | None property

The last operation in this block.

is_default_block_name(name: str) -> bool staticmethod

Check if a name matches the default block naming pattern (bb followed by digits).

Source code in xdsl/ir/core.py
1750
1751
1752
1753
@staticmethod
def is_default_block_name(name: str) -> bool:
    """Check if a name matches the default block naming pattern (bb followed by digits)."""
    return name.startswith("bb") and name[2:].isdigit() and name[2:] != ""

__init__(ops: Iterable[Operation] = (), *, arg_types: Iterable[Attribute] = ())

Source code in xdsl/ir/core.py
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
def __init__(
    self,
    ops: Iterable[Operation] = (),
    *,
    arg_types: Iterable[Attribute] = (),
):
    super().__init__()
    self._args = tuple(
        BlockArgument(arg_type, self, index)
        for index, arg_type in enumerate(arg_types)
    )
    self._first_op = None
    self._last_op = None

    self.add_ops(ops)

predecessors() -> tuple[Block, ...]

Source code in xdsl/ir/core.py
1794
1795
1796
1797
def predecessors(self) -> tuple[Block, ...]:
    return tuple(
        p for use in self.uses if (p := use.operation.parent_block()) is not None
    )

parent_op() -> Operation | None

Source code in xdsl/ir/core.py
1799
1800
def parent_op(self) -> Operation | None:
    return self.parent.parent if self.parent else None

parent_region() -> Region | None

Source code in xdsl/ir/core.py
1802
1803
def parent_region(self) -> Region | None:
    return self.parent

parent_block() -> Block | None

Source code in xdsl/ir/core.py
1805
1806
def parent_block(self) -> Block | None:
    return self.parent.parent.parent if self.parent and self.parent.parent else None

__repr__() -> str

Source code in xdsl/ir/core.py
1808
1809
def __repr__(self) -> str:
    return f"<Block {id(self)}(_args={repr(self._args)}, num_ops={len(self.ops)})>"

insert_arg(arg_type: Attribute, index: int) -> BlockArgument

Insert a new argument with a given type to the arguments list at a specific index. Returns the new argument.

Source code in xdsl/ir/core.py
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
def insert_arg(self, arg_type: Attribute, index: int) -> BlockArgument:
    """
    Insert a new argument with a given type to the arguments list at a specific index.
    Returns the new argument.
    """
    if index < 0 or index > len(self._args):
        raise ValueError("Unexpected index")
    new_arg = BlockArgument(arg_type, self, index)
    for arg in self._args[index:]:
        arg.index += 1
    self._args = tuple(chain(self._args[:index], [new_arg], self._args[index:]))
    return new_arg

erase_arg(arg: BlockArgument, safe_erase: bool = True) -> None

Erase a block argument. If safe_erase is True, check that the block argument is not used. If safe_erase is False, replace the block argument uses with an ErasedSSAVAlue.

Source code in xdsl/ir/core.py
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
def erase_arg(self, arg: BlockArgument, safe_erase: bool = True) -> None:
    """
    Erase a block argument.
    If safe_erase is True, check that the block argument is not used.
    If safe_erase is False, replace the block argument uses with an ErasedSSAVAlue.
    """
    if arg.block is not self:
        raise ValueError("Attempting to delete an argument of the wrong block")
    for block_arg in self._args[arg.index + 1 :]:
        block_arg.index -= 1
    self._args = tuple(chain(self._args[: arg.index], self._args[arg.index + 1 :]))
    arg.erase(safe_erase=safe_erase)

insert_op_after(new_op: Operation, existing_op: Operation) -> None

Inserts new_op into this block, after existing_op. new_op should not be attached to a block.

Source code in xdsl/ir/core.py
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
def insert_op_after(self, new_op: Operation, existing_op: Operation) -> None:
    """
    Inserts `new_op` into this block, after `existing_op`.
    `new_op` should not be attached to a block.
    """
    if existing_op.parent is not self:
        raise ValueError(
            "Can't insert operation after operation not in this block."
        )

    self._attach_op(new_op)

    next_op = existing_op.next_op
    existing_op._insert_next_op(new_op)  # pyright: ignore[reportPrivateUsage]
    if next_op is None:
        # No `next_op`, means `prev_op` is the last op in the block.
        self._last_op = new_op

insert_op_before(new_op: Operation, existing_op: Operation) -> None

Inserts new_op into this block, before existing_op. new_op should not be attached to a block.

Source code in xdsl/ir/core.py
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
def insert_op_before(self, new_op: Operation, existing_op: Operation) -> None:
    """
    Inserts `new_op` into this block, before `existing_op`.
    `new_op` should not be attached to a block.
    """
    if existing_op.parent is not self:
        raise ValueError(
            "Can't insert operation before operation not in current block"
        )

    self._attach_op(new_op)

    prev_op = existing_op.prev_op
    existing_op._insert_prev_op(new_op)  # pyright: ignore[reportPrivateUsage]
    if prev_op is None:
        # No `prev_op`, means `next_op` is the first op in the block.
        self._first_op = new_op

add_op(operation: Operation) -> None

Add an operation at the end of the block. The operation should not be attached to another block already.

Source code in xdsl/ir/core.py
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
def add_op(self, operation: Operation) -> None:
    """
    Add an operation at the end of the block.
    The operation should not be attached to another block already.
    """
    if self._last_op is None:
        self._attach_op(operation)
        self._first_op = operation
        self._last_op = operation
    else:
        self.insert_op_after(operation, self._last_op)

add_ops(ops: Iterable[Operation]) -> None

Add operations at the end of the block. The operations should not be attached to another block.

Source code in xdsl/ir/core.py
1917
1918
1919
1920
1921
1922
1923
def add_ops(self, ops: Iterable[Operation]) -> None:
    """
    Add operations at the end of the block.
    The operations should not be attached to another block.
    """
    for op in ops:
        self.add_op(op)

insert_ops_before(ops: Sequence[Operation], existing_op: Operation) -> None

Source code in xdsl/ir/core.py
1925
1926
1927
1928
1929
def insert_ops_before(
    self, ops: Sequence[Operation], existing_op: Operation
) -> None:
    for op in ops:
        self.insert_op_before(op, existing_op)

insert_ops_after(ops: Sequence[Operation], existing_op: Operation) -> None

Source code in xdsl/ir/core.py
1931
1932
1933
1934
1935
1936
1937
def insert_ops_after(
    self, ops: Sequence[Operation], existing_op: Operation
) -> None:
    for op in ops:
        self.insert_op_after(op, existing_op)

        existing_op = op

split_before(b_first: Operation, *, arg_types: Iterable[Attribute] = ()) -> Block

Split the block into two blocks before the specified operation.

Note that all operations before the one given stay as part of the original basic block, and the rest of the operations in the original block are moved to the new block, including the old terminator. The original block is left without a terminator. The newly formed block is inserted into the parent region immediately after self and returned.

Source code in xdsl/ir/core.py
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
def split_before(
    self,
    b_first: Operation,
    *,
    arg_types: Iterable[Attribute] = (),
) -> Block:
    """
    Split the block into two blocks before the specified operation.

    Note that all operations before the one given stay as part of the original basic
    block, and the rest of the operations in the original block are moved to the new
    block, including the old terminator.
    The original block is left without a terminator.
    The newly formed block is inserted into the parent region immediately after `self`
    and returned.
    """
    # Use `a` for new contents of `self`, and `b` for new block.
    if b_first.parent is not self:
        raise ValueError("Cannot split block on operation outside of the block.")

    parent = self.parent
    if parent is None:
        raise ValueError("Cannot split block with no parent.")

    first_of_self = self._first_op
    assert first_of_self is not None

    last_of_self = self._last_op
    assert last_of_self is not None

    a_last = b_first.prev_op
    b_last = last_of_self
    if a_last is None:
        # `before` is the first op in the Block, so all the ops move to the new block
        a_first = None
    else:
        a_first = first_of_self

    # Update first and last ops of self
    self._first_op = a_first
    self._last_op = a_last

    b = Block(arg_types=arg_types)
    a_index = parent.get_block_index(self)
    parent.insert_block(b, a_index + 1)

    b._first_op = b_first
    b._last_op = b_last

    # Update parent for moved ops
    b_iter: Operation | None = b_first
    while b_iter is not None:
        b_iter.parent = b
        b_iter = b_iter.next_op

    # Update next op for self.last
    if a_last is not None:
        a_last._next_op = None  # pyright: ignore[reportPrivateUsage]

    # Update previous op for b.first
    b_first._prev_op = None  # pyright: ignore[reportPrivateUsage]

    return b

get_operation_index(op: Operation) -> int

Get the operation position in a block.

Source code in xdsl/ir/core.py
2003
2004
2005
2006
2007
def get_operation_index(self, op: Operation) -> int:
    """Get the operation position in a block."""
    if op.parent is not self:
        raise ValueError("Operation is not a child of the block.")
    return next(idx for idx, block_op in enumerate(self.ops) if block_op is op)

detach_op(op: Operation) -> Operation

Detach an operation from the block. Returns the detached operation.

Source code in xdsl/ir/core.py
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
def detach_op(self, op: Operation) -> Operation:
    """
    Detach an operation from the block.
    Returns the detached operation.
    """
    if op.parent is not self:
        raise ValueError("Cannot detach operation from a different block.")
    op.parent = None

    prev_op = op.prev_op
    next_op = op.next_op

    if prev_op is not None:
        # detach op from linked list
        prev_op._next_op = next_op  # pyright: ignore[reportPrivateUsage]
        # detach linked list from op
        op._prev_op = None  # pyright: ignore[reportPrivateUsage]
    else:
        # reattach linked list if op is first op this block
        assert self._first_op is op
        self._first_op = next_op

    if next_op is not None:
        # detach op from linked list
        next_op._prev_op = prev_op  # pyright: ignore[reportPrivateUsage]
        # detach linked list from op
        op._next_op = None  # pyright: ignore[reportPrivateUsage]
    else:
        # reattach linked list if op is last op in this block
        assert self._last_op is op
        self._last_op = prev_op

    return op

erase_op(op: Operation, safe_erase: bool = True) -> None

Erase an operation from the block. If safe_erase is True, check that the operation has no uses.

Source code in xdsl/ir/core.py
2043
2044
2045
2046
2047
2048
2049
def erase_op(self, op: Operation, safe_erase: bool = True) -> None:
    """
    Erase an operation from the block.
    If safe_erase is True, check that the operation has no uses.
    """
    op = self.detach_op(op)
    op.erase(safe_erase=safe_erase)

walk(*, reverse: bool = False, region_first: bool = False) -> Iterable[Operation]

Iterate over all operations contained in the block. If region_first is set, then the operation regions are iterated before the operation. If reverse is set, then the region, block, and operation lists are iterated in reverse order.

Source code in xdsl/ir/core.py
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
def walk(
    self, *, reverse: bool = False, region_first: bool = False
) -> Iterable[Operation]:
    """
    Iterate over all operations contained in the block.
    If region_first is set, then the operation regions are iterated before the
    operation. If reverse is set, then the region, block, and operation lists are
    iterated in reverse order.
    """
    for op in reversed(self.ops) if reverse else self.ops:
        yield from op.walk(reverse=reverse, region_first=region_first)

walk_blocks(*, reverse: bool = False) -> Iterator[Block]

Iterate over all the blocks nested within this block, including self, in the order in which they are printed in the IR. Iterate in reverse order if reverse is True.

Source code in xdsl/ir/core.py
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
def walk_blocks(self, *, reverse: bool = False) -> Iterator[Block]:
    """
    Iterate over all the blocks nested within this block, including self, in the
    order in which they are printed in the IR.
    Iterate in reverse order if reverse is True.
    """
    if not reverse:
        yield self
    for op in reversed(self.ops) if reverse else self.ops:
        yield from op.walk_blocks(reverse=reverse)
    if reverse:
        yield self

verify() -> None

Source code in xdsl/ir/core.py
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
def verify(self) -> None:
    for operation in self.ops:
        if operation.parent != self:
            raise ValueError(
                "Parent pointer of operation does not refer to containing region"
            )
        operation.verify()

    if len(self.ops) == 0:
        if (region_parent := self.parent) is not None and (
            parent_op := region_parent.parent
        ) is not None:
            if len(region_parent.blocks) == 1 and not parent_op.has_trait(
                NoTerminator
            ):
                raise VerifyException(
                    f"Operation {parent_op.name} contains empty block in "
                    "single-block region that expects at least a terminator"
                )

drop_all_references() -> None

Drop all references to other operations. This function is called prior to deleting a block.

Source code in xdsl/ir/core.py
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
def drop_all_references(self) -> None:
    """
    Drop all references to other operations.
    This function is called prior to deleting a block.
    """
    self.parent = None
    self._next_block = None
    self._prev_block = None
    for op in self.ops:
        op.drop_all_references()

find_ancestor_op_in_block(op: Operation) -> Operation | None

Traverse up the operation hierarchy starting from op to find the ancestor operation that resides in the block.

Returns None if no ancestor is found.

Source code in xdsl/ir/core.py
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
def find_ancestor_op_in_block(self, op: Operation) -> Operation | None:
    """
    Traverse up the operation hierarchy starting from op to find the ancestor
    operation that resides in the block.

    Returns None if no ancestor is found.
    """
    curr_op = op
    while curr_op.parent_block() != self:
        if (curr_op := curr_op.parent_op()) is None:
            return None

    return curr_op

erase(safe_erase: bool = True) -> None

Erase the block, and remove all its references to other operations. If safe_erase is specified, check that no operation results are used outside the block.

Source code in xdsl/ir/core.py
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
def erase(self, safe_erase: bool = True) -> None:
    """
    Erase the block, and remove all its references to other operations.
    If safe_erase is specified, check that no operation results are used outside
    the block.
    """
    assert self.parent is None, (
        "Blocks with parents should first be detached " + "before erasure."
    )
    self.drop_all_references()
    for op in self.ops:
        op.erase(safe_erase=safe_erase, drop_references=False)

is_structurally_equivalent(other: IRNode, context: dict[IRNode | SSAValue, IRNode | SSAValue] | None = None) -> bool

Check if two blocks are structurally equivalent. The context is a mapping of IR nodes to IR nodes that are already known to be equivalent. This enables checking whether the use dependencies and successors are equivalent.

Source code in xdsl/ir/core.py
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
def is_structurally_equivalent(
    self,
    other: IRNode,
    context: dict[IRNode | SSAValue, IRNode | SSAValue] | None = None,
) -> bool:
    """
    Check if two blocks are structurally equivalent.
    The context is a mapping of IR nodes to IR nodes that are already known
    to be equivalent. This enables checking whether the use dependencies and
    successors are equivalent.
    """
    if context is None:
        context = {}
    if not isinstance(other, Block):
        return False
    if len(self.args) != len(other.args) or len(self.ops) != len(other.ops):
        return False
    for arg, other_arg in zip(self.args, other.args):
        if arg.type != other_arg.type:
            return False
        context[arg] = other_arg
    # Add self to the context so Operations can check for identical parents
    context[self] = other
    if not all(
        op.is_structurally_equivalent(other_op, context)
        for op, other_op in zip(self.ops, other.ops)
    ):
        return False

    return True

Dialect dataclass

Contains the operations and attributes of a specific dialect

Source code in xdsl/ir/core.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
@dataclass
class Dialect:
    """Contains the operations and attributes of a specific dialect"""

    _name: str

    _operations: list[type[Operation]] = field(
        default_factory=list[type["Operation"]], init=True, repr=True
    )
    _attributes: list[type[Attribute]] = field(
        default_factory=list[type["Attribute"]], init=True, repr=True
    )
    _interfaces: list[DialectInterface] = field(
        default_factory=list[DialectInterface], init=True, repr=True
    )

    @property
    def operations(self) -> Iterator[type[Operation]]:
        return iter(self._operations)

    @property
    def attributes(self) -> Iterator[type[Attribute]]:
        return iter(self._attributes)

    @property
    def name(self) -> str:
        return self._name

    @staticmethod
    def split_name(name: str) -> tuple[str, str]:
        try:
            names = name.split(".", 1)
            first, second = names
            return (first, second)
        except ValueError as e:
            raise ValueError(f"Invalid operation or attribute name {name}.") from e

    def get_interface(
        self, interface: type[DialectInterfaceT]
    ) -> DialectInterfaceT | None:
        """
        Return a class that implements the 'interface' if it exists.
        """
        for i in self._interfaces:
            if isinstance(i, interface):
                return i
        return None

    def has_interface(self, interface: type[DialectInterfaceT]) -> bool:
        return self.get_interface(interface) is not None

operations: Iterator[type[Operation]] property

attributes: Iterator[type[Attribute]] property

name: str property

split_name(name: str) -> tuple[str, str] staticmethod

Source code in xdsl/ir/core.py
77
78
79
80
81
82
83
84
@staticmethod
def split_name(name: str) -> tuple[str, str]:
    try:
        names = name.split(".", 1)
        first, second = names
        return (first, second)
    except ValueError as e:
        raise ValueError(f"Invalid operation or attribute name {name}.") from e

get_interface(interface: type[DialectInterfaceT]) -> DialectInterfaceT | None

Return a class that implements the 'interface' if it exists.

Source code in xdsl/ir/core.py
86
87
88
89
90
91
92
93
94
95
def get_interface(
    self, interface: type[DialectInterfaceT]
) -> DialectInterfaceT | None:
    """
    Return a class that implements the 'interface' if it exists.
    """
    for i in self._interfaces:
        if isinstance(i, interface):
            return i
    return None

has_interface(interface: type[DialectInterfaceT]) -> bool

Source code in xdsl/ir/core.py
97
98
def has_interface(self, interface: type[DialectInterfaceT]) -> bool:
    return self.get_interface(interface) is not None

__init__(_name: str, _operations: list[type[Operation]] = list[type['Operation']](), _attributes: list[type[Attribute]] = list[type['Attribute']](), _interfaces: list[DialectInterface] = list[DialectInterface]()) -> None

EnumAttribute dataclass

Bases: Data[EnumType]

Core helper for Enum Attributes. Takes a StrEnum type parameter, and defines parsing/printing automatically from its values, restricted to be parsable as identifiers.

example:

class MyEnum(StrEnum):
    First = auto()
    Second = auto()

class MyEnumAttribute(EnumAttribute[MyEnum], SpacedOpaqueSyntaxAttribute):
    name = "example.my_enum"

To use this attribute suffices to have a textual representation of example<my_enum first> and example<my_enum second>

Source code in xdsl/ir/core.py
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
class EnumAttribute(Data[EnumType]):
    """
    Core helper for Enum Attributes. Takes a StrEnum type parameter, and defines
    parsing/printing automatically from its values, restricted to be parsable as
    identifiers.

    example:
    ```python
    class MyEnum(StrEnum):
        First = auto()
        Second = auto()

    class MyEnumAttribute(EnumAttribute[MyEnum], SpacedOpaqueSyntaxAttribute):
        name = "example.my_enum"
    ```
    To use this attribute suffices to have a textual representation
    of `example<my_enum first>` and ``example<my_enum second>``

    """

    enum_type: ClassVar[type[StrEnum]]

    def __init_subclass__(cls) -> None:
        _check_enum_constraints(cls)

    def print_parameter(self, printer: Printer) -> None:
        printer.print_identifier_or_string_literal(self.data.value)

    @classmethod
    def parse_parameter(cls, parser: AttrParser) -> EnumType:
        return cast(EnumType, parser.parse_str_enum(cls.enum_type))

enum_type: type[StrEnum] class-attribute

__init_subclass__() -> None

Source code in xdsl/ir/core.py
302
303
def __init_subclass__(cls) -> None:
    _check_enum_constraints(cls)

print_parameter(printer: Printer) -> None

Source code in xdsl/ir/core.py
305
306
def print_parameter(self, printer: Printer) -> None:
    printer.print_identifier_or_string_literal(self.data.value)

parse_parameter(parser: AttrParser) -> EnumType classmethod

Source code in xdsl/ir/core.py
308
309
310
@classmethod
def parse_parameter(cls, parser: AttrParser) -> EnumType:
    return cast(EnumType, parser.parse_str_enum(cls.enum_type))

ParametrizedAttribute dataclass

Bases: Attribute

An attribute parametrized by other attributes.

Source code in xdsl/ir/core.py
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
@dataclass(frozen=True, init=False)
class ParametrizedAttribute(Attribute):
    """An attribute parametrized by other attributes."""

    def __init__(self, *parameters: Any):
        irdl_def = self.get_irdl_definition()
        for (f, d), param in zip(irdl_def.parameters, parameters, strict=True):
            if d.converter is not None:
                param = d.converter(param)
            object.__setattr__(self, f, param)
        super().__init__()

    @property
    def parameters(self) -> tuple[Attribute, ...]:
        return (
            *(
                self.__getattribute__(field)
                for field, _ in self.get_irdl_definition().parameters
            ),
        )

    @classmethod
    def new(cls: type[Self], params: Sequence[Attribute]) -> Self:
        """
        Create a new `ParametrizedAttribute` given its parameters.

        This function should be preferred over `__init__` when instantiating
        attributes in a generic way (i.e., without knowing their concrete type
        statically).
        """
        # Create the new attribute object, without calling its __init__.
        # We do this to allow users to redefine their own __init__.
        attr = cls.__new__(cls)

        # Set the parameters based on the definition
        for (f, _), param in zip(
            cls.get_irdl_definition().parameters, params, strict=True
        ):
            object.__setattr__(attr, f, param)
        attr.__post_init__()

        return attr

    @classmethod
    def parse_parameters(cls, parser: AttrParser) -> Sequence[Attribute]:
        """Parse the attribute parameters."""
        return parser.parse_paramattr_parameters()

    def print_parameters(self, printer: Printer) -> None:
        """Print the attribute parameters."""
        printer.print_paramattr_parameters(self.parameters)

    @classmethod
    def get_irdl_definition(cls) -> ParamAttrDef:
        """Get the IRDL attribute definition."""
        ...

    def _verify(self):
        # Verifier generated by irdl_attr_def
        t: type[ParametrizedAttribute] = type(self)
        attr_def = t.get_irdl_definition()
        attr_def.verify(self)
        super()._verify()

parameters: tuple[Attribute, ...] property

__init__(*parameters: Any)

Source code in xdsl/ir/core.py
431
432
433
434
435
436
437
def __init__(self, *parameters: Any):
    irdl_def = self.get_irdl_definition()
    for (f, d), param in zip(irdl_def.parameters, parameters, strict=True):
        if d.converter is not None:
            param = d.converter(param)
        object.__setattr__(self, f, param)
    super().__init__()

new(params: Sequence[Attribute]) -> Self classmethod

Create a new ParametrizedAttribute given its parameters.

This function should be preferred over __init__ when instantiating attributes in a generic way (i.e., without knowing their concrete type statically).

Source code in xdsl/ir/core.py
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
@classmethod
def new(cls: type[Self], params: Sequence[Attribute]) -> Self:
    """
    Create a new `ParametrizedAttribute` given its parameters.

    This function should be preferred over `__init__` when instantiating
    attributes in a generic way (i.e., without knowing their concrete type
    statically).
    """
    # Create the new attribute object, without calling its __init__.
    # We do this to allow users to redefine their own __init__.
    attr = cls.__new__(cls)

    # Set the parameters based on the definition
    for (f, _), param in zip(
        cls.get_irdl_definition().parameters, params, strict=True
    ):
        object.__setattr__(attr, f, param)
    attr.__post_init__()

    return attr

parse_parameters(parser: AttrParser) -> Sequence[Attribute] classmethod

Parse the attribute parameters.

Source code in xdsl/ir/core.py
470
471
472
473
@classmethod
def parse_parameters(cls, parser: AttrParser) -> Sequence[Attribute]:
    """Parse the attribute parameters."""
    return parser.parse_paramattr_parameters()

print_parameters(printer: Printer) -> None

Print the attribute parameters.

Source code in xdsl/ir/core.py
475
476
477
def print_parameters(self, printer: Printer) -> None:
    """Print the attribute parameters."""
    printer.print_paramattr_parameters(self.parameters)

get_irdl_definition() -> ParamAttrDef classmethod

Get the IRDL attribute definition.

Source code in xdsl/ir/core.py
479
480
481
482
@classmethod
def get_irdl_definition(cls) -> ParamAttrDef:
    """Get the IRDL attribute definition."""
    ...

Region dataclass

Bases: _IRNode

A region contains a CFG of blocks. Regions are contained in operations.

Source code in xdsl/ir/core.py
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
@dataclass(init=False, eq=False, unsafe_hash=False)
class Region(_IRNode):
    """A region contains a CFG of blocks. Regions are contained in operations."""

    class DEFAULT:
        """
        A marker to be used as a default parameter to functions when a default
        single-block region should be constructed.
        """

    _first_block: Block | None = field(default=None, repr=False)
    """The first block in the region. This is the entry block if it is present."""

    _last_block: Block | None = field(default=None, repr=False)
    """The last block in the region."""

    parent: Operation | None = field(default=None, repr=False)
    """Operation containing the region."""

    def __init__(self, blocks: Block | Iterable[Block] = ()):
        super().__init__()
        self.add_block(blocks)

    @property
    def parent_node(self) -> IRNode | None:
        return self.parent

    def parent_block(self) -> Block | None:
        return self.parent.parent if self.parent else None

    def parent_op(self) -> Operation | None:
        return self.parent

    def parent_region(self) -> Region | None:
        return (
            self.parent.parent.parent
            if self.parent is not None and self.parent.parent is not None
            else None
        )

    def find_ancestor_block_in_region(self, block: Block) -> Block | None:
        """
        Returns 'block' if 'block' lies in this region, or otherwise finds
        the ancestor of 'block' that lies in this region.

        Returns None if no ancestor block that lies in this region is found.
        """
        curr_block = block
        while curr_block.parent_region() != self:
            curr_block = curr_block.parent_block()
            if curr_block is None:
                return None

        return curr_block

    @property
    def blocks(self) -> RegionBlocks:
        """
        A multi-pass iterable of blocks.
        """
        return RegionBlocks(self)

    @property
    def first_block(self) -> Block | None:
        """First block in this region. This is the entry block if present."""
        return self._first_block

    @property
    def last_block(self) -> Block | None:
        """Last block in this region."""
        return self._last_block

    def __repr__(self) -> str:
        short_reprs = ", ".join(_short_repr(block) for block in self.blocks)
        return f"Region(blocks=[{short_reprs}])"

    @property
    def ops(self) -> BlockOps:
        """
        Get the operations of a single-block region.
        Returns an exception if the region is not single-block.
        """
        if len(self.blocks) != 1:
            raise ValueError(
                "'ops' property of Region class is only available "
                "for single-block regions."
            )
        return self.block.ops

    @property
    def op(self) -> Operation:
        """
        Get the operation of a single-operation single-block region.
        Returns an exception if the region is not single-operation single-block.
        """
        if len(self.blocks) == 1:
            block = self.block
            first_op = block.first_op
            last_op = block.last_op
            if first_op is last_op and first_op is not None:
                return first_op
        raise ValueError(
            "'op' property of Region class is only available "
            "for single-operation single-block regions."
        )

    @property
    def block(self) -> Block:
        """
        Get the block of a single-block region.
        Returns an exception if the region is not single-block.
        """
        if self._first_block is None or self._first_block is not self._last_block:
            raise ValueError(
                "'block' property of Region class is only available "
                "for single-block regions."
            )
        return self._first_block

    def _attach_block(self, block: Block) -> None:
        """Attach a block to the region, and check that it has no parents."""
        if block.parent:
            raise ValueError(
                "Can't add to a region a block already attached to a region."
            )
        if block.is_ancestor(self):
            raise ValueError("Can't add a block to a region contained in the block.")
        block.parent = self

    def add_block(self, block: Block | Iterable[Block]) -> None:
        """
        Insert one or multiple blocks at the end of the region.
        The blocks should not be attached to another region.
        """
        blocks_iter: Iterator[Block]
        if isinstance(block, Block):
            blocks_iter = iter((block,))
        else:
            blocks_iter = iter(block)
        prev_block = self.last_block

        if prev_block is None:
            try:
                # First block
                prev_block = next(blocks_iter)
                self._attach_block(prev_block)
                self._first_block = prev_block
            except StopIteration:
                # blocks_iter is empty, nothing to do
                return

        try:
            while True:
                next_block = next(blocks_iter)
                self._attach_block(next_block)
                next_block._prev_block = (  # pyright: ignore[reportPrivateUsage]
                    prev_block
                )
                prev_block._next_block = (  # pyright: ignore[reportPrivateUsage]
                    next_block
                )
                prev_block = next_block

        except StopIteration:
            # Repair last block
            self._last_block = prev_block
            return

    def insert_block_before(
        self, block: Block | Iterable[Block], target: Block
    ) -> None:
        """
        Insert one or multiple blocks before a given block in the region.
        The blocks should not be attached to another region.
        """
        if target.parent is not self:
            raise ValueError(
                "Cannot insert blocks before a block into a region that is not the target's parent"
            )
        blocks_iter: Iterator[Block]
        if isinstance(block, Block):
            blocks_iter = iter((block,))
        else:
            blocks_iter = iter(block)
        prev_block = target.prev_block

        if prev_block is None:
            try:
                # First block
                new_first = next(blocks_iter)
                self._attach_block(new_first)
                self._first_block = new_first
                new_first._next_block = target  # pyright: ignore[reportPrivateUsage]
                prev_block = new_first
            except StopIteration:
                # blocks_iter is empty, nothing to do
                return

        # The invariant for the loop is that prev_block is always before target when
        # calling `next`.

        try:
            while True:
                next_block = next(blocks_iter)
                self._attach_block(next_block)
                next_block._prev_block = (  # pyright: ignore[reportPrivateUsage]
                    prev_block
                )
                prev_block._next_block = (  # pyright: ignore[reportPrivateUsage]
                    next_block
                )
                prev_block = next_block

        except StopIteration:
            # Repair broken link
            prev_block._next_block = target  # pyright: ignore[reportPrivateUsage]
            target._prev_block = prev_block  # pyright: ignore[reportPrivateUsage]
            return

    def insert_block_after(self, block: Block | Iterable[Block], target: Block) -> None:
        """
        Insert one or multiple blocks after a given block in the region.
        The blocks should not be attached to another region.
        """
        next_block = target.next_block
        if next_block is None:
            self.add_block(block)
        else:
            self.insert_block_before(block, next_block)

    def insert_block(self, blocks: Block | Iterable[Block], index: int) -> None:
        """
        Insert one or multiple blocks at a given index in the region.
        The blocks should not be attached to another region.
        """
        i = -1
        for i, b in enumerate(self.blocks):
            if i == index:
                self.insert_block_before(blocks, b)
                return
        if i + 1 == index:
            # Append block
            self.add_block(blocks)

    def get_block_index(self, block: Block) -> int:
        """Get the block position in a region."""
        if block.parent is not self:
            raise ValueError("Block is not a child of the region.")
        return next(
            idx for idx, region_block in enumerate(self.blocks) if region_block is block
        )

    def detach_block(self, block: int | Block) -> Block:
        """
        Detach a block from the region.
        Returns the detached block.
        """
        if isinstance(block, int):
            block = self.blocks[block]
        else:
            if block.parent is not self:
                raise ValueError("Block is not a child of the region.")

        block.parent = None
        if (prev_block := block.prev_block) is None:
            self._first_block = block.next_block
        else:
            prev_block._next_block = (  # pyright: ignore[reportPrivateUsage]
                block.next_block
            )
        if (next_block := block.next_block) is None:
            self._last_block = block.prev_block
        else:
            next_block._prev_block = (  # pyright: ignore[reportPrivateUsage]
                block.prev_block
            )

        # Detach linked list from block
        block._prev_block = None  # pyright: ignore[reportPrivateUsage]
        block._next_block = None  # pyright: ignore[reportPrivateUsage]

        return block

    def erase_block(self, block: int | Block, safe_erase: bool = True) -> None:
        """
        Erase a block from the region.
        If safe_erase is True, check that the block has no uses.
        """
        block = self.detach_block(block)
        block.erase(safe_erase=safe_erase)

    def clone(self) -> Region:
        """
        Clone the entire region into a new one.
        """
        new_region = Region()
        self.clone_into(new_region)
        return new_region

    def clone_into(
        self,
        dest: Region,
        insert_index: int | None = None,
        value_mapper: dict[SSAValue, SSAValue] | None = None,
        block_mapper: dict[Block, Block] | None = None,
        *,
        clone_name_hints: bool = True,
        clone_operands: bool = True,
    ):
        """
        Clone all block of this region into `dest` to position `insert_index`
        """
        assert dest != self
        if insert_index is None:
            insert_index = len(dest.blocks)
        if value_mapper is None:
            value_mapper = {}
        if block_mapper is None:
            block_mapper = {}

        new_blocks: list[Block] = []

        # Clone all blocks without their contents, and register the block mapping
        # This ensures that operations can refer to blocks that are not yet cloned
        for block in self.blocks:
            new_block = Block()
            new_blocks.append(new_block)
            block_mapper[block] = new_block

        dest.insert_block(new_blocks, insert_index)

        # Populate the blocks with the cloned operations
        for block, new_block in zip(self.blocks, new_blocks):
            for idx, block_arg in enumerate(block.args):
                new_block.insert_arg(block_arg.type, idx)
                new_arg = new_block.args[idx]
                value_mapper[block_arg] = new_arg
                if clone_name_hints:
                    new_arg.name_hint = block_arg.name_hint
            for op in block.ops:
                new_block.add_op(
                    op.clone(
                        value_mapper,
                        block_mapper,
                        clone_name_hints=clone_name_hints,
                        clone_operands=False,
                    )
                )
        # Handle cases where results may be created after their first use when walking
        # in lexicographic order.
        if clone_operands:
            for old, new in zip(self.walk(), dest.walk()):
                new.operands = tuple(
                    value_mapper.get(operand, operand) for operand in old.operands
                )

    def walk(
        self, *, reverse: bool = False, region_first: bool = False
    ) -> Iterator[Operation]:
        """
        Call a function on all operations contained in the region.
        If region_first is set, then the operation regions are iterated before the
        operation. If reverse is set, then the region, block, and operation lists are
        iterated in reverse order.
        """
        for block in reversed(self.blocks) if reverse else self.blocks:
            yield from block.walk(reverse=reverse, region_first=region_first)

    def verify(self) -> None:
        for block in self.blocks:
            block.verify()
            if block.parent != self:
                raise ValueError(
                    "Parent pointer of block does not refer to containing region"
                )

    def drop_all_references(self) -> None:
        """
        Drop all references to other operations.
        This function is called prior to deleting a region.
        """
        self.parent = None
        for block in self.blocks:
            block.drop_all_references()

    def erase(self) -> None:
        """
        Erase the region, and remove all its references to other operations.
        """
        assert self.parent, (
            "Regions with parents should first be " + "detached before erasure."
        )
        self.drop_all_references()

    def move_blocks(self, region: Region) -> None:
        """
        Move the blocks of this region to another region. Leave no blocks in this region.
        """
        if region is self:
            raise ValueError("Cannot move region into itself.")
        self_first_block = self._first_block
        if self_first_block is None:
            return
        self_last_block = self._last_block
        assert self_last_block is not None
        other_last_block = region.last_block
        if other_last_block is None:
            region._first_block = self._first_block
        else:
            self_first_block._prev_block = (  # pyright: ignore[reportPrivateUsage]
                other_last_block
            )
            other_last_block._next_block = (  # pyright: ignore[reportPrivateUsage]
                self_first_block
            )
        region._last_block = self_last_block

        for block in self.blocks:
            block.parent = region

        self._first_block = None
        self._last_block = None

    def move_blocks_before(self, target: Block) -> None:
        """
        Move the blocks of this region to another region, before the target block.
        Leave no blocks in this region.
        """
        region = target.parent
        if region is self:
            raise ValueError("Cannot move region into itself.")
        if region is None:
            raise ValueError("Cannot inline region before a block with no parent")

        first_block = self._first_block
        if not first_block:
            return
        last_block = self._last_block
        assert last_block is not None

        if target.prev_block is None:
            region._first_block = first_block
        else:
            target.prev_block._next_block = (  # pyright: ignore[reportPrivateUsage]
                first_block
            )
            first_block._prev_block = (  # pyright: ignore[reportPrivateUsage]
                target.prev_block
            )

        for block in self.blocks:
            block.parent = region

        last_block._next_block = target  # pyright: ignore[reportPrivateUsage]
        target._prev_block = last_block  # pyright: ignore[reportPrivateUsage]

        self._first_block = None
        self._last_block = None

    def is_structurally_equivalent(
        self,
        other: IRNode,
        context: dict[IRNode | SSAValue, IRNode | SSAValue] | None = None,
    ) -> bool:
        """
        Check if two regions are structurally equivalent.
        The context is a mapping of IR nodes to IR nodes that are already known
        to be equivalent. This enables checking whether the use dependencies and
        successors are equivalent.
        """
        if context is None:
            context = {}
        if not isinstance(other, Region):
            return False
        if len(self.blocks) != len(other.blocks):
            return False
        # register all blocks in the context so we can check whether ops have
        # the corrects successors
        for block, other_block in zip(self.blocks, other.blocks):
            context[block] = other_block
        if not all(
            block.is_structurally_equivalent(other_block, context)
            for block, other_block in zip(self.blocks, other.blocks)
        ):
            return False
        return True

parent: Operation | None = field(default=None, repr=False) class-attribute instance-attribute

Operation containing the region.

parent_node: IRNode | None property

blocks: RegionBlocks property

A multi-pass iterable of blocks.

first_block: Block | None property

First block in this region. This is the entry block if present.

last_block: Block | None property

Last block in this region.

ops: BlockOps property

Get the operations of a single-block region. Returns an exception if the region is not single-block.

op: Operation property

Get the operation of a single-operation single-block region. Returns an exception if the region is not single-operation single-block.

block: Block property

Get the block of a single-block region. Returns an exception if the region is not single-block.

DEFAULT

A marker to be used as a default parameter to functions when a default single-block region should be constructed.

Source code in xdsl/ir/core.py
2317
2318
2319
2320
2321
class DEFAULT:
    """
    A marker to be used as a default parameter to functions when a default
    single-block region should be constructed.
    """

__init__(blocks: Block | Iterable[Block] = ())

Source code in xdsl/ir/core.py
2332
2333
2334
def __init__(self, blocks: Block | Iterable[Block] = ()):
    super().__init__()
    self.add_block(blocks)

parent_block() -> Block | None

Source code in xdsl/ir/core.py
2340
2341
def parent_block(self) -> Block | None:
    return self.parent.parent if self.parent else None

parent_op() -> Operation | None

Source code in xdsl/ir/core.py
2343
2344
def parent_op(self) -> Operation | None:
    return self.parent

parent_region() -> Region | None

Source code in xdsl/ir/core.py
2346
2347
2348
2349
2350
2351
def parent_region(self) -> Region | None:
    return (
        self.parent.parent.parent
        if self.parent is not None and self.parent.parent is not None
        else None
    )

find_ancestor_block_in_region(block: Block) -> Block | None

Returns 'block' if 'block' lies in this region, or otherwise finds the ancestor of 'block' that lies in this region.

Returns None if no ancestor block that lies in this region is found.

Source code in xdsl/ir/core.py
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
def find_ancestor_block_in_region(self, block: Block) -> Block | None:
    """
    Returns 'block' if 'block' lies in this region, or otherwise finds
    the ancestor of 'block' that lies in this region.

    Returns None if no ancestor block that lies in this region is found.
    """
    curr_block = block
    while curr_block.parent_region() != self:
        curr_block = curr_block.parent_block()
        if curr_block is None:
            return None

    return curr_block

__repr__() -> str

Source code in xdsl/ir/core.py
2385
2386
2387
def __repr__(self) -> str:
    short_reprs = ", ".join(_short_repr(block) for block in self.blocks)
    return f"Region(blocks=[{short_reprs}])"

add_block(block: Block | Iterable[Block]) -> None

Insert one or multiple blocks at the end of the region. The blocks should not be attached to another region.

Source code in xdsl/ir/core.py
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
def add_block(self, block: Block | Iterable[Block]) -> None:
    """
    Insert one or multiple blocks at the end of the region.
    The blocks should not be attached to another region.
    """
    blocks_iter: Iterator[Block]
    if isinstance(block, Block):
        blocks_iter = iter((block,))
    else:
        blocks_iter = iter(block)
    prev_block = self.last_block

    if prev_block is None:
        try:
            # First block
            prev_block = next(blocks_iter)
            self._attach_block(prev_block)
            self._first_block = prev_block
        except StopIteration:
            # blocks_iter is empty, nothing to do
            return

    try:
        while True:
            next_block = next(blocks_iter)
            self._attach_block(next_block)
            next_block._prev_block = (  # pyright: ignore[reportPrivateUsage]
                prev_block
            )
            prev_block._next_block = (  # pyright: ignore[reportPrivateUsage]
                next_block
            )
            prev_block = next_block

    except StopIteration:
        # Repair last block
        self._last_block = prev_block
        return

insert_block_before(block: Block | Iterable[Block], target: Block) -> None

Insert one or multiple blocks before a given block in the region. The blocks should not be attached to another region.

Source code in xdsl/ir/core.py
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
def insert_block_before(
    self, block: Block | Iterable[Block], target: Block
) -> None:
    """
    Insert one or multiple blocks before a given block in the region.
    The blocks should not be attached to another region.
    """
    if target.parent is not self:
        raise ValueError(
            "Cannot insert blocks before a block into a region that is not the target's parent"
        )
    blocks_iter: Iterator[Block]
    if isinstance(block, Block):
        blocks_iter = iter((block,))
    else:
        blocks_iter = iter(block)
    prev_block = target.prev_block

    if prev_block is None:
        try:
            # First block
            new_first = next(blocks_iter)
            self._attach_block(new_first)
            self._first_block = new_first
            new_first._next_block = target  # pyright: ignore[reportPrivateUsage]
            prev_block = new_first
        except StopIteration:
            # blocks_iter is empty, nothing to do
            return

    # The invariant for the loop is that prev_block is always before target when
    # calling `next`.

    try:
        while True:
            next_block = next(blocks_iter)
            self._attach_block(next_block)
            next_block._prev_block = (  # pyright: ignore[reportPrivateUsage]
                prev_block
            )
            prev_block._next_block = (  # pyright: ignore[reportPrivateUsage]
                next_block
            )
            prev_block = next_block

    except StopIteration:
        # Repair broken link
        prev_block._next_block = target  # pyright: ignore[reportPrivateUsage]
        target._prev_block = prev_block  # pyright: ignore[reportPrivateUsage]
        return

insert_block_after(block: Block | Iterable[Block], target: Block) -> None

Insert one or multiple blocks after a given block in the region. The blocks should not be attached to another region.

Source code in xdsl/ir/core.py
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
def insert_block_after(self, block: Block | Iterable[Block], target: Block) -> None:
    """
    Insert one or multiple blocks after a given block in the region.
    The blocks should not be attached to another region.
    """
    next_block = target.next_block
    if next_block is None:
        self.add_block(block)
    else:
        self.insert_block_before(block, next_block)

insert_block(blocks: Block | Iterable[Block], index: int) -> None

Insert one or multiple blocks at a given index in the region. The blocks should not be attached to another region.

Source code in xdsl/ir/core.py
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
def insert_block(self, blocks: Block | Iterable[Block], index: int) -> None:
    """
    Insert one or multiple blocks at a given index in the region.
    The blocks should not be attached to another region.
    """
    i = -1
    for i, b in enumerate(self.blocks):
        if i == index:
            self.insert_block_before(blocks, b)
            return
    if i + 1 == index:
        # Append block
        self.add_block(blocks)

get_block_index(block: Block) -> int

Get the block position in a region.

Source code in xdsl/ir/core.py
2557
2558
2559
2560
2561
2562
2563
def get_block_index(self, block: Block) -> int:
    """Get the block position in a region."""
    if block.parent is not self:
        raise ValueError("Block is not a child of the region.")
    return next(
        idx for idx, region_block in enumerate(self.blocks) if region_block is block
    )

detach_block(block: int | Block) -> Block

Detach a block from the region. Returns the detached block.

Source code in xdsl/ir/core.py
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
def detach_block(self, block: int | Block) -> Block:
    """
    Detach a block from the region.
    Returns the detached block.
    """
    if isinstance(block, int):
        block = self.blocks[block]
    else:
        if block.parent is not self:
            raise ValueError("Block is not a child of the region.")

    block.parent = None
    if (prev_block := block.prev_block) is None:
        self._first_block = block.next_block
    else:
        prev_block._next_block = (  # pyright: ignore[reportPrivateUsage]
            block.next_block
        )
    if (next_block := block.next_block) is None:
        self._last_block = block.prev_block
    else:
        next_block._prev_block = (  # pyright: ignore[reportPrivateUsage]
            block.prev_block
        )

    # Detach linked list from block
    block._prev_block = None  # pyright: ignore[reportPrivateUsage]
    block._next_block = None  # pyright: ignore[reportPrivateUsage]

    return block

erase_block(block: int | Block, safe_erase: bool = True) -> None

Erase a block from the region. If safe_erase is True, check that the block has no uses.

Source code in xdsl/ir/core.py
2596
2597
2598
2599
2600
2601
2602
def erase_block(self, block: int | Block, safe_erase: bool = True) -> None:
    """
    Erase a block from the region.
    If safe_erase is True, check that the block has no uses.
    """
    block = self.detach_block(block)
    block.erase(safe_erase=safe_erase)

clone() -> Region

Clone the entire region into a new one.

Source code in xdsl/ir/core.py
2604
2605
2606
2607
2608
2609
2610
def clone(self) -> Region:
    """
    Clone the entire region into a new one.
    """
    new_region = Region()
    self.clone_into(new_region)
    return new_region

clone_into(dest: Region, insert_index: int | None = None, value_mapper: dict[SSAValue, SSAValue] | None = None, block_mapper: dict[Block, Block] | None = None, *, clone_name_hints: bool = True, clone_operands: bool = True)

Clone all block of this region into dest to position insert_index

Source code in xdsl/ir/core.py
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
def clone_into(
    self,
    dest: Region,
    insert_index: int | None = None,
    value_mapper: dict[SSAValue, SSAValue] | None = None,
    block_mapper: dict[Block, Block] | None = None,
    *,
    clone_name_hints: bool = True,
    clone_operands: bool = True,
):
    """
    Clone all block of this region into `dest` to position `insert_index`
    """
    assert dest != self
    if insert_index is None:
        insert_index = len(dest.blocks)
    if value_mapper is None:
        value_mapper = {}
    if block_mapper is None:
        block_mapper = {}

    new_blocks: list[Block] = []

    # Clone all blocks without their contents, and register the block mapping
    # This ensures that operations can refer to blocks that are not yet cloned
    for block in self.blocks:
        new_block = Block()
        new_blocks.append(new_block)
        block_mapper[block] = new_block

    dest.insert_block(new_blocks, insert_index)

    # Populate the blocks with the cloned operations
    for block, new_block in zip(self.blocks, new_blocks):
        for idx, block_arg in enumerate(block.args):
            new_block.insert_arg(block_arg.type, idx)
            new_arg = new_block.args[idx]
            value_mapper[block_arg] = new_arg
            if clone_name_hints:
                new_arg.name_hint = block_arg.name_hint
        for op in block.ops:
            new_block.add_op(
                op.clone(
                    value_mapper,
                    block_mapper,
                    clone_name_hints=clone_name_hints,
                    clone_operands=False,
                )
            )
    # Handle cases where results may be created after their first use when walking
    # in lexicographic order.
    if clone_operands:
        for old, new in zip(self.walk(), dest.walk()):
            new.operands = tuple(
                value_mapper.get(operand, operand) for operand in old.operands
            )

walk(*, reverse: bool = False, region_first: bool = False) -> Iterator[Operation]

Call a function on all operations contained in the region. If region_first is set, then the operation regions are iterated before the operation. If reverse is set, then the region, block, and operation lists are iterated in reverse order.

Source code in xdsl/ir/core.py
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
def walk(
    self, *, reverse: bool = False, region_first: bool = False
) -> Iterator[Operation]:
    """
    Call a function on all operations contained in the region.
    If region_first is set, then the operation regions are iterated before the
    operation. If reverse is set, then the region, block, and operation lists are
    iterated in reverse order.
    """
    for block in reversed(self.blocks) if reverse else self.blocks:
        yield from block.walk(reverse=reverse, region_first=region_first)

verify() -> None

Source code in xdsl/ir/core.py
2681
2682
2683
2684
2685
2686
2687
def verify(self) -> None:
    for block in self.blocks:
        block.verify()
        if block.parent != self:
            raise ValueError(
                "Parent pointer of block does not refer to containing region"
            )

drop_all_references() -> None

Drop all references to other operations. This function is called prior to deleting a region.

Source code in xdsl/ir/core.py
2689
2690
2691
2692
2693
2694
2695
2696
def drop_all_references(self) -> None:
    """
    Drop all references to other operations.
    This function is called prior to deleting a region.
    """
    self.parent = None
    for block in self.blocks:
        block.drop_all_references()

erase() -> None

Erase the region, and remove all its references to other operations.

Source code in xdsl/ir/core.py
2698
2699
2700
2701
2702
2703
2704
2705
def erase(self) -> None:
    """
    Erase the region, and remove all its references to other operations.
    """
    assert self.parent, (
        "Regions with parents should first be " + "detached before erasure."
    )
    self.drop_all_references()

move_blocks(region: Region) -> None

Move the blocks of this region to another region. Leave no blocks in this region.

Source code in xdsl/ir/core.py
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
def move_blocks(self, region: Region) -> None:
    """
    Move the blocks of this region to another region. Leave no blocks in this region.
    """
    if region is self:
        raise ValueError("Cannot move region into itself.")
    self_first_block = self._first_block
    if self_first_block is None:
        return
    self_last_block = self._last_block
    assert self_last_block is not None
    other_last_block = region.last_block
    if other_last_block is None:
        region._first_block = self._first_block
    else:
        self_first_block._prev_block = (  # pyright: ignore[reportPrivateUsage]
            other_last_block
        )
        other_last_block._next_block = (  # pyright: ignore[reportPrivateUsage]
            self_first_block
        )
    region._last_block = self_last_block

    for block in self.blocks:
        block.parent = region

    self._first_block = None
    self._last_block = None

move_blocks_before(target: Block) -> None

Move the blocks of this region to another region, before the target block. Leave no blocks in this region.

Source code in xdsl/ir/core.py
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
def move_blocks_before(self, target: Block) -> None:
    """
    Move the blocks of this region to another region, before the target block.
    Leave no blocks in this region.
    """
    region = target.parent
    if region is self:
        raise ValueError("Cannot move region into itself.")
    if region is None:
        raise ValueError("Cannot inline region before a block with no parent")

    first_block = self._first_block
    if not first_block:
        return
    last_block = self._last_block
    assert last_block is not None

    if target.prev_block is None:
        region._first_block = first_block
    else:
        target.prev_block._next_block = (  # pyright: ignore[reportPrivateUsage]
            first_block
        )
        first_block._prev_block = (  # pyright: ignore[reportPrivateUsage]
            target.prev_block
        )

    for block in self.blocks:
        block.parent = region

    last_block._next_block = target  # pyright: ignore[reportPrivateUsage]
    target._prev_block = last_block  # pyright: ignore[reportPrivateUsage]

    self._first_block = None
    self._last_block = None

is_structurally_equivalent(other: IRNode, context: dict[IRNode | SSAValue, IRNode | SSAValue] | None = None) -> bool

Check if two regions are structurally equivalent. The context is a mapping of IR nodes to IR nodes that are already known to be equivalent. This enables checking whether the use dependencies and successors are equivalent.

Source code in xdsl/ir/core.py
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
def is_structurally_equivalent(
    self,
    other: IRNode,
    context: dict[IRNode | SSAValue, IRNode | SSAValue] | None = None,
) -> bool:
    """
    Check if two regions are structurally equivalent.
    The context is a mapping of IR nodes to IR nodes that are already known
    to be equivalent. This enables checking whether the use dependencies and
    successors are equivalent.
    """
    if context is None:
        context = {}
    if not isinstance(other, Region):
        return False
    if len(self.blocks) != len(other.blocks):
        return False
    # register all blocks in the context so we can check whether ops have
    # the corrects successors
    for block, other_block in zip(self.blocks, other.blocks):
        context[block] = other_block
    if not all(
        block.is_structurally_equivalent(other_block, context)
        for block, other_block in zip(self.blocks, other.blocks)
    ):
        return False
    return True

SpacedOpaqueSyntaxAttribute dataclass

Bases: OpaqueSyntaxAttribute

This class should only be inherited by classes inheriting Attribute. This class is only used for printing attributes in the opaque form.

See external documentation.

Source code in xdsl/ir/core.py
181
182
183
184
185
186
187
188
189
class SpacedOpaqueSyntaxAttribute(OpaqueSyntaxAttribute):
    """
    This class should only be inherited by classes inheriting Attribute.
    This class is only used for printing attributes in the opaque form.

    See external [documentation](https://mlir.llvm.org/docs/LangRef/#dialect-attribute-values.).
    """

    pass

SSAValue dataclass

Bases: IRWithUses, IRWithName, ABC, Generic[AttributeCovT]

A reference to an SSA variable. An SSA variable is either an operation result, or a basic block argument.

Source code in xdsl/ir/core.py
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
@dataclass(eq=False)
class SSAValue(IRWithUses, IRWithName, ABC, Generic[AttributeCovT]):
    """
    A reference to an SSA variable.
    An SSA variable is either an operation result, or a basic block argument.
    """

    _type: AttributeCovT
    """Each SSA variable is associated to a type."""

    @property
    def type(self) -> AttributeCovT:
        return self._type

    @property
    @abstractmethod
    def owner(self) -> Operation | Block:
        """
        An SSA variable is either an operation result, or a basic block argument.
        This property returns the Operation or Block that currently defines a specific value.
        """
        pass

    @staticmethod
    def get(
        arg: SSAValue | Operation, *, type: TypeForm[AttributeInvT] = Attribute
    ) -> SSAValue[AttributeInvT]:
        """
        Get a new SSAValue from either a SSAValue, or an operation with a single result.
        Checks that the resulting SSAValue is of the supplied type, if provided.
        """
        from xdsl.utils.hints import isa

        match arg:
            case SSAValue():
                if type is Attribute or isa(arg.type, type):
                    return cast(SSAValue[AttributeInvT], arg)
                raise ValueError(
                    f"SSAValue.get: Expected {type} but got SSAValue with type {arg.type}."
                )
            case Operation():
                if len(arg.results) == 1:
                    return SSAValue.get(arg.results[0], type=type)
                raise ValueError(
                    "SSAValue.get: expected operation with a single result."
                )

    @deprecated("Please use `self.replace_all_uses_with(value)`")
    def replace_by(self, value: SSAValue) -> None:
        return self.replace_all_uses_with(value)

    def replace_all_uses_with(self, value: SSAValue) -> None:
        """Replace the value by another value in all its uses."""
        for use in tuple(self.uses):
            use.operation.operands[use.index] = value
        # carry over name if possible
        if value.name_hint is None:
            value.name_hint = self.name_hint
        assert self.first_use is None, "unexpected error in xdsl"

    @deprecated(
        "Please use `self.replace_uses_with_if(value, lambda use: True)` (or `rewriter.replace_uses_if` if applicable)."
    )
    def replace_by_if(self, value: SSAValue, test: Callable[[Use], bool]) -> None:
        return self.replace_uses_with_if(value, test)

    def replace_uses_with_if(self, value: SSAValue, predicate: Callable[[Use], bool]):
        """
        Replace the value by another value in all its uses that pass the given test
        function.
        """
        for use in tuple(self.uses):
            if predicate(use):
                use.operation.operands[use.index] = value
        # carry over name if possible
        if value.name_hint is None:
            value.name_hint = self.name_hint

    def erase(self, safe_erase: bool = True) -> None:
        """
        Erase the value.
        If safe_erase is True, then check that no operations use the value anymore.
        If safe_erase is False, then replace its uses by an ErasedSSAValue.
        """
        if safe_erase and self.first_use is not None:
            raise ValueError(
                "Attempting to delete SSA value that still has uses of result "
                f"of operation:\n{self.owner}"
            )
        self.replace_all_uses_with(ErasedSSAValue(self.type, self))

    def __hash__(self):
        """
        Make SSAValue hashable. Two SSA Values are never the same, therefore
        the use of `id` is allowed here.
        """
        return id(self)

    def __eq__(self, other: object) -> bool:
        return self is other

type: AttributeCovT property

owner: Operation | Block abstractmethod property

An SSA variable is either an operation result, or a basic block argument. This property returns the Operation or Block that currently defines a specific value.

get(arg: SSAValue | Operation, *, type: TypeForm[AttributeInvT] = Attribute) -> SSAValue[AttributeInvT] staticmethod

Get a new SSAValue from either a SSAValue, or an operation with a single result. Checks that the resulting SSAValue is of the supplied type, if provided.

Source code in xdsl/ir/core.py
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
@staticmethod
def get(
    arg: SSAValue | Operation, *, type: TypeForm[AttributeInvT] = Attribute
) -> SSAValue[AttributeInvT]:
    """
    Get a new SSAValue from either a SSAValue, or an operation with a single result.
    Checks that the resulting SSAValue is of the supplied type, if provided.
    """
    from xdsl.utils.hints import isa

    match arg:
        case SSAValue():
            if type is Attribute or isa(arg.type, type):
                return cast(SSAValue[AttributeInvT], arg)
            raise ValueError(
                f"SSAValue.get: Expected {type} but got SSAValue with type {arg.type}."
            )
        case Operation():
            if len(arg.results) == 1:
                return SSAValue.get(arg.results[0], type=type)
            raise ValueError(
                "SSAValue.get: expected operation with a single result."
            )

replace_by(value: SSAValue) -> None

Source code in xdsl/ir/core.py
757
758
759
@deprecated("Please use `self.replace_all_uses_with(value)`")
def replace_by(self, value: SSAValue) -> None:
    return self.replace_all_uses_with(value)

replace_all_uses_with(value: SSAValue) -> None

Replace the value by another value in all its uses.

Source code in xdsl/ir/core.py
761
762
763
764
765
766
767
768
def replace_all_uses_with(self, value: SSAValue) -> None:
    """Replace the value by another value in all its uses."""
    for use in tuple(self.uses):
        use.operation.operands[use.index] = value
    # carry over name if possible
    if value.name_hint is None:
        value.name_hint = self.name_hint
    assert self.first_use is None, "unexpected error in xdsl"

replace_by_if(value: SSAValue, test: Callable[[Use], bool]) -> None

Source code in xdsl/ir/core.py
770
771
772
773
774
@deprecated(
    "Please use `self.replace_uses_with_if(value, lambda use: True)` (or `rewriter.replace_uses_if` if applicable)."
)
def replace_by_if(self, value: SSAValue, test: Callable[[Use], bool]) -> None:
    return self.replace_uses_with_if(value, test)

replace_uses_with_if(value: SSAValue, predicate: Callable[[Use], bool])

Replace the value by another value in all its uses that pass the given test function.

Source code in xdsl/ir/core.py
776
777
778
779
780
781
782
783
784
785
786
def replace_uses_with_if(self, value: SSAValue, predicate: Callable[[Use], bool]):
    """
    Replace the value by another value in all its uses that pass the given test
    function.
    """
    for use in tuple(self.uses):
        if predicate(use):
            use.operation.operands[use.index] = value
    # carry over name if possible
    if value.name_hint is None:
        value.name_hint = self.name_hint

erase(safe_erase: bool = True) -> None

Erase the value. If safe_erase is True, then check that no operations use the value anymore. If safe_erase is False, then replace its uses by an ErasedSSAValue.

Source code in xdsl/ir/core.py
788
789
790
791
792
793
794
795
796
797
798
799
def erase(self, safe_erase: bool = True) -> None:
    """
    Erase the value.
    If safe_erase is True, then check that no operations use the value anymore.
    If safe_erase is False, then replace its uses by an ErasedSSAValue.
    """
    if safe_erase and self.first_use is not None:
        raise ValueError(
            "Attempting to delete SSA value that still has uses of result "
            f"of operation:\n{self.owner}"
        )
    self.replace_all_uses_with(ErasedSSAValue(self.type, self))

__hash__()

Make SSAValue hashable. Two SSA Values are never the same, therefore the use of id is allowed here.

Source code in xdsl/ir/core.py
801
802
803
804
805
806
def __hash__(self):
    """
    Make SSAValue hashable. Two SSA Values are never the same, therefore
    the use of `id` is allowed here.
    """
    return id(self)

__eq__(other: object) -> bool

Source code in xdsl/ir/core.py
808
809
def __eq__(self, other: object) -> bool:
    return self is other

__init__(_type: AttributeCovT) -> None

TypeAttribute dataclass

Bases: Attribute

This class should only be inherited by classes inheriting Attribute. This class is only used for printing attributes in the MLIR format, inheriting this class prefix the attribute by ! instead of #.

Source code in xdsl/ir/core.py
160
161
162
163
164
165
166
167
class TypeAttribute(Attribute):
    """
    This class should only be inherited by classes inheriting Attribute.
    This class is only used for printing attributes in the MLIR format,
    inheriting this class prefix the attribute by `!` instead of `#`.
    """

    pass

IRDLOperation dataclass

Bases: Operation

Source code in xdsl/irdl/operations.py
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
@dataclass(init=False, repr=False)
class IRDLOperation(Operation):
    assembly_format: ClassVar[str | None] = None
    custom_directives: ClassVar[tuple[type[CustomDirective], ...]] = ()

    def __init__(
        self: IRDLOperation,
        *,
        operands: (
            Sequence[SSAValue | Operation | Sequence[SSAValue | Operation] | None]
            | None
        ) = None,
        result_types: Sequence[Attribute | Sequence[Attribute] | None] | None = None,
        properties: Mapping[str, Attribute | None] | None = None,
        attributes: Mapping[str, Attribute | None] | None = None,
        successors: Sequence[Block | Sequence[Block] | None] | None = None,
        regions: (
            Sequence[
                Region
                | None
                | Sequence[Operation]
                | Sequence[Block]
                | Sequence[Region | Sequence[Operation] | Sequence[Block]]
            ]
            | None
        ) = None,
    ):
        if operands is None:
            operands = []
        if result_types is None:
            result_types = []
        if properties is None:
            properties = {}
        if attributes is None:
            attributes = {}
        if successors is None:
            successors = []
        if regions is None:
            regions = []
        irdl_op_init(
            self,
            type(self).get_irdl_definition(),
            operands=operands,
            result_types=result_types,
            properties=properties,
            attributes=attributes,
            successors=successors,
            regions=regions,
        )

    def __post_init__(self):
        op_def = self.get_irdl_definition()
        # Fill in default properties
        for prop_name, prop_def in op_def.properties.items():
            if (
                prop_name not in self.properties
                and not isinstance(prop_def, OptionalDef)
                and prop_def.default_value is not None
            ):
                self.properties[prop_name] = prop_def.default_value

        # Fill in default attributes
        for attr_name, attr_def in op_def.attributes.items():
            if (
                attr_name not in self.attributes
                and not isinstance(attr_def, OptionalDef)
                and attr_def.default_value is not None
            ):
                self.attributes[attr_name] = attr_def.default_value

        return super().__post_init__()

    @classmethod
    def build(
        cls,
        *,
        operands: (
            Sequence[SSAValue | Operation | Sequence[SSAValue | Operation] | None]
            | None
        ) = None,
        result_types: Sequence[Attribute | Sequence[Attribute] | None] | None = None,
        attributes: Mapping[str, Attribute | None] | None = None,
        properties: Mapping[str, Attribute | None] | None = None,
        successors: Sequence[Block | Sequence[Block] | None] | None = None,
        regions: (
            Sequence[
                Region
                | None
                | Sequence[Operation]
                | Sequence[Block]
                | Sequence[Region | Sequence[Operation] | Sequence[Block]]
            ]
            | None
        ) = None,
    ) -> Self:
        """Create a new operation using builders."""
        op = cls.__new__(cls)
        IRDLOperation.__init__(
            op,
            operands=operands,
            result_types=result_types,
            properties=properties,
            attributes=attributes,
            successors=successors,
            regions=regions,
        )
        return op

    @classmethod
    def get_irdl_definition(cls) -> OpDef:
        """Get the IRDL operation definition."""
        ...

    def __eq__(self, other: object) -> bool:
        return self is other

    def __hash__(self) -> int:
        return id(self)

assembly_format: str | None = None class-attribute

custom_directives: tuple[type[CustomDirective], ...] = () class-attribute

__init__(*, operands: Sequence[SSAValue | Operation | Sequence[SSAValue | Operation] | None] | None = None, result_types: Sequence[Attribute | Sequence[Attribute] | None] | None = None, properties: Mapping[str, Attribute | None] | None = None, attributes: Mapping[str, Attribute | None] | None = None, successors: Sequence[Block | Sequence[Block] | None] | None = None, regions: Sequence[Region | None | Sequence[Operation] | Sequence[Block] | Sequence[Region | Sequence[Operation] | Sequence[Block]]] | None = None)

Source code in xdsl/irdl/operations.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def __init__(
    self: IRDLOperation,
    *,
    operands: (
        Sequence[SSAValue | Operation | Sequence[SSAValue | Operation] | None]
        | None
    ) = None,
    result_types: Sequence[Attribute | Sequence[Attribute] | None] | None = None,
    properties: Mapping[str, Attribute | None] | None = None,
    attributes: Mapping[str, Attribute | None] | None = None,
    successors: Sequence[Block | Sequence[Block] | None] | None = None,
    regions: (
        Sequence[
            Region
            | None
            | Sequence[Operation]
            | Sequence[Block]
            | Sequence[Region | Sequence[Operation] | Sequence[Block]]
        ]
        | None
    ) = None,
):
    if operands is None:
        operands = []
    if result_types is None:
        result_types = []
    if properties is None:
        properties = {}
    if attributes is None:
        attributes = {}
    if successors is None:
        successors = []
    if regions is None:
        regions = []
    irdl_op_init(
        self,
        type(self).get_irdl_definition(),
        operands=operands,
        result_types=result_types,
        properties=properties,
        attributes=attributes,
        successors=successors,
        regions=regions,
    )

__post_init__()

Source code in xdsl/irdl/operations.py
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def __post_init__(self):
    op_def = self.get_irdl_definition()
    # Fill in default properties
    for prop_name, prop_def in op_def.properties.items():
        if (
            prop_name not in self.properties
            and not isinstance(prop_def, OptionalDef)
            and prop_def.default_value is not None
        ):
            self.properties[prop_name] = prop_def.default_value

    # Fill in default attributes
    for attr_name, attr_def in op_def.attributes.items():
        if (
            attr_name not in self.attributes
            and not isinstance(attr_def, OptionalDef)
            and attr_def.default_value is not None
        ):
            self.attributes[attr_name] = attr_def.default_value

    return super().__post_init__()

build(*, operands: Sequence[SSAValue | Operation | Sequence[SSAValue | Operation] | None] | None = None, result_types: Sequence[Attribute | Sequence[Attribute] | None] | None = None, attributes: Mapping[str, Attribute | None] | None = None, properties: Mapping[str, Attribute | None] | None = None, successors: Sequence[Block | Sequence[Block] | None] | None = None, regions: Sequence[Region | None | Sequence[Operation] | Sequence[Block] | Sequence[Region | Sequence[Operation] | Sequence[Block]]] | None = None) -> Self classmethod

Create a new operation using builders.

Source code in xdsl/irdl/operations.py
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
@classmethod
def build(
    cls,
    *,
    operands: (
        Sequence[SSAValue | Operation | Sequence[SSAValue | Operation] | None]
        | None
    ) = None,
    result_types: Sequence[Attribute | Sequence[Attribute] | None] | None = None,
    attributes: Mapping[str, Attribute | None] | None = None,
    properties: Mapping[str, Attribute | None] | None = None,
    successors: Sequence[Block | Sequence[Block] | None] | None = None,
    regions: (
        Sequence[
            Region
            | None
            | Sequence[Operation]
            | Sequence[Block]
            | Sequence[Region | Sequence[Operation] | Sequence[Block]]
        ]
        | None
    ) = None,
) -> Self:
    """Create a new operation using builders."""
    op = cls.__new__(cls)
    IRDLOperation.__init__(
        op,
        operands=operands,
        result_types=result_types,
        properties=properties,
        attributes=attributes,
        successors=successors,
        regions=regions,
    )
    return op

get_irdl_definition() -> OpDef classmethod

Get the IRDL operation definition.

Source code in xdsl/irdl/operations.py
193
194
195
196
@classmethod
def get_irdl_definition(cls) -> OpDef:
    """Get the IRDL operation definition."""
    ...

__eq__(other: object) -> bool

Source code in xdsl/irdl/operations.py
198
199
def __eq__(self, other: object) -> bool:
    return self is other

__hash__() -> int

Source code in xdsl/irdl/operations.py
201
202
def __hash__(self) -> int:
    return id(self)

AttrParser dataclass

Bases: BaseParser

Basic recursive descent parser for attributes and types.

Methods named parse_* will consume tokens, and throw a ParseError if an unexpected token is parsed. Methods marked with parse_optional will return None if the first token is unexpected, and will throw a ParseError if the first token is expected, but a following token is not.

Methods with a context_msg argument allows to append the context message to the thrown error. For instance, if ',' expected is returned, setting context_msg to " in integer list" will throw the error ',' expected in integer list instead.

Source code in xdsl/parser/attribute_parser.py
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
@dataclass
class AttrParser(BaseParser):
    """
    Basic recursive descent parser for attributes and types.

    Methods named `parse_*` will consume tokens, and throw a `ParseError` if
    an unexpected token is parsed. Methods marked with `parse_optional` will return
    None if the first token is unexpected, and will throw a `ParseError` if the
    first token is expected, but a following token is not.

    Methods with a `context_msg` argument allows to append the context message to the
    thrown error. For instance, if `',' expected` is returned, setting `context_msg` to
    `" in integer list"` will throw the error `',' expected in integer list` instead.

    """

    ctx: Context

    attribute_aliases: dict[str, Attribute] = field(
        default_factory=dict[str, Attribute]
    )
    """
    A dictionary of aliases for attributes.
    The key is the alias name, including the `!` or `#` prefix.
    """

    dialect_resources: set[tuple[str, str]] = field(
        default_factory=set[tuple[str, str]]
    )
    """
    Set of resource references encountered during parsing.
    """

    def parse_optional_type(self) -> TypeAttribute | None:
        """
        Parse an xDSL type, if present.
        An xDSL type is either a builtin type, which can have various format,
        or a dialect type, with the following format:
            type          ::= builtin-type | dialect-type | alias-type
            alias-type    ::= `!` type-name
            dialect-type  ::= `!` type-name (`<` dialect-type-contents+ `>`)?
            type-name     ::= bare-id
            dialect-type-contents ::= `<` dialect-attribute-contents+ `>`
                            | `(` dialect-attribute-contents+ `)`
                            | `[` dialect-attribute-contents+ `]`
                            | `{` dialect-attribute-contents+ `}`
                            | [^[]<>(){}\0]+
        """
        if (
            token := self._parse_optional_token(MLIRTokenKind.EXCLAMATION_IDENT)
        ) is not None:
            return self._parse_extended_type_or_attribute(token.text[1:], True)
        return self._parse_optional_builtin_type()

    def parse_type(self) -> TypeAttribute:
        """
        Parse an xDSL type.
        An xDSL type is either a builtin type, which can have various format,
        or a dialect or alias type, with the following format:
            type          ::= builtin-type | dialect-type | alias-type
            alias-type    ::= `!` type-name
            dialect-type  ::= `!` type-name (`<` dialect-type-contents+ `>`)?
            type-name     ::= bare-id
            dialect-type-contents ::= `<` dialect-attribute-contents+ `>`
                            | `(` dialect-attribute-contents+ `)`
                            | `[` dialect-attribute-contents+ `]`
                            | `{` dialect-attribute-contents+ `}`
                            | [^[]<>(){}\0]+
        """
        return self.expect(self.parse_optional_type, "type expected")

    def parse_optional_attribute(self) -> Attribute | None:
        """
        Parse an xDSL attribute, if present.
        An attribute is either a builtin attribute, which can have various format,
        or a dialect or alias attribute, with the following format:
            attr          ::= builtin-attr | dialect-attr | alias-attr
            alias-attr    ::= `!` attr-name
            dialect-attr  ::= `#` attr-name (`<` dialect-attr-contents+ `>`)?
            attr-name     ::= bare-id
            dialect-attr-contents ::= `<` dialect-attribute-contents+ `>`
                            | `(` dialect-attribute-contents+ `)`
                            | `[` dialect-attribute-contents+ `]`
                            | `{` dialect-attribute-contents+ `}`
                            | [^[]<>(){}\0]+
        """
        if (token := self._parse_optional_token(MLIRTokenKind.HASH_IDENT)) is not None:
            return self._parse_extended_type_or_attribute(token.text[1:], False)
        return self._parse_optional_builtin_attr()

    def parse_attribute(self) -> Attribute:
        """
        Parse an xDSL attribute.
        An attribute is either a builtin attribute, which can have various format,
        or a dialect attribute, with the following format:
            attr          ::= builtin-attr | dialect-attr | alias-attr
            alias-attr    ::= `!` attr-name
            dialect-attr  ::= `#` attr-name (`<` dialect-attr-contents+ `>`)?
            attr-name     ::= bare-id
            dialect-attr-contents ::= `<` dialect-attribute-contents+ `>`
                            | `(` dialect-attribute-contents+ `)`
                            | `[` dialect-attribute-contents+ `]`
                            | `{` dialect-attribute-contents+ `}`
                            | [^[]<>(){}\0]+
        """
        return self.expect(self.parse_optional_attribute, "attribute expected")

    def _parse_attribute_entry(self) -> tuple[str, Attribute]:
        """
        Parse entry in attribute dict. Of format:

        attribute_entry := (bare-id | string-literal) `=` attribute
        attribute       := dialect-attribute | builtin-attribute
        """
        if (name := self.parse_optional_identifier_or_str_literal()) is None:
            self.raise_error(
                "Expected bare-id or string-literal here as part of attribute entry!"
            )

        if self.parse_optional_punctuation("=") is None:
            return name, UnitAttr()

        return name, self.parse_attribute()

    def _find_duplicated_key(self, attrs: list[tuple[str, Attribute]]) -> str | None:
        seen_keys: set[str] = set()
        for key, _ in attrs:
            if key in seen_keys:
                return key
            seen_keys.add(key)
        return None

    def parse_optional_dictionary_attr_dict(self) -> dict[str, Attribute]:
        attrs = self.parse_optional_comma_separated_list(
            self.Delimiter.BRACES, self._parse_attribute_entry
        )
        if attrs is None:
            return dict()

        if (key := self._find_duplicated_key(attrs)) is not None:
            self.raise_error(f"Duplicate key '{key}' in dictionary attribute")

        return dict(attrs)

    def _parse_dialect_type_or_attribute_body(
        self,
        attr_name: str,
        is_type: bool,
        is_opaque: bool,
        starting_opaque_pos: Position | None,
    ):
        """
        Parse the contents of an attribute or type, with syntax:
            dialect-attr-contents ::= `<` dialect-attr-contents+ `>`
                                    | `(` dialect-attr-contents+ `)`
                                    | `[` dialect-attr-contents+ `]`
                                    | `{` dialect-attr-contents+ `}`
                                    | [^[]<>(){}\0]+
        In the case where the attribute or type is using the opaque syntax,
        the attribute or type mnemonic should have already been parsed.
        """
        pretty = "." in attr_name
        if not pretty:
            self.parse_punctuation("<")
            attr_name += (
                "."
                + self._parse_token(
                    MLIRTokenKind.BARE_IDENT, "Expected attribute name."
                ).text
            )
        if is_type:
            attr_def = self.ctx.get_optional_type(
                attr_name,
            )
        else:
            attr_def = self.ctx.get_optional_attr(
                attr_name,
            )
        if attr_def is None:
            self.raise_error(f"'{attr_name}' is not registered")
        if issubclass(attr_def, UnregisteredAttr):
            if not is_opaque:
                if self.parse_optional_punctuation("<") is None:
                    return attr_def(attr_name, is_type, is_opaque, "")
            body = self._parse_unregistered_attr_body(starting_opaque_pos)
            attr = attr_def(attr_name, is_type, is_opaque, body)
            if not is_opaque:
                self.parse_punctuation(">")
            return attr

        elif issubclass(attr_def, ParametrizedAttribute):
            param_list = attr_def.parse_parameters(self)
            return attr_def.new(param_list)
        elif issubclass(attr_def, Data):
            _attr_def = cast(type[Data[Any]], attr_def)
            param = _attr_def.parse_parameter(self)
            return _attr_def(param)
        else:
            raise TypeError("Attributes are either ParametrizedAttribute or Data.")

    @overload
    def _parse_extended_type_or_attribute(
        self, attr_or_dialect_name: str, is_type: Literal[False]
    ) -> Attribute: ...

    @overload
    def _parse_extended_type_or_attribute(
        self, attr_or_dialect_name: str, is_type: Literal[True]
    ) -> TypeAttribute: ...

    def _parse_extended_type_or_attribute(
        self, attr_or_dialect_name: str, is_type: bool
    ) -> Attribute:
        """
        Parse the contents of a dialect or alias type or attribute, with format:
            dialect-attr-contents ::= `<` dialect-attr-contents+ `>`
                                    | `(` dialect-attr-contents+ `)`
                                    | `[` dialect-attr-contents+ `]`
                                    | `{` dialect-attr-contents+ `}`
                                    | [^[]<>(){}\0]+
        The contents will be parsed by a user-defined parser, or by a generic parser
        if the dialect attribute/type is not registered.

        In the case that the type or attribute is using the opaque syntax (where the
        identifier parsed is the dialect name), this function will parse the opaque
        attribute with the following format:
            opaque-attr-contents ::= `<` bare-ident dialect-attr-contents+ `>`
        otherwise, it will parse them with the pretty or alias syntax, with format:
            pretty-or-alias-attr-contents ::= `<` dialect-attr-contents+ `>`
        """
        is_pretty_name = "." in attr_or_dialect_name
        starting_opaque_pos = None

        if not is_pretty_name:
            # An attribute or type alias
            if self.parse_optional_punctuation("<") is None:
                alias_name = ("!" if is_type else "#") + attr_or_dialect_name
                if alias_name not in self.attribute_aliases:
                    self.raise_error(f"undefined symbol alias '{alias_name}'")
                return self.attribute_aliases[alias_name]

            # An opaque dialect attribute or type
            # Compared to MLIR, we still go through the symbol parser, instead of the
            # dialect parser.
            if not is_pretty_name:
                attr_name_token = self._parse_token(
                    MLIRTokenKind.BARE_IDENT, "Expected attribute name."
                )
                starting_opaque_pos = attr_name_token.span.end

                attr_or_dialect_name += "." + attr_name_token.text

        attr = self._parse_dialect_type_or_attribute_body(
            attr_or_dialect_name, is_type, not is_pretty_name, starting_opaque_pos
        )

        if not is_pretty_name:
            self.parse_punctuation(">")

        return attr

    def _parse_unregistered_attr_body(self, start_pos: Position | None) -> str:
        """
        Parse the body of an unregistered attribute, which is a balanced
        string for `<`, `(`, `[`, `{`, and may contain string literals.
        The body ends when no parentheses are opened, and an `>` is encountered.
        """

        if start_pos is None:
            start_pos = self.pos
        end_pos: Position = start_pos

        symbols_stack: list[MLIRTokenKind] = []
        parentheses = {
            MLIRTokenKind.GREATER: MLIRTokenKind.LESS,
            MLIRTokenKind.R_PAREN: MLIRTokenKind.L_PAREN,
            MLIRTokenKind.R_SQUARE: MLIRTokenKind.L_SQUARE,
            MLIRTokenKind.R_BRACE: MLIRTokenKind.L_BRACE,
        }
        parentheses_names = {
            MLIRTokenKind.GREATER: "`>`",
            MLIRTokenKind.R_PAREN: "`)`",
            MLIRTokenKind.R_SQUARE: "`]`",
            MLIRTokenKind.R_BRACE: "`}`",
        }
        while True:
            # Opening a new parenthesis
            if (
                token := self._parse_optional_token_in(parentheses.values())
            ) is not None:
                symbols_stack.append(token.kind)
                continue

            # Closing a parenthesis
            if (token := self._current_token).kind in parentheses.keys():
                closing = parentheses[token.kind]

                # If we don't have any open parenthesis, either we end the parsing if
                # the parenthesis is a `>`, or we raise an error.
                if len(symbols_stack) == 0:
                    if token.kind == MLIRTokenKind.GREATER:
                        end_pos = self.pos
                        break
                    self.raise_error(
                        "Unexpected closing parenthesis "
                        f"{parentheses_names[token.kind]} in attribute body!",
                        self._current_token.span,
                    )

                # If we have an open parenthesis, check that we are closing it
                # with the right parenthesis kind.
                if symbols_stack[-1] != closing:
                    self.raise_error(
                        "Unexpected closing parenthesis "
                        f"{parentheses_names[token.kind]} in attribute body! {symbols_stack}",
                        self._current_token.span,
                    )
                symbols_stack.pop()
                self._consume_token()
                continue

            # Checking for unexpected EOF
            if self._parse_optional_token(MLIRTokenKind.EOF) is not None:
                self.raise_error(
                    "Unexpected end of file before closing of attribute body!"
                )

            # Other tokens
            self._consume_token()

        body = self.lexer.input.slice(start_pos, end_pos)
        assert body is not None
        return body

    def _parse_optional_builtin_parametrized_type(self) -> TypeAttribute | None:
        """
        Parse an builtin parametrized type, if present, with format:
            builtin-parametrized-type ::= builtin-name `<` args `>`
            builtin-name ::= vector | memref | tensor | complex | tuple
            args ::= <defined by the builtin name>
        """
        if self._current_token.kind != MLIRTokenKind.BARE_IDENT:
            return None

        name = self._current_token.text

        def unimplemented() -> NoReturn:
            raise ParseError(
                self._current_token.span,
                f"Builtin {name} is not supported yet!",
            )

        builtin_parsers: dict[str, Callable[[], TypeAttribute]] = {
            "vector": self._parse_vector_attrs,
            "memref": self._parse_memref_attrs,
            "tensor": self._parse_tensor_attrs,
            "complex": self._parse_complex_attrs,
            "tuple": self._parse_tuple_attrs,
        }

        if name not in builtin_parsers:
            return None
        self._consume_token(MLIRTokenKind.BARE_IDENT)

        self.parse_punctuation("<", " after builtin name")
        # Get the parser for the type, falling back to the unimplemented warning
        res = builtin_parsers.get(name, unimplemented)()
        self.parse_punctuation(">", " after builtin parameter list")
        return res

    def parse_shape_dimension(self, allow_dynamic: bool = True) -> int:
        """
        Parse a single shape dimension, which is a decimal literal or `?`.
        `?` is interpreted as DYNAMIC_INDEX. Note that if the integer literal is in
        hexadecimal form, it will be split into multiple tokens. For example,
        `0x10` will be split into `0` and `x10`.
        Optionally allows to not parse `?` as DYNAMIC_INDEX.
        """
        if self._current_token.kind not in (
            MLIRTokenKind.INTEGER_LIT,
            MLIRTokenKind.QUESTION,
        ):
            if allow_dynamic:
                self.raise_error(
                    "Expected either integer literal or '?' in shape dimension, "
                    f"got {self._current_token.kind.name}!"
                )
            self.raise_error(
                "Expected integer literal in shape dimension, "
                f"got {self._current_token.kind.name}!"
            )

        if self.parse_optional_punctuation("?") is not None:
            if allow_dynamic:
                return DYNAMIC_INDEX
            self.raise_error("Unexpected dynamic dimension!")

        # If the integer literal starts with `0x`, this is decomposed into
        # `0` and `x`.
        int_token = self._consume_token(MLIRTokenKind.INTEGER_LIT)
        if int_token.text[:2] == "0x":
            self._resume_from(int_token.span.start + 1)
            return 0

        return int_token.kind.get_int_value(int_token.span)

    def _parse_optional_shape_delimiter(self) -> str | None:
        """
        Parse 'x', a shape delimiter. Note that if 'x' is followed by other
        characters, it will split the token. For instance, 'x1' will be split
        into 'x' and '1'.
        """
        if self._current_token.kind != MLIRTokenKind.BARE_IDENT:
            return None

        if self._current_token.text[0] != "x":
            return None

        # Move the lexer to the position after 'x'.
        self._resume_from(self._current_token.span.start + 1)
        return "x"

    def parse_shape_delimiter(self) -> None:
        """
        Parse 'x', a shape delimiter. Note that if 'x' is followed by other
        characters, it will split the token. For instance, 'x1' will be split
        into 'x' and '1'.
        """
        if self._parse_optional_shape_delimiter() is not None:
            return

        token = self._current_token
        tk = token.kind

        err_val = tk.name if tk != MLIRTokenKind.BARE_IDENT else token.text

        self.raise_error(
            f"Expected 'x' in shape delimiter, got {err_val}",
        )

    def parse_dimension_list(self) -> list[int]:
        """
        Parse a dimension list with the following format:
          dimension-list ::= (dimension `x`)* dimension
        each dimension is also required to be non-negative.
        """
        dims: list[int] = []
        accepted_token_kinds = (MLIRTokenKind.INTEGER_LIT, MLIRTokenKind.QUESTION)

        # empty case
        if self._current_token.kind not in accepted_token_kinds:
            return []

        # parse first number
        dim = self.parse_shape_dimension()
        dims.append(dim)

        while self._parse_optional_shape_delimiter():
            if self._current_token.kind in accepted_token_kinds:
                dim = self.parse_shape_dimension()
                dims.append(dim)
            else:
                # We want to preserve a trailing `x` as it provides useful
                # information to the rest of the parser, so we undo the parse
                self._resume_from(self._current_token.span.start - 1)
                break

        return dims

    def parse_ranked_shape(self) -> tuple[list[int], Attribute]:
        """
        Parse a ranked shape with the following format:
          ranked-shape ::= (dimension `x`)* type
          dimension ::= `?` | decimal-literal
        each dimension is also required to be non-negative.
        """
        dims = self.parse_dimension_list()
        if dims:
            self.parse_shape_delimiter()

        type = self.expect(self.parse_optional_type, "Expected shape type.")
        return dims, type

    def parse_shape(self) -> tuple[list[int] | None, Attribute]:
        """
        Parse a ranked or unranked shape with the following format:

        shape ::= ranked-shape | unranked-shape
        ranked-shape ::= (dimension `x`)* type
        unranked-shape ::= `*`x type
        dimension ::= `?` | decimal-literal

        each dimension is also required to be non-negative.
        """
        if self.parse_optional_punctuation("*") is not None:
            self.parse_shape_delimiter()
            type = self.expect(self.parse_optional_type, "Expected shape type.")
            return None, type
        return self.parse_ranked_shape()

    def _parse_complex_attrs(self) -> ComplexType:
        element_type = self.parse_attribute()
        if not isa(element_type, IntegerType | AnyFloat):
            self.raise_error(
                "Complex type must be parameterized by an integer or float type!"
            )
        return ComplexType(element_type)

    def _parse_memref_attrs(
        self,
    ) -> MemRefType | UnrankedMemRefType:
        shape, type = self.parse_shape()

        # Unranked case
        if shape is None:
            if self.parse_optional_punctuation(",") is None:
                return UnrankedMemRefType.from_type(type)
            memory_space = self.parse_attribute()
            return UnrankedMemRefType.from_type(type, memory_space)

        if self.parse_optional_punctuation(",") is None:
            return MemRefType(type, shape)

        memory_or_layout = self.parse_attribute()

        # If there is both a memory space and a layout, we know that the
        # layout is the second one
        if self.parse_optional_punctuation(",") is not None:
            memory_space = self.parse_attribute()
            if not isinstance(memory_or_layout, MemRefLayoutAttr):
                self.raise_error("Expected a MemRef layout attribute")
            return MemRefType(type, shape, memory_or_layout, memory_space)

        # If the argument is a MemRefLayoutAttr, use it as layout
        if isinstance(memory_or_layout, MemRefLayoutAttr):
            return MemRefType(type, shape, layout=memory_or_layout)

        # Otherwise, consider it as the memory space.
        else:
            return MemRefType(type, shape, memory_space=memory_or_layout)

    def _parse_vector_attrs(self) -> AnyVectorType:
        dims: list[int] = []
        scalable_dims: list[bool] = []

        while True:
            if self._current_token.kind == MLIRTokenKind.INTEGER_LIT:
                # Static dimension
                dims.append(self.parse_shape_dimension(allow_dynamic=False))
                scalable_dims.append(False)
                self.parse_shape_delimiter()
            elif self.parse_optional_punctuation("[") is not None:
                # Scalable dimension
                dims.append(self.parse_shape_dimension(allow_dynamic=False))
                scalable_dims.append(True)
                self.parse_punctuation("]")
                self.parse_shape_delimiter()
            else:
                break

        type = self.parse_optional_type()
        if type is None:
            self.raise_error("Expected vector element type")

        scalable_dims_attr = ArrayAttr(BoolAttr.from_bool(s) for s in scalable_dims)

        return VectorType(type, dims, scalable_dims_attr)

    def _parse_tensor_attrs(self) -> AnyTensorType | AnyUnrankedTensorType:
        shape, type = self.parse_shape()

        if shape is None:
            if self.parse_optional_punctuation(",") is not None:
                self.raise_error("Unranked tensors don't have an encoding!")
            return UnrankedTensorType(type)

        if self.parse_optional_punctuation(",") is not None:
            encoding = self.parse_attribute()
            return TensorType(type, shape, encoding)

        return TensorType(type, shape)

    def _parse_tuple_attrs(self) -> TupleType:
        params = self.parse_optional_undelimited_comma_separated_list(
            self.parse_optional_type, self.parse_type
        )
        if params is None:
            params = ()
        return TupleType(tuple(params))

    def _parse_attribute_type(self) -> Attribute:
        """
        Parses `:` type and returns the type
        """
        self.parse_characters(":", " in attribute type")
        return self.parse_type()

    def _parse_optional_builtin_attr(self) -> Attribute | None:
        """
        Tries to parse a builtin attribute, e.g. a string literal, int, array, etc..
        """

        # String literal
        if (str_lit := self.parse_optional_str_literal()) is not None:
            return StringAttr(str_lit)
        # Bytes literal
        if (bytes_lit := self.parse_optional_bytes_literal()) is not None:
            return BytesAttr(bytes_lit)

        attrs = (
            self.parse_optional_unit_attr,
            self.parse_optional_builtin_int_or_float_attr,
            self._parse_optional_array_attr,
            self._parse_optional_symref_attr,
            self.parse_optional_location,
            self._parse_optional_builtin_dict_attr,
            self.parse_optional_type,
            self._parse_optional_builtin_parametrized_attr,
        )

        for attr_parser in attrs:
            if (val := attr_parser()) is not None:
                return val

        return None

    def _parse_int_or_question(self, context_msg: str = "") -> int | Literal["?"]:
        """Parse either an integer literal, or a '?'."""
        if self._parse_optional_token(MLIRTokenKind.QUESTION) is not None:
            return "?"
        if (v := self.parse_optional_integer(allow_boolean=False)) is not None:
            return v
        self.raise_error("Expected an integer literal or `?`" + context_msg)

    def _parse_strided_layout_attr(self) -> Attribute:
        """
        Parse a strided layout attribute parameters.
        | `<` `[` comma-separated-int-or-question `]`
          (`,` `offset` `:` integer-literal)? `>`
        """
        # Parse stride list
        self._parse_token(MLIRTokenKind.LESS, "Expected `<` after `strided`")
        strides = self.parse_comma_separated_list(
            self.Delimiter.SQUARE,
            lambda: self._parse_int_or_question(" in stride list"),
            " in stride list",
        )
        # Pyright widen `Literal['?']` to `str` for some reasons
        strides = cast(list[int | Literal["?"]], strides)

        # Convert to the attribute expected input
        strides = [None if stride == "?" else stride for stride in strides]

        # Case without offset
        if self._parse_optional_token(MLIRTokenKind.GREATER) is not None:
            return StridedLayoutAttr(strides)

        # Parse the optional offset
        self._parse_token(
            MLIRTokenKind.COMMA, "Expected end of strided attribute or ',' for offset."
        )
        self.parse_keyword("offset", " after comma")
        self._parse_token(MLIRTokenKind.COLON, "Expected ':' after 'offset'")
        offset = self._parse_int_or_question(" in stride offset")
        self._parse_token(
            MLIRTokenKind.GREATER, "Expected '>' in end of stride attribute"
        )
        return StridedLayoutAttr(strides, None if offset == "?" else offset)

    def parse_optional_unit_attr(self) -> Attribute | None:
        """
        Parse a value of `unit` type.
        unit-attribute ::= `unit`
        """
        if self._current_token.kind != MLIRTokenKind.BARE_IDENT:
            return None
        name = self._current_token.span.text

        # Unit attribute
        if name == "unit":
            self._consume_token()
            return UnitAttr()

        return None

    def _parse_optional_builtin_parametrized_attr(self) -> Attribute | None:
        if self._current_token.kind != MLIRTokenKind.BARE_IDENT:
            return None
        name = self._current_token.span
        parsers = {
            "dense": self._parse_builtin_dense_attr,
            "opaque": self._parse_builtin_opaque_attr,
            "dense_resource": self._parse_builtin_dense_resource_attr,
            "array": self._parse_builtin_densearray_attr,
            "affine_map": self._parse_builtin_affine_map,
            "affine_set": self._parse_builtin_affine_set,
            "strided": self._parse_strided_layout_attr,
        }

        if name.text not in parsers:
            return None
        self._consume_token(MLIRTokenKind.BARE_IDENT)
        return parsers[name.text]()

    def _parse_dense_literal_type(
        self,
    ) -> RankedStructure[IntegerType | IndexType | AnyFloat | ComplexType]:
        type = self.expect(self.parse_optional_type, "Dense attribute must be typed!")
        # Check that the type is correct.
        if not (
            base(RankedStructure[IntegerType | IndexType | AnyFloat | ComplexType])
        ).verifies(
            type,
        ):
            self.raise_error(
                "Expected memref, vector or tensor type of "
                "integer, index, float, or complex type"
            )

        # Check for static shapes in type
        if any(dim == DYNAMIC_INDEX for dim in list(type.get_shape())):
            self.raise_error("Dense literal attribute should have a static shape.")
        return type

    def parse_dense_int_or_fp_elements_attr(
        self, type: RankedStructure[AnyDenseElement] | None
    ) -> DenseIntOrFPElementsAttr:
        dense_contents: (
            tuple[list[AttrParser._TensorLiteralElement], list[int]] | str | None
        )
        """
        If `None`, then the contents are empty.
        If `str`, then this is a hex-encoded string containing the data, which doesn't
        carry shape information.
        Otherwise, a tuple of `elements` and `shape`.
        If `shape` is `[]`, then this is a splat attribute, meaning it has the same value
        everywhere.
        """

        self.parse_punctuation("<", " in dense attribute")
        if self.parse_optional_punctuation(">") is not None:
            # Empty case
            dense_contents = None
        else:
            if (hex_string := self.parse_optional_str_literal()) is not None:
                dense_contents, shape = hex_string, None
            else:
                # Expect a tensor literal instead
                dense_contents = self._parse_tensor_literal()
            self.parse_punctuation(">", " in dense attribute")

        # Parse the dense type and check for correctness
        if type is None:
            self.parse_punctuation(":", " in dense attribute")
            type = self._parse_dense_literal_type()
        type_shape = list(type.get_shape())
        type_num_values = math.prod(type_shape)

        if dense_contents is None:
            # Empty case
            if type_num_values != 0:
                self.raise_error(
                    "Expected at least one element in the dense literal, but got None"
                )
            data_values = []
        elif isinstance(dense_contents, str):
            # Hex-encoded string case: convert straight to bytes (without the 0x prefix)
            try:
                bytes_values = bytes.fromhex(dense_contents[2:])
            except ValueError:
                self.raise_error("Hex string in denseAttr is invalid")

            # Handle splat values given in hex
            if len(bytes_values) == type.element_type.compile_time_size:
                bytes_values *= type_num_values

            # Create attribute
            attr = DenseIntOrFPElementsAttr(type, BytesAttr(bytes_values))
            if type_num_values != len(attr):
                self.raise_error(
                    f"Shape mismatch in dense literal. Expected {type_num_values} "
                    f"elements from the type, but got {len(attr)} elements."
                )
            return attr

        else:
            # Tensor literal case
            dense_values, shape = dense_contents
            data_values = [
                value.to_type(self, type.element_type) for value in dense_values
            ]
            # Elements from _parse_tensor_literal need to be converted to values.
            if shape:
                # Check that the shape matches the data when given a shaped data.
                # For splat attributes any shape is fine
                if type_shape != shape:
                    self.raise_error(
                        f"Shape mismatch in dense literal. Expected {type_shape} "
                        f"shape from the type, but got {shape} shape."
                    )
            else:
                assert len(data_values) == 1, "Fatal error in parser"
                data_values *= type_num_values

        if isinstance(type.element_type, AnyFloat):
            new_type = cast(RankedStructure[AnyFloat], type)
            new_data = cast(Sequence[int | float], data_values)
            return DenseIntOrFPElementsAttr.from_list(new_type, new_data)
        elif isinstance(type.element_type, ComplexType):
            new_type = cast(RankedStructure[ComplexType], type)
            return DenseIntOrFPElementsAttr.from_list(new_type, data_values)  # pyright: ignore[reportCallIssue,reportUnknownVariableType,reportArgumentType]
        else:
            new_type = cast(RankedStructure[IntegerType | IndexType], type)
            new_data = cast(Sequence[int], data_values)
            return DenseIntOrFPElementsAttr.from_list(new_type, new_data)

    def _parse_builtin_dense_attr(self) -> DenseIntOrFPElementsAttr:
        return self.parse_dense_int_or_fp_elements_attr(None)

    def _parse_builtin_opaque_attr(self):
        str_lit_list = self.parse_comma_separated_list(
            self.Delimiter.ANGLE, self.parse_str_literal
        )

        if len(str_lit_list) != 2:
            self.raise_error("Opaque expects 2 string literal parameters!")

        type = NoneAttr()
        if self.parse_optional_punctuation(":") is not None:
            type = self.expect(
                self.parse_optional_type, "opaque attribute must be typed!"
            )

        return OpaqueAttr.from_strings(*str_lit_list, type=type)

    def _parse_dialect_resource_handle(
        self, dialect_name: str, interface: OpAsmDialectInterface
    ) -> str:
        key = self.parse_identifier(" for resource handle")

        if (dialect_name, key) not in self.dialect_resources:
            key = interface.declare_resource(key)
            self.dialect_resources.add((dialect_name, key))

        return key

    def _parse_builtin_dense_resource_attr(self) -> DenseResourceAttr:
        self.parse_characters("<", " in dense_resource attribute")

        resource_interface = self.ctx.get_dialect("builtin").get_interface(
            OpAsmDialectInterface
        )
        if not resource_interface:
            self.raise_error("builtin dialect should have an OpAsmDialectInterface")

        resource_handle = self._parse_dialect_resource_handle(
            "builtin", resource_interface
        )

        self.parse_characters(">", " in dense_resource attribute")
        self.parse_characters(":", " in dense_resource attribute")

        type = self.parse_type()
        if not isinstance(type, ShapedType):
            self.raise_error(f"dense resource should have a shaped type, got: {type}")

        return DenseResourceAttr.from_params(resource_handle, type)

    def _parse_typed_integer(
        self,
        type: IntegerType,
        allow_boolean: bool = True,
        allow_negative: bool = True,
        context_msg: str = "",
    ) -> int:
        """
        Parse an (possible negative) integer. The integer can
        either be decimal or hexadecimal.
        Optionally allow parsing of 'true' or 'false' into 1 and 0.
        """

        pos = self.pos
        res = self.parse_integer(
            allow_boolean=allow_boolean,
            allow_negative=allow_negative,
            context_msg=context_msg,
        )

        try:
            type.verify_value(res)
        except VerifyException as e:
            self.raise_error(str(e), pos, self.pos)

        return res

    def _parse_builtin_densearray_attr(self) -> DenseArrayBase | None:
        self.parse_characters("<", " in dense array")
        pos = self.pos
        element_type = self.parse_attribute()

        if not isa(element_type, IntegerType | AnyFloat):
            self.raise_error(
                "dense array element type must be an integer or floating point type",
                pos,
                self.pos,
            )

        # Empty array
        if self.parse_optional_punctuation(">"):
            return DenseArrayBase.from_list(element_type, [])

        self.parse_characters(":", " in dense array")

        if isinstance(element_type, IntegerType):
            values = self.parse_comma_separated_list(
                self.Delimiter.NONE,
                lambda: self._parse_typed_integer(element_type, allow_boolean=True),
            )
            res = DenseArrayBase.from_list(element_type, values)
        else:
            values = self.parse_comma_separated_list(
                self.Delimiter.NONE,
                lambda: self.parse_float(),
            )
            res = DenseArrayBase.from_list(element_type, values)

        self.parse_characters(">", " in dense array")

        return res

    def _parse_builtin_affine_map(self) -> AffineMapAttr:
        self.parse_characters("<", " in affine_map attribute")
        affine_map = self.parse_affine_map()
        self.parse_characters(">", " in affine_map attribute")
        return AffineMapAttr(affine_map)

    def _parse_builtin_affine_set(self) -> AffineSetAttr:
        self.parse_characters("<", " in affine_set attribute")
        affine_set = self.parse_affine_set()
        self.parse_characters(">", " in affine_set attribute")
        return AffineSetAttr(affine_set)

    @dataclass
    class _TensorLiteralElement:
        """
        The representation of a tensor literal element used during parsing.
        It is either an integer, float, boolean, or complex. It also has a check if
        the element has a negative sign (it is already applied to the value).
        This class is used to parse a tensor literal before the tensor literal
        type is known
        """

        is_negative: bool
        value: int | float | bool | tuple[int, int] | tuple[float, float]
        """
        An integer, float, boolean, integer complex, or float complex value.
        The tuple should be of type `_TensorLiteralElement`, but python does
        not allow classes to self-reference.
        """
        span: Span

        def to_int(
            self,
            parser: AttrParser,
            allow_negative: bool = True,
            allow_booleans: bool = True,
        ) -> int:
            """
            Convert the element to an int value, possibly disallowing negative
            values. Raises an error if the type is compatible.
            """
            if self.is_negative and not allow_negative:
                parser.raise_error(
                    "Expected non-negative integer values", at_position=self.span
                )
            if isinstance(self.value, bool) and not allow_booleans:
                parser.raise_error(
                    "Boolean values are only allowed for i1 types",
                    at_position=self.span,
                )
            if not isinstance(self.value, bool | int):
                parser.raise_error("Expected integer value", at_position=self.span)
            return int(self.value)

        def to_float(self, parser: AttrParser) -> float:
            """
            Convert the element to a float value. Raises an error if the type
            is compatible.
            """
            if isinstance(self.value, tuple):
                parser.raise_error("No conversion from complex to float")
            return float(self.value)

        def to_complex(
            self, parser: AttrParser, type: ComplexType
        ) -> tuple[float, float] | tuple[int, int]:
            assert isinstance(self.value, tuple)

            if isinstance(type.element_type, AnyFloat):
                return (float(self.value[0]), float(self.value[1]))

            match type.element_type:
                case IntegerType():
                    return (int(self.value[0]), int(self.value[1]))

            raise NotImplementedError()

        def to_type(
            self,
            parser: AttrParser,
            type: AnyFloat | IntegerType | IndexType | ComplexType,
        ):
            if isinstance(type, AnyFloat):
                return self.to_float(parser)

            match type:
                case IntegerType():
                    return self.to_int(
                        parser,
                        type.signedness.data != Signedness.UNSIGNED,
                        type.width.data == 1,
                    )
                case IndexType():
                    return self.to_int(
                        parser, allow_negative=True, allow_booleans=False
                    )

                case ComplexType():
                    return self.to_complex(parser, type)

    def _parse_optional_bool_int_or_float(
        self,
    ) -> tuple[bool, Span] | 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.BARE_IDENT and not is_negative:
            if self._current_token.text == "true":
                token = self._consume_token(MLIRTokenKind.BARE_IDENT)
                value = True
            elif self._current_token.text == "false":
                token = self._consume_token(MLIRTokenKind.BARE_IDENT)
                value = False
            else:
                self._resume_from(pos)
                return None

        elif 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] | tuple[bool, bool], 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_bool_int_or_float()
        self.parse_punctuation(",")
        imag, _ = self._parse_bool_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_bool_int_or_float(
        self,
    ) -> tuple[bool, Span] | tuple[int, Span] | tuple[float, Span]:
        retval = self._parse_optional_bool_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
        literal, or a float literal.
        """
        if scalar_span := self._parse_optional_bool_int_or_float():
            value, span = scalar_span
            return self._TensorLiteralElement(value < 0, value, span)
        elif complex_span := self._parse_optional_complex():
            value, span = complex_span
            return self._TensorLiteralElement(False, value, span)

        self.raise_error("Expected either a float, integer, or complex literal")

    def _parse_tensor_literal(
        self,
    ) -> tuple[list[AttrParser._TensorLiteralElement], list[int]]:
        """
        Parse a tensor literal, and returns its flatten data and its shape.

        For instance, [[0, 1, 2], [3, 4, 5]] will return [0, 1, 2, 3, 4, 5] for
        the data, and [2, 3] for the shape.
        """
        res = self.parse_optional_comma_separated_list(
            self.Delimiter.SQUARE, self._parse_tensor_literal
        )
        if res is not None:
            if len(res) == 0:
                return [], [0]
            sub_literal_shape = res[0][1]
            if any(r[1] != sub_literal_shape for r in res):
                self.raise_error(
                    "Tensor literal has inconsistent ranks between elements"
                )
            shape = [len(res)] + sub_literal_shape
            values = [elem for sub_list in res for elem in sub_list[0]]
            return values, shape
        else:
            element = self._parse_tensor_literal_element()
            return [element], []

    def parse_optional_visibility_keyword(self) -> StringAttr | None:
        """
        Parses the visibility keyword of a symbol if present.
        """
        if self.parse_optional_keyword("public"):
            return StringAttr("public")
        elif self.parse_optional_keyword("nested"):
            return StringAttr("nested")
        elif self.parse_optional_keyword("private"):
            return StringAttr("private")
        else:
            return None

    def parse_visibility_keyword(self) -> StringAttr:
        """
        Parses the visibility keyword of a symbol.
        """
        return self.expect(
            self.parse_optional_visibility_keyword, "expect symbol visibility keyword"
        )

    def parse_optional_symbol_name(self) -> StringAttr | None:
        """
        Parse an @-identifier if present, and return its name (without the '@') in a
        string attribute.
        """
        if (token := self._parse_optional_token(MLIRTokenKind.AT_IDENT)) is None:
            return None

        assert len(token.text) > 1, "token should be at least 2 characters long"

        # In the case where the symbol name is quoted, remove the quotes and escape
        # sequences.
        if token.text[1] == '"':
            literal_span = StringLiteral(
                token.span.start + 1, token.span.end, token.span.input
            )
            return StringAttr(literal_span.string_contents)
        return StringAttr(token.text[1:])

    def parse_symbol_name(self) -> StringAttr:
        """
        Parse an @-identifier and return its name (without the '@') in a string
        attribute.
        """
        return self.expect(self.parse_optional_symbol_name, "expect symbol name")

    def _parse_optional_symref_attr(self) -> SymbolRefAttr | None:
        """
        Parse a symbol reference attribute, if present.
          symbol-attr ::= symbol-ref-id (`::` symbol-ref-id)*
          symbol-ref-id ::= at-ident
        """
        # Parse the root symbol
        sym_root = self.parse_optional_symbol_name()
        if sym_root is None:
            return None

        # Parse nested symbols
        refs: list[StringAttr] = []
        while self._current_token.kind == MLIRTokenKind.COLON:
            # Parse `::`. As in MLIR, this require to backtrack if a single `:` is given.
            pos = self._current_token.span.start
            self._consume_token(MLIRTokenKind.COLON)
            if self._parse_optional_token(MLIRTokenKind.COLON) is None:
                self._resume_from(pos)
                break

            refs.append(self.parse_symbol_name())

        return SymbolRefAttr(sym_root, ArrayAttr(refs))

    def parse_optional_location(self) -> LocationAttr | None:
        """
        Parse a location attribute, if present.
          location ::= `loc` `(` `unknown` `)`
        """
        if not self.parse_optional_characters("loc"):
            return None

        with self.in_parens():
            if self.parse_optional_keyword("unknown"):
                return UnknownLoc()

            if (filename := self.parse_optional_str_literal()) is not None:
                self.parse_punctuation(":")
                line = self.parse_integer(False, False)
                self.parse_punctuation(":")
                col = self.parse_integer(False, False)
                return FileLineColLoc(StringAttr(filename), IntAttr(line), IntAttr(col))

            self.raise_error("Unexpected location syntax.")

    def parse_optional_builtin_int_or_float_attr(
        self,
    ) -> IntegerAttr | FloatAttr | None:
        bool = self.try_parse_builtin_boolean_attr()
        if bool is not None:
            return bool

        is_hexadecimal_token: bool = self._current_token.text[:2] in ["0x", "0X"]

        # Parse the value
        if (value := self.parse_optional_number()) is None:
            return None

        # If no types are given, we take the default ones
        if self._current_token.kind != MLIRTokenKind.COLON:
            if isinstance(value, float):
                return FloatAttr(value, f64)
            return IntegerAttr(value, i64)

        # Otherwise, we parse the attribute type
        type = self._parse_attribute_type()

        if isinstance(type, AnyFloat):
            if is_hexadecimal_token:
                assert isinstance(value, int)
                match type:
                    case Float16Type():
                        return FloatAttr(convert_u16_to_f16(value), type)
                    case Float32Type():
                        return FloatAttr(convert_u32_to_f32(value), type)
                    case Float64Type():
                        return FloatAttr(convert_u64_to_f64(value), type)
                    case _:
                        raise NotImplementedError(
                            f"Cannot parse hexadecimal literal for float type of bit width {type}"
                        )
            return FloatAttr(float(value), type)

        if isa(type, IntegerType | IndexType):
            if isinstance(value, float):
                self.raise_error("Floating point value is not valid for integer type.")
            return IntegerAttr(value, type)

        self.raise_error("Invalid type given for integer or float attribute.")

    def try_parse_builtin_boolean_attr(
        self,
    ) -> IntegerAttr[IntegerType | IndexType] | None:
        if (value := self.parse_optional_boolean()) is not None:
            return IntegerAttr(1 if value else 0, IntegerType(1))
        return None

    def _parse_optional_none_type(self) -> NoneType | None:
        """
        Parse a none type, if present
        none-type ::= `none`
        """
        if self.parse_optional_keyword("none"):
            return NoneType()
        return None

    def _parse_optional_string_attr(self) -> StringAttr | None:
        """
        Parse a string attribute, if present.
          string-attr ::= string-literal
        """
        token = self._parse_optional_token(MLIRTokenKind.STRING_LIT)
        return (
            StringAttr(token.kind.get_string_literal_value(token.span))
            if token is not None
            else None
        )

    def _parse_optional_array_attr(self) -> ArrayAttr | None:
        """
        Parse an array attribute, if present, with format:
            array-attr ::= `[` (attribute (`,` attribute)*)? `]`
        """
        attrs = self.parse_optional_comma_separated_list(
            self.Delimiter.SQUARE, self.parse_attribute
        )
        if attrs is None:
            return None
        return ArrayAttr(attrs)

    def parse_function_type(self) -> FunctionType:
        """
        Parse a function type.
            function-type ::= type-list `->` (type | type-list)
            type-list     ::= `(` `)` | `(` type (`,` type)* `)`
        """
        return self.expect(
            self.parse_optional_function_type,
            "function type expected",
        )

    def parse_optional_function_type(self) -> FunctionType | None:
        """
        Parse a function type, if present.
            function-type ::= type-list `->` (type | type-list)
            type-list     ::= `(` `)` | `(` type (`,` type)* `)`
        """
        if self._current_token.kind != MLIRTokenKind.L_PAREN:
            return None

        # Parse the arguments
        args = self.parse_comma_separated_list(self.Delimiter.PAREN, self.parse_type)

        self.parse_punctuation("->")

        # Parse the returns
        returns = self.parse_optional_comma_separated_list(
            self.Delimiter.PAREN, self.parse_type
        )
        if returns is None:
            returns = [self.parse_type()]
        return FunctionType.from_lists(args, returns)

    def parse_paramattr_parameters(
        self, skip_white_space: bool = True
    ) -> list[Attribute]:
        res = self.parse_optional_comma_separated_list(
            self.Delimiter.ANGLE, self.parse_attribute
        )
        if res is None:
            return []
        return res

    def _parse_optional_builtin_dict_attr(self) -> DictionaryAttr | None:
        """
        Parse a dictionary attribute, if present, with format:
        `dictionary-attr ::= `{` ( attribute-entry (`,` attribute-entry)* )? `}`
        `attribute-entry` := (bare-id | string-literal) `=` attribute
        """
        if self._current_token.kind != MLIRTokenKind.L_BRACE:
            return None
        return self._parse_builtin_dict_attr()

    def _parse_builtin_dict_attr(self) -> DictionaryAttr:
        """
        Parse a dictionary attribute with format:
        `dictionary-attr ::= `{` ( attribute-entry (`,` attribute-entry)* )? `}`
        `attribute-entry` := (bare-id | string-literal) `=` attribute
        """
        return DictionaryAttr(
            immutabledict[str, Attribute](self.parse_optional_dictionary_attr_dict())
        )

    _builtin_integer_type_regex = re.compile(r"^[su]?i(\d+)$")
    _builtin_float_type_regex = re.compile(r"^f(\d+)$")

    def _parse_optional_integer_or_float_type(self) -> TypeAttribute | None:
        """
        Parse as integer or float type, if present.
          integer-or-float-type ::= index-type | integer-type | float-type
          index-type            ::= `index`
          integer-type          ::= (`i` | `si` | `ui`) decimal-literal
          float-type            ::= `f16` | `f32` | `f64` | `f80` | `f128` | `bf16`
        """
        if self._current_token.kind != MLIRTokenKind.BARE_IDENT:
            return None
        name = self._current_token.text

        # Index type
        if name == "index":
            self._consume_token()
            return IndexType()

        # Integer type
        if (match := self._builtin_integer_type_regex.match(name)) is not None:
            signedness = {
                "s": Signedness.SIGNED,
                "u": Signedness.UNSIGNED,
                "i": Signedness.SIGNLESS,
            }
            self._consume_token()
            return IntegerType(int(match.group(1)), signedness[name[0]])

        # bf16 type
        if name == "bf16":
            self._consume_token()
            return bf16

        # Float type
        if (re_match := self._builtin_float_type_regex.match(name)) is not None:
            width = int(re_match.group(1))
            type = {
                16: Float16Type,
                32: Float32Type,
                64: Float64Type,
                80: Float80Type,
                128: Float128Type,
            }.get(width, None)
            if type is None:
                self.raise_error(f"Unsupported floating point width: {width}")
            self._consume_token()
            return type()

        return None

    def _parse_optional_builtin_type(self) -> TypeAttribute | None:
        """
        parse a builtin-type, like i32, index, vector<i32>, none, if present.
        """

        # Check for a function type
        if (function_type := self.parse_optional_function_type()) is not None:
            return function_type

        # Check for an integer or float type
        if (number_type := self._parse_optional_integer_or_float_type()) is not None:
            return number_type

        # check for a none type
        if (none_type := self._parse_optional_none_type()) is not None:
            return none_type

        return self._parse_optional_builtin_parametrized_type()

    def parse_affine_map(self) -> AffineMap:
        affp = affine_parser.AffineParser(self._parser_state)
        return affp.parse_affine_map()

    def parse_affine_set(self) -> AffineSet:
        affp = affine_parser.AffineParser(self._parser_state)
        return affp.parse_affine_set()

ctx: Context instance-attribute

attribute_aliases: dict[str, Attribute] = field(default_factory=(dict[str, Attribute])) class-attribute instance-attribute

A dictionary of aliases for attributes. The key is the alias name, including the ! or # prefix.

dialect_resources: set[tuple[str, str]] = field(default_factory=(set[tuple[str, str]])) class-attribute instance-attribute

Set of resource references encountered during parsing.

parse_optional_type() -> TypeAttribute | None

Parse an xDSL type, if present. An xDSL type is either a builtin type, which can have various format, or a dialect type, with the following format: type ::= builtin-type | dialect-type | alias-type alias-type ::= ! type-name dialect-type ::= ! type-name (< dialect-type-contents+ >)? type-name ::= bare-id dialect-type-contents ::= < dialect-attribute-contents+ > | ( dialect-attribute-contents+ ) | [ dialect-attribute-contents+ ] | { dialect-attribute-contents+ } | [^[]<>(){}]+

Source code in xdsl/parser/attribute_parser.py
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
def parse_optional_type(self) -> TypeAttribute | None:
    """
    Parse an xDSL type, if present.
    An xDSL type is either a builtin type, which can have various format,
    or a dialect type, with the following format:
        type          ::= builtin-type | dialect-type | alias-type
        alias-type    ::= `!` type-name
        dialect-type  ::= `!` type-name (`<` dialect-type-contents+ `>`)?
        type-name     ::= bare-id
        dialect-type-contents ::= `<` dialect-attribute-contents+ `>`
                        | `(` dialect-attribute-contents+ `)`
                        | `[` dialect-attribute-contents+ `]`
                        | `{` dialect-attribute-contents+ `}`
                        | [^[]<>(){}\0]+
    """
    if (
        token := self._parse_optional_token(MLIRTokenKind.EXCLAMATION_IDENT)
    ) is not None:
        return self._parse_extended_type_or_attribute(token.text[1:], True)
    return self._parse_optional_builtin_type()

parse_type() -> TypeAttribute

Parse an xDSL type. An xDSL type is either a builtin type, which can have various format, or a dialect or alias type, with the following format: type ::= builtin-type | dialect-type | alias-type alias-type ::= ! type-name dialect-type ::= ! type-name (< dialect-type-contents+ >)? type-name ::= bare-id dialect-type-contents ::= < dialect-attribute-contents+ > | ( dialect-attribute-contents+ ) | [ dialect-attribute-contents+ ] | { dialect-attribute-contents+ } | [^[]<>(){}]+

Source code in xdsl/parser/attribute_parser.py
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
def parse_type(self) -> TypeAttribute:
    """
    Parse an xDSL type.
    An xDSL type is either a builtin type, which can have various format,
    or a dialect or alias type, with the following format:
        type          ::= builtin-type | dialect-type | alias-type
        alias-type    ::= `!` type-name
        dialect-type  ::= `!` type-name (`<` dialect-type-contents+ `>`)?
        type-name     ::= bare-id
        dialect-type-contents ::= `<` dialect-attribute-contents+ `>`
                        | `(` dialect-attribute-contents+ `)`
                        | `[` dialect-attribute-contents+ `]`
                        | `{` dialect-attribute-contents+ `}`
                        | [^[]<>(){}\0]+
    """
    return self.expect(self.parse_optional_type, "type expected")

parse_optional_attribute() -> Attribute | None

Parse an xDSL attribute, if present. An attribute is either a builtin attribute, which can have various format, or a dialect or alias attribute, with the following format: attr ::= builtin-attr | dialect-attr | alias-attr alias-attr ::= ! attr-name dialect-attr ::= # attr-name (< dialect-attr-contents+ >)? attr-name ::= bare-id dialect-attr-contents ::= < dialect-attribute-contents+ > | ( dialect-attribute-contents+ ) | [ dialect-attribute-contents+ ] | { dialect-attribute-contents+ } | [^[]<>(){}]+

Source code in xdsl/parser/attribute_parser.py
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
def parse_optional_attribute(self) -> Attribute | None:
    """
    Parse an xDSL attribute, if present.
    An attribute is either a builtin attribute, which can have various format,
    or a dialect or alias attribute, with the following format:
        attr          ::= builtin-attr | dialect-attr | alias-attr
        alias-attr    ::= `!` attr-name
        dialect-attr  ::= `#` attr-name (`<` dialect-attr-contents+ `>`)?
        attr-name     ::= bare-id
        dialect-attr-contents ::= `<` dialect-attribute-contents+ `>`
                        | `(` dialect-attribute-contents+ `)`
                        | `[` dialect-attribute-contents+ `]`
                        | `{` dialect-attribute-contents+ `}`
                        | [^[]<>(){}\0]+
    """
    if (token := self._parse_optional_token(MLIRTokenKind.HASH_IDENT)) is not None:
        return self._parse_extended_type_or_attribute(token.text[1:], False)
    return self._parse_optional_builtin_attr()

parse_attribute() -> Attribute

Parse an xDSL attribute. An attribute is either a builtin attribute, which can have various format, or a dialect attribute, with the following format: attr ::= builtin-attr | dialect-attr | alias-attr alias-attr ::= ! attr-name dialect-attr ::= # attr-name (< dialect-attr-contents+ >)? attr-name ::= bare-id dialect-attr-contents ::= < dialect-attribute-contents+ > | ( dialect-attribute-contents+ ) | [ dialect-attribute-contents+ ] | { dialect-attribute-contents+ } | [^[]<>(){}]+

Source code in xdsl/parser/attribute_parser.py
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
def parse_attribute(self) -> Attribute:
    """
    Parse an xDSL attribute.
    An attribute is either a builtin attribute, which can have various format,
    or a dialect attribute, with the following format:
        attr          ::= builtin-attr | dialect-attr | alias-attr
        alias-attr    ::= `!` attr-name
        dialect-attr  ::= `#` attr-name (`<` dialect-attr-contents+ `>`)?
        attr-name     ::= bare-id
        dialect-attr-contents ::= `<` dialect-attribute-contents+ `>`
                        | `(` dialect-attribute-contents+ `)`
                        | `[` dialect-attribute-contents+ `]`
                        | `{` dialect-attribute-contents+ `}`
                        | [^[]<>(){}\0]+
    """
    return self.expect(self.parse_optional_attribute, "attribute expected")

parse_optional_dictionary_attr_dict() -> dict[str, Attribute]

Source code in xdsl/parser/attribute_parser.py
215
216
217
218
219
220
221
222
223
224
225
def parse_optional_dictionary_attr_dict(self) -> dict[str, Attribute]:
    attrs = self.parse_optional_comma_separated_list(
        self.Delimiter.BRACES, self._parse_attribute_entry
    )
    if attrs is None:
        return dict()

    if (key := self._find_duplicated_key(attrs)) is not None:
        self.raise_error(f"Duplicate key '{key}' in dictionary attribute")

    return dict(attrs)

parse_shape_dimension(allow_dynamic: bool = True) -> int

Parse a single shape dimension, which is a decimal literal or ?. ? is interpreted as DYNAMIC_INDEX. Note that if the integer literal is in hexadecimal form, it will be split into multiple tokens. For example, 0x10 will be split into 0 and x10. Optionally allows to not parse ? as DYNAMIC_INDEX.

Source code in xdsl/parser/attribute_parser.py
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
def parse_shape_dimension(self, allow_dynamic: bool = True) -> int:
    """
    Parse a single shape dimension, which is a decimal literal or `?`.
    `?` is interpreted as DYNAMIC_INDEX. Note that if the integer literal is in
    hexadecimal form, it will be split into multiple tokens. For example,
    `0x10` will be split into `0` and `x10`.
    Optionally allows to not parse `?` as DYNAMIC_INDEX.
    """
    if self._current_token.kind not in (
        MLIRTokenKind.INTEGER_LIT,
        MLIRTokenKind.QUESTION,
    ):
        if allow_dynamic:
            self.raise_error(
                "Expected either integer literal or '?' in shape dimension, "
                f"got {self._current_token.kind.name}!"
            )
        self.raise_error(
            "Expected integer literal in shape dimension, "
            f"got {self._current_token.kind.name}!"
        )

    if self.parse_optional_punctuation("?") is not None:
        if allow_dynamic:
            return DYNAMIC_INDEX
        self.raise_error("Unexpected dynamic dimension!")

    # If the integer literal starts with `0x`, this is decomposed into
    # `0` and `x`.
    int_token = self._consume_token(MLIRTokenKind.INTEGER_LIT)
    if int_token.text[:2] == "0x":
        self._resume_from(int_token.span.start + 1)
        return 0

    return int_token.kind.get_int_value(int_token.span)

parse_shape_delimiter() -> None

Parse 'x', a shape delimiter. Note that if 'x' is followed by other characters, it will split the token. For instance, 'x1' will be split into 'x' and '1'.

Source code in xdsl/parser/attribute_parser.py
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
def parse_shape_delimiter(self) -> None:
    """
    Parse 'x', a shape delimiter. Note that if 'x' is followed by other
    characters, it will split the token. For instance, 'x1' will be split
    into 'x' and '1'.
    """
    if self._parse_optional_shape_delimiter() is not None:
        return

    token = self._current_token
    tk = token.kind

    err_val = tk.name if tk != MLIRTokenKind.BARE_IDENT else token.text

    self.raise_error(
        f"Expected 'x' in shape delimiter, got {err_val}",
    )

parse_dimension_list() -> list[int]

Parse a dimension list with the following format

dimension-list ::= (dimension x)* dimension

each dimension is also required to be non-negative.

Source code in xdsl/parser/attribute_parser.py
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
def parse_dimension_list(self) -> list[int]:
    """
    Parse a dimension list with the following format:
      dimension-list ::= (dimension `x`)* dimension
    each dimension is also required to be non-negative.
    """
    dims: list[int] = []
    accepted_token_kinds = (MLIRTokenKind.INTEGER_LIT, MLIRTokenKind.QUESTION)

    # empty case
    if self._current_token.kind not in accepted_token_kinds:
        return []

    # parse first number
    dim = self.parse_shape_dimension()
    dims.append(dim)

    while self._parse_optional_shape_delimiter():
        if self._current_token.kind in accepted_token_kinds:
            dim = self.parse_shape_dimension()
            dims.append(dim)
        else:
            # We want to preserve a trailing `x` as it provides useful
            # information to the rest of the parser, so we undo the parse
            self._resume_from(self._current_token.span.start - 1)
            break

    return dims

parse_ranked_shape() -> tuple[list[int], Attribute]

Parse a ranked shape with the following format

ranked-shape ::= (dimension x)* type dimension ::= ? | decimal-literal

each dimension is also required to be non-negative.

Source code in xdsl/parser/attribute_parser.py
552
553
554
555
556
557
558
559
560
561
562
563
564
def parse_ranked_shape(self) -> tuple[list[int], Attribute]:
    """
    Parse a ranked shape with the following format:
      ranked-shape ::= (dimension `x`)* type
      dimension ::= `?` | decimal-literal
    each dimension is also required to be non-negative.
    """
    dims = self.parse_dimension_list()
    if dims:
        self.parse_shape_delimiter()

    type = self.expect(self.parse_optional_type, "Expected shape type.")
    return dims, type

parse_shape() -> tuple[list[int] | None, Attribute]

Parse a ranked or unranked shape with the following format:

shape ::= ranked-shape | unranked-shape ranked-shape ::= (dimension x)* type unranked-shape ::= *x type dimension ::= ? | decimal-literal

each dimension is also required to be non-negative.

Source code in xdsl/parser/attribute_parser.py
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
def parse_shape(self) -> tuple[list[int] | None, Attribute]:
    """
    Parse a ranked or unranked shape with the following format:

    shape ::= ranked-shape | unranked-shape
    ranked-shape ::= (dimension `x`)* type
    unranked-shape ::= `*`x type
    dimension ::= `?` | decimal-literal

    each dimension is also required to be non-negative.
    """
    if self.parse_optional_punctuation("*") is not None:
        self.parse_shape_delimiter()
        type = self.expect(self.parse_optional_type, "Expected shape type.")
        return None, type
    return self.parse_ranked_shape()

parse_optional_unit_attr() -> Attribute | None

Parse a value of unit type. unit-attribute ::= unit

Source code in xdsl/parser/attribute_parser.py
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
def parse_optional_unit_attr(self) -> Attribute | None:
    """
    Parse a value of `unit` type.
    unit-attribute ::= `unit`
    """
    if self._current_token.kind != MLIRTokenKind.BARE_IDENT:
        return None
    name = self._current_token.span.text

    # Unit attribute
    if name == "unit":
        self._consume_token()
        return UnitAttr()

    return None

parse_dense_int_or_fp_elements_attr(type: RankedStructure[AnyDenseElement] | None) -> DenseIntOrFPElementsAttr

Source code in xdsl/parser/attribute_parser.py
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
def parse_dense_int_or_fp_elements_attr(
    self, type: RankedStructure[AnyDenseElement] | None
) -> DenseIntOrFPElementsAttr:
    dense_contents: (
        tuple[list[AttrParser._TensorLiteralElement], list[int]] | str | None
    )
    """
    If `None`, then the contents are empty.
    If `str`, then this is a hex-encoded string containing the data, which doesn't
    carry shape information.
    Otherwise, a tuple of `elements` and `shape`.
    If `shape` is `[]`, then this is a splat attribute, meaning it has the same value
    everywhere.
    """

    self.parse_punctuation("<", " in dense attribute")
    if self.parse_optional_punctuation(">") is not None:
        # Empty case
        dense_contents = None
    else:
        if (hex_string := self.parse_optional_str_literal()) is not None:
            dense_contents, shape = hex_string, None
        else:
            # Expect a tensor literal instead
            dense_contents = self._parse_tensor_literal()
        self.parse_punctuation(">", " in dense attribute")

    # Parse the dense type and check for correctness
    if type is None:
        self.parse_punctuation(":", " in dense attribute")
        type = self._parse_dense_literal_type()
    type_shape = list(type.get_shape())
    type_num_values = math.prod(type_shape)

    if dense_contents is None:
        # Empty case
        if type_num_values != 0:
            self.raise_error(
                "Expected at least one element in the dense literal, but got None"
            )
        data_values = []
    elif isinstance(dense_contents, str):
        # Hex-encoded string case: convert straight to bytes (without the 0x prefix)
        try:
            bytes_values = bytes.fromhex(dense_contents[2:])
        except ValueError:
            self.raise_error("Hex string in denseAttr is invalid")

        # Handle splat values given in hex
        if len(bytes_values) == type.element_type.compile_time_size:
            bytes_values *= type_num_values

        # Create attribute
        attr = DenseIntOrFPElementsAttr(type, BytesAttr(bytes_values))
        if type_num_values != len(attr):
            self.raise_error(
                f"Shape mismatch in dense literal. Expected {type_num_values} "
                f"elements from the type, but got {len(attr)} elements."
            )
        return attr

    else:
        # Tensor literal case
        dense_values, shape = dense_contents
        data_values = [
            value.to_type(self, type.element_type) for value in dense_values
        ]
        # Elements from _parse_tensor_literal need to be converted to values.
        if shape:
            # Check that the shape matches the data when given a shaped data.
            # For splat attributes any shape is fine
            if type_shape != shape:
                self.raise_error(
                    f"Shape mismatch in dense literal. Expected {type_shape} "
                    f"shape from the type, but got {shape} shape."
                )
        else:
            assert len(data_values) == 1, "Fatal error in parser"
            data_values *= type_num_values

    if isinstance(type.element_type, AnyFloat):
        new_type = cast(RankedStructure[AnyFloat], type)
        new_data = cast(Sequence[int | float], data_values)
        return DenseIntOrFPElementsAttr.from_list(new_type, new_data)
    elif isinstance(type.element_type, ComplexType):
        new_type = cast(RankedStructure[ComplexType], type)
        return DenseIntOrFPElementsAttr.from_list(new_type, data_values)  # pyright: ignore[reportCallIssue,reportUnknownVariableType,reportArgumentType]
    else:
        new_type = cast(RankedStructure[IntegerType | IndexType], type)
        new_data = cast(Sequence[int], data_values)
        return DenseIntOrFPElementsAttr.from_list(new_type, new_data)

parse_optional_visibility_keyword() -> StringAttr | None

Parses the visibility keyword of a symbol if present.

Source code in xdsl/parser/attribute_parser.py
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
def parse_optional_visibility_keyword(self) -> StringAttr | None:
    """
    Parses the visibility keyword of a symbol if present.
    """
    if self.parse_optional_keyword("public"):
        return StringAttr("public")
    elif self.parse_optional_keyword("nested"):
        return StringAttr("nested")
    elif self.parse_optional_keyword("private"):
        return StringAttr("private")
    else:
        return None

parse_visibility_keyword() -> StringAttr

Parses the visibility keyword of a symbol.

Source code in xdsl/parser/attribute_parser.py
1241
1242
1243
1244
1245
1246
1247
def parse_visibility_keyword(self) -> StringAttr:
    """
    Parses the visibility keyword of a symbol.
    """
    return self.expect(
        self.parse_optional_visibility_keyword, "expect symbol visibility keyword"
    )

parse_optional_symbol_name() -> StringAttr | None

Parse an @-identifier if present, and return its name (without the '@') in a string attribute.

Source code in xdsl/parser/attribute_parser.py
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
def parse_optional_symbol_name(self) -> StringAttr | None:
    """
    Parse an @-identifier if present, and return its name (without the '@') in a
    string attribute.
    """
    if (token := self._parse_optional_token(MLIRTokenKind.AT_IDENT)) is None:
        return None

    assert len(token.text) > 1, "token should be at least 2 characters long"

    # In the case where the symbol name is quoted, remove the quotes and escape
    # sequences.
    if token.text[1] == '"':
        literal_span = StringLiteral(
            token.span.start + 1, token.span.end, token.span.input
        )
        return StringAttr(literal_span.string_contents)
    return StringAttr(token.text[1:])

parse_symbol_name() -> StringAttr

Parse an @-identifier and return its name (without the '@') in a string attribute.

Source code in xdsl/parser/attribute_parser.py
1268
1269
1270
1271
1272
1273
def parse_symbol_name(self) -> StringAttr:
    """
    Parse an @-identifier and return its name (without the '@') in a string
    attribute.
    """
    return self.expect(self.parse_optional_symbol_name, "expect symbol name")

parse_optional_location() -> LocationAttr | None

Parse a location attribute, if present. location ::= loc ( unknown )

Source code in xdsl/parser/attribute_parser.py
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
def parse_optional_location(self) -> LocationAttr | None:
    """
    Parse a location attribute, if present.
      location ::= `loc` `(` `unknown` `)`
    """
    if not self.parse_optional_characters("loc"):
        return None

    with self.in_parens():
        if self.parse_optional_keyword("unknown"):
            return UnknownLoc()

        if (filename := self.parse_optional_str_literal()) is not None:
            self.parse_punctuation(":")
            line = self.parse_integer(False, False)
            self.parse_punctuation(":")
            col = self.parse_integer(False, False)
            return FileLineColLoc(StringAttr(filename), IntAttr(line), IntAttr(col))

        self.raise_error("Unexpected location syntax.")

parse_optional_builtin_int_or_float_attr() -> IntegerAttr | FloatAttr | None

Source code in xdsl/parser/attribute_parser.py
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
def parse_optional_builtin_int_or_float_attr(
    self,
) -> IntegerAttr | FloatAttr | None:
    bool = self.try_parse_builtin_boolean_attr()
    if bool is not None:
        return bool

    is_hexadecimal_token: bool = self._current_token.text[:2] in ["0x", "0X"]

    # Parse the value
    if (value := self.parse_optional_number()) is None:
        return None

    # If no types are given, we take the default ones
    if self._current_token.kind != MLIRTokenKind.COLON:
        if isinstance(value, float):
            return FloatAttr(value, f64)
        return IntegerAttr(value, i64)

    # Otherwise, we parse the attribute type
    type = self._parse_attribute_type()

    if isinstance(type, AnyFloat):
        if is_hexadecimal_token:
            assert isinstance(value, int)
            match type:
                case Float16Type():
                    return FloatAttr(convert_u16_to_f16(value), type)
                case Float32Type():
                    return FloatAttr(convert_u32_to_f32(value), type)
                case Float64Type():
                    return FloatAttr(convert_u64_to_f64(value), type)
                case _:
                    raise NotImplementedError(
                        f"Cannot parse hexadecimal literal for float type of bit width {type}"
                    )
        return FloatAttr(float(value), type)

    if isa(type, IntegerType | IndexType):
        if isinstance(value, float):
            self.raise_error("Floating point value is not valid for integer type.")
        return IntegerAttr(value, type)

    self.raise_error("Invalid type given for integer or float attribute.")

try_parse_builtin_boolean_attr() -> IntegerAttr[IntegerType | IndexType] | None

Source code in xdsl/parser/attribute_parser.py
1366
1367
1368
1369
1370
1371
def try_parse_builtin_boolean_attr(
    self,
) -> IntegerAttr[IntegerType | IndexType] | None:
    if (value := self.parse_optional_boolean()) is not None:
        return IntegerAttr(1 if value else 0, IntegerType(1))
    return None

parse_function_type() -> FunctionType

Parse a function type. function-type ::= type-list -> (type | type-list) type-list ::= ( ) | ( type (, type)* )

Source code in xdsl/parser/attribute_parser.py
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
def parse_function_type(self) -> FunctionType:
    """
    Parse a function type.
        function-type ::= type-list `->` (type | type-list)
        type-list     ::= `(` `)` | `(` type (`,` type)* `)`
    """
    return self.expect(
        self.parse_optional_function_type,
        "function type expected",
    )

parse_optional_function_type() -> FunctionType | None

Parse a function type, if present. function-type ::= type-list -> (type | type-list) type-list ::= ( ) | ( type (, type)* )

Source code in xdsl/parser/attribute_parser.py
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
def parse_optional_function_type(self) -> FunctionType | None:
    """
    Parse a function type, if present.
        function-type ::= type-list `->` (type | type-list)
        type-list     ::= `(` `)` | `(` type (`,` type)* `)`
    """
    if self._current_token.kind != MLIRTokenKind.L_PAREN:
        return None

    # Parse the arguments
    args = self.parse_comma_separated_list(self.Delimiter.PAREN, self.parse_type)

    self.parse_punctuation("->")

    # Parse the returns
    returns = self.parse_optional_comma_separated_list(
        self.Delimiter.PAREN, self.parse_type
    )
    if returns is None:
        returns = [self.parse_type()]
    return FunctionType.from_lists(args, returns)

parse_paramattr_parameters(skip_white_space: bool = True) -> list[Attribute]

Source code in xdsl/parser/attribute_parser.py
1439
1440
1441
1442
1443
1444
1445
1446
1447
def parse_paramattr_parameters(
    self, skip_white_space: bool = True
) -> list[Attribute]:
    res = self.parse_optional_comma_separated_list(
        self.Delimiter.ANGLE, self.parse_attribute
    )
    if res is None:
        return []
    return res

parse_affine_map() -> AffineMap

Source code in xdsl/parser/attribute_parser.py
1540
1541
1542
def parse_affine_map(self) -> AffineMap:
    affp = affine_parser.AffineParser(self._parser_state)
    return affp.parse_affine_map()

parse_affine_set() -> AffineSet

Source code in xdsl/parser/attribute_parser.py
1544
1545
1546
def parse_affine_set(self) -> AffineSet:
    affp = affine_parser.AffineParser(self._parser_state)
    return affp.parse_affine_set()

__init__(_parser_state: ParserState[TokenKindT], ctx: Context, attribute_aliases: dict[str, Attribute] = dict[str, Attribute](), dialect_resources: set[tuple[str, str]] = set[tuple[str, str]]()) -> None

Parser

Bases: AttrParser

Basic recursive descent parser.

methods marked try_... will attempt to parse, and return None if they failed. If they return None they must make sure to restore all state.

methods marked parse_... will do "greedy" parsing, meaning they consume as much as they can. They will also throw an error if the think they should still be parsing. e.g. when parsing a list of numbers separated by '::', the following input will trigger an exception: 1::2:: Due to the '::' present after the last element. This is useful for parsing lists, as a trailing separator is usually considered a syntax error there.

must_ type parsers are preferred because they are explicit about their failure modes.

Source code in xdsl/parser/core.py
  64
  65
  66
  67
  68
  69
  70
  71
  72
  73
  74
  75
  76
  77
  78
  79
  80
  81
  82
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
class Parser(AttrParser):
    """
    Basic recursive descent parser.

    methods marked try_... will attempt to parse, and return None if they failed.
    If they return None they must make sure to restore all state.

    methods marked parse_... will do "greedy" parsing, meaning they consume
    as much as they can. They will also throw an error if the think they should
    still be parsing. e.g. when parsing a list of numbers separated by '::',
    the following input will trigger an exception:
        1::2::
    Due to the '::' present after the last element. This is useful for parsing lists,
    as a trailing separator is usually considered a syntax error there.

    must_ type parsers are preferred because they are explicit about their failure modes.
    """

    ssa_values: dict[str, tuple[SSAValue, ...]]
    blocks: dict[str, tuple[Block, Span | None]]
    forward_block_references: dict[str, list[Span]]
    """
    Blocks we encountered references to before the definition (must be empty after
    parsing of region completes)
    """
    forward_ssa_references: dict[str, dict[int, ForwardDeclaredValue]]
    """
    SSA values that are referenced, but are not yet defined.
    This field map a name and a tuple index to the forward declared SSA value.
    """

    def __init__(
        self,
        ctx: Context,
        input: str,
        name: str = "<unknown>",
    ) -> None:
        super().__init__(ParserState(MLIRLexer(Input(input, name))), ctx)
        self.ssa_values = dict()
        self.blocks = dict()
        self.forward_block_references = defaultdict(list)
        self.forward_ssa_references = dict()

    def parse_module(self, allow_implicit_module: bool = True) -> ModuleOp:
        module_op: Operation

        if not allow_implicit_module:
            parsed_op = self.parse_optional_operation()

            if parsed_op is None:
                self.raise_error("Could not parse entire input!")

            if not isinstance(parsed_op, ModuleOp):
                self._resume_from(0)
                self.raise_error("builtin.module operation expected", 0)

            module_op = parsed_op
        else:
            parsed_ops: list[Operation] = []

            while self._current_token.kind != MLIRTokenKind.EOF:
                if self._current_token.kind in (
                    MLIRTokenKind.HASH_IDENT,
                    MLIRTokenKind.EXCLAMATION_IDENT,
                ):
                    self._parse_alias_def()
                    continue
                if self._current_token.kind in (MLIRTokenKind.FILE_METADATA_BEGIN,):
                    self._parse_file_metadata_dictionary()
                    continue
                if (parsed_op := self.parse_optional_operation()) is not None:
                    parsed_ops.append(parsed_op)
                    continue
                self.raise_error("Could not parse entire input!")

            if len(parsed_ops) == 0:
                self.raise_error("Could not parse entire input!")

            module_op = (
                parsed_ops[0]
                if isinstance(parsed_ops[0], ModuleOp) and len(parsed_ops) == 1
                else ModuleOp(parsed_ops)
            )

        if self.forward_ssa_references:
            value_names = ", ".join(
                "%" + name for name in self.forward_ssa_references.keys()
            )
            self.raise_error(f"values used but not defined: [{value_names}]")

        return module_op

    def _parse_alias_def(self):
        """
        Parse an attribute or type alias definition with format:
            alias-def           ::= type-alias-def | attribute-alias-def
            type-alias-def      ::= `!` bare-id `=` type
            attribute-alias-def ::= `#` `alias` bare-id `=` attribute
        """
        if (
            token := self._parse_optional_token_in(
                [MLIRTokenKind.EXCLAMATION_IDENT, MLIRTokenKind.HASH_IDENT]
            )
        ) is None:
            self.raise_error("expected attribute name")

        type_or_attr_name = token.text
        if type_or_attr_name in self.attribute_aliases:
            self.raise_error(f"re-declaration of alias '{type_or_attr_name}'")

        self.parse_punctuation("=", "after attribute alias name")
        value = self.parse_attribute()
        self.attribute_aliases[type_or_attr_name] = value

    def _get_block_from_name(self, block_name: Span) -> Block:
        """
        This function takes a span containing a block id (like `^bb42`) and returns a block.

        If the block definition was not seen yet, we create a forward declaration.
        """
        name = block_name.text[1:]
        if name not in self.blocks:
            self.forward_block_references[name].append(block_name)
            block = Block()
            if Block.is_valid_name(name) and not Block.is_default_block_name(name):
                block.name_hint = name  # setter verifies validity
            self.blocks[name] = (block, None)
        return self.blocks[name][0]

    def _parse_optional_block_arg_list(self, block: Block):
        """
        Parse a block argument list, if present, and add them to the block.

            value-id-and-type-list ::= value-id-and-type (`,` ssa-id-and-type)*
            block-arg-list ::= `(` value-id-and-type-list? `)`
        """
        if self._current_token.kind != MLIRTokenKind.L_PAREN:
            return None

        def parse_argument() -> None:
            """Parse a single block argument with its type."""
            arg_name = self._parse_token(
                MLIRTokenKind.PERCENT_IDENT, "block argument expected"
            ).span
            self.parse_punctuation(":")
            arg_type = self.parse_attribute()
            self.parse_optional_location()

            # Insert the block argument in the block, and register it in the parser
            block_arg = block.insert_arg(arg_type, len(block.args))
            self._register_ssa_definition(arg_name.text[1:], (block_arg,), arg_name)

        self.parse_comma_separated_list(self.Delimiter.PAREN, parse_argument)
        return block

    def _parse_block_body(self, block: Block):
        """
        Parse a block body, which consist of a list of operations.
        The operations are added at the end of the block.
        """
        while (op := self.parse_optional_operation()) is not None:
            block.add_op(op)

    def _parse_block(self) -> Block:
        """
        Parse a block with the following format:
          block ::= block-label operation*
          block-label    ::= block-id block-arg-list? `:`
          block-id       ::= caret-id
          block-arg-list ::= `(` ssa-id-and-type-list? `)`
        """
        name_token = self._parse_token(
            MLIRTokenKind.CARET_IDENT, " in block definition"
        )

        name = name_token.text[1:]
        if name not in self.blocks:
            block = Block()
            self.blocks[name] = (block, name_token.span)
        else:
            block, original_definition = self.blocks[name]
            if original_definition is not None:
                raise MultipleSpansParseError(
                    name_token.span,
                    f"re-declaration of block '{name}'",
                    "originally declared here:",
                    [(original_definition, None)],
                )
            self.forward_block_references.pop(name)

        # Don't set name_hint for blocks that match the default pattern
        if not Block.is_default_block_name(name):
            block.name_hint = name  # setter verifies validity
        # If it matches pattern "bb" followed by digits, leave name_hint as None

        self._parse_optional_block_arg_list(block)
        self.parse_punctuation(":")
        self._parse_block_body(block)
        return block

    _decimal_integer_regex = re.compile(r"[0-9]+")

    def parse_optional_unresolved_operand(self) -> UnresolvedOperand | None:
        """
        Parse an operand with format `%<value-id>(#<int-literal>)?`, if present.
        The operand may be forward declared.
        """
        name_token = self._parse_optional_token(MLIRTokenKind.PERCENT_IDENT)
        if name_token is None:
            return None

        index = 0
        index_token = self._parse_optional_token(MLIRTokenKind.HASH_IDENT)
        if index_token is not None:
            if re.fullmatch(self._decimal_integer_regex, index_token.text[1:]) is None:
                self.raise_error(
                    "Expected integer as SSA value tuple index", index_token.span
                )
            index = int(index_token.text[1:], 10)

        return UnresolvedOperand(name_token.span, index)

    def parse_unresolved_operand(
        self, msg: str = "operand expected"
    ) -> UnresolvedOperand:
        """
        Parse an operand with format `%<value-id>(#<int-literal>)?`.
        The operand may be forward declared.
        """
        return self.expect(self.parse_optional_unresolved_operand, msg)

    def resolve_operand(self, operand: UnresolvedOperand, type: Attribute) -> SSAValue:
        """
        Resolve an unresolved operand.
        If the operand is not yet defined, it creates a forward reference.
        If the operand is already defined, it returns the corresponding SSA value,
        and checks that the type is consistent.
        """
        name = operand.operand_name

        # If the indexed operand is already used as a forward reference, return it
        if (
            name in self.forward_ssa_references
            and operand.index in self.forward_ssa_references[name]
        ):
            return self.forward_ssa_references[name][operand.index]

        # If the operand is not yet defined, create a forward reference
        if name not in self.ssa_values:
            forward_value = ForwardDeclaredValue(type)
            reference_tuple = self.forward_ssa_references.setdefault(name, {})
            reference_tuple[operand.index] = forward_value
            return forward_value

        # If the operand is already defined, check that the tuple index is in range
        tuple_size = len(self.ssa_values[name])
        if operand.index >= tuple_size:
            self.raise_error(
                "SSA value tuple index out of bounds. "
                f"Tuple is of size {tuple_size} but tried to access element {operand.index}.",
                operand.span,
            )

        # Check that the type is consistent
        resolved = self.ssa_values[name][operand.index]
        if resolved.type != type:
            self.raise_error(
                f"operand is used with type {type}, but has been "
                f"previously used or defined with type {resolved.type}",
                operand.span,
            )

        return resolved

    def parse_optional_operand(self) -> SSAValue | None:
        """
        Parse an operand with format `%<value-id>(#<int-literal>)?`, if present.
        """
        unresolved_operand = self.parse_optional_unresolved_operand()
        if unresolved_operand is None:
            return None

        name = unresolved_operand.operand_name
        index = unresolved_operand.index

        if name not in self.ssa_values.keys():
            self.raise_error(
                "SSA value used before assignment", unresolved_operand.span
            )

        tuple_size = len(self.ssa_values[name])
        if index >= tuple_size:
            self.raise_error(
                "SSA value tuple index out of bounds. "
                f"Tuple is of size {tuple_size} but tried to access element {index}.",
                unresolved_operand.span,
            )

        return self.ssa_values[name][index]

    def parse_operand(self, msg: str = "Expected an operand.") -> SSAValue:
        """Parse an operand with format `%<value-id>`."""
        return self.expect(self.parse_optional_operand, msg)

    def _register_ssa_definition(
        self, name: str, values: Sequence[SSAValue], span: Span
    ) -> None:
        """
        Register an SSA definition in the parsing context.
        In the case the value was already used as a forward reference, the forward
        references are replaced by this value.
        """

        # Check for duplicate SSA value names.
        if name in self.ssa_values:
            self.raise_error(f"SSA value %{name} is already defined", span)

        # Register the SSA values in the context
        self.ssa_values[name] = tuple(values)

        tuple_size = len(values)
        # Check for forward references of this value
        if name in self.forward_ssa_references:
            index_references = self.forward_ssa_references[name]
            del self.forward_ssa_references[name]
            if any(index >= tuple_size for index in index_references):
                self.raise_error(
                    f"SSA value %{name} is referenced with an index "
                    f"larger than its size",
                    span,
                )

            # Replace the forward references with the actual SSA value
            for index, value in index_references.items():
                if index >= tuple_size:
                    self.raise_error(
                        f"SSA value tuple %{name} is referenced with index {index}, but "
                        f"has size {tuple_size}",
                        span,
                    )

                result = values[index]
                if value.type != result.type:
                    result_name = f"%{name}"
                    if tuple_size != 1:
                        result_name = f"%{name}#{index}"
                    self.raise_error(
                        f"Result {result_name} is defined with "
                        f"type {result.type}, but used with type {value.type}",
                        span,
                    )
                value.replace_all_uses_with(result)

        if SSAValue.is_valid_name(name):
            for val in values:
                val.name_hint = name

    @dataclass
    class UnresolvedArgument:
        """
        A block argument parsed from the assembly.
        Arguments should be parsed by `parse_argument` or `parse_optional_argument`.
        """

        name: Span
        """The name as displayed in the assembly."""

        def resolve(self, type: Attribute) -> Parser.Argument:
            return Parser.Argument(self.name, type)

    @dataclass
    class Argument:
        """
        A block argument parsed from the assembly.
        Arguments should be parsed by `parse_argument` or `parse_optional_argument`.
        """

        name: Span
        """The name as displayed in the assembly."""

        type: Attribute
        """The type of the argument, if any."""

    @overload
    def parse_optional_argument(
        self, expect_type: Literal[True] = True
    ) -> Argument | None: ...

    @overload
    def parse_optional_argument(
        self, expect_type: Literal[False]
    ) -> UnresolvedArgument | None: ...

    @overload
    def parse_optional_argument(
        self, expect_type: bool = True
    ) -> UnresolvedArgument | Argument | None: ...

    def parse_optional_argument(
        self, expect_type: bool = True
    ) -> UnresolvedArgument | Argument | None:
        """
        Parse a block argument, if present, with format:
          arg ::= percent-id `:` type
        if `expect_type` is False, the type is not parsed.
        """

        # The argument name
        name_token = self._parse_optional_token(MLIRTokenKind.PERCENT_IDENT)
        if name_token is None:
            return None

        # The argument type
        if expect_type:
            self.parse_punctuation(":", " after block argument name!")
            type = self.parse_type()
            return self.Argument(name_token.span, type)
        else:
            return self.UnresolvedArgument(name_token.span)

    @overload
    def parse_argument(self, *, expect_type: Literal[True] = True) -> Argument: ...

    @overload
    def parse_argument(self, *, expect_type: Literal[False]) -> UnresolvedArgument: ...

    @overload
    def parse_argument(
        self, *, expect_type: bool = True
    ) -> UnresolvedArgument | Argument: ...

    def parse_argument(
        self, *, expect_type: bool = True
    ) -> UnresolvedArgument | Argument:
        """
        Parse a block argument with format:
          arg ::= percent-id `:` type
        if `expect_type` is False, the type is not parsed.
        """

        arg = self.parse_optional_argument(expect_type)
        if arg is None:
            self.raise_error("Expected block argument!")
        return arg

    def parse_optional_region(
        self, arguments: Iterable[Argument] | None = None
    ) -> Region | None:
        """
        Parse a region, if present, with format:
          region ::= `{` entry-block? block* `}`
        If `arguments` is provided, the entry block will use these as block arguments,
        and the entry-block cannot be labeled. It also cannot be empty, unless it is the
        only block in the region.
        """
        # Check if a region is present.
        if self.parse_optional_punctuation("{") is None:
            return None

        region = Region()

        # Create a new scope for values and blocks.
        # Outside blocks cannot be referenced from inside the region, and vice versa.
        # Outside values can be referenced from inside the region, but the region
        # values cannot be referred to from outside the region.
        old_ssa_values = self.ssa_values.copy()
        old_blocks = self.blocks
        old_forward_blocks = self.forward_block_references
        self.blocks = dict()
        self.forward_block_references = defaultdict(list)

        # Parse the entry block without label if arguments are provided.
        # Since the entry block cannot be jumped to, this is fine.
        if arguments is not None:
            # Check that the provided arguments have types.
            arg_types = [arg.type for arg in arguments]

            # Check that the entry block has no label.
            # Since a multi-block region block must have a terminator, there isn't a
            # possibility of having an empty entry block, and thus parsing the label directly.
            if self._current_token.kind == MLIRTokenKind.CARET_IDENT:
                self.raise_error("invalid block name in region with named arguments")

            # Set the block arguments in the context
            entry_block = Block(arg_types=arg_types)
            for block_arg, arg in zip(entry_block.args, arguments):
                self._register_ssa_definition(arg.name.text[1:], (block_arg,), arg.name)

            # Parse the entry block body
            self._parse_block_body(entry_block)
            region.add_block(entry_block)

        # If no arguments was provided, parse the entry block if present.
        elif self._current_token.kind not in (
            MLIRTokenKind.CARET_IDENT,
            MLIRTokenKind.R_BRACE,
        ):
            block = Block()
            self._parse_block_body(block)
            region.add_block(block)

        # Parse the region blocks.
        # In the case where arguments are provided, the entry block is already parsed,
        # and the following blocks will have a label (since the entry block will parse
        # greedily all operations).
        # In the case where no arguments areprovided, the entry block can either have a
        # label or not.
        while self.parse_optional_punctuation("}") is None:
            block = self._parse_block()
            region.add_block(block)

        # Finally, check that all forward block references have been resolved.
        if self.forward_block_references:
            pos = self.pos
            raise MultipleSpansParseError(
                Span(pos, pos + 1, self.lexer.input),
                "region ends with missing block declarations for block(s) {}".format(
                    ", ".join(self.forward_block_references.keys())
                ),
                "dangling block references:",
                [
                    (
                        span,
                        f'reference to block "{span.text}" without implementation',
                    )
                    for span in itertools.chain(*self.forward_block_references.values())
                ],
            )

        # Close the value and block scope.
        self.ssa_values = old_ssa_values
        self.blocks = old_blocks
        self.forward_block_references = old_forward_blocks

        return region

    def parse_region(self, arguments: Iterable[Argument] | None = None) -> Region:
        """
        Parse a region with format:
          region ::= `{` entry-block? block* `}`
        If `arguments` is provided, the entry block will use these as block arguments,
        and the entry-block cannot be labeled. It also cannot be empty, unless it is the
        only block in the region.
        """
        region = self.parse_optional_region(arguments)
        if region is None:
            self.raise_error("Expected region!")
        return region

    def parse_region_list(self) -> list[Region]:
        """
        Parse the list of operation regions.
        If no regions are present, return an empty list.
        Parse a list of regions with format:
           regions-list ::= `(` region (`,` region)* `)`
        """
        if self._current_token.kind == MLIRTokenKind.L_PAREN:
            return self.parse_comma_separated_list(
                self.Delimiter.PAREN, self.parse_region, " in operation region list"
            )
        return []

    def parse_op(self) -> Operation:
        return self.parse_operation()

    def parse_optional_attr_dict_with_keyword(
        self, reserved_attr_names: Iterable[str] = ()
    ) -> DictionaryAttr | None:
        """
        Parse a dictionary attribute, preceeded with `attributes` keyword, if the
        keyword is present.
        This is intended to be used in operation custom assembly format.
        `reserved_attr_names` contains names that should not be present in the attribute
        dictionary, and usually correspond to the names of the attributes that are
        already passed through the operation custom assembly format.
        """
        if self.parse_optional_keyword("attributes") is None:
            return None
        return self.parse_optional_attr_dict_with_reserved_attr_names(
            reserved_attr_names
        )

    def parse_optional_attr_dict_with_reserved_attr_names(
        self, reserved_attr_names: Iterable[str] = ()
    ) -> DictionaryAttr | None:
        """
        Parse a dictionary attribute if present.
        This is intended to be used in operation custom assembly format.
        `reserved_attr_names` contains names that should not be present in the attribute
        dictionary, and usually correspond to the names of the attributes that are
        already passed through the operation custom assembly format.
        """
        begin_pos = self.lexer.pos
        attr = self._parse_builtin_dict_attr()

        for reserved_name in reserved_attr_names:
            if reserved_name in attr.data:
                self.raise_error(
                    f"Attribute dictionary entry '{reserved_name}' is already passed "
                    "through the operation custom assembly format.",
                    Span(begin_pos, begin_pos, self.lexer.input),
                )
        return attr

    def parse_optional_operation(self) -> Operation | None:
        """
        Parse an operation with format:
            operation             ::= op-result-list? (generic-operation | custom-operation)
            generic-operation     ::= string-literal `(` value-use-list? `)`  successor-list?
                                      properties? region-list? dictionary-attribute? `:`
                                      function-type location?
            custom-operation      ::= bare-id custom-operation-format
            op-result-list        ::= op-result (`,` op-result)* `=`
            op-result             ::= value-id (`:` integer-literal)
            successor-list        ::= `[` successor (`,` successor)* `]`
            successor             ::= caret-id (`:` block-arg-list)?
            region-list           ::= `(` region (`,` region)* `)`
            dictionary-attribute  ::= `{` (attribute-entry (`,` attribute-entry)*)? `}`
            properties            ::= `<` dictionary-attribute `>`
        """
        if self._current_token.kind not in (
            MLIRTokenKind.PERCENT_IDENT,
            MLIRTokenKind.BARE_IDENT,
            MLIRTokenKind.STRING_LIT,
        ):
            return None
        return self.parse_operation()

    def parse_operation(self) -> Operation:
        """
        Parse an operation with format:
            operation             ::= op-result-list? (generic-operation | custom-operation)
            generic-operation     ::= string-literal `(` value-use-list? `)`  successor-list?
                                      properties? region-list? dictionary-attribute? `:`
                                      function-type location?
            custom-operation      ::= bare-id custom-operation-format
            op-result-list        ::= op-result (`,` op-result)* `=`
            op-result             ::= value-id (`:` integer-literal)
            successor-list        ::= `[` successor (`,` successor)* `]`
            successor             ::= caret-id (`:` block-arg-list)?
            region-list           ::= `(` region (`,` region)* `)`
            dictionary-attribute  ::= `{` (attribute-entry (`,` attribute-entry)*)? `}`
            properties            ::= `<` dictionary-attribute `>`
        """
        # Parse the operation results
        op_loc = self._current_token.span
        bound_results = self._parse_op_result_list()

        if (
            op_name := self._parse_optional_token(MLIRTokenKind.BARE_IDENT)
        ) is not None:
            # Custom operation format
            op_type = self._get_op_by_name(op_name.text)
            dialect_name = op_type.dialect_name()
            self._parser_state.dialect_stack.append(dialect_name)
            op = op_type.parse(self)
            self._parser_state.dialect_stack.pop()
        else:
            # Generic operation format
            op_name = self.expect(
                self.parse_optional_str_literal, "operation name expected"
            )
            op_type = self._get_op_by_name(op_name)
            dialect_name = op_type.dialect_name()
            self._parser_state.dialect_stack.append(dialect_name)
            op = self._parse_generic_operation(op_type)
            self._parser_state.dialect_stack.pop()

        n_bound_results = sum(r[1] for r in bound_results)
        if (n_bound_results != 0) and (len(op.results) != n_bound_results):
            self.raise_error(
                f"Operation has {len(op.results)} results, "
                f"but was given {n_bound_results} to bind.",
                at_position=op_loc,
            )

        # Register the result SSA value names in the parser
        res_idx = 0
        for res_span, res_size in bound_results:
            ssa_val_name = res_span.text[1:]  # Removing the leading '%'
            self._register_ssa_definition(
                ssa_val_name, op.results[res_idx : res_idx + res_size], res_span
            )
            res_idx += res_size

        return op

    def _get_op_by_name(self, name: str) -> type[Operation]:
        """
        Get an operation type by its name.
        Raises an error if the operation is not registered, and if unregistered
        dialects are not allowed.
        """
        if op_type := self.ctx.get_optional_op(
            name, dialect_stack=self._parser_state.dialect_stack
        ):
            return op_type
        self.raise_error(f"Operation {name} is not registered")

    def _parse_op_result(self) -> tuple[Span, int]:
        """
        Parse an operation result.
        Returns the span of the SSA value name (including the `%`), and the size of the
        value tuple (by default 1).
        """
        value_token = self._parse_token(
            MLIRTokenKind.PERCENT_IDENT, "Expected result SSA value!"
        )
        if self._parse_optional_token(MLIRTokenKind.COLON) is None:
            return (value_token.span, 1)

        size_token = self._parse_token(
            MLIRTokenKind.INTEGER_LIT, "Expected SSA value tuple size"
        )
        size = size_token.kind.get_int_value(size_token.span)
        return (value_token.span, size)

    def _parse_op_result_list(self) -> list[tuple[Span, int]]:
        """
        Parse the list of operation results.
        If no results are present, returns an empty list.
        Each result is a tuple of the span of the SSA value name (including the `%`),
        and the size of the value tuple (by default 1).
        """
        if self._current_token.kind == MLIRTokenKind.PERCENT_IDENT:
            res = self.parse_comma_separated_list(
                self.Delimiter.NONE, self._parse_op_result, " in operation result list"
            )
            self.parse_punctuation("=", " after operation result list")
            return res
        return []

    def parse_optional_attr_dict(self) -> dict[str, Attribute]:
        return self.parse_optional_dictionary_attr_dict()

    def parse_optional_properties_dict(self) -> dict[str, Attribute]:
        """
        Parse a property dictionary, if present, with format:
            dictionary-attribute  ::= `{` (attribute-entry (`,` attribute-entry)*)? `}`
            properties            ::= `<` dictionary-attribute `>`
        """
        if self.parse_optional_punctuation("<") is None:
            return dict()

        entries = self.parse_comma_separated_list(
            self.Delimiter.BRACES, self._parse_attribute_entry
        )
        self.parse_punctuation(">")

        if (key := self._find_duplicated_key(entries)) is not None:
            self.raise_error(f"Duplicate key '{key}' in properties dictionary")

        return dict(entries)

    def resolve_operands(
        self,
        args: Sequence[UnresolvedOperand],
        input_types: Sequence[Attribute],
        error_pos: Position,
    ) -> Sequence[SSAValue]:
        """
        Resolve unresolved operands. For each operand in `args` and its corresponding input
        type the following happens:

        If the operand is not yet defined, it creates a forward reference.
        If the operand is already defined, it returns the corresponding SSA value,
        and checks that the type is consistent.

        If the length of args and input_types does not match, an error is raised at
        the location error_pos.
        """
        length = len(list(input_types))
        if len(args) != length:
            self.raise_error(
                f"expected {length} operand types but had {len(args)}",
                error_pos,
            )

        return [
            self.resolve_operand(operand, type)
            for operand, type in zip(args, input_types)
        ]

    def _parse_generic_operation(self, op_type: type[Operation]) -> Operation:
        """
        Parse a generic operation with format:
            generic-operation     ::= string-literal `(` value-use-list? `)`  successor-list?
                                      properties? region-list? dictionary-attribute? `:`
                                      function-type location?
            custom-operation      ::= bare-id custom-operation-format
            op-result-list        ::= op-result (`,` op-result)* `=`
            op-result             ::= value-id (`:` integer-literal)
            successor-list        ::= `[` successor (`,` successor)* `]`
            successor             ::= caret-id (`:` block-arg-list)?
            region-list           ::= `(` region (`,` region)* `)`
            dictionary-attribute  ::= `{` (attribute-entry (`,` attribute-entry)*)? `}`
            properties            ::= `<` dictionary-attribute `>`
        """
        # Parse arguments
        args = self.parse_op_args_list()

        # Parse successors
        successors = self.parse_optional_successors()
        if successors is None:
            successors = []

        # Parse attribute dictionary
        properties = self.parse_optional_properties_dict()

        # Parse regions
        regions = self.parse_region_list()

        # Parse attribute dictionary
        attributes = self.parse_optional_attr_dict()

        self.parse_punctuation(":", "function type signature expected")

        func_type_pos = self._current_token.span.start

        # Parse function type
        func_type = self.parse_function_type()

        self.parse_optional_location()

        operands = self.resolve_operands(args, func_type.inputs.data, func_type_pos)

        # Properties retrocompatibility :
        # We extract properties from the attribute dictionary by name.
        if issubclass(op_type, IRDLOperation):
            op_def = op_type.get_irdl_definition()
            for property_name in op_def.properties.keys():
                if property_name in attributes and property_name not in properties:
                    properties[property_name] = attributes.pop(property_name)

        return op_type.create(
            operands=operands,
            result_types=func_type.outputs.data,
            properties=properties,
            attributes=attributes,
            successors=successors,
            regions=regions,
        )

    def parse_optional_successor(self) -> Block | None:
        """
        Parse a successor with format:
            successor      ::= caret-id
        """
        block_token = self._parse_optional_token(MLIRTokenKind.CARET_IDENT)
        if block_token is None:
            return None
        name = block_token.text[1:]
        if name not in self.blocks:
            self.forward_block_references[name].append(block_token.span)
            block = Block()
            if not Block.is_default_block_name(name):
                block.name_hint = name  # setter verifies validity
            self.blocks[name] = (block, None)
        return self.blocks[name][0]

    def parse_successor(self) -> Block:
        """
        Parse a successor with format:
            successor      ::= caret-id
        """
        return self.expect(self.parse_optional_successor, "successor expected")

    def parse_optional_successors(self) -> list[Block] | None:
        """
        Parse a list of successors, if present, with format
            successor-list ::= `[` successor (`,` successor)* `]`
            successor      ::= caret-id
        """
        if self._current_token.kind != MLIRTokenKind.L_SQUARE:
            return None
        return self.parse_successors()

    def parse_successors(self) -> list[Block]:
        """
        Parse a list of successors with format:
            successor-list ::= `[` successor (`,` successor)* `]`
            successor      ::= caret-id
        """
        return self.parse_comma_separated_list(
            self.Delimiter.SQUARE,
            lambda: self.expect(self.parse_successor, "block-id expected"),
        )

    def parse_op_args_list(self) -> list[UnresolvedOperand]:
        """
        Parse a list of arguments with format:
           args-list ::= `(` value-use-list? `)`
           value-use-list ::= `%` suffix-id (`,` `%` suffix-id)*
        """
        return self.parse_comma_separated_list(
            self.Delimiter.PAREN,
            self.parse_unresolved_operand,
            " in operation argument list",
        )

    def _parse_resource(
        self, dialect_name: str, interface: OpAsmDialectInterface
    ) -> None:
        key = self._parse_dialect_resource_handle(dialect_name, interface)
        self._parse_token(MLIRTokenKind.COLON, "expected `:`")
        value = self.parse_str_literal()

        try:
            interface.parse_resource(key, value)
        except Exception as e:
            self.raise_error(f"got an error when parsing a resource: {e}")

    def _parse_single_dialect_resources(self) -> None:
        dialect_name = self._parse_token(
            MLIRTokenKind.BARE_IDENT, "Expected a dialect name"
        )
        self._parse_token(MLIRTokenKind.COLON, "expected `:`")

        dialect = self.ctx.get_optional_dialect(dialect_name.text)
        if dialect is None:
            self.raise_error(f"dialect {dialect_name.text} is not registered")

        interface = dialect.get_interface(OpAsmDialectInterface)
        if not interface:
            self.raise_error(
                f"dialect {dialect.name} doesn't have an OpAsmDialectInterface interface"
            )

        self.parse_comma_separated_list(
            self.Delimiter.BRACES, lambda: self._parse_resource(dialect.name, interface)
        )

    def _parse_dialect_resources(self) -> None:
        self.parse_comma_separated_list(
            self.Delimiter.BRACES, self._parse_single_dialect_resources
        )

    def _parse_external_resources(self) -> None:
        raise NotImplementedError("Currently only dialect resources are supported")

    def _parse_metadata_element(self) -> None:
        resource_type = self._parse_token(
            MLIRTokenKind.BARE_IDENT, "Expected a resource type key"
        )

        self._parse_token(MLIRTokenKind.COLON, "expected `:`")

        match resource_type.text:
            case "dialect_resources":
                self._parse_dialect_resources()
            case "external_resources":
                self._parse_external_resources()
            case _:
                self.raise_error(
                    f"got an unexpected key in file metadata: {resource_type.text}"
                )

    def _parse_file_metadata_dictionary(self) -> None:
        """
        Parse metadata section {-# ... #-} of the file.
        Returns None since results are stored in the context object.
        """
        self.parse_comma_separated_list(
            self.Delimiter.METADATA_TOKEN, self._parse_metadata_element
        )

ssa_values: dict[str, tuple[SSAValue, ...]] = dict() instance-attribute

blocks: dict[str, tuple[Block, Span | None]] = dict() instance-attribute

forward_block_references: dict[str, list[Span]] = defaultdict(list) instance-attribute

Blocks we encountered references to before the definition (must be empty after parsing of region completes)

forward_ssa_references: dict[str, dict[int, ForwardDeclaredValue]] = dict() instance-attribute

SSA values that are referenced, but are not yet defined. This field map a name and a tuple index to the forward declared SSA value.

UnresolvedArgument dataclass

A block argument parsed from the assembly. Arguments should be parsed by parse_argument or parse_optional_argument.

Source code in xdsl/parser/core.py
421
422
423
424
425
426
427
428
429
430
431
432
@dataclass
class UnresolvedArgument:
    """
    A block argument parsed from the assembly.
    Arguments should be parsed by `parse_argument` or `parse_optional_argument`.
    """

    name: Span
    """The name as displayed in the assembly."""

    def resolve(self, type: Attribute) -> Parser.Argument:
        return Parser.Argument(self.name, type)
name: Span instance-attribute

The name as displayed in the assembly.

resolve(type: Attribute) -> Parser.Argument
Source code in xdsl/parser/core.py
431
432
def resolve(self, type: Attribute) -> Parser.Argument:
    return Parser.Argument(self.name, type)
__init__(name: Span) -> None

Argument dataclass

A block argument parsed from the assembly. Arguments should be parsed by parse_argument or parse_optional_argument.

Source code in xdsl/parser/core.py
434
435
436
437
438
439
440
441
442
443
444
445
@dataclass
class Argument:
    """
    A block argument parsed from the assembly.
    Arguments should be parsed by `parse_argument` or `parse_optional_argument`.
    """

    name: Span
    """The name as displayed in the assembly."""

    type: Attribute
    """The type of the argument, if any."""
name: Span instance-attribute

The name as displayed in the assembly.

type: Attribute instance-attribute

The type of the argument, if any.

__init__(name: Span, type: Attribute) -> None

__init__(ctx: Context, input: str, name: str = '<unknown>') -> None

Source code in xdsl/parser/core.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
def __init__(
    self,
    ctx: Context,
    input: str,
    name: str = "<unknown>",
) -> None:
    super().__init__(ParserState(MLIRLexer(Input(input, name))), ctx)
    self.ssa_values = dict()
    self.blocks = dict()
    self.forward_block_references = defaultdict(list)
    self.forward_ssa_references = dict()

parse_module(allow_implicit_module: bool = True) -> ModuleOp

Source code in xdsl/parser/core.py
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
def parse_module(self, allow_implicit_module: bool = True) -> ModuleOp:
    module_op: Operation

    if not allow_implicit_module:
        parsed_op = self.parse_optional_operation()

        if parsed_op is None:
            self.raise_error("Could not parse entire input!")

        if not isinstance(parsed_op, ModuleOp):
            self._resume_from(0)
            self.raise_error("builtin.module operation expected", 0)

        module_op = parsed_op
    else:
        parsed_ops: list[Operation] = []

        while self._current_token.kind != MLIRTokenKind.EOF:
            if self._current_token.kind in (
                MLIRTokenKind.HASH_IDENT,
                MLIRTokenKind.EXCLAMATION_IDENT,
            ):
                self._parse_alias_def()
                continue
            if self._current_token.kind in (MLIRTokenKind.FILE_METADATA_BEGIN,):
                self._parse_file_metadata_dictionary()
                continue
            if (parsed_op := self.parse_optional_operation()) is not None:
                parsed_ops.append(parsed_op)
                continue
            self.raise_error("Could not parse entire input!")

        if len(parsed_ops) == 0:
            self.raise_error("Could not parse entire input!")

        module_op = (
            parsed_ops[0]
            if isinstance(parsed_ops[0], ModuleOp) and len(parsed_ops) == 1
            else ModuleOp(parsed_ops)
        )

    if self.forward_ssa_references:
        value_names = ", ".join(
            "%" + name for name in self.forward_ssa_references.keys()
        )
        self.raise_error(f"values used but not defined: [{value_names}]")

    return module_op

parse_optional_unresolved_operand() -> UnresolvedOperand | None

Parse an operand with format %<value-id>(#<int-literal>)?, if present. The operand may be forward declared.

Source code in xdsl/parser/core.py
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
def parse_optional_unresolved_operand(self) -> UnresolvedOperand | None:
    """
    Parse an operand with format `%<value-id>(#<int-literal>)?`, if present.
    The operand may be forward declared.
    """
    name_token = self._parse_optional_token(MLIRTokenKind.PERCENT_IDENT)
    if name_token is None:
        return None

    index = 0
    index_token = self._parse_optional_token(MLIRTokenKind.HASH_IDENT)
    if index_token is not None:
        if re.fullmatch(self._decimal_integer_regex, index_token.text[1:]) is None:
            self.raise_error(
                "Expected integer as SSA value tuple index", index_token.span
            )
        index = int(index_token.text[1:], 10)

    return UnresolvedOperand(name_token.span, index)

parse_unresolved_operand(msg: str = 'operand expected') -> UnresolvedOperand

Parse an operand with format %<value-id>(#<int-literal>)?. The operand may be forward declared.

Source code in xdsl/parser/core.py
286
287
288
289
290
291
292
293
def parse_unresolved_operand(
    self, msg: str = "operand expected"
) -> UnresolvedOperand:
    """
    Parse an operand with format `%<value-id>(#<int-literal>)?`.
    The operand may be forward declared.
    """
    return self.expect(self.parse_optional_unresolved_operand, msg)

resolve_operand(operand: UnresolvedOperand, type: Attribute) -> SSAValue

Resolve an unresolved operand. If the operand is not yet defined, it creates a forward reference. If the operand is already defined, it returns the corresponding SSA value, and checks that the type is consistent.

Source code in xdsl/parser/core.py
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
def resolve_operand(self, operand: UnresolvedOperand, type: Attribute) -> SSAValue:
    """
    Resolve an unresolved operand.
    If the operand is not yet defined, it creates a forward reference.
    If the operand is already defined, it returns the corresponding SSA value,
    and checks that the type is consistent.
    """
    name = operand.operand_name

    # If the indexed operand is already used as a forward reference, return it
    if (
        name in self.forward_ssa_references
        and operand.index in self.forward_ssa_references[name]
    ):
        return self.forward_ssa_references[name][operand.index]

    # If the operand is not yet defined, create a forward reference
    if name not in self.ssa_values:
        forward_value = ForwardDeclaredValue(type)
        reference_tuple = self.forward_ssa_references.setdefault(name, {})
        reference_tuple[operand.index] = forward_value
        return forward_value

    # If the operand is already defined, check that the tuple index is in range
    tuple_size = len(self.ssa_values[name])
    if operand.index >= tuple_size:
        self.raise_error(
            "SSA value tuple index out of bounds. "
            f"Tuple is of size {tuple_size} but tried to access element {operand.index}.",
            operand.span,
        )

    # Check that the type is consistent
    resolved = self.ssa_values[name][operand.index]
    if resolved.type != type:
        self.raise_error(
            f"operand is used with type {type}, but has been "
            f"previously used or defined with type {resolved.type}",
            operand.span,
        )

    return resolved

parse_optional_operand() -> SSAValue | None

Parse an operand with format %<value-id>(#<int-literal>)?, if present.

Source code in xdsl/parser/core.py
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
def parse_optional_operand(self) -> SSAValue | None:
    """
    Parse an operand with format `%<value-id>(#<int-literal>)?`, if present.
    """
    unresolved_operand = self.parse_optional_unresolved_operand()
    if unresolved_operand is None:
        return None

    name = unresolved_operand.operand_name
    index = unresolved_operand.index

    if name not in self.ssa_values.keys():
        self.raise_error(
            "SSA value used before assignment", unresolved_operand.span
        )

    tuple_size = len(self.ssa_values[name])
    if index >= tuple_size:
        self.raise_error(
            "SSA value tuple index out of bounds. "
            f"Tuple is of size {tuple_size} but tried to access element {index}.",
            unresolved_operand.span,
        )

    return self.ssa_values[name][index]

parse_operand(msg: str = 'Expected an operand.') -> SSAValue

Parse an operand with format %<value-id>.

Source code in xdsl/parser/core.py
364
365
366
def parse_operand(self, msg: str = "Expected an operand.") -> SSAValue:
    """Parse an operand with format `%<value-id>`."""
    return self.expect(self.parse_optional_operand, msg)

parse_optional_argument(expect_type: bool = True) -> UnresolvedArgument | Argument | None

parse_optional_argument(
    expect_type: Literal[True] = True,
) -> Argument | None
parse_optional_argument(
    expect_type: Literal[False],
) -> UnresolvedArgument | None
parse_optional_argument(
    expect_type: bool = True,
) -> UnresolvedArgument | Argument | None

Parse a block argument, if present, with format: arg ::= percent-id : type if expect_type is False, the type is not parsed.

Source code in xdsl/parser/core.py
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
def parse_optional_argument(
    self, expect_type: bool = True
) -> UnresolvedArgument | Argument | None:
    """
    Parse a block argument, if present, with format:
      arg ::= percent-id `:` type
    if `expect_type` is False, the type is not parsed.
    """

    # The argument name
    name_token = self._parse_optional_token(MLIRTokenKind.PERCENT_IDENT)
    if name_token is None:
        return None

    # The argument type
    if expect_type:
        self.parse_punctuation(":", " after block argument name!")
        type = self.parse_type()
        return self.Argument(name_token.span, type)
    else:
        return self.UnresolvedArgument(name_token.span)

parse_argument(*, expect_type: bool = True) -> UnresolvedArgument | Argument

parse_argument(
    *, expect_type: Literal[True] = True
) -> Argument
parse_argument(
    *, expect_type: Literal[False]
) -> UnresolvedArgument
parse_argument(
    *, expect_type: bool = True
) -> UnresolvedArgument | Argument
Parse a block argument with format

arg ::= percent-id : type

if expect_type is False, the type is not parsed.

Source code in xdsl/parser/core.py
495
496
497
498
499
500
501
502
503
504
505
506
507
def parse_argument(
    self, *, expect_type: bool = True
) -> UnresolvedArgument | Argument:
    """
    Parse a block argument with format:
      arg ::= percent-id `:` type
    if `expect_type` is False, the type is not parsed.
    """

    arg = self.parse_optional_argument(expect_type)
    if arg is None:
        self.raise_error("Expected block argument!")
    return arg

parse_optional_region(arguments: Iterable[Argument] | None = None) -> Region | None

Parse a region, if present, with format: region ::= { entry-block? block* } If arguments is provided, the entry block will use these as block arguments, and the entry-block cannot be labeled. It also cannot be empty, unless it is the only block in the region.

Source code in xdsl/parser/core.py
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
def parse_optional_region(
    self, arguments: Iterable[Argument] | None = None
) -> Region | None:
    """
    Parse a region, if present, with format:
      region ::= `{` entry-block? block* `}`
    If `arguments` is provided, the entry block will use these as block arguments,
    and the entry-block cannot be labeled. It also cannot be empty, unless it is the
    only block in the region.
    """
    # Check if a region is present.
    if self.parse_optional_punctuation("{") is None:
        return None

    region = Region()

    # Create a new scope for values and blocks.
    # Outside blocks cannot be referenced from inside the region, and vice versa.
    # Outside values can be referenced from inside the region, but the region
    # values cannot be referred to from outside the region.
    old_ssa_values = self.ssa_values.copy()
    old_blocks = self.blocks
    old_forward_blocks = self.forward_block_references
    self.blocks = dict()
    self.forward_block_references = defaultdict(list)

    # Parse the entry block without label if arguments are provided.
    # Since the entry block cannot be jumped to, this is fine.
    if arguments is not None:
        # Check that the provided arguments have types.
        arg_types = [arg.type for arg in arguments]

        # Check that the entry block has no label.
        # Since a multi-block region block must have a terminator, there isn't a
        # possibility of having an empty entry block, and thus parsing the label directly.
        if self._current_token.kind == MLIRTokenKind.CARET_IDENT:
            self.raise_error("invalid block name in region with named arguments")

        # Set the block arguments in the context
        entry_block = Block(arg_types=arg_types)
        for block_arg, arg in zip(entry_block.args, arguments):
            self._register_ssa_definition(arg.name.text[1:], (block_arg,), arg.name)

        # Parse the entry block body
        self._parse_block_body(entry_block)
        region.add_block(entry_block)

    # If no arguments was provided, parse the entry block if present.
    elif self._current_token.kind not in (
        MLIRTokenKind.CARET_IDENT,
        MLIRTokenKind.R_BRACE,
    ):
        block = Block()
        self._parse_block_body(block)
        region.add_block(block)

    # Parse the region blocks.
    # In the case where arguments are provided, the entry block is already parsed,
    # and the following blocks will have a label (since the entry block will parse
    # greedily all operations).
    # In the case where no arguments areprovided, the entry block can either have a
    # label or not.
    while self.parse_optional_punctuation("}") is None:
        block = self._parse_block()
        region.add_block(block)

    # Finally, check that all forward block references have been resolved.
    if self.forward_block_references:
        pos = self.pos
        raise MultipleSpansParseError(
            Span(pos, pos + 1, self.lexer.input),
            "region ends with missing block declarations for block(s) {}".format(
                ", ".join(self.forward_block_references.keys())
            ),
            "dangling block references:",
            [
                (
                    span,
                    f'reference to block "{span.text}" without implementation',
                )
                for span in itertools.chain(*self.forward_block_references.values())
            ],
        )

    # Close the value and block scope.
    self.ssa_values = old_ssa_values
    self.blocks = old_blocks
    self.forward_block_references = old_forward_blocks

    return region

parse_region(arguments: Iterable[Argument] | None = None) -> Region

Parse a region with format

region ::= { entry-block? block* }

If arguments is provided, the entry block will use these as block arguments, and the entry-block cannot be labeled. It also cannot be empty, unless it is the only block in the region.

Source code in xdsl/parser/core.py
600
601
602
603
604
605
606
607
608
609
610
611
def parse_region(self, arguments: Iterable[Argument] | None = None) -> Region:
    """
    Parse a region with format:
      region ::= `{` entry-block? block* `}`
    If `arguments` is provided, the entry block will use these as block arguments,
    and the entry-block cannot be labeled. It also cannot be empty, unless it is the
    only block in the region.
    """
    region = self.parse_optional_region(arguments)
    if region is None:
        self.raise_error("Expected region!")
    return region

parse_region_list() -> list[Region]

Parse the list of operation regions. If no regions are present, return an empty list. Parse a list of regions with format: regions-list ::= ( region (, region)* )

Source code in xdsl/parser/core.py
613
614
615
616
617
618
619
620
621
622
623
624
def parse_region_list(self) -> list[Region]:
    """
    Parse the list of operation regions.
    If no regions are present, return an empty list.
    Parse a list of regions with format:
       regions-list ::= `(` region (`,` region)* `)`
    """
    if self._current_token.kind == MLIRTokenKind.L_PAREN:
        return self.parse_comma_separated_list(
            self.Delimiter.PAREN, self.parse_region, " in operation region list"
        )
    return []

parse_op() -> Operation

Source code in xdsl/parser/core.py
626
627
def parse_op(self) -> Operation:
    return self.parse_operation()

parse_optional_attr_dict_with_keyword(reserved_attr_names: Iterable[str] = ()) -> DictionaryAttr | None

Parse a dictionary attribute, preceeded with attributes keyword, if the keyword is present. This is intended to be used in operation custom assembly format. reserved_attr_names contains names that should not be present in the attribute dictionary, and usually correspond to the names of the attributes that are already passed through the operation custom assembly format.

Source code in xdsl/parser/core.py
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
def parse_optional_attr_dict_with_keyword(
    self, reserved_attr_names: Iterable[str] = ()
) -> DictionaryAttr | None:
    """
    Parse a dictionary attribute, preceeded with `attributes` keyword, if the
    keyword is present.
    This is intended to be used in operation custom assembly format.
    `reserved_attr_names` contains names that should not be present in the attribute
    dictionary, and usually correspond to the names of the attributes that are
    already passed through the operation custom assembly format.
    """
    if self.parse_optional_keyword("attributes") is None:
        return None
    return self.parse_optional_attr_dict_with_reserved_attr_names(
        reserved_attr_names
    )

parse_optional_attr_dict_with_reserved_attr_names(reserved_attr_names: Iterable[str] = ()) -> DictionaryAttr | None

Parse a dictionary attribute if present. This is intended to be used in operation custom assembly format. reserved_attr_names contains names that should not be present in the attribute dictionary, and usually correspond to the names of the attributes that are already passed through the operation custom assembly format.

Source code in xdsl/parser/core.py
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
def parse_optional_attr_dict_with_reserved_attr_names(
    self, reserved_attr_names: Iterable[str] = ()
) -> DictionaryAttr | None:
    """
    Parse a dictionary attribute if present.
    This is intended to be used in operation custom assembly format.
    `reserved_attr_names` contains names that should not be present in the attribute
    dictionary, and usually correspond to the names of the attributes that are
    already passed through the operation custom assembly format.
    """
    begin_pos = self.lexer.pos
    attr = self._parse_builtin_dict_attr()

    for reserved_name in reserved_attr_names:
        if reserved_name in attr.data:
            self.raise_error(
                f"Attribute dictionary entry '{reserved_name}' is already passed "
                "through the operation custom assembly format.",
                Span(begin_pos, begin_pos, self.lexer.input),
            )
    return attr

parse_optional_operation() -> Operation | None

Parse an operation with format

operation ::= op-result-list? (generic-operation | custom-operation) generic-operation ::= string-literal ( value-use-list? ) successor-list? properties? region-list? dictionary-attribute? : function-type location? custom-operation ::= bare-id custom-operation-format op-result-list ::= op-result (, op-result) = op-result ::= value-id (: integer-literal) successor-list ::= [ successor (, successor) ] successor ::= caret-id (: block-arg-list)? region-list ::= ( region (, region) ) dictionary-attribute ::= { (attribute-entry (, attribute-entry))? } properties ::= < dictionary-attribute >

Source code in xdsl/parser/core.py
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
def parse_optional_operation(self) -> Operation | None:
    """
    Parse an operation with format:
        operation             ::= op-result-list? (generic-operation | custom-operation)
        generic-operation     ::= string-literal `(` value-use-list? `)`  successor-list?
                                  properties? region-list? dictionary-attribute? `:`
                                  function-type location?
        custom-operation      ::= bare-id custom-operation-format
        op-result-list        ::= op-result (`,` op-result)* `=`
        op-result             ::= value-id (`:` integer-literal)
        successor-list        ::= `[` successor (`,` successor)* `]`
        successor             ::= caret-id (`:` block-arg-list)?
        region-list           ::= `(` region (`,` region)* `)`
        dictionary-attribute  ::= `{` (attribute-entry (`,` attribute-entry)*)? `}`
        properties            ::= `<` dictionary-attribute `>`
    """
    if self._current_token.kind not in (
        MLIRTokenKind.PERCENT_IDENT,
        MLIRTokenKind.BARE_IDENT,
        MLIRTokenKind.STRING_LIT,
    ):
        return None
    return self.parse_operation()

parse_operation() -> Operation

Parse an operation with format

operation ::= op-result-list? (generic-operation | custom-operation) generic-operation ::= string-literal ( value-use-list? ) successor-list? properties? region-list? dictionary-attribute? : function-type location? custom-operation ::= bare-id custom-operation-format op-result-list ::= op-result (, op-result) = op-result ::= value-id (: integer-literal) successor-list ::= [ successor (, successor) ] successor ::= caret-id (: block-arg-list)? region-list ::= ( region (, region) ) dictionary-attribute ::= { (attribute-entry (, attribute-entry))? } properties ::= < dictionary-attribute >

Source code in xdsl/parser/core.py
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
def parse_operation(self) -> Operation:
    """
    Parse an operation with format:
        operation             ::= op-result-list? (generic-operation | custom-operation)
        generic-operation     ::= string-literal `(` value-use-list? `)`  successor-list?
                                  properties? region-list? dictionary-attribute? `:`
                                  function-type location?
        custom-operation      ::= bare-id custom-operation-format
        op-result-list        ::= op-result (`,` op-result)* `=`
        op-result             ::= value-id (`:` integer-literal)
        successor-list        ::= `[` successor (`,` successor)* `]`
        successor             ::= caret-id (`:` block-arg-list)?
        region-list           ::= `(` region (`,` region)* `)`
        dictionary-attribute  ::= `{` (attribute-entry (`,` attribute-entry)*)? `}`
        properties            ::= `<` dictionary-attribute `>`
    """
    # Parse the operation results
    op_loc = self._current_token.span
    bound_results = self._parse_op_result_list()

    if (
        op_name := self._parse_optional_token(MLIRTokenKind.BARE_IDENT)
    ) is not None:
        # Custom operation format
        op_type = self._get_op_by_name(op_name.text)
        dialect_name = op_type.dialect_name()
        self._parser_state.dialect_stack.append(dialect_name)
        op = op_type.parse(self)
        self._parser_state.dialect_stack.pop()
    else:
        # Generic operation format
        op_name = self.expect(
            self.parse_optional_str_literal, "operation name expected"
        )
        op_type = self._get_op_by_name(op_name)
        dialect_name = op_type.dialect_name()
        self._parser_state.dialect_stack.append(dialect_name)
        op = self._parse_generic_operation(op_type)
        self._parser_state.dialect_stack.pop()

    n_bound_results = sum(r[1] for r in bound_results)
    if (n_bound_results != 0) and (len(op.results) != n_bound_results):
        self.raise_error(
            f"Operation has {len(op.results)} results, "
            f"but was given {n_bound_results} to bind.",
            at_position=op_loc,
        )

    # Register the result SSA value names in the parser
    res_idx = 0
    for res_span, res_size in bound_results:
        ssa_val_name = res_span.text[1:]  # Removing the leading '%'
        self._register_ssa_definition(
            ssa_val_name, op.results[res_idx : res_idx + res_size], res_span
        )
        res_idx += res_size

    return op

parse_optional_attr_dict() -> dict[str, Attribute]

Source code in xdsl/parser/core.py
796
797
def parse_optional_attr_dict(self) -> dict[str, Attribute]:
    return self.parse_optional_dictionary_attr_dict()

parse_optional_properties_dict() -> dict[str, Attribute]

Parse a property dictionary, if present, with format: dictionary-attribute ::= { (attribute-entry (, attribute-entry)*)? } properties ::= < dictionary-attribute >

Source code in xdsl/parser/core.py
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
def parse_optional_properties_dict(self) -> dict[str, Attribute]:
    """
    Parse a property dictionary, if present, with format:
        dictionary-attribute  ::= `{` (attribute-entry (`,` attribute-entry)*)? `}`
        properties            ::= `<` dictionary-attribute `>`
    """
    if self.parse_optional_punctuation("<") is None:
        return dict()

    entries = self.parse_comma_separated_list(
        self.Delimiter.BRACES, self._parse_attribute_entry
    )
    self.parse_punctuation(">")

    if (key := self._find_duplicated_key(entries)) is not None:
        self.raise_error(f"Duplicate key '{key}' in properties dictionary")

    return dict(entries)

resolve_operands(args: Sequence[UnresolvedOperand], input_types: Sequence[Attribute], error_pos: Position) -> Sequence[SSAValue]

Resolve unresolved operands. For each operand in args and its corresponding input type the following happens:

If the operand is not yet defined, it creates a forward reference. If the operand is already defined, it returns the corresponding SSA value, and checks that the type is consistent.

If the length of args and input_types does not match, an error is raised at the location error_pos.

Source code in xdsl/parser/core.py
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
def resolve_operands(
    self,
    args: Sequence[UnresolvedOperand],
    input_types: Sequence[Attribute],
    error_pos: Position,
) -> Sequence[SSAValue]:
    """
    Resolve unresolved operands. For each operand in `args` and its corresponding input
    type the following happens:

    If the operand is not yet defined, it creates a forward reference.
    If the operand is already defined, it returns the corresponding SSA value,
    and checks that the type is consistent.

    If the length of args and input_types does not match, an error is raised at
    the location error_pos.
    """
    length = len(list(input_types))
    if len(args) != length:
        self.raise_error(
            f"expected {length} operand types but had {len(args)}",
            error_pos,
        )

    return [
        self.resolve_operand(operand, type)
        for operand, type in zip(args, input_types)
    ]

parse_optional_successor() -> Block | None

Parse a successor with format

successor ::= caret-id

Source code in xdsl/parser/core.py
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
def parse_optional_successor(self) -> Block | None:
    """
    Parse a successor with format:
        successor      ::= caret-id
    """
    block_token = self._parse_optional_token(MLIRTokenKind.CARET_IDENT)
    if block_token is None:
        return None
    name = block_token.text[1:]
    if name not in self.blocks:
        self.forward_block_references[name].append(block_token.span)
        block = Block()
        if not Block.is_default_block_name(name):
            block.name_hint = name  # setter verifies validity
        self.blocks[name] = (block, None)
    return self.blocks[name][0]

parse_successor() -> Block

Parse a successor with format

successor ::= caret-id

Source code in xdsl/parser/core.py
924
925
926
927
928
929
def parse_successor(self) -> Block:
    """
    Parse a successor with format:
        successor      ::= caret-id
    """
    return self.expect(self.parse_optional_successor, "successor expected")

parse_optional_successors() -> list[Block] | None

Parse a list of successors, if present, with format successor-list ::= [ successor (, successor)* ] successor ::= caret-id

Source code in xdsl/parser/core.py
931
932
933
934
935
936
937
938
939
def parse_optional_successors(self) -> list[Block] | None:
    """
    Parse a list of successors, if present, with format
        successor-list ::= `[` successor (`,` successor)* `]`
        successor      ::= caret-id
    """
    if self._current_token.kind != MLIRTokenKind.L_SQUARE:
        return None
    return self.parse_successors()

parse_successors() -> list[Block]

Parse a list of successors with format

successor-list ::= [ successor (, successor)* ] successor ::= caret-id

Source code in xdsl/parser/core.py
941
942
943
944
945
946
947
948
949
950
def parse_successors(self) -> list[Block]:
    """
    Parse a list of successors with format:
        successor-list ::= `[` successor (`,` successor)* `]`
        successor      ::= caret-id
    """
    return self.parse_comma_separated_list(
        self.Delimiter.SQUARE,
        lambda: self.expect(self.parse_successor, "block-id expected"),
    )

parse_op_args_list() -> list[UnresolvedOperand]

Parse a list of arguments with format

args-list ::= ( value-use-list? ) value-use-list ::= % suffix-id (, % suffix-id)*

Source code in xdsl/parser/core.py
952
953
954
955
956
957
958
959
960
961
962
def parse_op_args_list(self) -> list[UnresolvedOperand]:
    """
    Parse a list of arguments with format:
       args-list ::= `(` value-use-list? `)`
       value-use-list ::= `%` suffix-id (`,` `%` suffix-id)*
    """
    return self.parse_comma_separated_list(
        self.Delimiter.PAREN,
        self.parse_unresolved_operand,
        " in operation argument list",
    )

Printer dataclass

Bases: BasePrinter

Source code in xdsl/printer.py
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
@dataclass(eq=False, repr=False)
class Printer(BasePrinter):
    print_generic_format: bool = field(default=False)
    print_properties_as_attributes: bool = field(default=False)
    print_debuginfo: bool = field(default=False)
    diagnostic: Diagnostic = field(default_factory=Diagnostic)

    _ssa_values: dict[SSAValue, str] = field(
        default_factory=dict[SSAValue, str], init=False
    )
    _blocks: dict[Block, str] = field(default_factory=dict[Block, str], init=False)

    """
    maps SSA Values to their "allocated" names
    """
    _ssa_names: list[dict[str, int]] = field(
        default_factory=lambda: [dict[str, int]()], init=False
    )
    _block_names: list[dict[str, int]] = field(
        default_factory=lambda: [dict[str, int]()], init=False
    )
    _next_valid_name_id: list[int] = field(default_factory=lambda: [0], init=False)
    _next_valid_block_id: list[int] = field(default_factory=lambda: [0], init=False)

    _dialect_resources: dict[str, set[str]] = field(default_factory=dict[str, set[str]])
    """
    resources that were referenced in the ir
    """

    @property
    def ssa_names(self):
        return self._ssa_names[-1]

    @property
    def block_names(self):
        return self._block_names[-1]

    @deprecated("Please use type-specific print methods")
    def print(self, *argv: Any) -> None:
        for arg in argv:
            if isinstance(arg, str):
                self.print_string(arg)
                continue
            if isinstance(arg, SSAValue):
                arg = cast(SSAValue[Attribute], arg)
                self.print_ssa_value(arg)
                continue
            if isinstance(arg, Attribute):
                self.print_attribute(arg)
                continue
            if isinstance(arg, Region):
                self.print_region(arg)
                continue
            if isinstance(arg, Block):
                self.print_block(arg)
                self._print_new_line()
                continue
            if isinstance(arg, Operation):
                self.print_op(arg)
                self._print_new_line()
                continue

            text = str(arg)
            self.print_string(text)

    K = TypeVar("K")
    V = TypeVar("V")

    def print_dictionary(
        self,
        elems: dict[K, V],
        print_key: Callable[[K], None],
        print_value: Callable[[V], None],
        delimiter: str = ", ",
    ) -> None:
        for i, (key, value) in enumerate(elems.items()):
            if i:
                self.print_string(delimiter)
            print_key(key)
            self.print_string("=")
            print_value(value)

    def _get_new_valid_name_id(self) -> str:
        self._next_valid_name_id[-1] += 1
        return str(self._next_valid_name_id[-1] - 1)

    def _get_new_valid_block_id(self) -> int:
        self._next_valid_block_id[-1] += 1
        return self._next_valid_block_id[-1] - 1

    def _print_results(self, op: Operation) -> None:
        results = op.results
        # No results
        if len(results) == 0:
            return

        # Multiple results
        self.print_list(op.results, self.print_ssa_value)
        self.print_string(" = ")

    def print_ssa_value(self, value: SSAValue) -> str:
        """
        Print an SSA value in the printer. This assigns a name to the value if the value
        does not have one in the current printing context.
        If the value has a name hint, it will use it as a prefix, and otherwise assign
        a number as the name. Numbers are assigned in order.

        Returns the name used for printing the value.
        """
        if value in self._ssa_values:
            name = self._ssa_values[value]
        elif value.name_hint:
            curr_ind = self.ssa_names.get(value.name_hint, 0)
            suffix = f"_{curr_ind}" if curr_ind != 0 else ""
            name = f"{value.name_hint}{suffix}"
            self._ssa_values[value] = name
            self.ssa_names[value.name_hint] = curr_ind + 1
        else:
            name = self._get_new_valid_name_id()
            self._ssa_values[value] = name

        self.print_string(f"%{name}")
        return name

    def print_operand(self, operand: SSAValue) -> None:
        self.print_ssa_value(operand)

    def print_block_name(self, block: Block) -> str:
        """
        Print a block name in the printer. This assigns a name to the block if the block
        does not have one in the current printing context.
        If the block has a name hint, it will use it as a prefix, and otherwise assign
        "bb" followed by a number as the name. Numbers are assigned in order.

        Returns the name used for printing the block.
        """
        if block in self._blocks:
            name = self._blocks[block]
        elif block.name_hint:
            curr_ind = self.block_names.get(block.name_hint, 0)
            suffix = f"_{curr_ind}" if curr_ind != 0 else ""
            name = f"{block.name_hint}{suffix}"
            self._blocks[block] = name
            self.block_names[block.name_hint] = curr_ind + 1
        else:
            name = f"bb{self._get_new_valid_block_id()}"
            self._blocks[block] = name

        self.print_string(f"^{name}")
        return name

    def print_block(
        self,
        block: Block,
        print_block_args: bool = True,
        print_block_terminator: bool = True,
    ) -> None:
        """
        Print a block with syntax `(<caret-ident>`(` <block-args> `)`)? ops* )`
        * If `print_block_args` is False, the label and arguments are not printed.
        * If `print_block_terminator` is False, the block terminator is not printed.
        """

        if print_block_args:
            self._print_new_line()
            self.print_block_name(block)
            if len(block.args) != 0:
                with self.in_parens():
                    self.print_list(block.args, self.print_block_argument)
            self.print_string(":")

        with self.indented():
            for op in block.ops:
                if not print_block_terminator and op.has_trait(
                    IsTerminator, value_if_unregistered=False
                ):
                    continue
                self._print_new_line()
                self.print_op(op)

    def print_block_argument(self, arg: BlockArgument, print_type: bool = True) -> None:
        """
        Print a block argument with its type, e.g. `%arg : i32`
        Optionally, do not print the type.
        """
        self.print_ssa_value(arg)
        if print_type:
            self.print_string(" : ")
            self.print_attribute(arg.type)
            if self.print_debuginfo:
                self.print_string(" ")
                self.print_attribute(UnknownLoc())

    def print_region(
        self,
        region: Region,
        print_entry_block_args: bool = True,
        print_empty_block: bool = True,
        print_block_terminators: bool = True,
    ) -> None:
        """
        Print a region with syntax `{ <block>* }`
        * If `print_entry_block_args` is False, the arguments of the entry block
          are not printed.
        * If `print_empty_block` is False, empty entry blocks are not printed.
        * If `print_block_terminators` is False, the block terminators are not printed.
        """
        # Empty region
        with self.in_braces():
            if (entry_block := region.blocks.first) is None:
                self._print_new_line()
                return

            print_entry_block_args = (
                bool(entry_block.args) and print_entry_block_args
            ) or (not entry_block.ops and print_empty_block)
            self.print_block(
                entry_block,
                print_block_args=print_entry_block_args,
                print_block_terminator=print_block_terminators,
            )

            next_block = entry_block.next_block
            while next_block is not None:
                self.print_block(
                    next_block, print_block_terminator=print_block_terminators
                )
                next_block = next_block.next_block

            self._print_new_line()

    def print_regions(self, regions: Sequence[Region]) -> None:
        if len(regions) == 0:
            return

        self.print_string(" ")
        with self.in_parens():
            self.print_list(regions, self.print_region)

    def print_operands(self, operands: Sequence[SSAValue]) -> None:
        with self.in_parens():
            self.print_list(operands, self.print_operand)

    def print_paramattr_parameters(
        self, params: Sequence[Attribute], always_print_brackets: bool = False
    ) -> None:
        if len(params) == 0 and not always_print_brackets:
            return
        with self.in_angle_brackets():
            self.print_list(params, self.print_attribute)

    def print_string_literal(self, string: str):
        self.print_string(json.dumps(string))

    def print_identifier_or_string_literal(self, string: str):
        """
        Prints the provided string as an identifier if it is one,
        and as a string literal otherwise.
        """
        if MLIRLexer.bare_identifier_regex.fullmatch(string) is None:
            self.print_string_literal(string)
            return
        self.print_string(string)

    def print_bytes_literal(self, bytestring: bytes):
        self.print_string('"')
        for byte in bytestring:
            match byte:
                case 0x5C:  # ord("\\")
                    self.print_string("\\\\")
                case _ if 0x20 > byte or byte > 0x7E or byte == 0x22:
                    self.print_string(f"\\{byte:02X}")
                case _:
                    self.print_string(chr(byte))
        self.print_string('"')

    def print_complex_float(
        self, value: tuple[float, float], type: ComplexType[AnyFloat]
    ):
        real, imag = value
        with self.in_parens():
            self.print_float(real, type.element_type)
            self.print_string(",")
            self.print_float(imag, type.element_type)

    def print_complex_int(self, value: tuple[int, int], type: ComplexType[IntegerType]):
        real, imag = value
        with self.in_parens():
            self.print_int(real, type.element_type)
            self.print_string(",")
            self.print_int(imag, type.element_type)

    def print_complex(
        self,
        value: tuple[float, float] | tuple[int, int],
        type: ComplexType[IntegerType | AnyFloat],
    ):
        if isinstance(type.element_type, IntegerType):
            assert isa(value, tuple[int, int])
            self.print_complex_int(value, cast(ComplexType[IntegerType], type))
        else:
            assert isa(value, tuple[float, float])
            self.print_complex_float(value, cast(ComplexType[AnyFloat], type))

    def print_float(self, value: float, type: AnyFloat):
        if math.isnan(value) or math.isinf(value):
            if isinstance(type, Float16Type):
                self.print_string(hex(convert_f16_to_u16(value)))
            elif isinstance(type, Float32Type):
                self.print_string(hex(convert_f32_to_u32(value)))
            elif isinstance(type, Float64Type):
                self.print_string(hex(convert_f64_to_u64(value)))
            else:
                raise NotImplementedError(
                    f"Cannot print '{value}' value for float type {str(type)}"
                )
        else:
            # to mirror mlir-opt, attempt to print scientific notation iff the value parses losslessly
            float_str = f"{value:.5e}"
            index = float_str.find("e")
            float_str = float_str[:index] + "0" + float_str[index:]

            parsed_value = type.unpack(type.pack([float(float_str)]), 1)[0]

            if parsed_value == value:
                self.print_string(float_str)
            else:
                if isinstance(type, Float32Type):
                    # f32 is printed with 9 significant digits
                    float_str = f"{value:.9g}"
                    if "." in float_str:
                        self.print_string(float_str)
                    else:
                        self.print_string(f"0x{convert_f32_to_u32(value):X}")
                elif isinstance(type, Float64Type):
                    # f64 is printed with 17 significant digits
                    float_str = f"{value:.17g}"
                    if "." in float_str:
                        self.print_string(float_str)
                    else:
                        self.print_string(f"0x{convert_f64_to_u64(value):X}")
                else:
                    # default to full python precision
                    self.print_string(f"{repr(value)}")

    def print_int(self, value: int, type: IntegerType | IndexType | None = None):
        """
        Prints the numeric value of an integer, except when the (optional) specified type
        is `i1`, in which case a boolean "true" or "false" is printed instead.
        """
        if type == i1:
            if value:
                self.print_string("true")
            else:
                self.print_string("false")
        else:
            self.print_string(f"{value:d}")

    def print_dimension_list(self, dims: Sequence[int]):
        """
        Prints the dimension list of a shape, ending with a dimension.

        e.g.:
          Input: [5, 1, DYNAMIC_INDEX, 4]
          Prints: "5x1x?x4"
        """
        self.print_list(
            dims,
            lambda x: self.print_int(x)
            if x != DYNAMIC_INDEX
            else self.print_string("?"),
            "x",
        )

    def print_attribute(self, attribute: Attribute) -> None:
        # Print builtin attributes
        if isinstance(attribute, BuiltinAttribute):
            attribute.print_builtin(self)
            return

        # Print dialect attributes
        self.print_string("!" if isinstance(attribute, TypeAttribute) else "#")

        if isinstance(attribute, OpaqueSyntaxAttribute):
            self.print_string(attribute.name.replace(".", "<", 1))
            if isinstance(attribute, SpacedOpaqueSyntaxAttribute):
                self.print_string(" ")
        else:
            self.print_string(attribute.name)

        if isinstance(attribute, Data):
            attribute.print_parameter(self)

        elif isinstance(attribute, ParametrizedAttribute):
            attribute.print_parameters(self)

        if isinstance(attribute, OpaqueSyntaxAttribute):
            self.print_string(">")

    def print_successors(self, successors: Sequence[Block]):
        if len(successors) == 0:
            return
        self.print_string(" ")
        with self.in_square_brackets():
            self.print_list(successors, self.print_block_name)

    def _print_attr_string(self, attr_tuple: tuple[str, Attribute]) -> None:
        self.print_identifier_or_string_literal(attr_tuple[0])

        if not isinstance(attr_tuple[1], UnitAttr):
            self.print_string(" = ")
            self.print_attribute(attr_tuple[1])

    def print_attr_dict(self, attr_dict: Mapping[str, Attribute]) -> None:
        with self.in_braces():
            self.print_list(attr_dict.items(), self._print_attr_string)

    def _print_op_properties(self, properties: dict[str, Attribute]) -> None:
        if not properties:
            return

        self.print_string(" ")
        with self.in_angle_brackets():
            self.print_attr_dict(properties)

    def print_op_attributes(
        self,
        attributes: Mapping[str, Attribute],
        *,
        reserved_attr_names: Iterable[str] = (),
        print_keyword: bool = False,
    ) -> bool:
        """
        Prints the attribute dictionary of an operation, with an optional `attributes`
        keyword.
        Values for `reserved_attr_names` are not printed even if present.
        If the printed dictionary would be empty, then nothing is printed, and this
        function returns False.
        """
        if not attributes:
            return False

        if reserved_attr_names:
            attributes = {
                name: attr
                for name, attr in attributes.items()
                if name not in reserved_attr_names
            }

        if not attributes:
            return False

        if print_keyword:
            self.print_string(" attributes")

        self.print_string(" ")
        self.print_attr_dict(attributes)
        return True

    def print_op_with_default_format(self, op: Operation) -> None:
        self.print_operands(op.operands)
        self.print_successors(op.successors)
        if not self.print_properties_as_attributes:
            self._print_op_properties(op.properties)
        self.print_regions(op.regions)
        if self.print_properties_as_attributes:
            clashing_names = op.properties.keys() & op.attributes.keys()
            if clashing_names:
                raise ValueError(
                    f"Properties {', '.join(clashing_names)} would overwrite the attributes of the same names."
                )

            self.print_op_attributes(op.attributes | op.properties)
        else:
            self.print_op_attributes(op.attributes)
        self.print_string(" : ")
        self.print_operation_type(op)

    def print_function_type(
        self, input_types: Iterable[Attribute], output_types: Iterable[Attribute]
    ):
        """
        Prints a function type like `(i32, i64) -> (f32, f64)` with the following
        format:

        The inputs are always a comma-separated list in parentheses.
        If the output has a single element, the parentheses are dropped, except when the
        only return type is a function type, in which case they are kept.

        ```
        () -> ()                 # no inputs, no outputs
        (i32) -> ()              # one input, no outputs
        (i32) -> i32             # one input, one output
        (i32) -> (i32, i32)      # one input, two outputs
        (i32) -> ((i32) -> i32)  # one input, one function type output
        ```
        """
        with self.in_parens():
            self.print_list(input_types, self.print_attribute)
        self.print_string(" -> ")

        remaining_outputs_iterator = iter(output_types)
        try:
            first_type = next(remaining_outputs_iterator)
        except StopIteration:
            # No outputs
            self.print_string("()")
            return

        try:
            second_type = next(remaining_outputs_iterator)
        except StopIteration:
            # One output, drop parentheses unless it's a FunctionType
            if isinstance(first_type, FunctionType):
                with self.in_parens():
                    self.print_attribute(first_type)
            else:
                self.print_attribute(first_type)
            return

        # Two or more outputs, comma-separated list
        with self.in_parens():
            self.print_list(
                chain((first_type, second_type), remaining_outputs_iterator),
                self.print_attribute,
            )

    def print_operation_type(self, op: Operation) -> None:
        self.print_function_type(op.operand_types, op.result_types)
        if self.print_debuginfo:
            self.print_string(" ")
            self.print_attribute(UnknownLoc())

    def enter_scope(self) -> None:
        self._next_valid_name_id.append(self._next_valid_name_id[-1])
        self._next_valid_block_id.append(self._next_valid_block_id[-1])
        self._ssa_names.append(self._ssa_names[-1].copy())
        self._block_names.append(self._block_names[-1].copy())

    def exit_scope(self) -> None:
        self._next_valid_name_id.pop()
        self._next_valid_block_id.pop()
        self._ssa_names.pop()
        self._block_names.pop()

    def print_op(self, op: Operation) -> None:
        scope = bool(op.get_traits_of_type(IsolatedFromAbove))
        begin_op_pos = self._current_column
        self._print_results(op)
        if scope:
            self.enter_scope()
        use_custom_format = False
        if isinstance(op, UnregisteredOp):
            self.print_string(f'"{op.op_name.data}"')
        # If we print with the generic format, or the operation does not have a custom
        # format
        elif self.print_generic_format or Operation.print is type(op).print:
            self.print_string(f'"{op.name}"')
        else:
            self.print_string(op.name)
            use_custom_format = True
        end_op_pos = self._current_column
        if op in self.diagnostic.op_messages:
            for message in self.diagnostic.op_messages[op]:
                self._add_message_on_next_line(message, begin_op_pos, end_op_pos)
        if isinstance(op, UnregisteredOp):
            op_name = op.op_name
            del op.attributes["op_name__"]
            self.print_op_with_default_format(op)
            op.attributes["op_name__"] = op_name
        elif use_custom_format:
            op.print(self)
        else:
            self.print_op_with_default_format(op)
        if scope:
            self.exit_scope()

    def print_resource_handle(self, dialect: str, handle: str) -> None:
        if dialect not in self._dialect_resources:
            self._dialect_resources[dialect] = set()
        self._dialect_resources[dialect].add(handle)
        self.print_string(handle)

    def print_metadata(self, dialects: Iterable[Dialect]) -> None:
        if not self._dialect_resources:
            return

        # Prepare data
        resources_for_printing: dict[str, dict[str, str]] = {}
        resource_dialects = {
            d.name: d.get_interface(OpAsmDialectInterface)
            for d in dialects
            if d.has_interface(OpAsmDialectInterface)
        }

        for dialect_name, resource_keys in self._dialect_resources.items():
            interface = resource_dialects.get(dialect_name)
            assert interface
            resources = interface.build_resources(resource_keys)
            if resources:
                resources_for_printing[dialect_name] = resources

        if not resources_for_printing:
            # None of the referenced resources actually exist
            return

        # Printing
        self._print_new_line()
        self._print_new_line()
        with self.delimited("{-#", "#-}"):
            with self.indented():
                self._print_new_line()
                self.print_string("dialect_resources: ")
                with self.in_braces():
                    with self.indented():
                        self._print_new_line()

                        for dialect_name, resources in resources_for_printing.items():
                            self.print_string(dialect_name + ": ")
                            with self.in_braces():
                                with self.indented():
                                    self._print_new_line()
                                    sorted_elements = sorted(
                                        resources.items(), key=lambda x: x[0]
                                    )
                                    for key, resource in sorted_elements[:-1]:
                                        self.print_string(f'{key}: "{resource}",')
                                    self.print_string(
                                        f'{sorted_elements[-1][0]}: "{sorted_elements[-1][1]}"'
                                    )
                                self._print_new_line()
                    self._print_new_line()
            self._print_new_line()

    def print_symbol_name(self, sym_name: str):
        """
        Prints a string attribute as a symbol name, prepending it with an '@',
        and printing it as a string literal if it is not an identifier.
        """
        self.print_string("@")
        self.print_identifier_or_string_literal(sym_name)

print_generic_format: bool = field(default=False) class-attribute instance-attribute

print_properties_as_attributes: bool = field(default=False) class-attribute instance-attribute

print_debuginfo: bool = field(default=False) class-attribute instance-attribute

diagnostic: Diagnostic = field(default_factory=Diagnostic) class-attribute instance-attribute

ssa_names property

block_names property

K = TypeVar('K') class-attribute instance-attribute

V = TypeVar('V') class-attribute instance-attribute

print(*argv: Any) -> None

Source code in xdsl/printer.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
@deprecated("Please use type-specific print methods")
def print(self, *argv: Any) -> None:
    for arg in argv:
        if isinstance(arg, str):
            self.print_string(arg)
            continue
        if isinstance(arg, SSAValue):
            arg = cast(SSAValue[Attribute], arg)
            self.print_ssa_value(arg)
            continue
        if isinstance(arg, Attribute):
            self.print_attribute(arg)
            continue
        if isinstance(arg, Region):
            self.print_region(arg)
            continue
        if isinstance(arg, Block):
            self.print_block(arg)
            self._print_new_line()
            continue
        if isinstance(arg, Operation):
            self.print_op(arg)
            self._print_new_line()
            continue

        text = str(arg)
        self.print_string(text)

print_dictionary(elems: dict[K, V], print_key: Callable[[K], None], print_value: Callable[[V], None], delimiter: str = ', ') -> None

Source code in xdsl/printer.py
123
124
125
126
127
128
129
130
131
132
133
134
135
def print_dictionary(
    self,
    elems: dict[K, V],
    print_key: Callable[[K], None],
    print_value: Callable[[V], None],
    delimiter: str = ", ",
) -> None:
    for i, (key, value) in enumerate(elems.items()):
        if i:
            self.print_string(delimiter)
        print_key(key)
        self.print_string("=")
        print_value(value)

print_ssa_value(value: SSAValue) -> str

Print an SSA value in the printer. This assigns a name to the value if the value does not have one in the current printing context. If the value has a name hint, it will use it as a prefix, and otherwise assign a number as the name. Numbers are assigned in order.

Returns the name used for printing the value.

Source code in xdsl/printer.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
def print_ssa_value(self, value: SSAValue) -> str:
    """
    Print an SSA value in the printer. This assigns a name to the value if the value
    does not have one in the current printing context.
    If the value has a name hint, it will use it as a prefix, and otherwise assign
    a number as the name. Numbers are assigned in order.

    Returns the name used for printing the value.
    """
    if value in self._ssa_values:
        name = self._ssa_values[value]
    elif value.name_hint:
        curr_ind = self.ssa_names.get(value.name_hint, 0)
        suffix = f"_{curr_ind}" if curr_ind != 0 else ""
        name = f"{value.name_hint}{suffix}"
        self._ssa_values[value] = name
        self.ssa_names[value.name_hint] = curr_ind + 1
    else:
        name = self._get_new_valid_name_id()
        self._ssa_values[value] = name

    self.print_string(f"%{name}")
    return name

print_operand(operand: SSAValue) -> None

Source code in xdsl/printer.py
179
180
def print_operand(self, operand: SSAValue) -> None:
    self.print_ssa_value(operand)

print_block_name(block: Block) -> str

Print a block name in the printer. This assigns a name to the block if the block does not have one in the current printing context. If the block has a name hint, it will use it as a prefix, and otherwise assign "bb" followed by a number as the name. Numbers are assigned in order.

Returns the name used for printing the block.

Source code in xdsl/printer.py
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
def print_block_name(self, block: Block) -> str:
    """
    Print a block name in the printer. This assigns a name to the block if the block
    does not have one in the current printing context.
    If the block has a name hint, it will use it as a prefix, and otherwise assign
    "bb" followed by a number as the name. Numbers are assigned in order.

    Returns the name used for printing the block.
    """
    if block in self._blocks:
        name = self._blocks[block]
    elif block.name_hint:
        curr_ind = self.block_names.get(block.name_hint, 0)
        suffix = f"_{curr_ind}" if curr_ind != 0 else ""
        name = f"{block.name_hint}{suffix}"
        self._blocks[block] = name
        self.block_names[block.name_hint] = curr_ind + 1
    else:
        name = f"bb{self._get_new_valid_block_id()}"
        self._blocks[block] = name

    self.print_string(f"^{name}")
    return name

print_block(block: Block, print_block_args: bool = True, print_block_terminator: bool = True) -> None

Print a block with syntax (<caret-ident>(<block-args>))? ops* ) * If print_block_args is False, the label and arguments are not printed. * If print_block_terminator is False, the block terminator is not printed.

Source code in xdsl/printer.py
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
def print_block(
    self,
    block: Block,
    print_block_args: bool = True,
    print_block_terminator: bool = True,
) -> None:
    """
    Print a block with syntax `(<caret-ident>`(` <block-args> `)`)? ops* )`
    * If `print_block_args` is False, the label and arguments are not printed.
    * If `print_block_terminator` is False, the block terminator is not printed.
    """

    if print_block_args:
        self._print_new_line()
        self.print_block_name(block)
        if len(block.args) != 0:
            with self.in_parens():
                self.print_list(block.args, self.print_block_argument)
        self.print_string(":")

    with self.indented():
        for op in block.ops:
            if not print_block_terminator and op.has_trait(
                IsTerminator, value_if_unregistered=False
            ):
                continue
            self._print_new_line()
            self.print_op(op)

print_block_argument(arg: BlockArgument, print_type: bool = True) -> None

Print a block argument with its type, e.g. %arg : i32 Optionally, do not print the type.

Source code in xdsl/printer.py
235
236
237
238
239
240
241
242
243
244
245
246
def print_block_argument(self, arg: BlockArgument, print_type: bool = True) -> None:
    """
    Print a block argument with its type, e.g. `%arg : i32`
    Optionally, do not print the type.
    """
    self.print_ssa_value(arg)
    if print_type:
        self.print_string(" : ")
        self.print_attribute(arg.type)
        if self.print_debuginfo:
            self.print_string(" ")
            self.print_attribute(UnknownLoc())

print_region(region: Region, print_entry_block_args: bool = True, print_empty_block: bool = True, print_block_terminators: bool = True) -> None

Print a region with syntax { <block>* } * If print_entry_block_args is False, the arguments of the entry block are not printed. * If print_empty_block is False, empty entry blocks are not printed. * If print_block_terminators is False, the block terminators are not printed.

Source code in xdsl/printer.py
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
def print_region(
    self,
    region: Region,
    print_entry_block_args: bool = True,
    print_empty_block: bool = True,
    print_block_terminators: bool = True,
) -> None:
    """
    Print a region with syntax `{ <block>* }`
    * If `print_entry_block_args` is False, the arguments of the entry block
      are not printed.
    * If `print_empty_block` is False, empty entry blocks are not printed.
    * If `print_block_terminators` is False, the block terminators are not printed.
    """
    # Empty region
    with self.in_braces():
        if (entry_block := region.blocks.first) is None:
            self._print_new_line()
            return

        print_entry_block_args = (
            bool(entry_block.args) and print_entry_block_args
        ) or (not entry_block.ops and print_empty_block)
        self.print_block(
            entry_block,
            print_block_args=print_entry_block_args,
            print_block_terminator=print_block_terminators,
        )

        next_block = entry_block.next_block
        while next_block is not None:
            self.print_block(
                next_block, print_block_terminator=print_block_terminators
            )
            next_block = next_block.next_block

        self._print_new_line()

print_regions(regions: Sequence[Region]) -> None

Source code in xdsl/printer.py
286
287
288
289
290
291
292
def print_regions(self, regions: Sequence[Region]) -> None:
    if len(regions) == 0:
        return

    self.print_string(" ")
    with self.in_parens():
        self.print_list(regions, self.print_region)

print_operands(operands: Sequence[SSAValue]) -> None

Source code in xdsl/printer.py
294
295
296
def print_operands(self, operands: Sequence[SSAValue]) -> None:
    with self.in_parens():
        self.print_list(operands, self.print_operand)

print_paramattr_parameters(params: Sequence[Attribute], always_print_brackets: bool = False) -> None

Source code in xdsl/printer.py
298
299
300
301
302
303
304
def print_paramattr_parameters(
    self, params: Sequence[Attribute], always_print_brackets: bool = False
) -> None:
    if len(params) == 0 and not always_print_brackets:
        return
    with self.in_angle_brackets():
        self.print_list(params, self.print_attribute)

print_string_literal(string: str)

Source code in xdsl/printer.py
306
307
def print_string_literal(self, string: str):
    self.print_string(json.dumps(string))

print_identifier_or_string_literal(string: str)

Prints the provided string as an identifier if it is one, and as a string literal otherwise.

Source code in xdsl/printer.py
309
310
311
312
313
314
315
316
317
def print_identifier_or_string_literal(self, string: str):
    """
    Prints the provided string as an identifier if it is one,
    and as a string literal otherwise.
    """
    if MLIRLexer.bare_identifier_regex.fullmatch(string) is None:
        self.print_string_literal(string)
        return
    self.print_string(string)

print_bytes_literal(bytestring: bytes)

Source code in xdsl/printer.py
319
320
321
322
323
324
325
326
327
328
329
def print_bytes_literal(self, bytestring: bytes):
    self.print_string('"')
    for byte in bytestring:
        match byte:
            case 0x5C:  # ord("\\")
                self.print_string("\\\\")
            case _ if 0x20 > byte or byte > 0x7E or byte == 0x22:
                self.print_string(f"\\{byte:02X}")
            case _:
                self.print_string(chr(byte))
    self.print_string('"')

print_complex_float(value: tuple[float, float], type: ComplexType[AnyFloat])

Source code in xdsl/printer.py
331
332
333
334
335
336
337
338
def print_complex_float(
    self, value: tuple[float, float], type: ComplexType[AnyFloat]
):
    real, imag = value
    with self.in_parens():
        self.print_float(real, type.element_type)
        self.print_string(",")
        self.print_float(imag, type.element_type)

print_complex_int(value: tuple[int, int], type: ComplexType[IntegerType])

Source code in xdsl/printer.py
340
341
342
343
344
345
def print_complex_int(self, value: tuple[int, int], type: ComplexType[IntegerType]):
    real, imag = value
    with self.in_parens():
        self.print_int(real, type.element_type)
        self.print_string(",")
        self.print_int(imag, type.element_type)

print_complex(value: tuple[float, float] | tuple[int, int], type: ComplexType[IntegerType | AnyFloat])

Source code in xdsl/printer.py
347
348
349
350
351
352
353
354
355
356
357
def print_complex(
    self,
    value: tuple[float, float] | tuple[int, int],
    type: ComplexType[IntegerType | AnyFloat],
):
    if isinstance(type.element_type, IntegerType):
        assert isa(value, tuple[int, int])
        self.print_complex_int(value, cast(ComplexType[IntegerType], type))
    else:
        assert isa(value, tuple[float, float])
        self.print_complex_float(value, cast(ComplexType[AnyFloat], type))

print_float(value: float, type: AnyFloat)

Source code in xdsl/printer.py
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
def print_float(self, value: float, type: AnyFloat):
    if math.isnan(value) or math.isinf(value):
        if isinstance(type, Float16Type):
            self.print_string(hex(convert_f16_to_u16(value)))
        elif isinstance(type, Float32Type):
            self.print_string(hex(convert_f32_to_u32(value)))
        elif isinstance(type, Float64Type):
            self.print_string(hex(convert_f64_to_u64(value)))
        else:
            raise NotImplementedError(
                f"Cannot print '{value}' value for float type {str(type)}"
            )
    else:
        # to mirror mlir-opt, attempt to print scientific notation iff the value parses losslessly
        float_str = f"{value:.5e}"
        index = float_str.find("e")
        float_str = float_str[:index] + "0" + float_str[index:]

        parsed_value = type.unpack(type.pack([float(float_str)]), 1)[0]

        if parsed_value == value:
            self.print_string(float_str)
        else:
            if isinstance(type, Float32Type):
                # f32 is printed with 9 significant digits
                float_str = f"{value:.9g}"
                if "." in float_str:
                    self.print_string(float_str)
                else:
                    self.print_string(f"0x{convert_f32_to_u32(value):X}")
            elif isinstance(type, Float64Type):
                # f64 is printed with 17 significant digits
                float_str = f"{value:.17g}"
                if "." in float_str:
                    self.print_string(float_str)
                else:
                    self.print_string(f"0x{convert_f64_to_u64(value):X}")
            else:
                # default to full python precision
                self.print_string(f"{repr(value)}")

print_int(value: int, type: IntegerType | IndexType | None = None)

Prints the numeric value of an integer, except when the (optional) specified type is i1, in which case a boolean "true" or "false" is printed instead.

Source code in xdsl/printer.py
400
401
402
403
404
405
406
407
408
409
410
411
def print_int(self, value: int, type: IntegerType | IndexType | None = None):
    """
    Prints the numeric value of an integer, except when the (optional) specified type
    is `i1`, in which case a boolean "true" or "false" is printed instead.
    """
    if type == i1:
        if value:
            self.print_string("true")
        else:
            self.print_string("false")
    else:
        self.print_string(f"{value:d}")

print_dimension_list(dims: Sequence[int])

Prints the dimension list of a shape, ending with a dimension.

e.g.: Input: [5, 1, DYNAMIC_INDEX, 4] Prints: "5x1x?x4"

Source code in xdsl/printer.py
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
def print_dimension_list(self, dims: Sequence[int]):
    """
    Prints the dimension list of a shape, ending with a dimension.

    e.g.:
      Input: [5, 1, DYNAMIC_INDEX, 4]
      Prints: "5x1x?x4"
    """
    self.print_list(
        dims,
        lambda x: self.print_int(x)
        if x != DYNAMIC_INDEX
        else self.print_string("?"),
        "x",
    )

print_attribute(attribute: Attribute) -> None

Source code in xdsl/printer.py
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
def print_attribute(self, attribute: Attribute) -> None:
    # Print builtin attributes
    if isinstance(attribute, BuiltinAttribute):
        attribute.print_builtin(self)
        return

    # Print dialect attributes
    self.print_string("!" if isinstance(attribute, TypeAttribute) else "#")

    if isinstance(attribute, OpaqueSyntaxAttribute):
        self.print_string(attribute.name.replace(".", "<", 1))
        if isinstance(attribute, SpacedOpaqueSyntaxAttribute):
            self.print_string(" ")
    else:
        self.print_string(attribute.name)

    if isinstance(attribute, Data):
        attribute.print_parameter(self)

    elif isinstance(attribute, ParametrizedAttribute):
        attribute.print_parameters(self)

    if isinstance(attribute, OpaqueSyntaxAttribute):
        self.print_string(">")

print_successors(successors: Sequence[Block])

Source code in xdsl/printer.py
454
455
456
457
458
459
def print_successors(self, successors: Sequence[Block]):
    if len(successors) == 0:
        return
    self.print_string(" ")
    with self.in_square_brackets():
        self.print_list(successors, self.print_block_name)

print_attr_dict(attr_dict: Mapping[str, Attribute]) -> None

Source code in xdsl/printer.py
468
469
470
def print_attr_dict(self, attr_dict: Mapping[str, Attribute]) -> None:
    with self.in_braces():
        self.print_list(attr_dict.items(), self._print_attr_string)

print_op_attributes(attributes: Mapping[str, Attribute], *, reserved_attr_names: Iterable[str] = (), print_keyword: bool = False) -> bool

Prints the attribute dictionary of an operation, with an optional attributes keyword. Values for reserved_attr_names are not printed even if present. If the printed dictionary would be empty, then nothing is printed, and this function returns False.

Source code in xdsl/printer.py
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
def print_op_attributes(
    self,
    attributes: Mapping[str, Attribute],
    *,
    reserved_attr_names: Iterable[str] = (),
    print_keyword: bool = False,
) -> bool:
    """
    Prints the attribute dictionary of an operation, with an optional `attributes`
    keyword.
    Values for `reserved_attr_names` are not printed even if present.
    If the printed dictionary would be empty, then nothing is printed, and this
    function returns False.
    """
    if not attributes:
        return False

    if reserved_attr_names:
        attributes = {
            name: attr
            for name, attr in attributes.items()
            if name not in reserved_attr_names
        }

    if not attributes:
        return False

    if print_keyword:
        self.print_string(" attributes")

    self.print_string(" ")
    self.print_attr_dict(attributes)
    return True

print_op_with_default_format(op: Operation) -> None

Source code in xdsl/printer.py
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
def print_op_with_default_format(self, op: Operation) -> None:
    self.print_operands(op.operands)
    self.print_successors(op.successors)
    if not self.print_properties_as_attributes:
        self._print_op_properties(op.properties)
    self.print_regions(op.regions)
    if self.print_properties_as_attributes:
        clashing_names = op.properties.keys() & op.attributes.keys()
        if clashing_names:
            raise ValueError(
                f"Properties {', '.join(clashing_names)} would overwrite the attributes of the same names."
            )

        self.print_op_attributes(op.attributes | op.properties)
    else:
        self.print_op_attributes(op.attributes)
    self.print_string(" : ")
    self.print_operation_type(op)

print_function_type(input_types: Iterable[Attribute], output_types: Iterable[Attribute])

Prints a function type like (i32, i64) -> (f32, f64) with the following format:

The inputs are always a comma-separated list in parentheses. If the output has a single element, the parentheses are dropped, except when the only return type is a function type, in which case they are kept.

() -> ()                 # no inputs, no outputs
(i32) -> ()              # one input, no outputs
(i32) -> i32             # one input, one output
(i32) -> (i32, i32)      # one input, two outputs
(i32) -> ((i32) -> i32)  # one input, one function type output
Source code in xdsl/printer.py
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
def print_function_type(
    self, input_types: Iterable[Attribute], output_types: Iterable[Attribute]
):
    """
    Prints a function type like `(i32, i64) -> (f32, f64)` with the following
    format:

    The inputs are always a comma-separated list in parentheses.
    If the output has a single element, the parentheses are dropped, except when the
    only return type is a function type, in which case they are kept.

    ```
    () -> ()                 # no inputs, no outputs
    (i32) -> ()              # one input, no outputs
    (i32) -> i32             # one input, one output
    (i32) -> (i32, i32)      # one input, two outputs
    (i32) -> ((i32) -> i32)  # one input, one function type output
    ```
    """
    with self.in_parens():
        self.print_list(input_types, self.print_attribute)
    self.print_string(" -> ")

    remaining_outputs_iterator = iter(output_types)
    try:
        first_type = next(remaining_outputs_iterator)
    except StopIteration:
        # No outputs
        self.print_string("()")
        return

    try:
        second_type = next(remaining_outputs_iterator)
    except StopIteration:
        # One output, drop parentheses unless it's a FunctionType
        if isinstance(first_type, FunctionType):
            with self.in_parens():
                self.print_attribute(first_type)
        else:
            self.print_attribute(first_type)
        return

    # Two or more outputs, comma-separated list
    with self.in_parens():
        self.print_list(
            chain((first_type, second_type), remaining_outputs_iterator),
            self.print_attribute,
        )

print_operation_type(op: Operation) -> None

Source code in xdsl/printer.py
582
583
584
585
586
def print_operation_type(self, op: Operation) -> None:
    self.print_function_type(op.operand_types, op.result_types)
    if self.print_debuginfo:
        self.print_string(" ")
        self.print_attribute(UnknownLoc())

enter_scope() -> None

Source code in xdsl/printer.py
588
589
590
591
592
def enter_scope(self) -> None:
    self._next_valid_name_id.append(self._next_valid_name_id[-1])
    self._next_valid_block_id.append(self._next_valid_block_id[-1])
    self._ssa_names.append(self._ssa_names[-1].copy())
    self._block_names.append(self._block_names[-1].copy())

exit_scope() -> None

Source code in xdsl/printer.py
594
595
596
597
598
def exit_scope(self) -> None:
    self._next_valid_name_id.pop()
    self._next_valid_block_id.pop()
    self._ssa_names.pop()
    self._block_names.pop()

print_op(op: Operation) -> None

Source code in xdsl/printer.py
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
def print_op(self, op: Operation) -> None:
    scope = bool(op.get_traits_of_type(IsolatedFromAbove))
    begin_op_pos = self._current_column
    self._print_results(op)
    if scope:
        self.enter_scope()
    use_custom_format = False
    if isinstance(op, UnregisteredOp):
        self.print_string(f'"{op.op_name.data}"')
    # If we print with the generic format, or the operation does not have a custom
    # format
    elif self.print_generic_format or Operation.print is type(op).print:
        self.print_string(f'"{op.name}"')
    else:
        self.print_string(op.name)
        use_custom_format = True
    end_op_pos = self._current_column
    if op in self.diagnostic.op_messages:
        for message in self.diagnostic.op_messages[op]:
            self._add_message_on_next_line(message, begin_op_pos, end_op_pos)
    if isinstance(op, UnregisteredOp):
        op_name = op.op_name
        del op.attributes["op_name__"]
        self.print_op_with_default_format(op)
        op.attributes["op_name__"] = op_name
    elif use_custom_format:
        op.print(self)
    else:
        self.print_op_with_default_format(op)
    if scope:
        self.exit_scope()

print_resource_handle(dialect: str, handle: str) -> None

Source code in xdsl/printer.py
632
633
634
635
636
def print_resource_handle(self, dialect: str, handle: str) -> None:
    if dialect not in self._dialect_resources:
        self._dialect_resources[dialect] = set()
    self._dialect_resources[dialect].add(handle)
    self.print_string(handle)

print_metadata(dialects: Iterable[Dialect]) -> None

Source code in xdsl/printer.py
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
def print_metadata(self, dialects: Iterable[Dialect]) -> None:
    if not self._dialect_resources:
        return

    # Prepare data
    resources_for_printing: dict[str, dict[str, str]] = {}
    resource_dialects = {
        d.name: d.get_interface(OpAsmDialectInterface)
        for d in dialects
        if d.has_interface(OpAsmDialectInterface)
    }

    for dialect_name, resource_keys in self._dialect_resources.items():
        interface = resource_dialects.get(dialect_name)
        assert interface
        resources = interface.build_resources(resource_keys)
        if resources:
            resources_for_printing[dialect_name] = resources

    if not resources_for_printing:
        # None of the referenced resources actually exist
        return

    # Printing
    self._print_new_line()
    self._print_new_line()
    with self.delimited("{-#", "#-}"):
        with self.indented():
            self._print_new_line()
            self.print_string("dialect_resources: ")
            with self.in_braces():
                with self.indented():
                    self._print_new_line()

                    for dialect_name, resources in resources_for_printing.items():
                        self.print_string(dialect_name + ": ")
                        with self.in_braces():
                            with self.indented():
                                self._print_new_line()
                                sorted_elements = sorted(
                                    resources.items(), key=lambda x: x[0]
                                )
                                for key, resource in sorted_elements[:-1]:
                                    self.print_string(f'{key}: "{resource}",')
                                self.print_string(
                                    f'{sorted_elements[-1][0]}: "{sorted_elements[-1][1]}"'
                                )
                            self._print_new_line()
                self._print_new_line()
        self._print_new_line()

print_symbol_name(sym_name: str)

Prints a string attribute as a symbol name, prepending it with an '@', and printing it as a string literal if it is not an identifier.

Source code in xdsl/printer.py
689
690
691
692
693
694
695
def print_symbol_name(self, sym_name: str):
    """
    Prints a string attribute as a symbol name, prepending it with an '@',
    and printing it as a string literal if it is not an identifier.
    """
    self.print_string("@")
    self.print_identifier_or_string_literal(sym_name)

__init__(stream: IO[str] | None = None, print_generic_format: bool = False, print_properties_as_attributes: bool = False, print_debuginfo: bool = False, diagnostic: Diagnostic = Diagnostic(), _dialect_resources: dict[str, set[str]] = dict[str, set[str]](), *, indent_num_spaces: int = 2) -> None

HasParent dataclass

Bases: OpTrait

Constraint the operation to have a specific parent operation.

Source code in xdsl/traits.py
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
@dataclass(frozen=True)
class HasParent(OpTrait):
    """Constraint the operation to have a specific parent operation."""

    op_types: tuple[type[Operation], ...]

    def __init__(self, head_param: type[Operation], *tail_params: type[Operation]):
        object.__setattr__(self, "op_types", (head_param, *tail_params))

    def verify(self, op: Operation) -> None:
        parent = op.parent_op()
        # Don't check parent when op is detached
        if parent is None:
            return
        if isinstance(parent, self.op_types):
            return
        if len(self.op_types) == 1:
            raise VerifyException(
                f"'{op.name}' expects parent op '{self.op_types[0].name}'"
            )
        names = ", ".join(f"'{p.name}'" for p in self.op_types)
        raise VerifyException(f"'{op.name}' expects parent op to be one of {names}")

op_types: tuple[type[Operation], ...] instance-attribute

__init__(head_param: type[Operation], *tail_params: type[Operation])

Source code in xdsl/traits.py
77
78
def __init__(self, head_param: type[Operation], *tail_params: type[Operation]):
    object.__setattr__(self, "op_types", (head_param, *tail_params))

verify(op: Operation) -> None

Source code in xdsl/traits.py
80
81
82
83
84
85
86
87
88
89
90
91
92
def verify(self, op: Operation) -> None:
    parent = op.parent_op()
    # Don't check parent when op is detached
    if parent is None:
        return
    if isinstance(parent, self.op_types):
        return
    if len(self.op_types) == 1:
        raise VerifyException(
            f"'{op.name}' expects parent op '{self.op_types[0].name}'"
        )
    names = ", ".join(f"'{p.name}'" for p in self.op_types)
    raise VerifyException(f"'{op.name}' expects parent op to be one of {names}")

NoTerminator dataclass

Bases: OpTrait

Allow an operation to have single block regions with no terminator.

See external documentation.

Source code in xdsl/traits.py
164
165
166
167
168
169
170
171
172
173
174
175
176
class NoTerminator(OpTrait):
    """
    Allow an operation to have single block regions with no terminator.

    See external [documentation](https://mlir.llvm.org/docs/Traits/#terminator).
    """

    def verify(self, op: Operation) -> None:
        for region in op.regions:
            if len(region.blocks) > 1:
                raise VerifyException(
                    f"'{op.name}' does not contain single-block regions"
                )

verify(op: Operation) -> None

Source code in xdsl/traits.py
171
172
173
174
175
176
def verify(self, op: Operation) -> None:
    for region in op.regions:
        if len(region.blocks) > 1:
            raise VerifyException(
                f"'{op.name}' does not contain single-block regions"
            )

SymbolOpInterface dataclass

Bases: OpTrait

A Symbol is a named operation that resides immediately within a region that defines a SymbolTable (TODO). A Symbol operation should use the SymbolOpInterface interface to provide the necessary verification and accessors.

A Symbol operation may be optional or not. If - the default - it is not optional, a sym_name attribute of type StringAttr is required. If it is optional, the attribute is optional too.

xDSL offers OptionalSymbolOpInterface as an always-optional SymbolOpInterface helper.

More requirements are defined in MLIR; Please see MLIR documentation for Symbol and SymbolTable for the requirements that are upcoming in xDSL.

See external documentation.

Source code in xdsl/traits.py
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
class SymbolOpInterface(OpTrait):
    """
    A `Symbol` is a named operation that resides immediately within a region that defines
    a `SymbolTable` (TODO). A Symbol operation should use the SymbolOpInterface interface to
    provide the necessary verification and accessors.

    A Symbol operation may be optional or not. If - the default - it is not optional,
    a `sym_name` attribute of type StringAttr is required. If it is optional,
    the attribute is optional too.

    xDSL offers OptionalSymbolOpInterface as an always-optional SymbolOpInterface helper.

    More requirements are defined in MLIR; Please see MLIR documentation for Symbol and
    SymbolTable for the requirements that are upcoming in xDSL.

    See external [documentation](https://mlir.llvm.org/docs/SymbolsAndSymbolTables/#symbol).
    """

    def get_sym_attr_name(self, op: Operation) -> StringAttr | None:
        """
        Returns the symbol of the operation, if any
        """
        # import builtin here to avoid circular import
        from xdsl.dialects.builtin import StringAttr

        sym_name = op.get_attr_or_prop("sym_name")
        if sym_name is None and self.is_optional_symbol(op):
            return None
        if not isinstance(sym_name, StringAttr):
            raise VerifyException(
                f'Operation {op.name} must have a "sym_name" attribute of type '
                f"`StringAttr` to conform to {SymbolOpInterface.__name__}"
            )
        return sym_name

    def is_optional_symbol(self, op: Operation) -> bool:
        """
        Returns true if this operation optionally defines a symbol based on the
        presence of the symbol name.
        """
        return False

    def verify(self, op: Operation) -> None:
        # This helper has the same behaviour, so we reuse it as a verifier.That is, it
        # raises a VerifyException iff this operation is a non-optional symbol *and*
        # there is no "sym_name" attribute or property.
        self.get_sym_attr_name(op)

get_sym_attr_name(op: Operation) -> StringAttr | None

Returns the symbol of the operation, if any

Source code in xdsl/traits.py
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
def get_sym_attr_name(self, op: Operation) -> StringAttr | None:
    """
    Returns the symbol of the operation, if any
    """
    # import builtin here to avoid circular import
    from xdsl.dialects.builtin import StringAttr

    sym_name = op.get_attr_or_prop("sym_name")
    if sym_name is None and self.is_optional_symbol(op):
        return None
    if not isinstance(sym_name, StringAttr):
        raise VerifyException(
            f'Operation {op.name} must have a "sym_name" attribute of type '
            f"`StringAttr` to conform to {SymbolOpInterface.__name__}"
        )
    return sym_name

is_optional_symbol(op: Operation) -> bool

Returns true if this operation optionally defines a symbol based on the presence of the symbol name.

Source code in xdsl/traits.py
448
449
450
451
452
453
def is_optional_symbol(self, op: Operation) -> bool:
    """
    Returns true if this operation optionally defines a symbol based on the
    presence of the symbol name.
    """
    return False

verify(op: Operation) -> None

Source code in xdsl/traits.py
455
456
457
458
459
def verify(self, op: Operation) -> None:
    # This helper has the same behaviour, so we reuse it as a verifier.That is, it
    # raises a VerifyException iff this operation is a non-optional symbol *and*
    # there is no "sym_name" attribute or property.
    self.get_sym_attr_name(op)

SymbolTable dataclass

Bases: OpTrait

SymbolTable operations are containers for Symbol operations. They offer lookup functionality for Symbols, and enforce unique symbols amongst its children.

A SymbolTable operation is constrained to have a single single-block region.

Source code in xdsl/traits.py
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
class SymbolTable(OpTrait):
    """
    SymbolTable operations are containers for Symbol operations. They offer lookup
    functionality for Symbols, and enforce unique symbols amongst its children.

    A SymbolTable operation is constrained to have a single single-block region.
    """

    def verify(self, op: Operation):
        # import builtin here to avoid circular import
        from xdsl.dialects.builtin import StringAttr

        if len(op.regions) != 1:
            raise VerifyException(
                "Operations with a 'SymbolTable' must have exactly one region"
            )
        if len(op.regions[0].blocks) != 1:
            raise VerifyException(
                "Operations with a 'SymbolTable' must have exactly one block"
            )
        block = op.regions[0].blocks[0]
        met_names: set[StringAttr] = set()
        for o in block.ops:
            if (sym_name := o.get_attr_or_prop("sym_name")) is None:
                continue
            if not isinstance(sym_name, StringAttr):
                continue
            if sym_name in met_names:
                raise VerifyException(f'Redefinition of symbol "{sym_name.data}"')
            met_names.add(sym_name)

    @staticmethod
    def lookup_symbol(
        op: Operation, name: str | StringAttr | SymbolRefAttr
    ) -> Operation | None:
        """
        Lookup a symbol by reference, starting from a specific operation's closest
        SymbolTable parent.
        """
        # import builtin here to avoid circular import
        from xdsl.dialects.builtin import StringAttr, SymbolRefAttr

        anchor: Operation | None = op
        while anchor is not None and not anchor.has_trait(SymbolTable):
            anchor = anchor.parent_op()
        if anchor is None:
            raise ValueError(f"Operation {op} has no SymbolTable ancestor")
        if isinstance(name, str | StringAttr):
            name = SymbolRefAttr(name)
        for o in anchor.regions[0].block.ops:
            if (
                sym_interface := o.get_trait(SymbolOpInterface)
            ) is not None and sym_interface.get_sym_attr_name(o) == name.root_reference:
                if not name.nested_references:
                    return o
                nested_root, *nested_references = name.nested_references.data
                nested_name = SymbolRefAttr(nested_root, nested_references)
                return SymbolTable.lookup_symbol(o, nested_name)
        return None

    @staticmethod
    def insert_or_update(
        symbol_table_op: Operation, symbol_op: Operation
    ) -> Operation | None:
        """
        This takes a symbol_table_op and a symbol_op. It looks if another operation
        inside symbol_table_op already defines symbol_ops symbol. If another operation
        is found, it replaces that operation with symbol_op. Otherwise, symbol_op is
        inserted at the end of symbol_table_op.

        This method returns the operation that was replaced or None if no operation
        was replaced.
        """
        trait = symbol_op.get_trait(SymbolOpInterface)

        if trait is None:
            raise ValueError(
                "Passed symbol_op does not have the SymbolOpInterface trait"
            )

        symbol_name = trait.get_sym_attr_name(symbol_op)

        if symbol_name is None:
            raise ValueError("Passed symbol_op does not have a symbol attribute name")

        tbl_trait = symbol_table_op.get_trait(SymbolTable)

        if tbl_trait is None:
            raise ValueError("Passed symbol_table_op does not have a SymbolTable trait")

        defined_symbol = tbl_trait.lookup_symbol(symbol_table_op, symbol_name)

        if defined_symbol is None:
            symbol_table_op.regions[0].blocks[0].add_op(symbol_op)
            return None
        else:
            parent = defined_symbol.parent
            assert parent is not None
            parent.insert_op_after(symbol_op, defined_symbol)
            parent.detach_op(defined_symbol)
            return defined_symbol

verify(op: Operation)

Source code in xdsl/traits.py
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
def verify(self, op: Operation):
    # import builtin here to avoid circular import
    from xdsl.dialects.builtin import StringAttr

    if len(op.regions) != 1:
        raise VerifyException(
            "Operations with a 'SymbolTable' must have exactly one region"
        )
    if len(op.regions[0].blocks) != 1:
        raise VerifyException(
            "Operations with a 'SymbolTable' must have exactly one block"
        )
    block = op.regions[0].blocks[0]
    met_names: set[StringAttr] = set()
    for o in block.ops:
        if (sym_name := o.get_attr_or_prop("sym_name")) is None:
            continue
        if not isinstance(sym_name, StringAttr):
            continue
        if sym_name in met_names:
            raise VerifyException(f'Redefinition of symbol "{sym_name.data}"')
        met_names.add(sym_name)

lookup_symbol(op: Operation, name: str | StringAttr | SymbolRefAttr) -> Operation | None staticmethod

Lookup a symbol by reference, starting from a specific operation's closest SymbolTable parent.

Source code in xdsl/traits.py
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
@staticmethod
def lookup_symbol(
    op: Operation, name: str | StringAttr | SymbolRefAttr
) -> Operation | None:
    """
    Lookup a symbol by reference, starting from a specific operation's closest
    SymbolTable parent.
    """
    # import builtin here to avoid circular import
    from xdsl.dialects.builtin import StringAttr, SymbolRefAttr

    anchor: Operation | None = op
    while anchor is not None and not anchor.has_trait(SymbolTable):
        anchor = anchor.parent_op()
    if anchor is None:
        raise ValueError(f"Operation {op} has no SymbolTable ancestor")
    if isinstance(name, str | StringAttr):
        name = SymbolRefAttr(name)
    for o in anchor.regions[0].block.ops:
        if (
            sym_interface := o.get_trait(SymbolOpInterface)
        ) is not None and sym_interface.get_sym_attr_name(o) == name.root_reference:
            if not name.nested_references:
                return o
            nested_root, *nested_references = name.nested_references.data
            nested_name = SymbolRefAttr(nested_root, nested_references)
            return SymbolTable.lookup_symbol(o, nested_name)
    return None

insert_or_update(symbol_table_op: Operation, symbol_op: Operation) -> Operation | None staticmethod

This takes a symbol_table_op and a symbol_op. It looks if another operation inside symbol_table_op already defines symbol_ops symbol. If another operation is found, it replaces that operation with symbol_op. Otherwise, symbol_op is inserted at the end of symbol_table_op.

This method returns the operation that was replaced or None if no operation was replaced.

Source code in xdsl/traits.py
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
@staticmethod
def insert_or_update(
    symbol_table_op: Operation, symbol_op: Operation
) -> Operation | None:
    """
    This takes a symbol_table_op and a symbol_op. It looks if another operation
    inside symbol_table_op already defines symbol_ops symbol. If another operation
    is found, it replaces that operation with symbol_op. Otherwise, symbol_op is
    inserted at the end of symbol_table_op.

    This method returns the operation that was replaced or None if no operation
    was replaced.
    """
    trait = symbol_op.get_trait(SymbolOpInterface)

    if trait is None:
        raise ValueError(
            "Passed symbol_op does not have the SymbolOpInterface trait"
        )

    symbol_name = trait.get_sym_attr_name(symbol_op)

    if symbol_name is None:
        raise ValueError("Passed symbol_op does not have a symbol attribute name")

    tbl_trait = symbol_table_op.get_trait(SymbolTable)

    if tbl_trait is None:
        raise ValueError("Passed symbol_table_op does not have a SymbolTable trait")

    defined_symbol = tbl_trait.lookup_symbol(symbol_table_op, symbol_name)

    if defined_symbol is None:
        symbol_table_op.regions[0].blocks[0].add_op(symbol_op)
        return None
    else:
        parent = defined_symbol.parent
        assert parent is not None
        parent.insert_op_after(symbol_op, defined_symbol)
        parent.detach_op(defined_symbol)
        return defined_symbol

VerifyException

Bases: DiagnosticException

Source code in xdsl/utils/exceptions.py
36
37
class VerifyException(DiagnosticException):
    pass

StrEnum

Bases: str, Enum

Homemade StrEnum. StrEnum is standard in Python>=3.11.

Source code in xdsl/utils/str_enum.py
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class StrEnum(str, Enum):
    """
    Homemade StrEnum. StrEnum is standard in Python>=3.11.
    """

    @staticmethod
    def _generate_next_value_(
        name: str, start: int, count: int, last_values: list[Any]
    ):
        return name.lower()

    def __str__(self) -> str:
        return self.value

__str__() -> str

Source code in xdsl/utils/str_enum.py
16
17
def __str__(self) -> str:
    return self.value

VariadicityEnum

Bases: StrEnum

Source code in xdsl/dialects/irdl/irdl.py
57
58
59
60
class VariadicityEnum(StrEnum):
    SINGLE = "single"
    OPTIONAL = "optional"
    VARIADIC = "variadic"

SINGLE = 'single' class-attribute instance-attribute

OPTIONAL = 'optional' class-attribute instance-attribute

VARIADIC = 'variadic' class-attribute instance-attribute

VariadicityAttr dataclass

Bases: EnumAttribute[VariadicityEnum], SpacedOpaqueSyntaxAttribute

Source code in xdsl/dialects/irdl/irdl.py
63
64
65
66
67
68
69
@irdl_attr_definition
class VariadicityAttr(EnumAttribute[VariadicityEnum], SpacedOpaqueSyntaxAttribute):
    name = "irdl.variadicity"

    SINGLE: ClassVar[VariadicityAttr]
    OPTIONAL: ClassVar[VariadicityAttr]
    VARIADIC: ClassVar[VariadicityAttr]

name = 'irdl.variadicity' class-attribute instance-attribute

SINGLE: VariadicityAttr class-attribute

OPTIONAL: VariadicityAttr class-attribute

VARIADIC: VariadicityAttr class-attribute

VariadicityArrayAttr dataclass

Bases: ParametrizedAttribute, SpacedOpaqueSyntaxAttribute

Source code in xdsl/dialects/irdl/irdl.py
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
@irdl_attr_definition
class VariadicityArrayAttr(ParametrizedAttribute, SpacedOpaqueSyntaxAttribute):
    name = "irdl.variadicity_array"

    value: ArrayAttr[VariadicityAttr]

    @classmethod
    def parse_parameters(cls, parser: AttrParser) -> tuple[ArrayAttr[VariadicityAttr]]:
        data = parser.parse_comma_separated_list(
            AttrParser.Delimiter.SQUARE, lambda: VariadicityAttr.parse_parameter(parser)
        )
        return (ArrayAttr(VariadicityAttr(x) for x in data),)

    def print_parameters(self, printer: Printer) -> None:
        printer.print_string("[")
        printer.print_list(self.value, lambda var: var.print_parameter(printer))
        printer.print_string("]")

name = 'irdl.variadicity_array' class-attribute instance-attribute

value: ArrayAttr[VariadicityAttr] instance-attribute

parse_parameters(parser: AttrParser) -> tuple[ArrayAttr[VariadicityAttr]] classmethod

Source code in xdsl/dialects/irdl/irdl.py
83
84
85
86
87
88
@classmethod
def parse_parameters(cls, parser: AttrParser) -> tuple[ArrayAttr[VariadicityAttr]]:
    data = parser.parse_comma_separated_list(
        AttrParser.Delimiter.SQUARE, lambda: VariadicityAttr.parse_parameter(parser)
    )
    return (ArrayAttr(VariadicityAttr(x) for x in data),)

print_parameters(printer: Printer) -> None

Source code in xdsl/dialects/irdl/irdl.py
90
91
92
93
def print_parameters(self, printer: Printer) -> None:
    printer.print_string("[")
    printer.print_list(self.value, lambda var: var.print_parameter(printer))
    printer.print_string("]")

AttributeType dataclass

Bases: ParametrizedAttribute, TypeAttribute

Type of a attribute handle.

Source code in xdsl/dialects/irdl/irdl.py
 96
 97
 98
 99
100
@irdl_attr_definition
class AttributeType(ParametrizedAttribute, TypeAttribute):
    """Type of a attribute handle."""

    name = "irdl.attribute"

name = 'irdl.attribute' class-attribute instance-attribute

RegionType dataclass

Bases: ParametrizedAttribute, TypeAttribute

IRDL handle to a region definition

Source code in xdsl/dialects/irdl/irdl.py
103
104
105
106
107
@irdl_attr_definition
class RegionType(ParametrizedAttribute, TypeAttribute):
    """IRDL handle to a region definition"""

    name = "irdl.region"

name = 'irdl.region' class-attribute instance-attribute

DialectOp

Bases: IRDLOperation

A dialect definition.

Source code in xdsl/dialects/irdl/irdl.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
@irdl_op_definition
class DialectOp(IRDLOperation):
    """A dialect definition."""

    name = "irdl.dialect"

    sym_name = attr_def(SymbolNameConstraint())
    body = region_def("single_block")

    traits = traits_def(NoTerminator(), SymbolOpInterface(), SymbolTable())

    def __init__(self, name: str | StringAttr, body: Region):
        if isinstance(name, str):
            name = StringAttr(name)
        super().__init__(attributes={"sym_name": name}, regions=[body])

    @classmethod
    def parse(cls, parser: Parser) -> DialectOp:
        sym_name = parser.parse_symbol_name()
        region = parser.parse_optional_region()
        if region is None:
            region = Region(Block())
        return DialectOp(sym_name, region)

    def print(self, printer: Printer) -> None:
        printer.print_string(" ")
        printer.print_symbol_name(self.sym_name.data)
        if self.body.block.ops:
            printer.print_string(" ")
            printer.print_region(self.body)

name = 'irdl.dialect' class-attribute instance-attribute

sym_name = attr_def(SymbolNameConstraint()) class-attribute instance-attribute

body = region_def('single_block') class-attribute instance-attribute

traits = traits_def(NoTerminator(), SymbolOpInterface(), SymbolTable()) class-attribute instance-attribute

__init__(name: str | StringAttr, body: Region)

Source code in xdsl/dialects/irdl/irdl.py
121
122
123
124
def __init__(self, name: str | StringAttr, body: Region):
    if isinstance(name, str):
        name = StringAttr(name)
    super().__init__(attributes={"sym_name": name}, regions=[body])

parse(parser: Parser) -> DialectOp classmethod

Source code in xdsl/dialects/irdl/irdl.py
126
127
128
129
130
131
132
@classmethod
def parse(cls, parser: Parser) -> DialectOp:
    sym_name = parser.parse_symbol_name()
    region = parser.parse_optional_region()
    if region is None:
        region = Region(Block())
    return DialectOp(sym_name, region)

print(printer: Printer) -> None

Source code in xdsl/dialects/irdl/irdl.py
134
135
136
137
138
139
def print(self, printer: Printer) -> None:
    printer.print_string(" ")
    printer.print_symbol_name(self.sym_name.data)
    if self.body.block.ops:
        printer.print_string(" ")
        printer.print_region(self.body)

TypeOp

Bases: IRDLOperation

A type definition.

Source code in xdsl/dialects/irdl/irdl.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
@irdl_op_definition
class TypeOp(IRDLOperation):
    """A type definition."""

    name = "irdl.type"

    sym_name = attr_def(SymbolNameConstraint())
    body = region_def("single_block")

    traits = traits_def(NoTerminator(), HasParent(DialectOp), SymbolOpInterface())

    def __init__(self, name: str | StringAttr, body: Region):
        if isinstance(name, str):
            name = StringAttr(name)
        super().__init__(attributes={"sym_name": name}, regions=[body])

    @classmethod
    def parse(cls, parser: Parser) -> TypeOp:
        sym_name = parser.parse_symbol_name()
        region = parser.parse_optional_region()
        if region is None:
            region = Region(Block())
        return TypeOp(sym_name, region)

    def print(self, printer: Printer) -> None:
        printer.print_string(" ")
        printer.print_symbol_name(self.sym_name.data)
        if self.body.block.ops:
            printer.print_string(" ")
            printer.print_region(self.body)

    @property
    def qualified_name(self):
        dialect_op = self.parent_op()
        if not isinstance(dialect_op, DialectOp):
            raise ValueError("Tried to get qualified name of an unverified TypeOp")
        return f"{dialect_op.sym_name.data}.{self.sym_name.data}"

name = 'irdl.type' class-attribute instance-attribute

sym_name = attr_def(SymbolNameConstraint()) class-attribute instance-attribute

body = region_def('single_block') class-attribute instance-attribute

traits = traits_def(NoTerminator(), HasParent(DialectOp), SymbolOpInterface()) class-attribute instance-attribute

qualified_name property

__init__(name: str | StringAttr, body: Region)

Source code in xdsl/dialects/irdl/irdl.py
153
154
155
156
def __init__(self, name: str | StringAttr, body: Region):
    if isinstance(name, str):
        name = StringAttr(name)
    super().__init__(attributes={"sym_name": name}, regions=[body])

parse(parser: Parser) -> TypeOp classmethod

Source code in xdsl/dialects/irdl/irdl.py
158
159
160
161
162
163
164
@classmethod
def parse(cls, parser: Parser) -> TypeOp:
    sym_name = parser.parse_symbol_name()
    region = parser.parse_optional_region()
    if region is None:
        region = Region(Block())
    return TypeOp(sym_name, region)

print(printer: Printer) -> None

Source code in xdsl/dialects/irdl/irdl.py
166
167
168
169
170
171
def print(self, printer: Printer) -> None:
    printer.print_string(" ")
    printer.print_symbol_name(self.sym_name.data)
    if self.body.block.ops:
        printer.print_string(" ")
        printer.print_region(self.body)

CPredOp

Bases: IRDLOperation

Constraints an attribute using a C++ predicate

Source code in xdsl/dialects/irdl/irdl.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
@irdl_op_definition
class CPredOp(IRDLOperation):
    """Constraints an attribute using a C++ predicate"""

    name = "irdl.c_pred"

    pred = attr_def(StringAttr)

    output = result_def(AttributeType())

    assembly_format = "$pred attr-dict"

    def __init__(self, pred: str | StringAttr):
        if isinstance(pred, str):
            pred = StringAttr(pred)
        super().__init__(attributes={"pred": pred}, result_types=[AttributeType()])

name = 'irdl.c_pred' class-attribute instance-attribute

pred = attr_def(StringAttr) class-attribute instance-attribute

output = result_def(AttributeType()) class-attribute instance-attribute

assembly_format = '$pred attr-dict' class-attribute instance-attribute

__init__(pred: str | StringAttr)

Source code in xdsl/dialects/irdl/irdl.py
193
194
195
196
def __init__(self, pred: str | StringAttr):
    if isinstance(pred, str):
        pred = StringAttr(pred)
    super().__init__(attributes={"pred": pred}, result_types=[AttributeType()])

AttributeOp

Bases: IRDLOperation

An attribute definition.

Source code in xdsl/dialects/irdl/irdl.py
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
@irdl_op_definition
class AttributeOp(IRDLOperation):
    """An attribute definition."""

    name = "irdl.attribute"

    sym_name = attr_def(SymbolNameConstraint())
    body = region_def("single_block")

    traits = traits_def(NoTerminator(), HasParent(DialectOp), SymbolOpInterface())

    def __init__(self, name: str | StringAttr, body: Region):
        if isinstance(name, str):
            name = StringAttr(name)
        super().__init__(attributes={"sym_name": name}, regions=[body])

    @classmethod
    def parse(cls, parser: Parser) -> AttributeOp:
        sym_name = parser.parse_symbol_name()
        region = parser.parse_optional_region()
        if region is None:
            region = Region(Block())
        return AttributeOp(sym_name, region)

    def print(self, printer: Printer) -> None:
        printer.print_string(" ")
        printer.print_symbol_name(self.sym_name.data)
        if self.body.block.ops:
            printer.print_string(" ")
            printer.print_region(self.body)

    @property
    def qualified_name(self):
        dialect_op = self.parent_op()
        if not isinstance(dialect_op, DialectOp):
            raise ValueError("Tried to get qualified name of an unverified AttributeOp")
        return f"{dialect_op.sym_name.data}.{self.sym_name.data}"

name = 'irdl.attribute' class-attribute instance-attribute

sym_name = attr_def(SymbolNameConstraint()) class-attribute instance-attribute

body = region_def('single_block') class-attribute instance-attribute

traits = traits_def(NoTerminator(), HasParent(DialectOp), SymbolOpInterface()) class-attribute instance-attribute

qualified_name property

__init__(name: str | StringAttr, body: Region)

Source code in xdsl/dialects/irdl/irdl.py
210
211
212
213
def __init__(self, name: str | StringAttr, body: Region):
    if isinstance(name, str):
        name = StringAttr(name)
    super().__init__(attributes={"sym_name": name}, regions=[body])

parse(parser: Parser) -> AttributeOp classmethod

Source code in xdsl/dialects/irdl/irdl.py
215
216
217
218
219
220
221
@classmethod
def parse(cls, parser: Parser) -> AttributeOp:
    sym_name = parser.parse_symbol_name()
    region = parser.parse_optional_region()
    if region is None:
        region = Region(Block())
    return AttributeOp(sym_name, region)

print(printer: Printer) -> None

Source code in xdsl/dialects/irdl/irdl.py
223
224
225
226
227
228
def print(self, printer: Printer) -> None:
    printer.print_string(" ")
    printer.print_symbol_name(self.sym_name.data)
    if self.body.block.ops:
        printer.print_string(" ")
        printer.print_region(self.body)

ParametersOp

Bases: IRDLOperation

An attribute or type parameter definition

Source code in xdsl/dialects/irdl/irdl.py
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
@irdl_op_definition
class ParametersOp(IRDLOperation):
    """An attribute or type parameter definition"""

    name = "irdl.parameters"

    args = var_operand_def(AttributeType)

    names = prop_def(ArrayAttr[StringAttr])

    traits = traits_def(HasParent(TypeOp, AttributeOp))

    def __init__(self, args: Sequence[SSAValue], names: ArrayAttr[StringAttr]):
        super().__init__(operands=[args], properties={"names": names})

    @classmethod
    def parse(cls, parser: Parser) -> ParametersOp:
        args = parser.parse_comma_separated_list(
            parser.Delimiter.PAREN, lambda: _parse_argument(parser)
        )
        return ParametersOp(
            tuple(x[1] for x in args),
            ArrayAttr(x[0] for x in args),
        )

    def print(self, printer: Printer) -> None:
        with printer.in_parens():
            printer.print_list(
                zip(self.names, self.args), lambda x: _print_argument(printer, x)
            )

name = 'irdl.parameters' class-attribute instance-attribute

args = var_operand_def(AttributeType) class-attribute instance-attribute

names = prop_def(ArrayAttr[StringAttr]) class-attribute instance-attribute

traits = traits_def(HasParent(TypeOp, AttributeOp)) class-attribute instance-attribute

__init__(args: Sequence[SSAValue], names: ArrayAttr[StringAttr])

Source code in xdsl/dialects/irdl/irdl.py
265
266
def __init__(self, args: Sequence[SSAValue], names: ArrayAttr[StringAttr]):
    super().__init__(operands=[args], properties={"names": names})

parse(parser: Parser) -> ParametersOp classmethod

Source code in xdsl/dialects/irdl/irdl.py
268
269
270
271
272
273
274
275
276
@classmethod
def parse(cls, parser: Parser) -> ParametersOp:
    args = parser.parse_comma_separated_list(
        parser.Delimiter.PAREN, lambda: _parse_argument(parser)
    )
    return ParametersOp(
        tuple(x[1] for x in args),
        ArrayAttr(x[0] for x in args),
    )

print(printer: Printer) -> None

Source code in xdsl/dialects/irdl/irdl.py
278
279
280
281
282
def print(self, printer: Printer) -> None:
    with printer.in_parens():
        printer.print_list(
            zip(self.names, self.args), lambda x: _print_argument(printer, x)
        )

OperationOp

Bases: IRDLOperation

An operation definition.

Source code in xdsl/dialects/irdl/irdl.py
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
@irdl_op_definition
class OperationOp(IRDLOperation):
    """An operation definition."""

    name = "irdl.operation"

    sym_name = attr_def(SymbolNameConstraint())
    body = region_def("single_block")

    traits = traits_def(NoTerminator(), HasParent(DialectOp), SymbolOpInterface())

    def __init__(self, name: str | StringAttr, body: Region):
        if isinstance(name, str):
            name = StringAttr(name)
        super().__init__(attributes={"sym_name": name}, regions=[body])

    @classmethod
    def parse(cls, parser: Parser) -> OperationOp:
        sym_name = parser.parse_symbol_name()
        region = parser.parse_optional_region()
        if region is None:
            region = Region(Block())
        return OperationOp(sym_name, region)

    def print(self, printer: Printer) -> None:
        printer.print_string(" ")
        printer.print_symbol_name(self.sym_name.data)
        if self.body.block.ops:
            printer.print_string(" ")
            printer.print_region(self.body)

    @property
    def qualified_name(self):
        dialect_op = self.parent_op()
        if not isinstance(dialect_op, DialectOp):
            raise ValueError("Tried to get qualified name of an unverified OperationOp")
        return f"{dialect_op.sym_name.data}.{self.sym_name.data}"

    def get_py_class_name(self) -> str:
        return (
            "".join(
                y[:1].upper() + y[1:]
                for x in self.sym_name.data.split(".")
                for y in x.split("_")
            )
            + "Op"
        )

name = 'irdl.operation' class-attribute instance-attribute

sym_name = attr_def(SymbolNameConstraint()) class-attribute instance-attribute

body = region_def('single_block') class-attribute instance-attribute

traits = traits_def(NoTerminator(), HasParent(DialectOp), SymbolOpInterface()) class-attribute instance-attribute

qualified_name property

__init__(name: str | StringAttr, body: Region)

Source code in xdsl/dialects/irdl/irdl.py
296
297
298
299
def __init__(self, name: str | StringAttr, body: Region):
    if isinstance(name, str):
        name = StringAttr(name)
    super().__init__(attributes={"sym_name": name}, regions=[body])

parse(parser: Parser) -> OperationOp classmethod

Source code in xdsl/dialects/irdl/irdl.py
301
302
303
304
305
306
307
@classmethod
def parse(cls, parser: Parser) -> OperationOp:
    sym_name = parser.parse_symbol_name()
    region = parser.parse_optional_region()
    if region is None:
        region = Region(Block())
    return OperationOp(sym_name, region)

print(printer: Printer) -> None

Source code in xdsl/dialects/irdl/irdl.py
309
310
311
312
313
314
def print(self, printer: Printer) -> None:
    printer.print_string(" ")
    printer.print_symbol_name(self.sym_name.data)
    if self.body.block.ops:
        printer.print_string(" ")
        printer.print_region(self.body)

get_py_class_name() -> str

Source code in xdsl/dialects/irdl/irdl.py
323
324
325
326
327
328
329
330
331
def get_py_class_name(self) -> str:
    return (
        "".join(
            y[:1].upper() + y[1:]
            for x in self.sym_name.data.split(".")
            for y in x.split("_")
        )
        + "Op"
    )

OperandsOp

Bases: IRDLOperation

An operation operand definition.

Source code in xdsl/dialects/irdl/irdl.py
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
@irdl_op_definition
class OperandsOp(IRDLOperation):
    """An operation operand definition."""

    name = "irdl.operands"

    args = var_operand_def(AttributeType)

    variadicity = prop_def(VariadicityArrayAttr)

    names = prop_def(ArrayAttr[StringAttr])

    traits = traits_def(HasParent(OperationOp))

    def __init__(
        self,
        operands: Sequence[SSAValue],
        variadicity: VariadicityArrayAttr,
        names: ArrayAttr[StringAttr],
    ):
        properties = {
            "variadicity": variadicity,
            "names": names,
        }
        super().__init__(operands=[operands], properties=properties)

    @classmethod
    def parse(cls, parser: Parser) -> OperandsOp:
        args = parser.parse_comma_separated_list(
            parser.Delimiter.PAREN, lambda: _parse_argument_with_var(parser)
        )
        return OperandsOp(
            tuple(x[2] for x in args),
            VariadicityArrayAttr(ArrayAttr(x[1] for x in args)),
            ArrayAttr(x[0] for x in args),
        )

    def print(self, printer: Printer) -> None:
        with printer.in_parens():
            printer.print_list(
                zip(self.names, self.variadicity.value, self.args),
                lambda x: _print_argument_with_var(printer, x),
                ", ",
            )

name = 'irdl.operands' class-attribute instance-attribute

args = var_operand_def(AttributeType) class-attribute instance-attribute

variadicity = prop_def(VariadicityArrayAttr) class-attribute instance-attribute

names = prop_def(ArrayAttr[StringAttr]) class-attribute instance-attribute

traits = traits_def(HasParent(OperationOp)) class-attribute instance-attribute

__init__(operands: Sequence[SSAValue], variadicity: VariadicityArrayAttr, names: ArrayAttr[StringAttr])

Source code in xdsl/dialects/irdl/irdl.py
374
375
376
377
378
379
380
381
382
383
384
def __init__(
    self,
    operands: Sequence[SSAValue],
    variadicity: VariadicityArrayAttr,
    names: ArrayAttr[StringAttr],
):
    properties = {
        "variadicity": variadicity,
        "names": names,
    }
    super().__init__(operands=[operands], properties=properties)

parse(parser: Parser) -> OperandsOp classmethod

Source code in xdsl/dialects/irdl/irdl.py
386
387
388
389
390
391
392
393
394
395
@classmethod
def parse(cls, parser: Parser) -> OperandsOp:
    args = parser.parse_comma_separated_list(
        parser.Delimiter.PAREN, lambda: _parse_argument_with_var(parser)
    )
    return OperandsOp(
        tuple(x[2] for x in args),
        VariadicityArrayAttr(ArrayAttr(x[1] for x in args)),
        ArrayAttr(x[0] for x in args),
    )

print(printer: Printer) -> None

Source code in xdsl/dialects/irdl/irdl.py
397
398
399
400
401
402
403
def print(self, printer: Printer) -> None:
    with printer.in_parens():
        printer.print_list(
            zip(self.names, self.variadicity.value, self.args),
            lambda x: _print_argument_with_var(printer, x),
            ", ",
        )

ResultsOp

Bases: IRDLOperation

An operation result definition.

Source code in xdsl/dialects/irdl/irdl.py
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
@irdl_op_definition
class ResultsOp(IRDLOperation):
    """An operation result definition."""

    name = "irdl.results"

    args = var_operand_def(AttributeType)

    variadicity = prop_def(VariadicityArrayAttr)

    names = prop_def(ArrayAttr[StringAttr])

    traits = traits_def(HasParent(OperationOp))

    def __init__(
        self,
        operands: Sequence[SSAValue],
        variadicity: VariadicityArrayAttr,
        names: ArrayAttr[StringAttr],
    ):
        properties = {
            "variadicity": variadicity,
            "names": names,
        }
        super().__init__(operands=[operands], properties=properties)

    @classmethod
    def parse(cls, parser: Parser) -> ResultsOp:
        args = parser.parse_comma_separated_list(
            parser.Delimiter.PAREN, lambda: _parse_argument_with_var(parser)
        )
        return ResultsOp(
            tuple(x[2] for x in args),
            VariadicityArrayAttr(ArrayAttr(x[1] for x in args)),
            ArrayAttr(x[0] for x in args),
        )

    def print(self, printer: Printer) -> None:
        with printer.in_parens():
            printer.print_list(
                zip(self.names, self.variadicity.value, self.args),
                lambda x: _print_argument_with_var(printer, x),
                ", ",
            )

name = 'irdl.results' class-attribute instance-attribute

args = var_operand_def(AttributeType) class-attribute instance-attribute

variadicity = prop_def(VariadicityArrayAttr) class-attribute instance-attribute

names = prop_def(ArrayAttr[StringAttr]) class-attribute instance-attribute

traits = traits_def(HasParent(OperationOp)) class-attribute instance-attribute

__init__(operands: Sequence[SSAValue], variadicity: VariadicityArrayAttr, names: ArrayAttr[StringAttr])

Source code in xdsl/dialects/irdl/irdl.py
420
421
422
423
424
425
426
427
428
429
430
def __init__(
    self,
    operands: Sequence[SSAValue],
    variadicity: VariadicityArrayAttr,
    names: ArrayAttr[StringAttr],
):
    properties = {
        "variadicity": variadicity,
        "names": names,
    }
    super().__init__(operands=[operands], properties=properties)

parse(parser: Parser) -> ResultsOp classmethod

Source code in xdsl/dialects/irdl/irdl.py
432
433
434
435
436
437
438
439
440
441
@classmethod
def parse(cls, parser: Parser) -> ResultsOp:
    args = parser.parse_comma_separated_list(
        parser.Delimiter.PAREN, lambda: _parse_argument_with_var(parser)
    )
    return ResultsOp(
        tuple(x[2] for x in args),
        VariadicityArrayAttr(ArrayAttr(x[1] for x in args)),
        ArrayAttr(x[0] for x in args),
    )

print(printer: Printer) -> None

Source code in xdsl/dialects/irdl/irdl.py
443
444
445
446
447
448
449
def print(self, printer: Printer) -> None:
    with printer.in_parens():
        printer.print_list(
            zip(self.names, self.variadicity.value, self.args),
            lambda x: _print_argument_with_var(printer, x),
            ", ",
        )

AttributesOp

Bases: IRDLOperation

Define the attributes of an operation

Source code in xdsl/dialects/irdl/irdl.py
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
@irdl_op_definition
class AttributesOp(IRDLOperation):
    """Define the attributes of an operation"""

    name = "irdl.attributes"

    attribute_values = var_operand_def(AttributeType())

    attribute_value_names = attr_def(ArrayAttr[StringAttr])

    def __init__(
        self,
        attribute_values: Sequence[SSAValue],
        attribute_value_names: ArrayAttr[StringAttr],
    ):
        super().__init__(
            operands=(attribute_values,),
            attributes={"attribute_value_names": attribute_value_names},
        )

    @classmethod
    def get(cls, attributes: dict[str, SSAValue]) -> AttributesOp:
        operands = tuple(attributes.values())
        names = ArrayAttr(StringAttr(x) for x in attributes.keys())
        return AttributesOp(operands, names)

    @classmethod
    def parse(cls, parser: Parser) -> AttributesOp:
        tuples = parser.parse_optional_comma_separated_list(
            parser.Delimiter.BRACES, lambda: _parse_attribute(parser)
        )
        if tuples is None:
            return AttributesOp.get(dict())
        return AttributesOp.get(dict(tuples))

    def print(self, printer: Printer) -> None:
        if not self.attribute_values:
            return
        with printer.indented():
            printer.print_string(" {\n")
            printer.print_list(
                zip(self.attribute_value_names, self.attribute_values),
                lambda x: _print_attribute(printer, x),
                ",\n",
            )
        printer.print_string("\n}")

    def verify_(self) -> None:
        if len(self.attribute_values) != len(self.attribute_value_names):
            raise VerifyException(
                (
                    "The number of attribute names and their constraints must be the same",
                    f"but got {len(self.attribute_value_names)} and {len(self.attribute_values)} respectively",
                )
            )

name = 'irdl.attributes' class-attribute instance-attribute

attribute_values = var_operand_def(AttributeType()) class-attribute instance-attribute

attribute_value_names = attr_def(ArrayAttr[StringAttr]) class-attribute instance-attribute

__init__(attribute_values: Sequence[SSAValue], attribute_value_names: ArrayAttr[StringAttr])

Source code in xdsl/dialects/irdl/irdl.py
476
477
478
479
480
481
482
483
484
def __init__(
    self,
    attribute_values: Sequence[SSAValue],
    attribute_value_names: ArrayAttr[StringAttr],
):
    super().__init__(
        operands=(attribute_values,),
        attributes={"attribute_value_names": attribute_value_names},
    )

get(attributes: dict[str, SSAValue]) -> AttributesOp classmethod

Source code in xdsl/dialects/irdl/irdl.py
486
487
488
489
490
@classmethod
def get(cls, attributes: dict[str, SSAValue]) -> AttributesOp:
    operands = tuple(attributes.values())
    names = ArrayAttr(StringAttr(x) for x in attributes.keys())
    return AttributesOp(operands, names)

parse(parser: Parser) -> AttributesOp classmethod

Source code in xdsl/dialects/irdl/irdl.py
492
493
494
495
496
497
498
499
@classmethod
def parse(cls, parser: Parser) -> AttributesOp:
    tuples = parser.parse_optional_comma_separated_list(
        parser.Delimiter.BRACES, lambda: _parse_attribute(parser)
    )
    if tuples is None:
        return AttributesOp.get(dict())
    return AttributesOp.get(dict(tuples))

print(printer: Printer) -> None

Source code in xdsl/dialects/irdl/irdl.py
501
502
503
504
505
506
507
508
509
510
511
def print(self, printer: Printer) -> None:
    if not self.attribute_values:
        return
    with printer.indented():
        printer.print_string(" {\n")
        printer.print_list(
            zip(self.attribute_value_names, self.attribute_values),
            lambda x: _print_attribute(printer, x),
            ",\n",
        )
    printer.print_string("\n}")

verify_() -> None

Source code in xdsl/dialects/irdl/irdl.py
513
514
515
516
517
518
519
520
def verify_(self) -> None:
    if len(self.attribute_values) != len(self.attribute_value_names):
        raise VerifyException(
            (
                "The number of attribute names and their constraints must be the same",
                f"but got {len(self.attribute_value_names)} and {len(self.attribute_values)} respectively",
            )
        )

RegionsOp

Bases: IRDLOperation

Define the regions of an operation

Source code in xdsl/dialects/irdl/irdl.py
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
@irdl_op_definition
class RegionsOp(IRDLOperation):
    """Define the regions of an operation"""

    name = "irdl.regions"

    args = var_operand_def(RegionType())

    names = prop_def(ArrayAttr[StringAttr])

    def __init__(self, args: Sequence[SSAValue], names: ArrayAttr[StringAttr]):
        super().__init__(operands=[args], properties={"names": names})

    @classmethod
    def parse(cls, parser: Parser) -> RegionsOp:
        args = parser.parse_comma_separated_list(
            parser.Delimiter.PAREN, lambda: _parse_argument(parser)
        )
        return RegionsOp(
            tuple(x[1] for x in args),
            ArrayAttr(x[0] for x in args),
        )

    def print(self, printer: Printer) -> None:
        with printer.in_parens():
            printer.print_list(
                zip(self.names, self.args), lambda x: _print_argument(printer, x)
            )

name = 'irdl.regions' class-attribute instance-attribute

args = var_operand_def(RegionType()) class-attribute instance-attribute

names = prop_def(ArrayAttr[StringAttr]) class-attribute instance-attribute

__init__(args: Sequence[SSAValue], names: ArrayAttr[StringAttr])

Source code in xdsl/dialects/irdl/irdl.py
533
534
def __init__(self, args: Sequence[SSAValue], names: ArrayAttr[StringAttr]):
    super().__init__(operands=[args], properties={"names": names})

parse(parser: Parser) -> RegionsOp classmethod

Source code in xdsl/dialects/irdl/irdl.py
536
537
538
539
540
541
542
543
544
@classmethod
def parse(cls, parser: Parser) -> RegionsOp:
    args = parser.parse_comma_separated_list(
        parser.Delimiter.PAREN, lambda: _parse_argument(parser)
    )
    return RegionsOp(
        tuple(x[1] for x in args),
        ArrayAttr(x[0] for x in args),
    )

print(printer: Printer) -> None

Source code in xdsl/dialects/irdl/irdl.py
546
547
548
549
550
def print(self, printer: Printer) -> None:
    with printer.in_parens():
        printer.print_list(
            zip(self.names, self.args), lambda x: _print_argument(printer, x)
        )

IsOp

Bases: IRDLOperation

Constraint an attribute/type to be a specific attribute instance.

Source code in xdsl/dialects/irdl/irdl.py
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
@irdl_op_definition
class IsOp(IRDLOperation):
    """Constraint an attribute/type to be a specific attribute instance."""

    name = "irdl.is"

    expected = attr_def()
    output = result_def(AttributeType)

    def __init__(self, expected: Attribute):
        super().__init__(
            attributes={"expected": expected}, result_types=[AttributeType()]
        )

    @classmethod
    def parse(cls, parser: Parser) -> IsOp:
        expected = parser.parse_attribute()
        return IsOp(expected)

    def print(self, printer: Printer) -> None:
        printer.print_string(" ")
        printer.print_attribute(self.expected)

name = 'irdl.is' class-attribute instance-attribute

expected = attr_def() class-attribute instance-attribute

output = result_def(AttributeType) class-attribute instance-attribute

__init__(expected: Attribute)

Source code in xdsl/dialects/irdl/irdl.py
567
568
569
570
def __init__(self, expected: Attribute):
    super().__init__(
        attributes={"expected": expected}, result_types=[AttributeType()]
    )

parse(parser: Parser) -> IsOp classmethod

Source code in xdsl/dialects/irdl/irdl.py
572
573
574
575
@classmethod
def parse(cls, parser: Parser) -> IsOp:
    expected = parser.parse_attribute()
    return IsOp(expected)

print(printer: Printer) -> None

Source code in xdsl/dialects/irdl/irdl.py
577
578
579
def print(self, printer: Printer) -> None:
    printer.print_string(" ")
    printer.print_attribute(self.expected)

BaseOp

Bases: IRDLOperation

Constraint an attribute/type base

Source code in xdsl/dialects/irdl/irdl.py
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
@irdl_op_definition
class BaseOp(IRDLOperation):
    """Constraint an attribute/type base"""

    name = "irdl.base"

    base_ref = opt_attr_def(SymbolRefAttr)
    base_name = opt_attr_def(StringAttr)
    output = result_def(AttributeType)

    def __init__(
        self,
        base: SymbolRefAttr | str | StringAttr,
        attr_dict: Mapping[str, Attribute] | None = None,
    ):
        attr_dict = attr_dict or {}
        if isinstance(base, str):
            base = StringAttr(base)
        if isinstance(base, StringAttr):
            super().__init__(
                attributes={"base_name": base, **attr_dict},
                result_types=[AttributeType()],
            )
        else:
            super().__init__(
                attributes={"base_ref": base, **attr_dict},
                result_types=[AttributeType()],
            )

    @classmethod
    def parse(cls, parser: Parser) -> BaseOp:
        attr = parser.parse_attribute()
        if not isinstance(attr, SymbolRefAttr | StringAttr):
            parser.raise_error("expected symbol reference or string")
        attr_dict = parser.parse_optional_attr_dict()
        return BaseOp(attr, attr_dict)

    def print(self, printer: Printer) -> None:
        if self.base_ref is not None:
            printer.print_string(" ")
            printer.print_attribute(self.base_ref)
        elif self.base_name is not None:
            printer.print_string(" ")
            printer.print_attribute(self.base_name)
        printer.print_op_attributes(self.attributes)

    def verify_(self) -> None:
        if not ((self.base_ref is None) ^ (self.base_name is None)):
            raise VerifyException("expected base as a reference or as a name")

name = 'irdl.base' class-attribute instance-attribute

base_ref = opt_attr_def(SymbolRefAttr) class-attribute instance-attribute

base_name = opt_attr_def(StringAttr) class-attribute instance-attribute

output = result_def(AttributeType) class-attribute instance-attribute

__init__(base: SymbolRefAttr | str | StringAttr, attr_dict: Mapping[str, Attribute] | None = None)

Source code in xdsl/dialects/irdl/irdl.py
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
def __init__(
    self,
    base: SymbolRefAttr | str | StringAttr,
    attr_dict: Mapping[str, Attribute] | None = None,
):
    attr_dict = attr_dict or {}
    if isinstance(base, str):
        base = StringAttr(base)
    if isinstance(base, StringAttr):
        super().__init__(
            attributes={"base_name": base, **attr_dict},
            result_types=[AttributeType()],
        )
    else:
        super().__init__(
            attributes={"base_ref": base, **attr_dict},
            result_types=[AttributeType()],
        )

parse(parser: Parser) -> BaseOp classmethod

Source code in xdsl/dialects/irdl/irdl.py
611
612
613
614
615
616
617
@classmethod
def parse(cls, parser: Parser) -> BaseOp:
    attr = parser.parse_attribute()
    if not isinstance(attr, SymbolRefAttr | StringAttr):
        parser.raise_error("expected symbol reference or string")
    attr_dict = parser.parse_optional_attr_dict()
    return BaseOp(attr, attr_dict)

print(printer: Printer) -> None

Source code in xdsl/dialects/irdl/irdl.py
619
620
621
622
623
624
625
626
def print(self, printer: Printer) -> None:
    if self.base_ref is not None:
        printer.print_string(" ")
        printer.print_attribute(self.base_ref)
    elif self.base_name is not None:
        printer.print_string(" ")
        printer.print_attribute(self.base_name)
    printer.print_op_attributes(self.attributes)

verify_() -> None

Source code in xdsl/dialects/irdl/irdl.py
628
629
630
def verify_(self) -> None:
    if not ((self.base_ref is None) ^ (self.base_name is None)):
        raise VerifyException("expected base as a reference or as a name")

ParametricOp

Bases: IRDLOperation

Constraint an attribute/type base and its parameters

Source code in xdsl/dialects/irdl/irdl.py
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
@irdl_op_definition
class ParametricOp(IRDLOperation):
    """Constraint an attribute/type base and its parameters"""

    name = "irdl.parametric"

    base_type = attr_def(SymbolRefAttr)
    args = var_operand_def(AttributeType)
    output = result_def(AttributeType)

    def __init__(
        self, base_type: str | StringAttr | SymbolRefAttr, args: Sequence[SSAValue]
    ):
        if isinstance(base_type, str | StringAttr):
            base_type = SymbolRefAttr(base_type)
        super().__init__(
            attributes={"base_type": base_type},
            operands=[args],
            result_types=[AttributeType()],
        )

    @classmethod
    def parse(cls, parser: Parser) -> ParametricOp:
        base_type = parser.parse_attribute()
        if not isinstance(base_type, SymbolRefAttr):
            parser.raise_error("expected symbol reference")
        args = parser.parse_comma_separated_list(
            parser.Delimiter.ANGLE, parser.parse_operand
        )
        return ParametricOp(base_type, args)

    def print(self, printer: Printer) -> None:
        printer.print_string(" ")
        printer.print_attribute(self.base_type)
        with printer.in_angle_brackets():
            printer.print_list(self.args, printer.print_ssa_value)

name = 'irdl.parametric' class-attribute instance-attribute

base_type = attr_def(SymbolRefAttr) class-attribute instance-attribute

args = var_operand_def(AttributeType) class-attribute instance-attribute

output = result_def(AttributeType) class-attribute instance-attribute

__init__(base_type: str | StringAttr | SymbolRefAttr, args: Sequence[SSAValue])

Source code in xdsl/dialects/irdl/irdl.py
643
644
645
646
647
648
649
650
651
652
def __init__(
    self, base_type: str | StringAttr | SymbolRefAttr, args: Sequence[SSAValue]
):
    if isinstance(base_type, str | StringAttr):
        base_type = SymbolRefAttr(base_type)
    super().__init__(
        attributes={"base_type": base_type},
        operands=[args],
        result_types=[AttributeType()],
    )

parse(parser: Parser) -> ParametricOp classmethod

Source code in xdsl/dialects/irdl/irdl.py
654
655
656
657
658
659
660
661
662
@classmethod
def parse(cls, parser: Parser) -> ParametricOp:
    base_type = parser.parse_attribute()
    if not isinstance(base_type, SymbolRefAttr):
        parser.raise_error("expected symbol reference")
    args = parser.parse_comma_separated_list(
        parser.Delimiter.ANGLE, parser.parse_operand
    )
    return ParametricOp(base_type, args)

print(printer: Printer) -> None

Source code in xdsl/dialects/irdl/irdl.py
664
665
666
667
668
def print(self, printer: Printer) -> None:
    printer.print_string(" ")
    printer.print_attribute(self.base_type)
    with printer.in_angle_brackets():
        printer.print_list(self.args, printer.print_ssa_value)

RegionOp

Bases: IRDLOperation

Define a region of an operation

Source code in xdsl/dialects/irdl/irdl.py
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
@irdl_op_definition
class RegionOp(IRDLOperation):
    """Define a region of an operation"""

    name = "irdl.region"

    entry_block_args = var_operand_def(AttributeType())

    constrained_arguments = opt_attr_def(UnitAttr)

    number_of_blocks = opt_attr_def(IntegerAttr[I32])

    output = result_def(RegionType())

    assembly_format = (
        "(```(` $entry_block_args $constrained_arguments^ `)`)?"
        "(` ` `with` `size` $number_of_blocks^)? attr-dict"
    )

    def __init__(
        self,
        number_of_blocks: IntegerAttr[I32],
        entry_block_args: Sequence[SSAValue],
        constrained_arguments: UnitAttr | NoneType = None,
    ):
        attributes: dict[str, Attribute] = {
            "number_of_blocks": number_of_blocks,
        }
        if isinstance(constrained_arguments, UnitAttr):
            attributes["constrained_arguments"] = constrained_arguments
        super().__init__(operands=entry_block_args, attributes=attributes)

    def verify_(self) -> None:
        if len(self.entry_block_args) > 0 and not self.constrained_arguments:
            raise VerifyException(
                "constrained_arguments must be set when specifying arguments"
            )

name = 'irdl.region' class-attribute instance-attribute

entry_block_args = var_operand_def(AttributeType()) class-attribute instance-attribute

constrained_arguments = opt_attr_def(UnitAttr) class-attribute instance-attribute

number_of_blocks = opt_attr_def(IntegerAttr[I32]) class-attribute instance-attribute

output = result_def(RegionType()) class-attribute instance-attribute

assembly_format = '(```(` $entry_block_args $constrained_arguments^ `)`)?(` ` `with` `size` $number_of_blocks^)? attr-dict' class-attribute instance-attribute

__init__(number_of_blocks: IntegerAttr[I32], entry_block_args: Sequence[SSAValue], constrained_arguments: UnitAttr | NoneType = None)

Source code in xdsl/dialects/irdl/irdl.py
690
691
692
693
694
695
696
697
698
699
700
701
def __init__(
    self,
    number_of_blocks: IntegerAttr[I32],
    entry_block_args: Sequence[SSAValue],
    constrained_arguments: UnitAttr | NoneType = None,
):
    attributes: dict[str, Attribute] = {
        "number_of_blocks": number_of_blocks,
    }
    if isinstance(constrained_arguments, UnitAttr):
        attributes["constrained_arguments"] = constrained_arguments
    super().__init__(operands=entry_block_args, attributes=attributes)

verify_() -> None

Source code in xdsl/dialects/irdl/irdl.py
703
704
705
706
707
def verify_(self) -> None:
    if len(self.entry_block_args) > 0 and not self.constrained_arguments:
        raise VerifyException(
            "constrained_arguments must be set when specifying arguments"
        )

AnyOp

Bases: IRDLOperation

Constraint an attribute/type to be any attribute/type instance.

Source code in xdsl/dialects/irdl/irdl.py
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
@irdl_op_definition
class AnyOp(IRDLOperation):
    """Constraint an attribute/type to be any attribute/type instance."""

    name = "irdl.any"

    output = result_def(AttributeType)

    def __init__(self):
        super().__init__(result_types=[AttributeType()])

    @classmethod
    def parse(cls, parser: Parser) -> AnyOp:
        return AnyOp()

    def print(self, printer: Printer) -> None:
        pass

name = 'irdl.any' class-attribute instance-attribute

output = result_def(AttributeType) class-attribute instance-attribute

__init__()

Source code in xdsl/dialects/irdl/irdl.py
718
719
def __init__(self):
    super().__init__(result_types=[AttributeType()])

parse(parser: Parser) -> AnyOp classmethod

Source code in xdsl/dialects/irdl/irdl.py
721
722
723
@classmethod
def parse(cls, parser: Parser) -> AnyOp:
    return AnyOp()

print(printer: Printer) -> None

Source code in xdsl/dialects/irdl/irdl.py
725
726
def print(self, printer: Printer) -> None:
    pass

AnyOfOp

Bases: IRDLOperation

Constraint an attribute/type to the union of the provided constraints.

Source code in xdsl/dialects/irdl/irdl.py
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
@irdl_op_definition
class AnyOfOp(IRDLOperation):
    """Constraint an attribute/type to the union of the provided constraints."""

    name = "irdl.any_of"

    args = var_operand_def(AttributeType)
    output = result_def(AttributeType)

    def __init__(self, args: Sequence[SSAValue]):
        super().__init__(operands=[args], result_types=[AttributeType()])

    @classmethod
    def parse(cls, parser: Parser) -> AnyOfOp:
        args = parser.parse_comma_separated_list(
            parser.Delimiter.PAREN, parser.parse_operand
        )
        return AnyOfOp(args)

    def print(self, printer: Printer) -> None:
        with printer.in_parens():
            printer.print_list(self.args, printer.print_ssa_value)

name = 'irdl.any_of' class-attribute instance-attribute

args = var_operand_def(AttributeType) class-attribute instance-attribute

output = result_def(AttributeType) class-attribute instance-attribute

__init__(args: Sequence[SSAValue])

Source code in xdsl/dialects/irdl/irdl.py
738
739
def __init__(self, args: Sequence[SSAValue]):
    super().__init__(operands=[args], result_types=[AttributeType()])

parse(parser: Parser) -> AnyOfOp classmethod

Source code in xdsl/dialects/irdl/irdl.py
741
742
743
744
745
746
@classmethod
def parse(cls, parser: Parser) -> AnyOfOp:
    args = parser.parse_comma_separated_list(
        parser.Delimiter.PAREN, parser.parse_operand
    )
    return AnyOfOp(args)

print(printer: Printer) -> None

Source code in xdsl/dialects/irdl/irdl.py
748
749
750
def print(self, printer: Printer) -> None:
    with printer.in_parens():
        printer.print_list(self.args, printer.print_ssa_value)

AllOfOp

Bases: IRDLOperation

Constraint an attribute/type to the intersection of the provided constraints.

Source code in xdsl/dialects/irdl/irdl.py
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
@irdl_op_definition
class AllOfOp(IRDLOperation):
    """Constraint an attribute/type to the intersection of the provided constraints."""

    name = "irdl.all_of"

    args = var_operand_def(AttributeType)
    output = result_def(AttributeType)

    def __init__(self, args: Sequence[SSAValue]):
        super().__init__(operands=[args], result_types=[AttributeType()])

    @classmethod
    def parse(cls, parser: Parser) -> AllOfOp:
        args = parser.parse_comma_separated_list(
            parser.Delimiter.PAREN, parser.parse_operand
        )
        return AllOfOp(args)

    def print(self, printer: Printer) -> None:
        with printer.in_parens():
            printer.print_list(self.args, printer.print_ssa_value)

name = 'irdl.all_of' class-attribute instance-attribute

args = var_operand_def(AttributeType) class-attribute instance-attribute

output = result_def(AttributeType) class-attribute instance-attribute

__init__(args: Sequence[SSAValue])

Source code in xdsl/dialects/irdl/irdl.py
762
763
def __init__(self, args: Sequence[SSAValue]):
    super().__init__(operands=[args], result_types=[AttributeType()])

parse(parser: Parser) -> AllOfOp classmethod

Source code in xdsl/dialects/irdl/irdl.py
765
766
767
768
769
770
@classmethod
def parse(cls, parser: Parser) -> AllOfOp:
    args = parser.parse_comma_separated_list(
        parser.Delimiter.PAREN, parser.parse_operand
    )
    return AllOfOp(args)

print(printer: Printer) -> None

Source code in xdsl/dialects/irdl/irdl.py
772
773
774
def print(self, printer: Printer) -> None:
    with printer.in_parens():
        printer.print_list(self.args, printer.print_ssa_value)

attr_def(constraint: IRDLAttrConstraint[AttributeInvT] = Attribute, default_value: Attribute | None = None, *, attr_name: str | None = None, default: None = None, resolver: None = None, init: Literal[False] = False) -> AttributeInvT

Defines an attribute of an operation.

Source code in xdsl/irdl/operations.py
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
def attr_def(
    constraint: IRDLAttrConstraint[AttributeInvT] = Attribute,
    default_value: Attribute | None = None,
    *,
    attr_name: str | None = None,
    default: None = None,
    resolver: None = None,
    init: Literal[False] = False,
) -> AttributeInvT:
    """
    Defines an attribute of an operation.
    """
    return cast(
        AttributeInvT,
        _AttributeFieldDef(AttributeDef, constraint, attr_name, default_value),
    )

irdl_attr_definition(cls: TypeAttributeInvT | None = None, *, init: bool = True) -> TypeAttributeInvT | Callable[[TypeAttributeInvT], TypeAttributeInvT]

irdl_attr_definition(
    cls: TypeAttributeInvT, *, init: bool = True
) -> TypeAttributeInvT
irdl_attr_definition(
    *, init: bool = True
) -> Callable[[TypeAttributeInvT], TypeAttributeInvT]
Source code in xdsl/irdl/attributes.py
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
@dataclass_transform(frozen_default=True, field_specifiers=(param_def,))
def irdl_attr_definition(
    cls: TypeAttributeInvT | None = None, *, init: bool = True
) -> TypeAttributeInvT | Callable[[TypeAttributeInvT], TypeAttributeInvT]:
    def decorator(cls: TypeAttributeInvT) -> TypeAttributeInvT:
        check_attr_name(cls)
        if issubclass(cls, ParametrizedAttribute):
            return irdl_param_attr_definition(cls)
        if issubclass(cls, Data):
            # This used to be convoluted
            # But Data is already frozen itself, so any child Attribute still throws on
            # .data!
            return runtime_final(cast(TypeAttributeInvT, cls))
        raise TypeError(
            f"Class {cls.__name__} should either be a subclass of 'Data' or "
            "'ParametrizedAttribute'"
        )

    if cls is None:
        return decorator
    return decorator(cls)

irdl_op_definition(cls: type[IRDLOperationInvT]) -> type[IRDLOperationInvT]

Decorator used on classes to define a new operation definition.

Source code in xdsl/irdl/operations.py
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
def irdl_op_definition(cls: type[IRDLOperationInvT]) -> type[IRDLOperationInvT]:
    """Decorator used on classes to define a new operation definition."""

    assert issubclass(cls, IRDLOperation), (
        f"class {cls.__name__} should be a subclass of IRDLOperation"
    )

    op_def = OpDef.from_pyrdl(cls)
    new_attrs = get_accessors_from_op_def(op_def, getattr(cls, "verify_", None))

    return type.__new__(
        type(cls), cls.__name__, cls.__mro__, {**cls.__dict__, **new_attrs}
    )

opt_attr_def(constraint: IRDLAttrConstraint[AttributeInvT] = Attribute, default_value: Attribute | None = None, *, attr_name: str | None = None, default: None = None, resolver: None = None, init: Literal[False] = False) -> AttributeInvT | None

opt_attr_def(
    constraint: IRDLAttrConstraint[AttributeInvT],
    default_value: Attribute,
    *,
    attr_name: str | None = None,
    default: None = None,
    resolver: None = None,
    init: Literal[False] = False,
) -> AttributeInvT
opt_attr_def(
    constraint: IRDLAttrConstraint[
        AttributeInvT
    ] = Attribute,
    default_value: Attribute | None = None,
    *,
    attr_name: str | None = None,
    default: None = None,
    resolver: None = None,
    init: Literal[False] = False,
) -> AttributeInvT | None

Defines an optional attribute of an operation.

Source code in xdsl/irdl/operations.py
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
def opt_attr_def(
    constraint: IRDLAttrConstraint[AttributeInvT] = Attribute,
    default_value: Attribute | None = None,
    *,
    attr_name: str | None = None,
    default: None = None,
    resolver: None = None,
    init: Literal[False] = False,
) -> AttributeInvT | None:
    """
    Defines an optional attribute of an operation.
    """
    return cast(
        AttributeInvT,
        _AttributeFieldDef(OptAttributeDef, constraint, attr_name, default_value),
    )

prop_def(constraint: IRDLAttrConstraint[AttributeInvT] = Attribute, default_value: Attribute | None = None, *, prop_name: str | None = None, default: None = None, resolver: None = None, init: Literal[False] = False) -> AttributeInvT

Defines a property of an operation.

Source code in xdsl/irdl/operations.py
620
621
622
623
624
625
626
627
628
629
630
631
632
633
def prop_def(
    constraint: IRDLAttrConstraint[AttributeInvT] = Attribute,
    default_value: Attribute | None = None,
    *,
    prop_name: str | None = None,
    default: None = None,
    resolver: None = None,
    init: Literal[False] = False,
) -> AttributeInvT:
    """Defines a property of an operation."""
    return cast(
        AttributeInvT,
        _PropertyFieldDef(PropertyDef, constraint, prop_name, default_value),
    )

region_def(single_block: Literal['single_block'] | None = None, *, entry_args: RangeConstraint | IRDLAttrConstraint = RangeOf(AnyAttr()), default: None = None, resolver: None = None, init: Literal[False] = False) -> Region

Defines a region of an operation.

Source code in xdsl/irdl/operations.py
775
776
777
778
779
780
781
782
783
784
785
786
787
def region_def(
    single_block: Literal["single_block"] | None = None,
    *,
    entry_args: RangeConstraint | IRDLAttrConstraint = RangeOf(AnyAttr()),
    default: None = None,
    resolver: None = None,
    init: Literal[False] = False,
) -> Region:
    """
    Defines a region of an operation.
    """
    cls = RegionDef if single_block is None else SingleBlockRegionDef
    return cast(Region, _RegionFieldDef(cls, entry_args))

result_def(constraint: IRDLAttrConstraint[AttributeInvT] = Attribute, *, default: None = None, resolver: None = None, init: Literal[False] = False) -> OpResult[AttributeInvT]

Defines a result of an operation.

Source code in xdsl/irdl/operations.py
577
578
579
580
581
582
583
584
585
586
587
def result_def(
    constraint: IRDLAttrConstraint[AttributeInvT] = Attribute,
    *,
    default: None = None,
    resolver: None = None,
    init: Literal[False] = False,
) -> OpResult[AttributeInvT]:
    """
    Defines a result of an operation.
    """
    return cast(OpResult[AttributeInvT], _ResultFieldDef(ResultDef, constraint))

traits_def(*traits: OpTrait)

Defines the traits of an operation. Note that traits_def from parent superclasses get included automatically.

Source code in xdsl/irdl/operations.py
859
860
861
862
863
864
def traits_def(*traits: OpTrait):
    """
    Defines the traits of an operation.
    Note that `traits_def` from parent superclasses get included automatically.
    """
    return OpTraits(lambda: traits)

var_operand_def(constraint: RangeConstraint | IRDLAttrConstraint = Attribute, *, default: None = None, resolver: None = None, init: Literal[False] = False) -> VarOperand

Defines a variadic operand of an operation.

Source code in xdsl/irdl/operations.py
749
750
751
752
753
754
755
756
757
758
759
def var_operand_def(
    constraint: RangeConstraint | IRDLAttrConstraint = Attribute,
    *,
    default: None = None,
    resolver: None = None,
    init: Literal[False] = False,
) -> VarOperand:
    """
    Defines a variadic operand of an operation.
    """
    return cast(VarOperand, _OperandFieldDef(VarOperandDef, constraint))