Skip to content

Refactor(Optimizer): improve typing coverage of optimizer modules#7446

Open
OutSquareCapital wants to merge 21 commits intotobymao:mainfrom
OutSquareCapital:optimizer-annotations
Open

Refactor(Optimizer): improve typing coverage of optimizer modules#7446
OutSquareCapital wants to merge 21 commits intotobymao:mainfrom
OutSquareCapital:optimizer-annotations

Conversation

@OutSquareCapital
Copy link
Copy Markdown
Contributor

Details

WIP

This is a work in progress. But since those files are very untyped ATM, it's better to split up in various passes. This is the first one, focusing on public level and simplify module.

datetime import

I changed the import datetime to from datetime import x, y, z. It's more concise when the datetime object are needed, but most importantly it's much less confusing.
Fault is on Python for sure for the API design, but since datetime is both a module and an object, and datetime.date can represent both a method and an object, IMO it's preferrable.

Partial typing

I skipped various methods/functions (or partially typed them like simplify_literals).
This is intentionnal. ATM there's various untypable behaviors, like "unsafe" non-none checks on Expr properties/args.
This would require changing the actual runtime behavior, which is out-of-scope here.


@annotate_types_on_change
def simplify_literals(self, expression, root=True):
def simplify_literals(self, expression, root: bool = True):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should we type expression and include a return type as well here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It wasn't really possible with the code as it was, but then I remembered that TypeIs is perfectly usable with typing_extensions , so now yes, fully typed!
This also open the door for lots of improvements at other places.
FYI, TypeIs is what ppl often think TypeGuard do

Comment on lines +962 to +978
def absorb_and_eliminate(self, expression, root=True):
def absorb_and_eliminate(self, expression: object, root: bool = True):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why object? Should we add a return type?

Copy link
Copy Markdown
Contributor Author

@OutSquareCapital OutSquareCapital Apr 3, 2026

Choose a reason for hiding this comment

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

My rule is to make the input as wide as possible and the output as narrow as possible. mypy won't let pass object as soon as we do something more specific than the likes of __eq__(), __str__(), or __dict__(), so it's permissive but strict at the same time (unlike Any for example). Narrowing the type would be incorrect as this would be an arbitrary decision. I added object as a return type tho.

Comment on lines +925 to +941
def uniq_sort(self, expression, root=True):
def uniq_sort(self, expression: object, root: bool = True):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ditto.

Copy link
Copy Markdown
Contributor Author

@OutSquareCapital OutSquareCapital Apr 3, 2026

Choose a reason for hiding this comment

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

Added object return type. Same logic as remove_complements

Comment on lines +908 to +924
def remove_complements(self, expression, root=True):
def remove_complements(self, expression: object, root: bool = True):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ditto.

Copy link
Copy Markdown
Contributor Author

@OutSquareCapital OutSquareCapital Apr 3, 2026

Choose a reason for hiding this comment

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

Added object return type. Since we can take any object and either return an Expr or the object itself.

def _simplify_comparison(self, expression, left, right, or_=False):
def _simplify_comparison(
self, expression: exp.Expr, left: exp.Expr, right: exp.Expr, or_: bool = False
):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ditto, re: return type.

Copy link
Copy Markdown
Contributor Author

@OutSquareCapital OutSquareCapital Apr 3, 2026

Choose a reason for hiding this comment

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

My bad, this was a miss on my part. Fixed to exp.Expr | None.

Comment on lines +798 to +799
def simplify_connectors(self, expression: exp.Expr, root: bool = True):
def _simplify_connectors(expression: exp.Expr, left: exp.Expr, right: exp.Expr):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

And here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Could be narrowed to -> exp.Expr thanks to the _simplify_comparison fix



def eval_boolean(
expression: object, a: SupportsComparison, b: SupportsComparison
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Same for object.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

here is a good example to explain why it's unecessary to use anything else than object:
each path will explicitely check the instance type, so it will always narrow from object to the specific check. Or else, we return None.
So really it can be any object as an input.

@OutSquareCapital
Copy link
Copy Markdown
Contributor Author

OutSquareCapital commented Apr 3, 2026

To clarify: the goal is to gradually add types, since optimizers as a whole is a lot more untyped than the rest of the codebase.
As such, this PR scope is:

  • fully type the public API (exported via __init__)
  • Fully type "easy" functions in the simplify module
  • Partially annotate complex blocks with hard-to-type structure like _simplify_comparison in the simplify module

The latter will be revisited in the future, hopefully with the help of more context.
This is precisely why object is a good fit most of the time: mypy don't check the body of untyped functions, and as such we can expect to be more precise as the typing coverage progress and mypy flag object has no attribute "x" problems

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