Skip to content

Commit 4f9f289

Browse files
Copilotm-aciek
andcommitted
Refactor to focus on datetime/date only with safe-datetime error code
Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com>
1 parent 089e952 commit 4f9f289

4 files changed

Lines changed: 30 additions & 35 deletions

File tree

docs/source/error_code_list2.rst

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -700,26 +700,22 @@ Example:
700700
def add_forty_two(value: int) -> int:
701701
return value + 42
702702
703-
.. _code-unsafe-subtype:
703+
.. _code-safe-datetime:
704704

705-
Check for unsafe subtype relationships [unsafe-subtype]
706-
--------------------------------------------------------
705+
Disallow datetime where date is expected [safe-datetime]
706+
---------------------------------------------------------
707707

708-
If enabled with :option:`--enable-error-code unsafe-subtype <mypy --enable-error-code>`,
709-
mypy will block certain subtype relationships that are unsafe at runtime despite
710-
being valid in Python's type system.
711-
712-
The primary use case is blocking the ``datetime.datetime`` to ``datetime.date``
713-
inheritance relationship. While ``datetime`` is a subclass of ``date`` at runtime,
714-
comparing a ``datetime`` with a ``date`` raises a ``TypeError``. When this error
715-
code is enabled, mypy will prevent ``datetime`` objects from being used where
716-
``date`` is expected, catching these errors at type-check time.
708+
If enabled with :option:`--enable-error-code safe-datetime <mypy --enable-error-code>`,
709+
mypy will prevent ``datetime.datetime`` objects from being used where ``datetime.date``
710+
is expected. While ``datetime`` is a subclass of ``date`` at runtime, comparing a
711+
``datetime`` with a ``date`` raises a ``TypeError``. This error code catches these
712+
errors at type-check time.
717713

718714
Example:
719715

720716
.. code-block:: python
721717
722-
# mypy: enable-error-code="unsafe-subtype"
718+
# mypy: enable-error-code="safe-datetime"
723719
from datetime import date, datetime
724720
725721
# Error: Incompatible types in assignment (expression has type "datetime", variable has type "date")
@@ -746,7 +742,7 @@ runtime:
746742
if dt < d:
747743
print("never reached")
748744
749-
When ``unsafe-subtype`` is enabled, assignment and parameter passing are blocked,
745+
When ``safe-datetime`` is enabled, assignment and parameter passing are blocked,
750746
preventing the runtime error.
751747

752748
**Note:** Equality comparisons (``==`` and ``!=``) still work between these types,

mypy/errorcodes.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,9 @@ def __hash__(self) -> int:
172172
COMPARISON_OVERLAP: Final = ErrorCode(
173173
"comparison-overlap", "Check that types in comparisons and 'in' expressions overlap", "General"
174174
)
175-
UNSAFE_SUBTYPE: Final = ErrorCode(
176-
"unsafe-subtype",
177-
"Warn about unsafe subtyping relationships that may cause runtime errors",
175+
SAFE_DATETIME: Final = ErrorCode(
176+
"safe-datetime",
177+
"Disallow datetime where date is expected",
178178
"General",
179179
default_enabled=False,
180180
)

mypy/subtypes.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,10 @@
8585
IS_VAR: Final = 4
8686
IS_EXPLICIT_SETTER: Final = 5
8787

88-
# Known unsafe subtyping relationships that should trigger warnings.
89-
# Each tuple is (subclass_fullname, superclass_fullname).
90-
# These are cases where a class is a subclass at runtime but treating it
91-
# as a subtype can cause runtime errors.
92-
UNSAFE_SUBTYPING_PAIRS: Final = [("datetime.datetime", "datetime.date")]
88+
# Disallow datetime.datetime where datetime.date is expected when safe-datetime is enabled.
89+
# While datetime is a subclass of date at runtime, comparing them raises TypeError,
90+
# making this inheritance relationship problematic in practice.
91+
DATETIME_DATE_FULLNAMES: Final = ("datetime.datetime", "datetime.date")
9392

9493
TypeParameterChecker: _TypeAlias = Callable[[Type, Type, int, bool, "SubtypeContext"], bool]
9594

@@ -537,12 +536,12 @@ def visit_instance(self, left: Instance) -> bool:
537536
rname = right.type.fullname
538537
lname = left.type.fullname
539538

540-
# Check if this is an unsafe subtype relationship that should be blocked
541-
if (
542-
self.options
543-
and codes.UNSAFE_SUBTYPE in self.options.enabled_error_codes
544-
and (lname, rname) in UNSAFE_SUBTYPING_PAIRS
545-
):
539+
# Check if this is the datetime/date relationship that should be blocked
540+
# when safe-datetime is enabled
541+
if (self.options
542+
and codes.SAFE_DATETIME in self.options.enabled_error_codes
543+
and lname == DATETIME_DATE_FULLNAMES[0]
544+
and rname == DATETIME_DATE_FULLNAMES[1]):
546545
return False
547546

548547
# Always try a nominal check if possible,
Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[case testDatetimeVsDateComparison]
2-
# flags: --enable-error-code unsafe-subtype
2+
# flags: --enable-error-code safe-datetime
33
from datetime import date, datetime
44

55
dt: datetime
@@ -79,7 +79,7 @@ class datetime(date):
7979
def __ge__(self, other: datetime) -> bool: ... # type: ignore[override]
8080

8181
[case testDatetimeVsDateComparisonExplicitTypes]
82-
# flags: --enable-error-code unsafe-subtype
82+
# flags: --enable-error-code safe-datetime
8383
from datetime import date, datetime
8484

8585
def compare_datetime_date(dt: datetime, d: date) -> bool:
@@ -107,7 +107,7 @@ class datetime(date):
107107
def __ge__(self, other: datetime) -> bool: ... # type: ignore[override]
108108

109109
[case testDatetimeComparisonOK]
110-
# flags: --enable-error-code unsafe-subtype
110+
# flags: --enable-error-code safe-datetime
111111
from datetime import date, datetime
112112

113113
dt1: datetime
@@ -145,7 +145,7 @@ class datetime(date):
145145
def __ge__(self, other: datetime) -> bool: ... # type: ignore[override]
146146

147147
[case testDatetimeVsDateComparisonWithNow]
148-
# flags: --enable-error-code unsafe-subtype
148+
# flags: --enable-error-code safe-datetime
149149
from datetime import date, datetime
150150

151151
if datetime.now() < date.today(): # E: Unsupported operand types for < ("datetime" and "date")
@@ -173,7 +173,7 @@ class datetime(date):
173173
def __ge__(self, other: datetime) -> bool: ... # type: ignore[override]
174174

175175
[case testDatetimeVsDateComparisonInExpression]
176-
# flags: --enable-error-code unsafe-subtype
176+
# flags: --enable-error-code safe-datetime
177177
from datetime import date, datetime
178178

179179
dt: datetime
@@ -203,7 +203,7 @@ class datetime(date):
203203
def __ge__(self, other: datetime) -> bool: ... # type: ignore[override]
204204

205205
[case testDateVsDateMixedWithDatetime]
206-
# flags: --enable-error-code unsafe-subtype
206+
# flags: --enable-error-code safe-datetime
207207
from datetime import date, datetime
208208

209209
def compare_dates(d1: date, d2: date) -> bool:
@@ -238,7 +238,7 @@ class datetime(date):
238238
def __ge__(self, other: datetime) -> bool: ... # type: ignore[override]
239239

240240
[case testInheritanceBlocking]
241-
# flags: --enable-error-code unsafe-subtype
241+
# flags: --enable-error-code safe-datetime
242242
from datetime import date, datetime
243243

244244
# Assignment should be blocked

0 commit comments

Comments
 (0)