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

Skip to content

[ty] Preserve exact class objects during identity narrowing#26117

Merged
charliermarsh merged 8 commits into
mainfrom
charlie/fix-explicit-type-check-widening
Jun 18, 2026
Merged

[ty] Preserve exact class objects during identity narrowing#26117
charliermarsh merged 8 commits into
mainfrom
charlie/fix-explicit-type-check-widening

Conversation

@charliermarsh

@charliermarsh charliermarsh commented Jun 17, 2026

Copy link
Copy Markdown
Member

Summary

Prior to this change, when narrowing klass is Y for T: (Y, Z), where Z extends Y, we incorrectly simplified type[T] & <class 'Y'> to type[T].

Both Y and Z instances are subtypes of Y, but only the class object Y satisfies klass is Y. Using instance subtyping for the exact class object discarded that information, causing Y itself to widen to type[T] and Y() to be checked against Z's constructor. We now exclude exact class literals and specialized generic aliases from that check.

We still retain this behavior when a type variable’s upper bound normalizes to the exact target class object. This can only happen for a final class, so that class object is the type variable’s only possible specialization. (This removed ~10 ecosystem false-positives.)

Closes astral-sh/ty#3074.

@astral-sh-bot astral-sh-bot Bot added the ty Multi-file analysis & type inference label Jun 17, 2026
@astral-sh-bot

astral-sh-bot Bot commented Jun 17, 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 17, 2026

Copy link
Copy Markdown

Memory usage report

Memory usage unchanged ✅

@astral-sh-bot

astral-sh-bot Bot commented Jun 17, 2026

Copy link
Copy Markdown

ecosystem-analyzer results

Lint rule Added Removed Changed
invalid-assignment 0 0 1
unused-type-ignore-comment 0 1 0
Total 0 1 1

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

Raw diff:

steam.py (https://github.com/Gobot1234/steam.py)
- steam/ext/commands/commands.py:826:11 error[invalid-assignment] Object of type `<class 'Command'>` is not assignable to `type[C@command]`
+ steam/ext/commands/commands.py:826:11 error[invalid-assignment] Object of type `(type[C@command] & ~AlwaysFalsy) | <class 'Command'>` is not assignable to `type[C@command]`

sympy (https://github.com/sympy/sympy)
- sympy/core/function.py:451:49 warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive

Full report with detailed diff (timing results)


def narrow[T: (Y, Z)](klass: type[T]) -> None:
if klass is Y:
reveal_type(klass) # revealed: type[T@narrow] & <class 'Y'>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

On main, reveals type[T@narrow]

@charliermarsh charliermarsh marked this pull request as ready for review June 17, 2026 23:59
@charliermarsh charliermarsh requested a review from a team as a code owner June 17, 2026 23:59
@astral-sh-bot astral-sh-bot Bot requested a review from dcreager June 17, 2026 23:59
@final
class GenericFinal[T]:
@classmethod
def class_object(cls) -> "TypeOf[GenericFinal[T]]":

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Without the final "special-case", this raises invalid-return-type

@charliermarsh charliermarsh requested review from carljm and removed request for dcreager June 18, 2026 00:03
@charliermarsh

Copy link
Copy Markdown
Member Author

Passing to @carljm since he reviewed my prior attempt at this...

from ty_extensions import TypeOf

@final
class Final: ...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: Avoid using the name Final, too easily confused with typing.Final.


```py
from typing import final
from ty_extensions import TypeOf

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is there a way to exercise what this test is supposed to without using TypeOf?


```py
from typing import final
from ty_extensions import TypeOf

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Also here, is there a more realistic way to demonstrate the motivating case here without TypeOf?

source_i
.typevar(db)
.upper_bound(db)
.and_then(|bound| SubclassOfType::try_from_instance(db, bound))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should also unwrap type aliases here, and add a test similar to this:

@final
class C: ...

type Alias = C

def accepts_exact(cls: TypeOf[C]) -> None: ...

def bounded[T: Alias](cls: type[T]) -> None:
    accepts_exact(cls)

@charliermarsh charliermarsh force-pushed the charlie/fix-explicit-type-check-widening branch from 82ff548 to c2689eb Compare June 18, 2026 10:38
@charliermarsh charliermarsh enabled auto-merge (squash) June 18, 2026 10:38
@charliermarsh charliermarsh merged commit 5b47d26 into main Jun 18, 2026
59 checks passed
@charliermarsh charliermarsh deleted the charlie/fix-explicit-type-check-widening branch June 18, 2026 10:43
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.

Explicit type checking causes widening of types

2 participants