Refactors the `Bindings` structure to use a two-level representation:
- Outer level: union elements (each can be a single callable or an intersection)
- Inner level: bindings within an intersection element
This enables proper handling of types like `(A & B) | C` when calling,
where `A & B` is an intersection that was narrowed by `callable()`.
Key changes:
- Add `BindingsElement` struct to represent a single union element
(which may contain multiple bindings for intersections)
- Update `from_union` to preserve intersection structure instead of
flattening
- Update `from_intersection` to create a single element with multiple
bindings
- Update `return_type`, `check_types_impl`, and `report_diagnostics`
to handle the two-level structure
- Add test for union-of-intersections case
The priority hierarchy for intersection call errors is preserved: when
all bindings in an intersection fail, only the highest-priority error
type is reported (BindingError > TopCallable > NotCallable).
When all intersection elements fail a call, we now use a priority
hierarchy to determine which errors to show:
1. NotCallable (lowest) - object has no __call__ method
2. TopCallable - object is a top callable with unknown signature
3. BindingError (highest) - specific errors like invalid-argument-type
Only errors from the highest priority category are shown. This prevents
noise from less-specific errors when more informative errors are available.
For example, if an intersection has one element that fails with
invalid-argument-type and another that's not callable, we only show
the invalid-argument-type error since it's more specific and actionable.
When calling an intersection type where all callable elements reject
the specific call arguments, we now show an error for each failing
element (similar to how unions work) instead of a single generic
"not callable" error.
This is achieved by:
- Not filtering out failed elements when ALL elements fail
- Returning BindingError instead of NotCallable to trigger individual diagnostics
- Adding IntersectionDiagnostic struct and CompoundDiagnostic trait
- Modifying report_diagnostics to iterate over intersection elements
Implement proper handling for calling intersection types. Previously,
calling an intersection type would return a `@Todo` type that suppressed
errors but provided no useful type information.
Now, when calling an intersection type:
- We try to call each positive element with the given arguments
- Elements where the call fails (wrong arguments, not callable, etc.)
are discarded
- If at least one element succeeds, the call is valid
- The return type is the intersection of return types from successful
elements
- If all elements fail, an appropriate error is reported
This approach means that if an intersection contains both a callable
with a known signature and a `Top[Callable[..., object]]` (from
narrowing by `callable()`), the call will succeed using the element
with the known signature, avoiding spurious `call-top-callable` errors.
Fixes https://github.com/astral-sh/ty/issues/1858
When working with constraint sets, we track transitive relationships
between the constraints in the set. For instance, in `S ≤ int ∧ int ≤
T`, we can infer that `S ≤ T`. However, we should only consider fully
static types when looking for a "pivot" for this kind of transitive
relationship. The same pattern does not hold for `S ≤ Any ∧ Any ≤ T`;
because the two `Any`s can materialize to different types, we cannot
infer that `S ≤ T`.
Fixes https://github.com/astral-sh/ty/issues/2371
Resolves#21892
## Summary
This PR updates `docs/editors/features.md` to clarify that Jupyter
Notebooks are now included by default as of version 0.6.0.
## Summary
Updates the fix title for RUF102 to either specify which rule code to
remove, or clarify
that the entire suppression comment should be removed.
## Test Plan
Updated test snapshots.
## Summary
This PR fixes `super()` handling when the first parameter (`self` or
`cls`) is annotated with a TypeVar, like `Self`.
Previously, `super()` would incorrectly resolve TypeVars to their bounds
before creating the `BoundSuperType`. So if you had `self: Self` where
`Self` is bounded by `Parent`, we'd process `Parent` as a
`NominalInstance` and end up with `SuperOwnerKind::Instance(Parent)`.
As a result:
```python
class Parent:
@classmethod
def create(cls) -> Self:
return cls()
class Child(Parent):
@classmethod
def create(cls) -> Self:
return super().create() # Error: Argument type `Self@create` does not satisfy upper bound `Parent`
```
We now track two additional variants on `SuperOwnerKind` for TypeVar
owners:
- `InstanceTypeVar`: for instance methods where self is a TypeVar (e.g.,
`self: Self`).
- `ClassTypeVar`: for classmethods where `cls` is a `TypeVar` wrapped in
`type[...]` (e.g., `cls: type[Self]`).
Closes https://github.com/astral-sh/ty/issues/2122.
---------
Co-authored-by: Carl Meyer <carl@astral.sh>
We enabled [`CompletionListisIncomplete`] in our LSP server a while back
in order to have more of a say in how we rank and filter completions.
When it isn't set, the client tends to ask for completions less
frequently and will instead do its own filtering.
But... we did not enable it for the playground. Which I guess didn't
result in anything noticeably bad until we started limiting completions
to 1,000 suggestions. This meant that if the _initial_ completion
response didn't include the ultimate desired answer, then it would never
show up in the results until the client requested completions again.
This in turn led to some very poor completions in some cases.
This all gets fixed by simply enabling `isIncomplete` for Monaco.
Fixesastral-sh/ty#2340
[`CompletionList::isIncomplete`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionList)
## Summary
Fixes https://github.com/astral-sh/ty/issues/2363
Fixes https://github.com/astral-sh/ty/issues/2013
And several other bugs with the same root cause. And makes any similar
bugs impossible by construction.
Previously we distinguished "no annotation" (Rust `None`) from
"explicitly annotated with something of type `Unknown`" (which is not an
error, and results in the annotation being of Rust type
`Some(Type::DynamicType(Unknown))`), even though semantically these
should be treated the same.
This was a bit of a bug magnet, because it was easy to forget to make
this `None` -> `Unknown` translation everywhere we needed to. And in
fact we did fail to do it in the case of materializing a callable,
leading to a top-materialized callable still having (rust) `None` return
type, which should have instead materialized to `object`.
This also fixes several other bugs related to not handling un-annotated
return types correctly:
1. We previously considered the return type of an unannotated `async
def` to be `Unknown`, where it should be `CoroutineType[Any, Any,
Unknown]`.
2. We previously failed to infer a ParamSpec if the return type of the
callable we are inferring against was not annotated.
3. We previously wrongly returned `Unknown` from `some_dict.get("key",
None)` if the value type of `some_dict` included a callable type with
un-annotated return type.
We now make signature return types and annotated parameter types
required, and we eagerly insert `Unknown` if there's no annotation. Most
of the diff is just a bunch of mechanical code changes where we
construct these types, and simplifications where we use them.
One exception is type display: when a callable type has un-annotated
parameters, we want to display them as un-annotated, but if it has a
parameter explicitly annotated with something of `Unknown` type, we want
to display that parameter as `x: Unknown` (it would be confusing if it
looked like your annotation just disappeared entirely).
Fortunately, we already have a mechanism in place for handling this: the
`inferred_annotation` flag, which suppresses display of an annotation.
Previously we used it only for `self` and `cls` parameters with an
inferred annotated type -- but we now also set it for any un-annotated
parameter, for which we infer `Unknown` type.
We also need to normalize `inferred_annotation`, since it's display-only
and shouldn't impact type equivalence. (This is technically a
previously-existing bug, it just never came up when it only affected
self types -- now it comes up because we have tests asserting that `def
f(x)` and `def g(x: Unknown)` are equivalent.)
## Test Plan
Added mdtests.
## Summary
I wondered if this might improve performance a little. It doesn't seem
to, but it's a net reduction in LOC and I think the changes make sense.
I think it's worth it anyway just in terms of simplifying the code.
## Test Plan
Our existing tests all pass and the primer report is clean (aside from
our usual flakes).
## Summary
fixes: https://github.com/astral-sh/ty/issues/2027
This PR fixes a bug where the type mapping for a `ParamSpec` was not
being applied in an overloaded function.
This PR also fixes https://github.com/astral-sh/ty/issues/2081 and
reveals new diagnostics which doesn't look related to the bug:
```py
from prefect import flow, task
@task
def task_get() -> int:
"""Task get integer."""
return 42
@task
def task_add(x: int, y: int) -> int:
"""Task add two integers."""
print(f"Adding {x} and {y}")
return x + y
@flow
def my_flow():
"""My flow."""
x = 23
future_y = task_get.submit()
# error: [no-matching-overload]
task_add(future_y, future_y)
# error: [no-matching-overload]
task_add(x, future_y)
```
The reason is that the type of `future_y` is `PrefectFuture[int]` while
the type of `task_add` is `Task[(x: int, y: int), int]` which means that
the assignment between `int` and `PrefectFuture[int]` fails which
results in no overload matching. Pyright also raises the invalid
argument type error on all three usages of `future_y` in those two
calls.
## Test Plan
Add regression mdtest from the linked issue.
@dhruvmanila encountered this in #22416 — there are two different
`TypeMapping` variants for apply a specialization to a type. One
operates on a full `Specialization` instance, the other on a partially
constructed one. If we move this enum-ness "down a level" it reduces
some copy/paste in places where we are operating on a `TypeMapping`.
## Summary
Combines diagnostics for matched suppression comments, so that ranges
and autofixes for both
the `#ruff:disable` and `#ruff:enable` comments will be reported as a
single diagnostic.
## Test Plan
Snapshot changes, added new snapshot for full output from preview mode
rather than just a diff.
Issue #3711
## Summary
Fixes https://github.com/astral-sh/ty/issues/2292
When solving a bounded typevar, we preferred the upper bound over the
actual type seen in the call. This change fixes that.
## Test Plan
Added mdtest, existing tests pass.
By stripping leading indents from codefence lines to ensure they're
properly understood by markdown (but otherwise preserving the indent in
the codeblock so all the code renders roughly at the right indent).
As described in [this
comment](https://github.com/astral-sh/ty/issues/2352#issuecomment-3711686053)
this solution is very "do what I mean" for when a user has an explicit
markdown codeblock in e.g. a `Returns:` section which "has" to be
indented but that indent makes the verbatim codefence invalid markdown.
* Fixes https://github.com/astral-sh/ty/issues/2352
I am updating these because we didn't have test coverage for the
different handling of `fmt: skip` comments applied to multiple
statements on the same line. This is in preparation for #22119 (to show
before/after deviations).
Follows the same procedure as in #20794
Edit: As it happens, the new fixtures do not even cover the case
relevant to #22119 - they just deal with the already handled case of a
one-line compound statement. Nevertheless, it seems worthwhile to make
this update, especially since it uncovered a (possible?) bug.
## Summary
This raises a `ValueError` at runtime:
```python
from functools import total_ordering
@total_ordering
class NoOrdering:
def __eq__(self, other: object) -> bool:
return True
```
Specifically:
```
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/functools.py", line 193, in total_ordering
raise ValueError('must define at least one ordering operation: < > <= >=')
ValueError: must define at least one ordering operation: < > <= >=
```
See: https://github.com/astral-sh/ty/issues/1202.