Skip to content

Lenient handling of *tuple[Any, ...] (part 3) - #22014

Open
ilevkivskyi wants to merge 3 commits into
python:masterfrom
ilevkivskyi:fix-any-tvt-3
Open

ilevkivskyi wants to merge 3 commits into
python:masterfrom
ilevkivskyi:fix-any-tvt-3

Conversation

@ilevkivskyi

Copy link
Copy Markdown
Member

Fixes #20771 (it already worked, but I am now adding a regression test)
Fixes #20651

This handles various edge cases related to inference with variadic unpacks (including Any). Interestingly, after properly handling some missing edge cases, I realized that the variadic case can be "merged in the main branch" of the inference logic, so that the final diff is very simple.

cc @JukkaL

@github-actions

This comment has been minimized.

reveal_type(get_first(X())) # N: Revealed type is "builtins.int"
[builtins fixtures/tuple.pyi]

[case testInferAgainstVariadicPrefixSuffix]

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.

This case isn't covered by tests and the behavior looks wrong:

from typing import Any

def f[T1, T2, *Ts](x: tuple[T1, T2, *Ts]) -> tuple[T1, T2, tuple[*Ts]]: ...

a: tuple[str, *tuple[Any, ...], bytes, float]
reveal_type(f(a))  # tuple[str, *tuple[Any, ...], tuple[bytes, float]]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This was already broken before (but in a different way). Most likely this should be an easy fix with the new logic.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Oh wow, this uncovered (a quite embarrassing) bug in split_with_prefix_and_suffix(), which is like ultra-foundation in the whole TypeVarTuple story. I am surprised it didn't cause other problems so far.

To give some more context, initially I prohibited partial overlap for variadic unpacks:

class C[T1, *Ts, T2]: ...
c: C[str, bool, *tuple[int, ...]]

at instance creation level. But relatively late in the process I decided to lift this restriction (and keep it only for the cases where partial overlap is genuinely ambiguous, e.g. where actual type arguments have *Us). The reason is that, unlike in "type dynamics" (i.e. is_subtype()), in "type kinematics" (i.e. expand_type()) there is no ambiguity w.r.t. what *tuple[X, ...] actually means (i.e. there is no strict vs lenient story).

However, now we need to be more careful in situations where split_with_prefix_and_suffix() is called with synthetic/ad-hoc type lists, i.e. those not directly coming from instance arguments. A more prudent way would be to either add an assert or make return type of split_with_prefix_and_suffix() optional. I however don't like either. I will probably just spot-check the most important call sites (and update if needed).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

OK, I think I handled everything except meet/join (I will handle those in a separate PR to limit the scope):

  • Fix a benign off-by-2 error in constraints.py with a test.
  • Change in checkpattern.py should be a pure refactoring (as it was hard to reason about).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Actually it looks like meet/join do not need anything (i.e. I don't see any bugs, and no places where Any requires special handling now). There are still couple TODOs (because we infer object/Never where we can infer something more precise), but those are quite tedious, so I would do this if/when someone asks about it.

@github-actions

Copy link
Copy Markdown
Contributor

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

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

Labels

None yet

Projects

None yet

2 participants