Skip to content

Conversation

ilevkivskyi
Copy link
Member

@ilevkivskyi ilevkivskyi commented Jul 26, 2025

This gives almost 4% performance boost (Python 3.12, compiled). Note there is an old bug in type_object_type(), we treat not ready types as Any without deferring, I disable caching in this case.

Unfortunately, using this in fine-grained mode is tricky, essentially I have three options:

  • Use some horrible hacks to invalidate cache when needed
  • Add (expensive) class target dependency from __init__/__new__
  • Only allow constructor caching during initial load, but disable it in fine-grained increments

I decided to choose the last option. I think it has the best balance complexity/benefits.

This comment has been minimized.

@ilevkivskyi
Copy link
Member Author

Non-trivial results in mypy_primer are surprising. I know there is a bug in type_object_type() that when it is called before the constructor is processed we may get wrong result (e.g. because it is decorated) instead of deferring the current node. But I thought the way I implemented caching should not be affected by this bug. I will take a look today or tomorrow.

This comment has been minimized.

@ilevkivskyi
Copy link
Member Author

Although remaining errors are kind of correct, they are sill weird. It looks like they appear because previously

class C @no_type_check def __new__(cls): ...

resulted in Any as the type of C class object. But somehow it is now C in case of an import cycle. FWIW I can't reproduce this in a simple test.

@ilevkivskyi
Copy link
Member Author

OK, so it turns out there was an existing bug (inconsistency) with @no_type_check, where the decorated function was not Any for a brief period between semantic analysis and type checking. I simply fix that.

While looking at this I found second place where we don't handle not-ready types properly (decorated overloads), again I simply don't cache in this case, since a proper fix may be non-trivial (I also update the relevant TODO item). I update the PR description to mention this.

This comment has been minimized.

Copy link
Collaborator

@sterliakov sterliakov left a comment

Choose a reason for hiding this comment

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

LG! The idea makes sense, and the bench on GHA shows 2.6-2.9% win for selfcheck. Nice! I'm a bit worried that it uncovered at least three semi-unrelated issues - can there be others, not revealed by primer runs? It may be too much fun to debug caching-related bugs later...

mypy/typeops.py Outdated
else:
builtins_type = named_type("builtins.type")

fallback = info.metaclass_type or builtins_type
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we keep it lazy (only call named_type if info.metaclass_type is None)?

@ilevkivskyi
Copy link
Member Author

@sterliakov Of course there can be more. But those are all real bugs, we would need to fix them sooner or later anyway. In an ideal bug-free world, we should be able to always cache.

Copy link
Contributor

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

@ilevkivskyi ilevkivskyi merged commit a8d2f13 into python:master Jul 27, 2025
20 checks passed
@ilevkivskyi ilevkivskyi deleted the cache-type-obj-type branch July 27, 2025 21:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants