Commit Graph

7922 Commits

Author SHA1 Message Date
Douglas Creager 77b2cee223
[ty] Add functions for revealing assignability/subtyping constraints (#20217)
This PR adds two new `ty_extensions` functions,
`reveal_when_assignable_to` and `reveal_when_subtype_of`. These are
closely related to the existing `is_assignable_to` and `is_subtype_of`,
but instead of returning when the property (always) holds, it produces a
diagnostic that describes _when_ the property holds. (This will let us
construct mdtests that print out constraints that are not always true or
always false — though we don't currently have any instances of those.)

I did not replace _every_ occurrence of the `is_property` variants in
the mdtest suite, instead focusing on the generics-related tests where
it will be important to see the full detail of the constraint sets.

As part of this, I also updated the mdtest harness to accept the shorter
`# revealed:` assertion format for more than just `reveal_type`, and
updated the existing uses of `reveal_protocol_interface` to take
advantage of this.
2025-09-03 16:44:35 -04:00
Dan Parizher 200349c6e8
[`flake8-comprehensions`] Skip `C417` when lambda contains `yield`/`yield from` (#20201)
## Summary

Fixes #20198
2025-09-03 16:39:11 -04:00
David Peter 0d4f7dde99
[ty] Treat `__new__` as a static method (#20212)
## Summary

Pull this out of https://github.com/astral-sh/ruff/pull/18473 as an
isolated change to make sure it has no adverse effects.

The wrong behavior is observable on `main` for something like
```py
class C:
    def __new__(cls) -> "C":
        cls.x = 1

C.x  # previously: Attribute `x` can only be accessed on instances
     # now:        Type `<class 'C'>` has no attribute `x`
```
where we currently treat `x` as an *instance* attribute (because we
consider `__new__` to be a normal function and `cls` to be the "self"
attribute). With this PR, we do not consider `x` to be an attribute,
neither on the class nor on instances of `C`. If this turns out to be an
important feature, we should add it intentionally, instead of
accidentally.

## Test Plan

Ecosystem checks.
2025-09-03 19:55:20 +02:00
Shahar Naveh cb1ba0d4c2
Expose `Indentation` in `ruff_python_codegen` (#20216)
## Summary

I'm trying to reduce code complexity for
[RustPython](https://github.com/RustPython/RustPython), we have this
file:
056795eed4/compiler/codegen/src/unparse.rs
which can be replaced entirely by `ruff_python_codegen::Generator`.
Unfortunately we can not create an instance of `Generator` easily,
because `Indentation` is not exported at
cda376afe0/crates/ruff_python_codegen/src/lib.rs (L3)

I have managed to bypass this restriction by doing:
```rust
let contents = r"x = 1";
let module = ruff_python_parser::parse_module(contents).unwrap();
let stylist = ruff_python_codegen::Stylist::from_tokens(module.tokens(), contents);
stylist.indentation()
```

But ideally I'd rather use:
```rust
ruff_python_codegen::Indentation::default()
```
2025-09-03 13:32:31 -04:00
Renkai Ge cda376afe0
[ty]eliminate definitely-impossible types from union in equality narrowing (#20164)
solves https://github.com/astral-sh/ty/issues/939

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-09-03 08:34:22 -07:00
Wei Lee c452a2cb79
[`airflow`] Move `airflow.operators.postgres_operator.Mapping` from `AIR302` to `AIR301` (#20172)
<!--
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? -->

### Why
Removal should be grouped into the same category. It doesn't matter
whether it's from a provider or not (and the only case we used to have
was not anyway).
`ProviderReplacement` is used to indicate that we have a replacement and
we might need to install an extra Python package to cater to it.

### What
Move `airflow.operators.postgres_operator.Mapping` from AIR302 to AIR301
and get rid of `ProviderReplace::None`

## Test Plan

<!-- How was it tested? -->

Update the test fixtures accordingly in the first commit and reorganize
them in the second commit
2025-09-03 10:18:17 -04:00
Bhuminjay Soni 4c3e1930f6
[syntax-errors] Detect `yield from` inside async function (#20051)
<!--
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 implements
https://docs.astral.sh/ruff/rules/yield-from-in-async-function/ as a
syntax semantic error

## Test Plan

<!-- How was it tested? -->
I have written a simple inline test as directed in
[https://github.com/astral-sh/ruff/issues/17412](https://github.com/astral-sh/ruff/issues/17412)

---------

Signed-off-by: 11happy <soni5happy@gmail.com>
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-09-03 10:13:05 -04:00
Wei Lee 5d7c17c20a
[`airflow`] Convert `DatasetOrTimeSchedule(datasets=...)` to `AssetOrTimeSchedule(assets=...)` (`AIR311`) (#20202)
<!--
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? -->

update the argument `datasets` as `assets`

## Test Plan

<!-- How was it tested? -->

update fixture accordingly
2025-09-03 10:12:11 -04:00
Andrew Gallant c402bf8ae2 [ty] Correct default value for experimental rename setting
Ref https://github.com/astral-sh/ruff/pull/20207#discussion_r2318336964
2025-09-03 09:57:26 -04:00
Andrew Gallant 6bc33a041f [ty] Pick up typed text as a query for unimported completions
It's almost certainly bad juju to show literally every single possible
symbol when completions are requested but there is nothing typed yet.
Moreover, since there are so many symbols, it is likely beneficial to
try and winnow them down before sending them to the client.

This change tries to extract text that has been typed and then uses
that as a query to listing all available symbols.
2025-09-03 09:57:26 -04:00
Andrew Gallant 0a0eaf5a9b [ty] Make auto-import completions opt-in via an experimental option
Instead of waiting to land auto-import until it is "ready
for users," it'd be nicer to get incremental progress merged
to `main`. By making it an experimental opt-in, we avoid making
the default completion experience worse but permit developers
and motivated users to try it.
2025-09-03 09:57:26 -04:00
Andrew Gallant 8e52027a88 [ty] Add naive implementation of completions for unimported symbols
This re-works the `all_symbols` based added previously to work across
all modules available, and not just what is directly in the workspace.

Note that we always pass an empty string as a query, which makes the
results always empty. We'll fix this in a subsequent commit.
2025-09-03 09:57:26 -04:00
Andrew Gallant 78db56e362 [ty] Add base for "all symbols" implementation
This just copies the existing "workspace symbols" implementation and
re-names some things.
2025-09-03 09:57:26 -04:00
Andrew Gallant 046893c186 [ty] Make `Module::all_submodules` return `Module` instead of `Name`
This is to facilitate recursive traversal of all modules in an
environment. This way, we can keep asking for submodules.

This also simplifies how this is used in completions, and probably makes
it faster. Namely, since we return the `Module` itself, callers don't
need to invoke the full module resolver just to get the module type.

Note that this doesn't include namespace packages. (Which were
previously not supported in `Module::all_submodules`.) Given how they
can be spread out across multiple search paths, they will likely require
special consideration here.
2025-09-03 09:57:26 -04:00
Andrew Gallant 9cea752934 [ty] Require that we can find a root when listing sub-modules
This is similar to a change made in the "list top-level modules"
implementation that had been masked by poor Salsa failure modes.
Basically, if we can't find a root here, it *must* be a bug. And if we
just silently skip over it, we risk voiding Salsa's purity contract,
leading to more difficult to debug panics.

This did cause one test to fail, but only because the test wasn't
properly setting up roots.
2025-09-03 09:57:26 -04:00
Wei Lee 3b913ce652
[`airflow`] Improve the `AIR002` error message (#20173)
<!--
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? -->


### What
Change the message from "DAG should have an explicit `schedule`
argument" to "`DAG` or `@dag` should have an explicit `schedule`
argument"

### Why
We're trying to get rid of the idea that DAG in airflow was Directed
acyclic graph. Thus, change it to refer to the class `DAG` or the
decorator `@dag` might help a bit.

## Test Plan

<!-- How was it tested? -->

update the test fixtures accordly
2025-09-03 09:22:56 -04:00
Brent Westbrook aee9350df1
[ty] Add GitLab output format (#20155)
## Summary

This wires up the GitLab output format moved into `ruff_db` in
https://github.com/astral-sh/ruff/pull/20117 to the ty CLI.

While I was here, I made one unrelated change to the CLI docs. Clap was
rendering the escapes around the `\[default\]` brackets for the `full`
output, so I just switched those to parentheses:

```
--output-format <OUTPUT_FORMAT>
    The format to use for printing diagnostic messages

    Possible values:
    - full:    Print diagnostics verbosely, with context and helpful hints \[default\]
    - concise: Print diagnostics concisely, one per line
    - gitlab:  Print diagnostics in the JSON format expected by GitLab Code Quality reports
```

## Test Plan

New CLI test, and a manual test with `--config 'terminal.output-format =
"gitlab"'` to make sure this works as a configuration option too. I also
tried piping the output through jq to make sure it's at least valid JSON
2025-09-03 09:08:12 -04:00
Aria Desires ec5584219e
[ty] Make initializer calls GotoTargets (#20014)
This introduces `GotoTarget::Call` that represents the kind of
ambiguous/overloaded click of a callable-being-called:

```py
x = mymodule.MyClass(1, 2)
             ^^^^^^^
```

This is equivalent to `GotoTarget::Expression` for the same span but
enriched
with information about the actual callable implementation.

That is, if you click on `MyClass` in `MyClass()` it is *both* a
reference to the class and to the initializer of the class. Therefore
it would be ideal for goto-* and docstrings to be some intelligent
merging of both the class and the initializer.

In particular the callable-implementation (initializer) is prioritized
over the callable-itself (class) so when showing docstrings we will
preferentially show the docs of the initializer if it exists, and then
fallback to the docs of the class.

For goto-definition/goto-declaration we will yield both the class and
the initializer, requiring you to pick which you want (this is perhaps
needlessly pedantic but...).

Fixes https://github.com/astral-sh/ty/issues/898
Fixes https://github.com/astral-sh/ty/issues/1010
2025-09-02 14:49:14 -04:00
chiri d5e48a0f80
[`flake8-use-pathlib`] Make `PTH119` and `PTH120` fixes unsafe because they can change behavior (#20118)
## Summary

Fixes https://github.com/astral-sh/ruff/issues/20112

## Test Plan
`cargo nextest run flake8_use_pathlib`

---------

Co-authored-by: dylwil3 <dylwil3@gmail.com>
2025-09-02 10:55:24 -05:00
Aria Desires f40a0b3800
[ty] add more lsp tests for overloads (#20148)
I decided to split out the addition of these tests from other PRs so
that it's easier to follow changes to the LSP's function call handling.
I'm not particularly concerned with whether the results produced by
these tests are "good" or "bad" in this PR, I'm just establishing a
baseline.
2025-09-02 10:38:26 -04:00
David Peter bbfcf6e111
[ty] `__class_getitem__` is a classmethod (#20192)
## Summary

`__class_getitem__` is [implicitly a
classmethod](https://docs.python.org/3/reference/datamodel.html#object.__class_getitem__).

## Test Plan

Added regression test.
2025-09-01 11:22:19 +02:00
David Peter 5518c84ab3
[ty] Support `__init_subclass__` (#20190)
## Summary

`__init_subclass__` is implicitly a classmethod.

closes https://github.com/astral-sh/ty/issues/1106

## Test Plan

Regression test
2025-09-01 10:16:28 +02:00
github-actions[bot] bfe501b802
[ty] Sync vendored typeshed stubs (#20188)
Close and reopen this PR to trigger CI

---------

Co-authored-by: typeshedbot <>
2025-09-01 06:27:48 +01:00
Carl Meyer 6f2b874d6c
[ty] improve cycle-detection coverage for apply_type_mapping (#20159)
## Summary

Thread visitors through the rest of `apply_type_mapping`: callable and
protocol types.

## Test Plan

Added mdtest that previously stack overflowed.
2025-08-29 16:20:07 -07:00
Carl Meyer 17dc2e4d80
[ty] don't assume that deferred type inference means deferred name resolution (#20160)
## Summary

We have the ability to defer type inference of some parts of
definitions, so as to allow us to create a type that may need to be
recursively referenced in those other parts of the definition.

We also have the ability to do type inference in a context where all
name resolution should be deferred (that is, names should be looked up
from all-reachable-definitions rather than from the location of use.)
This is used for all annotations in stubs, or if `from __future__ import
annotations` is active.

Previous to this PR, these two concepts were linked: deferred-inference
always implied deferred-name-resolution, though we also supported
deferred-name-resolution without deferred-inference, via
`DeferredExpressionState`.

For the upcoming `typing.TypeAlias` support, I will defer inference of
the entire RHS of the alias (so as to support cycles), but that doesn't
imply deferred name resolution; at runtime, the RHS of a name annotated
as `typing.TypeAlias` is executed eagerly.

So this PR fully de-couples the two concepts, instead explicitly setting
the `DeferredExpressionState` in those cases where we should defer name
resolution.

It also fixes a long-standing related bug, where we were deferring name
resolution of all names in class bases, if any of the class bases
contained a stringified annotation.

## Test Plan

Added test that failed before this PR.
2025-08-29 16:19:45 -07:00
Dylan 694e7ed52e
Less confidently mark f-strings as empty when inferring truthiness (#20152)
When computing the boolean value of an f-string, we over-eagerly
interpreted some f-string interpolations as empty. In this PR we now
mark the truthiness of f-strings involving format specs, debug text, and
bytes literals as "unknown".

This will probably result in some false negatives, which may be further
refined (for example - there are probably many cases where
`is_not_empty_f_string` should be modified to return `true`), but for
now at least we should have fewer false positives.

Affected rules (may not be an exhaustive list):

- [unnecessary-empty-iterable-within-deque-call
(RUF037)](https://docs.astral.sh/ruff/rules/unnecessary-empty-iterable-within-deque-call/#unnecessary-empty-iterable-within-deque-call-ruf037)
- [falsy-dict-get-fallback
(RUF056)](https://docs.astral.sh/ruff/rules/falsy-dict-get-fallback/#falsy-dict-get-fallback-ruf056)
- [pytest-assert-always-false
(PT015)](https://docs.astral.sh/ruff/rules/pytest-assert-always-false/#pytest-assert-always-false-pt015)
- [expr-or-not-expr
(SIM221)](https://docs.astral.sh/ruff/rules/expr-or-not-expr/#expr-or-not-expr-sim221)
- [expr-or-true
(SIM222)](https://docs.astral.sh/ruff/rules/expr-or-true/#expr-or-true-sim222)
- [expr-and-false
(SIM223)](https://docs.astral.sh/ruff/rules/expr-and-false/#expr-and-false-sim223)

Closes #19935
2025-08-29 22:12:54 +00:00
Carl Meyer fe953e5c5c
[ty] skip a slow seed in fuzzer (#20161)
## Summary

Fuzzer seed 208 seems to be timing out all fuzzer runs on PRs today.
This has happened on multiple unrelated PRs, as well as on an initial
version of this PR that made a comment-only change in ty and didn't skip
any seeds, so the timeout appears to be consistent in CI, on ty main
branch, as of today, but it started happening due to some change in a
factor outside ty; not sure what.

I checked the code generated for seed 208 locally, and it takes about
30s to check on current ty main branch. This is slow for a fuzzer seed,
but shouldn't be slow enough to make it time out after 20min in CI (even
accounting for GH runners being slower than my laptop.)

I tried to bisect the slowness of checking that code locally, but I
didn't go back far enough to find the change that made it slow. In fact
it seems like it became significantly faster in the last few days (on an
older checkout I had to stop it after several minutes.) So whatever the
cause of the slowness, it's not a recent change in ty.

I don't want to rabbit-hole on this right now (fuzzer-discovered issues
are lower-priority than real-world-code issues), and need a working CI,
so skip this seed for now until we can investigate it.

## Test Plan

CI. This PR contains a no-op (comment) change in ty, so that the fuzz
test is triggered in CI and we can verify it now works (as well as
verify, on the previous commit, that the fuzzer job is timing out on
that seed, even with just a no-op change in ty.)
2025-08-29 13:59:16 -07:00
Alex Waygood 0bf5d2a204
Revert "[ty] Use `invalid-assignment` error code for invalid assignments to `ClassVar`s" (#20158)
Reverts astral-sh/ruff#20156. As @sharkdp noted in his post-merge
review, there were several issues with that PR that I didn't spot before
merging — but I'm out for four days now, and would rather not leave
things in an inconsistent state for that long. I'll revisit this on
Wednesday.
2025-08-29 19:48:45 +01:00
Carl Meyer 8eb8d25565
[ty] add six ecosystem projects to good.txt (#20157)
## Summary

These projects all check successfully now.

(Pandas still takes 9s, as the comment in `bad.txt` said, but I don't
think this is slow enough to exclude it; mypy-primer overall still runs
in 4 minutes, faster than e.g. the test suite on Windows.)

## Test Plan

mypy-primer CI.
2025-08-29 11:37:29 -07:00
Alex Waygood 9b1b58a451
[ty] Use `invalid-assignment` error code for invalid assignments to `ClassVar`s (#20156)
## Summary

This error is about assigning to attributes rather than reading
attributes, so I think `invalid-assignment` makes more sense than
`invalid-attribute-access`

## Test Plan

existing mdtests updated
2025-08-29 18:43:30 +01:00
Carl Meyer fa7798ddd9
[ty] minor TypedDict fixes (#20146)
## Summary

In `is_disjoint_from_impl`, we should unpack type aliases before we
check `TypedDict`. This change probably doesn't have any visible effect
until we have a more discriminating implementation of disjointness for
`TypedDict`, but making the change now can avoid some confusion/bugs in
future.

In `type_ordering.rs`, we should order `TypedDict` near more similar
types, and leave Union/Intersection together at the end of the list.
This is not necessary for correctness, but it's more consistent and it
could have saved me some confusion trying to figure out why I was only
getting an unreachable panic when my code example included a `TypedDict`
type.

## Test Plan

None besides existing tests.
2025-08-29 09:46:48 -07:00
Carl Meyer 8223fea062
[ty] ensure union normalization really normalizes (#20147)
## Summary

Now that we have `Type::TypeAlias`, which can wrap a union, and the
possibility of unions including non-unpacked type aliases (which is
necessary to support recursive type aliases), we can no longer assume in
`UnionType::normalized_impl` that normalizing each element of an
existing union will result in a set of elements that we can order and
then place raw into `UnionType` to create a normalized union. It's now
possible for those elements to themselves include union types (unpacked
from an alias). So instead, we need to feed those elements into the full
`UnionBuilder` (with alias-unpacking turned on) to flatten/normalize
them, and then order them.

## Test Plan

Added mdtest.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-08-29 09:02:35 -07:00
Eric Jolibois 5a608f7366
[ty] typecheck dict methods for `TypedDict` (#19874)
## Summary

Typecheck `get()`, `setdefault()`, `pop()` for `TypedDict`

```py
from typing import TypedDict
from typing_extensions import NotRequired

class Employee(TypedDict):
    name: str
    department: NotRequired[str]

emp = Employee(name="Alice", department="Engineering")

emp.get("name")
emp.get("departmen", "Unknown")
emp.pop("department")
emp.pop("name")
```

<img width="838" height="529" alt="Screenshot 2025-08-12 at 11 42 12"
src="https://github.com/user-attachments/assets/77ce150a-223c-4931-b914-551095d8a3a6"
/>


part of https://github.com/astral-sh/ty/issues/154

## Test Plan

Updated Markdown tests

---------

Co-authored-by: David Peter <mail@david-peter.de>
2025-08-29 16:25:03 +02:00
Hans c2d7c673ca
[`pyupgrade`] Add fix safety section to docs (`UP029`) (#17490)
## Summary

Add `fix safety` section to `UP029: unnecessary_builtin_import.rs`, for
#15584
2025-08-29 13:55:19 +00:00
Brent Westbrook 8a6db4f257
Show fixes by default (#19919)
## Summary

This PR fixes #7352 by exposing the `show_fix_diff` option used in our
snapshot tests in the CLI. As the issue suggests, we plan to make this
the default output format in the future, so this is added to the `full`
output format in preview for now.

This turned out to be pretty straightforward. I just used our existing
`Applicability` settings to determine whether or not to print the diff.

The snapshot differences are because we now set
`Applicability::DisplayOnly` for our snapshot tests. This
`Applicability` is also used to determine whether or not the fix icon
(`[*]`) is rendered, so this is now shown for display-only fixes in our
snapshots. This was already the case previously, but we were only
setting `Applicability::Unsafe` in these tests and ignoring the
`Applicability` when rendering fix diffs. CLI users can't enable
display-only fixes, so this is only a test change for now, but this
should work smoothly if we decide to expose a `--display-only-fixes`
flag or similar in the future.

I also deleted the `PrinterFlags::SHOW_FIX_DIFF` flag. This was
completely unused before, and it seemed less confusing just to delete it
than to enable it in the right place and check it along with the
`OutputFormat` and `preview`.

## Test Plan

I only added one CLI test for now. I'm kind of assuming that we have
decent coverage of the cases where this shouldn't be firing, especially
the `output_format` CLI test, which shows that this definitely doesn't
affect non-preview `full` output. I'm happy to add more tests with
different combinations of options, if we're worried about any in
particular. I did try `--diff` and `--preview` and a few other
combinations manually.

And here's a screenshot using our trusty UP049 example from the design
discussion confirming that all the colors and other formatting still
look as expected:

<img width="786" height="629" alt="image"
src="https://github.com/user-attachments/assets/94e408bc-af7b-4573-b546-a5ceac2620f2"
/>

And one with an unsafe fix to see the footer:

<img width="782" height="367" alt="image"
src="https://github.com/user-attachments/assets/bbb29e47-310b-4293-b2c2-cc7aee3baff4"
/>


## Related issues and PR
- https://github.com/astral-sh/ruff/issues/7352
- https://github.com/astral-sh/ruff/pull/12595
- https://github.com/astral-sh/ruff/issues/12598
- https://github.com/astral-sh/ruff/issues/12599
- https://github.com/astral-sh/ruff/issues/12600

I think we could probably close all of these issues now. I think we've
either resolved or avoided most of them, and if we encounter them again
with the new output format, it would probably make sense to open new
ones anyway.
2025-08-29 09:53:05 -04:00
Hans ffcdd4ea42
[`refurb`] Add fix safety section (`FURB105`) (#17499)
## Summary

This PR add the `fix safety` section for rule `FURB105` in
`print_empty_string.rs` for #15584

Before:
```
def get_sep():
    print("side effect")
    return ""
    
print("", sep=get_sep())
```

After:
```
def get_sep():
    print("side effect")
    return ""
    
print()
```

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-08-29 09:41:06 -04:00
Dan Parizher 0ff0c70302
[`fastapi`] Fix false positive for paths with spaces around parameters (`FAST003`) (#20077)
## Summary

Fixes #20060
2025-08-29 13:40:25 +00:00
Alex Waygood f77315776c
[ty] Better error message for attempting to assign to a read-only property (#20150) 2025-08-29 13:22:23 +00:00
Alex Waygood 04dc223710
[ty] Improve disambiguation of types via fully qualified names (#20141) 2025-08-29 08:44:18 +00:00
Alex Waygood 0d7ed32494
[ty] Enforce that an attribute on a class `X` must be callable in order to satisfy a member on a protocol `P` (#20142)
## Summary

Small, incremental progress towards checking the types of method
members.

## Test Plan

Added an mdtest
2025-08-29 08:31:26 +01:00
Dhruv Manilawala 4ca38b2974
[ty] Unpack variadic argument type in specialization (#20130)
## Summary

This PR fixes various TODOs around overload call when a variadic
argument is used.

The reason this bug existed is because the specialization wouldn't
account for unpacking the type of the variadic argument.

This is fixed by expanding `MatchedArgument` to contain the type of that
argument _only_ when it is a variadic argument. The reason is that
there's a split for when the argument type is inferred -- the
non-variadic arguments are inferred using `infer_argument_types` _after_
parameter matching while the variadic argument type is inferred _during_
the parameter matching. And, the `MatchedArgument` is populated _during_
parameter matching which means the unpacking would need to happen during
parameter matching.

This split seems a bit inconsistent but I don't want to spend a lot of
time on trying to merge them such that all argument type inference
happens in a single place. I might look into it while adding support for
`**kwargs`.

## Test Plan

Update existing tests by resolving the todos.

The ecosystem changes looks correct to me except for the `slice` call
but it seems that it's unrelated to this PR as we infer `slice[Any, Any,
Any]` for a `slice(1, 2, 3)` call on `main` as well
([playground](https://play.ty.dev/9eacce00-c7d5-4dd5-a932-4265cb2bb4f6)).
2025-08-29 04:27:28 +00:00
Douglas Creager a8039f80f0
[ty] Add constraint set implementation (#19997)
This PR adds an implementation of constraint sets.

An individual constraint restricts the specialization of a single
typevar to be within a particular lower and upper bound: the typevar can
only specialize to types that are a supertype of the lower bound, and a
subtype of the upper bound. (Note that lower and upper bounds are fully
static; we take the bottom and top materializations of the bounds to
remove any gradual forms if needed.) Either bound can be “closed” (where
the bound is a valid specialization), or “open” (where it is not).

You can then build up more complex constraint sets using union,
intersection, and negation operations. We use a disjunctive normal form
(DNF) representation, just like we do for types: a _constraint set_ is
the union of zero or more _clauses_, each of which is the intersection
of zero or more individual constraints. Note that the constraint set
that contains no clauses is never satisfiable (`⋃ {} = 0`); and the
constraint set that contains a single clause, which contains no
constraints, is always satisfiable (`⋃ {⋂ {}} = 1`).

One thing to note is that this PR does not change the logic of the
actual assignability checks, and in particular, we still aren't ever
trying to create an "individual constraint" that constrains a typevar.
Technically we're still operating only on `bool`s, since we only ever
instantiate `C::always_satisfiable` (i.e., `true`) and
`C::unsatisfiable` (i.e., `false`) in the `has_relation_to` methods. So
if you thought that #19838 introduced an unnecessarily complex stand-in
for `bool`, well here you go, this one is worse! (But still seemingly
not yielding a performance regression!) The next PR in this series,
#20093, is where we will actually create some non-trivial constraint
sets and use them in anger.

That said, the PR does go ahead and update the assignability checks to
use the new `ConstraintSet` type instead of `bool`. That part is fairly
straightforward since we had already updated the assignability checks to
use the `Constraints` trait; we just have to actively choose a different
impl type. (For the `is_whatever` variants, which still return a `bool`,
we have to convert the constraint set, but the explicit
`is_always_satisfiable` calls serve as nice documentation of our
intent.)

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Carl Meyer <carl@astral.sh>
2025-08-28 20:04:29 -04:00
Lior Weissman 5c2d4d8d8f
[`perflint`] Handle tuples in dictionary comprehensions (`PERF403`) (#19934)
This pull request fixes the bug described in issue
[#19153](https://github.com/astral-sh/ruff/issues/19153).

The issue occurred when `PERF403` incorrectly flagged cases involving
tuple unpacking in a for loop. For example:

```python
def f():
    v = {}
    for (o, p), x in [("op", "x")]:
        v[x] = o, p
```

This code was wrongly suggested to be rewritten into a dictionary
comprehension, which changes the semantics.

Changes in this PR:

Updated the `PERF403` rule to correctly handle tuple unpacking in loop
targets.

Added regression tests to ensure this case (and similar ones) are no
longer flagged incorrectly.

Why:
This ensures that `PERF403` only triggers when a dictionary
comprehension is semantically equivalent to the original loop,
preventing false positives.

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-08-28 21:37:40 +00:00
Dan Parizher 26082e8ec1
[`ruff`] Fix false negative for empty f-strings in `deque` calls (`RUF037`) (#20109)
## Summary

Fixes #20050
2025-08-28 16:58:39 -04:00
Kot b6522cb534
[`pylint`] Add U+061C to `PLE2502` (#20106)
Resolves #20058
2025-08-28 16:35:48 -04:00
Dan Parizher 637a2b1170
[`pycodestyle`] Preserve return type annotation for `ParamSpec` (`E731`) (#20108)
## Summary

Fixes #20097

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-08-28 20:31:45 +00:00
Jelle Zijlstra 3927b0c931
[ty] Simplify materialization of specialized generics (#20121)
This is a variant of #20076 that moves some complexity out of
`apply_type_mapping_impl` in `generics.rs`. The tradeoff is that now
every place that applies `TypeMapping::Specialization` must take care to
call `.materialize()` afterwards. (A previous version of this didn't
work because I had missed a spot where I had to call `.materialize()`.)

@carljm as asked in
https://github.com/astral-sh/ruff/pull/20076#discussion_r2305385298 .
2025-08-28 11:35:00 -07:00
Amethyst Reese ca1f66a657
[`flake8-async`] Implement `blocking-input` rule (`ASYNC250`) (#20122)
## Summary

Adds new rule to catch use of builtins `input()` in async functions.

Issue #8451

## Test Plan

New snapshosts in `ASYNC250.py` with `cargo insta test`.
2025-08-28 11:04:24 -07:00
Matthew Mckee 166b63ad4d
Fix mdtest ignore python code blocks (#20139)
## Summary

Resolves https://github.com/astral-sh/ty/issues/1103

## Test Plan

Edited `crates/ty_python_semantic/resources/mdtest/literal/boolean.md`
with:

```
# Boolean literals

```python
reveal_type(True)  # revealed: Literal[False]
reveal_type(False)  # revealed: Literal[False]
```

Ran `cargo test -p ty_python_semantic --test mdtest -- mdtest__literal_boolean`

And we get a test failure:

```
running 1 test
test mdtest__literal_boolean ... FAILED

failures:

---- mdtest__literal_boolean stdout ----

boolean.md - Boolean literals (c336e1af3d538acd)

crates/ty_python_semantic/resources/mdtest/literal/boolean.md:4
unmatched assertion: revealed: Literal[False]
crates/ty_python_semantic/resources/mdtest/literal/boolean.md:4
unexpected error: 13 [revealed-type] "Revealed type: `Literal[True]`"

To rerun this specific test, set the environment variable:
MDTEST_TEST_FILTER='boolean.md - Boolean literals (c336e1af3d538acd)'
MDTEST_TEST_FILTER='boolean.md - Boolean literals (c336e1af3d538acd)'
cargo test -p ty_python_semantic --test mdtest --
mdtest__literal_boolean

--------------------------------------------------


thread 'mdtest__literal_boolean' panicked at
crates/ty_test/src/lib.rs:138:5:
Some tests failed.
note: run with `RUST_BACKTRACE=1` environment variable to display a
backtrace


failures:
    mdtest__literal_boolean

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 263
filtered out; finished in 0.18s

error: test failed, to rerun pass `-p ty_python_semantic --test mdtest`
```

As expected.

And when i checkout main and keep the same mdtest file all tests pass (as the repro).
2025-08-28 09:59:03 -07:00
Carl Meyer 9363eeca26
[ty] add support for cyclic legacy generic protocols (#20125)
## Summary

Just add the necessary Salsa cycle handling.

## Test Plan

Added mdtest.
2025-08-28 16:58:01 +00:00
Carl Meyer f4362b95d7
[ty] add cycle detection for find_legacy_typevars (#20124)
## Summary

Add cycle detection to the `find_legacy_typevars` type method.

## Test Plan

Added mdtest that stack overflowed without this.
2025-08-28 09:55:08 -07:00
Brent Westbrook f703536977
Use new diff rendering format in tests (#20101)
## Summary

I spun this off from #19919 to separate the rendering code change and
snapshot updates from the (much smaller) changes to expose this in the
CLI. I grouped all of the `ruff_linter` snapshot changes in the final
commit in an effort to make this easier to review. The code changes are
in [this
range](619395eb41).

I went through all of the snapshots, albeit fairly quickly, and they all
looked correct to me. In the last few commits I was trying to resolve an
existing issue in the alignment of the line number separator:


73720c73be/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C409_C409.py.snap (L87-L89)

In the snapshot above on `main`, you can see that a double-digit line
number at the end of the context lines for a snippet was causing a
misalignment with the other separators. That's now resolved. The one
downside is that this can lead to a mismatch with the diagnostic above:

```
C409 [*] Unnecessary list literal passed to `tuple()` (rewrite as a tuple literal)
 --> C409.py:4:6
  |
2 |   t2 = tuple([1, 2])
3 |   t3 = tuple((1, 2))
4 |   t4 = tuple([
  |  ______^
5 | |     1,
6 | |     2
7 | | ])
  | |__^
8 |   t5 = tuple(
9 |       (1, 2)
  |
help: Rewrite as a tuple literal
1  | t1 = tuple([])
2  | t2 = tuple([1, 2])
3  | t3 = tuple((1, 2))
   - t4 = tuple([
4  + t4 = (
5  |     1,
6  |     2
   - ])
7  + )
8  | t5 = tuple(
9  |     (1, 2)
10 | )
note: This is an unsafe fix and may remove comments or change runtime behavior
```

But I don't think we can avoid that without really reworking this
rendering to make the diagnostic and diff rendering aware of each other.
Anyway, this should only happen in relatively rare cases where the
diagnostic is near a digit boundary and also near a context boundary.
Most of our diagnostics line up nicely.

Another potential downside of the new rendering format is its handling
of long stretches of `+` or `-` lines:

```
help: Replace with `Literal[...] | None`
21 |     ...
22 |
23 |
   - def func6(arg1: Literal[
   -     "hello",
   -     None  # Comment 1
   -     , "world"
   -     ]):
24 + def func6(arg1: Literal["hello", "world"] | None):
25 |     ...
26 |
27 |
note: This is an unsafe fix and may remove comments or change runtime behavior
```

To me it just seems a little hard to tell what's going on with just a
long streak of `-`-prefixed lines. I saw an even more exaggerated
example at some point, but I think this is also fairly rare. Most of the
snapshots seem more like the examples we looked at on Discord with
plenty of `|` lines and pairs of `+` and `-` lines.

## Test Plan

Existing tests plus one new test in `ruff_db` to isolate a line
separator alignment issue
2025-08-28 10:56:58 -04:00
David Peter 1842cfe333
[ty] Fix 'too many cycle iterations' for unions of literals (#20137)
## Summary

Decrease the maximum number of literals in a union before we collapse to
the supertype. The better fix for this will be
https://github.com/astral-sh/ty/issues/957, but it is very tempting to
solve this for now by simply decreasing the limit by one, to get below
the salsa limit of 200.

closes https://github.com/astral-sh/ty/issues/660

## Test Plan

Added a regression test that would previously lead to a "too many cycle
iterations" panic.
2025-08-28 16:46:37 +02:00
David Peter b3c4005289
[ty] No boundness analysis for implicit instance attributes (#20128)
## Summary

With this PR, we stop performing boundness analysis for implicit
instance attributes:

```py
class C:
    def __init__(self):
        if False:   
            self.x = 1

C().x  # would previously show an error, with this PR we pretend the attribute exists
```

This PR is potentially just a temporary measure until we find a better
fix. But I have already invested a lot of time trying to find the root
cause of https://github.com/astral-sh/ty/issues/758 (and [this
example](https://github.com/astral-sh/ty/issues/758#issuecomment-3206108262),
which I'm not entirely sure is related) and I still don't understand
what is going on. This PR fixes the performance problems in both of
these problems (in a rather crude way).

The impact of the proposed change on the ecosystem is small, and the
three new diagnostics are arguably true positives (previously hidden
because we considered the code unreachable, based on e.g. `assert`ions
that depended on implicit instance attributes). So this seems like a
reasonable fix for now.

Note that we still support cases like these:

```py
class D:
    if False:  # or any other expression that statically evaluates to `False`
        x: int = 1

D().x  # still an error


class E:
    if False:  # or any other expression that statically evaluates to `False`
        def f(self):
            self.x = 1

E().x  # still an error
```

closes https://github.com/astral-sh/ty/issues/758

## Test Plan

Updated tests, benchmark results
2025-08-28 16:25:07 +02:00
Brent Westbrook c2bc15bc15
Bump 0.12.11 (#20136) 2025-08-28 09:45:01 -04:00
David Peter e586f6dcc4
[ty] Benchmarks for problematic implicit instance attributes cases (#20133)
## Summary

Add regression benchmarks for the problematic cases in
https://github.com/astral-sh/ty/issues/758. I'd like to merge this
before https://github.com/astral-sh/ruff/pull/20128 to measure the
impact (local tests show that this will "solve" both cases).
2025-08-28 15:25:25 +02:00
Takayuki Maeda 76a6b7e3e2
[`pyflakes`] Fix `allowed-unused-imports` matching for top-level modules (`F401`) (#20115)
<!--
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? -->

Fixes #19664

Fix allowed unused imports matching for top-level modules.

I've simply replaced `from_dotted_name` with `user_defined`. Since
QualifiedName for imports is created in
crates/ruff_python_semantic/src/imports.rs, I guess it's acceptable to
use `user_defined` here. Please tell me if there is better way.


0c5089ed9e/crates/ruff_python_semantic/src/imports.rs (L62)

## Test Plan

<!-- How was it tested? -->

I've added a snapshot test
`f401_allowed_unused_imports_top_level_module`.
2025-08-28 13:02:50 +00:00
Brent Westbrook 1ce65714c0
Move GitLab output rendering to `ruff_db` (#20117)
## Summary

This PR is a first step toward adding a GitLab output format to ty. It
converts the `GitlabEmitter` from `ruff_linter` to a `GitlabRenderer` in
`ruff_db` and updates its implementation to handle non-Ruff files and
diagnostics without primary spans. I tried to break up the changes here
so that they're easy to review commit-by-commit, or at least in groups
of commits:
- [preparatory changes in-place in `ruff_linter` and a `ruff_db`
skeleton](0761b73a61)
- [moving the code over with no implementation changes mixed
in](0761b73a61..8f909ea0bb)
- [tidying up the code now in
`ruff_db`](9f047c4f9f..e5e217fcd6)

This wasn't strictly necessary, but I also added some `Serialize`
structs instead of calling `json!` to make it a little clearer that we
weren't modifying the schema (e4c4bee35d).

I plan to follow this up with a separate PR exposing this output format
in the ty CLI, which should be quite straightforward.

## Test Plan

Existing tests, especially the two that show up in the diff as renamed
nearly without changes
2025-08-28 08:56:33 -04:00
Shaygan Hooshyari d9aaacd01f
[ty] Evaluate reachability of non-definitely-bound to Ambiguous (#19579)
## Summary

closes https://github.com/astral-sh/ty/issues/692

If the expression (or any child expressions) is not definitely bound the
reachability constraint evaluation is determined as ambiguous.

This fixes the infinite cycles panic in the following code:

```py
from typing import Literal

class Toggle:
    def __init__(self: "Toggle"):
        if not self.x:
            self.x: Literal[True] = True
```

Credit of this solution is for David.

## Test Plan

- Added a test case with too many cycle iterations panic.
- Previous tests.

---------

Co-authored-by: David Peter <mail@david-peter.de>
2025-08-28 14:34:49 +02:00
Jelle Zijlstra 18eaa659c1
[ty] Introduce a representation for the top/bottom materialization of an invariant generic (#20076)
Part of #994. This adds a new field to the Specialization struct to
record when we're dealing with the top or bottom materialization of an
invariant generic. It also implements subtyping and assignability for
these objects.

Next planned steps after this is done are to implement other operations
on top/bottom materializations; probably attribute access is an
important one.

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-08-27 17:53:57 -07:00
Amethyst Reese af259faed5
[`flake8-async`] Implement `blocking-http-call-httpx` (`ASYNC212`) (#20091)
## Summary

Adds new rule to find and report use of `httpx.Client` in synchronous
functions.

See issue #8451

## Test Plan

New snapshots for `ASYNC212.py` with `cargo insta test`.
2025-08-27 15:19:01 -07:00
Leandro Braga d75ef3823c
[ty] print diagnostics with fully qualified name to disambiguate some cases (#19850)
There are some situations that we have a confusing diagnostics due to
identical class names.

## Class with same name from different modules

```python
import pandas
import polars

df: pandas.DataFrame = polars.DataFrame()
```

This yields the following error:

**Actual:**
error: [invalid-assignment] "Object of type `DataFrame` is not
assignable to `DataFrame`"
**Expected**:
error: [invalid-assignment] "Object of type `polars.DataFrame` is not
assignable to `pandas.DataFrame`"

## Nested classes

```python
from enum import Enum

class A:
    class B(Enum):
        ACTIVE = "active"
        INACTIVE = "inactive"

class C:
    class B(Enum):
        ACTIVE = "active"
        INACTIVE = "inactive"
```

**Actual**:
error: [invalid-assignment] "Object of type `Literal[B.ACTIVE]` is not
assignable to `B`"
**Expected**:
error: [invalid-assignment] "Object of type
`Literal[my_module.C.B.ACTIVE]` is not assignable to `my_module.A.B`"

## Solution

In this MR we added an heuristics to detect when to use a fully
qualified name:
- There is an invalid assignment and;
- They are two different classes and;
- They have the same name

The fully qualified name always includes:
- module name
- nested classes name
- actual class name

There was no `QualifiedDisplay` so I had to implement it from scratch.
I'm very new to the codebase, so I might have done things inefficiently,
so I appreciate feedback.

Should we pre-compute the fully qualified name or do it on demand? 

## Not implemented

### Function-local classes

Should we approach this in a different PR?

**Example**:
```python 
# t.py
from __future__ import annotations


def function() -> A:
    class A:
        pass

    return A()


class A:
    pass


a: A = function()
```

#### mypy

```console
t.py:8: error: Incompatible return value type (got "t.A@5", expected "t.A")  [return-value]
```

From my testing the 5 in `A@5` comes from the like number. 

#### ty

```console
error[invalid-return-type]: Return type does not match returned value
 --> t.py:4:19
  |
4 | def function() -> A:
  |                   - Expected `A` because of return type
5 |     class A:
6 |         pass
7 |
8 |     return A()
  |            ^^^ expected `A`, found `A`
  |
info: rule `invalid-return-type` is enabled by default
```

Fixes https://github.com/astral-sh/ty/issues/848

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Carl Meyer <carl@astral.sh>
2025-08-27 20:46:07 +00:00
Dan Parizher 89ca493fd9
[`ruff`] Preserve relative whitespace in multi-line expressions (`RUF033`) (#19647)
## Summary

Fixes #19581

I decided to add in a `indent_first_line` function into
[`textwrap.rs`](https://github.com/astral-sh/ruff/blob/main/crates/ruff_python_trivia/src/textwrap.rs),
as it solely focuses on text manipulation utilities. It follows the same
design as `indent()`, and there may be situations in the future where it
can be reused as well.

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-08-27 19:15:44 +00:00
David Peter 4b80f5fa4f
[ty] Optimize TDD atom ordering (#20098)
## Summary

While looking at some logging output that I added to
`ReachabilityConstraintBuilder::add_and_constraint` in order to debug
https://github.com/astral-sh/ty/issues/1091, I noticed that it seemed to
suggest that the TDD was built in an imbalanced way for code like the
following, where we have a sequence of non-nested `if` conditions:

```py
def f(t1, t2, t3, t4, …):
    x = 0
    if t1:
        x = 1
    if t2:
        x = 2
    if t3:
        x = 3
    if t4:
        x = 4
    …
```

To understand this a bit better, I added some code to the
`ReachabilityConstraintBuilder` to render the resulting TDD. On `main`,
we get a tree that looks like the following, where you can see a pattern
of N sub-trees that grow linearly with N (number of `if` statements).
This results in an overall tree structure that has N² nodes (see graph
below):

<img alt="normal order"
src="https://github.com/user-attachments/assets/aab40ce9-e82a-4fcd-823a-811f05f15f66"
/>

If we zoom in to one of these subgraphs, we can see what the problem is.
When we add new constraints that represent combinations like `t1 AND ~t2
AND ~t3 AND t4 AND …`, they start with the evaluation of "early"
conditions (`t1`, `t2`, …). This means that we have to create new
subgraphs for each new `if` condition because there is little sharing
with the previous structure. We evaluate the Boolean condition in a
right-associative way: `t1 AND (~t2 AND (~t3 AND t4)))`:

<img width="500" align="center"
src="https://github.com/user-attachments/assets/31ea7182-9e00-4975-83df-d980464f545d"
/>

If we change the ordering of TDD atoms, we can change that to a
left-associative evaluation: `(((t1 AND ~t2) AND ~t3) AND t4) …`. This
means that we can re-use previous subgraphs `(t1 AND ~t2)`, which
results in a much more compact graph structure overall (note how "late"
conditions are now at the top, and "early" conditions are further down
in the graph):

<img alt="reverse order"
src="https://github.com/user-attachments/assets/96a6b7c1-3d35-4192-a917-0b2d24c6b144"
/>

If we count the number of TDD nodes for a growing number if `if`
statements, we can see that this change results in a slower growth. It's
worth noting that the growth is still superlinear, though:

<img width="800" height="600" alt="plot"
src="https://github.com/user-attachments/assets/22e8394f-e74e-4a9e-9687-0d41f94f2303"
/>

On the actual code from the referenced ticket (the `t_main.py` file
reduced to its main function, with the main function limited to 2000
lines instead of 11000 to allow the version on `main` to run to
completion), the effect is much more dramatic. Instead of 26 million TDD
nodes (`main`), we now only create 250 thousand (this branch), which is
slightly less than 1%.

The change in this PR allows us to build the semantic index and
type-check the problematic `t_main.py` file in
https://github.com/astral-sh/ty/issues/1091 in 9 seconds. This is still
not great, but an obvious improvement compared to running out of memory
after *minutes* of execution.

An open question remains whether this change is beneficial for all kinds
of code patterns, or just this linear sequence of `if` statements. It
does not seem unreasonable to think that referring to "earlier"
conditions is generally a good idea, but I learned from Doug that it's
generally not possible to find a TDD-construction heuristic that is
non-pathological for all kinds of inputs. Fortunately, it seems like
this change here results in performance improvements across *all of our
benchmarks*, which should increase the confidence in this change:

| Benchmark           | Improvement |
|---------------------|-------------------------|
| hydra-zen           | +13%                    |
| DateType            | +5%                     |
| sympy (walltime)    | +4%                     |
| attrs               | +4%                     |
| pydantic (walltime) | +2%                     |
| pandas (walltime)   | +2%                     |
| altair (walltime)   | +2%                     |
| static-frame        | +2%                     |
| anyio               | +1%                     |
| freqtrade           | +1%                     |
| colour-science      | +1%                     |
| tanjun              | +1%                     |

closes https://github.com/astral-sh/ty/issues/1091

---------

Co-authored-by: Douglas Creager <dcreager@dcreager.net>
2025-08-27 20:42:09 +02:00
Wei Lee 5663426d73
[`airflow`] Extend `AIR311` and `AIR312` rules (#20082)
<!--
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? -->

Extend the following rules.

### AIR311
* `airflow.sensors.base.BaseSensorOperator` →
airflow.sdk.bases.sensor.BaseSensorOperator`
* `airflow.sensors.base.PokeReturnValue` →
airflow.sdk.bases.sensor.PokeReturnValue`
* `airflow.sensors.base.poke_mode_only` →
airflow.sdk.bases.sensor.poke_mode_only`
* `airflow.decorators.base.DecoratedOperator` →
airflow.sdk.bases.decorator.DecoratedOperator`
* `airflow.models.param.Param` → airflow.sdk.definitions.param.Param`
* `airflow.decorators.base.DecoratedMappedOperator` →
`airflow.sdk.bases.decorator.DecoratedMappedOperator`
* `airflow.decorators.base.DecoratedOperator` →
`airflow.sdk.bases.decorator.DecoratedOperator`
* `airflow.decorators.base.TaskDecorator` →
`airflow.sdk.bases.decorator.TaskDecorator`
* `airflow.decorators.base.get_unique_task_id` →
`airflow.sdk.bases.decorator.get_unique_task_id`
* `airflow.decorators.base.task_decorator_factory` →
`airflow.sdk.bases.decorator.task_decorator_factory`


### AIR312
* `airflow.sensors.bash.BashSensor` →
`airflow.providers.standard.sensor.bash.BashSensor`
* `airflow.sensors.python.PythonSensor` →
`airflow.providers.standard.sensors.python.PythonSensor`



## Test Plan

<!-- How was it tested? -->

update the test fixture accordingly in the second commit and reorg in
the third
2025-08-27 14:11:22 -04:00
David Peter 0b3548755c
[ty] Preserve qualifiers when accessing attributes on unions/intersections (#20114)
## Summary

Properly preserve type qualifiers when accessing attributes on unions
and intersections. This is a prerequisite for
https://github.com/astral-sh/ruff/pull/19579.

Also fix a completely wrong implementation of
`map_with_boundness_and_qualifiers`. It now closely follows
`map_with_boundness` (just above).

## Test Plan

I thought about it, but didn't find any easy way to test this. This only
affected `Type::member`. Things like validation of attribute writes
(where type qualifiers like `ClassVar` and `Final` are important) were
already handling things correctly.
2025-08-27 20:01:45 +02:00
Alex Waygood ce1dc21e7e
[ty] Fix the inferred interface of specialized generic protocols (#19866) 2025-08-27 18:16:15 +01:00
Alex Waygood 7d0c8e045c
[ty] Infer slightly more precise types for comprehensions (#20111) 2025-08-27 13:21:47 +01:00
Alex Waygood d71518b369
[ty] Add more tests for protocols (#20095)
Co-authored-by: Shunsuke Shibayama <sbym1346@gmail.com>
2025-08-27 12:56:14 +01:00
Carl Meyer 9ab276b345
[ty] don't eagerly unpack aliases in user-authored unions (#20055)
## Summary

Add a subtly different test case for recursive PEP 695 type aliases,
which does require that we relax our union simplification, so we don't
eagerly unpack aliases from user-provided union annotations.

## Test Plan

Added mdtest.
2025-08-26 16:29:45 -07:00
chiri a60fb3f2c8
[`flake8-use-pathlib`] Update links to the table showing the correspondence between `os` and `pathlib` (#20103)
<!--
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

Part of https://github.com/astral-sh/ruff/pull/20100 |
https://github.com/astral-sh/ruff/pull/20100#issuecomment-3225349156
2025-08-26 17:33:33 -04:00
chiri f558bf721c
[`flake8-use-pathlib`] Make `PTH100` fix unsafe because it can change behavior (#20100)
<!--
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
Fixes https://github.com/astral-sh/ruff/issues/20088
<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

`cargo nextest run flake8_use_pathlib`

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-08-26 18:59:12 +00:00
chiri ea1c080881
[`flake8-use-pathlib`] Delete unused `Rule::OsSymlink` enabled check (#20099)
<!--
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
Part of #20009 (i forgot to delete it in this PR)
<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

<!-- How was it tested? -->
2025-08-26 12:05:52 -04:00
Renkai Ge 73720c73be
[ty] Add search paths info to unresolved import diagnostics (#20040)
Fixes https://github.com/astral-sh/ty/issues/457

---------

Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2025-08-26 11:01:16 -04:00
Hamir Mahal 136abace92
[`flake8-logging-format`] Add auto-fix for f-string logging calls (`G004`) (#19303)
Closes #19302

<!--
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 adds an auto-fix for `Logging statement uses f-string` Ruff G004,
so users don't have to resolve it manually.
<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan
I ran the auto-fixes on a Python file locally and and it worked as
expected.
<!-- How was it tested? -->

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-08-26 10:51:24 -04:00
Brent Westbrook bc7274d148
Add a `ScopeKind` for the `__class__` cell (#20048)
Summary
--

This PR aims to resolve (or help to resolve) #18442 and #19357 by
encoding the CPython semantics around the `__class__` cell in our
semantic model. Namely,

> `__class__` is an implicit closure reference created by the compiler
if any methods in a class body refer to either `__class__` or super.

from the Python
[docs](https://docs.python.org/3/reference/datamodel.html#creating-the-class-object).

As noted in the variant docs by @AlexWaygood, we don't fully model this
behavior, opting always to create the `__class__` cell binding in a new
`ScopeKind::DunderClassCell` around each method definition, without
checking if any method in the class body actually refers to `__class__`
or `super`.

As such, this PR fixes #18442 but not #19357.

Test Plan
--

Existing tests, plus the tests from #19783, which now pass without any
rule-specific code.

Note that we opted not to alter the behavior of F841 here because
flagging `__class__` in these cases still seems helpful. See the
discussion in
https://github.com/astral-sh/ruff/pull/20048#discussion_r2296252395 and
in the test comments for more information.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Mikko Leppänen <mleppan23@gmail.com>
2025-08-26 09:49:08 -04:00
Matthew Mckee 8d6dc7d3a3
[ty] Refactor inlay hints structure to use separate parts (#20052)
## Summary

Our internal inlay hints structure (`ty_ide::InlayHint`) now more
closely resembles `lsp_types::InlayHint`.

This mainly allows us to convert to `lsp_types::InlayHint` with less
hassle, but it also allows us to manage the different parts of the inlay
hint better, which in the future will allow us to implement features
like goto on the type part of the type inlay hint.

It also really isn't important to store a specific `Type` instance in
the `InlayHintContent`. So we remove this and use `InlayHintLabel`
instead which just shows the representation of the type (along with
other information).

We see a similar structure used in rust-analyzer too.
2025-08-26 10:21:31 +05:30
Dylan ef4897f9f3
[ty] Add support for PEP 750 t-strings (#20085)
This PR attempts to adds support for inferring`string.templatelib.Template` for t-string literals.
2025-08-25 18:49:49 +00:00
Alex Waygood ecf3c4ca11
[ty] Add support for PEP 800 (#20084) 2025-08-25 19:39:05 +01:00
Carl Meyer 33c5f6f4f8
[ty] don't mark entire type-alias scopes as Deferred (#20086)
## Summary

This has been here for awhile (since our initial PEP 695 type alias
support) but isn't really correct. The right-hand-side of a PEP 695 type
alias is a distinct scope, and we don't mark it as an "eager" nested
scope, so it automatically gets "deferred" resolution of names from
outer scopes (just like a nested function). Thus it's
redundant/unnecessary for us to use `DeferredExpressionState::Deferred`
for resolving that RHS expression -- that's for deferring resolution of
individual names within a scope. Using it here causes us to wrongly
ignore applicable outer-scope narrowing.

## Test Plan

Added mdtest that failed before this PR (the second snippet -- the first
snippet always passed.)
2025-08-25 11:32:18 -07:00
github-actions[bot] ba47010150
[ty] Sync vendored typeshed stubs (#20083)
Co-authored-by: typeshedbot <>
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2025-08-25 17:01:51 +00:00
Wei Lee db423ee978
[`airflow`] replace wrong path `airflow.io.stroage` as `airflow.io.store` (`AIR311`) (#20081)
## Summary

`airflow.io.storage` is not the correct path. it should be
`airflow.io.store` instead
2025-08-25 10:15:34 -05:00
Alex Waygood a04823cfad
[ty] Completely ignore typeshed's stub for `Any` (#20079) 2025-08-25 15:27:55 +01:00
Brent Westbrook d0bcf56bd9
Improve diff rendering for notebooks (#20036)
## Summary

As noted in a code TODO, our `Diff` rendering code previously didn't
have any
special handling for notebooks. This was particularly obvious when the
diffs
were rendered right next to the corresponding diagnostic because the
diagnostic
used cell-based line numbers, while the diff was still using line
numbers from
the concatenated source. This PR updates the diff rendering to handle
notebooks
too.

The main improvements shown in the example below are:

- Line numbers are now remapped to be relative to their cell
- Context lines from other cells are suppressed

```
error[unused-import][*]: `math` imported but unused                             
 --> notebook.ipynb:cell 2:2:8                                                  
  |                                                                             
1 | # cell 2                                                                    
2 | import math                                                                 
  |        ^^^^                                                                 
3 |                                                                             
4 | print('hello world')                                                        
  |                                                                             
help: Remove unused import: `math`                                              
                                                                                
ℹ Safe fix                                                                      
1 1 | # cell 2                                                                  
2   |-import math                                                               
3 2 |                                                                           
4 3 | print('hello world')                                                      
```

I tried a few different approaches here before finally just splitting
the notebook into separate text ranges by cell and diffing each one
separately. It seems to work and passes all of our tests, but I don't
know if it's actually enforced anywhere that a single edit doesn't span
cells. Such an edit would silently be dropped right now since it would
fail the `contains_range` check. I also feel like I may have overlooked
an existing way to partition a file into cells like this.

## Test Plan

Existing notebook tests, plus a new one in `ruff_db`
2025-08-25 09:20:42 -04:00
Eric Jolibois f9bbee33f6
[ty] validate constructor call of `TypedDict` (#19810)
## Summary
Implement validation for `TypedDict` constructor calls and dictionary
literal assignments, including support for `total=False` and proper
field management.
Also add support for `Required` and `NotRequired` type qualifiers in
`TypedDict` classes, along with proper inheritance behavior and the
`total=` parameter.
Support both constructor calls and dict literal syntax

part of https://github.com/astral-sh/ty/issues/154

### Basic Required Field Validation
```py
class Person(TypedDict):
    name: str
    age: int | None

# Error: Missing required field 'name' in TypedDict `Person` constructor
incomplete = Person(age=25)

# Error: Invalid argument to key "name" with declared type `str` on TypedDict `Person`
wrong_type = Person(name=123, age=25)

# Error: Invalid key access on TypedDict `Person`: Unknown key "extra"
extra_field = Person(name="Bob", age=25, extra=True)
```
<img width="773" height="191" alt="Screenshot 2025-08-07 at 17 59 22"
src="https://github.com/user-attachments/assets/79076d98-e85f-4495-93d6-a731aa72a5c9"
/>

### Support for `total=False`
```py
class OptionalPerson(TypedDict, total=False):
    name: str
    age: int | None

# All valid - all fields are optional with total=False
charlie = OptionalPerson()
david = OptionalPerson(name="David")
emily = OptionalPerson(age=30)
frank = OptionalPerson(name="Frank", age=25)

# But type validation and extra fields still apply
invalid_type = OptionalPerson(name=123)  # Error: Invalid argument type
invalid_extra = OptionalPerson(extra=True)  # Error: Invalid key access
```

### Dictionary Literal Validation
```py
# Type checking works for both constructors and dict literals
person: Person = {"name": "Alice", "age": 30}

reveal_type(person["name"])  # revealed: str
reveal_type(person["age"])   # revealed: int | None

# Error: Invalid key access on TypedDict `Person`: Unknown key "non_existing"
reveal_type(person["non_existing"])  # revealed: Unknown
```

### `Required`, `NotRequired`, `total`
```python
from typing import TypedDict
from typing_extensions import Required, NotRequired

class PartialUser(TypedDict, total=False):
    name: Required[str]      # Required despite total=False
    age: int                 # Optional due to total=False
    email: NotRequired[str]  # Explicitly optional (redundant)

class User(TypedDict):
    name: Required[str]      # Explicitly required (redundant)
    age: int                 # Required due to total=True
    bio: NotRequired[str]    # Optional despite total=True

# Valid constructions
partial = PartialUser(name="Alice")  # name required, age optional
full = User(name="Bob", age=25)      # name and age required, bio optional

# Inheritance maintains original field requirements
class Employee(PartialUser):
    department: str                  # Required (new field)
    # name: still Required (inherited)
    # age: still optional (inherited)

emp = Employee(name="Charlie", department="Engineering")  # 
Employee(department="Engineering")  # 
e: Employee = {"age": 1}  # 
```

<img width="898" height="683" alt="Screenshot 2025-08-11 at 22 02 57"
src="https://github.com/user-attachments/assets/4c1b18cd-cb2e-493a-a948-51589d121738"
/>

## Implementation
The implementation reuses existing validation logic done in
https://github.com/astral-sh/ruff/pull/19782

### ℹ️ Why I did NOT synthesize an `__init__` for `TypedDict`:

`TypedDict` inherits `dict.__init__(self, *args, **kwargs)` that accepts
all arguments.
The type resolution system finds this inherited signature **before**
looking for synthesized members.
So `own_synthesized_member()` is never called because a signature
already exists.

To force synthesis, you'd have to override Python’s inheritance
mechanism, which would break compatibility with the existing ecosystem.

This is why I went with ad-hoc validation. IMO it's the only viable
approach that respects Python’s
inheritance semantics while providing the required validation.

### Refacto of `Field`

**Before:**
```rust
struct Field<'db> {
    declared_ty: Type<'db>,
    default_ty: Option<Type<'db>>,     // NamedTuple and dataclass only
    init_only: bool,                   // dataclass only  
    init: bool,                        // dataclass only
    is_required: Option<bool>,         // TypedDict only
}
```

**After:**
```rust
struct Field<'db> {
    declared_ty: Type<'db>,
    kind: FieldKind<'db>,
}

enum FieldKind<'db> {
    NamedTuple { default_ty: Option<Type<'db>> },
    Dataclass { default_ty: Option<Type<'db>>, init_only: bool, init: bool },
    TypedDict { is_required: bool },
}
```

## Test Plan
Updated Markdown tests

---------

Co-authored-by: David Peter <mail@david-peter.de>
2025-08-25 14:45:52 +02:00
Dhruv Manilawala 376e3ff395
[ty] Limit argument expansion size for overload call evaluation (#20041)
## Summary

This PR limits the argument type expansion size for an overload call
evaluation to 512.

The limit chosen is arbitrary but I've taken the 256 limit from Pyright
into account and bumped it x2 to start with.

Initially, I actually started out by trying to refactor the entire
argument type expansion to be lazy. Currently, expanding a single
argument at any position eagerly creates the combination (argument
lists) and returns that (`Vec<CallArguments>`) but I thought we could
make it lazier by converting the return type of `expand` from
`Iterator<Item = Vec<CallArguments>>` to `Iterator<Item = Iterator<Item
= CallArguments>>` but that's proving to be difficult to implement
mainly because we **need** to maintain the previous expansion to
generate the next expansion which is the main reason to use
`std::iter::successors` in the first place.

Another approach would be to eagerly expand all the argument types and
then use the `combinations` from `itertools` to generate the
combinations but we would need to find the "boundary" between arguments
lists produced from expanding argument at position 1 and position 2
because that's important for the algorithm.

Closes: https://github.com/astral-sh/ty/issues/868

## Test Plan

Add test case to demonstrate the limit along with the diagnostic
snapshot stating that the limit has been reached.
2025-08-25 09:43:04 +00:00
Jelle Zijlstra ec86a4e960
[ty] Add Top[] and Bottom[] special forms, replacing top_materialization_of() function (#20054)
Part of astral-sh/ty#994

## Summary

Add new special forms to `ty_extensions`, `Top[T]` and `Bottom[T]`.
Remove `ty_extensions.top_materialization` and
`ty_extensions.bottom_materialization`.

## Test Plan

Converted the existing `materialization.md` mdtest to the new syntax.
Added some tests for invalid use of the new special form.
2025-08-23 11:20:56 -07:00
Andrew Gallant e7237652a9 [ty] Lightly refactor document symbols AST visitor
This makes use of early continue/return to keep rightward drift under
control. (I also find it easier to read.)
2025-08-23 12:53:41 -04:00
Andrew Gallant 205eae14d2 [ty] Rejigger workspace symbols for more efficient caching
In effect, we make the Salsa query aspect keyed only on whether we want
global symbols. We move everything else (hierarchical and querying) to
an aggregate step *after* the query.

This was a somewhat involved change since we want to return a flattened
list from visiting the source while also preserving enough information
to reform the symbols into a hierarchical structure that the LSP
expects. But I think overall the API has gotten simpler and we encode
more invariants into the type system. (For example, previously you got a
runtime assertion if you tried to provide a query string while enabling
hierarchical mode. But now that's prevented by construction.)
2025-08-23 12:53:41 -04:00
Andrew Gallant f407f12f4c [ty] Parallelize workspace symbols
This is a pretty naive approach, but it makes cold times for listing
workspace symbols in home-assistant under 1s on my machine.

Courtesy of Micha:
https://github.com/astral-sh/ruff/pull/20030#discussion_r2292924171
2025-08-23 12:53:41 -04:00
Andrew Gallant fb2d0af18c [ty] Optimize "workspace symbols" retrieval
Basically, this splits the implementation into two pieces:
the first piece does the traversal and finds *all* symbols
across the workspace. The second piece does filtering based
on a user provided query string. Only the first piece is
cached by Salsa.

This brings warm "workspace symbols" requests down from
500-600ms to 100-200ms.
2025-08-23 12:53:41 -04:00
Andrew Gallant 8ead02e0b1 [ty] Optimize query string matching
While this doesn't typically matter, when ty returns a very
large list of symbols, this can have an impact. Specifically,
when searching `async` in home-assistant, this gets times
closer to 500ms versus closer to 600ms before this change.
It looks like an overall ~50ms improvement (so around 10%),
but variance is all over the place and I didn't do any
statistical tests.

But this does make intuitive sense. Previously, we were
allocating intermediate strings, doing UTF-8 decoding and
consulting Unicode casing tables. Now we're just doing what
is likely a single DFA scan. In effect, we front load all
of the Unicode junk into regex compilation.
2025-08-23 12:53:41 -04:00
Andrew Gallant 330bb4efbf [ty] Add some unit tests for "query matches symbol"
There is a small amount of subtlety to this matching routine,
and it could be implemented in a faster way. So let's right some
tests for what we have to ensure we don't break anything when
we optimize it.
2025-08-23 12:53:41 -04:00
Andrew Gallant ad8c98117a [ty] Move query filtering outside of symbol visitor
This is prep work for turning this into a Salsa query.
Specifically, we don't want the Salsa query to be
dependent upon the query string.
2025-08-23 12:53:41 -04:00
Andrew Gallant 06dbec8479 [ty] Add debug trace for workspace symbol elapsed time
Useful for ad hoc debugging, but it's also useful to have permanently to
enable serendipitous discovery of performance problems.
2025-08-23 12:53:41 -04:00
Andrew Gallant 85931ab594 [ty] Add a TODO for linting on `todo!` 2025-08-23 12:53:41 -04:00
Ibraheem Ahmed b3cc733f06
[ty] Remove duplicate global lint registry (#20053)
## Summary

Looks like an oversight at some point that led to two identical globals,
the one in `ty_project` just calls `ty_python_semantic::register_lints`.
2025-08-22 19:43:12 -04:00
Ibraheem Ahmed 7abc41727b
[ty] Shrink size of `AstNodeRef` (#20028)
## Summary

Removes the `module_ptr` field from `AstNodeRef` in release mode, and
change `NodeIndex` to a `NonZeroU32` to reduce the size of
`Option<AstNodeRef<_>>` fields.

I believe CI runs in debug mode, so this won't show up in the memory
report, but this reduces memory by ~2% in release mode.
2025-08-22 17:03:22 -04:00
chiri 886c4e4773
[`flake8-use-pathlib`] Fix `PTH211` autofix (#20049)
## Summary
Part of #20009
2025-08-22 13:35:08 -05:00
Alex Waygood bc6ea68733
[ty] Add precise iteration and unpacking inference for string literals and bytes literals (#20023)
## Summary

Previously we held off from doing this because we weren't sure that it
was worth the added complexity cost. But our code has changed in the
months since we made that initial decision, and I think the structure of
the code is such that it no longer really leads to much added complexity
to add precise inference when unpacking a string literal or a bytes
literal.

The improved inference we gain from this has real benefits to users (see
the mypy_primer report), and this PR doesn't appear to have a
performance impact.

## Test plan

mdtests
2025-08-22 19:33:08 +01:00