Skip to content

Prevent quadratic runtime with generic overloads - #22393

Open
hauntsaninja wants to merge 3 commits into
python:masterfrom
hauntsaninja:overlimit2
Open

hauntsaninja wants to merge 3 commits into
python:masterfrom
hauntsaninja:overlimit2

Conversation

@hauntsaninja

Copy link
Copy Markdown
Collaborator

Fixes #20439

@github-actions

This comment has been minimized.

@hauntsaninja
hauntsaninja marked this pull request as ready for review November 7, 2026 23:04
@github-actions

github-actions Bot commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

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

@ilevkivskyi ilevkivskyi 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.

Thanks! General direction looks good, but have few comments.

Comment thread mypy/checker.py

max_overload_count = 50
max_expanded_count = 200
if len(defn.items) >= max_overload_count:

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.

Do we need to keep two separate limits? I think we should only have the expanded limit (and since type variables with values are rare, this would effectively act as total limit in most cases).

Comment thread mypy/checker.py
sig_expanded = expand_callable_variants(detach_callable(sig, type_vars))
expanded_signatures.append(sig_expanded)
expanded_count += len(sig_expanded)
del item

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.

Why is this needed? If this collides with other loop variable below, I would just use a different name.

Comment thread mypy/checker.py
signatures: list[CallableType | None] = []
expanded_signatures: list[list[CallableType] | None] = []
expanded_count = 0
with state.strict_optional_set(True):

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.

Do you really need this context manger here? I think code here can never reach to any is_subtype() calls.

Comment thread mypy/checker.py
ctx_item = defn.items[i + j + 1]
assert isinstance(ctx_item, Decorator)
self.msg.overloaded_signature_will_never_match(i + 1, i + j + 2, ctx_item.func)
elif not is_descriptor_get and expanded_count < max_expanded_count:

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 looks like unlike for the "old" limit (that does a sliding window) here we skip the check completely. This means that expanding the callables was wasteful. I think we then should simply pre-calculate the number of expanded items (as a sum of products of number of type variable values for each item), and only do the actual expansion if we are under the limit.

This will be more efficient, and will allow to simplify the logic (e.g. you will not need to accept callable or list of callables in is_unsafe_overlapping_overload_signatures()).

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.

extremely slow type checking for constrained TypeVars and overloads on generic classes

2 participants