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

(🎁) Convert code to utilize pep 695 #4617

Open
KotlinIsland opened this issue May 24, 2023 · 6 comments
Open

(🎁) Convert code to utilize pep 695 #4617

KotlinIsland opened this issue May 24, 2023 · 6 comments
Labels
accepted Ready for implementation help wanted Contributions especially welcome python312 Related to Python 3.12 rule Implementing or modifying a lint rule

Comments

@KotlinIsland
Copy link
Contributor

KotlinIsland commented May 24, 2023

input:

from typing import TypeVar, TypeAlias, Generic

T = TypeVar("T", bound=float)
Foo: TypeAlias = str | T | None

class A(Generic[T]):
    ...

def f(t: T):
    ...

output:

type Foo[T: float] = str | T | None

class A[T: float]:
    ...

def f[T: float](t: T):
    ...

And also ParamSpec and TypeVarTuple.

@KotlinIsland KotlinIsland changed the title (🎁) Convert code to utalize pep 695 (🎁) Convert code to utilize pep 695 May 24, 2023
@charliermarsh
Copy link
Member

Yes, I'd love to support this. (It will take some time, need to start by supporting it in the parser and the semantic model.)

@charliermarsh charliermarsh added the rule Implementing or modifying a lint rule label May 24, 2023
@zanieb zanieb added accepted Ready for implementation help wanted Contributions especially welcome labels Aug 2, 2023
@zanieb
Copy link
Member

zanieb commented Aug 2, 2023

This is ready to be worked on! I've started in #6289 but there is much more to do :)

zanieb added a commit that referenced this issue Aug 3, 2023
Adds rule to convert type aliases defined with annotations i.e. `x:
TypeAlias = int` to the new PEP-695 syntax e.g. `type x = int`.

Does not support using new generic syntax for type variables, will be
addressed in a follow-up.
Added as part of pyupgrade — ~the code 100 as chosen to avoid collision
with real pyupgrade codes~.

Part of #4617 
Builds on #5062
zanieb added a commit that referenced this issue Aug 7, 2023
Extends #6289 to support moving type variable usage in type aliases to
use PEP-695.

Does not remove the possibly unused type variable declaration.
Presumably this is handled by other rules, but is not working for me.

Does not handle type variables with bounds or variance declarations yet.

Part of #4617
durumu pushed a commit to durumu/ruff that referenced this issue Aug 12, 2023
Extends astral-sh#6289 to support moving type variable usage in type aliases to
use PEP-695.

Does not remove the possibly unused type variable declaration.
Presumably this is handled by other rules, but is not working for me.

Does not handle type variables with bounds or variance declarations yet.

Part of astral-sh#4617
@nathanwhit
Copy link
Contributor

Hello! I'm interested in working on this, if that's okay!

I think I have enough to go on here to round out support for generic type variables, the only snag is with handling type variables that have explicit variance. If a type var has explicit variance, I don't see a way to translate it to the PEP-695 style and guarantee identical semantics. The issue is that the new syntax for generics doesn't have a facility to explicitly set the variance of a type variable, the variance is always inferred.

If we're adhering strictly to the spec (from my reading, at least), only TypeVar definitions with infer_variance=True should have inferred variance and are therefore definitely safe to upgrade to the new syntax. The trouble here is that I would guess that many TypeVar declarations in practice will not have that flag set, which might limit the usefulness of this rule.

If we're being a bit less strict, we could just assume that the variance inference will usually produce the same result, but I'm not sure how acceptable it is to potentially change the meaning of code.

@zanieb
Copy link
Member

zanieb commented Aug 21, 2023

Hey @nathanwhit excited you're interested in working on this :)

We can use different applicability levels (#4183) to indicate our certainty that the fix will or will not change the meaning of the code. In this case, we could use an Automatic fix when infer_variance=True and a Suggested fix otherwise.

I've never seen someone set infer_variance=True before though 😬 maybe we can use a heuristic to infer if the variance would be the same?

@nathanwhit
Copy link
Contributor

Thanks for such a quick response @zanieb!

We can use different applicability levels (#4183) to indicate our certainty that the fix will or will not change the meaning of the code. In this case, we could use an Automatic fix when infer_variance=True and a Suggested fix otherwise.

Makes sense!

I've never seen someone set infer_variance=True before though 😬 maybe we can use a heuristic to infer if the variance would be the same?

Now that I'm looking, infer_variance is new in PEP-695, so unless someone has partially upgraded for 695 they wouldn't have it set🙃. Yeah, a heuristic would be good (though I can't think of one off the top of my head 😬) since doing it properly is probably out of the question (since it requires a full typechecker).

--

A little awkward, but I dug deeper and I think we don't have to worry about variance for type aliases after all! It turns out that the variance of a type variable is actually ignored for type aliases, all that matters is the variance of the type variable used in the generic class. (This makes sense now that I'm thinking about it, since variance is a property of a generic type and generic type aliases aren't actual generic types, just new names for an underlying generic type).

For example:

from typing import Generic, TypeVar

T = TypeVar("T", covariant=True)

class Covariant(Generic[T]):
    pass

S = TypeVar("S", contravariant=True)
Co = Covariant[S]

class MyInt(int):
    pass

foo: Covariant[MyInt] = Covariant[MyInt]()
bar: Covariant[int] = foo

This typechecks despite the fact that S (used by the Co type alias) is defined as contravariant, because all that matters is the variance of T (the var used in the actual generic class).

So I think we can safely ignore the variance of a type var used in a type alias. Variance will be relevant again when we support upgrading classes to the new syntax (class List(Generic[T]): to class List[T]) though.

@zanieb zanieb added the python312 Related to Python 3.12 label Aug 24, 2023
charliermarsh pushed a commit that referenced this issue Sep 15, 2023
… type variables to UP040 (#6749)

## Summary

Extends UP040 to support moving type variables with
bounds/constraints/variance that are used in type aliases to use PEP-695
syntax.

Part of #4617.

## Test Plan

The existing tests added by #6314 already cover the relevant cases.
@Goldziher
Copy link

Goldziher commented Jul 28, 2024

I wrote this in #12542

MyPy released support for PEP 695 generics: python/mypy#15238

It would be awesome to do any of the following:

  1. get linting errors for code that uses older style generics (TypeVar, ParamSpec etc.) for Python 3.12+

  2. get linting errors for code that uses newer style generics and should be backward compatible with lower Python versions

  3. automagically upgrade older style generics to use the new syntax

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepted Ready for implementation help wanted Contributions especially welcome python312 Related to Python 3.12 rule Implementing or modifying a lint rule
Projects
None yet
Development

No branches or pull requests

5 participants