From fba684fc9a3cbab77c7628acf725aea222976988 Mon Sep 17 00:00:00 2001 From: junkmd Date: Fri, 23 Oct 2026 09:27:41 +0900 Subject: [PATCH 1/6] generate: Refine type annotation for `item` parameter in `CodeGenerator.generate`. --- comtypes/tools/codegenerator/codegenerator.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/comtypes/tools/codegenerator/codegenerator.py b/comtypes/tools/codegenerator/codegenerator.py index a7172fc1..bdfe3627 100644 --- a/comtypes/tools/codegenerator/codegenerator.py +++ b/comtypes/tools/codegenerator/codegenerator.py @@ -85,7 +85,20 @@ def adjust_blank( yield self.stream self.last_item = item - def generate(self, item: Any) -> None: + def generate( + self, + item: _UnionT[ + tlbparser.ParsedType, + tlbparser.SpecType, + typedesc.EnumValue, + typedesc.StructureHead, + typedesc.StructureBody, + typedesc.ComInterfaceHead, + typedesc.DispInterfaceHead, + typedesc.ComInterfaceBody, + typedesc.DispInterfaceBody, + ], + ) -> None: if item in self.done: return if self._is_interface_typedesc(item): From ed27cb780b989ce90588b1a7ed0b1a53b116215c Mon Sep 17 00:00:00 2001 From: junkmd Date: Fri, 23 Oct 2026 09:27:41 +0900 Subject: [PATCH 2/6] refactor: Narrow types before retrieving `name` in `CodeGenerator.generate`. Use type narrowing after `generate` receives an item so `name` can be retrieved safely without relying on `getattr`. --- comtypes/tools/codegenerator/codegenerator.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/comtypes/tools/codegenerator/codegenerator.py b/comtypes/tools/codegenerator/codegenerator.py index bdfe3627..1b6a163c 100644 --- a/comtypes/tools/codegenerator/codegenerator.py +++ b/comtypes/tools/codegenerator/codegenerator.py @@ -105,10 +105,21 @@ def generate( self._define_interface(item) return if isinstance(item, typedesc.StructureHead): - name = getattr(item.struct, "name", None) + name = item.struct.name + elif isinstance( + item, + ( + typedesc.External, + typedesc.PointerType, + typedesc.ArrayType, + typedesc.SAFEARRAYType, + typedesc.StructureBody, + ), + ): + name = None else: - name = getattr(item, "name", None) - if name in self.known_symbols: + name = item.name + if name is not None and name in self.known_symbols: self.imports.add(name, symbols=self.known_symbols) self.done.add(item) if isinstance(item, typedesc.Structure): From 3d2d9a2c0673b36c9a92c3e1d28bd45232182c5e Mon Sep 17 00:00:00 2001 From: junkmd Date: Fri, 23 Oct 2026 09:27:41 +0900 Subject: [PATCH 3/6] refactor: Dispatch generated types explicitly in `CodeGenerator` Use explicit type checks for types and container element types that `tlbparser` can produce instead of dynamically retrieving type names and calling methods via `getattr`, improving type safety. Confirm that paths reaching an error without calling any method are inferred as `Never`. --- comtypes/tools/codegenerator/codegenerator.py | 50 +++++++++++++++++-- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/comtypes/tools/codegenerator/codegenerator.py b/comtypes/tools/codegenerator/codegenerator.py index 1b6a163c..be559594 100644 --- a/comtypes/tools/codegenerator/codegenerator.py +++ b/comtypes/tools/codegenerator/codegenerator.py @@ -126,11 +126,41 @@ def generate( self.done.add(item.get_head()) self.done.add(item.get_body()) return - mth = getattr(self, type(item).__name__) # to avoid infinite recursion, we have to mark it as done # before actually generating the code. self.done.add(item) - mth(item) + if isinstance(item, typedesc.ArrayType): + self.ArrayType(item) + elif isinstance(item, typedesc.EnumValue): + self.EnumValue(item) + elif isinstance(item, typedesc.Enumeration): + self.Enumeration(item) + elif isinstance(item, typedesc.Typedef): + self.Typedef(item) + elif isinstance(item, typedesc.FundamentalType): + self.FundamentalType(item) + elif isinstance(item, typedesc.StructureHead): + self.StructureHead(item) + elif isinstance(item, typedesc.Structure): + self.Structure(item) + elif isinstance(item, typedesc.Union): + self.Union(item) + elif isinstance(item, typedesc.StructureBody): + self.StructureBody(item) + elif isinstance(item, typedesc.TypeLib): + self.TypeLib(item) + elif isinstance(item, typedesc.External): + self.External(item) + elif isinstance(item, typedesc.Constant): + self.Constant(item) + elif isinstance(item, typedesc.SAFEARRAYType): + self.SAFEARRAYType(item) + elif isinstance(item, typedesc.PointerType): + self.PointerType(item) + elif isinstance(item, typedesc.CoClass): + self.CoClass(item) + else: + raise NotImplementedError(f"Unsupported: {type(item).__name__!r}") def generate_all(self, items: Iterable[tlbparser.ParsedType]) -> None: for item in items: @@ -634,8 +664,20 @@ def _define_interface(self, item: _InterfaceTypeDesc) -> None: else: raise TypeError self.done.add(item) # to avoid infinite recursion. - mth = getattr(self, type(item).__name__) - mth(item) + if isinstance(item, typedesc.ComInterface): + self.ComInterface(item) + elif isinstance(item, typedesc.ComInterfaceHead): + self.ComInterfaceHead(item) + elif isinstance(item, typedesc.ComInterfaceBody): + self.ComInterfaceBody(item) + elif isinstance(item, typedesc.DispInterface): + self.DispInterface(item) + elif isinstance(item, typedesc.DispInterfaceHead): + self.DispInterfaceHead(item) + elif isinstance(item, typedesc.DispInterfaceBody): + self.DispInterfaceBody(item) + else: + raise TypeError def ComInterface(self, itf: typedesc.ComInterface) -> None: self.generate(itf.get_head()) From 86630b185930495b7d75a09b7b973bc8d2fad5bb Mon Sep 17 00:00:00 2001 From: junkmd Date: Fri, 23 Oct 2026 09:27:41 +0900 Subject: [PATCH 4/6] refactor: Centralize type dispatch in `CodeGenerator`. Share the type-based dispatch logic between `generate` and `_define_interface` so all supported type descriptions use the same generation path. --- comtypes/tools/codegenerator/codegenerator.py | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/comtypes/tools/codegenerator/codegenerator.py b/comtypes/tools/codegenerator/codegenerator.py index be559594..07bd7a05 100644 --- a/comtypes/tools/codegenerator/codegenerator.py +++ b/comtypes/tools/codegenerator/codegenerator.py @@ -129,6 +129,9 @@ def generate( # to avoid infinite recursion, we have to mark it as done # before actually generating the code. self.done.add(item) + self._dispatch_by(item) + + def _dispatch_by(self, item: Any) -> None: if isinstance(item, typedesc.ArrayType): self.ArrayType(item) elif isinstance(item, typedesc.EnumValue): @@ -159,8 +162,20 @@ def generate( self.PointerType(item) elif isinstance(item, typedesc.CoClass): self.CoClass(item) + elif isinstance(item, typedesc.ComInterface): + self.ComInterface(item) + elif isinstance(item, typedesc.ComInterfaceHead): + self.ComInterfaceHead(item) + elif isinstance(item, typedesc.ComInterfaceBody): + self.ComInterfaceBody(item) + elif isinstance(item, typedesc.DispInterface): + self.DispInterface(item) + elif isinstance(item, typedesc.DispInterfaceHead): + self.DispInterfaceHead(item) + elif isinstance(item, typedesc.DispInterfaceBody): + self.DispInterfaceBody(item) else: - raise NotImplementedError(f"Unsupported: {type(item).__name__!r}") + raise TypeError(f"Unsupported: {type(item).__name__!r}") def generate_all(self, items: Iterable[tlbparser.ParsedType]) -> None: for item in items: @@ -664,20 +679,7 @@ def _define_interface(self, item: _InterfaceTypeDesc) -> None: else: raise TypeError self.done.add(item) # to avoid infinite recursion. - if isinstance(item, typedesc.ComInterface): - self.ComInterface(item) - elif isinstance(item, typedesc.ComInterfaceHead): - self.ComInterfaceHead(item) - elif isinstance(item, typedesc.ComInterfaceBody): - self.ComInterfaceBody(item) - elif isinstance(item, typedesc.DispInterface): - self.DispInterface(item) - elif isinstance(item, typedesc.DispInterfaceHead): - self.DispInterfaceHead(item) - elif isinstance(item, typedesc.DispInterfaceBody): - self.DispInterfaceBody(item) - else: - raise TypeError + self._dispatch_by(item) def ComInterface(self, itf: typedesc.ComInterface) -> None: self.generate(itf.get_head()) From 8a445c3f9b1d77061e3552319ac66cef39fe3953 Mon Sep 17 00:00:00 2001 From: junkmd Date: Fri, 23 Oct 2026 09:27:41 +0900 Subject: [PATCH 5/6] refactor: Use `isinstance` for `typedesc` dispatch Replace exact `type(...)` comparisons in `CodeGenerator` with `isinstance` checks. All `typedesc` produced by `tlbparser` and used by `CodeGenerator` are marked with `typing.final`, so the accepted concrete types cannot have subclasses. This preserves the previous exact-type guarantee while allowing `isinstance` to avoid E721 violations. Types that `tlbparser` does not produce, including skipped function-definition types, are intentionally not marked `final`. --- comtypes/tools/codegenerator/codegenerator.py | 16 ++++++++-------- comtypes/tools/typedesc.py | 16 +++++++++++++++- comtypes/tools/typedesc_base.py | 15 ++++++++++++++- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/comtypes/tools/codegenerator/codegenerator.py b/comtypes/tools/codegenerator/codegenerator.py index 07bd7a05..b8f44961 100644 --- a/comtypes/tools/codegenerator/codegenerator.py +++ b/comtypes/tools/codegenerator/codegenerator.py @@ -451,16 +451,16 @@ def StructureBody(self, body: typedesc.StructureBody) -> None: fields: list[typedesc.Field] = [] methods: list[typedesc.Method] = [] for m in body.struct.members: - if type(m) is typedesc.Field: + if isinstance(m, typedesc.Field): fields.append(m) - if type(m.typ) is typedesc.Typedef: + if isinstance(m.typ, typedesc.Typedef): self.generate(get_real_type(m.typ)) self.generate(m.typ) - elif type(m) is typedesc.Method: + elif isinstance(m, typedesc.Method): methods.append(m) self.generate(m.returns) self.generate_all(m.iterArgTypes()) - elif type(m) is typedesc.Constructor: + elif isinstance(m, typedesc.Constructor): pass # we don't need _pack_ on Unions (I hope, at least), and not @@ -597,17 +597,17 @@ def SAFEARRAYType(self, sa: typedesc.SAFEARRAYType) -> None: self.imports.add("comtypes.automation", "_midlSAFEARRAY") def PointerType(self, tp: typedesc.PointerType) -> None: - if type(tp.typ) is typedesc.ComInterface: + if isinstance(tp.typ, typedesc.ComInterface): # this defines the class self.generate(tp.typ.get_head()) # this defines the _methods_ self.more.add(tp.typ) - elif type(tp.typ) is typedesc.PointerType: + elif isinstance(tp.typ, typedesc.PointerType): self.generate(tp.typ) - elif type(tp.typ) in (typedesc.Union, typedesc.Structure): + elif isinstance(tp.typ, (typedesc.Union, typedesc.Structure)): self.generate(tp.typ.get_head()) self.more.add(tp.typ) - elif type(tp.typ) is typedesc.Typedef: + elif isinstance(tp.typ, typedesc.Typedef): self.generate(tp.typ) else: self.generate(tp.typ) diff --git a/comtypes/tools/typedesc.py b/comtypes/tools/typedesc.py index 5a218822..70121ac8 100644 --- a/comtypes/tools/typedesc.py +++ b/comtypes/tools/typedesc.py @@ -3,7 +3,7 @@ import ctypes from collections.abc import Sequence -from typing import Any, Generic, Optional, TypeVar +from typing import Any, Generic, Optional, TypeVar, final from typing import Union as _UnionT from comtypes import typeinfo @@ -34,6 +34,7 @@ _TD = TypeVar("_TD") +@final class TypeLib: def __init__( self, name: str, guid: str, major: int, minor: int, doc: Optional[str] = None @@ -48,6 +49,7 @@ def __repr__(self): return f"" +@final class Constant: def __init__( self, @@ -62,6 +64,7 @@ def __init__( self.doc = doc +@final class External: def __init__( self, @@ -85,12 +88,14 @@ def get_head(self) -> "External": return self +@final class SAFEARRAYType(Generic[_TD]): def __init__(self, typ: _TD) -> None: self.typ = typ self.align = self.size = ctypes.sizeof(ctypes.c_void_p) * 8 +@final class ComMethod: # custom COM method, parsed from typelib def __init__( @@ -116,6 +121,7 @@ def add_argument( self.arguments.append((typ, name, idlflags, default)) +@final class DispMethod: # dispatchable COM method, parsed from typelib def __init__( @@ -141,6 +147,7 @@ def add_argument( self.arguments.append((typ, name, idlflags, default)) +@final class DispProperty: # dispatchable COM property, parsed from typelib def __init__( @@ -153,16 +160,19 @@ def __init__( self.doc = doc +@final class DispInterfaceHead: def __init__(self, itf: "DispInterface") -> None: self.itf = itf +@final class DispInterfaceBody: def __init__(self, itf: "DispInterface") -> None: self.itf = itf +@final class DispInterface: def __init__( self, @@ -191,16 +201,19 @@ def get_head(self) -> DispInterfaceHead: return self.itf_head +@final class ComInterfaceHead: def __init__(self, itf: "ComInterface") -> None: self.itf = itf +@final class ComInterfaceBody: def __init__(self, itf: "ComInterface") -> None: self.itf = itf +@final class ComInterface: def __init__( self, @@ -233,6 +246,7 @@ def get_head(self) -> ComInterfaceHead: _Interface = _UnionT[ComInterface, DispInterface] +@final class CoClass: def __init__( self, diff --git a/comtypes/tools/typedesc_base.py b/comtypes/tools/typedesc_base.py index 2fd6797a..1cd1352a 100644 --- a/comtypes/tools/typedesc_base.py +++ b/comtypes/tools/typedesc_base.py @@ -1,5 +1,5 @@ # typedesc.py - classes representing C type descriptions -from typing import Any, Generic, Optional, SupportsInt, TypeVar +from typing import Any, Generic, Optional, SupportsInt, TypeVar, final from typing import Union as _UnionT _TD = TypeVar("_TD") @@ -72,6 +72,7 @@ def __init__(self, name, returns, attributes, extern): self.extern = extern +@final class Constructor(_HasArgs): location = None @@ -98,6 +99,7 @@ def __init__(self, returns, attributes): self.attributes = attributes +@final class Method(_HasArgs): location = None @@ -107,6 +109,7 @@ def __init__(self, name, returns): self.returns = returns +@final class FundamentalType: location = None @@ -116,6 +119,7 @@ def __init__(self, name: str, size: int, align: int) -> None: self.align = int(align) +@final class PointerType(Generic[_TD]): location = None @@ -125,6 +129,7 @@ def __init__(self, typ: _TD, size: int, align: int) -> None: self.align = int(align) +@final class Typedef(Generic[_TD]): location = None @@ -133,6 +138,7 @@ def __init__(self, name: str, typ: _TD) -> None: self.typ = typ +@final class ArrayType(Generic[_TD]): location = None @@ -142,6 +148,7 @@ def __init__(self, typ: _TD, min: int, max: int) -> None: self.max = max +@final class StructureHead: location = None @@ -149,6 +156,7 @@ def __init__(self, struct: "_Struct_Union_Base") -> None: self.struct = struct +@final class StructureBody: location = None @@ -178,6 +186,7 @@ def get_head(self) -> StructureHead: return self.struct_head +@final class Structure(_Struct_Union_Base): def __init__( self, @@ -200,6 +209,7 @@ def __init__( super().__init__() +@final class Union(_Struct_Union_Base): def __init__( self, @@ -222,6 +232,7 @@ def __init__( super().__init__() +@final class Field: def __init__( self, name: str, typ: Any, bits: Optional[Any], offset: SupportsInt @@ -239,6 +250,7 @@ def __init__(self, typ, const, volatile): self.volatile = volatile +@final class Enumeration: location = None @@ -252,6 +264,7 @@ def add_value(self, v: "EnumValue") -> None: self.values.append(v) +@final class EnumValue: def __init__(self, name: str, value: int, enumeration: Enumeration) -> None: self.name = name From 3cf2e3cd64b24a1c3d9e19c0de4bec2ae252616c Mon Sep 17 00:00:00 2001 From: junkmd Date: Fri, 23 Oct 2026 09:27:42 +0900 Subject: [PATCH 6/6] refactor: Model `IUnknown` and `IDispatch` interfaces in `tlbparser` Define `IUnknown` and `IDispatch` with the practical COM interface members needed by their use cases, while keeping the models intentionally simplified instead of reproducing every detail from `stdole`. Remove the `Typedef[None]` alias and related special handling now that `Typedef` no longer contains `None`. --- comtypes/tools/tlbparser.py | 40 +++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/comtypes/tools/tlbparser.py b/comtypes/tools/tlbparser.py index 74745766..123b9838 100644 --- a/comtypes/tools/tlbparser.py +++ b/comtypes/tools/tlbparser.py @@ -4,7 +4,7 @@ from collections.abc import Mapping from ctypes import alignment, c_void_p, sizeof, windll from ctypes.wintypes import MAX_PATH -from typing import Any, Optional, TypeVar, overload +from typing import Any, Optional, TypeVar from typing import Union as _UnionT from comtypes import automation, typeinfo @@ -16,14 +16,9 @@ ################################ _PT = TypeVar("_PT", bound=_UnionT["ParsedType", "SpecType"]) -_TypeDefNone = typedesc.Typedef[None] -@overload -def PTR(typ: _PT) -> typedesc.PointerType[_PT]: ... -@overload -def PTR(typ: _TypeDefNone) -> typedesc.PointerType[_TypeDefNone]: ... -def PTR(typ): +def PTR(typ: _PT) -> typedesc.PointerType[_PT]: return typedesc.PointerType(typ, sizeof(c_void_p) * 8, alignment(c_void_p) * 8) @@ -57,8 +52,34 @@ def PTR(typ): bases=[], size=sizeof(automation.VARIANT) * 8, ) -IDISPATCH_type = typedesc.Typedef("IDispatch", None) -IUNKNOWN_type = typedesc.Typedef("IUnknown", None) + +# These are simplified definitions of `IUnknown` and `IDispatch` from `stdole`. +# Their full definitions include method arguments and IDL flags. However, these +# basic interfaces are replaced with suitable definitions for each use case, so +# they do not need to be modeled perfectly here. Even a complete model would not +# be used, and adding more redundant fields would make maintenance harder. +IUNKNOWN_type = typedesc.ComInterface( + "IUnknown", None, "{00000000-0000-0000-C000-000000000046}", [], None +) +IUNKNOWN_type.extend_members( + [ + typedesc.ComMethod(1, 1610612736, "QueryInterface", HRESULT_type, [], None), + typedesc.ComMethod(1, 1610612737, "AddRef", ulong_type, [], None), + typedesc.ComMethod(1, 1610612738, "Release", ulong_type, [], None), + ] +) +IDISPATCH_type = typedesc.ComInterface( + "IDispatch", IUNKNOWN_type, "{00020400-0000-0000-C000-000000000046}", [], None +) +IDISPATCH_type.extend_members( + [ + typedesc.ComMethod(1, 1610678272, "GetTypeInfoCount", HRESULT_type, [], None), + typedesc.ComMethod(1, 1610678273, "GetTypeInfo", HRESULT_type, [], None), + typedesc.ComMethod(1, 1610678274, "GetIDsOfNames", HRESULT_type, [], None), + typedesc.ComMethod(1, 1610678275, "Invoke", HRESULT_type, [], None), + ] +) + DECIMAL_type = typedesc.Structure( "DECIMAL", align=alignment(automation.DECIMAL) * 8, @@ -123,7 +144,6 @@ def midlSAFEARRAY(typ: _PT) -> typedesc.SAFEARRAYType[_PT]: typedesc.ComInterface, typedesc.DispInterface, typedesc.Typedef["_UnionT[ParsedType, SpecType]"], - _TypeDefNone, typedesc.Structure, typedesc.Union, typedesc.Enumeration,