Skip to content

Conversation

@charliermarsh
Copy link
Member

Summary

Closes astral-sh/ty#1681.

@charliermarsh charliermarsh changed the title Emit a diagnostic for subclassing with order=True [ty] Emit a diagnostic for subclassing with order=True Nov 30, 2025
@charliermarsh charliermarsh added the ty Multi-file analysis & type inference label Nov 30, 2025
@astral-sh-bot
Copy link

astral-sh-bot bot commented Nov 30, 2025

Diagnostic diff on typing conformance tests

Changes were detected when running ty on typing conformance tests
--- old-output.txt	2025-11-30 16:19:49.924622031 +0000
+++ new-output.txt	2025-11-30 16:19:53.496646751 +0000
@@ -342,12 +342,14 @@
 dataclasses_transform_field.py:64:16: error[unknown-argument] Argument `id` does not match any known parameter
 dataclasses_transform_field.py:75:1: error[missing-argument] No argument provided for required parameter `name`
 dataclasses_transform_field.py:75:16: error[too-many-positional-arguments] Too many positional arguments: expected 0, got 1
+dataclasses_transform_func.py:45:25: warning[subclass-of-dataclass-with-order] Class `Customer2Subclass` inherits from dataclass `Customer2` which has `order=True`
 dataclasses_transform_func.py:56:1: error[invalid-assignment] Object of type `Literal[3]` is not assignable to attribute `name` of type `str`
 dataclasses_transform_func.py:60:6: error[unsupported-operator] Operator `<` is not supported for types `Customer1` and `Customer1`
 dataclasses_transform_func.py:64:36: error[unknown-argument] Argument `salary` does not match any known parameter
 dataclasses_transform_func.py:70:8: error[missing-argument] No arguments provided for required parameters `id`, `name`
 dataclasses_transform_func.py:70:18: error[too-many-positional-arguments] Too many positional arguments: expected 0, got 2
 dataclasses_transform_func.py:76:36: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `T@create_model_frozen`
+dataclasses_transform_func.py:89:25: warning[subclass-of-dataclass-with-order] Class `Customer3Subclass` inherits from dataclass `Customer3` which has `order=True`
 dataclasses_transform_func.py:96:1: error[invalid-assignment] Property `id` defined in `Customer3` is read-only
 dataclasses_transform_meta.py:63:1: error[invalid-assignment] Property `id` defined in `Customer1` is read-only
 dataclasses_transform_meta.py:66:8: error[missing-argument] No arguments provided for required parameters `id`, `name`
@@ -1039,4 +1041,4 @@
 typeddicts_usage.py:28:17: error[missing-typed-dict-key] Missing required key 'name' in TypedDict `Movie` constructor
 typeddicts_usage.py:28:18: error[invalid-key] Unknown key "title" for TypedDict `Movie`: Unknown key "title"
 typeddicts_usage.py:40:24: error[invalid-type-form] The special form `typing.TypedDict` is not allowed in type expressions
-Found 1041 diagnostics
+Found 1043 diagnostics

@astral-sh-bot
Copy link

astral-sh-bot bot commented Nov 30, 2025

mypy_primer results

Changes were detected when running on open source projects
attrs (https://github.com/python-attrs/attrs)
+ tests/test_annotations.py:189:17: warning[subclass-of-dataclass-with-order] Class `B` inherits from dataclass `A` which has `order=True`
+ tests/test_annotations.py:194:17: warning[subclass-of-dataclass-with-order] Class `C` inherits from dataclass `A` which has `order=True`
+ tests/test_functional.py:69:11: warning[subclass-of-dataclass-with-order] Class `Sub` inherits from dataclass `Base` which has `order=True`
+ tests/test_functional.py:74:16: warning[subclass-of-dataclass-with-order] Class `SubSlots` inherits from dataclass `BaseSlots` which has `order=True`
+ tests/test_functional.py:84:17: warning[subclass-of-dataclass-with-order] Class `SubFrozen` inherits from dataclass `Frozen` which has `order=True`
+ tests/test_functional.py:365:28: warning[subclass-of-dataclass-with-order] Class `SubOverwrite` inherits from dataclass `Base` which has `order=True`
+ tests/test_functional.py:423:17: warning[subclass-of-dataclass-with-order] Class `D` inherits from dataclass `C` which has `order=True`
+ tests/test_functional.py:472:17: warning[subclass-of-dataclass-with-order] Class `D` inherits from dataclass `C` which has `order=True`
+ tests/test_functional.py:478:17: warning[subclass-of-dataclass-with-order] Class `E` inherits from dataclass `D` which has `order=True`
+ tests/test_functional.py:519:19: warning[subclass-of-dataclass-with-order] Class `Sub` inherits from dataclass `Base` which has `order=True`
+ tests/test_functional.py:663:17: warning[subclass-of-dataclass-with-order] Class `D` inherits from dataclass `C` which has `order=True`
+ tests/test_functional.py:684:17: warning[subclass-of-dataclass-with-order] Class `D` inherits from dataclass `C` which has `order=True`
+ tests/test_functional.py:733:17: warning[subclass-of-dataclass-with-order] Class `D` inherits from dataclass `C` which has `order=True`
+ tests/test_functional.py:767:26: warning[subclass-of-dataclass-with-order] Class `ToRegister` inherits from dataclass `Base` which has `order=True`
+ tests/test_hooks.py:199:19: warning[subclass-of-dataclass-with-order] Class `Sub` inherits from dataclass `Base` which has `order=True`
+ tests/test_init_subclass.py:22:19: warning[subclass-of-dataclass-with-order] Class `Vanilla` inherits from dataclass `Base` which has `order=True`
+ tests/test_init_subclass.py:40:17: warning[subclass-of-dataclass-with-order] Class `Attrs` inherits from dataclass `Base` which has `order=True`
+ tests/test_make.py:264:17: warning[subclass-of-dataclass-with-order] Class `C` inherits from dataclass `B` which has `order=True`
+ tests/test_make.py:356:17: warning[subclass-of-dataclass-with-order] Class `B` inherits from dataclass `A` which has `order=True`
+ tests/test_make.py:361:17: warning[subclass-of-dataclass-with-order] Class `C` inherits from dataclass `B` which has `order=True`
+ tests/test_make.py:361:20: warning[subclass-of-dataclass-with-order] Class `C` inherits from dataclass `A` which has `order=True`
+ tests/test_make.py:366:17: warning[subclass-of-dataclass-with-order] Class `D` inherits from dataclass `A` which has `order=True`
+ tests/test_make.py:371:17: warning[subclass-of-dataclass-with-order] Class `E` inherits from dataclass `C` which has `order=True`
+ tests/test_make.py:371:20: warning[subclass-of-dataclass-with-order] Class `E` inherits from dataclass `D` which has `order=True`
+ tests/test_make.py:390:17: warning[subclass-of-dataclass-with-order] Class `D` inherits from dataclass `C` which has `order=True`
+ tests/test_make.py:408:17: warning[subclass-of-dataclass-with-order] Class `B` inherits from dataclass `A` which has `order=True`
+ tests/test_make.py:413:17: warning[subclass-of-dataclass-with-order] Class `C` inherits from dataclass `B` which has `order=True`
+ tests/test_make.py:413:20: warning[subclass-of-dataclass-with-order] Class `C` inherits from dataclass `A` which has `order=True`
+ tests/test_make.py:418:17: warning[subclass-of-dataclass-with-order] Class `D` inherits from dataclass `A` which has `order=True`
+ tests/test_make.py:423:17: warning[subclass-of-dataclass-with-order] Class `E` inherits from dataclass `C` which has `order=True`
+ tests/test_make.py:423:20: warning[subclass-of-dataclass-with-order] Class `E` inherits from dataclass `D` which has `order=True`
+ tests/test_make.py:447:17: warning[subclass-of-dataclass-with-order] Class `B` inherits from dataclass `A` which has `order=True`
+ tests/test_make.py:451:17: warning[subclass-of-dataclass-with-order] Class `C` inherits from dataclass `A` which has `order=True`
+ tests/test_make.py:458:17: warning[subclass-of-dataclass-with-order] Class `D` inherits from dataclass `B` which has `order=True`
+ tests/test_make.py:458:20: warning[subclass-of-dataclass-with-order] Class `D` inherits from dataclass `C` which has `order=True`
+ tests/test_make.py:475:17: warning[subclass-of-dataclass-with-order] Class `B` inherits from dataclass `A` which has `order=True`
+ tests/test_make.py:479:17: warning[subclass-of-dataclass-with-order] Class `C` inherits from dataclass `B` which has `order=True`
+ tests/test_make.py:964:24: warning[subclass-of-dataclass-with-order] Class `SubClass` inherits from dataclass `BaseClass` which has `order=True`
+ tests/test_make.py:1086:17: warning[subclass-of-dataclass-with-order] Class `C` inherits from dataclass `Base` which has `order=True`
+ tests/test_make.py:1124:17: warning[subclass-of-dataclass-with-order] Class `C` inherits from dataclass `Base` which has `order=True`
+ tests/test_make.py:1138:17: warning[subclass-of-dataclass-with-order] Class `C` inherits from dataclass `Base` which has `order=True`
+ tests/test_make.py:1186:41: warning[subclass-of-dataclass-with-order] Class `KwArgBeforeInitFalseChild` inherits from dataclass `KwArgBeforeInitFalseParent` which has `order=True`
+ tests/test_make.py:2201:18: warning[subclass-of-dataclass-with-order] Class `C2` inherits from dataclass `C` which has `order=True`
+ tests/test_make.py:2220:18: warning[subclass-of-dataclass-with-order] Class `C2` inherits from dataclass `C` which has `order=True`
+ tests/test_make.py:2410:17: warning[subclass-of-dataclass-with-order] Class `B` inherits from dataclass `A` which has `order=True`
+ tests/test_setattr.py:240:22: warning[subclass-of-dataclass-with-order] Class `NoHook` inherits from dataclass `WithOnSetAttrHook` which has `order=True`
+ tests/test_setattr.py:271:17: warning[subclass-of-dataclass-with-order] Class `C` inherits from dataclass `B` which has `order=True`
+ tests/test_setattr.py:323:17: warning[subclass-of-dataclass-with-order] Class `B` inherits from dataclass `A` which has `order=True`
+ tests/test_setattr.py:340:39: warning[subclass-of-dataclass-with-order] Class `RemoveNeedForOurSetAttr` inherits from dataclass `WithOnSetAttrHook` which has `order=True`
+ tests/test_setattr.py:373:33: warning[subclass-of-dataclass-with-order] Class `CustomSetAttr` inherits from dataclass `Frozen` which has `order=True`
+ tests/test_setattr.py:413:17: warning[subclass-of-dataclass-with-order] Class `B` inherits from dataclass `A` which has `order=True`
+ tests/test_setattr.py:420:17: warning[subclass-of-dataclass-with-order] Class `C` inherits from dataclass `B` which has `order=True`
+ tests/test_slots.py:152:19: warning[subclass-of-dataclass-with-order] Class `C2Slots` inherits from dataclass `C1` which has `order=True`
+ tests/test_slots.py:248:19: warning[subclass-of-dataclass-with-order] Class `C2Slots` inherits from dataclass `C1Slots` which has `order=True`
+ tests/test_slots.py:252:14: warning[subclass-of-dataclass-with-order] Class `C2` inherits from dataclass `C1` which has `order=True`
+ tests/test_slots.py:294:19: warning[subclass-of-dataclass-with-order] Class `C2Slots` inherits from dataclass `C1Slots` which has `order=True`
+ tests/test_slots.py:454:18: warning[subclass-of-dataclass-with-order] Class `C2` inherits from dataclass `C1` which has `order=True`
+ tests/test_slots.py:459:23: warning[subclass-of-dataclass-with-order] Class `C2Slots` inherits from dataclass `C1Slots` which has `order=True`
+ tests/test_slots.py:573:13: warning[subclass-of-dataclass-with-order] Class `D` inherits from dataclass `C` which has `order=True`
+ tests/test_slots.py:699:13: warning[subclass-of-dataclass-with-order] Class `B` inherits from dataclass `A` which has `order=True`
+ tests/test_slots.py:705:13: warning[subclass-of-dataclass-with-order] Class `C` inherits from dataclass `A` which has `order=True`
+ tests/test_slots.py:730:13: warning[subclass-of-dataclass-with-order] Class `B` inherits from dataclass `A` which has `order=True`
+ tests/test_slots.py:901:13: warning[subclass-of-dataclass-with-order] Class `B` inherits from dataclass `A` which has `order=True`
+ tests/test_slots.py:928:13: warning[subclass-of-dataclass-with-order] Class `B` inherits from dataclass `A` which has `order=True`
+ tests/test_slots.py:955:13: warning[subclass-of-dataclass-with-order] Class `B` inherits from dataclass `A` which has `order=True`
+ tests/test_slots.py:989:14: warning[subclass-of-dataclass-with-order] Class `AB` inherits from dataclass `A` which has `order=True`
+ tests/test_slots.py:989:17: warning[subclass-of-dataclass-with-order] Class `AB` inherits from dataclass `B` which has `order=True`
+ tests/test_slots.py:1009:13: warning[subclass-of-dataclass-with-order] Class `B` inherits from dataclass `A` which has `order=True`
+ tests/test_slots.py:1031:13: warning[subclass-of-dataclass-with-order] Class `B` inherits from dataclass `A` which has `order=True`
+ tests/test_slots.py:1054:13: warning[subclass-of-dataclass-with-order] Class `B` inherits from dataclass `A` which has `order=True`
+ typing-examples/mypy.py:83:10: warning[subclass-of-dataclass-with-order] Class `GG` inherits from dataclass `DD` which has `order=True`
+ typing-examples/mypy.py:91:10: warning[subclass-of-dataclass-with-order] Class `HH` inherits from dataclass `DD` which has `order=True`
+ typing-examples/mypy.py:91:14: warning[subclass-of-dataclass-with-order] Class `HH` inherits from dataclass `EE` which has `order=True`
- Found 606 diagnostics
+ Found 679 diagnostics

Expression (https://github.com/cognitedata/Expression)
+ expression/core/try_.py:17:11: warning[subclass-of-dataclass-with-order] Class `Try` inherits from dataclass `Result` which has `order=True`
- Found 211 diagnostics
+ Found 212 diagnostics

strawberry (https://github.com/strawberry-graphql/strawberry)
+ strawberry/relay/types.py:745:22: warning[subclass-of-dataclass-with-order] Class `ListConnection` inherits from dataclass `Connection` which has `order=True`
- Found 400 diagnostics
+ Found 401 diagnostics

materialize (https://github.com/MaterializeInc/materialize)
+ misc/python/materialize/output_consistency/ignore_filter/ignore_verdict.py:18:17: warning[subclass-of-dataclass-with-order] Class `YesIgnore` inherits from dataclass `IgnoreVerdict` which has `order=True`
+ misc/python/materialize/output_consistency/ignore_filter/ignore_verdict.py:27:16: warning[subclass-of-dataclass-with-order] Class `NoIgnore` inherits from dataclass `IgnoreVerdict` which has `order=True`
- Found 3371 diagnostics
+ Found 3373 diagnostics

pydantic (https://github.com/pydantic/pydantic)
- pydantic/fields.py:943:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
+ pydantic/fields.py:943:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
- pydantic/fields.py:983:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
+ pydantic/fields.py:983:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
- pydantic/fields.py:1026:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
+ pydantic/fields.py:1026:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
- pydantic/fields.py:1066:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
+ pydantic/fields.py:1066:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
- pydantic/fields.py:1109:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
+ pydantic/fields.py:1109:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
- pydantic/fields.py:1148:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
+ pydantic/fields.py:1148:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
- pydantic/fields.py:1188:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
+ pydantic/fields.py:1188:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
- pydantic/fields.py:1567:13: error[invalid-argument-type] Argument is incorrect: Expected `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`, found `Top[dict[Unknown, Unknown]] | (((dict[str, Divergent], /) -> None) & ~Top[dict[Unknown, Unknown]]) | None`
+ pydantic/fields.py:1567:13: error[invalid-argument-type] Argument is incorrect: Expected `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`, found `Top[dict[Unknown, Unknown]] | (((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) & ~Top[dict[Unknown, Unknown]]) | None`

No memory usage changes detected ✅

@charliermarsh
Copy link
Member Author

I believe those changes on the conformance tests are "correct" in-so-far as those are instances of the case we want to catch here.

@astral-sh-bot
Copy link

astral-sh-bot bot commented Nov 30, 2025

ecosystem-analyzer results

Lint rule Added Removed Changed
subclass-of-dataclass-with-order 77 0 0
invalid-argument-type 0 2 0
unsupported-base 0 2 0
Total 77 4 0

Full report with detailed diff (timing results)

Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Comment on lines +353 to +354
classes in the inheritance hierarchy will raise a `TypeError` at runtime. This violates the Liskov
Substitution Principle:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
classes in the inheritance hierarchy will raise a `TypeError` at runtime. This violates the Liskov
Substitution Principle:
classes in the inheritance hierarchy will raise a `TypeError` at runtime. The design of the stdlib
feature therefore violates the Liskov Substitution Principle:

Comment on lines -481 to 487

# TODO: specifying `order=True` on the subclass means that a `__lt__` method is
# generated that is incompatible with the generated `__lt__` method on the superclass.
# We could consider detecting this and emitting a diagnostic, though maybe it shouldn't
# be `invalid-method-override` since we'd emit it on the class definition rather than
# on any method definition. Note also that no other type checker complains about this
# as of 2025-11-21.
# Specifying `order=True` on the subclass means that a `__lt__` method is generated that
# is incompatible with the generated `__lt__` method on the superclass. We emit a diagnostic
# on the class definition because the design of `order=True` dataclasses themselves violates
# the Liskov Substitution Principle.
@dataclass(order=True)
class Bar2(Foo):
class Bar2(Foo): # error: [subclass-of-dataclass-with-order]
y: str
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should suppress the subclass-of-dataclass-with-order diagnostic if all the comparison methods are overridden on the subclass? (That's the case here, since Bar2 is also decorated with order=True.) This case here does feel like it should be flagged using the error code we use for Liskov Subsitution Principle violations, because by specifying order=True the user has in a sense explicitly asked for an incompatible override.

And something like the following also seems safe, if the user has taken care to override all the unsound methods from the superclass:

from dataclasses import dataclass

@dataclass(order=True)
class A:
    int

class B(A):
    def __lt__(self, other: A) -> bool:
        return True

    def __le__(self, other: A) -> bool:
        return True

    __gt__ = __lt__
    __ge__ = __le__

Comment on lines +749 to +762
if let Some(base_params) = base_class_literal.dataclass_params(self.db()) {
if base_params.flags(self.db()).contains(DataclassFlags::ORDER) {
if let Some(builder) = self
.context
.report_lint(&SUBCLASS_OF_DATACLASS_WITH_ORDER, &class_node.bases()[i])
{
builder.into_diagnostic(format_args!(
"Class `{}` inherits from dataclass `{}` which has `order=True`",
class.name(self.db()),
base_class.name(self.db()),
));
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add an info subdiagnostic here explaining why this is problematic?

/// class Parent:
/// value: int
///
/// class Child(Parent): # Error raised here
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// class Child(Parent): # Error raised here
/// class Child(Parent): # Ty emits an error here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...ty

Comment on lines +1676 to +1678
/// Consider using `functools.total_ordering` instead, which does not have this limitation.
///
/// [Liskov Substitution Principle]: https://en.wikipedia.org/wiki/Liskov_substitution_principle
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Consider using `functools.total_ordering` instead, which does not have this limitation.
///
/// [Liskov Substitution Principle]: https://en.wikipedia.org/wiki/Liskov_substitution_principle
/// Consider using [`functools.total_ordering`][total_ordering] instead, which does not have this limitation.
///
/// [Liskov Substitution Principle]: https://en.wikipedia.org/wiki/Liskov_substitution_principle
/// [total_ordering]: https://docs.python.org/3/library/functools.html#functools.total_ordering

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ecosystem-analyzer ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add diagnostic warning about unsound subclassing of order=True dataclasses

4 participants