typeshed
typeshed copied to clipboard
Adding descriptor methods to _SimpleCData
This allows adding type annotations to structures and unions that resolve to the underlying types when accessed inside structures.
I don't know how to make proper tests for this, so I've made some examples where I've marked the lines that are expected to fail typechecking.
import ctypes
class Annotated(ctypes.Structure):
_fields_ = [("x", ctypes.c_int), ("y", ctypes.c_int), ("z", ctypes.c_int), ("f", ctypes.c_float)]
x: ctypes.c_int
y: ctypes.c_int
z: ctypes.c_int
f: ctypes.c_float
class NoAnnotation(ctypes.Structure):
_fields_ = [("x", ctypes.c_int, "vec", Annotated)]
class NonCType:
x : ctypes.c_int
ctypes.c_int().value += 2
ctypes.c_int() += 2 # FAIL
ctypes.c_float().value += 2
ctypes.c_float() += 2 # FAIL
# All passes; all access are Any
NoAnnotation().x += 2
NoAnnotation().vec.x += 2
NoAnnotation().noexist += 2
Annotated.x.value += 2
Annotated.x += 2 # FAIL
Annotated().x += 2
Annotated().x.value += 2 # FAIL
Annotated().f += 3.14
Annotated().f.value += 3.14 # FAIL
NonCType.x.value += 2
NonCType.x += 2 # FAIL
NonCType().x += 2 # FAIL
NonCType().x.value += 2
Diff from mypy_primer, showing the effect of this PR on open source code:
comtypes (https://github.com/enthought/comtypes)
+ comtypes/typeinfo.py:391: error: Incompatible return value type (got "tuple[str, int | None]", expected "tuple[str, tagFUNCDESC | tagVARDESC | ITypeComp] | None") [return-value]
Diff from mypy_primer, showing the effect of this PR on open source code:
comtypes (https://github.com/enthought/comtypes)
+ comtypes/typeinfo.py:391: error: Incompatible return value type (got "tuple[str, int | None]", expected "tuple[str, tagFUNCDESC | tagVARDESC | ITypeComp] | None") [return-value]
I've checked out the problem from enthought/comtypes
and these are my findings:
ITypeComp.Bind
is specified to return Optional[Tuple[str, _UnionT[FUNCDESC, VARDESC, ITypeComp]]]
. typeinfo.py:375.
The offending line returns "type", bindptr.lptcomp
where bindptr
is an instance of BINDPTR
/tagBINDPTR
.
tagBindPtr.lptcomp
is annotated as ITypeComp
. typeinfo.py:646.
The actual field type is POINTER(ITypeComp)
. typeinfo.p:L978
ITypeComp
inherits from IUnknown
which inherits from _IUnknown_Base
which inherits from c_void_p
. ITypeComp
is effectively a c_void_p
.
When c_void_p
is accessed as a member of Structure
or Union
the returned type is int | None
.
When the interpreter returns bindptr.lptcomp
, the type is POINTER(ITypeComp)
, and the returned type is ITypeComp
, as defined by the _field_
.
When the typechecker sees the return, bindptr.lptcomp
resolves as int
, as instructed by the annotation.
Thus the actual problem is that the annotation of the member doesn't match the type of the field declaration
edit: clarified last part, was tired when I wrote it, still am
Now after the fact I saw #10595 . I think this proposal does what was intended in the PR and discussed in #10567, but better, as it is performed automatically by all primitives that are part of a structure/union and doesn't need manual type definitions as is done in that PR?
See the test_cases/stdlib directory for examples on how to write tests. Basically, use typing_extensions.assert_type()
to make sure a type is as expected, and # type: ignore
for expected failures.
Diff from mypy_primer, showing the effect of this PR on open source code:
comtypes (https://github.com/enthought/comtypes)
+ comtypes/typeinfo.py:391: error: Incompatible return value type (got "tuple[str, int | None]", expected "tuple[str, tagFUNCDESC | tagVARDESC | ITypeComp] | None") [return-value]
It turns out that subclassing the basic types causes them to not be unboxed, and this behaviour has always been part of ctypes. I'll try fix things to properly reflect this, and add more regression tests for the things added in #10595 so the type annotations added therein can be simplified.
I added # pyright: reportUninitializedInstanceVariable=false
to the top of the new regression testing file since pyright complained about uninitialized members.
Should _StructUnionBase
or _StructUnionMeta
be dataclassed? On one hand it'd solve that problem (if I understand dataclass
correctly), and people already annotate ctypes structures. On the other hand, it'd sort of falsely convey that the class is defined by the annotations
Diff from mypy_primer, showing the effect of this PR on open source code:
comtypes (https://github.com/enthought/comtypes)
+ comtypes/typeinfo.py:452: error: Incompatible return value type (got "tuple[str, int | None]", expected "tuple[str, tagFUNCDESC | tagVARDESC | ITypeComp] | None") [return-value]