[Guido]
> 1. Some edge case seems to be that if *tuple[...] is involved on either side we will never simplify.
Alright, let me think this through with some examples to get my head round it.
It would prohibit the following difficult case:
class C(Generic[*Ts]): ...
Alias = C[T, *Ts]
Alias[*tuple[int, ...]] # Does not simplify; stays C[T, *Ts][*tuple[int, ...]]
That seems pretty reasonable. It would also prohibit these other relatively simple cases, but I guess that's fine:
Alias = C[*Ts]
Alias[*tuple[int, ...]] # Does not simplify; stays C[*Ts][*tuple[int, ...]]
Alias = C[T, *tuple[int, ...]]
Alias[str] # Does not simplify; stays C[T, *tuple[int, ...]][str]
> Or perhaps a better rule is that *tuple[...] is never simplified away (but fixed items before and after it may be).
Is this to say that we effectively prohibit binding *tuple[...] to anything? If we can simplify without binding *tuple[...] to anything, then we do simplify, but otherwise, we don't simplify? So under this rule, the following WOULD work?
Alias = C[T, *tuple[int, ...]]
Alias[str] # Simplifies to C[str, *tuple[int, ...]], because we didn't have to bind *tuple[int, ...] to do it
> 2. Another edge case is that if neither side has any starred items we will always simplify (since this is the existing behavior in 3.10). This may raise an error if the number of subscripts on the right does not match the number of parameters on the left.
Alright, so this is business as usual.
> 3. If there's a single *Ts on the left but not on the right, we should be able to simplify, which again may raise an error if there are not enough values on the right, but if there are more than enough, the excess will be consumed by *Ts (in fact that's the only way *Ts is fed).
So then:
class C(Generic[*Ts]): ...
Alias = C[T, *Ts]
Alias[()] # Raises error
Alias[int] # Simplifies to C[int, *Ts]
Alias[int, str] # Simplifies to C[int, str]
Alias[int, str, bool] # Simplifies to C[int, str, bool]
Yup, seems straightforward.
> 4. If there's a *Ts on the right but not on the left, we should _not_ simplify, since whatever we have on the left serves as a constraint for *Ts.
Ok, so this is about the following situations:
class C(Generic[*Ts]): ...
Alias = C[T1, T2]
Alias[*Ts] # Does not simplify; stays C[T1, T2][*Ts]
Yikes - in fact, this is actually super hairy; I hadn't thought about this edge case at all in the PEP.
Agreed that it seems reasonable not to simplify here.
> E.g. tuple[int, int][*Ts] constrains *Ts to being (int, int).
Was that a typo? Surely tuple[int, int][*Ts] isn't valid - since tuple[int, int] doesn't have any free parameters?
> 5. If there's exactly one *Ts on the left and one on the right, we _might__ be able to simplify if the prefix and suffix of the __parameters__ match the prefix and suffix of the subscript on the right. E.g. C[int, T, *Ts, float][str, *Ts] can be simplified to C[int, str, *Ts, float]. OTOH C[int, T, *Ts, float][*Ts] cannot be simplified -- but we cannot flag it as an error either. Note that __parameters__ in this example is (T, Ts); we have to assume that typevartuples in __parameters__ are always used as *Ts (since the PEP recognizes no valid unstarred uses of Ts).
Ok, this also makes sense.
---
Still, though, doesn't the point that Serhiy brought up about __origin__, __parameters__ and __args__ still apply? In cases where we *don't* simplify, there'd still be the issue of what we'd set these things to be.
This evening I'll also revisit the PRs adding tests for substitution to try and make them a comprehensive reference as to what's currently possible. |