Skip to content

Fix redundant cast detection for generic calls - #21801

Open
Dendroculus wants to merge 3 commits into
python:masterfrom
Dendroculus:fix/redundant-cast-generics
Open

Fix redundant cast detection for generic calls#21801
Dendroculus wants to merge 3 commits into
python:masterfrom
Dendroculus:fix/redundant-cast-generics

Conversation

@Dendroculus

@Dendroculus Dendroculus commented Aug 2, 2026

Copy link
Copy Markdown

Fixes #21796

visit_cast_expr() currently checks the source expression with an Any type context. For generic calls, that context participates in type argument inference, so a call such as identity(xs) is inferred as list[Any] inside cast() even though it is independently inferred as list[int].

This prevents the redundant-cast check from recognizing that the source type and target type are the same.

Check the cast source expression without a type context so its type is inferred independently before comparing it with the cast target.

Also add a regression test covering redundant casts around generic return types.

Tests:

  • python runtests.py testRedundantCastGenericReturn
  • python runtests.py check-warnings.test
  • python runtests.py self
  • python runtests.py lint

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@A5rocks

A5rocks commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Sorry but this seems wrong. Why are we discarding type context?

@Dendroculus

Copy link
Copy Markdown
Author

Good point. I reproduced the original issue by inspecting the types: with the existing Any context, the source of cast(list[int], identity(xs)) is inferred as list[Any], while identity(xs) on its own is inferred as list[int].

My intention with the second context-free pass was only to avoid that Any-driven inference for the redundant-cast comparison; the normal cast check still uses the existing context.

But I see the concern: type_context=None does not necessarily model removing the cast, since the expression may still receive context from its surrounding expression. I'll investigate whether the fix should instead preserve that surrounding context or happen in generic inference itself.

@Dendroculus

Copy link
Copy Markdown
Author

Follow-up: I changed the speculative redundant-cast inference so it no longer discards the incoming type context.

The normal cast source check is unchanged and still uses the existing Any context. For the additional inference used only by --warn-redundant-casts, I now preserve the context received by the cast expression:

cast_context = self.type_context[-1]

inferred_source_type = self.accept(
    expr.expr,
    type_context=cast_context,
    allow_none_return=True,
    always_allow_any=True,
)

So instead of forcing type_context=None, the operand is checked using the same contextual type that the cast expression itself received.

I also updated the regression test to match the original issue more closely:

def f(xs: list[int]) -> list[int]:
    return cast(list[int], identity(xs))

Verification after the change:

  • testRedundantCastGenericReturn: passed
  • testCastToSuperclassNotRedundant: passed
  • check-warnings.test: 23 passed
  • Original --warn-redundant-cast doesn’t warn for simple generic types #21796 reproducer: identity(xs) is revealed as list[int] and the cast is reported as redundant
  • OpenLibrary reproducer: no issues
  • Kopf reproducer: only the expected Redundant cast to "Iterator[Body]", without the previous [arg-type] regressions
  • python runtests.py self: no issues in 341 source files
  • python runtests.py lint: all checks passed
  • git diff --check: clean

So the fix now preserves the surrounding type context while still avoiding the Any-driven generic inference that caused #21796.

Would you mind having another look when you have a chance? Sorry for the confusion with the earlier approach, and thanks for pointing this out.

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

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

steam.py (https://github.com/Gobot1234/steam.py)
+ steam/manifest.py:1200: error: Redundant cast to "list[DepotID]"  [redundant-cast]
+ steam/state.py:728: error: Redundant cast to "list[int]"  [redundant-cast]

openlibrary (https://github.com/internetarchive/openlibrary)
+ openlibrary/solr/updater/work.py: note: In member "update_key" of class "WorkSolrUpdater":
+ openlibrary/solr/updater/work.py:115: error: Redundant cast to "WorkSeriesEdge[SeriesDict]"  [redundant-cast]

prefect (https://github.com/PrefectHQ/prefect)
+ src/prefect/__init__.py:46: error: Redundant cast to "VersionInfo"  [redundant-cast]
+ src/prefect/input/run_input.py:527: error: Redundant cast to "type[AutomaticRunInput[T]]"  [redundant-cast]

scrapy (https://github.com/scrapy/scrapy)
- scrapy/extensions/feedexport.py:563: error: Redundant cast to "list[Any]"  [redundant-cast]
+ scrapy/extensions/feedexport.py:559: error: Redundant cast to "list[Task[None]]"  [redundant-cast]

pandas-stubs (https://github.com/pandas-dev/pandas-stubs)
+ tests/series/test_add.py:45: error: Unused "type: ignore" comment  [unused-ignore]
+ tests/indexes/test_indexes.py:1071: error: Redundant cast to "list[Index[Any]]"  [redundant-cast]

cwltool (https://github.com/common-workflow-language/cwltool)
+ cwltool/builder.py: note: In member "generate_arg" of class "Builder":
+ cwltool/builder.py:636:28: error: Redundant cast to "list[str]"  [redundant-cast]
+ cwltool/cwlprov/ro.py: note: In member "add_data_file" of class "ResearchObject":
+ cwltool/cwlprov/ro.py:572:47: error: Redundant cast to "Aggregate"  [redundant-cast]

rotki (https://github.com/rotki/rotki)
+ rotkehlchen/accounting/history_base_entries.py:176: error: Redundant cast to "HistoryBaseEntry[Any]"  [redundant-cast]
+ rotkehlchen/accounting/history_base_entries.py:186: error: Redundant cast to "HistoryBaseEntry[Any]"  [redundant-cast]

anyio (https://github.com/agronholm/anyio)
+ src/anyio/_backends/_asyncio.py:2782: error: Redundant cast to "tuple[Transport, StreamProtocol]"  [redundant-cast]

meson (https://github.com/mesonbuild/meson)
+ mesonbuild/utils/universal.py:1894:16: error: Redundant cast to "list[_T]"  [redundant-cast]
+ mesonbuild/build.py:1457:33: error: Redundant cast to "list[str | None]"  [redundant-cast]

paasta (https://github.com/yelp/paasta)
+ paasta_tools/utils.py:4220: error: Redundant cast to "list[str]"  [redundant-cast]
+ paasta_tools/spark_tools.py:120: error: Redundant cast to "list[DockerVolume]"  [redundant-cast]

mongo-python-driver (https://github.com/mongodb/mongo-python-driver)
+ pymongo/synchronous/encryption.py:149: error: Redundant cast to "Collection[RawBSONDocument]"  [redundant-cast]
+ pymongo/asynchronous/encryption.py:150: error: Redundant cast to "AsyncCollection[RawBSONDocument]"  [redundant-cast]

@A5rocks

A5rocks commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

I still don't think this is right, since it feels like this duplicates code. Why not simply pass through type context for the source_type to begin with? There must have been a bug here, but I don't see why.

@Dendroculus

Copy link
Copy Markdown
Author

I tried passing the incoming context directly to the original source_type check.

The focused redundant-cast tests still pass:

  • testRedundantCastGenericReturn: passed
  • testCastToSuperclassNotRedundant: passed
  • check-warnings.test: 23 passed

However, python runtests.py self exposes why the existing cast source check can't simply inherit the outer context.

In mypyc/test/test_statement.py, this existing cast:

cast(Graph, {name: object() for name in (graph or set())})

then produces:

mypyc/test/test_statement.py:46: error: Redundant cast to "dict[str, State]"  [redundant-cast]
mypyc/test/test_statement.py:46: error: Value expression in dictionary comprehension has incompatible type "object"; expected type "State"  [misc]

The outer Graph context gets propagated into the dictionary comprehension, so the operand is checked as though it were expected to satisfy Graph. That changes the normal semantics of the cast source expression.

So it looks like the existing Any context is acting as a context barrier for the cast operand, which explains why passing the outer context through directly isn't equivalent.

I agree that doing a second accept() is not ideal, though. Given this constraint, I'm happy to explore a cleaner way to compute the type used for the redundant-cast check.

Also, thanks for taking the time to reply and point me in the right direction. I really appreciate the review and the context here.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

--warn-redundant-cast doesn’t warn for simple generic types

2 participants