Commit graph

201 commits

Author SHA1 Message Date
Alex Waygood
4328df7226
[red-knot] type[T] is disjoint from type[S] if the metaclass of T is disjoint from the metaclass of S (#15547) 2025-01-17 10:41:36 +00:00
David Peter
6771b8ebd2
[red-knot] Pure instance variables declared in class body (#15515)
## Summary

This is a small, tentative step towards the bigger goal of understanding
instance attributes.

- Adds partial support for pure instance variables declared in the class
  body, i.e. this case:
  ```py
  class C:
      variable1: str = "a"
      variable2 = "b"

  reveal_type(C().variable1)  # str
  reveal_type(C().variable2)  # Unknown | Literal["b"]
  ```
- Adds `property` as a known class to query for `@property` decorators
- Splits up various `@Todo(instance attributes)` cases into
  sub-categories.

## Test Plan

Modified existing MD tests.
2025-01-17 10:48:20 +01:00
Alex Waygood
3950b00ee4
[red-knot] Implement disjointness for Instance types where the underlying class is @final (#15539)
## Summary

Closes https://github.com/astral-sh/ruff/issues/15508

For any two instance types `T` and `S`, we know they are disjoint if
either `T` is final and `T` is not a subclass of `S` or `S` is final and
`S` is not a subclass of `T`.

Correspondingly, for any two types `type[T]` and `S` where `S` is an
instance type, `type[T]` can be said to be disjoint from `S` if `S` is
disjoint from `U`, where `U` is the type that represents all instances
of `T`'s metaclass.

And a heterogeneous tuple type can be said to be disjoint from an
instance type if the instance type is disjoint from `tuple` (a type
representing all instances of the `tuple` class at runtime).

## Test Plan

- A new mdtest added. Most of our `is_disjoint_from()` tests are not
written as mdtests just yet, but it's pretty hard to test some of these
edge cases from a Rust unit test!
- Ran `QUICKCHECK_TESTS=1000000 cargo test --release -p
red_knot_python_semantic -- --ignored types::property_tests::stable`

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-01-16 23:48:52 +00:00
InSync
2e6729d900
[red-knot] Migrate bool/str/repr unit tests to Markdown tests (#15534)
## Summary

Part of #15397.

## Test Plan

Markdown tests.
2025-01-16 11:21:56 -08:00
InSync
6f0b66278f
[red-knot] Migrate is_fully_static/is_single_valued/is_singleton unit tests to Markdown tests (#15533)
## Summary

Part of #15397.

## Test Plan

Markdown tests.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-01-16 07:40:41 -08:00
Shaygan Hooshyari
cf4ab7cba1
Parse triple quoted string annotations as if parenthesized (#15387)
## Summary

Resolves #9467 

Parse quoted annotations as if the string content is inside parenthesis.
With this logic `x` and `y` in this example are equal:

```python
y: """
   int |
   str
"""

z: """(
    int |
    str
)
"""
```

Also this rule only applies to triple
quotes([link](https://github.com/python/typing-council/issues/9#issuecomment-1890808610)).

This PR is based on the
[comments](https://github.com/astral-sh/ruff/issues/9467#issuecomment-2579180991)
on the issue.

I did one extra change, since we don't want any indentation tokens I am
setting the `State::Other` as the initial state of the Lexer.

Remaining work:

- [x] Add a test case for red-knot.
- [x] Add more tests.

## Test Plan

Added a test which previously failed because quoted annotation contained
indentation.
Added an mdtest for red-knot.
Updated previous test.

Co-authored-by: Dhruv Manilawala <dhruvmanila@gmail.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-01-16 11:38:15 +05:30
David Peter
c034e280a9
[red-knot] Instance attributes: type inference clarifications (#15512)
## Summary

Some clarifications in the instance-attributes tests, mostly regarding
type inference behavior following this discussion:

https://github.com/astral-sh/ruff/pull/15474#discussion_r1917044566
2025-01-15 21:17:55 +01:00
Alex Waygood
49557a9129
[red-knot] Simplify object out of intersections (#15511) 2025-01-15 20:06:48 +00:00
David Peter
4f3209a3ec
[red-knot] More comprehensive 'is_subtype_of' tests (#15490)
## Summary

Make the `is_subtype_of` tests a bit easier to understand and
more comprehensive.
2025-01-15 18:33:29 +00:00
Alex Waygood
55a7f72035
[red-knot] Fix more edge cases for intersection simplification with LiteralString and AlwaysTruthy/AlwaysFalsy (#15496) 2025-01-15 15:02:41 +00:00
David Peter
8712438aec
[red-knot] Initial tests for instance attributes (#15474)
## Summary

Adds some initial tests for class and instance attributes, mostly to
document (and discuss) what we want to support eventually. These
tests are not exhaustive yet. The idea is to specify the coarse-grained
behavior first.

Things that we'll eventually want to test:

- Interplay with inheritance
- Support `Final` in addition to `ClassVar`
- Specific tests for `ClassVar`, like making sure that we support things
like `x: Annotated[ClassVar[int], "metadata"]`
- … or making sure that we raise an error here:
  ```py
  class Foo:
      def __init__(self):
          self.x: ClassVar[str] = "x"
  ```
- Add tests for `__new__` in addition to the tests for `__init__`
- Add tests that show that we use the union of types if multiple methods
define the symbol with different types
- Make sure that diagnostics are raised if, e.g., the inferred type of
an assignment within a method does not match the declared type in the
class body.
- https://github.com/astral-sh/ruff/pull/15474#discussion_r1916556284
- Method calls are completely left out for now.
- Same for `@property`
- … and the descriptor protocol

## Test Plan

New Markdown tests

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-01-15 14:43:41 +00:00
David Peter
3a6238d8c2
[red-knot] Typeshed sync and sys.platform fixes (#15492)
## Summary

The next sync of typeshed would have failed without manual changes
anyway, so I'm doing one manual sync + the required changes in our
`sys.platform` tests (which are necessary because of my tiny typeshed PR
here: https://github.com/python/typeshed/pull/13378).

closes #15485 (the next run of the pipeline in two weeks should be fine
as the bug has been fixed upstream)
2025-01-15 11:21:01 +01:00
InSync
aefb607405
[red-knot] Migrate is_equivalent_to unit tests to Markdown tests (#15470)
## Summary

Part of #15397, built on top of #15469.

## Test Plan

Markdown tests.
2025-01-14 18:57:23 +00:00
Alex Waygood
bcf0a715c2
[red-knot] Corrections and improvements to intersection simplification (#15475) 2025-01-14 18:15:38 +00:00
InSync
5ed7b55b15
[red-knot] Migrate is_subtype_of unit tests to Markdown tests (#15469)
## Summary

Part of #15397.

## Test Plan

Markdown tests.

---------

Co-authored-by: David Peter <mail@david-peter.de>
2025-01-14 15:57:24 +01:00
David Peter
8aac69bb2e
[red-knot] Add boundness and declaredness tests (#15453)
## Summary

This changeset adds new tests for public uses of symbols,
considering all possible declaredness and boundness states.

Note that this is a mere documentation of the current behavior. There is
still an [open ticket] questioning some of these choices (or unintential
behaviors).

## Test plan

Made sure that the respective test fails if I add the questionable case
again in `symbol_by_id`:

```rs
Symbol::Type(inferred_ty, Boundness::Bound) => {
    Symbol::Type(inferred_ty, Boundness::Bound)
}
```

[open ticket]: https://github.com/astral-sh/ruff/issues/14297
2025-01-14 13:07:16 +01:00
Carl Meyer
d54c19b983
[red-knot] remove CallOutcome::Cast variant (#15461)
## Summary

Simplification follow-up to #15413.

There's no need to have a dedicated `CallOutcome` variant for every
known function, it's only necessary if the special-cased behavior of the
known function includes emitting extra diagnostics. For `typing.cast`,
there's no such need; we can use the regular `Callable` outcome variant,
and update the return type according to the cast. (This is the same way
we already handle `len`.)

One reason to avoid proliferating unnecessary `CallOutcome` variants is
that currently we have to explicitly add emitting call-binding
diagnostics, for each outcome variant. So we were previously wrongly
silencing any binding diagnostics on calls to `typing.cast`. Fixing this
revealed a separate bug, that we were emitting a bogus error anytime
more than one keyword argument mapped to a `**kwargs` parameter. So this
PR also adds test and fix for that bug.

## Test Plan

Existing `cast` tests pass unchanged, added new test for `**kwargs` bug.
2025-01-13 10:58:53 -08:00
InSync
d1666fbbee
[red-knot] Add AlwaysTruthy and AlwaysFalsy to knot_extensions (#15437)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-01-12 17:00:57 +00:00
InSync
6ae3e8f8d7
[red-knot] Support cast (#15413)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-01-12 13:05:45 +00:00
David Peter
2d82445794
[red-knot] Simplify unions of T and ~T (#15400)
## Summary

Simplify unions of `T` and `~T` to `object`.

## Test Plan

Adapted existing tests.
2025-01-10 23:00:52 +01:00
David Peter
398f2e8b0c
[red-knot] Minor fixes in intersection-types tests (#15410)
## Summary

Minor fixes in intersection-types tests
2025-01-10 22:53:03 +01:00
InSync
232fbc1300
[red-knot] Understand type[Unknown] (#15409)
## Summary

Follow-up to #15194.

## Test Plan

Markdown tests.
2025-01-10 13:25:59 -08:00
InSync
6b98a26452
[red-knot] Support assert_type (#15194)
## Summary

See #15103.

## Test Plan

Markdown tests and unit tests.
2025-01-10 08:45:02 -08:00
David Peter
c87463842a
[red-knot] Move tuple-containing-Never tests to Markdown (#15402)
## Summary

See title.

Part of #15397

## Test Plan

Ran new Markdown test.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-01-10 15:31:30 +00:00
David Peter
f2c3ddc5ea
[red-knot] Move intersection type tests to Markdown (#15396)
## Summary

[**Rendered version of the new test
suite**](https://github.com/astral-sh/ruff/blob/david/intersection-type-tests/crates/red_knot_python_semantic/resources/mdtest/intersection_types.md)

Moves most of our existing intersection-types tests to a dedicated
Markdown test suite, extends the test coverage, unifies the notation for
these tests, groups tests into a proper structure, and adds some
explanations for various simplification strategies.

This changeset also:
- Adds a new simplification where `~Never` is removed from
intersections.
- Adds a new simplification where adding `~object` simplifies the whole
intersection to `Never`
- Avoids unnecessary assignment-checks between inferred and declared
type. This was added to this changeset to avoid many false positive
errors in this test suite.

Resolves the task described in this old comment
[here](e01da82a5a..e7e432bca2 (r1819924085)).

## Test Plan

Running the new Markdown tests

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-01-10 14:04:03 +01:00
David Peter
b33cf5baba
[red-knot] Move UnionBuilder tests to Markdown (#15374)
## Summary

This moves almost all of our existing `UnionBuilder` tests to a
Markdown-based test suite.

I see how this could be a more controversial change, since these tests
where written specifically for `UnionBuilder`, and by creating the union
types using Python type expressions, we add an additional layer on top
(parsing and inference of these expressions) that moves these tests away
from clean unit tests more in the direction of integration tests. Also,
there are probably a few implementation details of `UnionBuilder` hidden
in the test assertions (e.g. order of union elements after
simplifications).

That said, I think we would like to see all those properties that are
being tested here from *any* implementation of union types. And the
Markdown tests come with the usual advantages:

- More consice
- Better readability
- No re-compiliation when working on tests
- Easier to add additional explanations and structure to the test suite

This changeset adds a few additional tests, but keeps the logic of the
existing tests except for a few minor modifications for consistency.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: T-256 <132141463+T-256@users.noreply.github.com>
2025-01-09 21:45:06 +01:00
Carl Meyer
a95deec00f
[red-knot] handle synthetic 'self' argument in call-binding diagnostics (#15362) 2025-01-09 00:36:48 -08:00
InSync
21aa12a073
[red-knot] More precise inference for classes with non-class metaclasses (#15138)
## Summary

Resolves #14208.

## Test Plan

Markdown tests.

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-01-09 00:34:04 +00:00
David Peter
beb8e2dfe0
[red-knot] More comprehensive is_assignable_to tests (#15353)
## Summary

This changeset migrates all existing `is_assignable_to` tests to a
Markdown-based test. It also increases our test coverage in a hopefully
meaningful way (not claiming to be complete in any sense). But at least
I found and fixed one bug while doing so.

## Test Plan

Ran property tests to make sure the new test succeeds after fixing it.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-01-08 20:25:08 +01:00
David Peter
235fdfc57a
[red-knot] knot_extensions Python API (#15103)
## Summary

Adds a type-check-time Python API that allows us to create and
manipulate types and to test various of their properties. For example,
this can be used to write a Markdown test to make sure that `A & B` is a
subtype of `A` and `B`, but not of an unrelated class `C` (something
that requires quite a bit more code to do in Rust):
```py
from knot_extensions import Intersection, is_subtype_of, static_assert

class A: ...
class B: ...

type AB = Intersection[A, B]

static_assert(is_subtype_of(AB, A))
static_assert(is_subtype_of(AB, B))

class C: ...
static_assert(not is_subtype_of(AB, C))
```

I think this functionality is also helpful for interactive debugging
sessions, in order to query various properties of Red Knot's type
system. Which is something that otherwise requires a custom Rust unit
test, some boilerplate code and constant re-compilation.

## Test Plan

- New Markdown tests
- Tested the modified typeshed_sync workflow locally
2025-01-08 12:52:07 +01:00
Shaygan Hooshyari
03ff883626
Display Union of Literals as a Literal (#14993)
## Summary

Resolves #14988

Display union of Literals like other type checkers do.

With this change we lose the sorting behavior. And we show the types as
they appeared. So it's deterministic and tests should not be flaky.
This is similar to how Mypy [reveals the
type](https://mypy-play.net/?mypy=latest&python=3.12&gist=51ad03b153bfca3b940d5084345e230f).

In some cases this makes it harder to know what is the order in revealed
type when writing tests but since it's consistent after the test fails
we know the order.

## Test Plan

I adjusted mdtests for this change. Basically merged the int and string
types of the unions.

In cases where we have types other than numbers and strings like this
[one](https://github.com/astral-sh/ruff/pull/14993/files#diff-ac50bce02b9f0ad4dc7d6b8e1046d60dad919ac52d0aeb253e5884f89ea42bfeL51).
We only group the strings and numbers as the issue suggsted.

```
def _(flag: bool, flag2: bool):
    if flag:
        f = 1
    elif flag2:
        f = "foo"
    else:
        def f() -> int:
            return 1
    # error: "Object of type `Literal[1, "foo", f]` is not callable (due to union elements Literal[1], Literal["foo"])"
    # revealed: Unknown | int
    reveal_type(f())
```

[pyright
example](https://pyright-play.net/?code=GYJw9gtgBALgngBwJYDsDmUkQWEMoAySMApiAIYA2AUNQCYnBQD6AFMJeWgFxQBGYMJQA0UDlwBMvAUICU3alCWYm4nouWamAXigBGDUpKUkqzmimHNYqLoBEwQXavGAziQXXlDVa1lQAWgA%2BTBQYTy9rEBIYAFcQFH0rAGIoMnAQXjsAeT4AKxIAY3wwJngEEigAAyJSCkoAbT1RBydRYABdKsxXKBQwfEKqTj5KStY6WMqYMChYlCQwROMSCBIw3tqyKiaO0S36htawOw7ZZ01U6IA3EioSOl4AVRQAa36Ad0SAH1CYKxud0ozHKJHYflk1CAA)

[mypy
example](https://mypy-play.net/?mypy=latest&python=3.12&gist=31c8bdaa5521860cfeca4b92841cb3b7)

---------

Co-authored-by: Carl Meyer <carl@oddbird.net>
2025-01-08 00:58:38 +00:00
Douglas Creager
b2a0d68d70
Narrowing for class patterns in match statements (#15223)
We now support class patterns in a match statement, adding a narrowing
constraint that within the body of that match arm, we can assume that
the subject is an instance of that class.

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-01-07 15:58:12 -05:00
Carl Meyer
f2a86fcfda
[red-knot] add call checking (#15200)
## Summary

This implements checking of calls.

I ended up following Micha's original suggestion from back when the
signature representation was first introduced, and flattening it to a
single array of parameters. This turned out to be easier to manage,
because we can represent parameters using indices into that array, and
represent the bound argument types as an array of the same length.

Starred and double-starred arguments are still TODO; these won't be very
useful until we have generics.

The handling of diagnostics is just hacked into `return_ty_result`,
which was already inconsistent about whether it emitted diagnostics or
not; now it's even more inconsistent. This needs to be addressed, but
could be a follow-up.

The new benchmark errors here surface the need for intersection support
in `is_assignable_to`.

Fixes #14161.

## Test Plan

Added mdtests.
2025-01-07 20:39:45 +00:00
Alex Waygood
95294e657c
[red-knot] Eagerly normalize type[] types (#15272)
Co-authored-by: Carl Meyer <carl@astral.sh>
2025-01-07 12:53:07 +00:00
Douglas Creager
5e9259c96c
Don't special-case class instances in binary expression inference (#15161)
Just like in #15045 for unary expressions: In binary expressions, we
were only looking for dunder expressions for `Type::Instance` types. We
had some special cases for coercing the various `Literal` types into
their corresponding `Instance` types before doing the lookup. But we can
side-step all of that by using the existing `Type::to_meta_type` and
`Type::to_instance` methods.
2025-01-06 13:50:20 -05:00
Alex Waygood
980ce941c7
[red-knot] Minor simplifications and improvements to constraint narrowing logic (#15270) 2025-01-05 21:51:22 +00:00
Shaygan Hooshyari
b26448926a
Allow assigning ellipsis literal as parameter default value (#14982)
Resolves #14840

## Summary

Usage of ellipsis literal as default argument is allowed in stub files.

## Test Plan

Added mdtest for both python files and stub files.


---------

Co-authored-by: Carl Meyer <carl@oddbird.net>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-01-05 13:11:32 -06:00
Carl Meyer
2ea63620cf
[red-knot] fix control flow for assignment expressions in elif tests (#15274)
## Summary

The test expression in an `elif` clause is evaluated whether or not we
take the branch. Our control flow model for if/elif chains failed to
reflect this, causing wrong inference in cases where an assignment
expression occurs inside an `elif` test expression. Our "no branch taken
yet" snapshot (which is the starting state for every new elif branch)
can't simply be the pre-if state, it must be updated after visiting each
test expression.

Once we do this, it also means we no longer need to track a vector of
narrowing constraints to reapply for each new branch, since our "branch
not taken" state (which is the initial state for each branch) is
continuously updated to include the negative narrowing constraints of
all previous branches.

Fixes #15033.

## Test Plan

Added mdtests.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-01-05 18:35:29 +00:00
Micha Reiser
0caab81d3d
@no_type_check support (#15122)
Co-authored-by: Carl Meyer <carl@astral.sh>
2024-12-30 09:42:18 +00:00
Dhruv Manilawala
8a98d88847
[red-knot] Add diagnostic for invalid unpacking (#15086)
## Summary

Part of #13773 

This PR adds diagnostics when there is a length mismatch during
unpacking between the number of target expressions and the number of
types for the unpack value expression.

There are 3 cases of diagnostics here where the first two occurs when
there isn't a starred expression and the last one occurs when there's a
starred expression:
1. Number of target expressions is **less** than the number of types
that needs to be unpacked
2. Number of target expressions is **greater** then the number of types
that needs to be unpacked
3. When there's a starred expression as one of the target expression and
the number of target expressions is greater than the number of types

Examples for all each of the above cases:
```py
# red-knot: Too many values to unpack (expected 2, got 3) [lint:invalid-assignment]
a, b = (1, 2, 3)

# red-knot: Not enough values to unpack (expected 2, got 1) [lint:invalid-assignment]
a, b = (1,)

# red-knot: Not enough values to unpack (expected 3 or more, got 2) [lint:invalid-assignment]
a, *b, c, d = (1, 2)
```

The (3) case is a bit special because it uses a distinct wording
"expected n or more" instead of "expected n" because of the starred
expression.

### Location

The diagnostic location is the target expression that's being unpacked.
For nested targets, the location will be the nested expression. For
example:

```py
(a, (b, c), d) = (1, (2, 3, 4), 5)
#   ^^^^^^
#   red-knot: Too many values to unpack (expected 2, got 3) [lint:invalid-assignment]
```

For future improvements, it would be useful to show the context for why
this unpacking failed. For example, for why the expected number of
targets is `n`, we can highlight the relevant elements for the value
expression.

In the **ecosystem**, **Pyright** uses the target expressions for
location while **mypy** uses the value expression for the location. For
example:

```py
if 1:
#          mypy: Too many values to unpack (2 expected, 3 provided)  [misc]
#          vvvvvvvvv
	a, b = (1, 2, 3)
#   ^^^^
#   Pyright: Expression with type "tuple[Literal[1], Literal[2], Literal[3]]" cannot be assigned to target tuple
#     Type "tuple[Literal[1], Literal[2], Literal[3]]" is incompatible with target tuple
#       Tuple size mismatch; expected 2 but received 3 [reportAssignmentType]
#   red-knot: Too many values to unpack (expected 2, got 3) [lint:invalid-assignment]
```

## Test Plan

Update existing test cases TODO with the error directives.
2024-12-30 13:10:29 +05:30
Shantanu
bc3a735d93
Test explicit shadowing involving defs (#15174)
Co-authored-by: Carl Meyer <carl@astral.sh>
2024-12-29 00:47:03 +00:00
InSync
8d2d1a73c5
[red-knot] Report classes inheriting from bases with incompatible __slots__ (#15129) 2024-12-27 11:43:48 +00:00
Micha Reiser
6ed27c3786
Rename the knot|type-ignore mdtest files (#15147) 2024-12-26 10:25:05 +00:00
Micha Reiser
8d327087ef
Add invalid-ignore-comment rule (#15094) 2024-12-23 10:38:10 +00:00
Micha Reiser
2835d94ec5
Add unknown-rule (#15085)
Co-authored-by: Carl Meyer <carl@astral.sh>
2024-12-23 11:30:54 +01:00
Micha Reiser
2a99c0be02
Add unused-ignore-comment rule (#15084) 2024-12-23 11:15:28 +01:00
Micha Reiser
1c3d11e8a8
Support file-level type: ignore comments (#15081) 2024-12-23 09:59:04 +00:00
Micha Reiser
2f85749fa0
type: ignore[codes] and knot: ignore (#15078) 2024-12-23 10:52:43 +01:00
Dhruv Manilawala
113c804a62
[red-knot] Add support for unpacking for target (#15058)
## Summary

Related to #13773 

This PR adds support for unpacking `for` statement targets.

This involves updating the `value` field in the `Unpack` target to use
an enum which specifies the "where did the value expression came from?".
This is because for an iterable expression, we need to unpack the
iterator type while for assignment statement we need to unpack the value
type itself. And, this needs to be done in the unpack query.

### Question

One of the ways unpacking works in `for` statement is by looking at the
union of the types because if the iterable expression is a tuple then
the iterator type will be union of all the types in the tuple. This
means that the test cases that will test the unpacking in `for`
statement will also implicitly test the unpacking union logic. I was
wondering if it makes sense to merge these cases and only add the ones
that are specific to the union unpacking or for statement unpacking
logic.

## Test Plan

Add test cases involving iterating over a tuple type. I've intentionally
left out certain cases for now and I'm curious to know any thoughts on
the above query.
2024-12-23 06:13:49 +00:00
InSync
f764f59971
[red-knot] Treat classes as instances of their respective metaclasses in boolean tests (#15105)
## Summary

Follow-up to #15089.

## Test Plan

Markdown tests.

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2024-12-23 01:30:51 +00:00