mirror of
https://github.com/python/cpython.git
synced 2025-09-27 02:39:58 +00:00
gh-104050: Run mypy on clinic.py
in CI (#104421)
* Add basic mypy workflow to CI * Make the type check pass --------- Co-authored-by: Erlend E. Aasland <erlend.aasland@protonmail.com> Co-authored-by: Nikita Sobolev <mail@sobolevn.me> Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
This commit is contained in:
parent
a6bcc8fb92
commit
9d41f83c58
6 changed files with 101 additions and 24 deletions
7
.github/dependabot.yml
vendored
7
.github/dependabot.yml
vendored
|
@ -12,3 +12,10 @@ updates:
|
||||||
update-types:
|
update-types:
|
||||||
- "version-update:semver-minor"
|
- "version-update:semver-minor"
|
||||||
- "version-update:semver-patch"
|
- "version-update:semver-patch"
|
||||||
|
- package-ecosystem: "pip"
|
||||||
|
directory: "/Tools/clinic/"
|
||||||
|
schedule:
|
||||||
|
interval: "monthly"
|
||||||
|
labels:
|
||||||
|
- "skip issue"
|
||||||
|
- "skip news"
|
||||||
|
|
39
.github/workflows/mypy.yml
vendored
Normal file
39
.github/workflows/mypy.yml
vendored
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# Workflow to run mypy on select parts of the CPython repo
|
||||||
|
name: mypy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "Tools/clinic/**"
|
||||||
|
- ".github/workflows/mypy.yml"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
PIP_DISABLE_PIP_VERSION_CHECK: 1
|
||||||
|
FORCE_COLOR: 1
|
||||||
|
TERM: xterm-256color # needed for FORCE_COLOR to work on mypy on Ubuntu, see https://github.com/python/mypy/issues/13817
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
mypy:
|
||||||
|
name: Run mypy on Tools/clinic/
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
cache: pip
|
||||||
|
cache-dependency-path: Tools/clinic/requirements-dev.txt
|
||||||
|
- run: pip install -r Tools/clinic/requirements-dev.txt
|
||||||
|
- run: mypy --config-file Tools/clinic/mypy.ini
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
import ast
|
import ast
|
||||||
|
import builtins as bltns
|
||||||
import collections
|
import collections
|
||||||
import contextlib
|
import contextlib
|
||||||
import copy
|
import copy
|
||||||
|
@ -26,7 +27,9 @@ import textwrap
|
||||||
import traceback
|
import traceback
|
||||||
import types
|
import types
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
from types import *
|
from types import *
|
||||||
|
from typing import Any, NamedTuple
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
#
|
#
|
||||||
|
@ -78,8 +81,13 @@ unknown = Unknown()
|
||||||
|
|
||||||
sig_end_marker = '--'
|
sig_end_marker = '--'
|
||||||
|
|
||||||
|
Appender = Callable[[str], None]
|
||||||
|
Outputter = Callable[[None], str]
|
||||||
|
|
||||||
_text_accumulator_nt = collections.namedtuple("_text_accumulator", "text append output")
|
class _TextAccumulator(NamedTuple):
|
||||||
|
text: list[str]
|
||||||
|
append: Appender
|
||||||
|
output: Outputter
|
||||||
|
|
||||||
def _text_accumulator():
|
def _text_accumulator():
|
||||||
text = []
|
text = []
|
||||||
|
@ -87,10 +95,12 @@ def _text_accumulator():
|
||||||
s = ''.join(text)
|
s = ''.join(text)
|
||||||
text.clear()
|
text.clear()
|
||||||
return s
|
return s
|
||||||
return _text_accumulator_nt(text, text.append, output)
|
return _TextAccumulator(text, text.append, output)
|
||||||
|
|
||||||
|
|
||||||
text_accumulator_nt = collections.namedtuple("text_accumulator", "text append")
|
class TextAccumulator(NamedTuple):
|
||||||
|
text: list[str]
|
||||||
|
append: Appender
|
||||||
|
|
||||||
def text_accumulator():
|
def text_accumulator():
|
||||||
"""
|
"""
|
||||||
|
@ -104,7 +114,7 @@ def text_accumulator():
|
||||||
empties the accumulator.
|
empties the accumulator.
|
||||||
"""
|
"""
|
||||||
text, append, output = _text_accumulator()
|
text, append, output = _text_accumulator()
|
||||||
return text_accumulator_nt(append, output)
|
return TextAccumulator(append, output)
|
||||||
|
|
||||||
|
|
||||||
def warn_or_fail(fail=False, *args, filename=None, line_number=None):
|
def warn_or_fail(fail=False, *args, filename=None, line_number=None):
|
||||||
|
@ -1925,8 +1935,10 @@ class Destination:
|
||||||
# maps strings to Language objects.
|
# maps strings to Language objects.
|
||||||
# "languages" maps the name of the language ("C", "Python").
|
# "languages" maps the name of the language ("C", "Python").
|
||||||
# "extensions" maps the file extension ("c", "py").
|
# "extensions" maps the file extension ("c", "py").
|
||||||
|
LangDict = dict[str, Callable[[str], Language]]
|
||||||
|
|
||||||
languages = { 'C': CLanguage, 'Python': PythonLanguage }
|
languages = { 'C': CLanguage, 'Python': PythonLanguage }
|
||||||
extensions = { name: CLanguage for name in "c cc cpp cxx h hh hpp hxx".split() }
|
extensions: LangDict = { name: CLanguage for name in "c cc cpp cxx h hh hpp hxx".split() }
|
||||||
extensions['py'] = PythonLanguage
|
extensions['py'] = PythonLanguage
|
||||||
|
|
||||||
|
|
||||||
|
@ -2558,15 +2570,15 @@ class CConverter(metaclass=CConverterAutoRegister):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# The C name to use for this variable.
|
# The C name to use for this variable.
|
||||||
name = None
|
name: str | None = None
|
||||||
|
|
||||||
# The Python name to use for this variable.
|
# The Python name to use for this variable.
|
||||||
py_name = None
|
py_name: str | None = None
|
||||||
|
|
||||||
# The C type to use for this variable.
|
# The C type to use for this variable.
|
||||||
# 'type' should be a Python string specifying the type, e.g. "int".
|
# 'type' should be a Python string specifying the type, e.g. "int".
|
||||||
# If this is a pointer type, the type string should end with ' *'.
|
# If this is a pointer type, the type string should end with ' *'.
|
||||||
type = None
|
type: str | None = None
|
||||||
|
|
||||||
# The Python default value for this parameter, as a Python value.
|
# The Python default value for this parameter, as a Python value.
|
||||||
# Or the magic value "unspecified" if there is no default.
|
# Or the magic value "unspecified" if there is no default.
|
||||||
|
@ -2577,15 +2589,15 @@ class CConverter(metaclass=CConverterAutoRegister):
|
||||||
|
|
||||||
# If not None, default must be isinstance() of this type.
|
# If not None, default must be isinstance() of this type.
|
||||||
# (You can also specify a tuple of types.)
|
# (You can also specify a tuple of types.)
|
||||||
default_type = None
|
default_type: bltns.type[Any] | tuple[bltns.type[Any], ...] | None = None
|
||||||
|
|
||||||
# "default" converted into a C value, as a string.
|
# "default" converted into a C value, as a string.
|
||||||
# Or None if there is no default.
|
# Or None if there is no default.
|
||||||
c_default = None
|
c_default: str | None = None
|
||||||
|
|
||||||
# "default" converted into a Python value, as a string.
|
# "default" converted into a Python value, as a string.
|
||||||
# Or None if there is no default.
|
# Or None if there is no default.
|
||||||
py_default = None
|
py_default: str | None = None
|
||||||
|
|
||||||
# The default value used to initialize the C variable when
|
# The default value used to initialize the C variable when
|
||||||
# there is no default, but not specifying a default may
|
# there is no default, but not specifying a default may
|
||||||
|
@ -2597,14 +2609,14 @@ class CConverter(metaclass=CConverterAutoRegister):
|
||||||
#
|
#
|
||||||
# This value is specified as a string.
|
# This value is specified as a string.
|
||||||
# Every non-abstract subclass should supply a valid value.
|
# Every non-abstract subclass should supply a valid value.
|
||||||
c_ignored_default = 'NULL'
|
c_ignored_default: str = 'NULL'
|
||||||
|
|
||||||
# If true, wrap with Py_UNUSED.
|
# If true, wrap with Py_UNUSED.
|
||||||
unused = False
|
unused = False
|
||||||
|
|
||||||
# The C converter *function* to be used, if any.
|
# The C converter *function* to be used, if any.
|
||||||
# (If this is not None, format_unit must be 'O&'.)
|
# (If this is not None, format_unit must be 'O&'.)
|
||||||
converter = None
|
converter: str | None = None
|
||||||
|
|
||||||
# Should Argument Clinic add a '&' before the name of
|
# Should Argument Clinic add a '&' before the name of
|
||||||
# the variable when passing it into the _impl function?
|
# the variable when passing it into the _impl function?
|
||||||
|
@ -3432,7 +3444,7 @@ class robuffer: pass
|
||||||
def str_converter_key(types, encoding, zeroes):
|
def str_converter_key(types, encoding, zeroes):
|
||||||
return (frozenset(types), bool(encoding), bool(zeroes))
|
return (frozenset(types), bool(encoding), bool(zeroes))
|
||||||
|
|
||||||
str_converter_argument_map = {}
|
str_converter_argument_map: dict[str, str] = {}
|
||||||
|
|
||||||
class str_converter(CConverter):
|
class str_converter(CConverter):
|
||||||
type = 'const char *'
|
type = 'const char *'
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
from collections.abc import Callable
|
||||||
|
|
||||||
def negate(condition):
|
|
||||||
|
TokenAndCondition = tuple[str, str]
|
||||||
|
TokenStack = list[TokenAndCondition]
|
||||||
|
|
||||||
|
def negate(condition: str) -> str:
|
||||||
"""
|
"""
|
||||||
Returns a CPP conditional that is the opposite of the conditional passed in.
|
Returns a CPP conditional that is the opposite of the conditional passed in.
|
||||||
"""
|
"""
|
||||||
|
@ -22,17 +27,18 @@ class Monitor:
|
||||||
Anyway this implementation seems to work well enough for the CPython sources.
|
Anyway this implementation seems to work well enough for the CPython sources.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
is_a_simple_defined: Callable[[str], re.Match[str] | None]
|
||||||
is_a_simple_defined = re.compile(r'^defined\s*\(\s*[A-Za-z0-9_]+\s*\)$').match
|
is_a_simple_defined = re.compile(r'^defined\s*\(\s*[A-Za-z0-9_]+\s*\)$').match
|
||||||
|
|
||||||
def __init__(self, filename=None, *, verbose=False):
|
def __init__(self, filename=None, *, verbose: bool = False):
|
||||||
self.stack = []
|
self.stack: TokenStack = []
|
||||||
self.in_comment = False
|
self.in_comment = False
|
||||||
self.continuation = None
|
self.continuation: str | None = None
|
||||||
self.line_number = 0
|
self.line_number = 0
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.verbose = verbose
|
self.verbose = verbose
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return ''.join((
|
return ''.join((
|
||||||
'<Monitor ',
|
'<Monitor ',
|
||||||
str(id(self)),
|
str(id(self)),
|
||||||
|
@ -40,10 +46,10 @@ class Monitor:
|
||||||
" condition=", repr(self.condition()),
|
" condition=", repr(self.condition()),
|
||||||
">"))
|
">"))
|
||||||
|
|
||||||
def status(self):
|
def status(self) -> str:
|
||||||
return str(self.line_number).rjust(4) + ": " + self.condition()
|
return str(self.line_number).rjust(4) + ": " + self.condition()
|
||||||
|
|
||||||
def condition(self):
|
def condition(self) -> str:
|
||||||
"""
|
"""
|
||||||
Returns the current preprocessor state, as a single #if condition.
|
Returns the current preprocessor state, as a single #if condition.
|
||||||
"""
|
"""
|
||||||
|
@ -62,15 +68,15 @@ class Monitor:
|
||||||
if self.stack:
|
if self.stack:
|
||||||
self.fail("Ended file while still in a preprocessor conditional block!")
|
self.fail("Ended file while still in a preprocessor conditional block!")
|
||||||
|
|
||||||
def write(self, s):
|
def write(self, s: str) -> None:
|
||||||
for line in s.split("\n"):
|
for line in s.split("\n"):
|
||||||
self.writeline(line)
|
self.writeline(line)
|
||||||
|
|
||||||
def writeline(self, line):
|
def writeline(self, line: str) -> None:
|
||||||
self.line_number += 1
|
self.line_number += 1
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
|
|
||||||
def pop_stack():
|
def pop_stack() -> TokenAndCondition:
|
||||||
if not self.stack:
|
if not self.stack:
|
||||||
self.fail("#" + token + " without matching #if / #ifdef / #ifndef!")
|
self.fail("#" + token + " without matching #if / #ifdef / #ifndef!")
|
||||||
return self.stack.pop()
|
return self.stack.pop()
|
||||||
|
|
11
Tools/clinic/mypy.ini
Normal file
11
Tools/clinic/mypy.ini
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[mypy]
|
||||||
|
# make sure clinic can still be run on Python 3.10
|
||||||
|
python_version = 3.10
|
||||||
|
pretty = True
|
||||||
|
enable_error_code = ignore-without-code
|
||||||
|
disallow_any_generics = True
|
||||||
|
strict_concatenate = True
|
||||||
|
warn_redundant_casts = True
|
||||||
|
warn_unused_ignores = True
|
||||||
|
warn_unused_configs = True
|
||||||
|
files = Tools/clinic/
|
2
Tools/clinic/requirements-dev.txt
Normal file
2
Tools/clinic/requirements-dev.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# Requirements file for external linters and checks we run on Tools/clinic/ in CI
|
||||||
|
mypy==1.2.0
|
Loading…
Add table
Add a link
Reference in a new issue