Skip to content

Fix redundant cast detection for generic calls - #21801

Closed
Yoruxyv wants to merge 3 commits into
python:masterfrom
Yoruxyv:fix/redundant-cast-generics
Closed

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

Conversation

@Yoruxyv

@Yoruxyv Yoruxyv 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?

@Yoruxyv

Yoruxyv commented Aug 3, 2026

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.

@Yoruxyv

Yoruxyv commented Aug 3, 2026

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

This comment has been minimized.

@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.

@Yoruxyv

Yoruxyv commented Aug 3, 2026

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.

@Yoruxyv
Yoruxyv force-pushed the fix/redundant-cast-generics branch from e138c14 to ed8dc4e Compare September 20, 2026 05:19
@github-actions

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]

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]

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]

pandas-stubs (https://github.com/pandas-dev/pandas-stubs)
+ tests/series/test_add.py:49: error: Unused "type: ignore" comment  [unused-ignore]
+ tests/indexes/test_indexes.py:1118: 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:2854: error: Redundant cast to "tuple[Transport, StreamProtocol]"  [redundant-cast]

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

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

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

@Yoruxyv

Yoruxyv commented Sep 20, 2026

Copy link
Copy Markdown
Author

Hi @A5rocks - sorry for the ping. It’s been a little while since my last update, and I wanted to follow up when you have a chance.

I’ve rebased the PR onto the current master and revalidated it successfully. The fresh mypy_primer run and full CI are both green.

In my previous comment I tested passing the incoming context directly to the original source_type check and found that it changes normal cast-operand inference in existing cases, so I kept the current isolated inference approach for the redundant-cast check.

I agree the additional accept() isn’t ideal, though. Would you mind taking another look and letting me know whether you’d prefer this approach, or whether you think the fix should be handled somewhere deeper in inference instead?

Thanks again for your time and the earlier feedback.

@Yoruxyv

Yoruxyv commented Oct 25, 2026

Copy link
Copy Markdown
Author

Hi, I noticed this PR was closed. Could you share the reason? I'd really appreciate understanding what could have been improved. Thanks!

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

3 participants