diff --git a/mypy/plugins/ctypes.py b/mypy/plugins/ctypes.py index 6520cda6f0a0..00ad26087af2 100644 --- a/mypy/plugins/ctypes.py +++ b/mypy/plugins/ctypes.py @@ -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 ( @@ -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[{}]"' + .format(arg_num, arg_type, et), + ctx.context) + return ctx.default_return_type diff --git a/test-data/unit/check-ctypes.test b/test-data/unit/check-ctypes.test index 212c61acc86e..508b3bfd49c9 100644 --- a/test-data/unit/check-ctypes.test +++ b/test-data/unit/check-ctypes.test @@ -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 @@ -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 + +x = {"a": 1, "b": 2} +intarr4(**x) # E: Too many arguments for "Array" + [builtins fixtures/floatdict.pyi]