Commit Graph

8831 Commits

Author SHA1 Message Date
Brent Westbrook 17a1065fad
consolidate layout and preview checks 2025-12-12 10:05:24 -05:00
Brent Westbrook 93a958a734
share dangling comment logic 2025-12-12 09:53:47 -05:00
Brent Westbrook 4a66edf5f8
inline parameters_have_comments 2025-12-12 09:29:56 -05:00
Brent Westbrook b80fdfa1a5
move all body formatting to FormatBody 2025-12-11 15:52:43 -05:00
Brent Westbrook a48dc8ed64
inline current FormatBody 2025-12-11 15:45:07 -05:00
Brent Westbrook 3dfda9f6cc
unwrap format_body 2025-12-11 15:43:00 -05:00
Brent Westbrook 00745159d1
consolidate preview checks 2025-12-11 15:34:10 -05:00
Brent Westbrook 6f25547098
delete outdated preview comments 2025-12-11 15:24:53 -05:00
Brent Westbrook 854062f838
inline format_body 2025-12-11 15:16:44 -05:00
Brent Westbrook d1b79f5400
share common code 2025-12-11 15:13:47 -05:00
Brent Westbrook 5fee7534f7
save f.context.comments() to comments 2025-12-11 15:06:44 -05:00
Brent Westbrook 0f1ea90c5c
reposition dangling comments 2025-12-11 11:43:03 -05:00
Brent Westbrook 8711b1a301
Revert leading comment changes
This reverts commit 2cb98d4cdb.
2025-12-11 11:42:49 -05:00
Brent Westbrook 2cb98d4cdb
working for Micha's exact example 2025-12-11 10:34:35 -05:00
Brent Westbrook 9d7d94c4f0
factor out FormatBody 2025-12-10 16:03:06 -05:00
Brent Westbrook 553b45e27f
move dangling comment handling back out of placement.rs
Revert "re-apply 'pass preview to handle_lambda_comment'"

This reverts commit 33fcca9c53.
2025-12-10 15:58:48 -05:00
Brent Westbrook b96cf96e8b
add a few more call tests with comments 2025-12-10 14:06:08 -05:00
Brent Westbrook 8022343bd6
maybe_parenthesize_lambda -> maybe_parenthesize_value 2025-12-10 13:43:39 -05:00
Brent Westbrook 7c94e1026c
update lambda placement docs 2025-12-10 12:37:47 -05:00
Brent Westbrook f999da0eec
move tokenizer checks into the stable branch 2025-12-10 12:23:00 -05:00
Brent Westbrook a8bebaa681
add preview function taking a PreviewMode 2025-12-10 12:19:55 -05:00
Brent Westbrook 9f9b76b035
move preview comment handling mostly into placement.rs 2025-12-10 09:38:15 -05:00
Brent Westbrook 33fcca9c53
re-apply 'pass preview to handle_lambda_comment' 2025-12-10 09:11:00 -05:00
Brent Westbrook 6e9e42d343
factor out maybe_parenthesize_lambda 2025-12-10 09:04:00 -05:00
Brent Westbrook bb053f8388
fix lambda formatting in assignments 2025-12-10 08:51:54 -05:00
Brent Westbrook 05ff8f5cb1
Merge branch 'main' into brent/indent-lambda-params 2025-12-09 18:19:58 -05:00
Brent Westbrook f3714fd3c1
Fix leading comment formatting for lambdas with multiple parameters (#21879)
## Summary

This is a follow-up to #21868. As soon as I started merging #21868 into
#21385, I realized that I had missed a test case with `**kwargs` after
the `*args` parameter. Such a case is supposed to be formatted on one
line like:

```py
# input
(
    lambda
    # comment
    *x,
    **y: x
)

# output
(
    lambda
    # comment
    *x, **y: x
)
```

which you can still see on the
[playground](https://play.ruff.rs/bd88d339-1358-40d2-819f-865bfcb23aef?secondary=Format),
but on `main` after #21868, this was formatted as:

```py
(
    lambda
    # comment
    *x,
    **y: x
)
```

because the leading comment on the first parameter caused the whole
group around the parameters to break.

Instead of making these comments leading comments on the first
parameter, this PR makes them leading comments on the parameters list as
a whole.

## Test Plan

New tests, and I will also try merging this into #21385 _before_ opening
it for review this time.

<hr>

(labeling `internal` since #21868 should not be released before some
kind of fix)
2025-12-09 18:15:12 -05:00
Brent Westbrook 54c768989d
clean up deref 2025-12-09 17:42:25 -05:00
Brent Westbrook 829f10521f
Merge branch 'brent/fix-kwargs' into brent/indent-lambda-params 2025-12-09 17:26:25 -05:00
Brent Westbrook 2e0ee2e0a9
update lambda comment 2025-12-09 17:20:18 -05:00
Brent Westbrook acc49ac1e7
remove redundant parameters check
I believe parameters itself would be None if there were no next parameter
2025-12-09 17:17:26 -05:00
Brent Westbrook 2da4798def
update comments 2025-12-09 16:53:44 -05:00
Brent Westbrook 9d683da964
simplify a bit further 2025-12-09 16:51:55 -05:00
David Peter a9be810c38
[ty] Type inference for `@asynccontextmanager` (#21876)
## Summary

This PR adds special handling for `asynccontextmanager` calls as a
temporary solution for https://github.com/astral-sh/ty/issues/1804. We
will be able to remove this soon once we have support for generic
protocols in the solver.

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

## Ecosystem

```diff
+ tests/test_downloadermiddleware.py:305:56: error[invalid-argument-type] Argument to bound method `download` is incorrect: Expected `Spider`, found `Unknown | Spider | None`
+ tests/test_downloadermiddleware.py:305:56: warning[possibly-missing-attribute] Attribute `spider` may be missing on object of type `Crawler | None`
```
These look like true positives

```diff
+ pymongo/asynchronous/database.py:1021:35: error[invalid-assignment] Object of type `(AsyncClientSession & ~AlwaysTruthy & ~AlwaysFalsy) | (_ServerMode & ~AlwaysFalsy) | Unknown | Primary` is not assignable to `_ServerMode | None`
+ pymongo/asynchronous/database.py:1025:17: error[invalid-argument-type] Argument to bound method `_conn_for_reads` is incorrect: Expected `_ServerMode`, found `_ServerMode | None`
```

Known problems or true positives, just caused by the new type for
`session`

```diff
- src/integrations/prefect-sqlalchemy/prefect_sqlalchemy/database.py:269:16: error[invalid-return-type] Return type does not match returned value: expected `Connection | AsyncConnection`, found `_GeneratorContextManager[Unknown, None, None] | _AsyncGeneratorContextManager[Unknown, None] | Connection | AsyncConnection`
+ src/integrations/prefect-sqlalchemy/prefect_sqlalchemy/database.py:269:16: error[invalid-return-type] Return type does not match returned value: expected `Connection | AsyncConnection`, found `_GeneratorContextManager[Unknown, None, None] | _AsyncGeneratorContextManager[AsyncConnection, None] | Connection | AsyncConnection`
```

Just a more concrete type

```diff
- src/prefect/flow_engine.py:1277:24: error[missing-argument] No argument provided for required parameter `cls`
- src/prefect/server/api/server.py:696:49: error[missing-argument] No argument provided for required parameter `cls`
- src/prefect/task_engine.py:1426:24: error[missing-argument] No argument provided for required parameter `cls`
```

Good

## Test Plan

* Adapted and newly added Markdown tests
* Tested on internal codebase
2025-12-09 22:49:00 +01:00
Brent Westbrook f639389e6a
simplify check with parameters.start() == parameter.start()
and fix some names

the start check handles both the `are_parameters_parenthesized` check (because
lambda parameters cannot be parenthesized and thus nothing can come between the
start of the parameters and the first parameter) and the comparison with
first.range() since the parameters start where the first parameter starts
2025-12-09 16:48:15 -05:00
Brent Westbrook d722155762
make comments leading on parameter_s_ 2025-12-09 16:45:26 -05:00
Brent Westbrook 2bd64095a6
revert FormatParameters changes 2025-12-09 16:35:14 -05:00
Brent Westbrook b823866155
revert preview and comment placement changes 2025-12-09 16:34:01 -05:00
Brent Westbrook fd34cd6042
update snaps 2025-12-09 15:56:20 -05:00
Brent Westbrook 65c943568c
Merge branch 'brent/fix-kwargs' into brent/indent-lambda-params 2025-12-09 15:42:05 -05:00
Brent Westbrook b0a82983af
avoid breaking when the first parameter has leading comments 2025-12-09 15:32:24 -05:00
Brent Westbrook 90f43bde84
add broken test cases
the new leading comment is causing the whole Parameters list to break. these
cases should instead format like:

```py
(
    lambda
    # comment
    *x, **y: x
)

(
    lambda
    # comment 2
    *x, **y: x
)
```

without line breaks in the parameter list
2025-12-09 15:06:48 -05:00
Brent Westbrook 4ffbd496e3
Merge branch 'main' into brent/indent-lambda-params 2025-12-09 14:34:28 -05:00
Brent Westbrook 0bec5c0362
Fix comment placement in lambda parameters (#21868)
Summary
--

This PR makes two changes to comment placement in lambda parameters.
First, we
now insert a line break if the first parameter has a leading comment:

```py
# input
(
    lambda
    * # comment 2
    x:
    x
)

# main
(
    lambda # comment 2
    *x: x
)

# this PR
(
    lambda
	# comment 2
    *x: x
)
```

Note the missing space in the output from main. This case is currently
unstable
on main. Also note that the new formatting is more consistent with our
stable
formatting in cases where the lambda has its own dangling comment:

```py
# input
(
    lambda # comment 1
    * # comment 2
    x:
    x
)

# output
(
    lambda  # comment 1
    # comment 2
    *x: x
)
```

and when a parameter without a comment precedes the split `*x`:

```py
# input
(
    lambda y,
    * # comment 2
    x:
    x
)

# output
(
    lambda y,
    # comment 2
    *x: x
)
```

This does change the stable formatting, but I think such cases are rare
(expecting zero hits in the ecosystem report), this fixes an existing
instability, and it should not change any code we've previously
formatted.

Second, this PR modifies the comment placement such that `# comment 2`
in these
outputs is still a leading comment on the parameter. This is also not
the case
on main, where it becomes a [dangling lambda
comment](https://play.ruff.rs/3b29bb7e-70e4-4365-88e0-e60fe1857a35?secondary=Comments).
This doesn't cause any
instability that I'm aware of on main, but it does cause problems when
trying to
adjust the placement of dangling lambda comments in #21385. Changing the
placement in this way should not affect any formatting here.

Test Plan
--

New lambda tests, plus existing tests covering the cases above with
multiple
comments around the parameters (see lambda.py 122-143, and 122-205 or so
more
broadly)

I also checked manually that the comments are now leading on the
parameter:

```shell
❯ cargo run --bin ruff_python_formatter -- --emit stdout --target-version 3.10 --print-comments <<EOF
(
    lambda
        # comment 2
    *x: x
)
EOF
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.15s
     Running `target/debug/ruff_python_formatter --emit stdout --target-version 3.10 --print-comments`
# Comment decoration: Range, Preceding, Following, Enclosing, Comment
21..32, None, Some((Parameters, 37..39)), (ExprLambda, 6..42), "# comment 2"
{
    Node {
        kind: Parameter,
        range: 37..39,
        source: `*x`,
    }: {
        "leading": [
            SourceComment {
                text: "# comment 2",
                position: OwnLine,
                formatted: true,
            },
        ],
        "dangling": [],
        "trailing": [],
    },
}
(
    lambda
    # comment 2
    *x: x
)
```

But I didn't see a great place to put a test like this. Is there
somewhere I can assert this comment placement since it doesn't affect
any formatting yet? Or is it okay to wait until we use this in #21385?
2025-12-09 14:07:48 -05:00
Loïc Riegel 9490fbf1e1
[`pylint`] Detect subclasses of builtin exceptions (`PLW0133`) (#21382)
<!--
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? -->
Closes #17347

Goal is to detect the useless exception statement not just for builtin
exceptions but also custom (user defined) ones.

## Test Plan

<!-- How was it tested? -->
I added test cases in the rule fixture and updated the insta snapshot.
Note that I first moved up a test case case which was at the bottom to
the correct "violation category".
I wasn't sure if I should create new test cases or just insert inside
those tests. I know that ideally each test case should test only one
thing, but here, duplicating twice 12 test cases seemed very verbose,
and actually less maintainable in the future. The drawback is that the
diff in the snapshot is hard to review, sorry. But you can see that the
snapshot gives 38 diagnostics, which is what we expect.

Alternatively, I also created this file for manual testing.
```py
# tmp/test_error.py

class MyException(Exception):
    ...
class MyBaseException(BaseException):
    ...
class MyValueError(ValueError):
    ...
class MyExceptionCustom(Exception):
    ...
class MyBaseExceptionCustom(BaseException):
    ...
class MyValueErrorCustom(ValueError):
    ...
class MyDeprecationWarning(DeprecationWarning):
    ...
class MyDeprecationWarningCustom(MyDeprecationWarning):
    ...
class MyExceptionGroup(ExceptionGroup):
    ...
class MyExceptionGroupCustom(MyExceptionGroup):
    ...
class MyBaseExceptionGroup(ExceptionGroup):
    ...
class MyBaseExceptionGroupCustom(MyBaseExceptionGroup):
    ...


def foo():
    Exception("...")
    BaseException("...")
    ValueError("...")
    RuntimeError("...")
    DeprecationWarning("...")
    GeneratorExit("...")
    SystemExit("...")
    ExceptionGroup("eg", [ValueError(1), TypeError(2), OSError(3), OSError(4)])
    BaseExceptionGroup("eg", [ValueError(1), TypeError(2), OSError(3), OSError(4)])
    MyException("...")
    MyBaseException("...")
    MyValueError("...")
    MyExceptionCustom("...")
    MyBaseExceptionCustom("...")
    MyValueErrorCustom("...")
    MyDeprecationWarning("...")
    MyDeprecationWarningCustom("...")
    MyExceptionGroup("...")
    MyExceptionGroupCustom("...")
    MyBaseExceptionGroup("...")
    MyBaseExceptionGroupCustom("...")

```

and you can run this to check the PR:
```sh
target/debug/ruff check tmp/test_error.py --select PLW0133 --unsafe-fixes --diff --no-cache --isolated --target-version py310
target/debug/ruff check tmp/test_error.py --select PLW0133 --unsafe-fixes --diff --no-cache --isolated --target-version py314
```
2025-12-09 13:49:55 -05:00
Carl Meyer 8727a7b179
Fix stack overflow with recursive generic protocols (depth limit) (#21858)
## Summary

This fixes https://github.com/astral-sh/ty/issues/1736 where recursive
generic protocols with growing specializations caused a stack overflow.

The issue occurred with protocols like:
```python
class C[T](Protocol):
    a: 'C[set[T]]'
```

When checking `C[set[int]]` against e.g. `C[Unknown]`, member `a`
requires checking `C[set[set[int]]]`, which requires
`C[set[set[set[int]]]]`, etc. Each level has different type
specializations, so the existing cycle detection (using full types as
cache keys) didn't catch the infinite recursion.

This fix adds a simple recursion depth limit (64) to the CycleDetector.
When the depth exceeds the limit, we return the fallback value (assume
compatible) to safely terminate the recursion.

This is a bit of a blunt hammer, but it should be broadly effective to
prevent stack overflow in any nested-relation case, and it's hard to
imagine that non-recursive nested relation comparisons of depth > 64
exist much in the wild.

## Test Plan

Added mdtest.
2025-12-09 09:05:18 -08:00
Amethyst Reese 4e4d018344
New diagnostics for unused range suppressions (#21783)
Issue #3711
2025-12-09 08:30:27 -08:00
Andrew Gallant a9899af98a [ty] Use default settings in completion tests
This makes it so the test and production environments match.

Ref https://github.com/astral-sh/ruff/pull/21851#discussion_r2601579316
2025-12-09 10:42:46 -05:00
Brent Westbrook 711dd6eed4
pass preview to handle_lambda_comment 2025-12-09 10:27:45 -05:00
David Peter aea2bc2308
[ty] Infer type variables within generic unions (#21862)
## Summary

This PR allows our generics solver to find a solution for `T` in cases
like the following:
```py
def extract_t[T](x: P[T] | Q[T]) -> T:
    raise NotImplementedError

reveal_type(extract_t(P[int]()))  # revealed: int
reveal_type(extract_t(Q[str]()))  # revealed: str
```

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

## Ecosystem

The impact here looks very good!

It took me a long time to figure this out, but the new diagnostics on
bokeh are actually true positives. I should have tested with another
type-checker immediately, I guess. All other type checkers also emit
errors on these `__init__` calls. MRE
[here](https://play.ty.dev/5c19d260-65e2-4f70-a75e-1a25780843a2) (no
error on main, diagnostic on this branch)

A lot of false positives on home-assistant go away for calls to
functions like
[`async_listen`](180053fe98/homeassistant/core.py (L1581-L1587))
which take a `event_type: EventType[_DataT] | str` parameter. We can now
solve for `_DataT` here, which was previously falling back to its
default value, and then caused problems because it was used as an
argument to an invariant generic class.

## Test Plan

New Markdown tests
2025-12-09 16:22:59 +01:00