Merge branch 'main' into typing-self-argument

This commit is contained in:
Shaygan Hooshyari 2025-09-05 20:42:39 +02:00 committed by GitHub
commit a27013bdd3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 1023 additions and 389 deletions

View File

@ -42,3 +42,7 @@ b"a" in bytes("a", "utf-8")
1 in set(set([1]))
'' in {""}
frozenset() in {frozenset()}
# https://github.com/astral-sh/ruff/issues/20238
"b" in f"" "" # Error
"b" in f"" "x" # OK

View File

@ -1,6 +1,6 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast as ast;
use ruff_python_ast::identifier::Identifier;
use ruff_python_ast::{self as ast, ParameterWithDefault};
use ruff_python_semantic::analyze::function_type;
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
));
if let Some(arg) = function_def.parameters.args.get(skip) {
if is_old_style_positional_only(arg) {
checker.report_diagnostic(Pep484StylePositionalOnlyParameter, arg.identifier());
if let Some(param) = function_def.parameters.args.get(skip) {
if param.uses_pep_484_positional_only_convention() {
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("__")
}

View File

@ -1,5 +1,5 @@
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_text_size::Ranged;
@ -75,10 +75,7 @@ fn is_empty(expr: &Expr, semantic: &SemanticModel) -> bool {
Expr::Dict(ast::ExprDict { items, .. }) => items.is_empty(),
Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => value.is_empty(),
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value.is_empty(),
Expr::FString(s) => s
.value
.elements()
.all(|elt| elt.as_literal().is_some_and(|elt| elt.is_empty())),
Expr::FString(s) => is_empty_f_string(s),
Expr::Call(ast::ExprCall {
func,
arguments,

View File

@ -251,3 +251,12 @@ RUF060 Unnecessary membership test on empty collection
25 |
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
|

View File

@ -3225,7 +3225,6 @@ impl<'a> IntoIterator for &'a Box<Parameters> {
/// Used by `Arguments` original type.
///
/// NOTE: This type is different from original Python AST.
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
pub struct ParameterWithDefault {
@ -3247,6 +3246,14 @@ impl ParameterWithDefault {
pub fn annotation(&self) -> Option<&Expr> {
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.

View File

@ -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
* 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.
**Default value**: `null`

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

@ -36,7 +36,7 @@ def test(): -> "int":
<small>
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) ·
[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>
**What it does**
@ -58,7 +58,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
<small>
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) ·
[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>
**What it does**
@ -88,7 +88,7 @@ f(int) # error
<small>
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) ·
[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>
**What it does**
@ -117,7 +117,7 @@ a = 1
<small>
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) ·
[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>
**What it does**
@ -147,7 +147,7 @@ class C(A, B): ...
<small>
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) ·
[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>
**What it does**
@ -177,7 +177,7 @@ class B(A): ...
<small>
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) ·
[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>
**What it does**
@ -202,7 +202,7 @@ class B(A, A): ...
<small>
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) ·
[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>
**What it does**
@ -306,7 +306,7 @@ def test(): -> "Literal[5]":
<small>
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) ·
[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>
**What it does**
@ -334,7 +334,7 @@ class C(A, B): ...
<small>
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) ·
[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>
**What it does**
@ -358,7 +358,7 @@ t[3] # IndexError: tuple index out of range
<small>
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) ·
[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>
**What it does**
@ -445,7 +445,7 @@ an atypical memory layout.
<small>
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) ·
[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>
**What it does**
@ -470,7 +470,7 @@ func("foo") # error: [invalid-argument-type]
<small>
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) ·
[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>
**What it does**
@ -496,7 +496,7 @@ a: int = ''
<small>
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) ·
[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>
**What it does**
@ -528,7 +528,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
<small>
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) ·
[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>
**What it does**
@ -562,7 +562,7 @@ asyncio.run(main())
<small>
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) ·
[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>
**What it does**
@ -584,7 +584,7 @@ class A(42): ... # error: [invalid-base]
<small>
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) ·
[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>
**What it does**
@ -609,7 +609,7 @@ with 1:
<small>
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) ·
[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>
**What it does**
@ -636,7 +636,7 @@ a: str
<small>
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) ·
[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>
**What it does**
@ -678,7 +678,7 @@ except ZeroDivisionError:
<small>
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) ·
[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>
**What it does**
@ -709,7 +709,7 @@ class C[U](Generic[T]): ...
<small>
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) ·
[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>
**What it does**
@ -738,7 +738,7 @@ alice["height"] # KeyError: 'height'
<small>
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) ·
[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>
**What it does**
@ -771,7 +771,7 @@ def f(t: TypeVar("U")): ...
<small>
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) ·
[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>
**What it does**
@ -803,7 +803,7 @@ class B(metaclass=f): ...
<small>
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) ·
[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>
**What it does**
@ -833,7 +833,7 @@ TypeError: can only inherit from a NamedTuple type and Generic
<small>
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) ·
[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>
**What it does**
@ -881,7 +881,7 @@ def foo(x: int) -> int: ...
<small>
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) ·
[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>
**What it does**
@ -905,7 +905,7 @@ def f(a: int = ''): ...
<small>
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) ·
[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>
**What it does**
@ -937,7 +937,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
<small>
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) ·
[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>
Checks for `raise` statements that raise non-exceptions or use invalid
@ -984,7 +984,7 @@ def g():
<small>
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) ·
[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>
**What it does**
@ -1007,7 +1007,7 @@ def func() -> int:
<small>
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) ·
[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>
**What it does**
@ -1061,7 +1061,7 @@ TODO #14889
<small>
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) ·
[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>
**What it does**
@ -1086,7 +1086,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
<small>
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) ·
[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>
**What it does**
@ -1114,7 +1114,7 @@ TYPE_CHECKING = ''
<small>
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) ·
[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>
**What it does**
@ -1142,7 +1142,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
<small>
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) ·
[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>
**What it does**
@ -1174,7 +1174,7 @@ f(10) # Error
<small>
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) ·
[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>
**What it does**
@ -1206,7 +1206,7 @@ class C:
<small>
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) ·
[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>
**What it does**
@ -1239,7 +1239,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
<small>
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) ·
[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>
**What it does**
@ -1262,7 +1262,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
<small>
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) ·
[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>
**What it does**
@ -1293,7 +1293,7 @@ alice["age"] # KeyError
<small>
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) ·
[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>
**What it does**
@ -1320,7 +1320,7 @@ func("string") # error: [no-matching-overload]
<small>
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) ·
[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>
**What it does**
@ -1342,7 +1342,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
<small>
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) ·
[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>
**What it does**
@ -1366,7 +1366,7 @@ for i in 34: # TypeError: 'int' object is not iterable
<small>
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) ·
[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>
**What it does**
@ -1420,7 +1420,7 @@ def test(): -> "int":
<small>
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) ·
[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>
**What it does**
@ -1448,7 +1448,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
<small>
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) ·
[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>
**What it does**
@ -1475,7 +1475,7 @@ class B(A): ... # Error raised here
<small>
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) ·
[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>
**What it does**
@ -1500,7 +1500,7 @@ f("foo") # Error raised here
<small>
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) ·
[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>
**What it does**
@ -1526,7 +1526,7 @@ def _(x: int):
<small>
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) ·
[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>
**What it does**
@ -1570,7 +1570,7 @@ class A:
<small>
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) ·
[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>
**What it does**
@ -1595,7 +1595,7 @@ f(x=1, y=2) # Error raised here
<small>
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) ·
[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>
**What it does**
@ -1621,7 +1621,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
<small>
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) ·
[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>
**What it does**
@ -1644,7 +1644,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
<small>
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) ·
[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>
**What it does**
@ -1667,7 +1667,7 @@ print(x) # NameError: name 'x' is not defined
<small>
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) ·
[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>
**What it does**
@ -1702,7 +1702,7 @@ b1 < b2 < b1 # exception raised here
<small>
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) ·
[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>
**What it does**
@ -1728,7 +1728,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
<small>
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) ·
[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>
**What it does**
@ -1751,7 +1751,7 @@ l[1:10:0] # ValueError: slice step cannot be zero
<small>
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) ·
[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>
**What it does**
@ -1790,7 +1790,7 @@ class SubProto(BaseProto, Protocol):
<small>
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) ·
[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>
**What it does**
@ -1843,7 +1843,7 @@ a = 20 / 0 # type: ignore
<small>
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) ·
[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>
**What it does**
@ -1869,7 +1869,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
<small>
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) ·
[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>
**What it does**
@ -1899,7 +1899,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
<small>
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) ·
[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>
**What it does**
@ -1929,7 +1929,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
<small>
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) ·
[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>
**What it does**
@ -1954,7 +1954,7 @@ cast(int, f()) # Redundant
<small>
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) ·
[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>
**What it does**
@ -2005,7 +2005,7 @@ a = 20 / 0 # ty: ignore[division-by-zero]
<small>
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) ·
[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>
**What it does**
@ -2059,7 +2059,7 @@ def g():
<small>
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) ·
[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>
**What it does**
@ -2096,7 +2096,7 @@ class D(C): ... # error: [unsupported-base]
<small>
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) ·
[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>
**What it does**
@ -2118,7 +2118,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
<small>
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) ·
[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>
**What it does**

View File

@ -1752,3 +1752,126 @@ fn default_root_tests_package() -> anyhow::Result<()> {
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(())
}

View File

@ -259,11 +259,29 @@ impl Options {
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,
// we also include the `tests` directory if it exists and is not a package.
let tests_dir = project_root.join("tests");
if system.is_directory(&tests_dir)
&& !system.is_file(&tests_dir.join("__init__.py"))
&& !system.is_file(&tests_dir.join("__init__.pyi"))
&& !roots.contains(&tests_dir)
{
// 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
/// * 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.
#[serde(skip_serializing_if = "Option::is_none")]
#[option(

View File

@ -28,6 +28,23 @@ def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P.
class Foo:
def method(self, x: Self):
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

View File

@ -68,6 +68,78 @@ def _(flag: bool):
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 arguments 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
### 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
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`.
The issue is that argument expansion might produce a splatted value with a different arity than what

View File

@ -281,12 +281,10 @@ def if_else_exhaustive(x: A[D] | B[E] | C[F]):
elif isinstance(x, C):
pass
else:
# TODO: both of these are false positives (https://github.com/astral-sh/ty/issues/456)
no_diagnostic_here # error: [unresolved-reference]
assert_never(x) # error: [type-assertion-failure]
no_diagnostic_here
assert_never(x)
# 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: # error: [invalid-return-type]
def if_else_exhaustive_no_assertion(x: A[D] | B[E] | C[F]) -> int:
if isinstance(x, A):
return 0
elif isinstance(x, B):

View File

@ -308,3 +308,88 @@ def i[T: Intersection[type[Bar], type[Baz | Spam]], U: (type[Eggs], type[Ham])](
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]]]
```

View File

@ -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 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)
# revealed: {"__index__": MethodMember(`(self) -> int`)}
# revealed: {"__index__": MethodMember(`(self, /) -> int`)}
reveal_protocol_interface(SupportsIndex)
# revealed: {"__abs__": MethodMember(`(self) -> Unknown`)}
# revealed: {"__abs__": MethodMember(`(self, /) -> Unknown`)}
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)
# 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(Iterator[int])) # revealed: frozenset[str]
# revealed: {"__abs__": MethodMember(`(self) -> int`)}
# revealed: {"__abs__": MethodMember(`(self, /) -> 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])
class BaseProto(Protocol):
@ -450,10 +450,10 @@ class BaseProto(Protocol):
class SubProto(BaseProto, Protocol):
def member(self) -> bool: ...
# revealed: {"member": MethodMember(`(self) -> int`)}
# revealed: {"member": MethodMember(`(self, /) -> int`)}
reveal_protocol_interface(BaseProto)
# revealed: {"member": MethodMember(`(self) -> bool`)}
# revealed: {"member": MethodMember(`(self, /) -> bool`)}
reveal_protocol_interface(SubProto)
class ProtoWithClassVar(Protocol):
@ -1767,7 +1767,7 @@ class Foo(Protocol):
def method(self) -> str: ...
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:
def __init__(self):
@ -1776,6 +1776,31 @@ class Bar:
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
Two protocols `P1` and `P2`, both with a method member `x`, are considered equivalent if the

View File

@ -47,7 +47,7 @@ error[invalid-named-tuple]: NamedTuple field without default value cannot follow
| --------------------- 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…
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…
8 | longitude: float
|
@ -66,7 +66,7 @@ error[invalid-named-tuple]: NamedTuple field without default value cannot follow
6 | latitude: float
7 | # error: [invalid-named-tuple] "NamedTuple field without default value cannot follow field(s) with default value(s): Field `longit…
8 | longitude: float
| ^^^^^^^^^ Field `longitude` defined here without a default value
| ^^^^^^^^^^^^^^^^ Field `longitude` defined here without a default value
9 |
10 | class StrangeLocation(NamedTuple):
|
@ -83,7 +83,7 @@ error[invalid-named-tuple]: NamedTuple field without default value cannot follow
14 | altitude: float = 0.0
| --------------------- Earlier field `altitude` defined here with a default value
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]
|
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
15 | latitude: 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 |
18 | class VeryStrangeLocation(NamedTuple):
|
@ -115,7 +115,7 @@ error[invalid-named-tuple]: NamedTuple field without default value cannot follow
18 | class VeryStrangeLocation(NamedTuple):
19 | altitude: float = 0.0
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]
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
20 | latitude: 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
|
info: Earlier field `altitude` was defined with a default value

View File

@ -37,6 +37,14 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/typed_dict.md
23 |
24 | def write_to_non_literal_string_key(person: Person, str_key: str):
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
@ -100,6 +108,16 @@ error[invalid-assignment]: Invalid assignment to key "age" with declared type `i
20 |
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
```
@ -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):
25 | person[str_key] = "Alice" # error: [invalid-key]
| ^^^^^^^
26 | from typing_extensions import ReadOnly
|
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
```

View File

@ -444,8 +444,7 @@ def _(person: Person, unknown_key: Any):
## `ReadOnly`
`ReadOnly` is not supported yet, but this test makes sure that we do not emit any false positive
diagnostics:
Assignments to keys that are marked `ReadOnly` will produce an error:
```py
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["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
```
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`
```py
@ -846,6 +861,19 @@ def write_to_non_literal_string_key(person: Person, str_key: str):
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
`TypedDict` can be imported with aliases and should work correctly:

View File

@ -39,6 +39,7 @@ django-stubs
downforeveryone
dragonchain
dulwich
egglog-python
flake8
flake8-pyi
freqtrade

View File

@ -49,6 +49,12 @@ pub struct AstNodeRef<T> {
_node: PhantomData<T>,
}
impl<T> AstNodeRef<T> {
pub(crate) fn index(&self) -> NodeIndex {
self.index
}
}
impl<T> AstNodeRef<T>
where
T: HasNodeIndex + Ranged + PartialEq + Debug,

View File

@ -1,5 +1,7 @@
use ruff_python_ast::{HasNodeIndex, NodeIndex};
use crate::ast_node_ref::AstNodeRef;
/// Compact key for a node for use in a hash map.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
pub(super) struct NodeKey(NodeIndex);
@ -11,4 +13,8 @@ impl NodeKey {
{
NodeKey(node.node_index().load())
}
pub(super) fn from_node_ref<T>(node_ref: &AstNodeRef<T>) -> Self {
NodeKey(node_ref.index())
}
}

View File

@ -486,6 +486,9 @@ type DeclaredTypeAndConflictingTypes<'db> = (
pub(crate) struct PlaceFromDeclarationsResult<'db> {
place_and_quals: PlaceAndQualifiers<'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> {
@ -496,6 +499,7 @@ impl<'db> PlaceFromDeclarationsResult<'db> {
PlaceFromDeclarationsResult {
place_and_quals,
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.
///
/// 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)
}
/// 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.
pub(crate) fn is_bare_final(&self) -> Option<TypeQualifiers> {
match self {
@ -1211,6 +1205,8 @@ fn place_from_declarations_impl<'db>(
let reachability_constraints = declarations.reachability_constraints;
let boundness_analysis = declarations.boundness_analysis;
let mut declarations = declarations.peekable();
let mut first_declaration = None;
let mut exactly_one_declaration = false;
let is_non_exported = |declaration: Definition<'db>| {
requires_explicit_reexport.is_yes() && !is_reexported(db, declaration)
@ -1241,6 +1237,13 @@ fn place_from_declarations_impl<'db>(
return None;
}
if first_declaration.is_none() {
first_declaration = Some(declaration);
exactly_one_declaration = true;
} else {
exactly_one_declaration = false;
}
let static_reachability =
reachability_constraints.evaluate(db, predicates, reachability_constraint);
@ -1297,10 +1300,18 @@ fn place_from_declarations_impl<'db>(
if let Some(conflicting) = conflicting {
PlaceFromDeclarationsResult::conflict(place_and_quals, conflicting)
} else {
place_and_quals.into()
PlaceFromDeclarationsResult {
place_and_quals,
conflicting_types: None,
single_declaration: first_declaration.filter(|_| exactly_one_declaration),
}
}
} else {
Place::Unbound.into()
PlaceFromDeclarationsResult {
place_and_quals: Place::Unbound.into(),
conflicting_types: None,
single_declaration: None,
}
}
}

View File

@ -159,7 +159,6 @@ pub(crate) fn attribute_scopes<'db, 's>(
class_body_scope: ScopeId<'db>,
) -> impl Iterator<Item = FileScopeId> + use<'s, 'db> {
let file = class_body_scope.file(db);
let module = parsed_module(db, file).load(db);
let index = semantic_index(db, file);
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)
};
function_scope.node().as_function(&module)?;
function_scope.node().as_function()?;
Some(function_scope_id)
})
}
@ -332,6 +331,39 @@ impl<'db> SemanticIndex<'db> {
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 {
self.parent_scope_id(scope_id)
.is_none_or(|parent_scope_id| {

View File

@ -2644,7 +2644,7 @@ impl SemanticSyntaxContext for SemanticIndexBuilder<'_, '_> {
match scope.kind() {
ScopeKind::Class | ScopeKind::Lambda => return false,
ScopeKind::Function => {
return scope.node().expect_function(self.module).is_async;
return scope.node().expect_function().node(self.module).is_async;
}
ScopeKind::Comprehension
| ScopeKind::Module

View File

@ -769,13 +769,14 @@ impl DefinitionKind<'_> {
target_range.cover(value_range)
}
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 {
let value_range = value.node(module).range();
target_range.cover(value_range)
} else {
target_range
full_range = full_range.cover(value.node(module).range());
}
full_range
}
DefinitionKind::AugmentedAssignment(aug_assign) => aug_assign.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))
}
}
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))
}
}

View File

@ -397,52 +397,38 @@ impl NodeWithScopeKind {
}
}
pub(crate) fn expect_class<'ast>(
&self,
module: &'ast ParsedModuleRef,
) -> &'ast ast::StmtClassDef {
pub(crate) fn as_class(&self) -> Option<&AstNodeRef<ast::StmtClassDef>> {
match self {
Self::Class(class) => class.node(module),
_ => 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)),
Self::Class(class) => Some(class),
_ => None,
}
}
pub(crate) fn expect_function<'ast>(
&self,
module: &'ast ParsedModuleRef,
) -> &'ast ast::StmtFunctionDef {
self.as_function(module).expect("expected function")
pub(crate) fn expect_class(&self) -> &AstNodeRef<ast::StmtClassDef> {
self.as_class().expect("expected class")
}
pub(crate) fn expect_type_alias<'ast>(
&self,
module: &'ast ParsedModuleRef,
) -> &'ast ast::StmtTypeAlias {
pub(crate) fn as_function(&self) -> Option<&AstNodeRef<ast::StmtFunctionDef>> {
match self {
Self::TypeAlias(type_alias) => type_alias.node(module),
_ => 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)),
Self::Function(function) => Some(function),
_ => 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)]

View File

@ -5649,7 +5649,7 @@ impl<'db> Type<'db> {
SpecialFormType::TypingSelf => {
let module = parsed_module(db, scope_id.file(db)).load(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 {
fallback_type: Type::unknown(),
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::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)
if other_typevar == typevar =>
{
@ -6560,11 +6561,19 @@ impl<'db> VarianceInferable<'db> for Type<'db> {
Type::ProtocolInstance(protocol_instance_type) => {
protocol_instance_type.variance_of(db, typevar)
}
// unions are covariant in their disjuncts
Type::Union(union_type) => union_type
.elements(db)
.iter()
.map(|ty| ty.variance_of(db, typevar))
.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
.positive(db)
.iter()
@ -9355,9 +9364,7 @@ fn walk_pep_695_type_alias<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
impl<'db> PEP695TypeAliasType<'db> {
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'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(&module);
let type_alias_stmt_node = scope.node(db).expect_type_alias();
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> {
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(&module);
let type_alias_stmt_node = scope.node(db).expect_type_alias();
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 {

View File

@ -9,12 +9,10 @@ use super::{
use crate::FxOrderMap;
use crate::module_resolver::KnownModule;
use crate::semantic_index::definition::{Definition, DefinitionState};
use crate::semantic_index::place::ScopedPlaceId;
use crate::semantic_index::scope::NodeWithScopeKind;
use crate::semantic_index::symbol::Symbol;
use crate::semantic_index::{
BindingWithConstraints, DeclarationWithConstraint, SemanticIndex, attribute_declarations,
attribute_scopes,
DeclarationWithConstraint, SemanticIndex, attribute_declarations, attribute_scopes,
};
use crate::types::constraints::{ConstraintSet, Constraints, IteratorConstraintsExtension};
use crate::types::context::InferContext;
@ -29,10 +27,10 @@ use crate::types::typed_dict::typed_dict_params_from_class_def;
use crate::types::{
ApplyTypeMappingVisitor, Binding, BoundSuperError, BoundSuperType, CallableType,
DataclassParams, DeprecatedInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
IsEquivalentVisitor, KnownInstanceType, ManualPEP695TypeAliasType, NormalizedVisitor,
PropertyInstanceType, StringLiteralType, TypeAliasType, TypeMapping, TypeRelation,
TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypedDictParams, UnionBuilder,
VarianceInferable, declaration_type, infer_definition_types,
IsEquivalentVisitor, KnownInstanceType, ManualPEP695TypeAliasType, MaterializationKind,
NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType, TypeMapping,
TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypedDictParams,
UnionBuilder, VarianceInferable, declaration_type, infer_definition_types,
};
use crate::{
Db, FxIndexMap, FxOrderSet, Program,
@ -1271,6 +1269,8 @@ pub(crate) enum FieldKind<'db> {
TypedDict {
/// Whether this field is required
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>,
/// Kind-specific metadata for this field
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<'_> {
@ -1292,7 +1295,14 @@ impl Field<'_> {
FieldKind::Dataclass {
init, default_ty, ..
} => 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 file = scope.file(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| {
let index = semantic_index(db, scope.file(db));
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!).
fn node<'ast>(self, db: &'db dyn Db, module: &'ast ParsedModuleRef) -> &'ast ast::StmtClassDef {
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> {
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));
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(
@ -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
/// returned unchanged. For a non-specialized generic class, we return a generic alias that
/// applies the default specialization to the class's typevars.
@ -2264,8 +2285,36 @@ impl<'db> ClassLiteral<'db> {
(CodeGeneratorKind::TypedDict, "__setitem__") => {
let fields = self.fields(db, specialization, field_policy);
// Add (key type, value type) overloads for all TypedDict items ("fields"):
let overloads = fields.iter().map(|(name, field)| {
// Add (key type, value type) overloads for all TypedDict items ("fields") that are not read-only:
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()));
Signature::new(
@ -2617,7 +2666,9 @@ impl<'db> ClassLiteral<'db> {
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() {
continue;
}
@ -2668,13 +2719,18 @@ impl<'db> ClassLiteral<'db> {
.expect("TypedDictParams should be available for CodeGeneratorKind::TypedDict")
.contains(TypedDictParams::TOTAL)
};
FieldKind::TypedDict { is_required }
FieldKind::TypedDict {
is_required,
is_read_only: attr.is_read_only(),
}
}
};
let mut field = Field {
declared_ty: attr_ty.apply_optional_specialization(db, specialization),
kind,
single_declaration,
};
// 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 is_valid_scope = |method_scope: ScopeId<'db>| {
if let Some(method_def) = method_scope.node(db).as_function(&module) {
let method_name = method_def.name.as_str();
if let Some(method_def) = method_scope.node(db).as_function() {
let method_name = method_def.node(&module).name.as_str();
if let Place::Type(Type::FunctionLiteral(method_type), _) =
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
let is_method_reachable =
if let Some(method_def) = method_scope.node(db).as_function(&module) {
let is_method_reachable = if let Some(method_def) = method_scope.node(db).as_function()
{
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
.all_reachable_symbol_bindings(method_place)
.find_map(|bind| {
@ -3266,7 +3324,7 @@ impl<'db> ClassLiteral<'db> {
pub(super) fn header_range(self, db: &'db dyn Db) -> TextRange {
let class_scope = self.body_scope(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;
TextRange::new(
class_name.start(),
@ -3277,54 +3335,6 @@ impl<'db> ClassLiteral<'db> {
.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> {
@ -4775,8 +4785,7 @@ impl KnownClass {
// 2. The first parameter of the current function (typically `self` or `cls`)
match overload.parameter_types() {
[] => {
let Some(enclosing_class) =
nearest_enclosing_class(db, index, scope, module)
let Some(enclosing_class) = nearest_enclosing_class(db, index, scope)
else {
BoundSuperError::UnavailableImplicitArguments
.report_diagnostic(context, call_expression.into());

View File

@ -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.
let mut function_scope_tys = index
.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)))
.filter_map(Type::into_function_literal);

View File

@ -6,7 +6,6 @@ use super::{
add_inferred_python_version_hint_to_diagnostic,
};
use crate::lint::{Level, LintRegistryBuilder, LintStatus};
use crate::semantic_index::SemanticIndex;
use crate::semantic_index::definition::Definition;
use crate::semantic_index::place::{PlaceTable, ScopedPlaceId};
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>(
context: &InferContext<'db, '_>,
class: ClassLiteral<'db>,
index: &'db SemanticIndex<'db>,
field_name: &str,
field_with_default: &str,
(field, field_def): &(Name, Option<Definition<'db>>),
(field_with_default, field_with_default_def): &(Name, Option<Definition<'db>>),
) {
let db = context.db();
let module = context.module();
let diagnostic_range = class
.first_declaration_of_name(db, field_name, index)
.and_then(|definition| definition.declaration.definition())
let diagnostic_range = field_def
.map(|definition| definition.kind(db).full_range(module))
.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!(
"Field `{field_name}` defined here without a default value"
"Field `{field}` defined here without a default value",
));
let Some(field_with_default_range) = class
.first_binding_of_name(db, field_with_default, index)
.and_then(|definition| definition.binding.definition())
.map(|definition| definition.kind(db).full_range(module))
let Some(field_with_default_range) =
field_with_default_def.map(|definition| definition.kind(db).full_range(module))
else {
return;
};
@ -2933,7 +2927,7 @@ pub(super) fn report_namedtuple_field_without_default_after_field_with_default<'
);
} else {
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",
));
}
}

View File

@ -200,15 +200,15 @@ impl ClassDisplay<'_> {
match ancestor_scope.kind() {
ScopeKind::Class => {
if let Some(class_def) = node.as_class(&module_ast) {
name_parts.push(class_def.name.as_str().to_string());
if let Some(class_def) = node.as_class() {
name_parts.push(class_def.node(&module_ast).name.as_str().to_string());
}
}
ScopeKind::Function => {
if let Some(function_def) = node.as_function(&module_ast) {
if let Some(function_def) = node.as_function() {
name_parts.push(format!(
"<locals of function '{}'>",
function_def.name.as_str()
function_def.node(&module_ast).name.as_str()
));
}
}

View File

@ -55,7 +55,7 @@ use bitflags::bitflags;
use ruff_db::diagnostic::{Annotation, DiagnosticId, Severity, Span};
use ruff_db::files::{File, FileRange};
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 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::definition::Definition;
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::constraints::{ConstraintSet, Constraints};
use crate::types::context::InferContext;
@ -80,7 +80,7 @@ use crate::types::{
BoundMethodType, BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral, ClassType,
DeprecatedInstance, DynamicType, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
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};
@ -236,6 +236,22 @@ impl<'db> OverloadLiteral<'db> {
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>(
self,
db: &dyn Db,
@ -249,7 +265,7 @@ impl<'db> OverloadLiteral<'db> {
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.
@ -258,7 +274,8 @@ impl<'db> OverloadLiteral<'db> {
self.file(db),
self.body_scope(db)
.node(db)
.expect_function(module)
.expect_function()
.node(module)
.name
.range,
)
@ -274,9 +291,8 @@ impl<'db> OverloadLiteral<'db> {
/// over-invalidation.
fn definition(self, db: &'db dyn Db) -> Definition<'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));
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
@ -290,7 +306,8 @@ impl<'db> OverloadLiteral<'db> {
let use_id = self
.body_scope(db)
.node(db)
.expect_function(&module)
.expect_function()
.node(&module)
.name
.scoped_use_id(db, scope);
@ -325,17 +342,79 @@ impl<'db> OverloadLiteral<'db> {
db: &'db dyn Db,
inherited_generic_context: Option<GenericContext<'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 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 generic_context = function_stmt_node.type_params.as_ref().map(|type_params| {
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)
});
let index = semantic_index(db, scope.file(db));
let is_generator = scope.file_scope_id(db).is_generator_function(index);
let file_scope_id = scope.file_scope_id(db);
let is_generator = file_scope_id.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(
db,
@ -344,6 +423,7 @@ impl<'db> OverloadLiteral<'db> {
definition,
function_stmt_node,
is_generator,
has_implicitly_positional_first_parameter,
)
}
@ -356,7 +436,7 @@ impl<'db> OverloadLiteral<'db> {
let span = Span::from(function_scope.file(db));
let node = function_scope.node(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
.and_then(|parameter_index| {
func_def
@ -376,7 +456,7 @@ impl<'db> OverloadLiteral<'db> {
let span = Span::from(function_scope.file(db));
let node = function_scope.node(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 mut signature = func_def.name.range.cover(func_def.parameters.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
/// classmethod.
pub(crate) fn is_classmethod(self, db: &'db dyn Db) -> bool {
self.has_known_decorator(db, FunctionDecorators::CLASSMETHOD)
|| matches!(
self.name(db).as_str(),
"__init_subclass__" | "__class_getitem__"
)
self.iter_overloads_and_implementation(db)
.any(|overload| overload.is_classmethod(db))
}
/// Returns true if this method is decorated with `@staticmethod`, or if it is implicitly a
/// static method.
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`.

View File

@ -125,6 +125,7 @@ use crate::types::typed_dict::{
validate_typed_dict_key_assignment,
};
use crate::types::unpacker::{UnpackResult, Unpacker};
use crate::types::visitor::any_over_type;
use crate::types::{
CallDunderError, CallableType, ClassLiteral, ClassType, DataclassParams, DynamicType,
IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType, LintDiagnosticGuard,
@ -421,12 +422,11 @@ pub(crate) fn nearest_enclosing_class<'db>(
db: &'db dyn Db,
semantic: &SemanticIndex<'db>,
scope: ScopeId,
parsed: &ParsedModuleRef,
) -> Option<ClassLiteral<'db>> {
semantic
.ancestor_scopes(scope.file_scope_id(db))
.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);
infer_definition_types(db, definition)
.declaration_type(definition)
@ -1190,14 +1190,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
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()
{
report_namedtuple_field_without_default_after_field_with_default(
&self.context,
class,
self.index,
&field_name,
&(field_name, field.single_declaration),
field_with_default,
);
}
@ -2417,29 +2417,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
/// behaviour to the [`nearest_enclosing_class`] function.
fn class_context_of_current_method(&self) -> Option<ClassType<'db>> {
let current_scope_id = self.scope().file_scope_id(self.db());
let current_scope = self.index.scope(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);
let class_definition = self.index.class_definition_of_method(current_scope_id)?;
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() {
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>(
@ -9271,8 +9252,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let typevars: Result<FxOrderSet<_>, GenericContextError> = typevars
.iter()
.map(|typevar| match typevar {
Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => bind_typevar(
.map(|typevar| {
if let Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) = typevar {
bind_typevar(
self.db(),
self.module(),
self.index,
@ -9280,17 +9262,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.typevar_binding_context,
*typevar,
)
.ok_or(GenericContextError::InvalidArgument),
Type::Dynamic(DynamicType::TodoUnpack) => Err(GenericContextError::NotYetSupported),
Type::NominalInstance(nominal)
if matches!(
.ok_or(GenericContextError::InvalidArgument)
} else if any_over_type(self.db(), *typevar, &|ty| match ty {
Type::Dynamic(DynamicType::TodoUnpack) => true,
Type::NominalInstance(nominal) => matches!(
nominal.class(self.db()).known(self.db()),
Some(KnownClass::TypeVarTuple | KnownClass::ParamSpec)
) =>
{
),
_ => false,
}) {
Err(GenericContextError::NotYetSupported)
}
_ => {
} else {
if let Some(builder) =
self.context.report_lint(&INVALID_ARGUMENT_TYPE, value_node)
{
@ -11265,19 +11247,15 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
// `Callable[]`.
return None;
}
match self.infer_name_load(name) {
Type::Dynamic(DynamicType::TodoPEP695ParamSpec) => {
return Some(Parameters::todo());
}
Type::NominalInstance(nominal)
if nominal
if any_over_type(self.db(), self.infer_name_load(name), &|ty| match ty {
Type::Dynamic(DynamicType::TodoPEP695ParamSpec) => true,
Type::NominalInstance(nominal) => nominal
.class(self.db())
.is_known(self.db(), KnownClass::ParamSpec) =>
{
.is_known(self.db(), KnownClass::ParamSpec),
_ => false,
}) {
return Some(Parameters::todo());
}
_ => {}
}
}
_ => {}
}

View File

@ -174,10 +174,10 @@ impl ClassInfoConstraintFunction {
fn generate_constraint<'db>(self, db: &'db dyn Db, classinfo: Type<'db>) -> Option<Type<'db>> {
let constraint_fn = |class: ClassLiteral<'db>| match self {
ClassInfoConstraintFunction::IsInstance => {
Type::instance(db, class.default_specialization(db))
Type::instance(db, class.top_materialization(db))
}
ClassInfoConstraintFunction::IsSubclass => {
SubclassOfType::from(db, class.default_specialization(db))
SubclassOfType::from(db, class.top_materialization(db))
}
};

View File

@ -12,7 +12,7 @@
use std::{collections::HashMap, slice::Iter};
use itertools::EitherOrBoth;
use itertools::{EitherOrBoth, Itertools};
use smallvec::{SmallVec, smallvec_inline};
use super::{
@ -380,6 +380,7 @@ impl<'db> Signature<'db> {
definition: Definition<'db>,
function_node: &ast::StmtFunctionDef,
is_generator: bool,
has_implicitly_positional_first_parameter: bool,
) -> Self {
let parameters =
Parameters::from_parameters(db, definition, function_node.parameters.as_ref());
@ -1168,6 +1169,7 @@ impl<'db> Parameters<'db> {
db: &'db dyn Db,
definition: Definition<'db>,
parameters: &ast::Parameters,
has_implicitly_positional_first_parameter: bool,
) -> Self {
let ast::Parameters {
posonlyargs,
@ -1178,30 +1180,51 @@ impl<'db> Parameters<'db> {
range: _,
node_index: _,
} = parameters;
let default_type = |param: &ast::ParameterWithDefault| {
param
.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(
db,
definition,
&arg.parameter,
&param.parameter,
ParameterKind::PositionalOnly {
name: Some(arg.parameter.name.id.clone()),
default_type: default_type(arg),
name: Some(param.parameter.name.id.clone()),
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 is_method = method_type.is_some();
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 positional_or_keyword = args.iter().map(|arg| {
// TODO(https://github.com/astral-sh/ty/issues/159): Also set the type for `cls` argument
let positional_or_keyword = pos_or_keyword_iter.map(|arg| {
if is_method
&& !is_staticmethod
&& !is_classmethod
@ -1232,6 +1255,7 @@ impl<'db> Parameters<'db> {
)
}
});
let variadic = vararg.as_ref().map(|arg| {
Parameter::from_node_and_kind(
db,
@ -1242,6 +1266,7 @@ impl<'db> Parameters<'db> {
},
)
});
let keyword_only = kwonlyargs.iter().map(|arg| {
Parameter::from_node_and_kind(
db,
@ -1253,6 +1278,7 @@ impl<'db> Parameters<'db> {
},
)
});
let keywords = kwarg.as_ref().map(|arg| {
Parameter::from_node_and_kind(
db,
@ -1263,8 +1289,10 @@ impl<'db> Parameters<'db> {
},
)
});
Self::new(
positional_only
.into_iter()
.chain(positional_or_keyword)
.chain(variadic)
.chain(keyword_only)

View File

@ -1,6 +1,9 @@
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::{self as ast, AnyNodeRef, StmtClassDef, name::Name};
use ruff_text_size::Ranged;
use super::class::{ClassType, CodeGeneratorKind, Field};
use super::context::InferContext;
@ -122,6 +125,10 @@ impl TypedDictAssignmentKind {
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`.
@ -153,6 +160,47 @@ pub(super) fn validate_typed_dict_key_assignment<'db, 'ast>(
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
if value_ty.is_assignable_to(db, item.declared_ty) {
return true;
@ -184,6 +232,8 @@ pub(super) fn validate_typed_dict_key_assignment<'db, 'ast>(
.secondary(key_node.into())
.message(format_args!("key has declared type `{item_type_d}`")),
);
add_item_definition_subdiagnostic(&mut diagnostic, "Item declared here");
}
false

View File

@ -108,9 +108,30 @@ impl std::iter::FromIterator<Self> for TypeVarVariance {
}
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 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 {
variance_inferable: self,
polarity,

View File

@ -20,7 +20,7 @@ cd ..
echo "Project selector: ${PRIMER_SELECTOR}"
# Allow the exit code to be 0 or 1, only fail for actual mypy_primer crashes/bugs
uvx \
--from="git+https://github.com/hauntsaninja/mypy_primer@a3798a3d7b8470603e650179b0f82deb2154364d" \
--from="git+https://github.com/hauntsaninja/mypy_primer@830b80cb00dc8ffee20a7ddcad8d6a13b09c18ed" \
mypy_primer \
--repo ruff \
--type-checker ty \

2
ty.schema.json generated
View File

@ -101,7 +101,7 @@
]
},
"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": [
"array",
"null"