Compare commits

..

21 Commits

Author SHA1 Message Date
David Peter
0252ee6531 Equivalence 2025-06-05 15:48:09 +02:00
David Peter
969efae929 Update tests 2025-06-04 16:46:46 +02:00
David Peter
e08024ffda Revert "Update lists"
This reverts commit 9981ac7ecc.
2025-06-04 16:42:17 +02:00
David Peter
9981ac7ecc Update lists 2025-06-04 16:23:53 +02:00
David Peter
30c9a5d47e Cycle recorvery and todo types 2025-06-04 16:01:45 +02:00
David Peter
7684e23612 Change inference 2025-06-04 14:49:41 +02:00
David Peter
e9b1fc3942 Introduce TypeAliasRef 2025-06-04 14:32:51 +02:00
David Peter
11db567b0b [ty] ty_ide: Hotfix for expression_scope_id panics (#18455)
## Summary

Implement a hotfix for the playground/LSP crashes related to missing
`expression_scope_id`s.

relates to: https://github.com/astral-sh/ty/issues/572

## Test Plan

* Regression tests from https://github.com/astral-sh/ruff/pull/18441
* Ran the playground locally to check if panics occur / completions
still work.

---------

Co-authored-by: Andrew Gallant <andrew@astral.sh>
2025-06-04 10:39:16 +02:00
David Peter
9f8c3de462 [ty] Improve docs for Class{Literal,Type}::instance_member (#18454)
## Summary

Mostly just refer to `Type::instance_member` which has much more
details.
2025-06-04 09:55:45 +02:00
David Peter
293d4ac388 [ty] Add meta-type tests for legavy TypeVars (#18453)
## Summary

Follow up to the comment by @dcreager
[here](https://github.com/astral-sh/ruff/pull/18439#discussion_r2123802784).
2025-06-04 07:44:44 +00:00
Carl Meyer
9e8a7e9353 update to salsa that doesn't panic silently on cycles (#18450) 2025-06-04 07:40:16 +02:00
Dhruv Manilawala
453e5f5934 [ty] Add tests for empty list/tuple unpacking (#18451)
## Summary

This PR is to address this comment:
https://github.com/astral-sh/ruff/pull/18438#issuecomment-2935344415

## Test Plan

Run mdtest
2025-06-04 02:40:26 +00:00
Dhruv Manilawala
7ea773daf2 [ty] Argument type expansion for overload call evaluation (#18382)
## Summary

Part of astral-sh/ty#104, closes: astral-sh/ty#468

This PR implements the argument type expansion which is step 3 of the
overload call evaluation algorithm.

Specifically, this step needs to be taken if type checking resolves to
no matching overload and there are argument types that can be expanded.

## Test Plan

Add new test cases.

## Ecosystem analysis

This PR removes 174 `no-matching-overload` false positives -- I looked
at a lot of them and they all are false positives.

One thing that I'm not able to understand is that in
2b7e3adf27/sphinx/ext/autodoc/preserve_defaults.py (L179)
the inferred type of `value` is `str | None` by ty and Pyright, which is
correct, but it's only ty that raises `invalid-argument-type` error
while Pyright doesn't. The constructor method of `DefaultValue` has
declared type of `str` which is invalid.

There are few cases of false positives resulting due to the fact that ty
doesn't implement narrowing on attribute expressions.
2025-06-04 02:12:00 +00:00
Alex Waygood
0079cc6817 [ty] Minor cleanup for site-packages discovery logic (#18446) 2025-06-03 18:49:14 +00:00
Matthew Mckee
e8ea40012a [ty] Add generic inference for dataclasses (#18443)
## Summary

An issue seen here https://github.com/astral-sh/ty/issues/500

The `__init__` method of dataclasses had no inherited generic context,
so we could not infer the type of an instance from a constructor call
with generics

## Test Plan

Add tests to classes.md` in generics folder
2025-06-03 09:59:43 -07:00
Abhijeet Prasad Bodas
71d8a5da2a [ty] dataclasses: Allow using dataclasses.dataclass as a function. (#18440)
## Summary

Part of https://github.com/astral-sh/ty/issues/111

Using `dataclass` as a function, instead of as a decorator did not work
as expected prior to this.
Fix that by modifying the dataclass overload's return type.

## Test Plan

New mdtests, fixing the existing TODO.
2025-06-03 09:50:29 -07:00
Douglas Creager
2c3b3d3230 [ty] Create separate FunctionLiteral and FunctionType types (#18360)
This updates our representation of functions to more closely match our
representation of classes.

The new `OverloadLiteral` and `FunctionLiteral` classes represent a
function definition in the AST. If a function is generic, this is
unspecialized. `FunctionType` has been updated to represent a function
type, which is specialized if the function is generic. (These names are
chosen to match `ClassLiteral` and `ClassType` on the class side.)

This PR does not add a separate `Type` variant for `FunctionLiteral`.
Maybe we should? Possibly as a follow-on PR?

Part of https://github.com/astral-sh/ty/issues/462

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2025-06-03 10:59:31 -04:00
Dhruv Manilawala
8d98c601d8 [ty] Infer list[T] when unpacking non-tuple type (#18438)
## Summary

Follow-up from #18401, I was looking at whether that would fix the issue
at https://github.com/astral-sh/ty/issues/247#issuecomment-2917656676
and it didn't, which made me realize that the PR only inferred `list[T]`
when the value type was tuple but it could be other types as well.

This PR fixes the actual issue by inferring `list[T]` for the non-tuple
type case.

## Test Plan

Add test cases for starred expression involved with non-tuple type. I
also added a few test cases for list type and list literal.

I also verified that the example in the linked issue comment works:
```py
def _(line: str):
    a, b, *c = line.split(maxsplit=2)
    c.pop()
```
2025-06-03 19:17:47 +05:30
David Peter
0986edf427 [ty] Meta-type of type variables should be type[..] (#18439)
## Summary

Came across this while debugging some ecosystem changes in
https://github.com/astral-sh/ruff/pull/18347. I think the meta-type of a
typevar-annotated variable should be equal to `type`, not `<class
'object'>`.

## Test Plan

New Markdown tests.
2025-06-03 15:22:00 +02:00
chiri
03f1f8e218 [pyupgrade] Make fix unsafe if it deletes comments (UP050) (#18390)
<!--
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
/closes #18387
<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan
update snapshots
<!-- How was it tested? -->
2025-06-03 09:10:15 -04:00
chiri
628bb2cd1d [pyupgrade] Make fix unsafe if it deletes comments (UP004) (#18393)
<!--
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
https://github.com/astral-sh/ruff/issues/18387#issuecomment-2923039331
<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan
update snapshots
<!-- How was it tested? -->
2025-06-03 09:09:33 -04:00
40 changed files with 2776 additions and 1197 deletions

7
Cargo.lock generated
View File

@@ -3194,7 +3194,7 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "salsa"
version = "0.22.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=2b5188778e91a5ab50cb7d827148caf7eb2f4630#2b5188778e91a5ab50cb7d827148caf7eb2f4630"
source = "git+https://github.com/carljm/salsa.git?rev=0f6d406f6c309964279baef71588746b8c67b4a3#0f6d406f6c309964279baef71588746b8c67b4a3"
dependencies = [
"boxcar",
"compact_str",
@@ -3218,14 +3218,13 @@ dependencies = [
[[package]]
name = "salsa-macro-rules"
version = "0.22.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=2b5188778e91a5ab50cb7d827148caf7eb2f4630#2b5188778e91a5ab50cb7d827148caf7eb2f4630"
source = "git+https://github.com/carljm/salsa.git?rev=0f6d406f6c309964279baef71588746b8c67b4a3#0f6d406f6c309964279baef71588746b8c67b4a3"
[[package]]
name = "salsa-macros"
version = "0.22.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=2b5188778e91a5ab50cb7d827148caf7eb2f4630#2b5188778e91a5ab50cb7d827148caf7eb2f4630"
source = "git+https://github.com/carljm/salsa.git?rev=0f6d406f6c309964279baef71588746b8c67b4a3#0f6d406f6c309964279baef71588746b8c67b4a3"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",

View File

@@ -129,7 +129,7 @@ regex = { version = "1.10.2" }
rustc-hash = { version = "2.0.0" }
rustc-stable-hash = { version = "0.1.2" }
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "2b5188778e91a5ab50cb7d827148caf7eb2f4630" }
salsa = { git = "https://github.com/carljm/salsa.git", rev = "0f6d406f6c309964279baef71588746b8c67b4a3" }
schemars = { version = "0.8.16" }
seahash = { version = "4.1.0" }
serde = { version = "1.0.197", features = ["derive"] }

View File

@@ -1,6 +1,7 @@
use crate::checkers::ast::Checker;
use crate::fix::edits::{Parentheses, remove_argument};
use crate::{Fix, FixAvailability, Violation};
use ruff_diagnostics::Applicability;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::StmtClassDef;
use ruff_text_size::Ranged;
@@ -63,13 +64,21 @@ pub(crate) fn useless_class_metaclass_type(checker: &Checker, class_def: &StmtCl
);
diagnostic.try_set_fix(|| {
remove_argument(
let edit = remove_argument(
keyword,
arguments,
Parentheses::Remove,
checker.locator().contents(),
)
.map(Fix::safe_edit)
)?;
let range = edit.range();
let applicability = if checker.comment_ranges().intersects(range) {
Applicability::Unsafe
} else {
Applicability::Safe
};
Ok(Fix::applicable_edit(edit, applicability))
});
}
}

View File

@@ -1,3 +1,4 @@
use ruff_diagnostics::Applicability;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast as ast;
use ruff_text_size::Ranged;
@@ -61,14 +62,23 @@ pub(crate) fn useless_object_inheritance(checker: &Checker, class_def: &ast::Stm
},
base.range(),
);
diagnostic.try_set_fix(|| {
remove_argument(
let edit = remove_argument(
base,
arguments,
Parentheses::Remove,
checker.locator().contents(),
)
.map(Fix::safe_edit)
)?;
let range = edit.range();
let applicability = if checker.comment_ranges().intersects(range) {
Applicability::Unsafe
} else {
Applicability::Safe
};
Ok(Fix::applicable_edit(edit, applicability))
});
}
}

View File

@@ -51,7 +51,7 @@ UP004.py:16:5: UP004 [*] Class `A` inherits from `object`
|
= help: Remove `object` inheritance
Safe fix
Unsafe fix
12 12 | ...
13 13 |
14 14 |
@@ -75,7 +75,7 @@ UP004.py:24:5: UP004 [*] Class `A` inherits from `object`
|
= help: Remove `object` inheritance
Safe fix
Unsafe fix
19 19 | ...
20 20 |
21 21 |
@@ -99,7 +99,7 @@ UP004.py:31:5: UP004 [*] Class `A` inherits from `object`
|
= help: Remove `object` inheritance
Safe fix
Unsafe fix
26 26 | ...
27 27 |
28 28 |
@@ -122,7 +122,7 @@ UP004.py:37:5: UP004 [*] Class `A` inherits from `object`
|
= help: Remove `object` inheritance
Safe fix
Unsafe fix
33 33 | ...
34 34 |
35 35 |
@@ -146,7 +146,7 @@ UP004.py:45:5: UP004 [*] Class `A` inherits from `object`
|
= help: Remove `object` inheritance
Safe fix
Unsafe fix
40 40 | ...
41 41 |
42 42 |
@@ -171,7 +171,7 @@ UP004.py:53:5: UP004 [*] Class `A` inherits from `object`
|
= help: Remove `object` inheritance
Safe fix
Unsafe fix
48 48 | ...
49 49 |
50 50 |
@@ -196,7 +196,7 @@ UP004.py:61:5: UP004 [*] Class `A` inherits from `object`
|
= help: Remove `object` inheritance
Safe fix
Unsafe fix
56 56 | ...
57 57 |
58 58 |
@@ -221,7 +221,7 @@ UP004.py:69:5: UP004 [*] Class `A` inherits from `object`
|
= help: Remove `object` inheritance
Safe fix
Unsafe fix
64 64 | ...
65 65 |
66 66 |
@@ -320,7 +320,7 @@ UP004.py:98:5: UP004 [*] Class `B` inherits from `object`
|
= help: Remove `object` inheritance
Safe fix
Unsafe fix
95 95 |
96 96 |
97 97 | class B(
@@ -381,7 +381,7 @@ UP004.py:125:5: UP004 [*] Class `A` inherits from `object`
|
= help: Remove `object` inheritance
Safe fix
Unsafe fix
121 121 | ...
122 122 |
123 123 |
@@ -403,7 +403,7 @@ UP004.py:131:5: UP004 [*] Class `A` inherits from `object`
|
= help: Remove `object` inheritance
Safe fix
Unsafe fix
127 127 | ...
128 128 |
129 129 |

View File

@@ -51,7 +51,7 @@ UP050.py:16:5: UP050 [*] Class `A` uses `metaclass=type`, which is redundant
|
= help: Remove `metaclass=type`
Safe fix
Unsafe fix
12 12 | ...
13 13 |
14 14 |
@@ -75,7 +75,7 @@ UP050.py:24:5: UP050 [*] Class `A` uses `metaclass=type`, which is redundant
|
= help: Remove `metaclass=type`
Safe fix
Unsafe fix
19 19 | ...
20 20 |
21 21 |
@@ -98,7 +98,7 @@ UP050.py:30:5: UP050 [*] Class `A` uses `metaclass=type`, which is redundant
|
= help: Remove `metaclass=type`
Safe fix
Unsafe fix
26 26 | ...
27 27 |
28 28 |
@@ -122,7 +122,7 @@ UP050.py:38:5: UP050 [*] Class `A` uses `metaclass=type`, which is redundant
|
= help: Remove `metaclass=type`
Safe fix
Unsafe fix
33 33 | ...
34 34 |
35 35 |
@@ -185,7 +185,7 @@ UP050.py:58:5: UP050 [*] Class `B` uses `metaclass=type`, which is redundant
|
= help: Remove `metaclass=type`
Safe fix
Unsafe fix
54 54 |
55 55 | class B(
56 56 | A,
@@ -205,7 +205,7 @@ UP050.py:69:5: UP050 [*] Class `A` uses `metaclass=type`, which is redundant
|
= help: Remove `metaclass=type`
Safe fix
Unsafe fix
65 65 | ...
66 66 |
67 67 |

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

@@ -52,7 +52,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L92)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L93)
</details>
## `conflicting-argument-forms`
@@ -83,7 +83,7 @@ f(int) # error
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L136)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L137)
</details>
## `conflicting-declarations`
@@ -113,7 +113,7 @@ a = 1
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L162)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L163)
</details>
## `conflicting-metaclass`
@@ -144,7 +144,7 @@ class C(A, B): ...
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L187)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L188)
</details>
## `cyclic-class-definition`
@@ -175,7 +175,7 @@ class B(A): ...
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L213)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L214)
</details>
## `duplicate-base`
@@ -201,7 +201,7 @@ class B(A, A): ...
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L257)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L258)
</details>
## `escape-character-in-forward-annotation`
@@ -338,7 +338,7 @@ TypeError: multiple bases have instance lay-out conflict
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20incompatible-slots)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L278)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L279)
</details>
## `inconsistent-mro`
@@ -367,7 +367,7 @@ class C(A, B): ...
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L364)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L365)
</details>
## `index-out-of-bounds`
@@ -392,7 +392,7 @@ t[3] # IndexError: tuple index out of range
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L388)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L389)
</details>
## `invalid-argument-type`
@@ -418,7 +418,7 @@ func("foo") # error: [invalid-argument-type]
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L408)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L409)
</details>
## `invalid-assignment`
@@ -445,7 +445,7 @@ a: int = ''
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L448)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L449)
</details>
## `invalid-attribute-access`
@@ -478,7 +478,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1396)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1397)
</details>
## `invalid-base`
@@ -501,7 +501,7 @@ class A(42): ... # error: [invalid-base]
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L470)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L471)
</details>
## `invalid-context-manager`
@@ -527,7 +527,7 @@ with 1:
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L521)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L522)
</details>
## `invalid-declaration`
@@ -555,7 +555,7 @@ a: str
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L542)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L543)
</details>
## `invalid-exception-caught`
@@ -596,7 +596,7 @@ except ZeroDivisionError:
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L565)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L566)
</details>
## `invalid-generic-class`
@@ -627,7 +627,7 @@ class C[U](Generic[T]): ...
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L601)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L602)
</details>
## `invalid-legacy-type-variable`
@@ -660,7 +660,7 @@ def f(t: TypeVar("U")): ...
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L627)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L628)
</details>
## `invalid-metaclass`
@@ -692,7 +692,7 @@ class B(metaclass=f): ...
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L676)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L677)
</details>
## `invalid-overload`
@@ -740,7 +740,7 @@ def foo(x: int) -> int: ...
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L703)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L704)
</details>
## `invalid-parameter-default`
@@ -765,7 +765,7 @@ def f(a: int = ''): ...
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L746)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L747)
</details>
## `invalid-protocol`
@@ -798,7 +798,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L336)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L337)
</details>
## `invalid-raise`
@@ -846,7 +846,7 @@ def g():
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L766)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L767)
</details>
## `invalid-return-type`
@@ -870,7 +870,7 @@ def func() -> int:
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L429)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L430)
</details>
## `invalid-super-argument`
@@ -914,7 +914,7 @@ super(B, A) # error: `A` does not satisfy `issubclass(A, B)`
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L809)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L810)
</details>
## `invalid-syntax-in-forward-annotation`
@@ -954,7 +954,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L655)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L656)
</details>
## `invalid-type-checking-constant`
@@ -983,7 +983,7 @@ TYPE_CHECKING = ''
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L848)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L849)
</details>
## `invalid-type-form`
@@ -1012,7 +1012,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L872)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L873)
</details>
## `invalid-type-variable-constraints`
@@ -1046,7 +1046,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L896)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L897)
</details>
## `missing-argument`
@@ -1070,7 +1070,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L925)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L926)
</details>
## `no-matching-overload`
@@ -1098,7 +1098,7 @@ func("string") # error: [no-matching-overload]
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L944)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L945)
</details>
## `non-subscriptable`
@@ -1121,7 +1121,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L967)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L968)
</details>
## `not-iterable`
@@ -1146,7 +1146,7 @@ for i in 34: # TypeError: 'int' object is not iterable
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L985)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L986)
</details>
## `parameter-already-assigned`
@@ -1172,7 +1172,7 @@ f(1, x=2) # Error raised here
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1036)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1037)
</details>
## `raw-string-type-annotation`
@@ -1231,7 +1231,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1372)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1373)
</details>
## `subclass-of-final-class`
@@ -1259,7 +1259,7 @@ class B(A): ... # Error raised here
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1127)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1128)
</details>
## `too-many-positional-arguments`
@@ -1285,7 +1285,7 @@ f("foo") # Error raised here
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1172)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1173)
</details>
## `type-assertion-failure`
@@ -1312,7 +1312,7 @@ def _(x: int):
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1150)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1151)
</details>
## `unavailable-implicit-super-arguments`
@@ -1356,7 +1356,7 @@ class A:
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1193)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1194)
</details>
## `unknown-argument`
@@ -1382,7 +1382,7 @@ f(x=1, y=2) # Error raised here
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1250)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1251)
</details>
## `unresolved-attribute`
@@ -1409,7 +1409,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1271)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1272)
</details>
## `unresolved-import`
@@ -1433,7 +1433,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1293)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1294)
</details>
## `unresolved-reference`
@@ -1457,7 +1457,7 @@ print(x) # NameError: name 'x' is not defined
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1312)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1313)
</details>
## `unsupported-bool-conversion`
@@ -1493,7 +1493,7 @@ b1 < b2 < b1 # exception raised here
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1005)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1006)
</details>
## `unsupported-operator`
@@ -1520,7 +1520,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1331)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1332)
</details>
## `zero-stepsize-in-slice`
@@ -1544,7 +1544,7 @@ l[1:10:0] # ValueError: slice step cannot be zero
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1353)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1354)
</details>
## `invalid-ignore-comment`
@@ -1600,7 +1600,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1057)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1058)
</details>
## `possibly-unbound-implicit-call`
@@ -1631,7 +1631,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L110)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L111)
</details>
## `possibly-unbound-import`
@@ -1662,7 +1662,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1079)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1080)
</details>
## `redundant-cast`
@@ -1688,7 +1688,7 @@ cast(int, f()) # Redundant
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1424)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1425)
</details>
## `undefined-reveal`
@@ -1711,7 +1711,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1232)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1233)
</details>
## `unknown-rule`
@@ -1779,7 +1779,7 @@ class D(C): ... # error: [unsupported-base]
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L488)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L489)
</details>
## `division-by-zero`
@@ -1802,7 +1802,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L239)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L240)
</details>
## `possibly-unresolved-reference`
@@ -1829,7 +1829,7 @@ print(x) # NameError: name 'x' is not defined
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1105)
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1106)
</details>
## `unused-ignore-comment`

View File

@@ -870,9 +870,7 @@ def m<CURSOR>
",
);
assert_snapshot!(test.completions(), @r"
m
");
assert_snapshot!(test.completions(), @"<No completions found>");
}
// Ref: https://github.com/astral-sh/ty/issues/572
@@ -884,9 +882,7 @@ def m<CURSOR>(): pass
",
);
assert_snapshot!(test.completions(), @r"
m
");
assert_snapshot!(test.completions(), @"<No completions found>");
}
// Ref: https://github.com/astral-sh/ty/issues/572
@@ -913,9 +909,7 @@ class M<CURSOR>
",
);
assert_snapshot!(test.completions(), @r"
M
");
assert_snapshot!(test.completions(), @"<No completions found>");
}
// Ref: https://github.com/astral-sh/ty/issues/572
@@ -942,9 +936,7 @@ import fo<CURSOR>
",
);
assert_snapshot!(test.completions(), @r"
fo
");
assert_snapshot!(test.completions(), @"<No completions found>");
}
// Ref: https://github.com/astral-sh/ty/issues/572
@@ -956,9 +948,7 @@ import foo as ba<CURSOR>
",
);
assert_snapshot!(test.completions(), @r"
ba
");
assert_snapshot!(test.completions(), @"<No completions found>");
}
// Ref: https://github.com/astral-sh/ty/issues/572
@@ -970,9 +960,7 @@ from fo<CURSOR> import wat
",
);
assert_snapshot!(test.completions(), @r"
wat
");
assert_snapshot!(test.completions(), @"<No completions found>");
}
// Ref: https://github.com/astral-sh/ty/issues/572
@@ -984,9 +972,7 @@ from foo import wa<CURSOR>
",
);
assert_snapshot!(test.completions(), @r"
wa
");
assert_snapshot!(test.completions(), @"<No completions found>");
}
// Ref: https://github.com/astral-sh/ty/issues/572
@@ -998,9 +984,7 @@ from foo import wat as ba<CURSOR>
",
);
assert_snapshot!(test.completions(), @r"
ba
");
assert_snapshot!(test.completions(), @"<No completions found>");
}
// Ref: https://github.com/astral-sh/ty/issues/572
@@ -1030,10 +1014,7 @@ def _():
",
);
assert_snapshot!(test.completions(), @r"
_
fo
");
assert_snapshot!(test.completions(), @"<No completions found>");
}
impl CursorTest {

View File

@@ -0,0 +1,401 @@
# Overloads
When ty evaluates the call of an overloaded function, it attempts to "match" the supplied arguments
with one or more overloads. This document describes the algorithm that it uses for overload
matching, which is the same as the one mentioned in the
[spec](https://typing.python.org/en/latest/spec/overload.html#overload-call-evaluation).
## Arity check
The first step is to perform arity check. The non-overloaded cases are described in the
[function](./function.md) document.
`overloaded.pyi`:
```pyi
from typing import overload
@overload
def f() -> None: ...
@overload
def f(x: int) -> int: ...
```
```py
from overloaded import f
# These match a single overload
reveal_type(f()) # revealed: None
reveal_type(f(1)) # revealed: int
# error: [no-matching-overload] "No overload of function `f` matches arguments"
reveal_type(f("a", "b")) # revealed: Unknown
```
## Type checking
The second step is to perform type checking. This is done for all the overloads that passed the
arity check.
### Single match
`overloaded.pyi`:
```pyi
from typing import overload
@overload
def f(x: int) -> int: ...
@overload
def f(x: str) -> str: ...
@overload
def f(x: bytes) -> bytes: ...
```
Here, all of the calls below pass the arity check for all overloads, so we proceed to type checking
which filters out all but the matching overload:
```py
from overloaded import f
reveal_type(f(1)) # revealed: int
reveal_type(f("a")) # revealed: str
reveal_type(f(b"b")) # revealed: bytes
```
### Single match error
`overloaded.pyi`:
```pyi
from typing import overload
@overload
def f() -> None: ...
@overload
def f(x: int) -> int: ...
```
If the arity check only matches a single overload, it should be evaluated as a regular
(non-overloaded) function call. This means that any diagnostics resulted during type checking that
call should be reported directly and not as a `no-matching-overload` error.
```py
from overloaded import f
reveal_type(f()) # revealed: None
# TODO: This should be `invalid-argument-type` instead
# error: [no-matching-overload]
reveal_type(f("a")) # revealed: Unknown
```
### Multiple matches
`overloaded.pyi`:
```pyi
from typing import overload
class A: ...
class B(A): ...
@overload
def f(x: A) -> A: ...
@overload
def f(x: B, y: int = 0) -> B: ...
```
```py
from overloaded import A, B, f
# These calls pass the arity check, and type checking matches both overloads:
reveal_type(f(A())) # revealed: A
reveal_type(f(B())) # revealed: A
# But, in this case, the arity check filters out the first overload, so we only have one match:
reveal_type(f(B(), 1)) # revealed: B
```
## Argument type expansion
This step is performed only if the previous steps resulted in **no matches**.
In this case, the algorithm would perform
[argument type expansion](https://typing.python.org/en/latest/spec/overload.html#argument-type-expansion)
and loops over from the type checking step, evaluating the argument lists.
### Expanding the only argument
`overloaded.pyi`:
```pyi
from typing import overload
class A: ...
class B: ...
class C: ...
@overload
def f(x: A) -> A: ...
@overload
def f(x: B) -> B: ...
@overload
def f(x: C) -> C: ...
```
```py
from overloaded import A, B, C, f
def _(ab: A | B, ac: A | C, bc: B | C):
reveal_type(f(ab)) # revealed: A | B
reveal_type(f(bc)) # revealed: B | C
reveal_type(f(ac)) # revealed: A | C
```
### Expanding first argument
If the set of argument lists created by expanding the first argument evaluates successfully, the
algorithm shouldn't expand the second argument.
`overloaded.pyi`:
```pyi
from typing import Literal, overload
class A: ...
class B: ...
class C: ...
class D: ...
@overload
def f(x: A, y: C) -> A: ...
@overload
def f(x: A, y: D) -> B: ...
@overload
def f(x: B, y: C) -> C: ...
@overload
def f(x: B, y: D) -> D: ...
```
```py
from overloaded import A, B, C, D, f
def _(a_b: A | B):
reveal_type(f(a_b, C())) # revealed: A | C
reveal_type(f(a_b, D())) # revealed: B | D
# But, if it doesn't, it should expand the second argument and try again:
def _(a_b: A | B, c_d: C | D):
reveal_type(f(a_b, c_d)) # revealed: A | B | C | D
```
### Expanding second argument
If the first argument cannot be expanded, the algorithm should move on to the second argument,
keeping the first argument as is.
`overloaded.pyi`:
```pyi
from typing import overload
class A: ...
class B: ...
class C: ...
class D: ...
@overload
def f(x: A, y: B) -> B: ...
@overload
def f(x: A, y: C) -> C: ...
@overload
def f(x: B, y: D) -> D: ...
```
```py
from overloaded import A, B, C, D, f
def _(a: A, bc: B | C, cd: C | D):
# This also tests that partial matching works correctly as the argument type expansion results
# in matching the first and second overloads, but not the third one.
reveal_type(f(a, bc)) # revealed: B | C
# error: [no-matching-overload] "No overload of function `f` matches arguments"
reveal_type(f(a, cd)) # revealed: Unknown
```
### Generics (legacy)
`overloaded.pyi`:
```pyi
from typing import TypeVar, overload
_T = TypeVar("_T")
class A: ...
class B: ...
@overload
def f(x: A) -> A: ...
@overload
def f(x: _T) -> _T: ...
```
```py
from overloaded import A, f
def _(x: int, y: A | int):
reveal_type(f(x)) # revealed: int
reveal_type(f(y)) # revealed: A | int
```
### Generics (PEP 695)
```toml
[environment]
python-version = "3.12"
```
`overloaded.pyi`:
```pyi
from typing import overload
class A: ...
class B: ...
@overload
def f(x: B) -> B: ...
@overload
def f[T](x: T) -> T: ...
```
```py
from overloaded import B, f
def _(x: int, y: B | int):
reveal_type(f(x)) # revealed: int
reveal_type(f(y)) # revealed: B | int
```
### Expanding `bool`
`overloaded.pyi`:
```pyi
from typing import Literal, overload
class T: ...
class F: ...
@overload
def f(x: Literal[True]) -> T: ...
@overload
def f(x: Literal[False]) -> F: ...
```
```py
from overloaded import f
def _(flag: bool):
reveal_type(f(True)) # revealed: T
reveal_type(f(False)) # revealed: F
reveal_type(f(flag)) # revealed: T | F
```
### Expanding `tuple`
`overloaded.pyi`:
```pyi
from typing import Literal, overload
class A: ...
class B: ...
class C: ...
class D: ...
@overload
def f(x: tuple[A, int], y: tuple[int, Literal[True]]) -> A: ...
@overload
def f(x: tuple[A, int], y: tuple[int, Literal[False]]) -> B: ...
@overload
def f(x: tuple[B, int], y: tuple[int, Literal[True]]) -> C: ...
@overload
def f(x: tuple[B, int], y: tuple[int, Literal[False]]) -> D: ...
```
```py
from overloaded import A, B, f
def _(x: tuple[A | B, int], y: tuple[int, bool]):
reveal_type(f(x, y)) # revealed: A | B | C | D
```
### Expanding `type`
There's no special handling for expanding `type[A | B]` type because ty stores this type in it's
distributed form, which is `type[A] | type[B]`.
`overloaded.pyi`:
```pyi
from typing import overload
class A: ...
class B: ...
@overload
def f(x: type[A]) -> A: ...
@overload
def f(x: type[B]) -> B: ...
```
```py
from overloaded import A, B, f
def _(x: type[A | B]):
reveal_type(x) # revealed: type[A] | type[B]
reveal_type(f(x)) # revealed: A | B
```
### Expanding enums
`overloaded.pyi`:
```pyi
from enum import Enum
from typing import Literal, overload
class SomeEnum(Enum):
A = 1
B = 2
C = 3
class A: ...
class B: ...
class C: ...
@overload
def f(x: Literal[SomeEnum.A]) -> A: ...
@overload
def f(x: Literal[SomeEnum.B]) -> B: ...
@overload
def f(x: Literal[SomeEnum.C]) -> C: ...
```
```py
from overloaded import SomeEnum, A, B, C, f
def _(x: SomeEnum):
reveal_type(f(SomeEnum.A)) # revealed: A
# TODO: This should be `B` once enums are supported and are expanded
reveal_type(f(SomeEnum.B)) # revealed: A
# TODO: This should be `C` once enums are supported and are expanded
reveal_type(f(SomeEnum.C)) # revealed: A
# TODO: This should be `A | B | C` once enums are supported and are expanded
reveal_type(f(x)) # revealed: A
```

View File

@@ -797,7 +797,20 @@ C(1) < C(2) # ok
### Using `dataclass` as a function
To do
```py
from dataclasses import dataclass
class B:
x: int
# error: [missing-argument]
dataclass(B)()
# error: [invalid-argument-type]
dataclass(B)("a")
reveal_type(dataclass(B)(3).x) # revealed: int
```
## Internals

View File

@@ -379,6 +379,21 @@ C[None](b"bytes") # error: [no-matching-overload]
C[None](12)
```
### Synthesized methods with dataclasses
```py
from dataclasses import dataclass
from typing import Generic, TypeVar
T = TypeVar("T")
@dataclass
class A(Generic[T]):
x: T
reveal_type(A(x=1)) # revealed: A[int]
```
## Generic subclass
When a generic subclass fills its superclass's type parameter with one of its own, the actual types

View File

@@ -196,4 +196,33 @@ def constrained(f: T):
reveal_type(f()) # revealed: int | str
```
## Meta-type
The meta-type of a typevar is the same as the meta-type of the upper bound, or the union of the
meta-types of the constraints:
```py
from typing import TypeVar
T_normal = TypeVar("T_normal")
def normal(x: T_normal):
reveal_type(type(x)) # revealed: type
T_bound_object = TypeVar("T_bound_object", bound=object)
def bound_object(x: T_bound_object):
reveal_type(type(x)) # revealed: type
T_bound_int = TypeVar("T_bound_int", bound=int)
def bound_int(x: T_bound_int):
reveal_type(type(x)) # revealed: type[int]
T_constrained = TypeVar("T_constrained", int, str)
def constrained(x: T_constrained):
reveal_type(type(x)) # revealed: type[int] | type[str]
```
[generics]: https://typing.python.org/en/latest/spec/generics.html

View File

@@ -354,6 +354,18 @@ C[None](b"bytes") # error: [no-matching-overload]
C[None](12)
```
### Synthesized methods with dataclasses
```py
from dataclasses import dataclass
@dataclass
class A[T]:
x: T
reveal_type(A(x=1)) # revealed: A[int]
```
## Generic subclass
When a generic subclass fills its superclass's type parameter with one of its own, the actual types

View File

@@ -766,4 +766,23 @@ def constrained[T: (Callable[[], int], Callable[[], str])](f: T):
reveal_type(f()) # revealed: int | str
```
## Meta-type
The meta-type of a typevar is the same as the meta-type of the upper bound, or the union of the
meta-types of the constraints:
```py
def normal[T](x: T):
reveal_type(type(x)) # revealed: type
def bound_object[T: object](x: T):
reveal_type(type(x)) # revealed: type
def bound_int[T: int](x: T):
reveal_type(type(x)) # revealed: type[int]
def constrained[T: (int, str)](x: T):
reveal_type(type(x)) # revealed: type[int] | type[str]
```
[pep 695]: https://peps.python.org/pep-0695/

View File

@@ -134,9 +134,7 @@ class Property[T](NamedTuple):
name: str
value: T
# TODO: this should be supported (no error, revealed type of `Property[float]`)
# error: [invalid-argument-type]
reveal_type(Property("height", 3.4)) # revealed: Property[Unknown]
reveal_type(Property("height", 3.4)) # revealed: Property[float]
```
## Attributes on `NamedTuple`

View File

@@ -20,7 +20,99 @@ x: IntOrStr = 1
reveal_type(x) # revealed: Literal[1]
def f() -> None:
reveal_type(x) # revealed: int | str
reveal_type(x) # revealed: IntOrStr
```
## Type properties
### Equivalence
```py
from ty_extensions import static_assert, is_equivalent_to
type IntOrStr = int | str
type StrOrInt = str | int
static_assert(is_equivalent_to(IntOrStr, IntOrStr))
static_assert(is_equivalent_to(IntOrStr, StrOrInt))
type Rec1 = tuple[Rec1, int]
type Rec2 = tuple[Rec2, int]
type Other = tuple[Other, str]
static_assert(is_equivalent_to(Rec1, Rec2))
static_assert(not is_equivalent_to(Rec1, Other))
type Cycle1A = tuple[Cycle1B, int]
type Cycle1B = tuple[Cycle1A, str]
type Cycle2A = tuple[Cycle2B, int]
type Cycle2B = tuple[Cycle2A, str]
static_assert(is_equivalent_to(Cycle1A, Cycle2A))
static_assert(is_equivalent_to(Cycle1B, Cycle2B))
static_assert(not is_equivalent_to(Cycle1A, Cycle1B))
static_assert(not is_equivalent_to(Cycle1A, Cycle2B))
# type Cycle3A = tuple[Cycle3B] | None
# type Cycle3B = tuple[Cycle3A] | None
# static_assert(is_equivalent_to(Cycle3A, Cycle3A))
# static_assert(is_equivalent_to(Cycle3A, Cycle3B))
```
### Assignability
```py
type IntOrStr = int | str
x1: IntOrStr = 1
x2: IntOrStr = "1"
x3: IntOrStr | None = None
def _(int_or_str: IntOrStr) -> None:
# TODO: those should not be errors
x3: int | str = int_or_str # error: [invalid-assignment]
x4: int | str | None = int_or_str # error: [invalid-assignment]
x5: int | str | None = int_or_str or None # error: [invalid-assignment]
```
### Narrowing (intersections)
```py
class P: ...
class Q: ...
type EitherOr = P | Q
def _(x: EitherOr) -> None:
if isinstance(x, P):
reveal_type(x) # revealed: P
elif isinstance(x, Q):
reveal_type(x) # revealed: Q & ~P
else:
# TODO: This should be Never
reveal_type(x) # revealed: EitherOr & ~P & ~Q
```
### Fully static
```py
from typing import Any
from ty_extensions import static_assert, is_fully_static
type IntOrStr = int | str
type RecFullyStatic = int | tuple[RecFullyStatic]
static_assert(is_fully_static(IntOrStr))
static_assert(is_fully_static(RecFullyStatic))
type IntOrAny = int | Any
type RecNotFullyStatic = Any | tuple[RecNotFullyStatic]
static_assert(not is_fully_static(IntOrAny))
static_assert(not is_fully_static(RecNotFullyStatic))
```
## `__value__` attribute
@@ -49,7 +141,7 @@ type IntOrStrOrBytes = IntOrStr | bytes
x: IntOrStrOrBytes = 1
def f() -> None:
reveal_type(x) # revealed: int | str | bytes
reveal_type(x) # revealed: IntOrStrOrBytes
```
## Aliased type aliases
@@ -109,7 +201,7 @@ reveal_type(IntOrStr) # revealed: typing.TypeAliasType
reveal_type(IntOrStr.__name__) # revealed: Literal["IntOrStr"]
def f(x: IntOrStr) -> None:
reveal_type(x) # revealed: int | str
reveal_type(x) # revealed: IntOrStr
```
### Generic example
@@ -138,3 +230,12 @@ def get_name() -> str:
# error: [invalid-type-alias-type] "The name of a `typing.TypeAlias` must be a string literal"
IntOrStr = TypeAliasType(get_name(), int | str)
```
## Recursive type aliases
```py
type Recursive = dict[str, "Recursive"]
# TODO: this should not be an error
r: Recursive = {"key": {}} # error: [invalid-assignment]
```

View File

@@ -396,10 +396,11 @@ type LiteralInt = TypeOf[int]
type LiteralStr = TypeOf[str]
type LiteralObject = TypeOf[object]
assert_type(bool, LiteralBool)
assert_type(int, LiteralInt)
assert_type(str, LiteralStr)
assert_type(object, LiteralObject)
# TODO: these should not be errors
assert_type(bool, LiteralBool) # error: [type-assertion-failure]
assert_type(int, LiteralInt) # error: [type-assertion-failure]
assert_type(str, LiteralStr) # error: [type-assertion-failure]
assert_type(object, LiteralObject) # error: [type-assertion-failure]
# bool
@@ -462,9 +463,10 @@ type LiteralBase = TypeOf[Base]
type LiteralDerived = TypeOf[Derived]
type LiteralUnrelated = TypeOf[Unrelated]
assert_type(Base, LiteralBase)
assert_type(Derived, LiteralDerived)
assert_type(Unrelated, LiteralUnrelated)
# TODO: these should not be errors
assert_type(Base, LiteralBase) # error: [type-assertion-failure]
assert_type(Derived, LiteralDerived) # error: [type-assertion-failure]
assert_type(Unrelated, LiteralUnrelated) # error: [type-assertion-failure]
static_assert(is_subtype_of(LiteralBase, type))
static_assert(is_subtype_of(LiteralBase, object))

View File

@@ -196,21 +196,21 @@ def _(
bytes_or_falsy: bytes | AlwaysFalsy,
falsy_or_bytes: AlwaysFalsy | bytes,
):
reveal_type(strings_or_truthy) # revealed: Literal[""] | AlwaysTruthy
reveal_type(truthy_or_strings) # revealed: AlwaysTruthy | Literal[""]
reveal_type(strings_or_truthy) # revealed: strings | AlwaysTruthy
reveal_type(truthy_or_strings) # revealed: AlwaysTruthy | strings
reveal_type(strings_or_falsy) # revealed: Literal["foo"] | AlwaysFalsy
reveal_type(falsy_or_strings) # revealed: AlwaysFalsy | Literal["foo"]
reveal_type(strings_or_falsy) # revealed: strings | AlwaysFalsy
reveal_type(falsy_or_strings) # revealed: AlwaysFalsy | strings
reveal_type(ints_or_truthy) # revealed: Literal[0] | AlwaysTruthy
reveal_type(truthy_or_ints) # revealed: AlwaysTruthy | Literal[0]
reveal_type(ints_or_truthy) # revealed: ints | AlwaysTruthy
reveal_type(truthy_or_ints) # revealed: AlwaysTruthy | ints
reveal_type(ints_or_falsy) # revealed: Literal[1] | AlwaysFalsy
reveal_type(falsy_or_ints) # revealed: AlwaysFalsy | Literal[1]
reveal_type(ints_or_falsy) # revealed: ints | AlwaysFalsy
reveal_type(falsy_or_ints) # revealed: AlwaysFalsy | ints
reveal_type(bytes_or_truthy) # revealed: Literal[b""] | AlwaysTruthy
reveal_type(truthy_or_bytes) # revealed: AlwaysTruthy | Literal[b""]
reveal_type(bytes_or_truthy) # revealed: bytes | AlwaysTruthy
reveal_type(truthy_or_bytes) # revealed: AlwaysTruthy | bytes
reveal_type(bytes_or_falsy) # revealed: Literal[b"foo"] | AlwaysFalsy
reveal_type(falsy_or_bytes) # revealed: AlwaysFalsy | Literal[b"foo"]
reveal_type(bytes_or_falsy) # revealed: bytes | AlwaysFalsy
reveal_type(falsy_or_bytes) # revealed: AlwaysFalsy | bytes
```

View File

@@ -1,8 +1,8 @@
# Unpacking
If there are not enough or too many values when unpacking, an error will occur and the types of
all variables (if nested tuple unpacking fails, only the variables within the failed tuples) is
inferred to be `Unknown`.
If there are not enough or too many values when unpacking, an error will occur and the types of all
variables (if nested tuple unpacking fails, only the variables within the failed tuples) is inferred
to be `Unknown`.
## Tuple
@@ -207,6 +207,57 @@ reveal_type(c) # revealed: int
reveal_type(d) # revealed: Literal[2]
```
## List
### Literal unpacking
```py
a, b = [1, 2]
# TODO: should be `int` for both `a` and `b`
reveal_type(a) # revealed: Unknown
reveal_type(b) # revealed: Unknown
```
### Simple unpacking
```py
def _(value: list[int]):
a, b = value
reveal_type(a) # revealed: int
reveal_type(b) # revealed: int
```
### Nested unpacking
```py
def _(value: list[list[int]]):
a, (b, c) = value
reveal_type(a) # revealed: list[int]
reveal_type(b) # revealed: int
reveal_type(c) # revealed: int
```
### Invalid nested unpacking
```py
def _(value: list[int]):
# error: [not-iterable] "Object of type `int` is not iterable"
a, (b, c) = value
reveal_type(a) # revealed: int
reveal_type(b) # revealed: Unknown
reveal_type(c) # revealed: Unknown
```
### Starred expression
```py
def _(value: list[int]):
a, *b, c = value
reveal_type(a) # revealed: int
reveal_type(b) # revealed: list[int]
reveal_type(c) # revealed: int
```
## String
### Simple unpacking
@@ -293,6 +344,18 @@ reveal_type(b) # revealed: LiteralString
reveal_type(c) # revealed: list[LiteralString]
```
### Starred expression (6)
```py
from typing_extensions import LiteralString
def _(s: LiteralString):
a, b, *c = s
reveal_type(a) # revealed: LiteralString
reveal_type(b) # revealed: LiteralString
reveal_type(c) # revealed: list[LiteralString]
```
### Unicode
```py
@@ -788,3 +851,14 @@ def _(arg: tuple[tuple[int, str], Iterable]):
# revealed: tuple[int | bytes, str | bytes]
[reveal_type((a, b)) for a, b in arg]
```
## Empty
Unpacking an empty tuple or list shouldn't raise any diagnostics.
```py
[] = []
() = ()
[] = ()
() = []
```

View File

@@ -259,6 +259,14 @@ impl<'db> SemanticIndex<'db> {
self.scopes_by_expression[&expression.into()]
}
/// Returns the ID of the `expression`'s enclosing scope.
pub(crate) fn try_expression_scope_id(
&self,
expression: impl Into<ExpressionNodeKey>,
) -> Option<FileScopeId> {
self.scopes_by_expression.get(&expression.into()).copied()
}
/// Returns the [`Scope`] of the `expression`'s enclosing scope.
#[allow(unused)]
#[track_caller]

View File

@@ -1040,11 +1040,6 @@ impl<'db> SemanticIndexBuilder<'db> {
}
}
fn record_scope_for_identifier(&mut self, name: &ast::Identifier) {
self.scopes_by_expression
.insert(name.into(), self.current_scope());
}
pub(super) fn build(mut self) -> SemanticIndex<'db> {
let module = self.module;
self.visit_body(module.suite());
@@ -1144,7 +1139,6 @@ where
is_async: _,
range: _,
} = function_def;
self.record_scope_for_identifier(name);
for decorator in decorator_list {
self.visit_decorator(decorator);
}
@@ -1227,7 +1221,6 @@ where
self.add_definition(symbol, function_def);
}
ast::Stmt::ClassDef(class) => {
self.record_scope_for_identifier(&class.name);
for decorator in &class.decorator_list {
self.visit_decorator(decorator);
}
@@ -1277,10 +1270,6 @@ where
.record_node_reachability(NodeKey::from_node(node));
for (alias_index, alias) in node.names.iter().enumerate() {
self.record_scope_for_identifier(&alias.name);
if let Some(ref asname) = alias.asname {
self.record_scope_for_identifier(asname);
}
// Mark the imported module, and all of its parents, as being imported in this
// file.
if let Some(module_name) = ModuleName::new(&alias.name) {
@@ -1305,9 +1294,6 @@ where
}
}
ast::Stmt::ImportFrom(node) => {
if let Some(ref module) = node.module {
self.record_scope_for_identifier(module);
}
self.current_use_def_map_mut()
.record_node_reachability(NodeKey::from_node(node));
@@ -1392,10 +1378,6 @@ where
continue;
}
self.record_scope_for_identifier(&alias.name);
if let Some(ref asname) = alias.asname {
self.record_scope_for_identifier(asname);
}
let (symbol_name, is_reexported) = if let Some(asname) = &alias.asname {
(&asname.id, asname.id == alias.name.id)
} else {
@@ -1937,7 +1919,6 @@ where
}
ast::Stmt::Global(ast::StmtGlobal { range: _, names }) => {
for name in names {
self.record_scope_for_identifier(name);
let symbol_id = self.add_symbol(name.id.clone());
let symbol_table = self.current_symbol_table();
let symbol = symbol_table.symbol(symbol_id);

View File

@@ -47,15 +47,22 @@ impl<'db> SemanticModel<'db> {
/// scope of this model's `File` are returned.
pub fn completions(&self, node: ast::AnyNodeRef<'_>) -> Vec<Name> {
let index = semantic_index(self.db, self.file);
let file_scope = match node {
ast::AnyNodeRef::Identifier(identifier) => index.expression_scope_id(identifier),
// TODO: We currently use `try_expression_scope_id` here as a hotfix for [1].
// Revert this to use `expression_scope_id` once a proper fix is in place.
//
// [1] https://github.com/astral-sh/ty/issues/572
let Some(file_scope) = (match node {
ast::AnyNodeRef::Identifier(identifier) => index.try_expression_scope_id(identifier),
node => match node.as_expr_ref() {
// If we couldn't identify a specific
// expression that we're in, then just
// fall back to the global scope.
None => FileScopeId::global(),
Some(expr) => index.expression_scope_id(expr),
None => Some(FileScopeId::global()),
Some(expr) => index.try_expression_scope_id(expr),
},
}) else {
return vec![];
};
let mut symbols = vec![];
for (file_scope, _) in index.ancestor_scopes(file_scope) {

View File

@@ -95,15 +95,13 @@ impl PythonEnvironment {
origin: SysPrefixPathOrigin,
system: &dyn System,
) -> SitePackagesDiscoveryResult<Self> {
let path = SysPrefixPath::new(path, origin, system)?;
let path = SysPrefixPath::new(path.as_ref(), origin, system)?;
// Attempt to inspect as a virtual environment first
// TODO(zanieb): Consider avoiding the clone here by checking for `pyvenv.cfg` ahead-of-time
match VirtualEnvironment::new(path.clone(), system) {
match VirtualEnvironment::new(path, system) {
Ok(venv) => Ok(Self::Virtual(venv)),
// If there's not a `pyvenv.cfg` marker, attempt to inspect as a system environment
//
Err(SitePackagesDiscoveryError::NoPyvenvCfgFile(_, _))
Err(SitePackagesDiscoveryError::NoPyvenvCfgFile(path, _))
if !origin.must_be_virtual_env() =>
{
Ok(Self::System(SystemEnvironment::new(path)))
@@ -207,9 +205,10 @@ impl VirtualEnvironment {
let pyvenv_cfg_path = path.join("pyvenv.cfg");
tracing::debug!("Attempting to parse virtual environment metadata at '{pyvenv_cfg_path}'");
let pyvenv_cfg = system
.read_to_string(&pyvenv_cfg_path)
.map_err(|io_err| SitePackagesDiscoveryError::NoPyvenvCfgFile(path.origin, io_err))?;
let pyvenv_cfg = match system.read_to_string(&pyvenv_cfg_path) {
Ok(pyvenv_cfg) => pyvenv_cfg,
Err(err) => return Err(SitePackagesDiscoveryError::NoPyvenvCfgFile(path, err)),
};
let parsed_pyvenv_cfg =
PyvenvCfgParser::new(&pyvenv_cfg)
@@ -530,20 +529,40 @@ impl SystemEnvironment {
}
}
/// Enumeration of ways in which `site-packages` discovery can fail.
#[derive(Debug, thiserror::Error)]
pub(crate) enum SitePackagesDiscoveryError {
/// `site-packages` discovery failed because the provided path couldn't be canonicalized.
#[error("Invalid {1}: `{0}` could not be canonicalized")]
EnvDirCanonicalizationError(SystemPathBuf, SysPrefixPathOrigin, #[source] io::Error),
CanonicalizationError(SystemPathBuf, SysPrefixPathOrigin, #[source] io::Error),
/// `site-packages` discovery failed because the [`SysPrefixPathOrigin`] indicated that
/// the provided path should point to `sys.prefix` directly, but the path wasn't a directory.
#[error("Invalid {1}: `{0}` does not point to a directory on disk")]
EnvDirNotDirectory(SystemPathBuf, SysPrefixPathOrigin),
#[error("{0} points to a broken venv with no pyvenv.cfg file")]
NoPyvenvCfgFile(SysPrefixPathOrigin, #[source] io::Error),
SysPrefixNotADirectory(SystemPathBuf, SysPrefixPathOrigin),
/// `site-packages` discovery failed because the [`SysPrefixPathOrigin`] indicated that
/// the provided path should point to the `sys.prefix` of a virtual environment,
/// but there was no file at `<sys.prefix>/pyvenv.cfg`.
#[error("{} points to a broken venv with no pyvenv.cfg file", .0.origin)]
NoPyvenvCfgFile(SysPrefixPath, #[source] io::Error),
/// `site-packages` discovery failed because the `pyvenv.cfg` file could not be parsed.
#[error("Failed to parse the pyvenv.cfg file at {0} because {1}")]
PyvenvCfgParseError(SystemPathBuf, PyvenvCfgParseErrorKind),
/// `site-packages` discovery failed because we're on a Unix system,
/// we weren't able to figure out from the `pyvenv.cfg` file exactly where `site-packages`
/// would be relative to the `sys.prefix` path, and we tried to fallback to iterating
/// through the `<sys.prefix>/lib` directory looking for a `site-packages` directory,
/// but we came across some I/O error while trying to do so.
#[error(
"Failed to search the `lib` directory of the Python installation at {1} for `site-packages`"
"Failed to iterate over the contents of the `lib` directory of the Python installation at {1}"
)]
CouldNotReadLibDirectory(#[source] io::Error, SysPrefixPath),
/// We looked everywhere we could think of for the `site-packages` directory,
/// but none could be found despite our best endeavours.
#[error("Could not find the `site-packages` directory for the Python installation at {0}")]
NoSitePackagesDirFound(SysPrefixPath),
}
@@ -709,14 +728,6 @@ pub(crate) struct SysPrefixPath {
impl SysPrefixPath {
fn new(
unvalidated_path: impl AsRef<SystemPath>,
origin: SysPrefixPathOrigin,
system: &dyn System,
) -> SitePackagesDiscoveryResult<Self> {
Self::new_impl(unvalidated_path.as_ref(), origin, system)
}
fn new_impl(
unvalidated_path: &SystemPath,
origin: SysPrefixPathOrigin,
system: &dyn System,
@@ -727,7 +738,7 @@ impl SysPrefixPath {
let canonicalized = system
.canonicalize_path(unvalidated_path)
.map_err(|io_err| {
SitePackagesDiscoveryError::EnvDirCanonicalizationError(
SitePackagesDiscoveryError::CanonicalizationError(
unvalidated_path.to_path_buf(),
origin,
io_err,
@@ -740,7 +751,7 @@ impl SysPrefixPath {
origin,
})
.ok_or_else(|| {
SitePackagesDiscoveryError::EnvDirNotDirectory(
SitePackagesDiscoveryError::SysPrefixNotADirectory(
unvalidated_path.to_path_buf(),
origin,
)
@@ -1367,7 +1378,7 @@ mod tests {
let system = TestSystem::default();
assert!(matches!(
PythonEnvironment::new("/env", SysPrefixPathOrigin::PythonCliFlag, &system),
Err(SitePackagesDiscoveryError::EnvDirCanonicalizationError(..))
Err(SitePackagesDiscoveryError::CanonicalizationError(..))
));
}
@@ -1380,7 +1391,7 @@ mod tests {
.unwrap();
assert!(matches!(
PythonEnvironment::new("/env", SysPrefixPathOrigin::PythonCliFlag, &system),
Err(SitePackagesDiscoveryError::EnvDirNotDirectory(..))
Err(SitePackagesDiscoveryError::SysPrefixNotADirectory(..))
));
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,11 @@
use std::borrow::Cow;
use std::ops::{Deref, DerefMut};
use itertools::{Either, Itertools};
use crate::Db;
use crate::types::{KnownClass, TupleType};
use super::Type;
/// Arguments for a single call, in source order.
@@ -86,6 +91,10 @@ impl<'a, 'db> CallArgumentTypes<'a, 'db> {
Self { arguments, types }
}
pub(crate) fn types(&self) -> &[Type<'db>] {
&self.types
}
/// Prepend an optional extra synthetic argument (for a `self` or `cls` parameter) to the front
/// of this argument list. (If `bound_self` is none, we return the argument list
/// unmodified.)
@@ -108,6 +117,72 @@ impl<'a, 'db> CallArgumentTypes<'a, 'db> {
pub(crate) fn iter(&self) -> impl Iterator<Item = (Argument<'a>, Type<'db>)> + '_ {
self.arguments.iter().zip(self.types.iter().copied())
}
/// Returns an iterator on performing [argument type expansion].
///
/// Each element of the iterator represents a set of argument lists, where each argument list
/// contains the same arguments, but with one or more of the argument types expanded.
///
/// [argument type expansion]: https://typing.python.org/en/latest/spec/overload.html#argument-type-expansion
pub(crate) fn expand(&self, db: &'db dyn Db) -> impl Iterator<Item = Vec<Vec<Type<'db>>>> + '_ {
/// Represents the state of the expansion process.
///
/// This is useful to avoid cloning the initial types vector if none of the types can be
/// expanded.
enum State<'a, 'db> {
Initial(&'a Vec<Type<'db>>),
Expanded(Vec<Vec<Type<'db>>>),
}
impl<'db> State<'_, 'db> {
fn len(&self) -> usize {
match self {
State::Initial(_) => 1,
State::Expanded(expanded) => expanded.len(),
}
}
fn iter(&self) -> impl Iterator<Item = &Vec<Type<'db>>> + '_ {
match self {
State::Initial(types) => std::slice::from_ref(*types).iter(),
State::Expanded(expanded) => expanded.iter(),
}
}
}
let mut index = 0;
std::iter::successors(Some(State::Initial(&self.types)), move |previous| {
// Find the next type that can be expanded.
let expanded_types = loop {
let arg_type = self.types.get(index)?;
if let Some(expanded_types) = expand_type(db, *arg_type) {
break expanded_types;
}
index += 1;
};
let mut expanded_arg_types = Vec::with_capacity(expanded_types.len() * previous.len());
for pre_expanded_types in previous.iter() {
for subtype in &expanded_types {
let mut new_expanded_types = pre_expanded_types.clone();
new_expanded_types[index] = *subtype;
expanded_arg_types.push(new_expanded_types);
}
}
// Increment the index to move to the next argument type for the next iteration.
index += 1;
Some(State::Expanded(expanded_arg_types))
})
.skip(1) // Skip the initial state, which has no expanded types.
.map(|state| match state {
State::Initial(_) => unreachable!("initial state should be skipped"),
State::Expanded(expanded) => expanded,
})
}
}
impl<'a> Deref for CallArgumentTypes<'a, '_> {
@@ -122,3 +197,138 @@ impl<'a> DerefMut for CallArgumentTypes<'a, '_> {
&mut self.arguments
}
}
/// Expands a type into its possible subtypes, if applicable.
///
/// Returns [`None`] if the type cannot be expanded.
fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Vec<Type<'db>>> {
// TODO: Expand enums to their variants
match ty {
Type::NominalInstance(instance) if instance.class.is_known(db, KnownClass::Bool) => {
Some(vec![
Type::BooleanLiteral(true),
Type::BooleanLiteral(false),
])
}
Type::Tuple(tuple) => {
// Note: This should only account for tuples of known length, i.e., `tuple[bool, ...]`
// should not be expanded here.
let expanded = tuple
.iter(db)
.map(|element| {
if let Some(expanded) = expand_type(db, element) {
Either::Left(expanded.into_iter())
} else {
Either::Right(std::iter::once(element))
}
})
.multi_cartesian_product()
.map(|types| TupleType::from_elements(db, types))
.collect::<Vec<_>>();
if expanded.len() == 1 {
// There are no elements in the tuple type that can be expanded.
None
} else {
Some(expanded)
}
}
Type::Union(union) => Some(union.iter(db).copied().collect()),
// We don't handle `type[A | B]` here because it's already stored in the expanded form
// i.e., `type[A] | type[B]` which is handled by the `Type::Union` case.
_ => None,
}
}
#[cfg(test)]
mod tests {
use crate::db::tests::setup_db;
use crate::types::{KnownClass, TupleType, Type, UnionType};
use super::expand_type;
#[test]
fn expand_union_type() {
let db = setup_db();
let types = [
KnownClass::Int.to_instance(&db),
KnownClass::Str.to_instance(&db),
KnownClass::Bytes.to_instance(&db),
];
let union_type = UnionType::from_elements(&db, types);
let expanded = expand_type(&db, union_type).unwrap();
assert_eq!(expanded.len(), types.len());
assert_eq!(expanded, types);
}
#[test]
fn expand_bool_type() {
let db = setup_db();
let bool_instance = KnownClass::Bool.to_instance(&db);
let expanded = expand_type(&db, bool_instance).unwrap();
let expected_types = [Type::BooleanLiteral(true), Type::BooleanLiteral(false)];
assert_eq!(expanded.len(), expected_types.len());
assert_eq!(expanded, expected_types);
}
#[test]
fn expand_tuple_type() {
let db = setup_db();
let int_ty = KnownClass::Int.to_instance(&db);
let str_ty = KnownClass::Str.to_instance(&db);
let bytes_ty = KnownClass::Bytes.to_instance(&db);
let bool_ty = KnownClass::Bool.to_instance(&db);
let true_ty = Type::BooleanLiteral(true);
let false_ty = Type::BooleanLiteral(false);
// Empty tuple
let empty_tuple = TupleType::empty(&db);
let expanded = expand_type(&db, empty_tuple);
assert!(expanded.is_none());
// None of the elements can be expanded.
let tuple_type1 = TupleType::from_elements(&db, [int_ty, str_ty]);
let expanded = expand_type(&db, tuple_type1);
assert!(expanded.is_none());
// All elements can be expanded.
let tuple_type2 = TupleType::from_elements(
&db,
[
bool_ty,
UnionType::from_elements(&db, [int_ty, str_ty, bytes_ty]),
],
);
let expected_types = [
TupleType::from_elements(&db, [true_ty, int_ty]),
TupleType::from_elements(&db, [true_ty, str_ty]),
TupleType::from_elements(&db, [true_ty, bytes_ty]),
TupleType::from_elements(&db, [false_ty, int_ty]),
TupleType::from_elements(&db, [false_ty, str_ty]),
TupleType::from_elements(&db, [false_ty, bytes_ty]),
];
let expanded = expand_type(&db, tuple_type2).unwrap();
assert_eq!(expanded.len(), expected_types.len());
assert_eq!(expanded, expected_types);
// Mixed set of elements where some can be expanded while others cannot be.
let tuple_type3 = TupleType::from_elements(
&db,
[
bool_ty,
int_ty,
UnionType::from_elements(&db, [str_ty, bytes_ty]),
str_ty,
],
);
let expected_types = [
TupleType::from_elements(&db, [true_ty, int_ty, str_ty, str_ty]),
TupleType::from_elements(&db, [true_ty, int_ty, bytes_ty, str_ty]),
TupleType::from_elements(&db, [false_ty, int_ty, str_ty, str_ty]),
TupleType::from_elements(&db, [false_ty, int_ty, bytes_ty, str_ty]),
];
let expanded = expand_type(&db, tuple_type3).unwrap();
assert_eq!(expanded.len(), expected_types.len());
assert_eq!(expanded, expected_types);
}
}

View File

@@ -18,13 +18,13 @@ use crate::types::diagnostic::{
NO_MATCHING_OVERLOAD, PARAMETER_ALREADY_ASSIGNED, TOO_MANY_POSITIONAL_ARGUMENTS,
UNKNOWN_ARGUMENT,
};
use crate::types::function::{DataclassTransformerParams, FunctionDecorators, KnownFunction};
use crate::types::generics::{Specialization, SpecializationBuilder, SpecializationError};
use crate::types::signatures::{Parameter, ParameterForm};
use crate::types::{
BoundMethodType, DataclassParams, DataclassTransformerParams, FunctionDecorators, FunctionType,
KnownClass, KnownFunction, KnownInstanceType, MethodWrapperKind, PropertyInstanceType,
SpecialFormType, TupleType, TypeMapping, UnionType, WrapperDescriptorKind, ide_support,
todo_type,
BoundMethodType, ClassLiteral, DataclassParams, KnownClass, KnownInstanceType,
MethodWrapperKind, PropertyInstanceType, SpecialFormType, TupleType, TypeMapping, UnionType,
WrapperDescriptorKind, ide_support, todo_type,
};
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic};
use ruff_python_ast as ast;
@@ -839,6 +839,21 @@ impl<'db> Bindings<'db> {
overload.set_return_type(Type::DataclassDecorator(params));
}
// `dataclass` being used as a non-decorator
if let [Some(Type::ClassLiteral(class_literal))] =
overload.parameter_types()
{
let params = DataclassParams::default();
overload.set_return_type(Type::from(ClassLiteral::new(
db,
class_literal.name(db),
class_literal.body_scope(db),
class_literal.known(db),
Some(params),
class_literal.dataclass_transformer_params(db),
)));
}
}
Some(KnownFunction::DataclassTransform) => {
@@ -871,47 +886,47 @@ impl<'db> Bindings<'db> {
}
_ => {
let mut handle_dataclass_transformer_params =
|function_type: &FunctionType| {
if let Some(params) =
function_type.dataclass_transformer_params(db)
{
// This is a call to a custom function that was decorated with `@dataclass_transformer`.
// If this function was called with a keyword argument like `order=False`, we extract
// the argument type and overwrite the corresponding flag in `dataclass_params` after
// constructing them from the `dataclass_transformer`-parameter defaults.
let mut dataclass_params = DataclassParams::from(params);
if let Some(Some(Type::BooleanLiteral(order))) = overload
.signature
.parameters()
.keyword_by_name("order")
.map(|(idx, _)| idx)
.and_then(|idx| overload.parameter_types().get(idx))
{
dataclass_params.set(DataclassParams::ORDER, *order);
}
overload.set_return_type(Type::DataclassDecorator(
dataclass_params,
));
}
};
// Ideally, either the implementation, or exactly one of the overloads
// of the function can have the dataclass_transform decorator applied.
// However, we do not yet enforce this, and in the case of multiple
// applications of the decorator, we will only consider the last one
// for the return value, since the prior ones will be over-written.
if let Some(overloaded) = function_type.to_overloaded(db) {
overloaded
.overloads
.iter()
.for_each(&mut handle_dataclass_transformer_params);
}
let return_type = function_type
.iter_overloads_and_implementation(db)
.filter_map(|function_overload| {
function_overload.dataclass_transformer_params(db).map(
|params| {
// This is a call to a custom function that was decorated with `@dataclass_transformer`.
// If this function was called with a keyword argument like `order=False`, we extract
// the argument type and overwrite the corresponding flag in `dataclass_params` after
// constructing them from the `dataclass_transformer`-parameter defaults.
handle_dataclass_transformer_params(&function_type);
let mut dataclass_params =
DataclassParams::from(params);
if let Some(Some(Type::BooleanLiteral(order))) =
overload
.signature
.parameters()
.keyword_by_name("order")
.map(|(idx, _)| idx)
.and_then(|idx| {
overload.parameter_types().get(idx)
})
{
dataclass_params
.set(DataclassParams::ORDER, *order);
}
Type::DataclassDecorator(dataclass_params)
},
)
})
.last();
if let Some(return_type) = return_type {
overload.set_return_type(return_type);
}
}
},
@@ -997,6 +1012,7 @@ impl<'db> From<Binding<'db>> for Bindings<'db> {
signature_type,
dunder_call_is_possibly_unbound: false,
bound_type: None,
return_type: None,
overloads: smallvec![from],
};
Bindings {
@@ -1015,14 +1031,9 @@ impl<'db> From<Binding<'db>> for Bindings<'db> {
/// If the callable has multiple overloads, the first one that matches is used as the overall
/// binding match.
///
/// TODO: Implement the call site evaluation algorithm in the [proposed updated typing
/// spec][overloads], which is much more subtle than “first match wins”.
///
/// If the arguments cannot be matched to formal parameters, we store information about the
/// specific errors that occurred when trying to match them up. If the callable has multiple
/// overloads, we store this error information for each overload.
///
/// [overloads]: https://github.com/python/typing/pull/1839
#[derive(Debug)]
pub(crate) struct CallableBinding<'db> {
/// The type that is (hopefully) callable.
@@ -1040,6 +1051,14 @@ pub(crate) struct CallableBinding<'db> {
/// The type of the bound `self` or `cls` parameter if this signature is for a bound method.
pub(crate) bound_type: Option<Type<'db>>,
/// The return type of this callable.
///
/// This is only `Some` if it's an overloaded callable, "argument type expansion" was
/// performed, and one of the expansion evaluated successfully for all of the argument lists.
/// This type is then the union of all the return types of the matched overloads for the
/// expanded argument lists.
return_type: Option<Type<'db>>,
/// The bindings of each overload of this callable. Will be empty if the type is not callable.
///
/// By using `SmallVec`, we avoid an extra heap allocation for the common case of a
@@ -1061,6 +1080,7 @@ impl<'db> CallableBinding<'db> {
signature_type,
dunder_call_is_possibly_unbound: false,
bound_type: None,
return_type: None,
overloads,
}
}
@@ -1071,6 +1091,7 @@ impl<'db> CallableBinding<'db> {
signature_type,
dunder_call_is_possibly_unbound: false,
bound_type: None,
return_type: None,
overloads: smallvec![],
}
}
@@ -1099,12 +1120,6 @@ impl<'db> CallableBinding<'db> {
// before checking.
let arguments = arguments.with_self(self.bound_type);
// TODO: This checks every overload. In the proposed more detailed call checking spec [1],
// arguments are checked for arity first, and are only checked for type assignability against
// the matching overloads. Make sure to implement that as part of separating call binding into
// two phases.
//
// [1] https://typing.python.org/en/latest/spec/overload.html#overload-call-evaluation
for overload in &mut self.overloads {
overload.match_parameters(arguments.as_ref(), argument_forms, conflicting_forms);
}
@@ -1114,9 +1129,154 @@ impl<'db> CallableBinding<'db> {
// If this callable is a bound method, prepend the self instance onto the arguments list
// before checking.
let argument_types = argument_types.with_self(self.bound_type);
for overload in &mut self.overloads {
overload.check_types(db, argument_types.as_ref());
// Step 1: Check the result of the arity check which is done by `match_parameters`
let matching_overload_indexes = match self.matching_overload_index() {
MatchingOverloadIndex::None => {
// If no candidate overloads remain from the arity check, we can stop here. We
// still perform type checking for non-overloaded function to provide better user
// experience.
if let [overload] = self.overloads.as_mut_slice() {
overload.check_types(db, argument_types.as_ref(), argument_types.types());
}
return;
}
MatchingOverloadIndex::Single(index) => {
// If only one candidate overload remains, it is the winning match.
// TODO: Evaluate it as a regular (non-overloaded) call. This means that any
// diagnostics reported in this check should be reported directly instead of
// reporting it as `no-matching-overload`.
self.overloads[index].check_types(
db,
argument_types.as_ref(),
argument_types.types(),
);
return;
}
MatchingOverloadIndex::Multiple(indexes) => {
// If two or more candidate overloads remain, proceed to step 2.
indexes
}
};
let snapshotter = MatchingOverloadsSnapshotter::new(matching_overload_indexes);
// State of the bindings _before_ evaluating (type checking) the matching overloads using
// the non-expanded argument types.
let pre_evaluation_snapshot = snapshotter.take(self);
// Step 2: Evaluate each remaining overload as a regular (non-overloaded) call to determine
// whether it is compatible with the supplied argument list.
for (_, overload) in self.matching_overloads_mut() {
overload.check_types(db, argument_types.as_ref(), argument_types.types());
}
match self.matching_overload_index() {
MatchingOverloadIndex::None => {
// If all overloads result in errors, proceed to step 3.
}
MatchingOverloadIndex::Single(_) => {
// If only one overload evaluates without error, it is the winning match.
return;
}
MatchingOverloadIndex::Multiple(_) => {
// If two or more candidate overloads remain, proceed to step 4.
// TODO: Step 4 and Step 5 goes here...
// We're returning here because this shouldn't lead to argument type expansion.
return;
}
}
// Step 3: Perform "argument type expansion". Reference:
// https://typing.python.org/en/latest/spec/overload.html#argument-type-expansion
let mut expansions = argument_types.expand(db).peekable();
if expansions.peek().is_none() {
// Return early if there are no argument types to expand.
return;
}
// State of the bindings _after_ evaluating (type checking) the matching overloads using
// the non-expanded argument types.
let post_evaluation_snapshot = snapshotter.take(self);
// Restore the bindings state to the one prior to the type checking step in preparation
// for evaluating the expanded argument lists.
snapshotter.restore(self, pre_evaluation_snapshot);
for expanded_argument_lists in expansions {
// This is the merged state of the bindings after evaluating all of the expanded
// argument lists. This will be the final state to restore the bindings to if all of
// the expanded argument lists evaluated successfully.
let mut merged_evaluation_state: Option<MatchingOverloadsSnapshot<'db>> = None;
let mut return_types = Vec::new();
for expanded_argument_types in &expanded_argument_lists {
let pre_evaluation_snapshot = snapshotter.take(self);
for (_, overload) in self.matching_overloads_mut() {
overload.check_types(db, argument_types.as_ref(), expanded_argument_types);
}
let return_type = match self.matching_overload_index() {
MatchingOverloadIndex::None => None,
MatchingOverloadIndex::Single(index) => {
Some(self.overloads[index].return_type())
}
MatchingOverloadIndex::Multiple(index) => {
// TODO: Step 4 and Step 5 goes here... but for now we just use the return
// type of the first matched overload.
Some(self.overloads[index[0]].return_type())
}
};
// This split between initializing and updating the merged evaluation state is
// required because otherwise it's difficult to differentiate between the
// following:
// 1. An initial unmatched overload becomes a matched overload when evaluating the
// first argument list
// 2. An unmatched overload after evaluating the first argument list becomes a
// matched overload when evaluating the second argument list
if let Some(merged_evaluation_state) = merged_evaluation_state.as_mut() {
merged_evaluation_state.update(self);
} else {
merged_evaluation_state = Some(snapshotter.take(self));
}
// Restore the bindings state before evaluating the next argument list.
snapshotter.restore(self, pre_evaluation_snapshot);
if let Some(return_type) = return_type {
return_types.push(return_type);
} else {
// No need to check the remaining argument lists if the current argument list
// doesn't evaluate successfully. Move on to expanding the next argument type.
break;
}
}
if return_types.len() == expanded_argument_lists.len() {
// If the number of return types is equal to the number of expanded argument lists,
// they all evaluated successfully. So, we need to combine their return types by
// union to determine the final return type.
self.return_type = Some(UnionType::from_elements(db, return_types));
// Restore the bindings state to the one that merges the bindings state evaluating
// each of the expanded argument list.
if let Some(merged_evaluation_state) = merged_evaluation_state {
snapshotter.restore(self, merged_evaluation_state);
}
return;
}
}
// If the type expansion didn't yield any successful return type, we need to restore the
// bindings state back to the one after the type checking step using the non-expanded
// argument types. This is necessary because we restore the state to the pre-evaluation
// snapshot when processing the expanded argument lists.
snapshotter.restore(self, post_evaluation_snapshot);
}
fn as_result(&self) -> Result<(), CallErrorKind> {
@@ -1145,6 +1305,25 @@ impl<'db> CallableBinding<'db> {
self.matching_overloads().next().is_none()
}
/// Returns the index of the matching overload in the form of [`MatchingOverloadIndex`].
fn matching_overload_index(&self) -> MatchingOverloadIndex {
let mut matching_overloads = self.matching_overloads();
match matching_overloads.next() {
None => MatchingOverloadIndex::None,
Some((first, _)) => {
if let Some((second, _)) = matching_overloads.next() {
let mut indexes = vec![first, second];
for (index, _) in matching_overloads {
indexes.push(index);
}
MatchingOverloadIndex::Multiple(indexes)
} else {
MatchingOverloadIndex::Single(first)
}
}
}
}
/// Returns an iterator over all the overloads that matched for this call binding.
pub(crate) fn matching_overloads(&self) -> impl Iterator<Item = (usize, &Binding<'db>)> {
self.overloads
@@ -1163,16 +1342,20 @@ impl<'db> CallableBinding<'db> {
.filter(|(_, overload)| overload.as_result().is_ok())
}
/// Returns the return type of this call. For a valid call, this is the return type of the
/// first overload that the arguments matched against. For an invalid call to a non-overloaded
/// function, this is the return type of the function. For an invalid call to an overloaded
/// function, we return `Type::unknown`, since we cannot make any useful conclusions about
/// which overload was intended to be called.
/// Returns the return type of this call.
///
/// For a valid call, this is the return type of either a successful argument type expansion of
/// an overloaded function, or the return type of the first overload that the arguments matched
/// against.
///
/// For an invalid call to a non-overloaded function, this is the return type of the function.
///
/// For an invalid call to an overloaded function, we return `Type::unknown`, since we cannot
/// make any useful conclusions about which overload was intended to be called.
pub(crate) fn return_type(&self) -> Type<'db> {
// TODO: Implement the overload call evaluation algorithm as mentioned in the spec [1] to
// get the matching overload and use that to get the return type.
//
// [1]: https://typing.python.org/en/latest/spec/overload.html#overload-call-evaluation
if let Some(return_type) = self.return_type {
return return_type;
}
if let Some((_, first_overload)) = self.matching_overloads().next() {
return first_overload.return_type();
}
@@ -1261,47 +1444,49 @@ impl<'db> CallableBinding<'db> {
_ => None,
};
if let Some((kind, function)) = function_type_and_kind {
if let Some(overloaded_function) = function.to_overloaded(context.db()) {
if let Some(spans) = overloaded_function
.overloads
.first()
.and_then(|overload| overload.spans(context.db()))
{
let mut sub =
SubDiagnostic::new(Severity::Info, "First overload defined here");
sub.annotate(Annotation::primary(spans.signature));
diag.sub(sub);
}
let (overloads, implementation) =
function.overloads_and_implementation(context.db());
if let Some(spans) = overloads
.first()
.and_then(|overload| overload.spans(context.db()))
{
let mut sub =
SubDiagnostic::new(Severity::Info, "First overload defined here");
sub.annotate(Annotation::primary(spans.signature));
diag.sub(sub);
}
diag.info(format_args!(
"Possible overloads for {kind} `{}`:",
function.name(context.db())
));
for overload in overloads.iter().take(MAXIMUM_OVERLOADS) {
diag.info(format_args!(
"Possible overloads for {kind} `{}`:",
function.name(context.db())
" {}",
overload.signature(context.db(), None).display(context.db())
));
}
if overloads.len() > MAXIMUM_OVERLOADS {
diag.info(format_args!(
"... omitted {remaining} overloads",
remaining = overloads.len() - MAXIMUM_OVERLOADS
));
}
let overloads = &function.signature(context.db()).overloads.overloads;
for overload in overloads.iter().take(MAXIMUM_OVERLOADS) {
diag.info(format_args!(" {}", overload.display(context.db())));
}
if overloads.len() > MAXIMUM_OVERLOADS {
diag.info(format_args!(
"... omitted {remaining} overloads",
remaining = overloads.len() - MAXIMUM_OVERLOADS
));
}
if let Some(spans) = overloaded_function
.implementation
.and_then(|function| function.spans(context.db()))
{
let mut sub = SubDiagnostic::new(
Severity::Info,
"Overload implementation defined here",
);
sub.annotate(Annotation::primary(spans.signature));
diag.sub(sub);
}
if let Some(spans) =
implementation.and_then(|function| function.spans(context.db()))
{
let mut sub = SubDiagnostic::new(
Severity::Info,
"Overload implementation defined here",
);
sub.annotate(Annotation::primary(spans.signature));
diag.sub(sub);
}
}
if let Some(union_diag) = union_diag {
union_diag.add_union_context(context.db(), &mut diag);
}
@@ -1319,6 +1504,18 @@ impl<'a, 'db> IntoIterator for &'a CallableBinding<'db> {
}
}
#[derive(Debug)]
enum MatchingOverloadIndex {
/// No matching overloads found.
None,
/// Exactly one matching overload found at the given index.
Single(usize),
/// Multiple matching overloads found at the given indexes.
Multiple(Vec<usize>),
}
/// Binding information for one of the overloads of a callable.
#[derive(Debug)]
pub(crate) struct Binding<'db> {
@@ -1493,7 +1690,12 @@ impl<'db> Binding<'db> {
self.parameter_tys = vec![None; parameters.len()].into_boxed_slice();
}
fn check_types(&mut self, db: &'db dyn Db, argument_types: &CallArgumentTypes<'_, 'db>) {
fn check_types(
&mut self,
db: &'db dyn Db,
arguments: &CallArguments<'_>,
argument_types: &[Type<'db>],
) {
let mut num_synthetic_args = 0;
let get_argument_index = |argument_index: usize, num_synthetic_args: usize| {
if argument_index >= num_synthetic_args {
@@ -1507,13 +1709,20 @@ impl<'db> Binding<'db> {
}
};
let enumerate_argument_types = || {
arguments
.iter()
.zip(argument_types.iter().copied())
.enumerate()
};
// If this overload is generic, first see if we can infer a specialization of the function
// from the arguments that were passed in.
let signature = &self.signature;
let parameters = signature.parameters();
if signature.generic_context.is_some() || signature.inherited_generic_context.is_some() {
let mut builder = SpecializationBuilder::new(db);
for (argument_index, (argument, argument_type)) in argument_types.iter().enumerate() {
for (argument_index, (argument, argument_type)) in enumerate_argument_types() {
if matches!(argument, Argument::Synthetic) {
num_synthetic_args += 1;
}
@@ -1545,7 +1754,7 @@ impl<'db> Binding<'db> {
}
num_synthetic_args = 0;
for (argument_index, (argument, mut argument_type)) in argument_types.iter().enumerate() {
for (argument_index, (argument, mut argument_type)) in enumerate_argument_types() {
if matches!(argument, Argument::Synthetic) {
num_synthetic_args += 1;
}
@@ -1648,6 +1857,133 @@ impl<'db> Binding<'db> {
}
Ok(())
}
fn snapshot(&self) -> BindingSnapshot<'db> {
BindingSnapshot {
return_ty: self.return_ty,
specialization: self.specialization,
inherited_specialization: self.inherited_specialization,
argument_parameters: self.argument_parameters.clone(),
parameter_tys: self.parameter_tys.clone(),
errors: self.errors.clone(),
}
}
fn restore(&mut self, snapshot: BindingSnapshot<'db>) {
let BindingSnapshot {
return_ty,
specialization,
inherited_specialization,
argument_parameters,
parameter_tys,
errors,
} = snapshot;
self.return_ty = return_ty;
self.specialization = specialization;
self.inherited_specialization = inherited_specialization;
self.argument_parameters = argument_parameters;
self.parameter_tys = parameter_tys;
self.errors = errors;
}
}
#[derive(Clone, Debug)]
struct BindingSnapshot<'db> {
return_ty: Type<'db>,
specialization: Option<Specialization<'db>>,
inherited_specialization: Option<Specialization<'db>>,
argument_parameters: Box<[Option<usize>]>,
parameter_tys: Box<[Option<Type<'db>>]>,
errors: Vec<BindingError<'db>>,
}
/// Represents the snapshot of the matched overload bindings.
///
/// The reason that this only contains the matched overloads are:
/// 1. Avoid creating snapshots for the overloads that have been filtered by the arity check
/// 2. Avoid duplicating errors when merging the snapshots on a successful evaluation of all the
/// expanded argument lists
#[derive(Clone, Debug)]
struct MatchingOverloadsSnapshot<'db>(Vec<(usize, BindingSnapshot<'db>)>);
impl<'db> MatchingOverloadsSnapshot<'db> {
/// Update the state of the matched overload bindings in this snapshot with the current
/// state in the given `binding`.
fn update(&mut self, binding: &CallableBinding<'db>) {
// Here, the `snapshot` is the state of this binding for the previous argument list and
// `binding` would contain the state after evaluating the current argument list.
for (snapshot, binding) in self
.0
.iter_mut()
.map(|(index, snapshot)| (snapshot, &binding.overloads[*index]))
{
if binding.errors.is_empty() {
// If the binding has no errors, this means that the current argument list was
// evaluated successfully and this is the matching overload.
//
// Clear the errors from the snapshot of this overload to signal this change ...
snapshot.errors.clear();
// ... and update the snapshot with the current state of the binding.
snapshot.return_ty = binding.return_ty;
snapshot.specialization = binding.specialization;
snapshot.inherited_specialization = binding.inherited_specialization;
snapshot
.argument_parameters
.clone_from(&binding.argument_parameters);
snapshot.parameter_tys.clone_from(&binding.parameter_tys);
}
// If the errors in the snapshot was empty, then this binding is the matching overload
// for a previously evaluated argument list. This means that we don't need to change
// any information for an already matched overload binding.
//
// If it does have errors, we could extend it with the errors from evaluating the
// current argument list. Arguably, this isn't required, since the errors in the
// snapshot should already signal that this is an unmatched overload which is why we
// don't do it. Similarly, due to this being an unmatched overload, there's no point in
// updating the binding state.
}
}
}
/// A helper to take snapshots of the matched overload bindings for the current state of the
/// bindings.
struct MatchingOverloadsSnapshotter(Vec<usize>);
impl MatchingOverloadsSnapshotter {
/// Creates a new snapshotter for the given indexes of the matched overloads.
fn new(indexes: Vec<usize>) -> Self {
debug_assert!(indexes.len() > 1);
MatchingOverloadsSnapshotter(indexes)
}
/// Takes a snapshot of the current state of the matched overload bindings.
///
/// # Panics
///
/// Panics if the indexes of the matched overloads are not valid for the given binding.
fn take<'db>(&self, binding: &CallableBinding<'db>) -> MatchingOverloadsSnapshot<'db> {
MatchingOverloadsSnapshot(
self.0
.iter()
.map(|index| (*index, binding.overloads[*index].snapshot()))
.collect(),
)
}
/// Restores the state of the matched overload bindings from the given snapshot.
fn restore<'db>(
&self,
binding: &mut CallableBinding<'db>,
snapshot: MatchingOverloadsSnapshot<'db>,
) {
debug_assert_eq!(self.0.len(), snapshot.0.len());
for (index, snapshot) in snapshot.0 {
binding.overloads[index].restore(snapshot);
}
}
}
/// Describes a callable for the purposes of diagnostics.

View File

@@ -2,17 +2,17 @@ use std::hash::BuildHasherDefault;
use std::sync::{LazyLock, Mutex};
use super::{
IntersectionBuilder, KnownFunction, MemberLookupPolicy, Mro, MroError, MroIterator,
SpecialFormType, SubclassOfType, Truthiness, Type, TypeQualifiers, class_base::ClassBase,
infer_expression_type, infer_unpack_types,
IntersectionBuilder, MemberLookupPolicy, Mro, MroError, MroIterator, SpecialFormType,
SubclassOfType, Truthiness, Type, TypeQualifiers, class_base::ClassBase, infer_expression_type,
infer_unpack_types,
};
use crate::semantic_index::DeclarationWithConstraint;
use crate::semantic_index::definition::Definition;
use crate::types::function::{DataclassTransformerParams, KnownFunction};
use crate::types::generics::{GenericContext, Specialization};
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
use crate::types::{
CallableType, DataclassParams, DataclassTransformerParams, KnownInstanceType, TypeMapping,
TypeVarInstance,
CallableType, DataclassParams, KnownInstanceType, TypeMapping, TypeVarInstance,
};
use crate::{
Db, FxOrderSet, KnownModule, Program,
@@ -472,12 +472,9 @@ impl<'db> ClassType<'db> {
.map_type(|ty| ty.apply_optional_specialization(db, specialization))
}
/// Returns the `name` attribute of an instance of this class.
/// Look up an instance attribute (available in `__dict__`) of the given name.
///
/// The attribute could be defined in the class body, but it could also be an implicitly
/// defined attribute that is only present in a method (typically `__init__`).
///
/// The attribute might also be defined in a superclass of this class.
/// See [`Type::instance_member`] for more details.
pub(super) fn instance_member(self, db: &'db dyn Db, name: &str) -> SymbolAndQualifiers<'db> {
let (class_literal, specialization) = self.class_literal(db);
class_literal
@@ -1372,7 +1369,8 @@ impl<'db> ClassLiteral<'db> {
parameters.push(parameter);
}
let signature = Signature::new(Parameters::new(parameters), Some(Type::none(db)));
let mut signature = Signature::new(Parameters::new(parameters), Some(Type::none(db)));
signature.inherited_generic_context = self.generic_context(db);
Some(CallableType::function_like(db, signature))
};
@@ -1536,12 +1534,9 @@ impl<'db> ClassLiteral<'db> {
attributes
}
/// Returns the `name` attribute of an instance of this class.
/// Look up an instance attribute (available in `__dict__`) of the given name.
///
/// The attribute could be defined in the class body, but it could also be an implicitly
/// defined attribute that is only present in a method (typically `__init__`).
///
/// The attribute might also be defined in a superclass of this class.
/// See [`Type::instance_member`] for more details.
pub(super) fn instance_member(
self,
db: &'db dyn Db,

View File

@@ -63,6 +63,7 @@ impl<'db> ClassBase<'db> {
/// Return `None` if `ty` is not an acceptable type for a class base.
pub(super) fn try_from_type(db: &'db dyn Db, ty: Type<'db>) -> Option<Self> {
match ty {
Type::TypeAliasRef(_) => None, //TODO
Type::Dynamic(dynamic) => Some(Self::Dynamic(dynamic)),
Type::ClassLiteral(literal) => {
if literal.is_known(db, KnownClass::Any) {

View File

@@ -11,13 +11,14 @@ use ruff_text_size::{Ranged, TextRange};
use super::{Type, TypeCheckDiagnostics, binding_type};
use crate::lint::LintSource;
use crate::semantic_index::semantic_index;
use crate::semantic_index::symbol::ScopeId;
use crate::types::function::FunctionDecorators;
use crate::{
Db,
lint::{LintId, LintMetadata},
suppression::suppressions,
};
use crate::{semantic_index::semantic_index, types::FunctionDecorators};
/// Context for inferring the types of a single file.
///

View File

@@ -8,12 +8,13 @@ use super::{
use crate::lint::{Level, LintRegistryBuilder, LintStatus};
use crate::suppression::FileSuppressionId;
use crate::types::LintDiagnosticGuard;
use crate::types::function::KnownFunction;
use crate::types::string_annotation::{
BYTE_STRING_TYPE_ANNOTATION, ESCAPE_CHARACTER_IN_FORWARD_ANNOTATION, FSTRING_TYPE_ANNOTATION,
IMPLICIT_CONCATENATED_STRING_TYPE_ANNOTATION, INVALID_SYNTAX_IN_FORWARD_ANNOTATION,
RAW_STRING_TYPE_ANNOTATION,
};
use crate::types::{KnownFunction, SpecialFormType, Type, protocol_class::ProtocolClassLiteral};
use crate::types::{SpecialFormType, Type, protocol_class::ProtocolClassLiteral};
use crate::{Db, Module, ModuleName, Program, declare_lint};
use itertools::Itertools;
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic};

View File

@@ -7,6 +7,7 @@ use ruff_python_ast::str::{Quote, TripleQuotes};
use ruff_python_literal::escape::AsciiEscape;
use crate::types::class::{ClassLiteral, ClassType, GenericAlias};
use crate::types::function::{FunctionType, OverloadLiteral};
use crate::types::generics::{GenericContext, Specialization};
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
use crate::types::{
@@ -66,6 +67,7 @@ struct DisplayRepresentation<'db> {
impl Display for DisplayRepresentation<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self.ty {
Type::TypeAliasRef(alias) => f.write_str(alias.name(self.db)),
Type::Dynamic(dynamic) => dynamic.fmt(f),
Type::Never => f.write_str("Never"),
Type::NominalInstance(instance) => {
@@ -112,34 +114,7 @@ impl Display for DisplayRepresentation<'_> {
},
Type::SpecialForm(special_form) => special_form.fmt(f),
Type::KnownInstance(known_instance) => known_instance.repr(self.db).fmt(f),
Type::FunctionLiteral(function) => {
let signature = function.signature(self.db);
// TODO: when generic function types are supported, we should add
// the generic type parameters to the signature, i.e.
// show `def foo[T](x: T) -> T`.
match signature.overloads.as_slice() {
[signature] => {
write!(
f,
// "def {name}{specialization}{signature}",
"def {name}{signature}",
name = function.name(self.db),
signature = signature.display(self.db)
)
}
signatures => {
// TODO: How to display overloads?
f.write_str("Overload[")?;
let mut join = f.join(", ");
for signature in signatures {
join.entry(&signature.display(self.db));
}
f.write_str("]")
}
}
}
Type::FunctionLiteral(function) => function.display(self.db).fmt(f),
Type::Callable(callable) => callable.display(self.db).fmt(f),
Type::BoundMethod(bound_method) => {
let function = bound_method.function(self.db);
@@ -241,6 +216,71 @@ impl Display for DisplayRepresentation<'_> {
}
}
impl<'db> OverloadLiteral<'db> {
// Not currently used, but useful for debugging.
#[expect(dead_code)]
pub(crate) fn display(self, db: &'db dyn Db) -> DisplayOverloadLiteral<'db> {
DisplayOverloadLiteral { literal: self, db }
}
}
pub(crate) struct DisplayOverloadLiteral<'db> {
literal: OverloadLiteral<'db>,
db: &'db dyn Db,
}
impl Display for DisplayOverloadLiteral<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let signature = self.literal.signature(self.db, None);
write!(
f,
"def {name}{signature}",
name = self.literal.name(self.db),
signature = signature.display(self.db)
)
}
}
impl<'db> FunctionType<'db> {
pub(crate) fn display(self, db: &'db dyn Db) -> DisplayFunctionType<'db> {
DisplayFunctionType { ty: self, db }
}
}
pub(crate) struct DisplayFunctionType<'db> {
ty: FunctionType<'db>,
db: &'db dyn Db,
}
impl Display for DisplayFunctionType<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let signature = self.ty.signature(self.db);
// TODO: We should consider adding the type parameters to the signature of a generic
// function, i.e. `def foo[T](x: T) -> T`.
match signature.overloads.as_slice() {
[signature] => {
write!(
f,
"def {name}{signature}",
name = self.ty.name(self.db),
signature = signature.display(self.db)
)
}
signatures => {
// TODO: How to display overloads?
f.write_str("Overload[")?;
let mut join = f.join(", ");
for signature in signatures {
join.entry(&signature.display(self.db));
}
f.write_str("]")
}
}
}
}
impl<'db> GenericAlias<'db> {
pub(crate) fn display(&'db self, db: &'db dyn Db) -> DisplayGenericAlias<'db> {
DisplayGenericAlias {

View File

@@ -0,0 +1,996 @@
//! Contains representations of function literals. There are several complicating factors:
//!
//! - Functions can be generic, and can have specializations applied to them. These are not the
//! same thing! For instance, a method of a generic class might not itself be generic, but it can
//! still have the class's specialization applied to it.
//!
//! - Functions can be overloaded, and each overload can be independently generic or not, with
//! different sets of typevars for different generic overloads. In some cases we need to consider
//! each overload separately; in others we need to consider all of the overloads (and any
//! implementation) as a single collective entity.
//!
//! - Certain “known” functions need special treatment — for instance, inferring a special return
//! type, or raising custom diagnostics.
//!
//! - TODO: Some functions don't correspond to a function definition in the AST, and are instead
//! synthesized as we mimic the behavior of the Python interpreter. Even though they are
//! synthesized, and are “implemented” as Rust code, they are still functions from the POV of the
//! rest of the type system.
//!
//! Given these constraints, we have the following representation: a function is a list of one or
//! more overloads, with zero or more specializations (more specifically, “type mappings”) applied
//! to it. [`FunctionType`] is the outermost type, which is what [`Type::FunctionLiteral`] wraps.
//! It contains the list of type mappings to apply. It wraps a [`FunctionLiteral`], which collects
//! together all of the overloads (and implementation) of an overloaded function. An
//! [`OverloadLiteral`] represents an individual function definition in the AST — that is, each
//! overload (and implementation) of an overloaded function, or the single definition of a
//! non-overloaded function.
//!
//! Technically, each `FunctionLiteral` wraps a particular overload and all _previous_ overloads.
//! So it's only true that it wraps _all_ overloads if you are looking at the last definition. For
//! instance, in
//!
//! ```py
//! @overload
//! def f(x: int) -> None: ...
//! # <-- 1
//!
//! @overload
//! def f(x: str) -> None: ...
//! # <-- 2
//!
//! def f(x): pass
//! # <-- 3
//! ```
//!
//! resolving `f` at each of the three numbered positions will give you a `FunctionType`, which
//! wraps a `FunctionLiteral`, which contain `OverloadLiteral`s only for the definitions that
//! appear before that position. We rely on the fact that later definitions shadow earlier ones, so
//! the public type of `f` is resolved at position 3, correctly giving you all of the overloads
//! (and the implementation).
use std::str::FromStr;
use bitflags::bitflags;
use ruff_db::diagnostic::Span;
use ruff_db::files::{File, FileRange};
use ruff_python_ast as ast;
use ruff_text_size::Ranged;
use crate::module_resolver::{KnownModule, file_to_module};
use crate::semantic_index::ast_ids::HasScopedUseId;
use crate::semantic_index::definition::Definition;
use crate::semantic_index::semantic_index;
use crate::semantic_index::symbol::ScopeId;
use crate::symbol::{Boundness, Symbol, symbol_from_bindings};
use crate::types::generics::GenericContext;
use crate::types::narrow::ClassInfoConstraintFunction;
use crate::types::signatures::{CallableSignature, Signature};
use crate::types::{BoundMethodType, CallableType, Type, TypeMapping, TypeVarInstance};
use crate::{Db, FxOrderSet};
/// A collection of useful spans for annotating functions.
///
/// This can be retrieved via `FunctionType::spans` or
/// `Type::function_spans`.
pub(crate) struct FunctionSpans {
/// The span of the entire function "signature." This includes
/// the name, parameter list and return type (if present).
pub(crate) signature: Span,
/// The span of the function name. i.e., `foo` in `def foo(): ...`.
pub(crate) name: Span,
/// The span of the parameter list, including the opening and
/// closing parentheses.
#[expect(dead_code)]
pub(crate) parameters: Span,
/// The span of the annotated return type, if present.
pub(crate) return_type: Option<Span>,
}
bitflags! {
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Hash)]
pub struct FunctionDecorators: u8 {
/// `@classmethod`
const CLASSMETHOD = 1 << 0;
/// `@typing.no_type_check`
const NO_TYPE_CHECK = 1 << 1;
/// `@typing.overload`
const OVERLOAD = 1 << 2;
/// `@abc.abstractmethod`
const ABSTRACT_METHOD = 1 << 3;
/// `@typing.final`
const FINAL = 1 << 4;
/// `@typing.override`
const OVERRIDE = 1 << 6;
}
}
bitflags! {
/// Used for the return type of `dataclass_transform(…)` calls. Keeps track of the
/// arguments that were passed in. For the precise meaning of the fields, see [1].
///
/// [1]: https://docs.python.org/3/library/typing.html#typing.dataclass_transform
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)]
pub struct DataclassTransformerParams: u8 {
const EQ_DEFAULT = 1 << 0;
const ORDER_DEFAULT = 1 << 1;
const KW_ONLY_DEFAULT = 1 << 2;
const FROZEN_DEFAULT = 1 << 3;
}
}
impl Default for DataclassTransformerParams {
fn default() -> Self {
Self::EQ_DEFAULT
}
}
/// Representation of a function definition in the AST: either a non-generic function, or a generic
/// function that has not been specialized.
///
/// If a function has multiple overloads, each overload is represented by a separate function
/// definition in the AST, and is therefore a separate `OverloadLiteral` instance.
///
/// # Ordering
/// Ordering is based on the function's id assigned by salsa and not on the function literal's
/// values. The id may change between runs, or when the function literal was garbage collected and
/// recreated.
#[salsa::interned(debug)]
#[derive(PartialOrd, Ord)]
pub struct OverloadLiteral<'db> {
/// Name of the function at definition.
#[returns(ref)]
pub name: ast::name::Name,
/// Is this a function that we special-case somehow? If so, which one?
pub(crate) known: Option<KnownFunction>,
/// The scope that's created by the function, in which the function body is evaluated.
pub(crate) body_scope: ScopeId<'db>,
/// A set of special decorators that were applied to this function
pub(crate) decorators: FunctionDecorators,
/// The arguments to `dataclass_transformer`, if this function was annotated
/// with `@dataclass_transformer(...)`.
pub(crate) dataclass_transformer_params: Option<DataclassTransformerParams>,
}
#[salsa::tracked]
impl<'db> OverloadLiteral<'db> {
fn with_dataclass_transformer_params(
self,
db: &'db dyn Db,
params: DataclassTransformerParams,
) -> Self {
Self::new(
db,
self.name(db).clone(),
self.known(db),
self.body_scope(db),
self.decorators(db),
Some(params),
)
}
fn file(self, db: &'db dyn Db) -> File {
// NOTE: Do not use `self.definition(db).file(db)` here, as that could create a
// cross-module dependency on the full AST.
self.body_scope(db).file(db)
}
pub(crate) fn has_known_decorator(self, db: &dyn Db, decorator: FunctionDecorators) -> bool {
self.decorators(db).contains(decorator)
}
pub(crate) fn is_overload(self, db: &dyn Db) -> bool {
self.has_known_decorator(db, FunctionDecorators::OVERLOAD)
}
fn node(self, db: &'db dyn Db, file: File) -> &'db ast::StmtFunctionDef {
debug_assert_eq!(
file,
self.file(db),
"OverloadLiteral::node() must be called with the same file as the one where \
the function is defined."
);
self.body_scope(db).node(db).expect_function()
}
/// Returns the [`FileRange`] of the function's name.
pub(crate) fn focus_range(self, db: &dyn Db) -> FileRange {
FileRange::new(
self.file(db),
self.body_scope(db).node(db).expect_function().name.range,
)
}
/// Returns the [`Definition`] of this function.
///
/// ## Warning
///
/// This uses the semantic index to find the definition of the function. This means that if the
/// calling query is not in the same file as this function is defined in, then this will create
/// a cross-module dependency directly on the full AST which will lead to cache
/// over-invalidation.
fn definition(self, db: &'db dyn Db) -> Definition<'db> {
let body_scope = self.body_scope(db);
let index = semantic_index(db, body_scope.file(db));
index.expect_single_definition(body_scope.node(db).expect_function())
}
/// Returns the overload immediately before this one in the AST. Returns `None` if there is no
/// previous overload.
fn previous_overload(self, db: &'db dyn Db) -> Option<FunctionLiteral<'db>> {
// The semantic model records a use for each function on the name node. This is used
// here to get the previous function definition with the same name.
let scope = self.definition(db).scope(db);
let use_def = semantic_index(db, scope.file(db)).use_def_map(scope.file_scope_id(db));
let use_id = self
.body_scope(db)
.node(db)
.expect_function()
.name
.scoped_use_id(db, scope);
let Symbol::Type(Type::FunctionLiteral(previous_type), Boundness::Bound) =
symbol_from_bindings(db, use_def.bindings_at_use(use_id))
else {
return None;
};
let previous_literal = previous_type.literal(db);
let previous_overload = previous_literal.last_definition(db);
if !previous_overload.is_overload(db) {
return None;
}
Some(previous_literal)
}
/// Typed internally-visible signature for this function.
///
/// This represents the annotations on the function itself, unmodified by decorators and
/// overloads.
///
/// ## Warning
///
/// This uses the semantic index to find the definition of the function. This means that if the
/// calling query is not in the same file as this function is defined in, then this will create
/// a cross-module dependency directly on the full AST which will lead to cache
/// over-invalidation.
pub(crate) fn signature(
self,
db: &'db dyn Db,
inherited_generic_context: Option<GenericContext<'db>>,
) -> Signature<'db> {
let scope = self.body_scope(db);
let function_stmt_node = scope.node(db).expect_function();
let definition = self.definition(db);
let generic_context = function_stmt_node.type_params.as_ref().map(|type_params| {
let index = semantic_index(db, scope.file(db));
GenericContext::from_type_params(db, index, type_params)
});
Signature::from_function(
db,
generic_context,
inherited_generic_context,
definition,
function_stmt_node,
)
}
fn parameter_span(
self,
db: &'db dyn Db,
parameter_index: Option<usize>,
) -> Option<(Span, Span)> {
let function_scope = self.body_scope(db);
let span = Span::from(function_scope.file(db));
let node = function_scope.node(db);
let func_def = node.as_function()?;
let range = parameter_index
.and_then(|parameter_index| {
func_def
.parameters
.iter()
.nth(parameter_index)
.map(|param| param.range())
})
.unwrap_or(func_def.parameters.range);
let name_span = span.clone().with_range(func_def.name.range);
let parameter_span = span.with_range(range);
Some((name_span, parameter_span))
}
pub(crate) fn spans(self, db: &'db dyn Db) -> Option<FunctionSpans> {
let function_scope = self.body_scope(db);
let span = Span::from(function_scope.file(db));
let node = function_scope.node(db);
let func_def = node.as_function()?;
let return_type_range = func_def.returns.as_ref().map(|returns| returns.range());
let mut signature = func_def.name.range.cover(func_def.parameters.range);
if let Some(return_type_range) = return_type_range {
signature = signature.cover(return_type_range);
}
Some(FunctionSpans {
signature: span.clone().with_range(signature),
name: span.clone().with_range(func_def.name.range),
parameters: span.clone().with_range(func_def.parameters.range),
return_type: return_type_range.map(|range| span.clone().with_range(range)),
})
}
}
/// Representation of a function definition in the AST, along with any previous overloads of the
/// function. Each overload can be separately generic or not, and each generic overload uses
/// distinct typevars.
///
/// # Ordering
/// Ordering is based on the function's id assigned by salsa and not on the function literal's
/// values. The id may change between runs, or when the function literal was garbage collected and
/// recreated.
#[salsa::interned(debug)]
#[derive(PartialOrd, Ord)]
pub struct FunctionLiteral<'db> {
pub(crate) last_definition: OverloadLiteral<'db>,
/// The inherited generic context, if this function is a constructor method (`__new__` or
/// `__init__`) being used to infer the specialization of its generic class. If any of the
/// method's overloads are themselves generic, this is in addition to those per-overload
/// generic contexts (which are created lazily in [`OverloadLiteral::signature`]).
///
/// If the function is not a constructor method, this field will always be `None`.
///
/// If the function is a constructor method, we will end up creating two `FunctionLiteral`
/// instances for it. The first is created in [`TypeInferenceBuilder`][infer] when we encounter
/// the function definition during type inference. At this point, we don't yet know if the
/// function is a constructor method, so we create a `FunctionLiteral` with `None` for this
/// field.
///
/// If at some point we encounter a call expression, which invokes the containing class's
/// constructor, as will create a _new_ `FunctionLiteral` instance for the function, with this
/// field [updated][] to contain the containing class's generic context.
///
/// [infer]: crate::types::infer::TypeInferenceBuilder::infer_function_definition
/// [updated]: crate::types::class::ClassLiteral::own_class_member
inherited_generic_context: Option<GenericContext<'db>>,
}
#[salsa::tracked]
impl<'db> FunctionLiteral<'db> {
fn with_inherited_generic_context(
self,
db: &'db dyn Db,
inherited_generic_context: GenericContext<'db>,
) -> Self {
// A function cannot inherit more than one generic context from its containing class.
debug_assert!(self.inherited_generic_context(db).is_none());
Self::new(
db,
self.last_definition(db),
Some(inherited_generic_context),
)
}
fn name(self, db: &'db dyn Db) -> &'db ast::name::Name {
// All of the overloads of a function literal should have the same name.
self.last_definition(db).name(db)
}
fn known(self, db: &'db dyn Db) -> Option<KnownFunction> {
// Whether a function is known is based on its name (and its containing module's name), so
// all overloads should be known (or not) equivalently.
self.last_definition(db).known(db)
}
fn has_known_decorator(self, db: &dyn Db, decorator: FunctionDecorators) -> bool {
self.iter_overloads_and_implementation(db)
.any(|overload| overload.decorators(db).contains(decorator))
}
fn definition(self, db: &'db dyn Db) -> Definition<'db> {
self.last_definition(db).definition(db)
}
fn parameter_span(
self,
db: &'db dyn Db,
parameter_index: Option<usize>,
) -> Option<(Span, Span)> {
self.last_definition(db).parameter_span(db, parameter_index)
}
fn spans(self, db: &'db dyn Db) -> Option<FunctionSpans> {
self.last_definition(db).spans(db)
}
#[salsa::tracked(returns(ref))]
fn overloads_and_implementation(
self,
db: &'db dyn Db,
) -> (Box<[OverloadLiteral<'db>]>, Option<OverloadLiteral<'db>>) {
let self_overload = self.last_definition(db);
let mut current = self_overload;
let mut overloads = vec![];
while let Some(previous) = current.previous_overload(db) {
let overload = previous.last_definition(db);
overloads.push(overload);
current = overload;
}
// Overloads are inserted in reverse order, from bottom to top.
overloads.reverse();
let implementation = if self_overload.is_overload(db) {
overloads.push(self_overload);
None
} else {
Some(self_overload)
};
(overloads.into_boxed_slice(), implementation)
}
fn iter_overloads_and_implementation(
self,
db: &'db dyn Db,
) -> impl Iterator<Item = OverloadLiteral<'db>> + 'db {
let (implementation, overloads) = self.overloads_and_implementation(db);
overloads.iter().chain(implementation).copied()
}
/// Typed externally-visible signature for this function.
///
/// This is the signature as seen by external callers, possibly modified by decorators and/or
/// overloaded.
///
/// ## Warning
///
/// This uses the semantic index to find the definition of the function. This means that if the
/// calling query is not in the same file as this function is defined in, then this will create
/// a cross-module dependency directly on the full AST which will lead to cache
/// over-invalidation.
fn signature<'a>(
self,
db: &'db dyn Db,
type_mappings: &'a [TypeMapping<'a, 'db>],
) -> CallableSignature<'db>
where
'db: 'a,
{
// We only include an implementation (i.e. a definition not decorated with `@overload`) if
// it's the only definition.
let inherited_generic_context = self.inherited_generic_context(db);
let (overloads, implementation) = self.overloads_and_implementation(db);
if let Some(implementation) = implementation {
if overloads.is_empty() {
return CallableSignature::single(type_mappings.iter().fold(
implementation.signature(db, inherited_generic_context),
|ty, mapping| ty.apply_type_mapping(db, mapping),
));
}
}
CallableSignature::from_overloads(overloads.iter().map(|overload| {
type_mappings.iter().fold(
overload.signature(db, inherited_generic_context),
|ty, mapping| ty.apply_type_mapping(db, mapping),
)
}))
}
fn normalized(self, db: &'db dyn Db) -> Self {
let context = self
.inherited_generic_context(db)
.map(|ctx| ctx.normalized(db));
Self::new(db, self.last_definition(db), context)
}
}
/// Represents a function type, which might be a non-generic function, or a specialization of a
/// generic function.
#[salsa::interned(debug)]
#[derive(PartialOrd, Ord)]
pub struct FunctionType<'db> {
pub(crate) literal: FunctionLiteral<'db>,
/// Type mappings that should be applied to the function's parameter and return types. This
/// might include specializations of enclosing generic contexts (e.g. for non-generic methods
/// of a specialized generic class).
#[returns(deref)]
type_mappings: Box<[TypeMapping<'db, 'db>]>,
}
#[salsa::tracked]
impl<'db> FunctionType<'db> {
pub(crate) fn with_inherited_generic_context(
self,
db: &'db dyn Db,
inherited_generic_context: GenericContext<'db>,
) -> Self {
let literal = self
.literal(db)
.with_inherited_generic_context(db, inherited_generic_context);
Self::new(db, literal, self.type_mappings(db))
}
pub(crate) fn with_type_mapping<'a>(
self,
db: &'db dyn Db,
type_mapping: &TypeMapping<'a, 'db>,
) -> Self {
let type_mappings: Box<[_]> = self
.type_mappings(db)
.iter()
.cloned()
.chain(std::iter::once(type_mapping.to_owned()))
.collect();
Self::new(db, self.literal(db), type_mappings)
}
pub(crate) fn with_dataclass_transformer_params(
self,
db: &'db dyn Db,
params: DataclassTransformerParams,
) -> Self {
// A decorator only applies to the specific overload that it is attached to, not to all
// previous overloads.
let literal = self.literal(db);
let last_definition = literal
.last_definition(db)
.with_dataclass_transformer_params(db, params);
let literal =
FunctionLiteral::new(db, last_definition, literal.inherited_generic_context(db));
Self::new(db, literal, self.type_mappings(db))
}
/// Returns the [`File`] in which this function is defined.
pub(crate) fn file(self, db: &'db dyn Db) -> File {
self.literal(db).last_definition(db).file(db)
}
/// Returns the AST node for this function.
pub(crate) fn node(self, db: &'db dyn Db, file: File) -> &'db ast::StmtFunctionDef {
self.literal(db).last_definition(db).node(db, file)
}
pub(crate) fn name(self, db: &'db dyn Db) -> &'db ast::name::Name {
self.literal(db).name(db)
}
pub(crate) fn known(self, db: &'db dyn Db) -> Option<KnownFunction> {
self.literal(db).known(db)
}
pub(crate) fn is_known(self, db: &'db dyn Db, known_function: KnownFunction) -> bool {
self.known(db) == Some(known_function)
}
/// Returns if any of the overloads of this function have a particular decorator.
///
/// Some decorators are expected to appear on every overload; others are expected to appear
/// only the implementation or first overload. This method does not check either of those
/// conditions.
pub(crate) fn has_known_decorator(self, db: &dyn Db, decorator: FunctionDecorators) -> bool {
self.literal(db).has_known_decorator(db, decorator)
}
/// Returns the [`Definition`] of the implementation or first overload of this function.
///
/// ## Warning
///
/// This uses the semantic index to find the definition of the function. This means that if the
/// calling query is not in the same file as this function is defined in, then this will create
/// a cross-module dependency directly on the full AST which will lead to cache
/// over-invalidation.
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
self.literal(db).definition(db)
}
/// Returns a tuple of two spans. The first is
/// the span for the identifier of the function
/// definition for `self`. The second is
/// the span for the parameter in the function
/// definition for `self`.
///
/// If there are no meaningful spans, then this
/// returns `None`. For example, when this type
/// isn't callable.
///
/// When `parameter_index` is `None`, then the
/// second span returned covers the entire parameter
/// list.
///
/// # Performance
///
/// Note that this may introduce cross-module
/// dependencies. This can have an impact on
/// the effectiveness of incremental caching
/// and should therefore be used judiciously.
///
/// An example of a good use case is to improve
/// a diagnostic.
pub(crate) fn parameter_span(
self,
db: &'db dyn Db,
parameter_index: Option<usize>,
) -> Option<(Span, Span)> {
self.literal(db).parameter_span(db, parameter_index)
}
/// Returns a collection of useful spans for a
/// function signature. These are useful for
/// creating annotations on diagnostics.
///
/// # Performance
///
/// Note that this may introduce cross-module
/// dependencies. This can have an impact on
/// the effectiveness of incremental caching
/// and should therefore be used judiciously.
///
/// An example of a good use case is to improve
/// a diagnostic.
pub(crate) fn spans(self, db: &'db dyn Db) -> Option<FunctionSpans> {
self.literal(db).spans(db)
}
/// Returns all of the overload signatures and the implementation definition, if any, of this
/// function. The overload signatures will be in source order.
pub(crate) fn overloads_and_implementation(
self,
db: &'db dyn Db,
) -> &'db (Box<[OverloadLiteral<'db>]>, Option<OverloadLiteral<'db>>) {
self.literal(db).overloads_and_implementation(db)
}
/// Returns an iterator of all of the definitions of this function, including both overload
/// signatures and any implementation, all in source order.
pub(crate) fn iter_overloads_and_implementation(
self,
db: &'db dyn Db,
) -> impl Iterator<Item = OverloadLiteral<'db>> + 'db {
self.literal(db).iter_overloads_and_implementation(db)
}
/// Typed externally-visible signature for this function.
///
/// This is the signature as seen by external callers, possibly modified by decorators and/or
/// overloaded.
///
/// ## Why is this a salsa query?
///
/// This is a salsa query to short-circuit the invalidation
/// when the function's AST node changes.
///
/// Were this not a salsa query, then the calling query
/// would depend on the function's AST and rerun for every change in that file.
#[salsa::tracked(returns(ref), cycle_fn=signature_cycle_recover, cycle_initial=signature_cycle_initial)]
pub(crate) fn signature(self, db: &'db dyn Db) -> CallableSignature<'db> {
self.literal(db).signature(db, self.type_mappings(db))
}
/// Convert the `FunctionType` into a [`Type::Callable`].
pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> Type<'db> {
Type::Callable(CallableType::new(db, self.signature(db), false))
}
/// Convert the `FunctionType` into a [`Type::BoundMethod`].
pub(crate) fn into_bound_method_type(
self,
db: &'db dyn Db,
self_instance: Type<'db>,
) -> Type<'db> {
Type::BoundMethod(BoundMethodType::new(db, self, self_instance))
}
pub(crate) fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool {
// A function type is the subtype of itself, and not of any other function type. However,
// our representation of a function type includes any specialization that should be applied
// to the signature. Different specializations of the same function type are only subtypes
// of each other if they result in subtype signatures.
if self.normalized(db) == other.normalized(db) {
return true;
}
if self.literal(db) != other.literal(db) {
return false;
}
let self_signature = self.signature(db);
let other_signature = other.signature(db);
if !self_signature.is_fully_static(db) || !other_signature.is_fully_static(db) {
return false;
}
self_signature.is_subtype_of(db, other_signature)
}
pub(crate) fn is_assignable_to(self, db: &'db dyn Db, other: Self) -> bool {
// A function type is assignable to itself, and not to any other function type. However,
// our representation of a function type includes any specialization that should be applied
// to the signature. Different specializations of the same function type are only
// assignable to each other if they result in assignable signatures.
self.literal(db) == other.literal(db)
&& self.signature(db).is_assignable_to(db, other.signature(db))
}
pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
if self.normalized(db) == other.normalized(db) {
return true;
}
if self.literal(db) != other.literal(db) {
return false;
}
let self_signature = self.signature(db);
let other_signature = other.signature(db);
if !self_signature.is_fully_static(db) || !other_signature.is_fully_static(db) {
return false;
}
self_signature.is_equivalent_to(db, other_signature)
}
pub(crate) fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
self.literal(db) == other.literal(db)
&& self
.signature(db)
.is_gradual_equivalent_to(db, other.signature(db))
}
pub(crate) fn find_legacy_typevars(
self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
) {
let signatures = self.signature(db);
for signature in &signatures.overloads {
signature.find_legacy_typevars(db, typevars);
}
}
pub(crate) fn normalized(self, db: &'db dyn Db) -> Self {
let mappings: Box<_> = self
.type_mappings(db)
.iter()
.map(|mapping| mapping.normalized(db))
.collect();
Self::new(db, self.literal(db).normalized(db), mappings)
}
}
fn signature_cycle_recover<'db>(
_db: &'db dyn Db,
_value: &CallableSignature<'db>,
_count: u32,
_function: FunctionType<'db>,
) -> salsa::CycleRecoveryAction<CallableSignature<'db>> {
salsa::CycleRecoveryAction::Iterate
}
fn signature_cycle_initial<'db>(
db: &'db dyn Db,
_function: FunctionType<'db>,
) -> CallableSignature<'db> {
CallableSignature::single(Signature::bottom(db))
}
/// Non-exhaustive enumeration of known functions (e.g. `builtins.reveal_type`, ...) that might
/// have special behavior.
#[derive(
Debug, Copy, Clone, PartialEq, Eq, Hash, strum_macros::EnumString, strum_macros::IntoStaticStr,
)]
#[strum(serialize_all = "snake_case")]
#[cfg_attr(test, derive(strum_macros::EnumIter))]
pub enum KnownFunction {
/// `builtins.isinstance`
#[strum(serialize = "isinstance")]
IsInstance,
/// `builtins.issubclass`
#[strum(serialize = "issubclass")]
IsSubclass,
/// `builtins.hasattr`
#[strum(serialize = "hasattr")]
HasAttr,
/// `builtins.reveal_type`, `typing.reveal_type` or `typing_extensions.reveal_type`
RevealType,
/// `builtins.len`
Len,
/// `builtins.repr`
Repr,
/// `typing(_extensions).final`
Final,
/// [`typing(_extensions).no_type_check`](https://typing.python.org/en/latest/spec/directives.html#no-type-check)
NoTypeCheck,
/// `typing(_extensions).assert_type`
AssertType,
/// `typing(_extensions).assert_never`
AssertNever,
/// `typing(_extensions).cast`
Cast,
/// `typing(_extensions).overload`
Overload,
/// `typing(_extensions).override`
Override,
/// `typing(_extensions).is_protocol`
IsProtocol,
/// `typing(_extensions).get_protocol_members`
GetProtocolMembers,
/// `typing(_extensions).runtime_checkable`
RuntimeCheckable,
/// `typing(_extensions).dataclass_transform`
DataclassTransform,
/// `abc.abstractmethod`
#[strum(serialize = "abstractmethod")]
AbstractMethod,
/// `dataclasses.dataclass`
Dataclass,
/// `inspect.getattr_static`
GetattrStatic,
/// `ty_extensions.static_assert`
StaticAssert,
/// `ty_extensions.is_equivalent_to`
IsEquivalentTo,
/// `ty_extensions.is_subtype_of`
IsSubtypeOf,
/// `ty_extensions.is_assignable_to`
IsAssignableTo,
/// `ty_extensions.is_disjoint_from`
IsDisjointFrom,
/// `ty_extensions.is_gradual_equivalent_to`
IsGradualEquivalentTo,
/// `ty_extensions.is_fully_static`
IsFullyStatic,
/// `ty_extensions.is_singleton`
IsSingleton,
/// `ty_extensions.is_single_valued`
IsSingleValued,
/// `ty_extensions.generic_context`
GenericContext,
/// `ty_extensions.dunder_all_names`
DunderAllNames,
/// `ty_extensions.all_members`
AllMembers,
}
impl KnownFunction {
pub fn into_classinfo_constraint_function(self) -> Option<ClassInfoConstraintFunction> {
match self {
Self::IsInstance => Some(ClassInfoConstraintFunction::IsInstance),
Self::IsSubclass => Some(ClassInfoConstraintFunction::IsSubclass),
_ => None,
}
}
pub(crate) fn try_from_definition_and_name<'db>(
db: &'db dyn Db,
definition: Definition<'db>,
name: &str,
) -> Option<Self> {
let candidate = Self::from_str(name).ok()?;
candidate
.check_module(file_to_module(db, definition.file(db))?.known()?)
.then_some(candidate)
}
/// Return `true` if `self` is defined in `module` at runtime.
const fn check_module(self, module: KnownModule) -> bool {
match self {
Self::IsInstance | Self::IsSubclass | Self::HasAttr | Self::Len | Self::Repr => {
module.is_builtins()
}
Self::AssertType
| Self::AssertNever
| Self::Cast
| Self::Overload
| Self::Override
| Self::RevealType
| Self::Final
| Self::IsProtocol
| Self::GetProtocolMembers
| Self::RuntimeCheckable
| Self::DataclassTransform
| Self::NoTypeCheck => {
matches!(module, KnownModule::Typing | KnownModule::TypingExtensions)
}
Self::AbstractMethod => {
matches!(module, KnownModule::Abc)
}
Self::Dataclass => {
matches!(module, KnownModule::Dataclasses)
}
Self::GetattrStatic => module.is_inspect(),
Self::IsAssignableTo
| Self::IsDisjointFrom
| Self::IsEquivalentTo
| Self::IsGradualEquivalentTo
| Self::IsFullyStatic
| Self::IsSingleValued
| Self::IsSingleton
| Self::IsSubtypeOf
| Self::GenericContext
| Self::DunderAllNames
| Self::StaticAssert
| Self::AllMembers => module.is_ty_extensions(),
}
}
}
#[cfg(test)]
pub(crate) mod tests {
use strum::IntoEnumIterator;
use super::*;
use crate::db::tests::setup_db;
use crate::symbol::known_module_symbol;
#[test]
fn known_function_roundtrip_from_str() {
let db = setup_db();
for function in KnownFunction::iter() {
let function_name: &'static str = function.into();
let module = match function {
KnownFunction::Len
| KnownFunction::Repr
| KnownFunction::IsInstance
| KnownFunction::HasAttr
| KnownFunction::IsSubclass => KnownModule::Builtins,
KnownFunction::AbstractMethod => KnownModule::Abc,
KnownFunction::Dataclass => KnownModule::Dataclasses,
KnownFunction::GetattrStatic => KnownModule::Inspect,
KnownFunction::Cast
| KnownFunction::Final
| KnownFunction::Overload
| KnownFunction::Override
| KnownFunction::RevealType
| KnownFunction::AssertType
| KnownFunction::AssertNever
| KnownFunction::IsProtocol
| KnownFunction::GetProtocolMembers
| KnownFunction::RuntimeCheckable
| KnownFunction::DataclassTransform
| KnownFunction::NoTypeCheck => KnownModule::TypingExtensions,
KnownFunction::IsSingleton
| KnownFunction::IsSubtypeOf
| KnownFunction::GenericContext
| KnownFunction::DunderAllNames
| KnownFunction::StaticAssert
| KnownFunction::IsFullyStatic
| KnownFunction::IsDisjointFrom
| KnownFunction::IsSingleValued
| KnownFunction::IsAssignableTo
| KnownFunction::IsEquivalentTo
| KnownFunction::IsGradualEquivalentTo
| KnownFunction::AllMembers => KnownModule::TyExtensions,
};
let function_definition = known_module_symbol(&db, module, function_name)
.symbol
.expect_type()
.expect_function_literal()
.definition(&db);
assert_eq!(
KnownFunction::try_from_definition_and_name(
&db,
function_definition,
function_name
),
Some(function),
"The strum `EnumString` implementation appears to be incorrect for `{function_name}`"
);
}
}
}

View File

@@ -23,6 +23,8 @@ impl AllMembers {
fn extend_with_type<'db>(&mut self, db: &'db dyn Db, ty: Type<'db>) {
match ty {
Type::TypeAliasRef(_) => {} // TODO
Type::Union(union) => self.members.extend(
union
.elements(db)

View File

@@ -81,19 +81,21 @@ use crate::types::diagnostic::{
report_invalid_attribute_assignment, report_invalid_generator_function_return_type,
report_invalid_return_type, report_possibly_unbound_attribute,
};
use crate::types::function::{
FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral,
};
use crate::types::generics::GenericContext;
use crate::types::mro::MroErrorKind;
use crate::types::signatures::{CallableSignature, Signature};
use crate::types::unpacker::{UnpackResult, Unpacker};
use crate::types::{
BareTypeAliasType, CallDunderError, CallableType, ClassLiteral, ClassType, DataclassParams,
DynamicType, FunctionDecorators, FunctionType, GenericAlias, IntersectionBuilder,
IntersectionType, KnownClass, KnownFunction, KnownInstanceType, MemberLookupPolicy,
MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm, Parameters, SpecialFormType,
StringLiteralType, SubclassOfType, Symbol, SymbolAndQualifiers, Truthiness, TupleType, Type,
TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints,
TypeVarInstance, TypeVarKind, TypeVarVariance, UnionBuilder, UnionType, binding_type,
todo_type,
DynamicType, GenericAlias, IntersectionBuilder, IntersectionType, KnownClass,
KnownInstanceType, MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter,
ParameterForm, Parameters, SpecialFormType, StringLiteralType, SubclassOfType, Symbol,
SymbolAndQualifiers, Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers,
TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind,
TypeVarVariance, UnionBuilder, UnionType, binding_type, todo_type,
};
use crate::unpack::{Unpack, UnpackPosition};
use crate::util::subscript::{PyIndex, PySlice};
@@ -1131,12 +1133,13 @@ impl<'db> TypeInferenceBuilder<'db> {
}
for function in self.called_functions.union(&public_functions) {
let Some(overloaded) = function.to_overloaded(self.db()) else {
let (overloads, implementation) = function.overloads_and_implementation(self.db());
if overloads.is_empty() {
continue;
};
}
// Check that the overloaded function has at least two overloads
if let [single_overload] = overloaded.overloads.as_slice() {
if let [single_overload] = overloads.as_ref() {
let function_node = function.node(self.db(), self.file());
if let Some(builder) = self
.context
@@ -1157,7 +1160,7 @@ impl<'db> TypeInferenceBuilder<'db> {
// Check that the overloaded function has an implementation. Overload definitions
// within stub files, protocols, and on abstract methods within abstract base classes
// are exempt from this check.
if overloaded.implementation.is_none() && !self.in_stub() {
if implementation.is_none() && !self.in_stub() {
let mut implementation_required = true;
if let NodeWithScopeKind::Class(class_node_ref) = scope {
@@ -1169,7 +1172,7 @@ impl<'db> TypeInferenceBuilder<'db> {
if class.is_protocol(self.db())
|| (class.is_abstract(self.db())
&& overloaded.overloads.iter().all(|overload| {
&& overloads.iter().all(|overload| {
overload.has_known_decorator(
self.db(),
FunctionDecorators::ABSTRACT_METHOD,
@@ -1199,7 +1202,7 @@ impl<'db> TypeInferenceBuilder<'db> {
let mut decorator_present = false;
let mut decorator_missing = vec![];
for function in overloaded.all() {
for function in overloads.iter().chain(implementation.as_ref()) {
if function.has_known_decorator(self.db(), decorator) {
decorator_present = true;
} else {
@@ -1240,8 +1243,8 @@ impl<'db> TypeInferenceBuilder<'db> {
(FunctionDecorators::FINAL, "final"),
(FunctionDecorators::OVERRIDE, "override"),
] {
if let Some(implementation) = overloaded.implementation.as_ref() {
for overload in &overloaded.overloads {
if let Some(implementation) = implementation {
for overload in overloads.as_ref() {
if !overload.has_known_decorator(self.db(), decorator) {
continue;
}
@@ -1263,7 +1266,7 @@ impl<'db> TypeInferenceBuilder<'db> {
);
}
} else {
let mut overloads = overloaded.overloads.iter();
let mut overloads = overloads.iter();
let Some(first_overload) = overloads.next() else {
continue;
};
@@ -2027,7 +2030,7 @@ impl<'db> TypeInferenceBuilder<'db> {
}
}
let function_kind =
let known_function =
KnownFunction::try_from_definition_and_name(self.db(), definition, name);
let body_scope = self
@@ -2035,17 +2038,23 @@ impl<'db> TypeInferenceBuilder<'db> {
.node_scope(NodeWithScopeRef::Function(function))
.to_scope_id(self.db(), self.file());
let inherited_generic_context = None;
let type_mappings = Box::from([]);
let mut inferred_ty = Type::FunctionLiteral(FunctionType::new(
let overload_literal = OverloadLiteral::new(
self.db(),
&name.id,
function_kind,
known_function,
body_scope,
function_decorators,
dataclass_transformer_params,
inherited_generic_context,
);
let inherited_generic_context = None;
let function_literal =
FunctionLiteral::new(self.db(), overload_literal, inherited_generic_context);
let type_mappings = Box::from([]);
let mut inferred_ty = Type::FunctionLiteral(FunctionType::new(
self.db(),
function_literal,
type_mappings,
));
@@ -2314,14 +2323,10 @@ impl<'db> TypeInferenceBuilder<'db> {
// overload, or an overload and the implementation both. Nevertheless, this is not
// allowed. We do not try to treat the offenders intelligently -- just use the
// params of the last seen usage of `@dataclass_transform`
if let Some(overloaded) = f.to_overloaded(self.db()) {
overloaded.overloads.iter().for_each(|overload| {
if let Some(params) = overload.dataclass_transformer_params(self.db()) {
dataclass_params = Some(params.into());
}
});
}
if let Some(params) = f.dataclass_transformer_params(self.db()) {
let params = f
.iter_overloads_and_implementation(self.db())
.find_map(|overload| overload.dataclass_transformer_params(self.db()));
if let Some(params) = params {
dataclass_params = Some(params.into());
continue;
}
@@ -3007,6 +3012,7 @@ impl<'db> TypeInferenceBuilder<'db> {
};
match object_ty {
Type::TypeAliasRef(_) => true, // TODO
Type::Union(union) => {
if union.elements(self.db()).iter().all(|elem| {
self.validate_attribute_assignment(target, *elem, attribute, value_ty, false)
@@ -6028,6 +6034,8 @@ impl<'db> TypeInferenceBuilder<'db> {
let operand_type = self.infer_expression(operand);
match (op, operand_type) {
(_, Type::TypeAliasRef(_)) => todo_type!("type alias in unary expression"),
(_, Type::Dynamic(_)) => operand_type,
(_, Type::Never) => Type::Never,
@@ -6169,6 +6177,21 @@ impl<'db> TypeInferenceBuilder<'db> {
}
match (left_ty, right_ty, op) {
(Type::TypeAliasRef(alias), _, _) => self.infer_binary_expression_type(
node,
emitted_division_by_zero_diagnostic,
alias.value_type(self.db()),
right_ty,
op,
),
(_, Type::TypeAliasRef(alias), _) => self.infer_binary_expression_type(
node,
emitted_division_by_zero_diagnostic,
left_ty,
alias.value_type(self.db()),
op,
),
(Type::Union(lhs_union), rhs, _) => {
let mut union = UnionBuilder::new(self.db());
for lhs in lhs_union.elements(self.db()) {

View File

@@ -6,6 +6,7 @@ use crate::semantic_index::predicate::{
};
use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId, SymbolTable};
use crate::semantic_index::symbol_table;
use crate::types::function::KnownFunction;
use crate::types::infer::infer_same_file_expression_type;
use crate::types::{
IntersectionBuilder, KnownClass, SubclassOfType, Truthiness, Type, UnionBuilder,
@@ -20,7 +21,7 @@ use ruff_python_ast::{BoolOp, ExprBoolOp};
use rustc_hash::FxHashMap;
use std::collections::hash_map::Entry;
use super::{KnownFunction, UnionType};
use super::UnionType;
/// Return the type constraint that `test` (if true) would place on `symbol`, if any.
///

View File

@@ -7,9 +7,8 @@ use ruff_python_ast::name::Name;
use crate::{
semantic_index::{symbol_table, use_def_map},
symbol::{symbol_from_bindings, symbol_from_declarations},
types::{
ClassBase, ClassLiteral, KnownFunction, Type, TypeMapping, TypeQualifiers, TypeVarInstance,
},
types::function::KnownFunction,
types::{ClassBase, ClassLiteral, Type, TypeMapping, TypeQualifiers, TypeVarInstance},
{Db, FxOrderSet},
};

View File

@@ -53,10 +53,6 @@ impl<'db> CallableSignature<'db> {
self.overloads.iter()
}
pub(crate) fn as_slice(&self) -> &[Signature<'db>] {
self.overloads.as_slice()
}
pub(crate) fn normalized(&self, db: &'db dyn Db) -> Self {
Self::from_overloads(
self.overloads
@@ -1538,7 +1534,8 @@ mod tests {
use super::*;
use crate::db::tests::{TestDb, setup_db};
use crate::symbol::global_symbol;
use crate::types::{FunctionSignature, FunctionType, KnownClass};
use crate::types::KnownClass;
use crate::types::function::FunctionType;
use ruff_db::system::DbWithWritableSystem as _;
#[track_caller]
@@ -1559,9 +1556,11 @@ mod tests {
fn empty() {
let mut db = setup_db();
db.write_dedented("/src/a.py", "def f(): ...").unwrap();
let func = get_function_f(&db, "/src/a.py");
let func = get_function_f(&db, "/src/a.py")
.literal(&db)
.last_definition(&db);
let sig = func.internal_signature(&db, None);
let sig = func.signature(&db, None);
assert!(sig.return_ty.is_none());
assert_params(&sig, &[]);
@@ -1582,9 +1581,11 @@ mod tests {
",
)
.unwrap();
let func = get_function_f(&db, "/src/a.py");
let func = get_function_f(&db, "/src/a.py")
.literal(&db)
.last_definition(&db);
let sig = func.internal_signature(&db, None);
let sig = func.signature(&db, None);
assert_eq!(sig.return_ty.unwrap().display(&db).to_string(), "bytes");
assert_params(
@@ -1633,9 +1634,11 @@ mod tests {
",
)
.unwrap();
let func = get_function_f(&db, "/src/a.py");
let func = get_function_f(&db, "/src/a.py")
.literal(&db)
.last_definition(&db);
let sig = func.internal_signature(&db, None);
let sig = func.signature(&db, None);
let [
Parameter {
@@ -1669,9 +1672,11 @@ mod tests {
",
)
.unwrap();
let func = get_function_f(&db, "/src/a.pyi");
let func = get_function_f(&db, "/src/a.pyi")
.literal(&db)
.last_definition(&db);
let sig = func.internal_signature(&db, None);
let sig = func.signature(&db, None);
let [
Parameter {
@@ -1705,9 +1710,11 @@ mod tests {
",
)
.unwrap();
let func = get_function_f(&db, "/src/a.py");
let func = get_function_f(&db, "/src/a.py")
.literal(&db)
.last_definition(&db);
let sig = func.internal_signature(&db, None);
let sig = func.signature(&db, None);
let [
Parameter {
@@ -1751,9 +1758,11 @@ mod tests {
",
)
.unwrap();
let func = get_function_f(&db, "/src/a.pyi");
let func = get_function_f(&db, "/src/a.pyi")
.literal(&db)
.last_definition(&db);
let sig = func.internal_signature(&db, None);
let sig = func.signature(&db, None);
let [
Parameter {
@@ -1789,15 +1798,13 @@ mod tests {
.unwrap();
let func = get_function_f(&db, "/src/a.py");
let expected_sig = func.internal_signature(&db, None);
let overload = func.literal(&db).last_definition(&db);
let expected_sig = overload.signature(&db, None);
// With no decorators, internal and external signature are the same
assert_eq!(
func.signature(&db),
&FunctionSignature {
overloads: CallableSignature::single(expected_sig),
implementation: None
},
&CallableSignature::single(expected_sig)
);
}
}

View File

@@ -187,6 +187,10 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
(Type::KnownInstance(_), _) => Ordering::Less,
(_, Type::KnownInstance(_)) => Ordering::Greater,
(Type::TypeAliasRef(left), Type::TypeAliasRef(right)) => left.cmp(right),
(Type::TypeAliasRef(_), _) => Ordering::Less,
(_, Type::TypeAliasRef(_)) => Ordering::Greater,
(Type::PropertyInstance(left), Type::PropertyInstance(right)) => left.cmp(right),
(Type::PropertyInstance(_), _) => Ordering::Less,
(_, Type::PropertyInstance(_)) => Ordering::Greater,

View File

@@ -192,8 +192,15 @@ impl<'db> Unpacker<'db> {
err.fallback_element_type(self.db())
})
};
for target_type in &mut target_types {
target_type.push(ty);
// Both `elts` and `target_types` are guaranteed to have the same length.
for (element, target_type) in elts.iter().zip(&mut target_types) {
if element.is_starred_expr() {
target_type.push(
KnownClass::List.to_specialized_instance(self.db(), [ty]),
);
} else {
target_type.push(ty);
}
}
}
}

View File

@@ -30,7 +30,7 @@ ty_python_semantic = { path = "../crates/ty_python_semantic" }
ty_vendored = { path = "../crates/ty_vendored" }
libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer", default-features = false }
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "2b5188778e91a5ab50cb7d827148caf7eb2f4630" }
salsa = { git = "https://github.com/carljm/salsa.git", rev = "0f6d406f6c309964279baef71588746b8c67b4a3" }
similar = { version = "2.5.0" }
tracing = { version = "0.1.40" }