Skip to content

[Tracking branch] core: (Constraints) introduce constraint simplification#6034

Draft
alexarice wants to merge 3 commits into
mainfrom
alexarice/constraint-simpl
Draft

[Tracking branch] core: (Constraints) introduce constraint simplification#6034
alexarice wants to merge 3 commits into
mainfrom
alexarice/constraint-simpl

Conversation

@alexarice

Copy link
Copy Markdown
Collaborator

Going to try the @superlopuh method of make a big PR and slowly split things off

Proposes to implement basic constraint simplification by adding create methods to various constraints that are drop ins for their __init__ methods, but possibly perform some simplification. These can't just be the __init__ methods as the types don't line up (e.g. an AnyOf.create of one constraint doesn't return an AnyOf). Further uses these create methods in mapping_type_vars and in irdl_to_attr_constraint to force simplification when converting python types.

Simplifications added in this PR:

  • AnyOf 1 constraint returns just that constraint
  • AnyOf of AnyOf is flattened to a single AnyOf (just does one "layer" of flattening at the moment which I think should be sufficient)
  • ParamAttrConstraint of EqAttrConstraints becomes an EqAttrConstraint
  • IntAttrConstraint of a EqIntConstraint(X) becomes a EqAttrConstraint(IntAttr(X))

The last two suffice to make irdl_to_attr_constraint(I32) == EqAttrConstraint(i32)
I have checked that this PR allows @hhkit 's type:

ValType: TypeAlias = (
    I32 | I64 | I128 | Float32Type | Float64Type | FuncRefType | ExternRefType
)

I didn't implement @superlopuh 's suggested AnyOf of ParamAttrConstraint goes to ParamAttrConstaint of AnyOf as it didn't seem immediately necessary but it could be added.

@alexarice alexarice requested review from hhkit and superlopuh May 5, 2026 20:24
@alexarice alexarice self-assigned this May 5, 2026
@codecov

codecov Bot commented May 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.78%. Comparing base (89f0a81) to head (829c89b).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #6034   +/-   ##
=======================================
  Coverage   86.78%   86.78%           
=======================================
  Files         423      423           
  Lines       62638    62653   +15     
  Branches     7185     7186    +1     
=======================================
+ Hits        54361    54375   +14     
- Misses       6703     6705    +2     
+ Partials     1574     1573    -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@superlopuh superlopuh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's interesting that you went with that, I was actually thinking of doing the opposite... In operations we have inits be the nice sugar that does magic, and create basically just populate the fields. In Python it's quite easy to have init return a different type to the one being inited. It's a little dirty but it kind of also makes sense if you want the optimisation to kick in automatically, and to have clients explicitly opt out of the magic if they want to. How do you feel about that?

@alexarice

Copy link
Copy Markdown
Collaborator Author

Ah, I didn't realise that an init could return a different type. I'm not sure how I feel about that

@superlopuh

Copy link
Copy Markdown
Member

If not init, I would expect .get to have magic, but I still think that having init do this would probably result in the least weirdness.

@superlopuh

Copy link
Copy Markdown
Member

@alexarice

Copy link
Copy Markdown
Collaborator Author

I've thought about it a little and having an init that returns a different type feels a bit too cursed for me. Does pyright even recognise that this can happen? I'd be happy to use a different name to create though.

@superlopuh

Copy link
Copy Markdown
Member

Another option is an explicit simplify call like on AffineExpr.

@superlopuh

Copy link
Copy Markdown
Member

Although I would prefer to have a normal form that is automatically simplified, instead of having to re-simplify already simplified subtrees.

@alexarice

Copy link
Copy Markdown
Collaborator Author

The issue with "resimplifying" is I can imagine cases where the "non-normal" form is invalid but the simplified version is

@alexarice

Copy link
Copy Markdown
Collaborator Author

Would you be ok with renaming to get (which we have used in other places for "smart constructors") or would you really prefer the hacky init?

@superlopuh

Copy link
Copy Markdown
Member

My gut tells me that the hacky init is the best way forward. It's really hard to enforce that things are normalised otherwise, it's quite easy for people implementing mapping_type_vars or whatever not to call the simplifying constructor.

@superlopuh

superlopuh commented May 6, 2026

Copy link
Copy Markdown
Member

We are in Python after all, and if I understand correctly if the new method is typed Pyright actually understands it.

from typing import reveal_type


class A:
    def __init__(self, param: str):
        self.param = param

    def __new__(cls, param: str) -> "A|B":
        if param == "b":
            return B(param)
        else:
            return cls(param)


class B(A): ...


a = A("a")
reveal_type(a)
b = A("b")
reveal_type(b)

@alexarice

Copy link
Copy Markdown
Collaborator Author

Cool, I'll try to make it a __new__ method then

@alexarice

Copy link
Copy Markdown
Collaborator Author

I've had a little play around and am now more convinced that this is cursed. All the comments on the stackexchange post you linked say not to do this, and I can't seem to avoid calling irdl_to_attr_constr on everything twice (once in new and once in init), as you don't seem to be able to pass arguments from new to init (maybe you can mutate the arguments, but that's a bit yucky)

@superlopuh

Copy link
Copy Markdown
Member

OK how about this: we change the constraints to not do any smart stuff in the init (like no constraint coersion or whatever) and add .get methods everywhere that do the smart thing. That way there's an understanding that there's a specific thing to call, that always has the same name. I would use .get and not .create since .create is specifically the name we shose for the method that does nothing smart in operations.

@alexarice

Copy link
Copy Markdown
Collaborator Author

I like that

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants