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
33 changes: 32 additions & 1 deletion mypy/semanal_namedtuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
ExpressionStmt,
FuncBase,
FuncDef,
IfStmt,
ListExpr,
NamedTupleExpr,
NameExpr,
Expand All @@ -48,6 +49,7 @@
is_StrExpr_list,
)
from mypy.options import Options
from mypy.reachability import ALWAYS_FALSE, ALWAYS_TRUE, infer_condition_value
from mypy.semanal_shared import (
PRIORITY_FALLBACKS,
SemanticAnalyzerInterface,
Expand Down Expand Up @@ -139,6 +141,35 @@ def analyze_namedtuple_classdef(
# This can't be a valid named tuple.
return False, None

def iter_reachable_namedtuple_statements(
self, statements: list[Statement]
) -> Iterator[Statement]:
for stmt in statements:
if isinstance(stmt, IfStmt):
encountered_unknown = False
handled = False

for expr, body in zip(stmt.expr, stmt.body):
truth = infer_condition_value(expr, self.options)

if truth == ALWAYS_TRUE:
yield from self.iter_reachable_namedtuple_statements(body.body)
handled = True
break

elif truth == ALWAYS_FALSE:
continue

else:
encountered_unknown = True

if not handled and not encountered_unknown and stmt.else_body is not None:
yield from self.iter_reachable_namedtuple_statements(stmt.else_body.body)

continue

yield stmt

def check_namedtuple_classdef(
self, defn: ClassDef, is_stub_file: bool
) -> tuple[list[str], list[Type], dict[str, Expression], list[Statement]] | None:
Expand All @@ -157,7 +188,7 @@ def check_namedtuple_classdef(
types: list[Type] = []
default_items: dict[str, Expression] = {}
statements: list[Statement] = []
for stmt in defn.defs.body:
for stmt in self.iter_reachable_namedtuple_statements(defn.defs.body):
statements.append(stmt)
if not isinstance(stmt, AssignmentStmt):
# Still allow pass or ... (for empty namedtuples).
Expand Down
252 changes: 252 additions & 0 deletions test-data/unit/check-namedtuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -1541,3 +1541,255 @@ from foo import DEFERRED_INT, DEFERRED_STR
DEFERRED_INT = 1
DEFERRED_STR = "a"
[builtins fixtures/tuple.pyi]

[case testConditionalNamedTupleFieldPy312]
# flags: --python-version 3.12
from typing import NamedTuple
import sys

class NT(NamedTuple):
x: int
if sys.version_info >= (3, 12):
y: str

reveal_type(NT.x)
reveal_type(NT.y)

[out]
main:10: note: Revealed type is "builtins.int"
main:11: note: Revealed type is "builtins.str"

[builtins fixtures/tuple.pyi]

[case testConditionalNamedTupleFieldPy311]
# flags: --python-version 3.11
from typing import NamedTuple
import sys

class NT(NamedTuple):
x: int
if sys.version_info >= (3, 12):
y: str

reveal_type(NT.x)
NT.y

[out]
main:10: note: Revealed type is "builtins.int"
main:11: error: "type[NT]" has no attribute "y"

[builtins fixtures/tuple.pyi]

[case testConditionalNamedTupleElseBranch]
# flags: --python-version 3.11
from typing import NamedTuple
import sys

class NT(NamedTuple):
x: int

if sys.version_info >= (3, 12):
y: str
else:
z: int

reveal_type(NT.z)
NT.y

[out]
main:13: note: Revealed type is "builtins.int"
main:14: error: "type[NT]" has no attribute "y"

[builtins fixtures/tuple.pyi]

[case testConditionalNamedTupleElseBranchPy312]
# flags: --python-version 3.12
from typing import NamedTuple
import sys

class NT(NamedTuple):
x: int

if sys.version_info >= (3, 12):
y: str
else:
z: int

reveal_type(NT.y)
NT.z

[out]
main:13: note: Revealed type is "builtins.str"
main:14: error: "type[NT]" has no attribute "z"

[builtins fixtures/tuple.pyi]

[case testConditionalNamedTupleElif]
# flags: --python-version 3.12
from typing import NamedTuple
import sys

class NT(NamedTuple):
x: int

if sys.version_info >= (3, 13):
a: int
elif sys.version_info >= (3, 12):
b: str
else:
c: bool

reveal_type(NT.b)
NT.a
NT.c

[out]
main:15: note: Revealed type is "builtins.str"
main:16: error: "type[NT]" has no attribute "a"
main:17: error: "type[NT]" has no attribute "c"

[builtins fixtures/tuple.pyi]

[case testConditionalNamedTupleNestedIf]
# flags: --python-version 3.12
from typing import NamedTuple
import sys

class NT(NamedTuple):
x: int

if sys.version_info >= (3, 12):
if sys.version_info >= (3, 12):
y: str

reveal_type(NT.y)

[out]
main:12: note: Revealed type is "builtins.str"

[builtins fixtures/tuple.pyi]

[case testConditionalNamedTupleUnknownCondition]
from typing import NamedTuple

SOME_FLAG = True

class NT(NamedTuple):
x: int

if SOME_FLAG:
y: str

NT.y

[out]
main:11: error: "type[NT]" has no attribute "y"

[builtins fixtures/tuple.pyi]

[case testConditionalNamedTupleMultipleFields]
# flags: --python-version 3.12
from typing import NamedTuple
import sys

class NT(NamedTuple):
if sys.version_info >= (3, 12):
x: int
y: str

reveal_type(NT.x)
reveal_type(NT.y)

[out]
main:10: note: Revealed type is "builtins.int"
main:11: note: Revealed type is "builtins.str"

[builtins fixtures/tuple.pyi]

[case testConditionalNamedTupleMultipleElif]
# flags: --python-version 3.12
from typing import NamedTuple
import sys

class NT(NamedTuple):
if sys.version_info >= (3, 14):
a: int
elif sys.version_info >= (3, 13):
b: int
elif sys.version_info >= (3, 12):
c: str
else:
d: bool

reveal_type(NT.c)
NT.a
NT.b
NT.d

[out]
main:15: note: Revealed type is "builtins.str"
main:16: error: "type[NT]" has no attribute "a"
main:17: error: "type[NT]" has no attribute "b"
main:18: error: "type[NT]" has no attribute "d"

[builtins fixtures/tuple.pyi]

[case testConditionalNamedTupleEmptyBranch]
# flags: --python-version 3.12
from typing import NamedTuple
import sys

class NT(NamedTuple):
x: int

if sys.version_info >= (3, 12):
pass
else:
y: str

NT.y

[out]
main:13: error: "type[NT]" has no attribute "y"

[builtins fixtures/tuple.pyi]

[case testConditionalNamedTupleUnknownConditionElse]
from typing import NamedTuple

FLAG = True

class NT(NamedTuple):
x: int

if FLAG:
y: str
else:
z: int

NT.y
NT.z

[out]
main:13: error: "type[NT]" has no attribute "y"
main:14: error: "type[NT]" has no attribute "z"

[builtins fixtures/tuple.pyi]

[case testConditionalNamedTupleDefaultValue]
# flags: --python-version 3.12
from typing import NamedTuple
import sys

class NT(NamedTuple):
if sys.version_info >= (3, 12):
x: int = 1

reveal_type(NT.x)
reveal_type(NT().x)

[out]
main:9: note: Revealed type is "builtins.int"
main:10: note: Revealed type is "builtins.int"

[builtins fixtures/tuple.pyi]
Loading