From 43eddc566faffd50da7b1e9b71a2c63f02ab31d8 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 14 Oct 2025 19:31:34 +0100 Subject: [PATCH] [ty] Improve and extend tests for instance attributes redeclared in subclasses (#20866) Part of https://github.com/astral-sh/ty/issues/1345 --- .../resources/mdtest/attributes.md | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/attributes.md b/crates/ty_python_semantic/resources/mdtest/attributes.md index bff72953e2..ef36bea8d0 100644 --- a/crates/ty_python_semantic/resources/mdtest/attributes.md +++ b/crates/ty_python_semantic/resources/mdtest/attributes.md @@ -828,6 +828,10 @@ class Base: redeclared_with_narrower_type: str | None redeclared_with_wider_type: str | None + redeclared_in_method_with_same_type: str | None + redeclared_in_method_with_narrower_type: str | None + redeclared_in_method_with_wider_type: str | None + overwritten_in_subclass_body: str overwritten_in_subclass_method: str @@ -873,6 +877,14 @@ class Intermediate(Base): undeclared = "intermediate" def set_attributes(self) -> None: + self.redeclared_in_method_with_same_type: str | None = None + + # TODO: This should be an error (violates Liskov) + self.redeclared_in_method_with_narrower_type: str = "foo" + + # TODO: This should be an error (violates Liskov) + self.redeclared_in_method_with_wider_type: object = object() + # TODO: This should be an `invalid-assignment` error self.overwritten_in_subclass_method = None @@ -889,11 +901,13 @@ reveal_type(Derived().attribute) # revealed: int | None reveal_type(Derived.redeclared_with_same_type) # revealed: str | None reveal_type(Derived().redeclared_with_same_type) # revealed: str | None -# TODO: It would probably be more consistent if these were `str | None` +# We infer the narrower type here, despite the Liskov violation, +# for compatibility with other type checkers (and to reflect the clear user intent) reveal_type(Derived.redeclared_with_narrower_type) # revealed: str reveal_type(Derived().redeclared_with_narrower_type) # revealed: str -# TODO: It would probably be more consistent if these were `str | None` +# We infer the wider type here, despite the Liskov violation, +# for compatibility with other type checkers (and to reflect the clear user intent) reveal_type(Derived.redeclared_with_wider_type) # revealed: str | int | None reveal_type(Derived().redeclared_with_wider_type) # revealed: str | int | None @@ -901,6 +915,19 @@ reveal_type(Derived().redeclared_with_wider_type) # revealed: str | int | None reveal_type(Derived.overwritten_in_subclass_body) # revealed: Unknown | None reveal_type(Derived().overwritten_in_subclass_body) # revealed: Unknown | None | str +reveal_type(Derived.redeclared_in_method_with_same_type) # revealed: str | None +reveal_type(Derived().redeclared_in_method_with_same_type) # revealed: str | None + +# TODO: both of these should be `str`, despite the Liskov violation, +# for compatibility with other type checkers (and to reflect the clear user intent) +reveal_type(Derived.redeclared_in_method_with_narrower_type) # revealed: str | None +reveal_type(Derived().redeclared_in_method_with_narrower_type) # revealed: str | None + +# TODO: both of these should be `object`, despite the Liskov violation, +# for compatibility with other type checkers (and to reflect the clear user intent) +reveal_type(Derived.redeclared_in_method_with_wider_type) # revealed: str | None +reveal_type(Derived().redeclared_in_method_with_wider_type) # revealed: object + reveal_type(Derived.overwritten_in_subclass_method) # revealed: str reveal_type(Derived().overwritten_in_subclass_method) # revealed: str