Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1332,6 +1332,11 @@ def visit_func_def(self, defn: FuncDef) -> None:
self.visit_func_def_impl(defn)

def visit_func_def_impl(self, defn: FuncDef) -> None:
if defn.is_no_type_check:
# @typing.no_type_check: the body must be skipped. The decorator
# dispatch (visit_decorator) handles this at the top level, but a
# fine-grained reprocess can re-check the bare FuncDef target.
return
with self.tscope.function_scope(defn), self.set_recurse_into_functions():
self.check_func_item(defn, name=defn.name)
if not self.can_skip_diagnostics:
Expand Down
6 changes: 6 additions & 0 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,7 @@ def is_dynamic(self) -> bool:
"is_trivial_body",
"is_trivial_self",
"is_mypy_only",
"is_no_type_check",
]

# Abstract status of a function
Expand All @@ -1109,6 +1110,7 @@ class FuncDef(FuncItem, SymbolNode, Statement):
"is_trivial_self",
"is_invalid_redefinition",
"is_mypy_only",
"is_no_type_check",
# Present only when a function is decorated with @typing.dataclass_transform or similar
"dataclass_transform_spec",
"docstring",
Expand Down Expand Up @@ -1139,6 +1141,10 @@ def __init__(
self.original_def: None | FuncDef | Var | Decorator = None
# Definitions that appear in if TYPE_CHECKING are marked with this flag.
self.is_mypy_only = False
# Decorated with @typing.no_type_check: the body must never be type-checked.
# Stored as a durable flag so it survives fine-grained reprocessing of the
# FuncDef target (which bypasses the decorator dispatch in the checker).
self.is_no_type_check = False
self.dataclass_transform_spec: DataclassTransformSpec | None = None
self.docstring: str | None = None
self.deprecated: str | None = None
Expand Down
1 change: 1 addition & 0 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1810,6 +1810,7 @@ def visit_decorator(self, dec: Decorator) -> None:
if (not dec.is_overload or dec.var.is_property) and self.type:
dec.var.info = self.type
dec.var.is_initialized_in_class = True
dec.func.is_no_type_check = no_type_check
if no_type_check:
erase_func_annotations(dec.func)
if not no_type_check and (self.recurse_into_functions or dec.func.def_or_infer_vars):
Expand Down
22 changes: 22 additions & 0 deletions test-data/unit/fine-grained.test
Original file line number Diff line number Diff line change
Expand Up @@ -11839,3 +11839,25 @@ def bar() -> str: return "a"
main:2: note: Revealed type is "None | builtins.int"
==
main:2: note: Revealed type is "None | builtins.str"

[case testNoTypeCheckReprocessedViaDependency]
# A @no_type_check function reprocessed via a dependency change (without editing
# its own module) must still have its body skipped. Regression test: the daemon
# used to re-check the bare FuncDef target, bypassing the @no_type_check guard.
# flags: --check-untyped-defs
import a
[file a.py]
from typing import no_type_check
from b import SCALE

@no_type_check
def f() -> None:
factor: int = SCALE
bad: int = "not an int"
[file b.py]
SCALE: int = 3
[file b.py.2]
SCALE: str = ""
[typing fixtures/typing-medium.pyi]
[out]
==
Loading