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

Cannot assign to variables of generic type #4112

Closed
DaviDevMod opened this issue Mar 20, 2023 · 15 comments
Closed

Cannot assign to variables of generic type #4112

DaviDevMod opened this issue Mar 20, 2023 · 15 comments

Comments

@DaviDevMod
Copy link

DaviDevMod commented Mar 20, 2023

Environment data

  • Language Server version: 2023.3.20
  • OS and version: Ubuntu 22.04.2 LTS
  • Python version: 3.11.2

Code Snippet

from typing import TypeVar


T = TypeVar("T", int, float)


def f(arg: T):
    arg = 1

Expected behavior

It should be possible to assing an int to a variable of a generic type bound to int.

mypy Playground runs without errors.

Actual behavior

When trying to assign an int to a variable of a generic type bound to int, I get a squiggling line under the integer which expands to:

Expression of type "Literal[1]" cannot be assigned to declared type "T@f"
Type "Literal[1]" cannot be assigned to type "T@f"

Logs

Python Language Server Log
   [Info  - 19:07:23] (11618) Pylance language server 2023.3.20 (pyright 93a4ef87) starting
   [Info  - 19:07:23] (11618) Server root directory: /home/sheikyerbouty/.vscode/extensions/ms-python.vscode-pylance-2023.3.20/dist
   [Info  - 19:07:23] (11618) Auto-indent enabled
   [Info  - 19:07:23] (11618) Starting service instance "<default>"
   [Info  - 19:07:24] (11618) No pyproject.toml file found.
   [Info  - 19:07:24] (11618) Setting pythonPath for service "<default>": "/bin/python3.11"
   [Warn  - 19:07:24] (11618) stubPath typings is not a valid directory.
   [Info  - 19:07:24] (11618) Assuming Python version 3.11
   [Info  - 19:07:24] (11618) Assuming Python platform Linux
   [Info  - 19:07:24] (11618) Search paths for <default>
   [Info  - 19:07:24] (11618)   /home/sheikyerbouty/.vscode/extensions/ms-python.vscode-pylance-2023.3.20/dist/typeshed-fallback/stdlib
   [Info  - 19:07:24] (11618)   typings
   [Info  - 19:07:24] (11618)   /home/sheikyerbouty/.vscode/extensions/ms-python.vscode-pylance-2023.3.20/dist/typeshed-fallback/stubs/...
   [Info  - 19:07:24] (11618)   /home/sheikyerbouty/.vscode/extensions/ms-python.vscode-pylance-2023.3.20/dist/bundled/stubs
   [Info  - 19:07:24] (11618)   /usr/lib/python3.11
   [Info  - 19:07:24] (11618)   /usr/lib/python3.11/lib-dynload
   [Info  - 19:07:24] (11618)   /usr/local/lib/python3.11/dist-packages
   [Info  - 19:07:24] (11618)   /usr/lib/python3/dist-packages
   [Info  - 19:07:24] (11618) Searching for source files
   [Info  - 19:07:24] (11618) No source files found.
   [Info  - 19:07:24] (11618) No pyproject.toml file found.
   [Info  - 19:07:24] (11618) Setting pythonPath for service "<default>": "/bin/python3.11"
   [Warn  - 19:07:24] (11618) stubPath typings is not a valid directory.
   [Info  - 19:07:24] (11618) Assuming Python version 3.11
   [Info  - 19:07:24] (11618) Assuming Python platform Linux
   [Info  - 19:07:24] (11618) Search paths for <default>
   [Info  - 19:07:24] (11618)   /home/sheikyerbouty/.vscode/extensions/ms-python.vscode-pylance-2023.3.20/dist/typeshed-fallback/stdlib
   [Info  - 19:07:24] (11618)   typings
   [Info  - 19:07:24] (11618)   /home/sheikyerbouty/.vscode/extensions/ms-python.vscode-pylance-2023.3.20/dist/typeshed-fallback/stubs/...
   [Info  - 19:07:24] (11618)   /home/sheikyerbouty/.vscode/extensions/ms-python.vscode-pylance-2023.3.20/dist/bundled/stubs
   [Info  - 19:07:24] (11618)   /usr/lib/python3.11
   [Info  - 19:07:24] (11618)   /usr/lib/python3.11/lib-dynload
   [Info  - 19:07:24] (11618)   /usr/local/lib/python3.11/dist-packages
   [Info  - 19:07:24] (11618)   /usr/lib/python3/dist-packages
   [Info  - 19:07:24] (11618) Searching for source files
   [Info  - 19:07:24] (11618) No source files found.
   (11618) [IDX(FG)] index libraries  (index) ...
   (11618) [IDX(FG)]   read stdlib indices (78ms)
   (11618) [IDX(FG)] index libraries  (index) [succeed] (81ms)
   (11618) [IDX(FG)] index libraries  (index) ...
   (11618) [IDX(FG)]   read stdlib indices (0ms)
   (11618) [IDX(FG)] index libraries  (index) [succeed] (3ms)
   (11618) [FG] parsing: /home/sheikyerbouty/Desktop/python_playground.py (84ms)
   (11618) [FG] parsing: /home/sheikyerbouty/.vscode/extensions/ms-python.vscode-pylance-2023.3.20/dist/typeshed-fallback/stdlib/builtins.pyi [fs read 6ms] (194ms)
   (11618) [FG] binding: /home/sheikyerbouty/.vscode/extensions/ms-python.vscode-pylance-2023.3.20/dist/typeshed-fallback/stdlib/builtins.pyi (70ms)
   (11618) [FG] binding: /home/sheikyerbouty/Desktop/python_playground.py (1ms)
   [Info  - 19:07:24] (11618) Background analysis(1) root directory: /home/sheikyerbouty/.vscode/extensions/ms-python.vscode-pylance-2023.3.20/dist
   [Info  - 19:07:24] (11618) Background analysis(1) started
   (11618) Background analysis message: setConfigOptions
   (11618) Background analysis message: setImportResolver
   (11618) Background analysis message: ensurePartialStubPackages
   (11618) Background analysis message: setConfigOptions
   (11618) Background analysis message: setTrackedFiles
   (11618) Background analysis message: markAllFilesDirty
   (11618) Background analysis message: setConfigOptions
   (11618) Background analysis message: setImportResolver
   (11618) Background analysis message: ensurePartialStubPackages
   (11618) Background analysis message: setConfigOptions
   (11618) Background analysis message: setTrackedFiles
   (11618) Background analysis message: markAllFilesDirty
   (11618) Background analysis message: setFileOpened
   (11618) Background analysis message: getSemanticTokens full
   (11618) [BG(1)] getSemanticTokens full at /home/sheikyerbouty/Desktop/python_playground.py ...
   (11618) [BG(1)]   parsing: /home/sheikyerbouty/Desktop/python_playground.py (76ms)
   (11618) [BG(1)]   parsing: /home/sheikyerbouty/.vscode/extensions/ms-python.vscode-pylance-2023.3.20/dist/typeshed-fallback/stdlib/builtins.pyi [fs read 4ms] (153ms)
   (11618) [BG(1)]   binding: /home/sheikyerbouty/.vscode/extensions/ms-python.vscode-pylance-2023.3.20/dist/typeshed-fallback/stdlib/builtins.pyi (55ms)
   (11618) [BG(1)]   binding: /home/sheikyerbouty/Desktop/python_playground.py (0ms)
   (11618) [BG(1)]   parsing: /home/sheikyerbouty/.vscode/extensions/ms-python.vscode-pylance-2023.3.20/dist/typeshed-fallback/stdlib/typing.pyi [fs read 0ms] (52ms)
   (11618) [BG(1)]   binding: /home/sheikyerbouty/.vscode/extensions/ms-python.vscode-pylance-2023.3.20/dist/typeshed-fallback/stdlib/typing.pyi (14ms)
   (11618) [BG(1)]   parsing: /home/sheikyerbouty/.vscode/extensions/ms-python.vscode-pylance-2023.3.20/dist/typeshed-fallback/stdlib/typing_extensions.pyi [fs read 0ms] (10ms)
   (11618) [BG(1)]   binding: /home/sheikyerbouty/.vscode/extensions/ms-python.vscode-pylance-2023.3.20/dist/typeshed-fallback/stdlib/typing_extensions.pyi (7ms)
   (11618) [BG(1)]   parsing: /home/sheikyerbouty/.vscode/extensions/ms-python.vscode-pylance-2023.3.20/dist/typeshed-fallback/stdlib/_typeshed/__init__.pyi [fs read 0ms] (25ms)
   (11618) [BG(1)]   binding: /home/sheikyerbouty/.vscode/extensions/ms-python.vscode-pylance-2023.3.20/dist/typeshed-fallback/stdlib/_typeshed/__init__.pyi (5ms)
   (11618) [BG(1)]   parsing: /home/sheikyerbouty/.vscode/extensions/ms-python.vscode-pylance-2023.3.20/dist/typeshed-fallback/stdlib/types.pyi [fs read 0ms] (35ms)
   (11618) [BG(1)]   binding: /home/sheikyerbouty/.vscode/extensions/ms-python.vscode-pylance-2023.3.20/dist/typeshed-fallback/stdlib/types.pyi (11ms)
   (11618) [BG(1)]   parsing: /home/sheikyerbouty/.vscode/extensions/ms-python.vscode-pylance-2023.3.20/dist/typeshed-fallback/stdlib/abc.pyi [fs read 1ms] (2ms)
   (11618) [BG(1)]   binding: /home/sheikyerbouty/.vscode/extensions/ms-python.vscode-pylance-2023.3.20/dist/typeshed-fallback/stdlib/abc.pyi (1ms)
   (11618) [BG(1)] getSemanticTokens full at /home/sheikyerbouty/Desktop/python_playground.py (515ms)
   (11618) Background analysis message: analyze
   (11618) [BG(1)] analyzing: /home/sheikyerbouty/Desktop/python_playground.py ...
   (11618) [BG(1)]   checking: /home/sheikyerbouty/Desktop/python_playground.py ...
   (11618) [BG(1)]     parsing: /usr/lib/python3.11/typing.py [fs read 1ms] (137ms)
   (11618) [BG(1)]     binding: /usr/lib/python3.11/typing.py (47ms)
   (11618) [BG(1)]   checking: /home/sheikyerbouty/Desktop/python_playground.py (210ms)
   (11618) [BG(1)] analyzing: /home/sheikyerbouty/Desktop/python_playground.py (210ms)
   (11618) Background analysis message: getSemanticTokens range
   (11618) [BG(1)] getSemanticTokens range 270:0 - 294:0 at /home/sheikyerbouty/Desktop/python_playground.py (3ms)
   (11618) Background analysis message: resumeAnalysis
@erictraut
Copy link
Contributor

erictraut commented Mar 20, 2023

Pyright (the type checker upon which pylance is built) is correct here. You cannot assign a value of type int to a variable whose type is declared to be generic.

This appears to be a bug in mypy. Your code sample is defining a constrained TypeVar with types float and int, which is problematic because int is a subtype of float in the Python type system. I suspect that mypy has a bug in this particular case.

If you modify your sample to use constraints that are disjoint, such as str and int, you'll see that mypy then correctly reports an error, as pyright does.

T = TypeVar("T", int, str)

def f(arg: T):
    arg = 1 # Type error

You mentioned in your bug report that your type variable is "bound to" type int. Perhaps it's your intent to use a "bound" TypeVar? This is more typical than a "constrained" TypeVar which is an odd construct not found in any language other than Python. Here's how to define a bound TypeVar:

T = TypeVar("T", bound=int)

@DaviDevMod
Copy link
Author

DaviDevMod commented Mar 20, 2023

Hey thanks for the quick reply!

You mentioned in your bug report that your type variable is "bound to" type int

Sorry this was confusion from my side, I started learning python two days ago and went through a lot of new terminology.

Anyway the error in case of T = TypeVar("T", int, str) is understandable, but since pyright does not complain about this:

floatNumber: float = 1

Why would it complain about the same thing written with generics rather than explicit types?

Edit: I just realised that in the OP I never mentioned anything about assigning an int to a float, I apologise for my poor illustration of the issue.

Re-edit: FWIW the reason why in the OP I only spoke about assigning an int to an int is because that was what I originally needed in my code:

from typing import TypeVar

T = TypeVar("T", int, float, str)


def fun(arg: T):
    if isinstance(arg, int):
        arg = 1

And when it didn't work I though it was because isinstance hasn't been implemented as type guard, but then I found out that isinstance was working as I expected so I concluded that I was simply not allowed to assign a value (of the right type) to a variable of generic type.

@DaviDevMod
Copy link
Author

@debonte Thank you for looking into the issue. Is there any chance that you are going to implement the behaviour I was expecting? Is this a limitation of of pyright's design or just a feature no one ever needed?

@DaviDevMod
Copy link
Author

I saw that pyright is written in TypeScript, which allows for the behaviour I was expecting.
I gave a look at the codebase, but since it's huge it would take me a while to find out how it works.
The feature I am looking for would probably live in constraintSolver.ts but I'm not going to try to implemt it.

Anyway thank you again for having looked into the issue!

@heejaechang
Copy link
Contributor

I am not sure when TypeVar has constraints, type var is considered as invariant or not. if it is not, then

def fun(arg: T):
    if isinstance(arg, int):
        arg = 1

arg could be sub type of int, so static checker can not verify arg = 1 is safe or not, since T might be derived type of int, isinstance is also not enough since it will say "true" for sub type of int but since arg is T which point to actual derived type, you can't assign instance of the base type to the derived type.

https://docs.python.org/3/library/typing.html#typing.TypeVar says TypeVar is by default invariant, but one of example says this

Using a constrained type variable, however, means that the TypeVar can only ever be solved as being exactly one of the constraints given:

def concatenate(x: A, y: A) -> A:
    """Add two strings or bytes objects together."""
    return x + y

a = concatenate('one', 'two')
reveal_type(a)  # revealed type is str

b = concatenate(StringSubclass('one'), StringSubclass('two'))
reveal_type(b)  # revealed type is str, despite StringSubclass being passed in

which says T will ever be resolved to str but allow subtype as input, which means T is covariant.

I tried this

from typing import List, TypeVar

T = TypeVar("T", Base, UnusedType)

derived = DerivedBase(1)
base = Base(1)

def funcWithTypeVar(arg: List[T]) -> T:
    arg.append(Base(1)) # error - Argument of type \"Base\" cannot be assigned to parameter \"T@funcWithTypeVar\" in function \"append\"
    return arg[0]

def funcWithRealType(arg: list[Base]):
    arg.append(Base(1))
    pass

arr: list[DerivedBase] = [derived, derived]

funcWithTypeVar(arr) # no error
funcWithRealType(arr) # error - \"list[DerivedBase]\" is incompatible with \"list[Base]\"\n    TypeVar \"_T@list\" is invariant\n      \"DerivedBase\" is incompatible with \"Base\""

where funcWithRealType shows errors saying _T@list is invariant but funcWithTypeVar accepted it fine. if T used for funcWithTypeVar is covariant, then arg.append(Base(1)) is invalid.

@DaviDevMod
Copy link
Author

@heejaechang Thank you for trying to shed light on the behaviour of pyright.

I haven't fully understood covariance and contravariance (in particular the examples in here), but regarding the example you mentioned

which says T will ever be resolved to str but allow subtype as input, which means T is covariant.

I assume that for "T" you mean the generic type "A" that was defined as A = TypeVar('A', str, bytes), meaning that T can only be str or bytes (e.g., it can't be a subtype of str). Now in the concatenate call:

b = concatenate(StringSubclass('one'), StringSubclass('two'))
reveal_type(b)  # revealed type is str, despite StringSubclass being passed in

you say that T is acting as if it was covariant because a subtype of it is allowed to be used in its place.
However (in order for the docs to make some sense, it must be that) since T is constrained, it is invariant (by default) and the concept of e.g. subclass of str doesn't exist anymore: as far as T is concerned a value is either a str or not. StringSubclass('one') and StringSubclass('two') both satisfy the string interface and therefore T is successfully resolved to str.

With that said, I don't understand why this should not work:

from typing import TypeVar


T = TypeVar("T", int, float)


def f(arg: T):
    arg = 1

As far as T is concerned, arg is either an int or a float, subtypes are not a thing.
Both an int and a float can be assigned the integer 1, so there should be no error.

But as I said at the beginning of this comment, I haven't fully understood covariance (and generic types in general) yet, so I can't really defend my point of view, only put it out there to be considered by someone more knowledgeable.

@erictraut
Copy link
Contributor

Pyright is doing the right thing here. This isn't a bug, so there's nothing to fix.

You mentioned that this works in TypeScript, but TypeScript (and no other language, to my knowledge) has the notion of a "constrained TypeVar" like Python does. It was added mainly to handle the AnyStr type. I recommend against using it in general because it does not work the way most people expect, and there's almost always a better solution.

As I said above, you're using a constrained TypeVar in an especially odd manner because you're specifying two constraints where one is a subtype of the other.

I'm still not exactly sure what you're trying to do here, but I suspect what you really mean is for arg to have a type of int | float (which is the equivalent of just float, since int is a proper subtype of float). Perhaps you can provide more details about what you're trying to do here. I may be able to offer a better suggestion.

@heejaechang
Copy link
Contributor

heejaechang commented Mar 28, 2023

@DaviDevMod the reason I wasn't sure whether T is invariant is due to this

arr: list[DerivedBase] = [derived, derived]

funcWithTypeVar(arr) # no error
funcWithRealType(arr) # error

I expected funcWithTypeVar to show errors like funcWithRealType since list[_T] expects _T to be invariant. giving array of type DerivedBase to array of type Base is not allowed.

but funcWithTypeVar didn't show the error meaning

def funcWithTypeVar(arg: List[T]) -> T:
    arg.append(Base(1)) # this should be error
    return arg[0]

arr: list[DerivedBase] = [derived, derived]
funcWithTypeVar(arr) # no error

T here can't be Base otherwise, arg.append(Base(1)) should work, which violates that all elements inside of arg should be DerivedBase for funcWithTypeVar(arr) call.

unfortunately, currently pylance tooltip doesn't show what actual type the T is resolved to for funcWithTypeVar(arr), so I can't verify what type 'T' is. but from the result, It looks like T is not invariant. in that case, arg.append(Base(1)) should be error since arg could point to something like list[DerivedBase]

For your example, if I have something like this

class MyInt(int):
      pass

T = TypeVar("T", int, float)

def f(arg: T):
    arg = 1
    
f(MyInt(1))

and if T is resolved to MyInt such as

def f(arg: MyInt): 
    arg = 1

arg = 1 is invalid since we don't know whether 1 can implicitly converted to MyInt or not

you could say int and float is special since both of them are builtin types. but that will be a special casing, in general case, it should marked as error I believe.

but if T is invariant, then, I am not sure, then I think funcWithTypeVar(arr) should show error?

@erictraut
Copy link
Contributor

erictraut commented Mar 28, 2023

@heejaechang, variance applies only when a TypeVar is used as a type parameter for a class. In this case, T is a "naked" type variable, and it's scoped to a function f. Variance has no bearing in this issue.

@heejaechang
Copy link
Contributor

@erictraut thank you for the explanation, so in "naked" type variable, how does it supposed to behave? like invariant or some other way?

@erictraut
Copy link
Contributor

I'm not sure what you're referring to when you say "how does it supposed to behave"? The current behavior of pyright for this code snippet is correct.

Constrained TypeVars involves some strange rules. I won't repeat all of those details here. You can look it up if you want.

However, I recommend avoiding the use of constrained TypeVars if you are not an expert at Python typing. They're rarely the right tool for the job. Once I understand what the OP is trying to do, I can offer better advice about the best approach.

@heejaechang
Copy link
Contributor

heejaechang commented Mar 28, 2023

@erictraut

Constrained TypeVars involves some strange rules. I won't repeat all of those details here. You can look it up if you want.

I have been searching the rule but I couldn't find one. are you talking about this or this? or pyright's implementation? or some other docs? I will appreciate any pointer so I can learn about python generic a bit more.

what I mean by I couldn't find one is explanation on why the code above show errors (so we can understand why it does what it does)

arg.append(Base(1)) # error - Argument of type \"Base\" cannot be assigned to parameter \"T@funcWithTypeVar\" in function \"append\"

# or this

def f(arg: T):
    arg = 1

by the way, it would be nice if tooltip shows actual type of T as well

image

like some other language

image

@DaviDevMod
Copy link
Author

DaviDevMod commented Mar 29, 2023

@erictraut what I was trying to do was having a function that does the task same regardless of the argument passed to it and also does either something or somethingelse depending on the nature of the argument; finally, the return type would have been decided by the type of the argument, but it would not necessarily be its same type.

This is off topic

Here is an example of my problem:

from typing import Literal

Answers = Literal["YES", "NO"]

Vowels = Literal["A", "E", "I", "O", "U"]


def fun(arg: Literal["INT", "ANSWERS", "VOWELS"]) -> int | Answers | Vowels:
    print("Print this, no matter what `arg` is.")

    if arg == "INT" and not input("") == 0:
        return 5

    elif arg == "ANSWERS" and input("") == "YES":
        return "YES"

    elif arg == "VOWELS" and input("") == "A":
        return "A"

    print("Invalid input.")

    return fun(arg)


# Obviously these variables are all of the same type `int | Answers | Vowels`
# which is not what I want.
integer = fun("INT")
answer = fun("ANSWERS")
vowel = fun("VOWELS")

What I did was using overloads, but the four overloads I need in my actual code are bigger than the function itself.
Furthermore, due to the multiple arguments I have, these overloads take time and effort to be parsed by a human, resulting in poor readability.
I was looking for a way to have the same functionality of overloads with less noise.

Here is the above function overloaded:

from typing import Literal, overload

Answers = Literal["YES", "NO"]

Vowels = Literal["A", "E", "I", "O", "U"]


@overload
def fun(arg: Literal["INT"]) -> int:
    ...


@overload
def fun(arg: Literal["ANSWERS"]) -> Answers:
    ...


@overload
def fun(arg: Literal["VOWELS"]) -> Vowels:
    ...


def fun(arg: Literal["INT", "ANSWERS", "VOWELS"]):
    print("Print this, no matter what `arg` is.")

    if arg == "INT" and not input("") == 0:
        return 5

    elif arg == "ANSWERS" and input("") == "YES":
        return "YES"

    elif arg == "VOWELS" and input("") == "A":
        return "A"

    print("Invalid input.")

    return fun(arg)


integer = fun("INT")
answer = fun("ANSWERS")
vowel = fun("VOWELS")

Another option would be singledispatch but it looks like it would be even more noisy.

Using a generic function would be the obvious solution in case the type of the argument is the same as the return type.
But needing a return type that is based on that of the argument, without necessarily being the same, I ended up using generics in this way:

from typing import Literal, TypeVar

Answers = Literal["YES", "NO"]

Vowels = Literal["A", "E", "I", "O", "U"]


class AnswersProxy:
    def __init__(self, answer: Answers = "YES"):
        self.value: Answers = answer


class VowelsProxy:
    def __init__(self, answer: Vowels = "A"):
        self.value: Vowels = answer


T = TypeVar("T", int, AnswersProxy, VowelsProxy)


def fun(arg: T) -> T:
    print("Print this, no matter what `arg` is.")

    if isinstance(arg, int) and not input("") == 0:
        return 5

    elif isinstance(arg, AnswersProxy) and input("") == "YES":
        return AnswersProxy("YES")

    elif isinstance(arg, VowelsProxy) and input("") == "A":
        return VowelsProxy("A")

    print("Invalid input.")

    return fun(arg)


integer = fun(int())
answer = fun(AnswersProxy()).value
vowel = fun(VowelsProxy()).value

Which is definitely less noisy in my actual code (where each of int and float would require an overload, but not a proxy class; and where each overload is much more code than in here, while the proxy classes are still as simple) but it's definitely not the best solution, first and foremost because of the need to access the .value of the proxy class.

Now I don't exactly know how the behaviour exposed in the OP would help create such a function.
I didn't know how to create such a function, I just played around with typings till I stumbled in the unexpected error (which, by the way, mypy does not show) and so I reported it.

It is still not clear to me why the behaviour is expected by pyright.
You mentioned that using a TypeVar constrained with int and float is problematic because one is a subtype of the other.
So let's consider this other example (mypy gives no errors):

from typing import TypeVar

T = TypeVar("T", int, str)


def fun(arg: T):
    if isinstance(arg, int):
        arg = 1

Would you please point out why an error is expected here?

@erictraut
Copy link
Contributor

Based on the problem you're describing, the correct tool for the job is an overload.

@overload
def fun(arg: int) -> float: ...

@overload
def fun(arg: str) -> bytes: ...

def fun(arg: int | str) -> float | bytes:
    if isinstance(arg, int):
        return float(arg)
    return arg.encode()

@DaviDevMod
Copy link
Author

DaviDevMod commented Mar 29, 2023

@erictraut Thank you so much for settling my inner battle, I'll accept the noise that overloads carry with them.

But I still don't understand why an error is expected in here:

from typing import TypeVar

T = TypeVar("T", int, str)


def fun(arg: T):
    if isinstance(arg, int):
        arg = 1

The docs say:

Using a constrained type variable, however, means that the TypeVar can only ever be solved as being exactly one of the constraints given

So within the scope of fun, T is either exactly int or exactly str: it can't be a subtype of the constraints.
So in the branch of if isinstance(arg, int): arg should be exactly of type int, and it should be allowed to be assigned 1.

What am I missing here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants