Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Polymorphic inference: basic support for variadic types #15879

Merged
merged 6 commits into from
Aug 18, 2023

Conversation

ilevkivskyi
Copy link
Member

This is the fifth PR in the series started by #15287, and a last one for the foreseeable future. This completes polymorphic inference sufficiently for extensive experimentation, and enabling polymorphic fallback by default.

Remaining items for which I am going to open follow-up issues:

  • Enable --new-type-inference by default (should be done before everything else in this list).
  • Use polymorphic inference during unification.
  • Use polymorphic inference as primary an only mechanism, rather than a fallback if basic inference fails in some way.
  • Move apply_poly() logic from checkexpr.py to applytype.py (this one depends on everything above).
  • Experiment with backtracking in the new solver.
  • Experiment with universal quantification for types other that Callable (btw we already have a hacky support for capturing a generic function in an instance with ParamSpec).

Now some comments on the PR proper. First of all I decided to do some clean-up of TypeVarTuple support, but added only strictly necessary parts of the cleanup here. Everything else will be in follow up PR(s). The polymorphic inference/solver/application is practically trivial here, so here is my view on how I see large-scale structure of TypeVarTuple implementation:

  • There should be no special-casing in applytype.py, so I deleted everything from there (as I did for ParamSpec) and complemented visit_callable_type() in expandtype.py. Basically, applytype.py should have three simple steps: validate substitutions (upper bounds, values, argument kinds etc.); call expand_type(); update callable type variables (currently we just reduce the number, but in future we may also add variables there, see TODO that I added).
  • The only valid positions for a variadic item (a.k.a. UnpackType) are inside Instances, TupleTypes, and CallableTypes. I like how there is an agreement that for callables there should never be a prefix, and instead prefix should be represented with regular positional arguments. I think that ideally we should enforce this with an assert in CallableType constructor (similar to how I did this for ParamSpec).
  • Completing expand_type() should be a priority (since it describes basic semantics of TypeVarLikeTypes). I think I made good progress in this direction. IIUC the only valid substitution for *Ts are TupleType.items, *tuple[X, ...], Any, and <nothing>, so it was not hard.
  • I propose to only allow TupleType (mostly for semanal.py, see item below), plain TypeVarTupleType, and a homogeneous tuple instances inside UnpackType. Supporting unions of those is not specified by the PEP and support will likely be quite tricky to implement. Also I propose to even eagerly expand type aliases to tuples (since there is no point in supporting recursive types like A = Tuple[int, *A]).
  • I propose to forcefully flatten nested TupleTypes, there should be no things like Tuple[X1, *Tuple[X2, *Ts, Y2], Y1] etc after semantic analysis. (Similarly to how we always flatten Parameters for ParamSpec, and how we flatten nested unions in UnionType constructor). Currently we do the flattening/normalization of tuples in expand_type() etc.
  • I suspect build_constraints_for_unpack() may be broken, at least when it was used for tuples and callables it did something wrong in few cases I tested (and there are other symptoms I mentioned in a TODO). I therefore re-implemented logic for callables/tuples using a separate dedicated helper. I will investigate more later.

As I mentioned above I only implemented strictly minimal amount of the above plan to make my tests pass, but still wanted to write this out to see if there are any objections (or maybe I don't understand something). If there are no objections to this plan, I will continue it in separate PR(s). Btw, I like how with this plan we will have clear logical parallels between TypeVarTuple implementation and (recently updated) ParamSpec implementation.

@github-actions

This comment has been minimized.

@github-actions
Copy link
Contributor

Diff from mypy_primer, showing the effect of this PR on open source code:

vision (https://github.com/pytorch/vision): typechecking got 1.32x faster (49.3s -> 37.5s)
(Performance measurements are based on a single noisy sample)

Copy link
Collaborator

@JukkaL JukkaL left a comment

Choose a reason for hiding this comment

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

Thanks, LGTM!

@ilevkivskyi
Copy link
Member Author

OK, I am merging this now, I will open the follow-up issues I mentioned shortly.

@ilevkivskyi ilevkivskyi merged commit b02ddf1 into python:master Aug 18, 2023
17 checks passed
@ilevkivskyi ilevkivskyi deleted the fix-generic-inference-5 branch August 18, 2023 15:18
ilevkivskyi added a commit that referenced this pull request Aug 23, 2023
Fixes #13981
Fixes #15241
Fixes #15495
Fixes #15633
Fixes #15667
Fixes #15897
Fixes #15929

OK, I started following the plan outlined in
#15879. In this PR I focused mostly
on "kinematics". Here are some notes (in random order):
* I decided to normalize `TupleType` and `Instance` items in
`semanal_typeargs.py` (not in the type constructors, like for unions).
It looks like a simpler way to normalize for now. After this, we can
rely on the fact that only non-trivial (more below on what is trivial)
variadic items in a type list is either `*Ts` or `*tuple[X, ...]`. A
single top-level `TupleType` can appear in an unpack only as type of
`*args`.
* Callables turned out to be tricky. There is certain tight coupling
between `FuncDef.type` and `FuncDef.arguments` that makes it hard to
normalize prefix to be expressed as individual arguments _at
definition_. I faced exactly the same problem when I implemented `**`
unpacking for TypedDicts. So we have two choices: either handle prefixes
everywhere, or use normalization helper in relevant code. I propose to
go with the latter (it worked well for `**` unpacking).
* I decided to switch `Unpack` to be disallowed by default in
`typeanal.py`, only very specific potions are allowed now. Although this
required plumbing `allow_unpack` all the way from `semanal.py`,
conceptually it is simple. This is similar to how `ParamSpec` is
handled.
* This PR fixes all currently open crash issues (some intentionally,
some accidentally) plus a bunch of TODOs I found in the tests (but not
all).
* I decided to simplify `TypeAliasExpr` (and made it simple reference to
the `SymbolNode`, like e.g. `TypedDictExpr` and `NamedTupleExpr`). This
is not strictly necessary for this PR, but it makes some parts of it a
bit simpler, and I wanted to do it for long time.

Here is a more detailed plan of what I am leaving for future PRs (in
rough order of priority):
* Close non-crash open issues (it looks like there are only three, and
all seem to be straightforward)
* Handle trivial items in `UnpackType` gracefully. These are `<nothing>`
and `Any` (and also potentially `object`). They can appear e.g. after a
user error. Currently they can cause assert crashes. (Not sure what is
the best way to do this).
* Go over current places where `Unpack` is handled, and verify both
possible variadic items are handled.
* Audit variadic `Instance` constrains and subtyping (the latter is
probably OK, but the former may be broken).
* Audit `Callable` and `Tuple` subtyping for variadic-related edge cases
(constraints seem OK for these).
* Figure out story about `map_instance_to_supertype()` (if no changes
are needed, add tests for subclassing).
* Clear most remaining TODOs.
* Go once more over the large scale picture and check whether we have
some important parts missing (or unhandled interactions between those).
* Verify various "advanced" typing features work well with
`TypeVarTuple`s (and add some support if missing but looks important).
* Enable this feature by default.

I hope to finish these in next few weeks.
Copy link

@davidhalter davidhalter left a comment

Choose a reason for hiding this comment

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

@ilevkivskyi Thanks for this PR. Looking through some of the tests I found this small issue. I'm not sure if you still want to fix it, but I guess it tests relevant behavior. You might also just remove the line.

g: Callable[[T], Foo[int]]
reveal_type(dec(g)) # N: Revealed type is "def (builtins.int) -> __main__.Foo[builtins.int]"
h: Callable[[Unpack[Us]], Foo[int]]
reveal_type(dec(g)) # N: Revealed type is "def (builtins.int) -> __main__.Foo[builtins.int]"

Choose a reason for hiding this comment

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

This line is probably wrong, because already check this two lines above, it should probably be dec(h).

Copy link
Member

Choose a reason for hiding this comment

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

Good catch! Submitted a fix in #16853.

JelleZijlstra added a commit to JelleZijlstra/mypy that referenced this pull request Feb 2, 2024
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.

5 participants