mirror of https://github.com/astral-sh/ruff
Merge branch 'main' into typing-self-argument
This commit is contained in:
commit
a27013bdd3
|
|
@ -42,3 +42,7 @@ b"a" in bytes("a", "utf-8")
|
||||||
1 in set(set([1]))
|
1 in set(set([1]))
|
||||||
'' in {""}
|
'' in {""}
|
||||||
frozenset() in {frozenset()}
|
frozenset() in {frozenset()}
|
||||||
|
|
||||||
|
# https://github.com/astral-sh/ruff/issues/20238
|
||||||
|
"b" in f"" "" # Error
|
||||||
|
"b" in f"" "x" # OK
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||||
|
use ruff_python_ast as ast;
|
||||||
use ruff_python_ast::identifier::Identifier;
|
use ruff_python_ast::identifier::Identifier;
|
||||||
use ruff_python_ast::{self as ast, ParameterWithDefault};
|
|
||||||
use ruff_python_semantic::analyze::function_type;
|
use ruff_python_semantic::analyze::function_type;
|
||||||
|
|
||||||
use crate::Violation;
|
use crate::Violation;
|
||||||
|
|
@ -85,16 +85,9 @@ pub(crate) fn pep_484_positional_parameter(checker: &Checker, function_def: &ast
|
||||||
function_type::FunctionType::Method | function_type::FunctionType::ClassMethod
|
function_type::FunctionType::Method | function_type::FunctionType::ClassMethod
|
||||||
));
|
));
|
||||||
|
|
||||||
if let Some(arg) = function_def.parameters.args.get(skip) {
|
if let Some(param) = function_def.parameters.args.get(skip) {
|
||||||
if is_old_style_positional_only(arg) {
|
if param.uses_pep_484_positional_only_convention() {
|
||||||
checker.report_diagnostic(Pep484StylePositionalOnlyParameter, arg.identifier());
|
checker.report_diagnostic(Pep484StylePositionalOnlyParameter, param.identifier());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the [`ParameterWithDefault`] is an old-style positional-only parameter (i.e.,
|
|
||||||
/// its name starts with `__` and does not end with `__`).
|
|
||||||
fn is_old_style_positional_only(param: &ParameterWithDefault) -> bool {
|
|
||||||
let arg_name = param.name();
|
|
||||||
arg_name.starts_with("__") && !arg_name.ends_with("__")
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||||
use ruff_python_ast::{self as ast, CmpOp, Expr};
|
use ruff_python_ast::{self as ast, CmpOp, Expr, helpers::is_empty_f_string};
|
||||||
use ruff_python_semantic::SemanticModel;
|
use ruff_python_semantic::SemanticModel;
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
|
|
@ -75,10 +75,7 @@ fn is_empty(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
Expr::Dict(ast::ExprDict { items, .. }) => items.is_empty(),
|
Expr::Dict(ast::ExprDict { items, .. }) => items.is_empty(),
|
||||||
Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => value.is_empty(),
|
Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => value.is_empty(),
|
||||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value.is_empty(),
|
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value.is_empty(),
|
||||||
Expr::FString(s) => s
|
Expr::FString(s) => is_empty_f_string(s),
|
||||||
.value
|
|
||||||
.elements()
|
|
||||||
.all(|elt| elt.as_literal().is_some_and(|elt| elt.is_empty())),
|
|
||||||
Expr::Call(ast::ExprCall {
|
Expr::Call(ast::ExprCall {
|
||||||
func,
|
func,
|
||||||
arguments,
|
arguments,
|
||||||
|
|
|
||||||
|
|
@ -251,3 +251,12 @@ RUF060 Unnecessary membership test on empty collection
|
||||||
25 |
|
25 |
|
||||||
26 | # OK
|
26 | # OK
|
||||||
|
|
|
|
||||||
|
|
||||||
|
RUF060 Unnecessary membership test on empty collection
|
||||||
|
--> RUF060.py:47:1
|
||||||
|
|
|
||||||
|
46 | # https://github.com/astral-sh/ruff/issues/20238
|
||||||
|
47 | "b" in f"" "" # Error
|
||||||
|
| ^^^^^^^^^^^^^
|
||||||
|
48 | "b" in f"" "x" # OK
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
@ -3225,7 +3225,6 @@ impl<'a> IntoIterator for &'a Box<Parameters> {
|
||||||
/// Used by `Arguments` original type.
|
/// Used by `Arguments` original type.
|
||||||
///
|
///
|
||||||
/// NOTE: This type is different from original Python AST.
|
/// NOTE: This type is different from original Python AST.
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
|
#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
|
||||||
pub struct ParameterWithDefault {
|
pub struct ParameterWithDefault {
|
||||||
|
|
@ -3247,6 +3246,14 @@ impl ParameterWithDefault {
|
||||||
pub fn annotation(&self) -> Option<&Expr> {
|
pub fn annotation(&self) -> Option<&Expr> {
|
||||||
self.parameter.annotation()
|
self.parameter.annotation()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return `true` if the parameter name uses the pre-PEP-570 convention
|
||||||
|
/// (specified in PEP 484) to indicate to a type checker that it should be treated
|
||||||
|
/// as positional-only.
|
||||||
|
pub fn uses_pep_484_positional_only_convention(&self) -> bool {
|
||||||
|
let name = self.name();
|
||||||
|
name.starts_with("__") && !name.ends_with("__")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An AST node used to represent the arguments passed to a function call or class definition.
|
/// An AST node used to represent the arguments passed to a function call or class definition.
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ If left unspecified, ty will try to detect common project layouts and initialize
|
||||||
* if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path
|
* if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path
|
||||||
* otherwise, default to `.` (flat layout)
|
* otherwise, default to `.` (flat layout)
|
||||||
|
|
||||||
Besides, if a `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` file),
|
Besides, if a `./python` or `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` or `__init__.pyi` file),
|
||||||
it will also be included in the first party search path.
|
it will also be included in the first party search path.
|
||||||
|
|
||||||
**Default value**: `null`
|
**Default value**: `null`
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ def test(): -> "int":
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable) ·
|
[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#L114)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L113)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -58,7 +58,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) ·
|
[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#L158)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L157)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -88,7 +88,7 @@ f(int) # error
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) ·
|
[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#L184)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L183)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -117,7 +117,7 @@ a = 1
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) ·
|
[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#L209)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L208)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -147,7 +147,7 @@ class C(A, B): ...
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) ·
|
[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#L235)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L234)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -177,7 +177,7 @@ class B(A): ...
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) ·
|
[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#L300)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L299)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -202,7 +202,7 @@ class B(A, A): ...
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only) ·
|
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only) ·
|
||||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L321)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L320)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -306,7 +306,7 @@ def test(): -> "Literal[5]":
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) ·
|
[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#L524)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L523)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -334,7 +334,7 @@ class C(A, B): ...
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) ·
|
[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#L548)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L547)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -358,7 +358,7 @@ t[3] # IndexError: tuple index out of range
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict) ·
|
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict) ·
|
||||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L353)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L352)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -445,7 +445,7 @@ an atypical memory layout.
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) ·
|
[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#L593)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L592)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -470,7 +470,7 @@ func("foo") # error: [invalid-argument-type]
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) ·
|
[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#L633)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L632)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -496,7 +496,7 @@ a: int = ''
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) ·
|
[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#L1667)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1666)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -528,7 +528,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-await) ·
|
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-await) ·
|
||||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L655)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L654)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -562,7 +562,7 @@ asyncio.run(main())
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) ·
|
[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#L685)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L684)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -584,7 +584,7 @@ class A(42): ... # error: [invalid-base]
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) ·
|
[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#L736)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L735)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -609,7 +609,7 @@ with 1:
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) ·
|
[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#L757)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L756)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -636,7 +636,7 @@ a: str
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) ·
|
[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#L780)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L779)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -678,7 +678,7 @@ except ZeroDivisionError:
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) ·
|
[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#L816)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L815)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -709,7 +709,7 @@ class C[U](Generic[T]): ...
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-key) ·
|
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-key) ·
|
||||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L568)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L567)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -738,7 +738,7 @@ alice["height"] # KeyError: 'height'
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) ·
|
[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#L842)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L841)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -771,7 +771,7 @@ def f(t: TypeVar("U")): ...
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) ·
|
[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#L891)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L890)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -803,7 +803,7 @@ class B(metaclass=f): ...
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-named-tuple) ·
|
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-named-tuple) ·
|
||||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L498)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L497)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -833,7 +833,7 @@ TypeError: can only inherit from a NamedTuple type and Generic
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) ·
|
[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#L918)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L917)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -881,7 +881,7 @@ def foo(x: int) -> int: ...
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) ·
|
[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#L961)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L960)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -905,7 +905,7 @@ def f(a: int = ''): ...
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) ·
|
[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#L435)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L434)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -937,7 +937,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) ·
|
[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#L981)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L980)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
Checks for `raise` statements that raise non-exceptions or use invalid
|
Checks for `raise` statements that raise non-exceptions or use invalid
|
||||||
|
|
@ -984,7 +984,7 @@ def g():
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) ·
|
[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#L614)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L613)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1007,7 +1007,7 @@ def func() -> int:
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) ·
|
[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#L1024)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1023)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1061,7 +1061,7 @@ TODO #14889
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type) ·
|
[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#L870)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L869)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1086,7 +1086,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) ·
|
[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#L1063)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1062)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1114,7 +1114,7 @@ TYPE_CHECKING = ''
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) ·
|
[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#L1087)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1086)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1142,7 +1142,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call) ·
|
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call) ·
|
||||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1139)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1138)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1174,7 +1174,7 @@ f(10) # Error
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition) ·
|
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition) ·
|
||||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1111)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1110)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1206,7 +1206,7 @@ class C:
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) ·
|
[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#L1167)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1166)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1239,7 +1239,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) ·
|
[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#L1196)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1195)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1262,7 +1262,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-typed-dict-key) ·
|
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-typed-dict-key) ·
|
||||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1766)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1765)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1293,7 +1293,7 @@ alice["age"] # KeyError
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) ·
|
[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#L1215)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1214)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1320,7 +1320,7 @@ func("string") # error: [no-matching-overload]
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) ·
|
[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#L1238)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1237)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1342,7 +1342,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) ·
|
[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#L1256)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1255)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1366,7 +1366,7 @@ for i in 34: # TypeError: 'int' object is not iterable
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) ·
|
[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#L1307)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1306)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1420,7 +1420,7 @@ def test(): -> "int":
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) ·
|
[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#L1643)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1642)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1448,7 +1448,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) ·
|
[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#L1398)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1397)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1475,7 +1475,7 @@ class B(A): ... # Error raised here
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) ·
|
[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#L1443)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1442)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1500,7 +1500,7 @@ f("foo") # Error raised here
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) ·
|
[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#L1421)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1420)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1526,7 +1526,7 @@ def _(x: int):
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) ·
|
[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#L1464)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1463)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1570,7 +1570,7 @@ class A:
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) ·
|
[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#L1521)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1520)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1595,7 +1595,7 @@ f(x=1, y=2) # Error raised here
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) ·
|
[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#L1542)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1541)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1621,7 +1621,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) ·
|
[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#L1564)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1563)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1644,7 +1644,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) ·
|
[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#L1583)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1582)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1667,7 +1667,7 @@ print(x) # NameError: name 'x' is not defined
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) ·
|
[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#L1276)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1275)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1702,7 +1702,7 @@ b1 < b2 < b1 # exception raised here
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) ·
|
[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#L1602)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1601)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1728,7 +1728,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
|
||||||
<small>
|
<small>
|
||||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) ·
|
[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#L1624)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1623)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1751,7 +1751,7 @@ l[1:10:0] # ValueError: slice step cannot be zero
|
||||||
<small>
|
<small>
|
||||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20ambiguous-protocol-member) ·
|
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20ambiguous-protocol-member) ·
|
||||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L463)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L462)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1790,7 +1790,7 @@ class SubProto(BaseProto, Protocol):
|
||||||
<small>
|
<small>
|
||||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20deprecated) ·
|
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20deprecated) ·
|
||||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L279)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L278)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1843,7 +1843,7 @@ a = 20 / 0 # type: ignore
|
||||||
<small>
|
<small>
|
||||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) ·
|
[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#L1328)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1327)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1869,7 +1869,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
|
||||||
<small>
|
<small>
|
||||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call) ·
|
[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#L132)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L131)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1899,7 +1899,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
|
||||||
<small>
|
<small>
|
||||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) ·
|
[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#L1350)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1349)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1929,7 +1929,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
|
||||||
<small>
|
<small>
|
||||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) ·
|
[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#L1695)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1694)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -1954,7 +1954,7 @@ cast(int, f()) # Redundant
|
||||||
<small>
|
<small>
|
||||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) ·
|
[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#L1503)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1502)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -2005,7 +2005,7 @@ a = 20 / 0 # ty: ignore[division-by-zero]
|
||||||
<small>
|
<small>
|
||||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global) ·
|
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global) ·
|
||||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1716)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1715)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -2059,7 +2059,7 @@ def g():
|
||||||
<small>
|
<small>
|
||||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base) ·
|
[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#L703)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L702)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -2096,7 +2096,7 @@ class D(C): ... # error: [unsupported-base]
|
||||||
<small>
|
<small>
|
||||||
Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") ·
|
Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) ·
|
[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#L261)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L260)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
@ -2118,7 +2118,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
|
||||||
<small>
|
<small>
|
||||||
Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") ·
|
Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") ·
|
||||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) ·
|
[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#L1376)
|
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1375)
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
**What it does**
|
**What it does**
|
||||||
|
|
|
||||||
|
|
@ -1752,3 +1752,126 @@ fn default_root_tests_package() -> anyhow::Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_root_python_folder() -> anyhow::Result<()> {
|
||||||
|
let case = CliTest::with_files([
|
||||||
|
("src/foo.py", "foo = 10"),
|
||||||
|
("python/bar.py", "bar = 20"),
|
||||||
|
(
|
||||||
|
"python/test_bar.py",
|
||||||
|
r#"
|
||||||
|
from foo import foo
|
||||||
|
from bar import bar
|
||||||
|
|
||||||
|
print(f"{foo} {bar}")
|
||||||
|
"#,
|
||||||
|
),
|
||||||
|
])?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(case.command(), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
All checks passed!
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If `python/__init__.py` is present, it is considered a package and `python` is not added to search paths.
|
||||||
|
#[test]
|
||||||
|
fn default_root_python_package() -> anyhow::Result<()> {
|
||||||
|
let case = CliTest::with_files([
|
||||||
|
("src/foo.py", "foo = 10"),
|
||||||
|
("python/__init__.py", ""),
|
||||||
|
("python/bar.py", "bar = 20"),
|
||||||
|
(
|
||||||
|
"python/test_bar.py",
|
||||||
|
r#"
|
||||||
|
from foo import foo
|
||||||
|
from bar import bar # expected unresolved import
|
||||||
|
|
||||||
|
print(f"{foo} {bar}")
|
||||||
|
"#,
|
||||||
|
),
|
||||||
|
])?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(case.command(), @r#"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
error[unresolved-import]: Cannot resolve imported module `bar`
|
||||||
|
--> python/test_bar.py:3:6
|
||||||
|
|
|
||||||
|
2 | from foo import foo
|
||||||
|
3 | from bar import bar # expected unresolved import
|
||||||
|
| ^^^
|
||||||
|
4 |
|
||||||
|
5 | print(f"{foo} {bar}")
|
||||||
|
|
|
||||||
|
info: Searched in the following paths during module resolution:
|
||||||
|
info: 1. <temp_dir>/ (first-party code)
|
||||||
|
info: 2. <temp_dir>/src (first-party code)
|
||||||
|
info: 3. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||||
|
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||||
|
info: rule `unresolved-import` is enabled by default
|
||||||
|
|
||||||
|
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.
|
||||||
|
"#);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Similarly, if `python/__init__.pyi` is present, it is considered a package and `python` is not added to search paths.
|
||||||
|
#[test]
|
||||||
|
fn default_root_python_package_pyi() -> anyhow::Result<()> {
|
||||||
|
let case = CliTest::with_files([
|
||||||
|
("src/foo.py", "foo = 10"),
|
||||||
|
("python/__init__.pyi", ""),
|
||||||
|
("python/bar.py", "bar = 20"),
|
||||||
|
(
|
||||||
|
"python/test_bar.py",
|
||||||
|
r#"
|
||||||
|
from foo import foo
|
||||||
|
from bar import bar # expected unresolved import
|
||||||
|
|
||||||
|
print(f"{foo} {bar}")
|
||||||
|
"#,
|
||||||
|
),
|
||||||
|
])?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(case.command(), @r#"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
error[unresolved-import]: Cannot resolve imported module `bar`
|
||||||
|
--> python/test_bar.py:3:6
|
||||||
|
|
|
||||||
|
2 | from foo import foo
|
||||||
|
3 | from bar import bar # expected unresolved import
|
||||||
|
| ^^^
|
||||||
|
4 |
|
||||||
|
5 | print(f"{foo} {bar}")
|
||||||
|
|
|
||||||
|
info: Searched in the following paths during module resolution:
|
||||||
|
info: 1. <temp_dir>/ (first-party code)
|
||||||
|
info: 2. <temp_dir>/src (first-party code)
|
||||||
|
info: 3. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||||
|
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||||
|
info: rule `unresolved-import` is enabled by default
|
||||||
|
|
||||||
|
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.
|
||||||
|
"#);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -259,11 +259,29 @@ impl Options {
|
||||||
vec![project_root.to_path_buf()]
|
vec![project_root.to_path_buf()]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let python = project_root.join("python");
|
||||||
|
if system.is_directory(&python)
|
||||||
|
&& !system.is_file(&python.join("__init__.py"))
|
||||||
|
&& !system.is_file(&python.join("__init__.pyi"))
|
||||||
|
&& !roots.contains(&python)
|
||||||
|
{
|
||||||
|
// If a `./python` directory exists, include it as a source root. This is the recommended layout
|
||||||
|
// for maturin-based rust/python projects [1].
|
||||||
|
//
|
||||||
|
// https://github.com/PyO3/maturin/blob/979fe1db42bb9e58bc150fa6fc45360b377288bf/README.md?plain=1#L88-L99
|
||||||
|
tracing::debug!(
|
||||||
|
"Including `./python` in `environment.root` because a `./python` directory exists"
|
||||||
|
);
|
||||||
|
|
||||||
|
roots.push(python);
|
||||||
|
}
|
||||||
|
|
||||||
// Considering pytest test discovery conventions,
|
// Considering pytest test discovery conventions,
|
||||||
// we also include the `tests` directory if it exists and is not a package.
|
// we also include the `tests` directory if it exists and is not a package.
|
||||||
let tests_dir = project_root.join("tests");
|
let tests_dir = project_root.join("tests");
|
||||||
if system.is_directory(&tests_dir)
|
if system.is_directory(&tests_dir)
|
||||||
&& !system.is_file(&tests_dir.join("__init__.py"))
|
&& !system.is_file(&tests_dir.join("__init__.py"))
|
||||||
|
&& !system.is_file(&tests_dir.join("__init__.pyi"))
|
||||||
&& !roots.contains(&tests_dir)
|
&& !roots.contains(&tests_dir)
|
||||||
{
|
{
|
||||||
// If the `tests` directory exists and is not a package, include it as a source root.
|
// If the `tests` directory exists and is not a package, include it as a source root.
|
||||||
|
|
@ -428,7 +446,7 @@ pub struct EnvironmentOptions {
|
||||||
/// * if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path
|
/// * if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path
|
||||||
/// * otherwise, default to `.` (flat layout)
|
/// * otherwise, default to `.` (flat layout)
|
||||||
///
|
///
|
||||||
/// Besides, if a `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` file),
|
/// Besides, if a `./python` or `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` or `__init__.pyi` file),
|
||||||
/// it will also be included in the first party search path.
|
/// it will also be included in the first party search path.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
#[option(
|
#[option(
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,23 @@ def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P.
|
||||||
class Foo:
|
class Foo:
|
||||||
def method(self, x: Self):
|
def method(self, x: Self):
|
||||||
reveal_type(x) # revealed: Self@method
|
reveal_type(x) # revealed: Self@method
|
||||||
|
|
||||||
|
def ex2(msg: str):
|
||||||
|
def wrapper(fn: Callable[P, R_co]) -> Callable[P, R_co]:
|
||||||
|
def wrapped(*args: P.args, **kwargs: P.kwargs) -> R_co:
|
||||||
|
print(msg)
|
||||||
|
return fn(*args, **kwargs)
|
||||||
|
return wrapped
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
def ex3(msg: str):
|
||||||
|
P = ParamSpec("P")
|
||||||
|
def wrapper(fn: Callable[P, R_co]) -> Callable[P, R_co]:
|
||||||
|
def wrapped(*args: P.args, **kwargs: P.kwargs) -> R_co:
|
||||||
|
print(msg)
|
||||||
|
return fn(*args, **kwargs)
|
||||||
|
return wrapped
|
||||||
|
return wrapper
|
||||||
```
|
```
|
||||||
|
|
||||||
## Type expressions
|
## Type expressions
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,78 @@ def _(flag: bool):
|
||||||
reveal_type(foo()) # revealed: int
|
reveal_type(foo()) # revealed: int
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## PEP-484 convention for positional-only parameters
|
||||||
|
|
||||||
|
PEP 570, introduced in Python 3.8, added dedicated Python syntax for denoting positional-only
|
||||||
|
parameters (the `/` in a function signature). However, functions implemented in C were able to have
|
||||||
|
positional-only parameters prior to Python 3.8 (there was just no syntax for expressing this at the
|
||||||
|
Python level).
|
||||||
|
|
||||||
|
Stub files describing functions implemented in C nonetheless needed a way of expressing that certain
|
||||||
|
parameters were positional-only. In the absence of dedicated Python syntax, PEP 484 described a
|
||||||
|
convention that type checkers were expected to understand:
|
||||||
|
|
||||||
|
> Some functions are designed to take their arguments only positionally, and expect their callers
|
||||||
|
> never to use the argument’s name to provide that argument by keyword. All arguments with names
|
||||||
|
> beginning with `__` are assumed to be positional-only, except if their names also end with `__`.
|
||||||
|
|
||||||
|
While this convention is now redundant (following the implementation of PEP 570), many projects
|
||||||
|
still continue to use the old convention, so it is supported by ty as well.
|
||||||
|
|
||||||
|
```py
|
||||||
|
def f(__x: int): ...
|
||||||
|
|
||||||
|
f(1)
|
||||||
|
# error: [missing-argument]
|
||||||
|
# error: [unknown-argument]
|
||||||
|
f(__x=1)
|
||||||
|
```
|
||||||
|
|
||||||
|
But not if they follow a non-positional-only parameter:
|
||||||
|
|
||||||
|
```py
|
||||||
|
def g(x: int, __y: str): ...
|
||||||
|
|
||||||
|
g(x=1, __y="foo")
|
||||||
|
```
|
||||||
|
|
||||||
|
And also not if they both start and end with `__`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
def h(__x__: str): ...
|
||||||
|
|
||||||
|
h(__x__="foo")
|
||||||
|
```
|
||||||
|
|
||||||
|
And if *any* parameters use the new PEP-570 convention, the old convention does not apply:
|
||||||
|
|
||||||
|
```py
|
||||||
|
def i(x: str, /, __y: int): ...
|
||||||
|
|
||||||
|
i("foo", __y=42) # fine
|
||||||
|
```
|
||||||
|
|
||||||
|
And `self`/`cls` are implicitly positional-only:
|
||||||
|
|
||||||
|
```py
|
||||||
|
class C:
|
||||||
|
def method(self, __x: int): ...
|
||||||
|
@classmethod
|
||||||
|
def class_method(cls, __x: str): ...
|
||||||
|
# (the name of the first parameter is irrelevant;
|
||||||
|
# a staticmethod works the same as a free function in the global scope)
|
||||||
|
@staticmethod
|
||||||
|
def static_method(self, __x: int): ...
|
||||||
|
|
||||||
|
# error: [missing-argument]
|
||||||
|
# error: [unknown-argument]
|
||||||
|
C().method(__x=1)
|
||||||
|
# error: [missing-argument]
|
||||||
|
# error: [unknown-argument]
|
||||||
|
C.class_method(__x="1")
|
||||||
|
C.static_method("x", __x=42) # fine
|
||||||
|
```
|
||||||
|
|
||||||
## Splatted arguments
|
## Splatted arguments
|
||||||
|
|
||||||
### Unknown argument length
|
### Unknown argument length
|
||||||
|
|
@ -545,7 +617,7 @@ def _(args: str) -> None:
|
||||||
|
|
||||||
This is a regression that was highlighted by the ecosystem check, which shows that we might need to
|
This is a regression that was highlighted by the ecosystem check, which shows that we might need to
|
||||||
rethink how we perform argument expansion during overload resolution. In particular, we might need
|
rethink how we perform argument expansion during overload resolution. In particular, we might need
|
||||||
to retry both `match_parameters` _and_ `check_types` for each expansion. Currently we only retry
|
to retry both `match_parameters` *and* `check_types` for each expansion. Currently we only retry
|
||||||
`check_types`.
|
`check_types`.
|
||||||
|
|
||||||
The issue is that argument expansion might produce a splatted value with a different arity than what
|
The issue is that argument expansion might produce a splatted value with a different arity than what
|
||||||
|
|
|
||||||
|
|
@ -281,12 +281,10 @@ def if_else_exhaustive(x: A[D] | B[E] | C[F]):
|
||||||
elif isinstance(x, C):
|
elif isinstance(x, C):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
# TODO: both of these are false positives (https://github.com/astral-sh/ty/issues/456)
|
no_diagnostic_here
|
||||||
no_diagnostic_here # error: [unresolved-reference]
|
assert_never(x)
|
||||||
assert_never(x) # error: [type-assertion-failure]
|
|
||||||
|
|
||||||
# TODO: false-positive diagnostic (https://github.com/astral-sh/ty/issues/456)
|
def if_else_exhaustive_no_assertion(x: A[D] | B[E] | C[F]) -> int:
|
||||||
def if_else_exhaustive_no_assertion(x: A[D] | B[E] | C[F]) -> int: # error: [invalid-return-type]
|
|
||||||
if isinstance(x, A):
|
if isinstance(x, A):
|
||||||
return 0
|
return 0
|
||||||
elif isinstance(x, B):
|
elif isinstance(x, B):
|
||||||
|
|
|
||||||
|
|
@ -308,3 +308,88 @@ def i[T: Intersection[type[Bar], type[Baz | Spam]], U: (type[Eggs], type[Ham])](
|
||||||
|
|
||||||
return (y, z)
|
return (y, z)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Narrowing with generics
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
|
Narrowing to a generic class using `isinstance()` uses the top materialization of the generic. With
|
||||||
|
a covariant generic, this is equivalent to using the upper bound of the type parameter (by default,
|
||||||
|
`object`):
|
||||||
|
|
||||||
|
```py
|
||||||
|
class Covariant[T]:
|
||||||
|
def get(self) -> T:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _(x: object):
|
||||||
|
if isinstance(x, Covariant):
|
||||||
|
reveal_type(x) # revealed: Covariant[object]
|
||||||
|
reveal_type(x.get()) # revealed: object
|
||||||
|
```
|
||||||
|
|
||||||
|
Similarly, contravariant type parameters use their lower bound of `Never`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
class Contravariant[T]:
|
||||||
|
def push(self, x: T) -> None: ...
|
||||||
|
|
||||||
|
def _(x: object):
|
||||||
|
if isinstance(x, Contravariant):
|
||||||
|
reveal_type(x) # revealed: Contravariant[Never]
|
||||||
|
# error: [invalid-argument-type] "Argument to bound method `push` is incorrect: Expected `Never`, found `Literal[42]`"
|
||||||
|
x.push(42)
|
||||||
|
```
|
||||||
|
|
||||||
|
Invariant generics are trickiest. The top materialization, conceptually the type that includes all
|
||||||
|
instances of the generic class regardless of the type parameter, cannot be represented directly in
|
||||||
|
the type system, so we represent it with the internal `Top[]` special form.
|
||||||
|
|
||||||
|
```py
|
||||||
|
class Invariant[T]:
|
||||||
|
def push(self, x: T) -> None: ...
|
||||||
|
def get(self) -> T:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _(x: object):
|
||||||
|
if isinstance(x, Invariant):
|
||||||
|
reveal_type(x) # revealed: Top[Invariant[Unknown]]
|
||||||
|
reveal_type(x.get()) # revealed: object
|
||||||
|
# error: [invalid-argument-type] "Argument to bound method `push` is incorrect: Expected `Never`, found `Literal[42]`"
|
||||||
|
x.push(42)
|
||||||
|
```
|
||||||
|
|
||||||
|
When more complex types are involved, the `Top[]` type may get simplified away.
|
||||||
|
|
||||||
|
```py
|
||||||
|
def _(x: list[int] | set[str]):
|
||||||
|
if isinstance(x, list):
|
||||||
|
reveal_type(x) # revealed: list[int]
|
||||||
|
else:
|
||||||
|
reveal_type(x) # revealed: set[str]
|
||||||
|
```
|
||||||
|
|
||||||
|
Though if the types involved are not disjoint bases, we necessarily keep a more complex type.
|
||||||
|
|
||||||
|
```py
|
||||||
|
def _(x: Invariant[int] | Covariant[str]):
|
||||||
|
if isinstance(x, Invariant):
|
||||||
|
reveal_type(x) # revealed: Invariant[int] | (Covariant[str] & Top[Invariant[Unknown]])
|
||||||
|
else:
|
||||||
|
reveal_type(x) # revealed: Covariant[str] & ~Top[Invariant[Unknown]]
|
||||||
|
```
|
||||||
|
|
||||||
|
The behavior of `issubclass()` is similar.
|
||||||
|
|
||||||
|
```py
|
||||||
|
def _(x: type[object], y: type[object], z: type[object]):
|
||||||
|
if issubclass(x, Covariant):
|
||||||
|
reveal_type(x) # revealed: type[Covariant[object]]
|
||||||
|
if issubclass(y, Contravariant):
|
||||||
|
reveal_type(y) # revealed: type[Contravariant[Never]]
|
||||||
|
if issubclass(z, Invariant):
|
||||||
|
reveal_type(z) # revealed: type[Top[Invariant[Unknown]]]
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -413,13 +413,13 @@ To see the kinds and types of the protocol members, you can use the debugging ai
|
||||||
from ty_extensions import reveal_protocol_interface
|
from ty_extensions import reveal_protocol_interface
|
||||||
from typing import SupportsIndex, SupportsAbs, ClassVar, Iterator
|
from typing import SupportsIndex, SupportsAbs, ClassVar, Iterator
|
||||||
|
|
||||||
# revealed: {"method_member": MethodMember(`(self) -> bytes`), "x": AttributeMember(`int`), "y": PropertyMember { getter: `def y(self) -> str` }, "z": PropertyMember { getter: `def z(self) -> int`, setter: `def z(self, z: int) -> None` }}
|
# revealed: {"method_member": MethodMember(`(self, /) -> bytes`), "x": AttributeMember(`int`), "y": PropertyMember { getter: `def y(self, /) -> str` }, "z": PropertyMember { getter: `def z(self, /) -> int`, setter: `def z(self, /, z: int) -> None` }}
|
||||||
reveal_protocol_interface(Foo)
|
reveal_protocol_interface(Foo)
|
||||||
# revealed: {"__index__": MethodMember(`(self) -> int`)}
|
# revealed: {"__index__": MethodMember(`(self, /) -> int`)}
|
||||||
reveal_protocol_interface(SupportsIndex)
|
reveal_protocol_interface(SupportsIndex)
|
||||||
# revealed: {"__abs__": MethodMember(`(self) -> Unknown`)}
|
# revealed: {"__abs__": MethodMember(`(self, /) -> Unknown`)}
|
||||||
reveal_protocol_interface(SupportsAbs)
|
reveal_protocol_interface(SupportsAbs)
|
||||||
# revealed: {"__iter__": MethodMember(`(self) -> Iterator[Unknown]`), "__next__": MethodMember(`(self) -> Unknown`)}
|
# revealed: {"__iter__": MethodMember(`(self, /) -> Iterator[Unknown]`), "__next__": MethodMember(`(self, /) -> Unknown`)}
|
||||||
reveal_protocol_interface(Iterator)
|
reveal_protocol_interface(Iterator)
|
||||||
|
|
||||||
# error: [invalid-argument-type] "Invalid argument to `reveal_protocol_interface`: Only protocol classes can be passed to `reveal_protocol_interface`"
|
# error: [invalid-argument-type] "Invalid argument to `reveal_protocol_interface`: Only protocol classes can be passed to `reveal_protocol_interface`"
|
||||||
|
|
@ -439,9 +439,9 @@ do not implement any special handling for generic aliases passed to the function
|
||||||
reveal_type(get_protocol_members(SupportsAbs[int])) # revealed: frozenset[str]
|
reveal_type(get_protocol_members(SupportsAbs[int])) # revealed: frozenset[str]
|
||||||
reveal_type(get_protocol_members(Iterator[int])) # revealed: frozenset[str]
|
reveal_type(get_protocol_members(Iterator[int])) # revealed: frozenset[str]
|
||||||
|
|
||||||
# revealed: {"__abs__": MethodMember(`(self) -> int`)}
|
# revealed: {"__abs__": MethodMember(`(self, /) -> int`)}
|
||||||
reveal_protocol_interface(SupportsAbs[int])
|
reveal_protocol_interface(SupportsAbs[int])
|
||||||
# revealed: {"__iter__": MethodMember(`(self) -> Iterator[int]`), "__next__": MethodMember(`(self) -> int`)}
|
# revealed: {"__iter__": MethodMember(`(self, /) -> Iterator[int]`), "__next__": MethodMember(`(self, /) -> int`)}
|
||||||
reveal_protocol_interface(Iterator[int])
|
reveal_protocol_interface(Iterator[int])
|
||||||
|
|
||||||
class BaseProto(Protocol):
|
class BaseProto(Protocol):
|
||||||
|
|
@ -450,10 +450,10 @@ class BaseProto(Protocol):
|
||||||
class SubProto(BaseProto, Protocol):
|
class SubProto(BaseProto, Protocol):
|
||||||
def member(self) -> bool: ...
|
def member(self) -> bool: ...
|
||||||
|
|
||||||
# revealed: {"member": MethodMember(`(self) -> int`)}
|
# revealed: {"member": MethodMember(`(self, /) -> int`)}
|
||||||
reveal_protocol_interface(BaseProto)
|
reveal_protocol_interface(BaseProto)
|
||||||
|
|
||||||
# revealed: {"member": MethodMember(`(self) -> bool`)}
|
# revealed: {"member": MethodMember(`(self, /) -> bool`)}
|
||||||
reveal_protocol_interface(SubProto)
|
reveal_protocol_interface(SubProto)
|
||||||
|
|
||||||
class ProtoWithClassVar(Protocol):
|
class ProtoWithClassVar(Protocol):
|
||||||
|
|
@ -1767,7 +1767,7 @@ class Foo(Protocol):
|
||||||
def method(self) -> str: ...
|
def method(self) -> str: ...
|
||||||
|
|
||||||
def f(x: Foo):
|
def f(x: Foo):
|
||||||
reveal_type(type(x).method) # revealed: def method(self) -> str
|
reveal_type(type(x).method) # revealed: def method(self, /) -> str
|
||||||
|
|
||||||
class Bar:
|
class Bar:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
@ -1776,6 +1776,31 @@ class Bar:
|
||||||
f(Bar()) # error: [invalid-argument-type]
|
f(Bar()) # error: [invalid-argument-type]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Some protocols use the old convention (specified in PEP-484) for denoting positional-only
|
||||||
|
parameters. This is supported by ty:
|
||||||
|
|
||||||
|
```py
|
||||||
|
class HasPosOnlyDunders:
|
||||||
|
def __invert__(self, /) -> "HasPosOnlyDunders":
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __lt__(self, other, /) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
class SupportsLessThan(Protocol):
|
||||||
|
def __lt__(self, __other) -> bool: ...
|
||||||
|
|
||||||
|
class Invertable(Protocol):
|
||||||
|
# `self` and `cls` are always implicitly positional-only for methods defined in `Protocol`
|
||||||
|
# classes, even if no parameters in the method use the PEP-484 convention.
|
||||||
|
def __invert__(self) -> object: ...
|
||||||
|
|
||||||
|
static_assert(is_assignable_to(HasPosOnlyDunders, SupportsLessThan))
|
||||||
|
static_assert(is_assignable_to(HasPosOnlyDunders, Invertable))
|
||||||
|
static_assert(is_assignable_to(str, SupportsLessThan))
|
||||||
|
static_assert(is_assignable_to(int, Invertable))
|
||||||
|
```
|
||||||
|
|
||||||
## Equivalence of protocols with method or property members
|
## Equivalence of protocols with method or property members
|
||||||
|
|
||||||
Two protocols `P1` and `P2`, both with a method member `x`, are considered equivalent if the
|
Two protocols `P1` and `P2`, both with a method member `x`, are considered equivalent if the
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ error[invalid-named-tuple]: NamedTuple field without default value cannot follow
|
||||||
| --------------------- Earlier field `altitude` defined here with a default value
|
| --------------------- Earlier field `altitude` defined here with a default value
|
||||||
5 | # error: [invalid-named-tuple] "NamedTuple field without default value cannot follow field(s) with default value(s): Field `latitud…
|
5 | # error: [invalid-named-tuple] "NamedTuple field without default value cannot follow field(s) with default value(s): Field `latitud…
|
||||||
6 | latitude: float
|
6 | latitude: float
|
||||||
| ^^^^^^^^ Field `latitude` defined here without a default value
|
| ^^^^^^^^^^^^^^^ Field `latitude` defined here without a default value
|
||||||
7 | # error: [invalid-named-tuple] "NamedTuple field without default value cannot follow field(s) with default value(s): Field `longitu…
|
7 | # error: [invalid-named-tuple] "NamedTuple field without default value cannot follow field(s) with default value(s): Field `longitu…
|
||||||
8 | longitude: float
|
8 | longitude: float
|
||||||
|
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ error[invalid-named-tuple]: NamedTuple field without default value cannot follow
|
||||||
6 | latitude: float
|
6 | latitude: float
|
||||||
7 | # error: [invalid-named-tuple] "NamedTuple field without default value cannot follow field(s) with default value(s): Field `longit…
|
7 | # error: [invalid-named-tuple] "NamedTuple field without default value cannot follow field(s) with default value(s): Field `longit…
|
||||||
8 | longitude: float
|
8 | longitude: float
|
||||||
| ^^^^^^^^^ Field `longitude` defined here without a default value
|
| ^^^^^^^^^^^^^^^^ Field `longitude` defined here without a default value
|
||||||
9 |
|
9 |
|
||||||
10 | class StrangeLocation(NamedTuple):
|
10 | class StrangeLocation(NamedTuple):
|
||||||
|
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ error[invalid-named-tuple]: NamedTuple field without default value cannot follow
|
||||||
14 | altitude: float = 0.0
|
14 | altitude: float = 0.0
|
||||||
| --------------------- Earlier field `altitude` defined here with a default value
|
| --------------------- Earlier field `altitude` defined here with a default value
|
||||||
15 | latitude: float # error: [invalid-named-tuple]
|
15 | latitude: float # error: [invalid-named-tuple]
|
||||||
| ^^^^^^^^ Field `latitude` defined here without a default value
|
| ^^^^^^^^^^^^^^^ Field `latitude` defined here without a default value
|
||||||
16 | longitude: float # error: [invalid-named-tuple]
|
16 | longitude: float # error: [invalid-named-tuple]
|
||||||
|
|
|
|
||||||
info: rule `invalid-named-tuple` is enabled by default
|
info: rule `invalid-named-tuple` is enabled by default
|
||||||
|
|
@ -100,7 +100,7 @@ error[invalid-named-tuple]: NamedTuple field without default value cannot follow
|
||||||
| --------------------- Earlier field `altitude` defined here with a default value
|
| --------------------- Earlier field `altitude` defined here with a default value
|
||||||
15 | latitude: float # error: [invalid-named-tuple]
|
15 | latitude: float # error: [invalid-named-tuple]
|
||||||
16 | longitude: float # error: [invalid-named-tuple]
|
16 | longitude: float # error: [invalid-named-tuple]
|
||||||
| ^^^^^^^^^ Field `longitude` defined here without a default value
|
| ^^^^^^^^^^^^^^^^ Field `longitude` defined here without a default value
|
||||||
17 |
|
17 |
|
||||||
18 | class VeryStrangeLocation(NamedTuple):
|
18 | class VeryStrangeLocation(NamedTuple):
|
||||||
|
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ error[invalid-named-tuple]: NamedTuple field without default value cannot follow
|
||||||
18 | class VeryStrangeLocation(NamedTuple):
|
18 | class VeryStrangeLocation(NamedTuple):
|
||||||
19 | altitude: float = 0.0
|
19 | altitude: float = 0.0
|
||||||
20 | latitude: float # error: [invalid-named-tuple]
|
20 | latitude: float # error: [invalid-named-tuple]
|
||||||
| ^^^^^^^^ Field `latitude` defined here without a default value
|
| ^^^^^^^^^^^^^^^ Field `latitude` defined here without a default value
|
||||||
21 | longitude: float # error: [invalid-named-tuple]
|
21 | longitude: float # error: [invalid-named-tuple]
|
||||||
22 | altitude: float = 0.0
|
22 | altitude: float = 0.0
|
||||||
|
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ error[invalid-named-tuple]: NamedTuple field without default value cannot follow
|
||||||
19 | altitude: float = 0.0
|
19 | altitude: float = 0.0
|
||||||
20 | latitude: float # error: [invalid-named-tuple]
|
20 | latitude: float # error: [invalid-named-tuple]
|
||||||
21 | longitude: float # error: [invalid-named-tuple]
|
21 | longitude: float # error: [invalid-named-tuple]
|
||||||
| ^^^^^^^^^ Field `longitude` defined here without a default value
|
| ^^^^^^^^^^^^^^^^ Field `longitude` defined here without a default value
|
||||||
22 | altitude: float = 0.0
|
22 | altitude: float = 0.0
|
||||||
|
|
|
|
||||||
info: Earlier field `altitude` was defined with a default value
|
info: Earlier field `altitude` was defined with a default value
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,14 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/typed_dict.md
|
||||||
23 |
|
23 |
|
||||||
24 | def write_to_non_literal_string_key(person: Person, str_key: str):
|
24 | def write_to_non_literal_string_key(person: Person, str_key: str):
|
||||||
25 | person[str_key] = "Alice" # error: [invalid-key]
|
25 | person[str_key] = "Alice" # error: [invalid-key]
|
||||||
|
26 | from typing_extensions import ReadOnly
|
||||||
|
27 |
|
||||||
|
28 | class Employee(TypedDict):
|
||||||
|
29 | id: ReadOnly[int]
|
||||||
|
30 | name: str
|
||||||
|
31 |
|
||||||
|
32 | def write_to_readonly_key(employee: Employee):
|
||||||
|
33 | employee["id"] = 42 # error: [invalid-assignment]
|
||||||
```
|
```
|
||||||
|
|
||||||
# Diagnostics
|
# Diagnostics
|
||||||
|
|
@ -100,6 +108,16 @@ error[invalid-assignment]: Invalid assignment to key "age" with declared type `i
|
||||||
20 |
|
20 |
|
||||||
21 | def write_to_non_existing_key(person: Person):
|
21 | def write_to_non_existing_key(person: Person):
|
||||||
|
|
|
|
||||||
|
info: Item declaration
|
||||||
|
--> src/mdtest_snippet.py:5:5
|
||||||
|
|
|
||||||
|
3 | class Person(TypedDict):
|
||||||
|
4 | name: str
|
||||||
|
5 | age: int | None
|
||||||
|
| --------------- Item declared here
|
||||||
|
6 |
|
||||||
|
7 | def access_invalid_literal_string_key(person: Person):
|
||||||
|
|
|
||||||
info: rule `invalid-assignment` is enabled by default
|
info: rule `invalid-assignment` is enabled by default
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
@ -127,7 +145,30 @@ error[invalid-key]: Cannot access `Person` with a key of type `str`. Only string
|
||||||
24 | def write_to_non_literal_string_key(person: Person, str_key: str):
|
24 | def write_to_non_literal_string_key(person: Person, str_key: str):
|
||||||
25 | person[str_key] = "Alice" # error: [invalid-key]
|
25 | person[str_key] = "Alice" # error: [invalid-key]
|
||||||
| ^^^^^^^
|
| ^^^^^^^
|
||||||
|
26 | from typing_extensions import ReadOnly
|
||||||
|
|
|
|
||||||
info: rule `invalid-key` is enabled by default
|
info: rule `invalid-key` is enabled by default
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-assignment]: Cannot assign to key "id" on TypedDict `Employee`
|
||||||
|
--> src/mdtest_snippet.py:33:5
|
||||||
|
|
|
||||||
|
32 | def write_to_readonly_key(employee: Employee):
|
||||||
|
33 | employee["id"] = 42 # error: [invalid-assignment]
|
||||||
|
| -------- ^^^^ key is marked read-only
|
||||||
|
| |
|
||||||
|
| TypedDict `Employee`
|
||||||
|
|
|
||||||
|
info: Item declaration
|
||||||
|
--> src/mdtest_snippet.py:29:5
|
||||||
|
|
|
||||||
|
28 | class Employee(TypedDict):
|
||||||
|
29 | id: ReadOnly[int]
|
||||||
|
| ----------------- Read-only item declared here
|
||||||
|
30 | name: str
|
||||||
|
|
|
||||||
|
info: rule `invalid-assignment` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -444,8 +444,7 @@ def _(person: Person, unknown_key: Any):
|
||||||
|
|
||||||
## `ReadOnly`
|
## `ReadOnly`
|
||||||
|
|
||||||
`ReadOnly` is not supported yet, but this test makes sure that we do not emit any false positive
|
Assignments to keys that are marked `ReadOnly` will produce an error:
|
||||||
diagnostics:
|
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from typing_extensions import TypedDict, ReadOnly, Required
|
from typing_extensions import TypedDict, ReadOnly, Required
|
||||||
|
|
@ -458,10 +457,26 @@ class Person(TypedDict, total=False):
|
||||||
alice: Person = {"id": 1, "name": "Alice", "age": 30}
|
alice: Person = {"id": 1, "name": "Alice", "age": 30}
|
||||||
alice["age"] = 31 # okay
|
alice["age"] = 31 # okay
|
||||||
|
|
||||||
# TODO: this should be an error
|
# error: [invalid-assignment] "Cannot assign to key "id" on TypedDict `Person`: key is marked read-only"
|
||||||
alice["id"] = 2
|
alice["id"] = 2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This also works if all fields on a `TypedDict` are `ReadOnly`, in which case we synthesize a
|
||||||
|
`__setitem__` method with a `key` type of `Never`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
class Config(TypedDict):
|
||||||
|
host: ReadOnly[str]
|
||||||
|
port: ReadOnly[int]
|
||||||
|
|
||||||
|
config: Config = {"host": "localhost", "port": 8080}
|
||||||
|
|
||||||
|
# error: [invalid-assignment] "Cannot assign to key "host" on TypedDict `Config`: key is marked read-only"
|
||||||
|
config["host"] = "127.0.0.1"
|
||||||
|
# error: [invalid-assignment] "Cannot assign to key "port" on TypedDict `Config`: key is marked read-only"
|
||||||
|
config["port"] = 80
|
||||||
|
```
|
||||||
|
|
||||||
## Methods on `TypedDict`
|
## Methods on `TypedDict`
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
|
@ -846,6 +861,19 @@ def write_to_non_literal_string_key(person: Person, str_key: str):
|
||||||
person[str_key] = "Alice" # error: [invalid-key]
|
person[str_key] = "Alice" # error: [invalid-key]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Assignment to `ReadOnly` keys:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing_extensions import ReadOnly
|
||||||
|
|
||||||
|
class Employee(TypedDict):
|
||||||
|
id: ReadOnly[int]
|
||||||
|
name: str
|
||||||
|
|
||||||
|
def write_to_readonly_key(employee: Employee):
|
||||||
|
employee["id"] = 42 # error: [invalid-assignment]
|
||||||
|
```
|
||||||
|
|
||||||
## Import aliases
|
## Import aliases
|
||||||
|
|
||||||
`TypedDict` can be imported with aliases and should work correctly:
|
`TypedDict` can be imported with aliases and should work correctly:
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ django-stubs
|
||||||
downforeveryone
|
downforeveryone
|
||||||
dragonchain
|
dragonchain
|
||||||
dulwich
|
dulwich
|
||||||
|
egglog-python
|
||||||
flake8
|
flake8
|
||||||
flake8-pyi
|
flake8-pyi
|
||||||
freqtrade
|
freqtrade
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,12 @@ pub struct AstNodeRef<T> {
|
||||||
_node: PhantomData<T>,
|
_node: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> AstNodeRef<T> {
|
||||||
|
pub(crate) fn index(&self) -> NodeIndex {
|
||||||
|
self.index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> AstNodeRef<T>
|
impl<T> AstNodeRef<T>
|
||||||
where
|
where
|
||||||
T: HasNodeIndex + Ranged + PartialEq + Debug,
|
T: HasNodeIndex + Ranged + PartialEq + Debug,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use ruff_python_ast::{HasNodeIndex, NodeIndex};
|
use ruff_python_ast::{HasNodeIndex, NodeIndex};
|
||||||
|
|
||||||
|
use crate::ast_node_ref::AstNodeRef;
|
||||||
|
|
||||||
/// Compact key for a node for use in a hash map.
|
/// Compact key for a node for use in a hash map.
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||||
pub(super) struct NodeKey(NodeIndex);
|
pub(super) struct NodeKey(NodeIndex);
|
||||||
|
|
@ -11,4 +13,8 @@ impl NodeKey {
|
||||||
{
|
{
|
||||||
NodeKey(node.node_index().load())
|
NodeKey(node.node_index().load())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn from_node_ref<T>(node_ref: &AstNodeRef<T>) -> Self {
|
||||||
|
NodeKey(node_ref.index())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -486,6 +486,9 @@ type DeclaredTypeAndConflictingTypes<'db> = (
|
||||||
pub(crate) struct PlaceFromDeclarationsResult<'db> {
|
pub(crate) struct PlaceFromDeclarationsResult<'db> {
|
||||||
place_and_quals: PlaceAndQualifiers<'db>,
|
place_and_quals: PlaceAndQualifiers<'db>,
|
||||||
conflicting_types: Option<Box<indexmap::set::Slice<Type<'db>>>>,
|
conflicting_types: Option<Box<indexmap::set::Slice<Type<'db>>>>,
|
||||||
|
/// Contains `Some(declaration)` if the declared type originates from exactly one declaration.
|
||||||
|
/// This field is used for backreferences in diagnostics.
|
||||||
|
pub(crate) single_declaration: Option<Definition<'db>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> PlaceFromDeclarationsResult<'db> {
|
impl<'db> PlaceFromDeclarationsResult<'db> {
|
||||||
|
|
@ -496,6 +499,7 @@ impl<'db> PlaceFromDeclarationsResult<'db> {
|
||||||
PlaceFromDeclarationsResult {
|
PlaceFromDeclarationsResult {
|
||||||
place_and_quals,
|
place_and_quals,
|
||||||
conflicting_types: Some(conflicting_types),
|
conflicting_types: Some(conflicting_types),
|
||||||
|
single_declaration: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -513,21 +517,6 @@ impl<'db> PlaceFromDeclarationsResult<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> From<PlaceAndQualifiers<'db>> for PlaceFromDeclarationsResult<'db> {
|
|
||||||
fn from(place_and_quals: PlaceAndQualifiers<'db>) -> Self {
|
|
||||||
PlaceFromDeclarationsResult {
|
|
||||||
place_and_quals,
|
|
||||||
conflicting_types: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'db> From<Place<'db>> for PlaceFromDeclarationsResult<'db> {
|
|
||||||
fn from(place: Place<'db>) -> Self {
|
|
||||||
PlaceFromDeclarationsResult::from(PlaceAndQualifiers::from(place))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A type with declaredness information, and a set of type qualifiers.
|
/// A type with declaredness information, and a set of type qualifiers.
|
||||||
///
|
///
|
||||||
/// This is used to represent the result of looking up the declared type. Consider this
|
/// This is used to represent the result of looking up the declared type. Consider this
|
||||||
|
|
@ -589,6 +578,11 @@ impl<'db> PlaceAndQualifiers<'db> {
|
||||||
self.qualifiers.contains(TypeQualifiers::NOT_REQUIRED)
|
self.qualifiers.contains(TypeQualifiers::NOT_REQUIRED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the place has a `ReadOnly` type qualifier.
|
||||||
|
pub(crate) fn is_read_only(&self) -> bool {
|
||||||
|
self.qualifiers.contains(TypeQualifiers::READ_ONLY)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns `Some(…)` if the place is qualified with `typing.Final` without a specified type.
|
/// Returns `Some(…)` if the place is qualified with `typing.Final` without a specified type.
|
||||||
pub(crate) fn is_bare_final(&self) -> Option<TypeQualifiers> {
|
pub(crate) fn is_bare_final(&self) -> Option<TypeQualifiers> {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -1211,6 +1205,8 @@ fn place_from_declarations_impl<'db>(
|
||||||
let reachability_constraints = declarations.reachability_constraints;
|
let reachability_constraints = declarations.reachability_constraints;
|
||||||
let boundness_analysis = declarations.boundness_analysis;
|
let boundness_analysis = declarations.boundness_analysis;
|
||||||
let mut declarations = declarations.peekable();
|
let mut declarations = declarations.peekable();
|
||||||
|
let mut first_declaration = None;
|
||||||
|
let mut exactly_one_declaration = false;
|
||||||
|
|
||||||
let is_non_exported = |declaration: Definition<'db>| {
|
let is_non_exported = |declaration: Definition<'db>| {
|
||||||
requires_explicit_reexport.is_yes() && !is_reexported(db, declaration)
|
requires_explicit_reexport.is_yes() && !is_reexported(db, declaration)
|
||||||
|
|
@ -1241,6 +1237,13 @@ fn place_from_declarations_impl<'db>(
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if first_declaration.is_none() {
|
||||||
|
first_declaration = Some(declaration);
|
||||||
|
exactly_one_declaration = true;
|
||||||
|
} else {
|
||||||
|
exactly_one_declaration = false;
|
||||||
|
}
|
||||||
|
|
||||||
let static_reachability =
|
let static_reachability =
|
||||||
reachability_constraints.evaluate(db, predicates, reachability_constraint);
|
reachability_constraints.evaluate(db, predicates, reachability_constraint);
|
||||||
|
|
||||||
|
|
@ -1297,10 +1300,18 @@ fn place_from_declarations_impl<'db>(
|
||||||
if let Some(conflicting) = conflicting {
|
if let Some(conflicting) = conflicting {
|
||||||
PlaceFromDeclarationsResult::conflict(place_and_quals, conflicting)
|
PlaceFromDeclarationsResult::conflict(place_and_quals, conflicting)
|
||||||
} else {
|
} else {
|
||||||
place_and_quals.into()
|
PlaceFromDeclarationsResult {
|
||||||
|
place_and_quals,
|
||||||
|
conflicting_types: None,
|
||||||
|
single_declaration: first_declaration.filter(|_| exactly_one_declaration),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Place::Unbound.into()
|
PlaceFromDeclarationsResult {
|
||||||
|
place_and_quals: Place::Unbound.into(),
|
||||||
|
conflicting_types: None,
|
||||||
|
single_declaration: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,6 @@ pub(crate) fn attribute_scopes<'db, 's>(
|
||||||
class_body_scope: ScopeId<'db>,
|
class_body_scope: ScopeId<'db>,
|
||||||
) -> impl Iterator<Item = FileScopeId> + use<'s, 'db> {
|
) -> impl Iterator<Item = FileScopeId> + use<'s, 'db> {
|
||||||
let file = class_body_scope.file(db);
|
let file = class_body_scope.file(db);
|
||||||
let module = parsed_module(db, file).load(db);
|
|
||||||
let index = semantic_index(db, file);
|
let index = semantic_index(db, file);
|
||||||
let class_scope_id = class_body_scope.file_scope_id(db);
|
let class_scope_id = class_body_scope.file_scope_id(db);
|
||||||
|
|
||||||
|
|
@ -175,7 +174,7 @@ pub(crate) fn attribute_scopes<'db, 's>(
|
||||||
(child_scope_id, scope)
|
(child_scope_id, scope)
|
||||||
};
|
};
|
||||||
|
|
||||||
function_scope.node().as_function(&module)?;
|
function_scope.node().as_function()?;
|
||||||
Some(function_scope_id)
|
Some(function_scope_id)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -332,6 +331,39 @@ impl<'db> SemanticIndex<'db> {
|
||||||
Some(&self.scopes[self.parent_scope_id(scope_id)?])
|
Some(&self.scopes[self.parent_scope_id(scope_id)?])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the [`Definition`] of the class enclosing this method, given the
|
||||||
|
/// method's body scope, or `None` if it is not a method.
|
||||||
|
pub(crate) fn class_definition_of_method(
|
||||||
|
&self,
|
||||||
|
function_body_scope: FileScopeId,
|
||||||
|
) -> Option<Definition<'db>> {
|
||||||
|
let current_scope = self.scope(function_body_scope);
|
||||||
|
if current_scope.kind() != ScopeKind::Function {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let parent_scope_id = current_scope.parent()?;
|
||||||
|
let parent_scope = self.scope(parent_scope_id);
|
||||||
|
|
||||||
|
let class_scope = match parent_scope.kind() {
|
||||||
|
ScopeKind::Class => parent_scope,
|
||||||
|
ScopeKind::TypeParams => {
|
||||||
|
let class_scope_id = parent_scope.parent()?;
|
||||||
|
let potentially_class_scope = self.scope(class_scope_id);
|
||||||
|
|
||||||
|
match potentially_class_scope.kind() {
|
||||||
|
ScopeKind::Class => potentially_class_scope,
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
class_scope
|
||||||
|
.node()
|
||||||
|
.as_class()
|
||||||
|
.map(|node_ref| self.expect_single_definition(node_ref))
|
||||||
|
}
|
||||||
|
|
||||||
fn is_scope_reachable(&self, db: &'db dyn Db, scope_id: FileScopeId) -> bool {
|
fn is_scope_reachable(&self, db: &'db dyn Db, scope_id: FileScopeId) -> bool {
|
||||||
self.parent_scope_id(scope_id)
|
self.parent_scope_id(scope_id)
|
||||||
.is_none_or(|parent_scope_id| {
|
.is_none_or(|parent_scope_id| {
|
||||||
|
|
|
||||||
|
|
@ -2644,7 +2644,7 @@ impl SemanticSyntaxContext for SemanticIndexBuilder<'_, '_> {
|
||||||
match scope.kind() {
|
match scope.kind() {
|
||||||
ScopeKind::Class | ScopeKind::Lambda => return false,
|
ScopeKind::Class | ScopeKind::Lambda => return false,
|
||||||
ScopeKind::Function => {
|
ScopeKind::Function => {
|
||||||
return scope.node().expect_function(self.module).is_async;
|
return scope.node().expect_function().node(self.module).is_async;
|
||||||
}
|
}
|
||||||
ScopeKind::Comprehension
|
ScopeKind::Comprehension
|
||||||
| ScopeKind::Module
|
| ScopeKind::Module
|
||||||
|
|
|
||||||
|
|
@ -769,13 +769,14 @@ impl DefinitionKind<'_> {
|
||||||
target_range.cover(value_range)
|
target_range.cover(value_range)
|
||||||
}
|
}
|
||||||
DefinitionKind::AnnotatedAssignment(assign) => {
|
DefinitionKind::AnnotatedAssignment(assign) => {
|
||||||
let target_range = assign.target.node(module).range();
|
let mut full_range = assign.target.node(module).range();
|
||||||
|
full_range = full_range.cover(assign.annotation.node(module).range());
|
||||||
|
|
||||||
if let Some(ref value) = assign.value {
|
if let Some(ref value) = assign.value {
|
||||||
let value_range = value.node(module).range();
|
full_range = full_range.cover(value.node(module).range());
|
||||||
target_range.cover(value_range)
|
|
||||||
} else {
|
|
||||||
target_range
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
full_range
|
||||||
}
|
}
|
||||||
DefinitionKind::AugmentedAssignment(aug_assign) => aug_assign.node(module).range(),
|
DefinitionKind::AugmentedAssignment(aug_assign) => aug_assign.node(module).range(),
|
||||||
DefinitionKind::For(for_stmt) => for_stmt.target.node(module).range(),
|
DefinitionKind::For(for_stmt) => for_stmt.target.node(module).range(),
|
||||||
|
|
@ -1226,3 +1227,12 @@ impl From<&ast::TypeParamTypeVarTuple> for DefinitionNodeKey {
|
||||||
Self(NodeKey::from_node(value))
|
Self(NodeKey::from_node(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> From<&AstNodeRef<T>> for DefinitionNodeKey
|
||||||
|
where
|
||||||
|
for<'a> &'a T: Into<DefinitionNodeKey>,
|
||||||
|
{
|
||||||
|
fn from(value: &AstNodeRef<T>) -> Self {
|
||||||
|
Self(NodeKey::from_node_ref(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -397,52 +397,38 @@ impl NodeWithScopeKind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn expect_class<'ast>(
|
pub(crate) fn as_class(&self) -> Option<&AstNodeRef<ast::StmtClassDef>> {
|
||||||
&self,
|
|
||||||
module: &'ast ParsedModuleRef,
|
|
||||||
) -> &'ast ast::StmtClassDef {
|
|
||||||
match self {
|
match self {
|
||||||
Self::Class(class) => class.node(module),
|
Self::Class(class) => Some(class),
|
||||||
_ => panic!("expected class"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn as_class<'ast>(
|
|
||||||
&self,
|
|
||||||
module: &'ast ParsedModuleRef,
|
|
||||||
) -> Option<&'ast ast::StmtClassDef> {
|
|
||||||
match self {
|
|
||||||
Self::Class(class) => Some(class.node(module)),
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn expect_function<'ast>(
|
pub(crate) fn expect_class(&self) -> &AstNodeRef<ast::StmtClassDef> {
|
||||||
&self,
|
self.as_class().expect("expected class")
|
||||||
module: &'ast ParsedModuleRef,
|
|
||||||
) -> &'ast ast::StmtFunctionDef {
|
|
||||||
self.as_function(module).expect("expected function")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn expect_type_alias<'ast>(
|
pub(crate) fn as_function(&self) -> Option<&AstNodeRef<ast::StmtFunctionDef>> {
|
||||||
&self,
|
|
||||||
module: &'ast ParsedModuleRef,
|
|
||||||
) -> &'ast ast::StmtTypeAlias {
|
|
||||||
match self {
|
match self {
|
||||||
Self::TypeAlias(type_alias) => type_alias.node(module),
|
Self::Function(function) => Some(function),
|
||||||
_ => panic!("expected type alias"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn as_function<'ast>(
|
|
||||||
&self,
|
|
||||||
module: &'ast ParsedModuleRef,
|
|
||||||
) -> Option<&'ast ast::StmtFunctionDef> {
|
|
||||||
match self {
|
|
||||||
Self::Function(function) => Some(function.node(module)),
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn expect_function(&self) -> &AstNodeRef<ast::StmtFunctionDef> {
|
||||||
|
self.as_function().expect("expected function")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn as_type_alias(&self) -> Option<&AstNodeRef<ast::StmtTypeAlias>> {
|
||||||
|
match self {
|
||||||
|
Self::TypeAlias(type_alias) => Some(type_alias),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn expect_type_alias(&self) -> &AstNodeRef<ast::StmtTypeAlias> {
|
||||||
|
self.as_type_alias().expect("expected type alias")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||||
|
|
|
||||||
|
|
@ -5649,7 +5649,7 @@ impl<'db> Type<'db> {
|
||||||
SpecialFormType::TypingSelf => {
|
SpecialFormType::TypingSelf => {
|
||||||
let module = parsed_module(db, scope_id.file(db)).load(db);
|
let module = parsed_module(db, scope_id.file(db)).load(db);
|
||||||
let index = semantic_index(db, scope_id.file(db));
|
let index = semantic_index(db, scope_id.file(db));
|
||||||
let Some(class) = nearest_enclosing_class(db, index, scope_id, &module) else {
|
let Some(class) = nearest_enclosing_class(db, index, scope_id) else {
|
||||||
return Err(InvalidTypeExpressionError {
|
return Err(InvalidTypeExpressionError {
|
||||||
fallback_type: Type::unknown(),
|
fallback_type: Type::unknown(),
|
||||||
invalid_expressions: smallvec::smallvec_inline![
|
invalid_expressions: smallvec::smallvec_inline![
|
||||||
|
|
@ -6551,6 +6551,7 @@ impl<'db> VarianceInferable<'db> for Type<'db> {
|
||||||
}
|
}
|
||||||
Type::GenericAlias(generic_alias) => generic_alias.variance_of(db, typevar),
|
Type::GenericAlias(generic_alias) => generic_alias.variance_of(db, typevar),
|
||||||
Type::Callable(callable_type) => callable_type.signatures(db).variance_of(db, typevar),
|
Type::Callable(callable_type) => callable_type.signatures(db).variance_of(db, typevar),
|
||||||
|
// A type variable is always covariant in itself.
|
||||||
Type::TypeVar(other_typevar) | Type::NonInferableTypeVar(other_typevar)
|
Type::TypeVar(other_typevar) | Type::NonInferableTypeVar(other_typevar)
|
||||||
if other_typevar == typevar =>
|
if other_typevar == typevar =>
|
||||||
{
|
{
|
||||||
|
|
@ -6560,11 +6561,19 @@ impl<'db> VarianceInferable<'db> for Type<'db> {
|
||||||
Type::ProtocolInstance(protocol_instance_type) => {
|
Type::ProtocolInstance(protocol_instance_type) => {
|
||||||
protocol_instance_type.variance_of(db, typevar)
|
protocol_instance_type.variance_of(db, typevar)
|
||||||
}
|
}
|
||||||
|
// unions are covariant in their disjuncts
|
||||||
Type::Union(union_type) => union_type
|
Type::Union(union_type) => union_type
|
||||||
.elements(db)
|
.elements(db)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ty| ty.variance_of(db, typevar))
|
.map(|ty| ty.variance_of(db, typevar))
|
||||||
.collect(),
|
.collect(),
|
||||||
|
|
||||||
|
// Products are covariant in their conjuncts. For negative
|
||||||
|
// conjuncts, they're contravariant. To see this, suppose we have
|
||||||
|
// `B` a subtype of `A`. A value of type `~B` could be some non-`B`
|
||||||
|
// `A`, and so is not assignable to `~A`. On the other hand, a value
|
||||||
|
// of type `~A` excludes all `A`s, and thus all `B`s, and so _is_
|
||||||
|
// assignable to `~B`.
|
||||||
Type::Intersection(intersection_type) => intersection_type
|
Type::Intersection(intersection_type) => intersection_type
|
||||||
.positive(db)
|
.positive(db)
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -9355,9 +9364,7 @@ fn walk_pep_695_type_alias<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||||
impl<'db> PEP695TypeAliasType<'db> {
|
impl<'db> PEP695TypeAliasType<'db> {
|
||||||
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
|
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
|
||||||
let scope = self.rhs_scope(db);
|
let scope = self.rhs_scope(db);
|
||||||
let module = parsed_module(db, scope.file(db)).load(db);
|
let type_alias_stmt_node = scope.node(db).expect_type_alias();
|
||||||
let type_alias_stmt_node = scope.node(db).expect_type_alias(&module);
|
|
||||||
|
|
||||||
semantic_index(db, scope.file(db)).expect_single_definition(type_alias_stmt_node)
|
semantic_index(db, scope.file(db)).expect_single_definition(type_alias_stmt_node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -9365,9 +9372,9 @@ impl<'db> PEP695TypeAliasType<'db> {
|
||||||
pub(crate) fn value_type(self, db: &'db dyn Db) -> Type<'db> {
|
pub(crate) fn value_type(self, db: &'db dyn Db) -> Type<'db> {
|
||||||
let scope = self.rhs_scope(db);
|
let scope = self.rhs_scope(db);
|
||||||
let module = parsed_module(db, scope.file(db)).load(db);
|
let module = parsed_module(db, scope.file(db)).load(db);
|
||||||
let type_alias_stmt_node = scope.node(db).expect_type_alias(&module);
|
let type_alias_stmt_node = scope.node(db).expect_type_alias();
|
||||||
let definition = self.definition(db);
|
let definition = self.definition(db);
|
||||||
definition_expression_type(db, definition, &type_alias_stmt_node.value)
|
definition_expression_type(db, definition, &type_alias_stmt_node.node(&module).value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normalized_impl(self, _db: &'db dyn Db, _visitor: &NormalizedVisitor<'db>) -> Self {
|
fn normalized_impl(self, _db: &'db dyn Db, _visitor: &NormalizedVisitor<'db>) -> Self {
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,10 @@ use super::{
|
||||||
use crate::FxOrderMap;
|
use crate::FxOrderMap;
|
||||||
use crate::module_resolver::KnownModule;
|
use crate::module_resolver::KnownModule;
|
||||||
use crate::semantic_index::definition::{Definition, DefinitionState};
|
use crate::semantic_index::definition::{Definition, DefinitionState};
|
||||||
use crate::semantic_index::place::ScopedPlaceId;
|
|
||||||
use crate::semantic_index::scope::NodeWithScopeKind;
|
use crate::semantic_index::scope::NodeWithScopeKind;
|
||||||
use crate::semantic_index::symbol::Symbol;
|
use crate::semantic_index::symbol::Symbol;
|
||||||
use crate::semantic_index::{
|
use crate::semantic_index::{
|
||||||
BindingWithConstraints, DeclarationWithConstraint, SemanticIndex, attribute_declarations,
|
DeclarationWithConstraint, SemanticIndex, attribute_declarations, attribute_scopes,
|
||||||
attribute_scopes,
|
|
||||||
};
|
};
|
||||||
use crate::types::constraints::{ConstraintSet, Constraints, IteratorConstraintsExtension};
|
use crate::types::constraints::{ConstraintSet, Constraints, IteratorConstraintsExtension};
|
||||||
use crate::types::context::InferContext;
|
use crate::types::context::InferContext;
|
||||||
|
|
@ -29,10 +27,10 @@ use crate::types::typed_dict::typed_dict_params_from_class_def;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
ApplyTypeMappingVisitor, Binding, BoundSuperError, BoundSuperType, CallableType,
|
ApplyTypeMappingVisitor, Binding, BoundSuperError, BoundSuperType, CallableType,
|
||||||
DataclassParams, DeprecatedInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
|
DataclassParams, DeprecatedInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
|
||||||
IsEquivalentVisitor, KnownInstanceType, ManualPEP695TypeAliasType, NormalizedVisitor,
|
IsEquivalentVisitor, KnownInstanceType, ManualPEP695TypeAliasType, MaterializationKind,
|
||||||
PropertyInstanceType, StringLiteralType, TypeAliasType, TypeMapping, TypeRelation,
|
NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType, TypeMapping,
|
||||||
TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypedDictParams, UnionBuilder,
|
TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypedDictParams,
|
||||||
VarianceInferable, declaration_type, infer_definition_types,
|
UnionBuilder, VarianceInferable, declaration_type, infer_definition_types,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
Db, FxIndexMap, FxOrderSet, Program,
|
Db, FxIndexMap, FxOrderSet, Program,
|
||||||
|
|
@ -1271,6 +1269,8 @@ pub(crate) enum FieldKind<'db> {
|
||||||
TypedDict {
|
TypedDict {
|
||||||
/// Whether this field is required
|
/// Whether this field is required
|
||||||
is_required: bool,
|
is_required: bool,
|
||||||
|
/// Whether this field is marked read-only
|
||||||
|
is_read_only: bool,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1281,6 +1281,9 @@ pub(crate) struct Field<'db> {
|
||||||
pub(crate) declared_ty: Type<'db>,
|
pub(crate) declared_ty: Type<'db>,
|
||||||
/// Kind-specific metadata for this field
|
/// Kind-specific metadata for this field
|
||||||
pub(crate) kind: FieldKind<'db>,
|
pub(crate) kind: FieldKind<'db>,
|
||||||
|
/// The original declaration of this field, if there is exactly one.
|
||||||
|
/// This field is used for backreferences in diagnostics.
|
||||||
|
pub(crate) single_declaration: Option<Definition<'db>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Field<'_> {
|
impl Field<'_> {
|
||||||
|
|
@ -1292,7 +1295,14 @@ impl Field<'_> {
|
||||||
FieldKind::Dataclass {
|
FieldKind::Dataclass {
|
||||||
init, default_ty, ..
|
init, default_ty, ..
|
||||||
} => default_ty.is_none() && *init,
|
} => default_ty.is_none() && *init,
|
||||||
FieldKind::TypedDict { is_required } => *is_required,
|
FieldKind::TypedDict { is_required, .. } => *is_required,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) const fn is_read_only(&self) -> bool {
|
||||||
|
match &self.kind {
|
||||||
|
FieldKind::TypedDict { is_read_only, .. } => *is_read_only,
|
||||||
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1393,7 +1403,7 @@ impl<'db> ClassLiteral<'db> {
|
||||||
let scope = self.body_scope(db);
|
let scope = self.body_scope(db);
|
||||||
let file = scope.file(db);
|
let file = scope.file(db);
|
||||||
let parsed = parsed_module(db, file).load(db);
|
let parsed = parsed_module(db, file).load(db);
|
||||||
let class_def_node = scope.node(db).expect_class(&parsed);
|
let class_def_node = scope.node(db).expect_class().node(&parsed);
|
||||||
class_def_node.type_params.as_ref().map(|type_params| {
|
class_def_node.type_params.as_ref().map(|type_params| {
|
||||||
let index = semantic_index(db, scope.file(db));
|
let index = semantic_index(db, scope.file(db));
|
||||||
let definition = index.expect_single_definition(class_def_node);
|
let definition = index.expect_single_definition(class_def_node);
|
||||||
|
|
@ -1435,14 +1445,13 @@ impl<'db> ClassLiteral<'db> {
|
||||||
/// query depends on the AST of another file (bad!).
|
/// query depends on the AST of another file (bad!).
|
||||||
fn node<'ast>(self, db: &'db dyn Db, module: &'ast ParsedModuleRef) -> &'ast ast::StmtClassDef {
|
fn node<'ast>(self, db: &'db dyn Db, module: &'ast ParsedModuleRef) -> &'ast ast::StmtClassDef {
|
||||||
let scope = self.body_scope(db);
|
let scope = self.body_scope(db);
|
||||||
scope.node(db).expect_class(module)
|
scope.node(db).expect_class().node(module)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
|
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
|
||||||
let body_scope = self.body_scope(db);
|
let body_scope = self.body_scope(db);
|
||||||
let module = parsed_module(db, body_scope.file(db)).load(db);
|
|
||||||
let index = semantic_index(db, body_scope.file(db));
|
let index = semantic_index(db, body_scope.file(db));
|
||||||
index.expect_single_definition(body_scope.node(db).expect_class(&module))
|
index.expect_single_definition(body_scope.node(db).expect_class())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn apply_specialization(
|
pub(crate) fn apply_specialization(
|
||||||
|
|
@ -1470,6 +1479,18 @@ impl<'db> ClassLiteral<'db> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn top_materialization(self, db: &'db dyn Db) -> ClassType<'db> {
|
||||||
|
self.apply_specialization(db, |generic_context| {
|
||||||
|
generic_context
|
||||||
|
.default_specialization(db, self.known(db))
|
||||||
|
.materialize_impl(
|
||||||
|
db,
|
||||||
|
MaterializationKind::Top,
|
||||||
|
&ApplyTypeMappingVisitor::default(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the default specialization of this class. For non-generic classes, the class is
|
/// Returns the default specialization of this class. For non-generic classes, the class is
|
||||||
/// returned unchanged. For a non-specialized generic class, we return a generic alias that
|
/// returned unchanged. For a non-specialized generic class, we return a generic alias that
|
||||||
/// applies the default specialization to the class's typevars.
|
/// applies the default specialization to the class's typevars.
|
||||||
|
|
@ -2264,8 +2285,36 @@ impl<'db> ClassLiteral<'db> {
|
||||||
(CodeGeneratorKind::TypedDict, "__setitem__") => {
|
(CodeGeneratorKind::TypedDict, "__setitem__") => {
|
||||||
let fields = self.fields(db, specialization, field_policy);
|
let fields = self.fields(db, specialization, field_policy);
|
||||||
|
|
||||||
// Add (key type, value type) overloads for all TypedDict items ("fields"):
|
// Add (key type, value type) overloads for all TypedDict items ("fields") that are not read-only:
|
||||||
let overloads = fields.iter().map(|(name, field)| {
|
|
||||||
|
let mut writeable_fields = fields
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, field)| !field.is_read_only())
|
||||||
|
.peekable();
|
||||||
|
|
||||||
|
if writeable_fields.peek().is_none() {
|
||||||
|
// If there are no writeable fields, synthesize a `__setitem__` that takes
|
||||||
|
// a `key` of type `Never` to signal that no keys are accepted. This leads
|
||||||
|
// to slightly more user-friendly error messages compared to returning an
|
||||||
|
// empty overload set.
|
||||||
|
return Some(Type::Callable(CallableType::new(
|
||||||
|
db,
|
||||||
|
CallableSignature::single(Signature::new(
|
||||||
|
Parameters::new([
|
||||||
|
Parameter::positional_only(Some(Name::new_static("self")))
|
||||||
|
.with_annotated_type(instance_ty),
|
||||||
|
Parameter::positional_only(Some(Name::new_static("key")))
|
||||||
|
.with_annotated_type(Type::Never),
|
||||||
|
Parameter::positional_only(Some(Name::new_static("value")))
|
||||||
|
.with_annotated_type(Type::any()),
|
||||||
|
]),
|
||||||
|
Some(Type::none(db)),
|
||||||
|
)),
|
||||||
|
true,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let overloads = writeable_fields.map(|(name, field)| {
|
||||||
let key_type = Type::StringLiteral(StringLiteralType::new(db, name.as_str()));
|
let key_type = Type::StringLiteral(StringLiteralType::new(db, name.as_str()));
|
||||||
|
|
||||||
Signature::new(
|
Signature::new(
|
||||||
|
|
@ -2617,7 +2666,9 @@ impl<'db> ClassLiteral<'db> {
|
||||||
|
|
||||||
let symbol = table.symbol(symbol_id);
|
let symbol = table.symbol(symbol_id);
|
||||||
|
|
||||||
let attr = place_from_declarations(db, declarations).ignore_conflicting_declarations();
|
let result = place_from_declarations(db, declarations.clone());
|
||||||
|
let single_declaration = result.single_declaration;
|
||||||
|
let attr = result.ignore_conflicting_declarations();
|
||||||
if attr.is_class_var() {
|
if attr.is_class_var() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -2668,13 +2719,18 @@ impl<'db> ClassLiteral<'db> {
|
||||||
.expect("TypedDictParams should be available for CodeGeneratorKind::TypedDict")
|
.expect("TypedDictParams should be available for CodeGeneratorKind::TypedDict")
|
||||||
.contains(TypedDictParams::TOTAL)
|
.contains(TypedDictParams::TOTAL)
|
||||||
};
|
};
|
||||||
FieldKind::TypedDict { is_required }
|
|
||||||
|
FieldKind::TypedDict {
|
||||||
|
is_required,
|
||||||
|
is_read_only: attr.is_read_only(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut field = Field {
|
let mut field = Field {
|
||||||
declared_ty: attr_ty.apply_optional_specialization(db, specialization),
|
declared_ty: attr_ty.apply_optional_specialization(db, specialization),
|
||||||
kind,
|
kind,
|
||||||
|
single_declaration,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if this is a KW_ONLY sentinel and mark subsequent fields as keyword-only
|
// Check if this is a KW_ONLY sentinel and mark subsequent fields as keyword-only
|
||||||
|
|
@ -2813,8 +2869,8 @@ impl<'db> ClassLiteral<'db> {
|
||||||
let class_table = place_table(db, class_body_scope);
|
let class_table = place_table(db, class_body_scope);
|
||||||
|
|
||||||
let is_valid_scope = |method_scope: ScopeId<'db>| {
|
let is_valid_scope = |method_scope: ScopeId<'db>| {
|
||||||
if let Some(method_def) = method_scope.node(db).as_function(&module) {
|
if let Some(method_def) = method_scope.node(db).as_function() {
|
||||||
let method_name = method_def.name.as_str();
|
let method_name = method_def.node(&module).name.as_str();
|
||||||
if let Place::Type(Type::FunctionLiteral(method_type), _) =
|
if let Place::Type(Type::FunctionLiteral(method_type), _) =
|
||||||
class_symbol(db, class_body_scope, method_name).place
|
class_symbol(db, class_body_scope, method_name).place
|
||||||
{
|
{
|
||||||
|
|
@ -2889,10 +2945,12 @@ impl<'db> ClassLiteral<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// The attribute assignment inherits the reachability of the method which contains it
|
// The attribute assignment inherits the reachability of the method which contains it
|
||||||
let is_method_reachable =
|
let is_method_reachable = if let Some(method_def) = method_scope.node(db).as_function()
|
||||||
if let Some(method_def) = method_scope.node(db).as_function(&module) {
|
{
|
||||||
let method = index.expect_single_definition(method_def);
|
let method = index.expect_single_definition(method_def);
|
||||||
let method_place = class_table.symbol_id(&method_def.name).unwrap();
|
let method_place = class_table
|
||||||
|
.symbol_id(&method_def.node(&module).name)
|
||||||
|
.unwrap();
|
||||||
class_map
|
class_map
|
||||||
.all_reachable_symbol_bindings(method_place)
|
.all_reachable_symbol_bindings(method_place)
|
||||||
.find_map(|bind| {
|
.find_map(|bind| {
|
||||||
|
|
@ -3266,7 +3324,7 @@ impl<'db> ClassLiteral<'db> {
|
||||||
pub(super) fn header_range(self, db: &'db dyn Db) -> TextRange {
|
pub(super) fn header_range(self, db: &'db dyn Db) -> TextRange {
|
||||||
let class_scope = self.body_scope(db);
|
let class_scope = self.body_scope(db);
|
||||||
let module = parsed_module(db, class_scope.file(db)).load(db);
|
let module = parsed_module(db, class_scope.file(db)).load(db);
|
||||||
let class_node = class_scope.node(db).expect_class(&module);
|
let class_node = class_scope.node(db).expect_class().node(&module);
|
||||||
let class_name = &class_node.name;
|
let class_name = &class_node.name;
|
||||||
TextRange::new(
|
TextRange::new(
|
||||||
class_name.start(),
|
class_name.start(),
|
||||||
|
|
@ -3277,54 +3335,6 @@ impl<'db> ClassLiteral<'db> {
|
||||||
.unwrap_or_else(|| class_name.end()),
|
.unwrap_or_else(|| class_name.end()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn declarations_of_name(
|
|
||||||
self,
|
|
||||||
db: &'db dyn Db,
|
|
||||||
name: &str,
|
|
||||||
index: &'db SemanticIndex<'db>,
|
|
||||||
) -> Option<impl Iterator<Item = DeclarationWithConstraint<'db>>> {
|
|
||||||
let class_body_scope = self.body_scope(db).file_scope_id(db);
|
|
||||||
let symbol_id = index.place_table(class_body_scope).symbol_id(name)?;
|
|
||||||
let use_def = index.use_def_map(class_body_scope);
|
|
||||||
Some(use_def.end_of_scope_declarations(ScopedPlaceId::Symbol(symbol_id)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn first_declaration_of_name(
|
|
||||||
self,
|
|
||||||
db: &'db dyn Db,
|
|
||||||
name: &str,
|
|
||||||
index: &'db SemanticIndex<'db>,
|
|
||||||
) -> Option<DeclarationWithConstraint<'db>> {
|
|
||||||
self.declarations_of_name(db, name, index)
|
|
||||||
.into_iter()
|
|
||||||
.flatten()
|
|
||||||
.next()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn bindings_of_name(
|
|
||||||
self,
|
|
||||||
db: &'db dyn Db,
|
|
||||||
name: &str,
|
|
||||||
index: &'db SemanticIndex<'db>,
|
|
||||||
) -> Option<impl Iterator<Item = BindingWithConstraints<'db, 'db>>> {
|
|
||||||
let class_body_scope = self.body_scope(db).file_scope_id(db);
|
|
||||||
let symbol_id = index.place_table(class_body_scope).symbol_id(name)?;
|
|
||||||
let use_def = index.use_def_map(class_body_scope);
|
|
||||||
Some(use_def.end_of_scope_bindings(ScopedPlaceId::Symbol(symbol_id)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn first_binding_of_name(
|
|
||||||
self,
|
|
||||||
db: &'db dyn Db,
|
|
||||||
name: &str,
|
|
||||||
index: &'db SemanticIndex<'db>,
|
|
||||||
) -> Option<BindingWithConstraints<'db, 'db>> {
|
|
||||||
self.bindings_of_name(db, name, index)
|
|
||||||
.into_iter()
|
|
||||||
.flatten()
|
|
||||||
.next()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> From<ClassLiteral<'db>> for Type<'db> {
|
impl<'db> From<ClassLiteral<'db>> for Type<'db> {
|
||||||
|
|
@ -4775,8 +4785,7 @@ impl KnownClass {
|
||||||
// 2. The first parameter of the current function (typically `self` or `cls`)
|
// 2. The first parameter of the current function (typically `self` or `cls`)
|
||||||
match overload.parameter_types() {
|
match overload.parameter_types() {
|
||||||
[] => {
|
[] => {
|
||||||
let Some(enclosing_class) =
|
let Some(enclosing_class) = nearest_enclosing_class(db, index, scope)
|
||||||
nearest_enclosing_class(db, index, scope, module)
|
|
||||||
else {
|
else {
|
||||||
BoundSuperError::UnavailableImplicitArguments
|
BoundSuperError::UnavailableImplicitArguments
|
||||||
.report_diagnostic(context, call_expression.into());
|
.report_diagnostic(context, call_expression.into());
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@ impl<'db, 'ast> InferContext<'db, 'ast> {
|
||||||
// Inspect all ancestor function scopes by walking bottom up and infer the function's type.
|
// Inspect all ancestor function scopes by walking bottom up and infer the function's type.
|
||||||
let mut function_scope_tys = index
|
let mut function_scope_tys = index
|
||||||
.ancestor_scopes(scope_id)
|
.ancestor_scopes(scope_id)
|
||||||
.filter_map(|(_, scope)| scope.node().as_function(self.module()))
|
.filter_map(|(_, scope)| scope.node().as_function())
|
||||||
.map(|node| binding_type(self.db, index.expect_single_definition(node)))
|
.map(|node| binding_type(self.db, index.expect_single_definition(node)))
|
||||||
.filter_map(Type::into_function_literal);
|
.filter_map(Type::into_function_literal);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ use super::{
|
||||||
add_inferred_python_version_hint_to_diagnostic,
|
add_inferred_python_version_hint_to_diagnostic,
|
||||||
};
|
};
|
||||||
use crate::lint::{Level, LintRegistryBuilder, LintStatus};
|
use crate::lint::{Level, LintRegistryBuilder, LintStatus};
|
||||||
use crate::semantic_index::SemanticIndex;
|
|
||||||
use crate::semantic_index::definition::Definition;
|
use crate::semantic_index::definition::Definition;
|
||||||
use crate::semantic_index::place::{PlaceTable, ScopedPlaceId};
|
use crate::semantic_index::place::{PlaceTable, ScopedPlaceId};
|
||||||
use crate::suppression::FileSuppressionId;
|
use crate::suppression::FileSuppressionId;
|
||||||
|
|
@ -2887,16 +2886,13 @@ pub(crate) fn report_invalid_key_on_typed_dict<'db>(
|
||||||
pub(super) fn report_namedtuple_field_without_default_after_field_with_default<'db>(
|
pub(super) fn report_namedtuple_field_without_default_after_field_with_default<'db>(
|
||||||
context: &InferContext<'db, '_>,
|
context: &InferContext<'db, '_>,
|
||||||
class: ClassLiteral<'db>,
|
class: ClassLiteral<'db>,
|
||||||
index: &'db SemanticIndex<'db>,
|
(field, field_def): &(Name, Option<Definition<'db>>),
|
||||||
field_name: &str,
|
(field_with_default, field_with_default_def): &(Name, Option<Definition<'db>>),
|
||||||
field_with_default: &str,
|
|
||||||
) {
|
) {
|
||||||
let db = context.db();
|
let db = context.db();
|
||||||
let module = context.module();
|
let module = context.module();
|
||||||
|
|
||||||
let diagnostic_range = class
|
let diagnostic_range = field_def
|
||||||
.first_declaration_of_name(db, field_name, index)
|
|
||||||
.and_then(|definition| definition.declaration.definition())
|
|
||||||
.map(|definition| definition.kind(db).full_range(module))
|
.map(|definition| definition.kind(db).full_range(module))
|
||||||
.unwrap_or_else(|| class.header_range(db));
|
.unwrap_or_else(|| class.header_range(db));
|
||||||
|
|
||||||
|
|
@ -2908,13 +2904,11 @@ pub(super) fn report_namedtuple_field_without_default_after_field_with_default<'
|
||||||
));
|
));
|
||||||
|
|
||||||
diagnostic.set_primary_message(format_args!(
|
diagnostic.set_primary_message(format_args!(
|
||||||
"Field `{field_name}` defined here without a default value"
|
"Field `{field}` defined here without a default value",
|
||||||
));
|
));
|
||||||
|
|
||||||
let Some(field_with_default_range) = class
|
let Some(field_with_default_range) =
|
||||||
.first_binding_of_name(db, field_with_default, index)
|
field_with_default_def.map(|definition| definition.kind(db).full_range(module))
|
||||||
.and_then(|definition| definition.binding.definition())
|
|
||||||
.map(|definition| definition.kind(db).full_range(module))
|
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
@ -2933,7 +2927,7 @@ pub(super) fn report_namedtuple_field_without_default_after_field_with_default<'
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
diagnostic.info(format_args!(
|
diagnostic.info(format_args!(
|
||||||
"Earlier field `{field_with_default}` was defined with a default value"
|
"Earlier field `{field_with_default}` was defined with a default value",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -200,15 +200,15 @@ impl ClassDisplay<'_> {
|
||||||
|
|
||||||
match ancestor_scope.kind() {
|
match ancestor_scope.kind() {
|
||||||
ScopeKind::Class => {
|
ScopeKind::Class => {
|
||||||
if let Some(class_def) = node.as_class(&module_ast) {
|
if let Some(class_def) = node.as_class() {
|
||||||
name_parts.push(class_def.name.as_str().to_string());
|
name_parts.push(class_def.node(&module_ast).name.as_str().to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ScopeKind::Function => {
|
ScopeKind::Function => {
|
||||||
if let Some(function_def) = node.as_function(&module_ast) {
|
if let Some(function_def) = node.as_function() {
|
||||||
name_parts.push(format!(
|
name_parts.push(format!(
|
||||||
"<locals of function '{}'>",
|
"<locals of function '{}'>",
|
||||||
function_def.name.as_str()
|
function_def.node(&module_ast).name.as_str()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ use bitflags::bitflags;
|
||||||
use ruff_db::diagnostic::{Annotation, DiagnosticId, Severity, Span};
|
use ruff_db::diagnostic::{Annotation, DiagnosticId, Severity, Span};
|
||||||
use ruff_db::files::{File, FileRange};
|
use ruff_db::files::{File, FileRange};
|
||||||
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
|
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast::{self as ast, ParameterWithDefault};
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
use crate::module_resolver::{KnownModule, file_to_module};
|
use crate::module_resolver::{KnownModule, file_to_module};
|
||||||
|
|
@ -63,7 +63,7 @@ use crate::place::{Boundness, Place, place_from_bindings};
|
||||||
use crate::semantic_index::ast_ids::HasScopedUseId;
|
use crate::semantic_index::ast_ids::HasScopedUseId;
|
||||||
use crate::semantic_index::definition::Definition;
|
use crate::semantic_index::definition::Definition;
|
||||||
use crate::semantic_index::scope::ScopeId;
|
use crate::semantic_index::scope::ScopeId;
|
||||||
use crate::semantic_index::semantic_index;
|
use crate::semantic_index::{FileScopeId, SemanticIndex, semantic_index};
|
||||||
use crate::types::call::{Binding, CallArguments};
|
use crate::types::call::{Binding, CallArguments};
|
||||||
use crate::types::constraints::{ConstraintSet, Constraints};
|
use crate::types::constraints::{ConstraintSet, Constraints};
|
||||||
use crate::types::context::InferContext;
|
use crate::types::context::InferContext;
|
||||||
|
|
@ -80,7 +80,7 @@ use crate::types::{
|
||||||
BoundMethodType, BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral, ClassType,
|
BoundMethodType, BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral, ClassType,
|
||||||
DeprecatedInstance, DynamicType, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
|
DeprecatedInstance, DynamicType, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
|
||||||
IsEquivalentVisitor, KnownClass, NormalizedVisitor, SpecialFormType, Truthiness, Type,
|
IsEquivalentVisitor, KnownClass, NormalizedVisitor, SpecialFormType, Truthiness, Type,
|
||||||
TypeMapping, TypeRelation, UnionBuilder, all_members, walk_type_mapping,
|
TypeMapping, TypeRelation, UnionBuilder, all_members, binding_type, walk_type_mapping,
|
||||||
};
|
};
|
||||||
use crate::{Db, FxOrderSet, ModuleName, resolve_module};
|
use crate::{Db, FxOrderSet, ModuleName, resolve_module};
|
||||||
|
|
||||||
|
|
@ -236,6 +236,22 @@ impl<'db> OverloadLiteral<'db> {
|
||||||
self.has_known_decorator(db, FunctionDecorators::OVERLOAD)
|
self.has_known_decorator(db, FunctionDecorators::OVERLOAD)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if this overload is decorated with `@staticmethod`, or if it is implicitly a
|
||||||
|
/// staticmethod.
|
||||||
|
pub(crate) fn is_staticmethod(self, db: &dyn Db) -> bool {
|
||||||
|
self.has_known_decorator(db, FunctionDecorators::STATICMETHOD) || self.name(db) == "__new__"
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this overload is decorated with `@classmethod`, or if it is implicitly a
|
||||||
|
/// classmethod.
|
||||||
|
pub(crate) fn is_classmethod(self, db: &dyn Db) -> bool {
|
||||||
|
self.has_known_decorator(db, FunctionDecorators::CLASSMETHOD)
|
||||||
|
|| matches!(
|
||||||
|
self.name(db).as_str(),
|
||||||
|
"__init_subclass__" | "__class_getitem__"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn node<'ast>(
|
fn node<'ast>(
|
||||||
self,
|
self,
|
||||||
db: &dyn Db,
|
db: &dyn Db,
|
||||||
|
|
@ -249,7 +265,7 @@ impl<'db> OverloadLiteral<'db> {
|
||||||
the function is defined."
|
the function is defined."
|
||||||
);
|
);
|
||||||
|
|
||||||
self.body_scope(db).node(db).expect_function(module)
|
self.body_scope(db).node(db).expect_function().node(module)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the [`FileRange`] of the function's name.
|
/// Returns the [`FileRange`] of the function's name.
|
||||||
|
|
@ -258,7 +274,8 @@ impl<'db> OverloadLiteral<'db> {
|
||||||
self.file(db),
|
self.file(db),
|
||||||
self.body_scope(db)
|
self.body_scope(db)
|
||||||
.node(db)
|
.node(db)
|
||||||
.expect_function(module)
|
.expect_function()
|
||||||
|
.node(module)
|
||||||
.name
|
.name
|
||||||
.range,
|
.range,
|
||||||
)
|
)
|
||||||
|
|
@ -274,9 +291,8 @@ impl<'db> OverloadLiteral<'db> {
|
||||||
/// over-invalidation.
|
/// over-invalidation.
|
||||||
fn definition(self, db: &'db dyn Db) -> Definition<'db> {
|
fn definition(self, db: &'db dyn Db) -> Definition<'db> {
|
||||||
let body_scope = self.body_scope(db);
|
let body_scope = self.body_scope(db);
|
||||||
let module = parsed_module(db, self.file(db)).load(db);
|
|
||||||
let index = semantic_index(db, body_scope.file(db));
|
let index = semantic_index(db, body_scope.file(db));
|
||||||
index.expect_single_definition(body_scope.node(db).expect_function(&module))
|
index.expect_single_definition(body_scope.node(db).expect_function())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the overload immediately before this one in the AST. Returns `None` if there is no
|
/// Returns the overload immediately before this one in the AST. Returns `None` if there is no
|
||||||
|
|
@ -290,7 +306,8 @@ impl<'db> OverloadLiteral<'db> {
|
||||||
let use_id = self
|
let use_id = self
|
||||||
.body_scope(db)
|
.body_scope(db)
|
||||||
.node(db)
|
.node(db)
|
||||||
.expect_function(&module)
|
.expect_function()
|
||||||
|
.node(&module)
|
||||||
.name
|
.name
|
||||||
.scoped_use_id(db, scope);
|
.scoped_use_id(db, scope);
|
||||||
|
|
||||||
|
|
@ -325,17 +342,79 @@ impl<'db> OverloadLiteral<'db> {
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
inherited_generic_context: Option<GenericContext<'db>>,
|
inherited_generic_context: Option<GenericContext<'db>>,
|
||||||
) -> Signature<'db> {
|
) -> Signature<'db> {
|
||||||
|
/// `self` or `cls` can be implicitly positional-only if:
|
||||||
|
/// - It is a method AND
|
||||||
|
/// - No parameters in the method use PEP-570 syntax AND
|
||||||
|
/// - It is not a `@staticmethod` AND
|
||||||
|
/// - `self`/`cls` is not explicitly positional-only using the PEP-484 convention AND
|
||||||
|
/// - Either the next parameter after `self`/`cls` uses the PEP-484 convention,
|
||||||
|
/// or the enclosing class is a `Protocol` class
|
||||||
|
fn has_implicitly_positional_only_first_param<'db>(
|
||||||
|
db: &'db dyn Db,
|
||||||
|
literal: OverloadLiteral<'db>,
|
||||||
|
node: &ast::StmtFunctionDef,
|
||||||
|
scope: FileScopeId,
|
||||||
|
index: &SemanticIndex,
|
||||||
|
) -> bool {
|
||||||
|
let parameters = &node.parameters;
|
||||||
|
|
||||||
|
if !parameters.posonlyargs.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(first_param) = parameters.args.first() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if first_param.uses_pep_484_positional_only_convention() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if literal.is_staticmethod(db) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(class_definition) = index.class_definition_of_method(scope) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// `self` and `cls` are always positional-only if the next parameter uses the
|
||||||
|
// PEP-484 convention.
|
||||||
|
if parameters
|
||||||
|
.args
|
||||||
|
.get(1)
|
||||||
|
.is_some_and(ParameterWithDefault::uses_pep_484_positional_only_convention)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there isn't any parameter other than `self`/`cls`,
|
||||||
|
// or there is but it isn't using the PEP-484 convention,
|
||||||
|
// then `self`/`cls` are only implicitly positional-only if
|
||||||
|
// it is a protocol class.
|
||||||
|
let class_type = binding_type(db, class_definition);
|
||||||
|
class_type
|
||||||
|
.to_class_type(db)
|
||||||
|
.is_some_and(|class| class.is_protocol(db))
|
||||||
|
}
|
||||||
|
|
||||||
let scope = self.body_scope(db);
|
let scope = self.body_scope(db);
|
||||||
let module = parsed_module(db, self.file(db)).load(db);
|
let module = parsed_module(db, self.file(db)).load(db);
|
||||||
let function_stmt_node = scope.node(db).expect_function(&module);
|
let function_stmt_node = scope.node(db).expect_function().node(&module);
|
||||||
let definition = self.definition(db);
|
let definition = self.definition(db);
|
||||||
let generic_context = function_stmt_node.type_params.as_ref().map(|type_params| {
|
|
||||||
let index = semantic_index(db, scope.file(db));
|
let index = semantic_index(db, scope.file(db));
|
||||||
|
let generic_context = function_stmt_node.type_params.as_ref().map(|type_params| {
|
||||||
GenericContext::from_type_params(db, index, definition, type_params)
|
GenericContext::from_type_params(db, index, definition, type_params)
|
||||||
});
|
});
|
||||||
|
let file_scope_id = scope.file_scope_id(db);
|
||||||
let index = semantic_index(db, scope.file(db));
|
let is_generator = file_scope_id.is_generator_function(index);
|
||||||
let is_generator = scope.file_scope_id(db).is_generator_function(index);
|
let has_implicitly_positional_first_parameter = has_implicitly_positional_only_first_param(
|
||||||
|
db,
|
||||||
|
self,
|
||||||
|
function_stmt_node,
|
||||||
|
file_scope_id,
|
||||||
|
index,
|
||||||
|
);
|
||||||
|
|
||||||
Signature::from_function(
|
Signature::from_function(
|
||||||
db,
|
db,
|
||||||
|
|
@ -344,6 +423,7 @@ impl<'db> OverloadLiteral<'db> {
|
||||||
definition,
|
definition,
|
||||||
function_stmt_node,
|
function_stmt_node,
|
||||||
is_generator,
|
is_generator,
|
||||||
|
has_implicitly_positional_first_parameter,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -356,7 +436,7 @@ impl<'db> OverloadLiteral<'db> {
|
||||||
let span = Span::from(function_scope.file(db));
|
let span = Span::from(function_scope.file(db));
|
||||||
let node = function_scope.node(db);
|
let node = function_scope.node(db);
|
||||||
let module = parsed_module(db, self.file(db)).load(db);
|
let module = parsed_module(db, self.file(db)).load(db);
|
||||||
let func_def = node.as_function(&module)?;
|
let func_def = node.as_function()?.node(&module);
|
||||||
let range = parameter_index
|
let range = parameter_index
|
||||||
.and_then(|parameter_index| {
|
.and_then(|parameter_index| {
|
||||||
func_def
|
func_def
|
||||||
|
|
@ -376,7 +456,7 @@ impl<'db> OverloadLiteral<'db> {
|
||||||
let span = Span::from(function_scope.file(db));
|
let span = Span::from(function_scope.file(db));
|
||||||
let node = function_scope.node(db);
|
let node = function_scope.node(db);
|
||||||
let module = parsed_module(db, self.file(db)).load(db);
|
let module = parsed_module(db, self.file(db)).load(db);
|
||||||
let func_def = node.as_function(&module)?;
|
let func_def = node.as_function()?.node(&module);
|
||||||
let return_type_range = func_def.returns.as_ref().map(|returns| returns.range());
|
let return_type_range = func_def.returns.as_ref().map(|returns| returns.range());
|
||||||
let mut signature = func_def.name.range.cover(func_def.parameters.range);
|
let mut signature = func_def.name.range.cover(func_def.parameters.range);
|
||||||
if let Some(return_type_range) = return_type_range {
|
if let Some(return_type_range) = return_type_range {
|
||||||
|
|
@ -713,17 +793,15 @@ impl<'db> FunctionType<'db> {
|
||||||
/// Returns true if this method is decorated with `@classmethod`, or if it is implicitly a
|
/// Returns true if this method is decorated with `@classmethod`, or if it is implicitly a
|
||||||
/// classmethod.
|
/// classmethod.
|
||||||
pub(crate) fn is_classmethod(self, db: &'db dyn Db) -> bool {
|
pub(crate) fn is_classmethod(self, db: &'db dyn Db) -> bool {
|
||||||
self.has_known_decorator(db, FunctionDecorators::CLASSMETHOD)
|
self.iter_overloads_and_implementation(db)
|
||||||
|| matches!(
|
.any(|overload| overload.is_classmethod(db))
|
||||||
self.name(db).as_str(),
|
|
||||||
"__init_subclass__" | "__class_getitem__"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if this method is decorated with `@staticmethod`, or if it is implicitly a
|
/// Returns true if this method is decorated with `@staticmethod`, or if it is implicitly a
|
||||||
/// static method.
|
/// static method.
|
||||||
pub(crate) fn is_staticmethod(self, db: &'db dyn Db) -> bool {
|
pub(crate) fn is_staticmethod(self, db: &'db dyn Db) -> bool {
|
||||||
self.has_known_decorator(db, FunctionDecorators::STATICMETHOD) || self.name(db) == "__new__"
|
self.iter_overloads_and_implementation(db)
|
||||||
|
.any(|overload| overload.is_staticmethod(db))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If the implementation of this function is deprecated, returns the `@warnings.deprecated`.
|
/// If the implementation of this function is deprecated, returns the `@warnings.deprecated`.
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,7 @@ use crate::types::typed_dict::{
|
||||||
validate_typed_dict_key_assignment,
|
validate_typed_dict_key_assignment,
|
||||||
};
|
};
|
||||||
use crate::types::unpacker::{UnpackResult, Unpacker};
|
use crate::types::unpacker::{UnpackResult, Unpacker};
|
||||||
|
use crate::types::visitor::any_over_type;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
CallDunderError, CallableType, ClassLiteral, ClassType, DataclassParams, DynamicType,
|
CallDunderError, CallableType, ClassLiteral, ClassType, DataclassParams, DynamicType,
|
||||||
IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType, LintDiagnosticGuard,
|
IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType, LintDiagnosticGuard,
|
||||||
|
|
@ -421,12 +422,11 @@ pub(crate) fn nearest_enclosing_class<'db>(
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
semantic: &SemanticIndex<'db>,
|
semantic: &SemanticIndex<'db>,
|
||||||
scope: ScopeId,
|
scope: ScopeId,
|
||||||
parsed: &ParsedModuleRef,
|
|
||||||
) -> Option<ClassLiteral<'db>> {
|
) -> Option<ClassLiteral<'db>> {
|
||||||
semantic
|
semantic
|
||||||
.ancestor_scopes(scope.file_scope_id(db))
|
.ancestor_scopes(scope.file_scope_id(db))
|
||||||
.find_map(|(_, ancestor_scope)| {
|
.find_map(|(_, ancestor_scope)| {
|
||||||
let class = ancestor_scope.node().as_class(parsed)?;
|
let class = ancestor_scope.node().as_class()?;
|
||||||
let definition = semantic.expect_single_definition(class);
|
let definition = semantic.expect_single_definition(class);
|
||||||
infer_definition_types(db, definition)
|
infer_definition_types(db, definition)
|
||||||
.declaration_type(definition)
|
.declaration_type(definition)
|
||||||
|
|
@ -1190,14 +1190,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
default_ty: Some(_)
|
default_ty: Some(_)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
field_with_default_encountered = Some(field_name);
|
field_with_default_encountered =
|
||||||
|
Some((field_name, field.single_declaration));
|
||||||
} else if let Some(field_with_default) = field_with_default_encountered.as_ref()
|
} else if let Some(field_with_default) = field_with_default_encountered.as_ref()
|
||||||
{
|
{
|
||||||
report_namedtuple_field_without_default_after_field_with_default(
|
report_namedtuple_field_without_default_after_field_with_default(
|
||||||
&self.context,
|
&self.context,
|
||||||
class,
|
class,
|
||||||
self.index,
|
&(field_name, field.single_declaration),
|
||||||
&field_name,
|
|
||||||
field_with_default,
|
field_with_default,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -2417,29 +2417,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
/// behaviour to the [`nearest_enclosing_class`] function.
|
/// behaviour to the [`nearest_enclosing_class`] function.
|
||||||
fn class_context_of_current_method(&self) -> Option<ClassType<'db>> {
|
fn class_context_of_current_method(&self) -> Option<ClassType<'db>> {
|
||||||
let current_scope_id = self.scope().file_scope_id(self.db());
|
let current_scope_id = self.scope().file_scope_id(self.db());
|
||||||
let current_scope = self.index.scope(current_scope_id);
|
let class_definition = self.index.class_definition_of_method(current_scope_id)?;
|
||||||
if current_scope.kind() != ScopeKind::Function {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let parent_scope_id = current_scope.parent()?;
|
|
||||||
let parent_scope = self.index.scope(parent_scope_id);
|
|
||||||
|
|
||||||
let class_scope = match parent_scope.kind() {
|
|
||||||
ScopeKind::Class => parent_scope,
|
|
||||||
ScopeKind::TypeParams => {
|
|
||||||
let class_scope_id = parent_scope.parent()?;
|
|
||||||
let potentially_class_scope = self.index.scope(class_scope_id);
|
|
||||||
|
|
||||||
match potentially_class_scope.kind() {
|
|
||||||
ScopeKind::Class => potentially_class_scope,
|
|
||||||
_ => return None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let class_stmt = class_scope.node().as_class(self.module())?;
|
|
||||||
let class_definition = self.index.expect_single_definition(class_stmt);
|
|
||||||
binding_type(self.db(), class_definition).to_class_type(self.db())
|
binding_type(self.db(), class_definition).to_class_type(self.db())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2452,7 +2430,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
if !current_scope.kind().is_non_lambda_function() {
|
if !current_scope.kind().is_non_lambda_function() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
current_scope.node().as_function(self.module())
|
current_scope
|
||||||
|
.node()
|
||||||
|
.as_function()
|
||||||
|
.map(|node_ref| node_ref.node(self.module()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn function_decorator_types<'a>(
|
fn function_decorator_types<'a>(
|
||||||
|
|
@ -9271,8 +9252,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
|
|
||||||
let typevars: Result<FxOrderSet<_>, GenericContextError> = typevars
|
let typevars: Result<FxOrderSet<_>, GenericContextError> = typevars
|
||||||
.iter()
|
.iter()
|
||||||
.map(|typevar| match typevar {
|
.map(|typevar| {
|
||||||
Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => bind_typevar(
|
if let Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) = typevar {
|
||||||
|
bind_typevar(
|
||||||
self.db(),
|
self.db(),
|
||||||
self.module(),
|
self.module(),
|
||||||
self.index,
|
self.index,
|
||||||
|
|
@ -9280,17 +9262,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
self.typevar_binding_context,
|
self.typevar_binding_context,
|
||||||
*typevar,
|
*typevar,
|
||||||
)
|
)
|
||||||
.ok_or(GenericContextError::InvalidArgument),
|
.ok_or(GenericContextError::InvalidArgument)
|
||||||
Type::Dynamic(DynamicType::TodoUnpack) => Err(GenericContextError::NotYetSupported),
|
} else if any_over_type(self.db(), *typevar, &|ty| match ty {
|
||||||
Type::NominalInstance(nominal)
|
Type::Dynamic(DynamicType::TodoUnpack) => true,
|
||||||
if matches!(
|
Type::NominalInstance(nominal) => matches!(
|
||||||
nominal.class(self.db()).known(self.db()),
|
nominal.class(self.db()).known(self.db()),
|
||||||
Some(KnownClass::TypeVarTuple | KnownClass::ParamSpec)
|
Some(KnownClass::TypeVarTuple | KnownClass::ParamSpec)
|
||||||
) =>
|
),
|
||||||
{
|
_ => false,
|
||||||
|
}) {
|
||||||
Err(GenericContextError::NotYetSupported)
|
Err(GenericContextError::NotYetSupported)
|
||||||
}
|
} else {
|
||||||
_ => {
|
|
||||||
if let Some(builder) =
|
if let Some(builder) =
|
||||||
self.context.report_lint(&INVALID_ARGUMENT_TYPE, value_node)
|
self.context.report_lint(&INVALID_ARGUMENT_TYPE, value_node)
|
||||||
{
|
{
|
||||||
|
|
@ -11265,19 +11247,15 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
// `Callable[]`.
|
// `Callable[]`.
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
match self.infer_name_load(name) {
|
if any_over_type(self.db(), self.infer_name_load(name), &|ty| match ty {
|
||||||
Type::Dynamic(DynamicType::TodoPEP695ParamSpec) => {
|
Type::Dynamic(DynamicType::TodoPEP695ParamSpec) => true,
|
||||||
return Some(Parameters::todo());
|
Type::NominalInstance(nominal) => nominal
|
||||||
}
|
|
||||||
Type::NominalInstance(nominal)
|
|
||||||
if nominal
|
|
||||||
.class(self.db())
|
.class(self.db())
|
||||||
.is_known(self.db(), KnownClass::ParamSpec) =>
|
.is_known(self.db(), KnownClass::ParamSpec),
|
||||||
{
|
_ => false,
|
||||||
|
}) {
|
||||||
return Some(Parameters::todo());
|
return Some(Parameters::todo());
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -174,10 +174,10 @@ impl ClassInfoConstraintFunction {
|
||||||
fn generate_constraint<'db>(self, db: &'db dyn Db, classinfo: Type<'db>) -> Option<Type<'db>> {
|
fn generate_constraint<'db>(self, db: &'db dyn Db, classinfo: Type<'db>) -> Option<Type<'db>> {
|
||||||
let constraint_fn = |class: ClassLiteral<'db>| match self {
|
let constraint_fn = |class: ClassLiteral<'db>| match self {
|
||||||
ClassInfoConstraintFunction::IsInstance => {
|
ClassInfoConstraintFunction::IsInstance => {
|
||||||
Type::instance(db, class.default_specialization(db))
|
Type::instance(db, class.top_materialization(db))
|
||||||
}
|
}
|
||||||
ClassInfoConstraintFunction::IsSubclass => {
|
ClassInfoConstraintFunction::IsSubclass => {
|
||||||
SubclassOfType::from(db, class.default_specialization(db))
|
SubclassOfType::from(db, class.top_materialization(db))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
use std::{collections::HashMap, slice::Iter};
|
use std::{collections::HashMap, slice::Iter};
|
||||||
|
|
||||||
use itertools::EitherOrBoth;
|
use itertools::{EitherOrBoth, Itertools};
|
||||||
use smallvec::{SmallVec, smallvec_inline};
|
use smallvec::{SmallVec, smallvec_inline};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
|
@ -380,6 +380,7 @@ impl<'db> Signature<'db> {
|
||||||
definition: Definition<'db>,
|
definition: Definition<'db>,
|
||||||
function_node: &ast::StmtFunctionDef,
|
function_node: &ast::StmtFunctionDef,
|
||||||
is_generator: bool,
|
is_generator: bool,
|
||||||
|
has_implicitly_positional_first_parameter: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let parameters =
|
let parameters =
|
||||||
Parameters::from_parameters(db, definition, function_node.parameters.as_ref());
|
Parameters::from_parameters(db, definition, function_node.parameters.as_ref());
|
||||||
|
|
@ -1168,6 +1169,7 @@ impl<'db> Parameters<'db> {
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
definition: Definition<'db>,
|
definition: Definition<'db>,
|
||||||
parameters: &ast::Parameters,
|
parameters: &ast::Parameters,
|
||||||
|
has_implicitly_positional_first_parameter: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let ast::Parameters {
|
let ast::Parameters {
|
||||||
posonlyargs,
|
posonlyargs,
|
||||||
|
|
@ -1178,30 +1180,51 @@ impl<'db> Parameters<'db> {
|
||||||
range: _,
|
range: _,
|
||||||
node_index: _,
|
node_index: _,
|
||||||
} = parameters;
|
} = parameters;
|
||||||
|
|
||||||
let default_type = |param: &ast::ParameterWithDefault| {
|
let default_type = |param: &ast::ParameterWithDefault| {
|
||||||
param
|
param
|
||||||
.default()
|
.default()
|
||||||
.map(|default| definition_expression_type(db, definition, default))
|
.map(|default| definition_expression_type(db, definition, default))
|
||||||
};
|
};
|
||||||
let positional_only = posonlyargs.iter().map(|arg| {
|
|
||||||
|
let pos_only_param = |param: &ast::ParameterWithDefault| {
|
||||||
Parameter::from_node_and_kind(
|
Parameter::from_node_and_kind(
|
||||||
db,
|
db,
|
||||||
definition,
|
definition,
|
||||||
&arg.parameter,
|
¶m.parameter,
|
||||||
ParameterKind::PositionalOnly {
|
ParameterKind::PositionalOnly {
|
||||||
name: Some(arg.parameter.name.id.clone()),
|
name: Some(param.parameter.name.id.clone()),
|
||||||
default_type: default_type(arg),
|
default_type: default_type(param),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
});
|
};
|
||||||
|
|
||||||
|
let mut positional_only: Vec<Parameter> = posonlyargs.iter().map(pos_only_param).collect();
|
||||||
|
|
||||||
|
let mut pos_or_keyword_iter = args.iter();
|
||||||
|
|
||||||
|
// If there are no PEP-570 positional-only parameters, check for the legacy PEP-484 convention
|
||||||
|
// for denoting positional-only parameters (parameters that start with `__` and do not end with `__`)
|
||||||
|
if positional_only.is_empty() {
|
||||||
|
let pos_or_keyword_iter = pos_or_keyword_iter.by_ref();
|
||||||
|
|
||||||
|
if has_implicitly_positional_first_parameter {
|
||||||
|
positional_only.extend(pos_or_keyword_iter.next().map(pos_only_param));
|
||||||
|
}
|
||||||
|
|
||||||
|
positional_only.extend(
|
||||||
|
pos_or_keyword_iter
|
||||||
|
.peeking_take_while(|param| param.uses_pep_484_positional_only_convention())
|
||||||
|
.map(pos_only_param),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let method_type = infer_method_type(db, definition);
|
let method_type = infer_method_type(db, definition);
|
||||||
let is_method = method_type.is_some();
|
let is_method = method_type.is_some();
|
||||||
let is_classmethod = method_type.is_some_and(|f| f.is_classmethod(db));
|
let is_classmethod = method_type.is_some_and(|f| f.is_classmethod(db));
|
||||||
let is_staticmethod = method_type.is_some_and(|f| f.is_staticmethod(db));
|
let is_staticmethod = method_type.is_some_and(|f| f.is_staticmethod(db));
|
||||||
|
|
||||||
let positional_or_keyword = args.iter().map(|arg| {
|
let positional_or_keyword = pos_or_keyword_iter.map(|arg| {
|
||||||
// TODO(https://github.com/astral-sh/ty/issues/159): Also set the type for `cls` argument
|
|
||||||
if is_method
|
if is_method
|
||||||
&& !is_staticmethod
|
&& !is_staticmethod
|
||||||
&& !is_classmethod
|
&& !is_classmethod
|
||||||
|
|
@ -1232,6 +1255,7 @@ impl<'db> Parameters<'db> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let variadic = vararg.as_ref().map(|arg| {
|
let variadic = vararg.as_ref().map(|arg| {
|
||||||
Parameter::from_node_and_kind(
|
Parameter::from_node_and_kind(
|
||||||
db,
|
db,
|
||||||
|
|
@ -1242,6 +1266,7 @@ impl<'db> Parameters<'db> {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let keyword_only = kwonlyargs.iter().map(|arg| {
|
let keyword_only = kwonlyargs.iter().map(|arg| {
|
||||||
Parameter::from_node_and_kind(
|
Parameter::from_node_and_kind(
|
||||||
db,
|
db,
|
||||||
|
|
@ -1253,6 +1278,7 @@ impl<'db> Parameters<'db> {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let keywords = kwarg.as_ref().map(|arg| {
|
let keywords = kwarg.as_ref().map(|arg| {
|
||||||
Parameter::from_node_and_kind(
|
Parameter::from_node_and_kind(
|
||||||
db,
|
db,
|
||||||
|
|
@ -1263,8 +1289,10 @@ impl<'db> Parameters<'db> {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
Self::new(
|
Self::new(
|
||||||
positional_only
|
positional_only
|
||||||
|
.into_iter()
|
||||||
.chain(positional_or_keyword)
|
.chain(positional_or_keyword)
|
||||||
.chain(variadic)
|
.chain(variadic)
|
||||||
.chain(keyword_only)
|
.chain(keyword_only)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
|
use ruff_db::diagnostic::{Annotation, Diagnostic, Span, SubDiagnostic, SubDiagnosticSeverity};
|
||||||
|
use ruff_db::parsed::parsed_module;
|
||||||
use ruff_python_ast::Arguments;
|
use ruff_python_ast::Arguments;
|
||||||
use ruff_python_ast::{self as ast, AnyNodeRef, StmtClassDef, name::Name};
|
use ruff_python_ast::{self as ast, AnyNodeRef, StmtClassDef, name::Name};
|
||||||
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
use super::class::{ClassType, CodeGeneratorKind, Field};
|
use super::class::{ClassType, CodeGeneratorKind, Field};
|
||||||
use super::context::InferContext;
|
use super::context::InferContext;
|
||||||
|
|
@ -122,6 +125,10 @@ impl TypedDictAssignmentKind {
|
||||||
Self::Constructor => &INVALID_ARGUMENT_TYPE,
|
Self::Constructor => &INVALID_ARGUMENT_TYPE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fn is_subscript(self) -> bool {
|
||||||
|
matches!(self, Self::Subscript)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validates assignment of a value to a specific key on a `TypedDict`.
|
/// Validates assignment of a value to a specific key on a `TypedDict`.
|
||||||
|
|
@ -153,6 +160,47 @@ pub(super) fn validate_typed_dict_key_assignment<'db, 'ast>(
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let add_item_definition_subdiagnostic = |diagnostic: &mut Diagnostic, message| {
|
||||||
|
if let Some(declaration) = item.single_declaration {
|
||||||
|
let file = declaration.file(db);
|
||||||
|
let module = parsed_module(db, file).load(db);
|
||||||
|
|
||||||
|
let mut sub = SubDiagnostic::new(SubDiagnosticSeverity::Info, "Item declaration");
|
||||||
|
sub.annotate(
|
||||||
|
Annotation::secondary(
|
||||||
|
Span::from(file).with_range(declaration.full_range(db, &module).range()),
|
||||||
|
)
|
||||||
|
.message(message),
|
||||||
|
);
|
||||||
|
diagnostic.sub(sub);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if assignment_kind.is_subscript() && item.is_read_only() {
|
||||||
|
if let Some(builder) =
|
||||||
|
context.report_lint(assignment_kind.diagnostic_type(), key_node.into())
|
||||||
|
{
|
||||||
|
let typed_dict_ty = Type::TypedDict(typed_dict);
|
||||||
|
let typed_dict_d = typed_dict_ty.display(db);
|
||||||
|
|
||||||
|
let mut diagnostic = builder.into_diagnostic(format_args!(
|
||||||
|
"Cannot assign to key \"{key}\" on TypedDict `{typed_dict_d}`",
|
||||||
|
));
|
||||||
|
|
||||||
|
diagnostic.set_primary_message(format_args!("key is marked read-only"));
|
||||||
|
|
||||||
|
diagnostic.annotate(
|
||||||
|
context
|
||||||
|
.secondary(typed_dict_node.into())
|
||||||
|
.message(format_args!("TypedDict `{typed_dict_d}`")),
|
||||||
|
);
|
||||||
|
|
||||||
|
add_item_definition_subdiagnostic(&mut diagnostic, "Read-only item declared here");
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Key exists, check if value type is assignable to declared type
|
// Key exists, check if value type is assignable to declared type
|
||||||
if value_ty.is_assignable_to(db, item.declared_ty) {
|
if value_ty.is_assignable_to(db, item.declared_ty) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -184,6 +232,8 @@ pub(super) fn validate_typed_dict_key_assignment<'db, 'ast>(
|
||||||
.secondary(key_node.into())
|
.secondary(key_node.into())
|
||||||
.message(format_args!("key has declared type `{item_type_d}`")),
|
.message(format_args!("key has declared type `{item_type_d}`")),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
add_item_definition_subdiagnostic(&mut diagnostic, "Item declared here");
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
|
|
|
||||||
|
|
@ -108,9 +108,30 @@ impl std::iter::FromIterator<Self> for TypeVarVariance {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait VarianceInferable<'db>: Sized {
|
pub(crate) trait VarianceInferable<'db>: Sized {
|
||||||
|
/// The variance of `typevar` in `self`
|
||||||
|
///
|
||||||
|
/// Generally, one will implement this by traversing any types within `self`
|
||||||
|
/// in which `typevar` could occur, and calling `variance_of` recursively on
|
||||||
|
/// them.
|
||||||
|
///
|
||||||
|
/// Sometimes the recursive calls will be in positions where you need to
|
||||||
|
/// specify a non-covariant polarity. See `with_polarity` for more details.
|
||||||
fn variance_of(self, db: &'db dyn Db, typevar: BoundTypeVarInstance<'db>) -> TypeVarVariance;
|
fn variance_of(self, db: &'db dyn Db, typevar: BoundTypeVarInstance<'db>) -> TypeVarVariance;
|
||||||
|
|
||||||
fn with_polarity(self, polarity: TypeVarVariance) -> WithPolarity<Self> {
|
/// Creates a `VarianceInferable` that applies `polarity` (see
|
||||||
|
/// `TypeVarVariance::compose`) to the result of variance inference on the
|
||||||
|
/// underlying value.
|
||||||
|
///
|
||||||
|
/// In some cases, we need to apply a polarity to the recursive call.
|
||||||
|
/// You can do this with `ty.with_polarity(polarity).variance_of(typevar)`.
|
||||||
|
/// Generally, this will be whenever the type occurs in argument-position,
|
||||||
|
/// in which case you will want `TypeVarVariance::Contravariant`, or
|
||||||
|
/// `TypeVarVariance::Invariant` if the value(s) being annotated is known to
|
||||||
|
/// be mutable, such as `T` in `list[T]`. See the [typing spec][typing-spec]
|
||||||
|
/// for more details.
|
||||||
|
///
|
||||||
|
/// [typing-spec]: https://typing.python.org/en/latest/spec/generics.html#variance
|
||||||
|
fn with_polarity(self, polarity: TypeVarVariance) -> impl VarianceInferable<'db> {
|
||||||
WithPolarity {
|
WithPolarity {
|
||||||
variance_inferable: self,
|
variance_inferable: self,
|
||||||
polarity,
|
polarity,
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ cd ..
|
||||||
echo "Project selector: ${PRIMER_SELECTOR}"
|
echo "Project selector: ${PRIMER_SELECTOR}"
|
||||||
# Allow the exit code to be 0 or 1, only fail for actual mypy_primer crashes/bugs
|
# Allow the exit code to be 0 or 1, only fail for actual mypy_primer crashes/bugs
|
||||||
uvx \
|
uvx \
|
||||||
--from="git+https://github.com/hauntsaninja/mypy_primer@a3798a3d7b8470603e650179b0f82deb2154364d" \
|
--from="git+https://github.com/hauntsaninja/mypy_primer@830b80cb00dc8ffee20a7ddcad8d6a13b09c18ed" \
|
||||||
mypy_primer \
|
mypy_primer \
|
||||||
--repo ruff \
|
--repo ruff \
|
||||||
--type-checker ty \
|
--type-checker ty \
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"description": "The root paths of the project, used for finding first-party modules.\n\nAccepts a list of directory paths searched in priority order (first has highest priority).\n\nIf left unspecified, ty will try to detect common project layouts and initialize `root` accordingly:\n\n* if a `./src` directory exists, include `.` and `./src` in the first party search path (src layout or flat) * if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path * otherwise, default to `.` (flat layout)\n\nBesides, if a `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` file), it will also be included in the first party search path.",
|
"description": "The root paths of the project, used for finding first-party modules.\n\nAccepts a list of directory paths searched in priority order (first has highest priority).\n\nIf left unspecified, ty will try to detect common project layouts and initialize `root` accordingly:\n\n* if a `./src` directory exists, include `.` and `./src` in the first party search path (src layout or flat) * if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path * otherwise, default to `.` (flat layout)\n\nBesides, if a `./python` or `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` or `__init__.pyi` file), it will also be included in the first party search path.",
|
||||||
"type": [
|
"type": [
|
||||||
"array",
|
"array",
|
||||||
"null"
|
"null"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue