Skip to content

Attributes

attributes

TypeAttributeInvT = TypeVar('TypeAttributeInvT', bound=type[Attribute]) module-attribute

IRDLAttrConstraint: TypeAlias = AttrConstraint[AttributeInvT] | AttributeInvT | type[AttributeInvT] | type[ConstraintConvertible[AttributeInvT]] | ConstraintConvertible[AttributeInvT] | TypeForm[AttributeInvT] module-attribute

Attribute constraints represented using the IRDL python frontend. Attribute constraints can either be: - An instance of AttrConstraint representing a constraint on an attribute. - An instance of Attribute representing an equality constraint on an attribute. - A type representing a specific attribute class. - A TypeForm that can represent both unions and generic attributes. - An instance or subclass of ConstraintConvertible.

GenericData dataclass

Bases: Data[_DataElement], ABC

A Data with type parameters.

Source code in xdsl/irdl/attributes.py
class GenericData(Data[_DataElement], ABC):
    """
    A Data with type parameters.
    """

    @staticmethod
    @abstractmethod
    def constr() -> AttrConstraint:
        """
        Returns a constraint for this subclass.
        Generic arguments are constrained via TypeVarConstraints.
        """

constr() -> AttrConstraint abstractmethod staticmethod

Returns a constraint for this subclass. Generic arguments are constrained via TypeVarConstraints.

Source code in xdsl/irdl/attributes.py
@staticmethod
@abstractmethod
def constr() -> AttrConstraint:
    """
    Returns a constraint for this subclass.
    Generic arguments are constrained via TypeVarConstraints.
    """

ParamDef

Bases: NamedTuple

Contains information about a parameter of a parametrized attribute.

Source code in xdsl/irdl/attributes.py
class ParamDef(NamedTuple):
    """
    Contains information about a parameter of a parametrized attribute.
    """

    constr: AttrConstraint
    converter: Callable[[Any], Attribute] | None = None

constr: AttrConstraint instance-attribute

converter: Callable[[Any], Attribute] | None = None class-attribute instance-attribute

ParamAttrDef dataclass

The IRDL definition of a parametrized attribute.

Source code in xdsl/irdl/attributes.py
@dataclass
class ParamAttrDef:
    """The IRDL definition of a parametrized attribute."""

    name: str
    parameters: list[tuple[str, ParamDef]]

    @staticmethod
    def from_pyrdl(
        pyrdl_def: type[ParametrizedAttribute],
    ) -> "ParamAttrDef":
        # Get the fields from the class and its parents
        clsdict = {
            key: value
            for parent_cls in pyrdl_def.mro()[::-1]
            for key, value in parent_cls.__dict__.items()
            if key not in _PARAMETRIZED_ATTRIBUTE_DICT_KEYS and not _is_dunder(key)
        }

        if "name" not in clsdict:
            raise Exception(
                f"pyrdl attribute definition '{pyrdl_def.__name__}' does not "
                "define the attribute name. The attribute name is defined by "
                "adding a 'name' field."
            )

        name = clsdict["name"]

        # Get type hints
        field_types = {
            field_name: field_type
            for parent_cls in pyrdl_def.mro()[::-1]
            for field_name, field_type in get_annotations(
                parent_cls, eval_str=True
            ).items()
            if field_name not in _IGNORED_PARAM_ATTR_FIELD_TYPES
        }

        # Get assigned values
        field_values = {
            field_name: field_value
            for field_name, field_value in clsdict.items()
            if (
                # Ignore name field
                field_name != "name"
                # Ignore functions
                and not isinstance(
                    field_value,
                    FunctionType | PropertyType | classmethod | staticmethod,
                )
            )
        }

        # The resulting parameters
        parameters: dict[str, ParamDef] = {}

        for field_name, field_type in field_types.items():
            if is_const_classvar(field_name, field_type, PyRDLAttrDefinitionError):
                field_values.pop(field_name, None)
                continue
            try:
                constraint = irdl_to_attr_constraint(field_type, allow_type_var=True)
            except TypeError as e:
                raise PyRDLAttrDefinitionError(
                    f"Invalid field type {field_type} for field name {field_name}."
                ) from e

            converter: Callable[[Any], Attribute] | None = None

            if field_name in field_values:
                value = field_values.pop(field_name)
                if isinstance(value, ParamDef):
                    constraint &= value.constr
                    converter = value.converter
                else:
                    raise PyRDLAttrDefinitionError(
                        f"{field_name} is not a parameter definition."
                    )

            parameters[field_name] = ParamDef(constraint, converter)

        for field_name, value in field_values.items():
            raise PyRDLAttrDefinitionError(
                f"Missing field type for parameter name {field_name}"
            )

        return ParamAttrDef(name, list(parameters.items()))

    def verify(self, attr: ParametrizedAttribute):
        """Verify that `attr` satisfies the invariants."""

        constraint_context = VerificationContext()
        for field, param_def in self.parameters:
            try:
                param_def.constr.verify(getattr(attr, field), constraint_context)
            except VerifyException as e:
                raise VerifyException(
                    f"parameter '{field}' does not verify:\n{e}"
                ) from e

name: str instance-attribute

parameters: list[tuple[str, ParamDef]] instance-attribute

__init__(name: str, parameters: list[tuple[str, ParamDef]]) -> None

from_pyrdl(pyrdl_def: type[ParametrizedAttribute]) -> ParamAttrDef staticmethod

Source code in xdsl/irdl/attributes.py
@staticmethod
def from_pyrdl(
    pyrdl_def: type[ParametrizedAttribute],
) -> "ParamAttrDef":
    # Get the fields from the class and its parents
    clsdict = {
        key: value
        for parent_cls in pyrdl_def.mro()[::-1]
        for key, value in parent_cls.__dict__.items()
        if key not in _PARAMETRIZED_ATTRIBUTE_DICT_KEYS and not _is_dunder(key)
    }

    if "name" not in clsdict:
        raise Exception(
            f"pyrdl attribute definition '{pyrdl_def.__name__}' does not "
            "define the attribute name. The attribute name is defined by "
            "adding a 'name' field."
        )

    name = clsdict["name"]

    # Get type hints
    field_types = {
        field_name: field_type
        for parent_cls in pyrdl_def.mro()[::-1]
        for field_name, field_type in get_annotations(
            parent_cls, eval_str=True
        ).items()
        if field_name not in _IGNORED_PARAM_ATTR_FIELD_TYPES
    }

    # Get assigned values
    field_values = {
        field_name: field_value
        for field_name, field_value in clsdict.items()
        if (
            # Ignore name field
            field_name != "name"
            # Ignore functions
            and not isinstance(
                field_value,
                FunctionType | PropertyType | classmethod | staticmethod,
            )
        )
    }

    # The resulting parameters
    parameters: dict[str, ParamDef] = {}

    for field_name, field_type in field_types.items():
        if is_const_classvar(field_name, field_type, PyRDLAttrDefinitionError):
            field_values.pop(field_name, None)
            continue
        try:
            constraint = irdl_to_attr_constraint(field_type, allow_type_var=True)
        except TypeError as e:
            raise PyRDLAttrDefinitionError(
                f"Invalid field type {field_type} for field name {field_name}."
            ) from e

        converter: Callable[[Any], Attribute] | None = None

        if field_name in field_values:
            value = field_values.pop(field_name)
            if isinstance(value, ParamDef):
                constraint &= value.constr
                converter = value.converter
            else:
                raise PyRDLAttrDefinitionError(
                    f"{field_name} is not a parameter definition."
                )

        parameters[field_name] = ParamDef(constraint, converter)

    for field_name, value in field_values.items():
        raise PyRDLAttrDefinitionError(
            f"Missing field type for parameter name {field_name}"
        )

    return ParamAttrDef(name, list(parameters.items()))

verify(attr: ParametrizedAttribute)

Verify that attr satisfies the invariants.

Source code in xdsl/irdl/attributes.py
def verify(self, attr: ParametrizedAttribute):
    """Verify that `attr` satisfies the invariants."""

    constraint_context = VerificationContext()
    for field, param_def in self.parameters:
        try:
            param_def.constr.verify(getattr(attr, field), constraint_context)
        except VerifyException as e:
            raise VerifyException(
                f"parameter '{field}' does not verify:\n{e}"
            ) from e

ConstraintConvertible

Bases: Generic[AttributeCovT]

Abstract superclass for values that have corresponding Attribute constraints.

Source code in xdsl/irdl/attributes.py
class ConstraintConvertible(Generic[AttributeCovT]):
    """
    Abstract superclass for values that have corresponding Attribute constraints.
    """

    @staticmethod
    @abstractmethod
    def base_constr() -> AttrConstraint[AttributeCovT]:
        """The constraint for this class."""

    @abstractmethod
    def constr(self) -> AttrConstraint[AttributeCovT]:
        """The constraint for this instance."""

base_constr() -> AttrConstraint[AttributeCovT] abstractmethod staticmethod

The constraint for this class.

Source code in xdsl/irdl/attributes.py
@staticmethod
@abstractmethod
def base_constr() -> AttrConstraint[AttributeCovT]:
    """The constraint for this class."""

constr() -> AttrConstraint[AttributeCovT] abstractmethod

The constraint for this instance.

Source code in xdsl/irdl/attributes.py
@abstractmethod
def constr(self) -> AttrConstraint[AttributeCovT]:
    """The constraint for this instance."""

param_def(constraint: IRDLAttrConstraint[AttributeInvT] | None = None, *, converter: Callable[[Any], AttributeInvT] | None = None, init: Literal[True] = True) -> AttributeInvT

Defines a parameter of a ParametrizedAttribute.

Source code in xdsl/irdl/attributes.py
def param_def(
    constraint: "IRDLAttrConstraint[AttributeInvT] | None" = None,
    *,
    converter: Callable[[Any], AttributeInvT] | None = None,
    init: Literal[True] = True,
) -> AttributeInvT:
    """Defines a parameter of a ParametrizedAttribute."""
    return cast(
        AttributeInvT,
        ParamDef(
            AnyAttr()
            if constraint is None
            else irdl_to_attr_constraint(constraint, allow_type_var=True),
            converter,
        ),
    )

check_attr_name(cls: type)

Check that the attribute class has a correct name.

Source code in xdsl/irdl/attributes.py
def check_attr_name(cls: type):
    """Check that the attribute class has a correct name."""
    name = None
    for base in cls.mro():
        if "name" in base.__dict__:
            name = base.__dict__["name"]
            break

    if not isinstance(name, str):
        raise PyRDLAttrDefinitionError(
            f"pyrdl attribute definition '{cls.__name__}' does not "
            "define the attribute name. The attribute name is defined by "
            "adding a 'name' field with a string value."
        )

    dialect_attr_name = name.split(".")
    if len(dialect_attr_name) >= 2:
        return

    if not issubclass(cls, BuiltinAttribute):
        raise PyRDLAttrDefinitionError(
            f"Name '{name}' is not a valid attribute name. It should be of the form "
            "'<dialect>.<name>'."
        )

irdl_param_attr_definition(cls: _PAttrTT) -> _PAttrTT

Decorator used on classes to define a new attribute definition.

Source code in xdsl/irdl/attributes.py
def irdl_param_attr_definition(cls: _PAttrTT) -> _PAttrTT:
    """Decorator used on classes to define a new attribute definition."""

    attr_def = ParamAttrDef.from_pyrdl(cls)

    @classmethod
    def get_irdl_definition(cls: type[ParametrizedAttribute]):
        return attr_def

    cls.get_irdl_definition = get_irdl_definition

    if issubclass(cls, TypedAttribute):
        type_indexes = tuple(
            i for i, (p, _) in enumerate(attr_def.parameters) if p == "type"
        )
        if not type_indexes:
            raise PyRDLAttrDefinitionError(
                f"TypedAttribute {cls.__name__} should have a 'type' parameter."
            )
        type_index = type_indexes[0]

        @classmethod
        def get_type_index(cls: Any) -> int:
            return type_index

        setattr(cls, "get_type_index", get_type_index)

    return runtime_final(dataclass(frozen=True, init=False)(cls))

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
@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_list_to_attr_constraint(pyrdl_constraints: Sequence[IRDLAttrConstraint], *, allow_type_var: bool = False) -> AttrConstraint

Convert a list of PyRDL type annotations to an AttrConstraint. Each list element correspond to a constraint to satisfy. If there is a ConstraintVar annotation, we add the entire constraint to the constraint variable.

Source code in xdsl/irdl/attributes.py
def irdl_list_to_attr_constraint(
    pyrdl_constraints: Sequence[IRDLAttrConstraint],
    *,
    allow_type_var: bool = False,
) -> AttrConstraint:
    """
    Convert a list of PyRDL type annotations to an AttrConstraint.
    Each list element correspond to a constraint to satisfy.
    If there is a `ConstraintVar` annotation, we add the entire constraint to
    the constraint variable.
    """
    constraints = tuple(
        irdl_to_attr_constraint(arg, allow_type_var=allow_type_var)
        for arg in pyrdl_constraints
    )

    if len(constraints) > 1:
        return AllOf(constraints)

    if not constraints:
        return AnyAttr()

    return constraints[0]

irdl_to_attr_constraint(irdl: IRDLAttrConstraint[AttributeInvT], *, allow_type_var: bool = False) -> AttrConstraint[AttributeInvT]

Source code in xdsl/irdl/attributes.py
def irdl_to_attr_constraint(
    irdl: IRDLAttrConstraint[AttributeInvT],
    *,
    allow_type_var: bool = False,
) -> AttrConstraint[AttributeInvT]:
    if isinstance(irdl, AttrConstraint):
        return cast(AttrConstraint[AttributeInvT], irdl)

    if isinstance(irdl, Attribute):
        return cast(AttrConstraint[AttributeInvT], EqAttrConstraint(irdl))

    if isinstance(irdl, ConstraintConvertible):
        value = cast(ConstraintConvertible[AttributeInvT], irdl)
        return value.constr()

    # Annotated case
    # Each argument of the Annotated type corresponds to a constraint to satisfy.
    # If there is a `ConstraintVar` annotation, we add the entire constraint to
    # the constraint variable.
    if get_origin(irdl) == Annotated:
        return cast(
            AttrConstraint[AttributeInvT],
            irdl_list_to_attr_constraint(
                get_args(irdl),
                allow_type_var=allow_type_var,
            ),
        )

    # Attribute class case
    # This is an `AnyAttr`, which does not constrain the attribute.
    if irdl is Attribute:
        return cast(AttrConstraint[AttributeInvT], AnyAttr())

    # Attribute class case
    # This is a coercion for an `BaseAttr`.
    if (
        isclass(irdl)
        and not isinstance(irdl, GenericAlias)
        and issubclass(irdl, Attribute)
    ):
        return cast(AttrConstraint[AttributeInvT], BaseAttr(irdl))

    # Type variable case
    # We take the type variable bound constraint.
    if isinstance(irdl, TypeVar):
        if not allow_type_var:
            raise PyRDLTypeError("TypeVar in unexpected context.")
        if irdl.__bound__ is None:
            raise PyRDLTypeError(
                "Type variables used in IRDL are expected to be bound."
            )
        # We do not allow nested type variables.
        constraint = irdl_to_attr_constraint(irdl.__bound__)
        return cast(AttrConstraint[AttributeInvT], TypeVarConstraint(irdl, constraint))

    origin = get_origin(irdl)

    # Generic case
    if isclass(origin) and (
        issubclass(origin, GenericData)
        or (issubclass(origin, ParametrizedAttribute) and issubclass(origin, Generic))
    ):
        if issubclass(origin, GenericData):
            base_constr = origin.constr()
        else:
            base_constr = ParamAttrConstraint(
                origin,
                tuple(
                    param.constr for _, param in origin.get_irdl_definition().parameters
                ),
            )

        type_vars = get_type_var_from_generic_class(cast(type, origin))
        args: tuple[IRDLAttrConstraint | int | TypeForm[int], ...] = get_args(irdl)

        # Here, we use `__default__` to check for default values, instead of `has_default`.
        # This is so users that are not using typing-extensions can still use it, as well
        # as marimo notebooks, which seems to replace `typing-extensions` with `typing`.
        if len(args) < len(type_vars) and all(
            hasattr(arg, "__default__") for arg in type_vars[len(args) :]
        ):
            # Check for default values
            args += tuple(arg.__default__ for arg in type_vars[len(args) :])

        # Check that we have the right number of parameters
        if len(args) != len(type_vars):
            raise PyRDLTypeError(
                f"{origin.name} expects {len(type_vars)} parameters, got {len(args)}."
            )

        type_var_mapping = {
            type_var: get_constraint(arg, allow_type_var=allow_type_var)
            for type_var, arg in zip(type_vars, args)
        }

        return cast(
            AttrConstraint[AttributeInvT],
            base_constr.mapping_type_vars(type_var_mapping),
        )

    # Union case
    # This is a coercion for an `AnyOf` constraint.
    if origin == UnionType or origin == Union:
        constraints: list[AttrConstraint] = []
        for arg in get_args(irdl):
            constraints.append(
                irdl_to_attr_constraint(
                    arg,
                    allow_type_var=allow_type_var,
                )
            )
        return cast(AttrConstraint[AttributeInvT], AnyOf.get(*constraints))

    if isclass(irdl) and issubclass(irdl, ConstraintConvertible):
        attr_data = cast(type[ConstraintConvertible[AttributeInvT]], irdl)
        return attr_data.base_constr()

    if origin is Literal:
        literal_args = get_args(irdl)
        if any(isinstance(value, bool) for value in literal_args):
            raise PyRDLTypeError("Boolean literals are currently not supported")
        return AnyOf.get(*literal_args)

    # Better error messages for missing GenericData in Data definitions
    if isclass(origin) and issubclass(origin, Data):
        raise PyRDLTypeError(
            f"Generic `Data` type '{origin.name}' cannot be converted to "
            "an attribute constraint. Consider making it inherit from "
            "`GenericData` instead of `Data`."
        )

    raise PyRDLTypeError(f"Unexpected irdl constraint: {irdl}")

base(irdl: type[AttributeInvT]) -> AttrConstraint[AttributeInvT]

Converts an attribute type into the equivalent constraint, detecting generic parameters if present.

Source code in xdsl/irdl/attributes.py
@deprecated("Use constraint `.get` constructors or irdl_to_attr_constraint directly")
def base(irdl: type[AttributeInvT]) -> AttrConstraint[AttributeInvT]:
    """
    Converts an attribute type into the equivalent constraint, detecting generic
    parameters if present.
    """
    return irdl_to_attr_constraint(irdl)

eq(irdl: AttributeInvT) -> AttrConstraint[AttributeInvT]

Converts an attribute instance into the equivalent constraint.

Source code in xdsl/irdl/attributes.py
@deprecated("Use constraint `.get` constructors or irdl_to_attr_constraint directly")
def eq(irdl: AttributeInvT) -> AttrConstraint[AttributeInvT]:
    """
    Converts an attribute instance into the equivalent constraint.
    """
    return irdl_to_attr_constraint(irdl)

get_optional_int_constraint(arg: Any) -> IntConstraint | None

If the input is an int or an int type, return the corresponding constraint, otherwise return None. Boolean literals are currently unsupported and raise PyRDLTypeError.

Source code in xdsl/irdl/attributes.py
def get_optional_int_constraint(arg: Any) -> IntConstraint | None:
    """
    If the input is an int or an int type, return the corresponding constraint,
    otherwise return None.
    Boolean literals are currently unsupported and raise `PyRDLTypeError`.
    """
    if isinstance(arg, bool):
        raise PyRDLTypeError("Boolean literals are currently not supported")

    if isinstance(arg, int):
        return EqIntConstraint(arg)

    if isclass(arg) and issubclass(arg, int):
        return AnyInt()

    if get_origin(arg) is Literal:
        literal_args = get_args(arg)
        if any(isinstance(value, bool) for value in literal_args):
            raise PyRDLTypeError("Boolean literals are currently not supported")

        if all(isinstance(literal_arg, int) for literal_arg in literal_args):
            return IntSetConstraint.get(*literal_args)

    if get_origin(arg) in (Union, UnionType):
        # Check every member before returning None for a non-integer constraint.
        constraints = [get_optional_int_constraint(member) for member in get_args(arg)]
        ints: set[int] = set()
        for constraint in constraints:
            match constraint:
                case EqIntConstraint():
                    ints.add(constraint.value)
                case IntSetConstraint():
                    ints.update(constraint.values)
                case _:
                    return None
        return IntSetConstraint.get(*ints)

    if (
        isinstance(arg, TypeVar)
        and (base := get_optional_int_constraint(arg.__bound__)) is not None
    ):
        return IntTypeVarConstraint(arg, base)

get_int_constraint(arg: int | TypeForm[int]) -> IntConstraint

If the input is an int or an int type, return the corresponding constraint, otherwise raise PyRDLTypeError.

Source code in xdsl/irdl/attributes.py
def get_int_constraint(arg: "int | TypeForm[int]") -> IntConstraint:
    """
    If the input is an int or an int type, return the corresponding constraint,
    otherwise raise `PyRDLTypeError`.
    """
    if (ic := get_optional_int_constraint(arg)) is not None:
        return ic

    raise PyRDLTypeError(f"Unexpected int type: {arg}")

range_constr_coercion(attr: IRDLAttrConstraint[AttributeCovT] | RangeConstraint[AttributeCovT]) -> RangeConstraint[AttributeCovT]

Source code in xdsl/irdl/attributes.py
def range_constr_coercion(
    attr: (IRDLAttrConstraint[AttributeCovT] | RangeConstraint[AttributeCovT]),
) -> RangeConstraint[AttributeCovT]:
    if isinstance(attr, RangeConstraint):
        return cast(RangeConstraint[AttributeCovT], attr)
    return RangeOf.get(attr)

single_range_constr_coercion(attr: IRDLAttrConstraint[AttributeCovT]) -> RangeConstraint[AttributeCovT]

Source code in xdsl/irdl/attributes.py
def single_range_constr_coercion(
    attr: IRDLAttrConstraint[AttributeCovT],
) -> RangeConstraint[AttributeCovT]:
    return RangeOf.get(attr).of_length(1)

get_constraint(arg: IRDLAttrConstraint | int | TypeForm[int], *, allow_type_var: bool = False) -> AttrConstraint | IntConstraint

get_constraint(
    arg: int | TypeForm[int],
    *,
    allow_type_var: bool = False,
) -> IntConstraint
get_constraint(
    arg: IRDLAttrConstraint, *, allow_type_var: bool = False
) -> AttrConstraint
get_constraint(
    arg: IRDLAttrConstraint | int | TypeForm[int],
    *,
    allow_type_var: bool = False,
) -> AttrConstraint | IntConstraint

Converts the input expression, constraint, or type to the corresponding constraint.

Source code in xdsl/irdl/attributes.py
def get_constraint(
    arg: "IRDLAttrConstraint | int | TypeForm[int]",
    *,
    allow_type_var: bool = False,
) -> AttrConstraint | IntConstraint:
    """
    Converts the input expression, constraint, or type to the corresponding constraint.
    """
    if (ic := get_optional_int_constraint(arg)) is not None:
        return ic

    return irdl_to_attr_constraint(arg, allow_type_var=allow_type_var)  # pyright: ignore[reportArgumentType]