Skip to content

Commit 1fe5ba4

Browse files
committed
Merge branch 'dcreager/die-die-intersections' into dcreager/callable-return
* dcreager/die-die-intersections: (29 commits) simpler bounds [`pylint`] Detect subclasses of builtin exceptions (`PLW0133`) (#21382) Fix stack overflow with recursive generic protocols (depth limit) (#21858) New diagnostics for unused range suppressions (#21783) [ty] Use default settings in completion tests [ty] Infer type variables within generic unions (#21862) [ty] Fix overload filtering to prefer more "precise" match (#21859) [ty] Stabilize auto-import [ty] Fix reveal-type E2E test (#21865) [ty] Use concise message for LSP clients not supporting related diagnostic information (#21850) Include more details in Tokens 'offset is inside token' panic message (#21860) apply range suppressions to filter diagnostics (#21623) [ty] followup: add-import action for `reveal_type` too (#21668) [ty] Enrich function argument auto-complete suggestions with annotated types [ty] Add autocomplete suggestions for function arguments [`flake8-bugbear`] Accept immutable slice default arguments (`B008`) (#21823) [`pydocstyle`] Suppress `D417` for parameters with `Unpack` annotations (#21816) [ty] Remove legacy `concise_message` fallback behavior (#21847) [ty] Make Python-version subdiagnostics less verbose (#21849) [ty] Supress inlay hints when assigning a trivial initializer call (#21848) ...
2 parents f927aa9 + 78dc896 commit 1fe5ba4

File tree

109 files changed

+5195
-1607
lines changed

Some content is hidden

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

109 files changed

+5195
-1607
lines changed

.github/workflows/ci.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ env:
2424
PACKAGE_NAME: ruff
2525
PYTHON_VERSION: "3.14"
2626
NEXTEST_PROFILE: ci
27+
# Enable mdtests that require external dependencies
28+
MDTEST_EXTERNAL: "1"
2729

2830
jobs:
2931
determine_changes:

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ruff/tests/cli/lint.rs

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1440,6 +1440,78 @@ def function():
14401440
Ok(())
14411441
}
14421442

1443+
#[test]
1444+
fn ignore_noqa() -> Result<()> {
1445+
let fixture = CliTest::new()?;
1446+
fixture.write_file(
1447+
"ruff.toml",
1448+
r#"
1449+
[lint]
1450+
select = ["F401"]
1451+
"#,
1452+
)?;
1453+
1454+
fixture.write_file(
1455+
"noqa.py",
1456+
r#"
1457+
import os # noqa: F401
1458+
1459+
# ruff: disable[F401]
1460+
import sys
1461+
"#,
1462+
)?;
1463+
1464+
// without --ignore-noqa
1465+
assert_cmd_snapshot!(fixture
1466+
.check_command()
1467+
.args(["--config", "ruff.toml"])
1468+
.arg("noqa.py"),
1469+
@r"
1470+
success: false
1471+
exit_code: 1
1472+
----- stdout -----
1473+
noqa.py:5:8: F401 [*] `sys` imported but unused
1474+
Found 1 error.
1475+
[*] 1 fixable with the `--fix` option.
1476+
1477+
----- stderr -----
1478+
");
1479+
1480+
assert_cmd_snapshot!(fixture
1481+
.check_command()
1482+
.args(["--config", "ruff.toml"])
1483+
.arg("noqa.py")
1484+
.args(["--preview"]),
1485+
@r"
1486+
success: true
1487+
exit_code: 0
1488+
----- stdout -----
1489+
All checks passed!
1490+
1491+
----- stderr -----
1492+
");
1493+
1494+
// with --ignore-noqa --preview
1495+
assert_cmd_snapshot!(fixture
1496+
.check_command()
1497+
.args(["--config", "ruff.toml"])
1498+
.arg("noqa.py")
1499+
.args(["--ignore-noqa", "--preview"]),
1500+
@r"
1501+
success: false
1502+
exit_code: 1
1503+
----- stdout -----
1504+
noqa.py:2:8: F401 [*] `os` imported but unused
1505+
noqa.py:5:8: F401 [*] `sys` imported but unused
1506+
Found 2 errors.
1507+
[*] 2 fixable with the `--fix` option.
1508+
1509+
----- stderr -----
1510+
");
1511+
1512+
Ok(())
1513+
}
1514+
14431515
#[test]
14441516
fn add_noqa() -> Result<()> {
14451517
let fixture = CliTest::new()?;
@@ -1632,6 +1704,100 @@ def unused(x): # noqa: ANN001, ARG001, D103
16321704
Ok(())
16331705
}
16341706

1707+
#[test]
1708+
fn add_noqa_existing_file_level_noqa() -> Result<()> {
1709+
let fixture = CliTest::new()?;
1710+
fixture.write_file(
1711+
"ruff.toml",
1712+
r#"
1713+
[lint]
1714+
select = ["F401"]
1715+
"#,
1716+
)?;
1717+
1718+
fixture.write_file(
1719+
"noqa.py",
1720+
r#"
1721+
# ruff: noqa F401
1722+
import os
1723+
"#,
1724+
)?;
1725+
1726+
assert_cmd_snapshot!(fixture
1727+
.check_command()
1728+
.args(["--config", "ruff.toml"])
1729+
.arg("noqa.py")
1730+
.arg("--preview")
1731+
.args(["--add-noqa"])
1732+
.arg("-")
1733+
.pass_stdin(r#"
1734+
1735+
"#), @r"
1736+
success: true
1737+
exit_code: 0
1738+
----- stdout -----
1739+
1740+
----- stderr -----
1741+
");
1742+
1743+
let test_code =
1744+
fs::read_to_string(fixture.root().join("noqa.py")).expect("should read test file");
1745+
1746+
insta::assert_snapshot!(test_code, @r"
1747+
# ruff: noqa F401
1748+
import os
1749+
");
1750+
1751+
Ok(())
1752+
}
1753+
1754+
#[test]
1755+
fn add_noqa_existing_range_suppression() -> Result<()> {
1756+
let fixture = CliTest::new()?;
1757+
fixture.write_file(
1758+
"ruff.toml",
1759+
r#"
1760+
[lint]
1761+
select = ["F401"]
1762+
"#,
1763+
)?;
1764+
1765+
fixture.write_file(
1766+
"noqa.py",
1767+
r#"
1768+
# ruff: disable[F401]
1769+
import os
1770+
"#,
1771+
)?;
1772+
1773+
assert_cmd_snapshot!(fixture
1774+
.check_command()
1775+
.args(["--config", "ruff.toml"])
1776+
.arg("noqa.py")
1777+
.arg("--preview")
1778+
.args(["--add-noqa"])
1779+
.arg("-")
1780+
.pass_stdin(r#"
1781+
1782+
"#), @r"
1783+
success: true
1784+
exit_code: 0
1785+
----- stdout -----
1786+
1787+
----- stderr -----
1788+
");
1789+
1790+
let test_code =
1791+
fs::read_to_string(fixture.root().join("noqa.py")).expect("should read test file");
1792+
1793+
insta::assert_snapshot!(test_code, @r"
1794+
# ruff: disable[F401]
1795+
import os
1796+
");
1797+
1798+
Ok(())
1799+
}
1800+
16351801
#[test]
16361802
fn add_noqa_multiline_comment() -> Result<()> {
16371803
let fixture = CliTest::new()?;

crates/ruff_db/src/diagnostic/mod.rs

Lines changed: 13 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -166,28 +166,8 @@ impl Diagnostic {
166166
/// Returns the primary message for this diagnostic.
167167
///
168168
/// A diagnostic always has a message, but it may be empty.
169-
///
170-
/// NOTE: At present, this routine will return the first primary
171-
/// annotation's message as the primary message when the main diagnostic
172-
/// message is empty. This is meant to facilitate an incremental migration
173-
/// in ty over to the new diagnostic data model. (The old data model
174-
/// didn't distinguish between messages on the entire diagnostic and
175-
/// messages attached to a particular span.)
176169
pub fn primary_message(&self) -> &str {
177-
if !self.inner.message.as_str().is_empty() {
178-
return self.inner.message.as_str();
179-
}
180-
// FIXME: As a special case, while we're migrating ty
181-
// to the new diagnostic data model, we'll look for a primary
182-
// message from the primary annotation. This is because most
183-
// ty diagnostics are created with an empty diagnostic
184-
// message and instead attach the message to the annotation.
185-
// Fixing this will require touching basically every diagnostic
186-
// in ty, so we do it this way for now to match the old
187-
// semantics. ---AG
188-
self.primary_annotation()
189-
.and_then(|ann| ann.get_message())
190-
.unwrap_or_default()
170+
self.inner.message.as_str()
191171
}
192172

193173
/// Introspects this diagnostic and returns what kind of "primary" message
@@ -199,18 +179,6 @@ impl Diagnostic {
199179
/// contains *essential* information or context for understanding the
200180
/// diagnostic.
201181
///
202-
/// The reason why we don't just always return both the main diagnostic
203-
/// message and the primary annotation message is because this was written
204-
/// in the midst of an incremental migration of ty over to the new
205-
/// diagnostic data model. At time of writing, diagnostics were still
206-
/// constructed in the old model where the main diagnostic message and the
207-
/// primary annotation message were not distinguished from each other. So
208-
/// for now, we carefully return what kind of messages this diagnostic
209-
/// contains. In effect, if this diagnostic has a non-empty main message
210-
/// *and* a non-empty primary annotation message, then the diagnostic is
211-
/// 100% using the new diagnostic data model and we can format things
212-
/// appropriately.
213-
///
214182
/// The type returned implements the `std::fmt::Display` trait. In most
215183
/// cases, just converting it to a string (or printing it) will do what
216184
/// you want.
@@ -224,11 +192,10 @@ impl Diagnostic {
224192
.primary_annotation()
225193
.and_then(|ann| ann.get_message())
226194
.unwrap_or_default();
227-
match (main.is_empty(), annotation.is_empty()) {
228-
(false, true) => ConciseMessage::MainDiagnostic(main),
229-
(true, false) => ConciseMessage::PrimaryAnnotation(annotation),
230-
(false, false) => ConciseMessage::Both { main, annotation },
231-
(true, true) => ConciseMessage::Empty,
195+
if annotation.is_empty() {
196+
ConciseMessage::MainDiagnostic(main)
197+
} else {
198+
ConciseMessage::Both { main, annotation }
232199
}
233200
}
234201

@@ -693,18 +660,6 @@ impl SubDiagnostic {
693660
/// contains *essential* information or context for understanding the
694661
/// diagnostic.
695662
///
696-
/// The reason why we don't just always return both the main diagnostic
697-
/// message and the primary annotation message is because this was written
698-
/// in the midst of an incremental migration of ty over to the new
699-
/// diagnostic data model. At time of writing, diagnostics were still
700-
/// constructed in the old model where the main diagnostic message and the
701-
/// primary annotation message were not distinguished from each other. So
702-
/// for now, we carefully return what kind of messages this diagnostic
703-
/// contains. In effect, if this diagnostic has a non-empty main message
704-
/// *and* a non-empty primary annotation message, then the diagnostic is
705-
/// 100% using the new diagnostic data model and we can format things
706-
/// appropriately.
707-
///
708663
/// The type returned implements the `std::fmt::Display` trait. In most
709664
/// cases, just converting it to a string (or printing it) will do what
710665
/// you want.
@@ -714,11 +669,10 @@ impl SubDiagnostic {
714669
.primary_annotation()
715670
.and_then(|ann| ann.get_message())
716671
.unwrap_or_default();
717-
match (main.is_empty(), annotation.is_empty()) {
718-
(false, true) => ConciseMessage::MainDiagnostic(main),
719-
(true, false) => ConciseMessage::PrimaryAnnotation(annotation),
720-
(false, false) => ConciseMessage::Both { main, annotation },
721-
(true, true) => ConciseMessage::Empty,
672+
if annotation.is_empty() {
673+
ConciseMessage::MainDiagnostic(main)
674+
} else {
675+
ConciseMessage::Both { main, annotation }
722676
}
723677
}
724678
}
@@ -888,6 +842,10 @@ impl Annotation {
888842
pub fn hide_snippet(&mut self, yes: bool) {
889843
self.hide_snippet = yes;
890844
}
845+
846+
pub fn is_primary(&self) -> bool {
847+
self.is_primary
848+
}
891849
}
892850

893851
/// Tags that can be associated with an annotation.
@@ -1508,28 +1466,10 @@ pub enum DiagnosticFormat {
15081466
pub enum ConciseMessage<'a> {
15091467
/// A diagnostic contains a non-empty main message and an empty
15101468
/// primary annotation message.
1511-
///
1512-
/// This strongly suggests that the diagnostic is using the
1513-
/// "new" data model.
15141469
MainDiagnostic(&'a str),
1515-
/// A diagnostic contains an empty main message and a non-empty
1516-
/// primary annotation message.
1517-
///
1518-
/// This strongly suggests that the diagnostic is using the
1519-
/// "old" data model.
1520-
PrimaryAnnotation(&'a str),
15211470
/// A diagnostic contains a non-empty main message and a non-empty
15221471
/// primary annotation message.
1523-
///
1524-
/// This strongly suggests that the diagnostic is using the
1525-
/// "new" data model.
15261472
Both { main: &'a str, annotation: &'a str },
1527-
/// A diagnostic contains an empty main message and an empty
1528-
/// primary annotation message.
1529-
///
1530-
/// This indicates that the diagnostic is probably using the old
1531-
/// model.
1532-
Empty,
15331473
/// A custom concise message has been provided.
15341474
Custom(&'a str),
15351475
}
@@ -1540,13 +1480,9 @@ impl std::fmt::Display for ConciseMessage<'_> {
15401480
ConciseMessage::MainDiagnostic(main) => {
15411481
write!(f, "{main}")
15421482
}
1543-
ConciseMessage::PrimaryAnnotation(annotation) => {
1544-
write!(f, "{annotation}")
1545-
}
15461483
ConciseMessage::Both { main, annotation } => {
15471484
write!(f, "{main}: {annotation}")
15481485
}
1549-
ConciseMessage::Empty => Ok(()),
15501486
ConciseMessage::Custom(message) => {
15511487
write!(f, "{message}")
15521488
}

crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B006_B008.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,9 @@ def bytes_okay(value=bytes(1)):
199199
def int_okay(value=int("12")):
200200
pass
201201

202+
# Allow immutable slice()
203+
def slice_okay(value=slice(1,2)):
204+
pass
202205

203206
# Allow immutable complex() value
204207
def complex_okay(value=complex(1,2)):

crates/ruff_linter/resources/test/fixtures/pydocstyle/D417.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,3 +218,26 @@ def should_not_fail(payload, Args):
218218
Args:
219219
The other arguments.
220220
"""
221+
222+
223+
# Test cases for Unpack[TypedDict] kwargs
224+
from typing import TypedDict
225+
from typing_extensions import Unpack
226+
227+
class User(TypedDict):
228+
id: int
229+
name: str
230+
231+
def function_with_unpack_args_should_not_fail(query: str, **kwargs: Unpack[User]):
232+
"""Function with Unpack kwargs.
233+
234+
Args:
235+
query: some arg
236+
"""
237+
238+
def function_with_unpack_and_missing_arg_doc_should_fail(query: str, **kwargs: Unpack[User]):
239+
"""Function with Unpack kwargs but missing query arg documentation.
240+
241+
Args:
242+
**kwargs: keyword arguments
243+
"""

0 commit comments

Comments
 (0)