Detect unsafe datetime/date operators - #21927
Open
m-aciek wants to merge 1 commit into
Open
Conversation
datetime is a subclass of date, but their comparison and subtraction dunder methods are not substitutable: datetime.__lt__ only accepts another datetime, and date.__lt__ only accepts another date. Because of the subclass relationship, mypy currently accepts mixed date/datetime ordering comparisons and subtraction, which raise TypeError at runtime. Report these under the existing `operator` error code by checking, at each binary operator site, whether the operand that would resolve to date's dunder is satisfied by a datetime value with no override to support it (and vice versa). Descendants of date/datetime are covered by walking the MRO for the first class that defines the dunder. Ordering-comparison handling is Python-version-aware, since 3.13 changed how a date subclass compares against a datetime subclass; subtraction is checked on all versions. Equality, identity, and same-static-type operations are left untouched, since they don't raise TypeError. This covers only the operator-usage half of python#9015. A datetime can still survive under a date-only annotation across an assignment, argument, or return -- undetected here -- until it later reaches one of these operators; a follow-up narrowing-based check is planned separately. Towards python#9015.
Contributor
|
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Towards #9015 — this covers only the operator-usage half; see "Scope and follow-up" below.
datetimeis a subclass ofdate, but their comparison and subtraction dunder methods are not substitutable:datetime.__lt__only accepts anotherdatetime, anddate.__lt__only accepts anotherdate. Because of the subclass relationship, mypy currently accepts mixeddate/datetimeordering comparisons and subtraction, which raiseTypeErrorat runtime:After this change:
Approach
This reports the mismatch under the existing
operatorerror code, at each binary-operator call site incheckexpr.py, rather than changing subtype computation. Concretely: for<,<=,>,>=, and-, if the operand that would resolve todate's dunder is satisfied by adatetimevalue with no override in its MRO to support it (or vice versa), it's flagged. Descendants ofdate/datetimeare covered by walking the MRO for the first class defining the relevant dunder. Ordering-comparison handling is Python-version-aware (3.13 changed how adatesubclass compares against adatetimesubclass); subtraction is checked on all versions. Equality, identity, and same-static-type operations are untouched, since they don't raiseTypeError.No new error code and no new flag — this is a precision improvement to the existing, already-enabled-by-default
operatorcheck, so it applies without any opt-in.Scope and follow-up
This only catches the mismatch at the point an unsafe operator is actually used. A
datetimecan still be silently narrowed to adate-only annotation at an assignment, call argument, or return — undetected here — and only surface later, in a different function, when it collides with a realdateat one of these operators. That's a distinct check (assignability/narrowing rather than operator use) and I'm planning it as a separate follow-up PR with its own opt-in error code, rather than folding it in here.Relationship to #20448
Supersedes my earlier #20448 (closed): that version changed
is_subtype/is_proper_subtypeinmypy/subtypes.pydirectly, which Jukka flagged as too invasive since it's used pervasively (joins, overload resolution, variance checks), whereas this version only adds a diagnostic on top of an already-passing check and never touches subtype computation.Alternatives considered
date/datetimeinheritance relationship in typeshed: Experiment: revert datetime-date inheritance typeshed#15136 — rejected there as not configurable.unsafe-datetimeerror code (per earlier review feedback: naming should follow theunused-awaitable/explicit-anypattern) — dropped in favor of folding intooperator, per the above.Test plan
test-data/unit/check-unsafe-datetime.testcovering: ordering comparisons and subtraction acrossdate/datetime; equality/identity/same-type exemptions;Optional/bound-TypeVarnarrowing; subclasses of bothdateanddatetime(including the pre-/post-3.13 comparison behavior change);NewTypewrappers; and classes that override the relevant dunder to explicitly support the mixed pair.mypytest suite and self-check pass.AI usage
That's my first mypy contribution. I used various LLM agents help while creating those changes. I understand them, did my best to review them and tested them locally myself.
cc @JukkaL @sterliakov