Add type hints
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 6f99ce09ddf25ce80422f388b7690e6323ccb89f 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 db4fb21cd6040f1cca70ddc4a3fd5e42996f3f0b 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 cc7b1eaa277fd:
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.
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?
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
Where do we stand with this PR? How much of it has already been added?
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.
What's the status here?
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.
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?
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!).