<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:
- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
requests.)
- Does this pull request include references to any relevant issues?
-->
## Summary
<!-- What's the purpose of the change? What does it do, and why? -->
## Test Plan
<!-- How was it tested? -->
## Summary
Negative subscripts are also indicative of a thing being subcriptable:
```python
class Subscriptable:
def __getitem__(self, key: int) -> int:
return 42
class NotSubscriptable: ...
def _(x: list[Subscriptable | NotSubscriptable]):
if not isinstance(x[-1], NotSubscriptable):
# After narrowing, x[-1] excludes NotSubscriptable, which means subscripting works
reveal_type(x[-1]) # revealed: Subscriptable & ~NotSubscriptable
reveal_type(x[-1][0]) # revealed: int
```
## Summary
Fixes some TODOs introduced in #22672 around cases like the following:
```python
from collections import namedtuple
from dataclasses import dataclass
NT = namedtuple("NT", "x y")
# error: [invalid-dataclass] "Cannot use `dataclass()` on a `NamedTuple` class"
dataclass(NT)
```
On main, `dataclass(NT)` emits `# error: [no-matching-overload]`
instead, which is wrong -- the overload does match! On main, the logic
proceeds as follows:
1. `dataclass` has two overloads:
- `dataclass(cls: type[_T], ...) -> type[_T]`
- `dataclass(cls: None = None, ...) -> Callable[[type[_T]], type[_T]]`
2. When `dataclass(NT)` is called:
- Arity check: Both overloads accept one positional argument, so both
pass.
- Type checking on first overload: `NT` matches `type[_T]`... but then
`invalid_dataclass_target()` runs and adds `InvalidDataclassApplication`
error
- Type checking on second overload: `NT` doesn't match `None`, so we
have a type error.
3. After type checking, both overloads have errors.
4. `matching_overload_index()` filters by
`overload.as_result().is_ok()`, which checks if `errors.is_empty()`.
Since both overloads have errors, neither matches...
5. We emit the "No overload matches arguments" error.
Instead, we now differentiate between non-matching errors, and errors
that occur when we _do_ match, but the call has some other semantic
failure.
## Summary
Consider `x: str | bytes` and then `x.split(" ")`. Because we have a
union, and at least one variant errors (`bytes` expects a `Buffer`, not
a `str`), we call `binding.report_diagnostics` for each variant. For the
`str` variant, it has two overloads that both match arity, but only one
actually matches the signature... So
`matching_overload_before_type_checking` is `None` (because they both
match arity), but we don't actually have an error, and we fall through
to `NO_MATCHING_OVERLOAD`.
If one variant succeeds, we should avoid reporting errors for it, even
if not _all_ variants matched.
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:
- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
requests.)
- Does this pull request include references to any relevant issues?
-->
## Summary
This PR is related to https://github.com/apache/airflow/issues/48389.
In Airflow 3, the function signature for `airflow.Dataset`,
`airflow.datasets.Dataset`, and `airflow.sdk.Asset` has changed. Below
is the function signature for `airflow.sdk.Asset`. The second positional
argument is `uri` which is of type `str`. In the older version, the
second positional argument can be a dictionary which is the `extra:
'dict | None' = None` argument. Therefore, we need to flag this in
Airflow 3.
```python
Asset(name: 'str | None' = None, uri: 'str | None' = None, *, group: 'str | None' = None, extra: 'dict | None' = None, watchers: 'list[AssetWatcher | SerializedAssetWatcher] | None' = None) -> 'None'
```
As this is a check on constructor call, we need to create a new method
`check_constructor_arguments`, instead of on method call. The new rule
check whether the index 1 (the second argument) is a dictionary (either
a literal or a `dict()` call).
## Test Plan
The `AIR303.py` test file have been updated with new test cases. The
snapshot has been updated and all other test cases passed.
@Lee-W , could you please review it when you have a chance, and let me
know if you have further feedback. Thanks!