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

"TypeVar bound type cannot be generic" reported for non-Generic types #2017

Closed
ghost opened this issue Oct 30, 2021 · 3 comments
Closed

"TypeVar bound type cannot be generic" reported for non-Generic types #2017

ghost opened this issue Oct 30, 2021 · 3 comments
Labels
bug Something isn't working fixed in next version (main) A fix has been implemented and will appear in an upcoming version

Comments

@ghost
Copy link

ghost commented Oct 30, 2021

Environment data

  • Language Server version: 2021.10.3
  • OS and version: macOS 11.6
  • Python version: 3.9.6

Expected behaviour

No issue reported.

Actual behaviour

Pylance reports "TypeVar bound type cannot be generic" when using typing.TypeVar and specifying, for the bound= parameter, a non-generic class with some very specific conditions (see code snippets)

  • The class defines its own __init__ method, and
  • The __init__ has none of its declared positional or keyword arguments annotated with types (excluding *args and **kwargs), and
  • The class is a user-defined class (classes from the standard library report no issue, even if they otherwise match the above description).

Logs

Background analysis message: setFileOpened
Background analysis message: getDiagnosticsForRange
Background analysis message: getDiagnosticsForRange
[FG] parsing: /Users/tony/Desktop/trace.py (1ms)
[FG] binding: /Users/tony/Desktop/trace.py (2ms)
Background analysis message: getSemanticTokens full
[BG(1)] getSemanticTokens full at /Users/tony/Desktop/trace.py ...
[BG(1)]   parsing: /Users/tony/Desktop/trace.py (1ms)
[BG(1)]   binding: /Users/tony/Desktop/trace.py (1ms)
[BG(1)]   parsing: /Users/tony/.vscode/extensions/ms-python.vscode-pylance-2021.10.3/dist/typeshed-fallback/stdlib/imaplib.pyi [fs read 3ms] (15ms)
[BG(1)]   binding: /Users/tony/.vscode/extensions/ms-python.vscode-pylance-2021.10.3/dist/typeshed-fallback/stdlib/imaplib.pyi (3ms)
[BG(1)] getSemanticTokens full at /Users/tony/Desktop/trace.py (30ms)
Background analysis message: getSemanticTokens range
[BG(1)] getSemanticTokens range 0:0 - 59:0 at /Users/tony/Desktop/trace.py (7ms)
Background analysis message: analyze
[BG(1)] analyzing: /Users/tony/Desktop/trace.py ...
[BG(1)]   checking: /Users/tony/Desktop/trace.py (1ms)
[BG(1)] analyzing: /Users/tony/Desktop/trace.py (1ms)
Background analysis message: resumeAnalysis

Code Snippet / Additional information

The issue is suppressed if:

  • any of the named arguments is type-annotated (even if the annotation is invalid); or
  • a class, which would otherwise trigger the issue, is instead inheriting from a Generic type variable
from imaplib import IMAP4
from pprint import PrettyPrinter
from typing import Generic, TypeVar

KT = TypeVar('KT')

# I first encountered the issue with discord.py's Client class.
class Client:
    def __init__(self, addr, port):
        pass

class Bot(Client):
    def __init__(self, addr, port, loop):
        super().__init__(addr, port)

class Middleware(Client):
    pass

class Server(Client):
    def __init__(self, addr: str, port):
        super().__init__(addr, port)

class Router:
    def __init__(self, *args):
        pass

class Promise:
    def __init__(self, loop, *, args):
        pass

class Future(Generic[KT]):
    def __init__(self, loop, *, args):
        pass

class Deferred(int):
    def __init__(self, value):
        pass

T = TypeVar('T', bound=Client)      # Issue
U = TypeVar('U', bound=Bot)         # Issue
M = TypeVar('M', bound=Middleware)
S = TypeVar('S', bound=Server)
R = TypeVar('R', bound=Router)

P = TypeVar('P', bound=Promise)     # Issue
F = TypeVar('F', bound=Future)
G = TypeVar('G', bound=Deferred)    # Issue

A = TypeVar('A', bound=IMAP4)
D = TypeVar('D', bound=PrettyPrinter)
@erictraut
Copy link
Contributor

Thanks for submitting this issue. Yes, this is a bug.

This is due to the way pyright (the type checker that underlies pylance) handles classes whose constructor methods are unannotated. To improve type inference, pyright treats these classes as implicitly generic, effectively synthesizing a type variable for each of the constructor's input parameters.

This allows it to do clever type inference for instance variables and methods within the class even though it is lacking type annotations. Here's an example.

class Client:
    def __init__(self, addr, port):
        self.addr = addr
        self.port = port

c = Client("name", 1234)
reveal_type(c.addr) # str
reveal_type(c.port) # int

d = Client(1234, "left")
reveal_type(d.addr) # int
reveal_type(d.port) # str

Pyright refers to these classes a "pseudo-generic". They're not actually generic, at least from the perspective of the developer.

In this particular case, the check is incorrectly triggered when a pseudo-generic class is used as a TypeVar bound argument. That was probably more details than you wanted to know. :)

In any case, I've introduced a fix, and it will be included in the next release of pylance.

You can work around the issue in the meantime by providing type annotations for the constructor input parameters, assuming the code that defines those classes is within your own code base. If the classes are in a library, your options are more limited. You would need to find or create type stubs for the library or disable the python.analysis.useLibraryCodeForTypes option in pylance.

@erictraut erictraut added bug Something isn't working fixed in next version (main) A fix has been implemented and will appear in an upcoming version and removed triage labels Oct 30, 2021
@ghost
Copy link
Author

ghost commented Oct 30, 2021

Thank you for the explanation, that makes a lot of sense in terms of how it is able to infer implicit types

Until the fix, I’m using # type: ignore to temporarily silence the issues

@heejaechang
Copy link
Contributor

fixed in 2021.11.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working fixed in next version (main) A fix has been implemented and will appear in an upcoming version
Projects
None yet
Development

No branches or pull requests

2 participants