Repository navigation
Support method plugin hooks on unions #6560
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
Changes from all commits
ae0f78d
b3633bc
39301bb
15fafd3
efd04a6
2dbbefb
72870a7
8d1dcff
bd6e18d
1d8a32f
4d7aa10
a643bbd
b3b8d89
55b4624
2bdb8be
12b4303
d36ea3c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -585,6 +585,67 @@ reveal_type(instance(2)) # N: Revealed type is 'builtins.float*' | |
| [[mypy] | ||
| plugins=<ROOT>/test-data/unit/plugins/callable_instance.py | ||
|
|
||
| [case testGetMethodHooksOnUnions] | ||
| # flags: --config-file tmp/mypy.ini --no-strict-optional | ||
| from typing import Union | ||
|
|
||
| class Foo: | ||
| def meth(self, x: str) -> str: ... | ||
| class Bar: | ||
| def meth(self, x: int) -> float: ... | ||
| class Other: | ||
| meth: int | ||
|
|
||
| x: Union[Foo, Bar, Other] | ||
| if isinstance(x.meth, int): | ||
| reveal_type(x.meth) # N: Revealed type is 'builtins.int' | ||
| else: | ||
| reveal_type(x.meth(int())) # N: Revealed type is 'builtins.int' | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the plugin switches types between
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, I now have different return types for two |
||
|
|
||
| [builtins fixtures/isinstancelist.pyi] | ||
| [file mypy.ini] | ||
| [[mypy] | ||
| plugins=<ROOT>/test-data/unit/plugins/union_method.py | ||
|
|
||
| [case testGetMethodHooksOnUnionsStrictOptional] | ||
| # flags: --config-file tmp/mypy.ini --strict-optional | ||
| from typing import Union | ||
|
|
||
| class Foo: | ||
| def meth(self, x: str) -> str: ... | ||
| class Bar: | ||
| def meth(self, x: int) -> float: ... | ||
| class Other: | ||
| meth: int | ||
|
|
||
| x: Union[Foo, Bar, Other] | ||
| if isinstance(x.meth, int): | ||
| reveal_type(x.meth) # N: Revealed type is 'builtins.int' | ||
| else: | ||
| reveal_type(x.meth(int())) # N: Revealed type is 'builtins.int' | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is similar to above -- maybe use more than two separate types. |
||
|
|
||
| [builtins fixtures/isinstancelist.pyi] | ||
| [file mypy.ini] | ||
| [[mypy] | ||
| plugins=<ROOT>/test-data/unit/plugins/union_method.py | ||
|
|
||
| [case testGetMethodHooksOnUnionsSpecial] | ||
| # flags: --config-file tmp/mypy.ini | ||
| from typing import Union | ||
|
|
||
| class Foo: | ||
| def __getitem__(self, x: str) -> str: ... | ||
| class Bar: | ||
| def __getitem__(self, x: int) -> float: ... | ||
|
|
||
| x: Union[Foo, Bar] | ||
| reveal_type(x[int()]) # N: Revealed type is 'builtins.int' | ||
|
|
||
| [builtins fixtures/isinstancelist.pyi] | ||
| [file mypy.ini] | ||
| [[mypy] | ||
| plugins=<ROOT>/test-data/unit/plugins/union_method.py | ||
|
|
||
| [case testPluginDependencies] | ||
| # flags: --config-file tmp/mypy.ini | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| from mypy.plugin import ( | ||
| CallableType, CheckerPluginInterface, MethodSigContext, MethodContext, Plugin | ||
| ) | ||
| from mypy.types import Instance, Type | ||
|
|
||
|
|
||
| class MethodPlugin(Plugin): | ||
| def get_method_signature_hook(self, fullname): | ||
| if fullname.startswith('__main__.Foo.'): | ||
| return my_meth_sig_hook | ||
| return None | ||
|
|
||
| def get_method_hook(self, fullname): | ||
| if fullname.startswith('__main__.Bar.'): | ||
| return my_meth_hook | ||
| return None | ||
|
|
||
|
|
||
| def _str_to_int(api: CheckerPluginInterface, typ: Type) -> Type: | ||
| if isinstance(typ, Instance): | ||
| if typ.type.fullname() == 'builtins.str': | ||
| return api.named_generic_type('builtins.int', []) | ||
| elif typ.args: | ||
| return typ.copy_modified(args=[_str_to_int(api, t) for t in typ.args]) | ||
| return typ | ||
|
|
||
|
|
||
| def _float_to_int(api: CheckerPluginInterface, typ: Type) -> Type: | ||
| if isinstance(typ, Instance): | ||
| if typ.type.fullname() == 'builtins.float': | ||
| return api.named_generic_type('builtins.int', []) | ||
| elif typ.args: | ||
| return typ.copy_modified(args=[_float_to_int(api, t) for t in typ.args]) | ||
| return typ | ||
|
|
||
|
|
||
| def my_meth_sig_hook(ctx: MethodSigContext) -> CallableType: | ||
| return ctx.default_signature.copy_modified( | ||
| arg_types=[_str_to_int(ctx.api, t) for t in ctx.default_signature.arg_types], | ||
| ret_type=_str_to_int(ctx.api, ctx.default_signature.ret_type), | ||
| ) | ||
|
|
||
|
|
||
| def my_meth_hook(ctx: MethodContext) -> Type: | ||
| return _float_to_int(ctx.api, ctx.default_return_type) | ||
|
|
||
|
|
||
| def plugin(version): | ||
| return MethodPlugin |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The above docstring part was a bit confusing. It looks like we aren't actually overriding the callee type but to reusing a previously calculated callee type. This looks like an optimization. Maybe update the docstring to mention that? Otherwise it's not clear whether recalculating the callee type is correct.