Skip to content

Commit 036c3c1

Browse files
ccie18643claude
andcommitted
build(lint): enable 8 more mypy error codes + fix violations
Add eight error codes to [tool.mypy] enable_error_code on top of explicit-override: ignore-without-code, truthy-iterable, redundant-self, unused-awaitable (free — zero violations), truthy-bool, redundant-expr, possibly-undefined, mutable-override (cheap — fixed below). Deliberately NOT warn_unreachable / disallow_any_decorated: they false-positive against the ProtoEnum dynamic-_missing_ `case _:` pattern and stdlib decorator machinery (Any-typed), respectively. Categories fixed: - truthy-bool + packet-handler redundant-expr (cast-away-None root cause): the UDP/TCP/ICMPv4/ICMPv6 RX handlers cast stack.sockets.get(...) / get_for_ingress(...) — both `socket | None` — to a bare TcpSocket / UdpSocket, lying to mypy that the lookup never returns None. That single dishonesty produced the truthy-bool walrus warnings and the `is None or` / `is not None and` redundant-expr warnings. Made every cast honest (`cast(UdpSocket | None, ...)` / `cast(TcpSocket | None, ...)`) and converted walrus-truthiness to explicit `is None` / `is not None` checks. Runtime is identical — the handlers already coped with None via truthiness; only the type is now honest and the guard explicit. - possibly-undefined: enum __str__ `match self` blocks bound `name` only on the known-member cases, leaving it unbound for a dynamically materialised unknown ProtoEnum member. Added a `case _:` default that binds the existing fallback form and dropped the now-redundant `... if self.is_unknown else name` ternary, keeping identical output for known and unknown members (net_proto lib/enums + dhcp4/dhcp6/dns/llc/ ip6_routing enums). - redundant-expr (defensive isinstance guards): for net_addr Ip4/Ip6Network tuple-form validation and the DHCPv4 classless-static-route __post_init__, mypy proves the element-type isinstance operands statically true from the declared types, but the runtime checks are load-bearing (a test pins the DHCPv4 wrong-element rejection; the network guard prevents a bare-builtin int(str) later). Kept the guards: extracted a `_is_well_formed_route` helper typed `object` for DHCPv4 (no suppression needed), and a narrow justified `# type: ignore[redundant-expr]` for the two network files. The sendmsg ancdata 3-tuple check was genuinely redundant (item is already a tuple) — simplified to `len(item) != 3`. - mutable-override: TCP cubic/cwnd integration test classes set `_DEFAULT_CC_MODE = CcMode.RENO`, narrowing the TcpTestCase base attr typed `CcMode | None`. Annotated each override `: CcMode | None` to keep the base type. - examples_legacy / tests_runner (same gate): dropped a dead `if subsystem` truthy filter in stack.py, added a `case _: assert_never(ip_version)` exhaustiveness guard in client__icmp_echo.py, and annotated TestslideStyleRunner.resultclass to the base's mutable `Callable[..., TextTestResult]` type. - Real latent bug: stack/__init__.py boot defaults IP4_ADDRESS / IP6_ADDRESS / IP4_GATEWAY / IP6_GATEWAY were unannotated `= None`, so mypy inferred type `None`, making the operator-set boot-address path in lifecycle.init (`Ip4IfAddr(_stack.IP4_ADDRESS)` etc.) dead to the type checker. Annotated to the real intended types (`str | None` for the host addresses consumed by Ip{4,6}IfAddr(...), `Ip{4,6}Address | None` for the gateways passed to install_boot_default_routes). No cascade. Doc: typing.md §2.1 documents the now-enabled extra codes and why warn_unreachable / disallow_any_decorated stay off; §21 notes ignore-without-code now mechanically forbids bare `# type: ignore`. make lint clean, 12752 passing / 0 failing / 0 skipped, no leaked log lines. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent d0d1bc2 commit 036c3c1

22 files changed

Lines changed: 225 additions & 105 deletions

File tree

.claude/rules/typing.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,29 @@ You don't need to remember the flag names — `make lint` is
5757
the authoritative gate. The list above explains *why* a
5858
particular rule from this file is enforced.
5959

60+
### 2.1 Extra `enable_error_code` codes
61+
62+
On top of the strict bundle, `pyproject.toml` opts into
63+
nine extra error codes via `enable_error_code` — each one
64+
upgrades a latent footgun from silent-pass to build-break:
65+
66+
| Error code | Effect |
67+
|---|---|
68+
| `explicit-override` | A method overriding a parent MUST carry `@override` (§11) — enforced everywhere, tests included |
69+
| `ignore-without-code` | A bare `# type: ignore` is a hard error; the narrow `# type: ignore[code]` form is mandatory (§21) |
70+
| `truthy-bool` | An object with no `__bool__` / `__len__` used in a boolean context (e.g. `if socket :=` where the type can't be falsy) is flagged |
71+
| `truthy-iterable` | An always-truthy iterable used as a plain boolean condition is flagged |
72+
| `redundant-expr` | An `and` / `or` operand that mypy proves constant (always-true / always-false) is flagged |
73+
| `redundant-self` | A redundant `Self`-typed `self` annotation is flagged |
74+
| `possibly-undefined` | A name that may be unbound on some control-flow path (e.g. a `match` with no `case _:` default leaving a variable unset) is flagged |
75+
| `unused-awaitable` | An awaitable whose result is discarded without `await` is flagged |
76+
| `mutable-override` | A subclass narrowing a mutable base attribute's type (covariant override of a settable field) is flagged — applies to test classes too |
77+
78+
`warn_unreachable` and `disallow_any_decorated` are
79+
deliberately **not** enabled: they produce false positives
80+
against the `ProtoEnum` dynamic-`_missing_` `case _:` pattern
81+
and stdlib decorator machinery.
82+
6083
## 3. Annotation discipline — what MUST be annotated
6184

6285
| Construct | Annotation requirement |
@@ -1028,7 +1051,10 @@ Acceptable uses:
10281051
```
10291052
The narrow form `# type: ignore[error-code]` is mandatory
10301053
— bare `# type: ignore` is forbidden because it suppresses
1031-
every error on the line, not just the intended one.
1054+
every error on the line, not just the intended one. This is
1055+
now **mechanically enforced**: the `ignore-without-code`
1056+
error code (§2.1) makes a bare `# type: ignore` a hard
1057+
build error, not just a convention.
10321058

10331059
- **Mypy strict false-positive that has a known issue
10341060
upstream.** Cite the issue:

examples_legacy/client__icmp_echo.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
import os
3939
import struct
4040
import threading
41-
from typing import Any, override
41+
from typing import Any, assert_never, override
4242

4343
import click
4444
from examples_legacy.lib.client import Client
@@ -121,6 +121,8 @@ def _assemble_icmp_echo_request_message(
121121
case IpVersion.IP4:
122122
icmp_type = ICMP4__ECHO_REQUEST__TYPE
123123
icmp_code = ICMP4__ECHO_REQUEST__CODE
124+
case _:
125+
assert_never(ip_version)
124126

125127
data = payload(length=message_size)
126128

examples_legacy/stack.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ def _on_sigusr1(_signum: int, _frame: FrameType | None) -> None:
382382
_last_stats = time.monotonic()
383383
_last_snapshot = _capture_stats_snapshot()
384384

385-
while any(subsystem.is_alive for subsystem in subsystems if subsystem) or not subsystems:
385+
while any(subsystem.is_alive for subsystem in subsystems) or not subsystems:
386386
time.sleep(1)
387387
if _remove_requested[0]:
388388
_remove_requested[0] = False

packages/net_addr/net_addr/ip4_network.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,15 @@ def __init__(
9292
if len(network) != 2:
9393
raise Ip4NetworkFormatError(network)
9494
tuple_address, tuple_mask = network
95-
if not (isinstance(tuple_address, Ip4Address) and isinstance(tuple_mask, Ip4Mask)):
95+
# Defensive runtime guard: the parameter is typed
96+
# 'tuple[Ip4Address, Ip4Mask]', so mypy proves the first
97+
# 'isinstance' operand statically true, but the check is
98+
# load-bearing — a mistyped tuple must raise the net_addr
99+
# error here rather than blowing up later on 'int(...)'.
100+
if not (
101+
isinstance(tuple_address, Ip4Address) # type: ignore[redundant-expr]
102+
and isinstance(tuple_mask, Ip4Mask)
103+
):
96104
raise Ip4NetworkFormatError(network)
97105
if strict and int(tuple_address) & ~int(tuple_mask) & IP4__MASK:
98106
raise Ip4NetworkFormatError(network)

packages/net_addr/net_addr/ip6_network.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,15 @@ def __init__(
9292
if len(network) != 2:
9393
raise Ip6NetworkFormatError(network)
9494
tuple_address, tuple_mask = network
95-
if not (isinstance(tuple_address, Ip6Address) and isinstance(tuple_mask, Ip6Mask)):
95+
# Defensive runtime guard: the parameter is typed
96+
# 'tuple[Ip6Address, Ip6Mask]', so mypy proves the first
97+
# 'isinstance' operand statically true, but the check is
98+
# load-bearing — a mistyped tuple must raise the net_addr
99+
# error here rather than blowing up later on 'int(...)'.
100+
if not (
101+
isinstance(tuple_address, Ip6Address) # type: ignore[redundant-expr]
102+
and isinstance(tuple_mask, Ip6Mask)
103+
):
96104
raise Ip6NetworkFormatError(network)
97105
# A prefix has no RFC 4007 zone; reject a scoped
98106
# address rather than silently dropping the zone.

packages/net_proto/net_proto/lib/enums.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,10 @@ def __str__(self) -> str:
6161
name = "IPv6"
6262
case EtherType.RAW:
6363
name = "Raw"
64+
case _:
65+
name = f"0x{self.value:0>4x}"
6466

65-
return f"0x{self.value:0>4x}" if self.is_unknown else name
67+
return name
6668

6769
@staticmethod
6870
def from_proto(proto: Proto) -> EtherType:
@@ -142,8 +144,10 @@ def __str__(self) -> str:
142144
name = "IPv6_DestOpts"
143145
case IpProto.RAW:
144146
name = "Raw"
147+
case _:
148+
name = f"{self.value}"
145149

146-
return f"{self.value}" if self.is_unknown else name
150+
return name
147151

148152
@staticmethod
149153
def from_proto(proto: Proto) -> IpProto:

packages/net_proto/net_proto/protocols/dhcp4/dhcp4__enums.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,5 +92,7 @@ def __str__(self) -> str:
9292
name = "Release"
9393
case Dhcp4MessageType.INFORM:
9494
name = "Inform"
95+
case _:
96+
name = f"{self.value}"
9597

96-
return f"{self.value}" if self.is_unknown else name
98+
return name

packages/net_proto/net_proto/protocols/dhcp4/options/dhcp4__option__classless_static_route.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,25 @@ def _significant_octet_count(prefixlen: int, /) -> int:
7575
return (prefixlen + 7) // 8
7676

7777

78+
def _is_well_formed_route(route: object, /) -> bool:
79+
"""
80+
Get whether a 'routes' element is a well-formed
81+
(Ip4Network, Ip4Address) 2-tuple.
82+
"""
83+
84+
# Typed 'object' (not the field's 'tuple[Ip4Network, Ip4Address]')
85+
# so these are genuine runtime checks, not statically-redundant
86+
# ones: a caller may pass a mistyped tuple despite the field
87+
# annotation, and it must be rejected here rather than later on
88+
# 'network.prefixlen'.
89+
return (
90+
isinstance(route, tuple)
91+
and len(route) == 2
92+
and isinstance(route[0], Ip4Network)
93+
and isinstance(route[1], Ip4Address)
94+
)
95+
96+
7897
@dataclass(frozen=True, kw_only=False, slots=True)
7998
class Dhcp4OptionClasslessStaticRoute(Dhcp4Option):
8099
"""
@@ -101,13 +120,13 @@ def __post_init__(self) -> None:
101120

102121
assert isinstance(self.routes, list), f"The 'routes' field must be a list. Got: {type(self.routes)!r}"
103122

104-
assert all(
105-
isinstance(route, tuple)
106-
and len(route) == 2
107-
and isinstance(route[0], Ip4Network)
108-
and isinstance(route[1], Ip4Address)
109-
for route in self.routes
110-
), (
123+
# Defensive programmer-error guard: the field is typed
124+
# 'list[tuple[Ip4Network, Ip4Address]]', so mypy proves each
125+
# 'isinstance' operand statically true, but the runtime check
126+
# is load-bearing — a caller passing a wrong-shaped tuple (the
127+
# field annotation is advisory, not enforced) must be rejected
128+
# here rather than blowing up later on 'network.prefixlen'.
129+
assert all(_is_well_formed_route(route) for route in self.routes), (
111130
f"The 'routes' field must be a list of (Ip4Network, Ip4Address) tuples. "
112131
f"Got: {[type(route) for route in self.routes]!r}"
113132
)

packages/net_proto/net_proto/protocols/dhcp6/dhcp6__enums.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,10 @@ def __str__(self) -> str:
8787
name = "Relay-Forward"
8888
case Dhcp6MessageType.RELAY_REPL:
8989
name = "Relay-Reply"
90+
case _:
91+
name = f"{self.value}"
9092

91-
return f"{self.value}" if self.is_unknown else name
93+
return name
9294

9395

9496
class Dhcp6StatusCode(ProtoEnumWord):
@@ -125,5 +127,7 @@ def __str__(self) -> str:
125127
name = "UseMulticast"
126128
case Dhcp6StatusCode.NO_PREFIX_AVAIL:
127129
name = "NoPrefixAvail"
130+
case _:
131+
name = f"{self.value}"
128132

129-
return f"{self.value}" if self.is_unknown else name
133+
return name

packages/net_proto/net_proto/protocols/dns/dns__enums.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,10 @@ def __str__(self) -> str:
6363
name = "Notify"
6464
case DnsOpcode.UPDATE:
6565
name = "Update"
66+
case _:
67+
name = f"{self.value}"
6668

67-
return f"{self.value}" if self.is_unknown else name
69+
return name
6870

6971

7072
class DnsResponseCode(ProtoEnumByte):
@@ -98,8 +100,10 @@ def __str__(self) -> str:
98100
name = "NotImp"
99101
case DnsResponseCode.REFUSED:
100102
name = "Refused"
103+
case _:
104+
name = f"{self.value}"
101105

102-
return f"{self.value}" if self.is_unknown else name
106+
return name
103107

104108

105109
class DnsRecordType(ProtoEnumWord):
@@ -139,8 +143,10 @@ def __str__(self) -> str:
139143
name = "TXT"
140144
case DnsRecordType.AAAA:
141145
name = "AAAA"
146+
case _:
147+
name = f"{self.value}"
142148

143-
return f"{self.value}" if self.is_unknown else name
149+
return name
144150

145151

146152
class DnsRecordClass(ProtoEnumWord):
@@ -165,5 +171,7 @@ def __str__(self) -> str:
165171
name = "CH"
166172
case DnsRecordClass.HS:
167173
name = "HS"
174+
case _:
175+
name = f"{self.value}"
168176

169-
return f"{self.value}" if self.is_unknown else name
177+
return name

0 commit comments

Comments
 (0)