## 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!
## Summary
https://github.com/astral-sh/ty/issues/111
this pr adds an `invalid-dataclass-override` diagnostic when a custom
`__setattr__` or `__delattr__` is defined on a dataclass where
`frozen=True`
([docs](https://docs.python.org/3/library/dataclasses.html#frozen-instances))
### Runtime exception
```
Traceback (most recent call last):
File "/Users/justinchapman/src/ty-playground/main.py", line 4, in <module>
@dataclass(frozen=True)
~~~~~~~~~^^^^^^^^^^^^^
File "/Users/justinchapman/.local/share/uv/python/cpython-3.13.0-macos-aarch64-none/lib/python3.13/dataclasses.py", line 1295, in wrap
return _process_class(cls, init, repr, eq, order, unsafe_hash,
frozen, match_args, kw_only, slots,
weakref_slot)
File "/Users/justinchapman/.local/share/uv/python/cpython-3.13.0-macos-aarch64-none/lib/python3.13/dataclasses.py", line 1157, in _process_class
func_builder.add_fns_to_class(cls)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^
File "/Users/justinchapman/.local/share/uv/python/cpython-3.13.0-macos-aarch64-none/lib/python3.13/dataclasses.py", line 516, in add_fns_to_class
raise TypeError(error_msg)
TypeError: Cannot overwrite attribute __setattr__ in class A
```
### Diagnostic
```
error[invalid-dataclass-override]: Cannot overwrite attribute __setattr__ in class A
--> /Users/justinchapman/src/ty-playground/main.py:6:5
|
4 | @dataclass(frozen=True)
5 | class A:
6 | def __setattr__(self, name: str, value: object) -> None: ...
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
info: __setattr__
info: rule `invalid-dataclass-override` is enabled by default
Found 1 diagnostic
```
## Test Plan
- new mdtests
- e2e
- the `attrs` mypy primer diff looks to be a [true
positive](https://github.com/python-attrs/attrs/blob/main/tests/test_setattr.py#L373)
- the other results have been unpredictable and have changed every time
i pushed new code, even if the diagnostic logic didn't change...
---------
Co-authored-by: Carl Meyer <carl@astral.sh>
Hello,
This MR adds a new rule and its fix, `RUF069`,
`DuplicateEntryInDunderAll`. I'm using `RUF069` because we already have
[RUF068](https://github.com/astral-sh/ruff/pull/20585) and
[RUF069](https://github.com/astral-sh/ruff/pull/21079#issuecomment-3493839453)
in the works.
The rule job is to prevent users from accidentally adding duplicate
entries to `__all__`, which, for example, can result from copy-paste
mistakes.
It deals with the following syntaxes:
```python
__all__: list[str] = ["a", "a"]
__all__: typing.Any = ("a", "a")
__all__.extend(["a", "a"])
__all__ += ["a", "a"]
```
But it does not keep track of `__all__` contents, meaning the following
code snippet is a false negative:
```python
class A: ...
__all__ = ["A"]
__all__.extend(["A"])
```
## Violation Example
```console
RUF069 `__all__` contains duplicate entries
--> RUF069.py:2:17
|
1 | __all__ = ["A", "A", "B"]
| ^^^
help: Remove duplicate entries from `__all__`
1 | __all__ = ["A", "B"]
- __all__ = ["A", "A", "B"]
```
## Ecosystem Report
The `ruff-ecosystem` results contain seven violations in four projects,
all of them seem like true positives, with one instance appearing to be
an actual bug.
This [code
snippet](90d855985b/stubs/reportlab/reportlab/lib/rltempfile.pyi (L4))
from `reportlab` contains the same entry twice instead of exporting both
functions.
```python
def get_rl_tempdir(*subdirs: str) -> str: ...
def get_rl_tempfile(fn: str | None = None) -> str: ...
__all__ = ("get_rl_tempdir", "get_rl_tempdir")
```
Closes [#21945](https://github.com/astral-sh/ruff/issues/21945)
---------
Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>