diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py
index 35c26d81a..6a30cdda6 100644
--- a/mesonbuild/interpreter/interpreter.py
+++ b/mesonbuild/interpreter/interpreter.py
@@ -121,19 +121,14 @@ def stringifyUserArguments(args, quote=False):
     raise InvalidArguments('Function accepts only strings, integers, bools, lists, dictionaries and lists thereof.')
 
 class Summary:
-    def __init__(self, project_name, project_version):
+    def __init__(self, project_name: str, project_version: str):
         self.project_name = project_name
         self.project_version = project_version
         self.sections = collections.defaultdict(dict)
         self.max_key_len = 0
 
-    def add_section(self, section, values, kwargs, subproject):
-        bool_yn = kwargs.get('bool_yn', False)
-        if not isinstance(bool_yn, bool):
-            raise InterpreterException('bool_yn keyword argument must be boolean')
-        list_sep = kwargs.get('list_sep')
-        if list_sep is not None and not isinstance(list_sep, str):
-            raise InterpreterException('list_sep keyword argument must be string')
+    def add_section(self, section: str, values: T.Dict[str, T.Any], bool_yn: bool,
+                    list_sep: T.Optional[str], subproject: str) -> None:
         for k, v in values.items():
             if k in self.sections[section]:
                 raise InterpreterException(f'Summary section {section!r} already have key {k!r}')
@@ -267,7 +262,7 @@ class Interpreter(InterpreterBase, HoldableObject):
         self.environment = self.build.environment
         self.coredata = self.environment.get_coredata()
         self.backend = backend
-        self.summary = {}
+        self.summary: T.Dict[str, 'Summary'] = {}
         self.modules = {}
         # Subproject directory is usually the name of the subproject, but can
         # be different for dependencies provided by wrap files.
@@ -1193,31 +1188,33 @@ external dependencies (including libraries) must go to "dependencies".''')
         mlog.log(mlog.bold('Message:'), *args)
 
     @noArgsFlattening
-    @FeatureNewKwargs('summary', '0.54.0', ['list_sep'])
-    @permittedKwargs({'section', 'bool_yn', 'list_sep'})
     @FeatureNew('summary', '0.53.0')
-    def func_summary(self, node, args, kwargs):
-        if len(args) == 1:
+    @typed_pos_args('summary', (str, dict), optargs=[object])
+    @typed_kwargs(
+        'summary',
+        KwargInfo('section', str, default=''),
+        KwargInfo('bool_yn', bool, default=False),
+        KwargInfo('list_sep', (str, NoneType), since='0.54.0')
+    )
+    def func_summary(self, node: mparser.BaseNode, args: T.Tuple[T.Union[str, T.Dict[str, T.Any]], T.Optional[T.Any]],
+                     kwargs: 'kwargs.Summary') -> None:
+        if args[1] is None:
             if not isinstance(args[0], dict):
                 raise InterpreterException('Summary first argument must be dictionary.')
             values = args[0]
-        elif len(args) == 2:
+        else:
             if not isinstance(args[0], str):
                 raise InterpreterException('Summary first argument must be string.')
             values = {args[0]: args[1]}
-        else:
-            raise InterpreterException('Summary accepts at most 2 arguments.')
-        section = kwargs.get('section', '')
-        if not isinstance(section, str):
-            raise InterpreterException('Summary\'s section keyword argument must be string.')
-        self.summary_impl(section, values, kwargs)
+        self.summary_impl(kwargs['section'], values, kwargs)
 
-    def summary_impl(self, section, values, kwargs):
+    def summary_impl(self, section: str, values, kwargs: 'kwargs.Summary') -> None:
         if self.subproject not in self.summary:
             self.summary[self.subproject] = Summary(self.active_projectname, self.project_version)
-        self.summary[self.subproject].add_section(section, values, kwargs, self.subproject)
+        self.summary[self.subproject].add_section(
+            section, values, kwargs['bool_yn'], kwargs['list_sep'], self.subproject)
 
-    def _print_summary(self):
+    def _print_summary(self) -> None:
         # Add automatic 'Supbrojects' section in main project.
         all_subprojects = collections.OrderedDict()
         for name, subp in sorted(self.subprojects.items()):
@@ -1244,7 +1241,7 @@ external dependencies (including libraries) must go to "dependencies".''')
             sorted_options = sorted(self.user_defined_options.cmd_line_options.items())
             values.update({str(k): v for k, v in sorted_options})
             if values:
-                self.summary_impl('User defined options', values, {})
+                self.summary_impl('User defined options', values, {'bool_yn': False, 'list_sep': None})
         # Print all summaries, main project last.
         mlog.log('')  # newline
         main_summary = self.summary.pop('', None)
diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py
index 50bc5d19e..4777d8ac4 100644
--- a/mesonbuild/interpreter/kwargs.py
+++ b/mesonbuild/interpreter/kwargs.py
@@ -219,3 +219,10 @@ class _FoundProto(Protocol):
 class Subdir(TypedDict):
 
     if_found: T.List[_FoundProto]
+
+
+class Summary(TypedDict):
+
+    section: str
+    bool_yn: bool
+    list_sep: T.Optional[str]