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

Allow star args in ctypes.Array constructor #6213

Merged
merged 4 commits into from Jan 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 12 additions & 8 deletions mypy/plugins/ctypes.py
Expand Up @@ -4,6 +4,7 @@

# Fully qualified instead of "from mypy.plugin import ..." to avoid circular import problems.
import mypy.plugin
from mypy import nodes
from mypy.maptype import map_instance_to_supertype
from mypy.subtypes import is_subtype
from mypy.types import (
Expand Down Expand Up @@ -116,19 +117,22 @@ def array_constructor_callback(ctx: 'mypy.plugin.FunctionContext') -> Type:
allowed = _autoconvertible_to_cdata(et, ctx.api)
assert len(ctx.arg_types) == 1, \
"The stub of the ctypes.Array constructor should have a single vararg parameter"
for arg_num, arg_type in enumerate(ctx.arg_types[0], 1):
# TODO This causes false errors if the argument list contains *args.
# In a function hook, the type of an *args parameter is the type of the iterable being
# unpacked. However, FunctionContext currently doesn't provide a way to differentiate
# between normal arguments and *args, so the iterable type is considered invalid.
# Once FunctionContext has an API for this, *args should be allowed here if the
# iterable's element type is compatible with the array element type.
if not is_subtype(arg_type, allowed):
for arg_num, (arg_kind, arg_type) in enumerate(zip(ctx.arg_kinds[0], ctx.arg_types[0]), 1):
if arg_kind == nodes.ARG_POS and not is_subtype(arg_type, allowed):
ctx.api.msg.fail(
'Array constructor argument {} of type "{}"'
' is not convertible to the array element type "{}"'
.format(arg_num, arg_type, et),
ctx.context)
elif arg_kind == nodes.ARG_STAR:
ty = ctx.api.named_generic_type("typing.Iterable", [allowed])
if not is_subtype(arg_type, ty):
ctx.api.msg.fail(
'Array constructor argument {} of type "{}"'
' is not convertible to the array element type "Iterable[{}]"'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add a test for this error message.

.format(arg_num, arg_type, et),
ctx.context)

return ctx.default_return_type


Expand Down
14 changes: 13 additions & 1 deletion test-data/unit/check-ctypes.test
Expand Up @@ -156,7 +156,7 @@ oa.value # E: ctypes.Array attribute "value" is only available with element typ
oa.raw # E: ctypes.Array attribute "raw" is only available with element type c_char, not "ctypes.c_int"
[builtins fixtures/floatdict.pyi]

[case testCtypesArrayConstructorStarargs-skip]
[case testCtypesArrayConstructorStarargs]
import ctypes

intarr4 = ctypes.c_int * 4
Expand All @@ -167,4 +167,16 @@ reveal_type(intarr4(*int_values)) # E: Revealed type is 'ctypes.Array[ctypes.c_
reveal_type(intarr4(*c_int_values)) # E: Revealed type is 'ctypes.Array[ctypes.c_int]'
reveal_type(intarr6(1, ctypes.c_int(2), *int_values)) # E: Revealed type is 'ctypes.Array[ctypes.c_int]'
reveal_type(intarr6(1, ctypes.c_int(2), *c_int_values)) # E: Revealed type is 'ctypes.Array[ctypes.c_int]'

float_values = [1.0, 2.0, 3.0, 4.0]
intarr4(*float_values) # E: Array constructor argument 1 of type "builtins.list[builtins.float*]" is not convertible to the array element type "Iterable[ctypes.c_int]"
[builtins fixtures/floatdict.pyi]

[case testCtypesArrayConstructorKwargs]
import ctypes
intarr4 = ctypes.c_int * 4

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to add a intarr4(1, 2, 3, x=4) test case here, but not sure how to get the tests to pass: the output on my machine is:

Expected:
  main:4: error: Unexpected keyword argument "x" for "Array"
  main:7: error: Too many arguments for "Array" (diff)
Actual:
  main:4: error: Unexpected keyword argument "x" for "Array"
  /home/alan/workspace/python/mypy/mypy/typeshed/stdlib/2and3/ctypes/__init__.pyi:273: note: "Array" defined here (diff)
  main:7: error: Too many arguments for "Array" (diff)

I added a # E: comment for the error, but don't know how to handle the note (which includes the path, so will be setup specific). Thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, this note caused some problems before. One option would be to add some very simple mock stubs to test-data/unit/lib-stub. But probably even better is just not add this test. The two tests you have added are sufficient.

x = {"a": 1, "b": 2}
intarr4(**x) # E: Too many arguments for "Array"

[builtins fixtures/floatdict.pyi]