Skip to content

Improve annotations for various set methods #11403

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

AlexWaygood
Copy link
Member

It's a bit unfortunate how many type: ignores we have to introduce here, but I can't see a good way round them. I've added some test cases to make sure everything's working correctly.

This comment has been minimized.

@AlexWaygood
Copy link
Member Author

AlexWaygood commented Feb 11, 2024

Primer analysis:

@AlexWaygood AlexWaygood marked this pull request as ready for review February 11, 2024 16:59

This comment has been minimized.

@srittau
Copy link
Collaborator

srittau commented Mar 23, 2024

This has conflicts now.

@AlexWaygood
Copy link
Member Author

This has conflicts now.

I fixed the merge conflicts, but it's still not clear to me whether we want to fix the inconsistency between the different methods by making some annotations stricter (as this PR does), which seems to lead to false positives when running type checkers on user code. I think a better solution might be to make the methods consistent by making some annotations looser instead -- what do you think?

This comment has been minimized.

def discard(self, element: _T, /) -> None: ...
def difference(self, *s: Iterable[_T | None]) -> set[_T]: ...
def difference_update(self, *s: Iterable[_T | None]) -> None: ...
def discard(self, element: _T | None, /) -> None: ...
Copy link

@Bruntaz Bruntaz Feb 17, 2025

Choose a reason for hiding this comment

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

I believe the correct signature for this should be def discard(self, element: Hashable, /) -> None: ....

set.discard will only raise an exception if an unhashable type is passed into it. Any other input is valid (just if it doesn't match _T, it will do nothing instead of raising like set.remove():

>>> a = {1, 2, 3}
>>> a.discard(1)
>>> a
{2, 3}
>>> a.discard("Hello")
>>> a
{2, 3}
>>> a.discard([])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

Copy link
Contributor

Diff from mypy_primer, showing the effect of this PR on open source code:

pydantic (https://github.com/pydantic/pydantic)
+ pydantic/v1/main.py:901: error: Set comprehension has incompatible type Set[int | str]; expected Set[None]  [misc]
+ pydantic/main.py:1474: error: Argument 1 to "set" has incompatible type "AbstractSet[int] | AbstractSet[str] | Mapping[int, Any] | Mapping[str, Any]"; expected "Iterable[str | None]"  [arg-type]
+ pydantic/deprecated/copy_internals.py:222: error: Set comprehension has incompatible type Set[int | str]; expected Set[str | None]  [misc]

ibis (https://github.com/ibis-project/ibis)
- ibis/backends/sql/datatypes.py:1263: error: Unsupported operand types for - ("set[type[Never]]" and "set[type[SqlglotType]]")  [operator]
+ ibis/backends/sql/datatypes.py:1263: error: Argument 1 to <set> has incompatible type "type[SqlglotType]"; expected "None"  [arg-type]
+ ibis/backends/sql/datatypes.py:1263: error: Argument 2 to <set> has incompatible type "type[BigQueryUDFType]"; expected "None"  [arg-type]
- ibis/expr/datatypes/tests/test_core.py:548: error: Unsupported operand types for - ("set[type[Never]]" and "set[AnnotableMeta]")  [operator]
+ ibis/expr/datatypes/tests/test_core.py:550: error: Argument 1 to <set> has incompatible type "type[Array[T]]"; expected "None"  [arg-type]
+ ibis/expr/datatypes/tests/test_core.py:551: error: Argument 2 to <set> has incompatible type "type[String]"; expected "None"  [arg-type]
+ ibis/expr/datatypes/tests/test_core.py:552: error: Argument 3 to <set> has incompatible type "type[Floating]"; expected "None"  [arg-type]
+ ibis/expr/datatypes/tests/test_core.py:553: error: Argument 4 to <set> has incompatible type "type[GeoSpatial]"; expected "None"  [arg-type]
+ ibis/expr/datatypes/tests/test_core.py:554: error: Argument 5 to <set> has incompatible type "type[Integer]"; expected "None"  [arg-type]
+ ibis/expr/datatypes/tests/test_core.py:555: error: Argument 6 to <set> has incompatible type "type[Map[K, V]]"; expected "None"  [arg-type]
+ ibis/expr/datatypes/tests/test_core.py:556: error: Argument 7 to <set> has incompatible type "type[Numeric]"; expected "None"  [arg-type]
+ ibis/expr/datatypes/tests/test_core.py:557: error: Argument 8 to <set> has incompatible type "type[Primitive]"; expected "None"  [arg-type]
+ ibis/expr/datatypes/tests/test_core.py:558: error: Argument 9 to <set> has incompatible type "type[SignedInteger]"; expected "None"  [arg-type]
+ ibis/expr/datatypes/tests/test_core.py:559: error: Argument 10 to <set> has incompatible type "type[Struct]"; expected "None"  [arg-type]
+ ibis/expr/datatypes/tests/test_core.py:560: error: Argument 11 to <set> has incompatible type "type[Temporal]"; expected "None"  [arg-type]
+ ibis/expr/datatypes/tests/test_core.py:561: error: Argument 12 to <set> has incompatible type "type[UnsignedInteger]"; expected "None"  [arg-type]
+ ibis/expr/datatypes/tests/test_core.py:562: error: Argument 13 to <set> has incompatible type "type[Variadic]"; expected "None"  [arg-type]
+ ibis/expr/datatypes/tests/test_core.py:563: error: Argument 14 to <set> has incompatible type "type[Parametric]"; expected "None"  [arg-type]
+ ibis/expr/datatypes/tests/test_core.py:564: error: Argument 15 to <set> has incompatible type "type[Interval]"; expected "None"  [arg-type]

werkzeug (https://github.com/pallets/werkzeug)
+ src/werkzeug/datastructures/structures.py:1121: error: Argument 1 of "discard" is incompatible with supertype "MutableSet"; supertype defines the argument type as "Optional[str]"  [override]
+ src/werkzeug/datastructures/structures.py:1121: note: This violates the Liskov substitution principle
+ src/werkzeug/datastructures/structures.py:1121: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides

xarray (https://github.com/pydata/xarray)
+ xarray/core/utils.py: note: In member "discard" of class "OrderedSet":
+ xarray/core/utils.py:574: error: Argument 1 of "discard" is incompatible with supertype "MutableSet"; supertype defines the argument type as "T | None"  [override]
+ xarray/core/utils.py:574: note: This violates the Liskov substitution principle
+ xarray/core/utils.py:574: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides

jax (https://github.com/google/jax)
+ jax/_src/sharding_impls.py:107: error: Need type annotation for "remaining_axes" (hint: "remaining_axes: set[<type>] = ...")  [var-annotated]

graphql-core (https://github.com/graphql-python/graphql-core)
+ src/graphql/pyutils/ref_set.py:58: error: Argument 1 of "discard" is incompatible with supertype "MutableSet"; supertype defines the argument type as "T | None"  [override]
+ src/graphql/pyutils/ref_set.py:58: note: This violates the Liskov substitution principle
+ src/graphql/pyutils/ref_set.py:58: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides

schemathesis (https://github.com/schemathesis/schemathesis)
- src/schemathesis/cli/commands/run/hypothesis.py:35: error: Argument 1 to "set" has incompatible type "list[schemathesis.cli.commands.run.hypothesis.Phase]"; expected "Iterable[Optional[hypothesis._settings.Phase]]"  [arg-type]
+ src/schemathesis/cli/commands/run/hypothesis.py:35: error: Argument 1 to "set" has incompatible type "list[Phase]"; expected "Iterable[None]"  [arg-type]

django-stubs (https://github.com/typeddjango/django-stubs)
+ django-stubs/utils/datastructures.pyi:56: error: Argument 1 of "discard" is incompatible with supertype "MutableSet"; supertype defines the argument type as "Optional[_K]"  [override]
+ django-stubs/utils/datastructures.pyi:56: note: This violates the Liskov substitution principle
+ django-stubs/utils/datastructures.pyi:56: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides

core (https://github.com/home-assistant/core)
+ homeassistant/core_config.py:530: error: Argument 1 of "discard" is incompatible with supertype "set"; supertype defines the argument type as "str | None"  [override]
+ homeassistant/core_config.py:530: note: This violates the Liskov substitution principle
+ homeassistant/core_config.py:530: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
+ homeassistant/core_config.py:530: error: Argument 1 of "discard" is incompatible with supertype "MutableSet"; supertype defines the argument type as "str | None"  [override]

@srittau
Copy link
Collaborator

srittau commented Feb 17, 2025

While I'm not terribly worried about the new LSP warnings, this PR doesn't seem to fix any actual problems, only introduce new ones – at least not according to primer. I'm -0 on this solution (at least for now).

@ferdnyc
Copy link

ferdnyc commented Mar 13, 2025

The number of primer changes/additions where the expected type has demonstrably worsened is concerning.

schemathesis

Take the 'schemathesis' diff:

Before

error: Argument 1 to "set" has incompatible type "list[schemathesis.cli.commands.run.hypothesis.Phase]"; expected "Iterable[Optional[hypothesis._settings.Phase]]" [arg-type]

After

error: Argument 1 to "set" has incompatible type "list[Phase]"; expected "Iterable[None]" [arg-type]

Something's just plain wrong, if the expected type is Iterable[None].

pydantic

Same thing in the first new 'pydantic' message:

error: Set comprehension has incompatible type Set[int | str]; expected Set[None] [misc]

Not sure what kind of set comprehension is expected to contain only None values, but that feels less-than-useful in a type-checking context, and presumably has little-to-no relevance to the runtime type situation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants