refactor(memmap): apply additional type hints

This is a non-functional change which adds type hints wherever they
might be helpful. Outside of human readability, this can also help
static analysis tools and IDEs identify additional errors.

Change-Id: Ie63e7f1cdeb893ca7d8a703aff56361eaa4e8f1b
Signed-off-by: Chris Kay <chris.kay@arm.com>
diff --git a/tools/memory/src/memory/buildparser.py b/tools/memory/src/memory/buildparser.py
index d62249f..8caffd8 100755
--- a/tools/memory/src/memory/buildparser.py
+++ b/tools/memory/src/memory/buildparser.py
@@ -6,6 +6,7 @@
 
 import re
 from pathlib import Path
+from typing import Any, Dict, List, Tuple, Union
 
 from memory.elfparser import TfaElfParser
 from memory.mapparser import TfaMapParser
@@ -14,17 +15,17 @@
 class TfaBuildParser:
     """A class for performing analysis on the memory layout of a TF-A build."""
 
-    def __init__(self, path: Path, map_backend=False):
-        self._modules = dict()
-        self._path = path
-        self.map_backend = map_backend
+    def __init__(self, path: Path, map_backend: bool = False) -> None:
+        self._modules: Dict[str, Union[TfaElfParser, TfaMapParser]] = {}
+        self._path: Path = path
+        self.map_backend: bool = map_backend
         self._parse_modules()
 
-    def __getitem__(self, module: str):
+    def __getitem__(self, module: str) -> Union[TfaElfParser, TfaMapParser]:
         """Returns an TfaElfParser instance indexed by module."""
         return self._modules[module]
 
-    def _parse_modules(self):
+    def _parse_modules(self) -> None:
         """Parse the build files using the selected backend."""
         backend = TfaElfParser
         files = list(self._path.glob("**/*.elf"))
@@ -46,11 +47,13 @@
             )
 
     @property
-    def symbols(self) -> list:
+    def symbols(self) -> List[Tuple[str, int, str]]:
         return [(*sym, k) for k, v in self._modules.items() for sym in v.symbols]
 
     @staticmethod
-    def filter_symbols(symbols: list, regex: str) -> list:
+    def filter_symbols(
+        symbols: List[Tuple[str, int, str]], regex: str
+    ) -> List[Tuple[str, int, str]]:
         """Returns a map of symbols to modules."""
 
         return sorted(
@@ -59,16 +62,16 @@
             reverse=True,
         )
 
-    def get_mem_usage_dict(self) -> dict:
+    def get_mem_usage_dict(self) -> Dict[str, Dict[str, Dict[str, int]]]:
         """Returns map of memory usage per memory type for each module."""
-        mem_map = {}
+        mem_map: Dict[str, Dict[str, Dict[str, int]]] = {}
         for k, v in self._modules.items():
             mod_mem_map = v.get_memory_layout()
             if len(mod_mem_map):
                 mem_map[k] = mod_mem_map
         return mem_map
 
-    def get_mem_tree_as_dict(self) -> dict:
+    def get_mem_tree_as_dict(self) -> Dict[str, Dict[str, Any]]:
         """Returns _tree of modules, segments and segments and their total
         memory usage."""
         return {
@@ -81,6 +84,6 @@
         }
 
     @property
-    def module_names(self):
+    def module_names(self) -> List[str]:
         """Returns sorted list of module names."""
         return sorted(self._modules.keys())
diff --git a/tools/memory/src/memory/elfparser.py b/tools/memory/src/memory/elfparser.py
index 10d1123..559461e 100644
--- a/tools/memory/src/memory/elfparser.py
+++ b/tools/memory/src/memory/elfparser.py
@@ -6,9 +6,10 @@
 
 import re
 from dataclasses import asdict, dataclass
-from typing import BinaryIO
+from typing import BinaryIO, Dict, ItemsView, Iterable, List, Optional, Tuple, Union
 
 from elftools.elf.elffile import ELFFile
+from elftools.elf.sections import Section
 from elftools.elf.segments import Segment
 
 
@@ -18,7 +19,7 @@
     start: int
     end: int
     size: int
-    children: list
+    children: List["TfaMemObject"]
 
 
 class TfaElfParser:
@@ -29,29 +30,35 @@
     the contents an ELF file.
     """
 
-    def __init__(self, elf_file: BinaryIO):
-        self._segments = {}
-        self._memory_layout = {}
+    def __init__(self, elf_file: BinaryIO) -> None:
+        self._segments: Dict[int, TfaMemObject] = {}
+        self._memory_layout: Dict[str, Dict[str, int]] = {}
 
         elf = ELFFile(elf_file)
 
-        self._symbols = {
+        self._symbols: Dict[str, int] = {
             sym.name: sym.entry["st_value"]
             for sym in elf.get_section_by_name(".symtab").iter_symbols()
         }
 
         self.set_segment_section_map(elf.iter_segments(), elf.iter_sections())
         self._memory_layout = self.get_memory_layout_from_symbols()
-        self._start = elf["e_entry"]
+        self._start: int = elf["e_entry"]
+        self._size: int
+        self._free: int
         self._size, self._free = self._get_mem_usage()
-        self._end = self._start + self._size
+        self._end: int = self._start + self._size
 
     @property
-    def symbols(self):
+    def symbols(self) -> ItemsView[str, int]:
         return self._symbols.items()
 
     @staticmethod
-    def tfa_mem_obj_factory(elf_obj, name=None, children=None):
+    def tfa_mem_obj_factory(
+        elf_obj: Union[Segment, Section],
+        name: Optional[str] = None,
+        children: Optional[List[TfaMemObject]] = None,
+    ) -> TfaMemObject:
         """Converts a pyelfparser Segment or Section to a TfaMemObject."""
         # Ensure each segment is provided a name since they aren't in the
         # program header.
@@ -75,7 +82,7 @@
             [] if not children else children,
         )
 
-    def _get_mem_usage(self) -> (int, int):
+    def _get_mem_usage(self) -> Tuple[int, int]:
         """Get total size and free space for this component."""
         size = free = 0
 
@@ -90,7 +97,11 @@
 
         return size, free
 
-    def set_segment_section_map(self, segments, sections):
+    def set_segment_section_map(
+        self,
+        segments: Iterable[Segment],
+        sections: Iterable[Section],
+    ) -> None:
         """Set segment to section mappings."""
         segments = list(filter(lambda seg: seg["p_type"] == "PT_LOAD", segments))
 
@@ -104,7 +115,7 @@
 
                     self._segments[n].children.append(self.tfa_mem_obj_factory(sec))
 
-    def get_memory_layout_from_symbols(self) -> dict:
+    def get_memory_layout_from_symbols(self) -> Dict[str, Dict[str, int]]:
         """Retrieve information about the memory configuration from the symbol
         table.
         """
@@ -112,7 +123,7 @@
 
         expr = r".*(.?R.M)_REGION.*(START|END|LENGTH)"
         region_symbols = filter(lambda s: re.match(expr, s), self._symbols)
-        memory_layout = {}
+        memory_layout: Dict[str, Dict[str, int]] = {}
 
         for symbol in region_symbols:
             region, _, attr = tuple(symbol.lower().strip("__").split("_"))
@@ -124,16 +135,16 @@
 
         return memory_layout
 
-    def get_seg_map_as_dict(self):
+    def get_seg_map_as_dict(self) -> List[Dict[str, int]]:
         """Get a dictionary of segments and their section mappings."""
         return [asdict(v) for k, v in self._segments.items()]
 
-    def get_memory_layout(self):
+    def get_memory_layout(self) -> Dict[str, Dict[str, int]]:
         """Get the total memory consumed by this module from the memory
         configuration.
             {"rom": {"start": 0x0, "end": 0xFF, "length": ... }
         """
-        mem_dict = {}
+        mem_dict: Dict[str, Dict[str, int]] = {}
 
         for mem, attrs in self._memory_layout.items():
             limit = attrs["start"] + attrs["length"]
@@ -146,7 +157,7 @@
             }
         return mem_dict
 
-    def get_mod_mem_usage_dict(self):
+    def get_mod_mem_usage_dict(self) -> Dict[str, int]:
         """Get the total memory consumed by the module, this combines the
         information in the memory configuration.
         """
diff --git a/tools/memory/src/memory/mapparser.py b/tools/memory/src/memory/mapparser.py
index 93442b4..37d0c12 100644
--- a/tools/memory/src/memory/mapparser.py
+++ b/tools/memory/src/memory/mapparser.py
@@ -5,7 +5,7 @@
 #
 
 from re import match, search
-from typing import TextIO
+from typing import Dict, ItemsView, TextIO
 
 
 class TfaMapParser:
@@ -16,17 +16,17 @@
     are supported at this stage.
     """
 
-    def __init__(self, map_file: TextIO):
-        self._symbols = self.read_symbols(map_file)
+    def __init__(self, map_file: TextIO) -> None:
+        self._symbols: Dict[str, int] = self.read_symbols(map_file)
 
     @property
-    def symbols(self):
+    def symbols(self) -> ItemsView[str, int]:
         return self._symbols.items()
 
     @staticmethod
-    def read_symbols(file: TextIO) -> dict:
+    def read_symbols(file: TextIO) -> Dict[str, int]:
         pattern = r"\b(0x\w*)\s*(\w*)\s="
-        symbols = {}
+        symbols: Dict[str, int] = {}
 
         for line in file.readlines():
             match = search(pattern, line)
@@ -37,14 +37,14 @@
 
         return symbols
 
-    def get_memory_layout(self) -> dict:
+    def get_memory_layout(self) -> Dict[str, Dict[str, int]]:
         """Get the total memory consumed by this module from the memory
         configuration.
             {"rom": {"start": 0x0, "end": 0xFF, "length": ... }
         """
         assert len(self._symbols), "Symbol table is empty!"
         expr = r".*(.?R.M)_REGION.*(START|END|LENGTH)"
-        memory_layout = {}
+        memory_layout: Dict[str, Dict[str, int]] = {}
 
         region_symbols = filter(lambda s: match(expr, s), self._symbols)
 
diff --git a/tools/memory/src/memory/memmap.py b/tools/memory/src/memory/memmap.py
index f3ade87..553e51f 100755
--- a/tools/memory/src/memory/memmap.py
+++ b/tools/memory/src/memory/memmap.py
@@ -81,14 +81,14 @@
     root: Path,
     platform: str,
     build_type: str,
-    footprint: str,
+    footprint: bool,
     tree: bool,
     symbols: bool,
     depth: int,
     width: int,
     d: bool,
     no_elf_images: bool,
-):
+) -> None:
     build_path = root if root else Path("build/", platform, build_type)
     click.echo(f"build-path: {build_path.resolve()}")
 
diff --git a/tools/memory/src/memory/printer.py b/tools/memory/src/memory/printer.py
index dda7917..3690853 100755
--- a/tools/memory/src/memory/printer.py
+++ b/tools/memory/src/memory/printer.py
@@ -4,6 +4,8 @@
 # SPDX-License-Identifier: BSD-3-Clause
 #
 
+from typing import Any, Dict, List, Optional, Tuple
+
 from anytree import RenderTree
 from anytree.importer import DictImporter
 from prettytable import PrettyTable
@@ -17,19 +19,29 @@
     structured and consumed.
     """
 
-    def __init__(self, columns: int, as_decimal: bool = False):
-        self.term_size = columns
-        self._tree = None
-        self._footprint = None
-        self._symbol_map = None
-        self.as_decimal = as_decimal
+    def __init__(self, columns: int, as_decimal: bool = False) -> None:
+        self.term_size: int = columns
+        self._tree: Optional[List[str]] = None
+        self._symbol_map: Optional[List[str]] = None
+        self.as_decimal: bool = as_decimal
 
-    def format_args(self, *args, width=10, fmt=None):
+    def format_args(
+        self,
+        *args: Any,
+        width: int = 10,
+        fmt: Optional[str] = None,
+    ) -> List[Any]:
         if not fmt and type(args[0]) is int:
             fmt = f">{width}x" if not self.as_decimal else f">{width}"
         return [f"{arg:{fmt}}" if fmt else arg for arg in args]
 
-    def format_row(self, leading, *args, width=10, fmt=None):
+    def format_row(
+        self,
+        leading: str,
+        *args: Any,
+        width: int = 10,
+        fmt: Optional[str] = None,
+    ) -> str:
         formatted_args = self.format_args(*args, width=width, fmt=fmt)
         return leading + " ".join(formatted_args)
 
@@ -41,7 +53,7 @@
         columns: int,
         width: int,
         is_edge: bool = False,
-    ):
+    ) -> str:
         empty_col = "{:{}{}}"
 
         # Some symbols are longer than the column width, truncate them until
@@ -58,7 +70,9 @@
 
         return leading + sec_row_l + sec_row + sec_row_r
 
-    def print_footprint(self, app_mem_usage: dict):
+    def print_footprint(
+        self, app_mem_usage: Dict[str, Dict[str, Dict[str, int]]]
+    ) -> None:
         assert len(app_mem_usage), "Empty memory layout dictionary!"
 
         fields = ["Component", "Start", "Limit", "Size", "Free", "Total"]
@@ -86,10 +100,10 @@
 
     def print_symbol_table(
         self,
-        symbols: list,
-        modules: list,
+        symbols: List[Tuple[str, int, str]],
+        modules: List[str],
         start: int = 12,
-    ):
+    ) -> None:
         assert len(symbols), "Empty symbol list!"
         modules = sorted(modules)
         col_width = int((self.term_size - start) / len(modules))
@@ -125,8 +139,13 @@
         print("\n".join(self._symbol_map))
 
     def print_mem_tree(
-        self, mem_map_dict, modules, depth=1, min_pad=12, node_right_pad=12
-    ):
+        self,
+        mem_map_dict: Dict[str, Any],
+        modules: List[str],
+        depth: int = 1,
+        min_pad: int = 12,
+        node_right_pad: int = 12,
+    ) -> None:
         # Start column should have some padding between itself and its data
         # values.
         anchor = min_pad + node_right_pad * (depth - 1)