Compare commits
21 Commits
ag/fix-com
...
david/opaq
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0252ee6531 | ||
|
|
969efae929 | ||
|
|
e08024ffda | ||
|
|
9981ac7ecc | ||
|
|
30c9a5d47e | ||
|
|
7684e23612 | ||
|
|
e9b1fc3942 | ||
|
|
11db567b0b | ||
|
|
9f8c3de462 | ||
|
|
293d4ac388 | ||
|
|
9e8a7e9353 | ||
|
|
453e5f5934 | ||
|
|
7ea773daf2 | ||
|
|
0079cc6817 | ||
|
|
e8ea40012a | ||
|
|
71d8a5da2a | ||
|
|
2c3b3d3230 | ||
|
|
8d98c601d8 | ||
|
|
0986edf427 | ||
|
|
03f1f8e218 | ||
|
|
628bb2cd1d |
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
108
crates/ty/docs/rules.md
generated
@@ -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`
|
||||
|
||||
@@ -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 {
|
||||
|
||||
401
crates/ty_python_semantic/resources/mdtest/call/overloads.md
Normal file
401
crates/ty_python_semantic/resources/mdtest/call/overloads.md
Normal 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
|
||||
```
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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]
|
||||
```
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
|
||||
[] = []
|
||||
() = ()
|
||||
[] = ()
|
||||
() = []
|
||||
```
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
996
crates/ty_python_semantic/src/types/function.rs
Normal file
996
crates/ty_python_semantic/src/types/function.rs
Normal 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}`"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -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},
|
||||
};
|
||||
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user