Skip to content

Resolve remaining type-checking errors. - #954

Merged
junkmd merged 5 commits into
enthought:mainfrom
junkmd:fix_memberspec_type_annotations
Oct 3, 2026
Merged

junkmd merged 5 commits into
enthought:mainfrom
junkmd:fix_memberspec_type_annotations

Conversation

@junkmd

@junkmd junkmd commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

This PR is a follow-up to #949 (commit 7ca1829).
Enabling PEP 561 discovery in that PR surfaced some type-checking errors that were missed at review time.

Changes

1. STDMETHOD: Accept Sequence instead of tuple for argtypes

IUnknown._methods_ passes a list to the argtypes argument of STDMETHOD.
The previous annotation specified tuple for this argument, causing a reportArgumentType error in both pyright and ty.
The argument is now annotated with the abstract type Sequence[type[_CDataType]] instead of a concrete type, and converted to a tuple internally. This is consistent with COMMETHOD.

2. ArgSpecElmType: Add length-2 return-value argspec variant

automation.py uses (["out"], POINTER(UINT)) — a 2-element argspec for a return value with no parameter name. _ResArgSpecElmType has been added to hints.pyi to cover this pattern and included in ArgSpecElmType.

3. DISPMETHOD / DISPPROPERTY / COMMETHOD: Suppress idlflags cast error

tuple(idlflags) produces tuple[int | str, ...], which is not directly assignable to the narrowly-typed _DispIdlFlags / _ComIdlFlags fields.
A # type: ignore comment suppresses the false-positive at each call site; runtime behaviour is unchanged.

4. Add detailed docstrings to COM method helper functions in _memberspec.

The semantics of the arguments are now explained in the docstring.

5. Update DISPPROPERTY signature to use a non-optional proptype

This reflects that a dispinterface property never returns null/void, so None is never a valid proptype.

Related

Remaining work

A suitable test double has not yet been found for these cases.
Rather than defining a fictional interface or specifier specifically for testing, I believe a more robust approach would be to find an existing, working interface definition that naturally satisfies the requirements being tested.

junkmd added 5 commits October 4, 2026 00:20
… in `_memberspec.STDMETHOD`.

- Updated `STDMETHOD` signature in `_memberspec.py` to accept any `Sequence`
  and cast to `tuple`.
- The `IUnknown._methods_` entry for `QueryInterface` still provides a list
  (`[POINTER(GUID), POINTER(c_void_p)]`); `STDMETHOD` now internally converts
  that list to a tuple, so no change to the interface definition is needed.
- This keeps `STDMETHOD` consistent with `COMMETHOD` and ensures that the
  resulting `_ComMemberSpec` stores the `argtypes` as a concrete `tuple`,
  matching the expectations of both helpers.
- Resolves the static type‑checking errors reported by type checkers.

This change resolves the type-checker error below.
```
> uvx pyright comtypes/_post_coinit/unknwn.py
path\to\comtypes\comtypes\_post_coinit\unknwn.py
  ...
  path\to\comtypes\comtypes\_post_coinit\unknwn.py:389:46 - error: Argument of type "list[type[_Pointer[GUID]] | type[_Pointer[c_void_p]]]" cannot be assigned to parameter "argtypes" of type "tuple[type[_SimpleCData[Any]] | type[_Pointer[Any]] | type[CFuncPtr] | type[Union] | type[Structure] | type[Array[Any]], ...]" in function "STDMETHOD"
    "list[type[_Pointer[GUID]] | type[_Pointer[c_void_p]]]" is not assignable to "tuple[type[_SimpleCData[Any]] | type[_Pointer[Any]] | type[CFuncPtr] | type[Union] | type[Structure] | type[Array[Any]], ...]" (reportArgumentType)
  ...
```

```
> uvx ty check comtypes/_post_coinit/unknwn.py
...
error[invalid-argument-type]: Argument to function `STDMETHOD` is incorrect
   --> comtypes\_post_coinit\unknwn.py:389:46
    |
389 |         STDMETHOD(HRESULT, "QueryInterface", [POINTER(GUID), POINTER(c_void_p)]),
    |                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Expected `tuple[type[_SimpleCData[Any] | _Pointer[Any] | CFuncPtr | ... omitted 3 types], ...]`, found `list[type[_Pointer[GUID] | _Pointer[c_void_p]]]`
info: Function defined here
   --> comtypes\_memberspec.py:168:5
    |
168 | def STDMETHOD(
    |     ^^^^^^^^^
169 |     restype: Optional[type["_CDataType"]],
170 |     name: str,
171 |     argtypes: tuple[type["_CDataType"], ...] = (),
    |     --------------------------------------------- Parameter declared here
...
```
…OD`.

- Added `_ResArgSpecElmType` alias representing argspec tuples with just flags
  and return type.
- Updated `ArgSpecElmType` to include `_ResArgSpecElmType` alongside positional
  and optional specs.
- This matches the usage in `automation.py`.
- Enables static type checkers to accept return‑value‑only argspecs without
  extra string description.

This change resolves the type-checker error below.
```
> uvx pyright comtypes/_post_coinit/unknwn.py
path\to\comtypes\comtypes\automation.py
  ...
  path\to\comtypes\comtypes\automation.py:778:52 - error: Argument of type "tuple[list[str], type[_Pointer[c_uint]]]" cannot be assigned to parameter "argspec" of type "ArgSpecElmType" in function "COMMETHOD"
    Type "tuple[list[str], type[_Pointer[c_uint]]]" is not assignable to type "ArgSpecElmType"
      "tuple[list[str], type[_Pointer[c_uint]]]" is not assignable to "tuple[list[str], type[_SimpleCData[Any]] | type[_Pointer[Any]] | type[CFuncPtr] | type[Union] | type[Structure] | type[Array[Any]], str]"
        Tuple size mismatch; expected 3 but received 2
      "tuple[list[str], type[_Pointer[c_uint]]]" is not assignable to "tuple[list[str], type[_SimpleCData[Any]] | type[_Pointer[Any]] | type[CFuncPtr] | type[Union] | type[Structure] | type[Array[Any]], str, Any]"
        Tuple size mismatch; expected 4 but received 2 (reportArgumentType)
  ...
```

```
> uvx ty check comtypes/automation.py
...
error[invalid-argument-type]: Argument to function `COMMETHOD` is incorrect
   --> comtypes\automation.py:778:52
    |
778 |         COMMETHOD([], HRESULT, "GetTypeInfoCount", (["out"], POINTER(UINT))),
    |                                                    ^^^^^^^^^^^^^^^^^^^^^^^^ Expected `tuple[list[str], type[_SimpleCData[Any] | _Pointer[Any] | CFuncPtr | ... omitted 3 types], str] | tuple[list[str], type[_SimpleCData[Any] | _Pointer[Any] | CFuncPtr | ... omitted 3 types], str, Any]`, found `tuple[list[str], type[_Pointer[c_uint]]]`
info: type `tuple[list[str], type[_Pointer[c_uint]]]` is not assignable to any element of the union `tuple[list[str], type[_SimpleCData[Any] | _Pointer[Any] | CFuncPtr | ... omitted 3 types], str] | tuple[list[str], type[_SimpleCData[Any] | _Pointer[Any] | CFuncPtr | ... omitted 3 types], str, Any]`
info: ├── a tuple of length 2 is not assignable to a tuple of length 3
info: └── a tuple of length 2 is not assignable to a tuple of length 4
info: Function defined here
   --> comtypes\_memberspec.py:205:5
    |
205 | def COMMETHOD(
    |     ^^^^^^^^^
206 |     idlflags: Sequence[_UnionT[int, str]],
207 |     restype: Optional[type["_CDataType"]],
208 |     methodname: str,
209 |     *argspec: "hints.ArgSpecElmType",
    |     -------------------------------- Parameter declared here
...
```
…THOD`, `DISPPROPERTY`, and `COMMETHOD`.

- Updated `DISPMETHOD` and `DISPPROPERTY` to call `tuple(idlflags)` with a
  `# type: ignore` comment because the incoming `idlflags` sequence may contain
  a mix of `int` and `str` (e.g., `(dispid, "readonly")`).
  The static type checker expects a fixed‑length tuple of either only strings
  or a `dispid` followed by strings, which does not match the runtime
  representation.
- Updated `COMMETHOD` similarly, adding `# type: ignore` to the
  `tuple(idlflags)` conversion.
- These changes keep the runtime behavior unchanged (the function still
  receives a tuple) while silencing type‑checker errors about the
  incompatibility of the input sequence type.
- The function signatures continue to accept a flexible `Sequence` for
  `idlflags`; the `type: ignore` acknowledges that the cast cannot be expressed
  precisely in static typing.

This change resolves the type-checker error below.
```
> uvx pyright comtypes/_memberspec.py
path\to\comtypes\comtypes\_memberspec.py
  ...
  path\to\comtypes\comtypes\_memberspec.py:184:48 - error: Argument of type "tuple[int | str, ...]" cannot be assigned to parameter "idlflags" of type "_DispIdlFlags" in function "__new__"
    Type "tuple[int | str, ...]" is not assignable to type "_DispIdlFlags"
      "tuple[int | str, ...]" is not assignable to "tuple[dispid, helpstring, *tuple[str, ...]]"
        Tuple size mismatch; expected 2 or more but received indeterminate
      "tuple[int | str, ...]" is not assignable to "tuple[dispid, *tuple[str, ...]]"
        Tuple size mismatch; expected 1 or more but received indeterminate (reportArgumentType)
  path\to\comtypes\comtypes\_memberspec.py:193:50 - error: Argument of type "tuple[int | str, ...]" cannot be assigned to parameter "idlflags" of type "_DispIdlFlags" in function "__new__"
    Type "tuple[int | str, ...]" is not assignable to type "_DispIdlFlags"
      "tuple[int | str, ...]" is not assignable to "tuple[dispid, helpstring, *tuple[str, ...]]"
        Tuple size mismatch; expected 2 or more but received indeterminate
      "tuple[int | str, ...]" is not assignable to "tuple[dispid, *tuple[str, ...]]"
        Tuple size mismatch; expected 1 or more but received indeterminate (reportArgumentType)
  path\to\comtypes\comtypes\_memberspec.py:229:46 - error: Argument of type "tuple[int | str, ...]" cannot be assigned to parameter "idlflags" of type "_ComIdlFlags | _DispIdlFlags" in function "__new__"
    Type "tuple[int | str, ...]" is not assignable to type "_ComIdlFlags | _DispIdlFlags"
      "tuple[int | str, ...]" is not assignable to "tuple[str, ...]"
        Tuple entry 1 is incorrect type
          Type "int | str" is not assignable to type "str"
            "int" is not assignable to "str"
      "tuple[int | str, ...]" is not assignable to "tuple[helpstring, *tuple[str, ...]]"
        Tuple size mismatch; expected 1 or more but received indeterminate
      "tuple[int | str, ...]" is not assignable to "tuple[dispid, helpstring, *tuple[str, ...]]"
    ... (reportArgumentType)
  ...
```

```
> uvx ty check comtypes/_memberspec.py
...
error[invalid-argument-type]: Argument is incorrect
   --> comtypes\_memberspec.py:184:48
    |
184 |     return _DispMemberSpec("DISPMETHOD", name, tuple(idlflags), restype, argspec)
    |                                                ^^^^^^^^^^^^^^^ Expected `tuple[dispid, *tuple[str, ...]]`, found `tuple[int | str, ...]`

error[invalid-argument-type]: Argument is incorrect
   --> comtypes\_memberspec.py:193:50
    |
193 |     return _DispMemberSpec("DISPPROPERTY", name, tuple(idlflags), proptype, ())
    |                                                  ^^^^^^^^^^^^^^^ Expected `tuple[dispid, *tuple[str, ...]]`, found `tuple[int | str, ...]`

error[invalid-argument-type]: Argument is incorrect
   --> comtypes\_memberspec.py:229:46
    |
229 |         restype, name, argtypes, paramflags, tuple(idlflags), helptext
    |                                              ^^^^^^^^^^^^^^^ Expected `tuple[str, ...] | tuple[dispid, *tuple[str, ...]]`, found `tuple[int | str, ...]`
info: type `tuple[int | str, ...]` is not assignable to any element of the union `tuple[str, ...] | tuple[dispid, *tuple[str, ...]]`
info: ├── element `int` of union `int | str` is not assignable to `str`
info: └── ... omitted 1 union element without additional context
...
```
…HOD`, `DISPMETHOD`, `DISPPROPERTY`, `COMMETHOD`) in `_memberspec`.
…type` (type["_CDataType"]).

This reflects that a dispinterface property never returns `null`/`void`, so `None` is never a valid `proptype`.
@junkmd junkmd added this to the 1.5.0 / Support Python 3.15 milestone Oct 3, 2026
@junkmd junkmd added tests enhance or fix tests shared_info use cases, tips and troubleshoots typing related to Python static typing system labels Oct 3, 2026
@codecov-commenter

codecov-commenter commented Oct 3, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.98%. Comparing base (3b9f5ff) to head (cbb81df).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #954   +/-   ##
=======================================
  Coverage   88.98%   88.98%           
=======================================
  Files         140      140           
  Lines       13678    13678           
=======================================
  Hits        12171    12171           
  Misses       1507     1507           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@junkmd
junkmd merged commit 546ee51 into enthought:main Oct 3, 2026
65 checks passed
@junkmd
junkmd deleted the fix_memberspec_type_annotations branch October 3, 2026 16:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

shared_info use cases, tips and troubleshoots tests enhance or fix tests typing related to Python static typing system

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants