bug-gnulib@gnu.org mirror (unofficial)
 help / color / mirror / Atom feed
From: Collin Funk <collin.funk1@gmail.com>
To: bug-gnulib@gnu.org, Bruno Haible <bruno@clisp.org>
Subject: [PATCH] gnulib-tool.py: Add type hints to all functions.
Date: Wed, 27 Mar 2024 01:39:01 -0700	[thread overview]
Message-ID: <b993cd5f-6bd7-4db1-8c90-2196838e6f1a@gmail.com> (raw)

[-- Attachment #1: Type: text/plain, Size: 3314 bytes --]

Hi Bruno,

Here is the big patch adding type hints. I had a second glance through
all of the pygnulib/*.py files and believe I got everything.

The typing seems pretty helpful to me already. I am using Emacs eglot
with pyright which shows many unbound variables + bad function calls
on objects [1] [2]. Some are false positives, mostly due to the
dictionary-like usage of GLConfig. Others are real. For example, I can
see why this will fail before running it:

$ gnulib-tool.py --create-megatestdir --dir testdir bad-module
gnulib-tool: warning: file bad-module does not exist
gnulib-tool: warning: file None does not exist
Traceback (most recent call last):
  File "/home/collin/.local/src/gnulib/pygnulib/main.py", line 1370, in <module>
    main()
  File "/home/collin/.local/src/gnulib/pygnulib/main.py", line 1075, in main
    testdir.execute()
  File "/home/collin/.local/src/gnulib/pygnulib/GLTestDir.py", line 945, in execute
    GLTestDir(self.config, joinpath(self.megatestdir, str(module))).execute()
  File "/home/collin/.local/src/gnulib/pygnulib/GLTestDir.py", line 223, in execute
    requested_licence = requested_module.getLicense()
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'getLicense'

I've also confirmed that everything works with Python 3.7.

Since that requires all files need the __future__ import, I've removed
this comment from GLEmiter.py:

    # Allow the use of union type specifiers, using the syntax Type1 | Type2,
    # in Python ≥ 3.7.  Cf. <https://docs.python.org/3/library/__future__.html>
    # and <https://stackoverflow.com/questions/73879925/>.

And added a modified version to main.py under the rest of the rest of
the coding standards.

In those modifications I made a comment about using the standard
collection type hints over the 'from typing import *' ones.

The latter are deprecated and may be removed in the future [3].

I have also made a conscious choice not to use more abstract
collections like 'Iterable' even though they are recommended [4].

The first is because some functions check the type:

    def setModules(self, modules: list[str] | tuple[str]) -> None:
        '''Set the modules list.'''
        if type(modules) is list or type(modules) is tuple:
           # do something, or raise a TypeError

It is temping to use Iterable here:

    print(iter(list()))
    <list_iterator object at 0x7f7c13f5b1c0>
    print(iter(tuple()))
    <tuple_iterator object at 0x7f7c13f5b1c0>

but a what about a set?

    print(iter(set()))
    <set_iterator object at 0x7f7c13f56b80>

We would think it is okay to pass a set, but it would fail at runtime.
Using the uglier Union would let us know by looking or a warning if
using a lsp program.

The second reason is sorting. Since it is easy to mess up the sorting
for the test cases against gnulib-tool.sh, it is probably best to only
pass lists for now. We can change functions to Iterable or something
similar once we are sure it will not affect the output.

Let me know if I missed anything or if you want me to make any changes.

[1] https://www.gnu.org/software/emacs/manual/eglot.html
[2] https://github.com/microsoft/pyright/tree/main
[3] https://peps.python.org/pep-0585/
[4] https://docs.python.org/3/library/typing.html#typing.List

Collin

[-- Attachment #2: 0001-gnulib-tool.py-Add-type-hints-to-all-functions.patch --]
[-- Type: text/x-patch, Size: 118120 bytes --]

From fe4514d1f4487109238b51b028d0b5503c5b717d Mon Sep 17 00:00:00 2001
From: Collin Funk <collin.funk1@gmail.com>
Date: Wed, 27 Mar 2024 00:56:29 -0700
Subject: [PATCH] gnulib-tool.py: Add type hints to all functions.

* pygnulib/*.py: Add type hints and remove duplicate function signatures
from docstrings.
---
 ChangeLog                   |   6 +
 pygnulib/GLConfig.py        | 304 +++++++++++++++++--------------
 pygnulib/GLEmiter.py        |  90 +++------
 pygnulib/GLError.py         |   6 +-
 pygnulib/GLFileSystem.py    |  74 +++-----
 pygnulib/GLImport.py        |  65 +++----
 pygnulib/GLInfo.py          |  20 +-
 pygnulib/GLMakefileTable.py |  28 ++-
 pygnulib/GLModuleSystem.py  | 354 +++++++++++++-----------------------
 pygnulib/GLTestDir.py       |  36 ++--
 pygnulib/constants.py       |  69 ++++---
 pygnulib/main.py            |  12 +-
 12 files changed, 465 insertions(+), 599 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index e70c0e9271..d66e11c741 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2024-03-27  Collin Funk  <collin.funk1@gmail.com>
+
+	gnulib-tool.py: Add type hints to all functions.
+	* pygnulib/*.py: Add type hints and remove duplicate function signatures
+	from docstrings.
+
 2024-03-26  Collin Funk  <collin.funk1@gmail.com>
 
 	gnulib-tool.py: Allow the use of both configure.ac and configure.in.
diff --git a/pygnulib/GLConfig.py b/pygnulib/GLConfig.py
index b95d2dd613..662814b6be 100644
--- a/pygnulib/GLConfig.py
+++ b/pygnulib/GLConfig.py
@@ -13,6 +13,8 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
+from __future__ import annotations
+
 #===============================================================================
 # Define global imports
 #===============================================================================
@@ -53,19 +55,43 @@ class GLConfig(object):
     By default all attributes are set to empty string, empty list or zero.
     The most common value, however, is a None value.'''
 
-    def __init__(self, destdir=None, localpath=None, auxdir=None,
-                 sourcebase=None, m4base=None, pobase=None, docbase=None, testsbase=None,
-                 modules=None, avoids=None, files=None,
-                 incl_test_categories=None, excl_test_categories=None, libname=None,
-                 lgpl=None, gnu_make=None, makefile_name=None, tests_makefile_name=None,
-                 automake_subdir=None, automake_subdir_tests=None, libtool=None,
-                 conddeps=None, macro_prefix=None,
-                 podomain=None, witness_c_macro=None, vc_files=None, copymode=None,
-                 lcopymode=None, configure_ac=None, ac_version=None, libtests=None,
-                 single_configure=None, verbose=None, dryrun=None, errors=None):
-        '''GLConfig.__init__(arguments) -> GLConfig
-
-        Create new GLConfig instance.'''
+    def __init__(self,
+                 destdir: str | None = None,
+                 localpath: list[str] | None = None,
+                 auxdir: str | None = None,
+                 sourcebase: str | None = None,
+                 m4base: str | None = None,
+                 pobase: str | None = None,
+                 docbase: str | None = None,
+                 testsbase: str | None = None,
+                 modules: list[str] | None = None,
+                 avoids: list[str] | None = None,
+                 files: list[str] | None = None,
+                 incl_test_categories: list[int] | tuple[int] | None = None,
+                 excl_test_categories: list[int] | tuple[int] | None = None,
+                 libname: str | None = None,
+                 lgpl: str | bool | None = None,
+                 gnu_make: bool | None = None,
+                 makefile_name: str | None = None,
+                 tests_makefile_name: str | None = None,
+                 automake_subdir: bool | None = None,
+                 automake_subdir_tests: bool | None = None,
+                 libtool: bool | None = None,
+                 conddeps: bool | None  = None,
+                 macro_prefix: str | None = None,
+                 podomain: str | None = None,
+                 witness_c_macro: str | None = None,
+                 vc_files: bool | None = None,
+                 copymode: classes.CopyAction | None = None,
+                 lcopymode: classes.CopyAction | None = None,
+                 configure_ac: str | None = None,
+                 ac_version: float | int | None = None,
+                 libtests: bool | None = None,
+                 single_configure: bool | None = None,
+                 verbose: int | None = None,
+                 dryrun: bool | None = None,
+                 errors: bool | None = None) -> None:
+        '''Create new GLConfig instance.'''
         self.table = dict()
         self.table['tempdir'] = tempfile.mkdtemp()
         # Check and store the attributes.
@@ -218,11 +244,11 @@ class GLConfig(object):
             self.setErrors(errors)
 
     # Define special methods.
-    def __repr__(self):
+    def __repr__(self) -> str:
         '''x.__repr__() <==> repr(x)'''
         return '<pygnulib.GLConfig>'
 
-    def __getitem__(self, y):
+    def __getitem__(self, y: str) -> str | float | int | bool | classes.CopyAction | list[str] | None:
         '''x.__getitem__(y) <==> x[y]'''
         if y in self.table:
             result = self.table[y]
@@ -236,16 +262,16 @@ class GLConfig(object):
         else:  # if y not in self.table
             raise KeyError('GLConfig does not contain key: %s' % repr(y))
 
-    def dictionary(self):
+    def dictionary(self) -> dict[str, str | float | int | bool | classes.CopyAction | list[str] | None]:
         '''Return the configuration as a dict object.'''
         return dict(self.table)
 
-    def copy(self):
+    def copy(self) -> GLConfig:
         '''Return the copy of the configuration.'''
         table = copy.deepcopy(self)
         return table
 
-    def update(self, dictionary):
+    def update(self, dictionary: GLConfig) -> None:
         '''Specify the dictionary whose keys will be used to update config.'''
         if type(dictionary) is not GLConfig:
             raise TypeError('dictionary must be a GLConfig, not %s'
@@ -267,7 +293,7 @@ class GLConfig(object):
                         result[key] = dest
         self.table = dict(result)
 
-    def update_key(self, dictionary, key):
+    def update_key(self, dictionary: GLConfig, key: str) -> None:
         '''Update the given key using value from the given dictionary.'''
         if key in self.table:
             if type(dictionary) is not GLConfig:
@@ -278,7 +304,7 @@ class GLConfig(object):
         else:  # if key not in self.table
             raise KeyError('GLConfig does not contain key: %s' % repr(key))
 
-    def default(self, key):
+    def default(self, key: str) -> str | float | int | bool | classes.CopyAction | list[str] | None:
         '''Return default value for the given key.'''
         if key in self.table:
             if key == 'libname':
@@ -309,7 +335,7 @@ class GLConfig(object):
         else:  # if key not in self.table
             raise KeyError('GLConfig does not contain key: %s' % repr(key))
 
-    def isdefault(self, key, value):
+    def isdefault(self, key: str, value: str | float | int | bool | classes.CopyAction | list[str] | None) -> bool:
         '''Check whether the value for the given key is a default value.'''
         if key in self.table:
             default = self.default(key)
@@ -317,21 +343,21 @@ class GLConfig(object):
         else:  # if key not in self.table
             raise KeyError('GLConfig does not contain key: %s' % repr(key))
 
-    def keys(self):
+    def keys(self) -> list[str]:
         '''Return list of keys.'''
         return list(self.table.keys())
 
-    def values(self):
+    def values(self) -> list[str | float | int | bool | classes.CopyAction | list[str] | None]:
         '''Return list of values.'''
         return list(self.table.values())
 
     # Define destdir methods.
-    def getDestDir(self):
+    def getDestDir(self) -> str:
         '''Return the target directory. For --import, this specifies where your
         configure.ac can be found. Defaults to current directory.'''
         return self.table['destdir']
 
-    def setDestDir(self, destdir):
+    def setDestDir(self, destdir: str) -> None:
         '''Specify the target directory. For --import, this specifies where your
         configure.ac can be found. Defaults to current directory.'''
         if type(destdir) is str:
@@ -341,19 +367,19 @@ class GLConfig(object):
             raise TypeError('destdir must be a string, not %s'
                             % type(destdir).__name__)
 
-    def resetDestDir(self):
+    def resetDestDir(self) -> None:
         '''Reset the target directory. For --import, this specifies where your
         configure.ac can be found. Defaults to current directory.'''
         self.table['destdir'] = ''
 
     # Define localpath methods.
-    def getLocalPath(self):
+    def getLocalPath(self) -> list[str]:
         '''Return a list of local override directories where to look up files
         before looking in gnulib's directory. The first one has the highest
         priority.'''
         return self.table['localpath']
 
-    def setLocalPath(self, localpath):
+    def setLocalPath(self, localpath: list[str]) -> None:
         '''Specify a list of local override directories where to look up files
         before looking in gnulib's directory. The first one has the highest
         priority.'''
@@ -366,20 +392,20 @@ class GLConfig(object):
         self.table['localpath'] = [ remove_trailing_slashes(dir)
                                     for dir in localpath ]
 
-    def resetLocalPath(self):
+    def resetLocalPath(self) -> None:
         '''Reset a list of local override directories where to look up files
         before looking in gnulib's directory.'''
         self.table['localpath'] = []
 
     # Define auxdir methods.
-    def getAuxDir(self):
+    def getAuxDir(self) -> str:
         '''Return directory relative to --dir where auxiliary build tools are
         placed. Default comes from configure.ac or configure.in.'''
         if self.table['auxdir']:
             return self.table['auxdir']
         return "build-aux"
 
-    def setAuxDir(self, auxdir):
+    def setAuxDir(self, auxdir: str) -> None:
         '''Specify directory relative to --dir where auxiliary build tools are
         placed. Default comes from configure.ac or configure.in.'''
         if type(auxdir) is str:
@@ -389,17 +415,17 @@ class GLConfig(object):
             raise TypeError('auxdir must be a string, not %s'
                             % type(auxdir).__name__)
 
-    def resetAuxDir(self):
+    def resetAuxDir(self) -> None:
         '''Reset directory relative to --dir where auxiliary build tools are
         placed. Default comes from configure.ac or configure.in.'''
         self.table['auxdir'] = ''
 
     # Define sourcebase methods.
-    def getSourceBase(self):
+    def getSourceBase(self) -> str:
         '''Return directory relative to destdir where source code is placed.'''
         return self.table['sourcebase']
 
-    def setSourceBase(self, sourcebase):
+    def setSourceBase(self, sourcebase: str) -> None:
         '''Specify directory relative to destdir where source code is placed.'''
         if type(sourcebase) is str:
             if sourcebase:
@@ -408,16 +434,16 @@ class GLConfig(object):
             raise TypeError('sourcebase must be a string, not %s'
                             % type(sourcebase).__name__)
 
-    def resetSourceBase(self):
+    def resetSourceBase(self) -> None:
         '''Return directory relative to destdir where source code is placed.'''
         self.table['sourcebase'] = ''
 
     # Define m4base methods.
-    def getM4Base(self):
+    def getM4Base(self) -> str:
         '''Return directory relative to destdir where *.m4 macros are placed.'''
         return self.table['m4base']
 
-    def setM4Base(self, m4base):
+    def setM4Base(self, m4base: str) -> None:
         '''Specify directory relative to destdir where *.m4 macros are placed.'''
         if type(m4base) is str:
             if m4base:
@@ -426,16 +452,16 @@ class GLConfig(object):
             raise TypeError('m4base must be a string, not %s'
                             % type(m4base).__name__)
 
-    def resetM4Base(self):
+    def resetM4Base(self) -> None:
         '''Reset directory relative to destdir where *.m4 macros are placed.'''
         self.table['m4base'] = ''
 
     # Define pobase methods.
-    def getPoBase(self):
+    def getPoBase(self) -> str:
         '''Return directory relative to destdir where *.po files are placed.'''
         return self.table['pobase']
 
-    def setPoBase(self, pobase):
+    def setPoBase(self, pobase: str) -> None:
         '''Specify directory relative to destdir where *.po files are placed.'''
         if type(pobase) is str:
             if pobase:
@@ -444,17 +470,17 @@ class GLConfig(object):
             raise TypeError('pobase must be a string, not %s'
                             % type(pobase).__name__)
 
-    def resetPoBase(self):
+    def resetPoBase(self) -> None:
         '''Reset directory relative to destdir where *.po files are placed.'''
         self.table['pobase'] = ''
 
     # Define docbase methods.
-    def getDocBase(self):
+    def getDocBase(self) -> str:
         '''Return directory relative to destdir where doc files are placed.
         Default value for this variable is 'doc').'''
         return self.table['docbase']
 
-    def setDocBase(self, docbase):
+    def setDocBase(self, docbase: str) -> None:
         '''Specify directory relative to destdir where doc files are placed.
         Default value for this variable is 'doc').'''
         if type(docbase) is str:
@@ -464,18 +490,18 @@ class GLConfig(object):
             raise TypeError('docbase must be a string, not %s'
                             % type(docbase).__name__)
 
-    def resetDocBase(self):
+    def resetDocBase(self) -> None:
         '''Reset directory relative to destdir where doc files are placed.
         Default value for this variable is 'doc').'''
         self.table['docbase'] = ''
 
     # Define testsbase methods.
-    def getTestsBase(self):
+    def getTestsBase(self) -> str:
         '''Return directory relative to destdir where unit tests are placed.
         Default value for this variable is 'tests').'''
         return self.table['testsbase']
 
-    def setTestsBase(self, testsbase):
+    def setTestsBase(self, testsbase: str) -> None:
         '''Specify directory relative to destdir where unit tests are placed.
         Default value for this variable is 'tests').'''
         if type(testsbase) is str:
@@ -485,13 +511,13 @@ class GLConfig(object):
             raise TypeError('testsbase must be a string, not %s'
                             % type(testsbase).__name__)
 
-    def resetTestsBase(self):
+    def resetTestsBase(self) -> None:
         '''Reset directory relative to destdir where unit tests are placed.
         Default value for this variable is 'tests').'''
         self.table['testsbase'] = ''
 
     # Define modules methods.
-    def addModule(self, module):
+    def addModule(self, module: str) -> None:
         '''Add the module to the modules list.'''
         if type(module) is str:
             if module not in self.table['modules']:
@@ -500,7 +526,7 @@ class GLConfig(object):
             raise TypeError('module must be a string, not %s'
                             % type(module).__name__)
 
-    def removeModule(self, module):
+    def removeModule(self, module: str) -> None:
         '''Remove the module from the modules list.'''
         if type(module) is str:
             if module in self.table['modules']:
@@ -509,11 +535,11 @@ class GLConfig(object):
             raise TypeError('module must be a string, not %s'
                             % type(module).__name__)
 
-    def getModules(self):
+    def getModules(self) -> list[str]:
         '''Return the modules list.'''
         return list(self.table['modules'])
 
-    def setModules(self, modules):
+    def setModules(self, modules: list[str] | tuple[str]) -> None:
         '''Set the modules list.'''
         if type(modules) is list or type(modules) is tuple:
             old_modules = self.table['modules']
@@ -531,12 +557,12 @@ class GLConfig(object):
             raise TypeError('modules must be a list or a tuple, not %s'
                             % type(modules).__name__)
 
-    def resetModules(self):
+    def resetModules(self) -> None:
         '''Reset the list of the modules.'''
         self.table['modules'] = list()
 
     # Define avoids methods.
-    def addAvoid(self, module):
+    def addAvoid(self, module: str) -> None:
         '''Avoid including the given module. Useful if you have code that provides
         equivalent functionality.'''
         if type(module) is str:
@@ -546,7 +572,7 @@ class GLConfig(object):
             raise TypeError('avoid must be a string, not %s'
                             % type(module).__name__)
 
-    def removeAvoid(self, module):
+    def removeAvoid(self, module: str) -> None:
         '''Remove the given module from the list of avoided modules.'''
         if type(module) is str:
             if module in self.table['avoids']:
@@ -555,11 +581,11 @@ class GLConfig(object):
             raise TypeError('avoid must be a string, not %s'
                             % type(module).__name__)
 
-    def getAvoids(self):
+    def getAvoids(self) -> list[str]:
         '''Return the list of the avoided modules.'''
         return list(self.table['avoids'])
 
-    def setAvoids(self, modules):
+    def setAvoids(self, modules: list[str] | tuple[str]) -> None:
         '''Specify the modules which will be avoided.'''
         if type(modules) is list or type(modules) is tuple:
             old_avoids = self.table['avoids']
@@ -577,12 +603,12 @@ class GLConfig(object):
             raise TypeError('modules must be a list or a tuple, not %s'
                             % type(modules).__name__)
 
-    def resetAvoids(self):
+    def resetAvoids(self) -> None:
         '''Reset the list of the avoided modules.'''
         self.table['avoids'] = list()
 
     # Define files methods.
-    def addFile(self, file):
+    def addFile(self, file: str) -> None:
         '''Add file to the list of files.'''
         if type(file) is str:
             if file not in self.table['files']:
@@ -591,7 +617,7 @@ class GLConfig(object):
             raise TypeError('file must be a string, not %s'
                             % type(file).__name__)
 
-    def removeFile(self, file):
+    def removeFile(self, file: str) -> None:
         '''Remove the given file from the list of files.'''
         if type(file) is str:
             if file in self.table['files']:
@@ -600,11 +626,11 @@ class GLConfig(object):
             raise TypeError('file must be a string, not %s'
                             % type(file).__name__)
 
-    def getFiles(self):
+    def getFiles(self) -> list[str]:
         '''Return the list of files.'''
         return list(self.table['files'])
 
-    def setFiles(self, files):
+    def setFiles(self, files: list[str] | tuple[str]) -> None:
         '''Specify the list of files.'''
         if type(files) is list or type(files) is tuple:
             old_files = self.table['files']
@@ -622,19 +648,19 @@ class GLConfig(object):
             raise TypeError('files must be a list or a tuple, not %s'
                             % type(files).__name__)
 
-    def resetFiles(self):
+    def resetFiles(self) -> None:
         '''Reset the list of files.'''
         self.table['files'] = list()
 
     # Define incl_test_categories methods
-    def checkInclTestCategory(self, category):
+    def checkInclTestCategory(self, category: int) -> bool:
         '''Tests whether the given test category is included.'''
         if category in TESTS.values():
             return category in self.table['incl_test_categories']
         else:  # if category is not in TESTS
             raise TypeError('unknown category: %s' % repr(category))
 
-    def enableInclTestCategory(self, category):
+    def enableInclTestCategory(self, category: int) -> None:
         '''Enable the given test category.'''
         if category in TESTS.values():
             if category not in self.table['incl_test_categories']:
@@ -642,7 +668,7 @@ class GLConfig(object):
         else:  # if category is not in TESTS
             raise TypeError('unknown category: %s' % repr(category))
 
-    def disableInclTestCategory(self, category):
+    def disableInclTestCategory(self, category: int) -> None:
         '''Disable the given test category.'''
         if category in TESTS.values():
             if category in self.table['incl_test_categories']:
@@ -650,19 +676,19 @@ class GLConfig(object):
         else:  # if category is not in TESTS
             raise TypeError('unknown category: %s' % repr(category))
 
-    def setInclTestCategory(self, category, enable):
+    def setInclTestCategory(self, category: int, enable: bool) -> None:
         '''Enable or disable the given test category.'''
         if enable:
             self.enableInclTestCategory(category)
         else:
             self.disableInclTestCategory(category)
 
-    def getInclTestCategories(self):
+    def getInclTestCategories(self) -> list[int]:
         '''Return the test categories that should be included.
         To get the list of all test categories, use the TESTS variable.'''
         return list(self.table['incl_test_categories'])
 
-    def setInclTestCategories(self, categories):
+    def setInclTestCategories(self, categories: list[int] | tuple[int]) -> None:
         '''Specify the test categories that should be included.'''
         if type(categories) is list or type(categories) is tuple:
             old_categories = self.table['incl_test_categories']
@@ -677,19 +703,19 @@ class GLConfig(object):
             raise TypeError('categories must be a list or a tuple, not %s'
                             % type(categories).__name__)
 
-    def resetInclTestCategories(self):
+    def resetInclTestCategories(self) -> None:
         '''Reset test categories.'''
         self.table['incl_test_categories'] = list()
 
     # Define excl_test_categories methods
-    def checkExclTestCategory(self, category):
+    def checkExclTestCategory(self, category: int) -> bool:
         '''Tests whether the given test category is excluded.'''
         if category in TESTS.values():
             return category in self.table['excl_test_categories']
         else:  # if category is not in TESTS
             raise TypeError('unknown category: %s' % repr(category))
 
-    def enableExclTestCategory(self, category):
+    def enableExclTestCategory(self, category: int) -> None:
         '''Enable the given test category.'''
         if category in TESTS.values():
             if category not in self.table['excl_test_categories']:
@@ -697,7 +723,7 @@ class GLConfig(object):
         else:  # if category is not in TESTS
             raise TypeError('unknown category: %s' % repr(category))
 
-    def disableExclTestCategory(self, category):
+    def disableExclTestCategory(self, category: int) -> None:
         '''Disable the given test category.'''
         if category in TESTS.values():
             if category in self.table['excl_test_categories']:
@@ -705,12 +731,12 @@ class GLConfig(object):
         else:  # if category is not in TESTS
             raise TypeError('unknown category: %s' % repr(category))
 
-    def getExclTestCategories(self):
+    def getExclTestCategories(self) -> list[int]:
         '''Return the test categories that should be excluded.
         To get the list of all test categories, use the TESTS variable.'''
         return list(self.table['excl_test_categories'])
 
-    def setExclTestCategories(self, categories):
+    def setExclTestCategories(self, categories: list[int] | tuple[int]) -> None:
         '''Specify the test categories that should be excluded.'''
         if type(categories) is list or type(categories) is tuple:
             old_categories = self.table['excl_test_categories']
@@ -725,16 +751,16 @@ class GLConfig(object):
             raise TypeError('categories must be a list or a tuple, not %s'
                             % type(categories).__name__)
 
-    def resetExclTestCategories(self):
+    def resetExclTestCategories(self) -> None:
         '''Reset test categories.'''
         self.table['excl_test_categories'] = list()
 
     # Define libname methods.
-    def getLibName(self):
+    def getLibName(self) -> str:
         '''Return the library name.'''
         return self.table['libname']
 
-    def setLibName(self, libname):
+    def setLibName(self, libname: str) -> None:
         '''Specify the library name.'''
         if type(libname) is str:
             if libname:
@@ -743,16 +769,16 @@ class GLConfig(object):
             raise TypeError('libname must be a string, not %s'
                             % type(libname).__name__)
 
-    def resetLibName(self):
+    def resetLibName(self) -> None:
         '''Reset the library name to 'libgnu'.'''
         self.table['libname'] = 'libgnu'
 
     # Define libtool methods.
-    def checkLibtool(self):
+    def checkLibtool(self) -> bool:
         '''Check if user enabled libtool rules.'''
         return self.table['libtool']
 
-    def setLibtool(self, value):
+    def setLibtool(self, value: bool) -> None:
         '''Enable / disable libtool rules.'''
         if type(value) is bool:
             self.table['libtool'] = value
@@ -760,16 +786,16 @@ class GLConfig(object):
             raise TypeError('value must be a bool, not %s'
                             % type(value).__name__)
 
-    def resetLibtool(self):
+    def resetLibtool(self) -> None:
         '''Reset libtool rules.'''
         self.table['libtool'] = False
 
     # Define conddeps methods.
-    def checkCondDeps(self):
+    def checkCondDeps(self) -> bool:
         '''Check if user enabled conditional dependencies.'''
         return self.table['conddeps']
 
-    def setCondDeps(self, value):
+    def setCondDeps(self, value: bool) -> None:
         '''Enable / disable conditional dependencies (may save configure time and object code).'''
         if type(value) is bool:
             self.table['conddeps'] = value
@@ -777,17 +803,17 @@ class GLConfig(object):
             raise TypeError('value must be a bool, not %s'
                             % type(value).__name__)
 
-    def resetCondDeps(self):
+    def resetCondDeps(self) -> None:
         '''Reset conditional dependencies (may save configure time and object code).'''
         self.table['conddeps'] = False
 
     # Define lgpl methods.
-    def getLGPL(self):
+    def getLGPL(self) -> str | bool | None:
         '''Check for abort if modules aren't available under the LGPL.
         Default value is None, which means that lgpl is disabled.'''
         return self.table['lgpl']
 
-    def setLGPL(self, lgpl):
+    def setLGPL(self, lgpl: str | bool | None) -> None:
         '''Abort if modules aren't available under the LGPL.
         Default value is None, which means that lgpl is disabled.'''
         if lgpl in [None, True, '2', '3orGPLv2', '3']:
@@ -795,18 +821,18 @@ class GLConfig(object):
         else:
             raise TypeError('invalid LGPL version: %s' % repr(lgpl))
 
-    def resetLGPL(self):
+    def resetLGPL(self) -> None:
         '''Disable abort if modules aren't available under the LGPL.
         Default value is None, which means that lgpl is disabled.'''
         self.table['lgpl'] = None
 
     # Define gnu-make methods.
-    def getGnuMake(self):
+    def getGnuMake(self) -> bool:
         '''Return a boolean value describing whether the --gnu-make argument
         was used.'''
         return self.table['gnu_make']
 
-    def setGnuMake(self, value):
+    def setGnuMake(self, value: bool) -> None:
         '''Set the --gnu-make argument as if it were invoked using the
         command-line or disable it.'''
         if type(value) is bool:
@@ -815,29 +841,29 @@ class GLConfig(object):
             raise TypeError('value must be a bool, not %s'
                             % type(value).__name__)
 
-    def resetGnuMake(self):
+    def resetGnuMake(self) -> None:
         '''Reset the --gnu-make argument to its default. This feature must be
         explicitly enabled by programs who utilize GNU Make features instead
         of Autmake.'''
         self.table['gnu_make'] = False
 
-    def getModuleIndicatorPrefix(self):
+    def getModuleIndicatorPrefix(self) -> str:
         '''Return module_indicator_prefix to use inside GLEmiter class.'''
         return self.getIncludeGuardPrefix()
 
     # Define macro_prefix and include_guard_prefix methods.
     # The include_guard_prefix is a replacement for ${gl_include_guard_prefix}.
     # It is determined from the macro_prefix.
-    def getMacroPrefix(self):
+    def getMacroPrefix(self) -> str:
         '''Return the prefix of the macros 'gl_EARLY' and 'gl_INIT'.
         Default macro_prefix is 'gl'.'''
         return self.table['macro_prefix']
 
-    def getIncludeGuardPrefix(self):
+    def getIncludeGuardPrefix(self) -> str:
         '''Return the replacement for ${gl_include_guard_prefix}.'''
         return self.table['include_guard_prefix']
 
-    def setMacroPrefix(self, macro_prefix):
+    def setMacroPrefix(self, macro_prefix: str) -> None:
         '''Specify the prefix of the macros 'gl_EARLY' and 'gl_INIT'.
         Default macro_prefix is 'gl'.'''
         if type(macro_prefix) is str:
@@ -852,19 +878,19 @@ class GLConfig(object):
             include_guard_prefix = 'GL_%s' % macro_prefix.upper()
         self.table['include_guard_prefix'] = include_guard_prefix
 
-    def resetMacroPrefix(self):
+    def resetMacroPrefix(self) -> None:
         '''Reset the prefix of the macros 'gl_EARLY' and 'gl_INIT'.
         Default macro_prefix is 'gl'.'''
         self.table['macro_prefix'] = 'gl'
         self.table['include_guard_prefix'] = 'GL'
 
     # Define makefile_name methods.
-    def getMakefileName(self):
+    def getMakefileName(self) -> str:
         '''Return the name of makefile in automake syntax in the sourcebase directory.
         Default is 'Makefile.am'.'''
         return self.table['makefile_name']
 
-    def setMakefileName(self, makefile_name):
+    def setMakefileName(self, makefile_name: str) -> None:
         '''Specify the name of makefile in automake syntax in the sourcebase directory.
         Default is 'Makefile.am'.'''
         if type(makefile_name) is str:
@@ -874,18 +900,18 @@ class GLConfig(object):
             raise TypeError('makefile_name must be a string, not %s'
                             % type(makefile_name).__name__)
 
-    def resetMakefileName(self):
+    def resetMakefileName(self) -> None:
         '''Reset the name of makefile in automake syntax in the sourcebase directory.
         Default is 'Makefile.am'.'''
         self.table['makefile_name'] = ''
 
     # Define tests_makefile_name methods.
-    def getTestsMakefileName(self):
+    def getTestsMakefileName(self) -> str:
         '''Return the name of makefile in automake syntax in the testsbase directory.
         Default is the the value of 'makefile_name'.'''
         return self.table['tests_makefile_name']
 
-    def setTestsMakefileName(self, tests_makefile_name):
+    def setTestsMakefileName(self, tests_makefile_name: str) -> None:
         '''Specify the name of makefile in automake syntax in the testsbase directory.
         Default is the value of 'makefile_name'.'''
         if type(tests_makefile_name) is str:
@@ -895,7 +921,7 @@ class GLConfig(object):
             raise TypeError('tests_makefile_name must be a string, not %s'
                             % type(tests_makefile_name).__name__)
 
-    def resetTestsMakefileName(self):
+    def resetTestsMakefileName(self) -> None:
         '''Reset the name of makefile in automake syntax in the testsbase directory.
         Default is the value of 'makefile_name'.'''
         self.table['tests_makefile_name'] = ''
@@ -939,12 +965,12 @@ class GLConfig(object):
         self.table['automake_subdir_tests'] = False
 
     # Define podomain methods.
-    def getPoDomain(self):
+    def getPoDomain(self) -> str:
         '''Return the prefix of the i18n domain. Usually use the package name.
         A suffix '-gnulib' is appended.'''
         return self.table['podomain']
 
-    def setPoDomain(self, podomain):
+    def setPoDomain(self, podomain: str) -> None:
         '''Specify the prefix of the i18n domain. Usually use the package name.
         A suffix '-gnulib' is appended.'''
         if type(podomain) is str:
@@ -954,18 +980,18 @@ class GLConfig(object):
             raise TypeError('podomain must be a string, not %s'
                             % type(podomain).__name__)
 
-    def resetPoDomain(self):
+    def resetPoDomain(self) -> None:
         '''Reset the prefix of the i18n domain. Usually use the package name.
         A suffix '-gnulib' is appended.'''
         self.table['podomain'] = ''
 
     # Define witness_c_macro methods.
-    def getWitnessCMacro(self):
+    def getWitnessCMacro(self) -> str:
         '''Return the C macro that is defined when the sources in this directory
         are compiled or used.'''
         return self.table['witness_c_macro']
 
-    def setWitnessCMacro(self, witness_c_macro):
+    def setWitnessCMacro(self, witness_c_macro: str) -> None:
         '''Specify the C macro that is defined when the sources in this directory
         are compiled or used.'''
         if type(witness_c_macro) is str:
@@ -975,17 +1001,17 @@ class GLConfig(object):
             raise TypeError('witness_c_macro must be a string, not %s'
                             % type(witness_c_macro).__name__)
 
-    def resetWitnessCMacro(self):
+    def resetWitnessCMacro(self) -> None:
         '''Return the C macro that is defined when the sources in this directory
         are compiled or used.'''
         self.table['witness_c_macro'] = ''
 
     # Define vc_files methods.
-    def checkVCFiles(self):
+    def checkVCFiles(self) -> bool | None:
         '''Check if update of the version control files is enabled or disabled.'''
         return self.table['vc_files']
 
-    def setVCFiles(self, value):
+    def setVCFiles(self, value: bool) -> None:
         '''Enable /disable update of the version control files.'''
         if type(value) is bool:
             self.table['vc_files'] = value
@@ -993,16 +1019,16 @@ class GLConfig(object):
             raise TypeError('value must be a bool, not %s'
                             % type(value).__name__)
 
-    def resetVCFiles(self):
+    def resetVCFiles(self) -> None:
         '''Reset update of the version control files and set it to None.'''
         self.table['vc_files'] = None
 
     # Define configure_ac methods.
-    def getAutoconfFile(self):
+    def getAutoconfFile(self) -> str:
         '''Return path of autoconf file relative to destdir.'''
         return self.table['configure_ac']
 
-    def setAutoconfFile(self, configure_ac):
+    def setAutoconfFile(self, configure_ac: str) -> None:
         '''Specify path of autoconf file relative to destdir.'''
         if type(configure_ac) is str:
             if configure_ac:
@@ -1012,7 +1038,7 @@ class GLConfig(object):
             raise TypeError('configure_ac must be a string, not %s'
                             % type(configure_ac).__name__)
 
-    def resetAutoconfFile(self):
+    def resetAutoconfFile(self) -> None:
         '''Reset path of autoconf file relative to destdir.'''
         configure_ac = ''
         if isfile(joinpath(self.table['destdir'], 'configure.ac')):
@@ -1022,11 +1048,11 @@ class GLConfig(object):
         self.table['configure_ac'] = configure_ac
 
     # Define ac_version methods.
-    def getAutoconfVersion(self):
+    def getAutoconfVersion(self) -> float:
         '''Return preferred autoconf version. Default value is 2.64.'''
         return self.table['ac_version']
 
-    def setAutoconfVersion(self, ac_version):
+    def setAutoconfVersion(self, ac_version: float | int) -> None:
         '''Specify preferred autoconf version. Default value is 2.64.'''
         if type(ac_version) is float or type(ac_version) is int:
             self.table['ac_version'] = float(ac_version)
@@ -1034,16 +1060,16 @@ class GLConfig(object):
             raise TypeError('ac_version must be an int or a float, not %s'
                             % type(ac_version).__name__)
 
-    def resetAutoconfVersion(self):
+    def resetAutoconfVersion(self) -> None:
         '''Specify preferred autoconf version. Default value is 2.64.'''
         self.table['ac_version'] = 2.64
 
     # Define copymode methods.
-    def checkCopyMode(self):
+    def checkCopyMode(self) -> classes.CopyAction:
         '''Check if pygnulib will copy files, create symlinks, or create hard links.'''
         return self.table['copymode']
 
-    def setCopyMode(self, value):
+    def setCopyMode(self, value: classes.CopyAction) -> None:
         '''Change the method used for copying / linking files.'''
         if type(value) is classes.CopyAction:
             self.table['copymode'] = value
@@ -1051,17 +1077,17 @@ class GLConfig(object):
             raise TypeError('value must be a CopyAction, not %s'
                             % type(value).__name__)
 
-    def resetCopyMode(self):
+    def resetCopyMode(self) -> None:
         '''Reset the method used for creating files to copying instead of linking.'''
         self.table['copymode'] = classes.CopyAction.Copy
 
     # Define lcopymode methods.
-    def checkLCopyMode(self):
+    def checkLCopyMode(self) -> classes.CopyAction:
         '''Check if pygnulib will copy files, create symlinks, or create hard links,
         only for files from the local override directories.'''
         return self.table['lcopymode']
 
-    def setLCopyMode(self, value):
+    def setLCopyMode(self, value: classes.CopyAction) -> None:
         '''Change the method used for copying / linking files, only for files from
         the local override directories.'''
         if type(value) is classes.CopyAction:
@@ -1070,27 +1096,27 @@ class GLConfig(object):
             raise TypeError('value must be a CopyAction, not %s'
                             % type(value).__name__)
 
-    def resetLCopyMode(self):
+    def resetLCopyMode(self) -> None:
         '''Reset the method used for creating files to copying instead of linking,
         only for files from the local override directories.'''
         self.table['lcopymode'] = classes.CopyAction.Copy
 
     # Define verbosity methods.
-    def getVerbosity(self):
+    def getVerbosity(self) -> int:
         '''Get verbosity level.'''
         return self.table['verbosity']
 
-    def decreaseVerbosity(self):
+    def decreaseVerbosity(self) -> None:
         '''Decrease verbosity level.'''
         if self.table['verbosity'] > MODES['verbose-min']:
             self.table['verbosity'] -= 1
 
-    def increaseVerbosity(self):
+    def increaseVerbosity(self) -> None:
         '''Increase verbosity level.'''
         if self.table['verbosity'] < MODES['verbose-max']:
             self.table['verbosity'] += 1
 
-    def setVerbosity(self, verbose):
+    def setVerbosity(self, verbose: int) -> None:
         '''Set verbosity level to verbose, where -2 <= verbose <= 2.
         If verbosity level is less than -2, verbosity level will be set to -2.
         If verbosity level is greater than 2, verbosity level will be set to 2.'''
@@ -1105,16 +1131,16 @@ class GLConfig(object):
             raise TypeError('verbosity must be an int, not %s'
                             % type(verbose).__name__)
 
-    def resetVerbosity(self):
+    def resetVerbosity(self) -> None:
         '''Reset verbosity level.'''
         self.table['verbosity'] = 0
 
     # Define libtests methods.
-    def checkLibtests(self):
+    def checkLibtests(self) -> bool:
         '''Return True if a testsbase/libtests.a is needed.'''
         return self.table['libtests']
 
-    def setLibtests(self, value):
+    def setLibtests(self, value: bool) -> None:
         '''Specifies whether testsbase/libtests.a is needed.'''
         if type(value) is bool:
             self.table['libtests'] = value
@@ -1122,16 +1148,16 @@ class GLConfig(object):
             raise TypeError('value must be a bool, not %s'
                             % type(value).__name__)
 
-    def resetLibtests(self):
+    def resetLibtests(self) -> None:
         '''Reset status of testsbase/libtests.a.'''
         self.table['libtests'] = False
 
     # Define single_configure methods.
-    def checkSingleConfigure(self):
+    def checkSingleConfigure(self) -> bool:
         '''Check whether single configure file should be generated.'''
         return self.table['single_configure']
 
-    def setSingleConfigure(self, value):
+    def setSingleConfigure(self, value: bool) -> None:
         '''Enable / disable generation of the single configure file.'''
         if type(value) is bool:
             self.table['single_configure'] = value
@@ -1139,16 +1165,16 @@ class GLConfig(object):
             raise TypeError('value must be a bool, not %s'
                             % type(value).__name__)
 
-    def resetSingleConfigure(self):
+    def resetSingleConfigure(self) -> None:
         '''Reset status of the single configure file generation.'''
         self.table['single_configure'] = False
 
     # Define dryrun methods.
-    def checkDryRun(self):
+    def checkDryRun(self) -> bool:
         '''Check whether dryrun is enabled.'''
         return self.table['dryrun']
 
-    def setDryRun(self, value):
+    def setDryRun(self, value: bool) -> None:
         '''Enable / disable dryrun mode.'''
         if type(value) is bool:
             self.table['dryrun'] = value
@@ -1156,16 +1182,16 @@ class GLConfig(object):
             raise TypeError('value must be a bool, not %s'
                             % type(value).__name__)
 
-    def resetDryRun(self):
+    def resetDryRun(self) -> None:
         '''Reset status of dryrun mode.'''
         self.table['dryrun'] = False
 
     # Define errors methods.
-    def checkErrors(self):
+    def checkErrors(self) -> bool:
         '''Check if GLError will be raised in non-critical situations.'''
         return self.table['errors']
 
-    def setErrors(self, value):
+    def setErrors(self, value: bool) -> None:
         '''Enable / disable raising GLError in non-critical situations.'''
         if type(value) is bool:
             self.table['errors'] = value
@@ -1173,6 +1199,6 @@ class GLConfig(object):
             raise TypeError('value must be a bool, not %s'
                             % type(value).__name__)
 
-    def resetErrors(self):
+    def resetErrors(self) -> None:
         '''Reset status of raising GLError in non-critical situations.'''
         self.table['errors'] = False
diff --git a/pygnulib/GLEmiter.py b/pygnulib/GLEmiter.py
index ba04c6b170..e245390aa0 100644
--- a/pygnulib/GLEmiter.py
+++ b/pygnulib/GLEmiter.py
@@ -13,9 +13,6 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
-# Allow the use of union type specifiers, using the syntax Type1 | Type2,
-# in Python ≥ 3.7.  Cf. <https://docs.python.org/3/library/__future__.html>
-# and <https://stackoverflow.com/questions/73879925/>.
 from __future__ import annotations
 
 #===============================================================================
@@ -114,25 +111,21 @@ def _eliminate_NMD(snippet: str, automake_subdir: bool) -> str:
 class GLEmiter(object):
     '''This class is used to emit the contents of necessary files.'''
 
-    def __init__(self, config):
-        '''GLEmiter.__init__(config) -> GLEmiter
-
-        Create GLEmiter instance.'''
+    def __init__(self, config: GLConfig) -> None:
+        '''Create GLEmiter instance.'''
         self.info = GLInfo()
         if type(config) is not GLConfig:
             raise TypeError('config must be a GLConfig, not %s'
                             % type(config).__name__)
         self.config = config
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         '''x.__repr__() <==> repr(x)'''
         result = '<pygnulib.GLEmiter %s>' % hex(id(self))
         return result
 
-    def copyright_notice(self):
-        '''GLEmiter.copyright_notice() -> str
-
-        Emit a header for a generated file.'''
+    def copyright_notice(self) -> str:
+        '''Emit a header for a generated file.'''
         emit = '# %s' % self.info.copyright_range()
         emit += """
 #
@@ -188,12 +181,9 @@ class GLEmiter(object):
             emit += '  gl_source_base_prefix=\n'
         return emit
 
-    def autoconfSnippet(self, module, toplevel,
-                        disable_libtool, disable_gettext, replace_auxdir, indentation):
-        '''GLEmiter.autoconfSnippet(module, toplevel,
-          disable_libtool, disable_gettext, replace_auxdir, indentation) -> str
-
-        Emit the autoconf snippet of a module.
+    def autoconfSnippet(self, module: GLModule, toplevel: bool, disable_libtool: bool,
+                        disable_gettext: bool, replace_auxdir: bool, indentation: str) -> str:
+        '''Emit the autoconf snippet of a module.
         GLConfig: include_guard_prefix.
 
         module is a GLModule instance, which is processed.
@@ -269,12 +259,10 @@ class GLEmiter(object):
         emit = lines_to_multiline(lines)
         return emit
 
-    def autoconfSnippets(self, modules, referenceable_modules, moduletable,
-                         verifier, toplevel, disable_libtool, disable_gettext, replace_auxdir):
-        '''GLEmiter.autoconfSnippets(modules,
-          verifier, toplevel, disable_libtool, disable_gettext, replace_auxdir) -> str
-
-        Collect and emit the autoconf snippets of a set of modules.
+    def autoconfSnippets(self, modules: list[GLModule], referenceable_modules: list[GLModule],
+                         moduletable: GLModuleTable, verifier: int, toplevel: bool,
+                         disable_libtool: bool, disable_gettext: bool, replace_auxdir: bool) -> str:
+        '''Collect and emit the autoconf snippets of a set of modules.
         GLConfig: conddeps.
 
         basemodules argument represents list of modules; every module in this list
@@ -452,10 +440,8 @@ class GLEmiter(object):
         emit = lines_to_multiline(lines)
         return emit
 
-    def preEarlyMacros(self, require, indentation, modules):
-        '''GLEmiter.preEarlyMacros(require, indentation, modules) -> str
-
-        Collect and emit the pre-early section.
+    def preEarlyMacros(self, require: bool, indentation: str, modules: list[GLModule]) -> str:
+        '''Collect and emit the pre-early section.
 
         require parameter can be True (AC_REQUIRE) or False (direct call).
         indentation parameter is a string.
@@ -476,10 +462,8 @@ class GLEmiter(object):
         emit += '\n'
         return emit
 
-    def po_Makevars(self):
-        '''GLEmiter.po_Makevars() -> str
-
-        Emit the contents of po/ makefile parameterization.
+    def po_Makevars(self) -> str:
+        '''Emit the contents of po/ makefile parameterization.
         GLConfig: pobase, podomain.'''
         pobase = self.config['pobase']
         podomain = self.config['podomain']
@@ -533,10 +517,8 @@ EXTRA_LOCALE_CATEGORIES =
 USE_MSGCTXT = no\n"""
         return emit
 
-    def po_POTFILES_in(self, files):
-        '''GLEmiter.po_POTFILES_in(files) -> str
-
-        Emit the file list to be passed to xgettext.
+    def po_POTFILES_in(self, files: list[str]) -> str:
+        '''Emit the file list to be passed to xgettext.
         GLConfig: sourcebase.'''
         sourcebase = self.config['sourcebase'] + os.path.sep
         emit = ''
@@ -548,10 +530,8 @@ USE_MSGCTXT = no\n"""
                 emit += '%s\n' % constants.substart('lib/', sourcebase, file)
         return emit
 
-    def initmacro_start(self, macro_prefix_arg, gentests):
-        '''GLEmiter.initmacro_start(macro_prefix_arg, gentests) -> str
-
-        Emit the first few statements of the gl_INIT macro.
+    def initmacro_start(self, macro_prefix_arg: str, gentests: bool) -> str:
+        '''Emit the first few statements of the gl_INIT macro.
 
         macro_prefix_arg is the prefix of gl_EARLY and gl_INIT macros to use.
         gentests is True if a tests Makefile.am is being generated, False
@@ -598,10 +578,8 @@ USE_MSGCTXT = no\n"""
             emit += '  AC_REQUIRE([gl_CXX_ALLOW_WARNINGS])\n'
         return emit
 
-    def initmacro_end(self, macro_prefix_arg, gentests):
-        '''GLEmiter.initmacro_end(macro_prefix_arg) -> str
-
-        Emit the last few statements of the gl_INIT macro.
+    def initmacro_end(self, macro_prefix_arg: str, gentests: bool) -> str:
+        '''Emit the last few statements of the gl_INIT macro.
 
         macro_prefix_arg is the prefix of gl_EARLY, gl_INIT macros to use.
         gentests is a bool that is True if a tests Makefile.am is being
@@ -679,10 +657,8 @@ changequote([, ])dnl
 '''
         return emit
 
-    def initmacro_done(self, macro_prefix_arg, sourcebase_arg):
-        '''GLEmiter.initmacro_done(macro_prefix_arg, sourcebase_arg) -> str
-
-        Emit a few statements after the gl_INIT macro.
+    def initmacro_done(self, macro_prefix_arg: str, sourcebase_arg: str) -> str:
+        '''Emit a few statements after the gl_INIT macro.
         GLConfig: sourcebase.'''
         if type(macro_prefix_arg) is not str:
             raise TypeError('macro_prefix_arg must be a string, not %s'
@@ -723,12 +699,9 @@ AC_DEFUN([%V1%_LIBSOURCES], [
         emit = emit.replace('%V2%', sourcebase_arg)
         return emit
 
-    def lib_Makefile_am(self, destfile, modules,
-                        moduletable, makefiletable, actioncmd, for_test):
-        '''GLEmiter.lib_Makefile_am(destfile, modules, moduletable, makefiletable,
-             actioncmd, for_test) -> tuple of str and bool
-
-        Emit the contents of the library Makefile. Returns str and a bool
+    def lib_Makefile_am(self, destfile: str, modules: list[GLModule], moduletable: GLModuleTable,
+                        makefiletable: GLMakefileTable, actioncmd: str, for_test: bool) -> tuple[str, bool]:
+        '''Emit the contents of the library Makefile. Returns str and a bool
         variable which shows if subdirectories are used.
         GLConfig: localpath, sourcebase, libname, pobase, auxdir, makefile_name, libtool,
         macro_prefix, podomain, conddeps, witness_c_macro.
@@ -1035,12 +1008,9 @@ AC_DEFUN([%V1%_LIBSOURCES], [
         result = tuple([emit, uses_subdirs])
         return result
 
-    def tests_Makefile_am(self, destfile, modules, moduletable,
-                          makefiletable, witness_macro, for_test):
-        '''GLEmiter.tests_Makefile_am(destfile, modules, moduletable,
-             makefiletable, witness_c_macro, for_test) -> tuple of string and bool
-
-        Emit the contents of the tests Makefile. Returns str and a bool variable
+    def tests_Makefile_am(self, destfile: str, modules: list[GLModule], moduletable: GLModuleTable,
+                          makefiletable: GLMakefileTable, witness_macro: str, for_test: bool) -> tuple[str, bool]:
+        '''Emit the contents of the tests Makefile. Returns str and a bool variable
         which shows if subdirectories are used.
         GLConfig: localpath, modules, libname, auxdir, makefile_name, libtool,
         sourcebase, m4base, testsbase, macro_prefix, witness_c_macro,
diff --git a/pygnulib/GLError.py b/pygnulib/GLError.py
index 17b513beb6..e1b6d747d5 100644
--- a/pygnulib/GLError.py
+++ b/pygnulib/GLError.py
@@ -13,6 +13,8 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
+from __future__ import annotations
+
 #===============================================================================
 # Define global imports
 #===============================================================================
@@ -39,7 +41,7 @@ __copyright__ = constants.__copyright__
 class GLError(Exception):
     '''Exception handler for pygnulib classes.'''
 
-    def __init__(self, errno, errinfo=None):
+    def __init__(self, errno: int, errinfo: str | float | None = None) -> None:
         '''Each error has following parameters:
         errno: code of error; used to catch error type
           1: file does not exist in GLFileSystem: <file>
@@ -68,7 +70,7 @@ class GLError(Exception):
         self.errinfo = errinfo
         self.args = (self.errno, self.errinfo)
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         errno = self.errno
         errinfo = self.errinfo
         if self.message == None:
diff --git a/pygnulib/GLFileSystem.py b/pygnulib/GLFileSystem.py
index 72827c0547..0397af4df3 100644
--- a/pygnulib/GLFileSystem.py
+++ b/pygnulib/GLFileSystem.py
@@ -13,6 +13,8 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
+from __future__ import annotations
+
 #===============================================================================
 # Define global imports
 #===============================================================================
@@ -68,7 +70,7 @@ class GLFileSystem(object):
     Its main method lookup(file) is used to find file in these directories or
     combine it using Linux 'patch' utility.'''
 
-    def __init__(self, config):
+    def __init__(self, config: GLConfig) -> None:
         '''Create new GLFileSystem instance. The only argument is localpath,
         which can be an empty list.'''
         if type(config) is not GLConfig:
@@ -76,15 +78,13 @@ class GLFileSystem(object):
                             % type(config).__name__)
         self.config = config
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         '''x.__repr__ <==> repr(x)'''
         result = '<pygnulib.GLFileSystem %s>' % hex(id(self))
         return result
 
-    def lookup(self, name):
-        '''GLFileSystem.lookup(name) -> tuple
-
-        Lookup a file in gnulib and localpath directories or combine it using
+    def lookup(self, name: str) -> tuple[str, bool]:
+        '''Lookup a file in gnulib and localpath directories or combine it using
         'patch' utility. If file was found, method returns string, else it raises
         GLError telling that file was not found. Function also returns flag which
         indicates whether file is a temporary file.
@@ -138,7 +138,7 @@ class GLFileSystem(object):
             raise GLError(1, name)
         return result
 
-    def shouldLink(self, original, lookedup):
+    def shouldLink(self, original: str, lookedup: str) -> bool:
         '''GLFileSystem.shouldLink(original, lookedup)
 
         Determines whether the original file should be copied, symlinked,
@@ -163,7 +163,7 @@ class GLFileSystem(object):
 class GLFileAssistant(object):
     '''GLFileAssistant is used to help with file processing.'''
 
-    def __init__(self, config, transformers=dict()):
+    def __init__(self, config: GLConfig, transformers: dict = dict()):
         '''Create GLFileAssistant instance.'''
         if type(config) is not GLConfig:
             raise TypeError('config must be a GLConfig, not %s'
@@ -186,15 +186,13 @@ class GLFileAssistant(object):
         self.transformers = transformers
         self.filesystem = GLFileSystem(self.config)
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         '''x.__repr__() <==> repr(x)'''
         result = '<pygnulib.GLFileAssistant %s>' % hex(id(self))
         return result
 
-    def tmpfilename(self, path):
-        '''GLFileAssistant.tmpfilename() -> str
-
-        Return the name of a temporary file (file is relative to destdir).'''
+    def tmpfilename(self, path: str) -> str:
+        '''Return the name of a temporary file (file is relative to destdir).'''
         if type(path) is not str:
             raise TypeError('path must be a string, not %s'
                             % (type(path).__name__))
@@ -215,46 +213,36 @@ class GLFileAssistant(object):
                 os.makedirs(dirname)
         return result
 
-    def setOriginal(self, original):
-        '''GLFileAssistant.setOriginal(original)
-
-        Set the name of the original file which will be used.'''
+    def setOriginal(self, original: str) -> None:
+        '''Set the name of the original file which will be used.'''
         if type(original) is not str:
             raise TypeError('original must be a string, not %s'
                             % (type(original).__name__))
         self.original = original
 
-    def setRewritten(self, rewritten):
-        '''GLFileAssistant.setRewritten(rewritten)
-
-        Set the name of the rewritten file which will be used.'''
+    def setRewritten(self, rewritten: str) -> None:
+        '''Set the name of the rewritten file which will be used.'''
         if type(rewritten) is not str:
             raise TypeError('rewritten must be a string, not %s'
                             % type(rewritten).__name__)
         self.rewritten = rewritten
 
-    def addFile(self, file):
-        '''GLFileAssistant.addFile(file)
-
-        Add file to the list of added files.'''
+    def addFile(self, file: str) -> None:
+        '''Add file to the list of added files.'''
         if file not in self.added:
             self.added += [file]
 
-    def removeFile(self, file):
-        '''GLFileAssistant.removeFile(file)
-
-        Remove file from the list of added files.'''
+    def removeFile(self, file: str) -> None:
+        '''Remove file from the list of added files.'''
         if file in self.added:
             self.added.pop(file)
 
-    def getFiles(self):
+    def getFiles(self) -> list[str]:
         '''Return list of the added files.'''
         return list(self.added)
 
-    def add(self, lookedup, tmpflag, tmpfile):
-        '''GLFileAssistant.add(lookedup, tmpflag, tmpfile)
-
-        This method copies a file from gnulib into the destination directory.
+    def add(self, lookedup: str, tmpflag: bool, tmpfile: str) -> None:
+        '''This method copies a file from gnulib into the destination directory.
         The destination is known to exist. If tmpflag is True, then lookedup file
         is a temporary one.'''
         original = self.original
@@ -281,10 +269,8 @@ class GLFileAssistant(object):
         else:  # if self.config['dryrun']
             print('Copy file %s' % rewritten)
 
-    def update(self, lookedup, tmpflag, tmpfile, already_present):
-        '''GLFileAssistant.update(lookedup, tmpflag, tmpfile, already_present)
-
-        This method copies a file from gnulib into the destination directory.
+    def update(self, lookedup: str, tmpflag: bool, tmpfile: str, already_present: bool) -> None:
+        '''This method copies a file from gnulib into the destination directory.
         The destination is known to exist. If tmpflag is True, then lookedup file
         is a temporary one.'''
         original = self.original
@@ -339,10 +325,8 @@ class GLFileAssistant(object):
                 else:  # if not already_present
                     print('Replace file %s (backup in %s)' % (rewritten, backupname))
 
-    def add_or_update(self, already_present):
-        '''GLFileAssistant.add_or_update(already_present)
-
-        This method handles a file that ought to be present afterwards.'''
+    def add_or_update(self, already_present: bool) -> None:
+        '''This method handles a file that ought to be present afterwards.'''
         original = self.original
         rewritten = self.rewritten
         if original == None:
@@ -401,10 +385,8 @@ class GLFileAssistant(object):
         if isfile(tmpfile):
             os.remove(tmpfile)
 
-    def super_update(self, basename, tmpfile):
-        '''GLFileAssistant.super_update(basename, tmpfile) -> tuple
-
-        Move tmpfile to destdir/basename path, making a backup of it.
+    def super_update(self, basename: str, tmpfile: str) -> tuple[str, str, int]:
+        '''Move tmpfile to destdir/basename path, making a backup of it.
         Returns tuple, which contains basename, backupname and status.
           0: tmpfile is the same as destfile;
           1: tmpfile was used to update destfile;
diff --git a/pygnulib/GLImport.py b/pygnulib/GLImport.py
index d7d820f8d1..fcaffea650 100644
--- a/pygnulib/GLImport.py
+++ b/pygnulib/GLImport.py
@@ -13,6 +13,8 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
+from __future__ import annotations
+
 #===============================================================================
 # Define global imports
 #===============================================================================
@@ -68,7 +70,7 @@ class GLImport(object):
     scripts. However, if user needs just to use power of gnulib-tool, this class
     is a very good choice.'''
 
-    def __init__(self, config, mode):
+    def __init__(self, config: GLConfig, mode: int) -> None:
         '''Create GLImport instance.
         The first variable, mode, must be one of the values of the MODES dict
         object, which is accessible from constants module. The second one, config,
@@ -294,15 +296,13 @@ class GLImport(object):
                                          self.config.checkInclTestCategory(TESTS['all-tests']))
         self.makefiletable = GLMakefileTable(self.config)
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         '''x.__repr__ <==> repr(x)'''
         result = '<pygnulib.GLImport %s>' % hex(id(self))
         return result
 
-    def rewrite_old_files(self, files):
-        '''GLImport.rewrite_old_files(files) -> list
-
-        Replace auxdir, docbase, sourcebase, m4base and testsbase from default
+    def rewrite_old_files(self, files: list[str]) -> list[str]:
+        '''Replace auxdir, docbase, sourcebase, m4base and testsbase from default
         to their version from cached config.'''
         if type(files) is not list:
             raise TypeError('files argument must has list type, not %s'
@@ -340,11 +340,9 @@ class GLImport(object):
         result = sorted(set(result))
         return list(result)
 
-    def rewrite_new_files(self, files):
-        '''GLImport.rewrite_new_files(files)
-
-        Replace auxdir, docbase, sourcebase, m4base and testsbase from default
-        to their version from config.'''
+    def rewrite_new_files(self, files: list[str]) -> list[str]:
+        '''Replace auxdir, docbase, sourcebase, m4base and testsbase from
+        default to their version from config.'''
         if type(files) is not list:
             raise TypeError('files argument must has list type, not %s'
                             % type(files).__name__)
@@ -379,7 +377,7 @@ class GLImport(object):
         result = sorted(set(result))
         return list(result)
 
-    def actioncmd(self):
+    def actioncmd(self) -> str:
         '''Return command-line invocation comment.'''
         modules = self.config.getModules()
         avoids = self.config.getAvoids()
@@ -477,11 +475,9 @@ class GLImport(object):
             actioncmd += ''.join([f' \\\n#  {x}' for x in modules])
         return actioncmd
 
-    def relative_to_destdir(self, dir):
-        '''GLImport.relative_to_destdir(dir) -> str
-
-        Convert a filename that represents dir, relative to the current directory,
-        to a filename relative to destdir.
+    def relative_to_destdir(self, dir: str) -> str:
+        '''Convert a filename that represents dir, relative to the current
+        directory, to a filename relative to destdir.
         GLConfig: destdir.'''
         destdir = self.config['destdir']
         if dir.startswith('/'):
@@ -493,10 +489,8 @@ class GLImport(object):
             else:
                 return constants.relativize(destdir, dir)
 
-    def relative_to_currdir(self, dir):
-        '''GLImport.relative_to_currdir(dir) -> str
-
-        The opposite of GLImport.relative_to_destdir:
+    def relative_to_currdir(self, dir: str) -> str:
+        '''The opposite of GLImport.relative_to_destdir:
         Convert a filename that represents dir, relative to destdir,
         to a filename relative to the current directory.
         GLConfig: destdir.'''
@@ -510,10 +504,8 @@ class GLImport(object):
             else:
                 return constants.relconcat(destdir, dir)
 
-    def gnulib_cache(self):
-        '''GLImport.gnulib_cache() -> str
-
-        Emit the contents of generated $m4base/gnulib-cache.m4 file.
+    def gnulib_cache(self) -> str:
+        '''Emit the contents of generated $m4base/gnulib-cache.m4 file.
         GLConfig: destdir, localpath, tests, sourcebase, m4base, pobase, docbase,
         testsbase, conddeps, libtool, macro_prefix, podomain, vc_files.'''
         emit = ''
@@ -599,10 +591,8 @@ class GLImport(object):
             emit += 'gl_VC_FILES([%s])\n' % str(vc_files).lower()
         return constants.nlconvert(emit)
 
-    def gnulib_comp(self, filetable, gentests):
-        '''GLImport.gnulib_comp(files) -> str
-
-        Emit the contents of generated $m4base/gnulib-comp.m4 file.
+    def gnulib_comp(self, filetable: dict[str, list[str]], gentests: bool) -> str:
+        '''Emit the contents of generated $m4base/gnulib-comp.m4 file.
         GLConfig: destdir, localpath, tests, sourcebase, m4base, pobase, docbase,
         testsbase, conddeps, libtool, macro_prefix, podomain, vc_files.
 
@@ -764,10 +754,8 @@ AC_DEFUN([%s_FILE_LIST], [\n''' % macro_prefix
         emit += '])\n'
         return emit
 
-    def _done_dir_(self, directory, files_added, files_removed):
-        '''GLImport._done_dir_(directory, files_added, files_removed)
-
-        This method is used to determine ignore argument for _update_ignorelist_
+    def _done_dir_(self, directory: str, files_added: list[str], files_removed: list[str]) -> None:
+        '''This method is used to determine ignore argument for _update_ignorelist_
         method and then call it.'''
         destdir = self.config['destdir']
         if (isdir(joinpath(destdir, 'CVS'))
@@ -781,10 +769,9 @@ AC_DEFUN([%s_FILE_LIST], [\n''' % macro_prefix
             self._update_ignorelist_(directory, '.gitignore',
                                      files_added, files_removed)
 
-    def _update_ignorelist_(self, directory, ignore, files_added, files_removed):
-        '''GLImport._update_ignorelist_(directory, ignore, files_added, files_removed)
-
-        Update .gitignore or .cvsignore files.'''
+    def _update_ignorelist_(self, directory: str, ignore: str, files_added: list[str],
+                            files_removed: list[str]) -> None:
+        '''Update .gitignore or .cvsignore files.'''
         destdir = self.config['destdir']
         if ignore == '.gitignore':
             # In a .gitignore file, "foo" applies to the current directory and all
@@ -839,7 +826,7 @@ AC_DEFUN([%s_FILE_LIST], [\n''' % macro_prefix
                 else:  # if self.config['dryrun']
                     print('Create %s' % srcpath)
 
-    def prepare(self):
+    def prepare(self) -> tuple[dict[str, list[str]], dict[str, str]]:
         '''Make all preparations before the execution of the code.
         Returns filetable and sed transformers, which change the license.'''
         destdir = self.config['destdir']
@@ -1024,7 +1011,7 @@ AC_DEFUN([%s_FILE_LIST], [\n''' % macro_prefix
         result = tuple([filetable, transformers])
         return result
 
-    def execute(self, filetable, transformers):
+    def execute(self, filetable: dict[str, list[str]], transformers: dict[str, str]) -> None:
         '''Perform operations on the lists of files, which are given in a special
         format except filelist argument. Such lists of files can be created using
         GLImport.prepare() function.'''
diff --git a/pygnulib/GLInfo.py b/pygnulib/GLInfo.py
index 7cde62ad9e..ff96e93999 100644
--- a/pygnulib/GLInfo.py
+++ b/pygnulib/GLInfo.py
@@ -13,6 +13,8 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
+from __future__ import annotations
+
 #===============================================================================
 # Define global imports
 #===============================================================================
@@ -48,17 +50,17 @@ class GLInfo(object):
     anywhere else. The return values are not the same as for the module,
     but still depends on them.'''
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         '''x.__repr__ <==> repr(x)'''
         result = '<pygnulib.GLInfo %s>' % hex(id(self))
         return result
 
-    def package(self):
+    def package(self) -> str:
         '''Return formatted string which contains name of the package.'''
         result = 'GNU gnulib'
         return result
 
-    def authors(self):
+    def authors(self) -> str:
         '''Return formatted string which contains authors.
         The special __author__ variable is used (type is list).'''
         result = ''
@@ -69,14 +71,14 @@ class GLInfo(object):
                 result += '%s, ' % item
         return result
 
-    def license(self):
+    def license(self) -> str:
         '''Return formatted string which contains license and its description.'''
         result = 'License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>\n'
         result += 'This is free software: you are free to change and redistribute it.\n'
         result += 'There is NO WARRANTY, to the extent permitted by law.'
         return result
 
-    def copyright(self):
+    def copyright(self) -> str:
         '''Return formatted string which contains copyright.
         The special __copyright__ variable is used (type is str).'''
         copyright = __copyright__
@@ -85,7 +87,7 @@ class GLInfo(object):
         result = 'Copyright (C) %s' % copyright
         return result
 
-    def date(self):
+    def date(self) -> str:
         '''Return formatted string which contains date and time in GMT format.'''
         if isdir(DIRS['git']):
             have_git = None
@@ -125,11 +127,11 @@ class GLInfo(object):
         result = re.compile(r' .*').sub(r'', first_changelog_line)
         return result
 
-    def copyright_range(self):
+    def copyright_range(self) -> str:
         '''Returns a formatted copyright string showing a year range.'''
         return f'Copyright (C) {constants.__copyright__}'
 
-    def usage(self):
+    def usage(self) -> str:
         '''Show help message.'''
         result = '''\
 Usage: gnulib-tool --list
@@ -329,7 +331,7 @@ Options for --import, --add/remove-import, --update:
 Report bugs to <bug-gnulib@gnu.org>.'''
         return result
 
-    def version(self):
+    def version(self) -> str:
         '''Return formatted string which contains git version.'''
         if isdir(DIRS['git']):
             have_git = None
diff --git a/pygnulib/GLMakefileTable.py b/pygnulib/GLMakefileTable.py
index fe2c8e3360..40fbc44c43 100644
--- a/pygnulib/GLMakefileTable.py
+++ b/pygnulib/GLMakefileTable.py
@@ -13,6 +13,8 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
+from __future__ import annotations
+
 #===============================================================================
 # Define global imports
 #===============================================================================
@@ -43,17 +45,15 @@ class GLMakefileTable(object):
     '''This class is used to edit Makefile and store edits as table.
     When user creates  Makefile, he may need to use this class.'''
 
-    def __init__(self, config):
-        '''GLMakefileTable.__init__(config) -> GLMakefileTable
-
-        Create GLMakefileTable instance.'''
+    def __init__(self, config: GLConfig) -> None:
+        '''Create GLMakefileTable instance.'''
         if type(config) is not GLConfig:
             raise TypeError('config must be a GLConfig, not %s'
                             % type(config).__name__)
         self.config = config
         self.table = list()
 
-    def __getitem__(self, y):
+    def __getitem__(self, y: int) -> dict[str, bool]:
         '''x.__getitem__(y) = x[y]'''
         if type(y) is not int:
             raise TypeError('indices must be integers, not %s'
@@ -61,10 +61,8 @@ class GLMakefileTable(object):
         result = self.table[y]
         return dict(result)
 
-    def editor(self, dir, var, val, dotfirst=False):
-        '''GLMakefileTable.editor(dir, var, val, dotfirst)
-
-        This method is used to remember that ${dir}Makefile.am needs to be edited
+    def editor(self, dir: str, var: str, val: str, dotfirst: bool = False) -> None:
+        '''This method is used to remember that ${dir}Makefile.am needs to be edited
         to that ${var} mentions ${val}.
         If ${dotfirst} is non-empty, this mention needs to be present after '.'.
         This is a special hack for the SUBDIRS variable, cf.
@@ -80,10 +78,8 @@ class GLMakefileTable(object):
         dictionary = {'dir': dir, 'var': var, 'val': val, 'dotfirst': dotfirst}
         self.table += [dictionary]
 
-    def parent(self, gentests, source_makefile_am, tests_makefile_am):
-        '''GLMakefileTable.parent(gentests)
-
-        Add a special row to Makefile.am table with the first parent directory
+    def parent(self, gentests: bool, source_makefile_am: str, tests_makefile_am: str) -> None:
+        '''Add a special row to Makefile.am table with the first parent directory
         which contains or will contain Makefile.am file.
         GLConfig: sourcebase, m4base, testsbase, incl_test_categories,
         excl_test_categories, makefile_name.
@@ -109,8 +105,6 @@ class GLMakefileTable(object):
             dir1 = os.path.dirname(dir1)
         self.editor(dir1, 'EXTRA_DIST', joinpath(dir2, 'gnulib-cache.m4'))
 
-    def count(self):
-        '''GLMakefileTable.count() -> int
-
-        Count number of edits which were applied.'''
+    def count(self) -> int:
+        '''Count number of edits which were applied.'''
         return len(self.table)
diff --git a/pygnulib/GLModuleSystem.py b/pygnulib/GLModuleSystem.py
index d2c83b2911..6ec366eecd 100644
--- a/pygnulib/GLModuleSystem.py
+++ b/pygnulib/GLModuleSystem.py
@@ -13,6 +13,8 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
+from __future__ import annotations
+
 #===============================================================================
 # Define global imports
 #===============================================================================
@@ -57,10 +59,8 @@ class GLModuleSystem(object):
     '''GLModuleSystem is used to operate with module system using dynamic
     searching and patching.'''
 
-    def __init__(self, config):
-        '''GLModuleSystem.__init__(config) -> GLModuleSystem
-
-        Create new GLModuleSystem instance. Some functions use GLFileSystem class
+    def __init__(self, config: GLConfig) -> None:
+        '''Create new GLModuleSystem instance. Some functions use GLFileSystem class
         to look up a file in localpath or gnulib directories, or combine it through
         'patch' utility.'''
         self.args = dict()
@@ -70,15 +70,13 @@ class GLModuleSystem(object):
         self.config = config
         self.filesystem = GLFileSystem(self.config)
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         '''x.__repr__ <==> repr(x)'''
         result = '<pygnulib.GLModuleSystem %s>' % hex(id(self))
         return result
 
-    def exists(self, module):
-        '''GLModuleSystem.exists(module) -> bool
-
-        Check whether the given module exists.
+    def exists(self, module: str) -> bool:
+        '''Check whether the given module exists.
         GLConfig: localpath.'''
         if type(module) is not str:
             raise TypeError('module must be a string, not %s'
@@ -97,10 +95,8 @@ class GLModuleSystem(object):
                         break
         return result
 
-    def find(self, module):
-        '''GLModuleSystem.find(module) -> GLModule
-
-        Find the given module.'''
+    def find(self, module: str) -> GLModule | None:
+        '''Find the given module.'''
         if type(module) is not str:
             raise TypeError('module must be a string, not %s'
                             % type(module).__name__)
@@ -115,7 +111,7 @@ class GLModuleSystem(object):
                 sys.stderr.write('gnulib-tool: warning: ')
                 sys.stderr.write('file %s does not exist\n' % str(module))
 
-    def file_is_module(self, filename):
+    def file_is_module(self, filename: str) -> bool:
         '''Given the name of a file in the modules/ directory, return true
         if should be viewed as a module description file.'''
         return not (filename == 'ChangeLog' or filename.endswith('/ChangeLog')
@@ -129,10 +125,8 @@ class GLModuleSystem(object):
                     or filename.endswith('.rej')
                     or filename.endswith('~'))
 
-    def list(self):
-        '''GLModuleSystem.list() -> list
-
-        Return the available module names as tuple. We could use a combination
+    def list(self) -> list[str]:
+        '''Return the available module names as tuple. We could use a combination
         of os.walk() function and re module. However, it takes too much time to
         complete, so this version uses subprocess to run shell commands.'''
         result = ''
@@ -189,10 +183,8 @@ class GLModule(object):
     # List of characters allowed in shell identifiers.
     shell_id_chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'
 
-    def __init__(self, config, path, patched=False):
-        '''GLModule.__init__(config, path[, patched]) -> GLModule
-
-        Create new GLModule instance. Arguments are path and patched, where
+    def __init__(self, config: GLConfig, path: str, patched: bool = False) -> None:
+        '''Create new GLModule instance. Arguments are path and patched, where
         path is a string representing the path to the module and patched is a
         bool indicating that module was created after applying patch.'''
         self.args = dict()
@@ -227,7 +219,7 @@ class GLModule(object):
         if last_section_label != None:
             self.sections[last_section_label] = self.content[last_section_start:]
 
-    def __eq__(self, module):
+    def __eq__(self, module: object) -> bool:
         '''x.__eq__(y) <==> x==y'''
         result = False
         if type(module) is GLModule:
@@ -235,7 +227,7 @@ class GLModule(object):
                 result = True
         return result
 
-    def __ne__(self, module):
+    def __ne__(self, module: object) -> bool:
         '''x.__ne__(y) <==> x!=y'''
         result = False
         if type(module) is GLModule:
@@ -243,7 +235,7 @@ class GLModule(object):
                 result = True
         return result
 
-    def __ge__(self, module):
+    def __ge__(self, module: object) -> bool:
         '''x.__ge__(y) <==> x>=y'''
         result = False
         if type(module) is GLModule:
@@ -251,7 +243,7 @@ class GLModule(object):
                 result = True
         return result
 
-    def __gt__(self, module):
+    def __gt__(self, module: object) -> bool:
         '''x.__gt__(y) <==> x>y'''
         result = False
         if type(module) is GLModule:
@@ -259,12 +251,12 @@ class GLModule(object):
                 result = True
         return result
 
-    def __hash__(self):
+    def __hash__(self) -> int:
         '''x.__hash__() <==> hash(x)'''
         result = hash(self.path) ^ hash(self.patched)
         return result
 
-    def __le__(self, module):
+    def __le__(self, module: object) -> bool:
         '''x.__le__(y) <==> x<=y'''
         result = False
         if type(module) is GLModule:
@@ -272,7 +264,7 @@ class GLModule(object):
                 result = True
         return result
 
-    def __lt__(self, module):
+    def __lt__(self, module: object) -> bool:
         '''x.__lt__(y) <==> x<y'''
         result = False
         if type(module) is GLModule:
@@ -280,53 +272,45 @@ class GLModule(object):
                 result = True
         return result
 
-    def __str__(self):
+    def __str__(self) -> str:
         '''x.__str__() <==> str(x)'''
         result = self.getName()
         return result
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         '''x.__repr__ <==> repr(x)'''
         result = '<pygnulib.GLModule %s %s>' % (repr(self.getName()), hex(id(self)))
         return result
 
-    def getName(self):
-        '''GLModule.getName() -> str
-
-        Return the name of the module.'''
+    def getName(self) -> str:
+        '''Return the name of the module.'''
         pattern = re.compile(joinpath('modules', '(.*)$'))
         result = pattern.findall(self.path)[0]
         return result
 
-    def isPatched(self):
-        '''GLModule.isPatched() -> bool
-
-        Check whether module was created after applying patch.'''
+    def isPatched(self) -> bool:
+        '''Check whether module was created after applying patch.'''
         return self.patched
 
-    def isTests(self):
-        '''GLModule.isTests() -> bool
-
-        Check whether module is a *-tests module or a module of
+    def isTests(self) -> bool:
+        '''Check whether module is a *-tests module or a module of
         applicability 'all'.'''
         result = self.getApplicability() != 'main'
         return result
 
-    def isNonTests(self):
-        '''GLModule.isNonTests() -> bool
-
-        Check whether module is not a *-tests module.'''
+    def isNonTests(self) -> bool:
+        '''Check whether module is not a *-tests module.'''
         result = not self.getName().endswith('-tests')
         return result
 
-    def getTestsName(self):
+    def getTestsName(self) -> str:
         '''Return -tests version of the module name.'''
         result = self.getName()
         if not result.endswith('-tests'):
             result += '-tests'
         return result
 
-    def getTestsModule(self):
+    def getTestsModule(self) -> GLModule | None:
         '''Return -tests version of the module as GLModule.'''
         result = self.modulesystem.find(self.getTestsName())
         return result
@@ -406,11 +390,9 @@ class GLModule(object):
                               for line in section.splitlines() })
         return lines_to_multiline(directives)
 
-    def getShellFunc(self):
-        '''GLModule.getShellFunc() -> str
-
-        Computes the shell function name that will contain the m4 macros for the
-        module.'''
+    def getShellFunc(self) -> str:
+        '''Computes the shell function name that will contain the m4 macros
+        for the module.'''
         macro_prefix = self.config['macro_prefix']
         valid_shell_id = True
         for char in self.getName():
@@ -426,11 +408,9 @@ class GLModule(object):
         result = 'func_%s_gnulib_m4code_%s' % (macro_prefix, identifier)
         return result
 
-    def getShellVar(self):
-        '''GLModule.getShellVar() -> str
-
-        Compute the shell variable name the will be set to true once the m4 macros
-        for the module have been executed.'''
+    def getShellVar(self) -> str:
+        '''Compute the shell variable name the will be set to true once the
+        m4 macros for the module have been executed.'''
         macro_prefix = self.config['macro_prefix']
         valid_shell_id = True
         for char in self.getName():
@@ -446,10 +426,8 @@ class GLModule(object):
         result = '%s_gnulib_enabled_%s' % (macro_prefix, identifier)
         return result
 
-    def getConditionalName(self):
-        '''GLModule.getConditionalName() -> str
-
-        Return the automake conditional name.
+    def getConditionalName(self) -> str:
+        '''Return the automake conditional name.
         GLConfig: macro_prefix.'''
         macro_prefix = self.config['macro_prefix']
         valid_shell_id = True
@@ -466,28 +444,20 @@ class GLModule(object):
         result = '%s_GNULIB_ENABLED_%s' % (macro_prefix, identifier)
         return result
 
-    def getDescription(self):
-        '''GLModule.getDescription() -> str
-
-        Return description of the module.'''
+    def getDescription(self) -> str:
+        '''Return description of the module.'''
         return self.sections.get('Description', '')
 
-    def getComment(self):
-        '''GLModule.getComment() -> str
-
-        Return comment to module.'''
+    def getComment(self) -> str:
+        '''Return comment to module.'''
         return self.sections.get('Comment', '')
 
-    def getStatus(self):
-        '''GLModule.getStatus() -> str
-
-        Return module status.'''
+    def getStatus(self) -> str:
+        '''Return module status.'''
         return self.sections.get('Status', '')
 
-    def getStatuses(self):
-        '''GLModule.getStatuses() -> list
-
-        Return module status.'''
+    def getStatuses(self) -> list[str]:
+        '''Return module status.'''
         if 'statuses' not in self.cache:
             snippet = self.getStatus()
             result = [ line.strip()
@@ -496,16 +466,12 @@ class GLModule(object):
             self.cache['statuses'] = result
         return self.cache['statuses']
 
-    def getNotice(self):
-        '''GLModule.getNotice() -> str
-
-        Return notice to module.'''
+    def getNotice(self) -> str:
+        '''Return notice to module.'''
         return self.sections.get('Notice', '')
 
-    def getApplicability(self):
-        '''GLModule.getApplicability() -> str
-
-        Return applicability of module.'''
+    def getApplicability(self) -> str:
+        '''Return applicability of module.'''
         if 'applicability' not in self.cache:
             result = self.sections.get('Applicability', '')
             result = result.strip()
@@ -522,10 +488,8 @@ class GLModule(object):
         '''Return the unmodified list of files as a string.'''
         return self.sections.get('Files', '')
 
-    def getFiles(self):
-        '''GLModule.getFiles() -> list
-
-        Return list of files.
+    def getFiles(self) -> list[str]:
+        '''Return list of files.
         GLConfig: ac_version.'''
         if 'files' not in self.cache:
             snippet = self.getFiles_Raw()
@@ -538,10 +502,8 @@ class GLModule(object):
             self.cache['files'] = result
         return self.cache['files']
 
-    def getDependencies(self):
-        '''GLModule.getDependencies() -> str
-
-        Return list of dependencies, as a snippet.
+    def getDependencies(self) -> str:
+        '''Return list of dependencies, as a snippet.
         GLConfig: localpath.'''
         if 'dependencies' not in self.cache:
             result = ''
@@ -558,10 +520,8 @@ class GLModule(object):
             self.cache['dependencies'] = result
         return self.cache['dependencies']
 
-    def getDependenciesWithoutConditions(self):
-        '''GLModule.getDependenciesWithoutConditions() -> list
-
-        Return list of dependencies, as a list of GLModule objects.
+    def getDependenciesWithoutConditions(self) -> list[GLModule | None]:
+        '''Return list of dependencies, as a list of GLModule objects.
         GLConfig: localpath.'''
         if 'dependenciesWithoutCond' not in self.cache:
             snippet = self.getDependencies()
@@ -577,10 +537,8 @@ class GLModule(object):
             self.cache['dependenciesWithoutCond'] = result
         return self.cache['dependenciesWithoutCond']
 
-    def getDependenciesWithConditions(self):
-        '''GLModule.getDependenciesWithConditions() -> list
-
-        Return list of dependencies, as a list of pairs (GLModule object, condition).
+    def getDependenciesWithConditions(self) -> list[tuple[GLModule, str | None]]:
+        '''Return list of dependencies, as a list of pairs (GLModule object, condition).
         The "true" condition is denoted by None.
         GLConfig: localpath.'''
 
@@ -607,22 +565,16 @@ class GLModule(object):
             self.cache['dependenciesWithCond'] = result
         return self.cache['dependenciesWithCond']
 
-    def getAutoconfEarlySnippet(self):
-        '''GLModule.getAutoconfEarlySnippet() -> str
-
-        Return autoconf-early snippet.'''
+    def getAutoconfEarlySnippet(self) -> str:
+        '''Return autoconf-early snippet.'''
         return self.sections.get('configure.ac-early', '')
 
-    def getAutoconfSnippet(self):
-        '''GLModule.getAutoconfSnippet() -> str
-
-        Return autoconf snippet.'''
+    def getAutoconfSnippet(self) -> str:
+        '''Return autoconf snippet.'''
         return self.sections.get('configure.ac', '')
 
-    def getAutomakeSnippet(self):
-        '''getAutomakeSnippet() -> str
-
-        Get automake snippet.
+    def getAutomakeSnippet(self) -> str:
+        '''Get automake snippet.
         GLConfig: auxdir, ac_version.'''
         result = ''
         conditional = self.getAutomakeSnippet_Conditional()
@@ -633,16 +585,12 @@ class GLModule(object):
         result += self.getAutomakeSnippet_Unconditional()
         return result
 
-    def getAutomakeSnippet_Conditional(self):
-        '''GLModule.getAutomakeSnippet_Conditional() -> str
-
-        Return conditional automake snippet.'''
+    def getAutomakeSnippet_Conditional(self) -> str:
+        '''Return conditional automake snippet.'''
         return self.sections.get('Makefile.am', '')
 
-    def getAutomakeSnippet_Unconditional(self):
-        '''GLModule.getAutomakeSnippet_Unconditional() -> str
-
-        Return unconditional automake snippet.
+    def getAutomakeSnippet_Unconditional(self) -> str:
+        '''Return unconditional automake snippet.
         GLConfig: auxdir, ac_version.'''
         auxdir = self.config['auxdir']
         ac_version = self.config['ac_version']
@@ -709,10 +657,8 @@ class GLModule(object):
             self.cache['makefile-unconditional'] = result
         return self.cache['makefile-unconditional']
 
-    def getInclude(self):
-        '''GLModule.getInclude() -> str
-
-        Return include directive.'''
+    def getInclude(self) -> str:
+        '''Return include directive.'''
         if 'include' not in self.cache:
             snippet = self.sections.get('Include', '')
             pattern = re.compile(r'^(["<])', re.M)
@@ -720,22 +666,16 @@ class GLModule(object):
             self.cache['include'] = result
         return self.cache['include']
 
-    def getLink(self):
-        '''GLModule.getLink() -> str
-
-        Return link directive.'''
+    def getLink(self) -> str:
+        '''Return link directive.'''
         return self.sections.get('Link', '')
 
-    def getLicense_Raw(self):
-        '''GLModule.getLicense_Raw() -> str
-
-        Return module license.'''
+    def getLicense_Raw(self) -> str:
+        '''Return module license.'''
         return self.sections.get('License', '')
 
-    def getLicense(self):
-        '''GLModule.getLicense(self) -> str
-
-        Get license and warn user if module lacks a license.'''
+    def getLicense(self) -> str:
+        '''Get license and warn user if module lacks a license.'''
         if 'license' not in self.cache:
             license = self.getLicense_Raw().strip()
             # Warn if the License field is missing.
@@ -758,10 +698,8 @@ class GLModule(object):
             self.cache['license'] = result
         return self.cache['license']
 
-    def getMaintainer(self):
-        '''GLModule.getMaintainer() -> str
-
-        Return maintainer directive.'''
+    def getMaintainer(self) -> str:
+        '''Return maintainer directive.'''
         return self.sections.get('Maintainer', '')
 
 
@@ -771,10 +709,8 @@ class GLModule(object):
 class GLModuleTable(object):
     '''GLModuleTable is used to work with the list of the modules.'''
 
-    def __init__(self, config, inc_all_direct_tests, inc_all_indirect_tests):
-        '''GLModuleTable.__init__(config, inc_all_direct_tests, inc_all_indirect_tests) -> GLModuleTable
-
-        Create new GLModuleTable instance. If modules are specified, then add
+    def __init__(self, config: GLConfig, inc_all_direct_tests: bool, inc_all_indirect_tests: bool) -> None:
+        '''Create new GLModuleTable instance. If modules are specified, then add
         every module from iterable as unconditional module. If avoids is specified,
         then in transitive_closure every dependency which is in avoids won't be
         included in the final modules list. If conddeps are enabled,
@@ -824,12 +760,12 @@ class GLModuleTable(object):
             if module:
                 self.avoids.append(module)
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         '''x.__repr__() <==> repr(x)'''
         result = '<pygnulib.GLModuleTable %s>' % hex(id(self))
         return result
 
-    def __getitem__(self, y):
+    def __getitem__(self, y: str) -> list[GLModule]:
         '''x.__getitem__(y) <==> x[y]'''
         if y in ['base', 'final', 'main', 'tests', 'avoids']:
             if y == 'base':
@@ -845,10 +781,8 @@ class GLModuleTable(object):
         else:  # if y is not in list
             raise KeyError('GLModuleTable does not contain key: %s' % repr(y))
 
-    def addConditional(self, parent, module, condition):
-        '''GLModuleTable.addConditional(module, condition)
-
-        Add new conditional dependency from parent to module with condition.'''
+    def addConditional(self, parent: GLModule, module: GLModule, condition: str | bool) -> None:
+        '''Add new conditional dependency from parent to module with condition.'''
         if type(parent) is not GLModule:
             raise TypeError('parent must be a GLModule, not %s'
                             % type(parent).__name__)
@@ -867,10 +801,8 @@ class GLModuleTable(object):
             key = '%s---%s' % (str(parent), str(module))
             self.conditionals[key] = condition
 
-    def addUnconditional(self, module):
-        '''GLModuleTable.addUnconditional(module)
-
-        Add module as unconditional dependency.'''
+    def addUnconditional(self, module: GLModule) -> None:
+        '''Add module as unconditional dependency.'''
         if type(module) is not GLModule:
             raise TypeError('module must be a GLModule, not %s'
                             % type(module).__name__)
@@ -878,20 +810,16 @@ class GLModuleTable(object):
         if str(module) in self.dependers:
             self.dependers.pop(str(module))
 
-    def isConditional(self, module):
-        '''GLModuleTable.isConditional(module) -> bool
-
-        Check whether module is unconditional.'''
+    def isConditional(self, module: GLModule) -> bool:
+        '''Check whether module is unconditional.'''
         if type(module) is not GLModule:
             raise TypeError('module must be a GLModule, not %s'
                             % type(module).__name__)
         result = str(module) in self.dependers
         return result
 
-    def getCondition(self, parent, module):
-        '''GLModuleTable.getCondition(module) -> str or True
-
-        Return condition from parent to module. Condition can be string or True.
+    def getCondition(self, parent: GLModule, module: GLModule) -> str | bool:
+        '''Return condition from parent to module. Condition can be string or True.
         If module is not in the list of conddeps, method returns None.'''
         if type(parent) is not GLModule:
             raise TypeError('parent must be a GLModule, not %s'
@@ -903,10 +831,8 @@ class GLModuleTable(object):
         result = self.conditionals.get(key, None)
         return result
 
-    def transitive_closure(self, modules):
-        '''GLModuleTable.transitive_closure(modules) -> list
-
-        Use transitive closure to add module and its dependencies. Add every
+    def transitive_closure(self, modules: list[GLModule]) -> list[GLModule]:
+        '''Use transitive closure to add module and its dependencies. Add every
         module and its dependencies from modules list, but do not add dependencies
         which contain in avoids list. If any incl_test_categories is enabled, then
         add dependencies which are in these categories. If any excl_test_categories,
@@ -1015,10 +941,9 @@ class GLModuleTable(object):
         self.modules = modules
         return list(modules)
 
-    def transitive_closure_separately(self, basemodules, finalmodules):
-        '''GLModuleTable.transitive_closure_separately(*args, **kwargs) -> tuple
-
-        Determine main module list and tests-related module list separately.
+    def transitive_closure_separately(self, basemodules: list[GLModule],
+                                      finalmodules: list[GLModule]) -> tuple[list[GLModule], list[GLModule]]:
+        '''Determine main module list and tests-related module list separately.
         The main module list is the transitive closure of the specified modules,
         ignoring tests modules. Its lib/* sources go into $sourcebase/. If lgpl is
         specified, it will consist only of LGPLed source.
@@ -1087,11 +1012,9 @@ class GLModuleTable(object):
                 cleansed.append(line)
         return lines_to_multiline(cleansed)
 
-    def add_dummy(self, modules):
-        '''GLModuleTable.add_dummy(modules) -> list
-
-        Add dummy package to list of modules if dummy package is needed. If not,
-        return original list of modules.
+    def add_dummy(self, modules: list[GLModule]) -> list[GLModule]:
+        '''Add dummy package to list of modules if dummy package is needed.
+        If not, return original list of modules.
         GLConfig: auxdir, ac_version, conddeps.'''
         auxdir = self.config['auxdir']
         ac_version = self.config['ac_version']
@@ -1128,11 +1051,9 @@ class GLModuleTable(object):
                     modules = sorted(set(modules)) + [dummy]
         return list(modules)
 
-    def filelist(self, modules):
-        '''GLModuleTable.filelist(modules) -> list
-
-        Determine the final file list for the given list of modules. The list of
-        modules must already include dependencies.
+    def filelist(self, modules: list[GLModule]) -> list[str]:
+        '''Determine the final file list for the given list of modules.
+        The list of modules must already include dependencies.
         GLConfig: ac_version.'''
         ac_version = self.config['ac_version']
         filelist = list()
@@ -1147,10 +1068,9 @@ class GLModuleTable(object):
                     filelist += [file]
         return filelist
 
-    def filelist_separately(self, main_modules, tests_modules):
-        '''GLModuleTable.filelist_separately(**kwargs) -> list
-
-        Determine the final file lists. They must be computed separately, because
+    def filelist_separately(self, main_modules: list[GLModule],
+                            tests_modules: list[GLModule]) -> tuple[list[str], list[str]]:
+        '''Determine the final file lists. They must be computed separately, because
         files in lib/* go into $sourcebase/ if they are in the main file list but
         into $testsbase/ if they are in the tests-related file list. Furthermore
         lib/dummy.c can be in both.'''
@@ -1162,76 +1082,56 @@ class GLModuleTable(object):
         result = tuple([main_filelist, tests_filelist])
         return result
 
-    def getAvoids(self):
-        '''GLModuleTable.getAvoids() -> list
-
-        Return list of avoids.'''
+    def getAvoids(self) -> list[GLModule]:
+        '''Return list of avoids.'''
         return list(self.avoids)
 
-    def setAvoids(self, modules):
-        '''GLModuleTable.setAvoids(modules)
-
-        Specify list of avoids.'''
+    def setAvoids(self, modules: list[GLModule]) -> None:
+        '''Specify list of avoids.'''
         for module in modules:
             if type(module) is not GLModule:
                 raise TypeError('each module must be a GLModule instance')
         self.avoids = sorted(set(modules))
 
-    def getBaseModules(self):
-        '''GLModuleTable.getBaseModules() -> list
-
-        Return list of base modules.'''
+    def getBaseModules(self) -> list[GLModule]:
+        '''Return list of base modules.'''
         return list(self.base_modules)
 
-    def setBaseModules(self, modules):
-        '''GLModuleTable.setBaseModules(modules)
-
-        Specify list of base modules.'''
+    def setBaseModules(self, modules: list[GLModule]) -> None:
+        '''Specify list of base modules.'''
         for module in modules:
             if type(module) is not GLModule:
                 raise TypeError('each module must be a GLModule instance')
         self.base_modules = sorted(set(modules))
 
-    def getFinalModules(self):
-        '''GLModuleTable.getFinalModules() -> list
-
-        Return list of final modules.'''
+    def getFinalModules(self) -> list[GLModule]:
+        '''Return list of final modules.'''
         return list(self.final_modules)
 
-    def setFinalModules(self, modules):
-        '''GLModuleTable.setFinalModules(modules)
-
-        Specify list of final modules.'''
+    def setFinalModules(self, modules: list[GLModule]) -> None:
+        '''Specify list of final modules.'''
         for module in modules:
             if type(module) is not GLModule:
                 raise TypeError('each module must be a GLModule instance')
         self.final_modules = sorted(set(modules))
 
-    def getMainModules(self):
-        '''GLModuleTable.getMainModules() -> list
-
-        Return list of main modules.'''
+    def getMainModules(self) -> list[GLModule]:
+        '''Return list of main modules.'''
         return list(self.main_modules)
 
-    def setMainModules(self, modules):
-        '''GLModuleTable.setMainModules(modules)
-
-        Specify list of main modules.'''
+    def setMainModules(self, modules: list[GLModule]) -> None:
+        '''Specify list of main modules.'''
         for module in modules:
             if type(module) is not GLModule:
                 raise TypeError('each module must be a GLModule instance')
         self.main_modules = sorted(set(modules))
 
-    def getTestsModules(self):
-        '''GLModuleTable.getTestsModules() -> list
-
-        Return list of tests modules.'''
+    def getTestsModules(self) -> list[GLModule]:
+        '''Return list of tests modules.'''
         return list(self.tests_modules)
 
-    def setTestsModules(self, modules):
-        '''GLModuleTable.setTestsModules(modules)
-
-        Specify list of tests modules.'''
+    def setTestsModules(self, modules: list[GLModule]) -> None:
+        '''Specify list of tests modules.'''
         for module in modules:
             if type(module) is not GLModule:
                 raise TypeError('each module must be a GLModule instance')
diff --git a/pygnulib/GLTestDir.py b/pygnulib/GLTestDir.py
index 4de829538c..aa9a6b82ea 100644
--- a/pygnulib/GLTestDir.py
+++ b/pygnulib/GLTestDir.py
@@ -13,6 +13,8 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
+from __future__ import annotations
+
 #===============================================================================
 # Define global imports
 #===============================================================================
@@ -96,10 +98,8 @@ class GLTestDir(object):
     '''GLTestDir class is used to create a scratch package with the given
     list of the modules.'''
 
-    def __init__(self, config, testdir):
-        '''GLTestDir.__init__(config, testdir) -> GLTestDir
-
-        Create new GLTestDir instance.'''
+    def __init__(self, config: GLConfig, testdir: str) -> None:
+        '''Create new GLTestDir instance.'''
         if type(config) is not GLConfig:
             raise TypeError('config must be a GLConfig, not %s'
                             % type(config).__name__)
@@ -130,11 +130,9 @@ class GLTestDir(object):
         self.config.resetWitnessCMacro()
         self.config.resetVCFiles()
 
-    def rewrite_files(self, files):
-        '''GLTestDir.rewrite_files(files)
-
-        Replace auxdir, docbase, sourcebase, m4base and testsbase from default
-        to their version from config.'''
+    def rewrite_files(self, files: list[str]) -> list[str]:
+        '''Replace auxdir, docbase, sourcebase, m4base and testsbase from
+        default to their version from config.'''
         if type(files) is not list:
             raise TypeError('files argument must have list type, not %s'
                             % type(files).__name__)
@@ -169,10 +167,8 @@ class GLTestDir(object):
         result = sorted(set(result))
         return list(result)
 
-    def execute(self):
-        '''GLTestDir.execute()
-
-        Create a scratch package with the given modules.'''
+    def execute(self) -> None:
+        '''Create a scratch package with the given modules.'''
         auxdir = self.config['auxdir']
         sourcebase = self.config['sourcebase']
         m4base = self.config['m4base']
@@ -907,10 +903,8 @@ class GLMegaTestDir(object):
     '''GLMegaTestDir class is used to create a mega scratch package with the
     given modules one by one and all together.'''
 
-    def __init__(self, config, megatestdir):
-        '''GLMegaTestDir.__init__(config, megatestdir) -> GLMegaTestDir
-
-        Create new GLTestDir instance.'''
+    def __init__(self, config: GLConfig, megatestdir: str) -> None:
+        '''Create new GLTestDir instance.'''
         if type(config) is not GLConfig:
             raise TypeError('config must be a GLConfig, not %s'
                             % type(config).__name__)
@@ -930,11 +924,9 @@ class GLMegaTestDir(object):
         self.assistant = GLFileAssistant(self.config)
         self.makefiletable = GLMakefileTable(self.config)
 
-    def execute(self):
-        '''GLMegaTestDir.execute()
-
-        Create a mega scratch package with the given modules one by one and all
-        together.'''
+    def execute(self) -> None:
+        '''Create a mega scratch package with the given modules one by one
+        and all together.'''
         auxdir = self.config['auxdir']
         verbose = self.config['verbosity']
 
diff --git a/pygnulib/constants.py b/pygnulib/constants.py
index c8532f6ec7..dd20624fe8 100644
--- a/pygnulib/constants.py
+++ b/pygnulib/constants.py
@@ -16,6 +16,8 @@
 '''An easy access to pygnulib constants.'''
 
 from __future__ import unicode_literals
+from __future__ import annotations
+
 #===============================================================================
 # Define global imports
 #===============================================================================
@@ -30,7 +32,6 @@ import codecs
 import subprocess as sp
 import __main__ as interpreter
 
-
 #===============================================================================
 # Define module information
 #===============================================================================
@@ -79,7 +80,7 @@ APP['name'] = os.path.join(APP['root'], 'gnulib-tool.py')
 
 # Set DIRS directory
 DIRS['cwd'] = os.getcwd()
-def init_DIRS(gnulib_dir):
+def init_DIRS(gnulib_dir: str) -> None:
     DIRS['root'] = gnulib_dir
     DIRS['build-aux'] = os.path.join(gnulib_dir, 'build-aux')
     DIRS['config'] = os.path.join(gnulib_dir, 'config')
@@ -207,7 +208,7 @@ else:
 # Define global functions
 #===============================================================================
 
-def force_output():
+def force_output() -> None:
     '''This function is to be invoked before invoking external programs.
     It initiates bringing the the contents of process-internal output buffers
     to their respective destinations.'''
@@ -215,7 +216,7 @@ def force_output():
     sys.stderr.flush()
 
 
-def execute(args, verbose):
+def execute(args: list[str], verbose: int) -> None:
     '''Execute the given shell command.'''
     if verbose >= 0:
         print("executing %s" % ' '.join(args), flush=True)
@@ -245,7 +246,7 @@ def execute(args, verbose):
             sys.exit(retcode)
 
 
-def cleaner(sequence):
+def cleaner(sequence: str | list[str]) -> str | list[str | bool]:
     '''Clean string or list of strings after using regex.'''
     if type(sequence) is str:
         sequence = sequence.replace('[', '')
@@ -265,10 +266,8 @@ def cleaner(sequence):
     return sequence
 
 
-def joinpath(head, *tail):
-    '''joinpath(head, *tail) -> str
-
-    Join two or more pathname components, inserting '/' as needed. If any
+def joinpath(head: str, *tail: str) -> str:
+    '''Join two or more pathname components, inserting '/' as needed. If any
     component is an absolute path, all previous path components will be
     discarded.'''
     newtail = list()
@@ -278,7 +277,7 @@ def joinpath(head, *tail):
     return result
 
 
-def relativize(dir1, dir2):
+def relativize(dir1: str, dir2: str) -> str:
     '''Compute a relative pathname reldir such that dir1/reldir = dir2.
     dir1 and dir2 must be relative pathnames.'''
     dir0 = os.getcwd()
@@ -303,7 +302,7 @@ def relativize(dir1, dir2):
     return result
 
 
-def relconcat(dir1, dir2):
+def relconcat(dir1: str, dir2: str) -> str:
     '''Compute a relative pathname dir1/dir2, with obvious simplifications.
     dir1 and dir2 must be relative pathnames.
     dir2 is considered to be relative to dir1.'''
@@ -318,7 +317,7 @@ def ensure_writable(dest: str) -> None:
         os.chmod(dest, st.st_mode | stat.S_IWUSR)
 
 
-def relinverse(dir):
+def relinverse(dir: str) -> str:
     '''Compute the inverse of dir. Namely, a relative pathname consisting only
     of '..' components, such that dir/relinverse = '.'.
     dir must be a relative pathname.'''
@@ -333,7 +332,7 @@ def relinverse(dir):
         return os.path.normpath(inverse)
 
 
-def copyfile(src, dest):
+def copyfile(src: str, dest: str) -> None:
     '''Copy file src to file dest. Like shutil.copy, but ignore errors e.g. on
     VFAT file systems.'''
     shutil.copyfile(src, dest)
@@ -343,7 +342,7 @@ def copyfile(src, dest):
         pass
 
 
-def copyfile2(src, dest):
+def copyfile2(src: str, dest: str) -> None:
     '''Copy file src to file dest, preserving modification time. Like
     shutil.copy2, but ignore errors e.g. on VFAT file systems. This function
     is to be used for backup files.'''
@@ -354,7 +353,7 @@ def copyfile2(src, dest):
         pass
 
 
-def movefile(src, dest):
+def movefile(src: str, dest: str) -> None:
     '''Move/rename file src to file dest. Like shutil.move, but gracefully
     handle common errors.'''
     try:
@@ -367,7 +366,7 @@ def movefile(src, dest):
         os.remove(src)
 
 
-def symlink_relative(src, dest):
+def symlink_relative(src: str, dest: str) -> None:
     '''Like ln -s, except use cp -p if ln -s fails.
     src is either absolute or relative to the directory of dest.'''
     try:
@@ -388,7 +387,7 @@ def symlink_relative(src, dest):
         ensure_writable(dest)
 
 
-def as_link_value_at_dest(src, dest):
+def as_link_value_at_dest(src: str, dest: str) -> str:
     '''Compute the symbolic link value to place at dest, such that the
     resulting symbolic link points to src. src is given relative to the
     current directory (or absolute).'''
@@ -409,7 +408,7 @@ def as_link_value_at_dest(src, dest):
             return relativize(destdir, src)
 
 
-def link_relative(src, dest):
+def link_relative(src: str, dest: str) -> None:
     '''Like ln -s, except that src is given relative to the current directory
     (or absolute), not given relative to the directory of dest.'''
     if type(src) is not str:
@@ -420,7 +419,7 @@ def link_relative(src, dest):
     symlink_relative(link_value, dest)
 
 
-def link_if_changed(src, dest):
+def link_if_changed(src: str, dest: str) -> None:
     '''Create a symlink, but avoids munging timestamps if the link is correct.'''
     link_value = as_link_value_at_dest(src, dest)
     if not (os.path.islink(dest) and os.readlink(dest) == link_value):
@@ -453,12 +452,10 @@ def hardlink(src: str, dest: str) -> None:
         ensure_writable(dest)
 
 
-def filter_filelist(separator, filelist,
-                    prefix, suffix, removed_prefix, removed_suffix,
-                    added_prefix='', added_suffix=''):
-    '''filter_filelist(*args) -> str
-
-    Filter the given list of files. Filtering: Only the elements starting with
+def filter_filelist(separator: str, filelist: str, prefix: str, suffix: str,
+                    removed_prefix: str, removed_suffix: str,
+                    added_prefix: str = '', added_suffix: str = '') -> str:
+    '''Filter the given list of files. Filtering: Only the elements starting with
     prefix and ending with suffix are considered. Processing: removed_prefix
     and removed_suffix are removed from each element, added_prefix and
     added_suffix are added to each element.'''
@@ -479,18 +476,16 @@ def filter_filelist(separator, filelist,
     return result
 
 
-def lines_to_multiline(lines):
-    '''lines_to_multiline(List[str]) -> str
-
-    Combine the lines to a single string, terminating each line with a newline
-    character.'''
+def lines_to_multiline(lines: list[str]) -> str:
+    '''Combine the lines to a single string, terminating each line with a
+    newline character.'''
     if len(lines) > 0:
         return '\n'.join(lines) + '\n'
     else:
         return ''
 
 
-def substart(orig, repl, data):
+def substart(orig: str, repl: str, data: str) -> str:
     '''Replaces the start portion of a string.
 
     Returns data with orig replaced by repl, but only at the beginning of data.
@@ -501,7 +496,7 @@ def substart(orig, repl, data):
     return result
 
 
-def subend(orig, repl, data):
+def subend(orig: str, repl: str, data: str) -> str:
     '''Replaces the end portion of a string.
 
     Returns data with orig replaced by repl, but only at the end of data.
@@ -512,7 +507,7 @@ def subend(orig, repl, data):
     return result
 
 
-def nlconvert(text):
+def nlconvert(text: str) -> str:
     '''Convert line-endings to specific for this platform.'''
     system = platform.system().lower()
     text = text.replace('\r\n', '\n')
@@ -521,7 +516,7 @@ def nlconvert(text):
     return text
 
 
-def remove_trailing_slashes(text):
+def remove_trailing_slashes(text: str) -> str:
     '''Remove trailing slashes from a file name, except when the file name
     consists only of slashes.'''
     result = text
@@ -533,21 +528,21 @@ def remove_trailing_slashes(text):
     return result
 
 
-def remove_backslash_newline(text):
+def remove_backslash_newline(text: str) -> str:
     '''Given a multiline string text, join lines:
     When a line ends in a backslash, remove the backslash and join the next
     line to it.'''
     return text.replace('\\\n', '')
 
 
-def combine_lines(text):
+def combine_lines(text: str) -> str:
     '''Given a multiline string text, join lines by spaces:
     When a line ends in a backslash, remove the backslash and join the next
     line to it, inserting a space between them.'''
     return text.replace('\\\n', ' ')
 
 
-def combine_lines_matching(pattern, text):
+def combine_lines_matching(pattern: re.Pattern, text: str) -> str:
     '''Given a multiline string text, join lines by spaces, when the first
     such line matches a given RegexObject pattern.
     When a line that matches the pattern ends in a backslash, remove the
diff --git a/pygnulib/main.py b/pygnulib/main.py
index 204050facb..688ab249f3 100644
--- a/pygnulib/main.py
+++ b/pygnulib/main.py
@@ -43,6 +43,15 @@
 # - Avoid conditional expressions as in PEP 308 <https://peps.python.org/pep-0308/>.
 #   Rationale: They violate the principle that in conditional code, the
 #   condition comes first.
+# - Use type hints to document functions and catch programming mistakes.
+#   To allow for type annotations and union type specifiers, using the
+#   syntax Type1 | Type2, for all versions of Python ≥ 3.7 use the
+#   'from __future__ import annotations' import at the top of files.
+#   Cf. <https://docs.python.org/3/library/__future__.html>
+#   and <https://stackoverflow.com/questions/73879925/>.
+# - Prefer the standard collection type hints over those provided in the
+#   typing module. The latter are deprecated and may be removed.
+#   Cf. <https://peps.python.org/pep-0585/>.
 
 # You can use this command to check the style:
 #   $ pycodestyle *.py
@@ -58,6 +67,7 @@
 # Documentation:
 # <https://pylint.readthedocs.io/en/latest/user_guide/messages/messages_overview.html>
 
+from __future__ import annotations
 
 #===============================================================================
 # Define global imports
@@ -96,7 +106,7 @@ isfile = os.path.isfile
 #===============================================================================
 # Define main part
 #===============================================================================
-def main():
+def main() -> None:
     info = classes.GLInfo()
     parser = argparse.ArgumentParser(
         prog=constants.APP['name'],
-- 
2.44.0


             reply	other threads:[~2024-03-27  8:39 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-03-27  8:39 Collin Funk [this message]
2024-03-27 16:34 ` [PATCH] gnulib-tool.py: Add type hints to all functions Bruno Haible
2024-03-27 20:20   ` Collin Funk
2024-03-27 22:11     ` Bruno Haible
2024-03-27 22:44       ` Collin Funk
2024-03-27 16:52 ` Python Iterable Bruno Haible
2024-03-27 22:13   ` Darshit Shah
2024-03-27 22:50     ` what can we expect from developers Bruno Haible
2024-03-29 11:54       ` Darshit Shah
2024-03-29 13:47         ` Dmitrii Pasechnik
2024-03-30  1:30         ` Bruno Haible
2024-03-28  1:32   ` Python Iterable Collin Funk

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://lists.gnu.org/mailman/listinfo/bug-gnulib

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=b993cd5f-6bd7-4db1-8c90-2196838e6f1a@gmail.com \
    --to=collin.funk1@gmail.com \
    --cc=bruno@clisp.org \
    --cc=bug-gnulib@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).