Polymorphic inference: basic support for variadic types by ilevkivskyi · Pull Request #15879 · python/mypy (original) (raw)
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 fromcheckexpr.py
toapplytype.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 withParamSpec
).
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 forParamSpec
) and complementedvisit_callable_type()
inexpandtype.py
. Basically,applytype.py
should have three simple steps: validate substitutions (upper bounds, values, argument kinds etc.); callexpand_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 insideInstance
s,TupleType
s, andCallableType
s. 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 anassert
inCallableType
constructor (similar to how I did this forParamSpec
). - Completing
expand_type()
should be a priority (since it describes basic semantics ofTypeVarLikeType
s). I think I made good progress in this direction. IIUC the only valid substitution for*Ts
areTupleType.items
,*tuple[X, ...]
,Any
, and<nothing>
, so it was not hard. - I propose to only allow
TupleType
(mostly forsemanal.py
, see item below), plainTypeVarTupleType
, and a homogeneoustuple
instances insideUnpackType
. 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 likeA = Tuple[int, *A]
). - I propose to forcefully flatten nested
TupleType
s, there should be no things likeTuple[X1, *Tuple[X2, *Ts, Y2], Y1]
etc after semantic analysis. (Similarly to how we always flattenParameters
forParamSpec
, and how we flatten nested unions inUnionType
constructor). Currently we do the flattening/normalization of tuples inexpand_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.