Compare commits
8 Commits
0.11.11
...
david/allo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b19177cd6d | ||
|
|
a1399656c9 | ||
|
|
6392dccd24 | ||
|
|
93ac0934dd | ||
|
|
aae4482c55 | ||
|
|
d02c9ada5d | ||
|
|
6c0a59ea78 | ||
|
|
0b181bc2ad |
@@ -80,6 +80,7 @@ fn generate() -> String {
|
||||
|
||||
let mut parents = Vec::new();
|
||||
|
||||
output.push_str("<!-- WARNING: This file is auto-generated (cargo dev generate-all). Edit the doc comments in 'crates/ty/src/args.rs' if you want to change anything here. -->\n\n");
|
||||
output.push_str("# CLI Reference\n\n");
|
||||
generate_command(&mut output, &ty, &mut parents);
|
||||
|
||||
|
||||
@@ -25,6 +25,10 @@ pub(crate) fn main(args: &Args) -> anyhow::Result<()> {
|
||||
let file_name = "crates/ty/docs/configuration.md";
|
||||
let markdown_path = PathBuf::from(ROOT_DIR).join(file_name);
|
||||
|
||||
output.push_str(
|
||||
"<!-- WARNING: This file is auto-generated (cargo dev generate-all). Update the doc comments on the 'Options' struct in 'crates/ty_project/src/metadata/options.rs' if you want to change anything here. -->\n\n",
|
||||
);
|
||||
|
||||
generate_set(
|
||||
&mut output,
|
||||
Set::Toplevel(Options::metadata()),
|
||||
|
||||
@@ -56,6 +56,10 @@ fn generate_markdown() -> String {
|
||||
|
||||
let mut output = String::new();
|
||||
|
||||
let _ = writeln!(
|
||||
&mut output,
|
||||
"<!-- WARNING: This file is auto-generated (cargo dev generate-all). Edit the lint-declarations in 'crates/ty_python_semantic/src/types/diagnostic.rs' if you want to change anything here. -->\n"
|
||||
);
|
||||
let _ = writeln!(&mut output, "# Rules\n");
|
||||
|
||||
let mut lints: Vec<_> = registry.lints().iter().collect();
|
||||
|
||||
2
crates/ty/docs/cli.md
generated
2
crates/ty/docs/cli.md
generated
@@ -1,3 +1,5 @@
|
||||
<!-- WARNING: This file is auto-generated (cargo dev generate-all). Edit the doc comments in 'crates/ty/src/args.rs' if you want to change anything here. -->
|
||||
|
||||
# CLI Reference
|
||||
|
||||
## ty
|
||||
|
||||
2
crates/ty/docs/configuration.md
generated
2
crates/ty/docs/configuration.md
generated
@@ -1,3 +1,5 @@
|
||||
<!-- WARNING: This file is auto-generated (cargo dev generate-all). Update the doc comments on the 'Options' struct in 'crates/ty_project/src/metadata/options.rs' if you want to change anything here. -->
|
||||
|
||||
# Configuration
|
||||
#### `respect-ignore-files`
|
||||
|
||||
|
||||
110
crates/ty/docs/rules.md
generated
110
crates/ty/docs/rules.md
generated
@@ -1,3 +1,5 @@
|
||||
<!-- WARNING: This file is auto-generated (cargo dev generate-all). Edit the lint-declarations in 'crates/ty_python_semantic/src/types/diagnostic.rs' if you want to change anything here. -->
|
||||
|
||||
# Rules
|
||||
|
||||
## `byte-string-type-annotation`
|
||||
@@ -50,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#L90)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L91)
|
||||
</details>
|
||||
|
||||
## `conflicting-argument-forms`
|
||||
@@ -81,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#L134)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L135)
|
||||
</details>
|
||||
|
||||
## `conflicting-declarations`
|
||||
@@ -111,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#L160)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L161)
|
||||
</details>
|
||||
|
||||
## `conflicting-metaclass`
|
||||
@@ -142,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#L185)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L186)
|
||||
</details>
|
||||
|
||||
## `cyclic-class-definition`
|
||||
@@ -173,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#L211)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L212)
|
||||
</details>
|
||||
|
||||
## `duplicate-base`
|
||||
@@ -199,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#L255)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L256)
|
||||
</details>
|
||||
|
||||
## `escape-character-in-forward-annotation`
|
||||
@@ -336,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#L276)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L277)
|
||||
</details>
|
||||
|
||||
## `inconsistent-mro`
|
||||
@@ -365,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#L362)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L363)
|
||||
</details>
|
||||
|
||||
## `index-out-of-bounds`
|
||||
@@ -390,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#L386)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L387)
|
||||
</details>
|
||||
|
||||
## `invalid-argument-type`
|
||||
@@ -416,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#L406)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L407)
|
||||
</details>
|
||||
|
||||
## `invalid-assignment`
|
||||
@@ -443,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#L446)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L447)
|
||||
</details>
|
||||
|
||||
## `invalid-attribute-access`
|
||||
@@ -476,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#L1394)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1395)
|
||||
</details>
|
||||
|
||||
## `invalid-base`
|
||||
@@ -499,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#L468)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L469)
|
||||
</details>
|
||||
|
||||
## `invalid-context-manager`
|
||||
@@ -525,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#L519)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L520)
|
||||
</details>
|
||||
|
||||
## `invalid-declaration`
|
||||
@@ -553,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#L540)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L541)
|
||||
</details>
|
||||
|
||||
## `invalid-exception-caught`
|
||||
@@ -594,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#L563)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L564)
|
||||
</details>
|
||||
|
||||
## `invalid-generic-class`
|
||||
@@ -625,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#L599)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L600)
|
||||
</details>
|
||||
|
||||
## `invalid-legacy-type-variable`
|
||||
@@ -658,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#L625)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L626)
|
||||
</details>
|
||||
|
||||
## `invalid-metaclass`
|
||||
@@ -690,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#L674)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L675)
|
||||
</details>
|
||||
|
||||
## `invalid-overload`
|
||||
@@ -738,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#L701)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L702)
|
||||
</details>
|
||||
|
||||
## `invalid-parameter-default`
|
||||
@@ -763,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#L744)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L745)
|
||||
</details>
|
||||
|
||||
## `invalid-protocol`
|
||||
@@ -796,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#L334)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L335)
|
||||
</details>
|
||||
|
||||
## `invalid-raise`
|
||||
@@ -844,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#L764)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L765)
|
||||
</details>
|
||||
|
||||
## `invalid-return-type`
|
||||
@@ -868,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#L427)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L428)
|
||||
</details>
|
||||
|
||||
## `invalid-super-argument`
|
||||
@@ -912,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#L807)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L808)
|
||||
</details>
|
||||
|
||||
## `invalid-syntax-in-forward-annotation`
|
||||
@@ -952,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#L653)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L654)
|
||||
</details>
|
||||
|
||||
## `invalid-type-checking-constant`
|
||||
@@ -981,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#L846)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L847)
|
||||
</details>
|
||||
|
||||
## `invalid-type-form`
|
||||
@@ -1010,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#L870)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L871)
|
||||
</details>
|
||||
|
||||
## `invalid-type-variable-constraints`
|
||||
@@ -1044,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#L894)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L895)
|
||||
</details>
|
||||
|
||||
## `missing-argument`
|
||||
@@ -1068,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#L923)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L924)
|
||||
</details>
|
||||
|
||||
## `no-matching-overload`
|
||||
@@ -1096,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#L942)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L943)
|
||||
</details>
|
||||
|
||||
## `non-subscriptable`
|
||||
@@ -1119,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#L965)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L966)
|
||||
</details>
|
||||
|
||||
## `not-iterable`
|
||||
@@ -1144,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#L983)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L984)
|
||||
</details>
|
||||
|
||||
## `parameter-already-assigned`
|
||||
@@ -1170,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#L1034)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1035)
|
||||
</details>
|
||||
|
||||
## `raw-string-type-annotation`
|
||||
@@ -1229,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#L1370)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1371)
|
||||
</details>
|
||||
|
||||
## `subclass-of-final-class`
|
||||
@@ -1257,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#L1125)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1126)
|
||||
</details>
|
||||
|
||||
## `too-many-positional-arguments`
|
||||
@@ -1283,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#L1170)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1171)
|
||||
</details>
|
||||
|
||||
## `type-assertion-failure`
|
||||
@@ -1310,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#L1148)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1149)
|
||||
</details>
|
||||
|
||||
## `unavailable-implicit-super-arguments`
|
||||
@@ -1354,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#L1191)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1192)
|
||||
</details>
|
||||
|
||||
## `unknown-argument`
|
||||
@@ -1380,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#L1248)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1249)
|
||||
</details>
|
||||
|
||||
## `unresolved-attribute`
|
||||
@@ -1407,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#L1269)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1270)
|
||||
</details>
|
||||
|
||||
## `unresolved-import`
|
||||
@@ -1431,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#L1291)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1292)
|
||||
</details>
|
||||
|
||||
## `unresolved-reference`
|
||||
@@ -1455,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#L1310)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1311)
|
||||
</details>
|
||||
|
||||
## `unsupported-bool-conversion`
|
||||
@@ -1491,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#L1003)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1004)
|
||||
</details>
|
||||
|
||||
## `unsupported-operator`
|
||||
@@ -1518,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#L1329)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1330)
|
||||
</details>
|
||||
|
||||
## `zero-stepsize-in-slice`
|
||||
@@ -1542,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#L1351)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1352)
|
||||
</details>
|
||||
|
||||
## `invalid-ignore-comment`
|
||||
@@ -1598,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#L1055)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1056)
|
||||
</details>
|
||||
|
||||
## `possibly-unbound-implicit-call`
|
||||
@@ -1629,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#L108)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L109)
|
||||
</details>
|
||||
|
||||
## `possibly-unbound-import`
|
||||
@@ -1660,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#L1077)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1078)
|
||||
</details>
|
||||
|
||||
## `redundant-cast`
|
||||
@@ -1686,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#L1422)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1423)
|
||||
</details>
|
||||
|
||||
## `undefined-reveal`
|
||||
@@ -1709,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#L1230)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1231)
|
||||
</details>
|
||||
|
||||
## `unknown-rule`
|
||||
@@ -1777,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#L486)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L487)
|
||||
</details>
|
||||
|
||||
## `division-by-zero`
|
||||
@@ -1800,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#L237)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L238)
|
||||
</details>
|
||||
|
||||
## `possibly-unresolved-reference`
|
||||
@@ -1827,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#L1103)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1104)
|
||||
</details>
|
||||
|
||||
## `unused-ignore-comment`
|
||||
|
||||
@@ -1454,10 +1454,10 @@ fn cli_config_args_toml_string_basic() -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_config_args_overrides_knot_toml() -> anyhow::Result<()> {
|
||||
fn cli_config_args_overrides_ty_toml() -> anyhow::Result<()> {
|
||||
let case = TestCase::with_files(vec![
|
||||
(
|
||||
"knot.toml",
|
||||
"ty.toml",
|
||||
r#"
|
||||
[terminal]
|
||||
error-on-warning = true
|
||||
@@ -1465,6 +1465,27 @@ fn cli_config_args_overrides_knot_toml() -> anyhow::Result<()> {
|
||||
),
|
||||
("test.py", r"print(x) # [unresolved-reference]"),
|
||||
])?;
|
||||
|
||||
// Exit code of 1 due to the setting in `ty.toml`
|
||||
assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
warning[unresolved-reference]: Name `x` used when not defined
|
||||
--> test.py:1:7
|
||||
|
|
||||
1 | print(x) # [unresolved-reference]
|
||||
| ^
|
||||
|
|
||||
info: rule `unresolved-reference` was selected on the command line
|
||||
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||
");
|
||||
|
||||
// Exit code of 0 because the `ty.toml` setting is overwritten by `--config`
|
||||
assert_cmd_snapshot!(case.command().arg("--warn").arg("unresolved-reference").arg("--config").arg("terminal.error-on-warning=false"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
|
||||
@@ -81,23 +81,22 @@ import typing
|
||||
|
||||
class ListSubclass(typing.List): ...
|
||||
|
||||
# revealed: tuple[<class 'ListSubclass'>, <class 'list[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], <class 'object'>]
|
||||
# revealed: tuple[<class 'ListSubclass'>, <class 'list[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(ListSubclass.__mro__)
|
||||
|
||||
class DictSubclass(typing.Dict): ...
|
||||
|
||||
# TODO: should not have multiple `Generic[]` elements
|
||||
# revealed: tuple[<class 'DictSubclass'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], <class 'object'>]
|
||||
# revealed: tuple[<class 'DictSubclass'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(DictSubclass.__mro__)
|
||||
|
||||
class SetSubclass(typing.Set): ...
|
||||
|
||||
# revealed: tuple[<class 'SetSubclass'>, <class 'set[Unknown]'>, <class 'MutableSet[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], <class 'object'>]
|
||||
# revealed: tuple[<class 'SetSubclass'>, <class 'set[Unknown]'>, <class 'MutableSet[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(SetSubclass.__mro__)
|
||||
|
||||
class FrozenSetSubclass(typing.FrozenSet): ...
|
||||
|
||||
# revealed: tuple[<class 'FrozenSetSubclass'>, <class 'frozenset[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], <class 'object'>]
|
||||
# revealed: tuple[<class 'FrozenSetSubclass'>, <class 'frozenset[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(FrozenSetSubclass.__mro__)
|
||||
|
||||
####################
|
||||
@@ -106,30 +105,26 @@ reveal_type(FrozenSetSubclass.__mro__)
|
||||
|
||||
class ChainMapSubclass(typing.ChainMap): ...
|
||||
|
||||
# TODO: should not have multiple `Generic[]` elements
|
||||
# revealed: tuple[<class 'ChainMapSubclass'>, <class 'ChainMap[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], <class 'object'>]
|
||||
# revealed: tuple[<class 'ChainMapSubclass'>, <class 'ChainMap[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(ChainMapSubclass.__mro__)
|
||||
|
||||
class CounterSubclass(typing.Counter): ...
|
||||
|
||||
# TODO: Should have one `Generic[]` element, not three(!)
|
||||
# revealed: tuple[<class 'CounterSubclass'>, <class 'Counter[Unknown]'>, <class 'dict[Unknown, int]'>, <class 'MutableMapping[Unknown, int]'>, <class 'Mapping[Unknown, int]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], typing.Generic[_T], <class 'object'>]
|
||||
# revealed: tuple[<class 'CounterSubclass'>, <class 'Counter[Unknown]'>, <class 'dict[Unknown, int]'>, <class 'MutableMapping[Unknown, int]'>, <class 'Mapping[Unknown, int]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(CounterSubclass.__mro__)
|
||||
|
||||
class DefaultDictSubclass(typing.DefaultDict): ...
|
||||
|
||||
# TODO: Should not have multiple `Generic[]` elements
|
||||
# revealed: tuple[<class 'DefaultDictSubclass'>, <class 'defaultdict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], <class 'object'>]
|
||||
# revealed: tuple[<class 'DefaultDictSubclass'>, <class 'defaultdict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(DefaultDictSubclass.__mro__)
|
||||
|
||||
class DequeSubclass(typing.Deque): ...
|
||||
|
||||
# revealed: tuple[<class 'DequeSubclass'>, <class 'deque[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], <class 'object'>]
|
||||
# revealed: tuple[<class 'DequeSubclass'>, <class 'deque[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(DequeSubclass.__mro__)
|
||||
|
||||
class OrderedDictSubclass(typing.OrderedDict): ...
|
||||
|
||||
# TODO: Should not have multiple `Generic[]` elements
|
||||
# revealed: tuple[<class 'OrderedDictSubclass'>, <class 'OrderedDict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], <class 'object'>]
|
||||
# revealed: tuple[<class 'OrderedDictSubclass'>, <class 'OrderedDict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(OrderedDictSubclass.__mro__)
|
||||
```
|
||||
|
||||
@@ -239,3 +239,37 @@ def _(flag: bool):
|
||||
# error: [possibly-unbound-implicit-call]
|
||||
reveal_type(c[0]) # revealed: str
|
||||
```
|
||||
|
||||
## Dunder methods cannot be looked up on instances
|
||||
|
||||
Class-level annotations with no value assigned are considered instance-only, and aren't available as
|
||||
dunder methods:
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
class C:
|
||||
__call__: Callable[..., None]
|
||||
|
||||
# error: [call-non-callable]
|
||||
C()()
|
||||
|
||||
# error: [invalid-assignment]
|
||||
_: Callable[..., None] = C()
|
||||
```
|
||||
|
||||
And of course the same is true if we have only an implicit assignment inside a method:
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.__call__ = lambda *a, **kw: None
|
||||
|
||||
# error: [call-non-callable]
|
||||
C()()
|
||||
|
||||
# error: [invalid-assignment]
|
||||
_: Callable[..., None] = C()
|
||||
```
|
||||
|
||||
@@ -109,23 +109,50 @@ def _(o: object):
|
||||
|
||||
### Unsupported operators for positive contributions
|
||||
|
||||
Raise an error if any of the positive contributions to the intersection type are unsupported for the
|
||||
given operator:
|
||||
Raise an error if the given operator is unsupported for all positive contributions to the
|
||||
intersection type:
|
||||
|
||||
```py
|
||||
class NonContainer1: ...
|
||||
class NonContainer2: ...
|
||||
|
||||
def _(x: object):
|
||||
if isinstance(x, NonContainer1):
|
||||
if isinstance(x, NonContainer2):
|
||||
reveal_type(x) # revealed: NonContainer1 & NonContainer2
|
||||
|
||||
# error: [unsupported-operator] "Operator `in` is not supported for types `int` and `NonContainer1`"
|
||||
reveal_type(2 in x) # revealed: bool
|
||||
```
|
||||
|
||||
Do not raise an error if at least one of the positive contributions to the intersection type support
|
||||
the operator:
|
||||
|
||||
```py
|
||||
class Container:
|
||||
def __contains__(self, x) -> bool:
|
||||
return False
|
||||
|
||||
class NonContainer: ...
|
||||
|
||||
def _(x: object):
|
||||
if isinstance(x, Container):
|
||||
if isinstance(x, NonContainer):
|
||||
reveal_type(x) # revealed: Container & NonContainer
|
||||
if isinstance(x, NonContainer1):
|
||||
if isinstance(x, Container):
|
||||
if isinstance(x, NonContainer2):
|
||||
reveal_type(x) # revealed: NonContainer1 & Container & NonContainer2
|
||||
reveal_type(2 in x) # revealed: bool
|
||||
```
|
||||
|
||||
# error: [unsupported-operator] "Operator `in` is not supported for types `int` and `NonContainer`"
|
||||
reveal_type(2 in x) # revealed: bool
|
||||
Do also raise an error if the intersection has no positive contributions at all, unless the operator
|
||||
is supported on `object`:
|
||||
|
||||
```py
|
||||
def _(x: object):
|
||||
if not isinstance(x, NonContainer1):
|
||||
reveal_type(x) # revealed: ~NonContainer1
|
||||
|
||||
# error: [unsupported-operator] "Operator `in` is not supported for types `int` and `object`, in comparing `Literal[2]` with `~NonContainer1`"
|
||||
reveal_type(2 in x) # revealed: bool
|
||||
|
||||
reveal_type(2 is x) # revealed: bool
|
||||
```
|
||||
|
||||
### Unsupported operators for negative contributions
|
||||
|
||||
@@ -24,9 +24,7 @@ class:
|
||||
|
||||
```py
|
||||
class Bad(Generic[T], Generic[T]): ... # error: [duplicate-base]
|
||||
|
||||
# TODO: should emit an error (fails at runtime)
|
||||
class AlsoBad(Generic[T], Generic[S]): ...
|
||||
class AlsoBad(Generic[T], Generic[S]): ... # error: [duplicate-base]
|
||||
```
|
||||
|
||||
You cannot use the same typevar more than once.
|
||||
|
||||
@@ -527,6 +527,45 @@ reveal_type(unknown_object) # revealed: Unknown
|
||||
reveal_type(unknown_object.__mro__) # revealed: Unknown
|
||||
```
|
||||
|
||||
## MROs of classes that use multiple inheritance with generic aliases and subscripted `Generic`
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar, Iterator
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class peekable(Generic[T], Iterator[T]): ...
|
||||
|
||||
# revealed: tuple[<class 'peekable[Unknown]'>, <class 'Iterator[T]'>, <class 'Iterable[T]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(peekable.__mro__)
|
||||
|
||||
class peekable2(Iterator[T], Generic[T]): ...
|
||||
|
||||
# revealed: tuple[<class 'peekable2[Unknown]'>, <class 'Iterator[T]'>, <class 'Iterable[T]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(peekable2.__mro__)
|
||||
|
||||
class Base: ...
|
||||
class Intermediate(Base, Generic[T]): ...
|
||||
class Sub(Intermediate[T], Base): ...
|
||||
|
||||
# revealed: tuple[<class 'Sub[Unknown]'>, <class 'Intermediate[T]'>, <class 'Base'>, typing.Generic, <class 'object'>]
|
||||
reveal_type(Sub.__mro__)
|
||||
```
|
||||
|
||||
## Unresolvable MROs involving generics have the original bases reported in the error message, not the resolved bases
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import Protocol, TypeVar, Generic
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class Foo(Protocol): ...
|
||||
class Bar(Protocol[T]): ...
|
||||
class Baz(Protocol[T], Foo, Bar[T]): ... # error: [inconsistent-mro]
|
||||
```
|
||||
|
||||
## Classes that inherit from themselves
|
||||
|
||||
These are invalid, but we need to be able to handle them gracefully without panicking.
|
||||
|
||||
@@ -67,12 +67,10 @@ It's an error to include both bare `Protocol` and subscripted `Protocol[]` in th
|
||||
simultaneously:
|
||||
|
||||
```py
|
||||
# TODO: should emit a `[duplicate-bases]` error here:
|
||||
class DuplicateBases(Protocol, Protocol[T]):
|
||||
class DuplicateBases(Protocol, Protocol[T]): # error: [duplicate-base]
|
||||
x: T
|
||||
|
||||
# TODO: should not have `Protocol` or `Generic` multiple times
|
||||
# revealed: tuple[<class 'DuplicateBases[Unknown]'>, typing.Protocol, typing.Generic, typing.Protocol[T], typing.Generic[T], <class 'object'>]
|
||||
# revealed: tuple[<class 'DuplicateBases[Unknown]'>, Unknown, <class 'object'>]
|
||||
reveal_type(DuplicateBases.__mro__)
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: mro.md - Method Resolution Order tests - Unresolvable MROs involving generics have the original bases reported in the error message, not the resolved bases
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import Protocol, TypeVar, Generic
|
||||
2 |
|
||||
3 | T = TypeVar("T")
|
||||
4 |
|
||||
5 | class Foo(Protocol): ...
|
||||
6 | class Bar(Protocol[T]): ...
|
||||
7 | class Baz(Protocol[T], Foo, Bar[T]): ... # error: [inconsistent-mro]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[inconsistent-mro]: Cannot create a consistent method resolution order (MRO) for class `Baz` with bases list `[typing.Protocol[T], <class 'Foo'>, <class 'Bar[T]'>]`
|
||||
--> src/mdtest_snippet.py:7:1
|
||||
|
|
||||
5 | class Foo(Protocol): ...
|
||||
6 | class Bar(Protocol[T]): ...
|
||||
7 | class Baz(Protocol[T], Foo, Bar[T]): ... # error: [inconsistent-mro]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
info: rule `inconsistent-mro` is enabled by default
|
||||
|
||||
```
|
||||
@@ -16,7 +16,7 @@ class Foo[T]: ...
|
||||
class Bar(Foo[Bar]): ...
|
||||
|
||||
reveal_type(Bar) # revealed: <class 'Bar'>
|
||||
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, <class 'Foo[Bar]'>, <class 'object'>]
|
||||
reveal_type(Bar.__mro__) # revealed: tuple[<class 'Bar'>, <class 'Foo[Bar]'>, typing.Generic, <class 'object'>]
|
||||
```
|
||||
|
||||
## Access to attributes declared in stubs
|
||||
|
||||
@@ -83,7 +83,7 @@ python-version = "3.9"
|
||||
```py
|
||||
class A(tuple[int, str]): ...
|
||||
|
||||
# revealed: tuple[<class 'A'>, <class 'tuple[@Todo(Generic tuple specializations), ...]'>, <class 'Sequence[@Todo(Generic tuple specializations)]'>, <class 'Reversible[@Todo(Generic tuple specializations)]'>, <class 'Collection[@Todo(Generic tuple specializations)]'>, <class 'Iterable[@Todo(Generic tuple specializations)]'>, <class 'Container[@Todo(Generic tuple specializations)]'>, typing.Protocol[_T_co], typing.Generic[_T_co], <class 'object'>]
|
||||
# revealed: tuple[<class 'A'>, <class 'tuple[@Todo(Generic tuple specializations), ...]'>, <class 'Sequence[@Todo(Generic tuple specializations)]'>, <class 'Reversible[@Todo(Generic tuple specializations)]'>, <class 'Collection[@Todo(Generic tuple specializations)]'>, <class 'Iterable[@Todo(Generic tuple specializations)]'>, <class 'Container[@Todo(Generic tuple specializations)]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(A.__mro__)
|
||||
```
|
||||
|
||||
@@ -114,6 +114,6 @@ from typing import Tuple
|
||||
|
||||
class C(Tuple): ...
|
||||
|
||||
# revealed: tuple[<class 'C'>, <class 'tuple[Unknown, ...]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol[_T_co], typing.Generic[_T_co], <class 'object'>]
|
||||
# revealed: tuple[<class 'C'>, <class 'tuple[Unknown, ...]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||
reveal_type(C.__mro__)
|
||||
```
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Type compendium
|
||||
|
||||
The type compendium contains "fact sheets" about important, interesting, and peculiar types in (ty's
|
||||
interpretation of) Python's type system. It is meant to be an educational reference for developers
|
||||
and users of ty. It is also a living document that ensures that our implementation of these types
|
||||
and their properties is consistent with the specification.
|
||||
|
||||
## Table of contents
|
||||
|
||||
- [`Never`](never.md)
|
||||
- [`Object`](object.md)
|
||||
- [`None`](none.md)
|
||||
- [Integer `Literal`s](integer_literals.md)
|
||||
- String `Literal`s, `LiteralString`
|
||||
- [`tuple` types](tuple.md)
|
||||
- Class instance types
|
||||
- [`Any`](any.md)
|
||||
- Class literal types, `type[C]`, `type[object]`, `type[Any]`
|
||||
- [`AlwaysTruthy`, `AlwaysFalsy`](always_truthy_falsy.md)
|
||||
- [`Not[T]`](not_t.md)
|
||||
@@ -0,0 +1,175 @@
|
||||
# `AlwaysTruthy` and `AlwaysFalsy`
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
The types `AlwaysTruthy` and `AlwaysFalsy` describe the set of values that are always truthy or
|
||||
always falsy, respectively. More concretely, a value `at` is of type `AlwaysTruthy` if we can
|
||||
statically infer that `bool(at)` is always `True`, i.e. that the expression `bool(at)` has type
|
||||
`Literal[True]`. Conversely, a value `af` is of type `AlwaysFalsy` if we can statically infer that
|
||||
`bool(af)` is always `False`, i.e. that `bool(af)` has type `Literal[False]`.
|
||||
|
||||
## Examples
|
||||
|
||||
Here, we give a few examples of values that belong to these types:
|
||||
|
||||
```py
|
||||
from ty_extensions import AlwaysTruthy, AlwaysFalsy
|
||||
from typing_extensions import Literal
|
||||
|
||||
class CustomAlwaysTruthyType:
|
||||
def __bool__(self) -> Literal[True]:
|
||||
return True
|
||||
|
||||
class CustomAlwaysFalsyType:
|
||||
def __bool__(self) -> Literal[False]:
|
||||
return False
|
||||
|
||||
at: AlwaysTruthy
|
||||
at = True
|
||||
at = 1
|
||||
at = 123
|
||||
at = -1
|
||||
at = "non empty"
|
||||
at = b"non empty"
|
||||
at = CustomAlwaysTruthyType()
|
||||
|
||||
af: AlwaysFalsy
|
||||
af = False
|
||||
af = None
|
||||
af = 0
|
||||
af = ""
|
||||
af = b""
|
||||
af = CustomAlwaysFalsyType()
|
||||
```
|
||||
|
||||
## `AlwaysTruthy` and `AlwaysFalsy` are disjoint
|
||||
|
||||
It follows directly from the definition that `AlwaysTruthy` and `AlwaysFalsy` are disjoint types:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_disjoint_from, AlwaysTruthy, AlwaysFalsy
|
||||
|
||||
static_assert(is_disjoint_from(AlwaysTruthy, AlwaysFalsy))
|
||||
```
|
||||
|
||||
## `Truthy` and `Falsy`
|
||||
|
||||
It is useful to also define the types `Truthy = ~AlwaysFalsy` and `Falsy = ~AlwaysTruthy`. These
|
||||
types describe the set of values that *can* be truthy (`bool(t)` can return `True`) or falsy
|
||||
(`bool(f)` can return `False`), respectively.
|
||||
|
||||
Finally, we can also define the type `AmbiguousTruthiness = Truthy & Falsy`, which describes the set
|
||||
of values that can be truthy *and* falsy. This intersection is not empty. In the following, we give
|
||||
examples for values that belong to these three types:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_equivalent_to, is_disjoint_from, Not, Intersection, AlwaysTruthy, AlwaysFalsy
|
||||
from typing_extensions import Never
|
||||
from random import choice
|
||||
|
||||
type Truthy = Not[AlwaysFalsy]
|
||||
type Falsy = Not[AlwaysTruthy]
|
||||
|
||||
type AmbiguousTruthiness = Intersection[Truthy, Falsy]
|
||||
|
||||
static_assert(is_disjoint_from(AlwaysTruthy, AmbiguousTruthiness))
|
||||
static_assert(is_disjoint_from(AlwaysFalsy, AmbiguousTruthiness))
|
||||
static_assert(not is_disjoint_from(Truthy, Falsy))
|
||||
|
||||
class CustomAmbiguousTruthinessType:
|
||||
def __bool__(self) -> bool:
|
||||
return choice((True, False))
|
||||
|
||||
def maybe_empty_list() -> list[int]:
|
||||
return choice(([], [1, 2, 3]))
|
||||
|
||||
reveal_type(bool(maybe_empty_list())) # revealed: bool
|
||||
reveal_type(bool(CustomAmbiguousTruthinessType())) # revealed: bool
|
||||
|
||||
t: Truthy
|
||||
t = True
|
||||
t = 1
|
||||
# TODO: This assignment should be okay
|
||||
t = maybe_empty_list() # error: [invalid-assignment]
|
||||
# TODO: This assignment should be okay
|
||||
t = CustomAmbiguousTruthinessType() # error: [invalid-assignment]
|
||||
|
||||
a: AmbiguousTruthiness
|
||||
# TODO: This assignment should be okay
|
||||
a = maybe_empty_list() # error: [invalid-assignment]
|
||||
# TODO: This assignment should be okay
|
||||
a = CustomAmbiguousTruthinessType() # error: [invalid-assignment]
|
||||
|
||||
f: Falsy
|
||||
f = False
|
||||
f = None
|
||||
# TODO: This assignment should be okay
|
||||
f = maybe_empty_list() # error: [invalid-assignment]
|
||||
# TODO: This assignment should be okay
|
||||
f = CustomAmbiguousTruthinessType() # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
## Subtypes of `AlwaysTruthy`, `AlwaysFalsy`
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_subtype_of, is_disjoint_from, AlwaysTruthy, AlwaysFalsy
|
||||
from typing_extensions import Literal
|
||||
```
|
||||
|
||||
These two types are disjoint, so types (that are not equivalent to Never) can only be a subtype of
|
||||
either one of them.
|
||||
|
||||
```py
|
||||
static_assert(is_disjoint_from(AlwaysTruthy, AlwaysFalsy))
|
||||
```
|
||||
|
||||
Types that only contain always-truthy values
|
||||
|
||||
```py
|
||||
static_assert(is_subtype_of(Literal[True], AlwaysTruthy))
|
||||
static_assert(is_subtype_of(Literal[1], AlwaysTruthy))
|
||||
static_assert(is_subtype_of(Literal[-1], AlwaysTruthy))
|
||||
static_assert(is_subtype_of(Literal["non empty"], AlwaysTruthy))
|
||||
static_assert(is_subtype_of(Literal[b"non empty"], AlwaysTruthy))
|
||||
```
|
||||
|
||||
Types that only contain always-falsy values
|
||||
|
||||
```py
|
||||
static_assert(is_subtype_of(None, AlwaysFalsy))
|
||||
static_assert(is_subtype_of(Literal[False], AlwaysFalsy))
|
||||
static_assert(is_subtype_of(Literal[0], AlwaysFalsy))
|
||||
static_assert(is_subtype_of(Literal[""], AlwaysFalsy))
|
||||
static_assert(is_subtype_of(Literal[b""], AlwaysFalsy))
|
||||
static_assert(is_subtype_of(Literal[False] | Literal[0], AlwaysFalsy))
|
||||
```
|
||||
|
||||
Ambiguous truthiness types
|
||||
|
||||
```py
|
||||
static_assert(not is_subtype_of(bool, AlwaysTruthy))
|
||||
static_assert(not is_subtype_of(bool, AlwaysFalsy))
|
||||
|
||||
static_assert(not is_subtype_of(list[int], AlwaysTruthy))
|
||||
static_assert(not is_subtype_of(list[int], AlwaysFalsy))
|
||||
```
|
||||
|
||||
## Open questions
|
||||
|
||||
Is `tuple[()]` always falsy? We currently model it this way, but this is
|
||||
[under discussion](https://github.com/astral-sh/ruff/issues/15528).
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_subtype_of, AlwaysFalsy
|
||||
|
||||
static_assert(is_subtype_of(tuple[()], AlwaysFalsy))
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
See also:
|
||||
|
||||
- Our test suite on [narrowing for `if x` and `if not x`](../narrow/truthiness.md).
|
||||
@@ -0,0 +1,141 @@
|
||||
# `Any`
|
||||
|
||||
## Introduction
|
||||
|
||||
The type `Any` is the dynamic type in Python's gradual type system. It represents an unknown
|
||||
fully-static type, which means that it represents an *unknown* set of runtime values.
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_fully_static
|
||||
from typing import Any
|
||||
```
|
||||
|
||||
`Any` is a dynamic type:
|
||||
|
||||
```py
|
||||
static_assert(not is_fully_static(Any))
|
||||
```
|
||||
|
||||
## Every type is assignable to `Any`, and `Any` is assignable to every type
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_fully_static, is_assignable_to
|
||||
from typing_extensions import Never, Any
|
||||
|
||||
class C: ...
|
||||
|
||||
static_assert(is_assignable_to(C, Any))
|
||||
static_assert(is_assignable_to(Any, C))
|
||||
|
||||
static_assert(is_assignable_to(object, Any))
|
||||
static_assert(is_assignable_to(Any, object))
|
||||
|
||||
static_assert(is_assignable_to(Never, Any))
|
||||
static_assert(is_assignable_to(Any, Never))
|
||||
|
||||
static_assert(is_assignable_to(type, Any))
|
||||
static_assert(is_assignable_to(Any, type))
|
||||
|
||||
static_assert(is_assignable_to(type[Any], Any))
|
||||
static_assert(is_assignable_to(Any, type[Any]))
|
||||
```
|
||||
|
||||
`Any` is also assignable to itself (like every type):
|
||||
|
||||
```py
|
||||
static_assert(is_assignable_to(Any, Any))
|
||||
```
|
||||
|
||||
## Unions with `Any`: `Any | T`
|
||||
|
||||
The union `Any | T` of `Any` with a fully static type `T` describes an unknown set of values that is
|
||||
*at least as large* as the set of values described by `T`. It represents an unknown fully-static
|
||||
type with *lower bound* `T`. Again, this can be demonstrated using the assignable-to relation:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_assignable_to, is_equivalent_to
|
||||
from typing_extensions import Any
|
||||
|
||||
# A class hierarchy Small <: Medium <: Big
|
||||
|
||||
class Big: ...
|
||||
class Medium(Big): ...
|
||||
class Small(Medium): ...
|
||||
|
||||
static_assert(is_assignable_to(Any | Medium, Big))
|
||||
static_assert(is_assignable_to(Any | Medium, Medium))
|
||||
|
||||
# `Any | Medium` is at least as large as `Medium`, so we can not assign it to `Small`:
|
||||
static_assert(not is_assignable_to(Any | Medium, Small))
|
||||
```
|
||||
|
||||
The union `Any | object` is equivalent to `object`. This is true for every union with `object`, but
|
||||
it is worth demonstrating:
|
||||
|
||||
```py
|
||||
static_assert(is_equivalent_to(Any | object, object))
|
||||
static_assert(is_equivalent_to(object | Any, object))
|
||||
```
|
||||
|
||||
## Intersections with `Any`: `Any & T`
|
||||
|
||||
The intersection `Any & T` of `Any` with a fully static type `T` describes an unknown set of values
|
||||
that is *no larger than* the set of values described by `T`. It represents an unknown fully-static
|
||||
type with *upper bound* `T`:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_assignable_to, Intersection, is_equivalent_to
|
||||
from typing import Any
|
||||
|
||||
class Big: ...
|
||||
class Medium(Big): ...
|
||||
class Small(Medium): ...
|
||||
|
||||
static_assert(is_assignable_to(Small, Intersection[Any, Medium]))
|
||||
static_assert(is_assignable_to(Medium, Intersection[Any, Medium]))
|
||||
```
|
||||
|
||||
`Any & Medium` is no larger than `Medium`, so we can not assign `Big` to it. There is no possible
|
||||
materialization of `Any & Medium` that would make it as big as `Big`:
|
||||
|
||||
```py
|
||||
static_assert(not is_assignable_to(Big, Intersection[Any, Medium]))
|
||||
```
|
||||
|
||||
`Any & Never` represents an "unknown" fully-static type which is no larger than `Never`. There is no
|
||||
such fully-static type, except for `Never` itself. So `Any & Never` is equivalent to `Never`:
|
||||
|
||||
```py
|
||||
from typing_extensions import Never
|
||||
|
||||
static_assert(is_equivalent_to(Intersection[Any, Never], Never))
|
||||
static_assert(is_equivalent_to(Intersection[Never, Any], Never))
|
||||
```
|
||||
|
||||
## Tuples with `Any`
|
||||
|
||||
This section demonstrates the following passage from the [type system concepts] documentation on
|
||||
gradual types:
|
||||
|
||||
> A type such as `tuple[int, Any]` […] does not represent a single set of Python objects; rather, it
|
||||
> represents a (bounded) range of possible sets of values. […] In the same way that `Any` does not
|
||||
> represent "the set of all Python objects" but rather "an unknown set of objects",
|
||||
> `tuple[int, Any]` does not represent "the set of all length-two tuples whose first element is an
|
||||
> integer". That is a fully static type, spelled `tuple[int, object]`. By contrast,
|
||||
> `tuple[int, Any]` represents some unknown set of tuple values; it might be the set of all tuples
|
||||
> of two integers, or the set of all tuples of an integer and a string, or some other set of tuple
|
||||
> values.
|
||||
>
|
||||
> In practice, this difference is seen (for example) in the fact that we can assign an expression of
|
||||
> type `tuple[int, Any]` to a target typed as `tuple[int, int]`, whereas assigning
|
||||
> `tuple[int, object]` to `tuple[int, int]` is a static type error.
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_assignable_to
|
||||
from typing import Any
|
||||
|
||||
static_assert(is_assignable_to(tuple[int, Any], tuple[int, int]))
|
||||
static_assert(not is_assignable_to(tuple[int, object], tuple[int, int]))
|
||||
```
|
||||
|
||||
[type system concepts]: https://typing.readthedocs.io/en/latest/spec/concepts.html#gradual-types
|
||||
@@ -0,0 +1,234 @@
|
||||
# Integer `Literal`s
|
||||
|
||||
An integer literal type represents the set of all integer objects with one specific value. For
|
||||
example, the type `Literal[54165]` represents the set of all integer objects with the value `54165`.
|
||||
|
||||
## Integer `Literal`s are not singleton types
|
||||
|
||||
This does not necessarily mean that the type is a singleton type, i.e., a type with only one
|
||||
inhabitant. The reason for this is that there might be multiple Python runtime objects (at different
|
||||
memory locations) that all represent the same integer value. For example, the following code snippet
|
||||
may print `False`.
|
||||
|
||||
```py
|
||||
x = 54165
|
||||
y = 54165
|
||||
|
||||
print(x is y)
|
||||
```
|
||||
|
||||
In practice, on CPython 3.13.0, this program prints `True` when executed as a script, but `False`
|
||||
when executed in the REPL.
|
||||
|
||||
Since this is an implementation detail of the Python runtime, we model all integer literals as
|
||||
non-singleton types:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_singleton
|
||||
from typing import Literal
|
||||
|
||||
static_assert(not is_singleton(Literal[0]))
|
||||
static_assert(not is_singleton(Literal[1]))
|
||||
static_assert(not is_singleton(Literal[54165]))
|
||||
```
|
||||
|
||||
This has implications for type-narrowing. For example, you can not use the `is not` operator to
|
||||
check whether a variable has a specific integer literal type, but this is not a recommended practice
|
||||
anyway.
|
||||
|
||||
```py
|
||||
def f(x: int):
|
||||
if x is 54165:
|
||||
# This works, because if `x` is the same object as that left-hand-side literal, then it
|
||||
# must have the same value.
|
||||
reveal_type(x) # revealed: Literal[54165]
|
||||
|
||||
if x is not 54165:
|
||||
# But here, we can not narrow the type (to `int & ~Literal[54165]`), because `x` might also
|
||||
# have the value `54165`, but a different object identity.
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
## Integer `Literal`s are single-valued types
|
||||
|
||||
There is a slightly weaker property that integer literals have. They are single-valued types, which
|
||||
means that all objects of the type have the same value, i.e. they compare equal to each other:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_single_valued
|
||||
from typing import Literal
|
||||
|
||||
static_assert(is_single_valued(Literal[0]))
|
||||
static_assert(is_single_valued(Literal[1]))
|
||||
static_assert(is_single_valued(Literal[54165]))
|
||||
```
|
||||
|
||||
And this can be used for type-narrowing using not-equal comparisons:
|
||||
|
||||
```py
|
||||
def f(x: int):
|
||||
if x == 54165:
|
||||
# The reason that no narrowing occurs here is that there might be subclasses of `int`
|
||||
# that override `__eq__`. This is not specific to integer literals though, and generally
|
||||
# applies to `==` comparisons.
|
||||
reveal_type(x) # revealed: int
|
||||
|
||||
if x != 54165:
|
||||
reveal_type(x) # revealed: int & ~Literal[54165]
|
||||
```
|
||||
|
||||
## Subtyping relationships
|
||||
|
||||
### Subtypes of `int`
|
||||
|
||||
All integer literals are subtypes of `int`:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_subtype_of
|
||||
from typing import Literal
|
||||
|
||||
static_assert(is_subtype_of(Literal[0], int))
|
||||
static_assert(is_subtype_of(Literal[1], int))
|
||||
static_assert(is_subtype_of(Literal[54165], int))
|
||||
```
|
||||
|
||||
It is tempting to think that `int` is equivalent to the union of all integer literals,
|
||||
`… | Literal[-1] | Literal[0] | Literal[1] | …`, but this is not the case. `True` and `False` are
|
||||
also inhabitants of the `int` type, but they are not inhabitants of any integer literal type:
|
||||
|
||||
```py
|
||||
static_assert(is_subtype_of(Literal[True], int))
|
||||
static_assert(is_subtype_of(Literal[False], int))
|
||||
|
||||
static_assert(not is_subtype_of(Literal[True], Literal[1]))
|
||||
static_assert(not is_subtype_of(Literal[False], Literal[0]))
|
||||
```
|
||||
|
||||
Also, `int` can be subclassed, and instances of that subclass are also subtypes of `int`:
|
||||
|
||||
```py
|
||||
class CustomInt(int):
|
||||
pass
|
||||
|
||||
static_assert(is_subtype_of(CustomInt, int))
|
||||
```
|
||||
|
||||
### No subtypes of `float` and `complex`
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
Integer literals are _not_ subtypes of `float`, but the typing spec describes a special case for
|
||||
[`float` and `complex`] which accepts integers (and therefore also integer literals) in places where
|
||||
a `float` or `complex` is expected. We use the types `JustFloat` and `JustComplex` below, because ty
|
||||
recognizes an annotation of `float` as `int | float` to support that typing system special case.
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_subtype_of, JustFloat, JustComplex
|
||||
from typing import Literal
|
||||
|
||||
# Not subtypes of `float` and `complex`
|
||||
static_assert(not is_subtype_of(Literal[0], JustFloat) and not is_subtype_of(Literal[0], JustComplex))
|
||||
static_assert(not is_subtype_of(Literal[1], JustFloat) and not is_subtype_of(Literal[1], JustComplex))
|
||||
static_assert(not is_subtype_of(Literal[54165], JustFloat) and not is_subtype_of(Literal[54165], JustComplex))
|
||||
```
|
||||
|
||||
The typing system special case can be seen in the following example:
|
||||
|
||||
```py
|
||||
a: JustFloat = 1 # error: [invalid-assignment]
|
||||
b: JustComplex = 1 # error: [invalid-assignment]
|
||||
|
||||
x: float = 1
|
||||
y: complex = 1
|
||||
```
|
||||
|
||||
### Subtypes of integer `Literal`s?
|
||||
|
||||
The only subtypes of an integer literal type _that can be named_ are the type itself and `Never`:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_subtype_of
|
||||
from typing_extensions import Never, Literal
|
||||
|
||||
static_assert(is_subtype_of(Literal[54165], Literal[54165]))
|
||||
static_assert(is_subtype_of(Never, Literal[54165]))
|
||||
```
|
||||
|
||||
## Disjointness of integer `Literal`s
|
||||
|
||||
Two integer literal types `Literal[a]` and `Literal[b]` are disjoint if `a != b`:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_disjoint_from
|
||||
from typing import Literal
|
||||
|
||||
static_assert(is_disjoint_from(Literal[0], Literal[1]))
|
||||
static_assert(is_disjoint_from(Literal[0], Literal[54165]))
|
||||
|
||||
static_assert(not is_disjoint_from(Literal[0], Literal[0]))
|
||||
static_assert(not is_disjoint_from(Literal[54165], Literal[54165]))
|
||||
```
|
||||
|
||||
## Integer literal math
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
We support a whole range of arithmetic operations on integer literal types. For example, we can
|
||||
statically verify that (3, 4, 5) is a Pythagorean triple:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert
|
||||
|
||||
static_assert(3**2 + 4**2 == 5**2)
|
||||
```
|
||||
|
||||
Using unions of integer literals, we can even use this to solve equations over a finite domain
|
||||
(determine whether there is a solution or not):
|
||||
|
||||
```py
|
||||
from typing import Literal, assert_type
|
||||
|
||||
type Nat = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
|
||||
def pythagorean_triples(a: Nat, b: Nat, c: Nat):
|
||||
# Answer is `bool`, because solutions do exist (3² + 4² = 5²)
|
||||
assert_type(a**2 + b**2 == c**2, bool)
|
||||
|
||||
def fermats_last_theorem(a: Nat, b: Nat, c: Nat):
|
||||
# Answer is `Literal[False]`, because no solutions exist
|
||||
assert_type(a**3 + b**3 == c**3, Literal[False])
|
||||
```
|
||||
|
||||
## Truthiness
|
||||
|
||||
Integer literals are always-truthy, except for `0`, which is always-falsy:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert
|
||||
|
||||
static_assert(-54165)
|
||||
static_assert(-1)
|
||||
static_assert(not 0)
|
||||
static_assert(1)
|
||||
static_assert(54165)
|
||||
```
|
||||
|
||||
This can be used for type-narrowing:
|
||||
|
||||
```py
|
||||
from typing_extensions import Literal, assert_type
|
||||
|
||||
def f(x: Literal[0, 1, 54365]):
|
||||
if x:
|
||||
assert_type(x, Literal[1, 54365])
|
||||
else:
|
||||
assert_type(x, Literal[0])
|
||||
```
|
||||
|
||||
[`float` and `complex`]: https://typing.readthedocs.io/en/latest/spec/special-types.html#special-cases-for-float-and-complex
|
||||
@@ -0,0 +1,185 @@
|
||||
# `Never`
|
||||
|
||||
`Never` represents the empty set of values.
|
||||
|
||||
## `Never` is a subtype of every type
|
||||
|
||||
The `Never` type is the bottom type of Python's type system. It is a subtype of every type, but no
|
||||
type is a subtype of `Never`, except for `Never` itself.
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_subtype_of
|
||||
from typing_extensions import Never
|
||||
|
||||
class C: ...
|
||||
|
||||
static_assert(is_subtype_of(Never, int))
|
||||
static_assert(is_subtype_of(Never, object))
|
||||
static_assert(is_subtype_of(Never, C))
|
||||
static_assert(is_subtype_of(Never, Never))
|
||||
|
||||
static_assert(not is_subtype_of(int, Never))
|
||||
```
|
||||
|
||||
## `Never` is assignable to every type
|
||||
|
||||
`Never` is assignable to every type. This fact is useful when calling error-handling functions in a
|
||||
context that requires a value of a specific type. For example, changing the `Never` return type to
|
||||
`None` below would cause a type error:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_assignable_to
|
||||
from typing_extensions import Never, Any
|
||||
|
||||
static_assert(is_assignable_to(Never, int))
|
||||
static_assert(is_assignable_to(Never, object))
|
||||
static_assert(is_assignable_to(Never, Any))
|
||||
static_assert(is_assignable_to(Never, Never))
|
||||
|
||||
def raise_error() -> Never:
|
||||
raise Exception("...")
|
||||
|
||||
def f(divisor: int) -> None:
|
||||
x: float = (1 / divisor) if divisor != 0 else raise_error()
|
||||
```
|
||||
|
||||
## `Never` in annotations
|
||||
|
||||
`Never` can be used in functions to indicate that the function never returns. For example, if a
|
||||
function always raises an exception, if it calls `sys.exit()`, if it enters an infinite loop, or if
|
||||
it calls itself recursively. All of these functions "Never" return control back to the caller:
|
||||
|
||||
```py
|
||||
from typing_extensions import Never
|
||||
|
||||
def raises_unconditionally() -> Never:
|
||||
raise Exception("This function always raises an exception")
|
||||
|
||||
def exits_unconditionally() -> Never:
|
||||
import sys
|
||||
|
||||
return sys.exit(1)
|
||||
|
||||
def loops_forever() -> Never:
|
||||
while True:
|
||||
pass
|
||||
|
||||
def recursive_never() -> Never:
|
||||
return recursive_never()
|
||||
```
|
||||
|
||||
Similarly, if `Never` is used in parameter positions, it indicates that the function can "Never" be
|
||||
called, because it can never be passed a value of type `Never` (there are none):
|
||||
|
||||
```py
|
||||
def can_not_be_called(n: Never) -> int:
|
||||
return 0
|
||||
```
|
||||
|
||||
## `Never` is disjoint from every other type
|
||||
|
||||
Two types `A` and `B` are disjoint if their intersection is empty. Since `Never` has no inhabitants,
|
||||
it is disjoint from every other type:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_disjoint_from
|
||||
from typing_extensions import Never
|
||||
|
||||
class C: ...
|
||||
|
||||
static_assert(is_disjoint_from(Never, int))
|
||||
static_assert(is_disjoint_from(Never, object))
|
||||
static_assert(is_disjoint_from(Never, C))
|
||||
static_assert(is_disjoint_from(Never, Never))
|
||||
```
|
||||
|
||||
## Unions with `Never`
|
||||
|
||||
`Never` can always be removed from unions:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_equivalent_to
|
||||
from typing_extensions import Never
|
||||
|
||||
class P: ...
|
||||
class Q: ...
|
||||
|
||||
static_assert(is_equivalent_to(P | Never | Q | None, P | Q | None))
|
||||
```
|
||||
|
||||
## Intersections with `Never`
|
||||
|
||||
Intersecting with `Never` results in `Never`:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_equivalent_to, Intersection
|
||||
from typing_extensions import Never
|
||||
|
||||
class P: ...
|
||||
class Q: ...
|
||||
|
||||
static_assert(is_equivalent_to(Intersection[P, Never, Q], Never))
|
||||
```
|
||||
|
||||
## `Never` is the complement of `object`
|
||||
|
||||
`object` describes the set of all possible values, while `Never` describes the empty set. The two
|
||||
types are complements of each other:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_equivalent_to, Not
|
||||
from typing_extensions import Never
|
||||
|
||||
static_assert(is_equivalent_to(Not[object], Never))
|
||||
static_assert(is_equivalent_to(Not[Never], object))
|
||||
```
|
||||
|
||||
This duality is also reflected in other facts:
|
||||
|
||||
- `Never` is a subtype of every type, while `object` is a supertype of every type.
|
||||
- `Never` is assignable to every type, while `object` is assignable from every type.
|
||||
- `Never` is disjoint from every type, while `object` overlaps with every type.
|
||||
- Building a union with `Never` is a no-op, intersecting with `object` is a no-op.
|
||||
- Interecting with `Never` results in `Never`, building a union with `object` results in `object`.
|
||||
|
||||
## Lists of `Never`
|
||||
|
||||
`list[Never]` is a reasonable type that is *not* equivalent to `Never`. The empty list inhabits this
|
||||
type:
|
||||
|
||||
```py
|
||||
from typing_extensions import Never
|
||||
|
||||
x: list[Never] = []
|
||||
```
|
||||
|
||||
## Tuples involving `Never`
|
||||
|
||||
A type like `tuple[int, Never]` has no inhabitants, and so it is equivalent to `Never`:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_equivalent_to
|
||||
from typing_extensions import Never
|
||||
|
||||
static_assert(is_equivalent_to(tuple[int, Never], Never))
|
||||
```
|
||||
|
||||
Note that this is not the case for the homogenous tuple type `tuple[Never, ...]` though, because
|
||||
that type is inhabited by the empty tuple:
|
||||
|
||||
```py
|
||||
static_assert(not is_equivalent_to(tuple[Never, ...], Never))
|
||||
|
||||
t: tuple[Never, ...] = ()
|
||||
```
|
||||
|
||||
## `NoReturn` is the same as `Never`
|
||||
|
||||
The `NoReturn` type is a different name for `Never`:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_equivalent_to
|
||||
from typing_extensions import NoReturn, Never
|
||||
|
||||
static_assert(is_equivalent_to(NoReturn, Never))
|
||||
```
|
||||
@@ -0,0 +1,80 @@
|
||||
# `None`
|
||||
|
||||
## `None` as a singleton type
|
||||
|
||||
The type `None` (or `NoneType`, see below) is a singleton type that has only one inhabitant: the
|
||||
object `None`.
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_singleton, is_equivalent_to
|
||||
|
||||
n: None = None
|
||||
|
||||
static_assert(is_singleton(None))
|
||||
```
|
||||
|
||||
Just like for other singleton types, the only subtypes of `None` are `None` itself and `Never`:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_subtype_of
|
||||
from typing_extensions import Never
|
||||
|
||||
static_assert(is_subtype_of(None, None))
|
||||
static_assert(is_subtype_of(Never, None))
|
||||
```
|
||||
|
||||
## Relationship to `Optional[T]`
|
||||
|
||||
The type `Optional[T]` is an alias for `T | None` (or `Union[T, None]`):
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_equivalent_to
|
||||
from typing import Optional, Union
|
||||
|
||||
class T: ...
|
||||
|
||||
static_assert(is_equivalent_to(Optional[T], T | None))
|
||||
static_assert(is_equivalent_to(Optional[T], Union[T, None]))
|
||||
```
|
||||
|
||||
## Type narrowing using `is`
|
||||
|
||||
Just like for other singleton types, we support type narrowing using `is` or `is not` checks:
|
||||
|
||||
```py
|
||||
from typing_extensions import assert_type
|
||||
|
||||
class T: ...
|
||||
|
||||
def f(x: T | None):
|
||||
if x is None:
|
||||
assert_type(x, None)
|
||||
else:
|
||||
assert_type(x, T)
|
||||
|
||||
assert_type(x, T | None)
|
||||
|
||||
if x is not None:
|
||||
assert_type(x, T)
|
||||
else:
|
||||
assert_type(x, None)
|
||||
```
|
||||
|
||||
## `NoneType`
|
||||
|
||||
`None` is special in that the name of the instance at runtime can be used as a type as well: The
|
||||
object `None` is an instance of type `None`. When a distinction between the two is needed, the
|
||||
spelling `NoneType` can be used, which is available since Python 3.10. `NoneType` is equivalent to
|
||||
`None`:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_equivalent_to
|
||||
from types import NoneType
|
||||
|
||||
static_assert(is_equivalent_to(NoneType, None))
|
||||
```
|
||||
@@ -0,0 +1,120 @@
|
||||
# `Not[T]`
|
||||
|
||||
The type `Not[T]` is the complement of the type `T`. It describes the set of all values that are
|
||||
*not* in `T`.
|
||||
|
||||
## `Not[T]` is disjoint from `T`
|
||||
|
||||
`Not[T]` is disjoint from `T`:
|
||||
|
||||
```py
|
||||
from ty_extensions import Not, static_assert, is_disjoint_from
|
||||
|
||||
class T: ...
|
||||
class S(T): ...
|
||||
|
||||
static_assert(is_disjoint_from(Not[T], T))
|
||||
static_assert(is_disjoint_from(Not[T], S))
|
||||
```
|
||||
|
||||
## The union of `T` and `Not[T]` is equivalent to `object`
|
||||
|
||||
Together, `T` and `Not[T]` describe the set of all values. So the union of both types is equivalent
|
||||
to `object`:
|
||||
|
||||
```py
|
||||
from ty_extensions import Not, static_assert, is_equivalent_to
|
||||
|
||||
class T: ...
|
||||
|
||||
static_assert(is_equivalent_to(T | Not[T], object))
|
||||
```
|
||||
|
||||
## `Not[T]` reverses subtyping relationships
|
||||
|
||||
If `S <: T`, then `Not[T] <: Not[S]`:, similar to how negation in logic reverses the order of `<=`:
|
||||
|
||||
```py
|
||||
from ty_extensions import Not, static_assert, is_subtype_of
|
||||
|
||||
class T: ...
|
||||
class S(T): ...
|
||||
|
||||
static_assert(is_subtype_of(S, T))
|
||||
static_assert(is_subtype_of(Not[T], Not[S]))
|
||||
```
|
||||
|
||||
## `Not[T]` reverses assignability relationships
|
||||
|
||||
Assignability relationships are similarly reversed:
|
||||
|
||||
```py
|
||||
from ty_extensions import Not, Intersection, static_assert, is_assignable_to
|
||||
from typing import Any
|
||||
|
||||
class T: ...
|
||||
class S(T): ...
|
||||
|
||||
static_assert(is_assignable_to(S, T))
|
||||
static_assert(is_assignable_to(Not[T], Not[S]))
|
||||
|
||||
static_assert(is_assignable_to(Intersection[Any, S], Intersection[Any, T]))
|
||||
|
||||
static_assert(is_assignable_to(Not[Intersection[Any, S]], Not[Intersection[Any, T]]))
|
||||
```
|
||||
|
||||
## Subtyping and disjointness
|
||||
|
||||
If two types `P` and `Q` are disjoint, then `P` must be a subtype of `Not[Q]`, and vice versa:
|
||||
|
||||
```py
|
||||
from ty_extensions import Not, static_assert, is_subtype_of, is_disjoint_from
|
||||
from typing import final
|
||||
|
||||
@final
|
||||
class P: ...
|
||||
|
||||
@final
|
||||
class Q: ...
|
||||
|
||||
static_assert(is_disjoint_from(P, Q))
|
||||
|
||||
static_assert(is_subtype_of(P, Not[Q]))
|
||||
static_assert(is_subtype_of(Q, Not[P]))
|
||||
```
|
||||
|
||||
## De-Morgan's laws
|
||||
|
||||
Given two unrelated types `P` and `Q`, we can demonstrate De-Morgan's laws in the context of
|
||||
set-theoretic types:
|
||||
|
||||
```py
|
||||
from ty_extensions import Not, static_assert, is_equivalent_to, Intersection
|
||||
|
||||
class P: ...
|
||||
class Q: ...
|
||||
```
|
||||
|
||||
The negation of a union is the intersection of the negations:
|
||||
|
||||
```py
|
||||
static_assert(is_equivalent_to(Not[P | Q], Intersection[Not[P], Not[Q]]))
|
||||
```
|
||||
|
||||
Conversely, the negation of an intersection is the union of the negations:
|
||||
|
||||
```py
|
||||
static_assert(is_equivalent_to(Not[Intersection[P, Q]], Not[P] | Not[Q]))
|
||||
```
|
||||
|
||||
## Negation of gradual types
|
||||
|
||||
`Any` represents an unknown set of values. So `Not[Any]` also represents an unknown set of values.
|
||||
The two gradual types are equivalent:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_gradual_equivalent_to, Not
|
||||
from typing import Any
|
||||
|
||||
static_assert(is_gradual_equivalent_to(Not[Any], Any))
|
||||
```
|
||||
@@ -0,0 +1,78 @@
|
||||
# `object`
|
||||
|
||||
The `object` type represents the set of all Python objects.
|
||||
|
||||
## `object` is a supertype of all types
|
||||
|
||||
It is the top type in Python's type system, i.e., it is a supertype of all other types:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_subtype_of
|
||||
|
||||
static_assert(is_subtype_of(int, object))
|
||||
static_assert(is_subtype_of(str, object))
|
||||
static_assert(is_subtype_of(type, object))
|
||||
static_assert(is_subtype_of(object, object))
|
||||
```
|
||||
|
||||
## Every type is assignable to `object`
|
||||
|
||||
Everything can be assigned to the type `object`. This fact can be used to create heterogeneous
|
||||
collections of objects (but also erases more specific type information):
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_assignable_to
|
||||
from typing_extensions import Any, Never
|
||||
|
||||
static_assert(is_assignable_to(int, object))
|
||||
static_assert(is_assignable_to(str | bytes, object))
|
||||
static_assert(is_assignable_to(type, object))
|
||||
static_assert(is_assignable_to(object, object))
|
||||
static_assert(is_assignable_to(Never, object))
|
||||
static_assert(is_assignable_to(Any, object))
|
||||
|
||||
x: list[object] = [1, "a", ()]
|
||||
```
|
||||
|
||||
## `object` overlaps with all types
|
||||
|
||||
There is no type that is disjoint from `object` except for `Never`:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_disjoint_from
|
||||
from typing_extensions import Any, Never
|
||||
|
||||
static_assert(not is_disjoint_from(int, object))
|
||||
static_assert(not is_disjoint_from(str, object))
|
||||
static_assert(not is_disjoint_from(type, object))
|
||||
static_assert(not is_disjoint_from(object, object))
|
||||
static_assert(not is_disjoint_from(Any, object))
|
||||
static_assert(is_disjoint_from(Never, object))
|
||||
```
|
||||
|
||||
## Unions with `object`
|
||||
|
||||
Unions with `object` are equivalent to `object`:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_equivalent_to
|
||||
|
||||
static_assert(is_equivalent_to(int | object | None, object))
|
||||
```
|
||||
|
||||
## Intersections with `object`
|
||||
|
||||
Intersecting with `object` is equivalent to the original type:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_equivalent_to, Intersection
|
||||
|
||||
class P: ...
|
||||
class Q: ...
|
||||
|
||||
static_assert(is_equivalent_to(Intersection[P, object, Q], Intersection[P, Q]))
|
||||
```
|
||||
|
||||
## `object` is the complement of `Never`
|
||||
|
||||
See corresponding section in the fact sheet for [`Never`](never.md).
|
||||
@@ -0,0 +1,166 @@
|
||||
# Tuples
|
||||
|
||||
## Tuples as product types
|
||||
|
||||
Tuples can be used to construct product types. Inhabitants of the type `tuple[P, Q]` are ordered
|
||||
pairs `(p, q)` where `p` is an inhabitant of `P` and `q` is an inhabitant of `Q`, analogous to the
|
||||
Cartesian product of sets.
|
||||
|
||||
```py
|
||||
from typing_extensions import assert_type
|
||||
|
||||
class P: ...
|
||||
class Q: ...
|
||||
|
||||
def _(p: P, q: Q):
|
||||
assert_type((p, q), tuple[P, Q])
|
||||
```
|
||||
|
||||
## Subtyping relationships
|
||||
|
||||
The type `tuple[S1, S2]` is a subtype of `tuple[T1, T2]` if and only if `S1` is a subtype of `T1`
|
||||
and `S2` is a subtype of `T2`, and similar for other lengths of tuples:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_subtype_of
|
||||
|
||||
class T1: ...
|
||||
class S1(T1): ...
|
||||
class T2: ...
|
||||
class S2(T2): ...
|
||||
|
||||
static_assert(is_subtype_of(tuple[S1], tuple[T1]))
|
||||
static_assert(not is_subtype_of(tuple[T1], tuple[S1]))
|
||||
|
||||
static_assert(is_subtype_of(tuple[S1, S2], tuple[T1, T2]))
|
||||
static_assert(not is_subtype_of(tuple[T1, S2], tuple[S1, T2]))
|
||||
static_assert(not is_subtype_of(tuple[S1, T2], tuple[T1, S2]))
|
||||
```
|
||||
|
||||
Different-length tuples are not related via subtyping:
|
||||
|
||||
```py
|
||||
static_assert(not is_subtype_of(tuple[S1], tuple[T1, T2]))
|
||||
```
|
||||
|
||||
## The empty tuple
|
||||
|
||||
The type of the empty tuple `()` is spelled `tuple[()]`. It is [not a singleton type], because
|
||||
different instances of `()` are not guaranteed to be the same object (even if this is the case in
|
||||
CPython at the time of writing).
|
||||
|
||||
The empty tuple can also be subclassed (further clarifying that it is not a singleton type):
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_singleton, is_subtype_of, is_equivalent_to, is_assignable_to
|
||||
|
||||
static_assert(not is_singleton(tuple[()]))
|
||||
|
||||
class AnotherEmptyTuple(tuple[()]): ...
|
||||
|
||||
static_assert(not is_equivalent_to(AnotherEmptyTuple, tuple[()]))
|
||||
|
||||
# TODO: These should not be errors
|
||||
# error: [static-assert-error]
|
||||
static_assert(is_subtype_of(AnotherEmptyTuple, tuple[()]))
|
||||
# error: [static-assert-error]
|
||||
static_assert(is_assignable_to(AnotherEmptyTuple, tuple[()]))
|
||||
```
|
||||
|
||||
## Non-empty tuples
|
||||
|
||||
For the same reason as above (two instances of a tuple with the same elements might not be the same
|
||||
object), non-empty tuples are also not singleton types — even if all their elements are singletons:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_singleton
|
||||
|
||||
static_assert(is_singleton(None))
|
||||
|
||||
static_assert(not is_singleton(tuple[None]))
|
||||
```
|
||||
|
||||
## Disjointness
|
||||
|
||||
A tuple `tuple[P1, P2]` is disjoint from a tuple `tuple[Q1, Q2]` if either `P1` is disjoint from
|
||||
`Q1` or if `P2` is disjoint from `Q2`:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, is_disjoint_from
|
||||
from typing import final
|
||||
|
||||
@final
|
||||
class F1: ...
|
||||
|
||||
@final
|
||||
class F2: ...
|
||||
|
||||
class N1: ...
|
||||
class N2: ...
|
||||
|
||||
static_assert(is_disjoint_from(F1, F2))
|
||||
static_assert(not is_disjoint_from(N1, N2))
|
||||
|
||||
static_assert(is_disjoint_from(tuple[F1, F2], tuple[F2, F1]))
|
||||
static_assert(is_disjoint_from(tuple[F1, N1], tuple[F2, N2]))
|
||||
static_assert(is_disjoint_from(tuple[N1, F1], tuple[N2, F2]))
|
||||
static_assert(not is_disjoint_from(tuple[N1, N2], tuple[N2, N1]))
|
||||
```
|
||||
|
||||
We currently model tuple types to *not* be disjoint from arbitrary instance types, because we allow
|
||||
for the possibility of `tuple` to be subclassed
|
||||
|
||||
```py
|
||||
class C: ...
|
||||
|
||||
static_assert(not is_disjoint_from(tuple[int, str], C))
|
||||
|
||||
class CommonSubtype(tuple[int, str], C): ...
|
||||
```
|
||||
|
||||
Note: This is inconsistent with the fact that we model heterogeneous tuples to be disjoint from
|
||||
other heterogeneous tuples above:
|
||||
|
||||
```py
|
||||
class I1(tuple[F1, F2]): ...
|
||||
class I2(tuple[F2, F1]): ...
|
||||
|
||||
# TODO
|
||||
# This is a subtype of both `tuple[F1, F2]` and `tuple[F2, F1]`, so those two heterogeneous tuples
|
||||
# should not be disjoint from each other (see conflicting test above).
|
||||
class CommonSubtypeOfTuples(I1, I2): ...
|
||||
```
|
||||
|
||||
## Truthiness
|
||||
|
||||
The truthiness of the empty tuple is `False`:
|
||||
|
||||
```py
|
||||
from typing_extensions import assert_type, Literal
|
||||
|
||||
assert_type(bool(()), Literal[False])
|
||||
```
|
||||
|
||||
The truthiness of non-empty tuples is always `True`, even if all elements are falsy:
|
||||
|
||||
```py
|
||||
from typing_extensions import assert_type, Literal
|
||||
|
||||
assert_type(bool((False,)), Literal[True])
|
||||
assert_type(bool((False, False)), Literal[True])
|
||||
```
|
||||
|
||||
Both of these results are conflicting with the fact that tuples can be subclassed, and that we
|
||||
currently allow subclasses of `tuple` to overwrite `__bool__` (or `__len__`):
|
||||
|
||||
```py
|
||||
class NotAlwaysTruthyTuple(tuple[int]):
|
||||
def __bool__(self) -> bool:
|
||||
return False
|
||||
|
||||
# TODO: This assignment should be allowed
|
||||
# error: [invalid-assignment]
|
||||
t: tuple[int] = NotAlwaysTruthyTuple((1,))
|
||||
```
|
||||
|
||||
[not a singleton type]: https://discuss.python.org/t/should-we-specify-in-the-language-reference-that-the-empty-tuple-is-a-singleton/67957
|
||||
@@ -672,6 +672,29 @@ def f(x: int, y: str) -> None: ...
|
||||
c1: Callable[[int], None] = partial(f, y="a")
|
||||
```
|
||||
|
||||
### Classes with `__call__` as attribute
|
||||
|
||||
An instance type is assignable to a compatible callable type if the instance type's class has a
|
||||
callable `__call__` attribute.
|
||||
|
||||
TODO: for the moment, we don't consider the callable type as a bound-method descriptor, but this may
|
||||
change for better compatibility with mypy/pyright.
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
from ty_extensions import static_assert, is_assignable_to
|
||||
|
||||
def call_impl(a: int) -> str:
|
||||
return ""
|
||||
|
||||
class A:
|
||||
__call__: Callable[[int], str] = call_impl
|
||||
|
||||
static_assert(is_assignable_to(A, Callable[[int], str]))
|
||||
static_assert(not is_assignable_to(A, Callable[[int], int]))
|
||||
reveal_type(A()(1)) # revealed: str
|
||||
```
|
||||
|
||||
## Generics
|
||||
|
||||
### Assignability of generic types parameterized by gradual types
|
||||
|
||||
@@ -21,10 +21,7 @@ See the [typing documentation] for more information.
|
||||
as `int | float` and `int | float | complex`, respectively.
|
||||
|
||||
```py
|
||||
from ty_extensions import is_subtype_of, static_assert, TypeOf
|
||||
|
||||
type JustFloat = TypeOf[1.0]
|
||||
type JustComplex = TypeOf[1j]
|
||||
from ty_extensions import is_subtype_of, static_assert, JustFloat, JustComplex
|
||||
|
||||
static_assert(is_subtype_of(bool, bool))
|
||||
static_assert(is_subtype_of(bool, int))
|
||||
@@ -88,9 +85,7 @@ static_assert(is_subtype_of(C, object))
|
||||
|
||||
```py
|
||||
from typing_extensions import Literal, LiteralString
|
||||
from ty_extensions import is_subtype_of, static_assert, TypeOf
|
||||
|
||||
type JustFloat = TypeOf[1.0]
|
||||
from ty_extensions import is_subtype_of, static_assert, TypeOf, JustFloat
|
||||
|
||||
# Boolean literals
|
||||
static_assert(is_subtype_of(Literal[True], bool))
|
||||
@@ -1157,6 +1152,29 @@ def f(fn: Callable[[int], int]) -> None: ...
|
||||
f(a)
|
||||
```
|
||||
|
||||
### Classes with `__call__` as attribute
|
||||
|
||||
An instance type can be a subtype of a compatible callable type if the instance type's class has a
|
||||
callable `__call__` attribute.
|
||||
|
||||
TODO: for the moment, we don't consider the callable type as a bound-method descriptor, but this may
|
||||
change for better compatibility with mypy/pyright.
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
from ty_extensions import static_assert, is_subtype_of
|
||||
|
||||
def call_impl(a: int) -> str:
|
||||
return ""
|
||||
|
||||
class A:
|
||||
__call__: Callable[[int], str] = call_impl
|
||||
|
||||
static_assert(is_subtype_of(A, Callable[[int], str]))
|
||||
static_assert(not is_subtype_of(A, Callable[[int], int]))
|
||||
reveal_type(A()(1)) # revealed: str
|
||||
```
|
||||
|
||||
### Class literals
|
||||
|
||||
#### Classes with metaclasses
|
||||
|
||||
@@ -598,6 +598,10 @@ impl<'db> Type<'db> {
|
||||
matches!(self, Type::Dynamic(DynamicType::Todo(_)))
|
||||
}
|
||||
|
||||
pub const fn is_generic_alias(&self) -> bool {
|
||||
matches!(self, Type::GenericAlias(_))
|
||||
}
|
||||
|
||||
/// Replace references to the class `class` with a self-reference marker. This is currently
|
||||
/// used for recursive protocols, but could probably be extended to self-referential type-
|
||||
/// aliases and similar.
|
||||
@@ -1291,12 +1295,20 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
(Type::NominalInstance(_) | Type::ProtocolInstance(_), Type::Callable(_)) => {
|
||||
let call_symbol = self.member(db, "__call__").symbol;
|
||||
match call_symbol {
|
||||
Symbol::Type(Type::BoundMethod(call_function), _) => call_function
|
||||
.into_callable_type(db)
|
||||
.is_subtype_of(db, target),
|
||||
_ => false,
|
||||
let call_symbol = self
|
||||
.member_lookup_with_policy(
|
||||
db,
|
||||
Name::new_static("__call__"),
|
||||
MemberLookupPolicy::NO_INSTANCE_FALLBACK,
|
||||
)
|
||||
.symbol;
|
||||
// If the type of __call__ is a subtype of a callable type, this instance is.
|
||||
// Don't add other special cases here; our subtyping of a callable type
|
||||
// shouldn't get out of sync with the calls we will actually allow.
|
||||
if let Symbol::Type(t, Boundness::Bound) = call_symbol {
|
||||
t.is_subtype_of(db, target)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
(Type::ProtocolInstance(left), Type::ProtocolInstance(right)) => {
|
||||
@@ -1641,12 +1653,20 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
(Type::NominalInstance(_) | Type::ProtocolInstance(_), Type::Callable(_)) => {
|
||||
let call_symbol = self.member(db, "__call__").symbol;
|
||||
match call_symbol {
|
||||
Symbol::Type(Type::BoundMethod(call_function), _) => call_function
|
||||
.into_callable_type(db)
|
||||
.is_assignable_to(db, target),
|
||||
_ => false,
|
||||
let call_symbol = self
|
||||
.member_lookup_with_policy(
|
||||
db,
|
||||
Name::new_static("__call__"),
|
||||
MemberLookupPolicy::NO_INSTANCE_FALLBACK,
|
||||
)
|
||||
.symbol;
|
||||
// If the type of __call__ is assignable to a callable type, this instance is.
|
||||
// Don't add other special cases here; our assignability to a callable type
|
||||
// shouldn't get out of sync with the calls we will actually allow.
|
||||
if let Symbol::Type(t, Boundness::Bound) = call_symbol {
|
||||
t.is_assignable_to(db, target)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2746,6 +2766,7 @@ impl<'db> Type<'db> {
|
||||
instance.display(db),
|
||||
owner.display(db)
|
||||
);
|
||||
|
||||
let descr_get = self.class_member(db, "__get__".into()).symbol;
|
||||
|
||||
if let Symbol::Type(descr_get, descr_get_boundness) = descr_get {
|
||||
@@ -7182,10 +7203,9 @@ impl<'db> FunctionType<'db> {
|
||||
// However, our representation of a function literal includes any specialization that
|
||||
// should be applied to the signature. Different specializations of the same function
|
||||
// literal are only assignable to each other if they result in assignable signatures.
|
||||
self.body_scope(db) == other.body_scope(db)
|
||||
&& self
|
||||
.into_callable_type(db)
|
||||
.is_assignable_to(db, other.into_callable_type(db))
|
||||
|
||||
self.into_callable_type(db)
|
||||
.is_assignable_to(db, other.into_callable_type(db))
|
||||
}
|
||||
|
||||
fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
|
||||
@@ -223,6 +223,10 @@ impl<'db> ClassType<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) const fn is_generic(self) -> bool {
|
||||
matches!(self, Self::Generic(_))
|
||||
}
|
||||
|
||||
/// Returns the class literal and specialization for this class. For a non-generic class, this
|
||||
/// is the class itself. For a generic alias, this is the alias's origin.
|
||||
pub(crate) fn class_literal(
|
||||
@@ -352,7 +356,7 @@ impl<'db> ClassType<'db> {
|
||||
ClassBase::Dynamic(_) => false,
|
||||
|
||||
// Protocol and Generic are not represented by a ClassType.
|
||||
ClassBase::Protocol(_) | ClassBase::Generic(_) => false,
|
||||
ClassBase::Protocol | ClassBase::Generic => false,
|
||||
|
||||
ClassBase::Class(base) => match (base, other) {
|
||||
(ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => base == other,
|
||||
@@ -390,7 +394,7 @@ impl<'db> ClassType<'db> {
|
||||
ClassBase::Dynamic(_) => false,
|
||||
|
||||
// Protocol and Generic are not represented by a ClassType.
|
||||
ClassBase::Protocol(_) | ClassBase::Generic(_) => false,
|
||||
ClassBase::Protocol | ClassBase::Generic => false,
|
||||
|
||||
ClassBase::Class(base) => match (base, other) {
|
||||
(ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => base == other,
|
||||
@@ -602,11 +606,6 @@ impl<'db> ClassLiteral<'db> {
|
||||
)
|
||||
}
|
||||
|
||||
/// Return `true` if this class represents the builtin class `object`
|
||||
pub(crate) fn is_object(self, db: &'db dyn Db) -> bool {
|
||||
self.is_known(db, KnownClass::Object)
|
||||
}
|
||||
|
||||
fn file(self, db: &dyn Db) -> File {
|
||||
self.body_scope(db).file(db)
|
||||
}
|
||||
@@ -1068,7 +1067,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
|
||||
for superclass in mro_iter {
|
||||
match superclass {
|
||||
ClassBase::Generic(_) | ClassBase::Protocol(_) => {
|
||||
ClassBase::Generic | ClassBase::Protocol => {
|
||||
// Skip over these very special class bases that aren't really classes.
|
||||
}
|
||||
ClassBase::Dynamic(_) => {
|
||||
@@ -1427,7 +1426,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
|
||||
for superclass in self.iter_mro(db, specialization) {
|
||||
match superclass {
|
||||
ClassBase::Generic(_) | ClassBase::Protocol(_) => {
|
||||
ClassBase::Generic | ClassBase::Protocol => {
|
||||
// Skip over these very special class bases that aren't really classes.
|
||||
}
|
||||
ClassBase::Dynamic(_) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::Db;
|
||||
use crate::types::generics::{GenericContext, Specialization};
|
||||
use crate::types::generics::Specialization;
|
||||
use crate::types::{
|
||||
ClassType, DynamicType, KnownClass, KnownInstanceType, MroError, MroIterator, Type,
|
||||
TypeMapping, todo_type,
|
||||
@@ -19,11 +19,11 @@ pub enum ClassBase<'db> {
|
||||
Class(ClassType<'db>),
|
||||
/// Although `Protocol` is not a class in typeshed's stubs, it is at runtime,
|
||||
/// and can appear in the MRO of a class.
|
||||
Protocol(Option<GenericContext<'db>>),
|
||||
Protocol,
|
||||
/// Bare `Generic` cannot be subclassed directly in user code,
|
||||
/// but nonetheless appears in the MRO of classes that inherit from `Generic[T]`,
|
||||
/// `Protocol[T]`, or bare `Protocol`.
|
||||
Generic(Option<GenericContext<'db>>),
|
||||
Generic,
|
||||
}
|
||||
|
||||
impl<'db> ClassBase<'db> {
|
||||
@@ -35,60 +35,18 @@ impl<'db> ClassBase<'db> {
|
||||
match self {
|
||||
Self::Dynamic(dynamic) => Self::Dynamic(dynamic.normalized()),
|
||||
Self::Class(class) => Self::Class(class.normalized(db)),
|
||||
Self::Protocol(generic_context) => {
|
||||
Self::Protocol(generic_context.map(|context| context.normalized(db)))
|
||||
}
|
||||
Self::Generic(generic_context) => {
|
||||
Self::Generic(generic_context.map(|context| context.normalized(db)))
|
||||
}
|
||||
Self::Protocol | Self::Generic => self,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn display(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db {
|
||||
struct Display<'db> {
|
||||
base: ClassBase<'db>,
|
||||
db: &'db dyn Db,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Display<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.base {
|
||||
ClassBase::Dynamic(dynamic) => dynamic.fmt(f),
|
||||
ClassBase::Class(class @ ClassType::NonGeneric(_)) => {
|
||||
write!(f, "<class '{}'>", class.name(self.db))
|
||||
}
|
||||
ClassBase::Class(ClassType::Generic(alias)) => {
|
||||
write!(f, "<class '{}'>", alias.display(self.db))
|
||||
}
|
||||
ClassBase::Protocol(generic_context) => {
|
||||
f.write_str("typing.Protocol")?;
|
||||
if let Some(generic_context) = generic_context {
|
||||
generic_context.display(self.db).fmt(f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ClassBase::Generic(generic_context) => {
|
||||
f.write_str("typing.Generic")?;
|
||||
if let Some(generic_context) = generic_context {
|
||||
generic_context.display(self.db).fmt(f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Display { base: self, db }
|
||||
}
|
||||
|
||||
pub(crate) fn name(self, db: &'db dyn Db) -> &'db str {
|
||||
match self {
|
||||
ClassBase::Class(class) => class.name(db),
|
||||
ClassBase::Dynamic(DynamicType::Any) => "Any",
|
||||
ClassBase::Dynamic(DynamicType::Unknown) => "Unknown",
|
||||
ClassBase::Dynamic(DynamicType::Todo(_) | DynamicType::TodoPEP695ParamSpec) => "@Todo",
|
||||
ClassBase::Protocol(_) => "Protocol",
|
||||
ClassBase::Generic(_) => "Generic",
|
||||
ClassBase::Protocol => "Protocol",
|
||||
ClassBase::Generic => "Generic",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,12 +213,8 @@ impl<'db> ClassBase<'db> {
|
||||
KnownInstanceType::Callable => {
|
||||
Self::try_from_type(db, todo_type!("Support for Callable as a base class"))
|
||||
}
|
||||
KnownInstanceType::Protocol(generic_context) => {
|
||||
Some(ClassBase::Protocol(generic_context))
|
||||
}
|
||||
KnownInstanceType::Generic(generic_context) => {
|
||||
Some(ClassBase::Generic(generic_context))
|
||||
}
|
||||
KnownInstanceType::Protocol(_) => Some(ClassBase::Protocol),
|
||||
KnownInstanceType::Generic(_) => Some(ClassBase::Generic),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -268,14 +222,14 @@ impl<'db> ClassBase<'db> {
|
||||
pub(super) fn into_class(self) -> Option<ClassType<'db>> {
|
||||
match self {
|
||||
Self::Class(class) => Some(class),
|
||||
Self::Dynamic(_) | Self::Generic(_) | Self::Protocol(_) => None,
|
||||
Self::Dynamic(_) | Self::Generic | Self::Protocol => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
|
||||
match self {
|
||||
Self::Class(class) => Self::Class(class.apply_type_mapping(db, type_mapping)),
|
||||
Self::Dynamic(_) | Self::Generic(_) | Self::Protocol(_) => self,
|
||||
Self::Dynamic(_) | Self::Generic | Self::Protocol => self,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,7 +253,7 @@ impl<'db> ClassBase<'db> {
|
||||
.try_mro(db, specialization)
|
||||
.is_err_and(MroError::is_cycle)
|
||||
}
|
||||
ClassBase::Dynamic(_) | ClassBase::Generic(_) | ClassBase::Protocol(_) => false,
|
||||
ClassBase::Dynamic(_) | ClassBase::Generic | ClassBase::Protocol => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,12 +264,8 @@ impl<'db> ClassBase<'db> {
|
||||
additional_specialization: Option<Specialization<'db>>,
|
||||
) -> impl Iterator<Item = ClassBase<'db>> {
|
||||
match self {
|
||||
ClassBase::Protocol(context) => {
|
||||
ClassBaseMroIterator::length_3(db, self, ClassBase::Generic(context))
|
||||
}
|
||||
ClassBase::Dynamic(_) | ClassBase::Generic(_) => {
|
||||
ClassBaseMroIterator::length_2(db, self)
|
||||
}
|
||||
ClassBase::Protocol => ClassBaseMroIterator::length_3(db, self, ClassBase::Generic),
|
||||
ClassBase::Dynamic(_) | ClassBase::Generic => ClassBaseMroIterator::length_2(db, self),
|
||||
ClassBase::Class(class) => {
|
||||
ClassBaseMroIterator::from_class(db, class, additional_specialization)
|
||||
}
|
||||
@@ -338,12 +288,8 @@ impl<'db> From<ClassBase<'db>> for Type<'db> {
|
||||
match value {
|
||||
ClassBase::Dynamic(dynamic) => Type::Dynamic(dynamic),
|
||||
ClassBase::Class(class) => class.into(),
|
||||
ClassBase::Protocol(generic_context) => {
|
||||
Type::KnownInstance(KnownInstanceType::Protocol(generic_context))
|
||||
}
|
||||
ClassBase::Generic(generic_context) => {
|
||||
Type::KnownInstance(KnownInstanceType::Generic(generic_context))
|
||||
}
|
||||
ClassBase::Protocol => Type::KnownInstance(KnownInstanceType::Protocol(None)),
|
||||
ClassBase::Generic => Type::KnownInstance(KnownInstanceType::Generic(None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ use crate::types::string_annotation::{
|
||||
};
|
||||
use crate::types::{KnownFunction, KnownInstanceType, Type, protocol_class::ProtocolClassLiteral};
|
||||
use crate::{Program, PythonVersionWithSource, declare_lint};
|
||||
use itertools::Itertools;
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, Span, SubDiagnostic};
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
@@ -1698,10 +1699,7 @@ pub(super) fn report_implicit_return_type(
|
||||
let Some(class) = enclosing_class_of_method else {
|
||||
return;
|
||||
};
|
||||
if class
|
||||
.iter_mro(db, None)
|
||||
.any(|base| matches!(base, ClassBase::Protocol(_)))
|
||||
{
|
||||
if class.iter_mro(db, None).contains(&ClassBase::Protocol) {
|
||||
diagnostic.info(
|
||||
"Only functions in stub files, methods on protocol classes, \
|
||||
or methods with `@abstractmethod` are permitted to have empty bodies",
|
||||
|
||||
@@ -6537,20 +6537,27 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
intersection_on: IntersectionOn,
|
||||
range: TextRange,
|
||||
) -> Result<Type<'db>, CompareUnsupportedError<'db>> {
|
||||
enum State<'db> {
|
||||
// We have not seen any positive elements (yet)
|
||||
NoPositiveElements,
|
||||
// The operator was unsupported on all elements that we have seen so far.
|
||||
// Contains the first error we encountered.
|
||||
UnsupportedOnAllElements(CompareUnsupportedError<'db>),
|
||||
// The operator was supported on at least one positive element.
|
||||
Supported,
|
||||
}
|
||||
|
||||
// If a comparison yields a definitive true/false answer on a (positive) part
|
||||
// of an intersection type, it will also yield a definitive answer on the full
|
||||
// intersection type, which is even more specific.
|
||||
for pos in intersection.positive(self.db()) {
|
||||
let result = match intersection_on {
|
||||
IntersectionOn::Left => {
|
||||
self.infer_binary_type_comparison(*pos, op, other, range)?
|
||||
}
|
||||
IntersectionOn::Right => {
|
||||
self.infer_binary_type_comparison(other, op, *pos, range)?
|
||||
}
|
||||
IntersectionOn::Left => self.infer_binary_type_comparison(*pos, op, other, range),
|
||||
IntersectionOn::Right => self.infer_binary_type_comparison(other, op, *pos, range),
|
||||
};
|
||||
if let Type::BooleanLiteral(b) = result {
|
||||
return Ok(Type::BooleanLiteral(b));
|
||||
|
||||
if let Ok(Type::BooleanLiteral(_)) = result {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6619,19 +6626,55 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
|
||||
builder = builder.add_positive(KnownClass::Bool.to_instance(self.db()));
|
||||
|
||||
let mut state = State::NoPositiveElements;
|
||||
|
||||
for pos in intersection.positive(self.db()) {
|
||||
let result = match intersection_on {
|
||||
IntersectionOn::Left => {
|
||||
self.infer_binary_type_comparison(*pos, op, other, range)?
|
||||
}
|
||||
IntersectionOn::Right => {
|
||||
self.infer_binary_type_comparison(other, op, *pos, range)?
|
||||
}
|
||||
IntersectionOn::Left => self.infer_binary_type_comparison(*pos, op, other, range),
|
||||
IntersectionOn::Right => self.infer_binary_type_comparison(other, op, *pos, range),
|
||||
};
|
||||
builder = builder.add_positive(result);
|
||||
|
||||
match result {
|
||||
Ok(ty) => {
|
||||
state = State::Supported;
|
||||
builder = builder.add_positive(ty);
|
||||
}
|
||||
Err(error) => {
|
||||
match state {
|
||||
State::NoPositiveElements => {
|
||||
// This is the first positive element, but the operation is not supported.
|
||||
// Store the error and continue.
|
||||
state = State::UnsupportedOnAllElements(error);
|
||||
}
|
||||
State::UnsupportedOnAllElements(_) => {
|
||||
// We already have an error stored, and continue to see elements on which
|
||||
// the operator is not supported. Continue with the same state (only keep
|
||||
// the first error).
|
||||
}
|
||||
State::Supported => {
|
||||
// We previously saw a positive element that supported the operator,
|
||||
// so the overall operation is still supported.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(builder.build())
|
||||
match state {
|
||||
State::Supported => Ok(builder.build()),
|
||||
State::NoPositiveElements => {
|
||||
// We didn't see any positive elements, check if the operation is supported on `object`:
|
||||
match intersection_on {
|
||||
IntersectionOn::Left => {
|
||||
self.infer_binary_type_comparison(Type::object(self.db()), op, other, range)
|
||||
}
|
||||
IntersectionOn::Right => {
|
||||
self.infer_binary_type_comparison(other, op, Type::object(self.db()), range)
|
||||
}
|
||||
}
|
||||
}
|
||||
State::UnsupportedOnAllElements(error) => Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
/// Infers the type of a binary comparison (e.g. 'left == right'). See
|
||||
@@ -7529,7 +7572,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
if !value_ty.into_class_literal().is_some_and(|class| {
|
||||
class
|
||||
.iter_mro(self.db(), None)
|
||||
.any(|base| matches!(base, ClassBase::Generic(_)))
|
||||
.contains(&ClassBase::Generic)
|
||||
}) {
|
||||
report_non_subscriptable(
|
||||
&self.context,
|
||||
|
||||
@@ -7,7 +7,7 @@ use rustc_hash::FxBuildHasher;
|
||||
use crate::Db;
|
||||
use crate::types::class_base::ClassBase;
|
||||
use crate::types::generics::Specialization;
|
||||
use crate::types::{ClassLiteral, ClassType, Type};
|
||||
use crate::types::{ClassLiteral, ClassType, KnownInstanceType, Type};
|
||||
|
||||
/// The inferred method resolution order of a given class.
|
||||
///
|
||||
@@ -48,12 +48,12 @@ impl<'db> Mro<'db> {
|
||||
/// [`super::infer::TypeInferenceBuilder::infer_region_scope`].)
|
||||
pub(super) fn of_class(
|
||||
db: &'db dyn Db,
|
||||
class: ClassLiteral<'db>,
|
||||
class_literal: ClassLiteral<'db>,
|
||||
specialization: Option<Specialization<'db>>,
|
||||
) -> Result<Self, MroError<'db>> {
|
||||
Self::of_class_impl(db, class, specialization).map_err(|err| {
|
||||
err.into_mro_error(db, class.apply_optional_specialization(db, specialization))
|
||||
})
|
||||
let class = class_literal.apply_optional_specialization(db, specialization);
|
||||
Self::of_class_impl(db, class, class_literal.explicit_bases(db), specialization)
|
||||
.map_err(|err| err.into_mro_error(db, class))
|
||||
}
|
||||
|
||||
pub(super) fn from_error(db: &'db dyn Db, class: ClassType<'db>) -> Self {
|
||||
@@ -66,17 +66,16 @@ impl<'db> Mro<'db> {
|
||||
|
||||
fn of_class_impl(
|
||||
db: &'db dyn Db,
|
||||
class: ClassLiteral<'db>,
|
||||
class: ClassType<'db>,
|
||||
bases: &[Type<'db>],
|
||||
specialization: Option<Specialization<'db>>,
|
||||
) -> Result<Self, MroErrorKind<'db>> {
|
||||
let class_type = class.apply_optional_specialization(db, specialization);
|
||||
|
||||
match class.explicit_bases(db) {
|
||||
match bases {
|
||||
// `builtins.object` is the special case:
|
||||
// the only class in Python that has an MRO with length <2
|
||||
[] if class.is_object(db) => Ok(Self::from([
|
||||
// object is not generic, so the default specialization should be a no-op
|
||||
ClassBase::Class(class_type),
|
||||
ClassBase::Class(class),
|
||||
])),
|
||||
|
||||
// All other classes in Python have an MRO with length >=2.
|
||||
@@ -92,44 +91,82 @@ impl<'db> Mro<'db> {
|
||||
// >>> Foo.__mro__
|
||||
// (<class '__main__.Foo'>, <class 'object'>)
|
||||
// ```
|
||||
[] => Ok(Self::from([
|
||||
ClassBase::Class(class_type),
|
||||
ClassBase::object(db),
|
||||
])),
|
||||
[] => {
|
||||
// e.g. `class Foo[T]: ...` implicitly has `Generic` inserted into its bases
|
||||
if class.is_generic() {
|
||||
Ok(Self::from([
|
||||
ClassBase::Class(class),
|
||||
ClassBase::Generic,
|
||||
ClassBase::object(db),
|
||||
]))
|
||||
} else {
|
||||
Ok(Self::from([ClassBase::Class(class), ClassBase::object(db)]))
|
||||
}
|
||||
}
|
||||
|
||||
// Fast path for a class that has only a single explicit base.
|
||||
//
|
||||
// This *could* theoretically be handled by the final branch below,
|
||||
// but it's a common case (i.e., worth optimizing for),
|
||||
// and the `c3_merge` function requires lots of allocations.
|
||||
[single_base] => ClassBase::try_from_type(db, *single_base).map_or_else(
|
||||
|| Err(MroErrorKind::InvalidBases(Box::from([(0, *single_base)]))),
|
||||
|single_base| {
|
||||
if single_base.has_cyclic_mro(db) {
|
||||
Err(MroErrorKind::InheritanceCycle)
|
||||
} else {
|
||||
Ok(std::iter::once(ClassBase::Class(
|
||||
class.apply_optional_specialization(db, specialization),
|
||||
))
|
||||
.chain(single_base.mro(db, specialization))
|
||||
.collect())
|
||||
}
|
||||
},
|
||||
),
|
||||
[single_base]
|
||||
if !matches!(
|
||||
single_base,
|
||||
Type::GenericAlias(_)
|
||||
| Type::KnownInstance(
|
||||
KnownInstanceType::Generic(_) | KnownInstanceType::Protocol(_)
|
||||
)
|
||||
) =>
|
||||
{
|
||||
ClassBase::try_from_type(db, *single_base).map_or_else(
|
||||
|| Err(MroErrorKind::InvalidBases(Box::from([(0, *single_base)]))),
|
||||
|single_base| {
|
||||
if single_base.has_cyclic_mro(db) {
|
||||
Err(MroErrorKind::InheritanceCycle)
|
||||
} else {
|
||||
Ok(std::iter::once(ClassBase::Class(class))
|
||||
.chain(single_base.mro(db, specialization))
|
||||
.collect())
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// The class has multiple explicit bases.
|
||||
//
|
||||
// We'll fallback to a full implementation of the C3-merge algorithm to determine
|
||||
// what MRO Python will give this class at runtime
|
||||
// (if an MRO is indeed resolvable at all!)
|
||||
multiple_bases => {
|
||||
let mut valid_bases = vec![];
|
||||
original_bases => {
|
||||
let mut resolved_bases = vec![];
|
||||
let mut invalid_bases = vec![];
|
||||
|
||||
for (i, base) in multiple_bases.iter().enumerate() {
|
||||
match ClassBase::try_from_type(db, *base) {
|
||||
Some(valid_base) => valid_bases.push(valid_base),
|
||||
None => invalid_bases.push((i, *base)),
|
||||
for (i, base) in original_bases.iter().enumerate() {
|
||||
// This emulates the behavior of `typing._GenericAlias.__mro_entries__` at
|
||||
// <https://github.com/python/cpython/blob/ad42dc1909bdf8ec775b63fb22ed48ff42797a17/Lib/typing.py#L1487-L1500>.
|
||||
//
|
||||
// Note that emit a diagnostic for inheriting from bare (unsubscripted) `Generic` elsewhere
|
||||
// (see `infer::TypeInferenceBuilder::check_class_definitions`),
|
||||
// which is why we only care about `KnownInstanceType::Generic(Some(_))`,
|
||||
// not `KnownInstanceType::Generic(None)`.
|
||||
if let Type::KnownInstance(KnownInstanceType::Generic(Some(_))) = base {
|
||||
if original_bases
|
||||
.contains(&Type::KnownInstance(KnownInstanceType::Protocol(None)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if original_bases[i + 1..]
|
||||
.iter()
|
||||
.any(|b| b.is_generic_alias() && b != base)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
resolved_bases.push(ClassBase::Generic);
|
||||
} else {
|
||||
match ClassBase::try_from_type(db, *base) {
|
||||
Some(valid_base) => resolved_bases.push(valid_base),
|
||||
None => invalid_bases.push((i, *base)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,15 +174,15 @@ impl<'db> Mro<'db> {
|
||||
return Err(MroErrorKind::InvalidBases(invalid_bases.into_boxed_slice()));
|
||||
}
|
||||
|
||||
let mut seqs = vec![VecDeque::from([ClassBase::Class(class_type)])];
|
||||
for base in &valid_bases {
|
||||
let mut seqs = vec![VecDeque::from([ClassBase::Class(class)])];
|
||||
for base in &resolved_bases {
|
||||
if base.has_cyclic_mro(db) {
|
||||
return Err(MroErrorKind::InheritanceCycle);
|
||||
}
|
||||
seqs.push(base.mro(db, specialization).collect());
|
||||
}
|
||||
seqs.push(
|
||||
valid_bases
|
||||
resolved_bases
|
||||
.iter()
|
||||
.map(|base| base.apply_optional_specialization(db, specialization))
|
||||
.collect(),
|
||||
@@ -161,8 +198,20 @@ impl<'db> Mro<'db> {
|
||||
let mut base_to_indices: IndexMap<ClassBase<'db>, Vec<usize>, FxBuildHasher> =
|
||||
IndexMap::default();
|
||||
|
||||
for (index, base) in valid_bases.iter().enumerate() {
|
||||
base_to_indices.entry(*base).or_default().push(index);
|
||||
// We need to iterate over `original_bases` here rather than `resolved_bases`
|
||||
// so that we get the correct index of the duplicate bases if there were any
|
||||
// (`resolved_bases` may be a longer list than `original_bases`!). However, we
|
||||
// need to use a `ClassBase` rather than a `Type` as the key type for the
|
||||
// `base_to_indices` map so that a class such as
|
||||
// `class Foo(Protocol[T], Protocol): ...` correctly causes us to emit a
|
||||
// `duplicate-base` diagnostic (matching the runtime behaviour) rather than an
|
||||
// `inconsistent-mro` diagnostic (which would be accurate -- but not nearly as
|
||||
// precise!).
|
||||
for (index, base) in original_bases.iter().enumerate() {
|
||||
let Some(base) = ClassBase::try_from_type(db, *base) else {
|
||||
continue;
|
||||
};
|
||||
base_to_indices.entry(base).or_default().push(index);
|
||||
}
|
||||
|
||||
let mut errors = vec![];
|
||||
@@ -175,9 +224,7 @@ impl<'db> Mro<'db> {
|
||||
continue;
|
||||
}
|
||||
match base {
|
||||
ClassBase::Class(_)
|
||||
| ClassBase::Generic(_)
|
||||
| ClassBase::Protocol(_) => {
|
||||
ClassBase::Class(_) | ClassBase::Generic | ClassBase::Protocol => {
|
||||
errors.push(DuplicateBaseError {
|
||||
duplicate_base: base,
|
||||
first_index: *first_index,
|
||||
@@ -193,13 +240,10 @@ impl<'db> Mro<'db> {
|
||||
|
||||
if duplicate_bases.is_empty() {
|
||||
if duplicate_dynamic_bases {
|
||||
Ok(Mro::from_error(
|
||||
db,
|
||||
class.apply_optional_specialization(db, specialization),
|
||||
))
|
||||
Ok(Mro::from_error(db, class))
|
||||
} else {
|
||||
Err(MroErrorKind::UnresolvableMro {
|
||||
bases_list: valid_bases.into_boxed_slice(),
|
||||
bases_list: original_bases.iter().copied().collect(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
@@ -378,7 +422,7 @@ pub(super) enum MroErrorKind<'db> {
|
||||
/// The MRO is otherwise unresolvable through the C3-merge algorithm.
|
||||
///
|
||||
/// See [`c3_merge`] for more details.
|
||||
UnresolvableMro { bases_list: Box<[ClassBase<'db>]> },
|
||||
UnresolvableMro { bases_list: Box<[Type<'db>]> },
|
||||
}
|
||||
|
||||
impl<'db> MroErrorKind<'db> {
|
||||
|
||||
@@ -152,13 +152,11 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
|
||||
(ClassBase::Class(_), _) => Ordering::Less,
|
||||
(_, ClassBase::Class(_)) => Ordering::Greater,
|
||||
|
||||
(ClassBase::Protocol(left), ClassBase::Protocol(right)) => left.cmp(&right),
|
||||
(ClassBase::Protocol(_), _) => Ordering::Less,
|
||||
(_, ClassBase::Protocol(_)) => Ordering::Greater,
|
||||
(ClassBase::Protocol, _) => Ordering::Less,
|
||||
(_, ClassBase::Protocol) => Ordering::Greater,
|
||||
|
||||
(ClassBase::Generic(left), ClassBase::Generic(right)) => left.cmp(&right),
|
||||
(ClassBase::Generic(_), _) => Ordering::Less,
|
||||
(_, ClassBase::Generic(_)) => Ordering::Greater,
|
||||
(ClassBase::Generic, _) => Ordering::Less,
|
||||
(_, ClassBase::Generic) => Ordering::Greater,
|
||||
|
||||
(ClassBase::Dynamic(left), ClassBase::Dynamic(right)) => {
|
||||
dynamic_elements_ordering(left, right)
|
||||
|
||||
@@ -14,6 +14,15 @@ Intersection: _SpecialForm
|
||||
TypeOf: _SpecialForm
|
||||
CallableTypeOf: _SpecialForm
|
||||
|
||||
# ty treats annotations of `float` to mean `float | int`, and annotations of `complex`
|
||||
# to mean `complex | float | int`. This is to support a typing-system special case [1].
|
||||
# We therefore provide `JustFloat` and `JustComplex` to represent the "bare" `float` and
|
||||
# `complex` types, respectively.
|
||||
#
|
||||
# [1]: https://typing.readthedocs.io/en/latest/spec/special-types.html#special-cases-for-float-and-complex
|
||||
type JustFloat = TypeOf[1.0]
|
||||
type JustComplex = TypeOf[1.0j]
|
||||
|
||||
# Predicates on types
|
||||
#
|
||||
# Ideally, these would be annotated using `TypeForm`, but that has not been
|
||||
|
||||
@@ -5,4 +5,4 @@ mkdocs-material @ git+ssh://git@github.com/astral-sh/mkdocs-material-insiders.gi
|
||||
mkdocs-redirects==1.2.2
|
||||
mdformat==0.7.22
|
||||
mdformat-mkdocs==4.1.2
|
||||
mkdocs-github-admonitions-plugin @ https://github.com/PGijsbers/admonitions.git#7343d2f4a92e4d1491094530ef3d0d02d93afbb7
|
||||
mkdocs-github-admonitions-plugin @ git+https://github.com/PGijsbers/admonitions.git#7343d2f4a92e4d1491094530ef3d0d02d93afbb7
|
||||
|
||||
@@ -5,4 +5,4 @@ mkdocs-material==9.5.38
|
||||
mkdocs-redirects==1.2.2
|
||||
mdformat==0.7.22
|
||||
mdformat-mkdocs==4.1.2
|
||||
mkdocs-github-admonitions-plugin @ https://github.com/PGijsbers/admonitions.git#7343d2f4a92e4d1491094530ef3d0d02d93afbb7
|
||||
mkdocs-github-admonitions-plugin @ git+https://github.com/PGijsbers/admonitions.git#7343d2f4a92e4d1491094530ef3d0d02d93afbb7
|
||||
|
||||
Reference in New Issue
Block a user