Skip to content

Conversation

@pmhahn
Copy link
Contributor

@pmhahn pmhahn commented Mar 19, 2025

This is the mayor PR to add Python type hints #514 – without #609 this will not be complete as elftools.construct.Container is used in many places, which is a container for Anything: retrieving values from it will be typed Any, which basically means untyped: without manually type-hinting every such use case those values do propagate further and even spill into the public API.

Because of missing 6f99ce0 running mypy will find the following errors:

elftools/dwarf/structs.py:581: error: "Container" has no attribute "first"  [attr-defined]
elftools/dwarf/structs.py:583: error: "Container" has no attribute "first"  [attr-defined]
elftools/dwarf/structs.py:585: error: "Container" has no attribute "first"  [attr-defined]
elftools/dwarf/structs.py:587: error: "Container" has no attribute "second"  [attr-defined]
elftools/dwarf/structs.py:590: error: "Container" has no attribute "first"  [attr-defined]
elftools/dwarf/callframe.py:99: error: "Container" has no attribute "length"  [attr-defined]
elftools/dwarf/ranges.py:41: error: "Container" has no attribute "start_index"  [attr-defined]
elftools/dwarf/ranges.py:42: error: "Container" has no attribute "entry_offset"  [attr-defined]
elftools/dwarf/ranges.py:42: error: "Container" has no attribute "entry_length"  [attr-defined]
elftools/dwarf/ranges.py:42: error: "Container" has no attribute "length"  [attr-defined]
elftools/dwarf/ranges.py:46: error: "Container" has no attribute "entry_offset"  [attr-defined]
elftools/dwarf/ranges.py:46: error: "Container" has no attribute "address"  [attr-defined]
elftools/dwarf/ranges.py:47: error: "Container" has no attribute "entry_offset"  [attr-defined]
elftools/dwarf/ranges.py:47: error: "Container" has no attribute "entry_length"  [attr-defined]
elftools/dwarf/ranges.py:47: error: "Container" has no attribute "start_offset"  [attr-defined]
elftools/dwarf/ranges.py:47: error: "Container" has no attribute "end_offset"  [attr-defined]
elftools/dwarf/ranges.py:48: error: "Container" has no attribute "entry_offset"  [attr-defined]
elftools/dwarf/ranges.py:48: error: "Container" has no attribute "entry_length"  [attr-defined]
elftools/dwarf/ranges.py:48: error: "Container" has no attribute "start_address"  [attr-defined]
elftools/dwarf/ranges.py:48: error: "Container" has no attribute "end_address"  [attr-defined]
elftools/dwarf/ranges.py:49: error: "Container" has no attribute "entry_offset"  [attr-defined]
elftools/dwarf/ranges.py:49: error: "Container" has no attribute "entry_length"  [attr-defined]
elftools/dwarf/ranges.py:49: error: "Container" has no attribute "start_address"  [attr-defined]
elftools/dwarf/ranges.py:49: error: "Container" has no attribute "length"  [attr-defined]
elftools/dwarf/ranges.py:50: error: "Container" has no attribute "entry_offset"  [attr-defined]
elftools/dwarf/ranges.py:50: error: "Container" has no attribute "index"  [attr-defined]
elftools/dwarf/ranges.py:51: error: "Container" has no attribute "entry_offset"  [attr-defined]
elftools/dwarf/ranges.py:51: error: "Container" has no attribute "entry_length"  [attr-defined]
elftools/dwarf/ranges.py:51: error: "Container" has no attribute "start_index"  [attr-defined]
elftools/dwarf/ranges.py:51: error: "Container" has no attribute "end_index"  [attr-defined]
elftools/dwarf/ranges.py:70: error: "Container" has no attribute "version"  [attr-defined]
elftools/dwarf/ranges.py:180: error: "Container" has no attribute "offset_table_offset"  [attr-defined]
elftools/dwarf/ranges.py:180: error: "Container" has no attribute "is64"  [attr-defined]
elftools/dwarf/ranges.py:180: error: "Container" has no attribute "offset_count"  [attr-defined]
elftools/dwarf/ranges.py:181: error: "Container" has no attribute "offset_after_length"  [attr-defined]
elftools/dwarf/ranges.py:181: error: "Container" has no attribute "unit_length"  [attr-defined]
elftools/dwarf/ranges.py:188: error: "Container" has no attribute "entry_type"  [attr-defined]
elftools/dwarf/locationlists.py:53: error: "Container" has no attribute "start_index"  [attr-defined]
elftools/dwarf/locationlists.py:54: error: "Container" has no attribute "entry_offset"  [attr-defined]
elftools/dwarf/locationlists.py:54: error: "Container" has no attribute "entry_length"  [attr-defined]
elftools/dwarf/locationlists.py:54: error: "Container" has no attribute "length"  [attr-defined]
elftools/dwarf/locationlists.py:54: error: "Container" has no attribute "loc_expr"  [attr-defined]
elftools/dwarf/locationlists.py:58: error: "Container" has no attribute "entry_offset"  [attr-defined]
elftools/dwarf/locationlists.py:58: error: "Container" has no attribute "entry_length"  [attr-defined]
elftools/dwarf/locationlists.py:58: error: "Container" has no attribute "address"  [attr-defined]
elftools/dwarf/locationlists.py:59: error: "Container" has no attribute "entry_offset"  [attr-defined]
elftools/dwarf/locationlists.py:59: error: "Container" has no attribute "entry_length"  [attr-defined]
elftools/dwarf/locationlists.py:59: error: "Container" has no attribute "start_offset"  [attr-defined]
elftools/dwarf/locationlists.py:59: error: "Container" has no attribute "end_offset"  [attr-defined]
elftools/dwarf/locationlists.py:59: error: "Container" has no attribute "loc_expr"  [attr-defined]
elftools/dwarf/locationlists.py:60: error: "Container" has no attribute "entry_offset"  [attr-defined]
elftools/dwarf/locationlists.py:60: error: "Container" has no attribute "entry_length"  [attr-defined]
elftools/dwarf/locationlists.py:60: error: "Container" has no attribute "start_address"  [attr-defined]
elftools/dwarf/locationlists.py:60: error: "Container" has no attribute "length"  [attr-defined]
elftools/dwarf/locationlists.py:60: error: "Container" has no attribute "loc_expr"  [attr-defined]
elftools/dwarf/locationlists.py:61: error: "Container" has no attribute "entry_offset"  [attr-defined]
elftools/dwarf/locationlists.py:61: error: "Container" has no attribute "entry_length"  [attr-defined]
elftools/dwarf/locationlists.py:61: error: "Container" has no attribute "start_address"  [attr-defined]
elftools/dwarf/locationlists.py:61: error: "Container" has no attribute "end_address"  [attr-defined]
elftools/dwarf/locationlists.py:61: error: "Container" has no attribute "loc_expr"  [attr-defined]
elftools/dwarf/locationlists.py:62: error: "Container" has no attribute "entry_offset"  [attr-defined]
elftools/dwarf/locationlists.py:62: error: "Container" has no attribute "entry_length"  [attr-defined]
elftools/dwarf/locationlists.py:62: error: "Container" has no attribute "loc_expr"  [attr-defined]
elftools/dwarf/locationlists.py:63: error: "Container" has no attribute "entry_offset"  [attr-defined]
elftools/dwarf/locationlists.py:63: error: "Container" has no attribute "entry_length"  [attr-defined]
elftools/dwarf/locationlists.py:63: error: "Container" has no attribute "index"  [attr-defined]
elftools/dwarf/locationlists.py:64: error: "Container" has no attribute "entry_offset"  [attr-defined]
elftools/dwarf/locationlists.py:64: error: "Container" has no attribute "entry_length"  [attr-defined]
elftools/dwarf/locationlists.py:64: error: "Container" has no attribute "start_index"  [attr-defined]
elftools/dwarf/locationlists.py:64: error: "Container" has no attribute "end_index"  [attr-defined]
elftools/dwarf/locationlists.py:64: error: "Container" has no attribute "loc_expr"  [attr-defined]
elftools/dwarf/locationlists.py:81: error: "Container" has no attribute "version"  [attr-defined]
elftools/dwarf/locationlists.py:222: error: "Container" has no attribute "version"  [attr-defined]
elftools/dwarf/locationlists.py:286: error: "Container" has no attribute "entry_offset"  [attr-defined]
elftools/dwarf/locationlists.py:287: error: "Container" has no attribute "entry_end_offset"  [attr-defined]
elftools/dwarf/locationlists.py:288: error: "Container" has no attribute "entry_type"  [attr-defined]
elftools/dwarf/locationlists.py:290: error: "Container" has no attribute "address"  [attr-defined]
elftools/dwarf/locationlists.py:292: error: "Container" has no attribute "start_offset"  [attr-defined]
elftools/dwarf/locationlists.py:292: error: "Container" has no attribute "end_offset"  [attr-defined]
elftools/dwarf/locationlists.py:292: error: "Container" has no attribute "loc_expr"  [attr-defined]
elftools/dwarf/locationlists.py:294: error: "Container" has no attribute "start_address"  [attr-defined]
elftools/dwarf/locationlists.py:294: error: "Container" has no attribute "length"  [attr-defined]
elftools/dwarf/locationlists.py:294: error: "Container" has no attribute "loc_expr"  [attr-defined]
elftools/dwarf/locationlists.py:296: error: "Container" has no attribute "start_address"  [attr-defined]
elftools/dwarf/locationlists.py:296: error: "Container" has no attribute "end_address"  [attr-defined]
elftools/dwarf/locationlists.py:296: error: "Container" has no attribute "loc_expr"  [attr-defined]
elftools/dwarf/locationlists.py:298: error: "Container" has no attribute "loc_expr"  [attr-defined]
elftools/dwarf/dwarfinfo.py:478: error: "Container" has no attribute "address_size"  [attr-defined]
elftools/elf/structs.py:429: error: "Container" has no attribute "pr_datasz"  [attr-defined]
elftools/elf/structs.py:430: error: "Container" has no attribute "pr_datasz"  [attr-defined]
elftools/elf/structs.py:433: error: "Container" has no attribute "pr_type"  [attr-defined]
elftools/elf/structs.py:435: error: "Container" has no attribute "pr_type"  [attr-defined]
elftools/elf/structs.py:437: error: "Container" has no attribute "pr_type"  [attr-defined]
elftools/elf/structs.py:439: error: "Container" has no attribute "pr_type"  [attr-defined]
elftools/elf/structs.py:441: error: "Container" has no attribute "pr_type"  [attr-defined]
elftools/elf/structs.py:441: error: "Container" has no attribute "pr_datasz"  [attr-defined]
elftools/elf/notes.py:71: error: "Container" has no attribute "pr_datasz"  [attr-defined]
elftools/elf/dynamic.py:67: error: "Container" has no attribute "d_tag"  [attr-defined]
elftools/elf/dynamic.py:68: error: "Container" has no attribute "d_tag"  [attr-defined]
elftools/elf/dynamic.py:69: error: "Container" has no attribute "d_val"  [attr-defined]
elftools/elf/dynamic.py:77: error: "Container" has no attribute "d_tag"  [attr-defined]
elftools/elf/dynamic.py:80: error: "Container" has no attribute "d_tag"  [attr-defined]
elftools/elf/dynamic.py:81: error: "Container" has no attribute "d_tag"  [attr-defined]
elftools/elf/dynamic.py:83: error: "Container" has no attribute "d_ptr"  [attr-defined]
elftools/elf/dynamic.py:84: error: "Container" has no attribute "d_tag"  [attr-defined]
elftools/elf/dynamic.py:203: error: "Container" has no attribute "d_tag"  [attr-defined]
elftools/elf/elffile.py:152: error: "Container" has no attribute "sh_type"  [attr-defined]
elftools/elf/elffile.py:306: error: "Container" has no attribute "sh_offset"  [attr-defined]
elftools/elf/elffile.py:397: error: "Container" has no attribute "sh_offset"  [attr-defined]
elftools/elf/descriptions.py:59: error: "Container" has no attribute "d_val"  [attr-defined]
elftools/elf/descriptions.py:300: error: "Container" has no attribute "pr_type"  [attr-defined]
elftools/elf/descriptions.py:301: error: "Container" has no attribute "pr_data"  [attr-defined]
elftools/elf/descriptions.py:302: error: "Container" has no attribute "pr_datasz"  [attr-defined]

Similar missing db4fb21 is responsible for

elftools/elf/enums.py:37: error: Dict entry 2 has incompatible type "str": "type[Pass]"; expected "str": "int"  [dict-item]
elftools/elf/enums.py:64: error: Dict entry 22 has incompatible type "str": "type[Pass]"; expected "str": "int"  [dict-item]
elftools/elf/enums.py:76: error: Dict entry 7 has incompatible type "str": "type[Pass]"; expected "str": "int"  [dict-item]
elftools/elf/enums.py:278: error: Dict entry 187 has incompatible type "str": "type[Pass]"; expected "str": "int"  [dict-item]
elftools/elf/enums.py:324: error: Dict entry 30 has incompatible type "str": "type[Pass]"; expected "str": "int"  [dict-item]
elftools/elf/enums.py:393: error: Dict entry 5 has incompatible type "str": "type[Pass]"; expected "str": "int"  [dict-item]
elftools/elf/enums.py:421: error: Dict entry 14 has incompatible type "str": "type[Pass]"; expected "str": "int"  [dict-item]
elftools/elf/enums.py:454: error: Dict entry 8 has incompatible type "str": "type[Pass]"; expected "str": "int"  [dict-item]
elftools/elf/enums.py:473: error: Dict entry 14 has incompatible type "str": "type[Pass]"; expected "str": "int"  [dict-item]
elftools/elf/enums.py:485: error: Dict entry 7 has incompatible type "str": "type[Pass]"; expected "str": "int"  [dict-item]
elftools/elf/enums.py:489: error: Dict entry 0 has incompatible type "str": "type[Pass]"; expected "str": "int"  [dict-item]
elftools/elf/enums.py:497: error: Dict entry 3 has incompatible type "str": "type[Pass]"; expected "str": "int"  [dict-item]
elftools/elf/enums.py:585: error: Dict entry 83 has incompatible type "str": "type[Pass]"; expected "str": "int"  [dict-item]
elftools/elf/enums.py:748: error: Dict entry 51 has incompatible type "str": "type[Pass]"; expected "str": "int"  [dict-item]
elftools/elf/enums.py:795: error: Dict entry 43 has incompatible type "str": "type[Pass]"; expected "str": "int"  [dict-item]
elftools/elf/enums.py:838: error: Dict entry 39 has incompatible type "str": "type[Pass]"; expected "str": "int"  [dict-item]
elftools/elf/enums.py:848: error: Dict entry 6 has incompatible type "str": "type[Pass]"; expected "str": "int"  [dict-item]
elftools/elf/enums.py:951: error: Dict entry 98 has incompatible type "str": "type[Pass]"; expected "str": "int"  [dict-item]
elftools/elf/enums.py:1023: error: Dict entry 4 has incompatible type "str": "type[Pass]"; expected "str": "int"  [dict-item]
elftools/elf/enums.py:1042: error: Dict entry 5 has incompatible type "str": "type[Pass]"; expected "str": "int"  [dict-item]
elftools/elf/enums.py:1054: error: Dict entry 7 has incompatible type "str": "type[Pass]"; expected "str": "int"  [dict-item]
elftools/elf/enums.py:1065: error: Dict entry 6 has incompatible type "str": "type[Pass]"; expected "str": "int"  [dict-item]
elftools/elf/enums.py:1077: error: Dict entry 7 has incompatible type "str": "type[Pass]"; expected "str": "int"  [dict-item]
elftools/elf/enums.py:1085: error: Dict entry 4 has incompatible type "str": "type[Pass]"; expected "str": "int"  [dict-item]

These I do not know how to fix - their type is not static and depends dynamically on the opened ELF file:

elftools/dwarf/ranges.py:50: error: Cannot determine type of "dwarfinfo"  [has-type]
elftools/dwarf/ranges.py:51: error: Cannot determine type of "dwarfinfo"  [has-type]
elftools/dwarf/locationlists.py:63: error: Cannot determine type of "dwarfinfo"  [has-type]
elftools/dwarf/locationlists.py:64: error: Cannot determine type of "dwarfinfo"  [has-type]
elftools/elf/notes.py:25: error: Cannot determine type of "structs"  [has-type]
elftools/elf/notes.py:30: error: Cannot determine type of "structs"  [has-type]
elftools/elf/notes.py:48: error: Cannot determine type of "structs"  [has-type]
elftools/elf/notes.py:56: error: Cannot determine type of "structs"  [has-type]
elftools/elf/notes.py:60: error: Cannot determine type of "structs"  [has-type]
elftools/elf/notes.py:70: error: Cannot determine type of "structs"  [has-type]
elftools/elf/sections.py:42: error: Cannot determine type of "structs"  [has-type]
elftools/elf/sections.py:180: error: Cannot determine type of "structs"  [has-type]
elftools/elf/relocation.py:63: error: Cannot determine type of "structs"  [has-type]
elftools/elf/relocation.py:131: error: Cannot determine type of "structs"  [has-type]
elftools/elf/relocation.py:185: error: Cannot determine type of "structs"  [has-type]
elftools/elf/relocation.py:345: error: Cannot determine type of "structs"  [has-type]
elftools/elf/relocation.py:347: error: Cannot determine type of "structs"  [has-type]
elftools/elf/relocation.py:349: error: Cannot determine type of "structs"  [has-type]
elftools/elf/relocation.py:351: error: Cannot determine type of "structs"  [has-type]
elftools/elf/hash.py:49: error: Cannot determine type of "structs"  [has-type]
elftools/elf/hash.py:115: error: Cannot determine type of "structs"  [has-type]
elftools/elf/hash.py:120: error: Cannot determine type of "structs"  [has-type]
elftools/elf/hash.py:121: error: Cannot determine type of "structs"  [has-type]
elftools/elf/gnuversions.py:153: error: Cannot determine type of "structs"  [has-type]
elftools/elf/gnuversions.py:196: error: Cannot determine type of "structs"  [has-type]
elftools/elf/dynamic.py:110: error: Cannot determine type of "structs"  [has-type]

And finally the last group of issues, which are also caused by missing cc7b1ea:

elftools/dwarf/structs.py:413: error: Unused "type: ignore" comment  [unused-ignore]
elftools/common/utils.py:41: error: "FormatField" expects no type arguments, but 1 given  [type-arg]
elftools/common/utils.py:41: error: A function returning TypeVar should receive at least one argument containing the same TypeVar  [type-var]
elftools/elf/structs.py:54: error: "FormatField" expects no type arguments, but 1 given  [type-arg]
elftools/elf/structs.py:55: error: "FormatField" expects no type arguments, but 1 given  [type-arg]
elftools/elf/structs.py:56: error: "FormatField" expects no type arguments, but 1 given  [type-arg]
elftools/elf/structs.py:57: error: "FormatField" expects no type arguments, but 1 given  [type-arg]
elftools/elf/structs.py:58: error: "FormatField" expects no type arguments, but 1 given  [type-arg]
elftools/elf/structs.py:59: error: "FormatField" expects no type arguments, but 1 given  [type-arg]
elftools/elf/structs.py:60: error: "FormatField" expects no type arguments, but 1 given  [type-arg]
elftools/elf/structs.py:61: error: "FormatField" expects no type arguments, but 1 given  [type-arg]
elftools/elf/structs.py:62: error: "FormatField" expects no type arguments, but 1 given  [type-arg]
elftools/dwarf/descriptions.py:93: error: Incompatible types in string interpolation (expression has type "int | None", placeholder has type "int")  [str-format]
elftools/dwarf/descriptions.py:135: error: Incompatible types in string interpolation (expression has type "ListContainer", placeholder has type "int | float | SupportsInt")  [str-format]
elftools/dwarf/descriptions.py:135: error: Incompatible types in string interpolation (expression has type "None", placeholder has type "int | float | SupportsInt")  [str-format]
elftools/dwarf/descriptions.py:149: error: Incompatible types in string interpolation (expression has type "int | None", placeholder has type "int | float | SupportsInt")  [str-format]
elftools/ehabi/ehabiinfo.py:58: error: Incompatible types in string interpolation (expression has type "int | None", placeholder has type "int | float | SupportsInt")  [str-format]
elftools/ehabi/ehabiinfo.py:164: error: Incompatible types in string interpolation (expression has type "int | None", placeholder has type "int")  [str-format]
elftools/ehabi/ehabiinfo.py:164: error: Incompatible types in string interpolation (expression has type "int | None", placeholder has type "int | float | SupportsInt")  [str-format]
elftools/ehabi/ehabiinfo.py:193: error: Incompatible types in string interpolation (expression has type "None", placeholder has type "int")  [str-format]
elftools/ehabi/ehabiinfo.py:204: error: Incompatible types in string interpolation (expression has type "int | None", placeholder has type "int")  [str-format]
elftools/elf/sections.py:106: error: Incompatible types in string interpolation (expression has type "str", placeholder has type "int")  [str-format]
elftools/elf/descriptions.py:349: error: Incompatible types in string interpolation (expression has type "str | int", placeholder has type "int")  [str-format]

Please have a 1st look.

Then we can decide on how to proceed, e.g. just merge it or try to extract a subset for only some public API files.

@pmhahn pmhahn mentioned this pull request Mar 19, 2025
Copy link
Owner

@eliben eliben left a comment

Choose a reason for hiding this comment

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

Just a couple of initial questions

In general, I'd really prefer someone with Python type checking experience to take a careful look at this

Copy link
Owner

@eliben eliben left a comment

Choose a reason for hiding this comment

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

One question and general comment: would it be possible to split this PR to multiple? Even if just for the sake of review - could we start with a small-medium PR with a representative set of changes? It's OK if the intermediate steps don't fully type check until everything has landed

@pmhahn
Copy link
Contributor Author

pmhahn commented Apr 1, 2025

One question and general comment: would it be possible to split this PR to multiple? Even if just for the sake of review - could we start with a small-medium PR with a representative set of changes? It's OK if the intermediate steps don't fully type check until everything has landed

Yes, I can do that. Any advise on how to split best? Internel / low-level / high-level?

@eliben
Copy link
Owner

eliben commented Apr 1, 2025

One question and general comment: would it be possible to split this PR to multiple? Even if just for the sake of review - could we start with a small-medium PR with a representative set of changes? It's OK if the intermediate steps don't fully type check until everything has landed

Yes, I can do that. Any advise on how to split best? Internel / low-level / high-level?

Yes, by layers could be a great way to slice it. Starting at the lowest possible and then progressing upwards

@eliben
Copy link
Owner

eliben commented May 5, 2025

Where do we stand with this PR? How much of it has already been added?

@pmhahn
Copy link
Contributor Author

pmhahn commented May 8, 2025

Where do we stand with this PR? How much of it has already been added?

Sorry for the delay, I'm currently busy otherwise. Hope to find some time next weekend.

@sevaa
Copy link
Contributor

sevaa commented Aug 28, 2025

What's the status here?

@pmhahn
Copy link
Contributor Author

pmhahn commented Sep 8, 2025

What's the status here?

The bad news: Sadly I'm busy otherwise .
The good news: I found some time to update the PR and it again is in a state, where it could be merged:

pyright got unhappy, so I again had to re-introduce 6cb25b9 and fix some hints 7a0c243 , which already have been merged. mypy is still unhappy with a lot of things.

I have been experimenting with typeguard, which turns those type-hints into runtime checks. This allows validating those hints when running the test-suite. Sadly that drastically increases the runtime and – even more sad – shows several errors in my type annotations.
I have locally converted the unit-test to use pytest instead of Pythons built-in unittest as that allowed me to easily get coverage reports, but that also needs more work.

Copy link

@Timmmm Timmmm left a comment

Choose a reason for hiding this comment

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

This looks great to me. It would be good to split into smaller PRs - maybe you could do one with all the basic str, int, None types that are pretty obvious and then a second one with everything else?

On the other hand that's super tedious to do and nobody has time for that. If this were my project I think I would just merge it as-is and improve it in future PRs.

If this passes Pyright that's already a huge achievement and improvement.

pmhahn added 15 commits October 7, 2025 07:17
Convert from legacy `collections.namedtuple` to `typing.NamedTuple` to
allow adding type hints.

Signed-off-by: Philipp Hahn <[email protected]>
`pyelftools` does not use class hierarchies in all cases, but relies on
_duck typing_.
Introduce Protocols for those cases to allow type hinting.

Signed-off-by: Philipp Hahn <[email protected]>
- [x] `pyright`
- [x] `mypy` with several issues:
> Cannot determine type of "dwarfinfo"
> Cannot determine type of "structs"

Signed-off-by: Philipp Hahn <[email protected]>
Several classes inheriting from `Construct`, which only has a generic
`__getattr__(self, name: str) -> Any`.
To improve typing explicitly name several attributes with their type.

For `name` we know that it (almost) never will be `None` – an unnamed
entity does not make much sense. Overwrite the type-hint `str | None`
inherited from `Construct` with just `str` which saves us from a ton if
`name is not None` checks.

Signed-off-by: Philipp Hahn <[email protected]>
Several variable may have a `Union` type.
Explicitly assert that those variables have the expected type.

Signed-off-by: Philipp Hahn <[email protected]>
Several variable may have an `Optional` type and can be `None`.
Explicitly assert that those variables are `not None`.

Signed-off-by: Philipp Hahn <[email protected]>
Declare `lsda_pointer` and `aug_dict` and `last_line_in_CIE` before the
conditional. Static type checking will complain otherwise that variables
might not be declared on the `else` case.

Signed-off-by: Philipp Hahn <[email protected]>
`Dwarf_dw_form` contains `None` values, but type checkers fail to do a
static lookup to see, that they are constant and `not None`.

Signed-off-by: Philipp Hahn <[email protected]>
`die.tag` can be an `int` for "user-defined tags". Invert the type check
to silence type checking.

Signed-off-by: Philipp Hahn <[email protected]>
`describe_reg_name()` may return `None`, but `str + None` raises a
`TypeError`.

Signed-off-by: Philipp Hahn <[email protected]>
`DIE._terminator` may be `None` and `search._terminator.offset` would
raise a `AttributeError`.

Signed-off-by: Philipp Hahn <[email protected]>
Declaring constants as `list` is bad as `list` is modifiable.
Declare them as `Final[tuple]` instead to help type-checkers
de-reference individual entries for checking and using their correct
type `dict` or `None`.

Signed-off-by: Philipp Hahn <[email protected]>
Improve type hinting - at least for `t` and `sz` - `data` remains `Any`.

Signed-off-by: Philipp Hahn <[email protected]>
pmhahn added 21 commits October 7, 2025 07:21
`section_names` is only used internally as a `list`, as section names
might get modified and the list is appended.

Signed-off-by: Philipp Hahn <[email protected]>
`ENUM_D_TAG` is a constant, but built by code.
Convert it into a dict-comprehension.

Signed-off-by: Philipp Hahn <[email protected]>
`get_symbol()` may return `None` and `symbol.name` would raise an
`AttributeError`. Check for `not None` explicitly.

Signed-off-by: Philipp Hahn <[email protected]>
`entry_translate` may contain `None`.
Silence type checking.

Signed-off-by: Philipp Hahn <[email protected]>
An unknown `ST_SHNDC` `str` will raise `ValueError`.

Signed-off-by: Philipp Hahn <[email protected]>
`get_table_offset()` may return `tuple[…, None]`.

Signed-off-by: Philipp Hahn <[email protected]>
`entry_translate` may contain `None` in the DWARF-5-case.
Silence type checking.

Signed-off-by: Philipp Hahn <[email protected]>
Code explicitly checks for `AttributeError`, but `mypy` is picky here.

Signed-off-by: Philipp Hahn <[email protected]>
`params` is first declared as a `tuple` and then changed to `str`, which
static type checkers like `mypy` do not like.

Rename the first variable.

Signed-off-by: Philipp Hahn <[email protected]>
`reveal_type` is first declared as `TypeDesc` and then changed to `str`,
which static type checkers like `mypy` do not like.

Rename the first variable.

PS: Better rename `reveal_type()` to somethings else as there is
`typing.reveal_type()`[^1].

[^1]: https://docs.python.org/3/library/typing.html#typing.reveal_type

Signed-off-by: Philipp Hahn <[email protected]>
`all_offsets` is first declared as a `set` and then changed to `list`,
which static type checkers like `mypy` do not like.

Rename the first variable.

Signed-off-by: Philipp Hahn <[email protected]>
`get_parent()` may return `None`, in wich case `parent.tag` would raise
an `AttributeError`. Make the check explicit for type checking.

Signed-off-by: Philipp Hahn <[email protected]>
Raise `ValueError` in all `else` cases.

Signed-off-by: Philipp Hahn <[email protected]>
Combine the `if` statements to help type checking: Otherwise the 2nd
DWARF-5-case need another case to check for `die not None`.

FYI: This is a behavioral change as the file position gets changed in
the error case.

Signed-off-by: Philipp Hahn <[email protected]>
`dict.get() -> … | None`

Signed-off-by: Philipp Hahn <[email protected]>
Also store the reference to `RelocationTable` in a local variable to
help type checkers using the correct type when checking `entry_size`.

Signed-off-by: Philipp Hahn <[email protected]>
`Dynamic` expects an instance following the protocol `_StringTable`, but
`get_section()` just returns a `Section`.

Signed-off-by: Philipp Hahn <[email protected]>
Explicitly return `None` to silence `mypy`.

Signed-off-by: Philipp Hahn <[email protected]>
Also store the reference to `Container` in a local variable to help type
checkers using the correct type when checking `bloom_size` and
`nbuckets`.

Signed-off-by: Philipp Hahn <[email protected]>
`Attribute` and its related classes `Attribute(Sub)*Section` have two
concrete sub-classes for ARM and RISCV with *different* constructors.
This confuses type checkers like `mypy` and `pyright` and humans like
me, as this is not type safe: the `AttributeSubsubsection` classes
are factory methods for `Attributes`, but their actual signature
differs from the prototype. Basically you have to tell type-checkers:
Expect a class with this signature, but you will get back an instance of
another class having a different signature for `__init__()`.

After having tried `Protocols` and `TypeVars` I gave up and
re-implemented all 3*4 classes to have sane constructors:
- The concrete sub-classes for ARM and RISCV sections only set different
  class variables; no extra code needed.
- For `…Attributes` there's a new class method as `structs` must be
  handled at runtime.

Signed-off-by: Philipp Hahn <[email protected]>
@sevaa
Copy link
Contributor

sevaa commented Nov 25, 2025

I have a hunch this will never see the light of day. Would it be feasible to scale back the mission - type-annotate the user facing part of the API and mark the private stuff as off limits for the type checker?

@Timmmm
Copy link

Timmmm commented Nov 25, 2025

Yeah this should just be merged IMO. It's pretty much impossible to take an untyped Python project and add correct type hints to it all in one go. Once this is merged you can gradually fix the errors until it all type checks, and then enable type checking with Pyright in CI (or Pyrefly/Ty maybe by the time that actually happens!).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants