Skip to content

Commit 0d656db

Browse files
committed
Merge remote-tracking branch 'origin/main' into dcreager/special-class
* origin/main: [red-knot] Default `python-platform` to current platform (#17183) [red-knot] Add new 'unreachable code' test case (#17306) [red-knot] mypy_primer: Run on `async-utils` (#17303) [red-knot] Add custom `__setattr__` support (#16748) [red-knot] Add `__init__` arguments check when doing `try_call` on a class literal (#16512) [`flake8-pie`] Avoid false positive for multiple assignment with `auto()` (`PIE796`) (#17274) [syntax-errors] Async comprehension in sync comprehension (#17177) [`airflow`] Expand module path check to individual symbols (`AIR302`) (#17278) [syntax-errors] Check annotations in annotated assignments (#17283) [syntax-errors] Extend annotation checks to `await` (#17282) [red-knot] Add support for `assert_never` (#17287) [`flake8-pytest-style`] Avoid false positive for legacy form of `pytest.raises` (`PT011`) (#17231) [red-knot] Do not show types for literal expressions on hover (#17290) [red-knot] Fix dead-code clippy warning (#17291) [red-knot] Reachability analysis (#17199) [red-knot] Don't use latency-sensitive for handlers (#17227)
2 parents 43554e2 + 8249a72 commit 0d656db

File tree

77 files changed

+7297
-2337
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+7297
-2337
lines changed

.github/workflows/mypy_primer.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ jobs:
4545

4646
- name: Install mypy_primer
4747
run: |
48-
uv tool install "git+https://github.com/astral-sh/mypy_primer.git@add-red-knot-support-v3"
48+
uv tool install "git+https://github.com/astral-sh/mypy_primer.git@add-red-knot-support-v4"
4949
5050
- name: Run mypy_primer
5151
shell: bash
@@ -68,7 +68,7 @@ jobs:
6868
--type-checker knot \
6969
--old base_commit \
7070
--new "$GITHUB_SHA" \
71-
--project-selector '/(mypy_primer|black|pyp|git-revise|zipp|arrow|isort|itsdangerous|rich|packaging|pybind11|pyinstrument|typeshed-stats|scrapy|werkzeug|bidict)$' \
71+
--project-selector '/(mypy_primer|black|pyp|git-revise|zipp|arrow|isort|itsdangerous|rich|packaging|pybind11|pyinstrument|typeshed-stats|scrapy|werkzeug|bidict|async-utils)$' \
7272
--output concise \
7373
--debug > mypy_primer.diff || [ $? -eq 1 ]
7474

crates/red_knot/src/args.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ pub(crate) struct CheckCommand {
7575
///
7676
/// This is used to specialize the type of `sys.platform` and will affect the visibility
7777
/// of platform-specific functions and attributes. If the value is set to `all`, no
78-
/// assumptions are made about the target platform.
78+
/// assumptions are made about the target platform. If unspecified, the current system's
79+
/// platform will be used.
7980
#[arg(long, value_name = "PLATFORM", alias = "platform")]
8081
pub(crate) python_platform: Option<String>,
8182

crates/red_knot_ide/src/hover.rs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::goto::find_goto_target;
1+
use crate::goto::{find_goto_target, GotoTarget};
22
use crate::{Db, MarkupKind, RangedValue};
33
use red_knot_python_semantic::types::Type;
44
use red_knot_python_semantic::SemanticModel;
@@ -12,6 +12,12 @@ pub fn hover(db: &dyn Db, file: File, offset: TextSize) -> Option<RangedValue<Ho
1212
let parsed = parsed_module(db.upcast(), file);
1313
let goto_target = find_goto_target(parsed, offset)?;
1414

15+
if let GotoTarget::Expression(expr) = goto_target {
16+
if expr.is_literal_expr() {
17+
return None;
18+
}
19+
}
20+
1521
let model = SemanticModel::new(db.upcast(), file);
1622
let ty = goto_target.inferred_type(&model)?;
1723

@@ -496,6 +502,57 @@ mod tests {
496502
");
497503
}
498504

505+
#[test]
506+
fn hover_whitespace() {
507+
let test = cursor_test(
508+
r#"
509+
class C:
510+
<CURSOR>
511+
foo: str = 'bar'
512+
"#,
513+
);
514+
515+
assert_snapshot!(test.hover(), @"Hover provided no content");
516+
}
517+
518+
#[test]
519+
fn hover_literal_int() {
520+
let test = cursor_test(
521+
r#"
522+
print(
523+
0 + 1<CURSOR>
524+
)
525+
"#,
526+
);
527+
528+
assert_snapshot!(test.hover(), @"Hover provided no content");
529+
}
530+
531+
#[test]
532+
fn hover_literal_ellipsis() {
533+
let test = cursor_test(
534+
r#"
535+
print(
536+
.<CURSOR>..
537+
)
538+
"#,
539+
);
540+
541+
assert_snapshot!(test.hover(), @"Hover provided no content");
542+
}
543+
544+
#[test]
545+
fn hover_docstring() {
546+
let test = cursor_test(
547+
r#"
548+
def f():
549+
"""Lorem ipsum dolor sit amet.<CURSOR>"""
550+
"#,
551+
);
552+
553+
assert_snapshot!(test.hover(), @"Hover provided no content");
554+
}
555+
499556
impl CursorTest {
500557
fn hover(&self) -> String {
501558
use std::fmt::Write;

crates/red_knot_project/src/metadata/options.rs

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,20 +54,25 @@ impl Options {
5454
project_root: &SystemPath,
5555
system: &dyn System,
5656
) -> ProgramSettings {
57-
let (python_version, python_platform) = self
57+
let python_version = self
5858
.environment
5959
.as_ref()
60-
.map(|env| {
61-
(
62-
env.python_version.as_deref().copied(),
63-
env.python_platform.as_deref(),
64-
)
65-
})
60+
.and_then(|env| env.python_version.as_deref().copied())
6661
.unwrap_or_default();
67-
62+
let python_platform = self
63+
.environment
64+
.as_ref()
65+
.and_then(|env| env.python_platform.as_deref().cloned())
66+
.unwrap_or_else(|| {
67+
let default = PythonPlatform::default();
68+
tracing::info!(
69+
"Defaulting to default python version for this platform: '{default}'",
70+
);
71+
default
72+
});
6873
ProgramSettings {
69-
python_version: python_version.unwrap_or_default(),
70-
python_platform: python_platform.cloned().unwrap_or_default(),
74+
python_version,
75+
python_platform,
7176
search_paths: self.to_search_path_settings(project_root, system),
7277
}
7378
}
@@ -237,7 +242,12 @@ pub struct EnvironmentOptions {
237242
/// If specified, Red Knot will tailor its use of type stub files,
238243
/// which conditionalize type definitions based on the platform.
239244
///
240-
/// If no platform is specified, knot will use `all` or the current platform in the LSP use case.
245+
/// If no platform is specified, knot will use the current platform:
246+
/// - `win32` for Windows
247+
/// - `darwin` for macOS
248+
/// - `android` for Android
249+
/// - `ios` for iOS
250+
/// - `linux` for everything else
241251
#[serde(skip_serializing_if = "Option::is_none")]
242252
pub python_platform: Option<RangedValue<PythonPlatform>>,
243253

crates/red_knot_python_semantic/resources/mdtest/annotations/new_types.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ Currently, red-knot doesn't support `typing.NewType` in type annotations.
88
from typing_extensions import NewType
99
from types import GenericAlias
1010

11+
X = GenericAlias(type, ())
1112
A = NewType("A", int)
13+
# TODO: typeshed for `typing.GenericAlias` uses `type` for the first argument. `NewType` should be special-cased
14+
# to be compatible with `type`
15+
# error: [invalid-argument-type] "Object of type `NewType` cannot be assigned to parameter 2 (`origin`) of function `__new__`; expected type `type`"
1216
B = GenericAlias(A, ())
1317

1418
def _(

crates/red_knot_python_semantic/resources/mdtest/attributes.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,6 +1395,59 @@ def _(ns: argparse.Namespace):
13951395
reveal_type(ns.whatever) # revealed: Any
13961396
```
13971397

1398+
## Classes with custom `__setattr__` methods
1399+
1400+
### Basic
1401+
1402+
If a type provides a custom `__setattr__` method, we use the parameter type of that method as the
1403+
type to validate attribute assignments. Consider the following `CustomSetAttr` class:
1404+
1405+
```py
1406+
class CustomSetAttr:
1407+
def __setattr__(self, name: str, value: int) -> None:
1408+
pass
1409+
```
1410+
1411+
We can set arbitrary attributes on instances of this class:
1412+
1413+
```py
1414+
c = CustomSetAttr()
1415+
1416+
c.whatever = 42
1417+
```
1418+
1419+
### Type of the `name` parameter
1420+
1421+
If the `name` parameter of the `__setattr__` method is annotated with a (union of) literal type(s),
1422+
we only consider the attribute assignment to be valid if the assigned attribute is one of them:
1423+
1424+
```py
1425+
from typing import Literal
1426+
1427+
class Date:
1428+
def __setattr__(self, name: Literal["day", "month", "year"], value: int) -> None:
1429+
pass
1430+
1431+
date = Date()
1432+
date.day = 8
1433+
date.month = 4
1434+
date.year = 2025
1435+
1436+
# error: [unresolved-attribute] "Can not assign object of `Literal["UTC"]` to attribute `tz` on type `Date` with custom `__setattr__` method."
1437+
date.tz = "UTC"
1438+
```
1439+
1440+
### `argparse.Namespace`
1441+
1442+
A standard library example of a class with a custom `__setattr__` method is `argparse.Namespace`:
1443+
1444+
```py
1445+
import argparse
1446+
1447+
def _(ns: argparse.Namespace):
1448+
ns.whatever = 42
1449+
```
1450+
13981451
## Objects of all types have a `__class__` method
13991452

14001453
The type of `x.__class__` is the same as `x`'s meta-type. `x.__class__` is always the same value as

0 commit comments

Comments
 (0)