Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[ty] Simplify intersections of invariant generic types with Any specializations#26127

Merged
sharkdp merged 1 commit into
mainfrom
david/invariant-any
Jun 23, 2026
Merged

[ty] Simplify intersections of invariant generic types with Any specializations#26127
sharkdp merged 1 commit into
mainfrom
david/invariant-any

Conversation

@sharkdp

@sharkdp sharkdp commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Implement an intersection simplification for invariant generics, namely

Invariant[G] & Invariant[Any] = Invariant[G]

for all gradual types G. For the full justification, see #26054 (the result there can be generalized from fully-static types to all gradual types).

Ecosystem

Both new diagnostic in spark and prefect seems to be true positives.

@astral-sh-bot astral-sh-bot Bot added the ty Multi-file analysis & type inference label Jun 18, 2026
@sharkdp sharkdp changed the title [ty] [ty] Invariant[P] & Invariant[Any] = Invariant[P] Jun 18, 2026
@astral-sh-bot

astral-sh-bot Bot commented Jun 18, 2026

Copy link
Copy Markdown

Typing conformance results

No changes detected ✅

Current numbers
The percentage of diagnostics emitted that were expected errors held steady at 94.37%. The percentage of expected errors that received a diagnostic held steady at 89.00%. The number of fully passing files held steady at 94/134.

@astral-sh-bot

astral-sh-bot Bot commented Jun 18, 2026

Copy link
Copy Markdown

Memory usage report

Memory usage unchanged ✅

@astral-sh-bot

astral-sh-bot Bot commented Jun 18, 2026

Copy link
Copy Markdown

ecosystem-analyzer results

Lint rule Added Removed Changed
invalid-argument-type 1 0 0
invalid-assignment 1 0 0
Total 2 0 0

Flaky changes detected. This PR summary excludes flaky changes; see the HTML report for details.

Raw diff:

prefect (https://github.com/PrefectHQ/prefect)
+ src/integrations/prefect-github/tests/test_graphql.py:28:66 error[invalid-argument-type] Argument to function `_subset_return_fields` is incorrect: Expected `dict[tuple[Unknown, ...], tuple[Unknown, ...]]`, found `dict[tuple[str], list[str]] | dict[Unknown, Unknown]`

spark (https://github.com/apache/spark)
+ python/pyspark/pandas/internal.py:1491:29 error[invalid-assignment] Object of type `list[str]` is not assignable to `list[tuple[Any, ...]]`

Full report with detailed diff (timing results)

@codspeed-hq

codspeed-hq Bot commented Jun 18, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 71 untouched benchmarks
⏩ 64 skipped benchmarks1


Comparing david/invariant-any (17f45db) with main (e302394)

Open in CodSpeed

Footnotes

  1. 64 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@sharkdp sharkdp force-pushed the david/invariant-any branch 9 times, most recently from ac9c8b5 to e21d5b4 Compare June 19, 2026 12:12
@sharkdp sharkdp changed the title [ty] Invariant[P] & Invariant[Any] = Invariant[P] [ty] Simplify intersections of invariant generic types with Any specializations Jun 19, 2026
@sharkdp sharkdp force-pushed the david/invariant-any branch 6 times, most recently from a5ae658 to 9cc2667 Compare June 19, 2026 12:43
@sharkdp sharkdp marked this pull request as ready for review June 19, 2026 12:43
@sharkdp sharkdp requested a review from a team as a code owner June 19, 2026 12:43
@astral-sh-bot astral-sh-bot Bot requested a review from dhruvmanila June 19, 2026 12:43
@sharkdp

sharkdp commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

Hm, the ecosystem timing hit on pycryptodome seems to be persistent. Looking into it.

@dhruvmanila dhruvmanila left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

That was a lot to go through and understand but I think I've a basic understanding of this change. Regardless, I asked Codex to review it and it gave me the following two issues. Do you mind taking a look? You might be able to judge the validity of it quicker than me. I at least think (2) is a valid concern.

  1. Blocker: builder.rs:105class_specialization looks through bounded type variables and NewType wrappers. Consequently, ordinary TypeIs[list[int]] narrowing erases T from T & list[int] and the identity from ListNewType & list[int], producing false invalid-return-type errors. The same reproduction passes on the base commit.
  2. Blocker: builder.rs:130is_dynamic() includes Divergent. A focused unit test showed list[Divergent] & list[int] collapsing to list[int], violating the requirement that divergent markers survive dynamic reduction and risking broken cycle convergence.

(2) is mentioned specifically in

/// This type must never be eliminated by dynamic type reduction
/// (e.g. `Divergent` is assignable to `@Todo`, but `@Todo | Divergent` must not be reducted to `@Todo`).
/// Otherwise, type inference cannot converge properly.

So, it seems like we should use is_non_divergent_dynamic instead of is_dynamic?

For (1), it gave me the following example for a NewType wrapper:

from typing import Any, NewType, TypeIs

ListId = NewType("ListId", list[Any])

def preserve_id(value: ListId) -> ListId:
    if is_int_list(value):
        return value  # PR incorrectly sees `list[int]`, losing `ListId`
    return value

And the following for a bounded-TypeVar:

from typing import Any, TypeIs, reveal_type


def is_int_list(value: object) -> TypeIs[list[int]]:
    return isinstance(value, list) and all(isinstance(item, int) for item in value)


def preserve_type[T: list[Any]](value: T) -> T:
    if is_int_list(value):
        reveal_type(value)
        return value

    return value

Comment thread crates/ty_python_semantic/resources/mdtest/intersection_types.md
Comment thread crates/ty_python_semantic/src/types/set_theoretic/builder.rs Outdated
@sharkdp sharkdp force-pushed the david/invariant-any branch 3 times, most recently from ad7f8a9 to 52dc1d2 Compare June 23, 2026 13:23
@sharkdp

sharkdp commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

I asked Codex to review it and it gave me the following two issues. Do you mind taking a look? You might be able to judge the validity of it quicker than me. I at least think (2) is a valid concern.

Thanks. Those are valid concerns, yes. I wouldn't call them "blockers", but that's okay 😄. Both issues should be addressed now.

  1. class_specialization looks through bounded type variables and NewType wrappers

That behavior is pretty surprising actually. I wouldn't have expected it to resolve typevars to their upper bound. But I guess we do something similar for other type operations as well.

@sharkdp sharkdp force-pushed the david/invariant-any branch 3 times, most recently from f17b256 to 5effca5 Compare June 23, 2026 13:30
@dhruvmanila

Copy link
Copy Markdown
Member

Hmm, interesting, the perf regression is gone now

@sharkdp

sharkdp commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

Hmm, interesting, the perf regression is gone now

No, it's just below the huge 50% threshold. There's still a 1.42x slowdown in pycryptodome in the latest measurement. I will take another look at this.

@sharkdp sharkdp force-pushed the david/invariant-any branch from 5effca5 to 17f45db Compare June 23, 2026 15:06
@sharkdp

sharkdp commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

low the huge 50% threshold. There's still a 1.42x slowdown in pycryptodome in the latest measurement. I will take another look at this.

Now it's gone! 😄

@sharkdp sharkdp merged commit 3e46953 into main Jun 23, 2026
60 checks passed
@sharkdp sharkdp deleted the david/invariant-any branch June 23, 2026 15:15
specific: Type<'db>,
) -> bool {
// Fast path to avoid performance regressions.
if !general.has_dynamic(db) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should this be has_non_divergent_dynamic?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It could be (if that method would exist, or if we would implement it).. but it's not necessary. Divergent will pass this fast-path check, but it will be filtered out below. So we don't risk simplifying Divergent out of intersections.

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

Labels

ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants