[ty] preserve bounds in generic callable assignability#24134
[ty] preserve bounds in generic callable assignability#24134Hugo-Polloli wants to merge 2 commits intoastral-sh:mainfrom
Conversation
Typing conformance resultsNo changes detected ✅Current numbersThe percentage of diagnostics emitted that were expected errors held steady at 86.46%. The percentage of expected errors that received a diagnostic held steady at 80.68%. The number of fully passing files held steady at 67/132. |
Memory usage reportSummary
Significant changesClick to expand detailed breakdownprefect
sphinx
|
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
invalid-argument-type |
1 | 45 | 0 |
invalid-await |
40 | 0 | 0 |
invalid-assignment |
0 | 1 | 0 |
invalid-return-type |
1 | 0 | 0 |
no-matching-overload |
0 | 1 | 0 |
| Total | 42 | 47 | 0 |
Changes in flaky projects detected. Raw diff output excludes flaky projects; see the HTML report for details.
Raw diff (48 changes)
Expression (https://github.com/cognitedata/Expression)
- expression/collections/array.py:496:1 error[invalid-argument-type] Argument is incorrect: Expected `(Never, /, *args: Unknown, **kwargs: Unknown) -> Never`, found `def filter[_TSource](source: TypedArray[_TSource], predicate: (_TSource, /) -> bool) -> TypedArray[_TSource]`
- expression/collections/array.py:592:1 error[invalid-argument-type] Argument is incorrect: Expected `[_TSource](Never, /, count: int) -> Never`, found `def take[_TSource](source: TypedArray[_TSource], count: int) -> TypedArray[_TSource]`
- expression/collections/array.py:606:1 error[invalid-argument-type] Argument is incorrect: Expected `[_TSource](Never, /, count: int) -> Never`, found `def take_last[_TSource](source: TypedArray[_TSource], count: int) -> TypedArray[_TSource]`
- expression/collections/map.py:258:1 error[invalid-argument-type] Argument is incorrect: Expected `(Never, /, *args: Unknown, **kwargs: Unknown) -> Never`, found `def add[_Key, _Value](table: Map[_Key, _Value], key: _Key, value: _Value) -> Map[_Key, _Value]`
- expression/collections/map.py:279:1 error[invalid-argument-type] Argument is incorrect: Expected `(Never, /, *args: Unknown, **kwargs: Unknown) -> Never`, found `def change[_Key, _Value](table: Map[_Key, _Value], key: _Key, fn: (Option[_Value], /) -> Option[_Value]) -> Map[_Key, _Value]`
- expression/collections/map.py:391:1 error[invalid-argument-type] Argument is incorrect: Expected `(Never, /, *args: Unknown, **kwargs: Unknown) -> Never`, found `def filter[_Key, _Value](table: Map[_Key, _Value], predicate: (_Key, _Value, /) -> bool) -> Map[_Key, _Value]`
- expression/collections/map.py:431:1 error[invalid-argument-type] Argument is incorrect: Expected `(Never, /, *args: Unknown, **kwargs: Unknown) -> Never`, found `def remove[_Key, _Value](table: Map[_Key, _Value], key: _Key) -> Map[_Key, _Value]`
- expression/collections/seq.py:93:38 error[invalid-argument-type] Argument is incorrect: Expected `Never`, found `Self@filter`
- expression/collections/seq.py:287:31 error[invalid-argument-type] Argument to function `pipe` is incorrect: Expected `(Self@skip, /) -> Unknown`, found `(Never, /) -> Never`
- expression/collections/seq.py:316:31 error[invalid-argument-type] Argument to function `pipe` is incorrect: Expected `(Self@take, /) -> Unknown`, found `(Never, /) -> Never`
- expression/collections/seq.py:515:1 error[invalid-argument-type] Argument is incorrect: Expected `(Never, /, *args: Unknown, **kwargs: Unknown) -> Never`, found `def filter[_TSource](source: Iterable[_TSource], predicate: (_TSource, /) -> bool) -> Iterable[_TSource]`
- expression/collections/seq.py:846:1 error[invalid-argument-type] Argument is incorrect: Expected `[_TSource](Never, /, count: int) -> Never`, found `def skip[_TSource](source: Iterable[_TSource], count: int) -> Iterable[_TSource]`
- expression/collections/seq.py:892:1 error[invalid-argument-type] Argument is incorrect: Expected `[_TSource](Never, /, count: int) -> Never`, found `def take[_TSource](source: Iterable[_TSource], count: int) -> Iterable[_TSource]`
- expression/collections/block.py:560:1 error[invalid-argument-type] Argument is incorrect: Expected `(Never, /, *args: Unknown, **kwargs: Unknown) -> Never`, found `def append[_TSource](source: Block[_TSource], other: Block[_TSource]) -> Block[_TSource]`
- expression/collections/block.py:607:1 error[invalid-argument-type] Argument is incorrect: Expected `(Never, /, *args: Unknown, **kwargs: Unknown) -> Never`, found `def filter[_TSource](source: Block[_TSource], predicate: (_TSource, /) -> bool) -> Block[_TSource]`
- expression/collections/block.py:873:1 error[invalid-argument-type] Argument is incorrect: Expected `[_TSource](Never, /, count: int) -> Never`, found `def skip[_TSource](source: Block[_TSource], count: int) -> Block[_TSource]`
- expression/collections/block.py:887:1 error[invalid-argument-type] Argument is incorrect: Expected `[_TSource](Never, /, count: int) -> Never`, found `def skip_last[_TSource](source: Block[_TSource], count: int) -> Block[_TSource]`
- expression/collections/block.py:901:1 error[invalid-argument-type] Argument is incorrect: Expected `[_TSourceSortable](Never, /, reverse: bool = False) -> Never`, found `def sort[_TSourceSortable](source: Block[_TSourceSortable], reverse: bool = False) -> Block[_TSourceSortable]`
- expression/collections/block.py:918:1 error[invalid-argument-type] Argument is incorrect: Expected `(Never, /, *args: Unknown, **kwargs: Unknown) -> Never`, found `def sort_with[_TSource](source: Block[_TSource], func: (_TSource, /) -> Any, reverse: bool = False) -> Block[_TSource]`
- expression/collections/block.py:947:1 error[invalid-argument-type] Argument is incorrect: Expected `[_TSource](Never, /, count: int) -> Never`, found `def take[_TSource](source: Block[_TSource], count: int) -> Block[_TSource]`
- expression/collections/block.py:961:1 error[invalid-argument-type] Argument is incorrect: Expected `[_TSource](Never, /, count: int) -> Never`, found `def take_last[_TSource](source: Block[_TSource], count: int) -> Block[_TSource]`
- expression/core/result.py:432:1 error[invalid-argument-type] Argument is incorrect: Expected `(Never, /, *args: Unknown, **kwargs: Unknown) -> Never`, found `def filter[_TSource, _TError](result: Result[_TSource, _TError], predicate: (_TSource, /) -> bool, default: _TError) -> Result[_TSource, _TError]`
- expression/core/result.py:441:1 error[invalid-argument-type] Argument is incorrect: Expected `(Never, /, *args: Unknown, **kwargs: Unknown) -> Never`, found `def filter_with[_TSource, _TError](result: Result[_TSource, _TError], predicate: (_TSource, /) -> bool, default: (_TSource, /) -> _TError) -> Result[_TSource, _TError]`
- expression/core/result.py:455:1 error[invalid-argument-type] Argument is incorrect: Expected `(Never, /, *args: Unknown, **kwargs: Unknown) -> Never`, found `def or_else[_TSource, _TError](result: Result[_TSource, _TError], other: Result[_TSource, _TError]) -> Result[_TSource, _TError]`
- expression/core/result.py:460:1 error[invalid-argument-type] Argument is incorrect: Expected `(Never, /, *args: Unknown, **kwargs: Unknown) -> Never`, found `def or_else_with[_TSource, _TError](result: Result[_TSource, _TError], other: (_TError, /) -> Result[_TSource, _TError]) -> Result[_TSource, _TError]`
- expression/extra/parser.py:54:39 error[invalid-argument-type] Argument is incorrect: Expected `Never`, found `Parser[_B@ignore_then]`
- expression/extra/parser.py:60:24 error[invalid-argument-type] Argument is incorrect: Expected `Never`, found `Self@or_else`
- expression/extra/parser.py:145:1 error[invalid-argument-type] Argument is incorrect: Expected `(Never, /, *args: Unknown, **kwargs: Unknown) -> Never`, found `def or_else[_A](p1: Parser[_A], p2: Parser[_A]) -> Parser[_A]`
- expression/extra/parser.py:381:1 error[invalid-argument-type] Argument is incorrect: Expected `[_B](Never, /, p1: Parser[Any]) -> Never`, found `def ignore_then[_B](p2: Parser[_B], p1: Parser[Any]) -> Parser[_B]`
- expression/extra/parser.py:457:33 error[invalid-argument-type] Argument is incorrect: Expected `Never`, found `Parser[Block[str]]`
- expression/extra/parser.py:490:21 error[invalid-argument-type] Argument is incorrect: Expected `Never`, found `Parser[_A@between]`
- tests/test_array.py:333:23 error[invalid-argument-type] Argument to function `pipe` is incorrect: Expected `(TypedArray[int], /) -> Unknown`, found `(Never, /) -> Never`
- tests/test_block.py:300:23 error[invalid-argument-type] Argument to function `pipe` is incorrect: Expected `(Block[int], /) -> Unknown`, found `(Never, /) -> Never`
- tests/test_block.py:309:23 error[invalid-argument-type] Argument to function `pipe` is incorrect: Expected `(Block[int], /) -> Unknown`, found `(Never, /) -> Never`
- tests/test_block.py:321:9 error[invalid-argument-type] Argument to function `pipe` is incorrect: Expected `(Block[str], /) -> Unknown`, found `(Never, /) -> Never`
- tests/test_compose.py:21:16 error[invalid-assignment] Object of type `(Never, /) -> Never` is not assignable to `(int, /) -> int`
- tests/test_map.py:74:21 error[invalid-argument-type] Argument to function `pipe` is incorrect: Expected `(Map[str, int] | Unknown, /) -> Unknown`, found `(Never, /) -> Never`
- tests/test_result.py:296:24 error[invalid-argument-type] Argument to bound method `pipe` is incorrect: Expected `(Result[Literal[42], Any], /) -> Unknown`, found `(Never, /) -> Never`
- tests/test_result.py:320:24 error[invalid-argument-type] Argument to bound method `pipe` is incorrect: Expected `(Result[Literal[42], Any], /) -> Unknown`, found `(Never, /) -> Never`
- tests/test_result.py:513:24 error[invalid-argument-type] Argument to bound method `pipe` is incorrect: Expected `(Result[Literal[42], Any], /) -> Unknown`, found `(Never, /) -> Never`
- tests/test_result.py:540:24 error[invalid-argument-type] Argument to bound method `pipe` is incorrect: Expected `(Result[Literal[42], Any], /) -> Unknown`, found `(Never, /) -> Never`
- tests/test_seq.py:229:23 error[invalid-argument-type] Argument to function `pipe` is incorrect: Expected `(Seq[int], /) -> Unknown`, found `(Never, /) -> Never`
- tests/test_seq.py:239:23 error[invalid-argument-type] Argument to function `pipe` is incorrect: Expected `(Seq[int], /) -> Unknown`, found `(Never, /) -> Never`
- tests/test_seq.py:248:19 error[invalid-argument-type] Argument to function `pipe` is incorrect: Expected `(Iterable[int], /) -> Unknown`, found `(Never, /) -> Never`
materialize (https://github.com/MaterializeInc/materialize)
- misc/python/materialize/cli/mz_workload_anonymize.py:251:13 error[no-matching-overload] No overload of bound method `join` matches arguments
+ misc/python/materialize/cli/mz_workload_anonymize.py:251:26 error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `(Sized, /) -> Sized`, found `def escape[AnyStr](pattern: AnyStr) -> AnyStr`
porcupine (https://github.com/Akuli/porcupine)
- porcupine/pluginmanager.py:133:49 error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[Never]`, found `Unknown | str`
sympy (https://github.com/sympy/sympy)
- sympy/parsing/mathematica.py:692:38 error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[Never]`, found `Unknown | list[str]`3e2f508 to
32f85bc
Compare
What this PR changes is that the widened type now surfaces as a downstream error in I would prefer not to chase that interaction down in this PR, and instead acknowledge it here and let it be resolved by fixing astral-sh/ty#2056. The rest of the non-flaky ecosystem changes look like improvements to me! 🚀 |
Extend generic callable assignability to bounded and constrained source typevars while preserving bounds and constraints during existential reduction. Keep the special callable path scoped to first-class callable-value comparisons so structural protocol checks stay on the general callable relation path.
3dbfc79 to
df1d7b4
Compare
dcreager
left a comment
There was a problem hiding this comment.
Having looked at astral-sh/ty#3114 more closely, I think that issue is a dup of astral-sh/ty#2799. So I think a better way to fix this will be to piggy-back on that work, where I am migrating SpecializationBuilder to use a constraint set for its internal state, rather than a simple hash map of type assignments.
I think your new mdtests are very useful for persisting expectations, though. Could you update this to keep just the new mdtests (with TODO comments calling out the expected behavior), but removing the implementation? I think it introduces a lot of incidental complexity that shouldn't be needed, and the SpecializationBuilder migration is close enough to landing that I don't think it's worth adding this even as an intermediate step.
| T_bound = TypeVar("T_bound", bound=object) | ||
|
|
||
| def bounded(t: T_bound) -> T_bound: | ||
| return t |
There was a problem hiding this comment.
nit: The other examples in this file use PEP 695 syntax. Can you update these to match? Or is there something that only works with legacy syntax? The classes below might require a particular variance, but you can control that by adding extra definitions to the class body, like we do here:
ruff/crates/ty_python_semantic/resources/mdtest/promotion.md
Lines 101 to 116 in ad8672a
| class Child(Base, Generic[PT]): | ||
| parent_id: PT | ||
|
|
||
| def get_parent_id(self) -> PT: ... |
There was a problem hiding this comment.
Same. This file uses PEP 695 syntax below. Please make sure the file remains consistent with itself if possible
Summary
Fixes astral-sh/ty#3114
Relates to astral-sh/ty#2859
Fix generic callable assignability for bounded and constrained source typevars.
tyalready enforced one coherent specialization for unconstrained source typevars when comparing a generic callable value against a fully static callable target. Bounded and constrained source generics still fell back to the legacy relation path, so cases likeTypeOf[bounded]being accepted asCallable[[str], int]were still wrong.This PR applies the same rule to bounded and constrained source generics in the existing fully-static-target case:
In
constraints.rs, existential reduction now preserves bounds and constraints before removing the inferable source type variables, so bounded and constrained typevars are not treated as unconstrained during reduction. The method regression also covers a set of inferable type variables that is larger than the method's own generic context because an outer class typevar participates in the signature.Test Plan
TypeOf[identity]andRegularCallableTypeOf[identity]rejectCallable[[str], int]while still acceptingCallable[[str], str]