Compare commits

...

21 Commits

Author SHA1 Message Date
David Peter
96e7ebb588 New KnownInstanceType 2025-11-26 13:24:21 +01:00
David Peter
54c88b599d Store binding context 2025-11-26 12:02:48 +01:00
David Peter
8ed96b04e4 Cleanup 2025-11-26 09:04:44 +01:00
David Peter
0a2536736b Better diagnostic message 2025-11-25 14:54:04 +01:00
David Peter
6aaa9d784a Fix problem with np.array related to type[T] 2025-11-25 12:04:41 +01:00
David Peter
d85469e94c Store definition in instance types 2025-11-25 09:56:21 +01:00
David Peter
f184132d69 Fix value-position specializations 2025-11-25 08:57:30 +01:00
David Peter
96c491099f Rename 2025-11-25 08:57:30 +01:00
David Peter
c1e6ecccc0 Patch panics for stringified annotations for now 2025-11-25 08:57:30 +01:00
David Peter
343c6b6287 Handle PEP 613 aliases as well 2025-11-25 08:57:30 +01:00
David Peter
f40ab81093 Handle attribute expressions as well 2025-11-25 08:57:30 +01:00
David Peter
eee6f25f2e Use assignment definition as typevar binding context 2025-11-25 08:57:30 +01:00
David Peter
013d43a2dd [ty] Generic implicit types aliases 2025-11-25 08:57:30 +01:00
Shunsuke Shibayama
dd15656deb [ty] fix ty playground initialization and vite optimization issues (#21471)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-11-25 07:42:56 +00:00
Alex Waygood
adf095e889 [ty] Extend Liskov checks to also cover classmethods and staticmethods (#21598)
## Summary

Building on https://github.com/astral-sh/ruff/pull/21436.

There's nothing conceptually more complicated about this, it just
requires its own set of tests and its own subdiagnostic hint.

I also uncovered another inconsistency between mypy/pyright/pyrefly,
which is fun. In this case, I suggest we go with pyright's behaviour.

## Test Plan

mdtests/snapshots
2025-11-24 23:14:06 +00:00
Alex Waygood
bfd65c4215 Dogfood ty on the scripts directory (#21617)
## Summary

This PR sets up CI jobs to run ty from the `main` branch on the files
and subdirectories in our `scripts` directory

## Test Plan

Both these commands pass for me locally:
- `uv run --project=./scripts cargo run -p ty check --project=./scripts`
- `uv run --project=./scripts/ty_benchmark cargo run -p ty check
--project=./scripts/ty_benchmark`
2025-11-24 23:13:44 +00:00
Jack O'Connor
0631e72187 [ty] support generic aliases in type[...], like type[C[int]] (#21552)
Closes https://github.com/astral-sh/ty/issues/1101.
2025-11-24 13:56:42 -08:00
Alex Waygood
bab688b76c [ty] Retain the function-like-ness of Callable types when binding self (#21614)
## Summary

For something like this:

```py
from typing import Callable

def my_lossy_decorator(fn: Callable[..., int]) -> Callable[..., int]:
    return fn

class MyClass:
    @my_lossy_decorator
    def method(self) -> int:
        return 42
```

we will currently infer the type of `MyClass.method` as a function-like
`Callable`, but we will infer the type of `MyClass().method` as a
`Callable` that is _not_ function-like. That's because a `CallableType`
currently "forgets" whether it was function-like or not during the
`bound_self` transformation:


a57e291311/crates/ty_python_semantic/src/types.rs (L10985-L10987)

This seems incorrect, and it's quite different to what we do when
binding the `self` parameter of `FunctionLiteral` types: `BoundMethod`
types are all seen as subtypes of function-like `Callable` supertypes --
here's `BoundMethodType::into_callable_type`:


a57e291311/crates/ty_python_semantic/src/types.rs (L10844-L10860)

The bug here is also causing lots of false positives in the ecosystem
report on https://github.com/astral-sh/ruff/pull/21611: a decorated
method on a subclass is currently not seen as validly overriding an
undecorated method with the same signature on a superclass, because the
undecorated superclass method is seen as function-like after binding
`self` whereas the decorated subclass method is not.

Fixing the bug required adding a new API in `protocol_class.rs`, because
it turns out that for our purposes in protocol subtyping/assignability,
we really do want a callable type to forget its function-like-ness when
binding `self`.

I initially tried out this change without changing anything in
`protocol_class.rs`. However, it resulted in many ecosystem false
positives and new false positives on the typing conformance test suite.
This is because it would mean that no protocol with a `__call__` method
would ever be seen as a subtype of a `Callable` type, since the
`__call__` method on the protocol would be seen as being function-like
whereas the `Callable` type would not be seen as function-like.

## Test Plan

Added an mdtest that fails on `main`
2025-11-24 21:14:03 +00:00
Douglas Creager
7e277667d1 [ty] Distinguish "unconstrained" from "constrained to any type" (#21539)
Before, we would collapse any constraint of the form `Never ≤ T ≤
object` down to the "always true" constraint set. This is correct in
terms of BDD semantics, but loses information, since "not constraining a
typevar at all" is different than "constraining a typevar to take on any
type". Once we get to specialization inference, we should fall back on
the typevar's default for the former, but not for the latter.

This is much easier to support now that we have a sequent map, since we
need to treat `¬(Never ≤ T ≤ object)` as being impossible, and prune it
when we walk through BDD paths, just like we do for other impossible
combinations.
2025-11-24 15:23:09 -05:00
Alex Waygood
d379f3826f Disable ty workspace diagnostics for VSCode users (#21620) 2025-11-24 20:06:09 +00:00
Matthew Mckee
6f9265d78d [ty] Double click to insert inlay hint (#21600)
<!--
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

Resolves
https://github.com/astral-sh/ty/issues/317#issuecomment-3567398107.

I can't get the auto import working great.

I haven't added many places where we specify that the type display is
invalid syntax.

## Test Plan

Nothing yet
2025-11-24 19:48:30 +00:00
38 changed files with 2131 additions and 420 deletions

View File

@@ -284,6 +284,10 @@ jobs:
run: cargo insta test --all-features --unreferenced reject --test-runner nextest
- name: Dogfood ty on py-fuzzer
run: uv run --project=./python/py-fuzzer cargo run -p ty check --project=./python/py-fuzzer
- name: Dogfood ty on the scripts directory
run: uv run --project=./scripts cargo run -p ty check --project=./scripts
- name: Dogfood ty on ty_benchmark
run: uv run --project=./scripts/ty_benchmark cargo run -p ty check --project=./scripts/ty_benchmark
# Check for broken links in the documentation.
- run: cargo doc --all --no-deps
env:

View File

@@ -5,5 +5,6 @@
"rust-analyzer.check.command": "clippy",
"search.exclude": {
"**/*.snap": true
}
},
"ty.diagnosticMode": "openFilesOnly"
}

138
crates/ty/docs/rules.md generated
View File

@@ -39,7 +39,7 @@ def test(): -> "int":
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L126" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L127" target="_blank">View source</a>
</small>
@@ -63,7 +63,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L170" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L171" target="_blank">View source</a>
</small>
@@ -95,7 +95,7 @@ f(int) # error
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L196" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L197" target="_blank">View source</a>
</small>
@@ -126,7 +126,7 @@ a = 1
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L221" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L222" target="_blank">View source</a>
</small>
@@ -158,7 +158,7 @@ class C(A, B): ...
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L247" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L248" target="_blank">View source</a>
</small>
@@ -190,7 +190,7 @@ class B(A): ...
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L312" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L313" target="_blank">View source</a>
</small>
@@ -217,7 +217,7 @@ class B(A, A): ...
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L333" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L334" target="_blank">View source</a>
</small>
@@ -329,7 +329,7 @@ def test(): -> "Literal[5]":
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L537" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L538" target="_blank">View source</a>
</small>
@@ -359,7 +359,7 @@ class C(A, B): ...
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L561" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L562" target="_blank">View source</a>
</small>
@@ -385,7 +385,7 @@ t[3] # IndexError: tuple index out of range
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L365" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L366" target="_blank">View source</a>
</small>
@@ -474,7 +474,7 @@ an atypical memory layout.
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L615" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L616" target="_blank">View source</a>
</small>
@@ -501,7 +501,7 @@ func("foo") # error: [invalid-argument-type]
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L655" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L656" target="_blank">View source</a>
</small>
@@ -529,7 +529,7 @@ a: int = ''
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1814" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1815" target="_blank">View source</a>
</small>
@@ -563,7 +563,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-await" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L677" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L678" target="_blank">View source</a>
</small>
@@ -599,7 +599,7 @@ asyncio.run(main())
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L707" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L708" target="_blank">View source</a>
</small>
@@ -623,7 +623,7 @@ class A(42): ... # error: [invalid-base]
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L758" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L759" target="_blank">View source</a>
</small>
@@ -650,7 +650,7 @@ with 1:
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L779" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L780" target="_blank">View source</a>
</small>
@@ -679,7 +679,7 @@ a: str
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L802" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L803" target="_blank">View source</a>
</small>
@@ -723,7 +723,7 @@ except ZeroDivisionError:
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L838" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L839" target="_blank">View source</a>
</small>
@@ -756,7 +756,7 @@ class C[U](Generic[T]): ...
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.17">0.0.1-alpha.17</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-key" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L582" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L583" target="_blank">View source</a>
</small>
@@ -795,7 +795,7 @@ carol = Person(name="Carol", age=25) # typo!
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L864" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L865" target="_blank">View source</a>
</small>
@@ -830,7 +830,7 @@ def f(t: TypeVar("U")): ...
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L961" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L962" target="_blank">View source</a>
</small>
@@ -864,7 +864,7 @@ class B(metaclass=f): ...
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-method-override" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1942" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1943" target="_blank">View source</a>
</small>
@@ -950,7 +950,7 @@ and `__ne__` methods accept `object` as their second argument.
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-named-tuple" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L511" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L512" target="_blank">View source</a>
</small>
@@ -982,7 +982,7 @@ TypeError: can only inherit from a NamedTuple type and Generic
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/1.0.0">1.0.0</a>) ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-newtype" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L937" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L938" target="_blank">View source</a>
</small>
@@ -1012,7 +1012,7 @@ Baz = NewType("Baz", int | str) # error: invalid base for `typing.NewType`
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L988" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L989" target="_blank">View source</a>
</small>
@@ -1062,7 +1062,7 @@ def foo(x: int) -> int: ...
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1087" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1088" target="_blank">View source</a>
</small>
@@ -1088,7 +1088,7 @@ def f(a: int = ''): ...
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-paramspec" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L892" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L893" target="_blank">View source</a>
</small>
@@ -1119,7 +1119,7 @@ P2 = ParamSpec("S2") # error: ParamSpec name must match the variable it's assig
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L447" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L448" target="_blank">View source</a>
</small>
@@ -1153,7 +1153,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1107" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1108" target="_blank">View source</a>
</small>
@@ -1202,7 +1202,7 @@ def g():
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L636" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L637" target="_blank">View source</a>
</small>
@@ -1227,7 +1227,7 @@ def func() -> int:
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1150" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1151" target="_blank">View source</a>
</small>
@@ -1285,7 +1285,7 @@ TODO #14889
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.6">0.0.1-alpha.6</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L916" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L917" target="_blank">View source</a>
</small>
@@ -1312,7 +1312,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1189" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1190" target="_blank">View source</a>
</small>
@@ -1342,7 +1342,7 @@ TYPE_CHECKING = ''
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1213" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1214" target="_blank">View source</a>
</small>
@@ -1372,7 +1372,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.11">0.0.1-alpha.11</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1265" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1266" target="_blank">View source</a>
</small>
@@ -1406,7 +1406,7 @@ f(10) # Error
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.11">0.0.1-alpha.11</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1237" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1238" target="_blank">View source</a>
</small>
@@ -1440,7 +1440,7 @@ class C:
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1293" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1294" target="_blank">View source</a>
</small>
@@ -1475,7 +1475,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1322" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1323" target="_blank">View source</a>
</small>
@@ -1500,7 +1500,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-typed-dict-key" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1915" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1916" target="_blank">View source</a>
</small>
@@ -1533,7 +1533,7 @@ alice["age"] # KeyError
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1341" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1342" target="_blank">View source</a>
</small>
@@ -1562,7 +1562,7 @@ func("string") # error: [no-matching-overload]
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1364" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1365" target="_blank">View source</a>
</small>
@@ -1586,7 +1586,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1382" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1383" target="_blank">View source</a>
</small>
@@ -1612,7 +1612,7 @@ for i in 34: # TypeError: 'int' object is not iterable
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1433" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1434" target="_blank">View source</a>
</small>
@@ -1639,7 +1639,7 @@ f(1, x=2) # Error raised here
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20positional-only-parameter-as-kwarg" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1668" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1669" target="_blank">View source</a>
</small>
@@ -1697,7 +1697,7 @@ def test(): -> "int":
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1790" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1791" target="_blank">View source</a>
</small>
@@ -1727,7 +1727,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1524" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1525" target="_blank">View source</a>
</small>
@@ -1756,7 +1756,7 @@ class B(A): ... # Error raised here
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1569" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1570" target="_blank">View source</a>
</small>
@@ -1783,7 +1783,7 @@ f("foo") # Error raised here
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1547" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1548" target="_blank">View source</a>
</small>
@@ -1811,7 +1811,7 @@ def _(x: int):
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1590" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1591" target="_blank">View source</a>
</small>
@@ -1857,7 +1857,7 @@ class A:
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1647" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1648" target="_blank">View source</a>
</small>
@@ -1884,7 +1884,7 @@ f(x=1, y=2) # Error raised here
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1689" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1690" target="_blank">View source</a>
</small>
@@ -1912,7 +1912,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1711" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1712" target="_blank">View source</a>
</small>
@@ -1937,7 +1937,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1730" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1731" target="_blank">View source</a>
</small>
@@ -1962,7 +1962,7 @@ print(x) # NameError: name 'x' is not defined
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1402" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1403" target="_blank">View source</a>
</small>
@@ -1999,7 +1999,7 @@ b1 < b2 < b1 # exception raised here
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1749" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1750" target="_blank">View source</a>
</small>
@@ -2027,7 +2027,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1771" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1772" target="_blank">View source</a>
</small>
@@ -2052,7 +2052,7 @@ l[1:10:0] # ValueError: slice step cannot be zero
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20ambiguous-protocol-member" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L476" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L477" target="_blank">View source</a>
</small>
@@ -2093,7 +2093,7 @@ class SubProto(BaseProto, Protocol):
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.16">0.0.1-alpha.16</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20deprecated" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L291" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L292" target="_blank">View source</a>
</small>
@@ -2181,7 +2181,7 @@ a = 20 / 0 # type: ignore
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-attribute" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1454" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1455" target="_blank">View source</a>
</small>
@@ -2209,7 +2209,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-implicit-call" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L144" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L145" target="_blank">View source</a>
</small>
@@ -2241,7 +2241,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-import" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1476" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1477" target="_blank">View source</a>
</small>
@@ -2273,7 +2273,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1842" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1843" target="_blank">View source</a>
</small>
@@ -2300,7 +2300,7 @@ cast(int, f()) # Redundant
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1629" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1630" target="_blank">View source</a>
</small>
@@ -2324,7 +2324,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.15">0.0.1-alpha.15</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1863" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1864" target="_blank">View source</a>
</small>
@@ -2382,7 +2382,7 @@ def g():
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.7">0.0.1-alpha.7</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L725" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L726" target="_blank">View source</a>
</small>
@@ -2421,7 +2421,7 @@ class D(C): ... # error: [unsupported-base]
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20useless-overload-body" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1031" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1032" target="_blank">View source</a>
</small>
@@ -2484,7 +2484,7 @@ def foo(x: int | str) -> int | str:
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a>) ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L273" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L274" target="_blank">View source</a>
</small>
@@ -2508,7 +2508,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1502" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1503" target="_blank">View source</a>
</small>

View File

@@ -6,8 +6,8 @@ use ruff_db::parsed::parsed_module;
use ruff_python_ast::visitor::source_order::{self, SourceOrderVisitor, TraversalSignal};
use ruff_python_ast::{AnyNodeRef, ArgOrKeyword, Expr, ExprUnaryOp, Stmt, UnaryOp};
use ruff_text_size::{Ranged, TextRange, TextSize};
use ty_python_semantic::types::Type;
use ty_python_semantic::types::ide_support::inlay_hint_call_argument_details;
use ty_python_semantic::types::{Type, TypeDetail};
use ty_python_semantic::{HasType, SemanticModel};
#[derive(Debug, Clone)]
@@ -15,10 +15,12 @@ pub struct InlayHint {
pub position: TextSize,
pub kind: InlayHintKind,
pub label: InlayHintLabel,
pub text_edits: Vec<InlayHintTextEdit>,
}
impl InlayHint {
fn variable_type(position: TextSize, ty: Type, db: &dyn Db) -> Self {
fn variable_type(expr: &Expr, ty: Type, db: &dyn Db, allow_edits: bool) -> Self {
let position = expr.range().end();
// Render the type to a string, and get subspans for all the types that make it up
let details = ty.display(db).to_string_parts();
@@ -34,7 +36,7 @@ impl InlayHint {
let mut label_parts = vec![": ".into()];
for (target, detail) in details.targets.iter().zip(&details.details) {
match detail {
ty_python_semantic::types::TypeDetail::Type(ty) => {
TypeDetail::Type(ty) => {
let start = target.start().to_usize();
let end = target.end().to_usize();
// If we skipped over some bytes, push them with no target
@@ -50,9 +52,9 @@ impl InlayHint {
offset = end;
}
}
ty_python_semantic::types::TypeDetail::SignatureStart
| ty_python_semantic::types::TypeDetail::SignatureEnd
| ty_python_semantic::types::TypeDetail::Parameter(_) => {
TypeDetail::SignatureStart
| TypeDetail::SignatureEnd
| TypeDetail::Parameter(_) => {
// Don't care about these
}
}
@@ -62,10 +64,20 @@ impl InlayHint {
label_parts.push(details.label[offset..details.label.len()].into());
}
let text_edits = if details.is_valid_syntax && allow_edits {
vec![InlayHintTextEdit {
range: TextRange::new(position, position),
new_text: format!(": {}", details.label),
}]
} else {
vec![]
};
Self {
position,
kind: InlayHintKind::Type,
label: InlayHintLabel { parts: label_parts },
text_edits,
}
}
@@ -83,6 +95,7 @@ impl InlayHint {
position,
kind: InlayHintKind::CallArgumentName,
label: InlayHintLabel { parts: label_parts },
text_edits: vec![],
}
}
@@ -175,6 +188,12 @@ impl From<&str> for InlayHintLabelPart {
}
}
#[derive(Debug, Clone)]
pub struct InlayHintTextEdit {
pub range: TextRange,
pub new_text: String,
}
pub fn inlay_hints(
db: &dyn Db,
file: File,
@@ -234,6 +253,7 @@ struct InlayHintVisitor<'a, 'db> {
in_assignment: bool,
range: TextRange,
settings: &'a InlayHintSettings,
in_no_edits_allowed: bool,
}
impl<'a, 'db> InlayHintVisitor<'a, 'db> {
@@ -245,15 +265,16 @@ impl<'a, 'db> InlayHintVisitor<'a, 'db> {
in_assignment: false,
range,
settings,
in_no_edits_allowed: false,
}
}
fn add_type_hint(&mut self, position: TextSize, ty: Type<'db>) {
fn add_type_hint(&mut self, expr: &Expr, ty: Type<'db>, allow_edits: bool) {
if !self.settings.variable_types {
return;
}
let inlay_hint = InlayHint::variable_type(position, ty, self.db);
let inlay_hint = InlayHint::variable_type(expr, ty, self.db, allow_edits);
self.hints.push(inlay_hint);
}
@@ -297,9 +318,13 @@ impl SourceOrderVisitor<'_> for InlayHintVisitor<'_, '_> {
match stmt {
Stmt::Assign(assign) => {
self.in_assignment = !type_hint_is_excessive_for_expr(&assign.value);
if !annotations_are_valid_syntax(assign) {
self.in_no_edits_allowed = true;
}
for target in &assign.targets {
self.visit_expr(target);
}
self.in_no_edits_allowed = false;
self.in_assignment = false;
self.visit_expr(&assign.value);
@@ -325,7 +350,7 @@ impl SourceOrderVisitor<'_> for InlayHintVisitor<'_, '_> {
if self.in_assignment {
if name.ctx.is_store() {
let ty = expr.inferred_type(&self.model);
self.add_type_hint(expr.range().end(), ty);
self.add_type_hint(expr, ty, !self.in_no_edits_allowed);
}
}
source_order::walk_expr(self, expr);
@@ -334,7 +359,7 @@ impl SourceOrderVisitor<'_> for InlayHintVisitor<'_, '_> {
if self.in_assignment {
if attribute.ctx.is_store() {
let ty = expr.inferred_type(&self.model);
self.add_type_hint(expr.range().end(), ty);
self.add_type_hint(expr, ty, !self.in_no_edits_allowed);
}
}
source_order::walk_expr(self, expr);
@@ -420,6 +445,22 @@ fn type_hint_is_excessive_for_expr(expr: &Expr) -> bool {
}
}
fn annotations_are_valid_syntax(stmt_assign: &ruff_python_ast::StmtAssign) -> bool {
if stmt_assign.targets.len() > 1 {
return false;
}
if stmt_assign
.targets
.iter()
.any(|target| matches!(target, Expr::Tuple(_)))
{
return false;
}
true
}
#[cfg(test)]
mod tests {
use super::*;
@@ -427,6 +468,7 @@ mod tests {
use crate::NavigationTarget;
use crate::tests::IntoDiagnostic;
use insta::{assert_snapshot, internals::SettingsBindDropGuard};
use itertools::Itertools;
use ruff_db::{
diagnostic::{
Annotation, Diagnostic, DiagnosticFormat, DiagnosticId, DisplayDiagnosticConfig,
@@ -517,12 +559,15 @@ mod tests {
fn inlay_hints_with_settings(&mut self, settings: &InlayHintSettings) -> String {
let hints = inlay_hints(&self.db, self.file, self.range, settings);
let mut buf = source_text(&self.db, self.file).as_str().to_string();
let mut inlay_hint_buf = source_text(&self.db, self.file).as_str().to_string();
let mut text_edit_buf = inlay_hint_buf.clone();
let mut tbd_diagnostics = Vec::new();
let mut offset = 0;
let mut edit_offset = 0;
for hint in hints {
let end_position = hint.position.to_usize() + offset;
let mut hint_str = "[".to_string();
@@ -538,36 +583,65 @@ mod tests {
hint_str.push_str(part.text());
}
for edit in hint.text_edits {
let start = edit.range.start().to_usize() + edit_offset;
let end = edit.range.end().to_usize() + edit_offset;
text_edit_buf.replace_range(start..end, &edit.new_text);
if start == end {
edit_offset += edit.new_text.len();
} else {
edit_offset += edit.new_text.len() - edit.range.len().to_usize();
}
}
hint_str.push(']');
offset += hint_str.len();
buf.insert_str(end_position, &hint_str);
inlay_hint_buf.insert_str(end_position, &hint_str);
}
self.db.write_file("main2.py", &buf).unwrap();
self.db.write_file("main2.py", &inlay_hint_buf).unwrap();
let inlayed_file =
system_path_to_file(&self.db, "main2.py").expect("newly written file to existing");
let diagnostics = tbd_diagnostics.into_iter().map(|(label_range, target)| {
let location_diagnostics = tbd_diagnostics.into_iter().map(|(label_range, target)| {
InlayHintLocationDiagnostic::new(FileRange::new(inlayed_file, label_range), &target)
});
let mut rendered_diagnostics = self.render_diagnostics(diagnostics);
let mut rendered_diagnostics = location_diagnostics
.map(|diagnostic| self.render_diagnostic(diagnostic))
.join("");
if !rendered_diagnostics.is_empty() {
rendered_diagnostics = format!(
"{}{}",
crate::MarkupKind::PlainText.horizontal_line(),
rendered_diagnostics
.strip_suffix("\n")
.unwrap_or(&rendered_diagnostics)
);
}
format!("{buf}{rendered_diagnostics}",)
let rendered_edit_diagnostic = if edit_offset != 0 {
let edit_diagnostic = InlayHintEditDiagnostic::new(text_edit_buf);
let text_edit_buf = self.render_diagnostic(edit_diagnostic);
format!(
"{}{}",
crate::MarkupKind::PlainText.horizontal_line(),
text_edit_buf
)
} else {
String::new()
};
format!("{inlay_hint_buf}{rendered_diagnostics}{rendered_edit_diagnostic}",)
}
fn render_diagnostics<I, D>(&self, diagnostics: I) -> String
fn render_diagnostic<D>(&self, diagnostic: D) -> String
where
I: IntoIterator<Item = D>,
D: IntoDiagnostic,
{
use std::fmt::Write;
@@ -578,10 +652,8 @@ mod tests {
.color(false)
.format(DiagnosticFormat::Full);
for diagnostic in diagnostics {
let diag = diagnostic.into_diagnostic();
write!(buf, "{}", diag.display(&self.db, &config)).unwrap();
}
let diag = diagnostic.into_diagnostic();
write!(buf, "{}", diag.display(&self.db, &config)).unwrap();
buf
}
@@ -728,6 +800,20 @@ mod tests {
10 | bb[: Literal[b"foo"]] = aa
| ^^^^^^
|
---------------------------------------------
info[inlay-hint-edit]: File after edits
info: Source
def i(x: int, /) -> int:
return x
x = 1
y: Literal[1] = x
z: int = i(1)
w: int = z
aa = b'foo'
bb: Literal[b"foo"] = aa
"#);
}
@@ -1321,6 +1407,20 @@ mod tests {
10 | w[: tuple[int, str]] = z
| ^^^
|
---------------------------------------------
info[inlay-hint-edit]: File after edits
info: Source
def i(x: int, /) -> int:
return x
def s(x: str, /) -> str:
return x
x = (1, 'abc')
y: tuple[Literal[1], Literal["abc"]] = x
z: tuple[int, str] = (i(1), s('abc'))
w: tuple[int, str] = z
"#);
}
@@ -1654,6 +1754,18 @@ mod tests {
8 | w[: int] = z
| ^^^
|
---------------------------------------------
info[inlay-hint-edit]: File after edits
info: Source
def i(x: int, /) -> int:
return x
x: int = 1
y: Literal[1] = x
z: int = i(1)
w: int = z
"#);
}
@@ -1691,6 +1803,15 @@ mod tests {
| ^^^
5 | z = x
|
---------------------------------------------
info[inlay-hint-edit]: File after edits
info: Source
def i(x: int, /) -> int:
return x
x: int = i(1)
z = x
"#);
}
@@ -1810,6 +1931,18 @@ mod tests {
8 | a.y[: int] = int(3)
| ^^^
|
---------------------------------------------
info[inlay-hint-edit]: File after edits
info: Source
class A:
def __init__(self, y):
self.x: int = int(1)
self.y: Unknown = y
a: A = A(2)
a.y: int = int(3)
"#);
}
@@ -2640,6 +2773,22 @@ mod tests {
12 | k[: list[Unknown | int | float]] = [-1, -2.0]
| ^^^^^
|
---------------------------------------------
info[inlay-hint-edit]: File after edits
info: Source
a: list[Unknown | int] = [1, 2]
b: list[Unknown | float] = [1.0, 2.0]
c: list[Unknown | bool] = [True, False]
d: list[Unknown | None] = [None, None]
e: list[Unknown | str] = ["hel", "lo"]
f: list[Unknown | str] = ['the', 're']
g: list[Unknown | str] = [f"{ft}", f"{ft}"]
h: list[Unknown | Template] = [t"wow %d", t"wow %d"]
i: list[Unknown | bytes] = [b'/x01', b'/x02']
j: list[Unknown | int | float] = [+1, +2.0]
k: list[Unknown | int | float] = [-1, -2.0]
"#);
}
@@ -2811,6 +2960,19 @@ mod tests {
9 | c[: MyClass], d[: MyClass] = (MyClass(), MyClass())
| ^^^^^^^
|
---------------------------------------------
info[inlay-hint-edit]: File after edits
info: Source
class MyClass:
def __init__(self):
self.x: int = 1
x: MyClass = MyClass()
y: tuple[MyClass, MyClass] = (MyClass(), MyClass())
a, b = MyClass(), MyClass()
c, d = (MyClass(), MyClass())
"#);
}
@@ -3681,6 +3843,20 @@ mod tests {
10 | c[: MyClass[Unknown | int, str]], d[: MyClass[Unknown | int, str]] = (MyClass([x=][42], [y=]("a", "b")), MyClass([x=][42], [y=]("a", "…
| ^
|
---------------------------------------------
info[inlay-hint-edit]: File after edits
info: Source
class MyClass[T, U]:
def __init__(self, x: list[T], y: tuple[U, U]):
self.x = x
self.y = y
x: MyClass[Unknown | int, str] = MyClass([42], ("a", "b"))
y: tuple[MyClass[Unknown | int, str], MyClass[Unknown | int, str]] = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b")))
a, b = MyClass([42], ("a", "b")), MyClass([42], ("a", "b"))
c, d = (MyClass([42], ("a", "b")), MyClass([42], ("a", "b")))
"#);
}
@@ -3836,6 +4012,20 @@ mod tests {
10 | foo([x=]val.y)
| ^
|
---------------------------------------------
info[inlay-hint-edit]: File after edits
info: Source
def foo(x: int): pass
class MyClass:
def __init__(self):
self.x: int = 1
self.y: int = 2
val: MyClass = MyClass()
foo(val.x)
foo(val.y)
");
}
@@ -3901,6 +4091,20 @@ mod tests {
10 | foo([x=]x.y)
| ^
|
---------------------------------------------
info[inlay-hint-edit]: File after edits
info: Source
def foo(x: int): pass
class MyClass:
def __init__(self):
self.x: int = 1
self.y: int = 2
x: MyClass = MyClass()
foo(x.x)
foo(x.y)
");
}
@@ -3969,6 +4173,22 @@ mod tests {
12 | foo([x=]val.y())
| ^
|
---------------------------------------------
info[inlay-hint-edit]: File after edits
info: Source
def foo(x: int): pass
class MyClass:
def __init__(self):
def x() -> int:
return 1
def y() -> int:
return 2
val: MyClass = MyClass()
foo(val.x())
foo(val.y())
");
}
@@ -4043,6 +4263,24 @@ mod tests {
14 | foo([x=]val.y()[1])
| ^
|
---------------------------------------------
info[inlay-hint-edit]: File after edits
info: Source
from typing import List
def foo(x: int): pass
class MyClass:
def __init__(self):
def x() -> List[int]:
return 1
def y() -> List[int]:
return 2
val: MyClass = MyClass()
foo(val.x()[0])
foo(val.y()[1])
");
}
@@ -4193,6 +4431,17 @@ mod tests {
7 | foo([x=]y[0])
| ^
|
---------------------------------------------
info[inlay-hint-edit]: File after edits
info: Source
def foo(x: int): pass
x: list[Unknown | int] = [1]
y: list[Unknown | int] = [2]
foo(x[0])
foo(y[0])
"#);
}
@@ -4378,6 +4627,15 @@ mod tests {
5 | f[: Foo] = Foo([x=]1)
| ^
|
---------------------------------------------
info[inlay-hint-edit]: File after edits
info: Source
class Foo:
def __init__(self, x: int): pass
Foo(1)
f: Foo = Foo(1)
");
}
@@ -4450,6 +4708,15 @@ mod tests {
5 | f[: Foo] = Foo([x=]1)
| ^
|
---------------------------------------------
info[inlay-hint-edit]: File after edits
info: Source
class Foo:
def __new__(cls, x: int): pass
Foo(1)
f: Foo = Foo(1)
");
}
@@ -5197,6 +5464,15 @@ mod tests {
| ^^^^^^^^^^^^^
5 | my_func(x="hello")
|
---------------------------------------------
info[inlay-hint-edit]: File after edits
info: Source
from typing import LiteralString
def my_func(x: LiteralString):
y: LiteralString = x
my_func(x="hello")
"#);
}
@@ -5339,6 +5615,23 @@ mod tests {
13 | y[: Literal[1, 2, 3, "hello"] | None] = x
| ^^^^
|
---------------------------------------------
info[inlay-hint-edit]: File after edits
info: Source
def branch(cond: int):
if cond < 10:
x = 1
elif cond < 20:
x = 2
elif cond < 30:
x = 3
elif cond < 40:
x = "hello"
else:
x = None
y: Literal[1, 2, 3, "hello"] | None = x
"#);
}
@@ -5454,6 +5747,13 @@ mod tests {
3 | y[: type[list[str]]] = type(x)
| ^^^
|
---------------------------------------------
info[inlay-hint-edit]: File after edits
info: Source
def f(x: list[str]):
y: type[list[str]] = type(x)
"#);
}
@@ -5493,6 +5793,16 @@ mod tests {
6 | ab[: property] = F.whatever
| ^^^^^^^^
|
---------------------------------------------
info[inlay-hint-edit]: File after edits
info: Source
class F:
@property
def whatever(self): ...
ab: property = F.whatever
");
}
@@ -5810,6 +6120,180 @@ mod tests {
");
}
#[test]
fn test_function_signature_inlay_hint() {
let mut test = inlay_hint_test(
"
def foo(x: int, *y: bool, z: str | int | list[str]): ...
a = foo",
);
assert_snapshot!(test.inlay_hints(), @r#"
def foo(x: int, *y: bool, z: str | int | list[str]): ...
a[: def foo(x: int, *y: bool, *, z: str | int | list[str]) -> Unknown] = foo
---------------------------------------------
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:348:7
|
347 | @disjoint_base
348 | class int:
| ^^^
349 | """int([x]) -> integer
350 | int(x, base=10) -> integer
|
info: Source
--> main2.py:4:16
|
2 | def foo(x: int, *y: bool, z: str | int | list[str]): ...
3 |
4 | a[: def foo(x: int, *y: bool, *, z: str | int | list[str]) -> Unknown] = foo
| ^^^
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2591:7
|
2590 | @final
2591 | class bool(int):
| ^^^^
2592 | """Returns True when the argument is true, False otherwise.
2593 | The builtins True and False are the only two instances of the class bool.
|
info: Source
--> main2.py:4:25
|
2 | def foo(x: int, *y: bool, z: str | int | list[str]): ...
3 |
4 | a[: def foo(x: int, *y: bool, *, z: str | int | list[str]) -> Unknown] = foo
| ^^^^
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:915:7
|
914 | @disjoint_base
915 | class str(Sequence[str]):
| ^^^
916 | """str(object='') -> str
917 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
info: Source
--> main2.py:4:37
|
2 | def foo(x: int, *y: bool, z: str | int | list[str]): ...
3 |
4 | a[: def foo(x: int, *y: bool, *, z: str | int | list[str]) -> Unknown] = foo
| ^^^
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:348:7
|
347 | @disjoint_base
348 | class int:
| ^^^
349 | """int([x]) -> integer
350 | int(x, base=10) -> integer
|
info: Source
--> main2.py:4:43
|
2 | def foo(x: int, *y: bool, z: str | int | list[str]): ...
3 |
4 | a[: def foo(x: int, *y: bool, *, z: str | int | list[str]) -> Unknown] = foo
| ^^^
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:2802:7
|
2801 | @disjoint_base
2802 | class list(MutableSequence[_T]):
| ^^^^
2803 | """Built-in mutable sequence.
|
info: Source
--> main2.py:4:49
|
2 | def foo(x: int, *y: bool, z: str | int | list[str]): ...
3 |
4 | a[: def foo(x: int, *y: bool, *, z: str | int | list[str]) -> Unknown] = foo
| ^^^^
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/builtins.pyi:915:7
|
914 | @disjoint_base
915 | class str(Sequence[str]):
| ^^^
916 | """str(object='') -> str
917 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
info: Source
--> main2.py:4:54
|
2 | def foo(x: int, *y: bool, z: str | int | list[str]): ...
3 |
4 | a[: def foo(x: int, *y: bool, *, z: str | int | list[str]) -> Unknown] = foo
| ^^^
|
info[inlay-hint-location]: Inlay Hint Target
--> stdlib/ty_extensions.pyi:20:1
|
19 | # Types
20 | Unknown = object()
| ^^^^^^^
21 | AlwaysTruthy = object()
22 | AlwaysFalsy = object()
|
info: Source
--> main2.py:4:63
|
2 | def foo(x: int, *y: bool, z: str | int | list[str]): ...
3 |
4 | a[: def foo(x: int, *y: bool, *, z: str | int | list[str]) -> Unknown] = foo
| ^^^^^^^
|
"#);
}
#[test]
fn test_module_inlay_hint() {
let mut test = inlay_hint_test(
"
import foo
a = foo",
);
test.with_extra_file("foo.py", "'''Foo module'''");
assert_snapshot!(test.inlay_hints(), @r"
import foo
a[: <module 'foo'>] = foo
---------------------------------------------
info[inlay-hint-location]: Inlay Hint Target
--> foo.py:1:1
|
1 | '''Foo module'''
| ^^^^^^^^^^^^^^^^
|
info: Source
--> main2.py:4:5
|
2 | import foo
3 |
4 | a[: <module 'foo'>] = foo
| ^^^^^^^^^^^^^^
|
");
}
struct InlayHintLocationDiagnostic {
source: FileRange,
target: FileRange,
@@ -5847,4 +6331,31 @@ mod tests {
main
}
}
struct InlayHintEditDiagnostic {
file_content: String,
}
impl InlayHintEditDiagnostic {
fn new(file_content: String) -> Self {
Self { file_content }
}
}
impl IntoDiagnostic for InlayHintEditDiagnostic {
fn into_diagnostic(self) -> Diagnostic {
let mut main = Diagnostic::new(
DiagnosticId::Lint(LintName::of("inlay-hint-edit")),
Severity::Info,
"File after edits".to_string(),
);
main.sub(SubDiagnostic::new(
SubDiagnosticSeverity::Info,
format!("{}\n{}", "Source", self.file_content),
));
main
}
}
}

View File

@@ -33,7 +33,9 @@ pub use document_symbols::document_symbols;
pub use goto::{goto_declaration, goto_definition, goto_type_definition};
pub use goto_references::goto_references;
pub use hover::hover;
pub use inlay_hints::{InlayHintKind, InlayHintLabel, InlayHintSettings, inlay_hints};
pub use inlay_hints::{
InlayHintKind, InlayHintLabel, InlayHintSettings, InlayHintTextEdit, inlay_hints,
};
pub use markup::MarkupKind;
pub use references::ReferencesMode;
pub use rename::{can_rename, rename};

View File

@@ -79,9 +79,8 @@ async def main():
task("B"),
)
# TODO: these should be `int`
reveal_type(a) # revealed: Unknown
reveal_type(b) # revealed: Unknown
reveal_type(a) # revealed: int
reveal_type(b) # revealed: int
```
## Under the hood

View File

@@ -237,4 +237,26 @@ class Matrix:
Matrix() < Matrix()
```
## `self`-binding behaviour of function-like `Callable`s
Binding the `self` parameter of a function-like `Callable` creates a new `Callable` that is also
function-like:
`main.py`:
```py
from typing import Callable
def my_lossy_decorator(fn: Callable[..., int]) -> Callable[..., int]:
return fn
class MyClass:
@my_lossy_decorator
def method(self) -> int:
return 42
reveal_type(MyClass().method) # revealed: (...) -> int
reveal_type(MyClass().method.__name__) # revealed: str
```
[`tensorbase`]: https://github.com/pytorch/pytorch/blob/f3913ea641d871f04fa2b6588a77f63efeeb9f10/torch/_tensor.py#L1084-L1092

View File

@@ -171,7 +171,7 @@ class Config:
import generic_a
import generic_b
# TODO should be error: [invalid-assignment] "Object of type `<class 'generic_b.Container[int]'>` is not assignable to `type[generic_a.Container[int]]`"
# error: [invalid-assignment] "Object of type `<class 'generic_b.Container[int]'>` is not assignable to `type[generic_a.Container[int]]`"
container: type[generic_a.Container[int]] = generic_b.Container[int]
```

View File

@@ -22,8 +22,10 @@ from ty_extensions import ConstraintSet, generic_context
# fmt: off
def unbounded[T]():
# revealed: ty_extensions.Specialization[T@unbounded = object]
# revealed: ty_extensions.Specialization[T@unbounded = Unknown]
reveal_type(generic_context(unbounded).specialize_constrained(ConstraintSet.always()))
# revealed: ty_extensions.Specialization[T@unbounded = object]
reveal_type(generic_context(unbounded).specialize_constrained(ConstraintSet.range(Never, T, object)))
# revealed: None
reveal_type(generic_context(unbounded).specialize_constrained(ConstraintSet.never()))
@@ -88,6 +90,7 @@ that makes the test succeed.
from typing import Any
def bounded_by_gradual[T: Any]():
# TODO: revealed: ty_extensions.Specialization[T@bounded_by_gradual = Any]
# revealed: ty_extensions.Specialization[T@bounded_by_gradual = object]
reveal_type(generic_context(bounded_by_gradual).specialize_constrained(ConstraintSet.always()))
# revealed: None
@@ -168,12 +171,16 @@ from typing import Any
# fmt: off
def constrained_by_gradual[T: (Base, Any)]():
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Unknown]
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = Base]
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.always()))
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = object]
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.always()))
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.range(Never, T, object)))
# revealed: None
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.never()))
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = Base]
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.range(Never, T, Base)))
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
@@ -181,14 +188,14 @@ def constrained_by_gradual[T: (Base, Any)]():
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.range(Never, T, Unrelated)))
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = Super]
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = Base]
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.range(Never, T, Super)))
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = Super]
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.range(Super, T, Super)))
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = object]
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = Base]
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.range(Sub, T, object)))
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = Sub]
@@ -288,7 +295,7 @@ class Unrelated: ...
# fmt: off
def mutually_bound[T: Base, U]():
# revealed: ty_extensions.Specialization[T@mutually_bound = Base, U@mutually_bound = object]
# revealed: ty_extensions.Specialization[T@mutually_bound = Base, U@mutually_bound = Unknown]
reveal_type(generic_context(mutually_bound).specialize_constrained(ConstraintSet.always()))
# revealed: None
reveal_type(generic_context(mutually_bound).specialize_constrained(ConstraintSet.never()))
@@ -296,7 +303,7 @@ def mutually_bound[T: Base, U]():
# revealed: ty_extensions.Specialization[T@mutually_bound = Base, U@mutually_bound = Base]
reveal_type(generic_context(mutually_bound).specialize_constrained(ConstraintSet.range(Never, U, T)))
# revealed: ty_extensions.Specialization[T@mutually_bound = Sub, U@mutually_bound = object]
# revealed: ty_extensions.Specialization[T@mutually_bound = Sub, U@mutually_bound = Unknown]
reveal_type(generic_context(mutually_bound).specialize_constrained(ConstraintSet.range(Never, T, Sub)))
# revealed: ty_extensions.Specialization[T@mutually_bound = Sub, U@mutually_bound = Sub]
reveal_type(generic_context(mutually_bound).specialize_constrained(ConstraintSet.range(Never, T, Sub) & ConstraintSet.range(Never, U, T)))

View File

@@ -191,13 +191,13 @@ def _(
reveal_type(int_or_callable) # revealed: int | ((str, /) -> bytes)
reveal_type(callable_or_int) # revealed: ((str, /) -> bytes) | int
# TODO should be Unknown | int
reveal_type(type_var_or_int) # revealed: typing.TypeVar | int
reveal_type(type_var_or_int) # revealed: T@TypeVarOrInt | int
# TODO should be int | Unknown
reveal_type(int_or_type_var) # revealed: int | typing.TypeVar
reveal_type(int_or_type_var) # revealed: int | T@IntOrTypeVar
# TODO should be Unknown | None
reveal_type(type_var_or_none) # revealed: typing.TypeVar | None
reveal_type(type_var_or_none) # revealed: T@TypeVarOrNone | None
# TODO should be None | Unknown
reveal_type(none_or_type_var) # revealed: None | typing.TypeVar
reveal_type(none_or_type_var) # revealed: None | T@NoneOrTypeVar
```
If a type is unioned with itself in a value expression, the result is just that type. No
@@ -366,7 +366,9 @@ def g(obj: Y):
reveal_type(obj) # revealed: list[int | str]
```
## Generic types
## Generic implicit type aliases
### Functionality
Implicit type aliases can also be generic:
@@ -388,24 +390,25 @@ ListOrTuple = list[T] | tuple[T, ...]
ListOrTupleLegacy = Union[list[T], tuple[T, ...]]
MyCallable = Callable[P, T]
AnnotatedType = Annotated[T, "tag"]
TransparentAlias = T
MyOptional = T | None
# TODO: Consider displaying this as `<class 'list[T]'>`, … instead? (and similar for some others below)
reveal_type(MyList) # revealed: <class 'list[typing.TypeVar]'>
reveal_type(MyDict) # revealed: <class 'dict[typing.TypeVar, typing.TypeVar]'>
reveal_type(MyList) # revealed: <class 'list[T@MyList]'>
reveal_type(MyDict) # revealed: <class 'dict[T@MyDict, U@MyDict]'>
reveal_type(MyType) # revealed: GenericAlias
reveal_type(IntAndType) # revealed: <class 'tuple[int, typing.TypeVar]'>
reveal_type(Pair) # revealed: <class 'tuple[typing.TypeVar, typing.TypeVar]'>
reveal_type(Sum) # revealed: <class 'tuple[typing.TypeVar, typing.TypeVar]'>
reveal_type(IntAndType) # revealed: <class 'tuple[int, T@IntAndType]'>
reveal_type(Pair) # revealed: <class 'tuple[T@Pair, T@Pair]'>
reveal_type(Sum) # revealed: <class 'tuple[T@Sum, U@Sum]'>
reveal_type(ListOrTuple) # revealed: types.UnionType
reveal_type(ListOrTupleLegacy) # revealed: types.UnionType
reveal_type(MyCallable) # revealed: GenericAlias
reveal_type(AnnotatedType) # revealed: <typing.Annotated special form>
reveal_type(TransparentAlias) # revealed: typing.TypeVar
reveal_type(MyOptional) # revealed: types.UnionType
def _(
list_of_ints: MyList[int],
dict_str_to_int: MyDict[str, int],
# TODO: no error here
# error: [invalid-type-form] "`typing.TypeVar` is not a generic class"
subclass_of_int: MyType[int],
int_and_str: IntAndType[str],
pair_of_ints: Pair[int],
@@ -413,48 +416,40 @@ def _(
list_or_tuple: ListOrTuple[int],
list_or_tuple_legacy: ListOrTupleLegacy[int],
# TODO: no error here
# error: [too-many-positional-arguments] "Too many positional arguments: expected 1, got 2"
# error: [invalid-type-form] "List literals are not allowed in this context in a type expression: Did you mean `tuple[str, bytes]`?"
my_callable: MyCallable[[str, bytes], int],
annotated_int: AnnotatedType[int],
transparent_alias: TransparentAlias[int],
optional_int: MyOptional[int],
):
# TODO: This should be `list[int]`
reveal_type(list_of_ints) # revealed: @Todo(specialized generic alias in type expression)
# TODO: This should be `dict[str, int]`
reveal_type(dict_str_to_int) # revealed: @Todo(specialized generic alias in type expression)
# TODO: This should be `type[int]`
reveal_type(subclass_of_int) # revealed: Unknown
# TODO: This should be `tuple[int, str]`
reveal_type(int_and_str) # revealed: @Todo(specialized generic alias in type expression)
# TODO: This should be `tuple[int, int]`
reveal_type(pair_of_ints) # revealed: @Todo(specialized generic alias in type expression)
# TODO: This should be `tuple[int, bytes]`
reveal_type(int_and_bytes) # revealed: @Todo(specialized generic alias in type expression)
# TODO: This should be `list[int] | tuple[int, ...]`
reveal_type(list_or_tuple) # revealed: @Todo(Generic specialization of types.UnionType)
# TODO: This should be `list[int] | tuple[int, ...]`
reveal_type(list_or_tuple_legacy) # revealed: @Todo(Generic specialization of types.UnionType)
reveal_type(list_of_ints) # revealed: list[int]
reveal_type(dict_str_to_int) # revealed: dict[str, int]
reveal_type(subclass_of_int) # revealed: type[int]
reveal_type(int_and_str) # revealed: tuple[int, str]
reveal_type(pair_of_ints) # revealed: tuple[int, int]
reveal_type(int_and_bytes) # revealed: tuple[int, bytes]
reveal_type(list_or_tuple) # revealed: list[int] | tuple[int, ...]
reveal_type(list_or_tuple_legacy) # revealed: list[int] | tuple[int, ...]
reveal_type(list_or_tuple_legacy) # revealed: list[int] | tuple[int, ...]
# TODO: This should be `(str, bytes) -> int`
reveal_type(my_callable) # revealed: @Todo(Generic specialization of typing.Callable)
# TODO: This should be `int`
reveal_type(annotated_int) # revealed: @Todo(Generic specialization of typing.Annotated)
reveal_type(my_callable) # revealed: Unknown
reveal_type(annotated_int) # revealed: int
reveal_type(transparent_alias) # revealed: int
reveal_type(optional_int) # revealed: int | None
```
Generic implicit type aliases can be partially specialized:
```py
U = TypeVar("U")
DictStrTo = MyDict[str, U]
reveal_type(DictStrTo) # revealed: GenericAlias
reveal_type(DictStrTo) # revealed: <class 'dict[str, U@DictStrTo]'>
def _(
# TODO: No error here
# error: [invalid-type-form] "Invalid subscript of object of type `GenericAlias` in type expression"
dict_str_to_int: DictStrTo[int],
):
# TODO: This should be `dict[str, int]`
reveal_type(dict_str_to_int) # revealed: Unknown
reveal_type(dict_str_to_int) # revealed: dict[str, int]
```
Using specializations of generic implicit type aliases in other implicit type aliases works as
@@ -464,26 +459,65 @@ expected:
IntsOrNone = MyList[int] | None
IntsOrStrs = Pair[int] | Pair[str]
ListOfPairs = MyList[Pair[str]]
ListOrTupleOfInts = ListOrTuple[int]
AnnotatedInt = AnnotatedType[int]
SubclassOfInt = MyType[int]
# TODO: No error here
# error: [too-many-positional-arguments] "Too many positional arguments: expected 1, got 2"
# error: [invalid-type-form] "List literals are not allowed in this context in a type expression: Did you mean `list[int]`?"
CallableIntToStr = MyCallable[[int], str]
reveal_type(IntsOrNone) # revealed: UnionType
reveal_type(IntsOrStrs) # revealed: UnionType
reveal_type(ListOfPairs) # revealed: GenericAlias
reveal_type(IntsOrNone) # revealed: types.UnionType
reveal_type(IntsOrStrs) # revealed: types.UnionType
reveal_type(ListOfPairs) # revealed: <class 'list[tuple[str, str]]'>
reveal_type(ListOrTupleOfInts) # revealed: types.UnionType
reveal_type(AnnotatedInt) # revealed: <typing.Annotated special form>
reveal_type(SubclassOfInt) # revealed: GenericAlias
reveal_type(CallableIntToStr) # revealed: Unknown
def _(
# TODO: This should not be an error
# error: [invalid-type-form] "Variable of type `UnionType` is not allowed in a type expression"
ints_or_none: IntsOrNone,
# TODO: This should not be an error
# error: [invalid-type-form] "Variable of type `UnionType` is not allowed in a type expression"
ints_or_strs: IntsOrStrs,
list_of_pairs: ListOfPairs,
list_or_tuple_of_ints: ListOrTupleOfInts,
annotated_int: AnnotatedInt,
subclass_of_int: SubclassOfInt,
callable_int_to_str: CallableIntToStr,
):
# TODO: This should be `list[int] | None`
reveal_type(ints_or_none) # revealed: Unknown
# TODO: This should be `tuple[int, int] | tuple[str, str]`
reveal_type(ints_or_strs) # revealed: Unknown
# TODO: This should be `list[tuple[str, str]]`
reveal_type(list_of_pairs) # revealed: @Todo(Support for `typing.GenericAlias` instances in type expressions)
reveal_type(ints_or_none) # revealed: list[int] | None
reveal_type(ints_or_strs) # revealed: tuple[int, int] | tuple[str, str]
reveal_type(list_of_pairs) # revealed: list[tuple[str, str]]
reveal_type(list_or_tuple_of_ints) # revealed: list[int] | tuple[int, ...]
reveal_type(annotated_int) # revealed: int
reveal_type(subclass_of_int) # revealed: type[int]
# TODO: This should be `(int, /) -> str`
reveal_type(callable_int_to_str) # revealed: Unknown
```
A generic implicit type alias can also be used in another generic implicit type alias:
```py
from typing_extensions import Any
B = TypeVar("B", bound=int)
MyOtherList = MyList[T]
MyOtherType = MyType[T]
TypeOrList = MyType[B] | MyList[B]
reveal_type(MyOtherList) # revealed: <class 'list[T@MyOtherList]'>
reveal_type(MyOtherType) # revealed: GenericAlias
reveal_type(TypeOrList) # revealed: types.UnionType
def _(
list_of_ints: MyOtherList[int],
subclass_of_int: MyOtherType[int],
type_or_list: TypeOrList[Any],
):
reveal_type(list_of_ints) # revealed: list[int]
reveal_type(subclass_of_int) # revealed: type[int]
# TODO: Should be `type[Any] | list[Any]`
reveal_type(type_or_list) # revealed: @Todo(type[T] for typevar T) | list[Any]
```
If a generic implicit type alias is used unspecialized in a type expression, we treat it as an
@@ -496,11 +530,11 @@ def _(
my_callable: MyCallable,
):
# TODO: Should be `list[Unknown]`
reveal_type(my_list) # revealed: list[typing.TypeVar]
reveal_type(my_list) # revealed: list[T@MyList]
# TODO: Should be `dict[Unknown, Unknown]`
reveal_type(my_dict) # revealed: dict[typing.TypeVar, typing.TypeVar]
reveal_type(my_dict) # revealed: dict[T@MyDict, U@MyDict]
# TODO: Should be `(...) -> Unknown`
reveal_type(my_callable) # revealed: (...) -> typing.TypeVar
reveal_type(my_callable) # revealed: (...) -> T@MyCallable
```
(Generic) implicit type aliases can be used as base classes:
@@ -522,37 +556,182 @@ reveal_mro(Derived1)
GenericBaseAlias = GenericBase[T]
# TODO: No error here
# error: [non-subscriptable] "Cannot subscript object of type `<class 'GenericBase[typing.TypeVar]'>` with no `__class_getitem__` method"
class Derived2(GenericBaseAlias[int]):
pass
```
### Imported aliases
Generic implicit type aliases can be imported from other modules and specialized:
`my_types.py`:
```py
from typing_extensions import TypeVar
T = TypeVar("T")
MyList = list[T]
```
`main.py`:
```py
from my_types import MyList
import my_types as mt
def _(
list_of_ints1: MyList[int],
list_of_ints2: mt.MyList[int],
):
reveal_type(list_of_ints1) # revealed: list[int]
reveal_type(list_of_ints2) # revealed: list[int]
```
### In stringified annotations
Generic implicit type aliases can be specialized in stringified annotations:
```py
from typing_extensions import TypeVar
T = TypeVar("T")
MyList = list[T]
def _(
list_of_ints: "MyList[int]",
):
reveal_type(list_of_ints) # revealed: list[int]
```
### Error cases
A generic alias that is already fully specialized cannot be specialized again:
```py
ListOfInts = list[int]
# TODO: this should be an error
# error: [too-many-positional-arguments] "Too many positional arguments: expected 0, got 1"
def _(doubly_specialized: ListOfInts[int]):
# TODO: this should be `Unknown`
reveal_type(doubly_specialized) # revealed: @Todo(specialized generic alias in type expression)
reveal_type(doubly_specialized) # revealed: Unknown
```
Specializing a generic implicit type alias with an incorrect number of type arguments also results
in an error:
```py
from typing_extensions import TypeVar
T = TypeVar("T")
U = TypeVar("U")
MyList = list[T]
MyDict = dict[T, U]
def _(
# TODO: this should be an error
# error: [too-many-positional-arguments] "Too many positional arguments: expected 1, got 2"
list_too_many_args: MyList[int, str],
# TODO: this should be an error
# error: [missing-argument] "No argument provided for required parameter `U`"
dict_too_few_args: MyDict[int],
):
# TODO: this should be `Unknown`
reveal_type(list_too_many_args) # revealed: @Todo(specialized generic alias in type expression)
# TODO: this should be `Unknown`
reveal_type(dict_too_few_args) # revealed: @Todo(specialized generic alias in type expression)
reveal_type(list_too_many_args) # revealed: Unknown
reveal_type(dict_too_few_args) # revealed: Unknown
```
Trying to specialize a non-name node results in an error:
```py
from ty_extensions import TypeOf
IntOrStr = int | str
def this_does_not_work() -> TypeOf[IntOrStr]:
raise NotImplementedError()
def _(
# TODO: Better error message? `invalid-type-form`
# error: [too-many-positional-arguments] "Too many positional arguments: expected 0, got 1"
specialized: this_does_not_work()[int],
):
reveal_type(specialized) # revealed: Unknown
```
Similarly, if you try to specialize a union type without a binding context, we emit an error:
```py
# error: [invalid-type-form] "`types.UnionType` is not subscriptable"
x: (list[T] | set[T])[int]
def _():
reveal_type(x) # revealed: Unknown
```
### Multiple definitions
#### Shadowed definitions
When a generic type alias shadows a definition from an outer scope, the inner definition is used:
```py
from typing_extensions import TypeVar
T = TypeVar("T")
MyAlias = list[T]
def outer():
MyAlias = set[T]
def _(x: MyAlias[int]):
reveal_type(x) # revealed: set[int]
```
#### Statically known conditions
```py
from typing_extensions import TypeVar
T = TypeVar("T")
if True:
MyAlias1 = list[T]
else:
MyAlias1 = set[T]
if False:
MyAlias2 = list[T]
else:
MyAlias2 = set[T]
def _(
x1: MyAlias1[int],
x2: MyAlias2[int],
):
reveal_type(x1) # revealed: list[int]
reveal_type(x2) # revealed: set[int]
```
#### Statically unknown conditions
If several definitions are visible, we emit an error:
```py
from typing_extensions import TypeVar
T = TypeVar("T")
def flag() -> bool:
return True
if flag():
MyAlias = list[T]
else:
MyAlias = set[T]
# error: [invalid-type-form] "Invalid subscript of object of type `<class 'list[T@MyAlias]'> | <class 'set[T@MyAlias]'>` in type expression"
def _(x: MyAlias[int]):
reveal_type(x) # revealed: Unknown
```
## `Literal`s
@@ -642,8 +821,7 @@ Deprecated = Annotated[T, "deprecated attribute"]
class C:
old: Deprecated[int]
# TODO: Should be `int`
reveal_type(C().old) # revealed: @Todo(Generic specialization of typing.Annotated)
reveal_type(C().old) # revealed: int
```
If the metadata argument is missing, we emit an error (because this code fails at runtime), but
@@ -1298,3 +1476,14 @@ def _(
reveal_type(recursive_dict3) # revealed: dict[Divergent, int]
reveal_type(recursive_dict4) # revealed: dict[Divergent, int]
```
### Self-referential generic implicit type aliases
<!-- expect-panic: execute: too many cycle iterations -->
```py
from typing import TypeVar
T = TypeVar("T")
NestedDict = dict[str, "NestedDict[T] | T"]
```

View File

@@ -523,3 +523,63 @@ class Baz(NamedTuple):
class Spam(Baz):
def _asdict(self) -> tuple[int, ...]: ... # error: [invalid-method-override]
```
## Staticmethods and classmethods
Methods decorated with `@staticmethod` or `@classmethod` are checked in much the same way as other
methods.
<!-- snapshot-diagnostics -->
```pyi
class Parent:
def instance_method(self, x: int) -> int: ...
@classmethod
def class_method(cls, x: int) -> int: ...
@staticmethod
def static_method(x: int) -> int: ...
class BadChild1(Parent):
@staticmethod
def instance_method(self, x: int) -> int: ... # error: [invalid-method-override]
# TODO: we should emit `invalid-method-override` here.
# Although the method has the same signature as `Parent.class_method`
# when accessed on instances, it does not have the same signature as
# `Parent.class_method` when accessed on the class object itself
def class_method(cls, x: int) -> int: ...
def static_method(x: int) -> int: ... # error: [invalid-method-override]
class BadChild2(Parent):
# TODO: we should emit `invalid-method-override` here.
# Although the method has the same signature as `Parent.class_method`
# when accessed on instances, it does not have the same signature as
# `Parent.class_method` when accessed on the class object itself.
#
# Note that whereas `BadChild1.class_method` is reported as a Liskov violation by
# mypy, pyright and pyrefly, pyright is the only one of those three to report a
# Liskov violation on this method as of 2025-11-23.
@classmethod
def instance_method(self, x: int) -> int: ...
@staticmethod
def class_method(cls, x: int) -> int: ... # error: [invalid-method-override]
@classmethod
def static_method(x: int) -> int: ... # error: [invalid-method-override]
class BadChild3(Parent):
@classmethod
def class_method(cls, x: bool) -> object: ... # error: [invalid-method-override]
@staticmethod
def static_method(x: bool) -> object: ... # error: [invalid-method-override]
class GoodChild1(Parent):
@classmethod
def class_method(cls, x: int) -> int: ...
@staticmethod
def static_method(x: int) -> int: ...
class GoodChild2(Parent):
@classmethod
def class_method(cls, x: object) -> bool: ...
@staticmethod
def static_method(x: object) -> bool: ...
```

View File

@@ -96,6 +96,24 @@ def _(x: MyAlias):
reveal_type(x) # revealed: int | ((str, /) -> int)
```
## Generic aliases
```py
from typing import TypeAlias, TypeVar
T = TypeVar("T")
MyList: TypeAlias = list[T]
ListOrSet: TypeAlias = list[T] | set[T]
reveal_type(MyList) # revealed: <class 'list[T]'>
reveal_type(ListOrSet) # revealed: types.UnionType
def _(list_of_int: MyList[int], list_or_set_of_str: ListOrSet[str]):
reveal_type(list_of_int) # revealed: list[int]
reveal_type(list_or_set_of_str) # revealed: list[str] | set[str]
```
## Subscripted generic alias in union
```py
@@ -107,8 +125,7 @@ Alias1: TypeAlias = list[T] | set[T]
MyAlias: TypeAlias = int | Alias1[str]
def _(x: MyAlias):
# TODO: int | list[str] | set[str]
reveal_type(x) # revealed: int | @Todo(Specialization of union type alias)
reveal_type(x) # revealed: int | list[str] | set[str]
```
## Imported

View File

@@ -0,0 +1,220 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: liskov.md - The Liskov Substitution Principle - Staticmethods and classmethods
mdtest path: crates/ty_python_semantic/resources/mdtest/liskov.md
---
# Python source files
## mdtest_snippet.pyi
```
1 | class Parent:
2 | def instance_method(self, x: int) -> int: ...
3 | @classmethod
4 | def class_method(cls, x: int) -> int: ...
5 | @staticmethod
6 | def static_method(x: int) -> int: ...
7 |
8 | class BadChild1(Parent):
9 | @staticmethod
10 | def instance_method(self, x: int) -> int: ... # error: [invalid-method-override]
11 | # TODO: we should emit `invalid-method-override` here.
12 | # Although the method has the same signature as `Parent.class_method`
13 | # when accessed on instances, it does not have the same signature as
14 | # `Parent.class_method` when accessed on the class object itself
15 | def class_method(cls, x: int) -> int: ...
16 | def static_method(x: int) -> int: ... # error: [invalid-method-override]
17 |
18 | class BadChild2(Parent):
19 | # TODO: we should emit `invalid-method-override` here.
20 | # Although the method has the same signature as `Parent.class_method`
21 | # when accessed on instances, it does not have the same signature as
22 | # `Parent.class_method` when accessed on the class object itself.
23 | #
24 | # Note that whereas `BadChild1.class_method` is reported as a Liskov violation by
25 | # mypy, pyright and pyrefly, pyright is the only one of those three to report a
26 | # Liskov violation on this method as of 2025-11-23.
27 | @classmethod
28 | def instance_method(self, x: int) -> int: ...
29 | @staticmethod
30 | def class_method(cls, x: int) -> int: ... # error: [invalid-method-override]
31 | @classmethod
32 | def static_method(x: int) -> int: ... # error: [invalid-method-override]
33 |
34 | class BadChild3(Parent):
35 | @classmethod
36 | def class_method(cls, x: bool) -> object: ... # error: [invalid-method-override]
37 | @staticmethod
38 | def static_method(x: bool) -> object: ... # error: [invalid-method-override]
39 |
40 | class GoodChild1(Parent):
41 | @classmethod
42 | def class_method(cls, x: int) -> int: ...
43 | @staticmethod
44 | def static_method(x: int) -> int: ...
45 |
46 | class GoodChild2(Parent):
47 | @classmethod
48 | def class_method(cls, x: object) -> bool: ...
49 | @staticmethod
50 | def static_method(x: object) -> bool: ...
```
# Diagnostics
```
error[invalid-method-override]: Invalid override of method `instance_method`
--> src/mdtest_snippet.pyi:10:9
|
8 | class BadChild1(Parent):
9 | @staticmethod
10 | def instance_method(self, x: int) -> int: ... # error: [invalid-method-override]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Parent.instance_method`
11 | # TODO: we should emit `invalid-method-override` here.
12 | # Although the method has the same signature as `Parent.class_method`
|
::: src/mdtest_snippet.pyi:2:9
|
1 | class Parent:
2 | def instance_method(self, x: int) -> int: ...
| ------------------------------------ `Parent.instance_method` defined here
3 | @classmethod
4 | def class_method(cls, x: int) -> int: ...
|
info: `BadChild1.instance_method` is a staticmethod but `Parent.instance_method` is an instance method
info: This violates the Liskov Substitution Principle
info: rule `invalid-method-override` is enabled by default
```
```
error[invalid-method-override]: Invalid override of method `static_method`
--> src/mdtest_snippet.pyi:16:9
|
14 | # `Parent.class_method` when accessed on the class object itself
15 | def class_method(cls, x: int) -> int: ...
16 | def static_method(x: int) -> int: ... # error: [invalid-method-override]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Parent.static_method`
17 |
18 | class BadChild2(Parent):
|
::: src/mdtest_snippet.pyi:6:9
|
4 | def class_method(cls, x: int) -> int: ...
5 | @staticmethod
6 | def static_method(x: int) -> int: ...
| ---------------------------- `Parent.static_method` defined here
7 |
8 | class BadChild1(Parent):
|
info: `BadChild1.static_method` is an instance method but `Parent.static_method` is a staticmethod
info: This violates the Liskov Substitution Principle
info: rule `invalid-method-override` is enabled by default
```
```
error[invalid-method-override]: Invalid override of method `class_method`
--> src/mdtest_snippet.pyi:30:9
|
28 | def instance_method(self, x: int) -> int: ...
29 | @staticmethod
30 | def class_method(cls, x: int) -> int: ... # error: [invalid-method-override]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Parent.class_method`
31 | @classmethod
32 | def static_method(x: int) -> int: ... # error: [invalid-method-override]
|
::: src/mdtest_snippet.pyi:4:9
|
2 | def instance_method(self, x: int) -> int: ...
3 | @classmethod
4 | def class_method(cls, x: int) -> int: ...
| -------------------------------- `Parent.class_method` defined here
5 | @staticmethod
6 | def static_method(x: int) -> int: ...
|
info: `BadChild2.class_method` is a staticmethod but `Parent.class_method` is a classmethod
info: This violates the Liskov Substitution Principle
info: rule `invalid-method-override` is enabled by default
```
```
error[invalid-method-override]: Invalid override of method `static_method`
--> src/mdtest_snippet.pyi:32:9
|
30 | def class_method(cls, x: int) -> int: ... # error: [invalid-method-override]
31 | @classmethod
32 | def static_method(x: int) -> int: ... # error: [invalid-method-override]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Parent.static_method`
33 |
34 | class BadChild3(Parent):
|
::: src/mdtest_snippet.pyi:6:9
|
4 | def class_method(cls, x: int) -> int: ...
5 | @staticmethod
6 | def static_method(x: int) -> int: ...
| ---------------------------- `Parent.static_method` defined here
7 |
8 | class BadChild1(Parent):
|
info: `BadChild2.static_method` is a classmethod but `Parent.static_method` is a staticmethod
info: This violates the Liskov Substitution Principle
info: rule `invalid-method-override` is enabled by default
```
```
error[invalid-method-override]: Invalid override of method `class_method`
--> src/mdtest_snippet.pyi:36:9
|
34 | class BadChild3(Parent):
35 | @classmethod
36 | def class_method(cls, x: bool) -> object: ... # error: [invalid-method-override]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Parent.class_method`
37 | @staticmethod
38 | def static_method(x: bool) -> object: ... # error: [invalid-method-override]
|
::: src/mdtest_snippet.pyi:4:9
|
2 | def instance_method(self, x: int) -> int: ...
3 | @classmethod
4 | def class_method(cls, x: int) -> int: ...
| -------------------------------- `Parent.class_method` defined here
5 | @staticmethod
6 | def static_method(x: int) -> int: ...
|
info: This violates the Liskov Substitution Principle
info: rule `invalid-method-override` is enabled by default
```
```
error[invalid-method-override]: Invalid override of method `static_method`
--> src/mdtest_snippet.pyi:38:9
|
36 | def class_method(cls, x: bool) -> object: ... # error: [invalid-method-override]
37 | @staticmethod
38 | def static_method(x: bool) -> object: ... # error: [invalid-method-override]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Parent.static_method`
39 |
40 | class GoodChild1(Parent):
|
::: src/mdtest_snippet.pyi:6:9
|
4 | def class_method(cls, x: int) -> int: ...
5 | @staticmethod
6 | def static_method(x: int) -> int: ...
| ---------------------------- `Parent.static_method` defined here
7 |
8 | class BadChild1(Parent):
|
info: This violates the Liskov Substitution Principle
info: rule `invalid-method-override` is enabled by default
```

View File

@@ -174,6 +174,39 @@ def _(x: Foo[int], y: Bar[str], z: list[bytes]):
reveal_type(type(z)) # revealed: type[list[bytes]]
```
## Checking generic `type[]` types
```toml
[environment]
python-version = "3.12"
```
```py
class C[T]:
pass
class D[T]:
pass
var: type[C[int]] = C[int]
var: type[C[int]] = D[int] # error: [invalid-assignment] "Object of type `<class 'D[int]'>` is not assignable to `type[C[int]]`"
```
However, generic `Protocol` classes are still TODO:
```py
from typing import Protocol
class Proto[U](Protocol):
def some_method(self): ...
# TODO: should be error: [invalid-assignment]
var: type[Proto[int]] = C[int]
def _(p: type[Proto[int]]):
reveal_type(p) # revealed: type[@Todo(type[T] for protocols)]
```
## `@final` classes
`type[]` types are eagerly converted to class-literal types if a class decorated with `@final` is

View File

@@ -66,12 +66,15 @@ def _[T]() -> None:
reveal_type(ConstraintSet.range(Base, T, object))
```
And a range constraint with _both_ a lower bound of `Never` and an upper bound of `object` does not
constrain the typevar at all.
And a range constraint with a lower bound of `Never` and an upper bound of `object` allows the
typevar to take on any type. We treat this differently than the `always` constraint set. During
specialization inference, that allows us to distinguish between not constraining a typevar (and
therefore falling back on its default specialization) and explicitly constraining it to any subtype
of `object`.
```py
def _[T]() -> None:
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@_ = *)]
reveal_type(ConstraintSet.range(Never, T, object))
```
@@ -156,7 +159,7 @@ cannot be satisfied at all.
```py
def _[T]() -> None:
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@_ ≠ *)]
reveal_type(~ConstraintSet.range(Never, T, object))
```
@@ -654,7 +657,7 @@ def _[T]() -> None:
reveal_type(~ConstraintSet.range(Never, T, Base))
# revealed: ty_extensions.ConstraintSet[¬(Sub ≤ T@_)]
reveal_type(~ConstraintSet.range(Sub, T, object))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@_ ≠ *)]
reveal_type(~ConstraintSet.range(Never, T, object))
```
@@ -811,7 +814,7 @@ def f[T]():
# "domain", which maps valid inputs to `true` and invalid inputs to `false`. This means that two
# constraint sets that are both always satisfied will not be identical if they have different
# domains!
always = ConstraintSet.range(Never, T, object)
always = ConstraintSet.always()
# revealed: ty_extensions.ConstraintSet[always]
reveal_type(always)
static_assert(always)
@@ -846,11 +849,11 @@ from typing import Never
from ty_extensions import ConstraintSet
def same_typevar[T]():
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@same_typevar = *)]
reveal_type(ConstraintSet.range(Never, T, T))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@same_typevar = *)]
reveal_type(ConstraintSet.range(T, T, object))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@same_typevar = *)]
reveal_type(ConstraintSet.range(T, T, T))
```
@@ -862,11 +865,11 @@ as shown above.)
from ty_extensions import Intersection
def same_typevar[T]():
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@same_typevar = *)]
reveal_type(ConstraintSet.range(Never, T, T | None))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@same_typevar = *)]
reveal_type(ConstraintSet.range(Intersection[T, None], T, object))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@same_typevar = *)]
reveal_type(ConstraintSet.range(Intersection[T, None], T, T | None))
```
@@ -877,8 +880,8 @@ constraint set can never be satisfied, since every type is disjoint with its neg
from ty_extensions import Not
def same_typevar[T]():
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@same_typevar ≠ *)]
reveal_type(ConstraintSet.range(Intersection[Not[T], None], T, object))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@same_typevar ≠ *)]
reveal_type(ConstraintSet.range(Not[T], T, object))
```

View File

@@ -243,13 +243,13 @@ static_assert(is_assignable_to(TypeOf[Bar[int]], type[Foo[int]]))
static_assert(is_assignable_to(TypeOf[Bar[bool]], type[Foo[int]]))
static_assert(is_assignable_to(TypeOf[Bar], type[Foo[int]]))
static_assert(is_assignable_to(TypeOf[Bar[Any]], type[Foo[int]]))
static_assert(is_assignable_to(TypeOf[Bar[Unknown]], type[Foo[int]]))
static_assert(is_assignable_to(TypeOf[Bar], type[Foo]))
static_assert(is_assignable_to(TypeOf[Bar[Any]], type[Foo[Any]]))
static_assert(is_assignable_to(TypeOf[Bar[Any]], type[Foo[int]]))
# TODO: these should pass (all subscripts inside `type[]` type expressions are currently TODO types)
static_assert(not is_assignable_to(TypeOf[Bar[int]], type[Foo[bool]])) # error: [static-assert-error]
static_assert(not is_assignable_to(TypeOf[Foo[bool]], type[Bar[int]])) # error: [static-assert-error]
static_assert(not is_assignable_to(TypeOf[Bar[int]], type[Foo[bool]]))
static_assert(not is_assignable_to(TypeOf[Foo[bool]], type[Bar[int]]))
```
## `type[]` is not assignable to types disjoint from `builtins.type`

View File

@@ -90,6 +90,12 @@ impl<'db> Definition<'db> {
.to_string(),
)
}
DefinitionKind::Assignment(assignment) => {
let target_node = assignment.target.node(&module);
target_node
.as_name_expr()
.map(|name_expr| name_expr.id.as_str().to_string())
}
_ => None,
}
}

View File

@@ -906,6 +906,13 @@ impl<'db> Type<'db> {
matches!(self, Type::GenericAlias(_))
}
// pub(crate) fn as_generic_alias(&self) -> Option<GenericAlias<'db>> {
// match self {
// Type::GenericAlias(alias) => Some(*alias),
// _ => None,
// }
// }
const fn is_dynamic(&self) -> bool {
matches!(self, Type::Dynamic(_))
}
@@ -1107,13 +1114,6 @@ impl<'db> Type<'db> {
}
}
pub(crate) const fn as_bound_method(self) -> Option<BoundMethodType<'db>> {
match self {
Type::BoundMethod(bound_method) => Some(bound_method),
_ => None,
}
}
#[track_caller]
pub(crate) const fn expect_class_literal(self) -> ClassLiteral<'db> {
self.as_class_literal()
@@ -1566,7 +1566,7 @@ impl<'db> Type<'db> {
}
}
Type::ClassLiteral(class_literal) => {
Some(ClassType::NonGeneric(class_literal).into_callable(db))
Some(class_literal.default_specialization(db).into_callable(db))
}
Type::GenericAlias(alias) => Some(ClassType::Generic(alias).into_callable(db)),
@@ -2406,7 +2406,7 @@ impl<'db> Type<'db> {
.subclass_of()
.into_class()
.map(|subclass_of_class| {
ClassType::NonGeneric(class).has_relation_to_impl(
class.default_specialization(db).has_relation_to_impl(
db,
subclass_of_class,
inferable,
@@ -6707,7 +6707,9 @@ impl<'db> Type<'db> {
KnownClass::Float.to_instance(db),
],
),
_ if class.is_typed_dict(db) => Type::typed_dict(*class),
_ if class.is_typed_dict(db) => {
Type::typed_dict(class.default_specialization(db))
}
_ => Type::instance(db, class.default_specialization(db)),
};
Ok(ty)
@@ -6797,7 +6799,7 @@ impl<'db> Type<'db> {
}
KnownInstanceType::Literal(ty) => Ok(ty.inner(db)),
KnownInstanceType::Annotated(ty) => Ok(ty.inner(db)),
KnownInstanceType::TypeGenericAlias(ty) => {
KnownInstanceType::TypeGenericAlias(instance) => {
// When `type[…]` appears in a value position (e.g. in an implicit type alias),
// we infer its argument as a type expression. This ensures that we can emit
// diagnostics for invalid type expressions, and more importantly, that we can
@@ -6806,9 +6808,26 @@ impl<'db> Type<'db> {
// (`int` -> instance of `int` -> subclass of `int`) can be lossy, but it is
// okay for all valid arguments to `type[…]`.
Ok(ty.inner(db).to_meta_type(db))
let ty = instance.inner(db);
if ty.is_type_var() {
// TODO:
// This is a temporary workaround until we have proper support for type[T].
// If we pass a typevar to `.to_meta_type()`, we currently get `type[B]`,
// where `B` is the upper bound of `T`. However, we really need `type[T]`
// here. Otherwise, when we specialize a generic implicit type alias like
// `TypeOrList[T] = type[T] | list[T]` using `TypeOrList[Any]`, we would get
// `type[B] | list[Any]`, which leads to a lot of false positives for numpy-
// users.
Ok(todo_type!("type[T] for typevar T"))
} else {
Ok(ty.to_meta_type(db))
}
}
KnownInstanceType::GenericAlias(instance) => Ok(instance.inner(db)),
KnownInstanceType::Callable(instance) => {
Ok(Type::Callable(instance.callable_type(db)))
}
KnownInstanceType::Callable(callable) => Ok(Type::Callable(*callable)),
KnownInstanceType::LiteralStringAlias(ty) => Ok(ty.inner(db)),
},
@@ -7203,17 +7222,83 @@ impl<'db> Type<'db> {
}
}
Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => match type_mapping {
TypeMapping::BindLegacyTypevars(binding_context) => {
Type::TypeVar(BoundTypeVarInstance::new(db, typevar, *binding_context))
Type::KnownInstance(known_instance) => match known_instance {
KnownInstanceType::TypeVar(typevar) => {
match type_mapping {
TypeMapping::BindLegacyTypevars(binding_context) => {
Type::TypeVar(BoundTypeVarInstance::new(db, typevar, *binding_context))
}
TypeMapping::Specialization(_) |
TypeMapping::PartialSpecialization(_) |
TypeMapping::PromoteLiterals(_) |
TypeMapping::BindSelf(_) |
TypeMapping::ReplaceSelf { .. } |
TypeMapping::Materialize(_) |
TypeMapping::ReplaceParameterDefaults => self,
}
}
TypeMapping::Specialization(_) |
TypeMapping::PartialSpecialization(_) |
TypeMapping::PromoteLiterals(_) |
TypeMapping::BindSelf(_) |
TypeMapping::ReplaceSelf { .. } |
TypeMapping::Materialize(_) |
TypeMapping::ReplaceParameterDefaults => self,
KnownInstanceType::UnionType(instance) => {
if let Ok(union_type) = instance.union_type(db) {
Type::KnownInstance(KnownInstanceType::UnionType(
UnionTypeInstance::new(
db,
instance._value_expr_types(db),
Ok(union_type.apply_type_mapping_impl(db, type_mapping, tcx, visitor)),
instance.binding_context(db),
)
))
} else {
self
}
},
KnownInstanceType::Annotated(instance) => {
Type::KnownInstance(KnownInstanceType::Annotated(
TypeInContext::new(
db,
instance.inner(db).apply_type_mapping_impl(db, type_mapping, tcx, visitor),
instance.binding_context(db),
)
))
},
KnownInstanceType::TypeGenericAlias(instance) => {
Type::KnownInstance(KnownInstanceType::TypeGenericAlias(
TypeInContext::new(
db,
instance.inner(db).apply_type_mapping_impl(db, type_mapping, tcx, visitor),
instance.binding_context(db),
)
))
},
KnownInstanceType::GenericAlias(instance) => {
Type::KnownInstance(KnownInstanceType::GenericAlias(
TypeInContext::new(
db,
instance.inner(db).apply_type_mapping_impl(db, type_mapping, tcx, visitor),
instance.binding_context(db),
)
))
},
KnownInstanceType::Callable(instance) => {
Type::KnownInstance(KnownInstanceType::Callable(CallableTypeInstance::new(
db,
instance.callable_type(db).apply_type_mapping_impl(db, type_mapping, tcx, visitor),
instance.binding_context(db),
)))
},
KnownInstanceType::SubscriptedProtocol(_) |
KnownInstanceType::SubscriptedGeneric(_) |
KnownInstanceType::TypeAliasType(_) |
KnownInstanceType::Deprecated(_) |
KnownInstanceType::Field(_) |
KnownInstanceType::ConstraintSet(_) |
KnownInstanceType::GenericContext(_) |
KnownInstanceType::Specialization(_) |
KnownInstanceType::Literal(_) |
KnownInstanceType::LiteralStringAlias(_) |
KnownInstanceType::NewType(_) => {
// TODO: For some of these, we may need to apply the type mapping to inner types.
self
},
}
Type::FunctionLiteral(function) => {
@@ -7374,8 +7459,7 @@ impl<'db> Type<'db> {
// some other generic context's specialization is applied to it.
| Type::ClassLiteral(_)
| Type::BoundSuper(_)
| Type::SpecialForm(_)
| Type::KnownInstance(_) => self,
| Type::SpecialForm(_) => self,
}
}
@@ -7512,6 +7596,53 @@ impl<'db> Type<'db> {
});
}
Type::KnownInstance(known_instance) => match known_instance {
KnownInstanceType::UnionType(instance) => {
if let Ok(union_type) = instance.union_type(db) {
union_type.find_legacy_typevars_impl(
db,
binding_context,
typevars,
visitor,
);
}
}
KnownInstanceType::Annotated(ty) => {
ty.inner(db)
.find_legacy_typevars_impl(db, binding_context, typevars, visitor);
}
KnownInstanceType::Callable(instance) => {
instance.callable_type(db).find_legacy_typevars_impl(
db,
binding_context,
typevars,
visitor,
);
}
KnownInstanceType::TypeGenericAlias(ty) => {
ty.inner(db)
.find_legacy_typevars_impl(db, binding_context, typevars, visitor);
}
KnownInstanceType::GenericAlias(ty) => {
ty.inner(db)
.find_legacy_typevars_impl(db, binding_context, typevars, visitor);
}
KnownInstanceType::SubscriptedProtocol(_)
| KnownInstanceType::SubscriptedGeneric(_)
| KnownInstanceType::TypeVar(_)
| KnownInstanceType::TypeAliasType(_)
| KnownInstanceType::Deprecated(_)
| KnownInstanceType::Field(_)
| KnownInstanceType::ConstraintSet(_)
| KnownInstanceType::GenericContext(_)
| KnownInstanceType::Specialization(_)
| KnownInstanceType::Literal(_)
| KnownInstanceType::LiteralStringAlias(_)
| KnownInstanceType::NewType(_) => {
// TODO: For some of these, we may need to try to find legacy typevars in inner types.
}
},
Type::Dynamic(_)
| Type::Never
| Type::AlwaysTruthy
@@ -7539,7 +7670,6 @@ impl<'db> Type<'db> {
| Type::EnumLiteral(_)
| Type::BoundSuper(_)
| Type::SpecialForm(_)
| Type::KnownInstance(_)
| Type::TypedDict(_) => {}
}
}
@@ -8080,13 +8210,15 @@ pub enum KnownInstanceType<'db> {
Literal(InternedType<'db>),
/// A single instance of `typing.Annotated`
Annotated(InternedType<'db>),
Annotated(TypeInContext<'db>),
/// An instance of `typing.GenericAlias` representing a `type[...]` expression.
TypeGenericAlias(InternedType<'db>),
TypeGenericAlias(TypeInContext<'db>),
GenericAlias(TypeInContext<'db>),
/// An instance of `typing.GenericAlias` representing a `Callable[...]` expression.
Callable(CallableType<'db>),
Callable(CallableTypeInstance<'db>),
/// A literal string which is the right-hand side of a PEP 613 `TypeAlias`.
LiteralStringAlias(InternedType<'db>),
@@ -8128,14 +8260,16 @@ fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
visitor.visit_type(db, *union_type);
}
}
KnownInstanceType::Literal(ty)
| KnownInstanceType::Annotated(ty)
| KnownInstanceType::TypeGenericAlias(ty)
| KnownInstanceType::LiteralStringAlias(ty) => {
KnownInstanceType::Annotated(instance)
| KnownInstanceType::TypeGenericAlias(instance)
| KnownInstanceType::GenericAlias(instance) => {
visitor.visit_type(db, instance.inner(db));
}
KnownInstanceType::Literal(ty) | KnownInstanceType::LiteralStringAlias(ty) => {
visitor.visit_type(db, ty.inner(db));
}
KnownInstanceType::Callable(callable) => {
visitor.visit_callable_type(db, callable);
KnownInstanceType::Callable(instance) => {
visitor.visit_callable_type(db, instance.callable_type(db));
}
KnownInstanceType::NewType(newtype) => {
if let ClassType::Generic(generic_alias) = newtype.base_class_type(db) {
@@ -8174,6 +8308,7 @@ impl<'db> KnownInstanceType<'db> {
Self::Literal(ty) => Self::Literal(ty.normalized_impl(db, visitor)),
Self::Annotated(ty) => Self::Annotated(ty.normalized_impl(db, visitor)),
Self::TypeGenericAlias(ty) => Self::TypeGenericAlias(ty.normalized_impl(db, visitor)),
Self::GenericAlias(ty) => Self::GenericAlias(ty.normalized_impl(db, visitor)),
Self::Callable(callable) => Self::Callable(callable.normalized_impl(db, visitor)),
Self::LiteralStringAlias(ty) => {
Self::LiteralStringAlias(ty.normalized_impl(db, visitor))
@@ -8212,6 +8347,7 @@ impl<'db> KnownInstanceType<'db> {
Self::Literal(_)
| Self::Annotated(_)
| Self::TypeGenericAlias(_)
| Self::GenericAlias(_)
| Self::Callable(_) => KnownClass::GenericAlias,
Self::LiteralStringAlias(_) => KnownClass::Str,
Self::NewType(_) => KnownClass::NewType,
@@ -8306,7 +8442,7 @@ impl<'db> KnownInstanceType<'db> {
write!(
f,
"ty_extensions.Specialization{}",
specialization.normalized(self.db).display_full(self.db)
specialization.display_full(self.db)
)
}
KnownInstanceType::UnionType(_) => f.write_str("types.UnionType"),
@@ -8317,6 +8453,7 @@ impl<'db> KnownInstanceType<'db> {
KnownInstanceType::TypeGenericAlias(_) | KnownInstanceType::Callable(_) => {
f.write_str("GenericAlias")
}
KnownInstanceType::GenericAlias(_) => f.write_str("GenericAlias(…)"), //TODO
KnownInstanceType::LiteralStringAlias(_) => f.write_str("str"),
KnownInstanceType::NewType(declaration) => {
write!(f, "<NewType pseudo-class '{}'>", declaration.name(self.db))
@@ -9499,6 +9636,11 @@ pub struct UnionTypeInstance<'db> {
/// contains the first encountered error.
#[returns(ref)]
union_type: Result<Type<'db>, InvalidTypeExpressionError<'db>>,
/// The typevar binding context in which this union type instance was created.
/// For an implicit type alias like `ListOrSet = list[T] | set[T]`, this would
/// be the definition of `ListOrSet`.
binding_context: Option<Definition<'db>>,
}
impl get_size2::GetSize for UnionTypeInstance<'_> {}
@@ -9518,7 +9660,12 @@ impl<'db> UnionTypeInstance<'db> {
Ok(ty) => builder.add_in_place(ty),
Err(error) => {
return Type::KnownInstance(KnownInstanceType::UnionType(
UnionTypeInstance::new(db, Some(value_expr_types), Err(error)),
UnionTypeInstance::new(
db,
Some(value_expr_types),
Err(error),
typevar_binding_context,
),
));
}
}
@@ -9528,6 +9675,7 @@ impl<'db> UnionTypeInstance<'db> {
db,
Some(value_expr_types),
Ok(builder.build()),
typevar_binding_context,
)))
}
@@ -9573,7 +9721,7 @@ impl<'db> UnionTypeInstance<'db> {
.clone()
.map(|ty| ty.normalized_impl(db, visitor));
Self::new(db, value_expr_types, union_type)
Self::new(db, value_expr_types, union_type, self.binding_context(db))
}
}
@@ -9596,6 +9744,56 @@ impl<'db> InternedType<'db> {
}
}
/// A salsa-interned `Type` with an associated binding context for type variables.
///
/// # Ordering
/// Ordering is based on the context's salsa-assigned id and not on its values.
/// The id may change between runs, or when the context was garbage collected and recreated.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct TypeInContext<'db> {
inner: Type<'db>,
/// The typevar binding context in which this type was inferred.
binding_context: Option<Definition<'db>>,
}
impl get_size2::GetSize for TypeInContext<'_> {}
impl<'db> TypeInContext<'db> {
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
TypeInContext::new(
db,
self.inner(db).normalized_impl(db, visitor),
self.binding_context(db),
)
}
}
/// An instance of `typing.GenericAlias` representing a `Callable[...]` expression.
///
/// # Ordering
/// Ordering is based on the context's salsa-assigned id and not on its values.
/// The id may change between runs, or when the context was garbage collected and recreated.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct CallableTypeInstance<'db> {
callable_type: CallableType<'db>,
/// The binding context in which this callable type instance was inferred.
binding_context: Option<Definition<'db>>,
}
impl get_size2::GetSize for CallableTypeInstance<'_> {}
impl<'db> CallableTypeInstance<'db> {
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
CallableTypeInstance::new(
db,
self.callable_type(db).normalized_impl(db, visitor),
self.binding_context(db),
)
}
}
/// Error returned if a type is not awaitable.
#[derive(Debug)]
enum AwaitError<'db> {
@@ -11018,11 +11216,19 @@ impl<'db> CallableType<'db> {
db: &'db dyn Db,
self_type: Option<Type<'db>>,
) -> CallableType<'db> {
CallableType::new(db, self.signatures(db).bind_self(db, self_type), false)
CallableType::new(
db,
self.signatures(db).bind_self(db, self_type),
self.is_function_like(db),
)
}
pub(crate) fn apply_self(self, db: &'db dyn Db, self_type: Type<'db>) -> CallableType<'db> {
CallableType::new(db, self.signatures(db).apply_self(db, self_type), false)
CallableType::new(
db,
self.signatures(db).apply_self(db, self_type),
self.is_function_like(db),
)
}
/// Create a callable type which represents a fully-static "bottom" callable.

View File

@@ -362,6 +362,11 @@ impl<'db> VarianceInferable<'db> for GenericAlias<'db> {
get_size2::GetSize,
)]
pub enum ClassType<'db> {
// `NonGeneric` is intended to mean that the `ClassLiteral` has no type parameters. There are
// places where we currently violate this rule (e.g. so that we print `Foo` instead of
// `Foo[Unknown]`), but most callers who need to make a `ClassType` from a `ClassLiteral`
// should use `ClassLiteral::default_specialization` instead of assuming
// `ClassType::NonGeneric`.
NonGeneric(ClassLiteral<'db>),
Generic(GenericAlias<'db>),
}
@@ -1259,6 +1264,14 @@ impl MethodDecorator {
(false, false) => Ok(Self::None),
}
}
pub(crate) const fn description(self) -> &'static str {
match self {
MethodDecorator::None => "an instance method",
MethodDecorator::ClassMethod => "a classmethod",
MethodDecorator::StaticMethod => "a staticmethod",
}
}
}
/// Kind-specific metadata for different types of fields
@@ -3664,12 +3677,6 @@ impl<'db> From<ClassLiteral<'db>> for Type<'db> {
}
}
impl<'db> From<ClassLiteral<'db>> for ClassType<'db> {
fn from(class: ClassLiteral<'db>) -> ClassType<'db> {
ClassType::NonGeneric(class)
}
}
#[salsa::tracked]
impl<'db> VarianceInferable<'db> for ClassLiteral<'db> {
#[salsa::tracked(cycle_initial=crate::types::variance_cycle_initial, heap_size=ruff_memory_usage::heap_size)]

View File

@@ -185,6 +185,9 @@ impl<'db> ClassBase<'db> {
KnownInstanceType::TypeGenericAlias(_) => {
Self::try_from_type(db, KnownClass::Type.to_class_literal(db), subclass)
}
KnownInstanceType::GenericAlias(instance) => {
Self::try_from_type(db, instance.inner(db), subclass)
}
KnownInstanceType::Annotated(ty) => {
// Unions are not supported in this position, so we only need to support
// something like `class C(Annotated[Base, "metadata"]): ...`, which we

View File

@@ -494,7 +494,11 @@ impl<'db> ConstrainedTypeVar<'db> {
})
}) =>
{
return Node::AlwaysFalse;
return Node::new_constraint(
db,
ConstrainedTypeVar::new(db, typevar, Type::Never, Type::object()),
)
.negate(db);
}
_ => {}
}
@@ -522,12 +526,6 @@ impl<'db> ConstrainedTypeVar<'db> {
return Node::AlwaysFalse;
}
// If the requested constraint is `Never ≤ T ≤ object`, then the typevar can be specialized
// to _any_ type, and the constraint does nothing.
if lower.is_never() && upper.is_object() {
return Node::AlwaysTrue;
}
// We have an (arbitrary) ordering for typevars. If the upper and/or lower bounds are
// typevars, we have to ensure that the bounds are "later" according to that order than the
// typevar being constrained.
@@ -574,13 +572,21 @@ impl<'db> ConstrainedTypeVar<'db> {
db,
ConstrainedTypeVar::new(db, lower, Type::Never, Type::TypeVar(typevar)),
);
let upper = Self::new_node(db, typevar, Type::Never, upper);
let upper = if upper.is_object() {
Node::AlwaysTrue
} else {
Self::new_node(db, typevar, Type::Never, upper)
};
lower.and(db, upper)
}
// L ≤ T ≤ U == (L ≤ [T]) && (T ≤ [U])
(_, Type::TypeVar(upper)) if typevar.can_be_bound_for(db, upper) => {
let lower = Self::new_node(db, typevar, lower, Type::object());
let lower = if lower.is_never() {
Node::AlwaysTrue
} else {
Self::new_node(db, typevar, lower, Type::object())
};
let upper = Node::new_constraint(
db,
ConstrainedTypeVar::new(db, upper, Type::TypeVar(typevar), Type::object()),
@@ -703,6 +709,15 @@ impl<'db> ConstrainedTypeVar<'db> {
);
}
if lower.is_never() && upper.is_object() {
return write!(
f,
"({} {} *)",
typevar.identity(self.db).display(self.db),
if self.negated { "" } else { "=" }
);
}
if self.negated {
f.write_str("¬")?;
}
@@ -1127,27 +1142,30 @@ impl<'db> Node<'db> {
/// Invokes a callback for each of the representative types of a particular typevar for this
/// constraint set.
///
/// There is a representative type for each distinct path from the BDD root to the `AlwaysTrue`
/// We first abstract the BDD so that it only mentions constraints on the requested typevar. We
/// then invoke your callback for each distinct path from the BDD root to the `AlwaysTrue`
/// terminal. Each of those paths can be viewed as the conjunction of the individual
/// constraints of each internal node that we traverse as we walk that path. We provide the
/// lower/upper bound of this conjunction to your callback, allowing you to choose any suitable
/// type in the range.
///
/// If the abstracted BDD does not mention the typevar at all (i.e., it leaves the typevar
/// completely unconstrained), we will invoke your callback once with `None`.
fn find_representative_types(
self,
db: &'db dyn Db,
bound_typevar: BoundTypeVarIdentity<'db>,
mut f: impl FnMut(Type<'db>, Type<'db>),
mut f: impl FnMut(Option<(Type<'db>, Type<'db>)>),
) {
self.retain_one(db, bound_typevar)
.find_representative_types_inner(db, Type::Never, Type::object(), &mut f);
.find_representative_types_inner(db, None, &mut f);
}
fn find_representative_types_inner(
self,
db: &'db dyn Db,
greatest_lower_bound: Type<'db>,
least_upper_bound: Type<'db>,
f: &mut dyn FnMut(Type<'db>, Type<'db>),
current_bounds: Option<(Type<'db>, Type<'db>)>,
f: &mut dyn FnMut(Option<(Type<'db>, Type<'db>)>),
) {
match self {
Node::AlwaysTrue => {
@@ -1157,12 +1175,16 @@ impl<'db> Node<'db> {
// If `lower ≰ upper`, then this path somehow represents in invalid specialization.
// That should have been removed from the BDD domain as part of the simplification
// process.
debug_assert!(greatest_lower_bound.is_subtype_of(db, least_upper_bound));
debug_assert!(current_bounds.is_none_or(
|(greatest_lower_bound, least_upper_bound)| {
greatest_lower_bound.is_subtype_of(db, least_upper_bound)
}
));
// We've been tracking the lower and upper bound that the types for this path must
// satisfy. Pass those bounds along and let the caller choose a representative type
// from within that range.
f(greatest_lower_bound, least_upper_bound);
f(current_bounds);
}
Node::AlwaysFalse => {
@@ -1171,6 +1193,9 @@ impl<'db> Node<'db> {
}
Node::Interior(interior) => {
let (greatest_lower_bound, least_upper_bound) =
current_bounds.unwrap_or((Type::Never, Type::object()));
// For an interior node, there are two outgoing paths: one for the `if_true`
// branch, and one for the `if_false` branch.
//
@@ -1185,8 +1210,7 @@ impl<'db> Node<'db> {
IntersectionType::from_elements(db, [least_upper_bound, constraint.upper(db)]);
interior.if_true(db).find_representative_types_inner(
db,
new_greatest_lower_bound,
new_least_upper_bound,
Some((new_greatest_lower_bound, new_least_upper_bound)),
f,
);
@@ -1202,8 +1226,7 @@ impl<'db> Node<'db> {
// path.
interior.if_false(db).find_representative_types_inner(
db,
greatest_lower_bound,
least_upper_bound,
Some((greatest_lower_bound, least_upper_bound)),
f,
);
}
@@ -2239,6 +2262,9 @@ impl<'db> ConstraintAssignment<'db> {
///
/// We support several kinds of sequent:
///
/// - `¬C₁ → false`: This indicates that `C₁` is always true. Any path that assumes it is false is
/// impossible and can be pruned.
///
/// - `C₁ ∧ C₂ → false`: This indicates that `C₁` and `C₂` are disjoint: it is not possible for
/// both to hold. Any path that assumes both is impossible and can be pruned.
///
@@ -2250,8 +2276,10 @@ impl<'db> ConstraintAssignment<'db> {
/// holds but `D` does _not_ is impossible and can be pruned.
#[derive(Debug, Default, Eq, PartialEq, get_size2::GetSize, salsa::Update)]
struct SequentMap<'db> {
/// Sequents of the form `¬C₁ → false`
single_tautologies: FxHashSet<ConstrainedTypeVar<'db>>,
/// Sequents of the form `C₁ ∧ C₂ → false`
impossibilities: FxHashSet<(ConstrainedTypeVar<'db>, ConstrainedTypeVar<'db>)>,
pair_impossibilities: FxHashSet<(ConstrainedTypeVar<'db>, ConstrainedTypeVar<'db>)>,
/// Sequents of the form `C₁ ∧ C₂ → D`
pair_implications: FxHashMap<
(ConstrainedTypeVar<'db>, ConstrainedTypeVar<'db>),
@@ -2310,13 +2338,17 @@ impl<'db> SequentMap<'db> {
}
}
fn add_impossibility(
fn add_single_tautology(&mut self, ante: ConstrainedTypeVar<'db>) {
self.single_tautologies.insert(ante);
}
fn add_pair_impossibility(
&mut self,
db: &'db dyn Db,
ante1: ConstrainedTypeVar<'db>,
ante2: ConstrainedTypeVar<'db>,
) {
self.impossibilities
self.pair_impossibilities
.insert(Self::pair_key(db, ante1, ante2));
}
@@ -2352,6 +2384,15 @@ impl<'db> SequentMap<'db> {
}
fn add_sequents_for_single(&mut self, db: &'db dyn Db, constraint: ConstrainedTypeVar<'db>) {
// If this constraint binds its typevar to `Never ≤ T ≤ object`, then the typevar can take
// on any type, and the constraint is always satisfied.
let lower = constraint.lower(db);
let upper = constraint.upper(db);
if lower.is_never() && upper.is_object() {
self.add_single_tautology(constraint);
return;
}
// If the lower or upper bound of this constraint is a typevar, we can propagate the
// constraint:
//
@@ -2362,8 +2403,6 @@ impl<'db> SequentMap<'db> {
// Technically, (1) also allows `(S = T) → (S = S)`, but the rhs of that is vacuously true,
// so we don't add a sequent for that case.
let lower = constraint.lower(db);
let upper = constraint.upper(db);
let post_constraint = match (lower, upper) {
// Case 1
(Type::TypeVar(lower_typevar), Type::TypeVar(upper_typevar)) => {
@@ -2568,7 +2607,7 @@ impl<'db> SequentMap<'db> {
self.enqueue_constraint(intersection_constraint);
}
None => {
self.add_impossibility(db, left_constraint, right_constraint);
self.add_pair_impossibility(db, left_constraint, right_constraint);
}
}
}
@@ -2593,7 +2632,7 @@ impl<'db> SequentMap<'db> {
}
};
for (ante1, ante2) in &self.map.impossibilities {
for (ante1, ante2) in &self.map.pair_impossibilities {
maybe_write_prefix(f)?;
write!(
f,
@@ -2726,7 +2765,15 @@ impl<'db> PathAssignments<'db> {
// don't anticipate the sequent maps to be very large. We might consider avoiding the
// brute-force search.
for (ante1, ante2) in &map.impossibilities {
for ante in &map.single_tautologies {
if self.assignment_holds(ante.when_false()) {
// The sequent map says (ante1) is always true, and the current path asserts that
// it's false.
return Err(PathAssignmentConflict);
}
}
for (ante1, ante2) in &map.pair_impossibilities {
if self.assignment_holds(ante1.when_true()) && self.assignment_holds(ante2.when_true())
{
// The sequent map says (ante1 ∧ ante2) is an impossible combination, and the
@@ -3088,8 +3135,8 @@ impl<'db> GenericContext<'db> {
});
// Then we find all of the "representative types" for each typevar in the constraint set.
let mut types = vec![Type::Never; self.len(db)];
for (i, bound_typevar) in self.variables(db).enumerate() {
let mut error_occurred = false;
let types = self.variables(db).map(|bound_typevar| {
// Each representative type represents one of the ways that the typevar can satisfy the
// constraint, expressed as a lower/upper bound on the types that the typevar can
// specialize to.
@@ -3101,40 +3148,55 @@ impl<'db> GenericContext<'db> {
// _each_ of the paths into separate specializations, but it's not clear what we would
// do with that, so instead we just report the ambiguity as a specialization failure.
let mut satisfied = false;
let mut unconstrained = false;
let mut greatest_lower_bound = Type::Never;
let mut least_upper_bound = Type::object();
abstracted.find_representative_types(
db,
bound_typevar.identity(db),
|lower_bound, upper_bound| {
satisfied = true;
greatest_lower_bound =
UnionType::from_elements(db, [greatest_lower_bound, lower_bound]);
least_upper_bound =
IntersectionType::from_elements(db, [least_upper_bound, upper_bound]);
},
);
abstracted.find_representative_types(db, bound_typevar.identity(db), |bounds| {
satisfied = true;
match bounds {
Some((lower_bound, upper_bound)) => {
greatest_lower_bound =
UnionType::from_elements(db, [greatest_lower_bound, lower_bound]);
least_upper_bound =
IntersectionType::from_elements(db, [least_upper_bound, upper_bound]);
}
None => {
unconstrained = true;
}
}
});
// If there are no satisfiable paths in the BDD, then there is no valid specialization
// for this constraint set.
if !satisfied {
// TODO: Construct a useful error here
return Err(());
error_occurred = true;
return None;
}
// The BDD is satisfiable, but the typevar is unconstrained, then we use `None` to tell
// specialize_recursive to fall back on the typevar's default.
if unconstrained {
return None;
}
// If `lower ≰ upper`, then there is no type that satisfies all of the paths in the
// BDD. That's an ambiguous specialization, as described above.
if !greatest_lower_bound.is_subtype_of(db, least_upper_bound) {
// TODO: Construct a useful error here
return Err(());
error_occurred = true;
return None;
}
// Of all of the types that satisfy all of the paths in the BDD, we choose the
// "largest" one (i.e., "closest to `object`") as the specialization.
types[i] = least_upper_bound;
}
Some(least_upper_bound)
});
Ok(self.specialize_recursive(db, types.into_boxed_slice()))
let specialization = self.specialize_recursive(db, types);
if error_occurred {
return Err(());
}
Ok(specialization)
}
}

View File

@@ -8,13 +8,13 @@ use super::{
use crate::diagnostic::did_you_mean;
use crate::diagnostic::format_enumeration;
use crate::lint::{Level, LintRegistryBuilder, LintStatus};
use crate::place::Place;
use crate::semantic_index::definition::{Definition, DefinitionKind};
use crate::semantic_index::place::{PlaceTable, ScopedPlaceId};
use crate::semantic_index::{global_scope, place_table, use_def_map};
use crate::suppression::FileSuppressionId;
use crate::types::KnownInstanceType;
use crate::types::call::CallError;
use crate::types::class::{DisjointBase, DisjointBaseKind, Field};
use crate::types::class::{DisjointBase, DisjointBaseKind, Field, MethodDecorator};
use crate::types::function::{FunctionType, KnownFunction};
use crate::types::liskov::{MethodKind, SynthesizedMethodKind};
use crate::types::string_annotation::{
@@ -27,6 +27,7 @@ use crate::types::{
ProtocolInstanceType, SpecialFormType, SubclassOfInner, Type, TypeContext, binding_type,
infer_isolated_expression, protocol_class::ProtocolClass,
};
use crate::types::{KnownInstanceType, MemberLookupPolicy};
use crate::{Db, DisplaySettings, FxIndexMap, Module, ModuleName, Program, declare_lint};
use itertools::Itertools;
use ruff_db::{
@@ -3519,6 +3520,27 @@ pub(super) fn report_invalid_method_override<'db>(
"Definition is incompatible with `{overridden_method}`"
));
let class_member = |cls: ClassType<'db>| {
cls.class_member(db, member, MemberLookupPolicy::default())
.place
};
if let Place::Defined(Type::FunctionLiteral(subclass_function), _, _) = class_member(subclass)
&& let Place::Defined(Type::FunctionLiteral(superclass_function), _, _) =
class_member(superclass)
&& let Ok(superclass_function_kind) =
MethodDecorator::try_from_fn_type(db, superclass_function)
&& let Ok(subclass_function_kind) = MethodDecorator::try_from_fn_type(db, subclass_function)
&& superclass_function_kind != subclass_function_kind
{
diagnostic.info(format_args!(
"`{class_name}.{member}` is {subclass_function_kind} \
but `{overridden_method}` is {superclass_function_kind}",
superclass_function_kind = superclass_function_kind.description(),
subclass_function_kind = subclass_function_kind.description(),
));
}
diagnostic.info("This violates the Liskov Substitution Principle");
if !subclass_definition_kind.is_function_def()
@@ -3545,9 +3567,11 @@ pub(super) fn report_invalid_method_override<'db>(
.full_range(db, &parsed_module(db, superclass_scope.file(db)).load(db)),
);
let superclass_function_span = superclass_type
.as_bound_method()
.and_then(|method| signature_span(method.function(db)));
let superclass_function_span = match superclass_type {
Type::FunctionLiteral(function) => signature_span(function),
Type::BoundMethod(method) => signature_span(method.function(db)),
_ => None,
};
let superclass_definition_kind = definition.kind(db);

View File

@@ -107,6 +107,8 @@ pub struct TypeDisplayDetails<'db> {
pub targets: Vec<TextRange>,
/// Metadata for each range
pub details: Vec<TypeDetail<'db>>,
/// Whether the label is valid Python syntax
pub is_valid_syntax: bool,
}
/// Abstraction over "are we doing normal formatting, or tracking ranges with metadata?"
@@ -119,6 +121,7 @@ struct TypeDetailsWriter<'db> {
label: String,
targets: Vec<TextRange>,
details: Vec<TypeDetail<'db>>,
is_valid_syntax: bool,
}
impl<'db> TypeDetailsWriter<'db> {
@@ -127,6 +130,7 @@ impl<'db> TypeDetailsWriter<'db> {
label: String::new(),
targets: Vec::new(),
details: Vec::new(),
is_valid_syntax: true,
}
}
@@ -136,6 +140,7 @@ impl<'db> TypeDetailsWriter<'db> {
label: self.label,
targets: self.targets,
details: self.details,
is_valid_syntax: self.is_valid_syntax,
}
}
@@ -192,6 +197,13 @@ impl<'a, 'b, 'db> TypeWriter<'a, 'b, 'db> {
self.with_detail(TypeDetail::Type(ty))
}
fn set_invalid_syntax(&mut self) {
match self {
TypeWriter::Formatter(_) => {}
TypeWriter::Details(details) => details.is_valid_syntax = false,
}
}
fn join<'c>(&'c mut self, separator: &'static str) -> Join<'a, 'b, 'c, 'db> {
Join {
fmt: self,
@@ -539,6 +551,7 @@ impl<'db> FmtDetailed<'db> for ClassDisplay<'db> {
let line_index = line_index(self.db, file);
let class_offset = self.class.header_range(self.db).start();
let line_number = line_index.line_index(class_offset);
f.set_invalid_syntax();
write!(f, " @ {path}:{line_number}")?;
}
Ok(())
@@ -599,6 +612,7 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
.fmt_detailed(f),
},
Protocol::Synthesized(synthetic) => {
f.set_invalid_syntax();
f.write_char('<')?;
f.with_type(Type::SpecialForm(SpecialFormType::Protocol))
.write_str("Protocol")?;
@@ -618,6 +632,7 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
},
Type::PropertyInstance(_) => f.with_type(self.ty).write_str("property"),
Type::ModuleLiteral(module) => {
f.set_invalid_syntax();
write!(
f.with_type(self.ty),
"<module '{}'>",
@@ -625,6 +640,7 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
)
}
Type::ClassLiteral(class) => {
f.set_invalid_syntax();
let mut f = f.with_type(self.ty);
f.write_str("<class '")?;
class
@@ -633,6 +649,7 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
f.write_str("'>")
}
Type::GenericAlias(generic) => {
f.set_invalid_syntax();
let mut f = f.with_type(self.ty);
f.write_str("<class '")?;
generic
@@ -691,7 +708,7 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
db: self.db,
settings: self.settings.clone(),
};
f.set_invalid_syntax();
f.write_str("bound method ")?;
self_ty
.display_with(self.db, self.settings.singleline())
@@ -729,51 +746,57 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
}
}
}
Type::KnownBoundMethod(KnownBoundMethodType::FunctionTypeDunderGet(function)) => {
write!(
f,
"<method-wrapper `__get__` of `{function}`>",
function = function.name(self.db),
)
Type::KnownBoundMethod(method_type) => {
f.set_invalid_syntax();
match method_type {
KnownBoundMethodType::FunctionTypeDunderGet(function) => {
write!(
f,
"<method-wrapper `__get__` of `{function}`>",
function = function.name(self.db),
)
}
KnownBoundMethodType::FunctionTypeDunderCall(function) => {
write!(
f,
"<method-wrapper `__call__` of `{function}`>",
function = function.name(self.db),
)
}
KnownBoundMethodType::PropertyDunderGet(_) => {
f.write_str("<method-wrapper `__get__` of `property` object>")
}
KnownBoundMethodType::PropertyDunderSet(_) => {
f.write_str("<method-wrapper `__set__` of `property` object>")
}
KnownBoundMethodType::StrStartswith(_) => {
f.write_str("<method-wrapper `startswith` of `str` object>")
}
KnownBoundMethodType::ConstraintSetRange => {
f.write_str("bound method `ConstraintSet.range`")
}
KnownBoundMethodType::ConstraintSetAlways => {
f.write_str("bound method `ConstraintSet.always`")
}
KnownBoundMethodType::ConstraintSetNever => {
f.write_str("bound method `ConstraintSet.never`")
}
KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_) => {
f.write_str("bound method `ConstraintSet.implies_subtype_of`")
}
KnownBoundMethodType::ConstraintSetSatisfies(_) => {
f.write_str("bound method `ConstraintSet.satisfies`")
}
KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_) => {
f.write_str("bound method `ConstraintSet.satisfied_by_all_typevars`")
}
KnownBoundMethodType::GenericContextSpecializeConstrained(_) => {
f.write_str("bound method `GenericContext.specialize_constrained`")
}
}
}
Type::KnownBoundMethod(KnownBoundMethodType::FunctionTypeDunderCall(function)) => {
write!(
f,
"<method-wrapper `__call__` of `{function}`>",
function = function.name(self.db),
)
}
Type::KnownBoundMethod(KnownBoundMethodType::PropertyDunderGet(_)) => {
f.write_str("<method-wrapper `__get__` of `property` object>")
}
Type::KnownBoundMethod(KnownBoundMethodType::PropertyDunderSet(_)) => {
f.write_str("<method-wrapper `__set__` of `property` object>")
}
Type::KnownBoundMethod(KnownBoundMethodType::StrStartswith(_)) => {
f.write_str("<method-wrapper `startswith` of `str` object>")
}
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetRange) => {
f.write_str("bound method `ConstraintSet.range`")
}
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetAlways) => {
f.write_str("bound method `ConstraintSet.always`")
}
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetNever) => {
f.write_str("bound method `ConstraintSet.never`")
}
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)) => {
f.write_str("bound method `ConstraintSet.implies_subtype_of`")
}
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetSatisfies(_)) => {
f.write_str("bound method `ConstraintSet.satisfies`")
}
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(
_,
)) => f.write_str("bound method `ConstraintSet.satisfied_by_all_typevars`"),
Type::KnownBoundMethod(KnownBoundMethodType::GenericContextSpecializeConstrained(
_,
)) => f.write_str("bound method `GenericContext.specialize_constrained`"),
Type::WrapperDescriptor(kind) => {
f.set_invalid_syntax();
let (method, object) = match kind {
WrapperDescriptorKind::FunctionTypeDunderGet => ("__get__", "function"),
WrapperDescriptorKind::PropertyDunderGet => ("__get__", "property"),
@@ -782,9 +805,11 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
write!(f, "<wrapper-descriptor `{method}` of `{object}` objects>")
}
Type::DataclassDecorator(_) => {
f.set_invalid_syntax();
f.write_str("<decorator produced by dataclass-like function>")
}
Type::DataclassTransformer(_) => {
f.set_invalid_syntax();
f.write_str("<decorator produced by typing.dataclass_transform>")
}
Type::Union(union) => union
@@ -828,11 +853,13 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
write!(f, ".{}", enum_literal.name(self.db))
}
Type::TypeVar(bound_typevar) => {
f.set_invalid_syntax();
write!(f, "{}", bound_typevar.identity(self.db).display(self.db))
}
Type::AlwaysTruthy => f.with_type(self.ty).write_str("AlwaysTruthy"),
Type::AlwaysFalsy => f.with_type(self.ty).write_str("AlwaysFalsy"),
Type::BoundSuper(bound_super) => {
f.set_invalid_syntax();
f.write_str("<super: ")?;
Type::from(bound_super.pivot_class(self.db))
.display_with(self.db, self.settings.singleline())
@@ -852,6 +879,7 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
.display_with(self.db, self.settings.singleline())
.fmt_detailed(f)?;
if let Some(name) = type_is.place_name(self.db) {
f.set_invalid_syntax();
f.write_str(" @ ")?;
f.write_str(&name)?;
}
@@ -1029,6 +1057,7 @@ impl<'db> FmtDetailed<'db> for DisplayOverloadLiteral<'db> {
settings: self.settings.clone(),
};
f.set_invalid_syntax();
f.write_str("def ")?;
write!(f, "{}", self.literal.name(self.db))?;
type_parameters.fmt_detailed(f)?;
@@ -1075,7 +1104,7 @@ impl<'db> FmtDetailed<'db> for DisplayFunctionType<'db> {
db: self.db,
settings: self.settings.clone(),
};
f.set_invalid_syntax();
f.write_str("def ")?;
write!(f, "{}", self.ty.name(self.db))?;
type_parameters.fmt_detailed(f)?;
@@ -1256,6 +1285,7 @@ impl<'db> DisplayGenericContext<'_, 'db> {
if idx > 0 {
f.write_str(", ")?;
}
f.set_invalid_syntax();
f.write_str(bound_typevar.typevar(self.db).name(self.db))?;
}
f.write_char(']')
@@ -1268,6 +1298,7 @@ impl<'db> DisplayGenericContext<'_, 'db> {
if idx > 0 {
f.write_str(", ")?;
}
f.set_invalid_syntax();
write!(f, "{}", bound_typevar.identity(self.db).display(self.db))?;
}
f.write_char(']')
@@ -1358,6 +1389,7 @@ impl<'db> DisplaySpecialization<'db> {
if idx > 0 {
f.write_str(", ")?;
}
f.set_invalid_syntax();
write!(f, "{}", bound_typevar.identity(self.db).display(self.db))?;
f.write_str(" = ")?;
ty.display_with(self.db, self.settings.clone())
@@ -1505,6 +1537,7 @@ impl<'db> FmtDetailed<'db> for DisplaySignature<'_, 'db> {
fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result {
// Immediately write a marker signaling we're starting a signature
let _ = f.with_detail(TypeDetail::SignatureStart);
f.set_invalid_syntax();
// When we exit this function, write a marker signaling we're ending a signature
let mut f = f.with_detail(TypeDetail::SignatureEnd);
let multiline = self.settings.multiline && self.parameters.len() > 1;
@@ -1694,6 +1727,7 @@ impl<'db> FmtDetailed<'db> for DisplayOmitted {
} else {
self.plural
};
f.set_invalid_syntax();
write!(f, "... omitted {} {}", self.count, noun)
}
}
@@ -1908,6 +1942,7 @@ impl<'db> FmtDetailed<'db> for DisplayIntersectionType<'_, 'db> {
}),
);
f.set_invalid_syntax();
f.join(" & ").entries(tys).finish()
}
}
@@ -1960,6 +1995,7 @@ struct DisplayMaybeParenthesizedType<'db> {
impl<'db> FmtDetailed<'db> for DisplayMaybeParenthesizedType<'db> {
fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result {
let write_parentheses = |f: &mut TypeWriter<'_, '_, 'db>| {
f.set_invalid_syntax();
f.write_char('(')?;
self.ty
.display_with(self.db, self.settings.clone())

View File

@@ -522,14 +522,15 @@ impl<'db> GenericContext<'db> {
/// Creates a specialization of this generic context. Panics if the length of `types` does not
/// match the number of typevars in the generic context.
///
/// You are allowed to provide types that mention the typevars in this generic context.
pub(crate) fn specialize_recursive(
self,
db: &'db dyn Db,
mut types: Box<[Type<'db>]>,
) -> Specialization<'db> {
/// If any provided type is `None`, we will use the corresponding typevar's default type. You
/// are allowed to provide types that mention the typevars in this generic context.
pub(crate) fn specialize_recursive<I>(self, db: &'db dyn Db, types: I) -> Specialization<'db>
where
I: IntoIterator<Item = Option<Type<'db>>>,
I::IntoIter: ExactSizeIterator,
{
let mut types = self.fill_in_defaults(db, types);
let len = types.len();
assert!(self.len(db) == len);
loop {
let mut any_changed = false;
for i in 0..len {
@@ -564,10 +565,7 @@ impl<'db> GenericContext<'db> {
Specialization::new(db, self, Box::from([element_type]), None, Some(tuple))
}
/// Creates a specialization of this generic context. Panics if the length of `types` does not
/// match the number of typevars in the generic context. If any provided type is `None`, we
/// will use the corresponding typevar's default type.
pub(crate) fn specialize_partial<I>(self, db: &'db dyn Db, types: I) -> Specialization<'db>
fn fill_in_defaults<I>(self, db: &'db dyn Db, types: I) -> Box<[Type<'db>]>
where
I: IntoIterator<Item = Option<Type<'db>>>,
I::IntoIter: ExactSizeIterator,
@@ -610,7 +608,18 @@ impl<'db> GenericContext<'db> {
expanded[idx] = default;
}
Specialization::new(db, self, expanded.into_boxed_slice(), None, None)
expanded.into_boxed_slice()
}
/// Creates a specialization of this generic context. Panics if the length of `types` does not
/// match the number of typevars in the generic context. If any provided type is `None`, we
/// will use the corresponding typevar's default type.
pub(crate) fn specialize_partial<I>(self, db: &'db dyn Db, types: I) -> Specialization<'db>
where
I: IntoIterator<Item = Option<Type<'db>>>,
I::IntoIter: ExactSizeIterator,
{
Specialization::new(db, self, self.fill_in_defaults(db, types), None, None)
}
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
@@ -1045,11 +1054,6 @@ impl<'db> Specialization<'db> {
Specialization::new(db, self.generic_context(db), types, None, None)
}
#[must_use]
pub(crate) fn normalized(self, db: &'db dyn Db) -> Self {
self.normalized_impl(db, &NormalizedVisitor::default())
}
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
let types: Box<[_]> = self
.types(db)

View File

@@ -101,14 +101,14 @@ use crate::types::typed_dict::{
};
use crate::types::visitor::any_over_type;
use crate::types::{
CallDunderError, CallableBinding, CallableType, CallableTypes, ClassLiteral, ClassType,
DataclassParams, DynamicType, InternedType, IntersectionBuilder, IntersectionType, KnownClass,
KnownInstanceType, LintDiagnosticGuard, MemberLookupPolicy, MetaclassCandidate,
PEP695TypeAliasType, Parameter, ParameterForm, Parameters, SpecialFormType, SubclassOfType,
TrackedConstraintSet, Truthiness, Type, TypeAliasType, TypeAndQualifiers, TypeContext,
TypeQualifiers, TypeVarBoundOrConstraintsEvaluation, TypeVarDefaultEvaluation, TypeVarIdentity,
TypeVarInstance, TypeVarKind, TypeVarVariance, TypedDictType, UnionBuilder, UnionType,
UnionTypeInstance, binding_type, liskov, todo_type,
CallDunderError, CallableBinding, CallableType, CallableTypeInstance, CallableTypes,
ClassLiteral, ClassType, DataclassParams, DynamicType, InternedType, IntersectionBuilder,
IntersectionType, KnownClass, KnownInstanceType, LintDiagnosticGuard, MemberLookupPolicy,
MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm, Parameters, SpecialFormType,
SubclassOfType, TrackedConstraintSet, Truthiness, Type, TypeAliasType, TypeAndQualifiers,
TypeContext, TypeInContext, TypeQualifiers, TypeVarBoundOrConstraintsEvaluation,
TypeVarDefaultEvaluation, TypeVarIdentity, TypeVarInstance, TypeVarKind, TypeVarVariance,
TypedDictType, UnionBuilder, UnionType, UnionTypeInstance, binding_type, liskov, todo_type,
};
use crate::types::{ClassBase, add_inferred_python_version_hint_to_diagnostic};
use crate::unpack::{EvaluationMode, UnpackPosition};
@@ -4739,6 +4739,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
unpacked.expression_type(target)
}
TargetKind::Single => {
// This could be an implicit type alias (OptionalList = list[T] | None). Use the definition
// of `OptionalList` as the binding context while inferring the RHS (`list[T] | None`), in
// order to bind `T` to `OptionalList`.
let previous_typevar_binding_context =
self.typevar_binding_context.replace(definition);
let value_ty = if let Some(standalone_expression) = self.index.try_expression(value)
{
self.infer_standalone_expression_impl(value, standalone_expression, tcx)
@@ -4777,6 +4783,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.infer_expression(value, tcx)
};
self.typevar_binding_context = previous_typevar_binding_context;
// `TYPE_CHECKING` is a special variable that should only be assigned `False`
// at runtime, but is always considered `True` in type checking.
// See mdtest/known_constants.md#user-defined-type_checking for details.
@@ -5523,11 +5531,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.deferred_state = DeferredExpressionState::Deferred;
}
// This might be a PEP-613 type alias (`OptionalList: TypeAlias = list[T] | None`). Use
// the definition of `OptionalList` as the binding context while inferring the
// RHS (`list[T] | None`), in order to bind `T` to `OptionalList`.
let previous_typevar_binding_context = self.typevar_binding_context.replace(definition);
let inferred_ty = self.infer_maybe_standalone_expression(
value,
TypeContext::new(Some(declared.inner_type())),
);
self.typevar_binding_context = previous_typevar_binding_context;
self.deferred_state = previous_deferred_state;
self.dataclass_field_specifiers.clear();
@@ -10794,7 +10809,52 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
fn infer_subscript_load(&mut self, subscript: &ast::ExprSubscript) -> Type<'db> {
let value_ty = self.infer_expression(&subscript.value, TypeContext::default());
self.infer_subscript_load_impl(value_ty, subscript)
// If we have an implicit type alias like `MyList = list[T]`, and if `MyList` is being
// used in another implicit type alias like `Numbers = MyList[int]`, then we infer the
// right hand side as a value expression, and need to handle the specialization here.
if let Type::KnownInstance(KnownInstanceType::GenericAlias(alias)) = value_ty {
return Type::KnownInstance(KnownInstanceType::GenericAlias(TypeInContext::new(
self.db(),
self.infer_explicitly_specialized_type_alias(
subscript,
value_ty,
alias.binding_context(self.db()),
false,
),
self.typevar_binding_context,
)));
}
// if let Type::GenericAlias(alias) = value_ty {
// return Type::KnownInstance(KnownInstanceType::GenericAlias(TypeInContext::new(
// self.db(),
// self.infer_explicitly_specialized_type_alias(
// subscript,
// value_ty,
// Some(alias.definition(self.db())),
// false,
// ),
// self.typevar_binding_context,
// )));
// }
let result_ty = self.infer_subscript_load_impl(value_ty, subscript);
// let result_ty = if result_ty.is_generic_alias() {
// Type::KnownInstance(KnownInstanceType::GenericAlias(TypeInContext::new(
// self.db(),
// result_ty,
// self.typevar_binding_context,
// )))
// } else {
// result_ty
// };
// eprintln!("Subscripting type: {}", value_ty.display(self.db()));
// eprintln!("Inferred subscript type: {}", result_ty.display(self.db()));
result_ty
}
fn infer_subscript_load_impl(
@@ -10848,7 +10908,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
} else if class.is_known(self.db(), KnownClass::Type) {
let argument_ty = self.infer_type_expression(slice);
return Type::KnownInstance(KnownInstanceType::TypeGenericAlias(
InternedType::new(self.db(), argument_ty),
TypeInContext::new(self.db(), argument_ty, self.typevar_binding_context),
));
}
@@ -10926,9 +10986,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let ty = self.infer_type_expression(type_expr);
return Type::KnownInstance(KnownInstanceType::Annotated(InternedType::new(
return Type::KnownInstance(KnownInstanceType::Annotated(TypeInContext::new(
self.db(),
ty,
self.typevar_binding_context,
)));
}
Type::SpecialForm(SpecialFormType::Optional) => {
@@ -10959,6 +11020,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
match **slice {
ast::Expr::Tuple(ref tuple) => {
let typevar_binding_context = self.typevar_binding_context;
let mut elements = tuple
.elts
.iter()
@@ -10971,6 +11033,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
db,
None,
Ok(UnionType::from_elements(db, elements)),
typevar_binding_context,
),
));
@@ -10995,7 +11058,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// Similar to the branch above that handles `type[…]`, handle `typing.Type[…]`
let argument_ty = self.infer_type_expression(slice);
return Type::KnownInstance(KnownInstanceType::TypeGenericAlias(
InternedType::new(self.db(), argument_ty),
TypeInContext::new(self.db(), argument_ty, self.typevar_binding_context),
));
}
Type::SpecialForm(SpecialFormType::Callable) => {
@@ -11004,7 +11067,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
.as_callable()
.expect("always returns Type::Callable");
return Type::KnownInstance(KnownInstanceType::Callable(callable));
return Type::KnownInstance(KnownInstanceType::Callable(
CallableTypeInstance::new(self.db(), callable, self.typevar_binding_context),
));
}
// `typing` special forms with a single generic argument
Type::SpecialForm(
@@ -11095,8 +11160,32 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
.map(Type::from)
.unwrap_or_else(Type::unknown);
}
Type::KnownInstance(KnownInstanceType::UnionType(_)) => {
return todo_type!("Specialization of union type alias");
Type::KnownInstance(KnownInstanceType::UnionType(instance)) => {
return self.infer_explicitly_specialized_type_alias(
subscript,
value_ty,
instance.binding_context(self.db()),
false,
);
}
Type::KnownInstance(
KnownInstanceType::Annotated(instance)
| KnownInstanceType::TypeGenericAlias(instance),
) => {
return self.infer_explicitly_specialized_type_alias(
subscript,
value_ty,
instance.binding_context(self.db()),
false,
);
}
Type::KnownInstance(KnownInstanceType::Callable(instance)) => {
return self.infer_explicitly_specialized_type_alias(
subscript,
value_ty,
instance.binding_context(self.db()),
false,
);
}
_ => {}
}

View File

@@ -2,6 +2,8 @@ use itertools::Either;
use ruff_python_ast as ast;
use super::{DeferredExpressionState, TypeInferenceBuilder};
use crate::FxOrderSet;
use crate::semantic_index::definition::Definition;
use crate::types::diagnostic::{
self, INVALID_TYPE_FORM, NON_SUBSCRIPTABLE, report_invalid_argument_number_to_special_form,
report_invalid_arguments_to_callable,
@@ -11,9 +13,10 @@ use crate::types::string_annotation::parse_string_annotation;
use crate::types::tuple::{TupleSpecBuilder, TupleType};
use crate::types::visitor::any_over_type;
use crate::types::{
CallableType, DynamicType, IntersectionBuilder, KnownClass, KnownInstanceType,
LintDiagnosticGuard, Parameter, Parameters, SpecialFormType, SubclassOfType, Type,
TypeAliasType, TypeContext, TypeIsType, UnionBuilder, UnionType, todo_type,
BindingContext, CallableType, DynamicType, GenericContext, IntersectionBuilder, KnownClass,
KnownInstanceType, LintDiagnosticGuard, Parameter, Parameters, SpecialFormType, SubclassOfType,
Type, TypeAliasType, TypeContext, TypeInContext, TypeIsType, TypeMapping, UnionBuilder,
UnionType, todo_type,
};
/// Type expressions
@@ -679,11 +682,13 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
Type::unknown()
}
ast::Expr::Subscript(ast::ExprSubscript {
value,
slice: parameters,
..
}) => {
ast::Expr::Subscript(
subscript @ ast::ExprSubscript {
value,
slice: parameters,
..
},
) => {
let parameters_ty = match self.infer_expression(value, TypeContext::default()) {
Type::SpecialForm(SpecialFormType::Union) => match &**parameters {
ast::Expr::Tuple(tuple) => {
@@ -698,6 +703,40 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
_ => self.infer_subclass_of_type_expression(parameters),
},
value_ty @ Type::ClassLiteral(class_literal) => {
if class_literal.is_protocol(self.db()) {
SubclassOfType::from(
self.db(),
todo_type!("type[T] for protocols").expect_dynamic(),
)
} else {
match class_literal.generic_context(self.db()) {
Some(generic_context) => {
let db = self.db();
let specialize = |types: &[Option<Type<'db>>]| {
SubclassOfType::from(
db,
class_literal.apply_specialization(db, |_| {
generic_context
.specialize_partial(db, types.iter().copied())
}),
)
};
self.infer_explicit_callable_specialization(
subscript,
value_ty,
generic_context,
specialize,
)
}
None => {
// TODO: emit a diagnostic if you try to specialize a non-generic class.
self.infer_type_expression(parameters);
todo_type!("specialized non-generic class")
}
}
}
}
_ => {
self.infer_type_expression(parameters);
todo_type!("unsupported nested subscript in type[X]")
@@ -714,6 +753,84 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
}
/// Infer the type of an explicitly specialized generic type alias (implicit or PEP 613).
pub(crate) fn infer_explicitly_specialized_type_alias(
&mut self,
subscript: &ast::ExprSubscript,
mut value_ty: Type<'db>,
typevar_binding_context: Option<Definition<'db>>,
in_type_expression: bool,
) -> Type<'db> {
let db = self.db();
let Some(typevar_binding_context) = typevar_binding_context else {
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
let mut diag = builder.into_diagnostic(format_args!(
"`{}` is not subscriptable",
value_ty.display(db)
));
diag.info("Consider creating a type alias to create a binding context for the type variable(s)");
}
return Type::unknown();
};
if let Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) = value_ty
&& let Some(definition) = typevar.definition(db)
{
value_ty = value_ty.apply_type_mapping(
db,
&TypeMapping::BindLegacyTypevars(BindingContext::Definition(definition)),
TypeContext::default(),
);
}
let mut variables = FxOrderSet::default();
value_ty.find_legacy_typevars(db, Some(typevar_binding_context), &mut variables);
let generic_context = GenericContext::from_typevar_instances(db, variables);
let scope_id = self.scope();
let current_typevar_binding_context = self.typevar_binding_context;
let specialize = |types: &[Option<Type<'db>>]| {
let specialized = value_ty.apply_specialization(
db,
generic_context.specialize_partial(db, types.iter().copied()),
);
if in_type_expression {
specialized
.in_type_expression(db, scope_id, current_typevar_binding_context)
.unwrap_or_else(|_| Type::unknown())
} else {
// Update the binding context
match specialized {
// Type::GenericAlias(alias) => Type::GenericAlias(GenericAlias::new(
// db,
// alias.origin(db),
// alias.specialization(db),
// current_typevar_binding_context,
// )),
Type::KnownInstance(KnownInstanceType::TypeGenericAlias(instance)) => {
Type::KnownInstance(KnownInstanceType::TypeGenericAlias(
TypeInContext::new(
db,
instance.inner(db),
current_typevar_binding_context,
),
))
}
_ => specialized,
}
}
};
self.infer_explicit_callable_specialization(
subscript,
value_ty,
generic_context,
specialize,
)
}
fn infer_subscript_type_expression(
&mut self,
subscript: &ast::ExprSubscript,
@@ -804,10 +921,6 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
Type::unknown()
}
KnownInstanceType::TypeVar(_) => {
self.infer_type_expression(slice);
todo_type!("TypeVar annotations")
}
KnownInstanceType::TypeAliasType(type_alias @ TypeAliasType::PEP695(_)) => {
match type_alias.generic_context(self.db()) {
Some(generic_context) => {
@@ -850,11 +963,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
self.infer_type_expression(slice);
todo_type!("Generic stringified PEP-613 type alias")
}
KnownInstanceType::UnionType(_) => {
self.infer_type_expression(slice);
todo_type!("Generic specialization of types.UnionType")
}
KnownInstanceType::Literal(ty) | KnownInstanceType::TypeGenericAlias(ty) => {
KnownInstanceType::Literal(ty) => {
self.infer_type_expression(slice);
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
builder.into_diagnostic(format_args!(
@@ -864,14 +973,36 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
Type::unknown()
}
KnownInstanceType::Callable(_) => {
self.infer_type_expression(slice);
todo_type!("Generic specialization of typing.Callable")
}
KnownInstanceType::Annotated(_) => {
self.infer_type_expression(slice);
todo_type!("Generic specialization of typing.Annotated")
}
KnownInstanceType::TypeVar(instance) => self
.infer_explicitly_specialized_type_alias(
subscript,
value_ty,
instance.definition(self.db()),
false,
),
KnownInstanceType::UnionType(instance) => self
.infer_explicitly_specialized_type_alias(
subscript,
value_ty,
instance.binding_context(self.db()),
true,
),
KnownInstanceType::Annotated(instance)
| KnownInstanceType::TypeGenericAlias(instance)
| KnownInstanceType::GenericAlias(instance) => self
.infer_explicitly_specialized_type_alias(
subscript,
value_ty,
instance.binding_context(self.db()),
true,
),
KnownInstanceType::Callable(instance) => self
.infer_explicitly_specialized_type_alias(
subscript,
value_ty,
instance.binding_context(self.db()),
true,
),
KnownInstanceType::NewType(newtype) => {
self.infer_type_expression(&subscript.slice);
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
@@ -912,13 +1043,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
}
}
Type::GenericAlias(_) => {
self.infer_type_expression(slice);
// If the generic alias is already fully specialized, this is an error. But it
// could have been specialized with another typevar (e.g. a type alias like `MyList
// = list[T]`), in which case it's later valid to do `MyList[int]`.
todo_type!("specialized generic alias in type expression")
}
// Type::GenericAlias(alias) => {
// self.infer_explicitly_specialized_type_alias(subscript, value_ty, None, true)
// }
Type::StringLiteral(_) => {
self.infer_type_expression(slice);
// For stringified TypeAlias; remove once properly supported
@@ -927,6 +1054,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
_ => {
self.infer_type_expression(slice);
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
dbg!(&value_ty);
builder.into_diagnostic(format_args!(
"Invalid subscript of object of type `{}` in type expression",
value_ty.display(self.db())

View File

@@ -49,11 +49,6 @@ fn check_class_declaration<'db>(
return;
};
// TODO: classmethods and staticmethods
if function.is_classmethod(db) || function.is_staticmethod(db) {
return;
}
// Constructor methods are not checked for Liskov compliance
if matches!(
&*member.name,

View File

@@ -100,7 +100,13 @@ impl<'db> Mro<'db> {
if original_bases.contains(&Type::SpecialForm(SpecialFormType::Protocol)) {
return;
}
if remaining_bases.iter().any(Type::is_generic_alias) {
if remaining_bases.iter().any(|ty| {
matches!(
ty,
Type::GenericAlias(..)
| Type::KnownInstance(KnownInstanceType::GenericAlias(_))
)
}) {
return;
}
resolved_bases.push(ClassBase::Generic);

View File

@@ -300,7 +300,7 @@ impl<'db> ProtocolInterface<'db> {
.and(db, || {
our_type.has_relation_to_impl(
db,
Type::Callable(other_type.bind_self(db, None)),
Type::Callable(protocol_bind_self(db, other_type, None)),
inferable,
relation,
relation_visitor,
@@ -313,7 +313,7 @@ impl<'db> ProtocolInterface<'db> {
ProtocolMemberKind::Method(other_method),
) => our_method.bind_self(db, None).has_relation_to_impl(
db,
other_method.bind_self(db, None),
protocol_bind_self(db, other_method, None),
inferable,
relation,
relation_visitor,
@@ -712,7 +712,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
.map(|callable| callable.apply_self(db, fallback_other))
.has_relation_to_impl(
db,
method.bind_self(db, Some(fallback_other)),
protocol_bind_self(db, *method, Some(fallback_other)),
inferable,
relation,
relation_visitor,
@@ -912,3 +912,16 @@ fn proto_interface_cycle_initial<'db>(
) -> ProtocolInterface<'db> {
ProtocolInterface::empty(db)
}
/// Bind `self`, and *also* discard the functionlike-ness of the callable.
///
/// This additional upcasting is required in order for protocols with `__call__` method
/// members to be considered assignable to `Callable` types, since the `Callable` supertype
/// of the `__call__` method will be function-like but a `Callable` type is not.
fn protocol_bind_self<'db>(
db: &'db dyn Db,
callable: CallableType<'db>,
self_type: Option<Type<'db>>,
) -> CallableType<'db> {
CallableType::new(db, callable.signatures(db).bind_self(db, self_type), false)
}

View File

@@ -2,7 +2,8 @@ use std::borrow::Cow;
use lsp_types::request::InlayHintRequest;
use lsp_types::{InlayHintParams, Url};
use ty_ide::{InlayHintKind, InlayHintLabel, inlay_hints};
use ruff_db::files::File;
use ty_ide::{InlayHintKind, InlayHintLabel, InlayHintTextEdit, inlay_hints};
use ty_project::ProjectDatabase;
use crate::PositionEncoding;
@@ -64,7 +65,14 @@ impl BackgroundDocumentRequestHandler for InlayHintRequestHandler {
padding_left: None,
padding_right: None,
data: None,
text_edits: None,
text_edits: Some(
hint.text_edits
.into_iter()
.filter_map(|text_edit| {
inlay_hint_text_edit(text_edit, db, file, snapshot.encoding())
})
.collect(),
),
})
})
.collect();
@@ -100,3 +108,26 @@ fn inlay_hint_label(
}
lsp_types::InlayHintLabel::LabelParts(label_parts)
}
fn inlay_hint_text_edit(
inlay_hint_text_edit: InlayHintTextEdit,
db: &ProjectDatabase,
file: File,
encoding: PositionEncoding,
) -> Option<lsp_types::TextEdit> {
Some(lsp_types::TextEdit {
range: lsp_types::Range {
start: inlay_hint_text_edit
.range
.start()
.to_lsp_position(db, file, encoding)?
.local_position(),
end: inlay_hint_text_edit
.range
.end()
.to_lsp_position(db, file, encoding)?
.local_position(),
},
new_text: inlay_hint_text_edit.new_text,
})
}

View File

@@ -63,7 +63,22 @@ y = foo(1)
}
}
],
"kind": 1
"kind": 1,
"textEdits": [
{
"range": {
"start": {
"line": 5,
"character": 1
},
"end": {
"line": 5,
"character": 1
}
},
"newText": ": int"
}
]
},
{
"position": {
@@ -91,7 +106,8 @@ y = foo(1)
"value": "="
}
],
"kind": 2
"kind": 2,
"textEdits": []
}
]
"#);

View File

@@ -501,7 +501,7 @@ export interface InitializedPlayground {
// Run once during startup. Initializes monaco, loads the wasm file, and restores the previous editor state.
async function startPlayground(): Promise<InitializedPlayground> {
const ty = await import("../ty_wasm");
const ty = await import("ty_wasm");
await ty.default();
const version = ty.version();
const monaco = await loader.init();

View File

@@ -3,6 +3,7 @@ import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react-swc";
import { dirname, join } from "path";
import { fileURLToPath } from "url";
import { normalizePath } from "vite";
import { viteStaticCopy } from "vite-plugin-static-copy";
const PYODIDE_EXCLUDE = [
@@ -15,15 +16,17 @@ const PYODIDE_EXCLUDE = [
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss(), viteStaticCopyPyodide()],
optimizeDeps: { exclude: ["pyodide"] },
optimizeDeps: { exclude: ["pyodide", "ty_wasm"] },
});
export function viteStaticCopyPyodide() {
const pyodideDir = dirname(fileURLToPath(import.meta.resolve("pyodide")));
const pyodideDir = normalizePath(
join(dirname(fileURLToPath(import.meta.resolve("pyodide"))), "*"),
);
return viteStaticCopy({
targets: [
{
src: [join(pyodideDir, "*"), ...PYODIDE_EXCLUDE],
src: [pyodideDir, ...PYODIDE_EXCLUDE],
dest: "assets",
},
],

View File

@@ -14,6 +14,7 @@ from __future__ import annotations
import argparse
import subprocess
from pathlib import Path
from _utils import ROOT_DIR, dir_name, get_indent, pascal_case, snake_case
@@ -153,7 +154,7 @@ pub(crate) fn {rule_name_snake}(checker: &mut Checker) {{}}
_rustfmt(rules_mod)
def _rustfmt(path: str) -> None:
def _rustfmt(path: str | Path) -> None:
subprocess.run(["rustfmt", path])

View File

@@ -22,10 +22,11 @@ import re
import tempfile
import time
from asyncio.subprocess import PIPE, create_subprocess_exec
from collections.abc import Awaitable
from contextlib import asynccontextmanager, nullcontext
from pathlib import Path
from signal import SIGINT, SIGTERM
from typing import TYPE_CHECKING, NamedTuple, Self, TypeVar
from typing import TYPE_CHECKING, Any, NamedTuple, Self, TypeVar
if TYPE_CHECKING:
from collections.abc import AsyncIterator, Iterator, Sequence
@@ -342,7 +343,7 @@ DIFF_LINE_RE = re.compile(
r"^(?P<pre>[+-]) (?P<inner>(?P<path>[^:]+):(?P<lnum>\d+):\d+:) (?P<post>.*)$",
)
T = TypeVar("T")
T = TypeVar("T", bound=Awaitable[Any])
async def main(

View File

@@ -1,7 +1,7 @@
[project]
name = "scripts"
version = "0.0.1"
dependencies = ["stdlibs"]
dependencies = ["stdlibs", "tqdm", "mdformat", "pyyaml"]
requires-python = ">=3.12"
[tool.black]
@@ -9,3 +9,11 @@ line-length = 88
[tool.ruff]
extend = "../pyproject.toml"
[tool.ty.src]
# `ty_benchmark` is a standalone project with its own pyproject.toml files, search paths, etc.
exclude = ["./ty_benchmark"]
[tool.ty.rules]
possibly-unresolved-reference = "error"
division-by-zero = "error"

View File

@@ -19,3 +19,7 @@ packages = ["src/benchmark"]
ignore = [
"E501", # We use ruff format
]
[tool.ty.rules]
possibly-unresolved-reference = "error"
division-by-zero = "error"

View File

@@ -43,7 +43,7 @@ def format_number(number: int) -> str:
# underscore-delimited in the generated file, so we now preserve that property to
# avoid unnecessary churn.
if number > 100000:
number = str(number)
number: str = str(number)
number = "_".join(number[i : i + 3] for i in range(0, len(number), 3))
return f"{number}_u32"