Reputation: 4572
Mypy is returning an error that I do not understand (nor can I recreate in a reduced form). Googling the error is proving difficult (given the question mark).
Does anyone know what this error means? Specifically what the question mark denotes?
Unsupported type Type[typeBasePage?]
The code in question:
typeBasePage = TypeVar("typeBasePage", bound="BasePage") # any instance subclass of BasePage
typeNavCls = Type[typeBasePage] # error occurs here - trying to make an alias
class Nav:
def __init__(self, cls: typeNavCls):
self.cls: typeNavCls = cls
class BasePage(Base):
...
# redacted because it's huge
Again, if I try to recreate the above code in a very reduced form, mypy doesn't error.
typeB = TypeVar('typeB', bound='B')
tAlias = Type[typeB]
class Nav:
def __init__(self, cls: tAlias):
self.cls: tAlias = cls
class A: pass
class B(A): pass
Upvotes: 4
Views: 596
Reputation: 64278
Without having a full repro, I'm afraid it'll be somewhat difficult to identify what the problem is. I think this is most likely a mypy bug: mypy is likely getting confused by some circularity in your code. Maybe an import cycle, some weirdness involving imports with modules and submodules, your mypy cache is somehow corrupted...
The other possibility is that your code is somehow not type safe in some other way, and this error is just a downstream consequence of mypy trying to struggle ahead anyways.
To help narrow down the issue, I recommend re-running mypy after making sure you're using the latest version and after deleting the .mypy_cache
file. If the issue still persists, it could be worth trying to steadily delete unrelated portions of your codebase to try and tease out a repro.
That said, it might also be worth just switching to not using a type alias at all here: you actually don't gain any benefits from using aliases/a type var in your example.
In short, if you use any type variables in a type aliases, those typevars are actually always left free: you need to explicitly bind them to some type when you start using the alias. So instead of doing def __init__(self, cls: typeNavCls)
, you'll want to be doing def __init__(self, cls: typeNavCls[SomeTypeHere])
instead.
Otherwise, mypy will treat your signature as being exactly the same as def __init__(self, cls: typeNavCls[Any])
in the same way a signature of def foo(x: List)
is treated as def foo(x: List[Any])
.
You can configure mypy to warn you about this problem by running it with either the --strict
tag or the --disallow-any-generics
flags: the former flag automatically enables the latter.
And instead, you should probably be doing one of the following instead:
# Approach 1: If you don't want Nav to be inherently generic
class Nav:
def __init__(self, cls: Type[BasePage]):
self.cls = cls
# Approach 2: If you want Nav to be inherently generic
_TBasePage = TypeVar('_TBasePage', bound='BasePage')
class Nav(Generic[_TBasePage]):
def __init__(self, cls: Type[_TBasePage]):
self.cls = cls
# Approach 3: If you want to ensure two or more types are the same,
# but nothing else
class Nav:
def __init__(self, cls: Type[_TBasePage], instance: _TBasePage):
# ...but you won't be able to save 'cls' as a field here: your
# Nav class needs to be generic with respect to _TBasePage if you
# want to "preserve" whatever type that's bound to as a field.
# Approach 4: If your alias is complex enough where you want to
# force a relationship between two or more types within the alias
MyAlias = Tuple[Type[_TBasePage], _TBasePage]
class Nav:
def __init__(self, info: MyAlias[BasePageSubtype]):
self.info = info
# Approach 5: ...or if you want Nav to also be generic:
MyAlias = Tuple[Type[_TBasePage], _TBasePage]
class Nav(Generic[_TBasePage]):
def __init__(self, info: MyAlias[_TBasePage]):
self.info = info
# Important note: the previous example is actually exactly
# equivalent to doing:
_T1 = TypeVar('_T1', bound=BasePage)
MyAlias = Tuple[Type[_T1], T1]
_T2 = TypeVar('_T2', bound=BasePage)
class Nav(Generic[_T2]):
def __init__(self, info: MyAlias[_T2]):
self.info = info
Approaches 1, 2, and 3 should sidestep the original issue you had. If those approaches are similar to what you actually want to do, then it's just simpler/more concise to not use aliases at all. And if you're not using aliases, then you won't run into the bug.
But if your actual code is more like approaches 4 or 5, some deeper investigation will unfortunately probably be necessary.
A final note regarding your self-answer here: that section of the docs is referring to the use of question marks just in the context of Literal types. But you're not using Literal types, so that section of the docs isn't relevant here.
If a question appears after a regular instance, that means that type is actually an "unbound type". This is a special internal-only kind of type mypy uses as a placeholder for things that ought to be types but couldn't find a corresponding definition for.
This either means there's a bug in mypy causing an Unbound type to appear in places it shouldn't be or that there's some other likely legitimate error contaminating the downstream errors.
Upvotes: 2
Reputation: 4572
I believe I found what the question mark is, but am nowhere closer to figuring out why the error occurs in mypy.
https://mypy.readthedocs.io/en/latest/literal_types.html
If you do not provide an explicit type in the
Final
, the type ofc
becomes context-sensitive: mypy will basically try “substituting” the original assigned value whenever it’s used before performing type checking. This is why the revealed type ofc
isLiteral[19]?
: the question mark at the end reflects this context-sensitive nature.
Upvotes: 2