…instance, not cast (#473)
- IPv4InterfaceField.post_process and IPv6InterfaceField.post_process each
built their return value with the polymorphic ipaddress.ip_interface(),
typed IPv4Interface | IPv6Interface, against a declared return type that
admits only one of the two -- two standing mypy [return-value] errors.
- The two ip_interface() calls in the matching pre_process methods already
wore a # type: ignore[assignment] for the identical reason, confirmed by
removing the suppressions and re-running mypy (same errors, different
code).
- Fixed both pairs the same way: replace the `val.version != self.version`
comparison with `not isinstance(val, IPv4Interface/IPv6Interface)`, which
mypy accepts as a narrowing type guard. Rejected constructing the concrete
class directly (ipaddress.IPv4Interface(...)/IPv6Interface(...)) instead,
because it changes behaviour: passing an already-constructed interface of
the *other* concrete type to pre_process would then raise before reaching
the version check, changing the message from "IP version mismatch: ..."
to "invalid IP interface: ...", breaking
test_wrong_version_message_is_not_relabelled_as_a_malformed_value.
- No behaviour change: probed both versions side by side on 16 representative
inputs (round trips at every extreme prefix length, wrong-version strings,
wrong-version pre-built Interface objects, malformed values, a non-
contiguous IPv4 netmask, an out-of-range IPv6 prefix length) -- 0
mismatches in output or exception message.
mypy pcapkit: 125 errors/40 files -> 123 errors/39 files, diffed line by
line; the only errors removed are the two named in #473, none added.
pytest tests/ (excluding files owned by other in-flight PRs): 963 passed,
17 skipped, 873 subtests passed, 0 failures.
Summary
IPv4InterfaceField.post_processandIPv6InterfaceField.post_processeach built their return value with the polymorphicipaddress.ip_interface(...)-- typedIPv4Interface | IPv6Interface-- against a declared return type that admits only one of the two. mypy flagged this as two standing[return-value]errors.pre_processmethods had the identical defect wearing a# type: ignore[assignment]suppression instead -- confirmed by removing the suppressions and re-running mypy, which reported the same union-vs-concrete mismatch at those lines.val.version != self.versioncomparison withnot isinstance(val, IPv4Interface)/not isinstance(val, IPv6Interface), a form mypy accepts as a genuine type-narrowing guard.Why isinstance-narrowing instead of a
cast, and instead of constructing the concrete class directlyA
cast/# type: ignorewas ruled out per the issue -- it would silence the checker while leaving the genuinely polymorphic call in place.Constructing the concrete class directly (
ipaddress.IPv4Interface(...)/IPv6Interface(...)) instead of going throughip_interface()looked like the natural alternative, but it changes runtime behaviour inpre_process: passing an already-constructed interface object of the other concrete type (e.g. anIPv4InterfaceintoIPv6InterfaceField.pre_process) makes the concrete constructor raise immediately with anAddressValueError-derived message, before the existing version check ever runs. That flips the raised message from"IP version mismatch: 4 != 6"to"invalid IP interface: ...", which breaks the existing testtest_wrong_version_message_is_not_relabelled_as_a_malformed_value(verified locally by making the swap and running it: it fails as predicted).The isinstance-narrowing approach keeps
ip_interface()'s polymorphic parsing (so all input forms it accepts still work identically) and only changes how the already-present version check is expressed, in a form mypy can follow through to the return statement. It also unifiespre_processandpost_processon one idiom instead of two.Verification
mypy pcapkit: 125 errors in 40 files -> 123 errors in 39 files, diffed error-list-to-error-list. The only two errors removed are exactly the ones named in Interface fields declare a narrower post_process return type than ip_interface() gives, as two standing mypy [return-value] errors #473 (ipaddress.py:286andipaddress.py:377on the pre-fix line numbers); nothing else changed anywhere in the package.Interfaceobjects (the case that would have broken under the concrete-constructor approach), malformed values, a non-contiguous IPv4 netmask, and an out-of-range IPv6 prefix length. 0 mismatches in return value or exception type/message.pytest tests/(excludingtest_mh_unit.py,test_hip_unit.py,test_fields_misc_packet_context.py,test_option_roundtrip_unit.py, owned by other in-flight PRs): 963 passed, 17 skipped, 873 subtests passed, 0 failures.tests/corekit/test_fields_ipaddress.pyspecifically: 12 passed, 17 subtests passed, including the version-mismatch-message test that would have caught the rejected alternative approach.Deliberately not fixed here
_IPAddressField.post_process(line 145) carries its own pre-existing# type: ignore[return-value]for a genericTypeVar-bound return (_AT), not a concrete-class union. It isn't one of the two errors Interface fields declare a narrower post_process return type than ip_interface() gives, as two standing mypy [return-value] errors #473 names, and fixing it is a different shape of problem (no concrete class to narrow to without the field carrying its own address-class reference). Left untouched; happy to file a follow-up issue if wanted.Closes #473