Skip to content

Commit

Permalink
Allow type ignores of PEP 695 constructs (#16608)
Browse files Browse the repository at this point in the history
This is basically a pre-existing bug and affects other errors that
ASTConverter might raise, like merging overloads.

It could vaguely be nice to move all the set_file_ignored_lines into
fastparse, instead of BuildManager.parse_file. Could also clean up the
ignore_errors logic a little bit more.

Fixes #16607
  • Loading branch information
hauntsaninja authored and wesleywright committed Dec 7, 2023
1 parent 7c33e7c commit f53f422
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 26 deletions.
4 changes: 2 additions & 2 deletions misc/dump-ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import sys

from mypy import defaults
from mypy.errors import CompileError
from mypy.errors import CompileError, Errors
from mypy.options import Options
from mypy.parse import parse

Expand All @@ -19,7 +19,7 @@ def dump(fname: str, python_version: tuple[int, int], quiet: bool = False) -> No
options.python_version = python_version
with open(fname, "rb") as f:
s = f.read()
tree = parse(s, fname, None, errors=None, options=options)
tree = parse(s, fname, None, errors=Errors(options), options=options)
if not quiet:
print(tree)

Expand Down
4 changes: 2 additions & 2 deletions mypy/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -2174,8 +2174,8 @@ def parse_file(self, *, temporary: bool = False) -> None:
self.id,
self.xpath,
source,
self.ignore_all or self.options.ignore_errors,
self.options,
ignore_errors=self.ignore_all or self.options.ignore_errors,
options=self.options,
)

else:
Expand Down
31 changes: 16 additions & 15 deletions mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ def parse(
source: str | bytes,
fnam: str,
module: str | None,
errors: Errors | None = None,
errors: Errors,
options: Options | None = None,
) -> MypyFile:
"""Parse a source file, without doing any semantic analysis.
Expand All @@ -199,16 +199,13 @@ def parse(
on failure. Otherwise, use the errors object to report parse errors.
"""
ignore_errors = (options is not None and options.ignore_errors) or (
errors is not None and fnam in errors.ignored_files
fnam in errors.ignored_files
)
# If errors are ignored, we can drop many function bodies to speed up type checking.
strip_function_bodies = ignore_errors and (options is None or not options.preserve_asts)
raise_on_error = False

if options is None:
options = Options()
if errors is None:
errors = Errors(options)
raise_on_error = True
errors.set_file(fnam, module, options=options)
is_stub_file = fnam.endswith(".pyi")
if is_stub_file:
Expand All @@ -228,11 +225,9 @@ def parse(
options=options,
is_stub=is_stub_file,
errors=errors,
ignore_errors=ignore_errors,
strip_function_bodies=strip_function_bodies,
path=fnam,
).visit(ast)
tree.path = fnam
tree.is_stub = is_stub_file
except SyntaxError as e:
# alias to please mypyc
is_py38_or_earlier = sys.version_info < (3, 9)
Expand All @@ -254,9 +249,6 @@ def parse(
)
tree = MypyFile([], [], False, {})

if raise_on_error and errors.is_errors():
errors.raise_error()

assert isinstance(tree, MypyFile)
return tree

Expand Down Expand Up @@ -357,8 +349,8 @@ def __init__(
is_stub: bool,
errors: Errors,
*,
ignore_errors: bool,
strip_function_bodies: bool,
path: str,
) -> None:
# 'C' for class, 'D' for function signature, 'F' for function, 'L' for lambda
self.class_and_function_stack: list[Literal["C", "D", "F", "L"]] = []
Expand All @@ -367,8 +359,8 @@ def __init__(
self.options = options
self.is_stub = is_stub
self.errors = errors
self.ignore_errors = ignore_errors
self.strip_function_bodies = strip_function_bodies
self.path = path

self.type_ignores: dict[int, list[str]] = {}

Expand All @@ -380,6 +372,10 @@ def note(self, msg: str, line: int, column: int) -> None:

def fail(self, msg: ErrorMessage, line: int, column: int, blocker: bool = True) -> None:
if blocker or not self.options.ignore_errors:
# Make sure self.errors reflects any type ignores that we have parsed
self.errors.set_file_ignored_lines(
self.path, self.type_ignores, self.options.ignore_errors
)
self.errors.report(line, column, msg.value, blocker=blocker, code=msg.code)

def fail_merge_overload(self, node: IfStmt) -> None:
Expand Down Expand Up @@ -858,8 +854,13 @@ def visit_Module(self, mod: ast3.Module) -> MypyFile:
self.type_ignores[ti.lineno] = parsed
else:
self.fail(message_registry.INVALID_TYPE_IGNORE, ti.lineno, -1, blocker=False)

body = self.fix_function_overloads(self.translate_stmt_list(mod.body, ismodule=True))
return MypyFile(body, self.imports, False, self.type_ignores)

ret = MypyFile(body, self.imports, False, ignored_lines=self.type_ignores)
ret.is_stub = self.is_stub
ret.path = self.path
return ret

# --- stmt ---
# FunctionDef(identifier name, arguments args,
Expand Down
12 changes: 10 additions & 2 deletions mypy/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@


def parse(
source: str | bytes, fnam: str, module: str | None, errors: Errors | None, options: Options
source: str | bytes,
fnam: str,
module: str | None,
errors: Errors,
options: Options,
raise_on_error: bool = False,
) -> MypyFile:
"""Parse a source file, without doing any semantic analysis.
Expand All @@ -19,4 +24,7 @@ def parse(
source = options.transform_source(source)
import mypy.fastparse

return mypy.fastparse.parse(source, fnam=fnam, module=module, errors=errors, options=options)
tree = mypy.fastparse.parse(source, fnam=fnam, module=module, errors=errors, options=options)
if raise_on_error and errors.is_errors():
errors.raise_error()
return tree
16 changes: 13 additions & 3 deletions mypy/test/testparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from mypy import defaults
from mypy.config_parser import parse_mypy_comments
from mypy.errors import CompileError
from mypy.errors import CompileError, Errors
from mypy.options import Options
from mypy.parse import parse
from mypy.test.data import DataDrivenTestCase, DataSuite
Expand Down Expand Up @@ -51,7 +51,12 @@ def test_parser(testcase: DataDrivenTestCase) -> None:

try:
n = parse(
bytes(source, "ascii"), fnam="main", module="__main__", errors=None, options=options
bytes(source, "ascii"),
fnam="main",
module="__main__",
errors=Errors(options),
options=options,
raise_on_error=True,
)
a = n.str_with_options(options).split("\n")
except CompileError as e:
Expand Down Expand Up @@ -82,7 +87,12 @@ def test_parse_error(testcase: DataDrivenTestCase) -> None:
skip()
# Compile temporary file. The test file contains non-ASCII characters.
parse(
bytes("\n".join(testcase.input), "utf-8"), INPUT_FILE_NAME, "__main__", None, options
bytes("\n".join(testcase.input), "utf-8"),
INPUT_FILE_NAME,
"__main__",
errors=Errors(options),
options=options,
raise_on_error=True,
)
raise AssertionError("No errors reported")
except CompileError as e:
Expand Down
6 changes: 4 additions & 2 deletions test-data/unit/check-errorcodes.test
Original file line number Diff line number Diff line change
Expand Up @@ -975,11 +975,13 @@ def f(d: D, s: str) -> None:
[typing fixtures/typing-typeddict.pyi]

[case testRecommendErrorCode]
# type: ignore[whatever] # E: type ignore with error code is not supported for modules; use `# mypy: disable-error-code="whatever"` [syntax]
# type: ignore[whatever] # E: type ignore with error code is not supported for modules; use `# mypy: disable-error-code="whatever"` [syntax] \
# N: Error code "syntax" not covered by "type: ignore" comment
1 + "asdf"

[case testRecommendErrorCode2]
# type: ignore[whatever, other] # E: type ignore with error code is not supported for modules; use `# mypy: disable-error-code="whatever, other"` [syntax]
# type: ignore[whatever, other] # E: type ignore with error code is not supported for modules; use `# mypy: disable-error-code="whatever, other"` [syntax] \
# N: Error code "syntax" not covered by "type: ignore" comment
1 + "asdf"

[case testShowErrorCodesInConfig]
Expand Down
5 changes: 5 additions & 0 deletions test-data/unit/check-python312.test
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ def g(x: MyList[int]) -> MyList[int]: # E: Variable "__main__.MyList" is not va
# N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
return reveal_type(x) # N: Revealed type is "MyList?[builtins.int]"

type MyInt2 = int # type: ignore[valid-type]

def h(x: MyInt2) -> MyInt2:
return reveal_type(x) # N: Revealed type is "builtins.int"

[case test695Class]
class MyGen[T]: # E: PEP 695 generics are not yet supported
def __init__(self, x: T) -> None: # E: Name "T" is not defined
Expand Down

0 comments on commit f53f422

Please sign in to comment.