Compare commits
1 Commits
david/sqla
...
david/asyn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07efdf8887 |
@@ -1440,78 +1440,6 @@ def function():
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignore_noqa() -> Result<()> {
|
||||
let fixture = CliTest::new()?;
|
||||
fixture.write_file(
|
||||
"ruff.toml",
|
||||
r#"
|
||||
[lint]
|
||||
select = ["F401"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
fixture.write_file(
|
||||
"noqa.py",
|
||||
r#"
|
||||
import os # noqa: F401
|
||||
|
||||
# ruff: disable[F401]
|
||||
import sys
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// without --ignore-noqa
|
||||
assert_cmd_snapshot!(fixture
|
||||
.check_command()
|
||||
.args(["--config", "ruff.toml"])
|
||||
.arg("noqa.py"),
|
||||
@r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
noqa.py:5:8: F401 [*] `sys` imported but unused
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
assert_cmd_snapshot!(fixture
|
||||
.check_command()
|
||||
.args(["--config", "ruff.toml"])
|
||||
.arg("noqa.py")
|
||||
.args(["--preview"]),
|
||||
@r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
// with --ignore-noqa --preview
|
||||
assert_cmd_snapshot!(fixture
|
||||
.check_command()
|
||||
.args(["--config", "ruff.toml"])
|
||||
.arg("noqa.py")
|
||||
.args(["--ignore-noqa", "--preview"]),
|
||||
@r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
noqa.py:2:8: F401 [*] `os` imported but unused
|
||||
noqa.py:5:8: F401 [*] `sys` imported but unused
|
||||
Found 2 errors.
|
||||
[*] 2 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_noqa() -> Result<()> {
|
||||
let fixture = CliTest::new()?;
|
||||
@@ -1704,100 +1632,6 @@ def unused(x): # noqa: ANN001, ARG001, D103
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_noqa_existing_file_level_noqa() -> Result<()> {
|
||||
let fixture = CliTest::new()?;
|
||||
fixture.write_file(
|
||||
"ruff.toml",
|
||||
r#"
|
||||
[lint]
|
||||
select = ["F401"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
fixture.write_file(
|
||||
"noqa.py",
|
||||
r#"
|
||||
# ruff: noqa F401
|
||||
import os
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(fixture
|
||||
.check_command()
|
||||
.args(["--config", "ruff.toml"])
|
||||
.arg("noqa.py")
|
||||
.arg("--preview")
|
||||
.args(["--add-noqa"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
|
||||
"#), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
let test_code =
|
||||
fs::read_to_string(fixture.root().join("noqa.py")).expect("should read test file");
|
||||
|
||||
insta::assert_snapshot!(test_code, @r"
|
||||
# ruff: noqa F401
|
||||
import os
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_noqa_existing_range_suppression() -> Result<()> {
|
||||
let fixture = CliTest::new()?;
|
||||
fixture.write_file(
|
||||
"ruff.toml",
|
||||
r#"
|
||||
[lint]
|
||||
select = ["F401"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
fixture.write_file(
|
||||
"noqa.py",
|
||||
r#"
|
||||
# ruff: disable[F401]
|
||||
import os
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(fixture
|
||||
.check_command()
|
||||
.args(["--config", "ruff.toml"])
|
||||
.arg("noqa.py")
|
||||
.arg("--preview")
|
||||
.args(["--add-noqa"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
|
||||
"#), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
let test_code =
|
||||
fs::read_to_string(fixture.root().join("noqa.py")).expect("should read test file");
|
||||
|
||||
insta::assert_snapshot!(test_code, @r"
|
||||
# ruff: disable[F401]
|
||||
import os
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_noqa_multiline_comment() -> Result<()> {
|
||||
let fixture = CliTest::new()?;
|
||||
|
||||
@@ -166,8 +166,28 @@ impl Diagnostic {
|
||||
/// Returns the primary message for this diagnostic.
|
||||
///
|
||||
/// A diagnostic always has a message, but it may be empty.
|
||||
///
|
||||
/// NOTE: At present, this routine will return the first primary
|
||||
/// annotation's message as the primary message when the main diagnostic
|
||||
/// message is empty. This is meant to facilitate an incremental migration
|
||||
/// in ty over to the new diagnostic data model. (The old data model
|
||||
/// didn't distinguish between messages on the entire diagnostic and
|
||||
/// messages attached to a particular span.)
|
||||
pub fn primary_message(&self) -> &str {
|
||||
self.inner.message.as_str()
|
||||
if !self.inner.message.as_str().is_empty() {
|
||||
return self.inner.message.as_str();
|
||||
}
|
||||
// FIXME: As a special case, while we're migrating ty
|
||||
// to the new diagnostic data model, we'll look for a primary
|
||||
// message from the primary annotation. This is because most
|
||||
// ty diagnostics are created with an empty diagnostic
|
||||
// message and instead attach the message to the annotation.
|
||||
// Fixing this will require touching basically every diagnostic
|
||||
// in ty, so we do it this way for now to match the old
|
||||
// semantics. ---AG
|
||||
self.primary_annotation()
|
||||
.and_then(|ann| ann.get_message())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Introspects this diagnostic and returns what kind of "primary" message
|
||||
@@ -179,6 +199,18 @@ impl Diagnostic {
|
||||
/// contains *essential* information or context for understanding the
|
||||
/// diagnostic.
|
||||
///
|
||||
/// The reason why we don't just always return both the main diagnostic
|
||||
/// message and the primary annotation message is because this was written
|
||||
/// in the midst of an incremental migration of ty over to the new
|
||||
/// diagnostic data model. At time of writing, diagnostics were still
|
||||
/// constructed in the old model where the main diagnostic message and the
|
||||
/// primary annotation message were not distinguished from each other. So
|
||||
/// for now, we carefully return what kind of messages this diagnostic
|
||||
/// contains. In effect, if this diagnostic has a non-empty main message
|
||||
/// *and* a non-empty primary annotation message, then the diagnostic is
|
||||
/// 100% using the new diagnostic data model and we can format things
|
||||
/// appropriately.
|
||||
///
|
||||
/// The type returned implements the `std::fmt::Display` trait. In most
|
||||
/// cases, just converting it to a string (or printing it) will do what
|
||||
/// you want.
|
||||
@@ -192,10 +224,11 @@ impl Diagnostic {
|
||||
.primary_annotation()
|
||||
.and_then(|ann| ann.get_message())
|
||||
.unwrap_or_default();
|
||||
if annotation.is_empty() {
|
||||
ConciseMessage::MainDiagnostic(main)
|
||||
} else {
|
||||
ConciseMessage::Both { main, annotation }
|
||||
match (main.is_empty(), annotation.is_empty()) {
|
||||
(false, true) => ConciseMessage::MainDiagnostic(main),
|
||||
(true, false) => ConciseMessage::PrimaryAnnotation(annotation),
|
||||
(false, false) => ConciseMessage::Both { main, annotation },
|
||||
(true, true) => ConciseMessage::Empty,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -660,6 +693,18 @@ impl SubDiagnostic {
|
||||
/// contains *essential* information or context for understanding the
|
||||
/// diagnostic.
|
||||
///
|
||||
/// The reason why we don't just always return both the main diagnostic
|
||||
/// message and the primary annotation message is because this was written
|
||||
/// in the midst of an incremental migration of ty over to the new
|
||||
/// diagnostic data model. At time of writing, diagnostics were still
|
||||
/// constructed in the old model where the main diagnostic message and the
|
||||
/// primary annotation message were not distinguished from each other. So
|
||||
/// for now, we carefully return what kind of messages this diagnostic
|
||||
/// contains. In effect, if this diagnostic has a non-empty main message
|
||||
/// *and* a non-empty primary annotation message, then the diagnostic is
|
||||
/// 100% using the new diagnostic data model and we can format things
|
||||
/// appropriately.
|
||||
///
|
||||
/// The type returned implements the `std::fmt::Display` trait. In most
|
||||
/// cases, just converting it to a string (or printing it) will do what
|
||||
/// you want.
|
||||
@@ -669,10 +714,11 @@ impl SubDiagnostic {
|
||||
.primary_annotation()
|
||||
.and_then(|ann| ann.get_message())
|
||||
.unwrap_or_default();
|
||||
if annotation.is_empty() {
|
||||
ConciseMessage::MainDiagnostic(main)
|
||||
} else {
|
||||
ConciseMessage::Both { main, annotation }
|
||||
match (main.is_empty(), annotation.is_empty()) {
|
||||
(false, true) => ConciseMessage::MainDiagnostic(main),
|
||||
(true, false) => ConciseMessage::PrimaryAnnotation(annotation),
|
||||
(false, false) => ConciseMessage::Both { main, annotation },
|
||||
(true, true) => ConciseMessage::Empty,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -842,10 +888,6 @@ impl Annotation {
|
||||
pub fn hide_snippet(&mut self, yes: bool) {
|
||||
self.hide_snippet = yes;
|
||||
}
|
||||
|
||||
pub fn is_primary(&self) -> bool {
|
||||
self.is_primary
|
||||
}
|
||||
}
|
||||
|
||||
/// Tags that can be associated with an annotation.
|
||||
@@ -1466,10 +1508,28 @@ pub enum DiagnosticFormat {
|
||||
pub enum ConciseMessage<'a> {
|
||||
/// A diagnostic contains a non-empty main message and an empty
|
||||
/// primary annotation message.
|
||||
///
|
||||
/// This strongly suggests that the diagnostic is using the
|
||||
/// "new" data model.
|
||||
MainDiagnostic(&'a str),
|
||||
/// A diagnostic contains an empty main message and a non-empty
|
||||
/// primary annotation message.
|
||||
///
|
||||
/// This strongly suggests that the diagnostic is using the
|
||||
/// "old" data model.
|
||||
PrimaryAnnotation(&'a str),
|
||||
/// A diagnostic contains a non-empty main message and a non-empty
|
||||
/// primary annotation message.
|
||||
///
|
||||
/// This strongly suggests that the diagnostic is using the
|
||||
/// "new" data model.
|
||||
Both { main: &'a str, annotation: &'a str },
|
||||
/// A diagnostic contains an empty main message and an empty
|
||||
/// primary annotation message.
|
||||
///
|
||||
/// This indicates that the diagnostic is probably using the old
|
||||
/// model.
|
||||
Empty,
|
||||
/// A custom concise message has been provided.
|
||||
Custom(&'a str),
|
||||
}
|
||||
@@ -1480,9 +1540,13 @@ impl std::fmt::Display for ConciseMessage<'_> {
|
||||
ConciseMessage::MainDiagnostic(main) => {
|
||||
write!(f, "{main}")
|
||||
}
|
||||
ConciseMessage::PrimaryAnnotation(annotation) => {
|
||||
write!(f, "{annotation}")
|
||||
}
|
||||
ConciseMessage::Both { main, annotation } => {
|
||||
write!(f, "{main}: {annotation}")
|
||||
}
|
||||
ConciseMessage::Empty => Ok(()),
|
||||
ConciseMessage::Custom(message) => {
|
||||
write!(f, "{message}")
|
||||
}
|
||||
|
||||
@@ -199,9 +199,6 @@ def bytes_okay(value=bytes(1)):
|
||||
def int_okay(value=int("12")):
|
||||
pass
|
||||
|
||||
# Allow immutable slice()
|
||||
def slice_okay(value=slice(1,2)):
|
||||
pass
|
||||
|
||||
# Allow immutable complex() value
|
||||
def complex_okay(value=complex(1,2)):
|
||||
|
||||
@@ -218,26 +218,3 @@ def should_not_fail(payload, Args):
|
||||
Args:
|
||||
The other arguments.
|
||||
"""
|
||||
|
||||
|
||||
# Test cases for Unpack[TypedDict] kwargs
|
||||
from typing import TypedDict
|
||||
from typing_extensions import Unpack
|
||||
|
||||
class User(TypedDict):
|
||||
id: int
|
||||
name: str
|
||||
|
||||
def function_with_unpack_args_should_not_fail(query: str, **kwargs: Unpack[User]):
|
||||
"""Function with Unpack kwargs.
|
||||
|
||||
Args:
|
||||
query: some arg
|
||||
"""
|
||||
|
||||
def function_with_unpack_and_missing_arg_doc_should_fail(query: str, **kwargs: Unpack[User]):
|
||||
"""Function with Unpack kwargs but missing query arg documentation.
|
||||
|
||||
Args:
|
||||
**kwargs: keyword arguments
|
||||
"""
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
def f():
|
||||
# These should both be ignored by the range suppression.
|
||||
# ruff: disable[E741, F841]
|
||||
I = 1
|
||||
# ruff: enable[E741, F841]
|
||||
|
||||
|
||||
def f():
|
||||
# These should both be ignored by the implicit range suppression.
|
||||
# Should also generate an "unmatched suppression" warning.
|
||||
# ruff:disable[E741,F841]
|
||||
I = 1
|
||||
|
||||
|
||||
def f():
|
||||
# Neither warning is ignored, and an "unmatched suppression"
|
||||
# should be generated.
|
||||
I = 1
|
||||
# ruff: enable[E741, F841]
|
||||
|
||||
|
||||
def f():
|
||||
# One should be ignored by the range suppression, and
|
||||
# the other logged to the user.
|
||||
# ruff: disable[E741]
|
||||
I = 1
|
||||
# ruff: enable[E741]
|
||||
|
||||
|
||||
def f():
|
||||
# Test interleaved range suppressions. The first and last
|
||||
# lines should each log a different warning, while the
|
||||
# middle line should be completely silenced.
|
||||
# ruff: disable[E741]
|
||||
l = 0
|
||||
# ruff: disable[F841]
|
||||
O = 1
|
||||
# ruff: enable[E741]
|
||||
I = 2
|
||||
# ruff: enable[F841]
|
||||
|
||||
|
||||
def f():
|
||||
# Neither of these are ignored and warnings are
|
||||
# logged to user
|
||||
# ruff: disable[E501]
|
||||
I = 1
|
||||
# ruff: enable[E501]
|
||||
|
||||
|
||||
def f():
|
||||
# These should both be ignored by the range suppression,
|
||||
# and an unusued noqa diagnostic should be logged.
|
||||
# ruff:disable[E741,F841]
|
||||
I = 1 # noqa: E741,F841
|
||||
# ruff:enable[E741,F841]
|
||||
@@ -12,20 +12,17 @@ use crate::fix::edits::delete_comment;
|
||||
use crate::noqa::{
|
||||
Code, Directive, FileExemption, FileNoqaDirectives, NoqaDirectives, NoqaMapping,
|
||||
};
|
||||
use crate::preview::is_range_suppressions_enabled;
|
||||
use crate::registry::Rule;
|
||||
use crate::rule_redirects::get_redirect_target;
|
||||
use crate::rules::pygrep_hooks;
|
||||
use crate::rules::ruff;
|
||||
use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA};
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::suppression::Suppressions;
|
||||
use crate::{Edit, Fix, Locator};
|
||||
|
||||
use super::ast::LintContext;
|
||||
|
||||
/// RUF100
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub(crate) fn check_noqa(
|
||||
context: &mut LintContext,
|
||||
path: &Path,
|
||||
@@ -34,7 +31,6 @@ pub(crate) fn check_noqa(
|
||||
noqa_line_for: &NoqaMapping,
|
||||
analyze_directives: bool,
|
||||
settings: &LinterSettings,
|
||||
suppressions: &Suppressions,
|
||||
) -> Vec<usize> {
|
||||
// Identify any codes that are globally exempted (within the current file).
|
||||
let file_noqa_directives =
|
||||
@@ -44,7 +40,7 @@ pub(crate) fn check_noqa(
|
||||
let mut noqa_directives =
|
||||
NoqaDirectives::from_commented_ranges(comment_ranges, &settings.external, path, locator);
|
||||
|
||||
if file_noqa_directives.is_empty() && noqa_directives.is_empty() && suppressions.is_empty() {
|
||||
if file_noqa_directives.is_empty() && noqa_directives.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
@@ -64,19 +60,11 @@ pub(crate) fn check_noqa(
|
||||
continue;
|
||||
}
|
||||
|
||||
// Apply file-level suppressions first
|
||||
if exemption.contains_secondary_code(code) {
|
||||
ignored_diagnostics.push(index);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Apply ranged suppressions next
|
||||
if is_range_suppressions_enabled(settings) && suppressions.check_diagnostic(diagnostic) {
|
||||
ignored_diagnostics.push(index);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Apply end-of-line noqa suppressions last
|
||||
let noqa_offsets = diagnostic
|
||||
.parent()
|
||||
.into_iter()
|
||||
|
||||
@@ -32,7 +32,6 @@ use crate::rules::ruff::rules::test_rules::{self, TEST_RULES, TestRule};
|
||||
use crate::settings::types::UnsafeFixes;
|
||||
use crate::settings::{LinterSettings, TargetVersion, flags};
|
||||
use crate::source_kind::SourceKind;
|
||||
use crate::suppression::Suppressions;
|
||||
use crate::{Locator, directives, fs};
|
||||
|
||||
pub(crate) mod float;
|
||||
@@ -129,7 +128,6 @@ pub fn check_path(
|
||||
source_type: PySourceType,
|
||||
parsed: &Parsed<ModModule>,
|
||||
target_version: TargetVersion,
|
||||
suppressions: &Suppressions,
|
||||
) -> Vec<Diagnostic> {
|
||||
// Aggregate all diagnostics.
|
||||
let mut context = LintContext::new(path, locator.contents(), settings);
|
||||
@@ -341,7 +339,6 @@ pub fn check_path(
|
||||
&directives.noqa_line_for,
|
||||
parsed.has_valid_syntax(),
|
||||
settings,
|
||||
suppressions,
|
||||
);
|
||||
if noqa.is_enabled() {
|
||||
for index in ignored.iter().rev() {
|
||||
@@ -403,9 +400,6 @@ pub fn add_noqa_to_path(
|
||||
&indexer,
|
||||
);
|
||||
|
||||
// Parse range suppression comments
|
||||
let suppressions = Suppressions::from_tokens(settings, locator.contents(), parsed.tokens());
|
||||
|
||||
// Generate diagnostics, ignoring any existing `noqa` directives.
|
||||
let diagnostics = check_path(
|
||||
path,
|
||||
@@ -420,7 +414,6 @@ pub fn add_noqa_to_path(
|
||||
source_type,
|
||||
&parsed,
|
||||
target_version,
|
||||
&suppressions,
|
||||
);
|
||||
|
||||
// Add any missing `# noqa` pragmas.
|
||||
@@ -434,7 +427,6 @@ pub fn add_noqa_to_path(
|
||||
&directives.noqa_line_for,
|
||||
stylist.line_ending(),
|
||||
reason,
|
||||
&suppressions,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -469,9 +461,6 @@ pub fn lint_only(
|
||||
&indexer,
|
||||
);
|
||||
|
||||
// Parse range suppression comments
|
||||
let suppressions = Suppressions::from_tokens(settings, locator.contents(), parsed.tokens());
|
||||
|
||||
// Generate diagnostics.
|
||||
let diagnostics = check_path(
|
||||
path,
|
||||
@@ -486,7 +475,6 @@ pub fn lint_only(
|
||||
source_type,
|
||||
&parsed,
|
||||
target_version,
|
||||
&suppressions,
|
||||
);
|
||||
|
||||
LinterResult {
|
||||
@@ -578,9 +566,6 @@ pub fn lint_fix<'a>(
|
||||
&indexer,
|
||||
);
|
||||
|
||||
// Parse range suppression comments
|
||||
let suppressions = Suppressions::from_tokens(settings, locator.contents(), parsed.tokens());
|
||||
|
||||
// Generate diagnostics.
|
||||
let diagnostics = check_path(
|
||||
path,
|
||||
@@ -595,7 +580,6 @@ pub fn lint_fix<'a>(
|
||||
source_type,
|
||||
&parsed,
|
||||
target_version,
|
||||
&suppressions,
|
||||
);
|
||||
|
||||
if iterations == 0 {
|
||||
@@ -785,7 +769,6 @@ mod tests {
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::source_kind::SourceKind;
|
||||
use crate::suppression::Suppressions;
|
||||
use crate::test::{TestedNotebook, assert_notebook_path, test_contents, test_snippet};
|
||||
use crate::{Locator, assert_diagnostics, directives, settings};
|
||||
|
||||
@@ -961,7 +944,6 @@ mod tests {
|
||||
&locator,
|
||||
&indexer,
|
||||
);
|
||||
let suppressions = Suppressions::from_tokens(settings, locator.contents(), parsed.tokens());
|
||||
let mut diagnostics = check_path(
|
||||
path,
|
||||
None,
|
||||
@@ -975,7 +957,6 @@ mod tests {
|
||||
source_type,
|
||||
&parsed,
|
||||
target_version,
|
||||
&suppressions,
|
||||
);
|
||||
diagnostics.sort_by(Diagnostic::ruff_start_ordering);
|
||||
diagnostics
|
||||
|
||||
@@ -20,14 +20,12 @@ use crate::Locator;
|
||||
use crate::fs::relativize_path;
|
||||
use crate::registry::Rule;
|
||||
use crate::rule_redirects::get_redirect_target;
|
||||
use crate::suppression::Suppressions;
|
||||
|
||||
/// Generates an array of edits that matches the length of `messages`.
|
||||
/// Each potential edit in the array is paired, in order, with the associated diagnostic.
|
||||
/// Each edit will add a `noqa` comment to the appropriate line in the source to hide
|
||||
/// the diagnostic. These edits may conflict with each other and should not be applied
|
||||
/// simultaneously.
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub fn generate_noqa_edits(
|
||||
path: &Path,
|
||||
diagnostics: &[Diagnostic],
|
||||
@@ -36,19 +34,11 @@ pub fn generate_noqa_edits(
|
||||
external: &[String],
|
||||
noqa_line_for: &NoqaMapping,
|
||||
line_ending: LineEnding,
|
||||
suppressions: &Suppressions,
|
||||
) -> Vec<Option<Edit>> {
|
||||
let file_directives = FileNoqaDirectives::extract(locator, comment_ranges, external, path);
|
||||
let exemption = FileExemption::from(&file_directives);
|
||||
let directives = NoqaDirectives::from_commented_ranges(comment_ranges, external, path, locator);
|
||||
let comments = find_noqa_comments(
|
||||
diagnostics,
|
||||
locator,
|
||||
&exemption,
|
||||
&directives,
|
||||
noqa_line_for,
|
||||
suppressions,
|
||||
);
|
||||
let comments = find_noqa_comments(diagnostics, locator, &exemption, &directives, noqa_line_for);
|
||||
build_noqa_edits_by_diagnostic(comments, locator, line_ending, None)
|
||||
}
|
||||
|
||||
@@ -735,7 +725,6 @@ pub(crate) fn add_noqa(
|
||||
noqa_line_for: &NoqaMapping,
|
||||
line_ending: LineEnding,
|
||||
reason: Option<&str>,
|
||||
suppressions: &Suppressions,
|
||||
) -> Result<usize> {
|
||||
let (count, output) = add_noqa_inner(
|
||||
path,
|
||||
@@ -746,7 +735,6 @@ pub(crate) fn add_noqa(
|
||||
noqa_line_for,
|
||||
line_ending,
|
||||
reason,
|
||||
suppressions,
|
||||
);
|
||||
|
||||
fs::write(path, output)?;
|
||||
@@ -763,7 +751,6 @@ fn add_noqa_inner(
|
||||
noqa_line_for: &NoqaMapping,
|
||||
line_ending: LineEnding,
|
||||
reason: Option<&str>,
|
||||
suppressions: &Suppressions,
|
||||
) -> (usize, String) {
|
||||
let mut count = 0;
|
||||
|
||||
@@ -773,14 +760,7 @@ fn add_noqa_inner(
|
||||
|
||||
let directives = NoqaDirectives::from_commented_ranges(comment_ranges, external, path, locator);
|
||||
|
||||
let comments = find_noqa_comments(
|
||||
diagnostics,
|
||||
locator,
|
||||
&exemption,
|
||||
&directives,
|
||||
noqa_line_for,
|
||||
suppressions,
|
||||
);
|
||||
let comments = find_noqa_comments(diagnostics, locator, &exemption, &directives, noqa_line_for);
|
||||
|
||||
let edits = build_noqa_edits_by_line(comments, locator, line_ending, reason);
|
||||
|
||||
@@ -879,7 +859,6 @@ fn find_noqa_comments<'a>(
|
||||
exemption: &'a FileExemption,
|
||||
directives: &'a NoqaDirectives,
|
||||
noqa_line_for: &NoqaMapping,
|
||||
suppressions: &Suppressions,
|
||||
) -> Vec<Option<NoqaComment<'a>>> {
|
||||
// List of noqa comments, ordered to match up with `messages`
|
||||
let mut comments_by_line: Vec<Option<NoqaComment<'a>>> = vec![];
|
||||
@@ -896,12 +875,6 @@ fn find_noqa_comments<'a>(
|
||||
continue;
|
||||
}
|
||||
|
||||
// Apply ranged suppressions next
|
||||
if suppressions.check_diagnostic(message) {
|
||||
comments_by_line.push(None);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is the violation ignored by a `noqa` directive on the parent line?
|
||||
if let Some(parent) = message.parent() {
|
||||
if let Some(directive_line) =
|
||||
@@ -1280,7 +1253,6 @@ mod tests {
|
||||
use crate::rules::pycodestyle::rules::{AmbiguousVariableName, UselessSemicolon};
|
||||
use crate::rules::pyflakes::rules::UnusedVariable;
|
||||
use crate::rules::pyupgrade::rules::PrintfStringFormatting;
|
||||
use crate::suppression::Suppressions;
|
||||
use crate::{Edit, Violation};
|
||||
use crate::{Locator, generate_noqa_edits};
|
||||
|
||||
@@ -2876,7 +2848,6 @@ mod tests {
|
||||
&noqa_line_for,
|
||||
LineEnding::Lf,
|
||||
None,
|
||||
&Suppressions::default(),
|
||||
);
|
||||
assert_eq!(count, 0);
|
||||
assert_eq!(output, format!("{contents}"));
|
||||
@@ -2901,7 +2872,6 @@ mod tests {
|
||||
&noqa_line_for,
|
||||
LineEnding::Lf,
|
||||
None,
|
||||
&Suppressions::default(),
|
||||
);
|
||||
assert_eq!(count, 1);
|
||||
assert_eq!(output, "x = 1 # noqa: F841\n");
|
||||
@@ -2933,7 +2903,6 @@ mod tests {
|
||||
&noqa_line_for,
|
||||
LineEnding::Lf,
|
||||
None,
|
||||
&Suppressions::default(),
|
||||
);
|
||||
assert_eq!(count, 1);
|
||||
assert_eq!(output, "x = 1 # noqa: E741, F841\n");
|
||||
@@ -2965,7 +2934,6 @@ mod tests {
|
||||
&noqa_line_for,
|
||||
LineEnding::Lf,
|
||||
None,
|
||||
&Suppressions::default(),
|
||||
);
|
||||
assert_eq!(count, 0);
|
||||
assert_eq!(output, "x = 1 # noqa");
|
||||
@@ -2988,7 +2956,6 @@ print(
|
||||
let messages = [PrintfStringFormatting
|
||||
.into_diagnostic(TextRange::new(12.into(), 79.into()), &source_file)];
|
||||
let comment_ranges = CommentRanges::default();
|
||||
let suppressions = Suppressions::default();
|
||||
let edits = generate_noqa_edits(
|
||||
path,
|
||||
&messages,
|
||||
@@ -2997,7 +2964,6 @@ print(
|
||||
&[],
|
||||
&noqa_line_for,
|
||||
LineEnding::Lf,
|
||||
&suppressions,
|
||||
);
|
||||
assert_eq!(
|
||||
edits,
|
||||
@@ -3021,7 +2987,6 @@ bar =
|
||||
[UselessSemicolon.into_diagnostic(TextRange::new(4.into(), 5.into()), &source_file)];
|
||||
let noqa_line_for = NoqaMapping::default();
|
||||
let comment_ranges = CommentRanges::default();
|
||||
let suppressions = Suppressions::default();
|
||||
let edits = generate_noqa_edits(
|
||||
path,
|
||||
&messages,
|
||||
@@ -3030,7 +2995,6 @@ bar =
|
||||
&[],
|
||||
&noqa_line_for,
|
||||
LineEnding::Lf,
|
||||
&suppressions,
|
||||
);
|
||||
assert_eq!(
|
||||
edits,
|
||||
|
||||
@@ -286,8 +286,3 @@ pub(crate) const fn is_s310_resolve_string_literal_bindings_enabled(
|
||||
) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/21623
|
||||
pub(crate) const fn is_range_suppressions_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
@@ -236,227 +236,227 @@ help: Replace with `None`; initialize within function
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:242:20
|
||||
--> B006_B008.py:239:20
|
||||
|
|
||||
240 | # B006 and B008
|
||||
241 | # We should handle arbitrary nesting of these B008.
|
||||
242 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
237 | # B006 and B008
|
||||
238 | # We should handle arbitrary nesting of these B008.
|
||||
239 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
243 | pass
|
||||
240 | pass
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
239 |
|
||||
240 | # B006 and B008
|
||||
241 | # We should handle arbitrary nesting of these B008.
|
||||
236 |
|
||||
237 | # B006 and B008
|
||||
238 | # We should handle arbitrary nesting of these B008.
|
||||
- def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
242 + def nested_combo(a=None):
|
||||
243 | pass
|
||||
244 |
|
||||
245 |
|
||||
239 + def nested_combo(a=None):
|
||||
240 | pass
|
||||
241 |
|
||||
242 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:279:27
|
||||
--> B006_B008.py:276:27
|
||||
|
|
||||
278 | def mutable_annotations(
|
||||
279 | a: list[int] | None = [],
|
||||
275 | def mutable_annotations(
|
||||
276 | a: list[int] | None = [],
|
||||
| ^^
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
276 |
|
||||
277 |
|
||||
278 | def mutable_annotations(
|
||||
273 |
|
||||
274 |
|
||||
275 | def mutable_annotations(
|
||||
- a: list[int] | None = [],
|
||||
279 + a: list[int] | None = None,
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
276 + a: list[int] | None = None,
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:280:35
|
||||
--> B006_B008.py:277:35
|
||||
|
|
||||
278 | def mutable_annotations(
|
||||
279 | a: list[int] | None = [],
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
275 | def mutable_annotations(
|
||||
276 | a: list[int] | None = [],
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
| ^^
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
277 |
|
||||
278 | def mutable_annotations(
|
||||
279 | a: list[int] | None = [],
|
||||
274 |
|
||||
275 | def mutable_annotations(
|
||||
276 | a: list[int] | None = [],
|
||||
- b: Optional[Dict[int, int]] = {},
|
||||
280 + b: Optional[Dict[int, int]] = None,
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
283 | ):
|
||||
277 + b: Optional[Dict[int, int]] = None,
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
280 | ):
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:281:62
|
||||
--> B006_B008.py:278:62
|
||||
|
|
||||
279 | a: list[int] | None = [],
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
276 | a: list[int] | None = [],
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
| ^^^^^
|
||||
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
283 | ):
|
||||
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
280 | ):
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
278 | def mutable_annotations(
|
||||
279 | a: list[int] | None = [],
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
275 | def mutable_annotations(
|
||||
276 | a: list[int] | None = [],
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
- c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
281 + c: Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
|
||||
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
283 | ):
|
||||
284 | pass
|
||||
278 + c: Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
|
||||
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
280 | ):
|
||||
281 | pass
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:282:80
|
||||
--> B006_B008.py:279:80
|
||||
|
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
| ^^^^^
|
||||
283 | ):
|
||||
284 | pass
|
||||
280 | ):
|
||||
281 | pass
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
279 | a: list[int] | None = [],
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
276 | a: list[int] | None = [],
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
- d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
282 + d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
|
||||
283 | ):
|
||||
284 | pass
|
||||
285 |
|
||||
279 + d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
|
||||
280 | ):
|
||||
281 | pass
|
||||
282 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:287:52
|
||||
--> B006_B008.py:284:52
|
||||
|
|
||||
287 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
284 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^
|
||||
288 | """Docstring"""
|
||||
285 | """Docstring"""
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
284 | pass
|
||||
285 |
|
||||
281 | pass
|
||||
282 |
|
||||
283 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
284 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
285 | """Docstring"""
|
||||
286 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
287 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
288 | """Docstring"""
|
||||
289 |
|
||||
290 |
|
||||
287 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:291:52
|
||||
--> B006_B008.py:288:52
|
||||
|
|
||||
291 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
288 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^
|
||||
292 | """Docstring"""
|
||||
293 | ...
|
||||
289 | """Docstring"""
|
||||
290 | ...
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
288 | """Docstring"""
|
||||
289 |
|
||||
290 |
|
||||
285 | """Docstring"""
|
||||
286 |
|
||||
287 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
291 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
292 | """Docstring"""
|
||||
293 | ...
|
||||
294 |
|
||||
288 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
289 | """Docstring"""
|
||||
290 | ...
|
||||
291 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:296:52
|
||||
--> B006_B008.py:293:52
|
||||
|
|
||||
296 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
293 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^
|
||||
297 | """Docstring"""; ...
|
||||
294 | """Docstring"""; ...
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
293 | ...
|
||||
294 |
|
||||
290 | ...
|
||||
291 |
|
||||
292 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
293 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
294 | """Docstring"""; ...
|
||||
295 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
296 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
297 | """Docstring"""; ...
|
||||
298 |
|
||||
299 |
|
||||
296 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:300:52
|
||||
--> B006_B008.py:297:52
|
||||
|
|
||||
300 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
297 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^
|
||||
301 | """Docstring"""; \
|
||||
302 | ...
|
||||
298 | """Docstring"""; \
|
||||
299 | ...
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
297 | """Docstring"""; ...
|
||||
298 |
|
||||
299 |
|
||||
294 | """Docstring"""; ...
|
||||
295 |
|
||||
296 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
300 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
301 | """Docstring"""; \
|
||||
302 | ...
|
||||
303 |
|
||||
297 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
298 | """Docstring"""; \
|
||||
299 | ...
|
||||
300 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:305:52
|
||||
--> B006_B008.py:302:52
|
||||
|
|
||||
305 | def single_line_func_wrong(value: dict[str, str] = {
|
||||
302 | def single_line_func_wrong(value: dict[str, str] = {
|
||||
| ____________________________________________________^
|
||||
306 | | # This is a comment
|
||||
307 | | }):
|
||||
303 | | # This is a comment
|
||||
304 | | }):
|
||||
| |_^
|
||||
308 | """Docstring"""
|
||||
305 | """Docstring"""
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
302 | ...
|
||||
303 |
|
||||
304 |
|
||||
299 | ...
|
||||
300 |
|
||||
301 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {
|
||||
- # This is a comment
|
||||
- }):
|
||||
305 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
306 | """Docstring"""
|
||||
307 |
|
||||
308 |
|
||||
302 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
303 | """Docstring"""
|
||||
304 |
|
||||
305 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:311:52
|
||||
--> B006_B008.py:308:52
|
||||
|
|
||||
311 | def single_line_func_wrong(value: dict[str, str] = {}) \
|
||||
308 | def single_line_func_wrong(value: dict[str, str] = {}) \
|
||||
| ^^
|
||||
312 | : \
|
||||
313 | """Docstring"""
|
||||
309 | : \
|
||||
310 | """Docstring"""
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:316:52
|
||||
--> B006_B008.py:313:52
|
||||
|
|
||||
316 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
313 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^
|
||||
317 | """Docstring without newline"""
|
||||
314 | """Docstring without newline"""
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
313 | """Docstring"""
|
||||
314 |
|
||||
315 |
|
||||
310 | """Docstring"""
|
||||
311 |
|
||||
312 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
316 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
317 | """Docstring without newline"""
|
||||
313 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
314 | """Docstring without newline"""
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
@@ -53,39 +53,39 @@ B008 Do not perform function call in argument defaults; instead, perform the cal
|
||||
|
|
||||
|
||||
B008 Do not perform function call `dt.datetime.now` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
||||
--> B006_B008.py:242:31
|
||||
--> B006_B008.py:239:31
|
||||
|
|
||||
240 | # B006 and B008
|
||||
241 | # We should handle arbitrary nesting of these B008.
|
||||
242 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
237 | # B006 and B008
|
||||
238 | # We should handle arbitrary nesting of these B008.
|
||||
239 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
243 | pass
|
||||
240 | pass
|
||||
|
|
||||
|
||||
B008 Do not perform function call `map` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
||||
--> B006_B008.py:248:22
|
||||
--> B006_B008.py:245:22
|
||||
|
|
||||
246 | # Don't flag nested B006 since we can't guarantee that
|
||||
247 | # it isn't made mutable by the outer operation.
|
||||
248 | def no_nested_b006(a=map(lambda s: s.upper(), ["a", "b", "c"])):
|
||||
243 | # Don't flag nested B006 since we can't guarantee that
|
||||
244 | # it isn't made mutable by the outer operation.
|
||||
245 | def no_nested_b006(a=map(lambda s: s.upper(), ["a", "b", "c"])):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
249 | pass
|
||||
246 | pass
|
||||
|
|
||||
|
||||
B008 Do not perform function call `random.randint` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
||||
--> B006_B008.py:253:19
|
||||
--> B006_B008.py:250:19
|
||||
|
|
||||
252 | # B008-ception.
|
||||
253 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
|
||||
249 | # B008-ception.
|
||||
250 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
254 | pass
|
||||
251 | pass
|
||||
|
|
||||
|
||||
B008 Do not perform function call `dt.datetime.now` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
|
||||
--> B006_B008.py:253:37
|
||||
--> B006_B008.py:250:37
|
||||
|
|
||||
252 | # B008-ception.
|
||||
253 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
|
||||
249 | # B008-ception.
|
||||
250 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
254 | pass
|
||||
251 | pass
|
||||
|
|
||||
|
||||
@@ -236,227 +236,227 @@ help: Replace with `None`; initialize within function
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:242:20
|
||||
--> B006_B008.py:239:20
|
||||
|
|
||||
240 | # B006 and B008
|
||||
241 | # We should handle arbitrary nesting of these B008.
|
||||
242 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
237 | # B006 and B008
|
||||
238 | # We should handle arbitrary nesting of these B008.
|
||||
239 | def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
243 | pass
|
||||
240 | pass
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
239 |
|
||||
240 | # B006 and B008
|
||||
241 | # We should handle arbitrary nesting of these B008.
|
||||
236 |
|
||||
237 | # B006 and B008
|
||||
238 | # We should handle arbitrary nesting of these B008.
|
||||
- def nested_combo(a=[float(3), dt.datetime.now()]):
|
||||
242 + def nested_combo(a=None):
|
||||
243 | pass
|
||||
244 |
|
||||
245 |
|
||||
239 + def nested_combo(a=None):
|
||||
240 | pass
|
||||
241 |
|
||||
242 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:279:27
|
||||
--> B006_B008.py:276:27
|
||||
|
|
||||
278 | def mutable_annotations(
|
||||
279 | a: list[int] | None = [],
|
||||
275 | def mutable_annotations(
|
||||
276 | a: list[int] | None = [],
|
||||
| ^^
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
276 |
|
||||
277 |
|
||||
278 | def mutable_annotations(
|
||||
273 |
|
||||
274 |
|
||||
275 | def mutable_annotations(
|
||||
- a: list[int] | None = [],
|
||||
279 + a: list[int] | None = None,
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
276 + a: list[int] | None = None,
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:280:35
|
||||
--> B006_B008.py:277:35
|
||||
|
|
||||
278 | def mutable_annotations(
|
||||
279 | a: list[int] | None = [],
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
275 | def mutable_annotations(
|
||||
276 | a: list[int] | None = [],
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
| ^^
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
277 |
|
||||
278 | def mutable_annotations(
|
||||
279 | a: list[int] | None = [],
|
||||
274 |
|
||||
275 | def mutable_annotations(
|
||||
276 | a: list[int] | None = [],
|
||||
- b: Optional[Dict[int, int]] = {},
|
||||
280 + b: Optional[Dict[int, int]] = None,
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
283 | ):
|
||||
277 + b: Optional[Dict[int, int]] = None,
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
280 | ):
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:281:62
|
||||
--> B006_B008.py:278:62
|
||||
|
|
||||
279 | a: list[int] | None = [],
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
276 | a: list[int] | None = [],
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
| ^^^^^
|
||||
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
283 | ):
|
||||
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
280 | ):
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
278 | def mutable_annotations(
|
||||
279 | a: list[int] | None = [],
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
275 | def mutable_annotations(
|
||||
276 | a: list[int] | None = [],
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
- c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
281 + c: Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
|
||||
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
283 | ):
|
||||
284 | pass
|
||||
278 + c: Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
|
||||
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
280 | ):
|
||||
281 | pass
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:282:80
|
||||
--> B006_B008.py:279:80
|
||||
|
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
| ^^^^^
|
||||
283 | ):
|
||||
284 | pass
|
||||
280 | ):
|
||||
281 | pass
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
279 | a: list[int] | None = [],
|
||||
280 | b: Optional[Dict[int, int]] = {},
|
||||
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
276 | a: list[int] | None = [],
|
||||
277 | b: Optional[Dict[int, int]] = {},
|
||||
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
- d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
282 + d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
|
||||
283 | ):
|
||||
284 | pass
|
||||
285 |
|
||||
279 + d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
|
||||
280 | ):
|
||||
281 | pass
|
||||
282 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:287:52
|
||||
--> B006_B008.py:284:52
|
||||
|
|
||||
287 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
284 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^
|
||||
288 | """Docstring"""
|
||||
285 | """Docstring"""
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
284 | pass
|
||||
285 |
|
||||
281 | pass
|
||||
282 |
|
||||
283 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
284 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
285 | """Docstring"""
|
||||
286 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
287 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
288 | """Docstring"""
|
||||
289 |
|
||||
290 |
|
||||
287 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:291:52
|
||||
--> B006_B008.py:288:52
|
||||
|
|
||||
291 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
288 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^
|
||||
292 | """Docstring"""
|
||||
293 | ...
|
||||
289 | """Docstring"""
|
||||
290 | ...
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
288 | """Docstring"""
|
||||
289 |
|
||||
290 |
|
||||
285 | """Docstring"""
|
||||
286 |
|
||||
287 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
291 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
292 | """Docstring"""
|
||||
293 | ...
|
||||
294 |
|
||||
288 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
289 | """Docstring"""
|
||||
290 | ...
|
||||
291 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:296:52
|
||||
--> B006_B008.py:293:52
|
||||
|
|
||||
296 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
293 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^
|
||||
297 | """Docstring"""; ...
|
||||
294 | """Docstring"""; ...
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
293 | ...
|
||||
294 |
|
||||
290 | ...
|
||||
291 |
|
||||
292 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
293 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
294 | """Docstring"""; ...
|
||||
295 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
296 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
297 | """Docstring"""; ...
|
||||
298 |
|
||||
299 |
|
||||
296 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:300:52
|
||||
--> B006_B008.py:297:52
|
||||
|
|
||||
300 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
297 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^
|
||||
301 | """Docstring"""; \
|
||||
302 | ...
|
||||
298 | """Docstring"""; \
|
||||
299 | ...
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
297 | """Docstring"""; ...
|
||||
298 |
|
||||
299 |
|
||||
294 | """Docstring"""; ...
|
||||
295 |
|
||||
296 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
300 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
301 | """Docstring"""; \
|
||||
302 | ...
|
||||
303 |
|
||||
297 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
298 | """Docstring"""; \
|
||||
299 | ...
|
||||
300 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:305:52
|
||||
--> B006_B008.py:302:52
|
||||
|
|
||||
305 | def single_line_func_wrong(value: dict[str, str] = {
|
||||
302 | def single_line_func_wrong(value: dict[str, str] = {
|
||||
| ____________________________________________________^
|
||||
306 | | # This is a comment
|
||||
307 | | }):
|
||||
303 | | # This is a comment
|
||||
304 | | }):
|
||||
| |_^
|
||||
308 | """Docstring"""
|
||||
305 | """Docstring"""
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
302 | ...
|
||||
303 |
|
||||
304 |
|
||||
299 | ...
|
||||
300 |
|
||||
301 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {
|
||||
- # This is a comment
|
||||
- }):
|
||||
305 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
306 | """Docstring"""
|
||||
307 |
|
||||
308 |
|
||||
302 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
303 | """Docstring"""
|
||||
304 |
|
||||
305 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
B006 Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:311:52
|
||||
--> B006_B008.py:308:52
|
||||
|
|
||||
311 | def single_line_func_wrong(value: dict[str, str] = {}) \
|
||||
308 | def single_line_func_wrong(value: dict[str, str] = {}) \
|
||||
| ^^
|
||||
312 | : \
|
||||
313 | """Docstring"""
|
||||
309 | : \
|
||||
310 | """Docstring"""
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
|
||||
B006 [*] Do not use mutable data structures for argument defaults
|
||||
--> B006_B008.py:316:52
|
||||
--> B006_B008.py:313:52
|
||||
|
|
||||
316 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
313 | def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
| ^^
|
||||
317 | """Docstring without newline"""
|
||||
314 | """Docstring without newline"""
|
||||
|
|
||||
help: Replace with `None`; initialize within function
|
||||
313 | """Docstring"""
|
||||
314 |
|
||||
315 |
|
||||
310 | """Docstring"""
|
||||
311 |
|
||||
312 |
|
||||
- def single_line_func_wrong(value: dict[str, str] = {}):
|
||||
316 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
317 | """Docstring without newline"""
|
||||
313 + def single_line_func_wrong(value: dict[str, str] = None):
|
||||
314 | """Docstring without newline"""
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
@@ -4,9 +4,7 @@ use rustc_hash::FxHashSet;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::Parameter;
|
||||
use ruff_python_ast::docstrings::{clean_space, leading_space};
|
||||
use ruff_python_ast::helpers::map_subscript;
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
use ruff_python_semantic::analyze::visibility::is_staticmethod;
|
||||
use ruff_python_trivia::textwrap::dedent;
|
||||
@@ -1186,9 +1184,6 @@ impl AlwaysFixableViolation for MissingSectionNameColon {
|
||||
/// This rule is enabled when using the `google` convention, and disabled when
|
||||
/// using the `pep257` and `numpy` conventions.
|
||||
///
|
||||
/// Parameters annotated with `typing.Unpack` are exempt from this rule.
|
||||
/// This follows the Python typing specification for unpacking keyword arguments.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def calculate_speed(distance: float, time: float) -> float:
|
||||
@@ -1238,7 +1233,6 @@ impl AlwaysFixableViolation for MissingSectionNameColon {
|
||||
/// - [PEP 257 – Docstring Conventions](https://peps.python.org/pep-0257/)
|
||||
/// - [PEP 287 – reStructuredText Docstring Format](https://peps.python.org/pep-0287/)
|
||||
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
|
||||
/// - [Python - Unpack for keyword arguments](https://typing.python.org/en/latest/spec/callables.html#unpack-kwargs)
|
||||
#[derive(ViolationMetadata)]
|
||||
#[violation_metadata(stable_since = "v0.0.73")]
|
||||
pub(crate) struct UndocumentedParam {
|
||||
@@ -1814,9 +1808,7 @@ fn missing_args(checker: &Checker, docstring: &Docstring, docstrings_args: &FxHa
|
||||
missing_arg_names.insert(starred_arg_name);
|
||||
}
|
||||
}
|
||||
if let Some(arg) = function.parameters.kwarg.as_ref()
|
||||
&& !has_unpack_annotation(checker, arg)
|
||||
{
|
||||
if let Some(arg) = function.parameters.kwarg.as_ref() {
|
||||
let arg_name = arg.name.as_str();
|
||||
let starred_arg_name = format!("**{arg_name}");
|
||||
if !arg_name.starts_with('_')
|
||||
@@ -1842,15 +1834,6 @@ fn missing_args(checker: &Checker, docstring: &Docstring, docstrings_args: &FxHa
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the parameter is annotated with `typing.Unpack`
|
||||
fn has_unpack_annotation(checker: &Checker, parameter: &Parameter) -> bool {
|
||||
parameter.annotation.as_ref().is_some_and(|annotation| {
|
||||
checker
|
||||
.semantic()
|
||||
.match_typing_expr(map_subscript(annotation), "Unpack")
|
||||
})
|
||||
}
|
||||
|
||||
// See: `GOOGLE_ARGS_REGEX` in `pydocstyle/checker.py`.
|
||||
static GOOGLE_ARGS_REGEX: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^\s*(\*?\*?\w+)\s*(\(.*?\))?\s*:(\r\n|\n)?\s*.+").unwrap());
|
||||
|
||||
@@ -101,13 +101,3 @@ D417 Missing argument description in the docstring for `should_fail`: `Args`
|
||||
200 | """
|
||||
201 | Send a message.
|
||||
|
|
||||
|
||||
D417 Missing argument description in the docstring for `function_with_unpack_and_missing_arg_doc_should_fail`: `query`
|
||||
--> D417.py:238:5
|
||||
|
|
||||
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.
|
||||
|
|
||||
|
||||
@@ -83,13 +83,3 @@ D417 Missing argument description in the docstring for `should_fail`: `Args`
|
||||
200 | """
|
||||
201 | Send a message.
|
||||
|
|
||||
|
||||
D417 Missing argument description in the docstring for `function_with_unpack_and_missing_arg_doc_should_fail`: `query`
|
||||
--> D417.py:238:5
|
||||
|
|
||||
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.
|
||||
|
|
||||
|
||||
@@ -101,13 +101,3 @@ D417 Missing argument description in the docstring for `should_fail`: `Args`
|
||||
200 | """
|
||||
201 | Send a message.
|
||||
|
|
||||
|
||||
D417 Missing argument description in the docstring for `function_with_unpack_and_missing_arg_doc_should_fail`: `query`
|
||||
--> D417.py:238:5
|
||||
|
|
||||
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.
|
||||
|
|
||||
|
||||
@@ -101,13 +101,3 @@ D417 Missing argument description in the docstring for `should_fail`: `Args`
|
||||
200 | """
|
||||
201 | Send a message.
|
||||
|
|
||||
|
||||
D417 Missing argument description in the docstring for `function_with_unpack_and_missing_arg_doc_should_fail`: `query`
|
||||
--> D417.py:238:5
|
||||
|
|
||||
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.
|
||||
|
|
||||
|
||||
@@ -28,7 +28,6 @@ mod tests {
|
||||
use crate::settings::types::PreviewMode;
|
||||
use crate::settings::{LinterSettings, flags};
|
||||
use crate::source_kind::SourceKind;
|
||||
use crate::suppression::Suppressions;
|
||||
use crate::test::{test_contents, test_path, test_snippet};
|
||||
use crate::{Locator, assert_diagnostics, assert_diagnostics_diff, directives};
|
||||
|
||||
@@ -956,8 +955,6 @@ mod tests {
|
||||
&locator,
|
||||
&indexer,
|
||||
);
|
||||
let suppressions =
|
||||
Suppressions::from_tokens(&settings, locator.contents(), parsed.tokens());
|
||||
let mut messages = check_path(
|
||||
Path::new("<filename>"),
|
||||
None,
|
||||
@@ -971,7 +968,6 @@ mod tests {
|
||||
source_type,
|
||||
&parsed,
|
||||
target_version,
|
||||
&suppressions,
|
||||
);
|
||||
messages.sort_by(Diagnostic::ruff_start_ordering);
|
||||
let actual = messages
|
||||
|
||||
@@ -305,25 +305,6 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_suppressions() -> Result<()> {
|
||||
assert_diagnostics_diff!(
|
||||
Path::new("ruff/suppressions.py"),
|
||||
&settings::LinterSettings::for_rules(vec![
|
||||
Rule::UnusedVariable,
|
||||
Rule::AmbiguousVariableName,
|
||||
Rule::UnusedNOQA,
|
||||
]),
|
||||
&settings::LinterSettings::for_rules(vec![
|
||||
Rule::UnusedVariable,
|
||||
Rule::AmbiguousVariableName,
|
||||
Rule::UnusedNOQA,
|
||||
])
|
||||
.with_preview_mode(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ruf100_0() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
--- Linter settings ---
|
||||
-linter.preview = disabled
|
||||
+linter.preview = enabled
|
||||
|
||||
--- Summary ---
|
||||
Removed: 9
|
||||
Added: 1
|
||||
|
||||
--- Removed ---
|
||||
E741 Ambiguous variable name: `I`
|
||||
--> suppressions.py:4:5
|
||||
|
|
||||
2 | # These should both be ignored by the range suppression.
|
||||
3 | # ruff: disable[E741, F841]
|
||||
4 | I = 1
|
||||
| ^
|
||||
5 | # ruff: enable[E741, F841]
|
||||
|
|
||||
|
||||
|
||||
F841 [*] Local variable `I` is assigned to but never used
|
||||
--> suppressions.py:4:5
|
||||
|
|
||||
2 | # These should both be ignored by the range suppression.
|
||||
3 | # ruff: disable[E741, F841]
|
||||
4 | I = 1
|
||||
| ^
|
||||
5 | # ruff: enable[E741, F841]
|
||||
|
|
||||
help: Remove assignment to unused variable `I`
|
||||
1 | def f():
|
||||
2 | # These should both be ignored by the range suppression.
|
||||
3 | # ruff: disable[E741, F841]
|
||||
- I = 1
|
||||
4 + pass
|
||||
5 | # ruff: enable[E741, F841]
|
||||
6 |
|
||||
7 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
E741 Ambiguous variable name: `I`
|
||||
--> suppressions.py:12:5
|
||||
|
|
||||
10 | # Should also generate an "unmatched suppression" warning.
|
||||
11 | # ruff:disable[E741,F841]
|
||||
12 | I = 1
|
||||
| ^
|
||||
|
|
||||
|
||||
|
||||
F841 [*] Local variable `I` is assigned to but never used
|
||||
--> suppressions.py:12:5
|
||||
|
|
||||
10 | # Should also generate an "unmatched suppression" warning.
|
||||
11 | # ruff:disable[E741,F841]
|
||||
12 | I = 1
|
||||
| ^
|
||||
|
|
||||
help: Remove assignment to unused variable `I`
|
||||
9 | # These should both be ignored by the implicit range suppression.
|
||||
10 | # Should also generate an "unmatched suppression" warning.
|
||||
11 | # ruff:disable[E741,F841]
|
||||
- I = 1
|
||||
12 + pass
|
||||
13 |
|
||||
14 |
|
||||
15 | def f():
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
E741 Ambiguous variable name: `I`
|
||||
--> suppressions.py:26:5
|
||||
|
|
||||
24 | # the other logged to the user.
|
||||
25 | # ruff: disable[E741]
|
||||
26 | I = 1
|
||||
| ^
|
||||
27 | # ruff: enable[E741]
|
||||
|
|
||||
|
||||
|
||||
E741 Ambiguous variable name: `l`
|
||||
--> suppressions.py:35:5
|
||||
|
|
||||
33 | # middle line should be completely silenced.
|
||||
34 | # ruff: disable[E741]
|
||||
35 | l = 0
|
||||
| ^
|
||||
36 | # ruff: disable[F841]
|
||||
37 | O = 1
|
||||
|
|
||||
|
||||
|
||||
E741 Ambiguous variable name: `O`
|
||||
--> suppressions.py:37:5
|
||||
|
|
||||
35 | l = 0
|
||||
36 | # ruff: disable[F841]
|
||||
37 | O = 1
|
||||
| ^
|
||||
38 | # ruff: enable[E741]
|
||||
39 | I = 2
|
||||
|
|
||||
|
||||
|
||||
F841 [*] Local variable `O` is assigned to but never used
|
||||
--> suppressions.py:37:5
|
||||
|
|
||||
35 | l = 0
|
||||
36 | # ruff: disable[F841]
|
||||
37 | O = 1
|
||||
| ^
|
||||
38 | # ruff: enable[E741]
|
||||
39 | I = 2
|
||||
|
|
||||
help: Remove assignment to unused variable `O`
|
||||
34 | # ruff: disable[E741]
|
||||
35 | l = 0
|
||||
36 | # ruff: disable[F841]
|
||||
- O = 1
|
||||
37 | # ruff: enable[E741]
|
||||
38 | I = 2
|
||||
39 | # ruff: enable[F841]
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
F841 [*] Local variable `I` is assigned to but never used
|
||||
--> suppressions.py:39:5
|
||||
|
|
||||
37 | O = 1
|
||||
38 | # ruff: enable[E741]
|
||||
39 | I = 2
|
||||
| ^
|
||||
40 | # ruff: enable[F841]
|
||||
|
|
||||
help: Remove assignment to unused variable `I`
|
||||
36 | # ruff: disable[F841]
|
||||
37 | O = 1
|
||||
38 | # ruff: enable[E741]
|
||||
- I = 2
|
||||
39 | # ruff: enable[F841]
|
||||
40 |
|
||||
41 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
|
||||
--- Added ---
|
||||
RUF100 [*] Unused `noqa` directive (unused: `E741`, `F841`)
|
||||
--> suppressions.py:55:12
|
||||
|
|
||||
53 | # and an unusued noqa diagnostic should be logged.
|
||||
54 | # ruff:disable[E741,F841]
|
||||
55 | I = 1 # noqa: E741,F841
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
56 | # ruff:enable[E741,F841]
|
||||
|
|
||||
help: Remove unused `noqa` directive
|
||||
52 | # These should both be ignored by the range suppression,
|
||||
53 | # and an unusued noqa diagnostic should be logged.
|
||||
54 | # ruff:disable[E741,F841]
|
||||
- I = 1 # noqa: E741,F841
|
||||
55 + I = 1
|
||||
56 | # ruff:enable[E741,F841]
|
||||
@@ -465,12 +465,6 @@ impl LinterSettings {
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_preview_mode(mut self) -> Self {
|
||||
self.preview = PreviewMode::Enabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// Resolve the [`TargetVersion`] to use for linting.
|
||||
///
|
||||
/// This method respects the per-file version overrides in
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use compact_str::CompactString;
|
||||
use core::fmt;
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_python_ast::token::{TokenKind, Tokens};
|
||||
use ruff_python_ast::whitespace::indentation;
|
||||
use std::{error::Error, fmt::Formatter};
|
||||
@@ -10,9 +9,6 @@ use ruff_python_trivia::Cursor;
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize, TextSlice};
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
|
||||
use crate::preview::is_range_suppressions_enabled;
|
||||
use crate::settings::LinterSettings;
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
enum SuppressionAction {
|
||||
@@ -102,8 +98,8 @@ pub(crate) struct InvalidSuppression {
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Suppressions {
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Suppressions {
|
||||
/// Valid suppression ranges with associated comments
|
||||
valid: Vec<Suppression>,
|
||||
|
||||
@@ -116,41 +112,9 @@ pub struct Suppressions {
|
||||
|
||||
#[allow(unused)]
|
||||
impl Suppressions {
|
||||
pub fn from_tokens(settings: &LinterSettings, source: &str, tokens: &Tokens) -> Suppressions {
|
||||
if is_range_suppressions_enabled(settings) {
|
||||
let builder = SuppressionsBuilder::new(source);
|
||||
builder.load_from_tokens(tokens)
|
||||
} else {
|
||||
Suppressions::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.valid.is_empty()
|
||||
}
|
||||
|
||||
/// Check if a diagnostic is suppressed by any known range suppressions
|
||||
pub(crate) fn check_diagnostic(&self, diagnostic: &Diagnostic) -> bool {
|
||||
if self.valid.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let Some(code) = diagnostic.secondary_code() else {
|
||||
return false;
|
||||
};
|
||||
let Some(span) = diagnostic.primary_span() else {
|
||||
return false;
|
||||
};
|
||||
let Some(range) = span.range() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
for suppression in &self.valid {
|
||||
if *code == suppression.code.as_str() && suppression.range.contains_range(range) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
pub(crate) fn from_tokens(source: &str, tokens: &Tokens) -> Suppressions {
|
||||
let builder = SuppressionsBuilder::new(source);
|
||||
builder.load_from_tokens(tokens)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -493,12 +457,9 @@ mod tests {
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use similar::DiffableStr;
|
||||
|
||||
use crate::{
|
||||
settings::LinterSettings,
|
||||
suppression::{
|
||||
InvalidSuppression, ParseError, Suppression, SuppressionAction, SuppressionComment,
|
||||
SuppressionParser, Suppressions,
|
||||
},
|
||||
use crate::suppression::{
|
||||
InvalidSuppression, ParseError, Suppression, SuppressionAction, SuppressionComment,
|
||||
SuppressionParser, Suppressions,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -1415,11 +1376,7 @@ def bar():
|
||||
/// Parse all suppressions and errors in a module for testing
|
||||
fn debug(source: &'_ str) -> DebugSuppressions<'_> {
|
||||
let parsed = parse(source, ParseOptions::from(Mode::Module)).unwrap();
|
||||
let suppressions = Suppressions::from_tokens(
|
||||
&LinterSettings::default().with_preview_mode(),
|
||||
source,
|
||||
parsed.tokens(),
|
||||
);
|
||||
let suppressions = Suppressions::from_tokens(source, parsed.tokens());
|
||||
DebugSuppressions {
|
||||
source,
|
||||
suppressions,
|
||||
|
||||
@@ -32,7 +32,6 @@ use crate::packaging::detect_package_root;
|
||||
use crate::settings::types::UnsafeFixes;
|
||||
use crate::settings::{LinterSettings, flags};
|
||||
use crate::source_kind::SourceKind;
|
||||
use crate::suppression::Suppressions;
|
||||
use crate::{Applicability, FixAvailability};
|
||||
use crate::{Locator, directives};
|
||||
|
||||
@@ -235,7 +234,6 @@ pub(crate) fn test_contents<'a>(
|
||||
&locator,
|
||||
&indexer,
|
||||
);
|
||||
let suppressions = Suppressions::from_tokens(settings, locator.contents(), parsed.tokens());
|
||||
let messages = check_path(
|
||||
path,
|
||||
path.parent()
|
||||
@@ -251,7 +249,6 @@ pub(crate) fn test_contents<'a>(
|
||||
source_type,
|
||||
&parsed,
|
||||
target_version,
|
||||
&suppressions,
|
||||
);
|
||||
|
||||
let source_has_errors = parsed.has_invalid_syntax();
|
||||
@@ -302,8 +299,6 @@ pub(crate) fn test_contents<'a>(
|
||||
&indexer,
|
||||
);
|
||||
|
||||
let suppressions =
|
||||
Suppressions::from_tokens(settings, locator.contents(), parsed.tokens());
|
||||
let fixed_messages = check_path(
|
||||
path,
|
||||
None,
|
||||
@@ -317,7 +312,6 @@ pub(crate) fn test_contents<'a>(
|
||||
source_type,
|
||||
&parsed,
|
||||
target_version,
|
||||
&suppressions,
|
||||
);
|
||||
|
||||
if parsed.has_invalid_syntax() && !source_has_errors {
|
||||
|
||||
@@ -326,15 +326,7 @@ pub fn is_immutable_return_type(qualified_name: &[&str]) -> bool {
|
||||
| ["re", "compile"]
|
||||
| [
|
||||
"",
|
||||
"bool"
|
||||
| "bytes"
|
||||
| "complex"
|
||||
| "float"
|
||||
| "frozenset"
|
||||
| "int"
|
||||
| "str"
|
||||
| "tuple"
|
||||
| "slice"
|
||||
"bool" | "bytes" | "complex" | "float" | "frozenset" | "int" | "str" | "tuple"
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ use ruff_linter::{
|
||||
packaging::detect_package_root,
|
||||
settings::flags,
|
||||
source_kind::SourceKind,
|
||||
suppression::Suppressions,
|
||||
};
|
||||
use ruff_notebook::Notebook;
|
||||
use ruff_python_codegen::Stylist;
|
||||
@@ -119,10 +118,6 @@ pub(crate) fn check(
|
||||
// Extract the `# noqa` and `# isort: skip` directives from the source.
|
||||
let directives = extract_directives(parsed.tokens(), Flags::all(), &locator, &indexer);
|
||||
|
||||
// Parse range suppression comments
|
||||
let suppressions =
|
||||
Suppressions::from_tokens(&settings.linter, locator.contents(), parsed.tokens());
|
||||
|
||||
// Generate checks.
|
||||
let diagnostics = check_path(
|
||||
&document_path,
|
||||
@@ -137,7 +132,6 @@ pub(crate) fn check(
|
||||
source_type,
|
||||
&parsed,
|
||||
target_version,
|
||||
&suppressions,
|
||||
);
|
||||
|
||||
let noqa_edits = generate_noqa_edits(
|
||||
@@ -148,7 +142,6 @@ pub(crate) fn check(
|
||||
&settings.linter.external,
|
||||
&directives.noqa_line_for,
|
||||
stylist.line_ending(),
|
||||
&suppressions,
|
||||
);
|
||||
|
||||
let mut diagnostics_map = DiagnosticsMap::default();
|
||||
|
||||
@@ -33,29 +33,26 @@ impl LineIndex {
|
||||
line_starts.push(TextSize::default());
|
||||
|
||||
let bytes = text.as_bytes();
|
||||
let mut utf8 = false;
|
||||
|
||||
assert!(u32::try_from(bytes.len()).is_ok());
|
||||
|
||||
for i in memchr::memchr2_iter(b'\n', b'\r', bytes) {
|
||||
// Skip `\r` in `\r\n` sequences (only count the `\n`).
|
||||
if bytes[i] == b'\r' && bytes.get(i + 1) == Some(&b'\n') {
|
||||
continue;
|
||||
for (i, byte) in bytes.iter().enumerate() {
|
||||
utf8 |= !byte.is_ascii();
|
||||
|
||||
match byte {
|
||||
// Only track one line break for `\r\n`.
|
||||
b'\r' if bytes.get(i + 1) == Some(&b'\n') => continue,
|
||||
b'\n' | b'\r' => {
|
||||
// SAFETY: Assertion above guarantees `i <= u32::MAX`
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
line_starts.push(TextSize::from(i as u32) + TextSize::from(1));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
// SAFETY: Assertion above guarantees `i <= u32::MAX`
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
line_starts.push(TextSize::from(i as u32) + TextSize::from(1));
|
||||
}
|
||||
|
||||
// Determine whether the source text is ASCII.
|
||||
//
|
||||
// Empirically, this simple loop is auto-vectorized by LLVM and benchmarks faster than both
|
||||
// `str::is_ascii()` and hand-written SIMD.
|
||||
let mut has_non_ascii = false;
|
||||
for byte in bytes {
|
||||
has_non_ascii |= !byte.is_ascii();
|
||||
}
|
||||
|
||||
let kind = if has_non_ascii {
|
||||
let kind = if utf8 {
|
||||
IndexKind::Utf8
|
||||
} else {
|
||||
IndexKind::Ascii
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::path::Path;
|
||||
|
||||
use js_sys::Error;
|
||||
use ruff_linter::settings::types::PythonVersion;
|
||||
use ruff_linter::suppression::Suppressions;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
@@ -213,9 +212,6 @@ impl Workspace {
|
||||
&indexer,
|
||||
);
|
||||
|
||||
let suppressions =
|
||||
Suppressions::from_tokens(&self.settings.linter, locator.contents(), parsed.tokens());
|
||||
|
||||
// Generate checks.
|
||||
let diagnostics = check_path(
|
||||
Path::new("<filename>"),
|
||||
@@ -230,7 +226,6 @@ impl Workspace {
|
||||
source_type,
|
||||
&parsed,
|
||||
target_version,
|
||||
&suppressions,
|
||||
);
|
||||
|
||||
let source_code = locator.to_source_code();
|
||||
|
||||
@@ -43,7 +43,7 @@ fn config_override_python_version() -> anyhow::Result<()> {
|
||||
|
|
||||
2 | [tool.ty.environment]
|
||||
3 | python-version = "3.11"
|
||||
| ^^^^^^ Python version configuration
|
||||
| ^^^^^^ Python 3.11 assumed due to this configuration setting
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
@@ -143,7 +143,7 @@ fn config_file_annotation_showing_where_python_version_set_typing_error() -> any
|
||||
),
|
||||
])?;
|
||||
|
||||
assert_cmd_snapshot!(case.command(), @r#"
|
||||
assert_cmd_snapshot!(case.command(), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
@@ -159,14 +159,14 @@ fn config_file_annotation_showing_where_python_version_set_typing_error() -> any
|
||||
|
|
||||
2 | [tool.ty.environment]
|
||||
3 | python-version = "3.8"
|
||||
| ^^^^^ Python version configuration
|
||||
| ^^^^^ Python 3.8 assumed due to this configuration setting
|
||||
|
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
"#);
|
||||
"###);
|
||||
|
||||
assert_cmd_snapshot!(case.command().arg("--python-version=3.9"), @r###"
|
||||
success: false
|
||||
@@ -772,7 +772,7 @@ fn pyvenv_cfg_file_annotation_showing_where_python_version_set() -> anyhow::Resu
|
||||
("test.py", "aiter"),
|
||||
])?;
|
||||
|
||||
assert_cmd_snapshot!(case.command(), @r"
|
||||
assert_cmd_snapshot!(case.command(), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
@@ -787,7 +787,7 @@ fn pyvenv_cfg_file_annotation_showing_where_python_version_set() -> anyhow::Resu
|
||||
--> venv/pyvenv.cfg:2:11
|
||||
|
|
||||
2 | version = 3.8
|
||||
| ^^^ Virtual environment metadata
|
||||
| ^^^ Python version inferred from virtual environment metadata file
|
||||
3 | home = foo/bar/bin
|
||||
|
|
||||
info: No Python version was specified on the command line or in a configuration file
|
||||
@@ -796,7 +796,7 @@ fn pyvenv_cfg_file_annotation_showing_where_python_version_set() -> anyhow::Resu
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -831,7 +831,7 @@ fn pyvenv_cfg_file_annotation_no_trailing_newline() -> anyhow::Result<()> {
|
||||
("test.py", "aiter"),
|
||||
])?;
|
||||
|
||||
assert_cmd_snapshot!(case.command(), @r"
|
||||
assert_cmd_snapshot!(case.command(), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
@@ -846,7 +846,7 @@ fn pyvenv_cfg_file_annotation_no_trailing_newline() -> anyhow::Result<()> {
|
||||
--> venv/pyvenv.cfg:4:23
|
||||
|
|
||||
4 | version = 3.8
|
||||
| ^^^ Virtual environment metadata
|
||||
| ^^^ Python version inferred from virtual environment metadata file
|
||||
|
|
||||
info: No Python version was specified on the command line or in a configuration file
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
@@ -854,7 +854,7 @@ fn pyvenv_cfg_file_annotation_no_trailing_newline() -> anyhow::Result<()> {
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -898,7 +898,7 @@ fn config_file_annotation_showing_where_python_version_set_syntax_error() -> any
|
||||
|
|
||||
2 | [project]
|
||||
3 | requires-python = ">=3.8"
|
||||
| ^^^^^^^ Python version configuration
|
||||
| ^^^^^^^ Python 3.8 assumed due to this configuration setting
|
||||
|
|
||||
|
||||
Found 1 diagnostic
|
||||
@@ -1206,7 +1206,7 @@ fn defaults_to_a_new_python_version() -> anyhow::Result<()> {
|
||||
|
|
||||
2 | [environment]
|
||||
3 | python-version = "3.10"
|
||||
| ^^^^^^ Python version configuration
|
||||
| ^^^^^^ Python 3.10 assumed due to this configuration setting
|
||||
4 | python-platform = "linux"
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
@@ -1225,7 +1225,7 @@ fn defaults_to_a_new_python_version() -> anyhow::Result<()> {
|
||||
|
|
||||
2 | [environment]
|
||||
3 | python-version = "3.10"
|
||||
| ^^^^^^ Python version configuration
|
||||
| ^^^^^^ Python 3.10 assumed due to this configuration setting
|
||||
4 | python-platform = "linux"
|
||||
|
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
@@ -5,8 +5,7 @@ use ruff_diagnostics::Edit;
|
||||
use ruff_text_size::TextRange;
|
||||
use ty_project::Db;
|
||||
use ty_python_semantic::create_suppression_fix;
|
||||
use ty_python_semantic::lint::LintId;
|
||||
use ty_python_semantic::types::{UNDEFINED_REVEAL, UNRESOLVED_REFERENCE};
|
||||
use ty_python_semantic::types::UNRESOLVED_REFERENCE;
|
||||
|
||||
/// A `QuickFix` Code Action
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -29,17 +28,12 @@ pub fn code_actions(
|
||||
|
||||
let mut actions = Vec::new();
|
||||
|
||||
// Suggest imports for unresolved references (often ideal)
|
||||
// TODO: suggest qualifying with an already imported symbol
|
||||
let is_unresolved_reference =
|
||||
lint_id == LintId::of(&UNRESOLVED_REFERENCE) || lint_id == LintId::of(&UNDEFINED_REVEAL);
|
||||
if is_unresolved_reference
|
||||
if lint_id.name() == UNRESOLVED_REFERENCE.name()
|
||||
&& let Some(import_quick_fix) = create_import_symbol_quick_fix(db, file, diagnostic_range)
|
||||
{
|
||||
actions.extend(import_quick_fix);
|
||||
}
|
||||
|
||||
// Suggest just suppressing the lint (always a valid option, but never ideal)
|
||||
actions.push(QuickFix {
|
||||
title: format!("Ignore '{}' for this line", lint_id.name()),
|
||||
edits: create_suppression_fix(db, file, lint_id, diagnostic_range).into_edits(),
|
||||
|
||||
@@ -9,7 +9,6 @@ use ruff_python_ast::token::{Token, TokenAt, TokenKind, Tokens};
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
use rustc_hash::FxHashSet;
|
||||
use ty_python_semantic::types::UnionType;
|
||||
use ty_python_semantic::{
|
||||
Completion as SemanticCompletion, KnownModule, ModuleName, NameKind, SemanticModel,
|
||||
@@ -21,7 +20,7 @@ use crate::find_node::covering_node;
|
||||
use crate::goto::Definitions;
|
||||
use crate::importer::{ImportRequest, Importer};
|
||||
use crate::symbols::QueryPattern;
|
||||
use crate::{Db, all_symbols, signature_help};
|
||||
use crate::{Db, all_symbols};
|
||||
|
||||
/// A collection of completions built up from various sources.
|
||||
#[derive(Clone)]
|
||||
@@ -437,10 +436,6 @@ pub fn completion<'db>(
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(arg_completions) = detect_function_arg_completions(db, file, &parsed, offset) {
|
||||
completions.extend(arg_completions);
|
||||
}
|
||||
}
|
||||
|
||||
if is_raising_exception(tokens) {
|
||||
@@ -456,89 +451,10 @@ pub fn completion<'db>(
|
||||
!ty.is_notimplemented(db)
|
||||
});
|
||||
}
|
||||
|
||||
completions.into_completions()
|
||||
}
|
||||
|
||||
/// Detect and construct completions for unset function arguments.
|
||||
///
|
||||
/// Suggestions are only provided if the cursor is currently inside a
|
||||
/// function call and the function arguments have not 1) already been
|
||||
/// set and 2) been defined as positional-only.
|
||||
fn detect_function_arg_completions<'db>(
|
||||
db: &'db dyn Db,
|
||||
file: File,
|
||||
parsed: &ParsedModuleRef,
|
||||
offset: TextSize,
|
||||
) -> Option<Vec<Completion<'db>>> {
|
||||
let sig_help = signature_help(db, file, offset)?;
|
||||
let set_function_args = detect_set_function_args(parsed, offset);
|
||||
|
||||
let completions = sig_help
|
||||
.signatures
|
||||
.iter()
|
||||
.flat_map(|sig| &sig.parameters)
|
||||
.filter(|p| !p.is_positional_only && !set_function_args.contains(&p.name.as_str()))
|
||||
.map(|p| {
|
||||
let name = Name::new(&p.name);
|
||||
let documentation = p
|
||||
.documentation
|
||||
.as_ref()
|
||||
.map(|d| Docstring::new(d.to_owned()));
|
||||
let insert = Some(format!("{name}=").into_boxed_str());
|
||||
Completion {
|
||||
name,
|
||||
qualified: None,
|
||||
insert,
|
||||
ty: p.ty,
|
||||
kind: Some(CompletionKind::Variable),
|
||||
module_name: None,
|
||||
import: None,
|
||||
builtin: false,
|
||||
is_type_check_only: false,
|
||||
is_definitively_raisable: false,
|
||||
documentation,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
Some(completions)
|
||||
}
|
||||
|
||||
/// Returns function arguments that have already been set.
|
||||
///
|
||||
/// If `offset` is inside an arguments node, this returns
|
||||
/// the list of argument names that are already set.
|
||||
///
|
||||
/// For example, given:
|
||||
///
|
||||
/// ```python
|
||||
/// def abc(foo, bar, baz): ...
|
||||
/// abc(foo=1, bar=2, b<CURSOR>)
|
||||
/// ```
|
||||
///
|
||||
/// the resulting value is `["foo", "bar"]`
|
||||
///
|
||||
/// This is useful to be able to exclude autocomplete suggestions
|
||||
/// for arguments that have already been set to some value.
|
||||
///
|
||||
/// If the parent node is not an arguments node, the return value
|
||||
/// is an empty Vec.
|
||||
fn detect_set_function_args(parsed: &ParsedModuleRef, offset: TextSize) -> FxHashSet<&str> {
|
||||
let range = TextRange::empty(offset);
|
||||
covering_node(parsed.syntax().into(), range)
|
||||
.parent()
|
||||
.and_then(|node| match node {
|
||||
ast::AnyNodeRef::Arguments(args) => Some(args),
|
||||
_ => None,
|
||||
})
|
||||
.map(|args| {
|
||||
args.keywords
|
||||
.iter()
|
||||
.filter_map(|kw| kw.arg.as_ref().map(|ident| ident.id.as_str()))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(crate) struct ImportEdit {
|
||||
pub label: String,
|
||||
pub edit: Edit,
|
||||
@@ -2470,11 +2386,10 @@ def frob(): ...
|
||||
",
|
||||
);
|
||||
|
||||
// FIXME: Should include `foo`.
|
||||
assert_snapshot!(
|
||||
builder.skip_keywords().skip_builtins().build().snapshot(),
|
||||
@r"
|
||||
foo
|
||||
",
|
||||
@"<No completions found after filtering out completions>",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2486,11 +2401,10 @@ def frob(): ...
|
||||
",
|
||||
);
|
||||
|
||||
// FIXME: Should include `foo`.
|
||||
assert_snapshot!(
|
||||
builder.skip_keywords().skip_builtins().build().snapshot(),
|
||||
@r"
|
||||
foo
|
||||
",
|
||||
@"<No completions found after filtering out completions>",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3125,6 +3039,7 @@ quux.<CURSOR>
|
||||
");
|
||||
}
|
||||
|
||||
// We don't yet take function parameters into account.
|
||||
#[test]
|
||||
fn call_prefix1() {
|
||||
let builder = completion_test_builder(
|
||||
@@ -3137,157 +3052,7 @@ bar(o<CURSOR>
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(
|
||||
builder.skip_keywords().skip_builtins().build().snapshot(),
|
||||
@r"
|
||||
foo
|
||||
okay
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_keyword_only_argument() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
def bar(*, okay): ...
|
||||
|
||||
foo = 1
|
||||
|
||||
bar(o<CURSOR>
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(
|
||||
builder.skip_keywords().skip_builtins().build().snapshot(),
|
||||
@r"
|
||||
foo
|
||||
okay
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_multiple_keyword_arguments() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
def foo(bar, baz, barbaz): ...
|
||||
|
||||
foo(b<CURSOR>
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(
|
||||
builder.skip_keywords().skip_builtins().build().snapshot(),
|
||||
@r"
|
||||
bar
|
||||
barbaz
|
||||
baz
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_multiple_keyword_arguments_some_set() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
def foo(bar, baz): ...
|
||||
|
||||
foo(bar=1, b<CURSOR>
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(
|
||||
builder.skip_keywords().skip_builtins().build().snapshot(),
|
||||
@r"
|
||||
baz
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_arguments_multi_def() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
def abc(okay, x): ...
|
||||
def bar(not_okay, y): ...
|
||||
def baz(foobarbaz, z): ...
|
||||
|
||||
abc(o<CURSOR>
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(
|
||||
builder.skip_keywords().skip_builtins().build().snapshot(),
|
||||
@r"
|
||||
okay
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_arguments_cursor_middle() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
def abc(okay, foo, bar, baz): ...
|
||||
|
||||
abc(okay=1, ba<CURSOR> baz=5
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(
|
||||
builder.skip_keywords().skip_builtins().build().snapshot(),
|
||||
@r"
|
||||
bar
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_positional_only_argument() {
|
||||
// If the parameter is positional only we don't
|
||||
// want to suggest it as specifying by name
|
||||
// is not valid.
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
def bar(okay, /): ...
|
||||
|
||||
foo = 1
|
||||
|
||||
bar(o<CURSOR>
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(
|
||||
builder.skip_keywords().skip_builtins().build().snapshot(),
|
||||
@"foo"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_positional_only_keyword_only_argument_mix() {
|
||||
// If the parameter is positional only we don't
|
||||
// want to suggest it as specifying by name
|
||||
// is not valid.
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
def bar(not_okay, no, /, okay, *, okay_abc, okay_okay): ...
|
||||
|
||||
foo = 1
|
||||
|
||||
bar(o<CURSOR>
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(
|
||||
builder.skip_keywords().skip_builtins().build().snapshot(),
|
||||
@r"
|
||||
foo
|
||||
okay
|
||||
okay_abc
|
||||
okay_okay
|
||||
"
|
||||
);
|
||||
assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3305,7 +3070,6 @@ bar(<CURSOR>
|
||||
assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @r"
|
||||
bar
|
||||
foo
|
||||
okay
|
||||
");
|
||||
}
|
||||
|
||||
|
||||
@@ -19,22 +19,11 @@ pub struct InlayHint {
|
||||
}
|
||||
|
||||
impl InlayHint {
|
||||
fn variable_type(
|
||||
expr: &Expr,
|
||||
rhs: &Expr,
|
||||
ty: Type,
|
||||
db: &dyn Db,
|
||||
allow_edits: bool,
|
||||
) -> Option<Self> {
|
||||
fn variable_type(expr: &Expr, ty: Type, db: &dyn Db, allow_edits: bool) -> Self {
|
||||
let position = expr.range().end();
|
||||
// Render the type to a string, and get subspans for all the types that make it up
|
||||
let details = ty.display(db).to_string_parts();
|
||||
|
||||
// Filter out a reptitive hints like `x: T = T()`
|
||||
if call_matches_name(rhs, &details.label) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Ok so the idea here is that we potentially have a random soup of spans here,
|
||||
// and each byte of the string can have at most one target associate with it.
|
||||
// Thankfully, they were generally pushed in print order, with the inner smaller types
|
||||
@@ -84,12 +73,12 @@ impl InlayHint {
|
||||
vec![]
|
||||
};
|
||||
|
||||
Some(Self {
|
||||
Self {
|
||||
position,
|
||||
kind: InlayHintKind::Type,
|
||||
label: InlayHintLabel { parts: label_parts },
|
||||
text_edits,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn call_argument_name(
|
||||
@@ -261,7 +250,7 @@ struct InlayHintVisitor<'a, 'db> {
|
||||
db: &'db dyn Db,
|
||||
model: SemanticModel<'db>,
|
||||
hints: Vec<InlayHint>,
|
||||
assignment_rhs: Option<&'a Expr>,
|
||||
in_assignment: bool,
|
||||
range: TextRange,
|
||||
settings: &'a InlayHintSettings,
|
||||
in_no_edits_allowed: bool,
|
||||
@@ -273,21 +262,21 @@ impl<'a, 'db> InlayHintVisitor<'a, 'db> {
|
||||
db,
|
||||
model: SemanticModel::new(db, file),
|
||||
hints: Vec::new(),
|
||||
assignment_rhs: None,
|
||||
in_assignment: false,
|
||||
range,
|
||||
settings,
|
||||
in_no_edits_allowed: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn add_type_hint(&mut self, expr: &Expr, rhs: &Expr, ty: Type<'db>, allow_edits: bool) {
|
||||
fn add_type_hint(&mut self, expr: &Expr, ty: Type<'db>, allow_edits: bool) {
|
||||
if !self.settings.variable_types {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(inlay_hint) = InlayHint::variable_type(expr, rhs, ty, self.db, allow_edits) {
|
||||
self.hints.push(inlay_hint);
|
||||
}
|
||||
let inlay_hint = InlayHint::variable_type(expr, ty, self.db, allow_edits);
|
||||
|
||||
self.hints.push(inlay_hint);
|
||||
}
|
||||
|
||||
fn add_call_argument_name(
|
||||
@@ -310,8 +299,8 @@ impl<'a, 'db> InlayHintVisitor<'a, 'db> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SourceOrderVisitor<'a> for InlayHintVisitor<'a, '_> {
|
||||
fn enter_node(&mut self, node: AnyNodeRef<'a>) -> TraversalSignal {
|
||||
impl SourceOrderVisitor<'_> for InlayHintVisitor<'_, '_> {
|
||||
fn enter_node(&mut self, node: AnyNodeRef<'_>) -> TraversalSignal {
|
||||
if self.range.intersect(node.range()).is_some() {
|
||||
TraversalSignal::Traverse
|
||||
} else {
|
||||
@@ -319,7 +308,7 @@ impl<'a> SourceOrderVisitor<'a> for InlayHintVisitor<'a, '_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_stmt(&mut self, stmt: &'a Stmt) {
|
||||
fn visit_stmt(&mut self, stmt: &Stmt) {
|
||||
let node = AnyNodeRef::from(stmt);
|
||||
|
||||
if !self.enter_node(node).is_traverse() {
|
||||
@@ -328,9 +317,7 @@ impl<'a> SourceOrderVisitor<'a> for InlayHintVisitor<'a, '_> {
|
||||
|
||||
match stmt {
|
||||
Stmt::Assign(assign) => {
|
||||
if !type_hint_is_excessive_for_expr(&assign.value) {
|
||||
self.assignment_rhs = Some(&*assign.value);
|
||||
}
|
||||
self.in_assignment = !type_hint_is_excessive_for_expr(&assign.value);
|
||||
if !annotations_are_valid_syntax(assign) {
|
||||
self.in_no_edits_allowed = true;
|
||||
}
|
||||
@@ -338,7 +325,7 @@ impl<'a> SourceOrderVisitor<'a> for InlayHintVisitor<'a, '_> {
|
||||
self.visit_expr(target);
|
||||
}
|
||||
self.in_no_edits_allowed = false;
|
||||
self.assignment_rhs = None;
|
||||
self.in_assignment = false;
|
||||
|
||||
self.visit_expr(&assign.value);
|
||||
|
||||
@@ -357,22 +344,22 @@ impl<'a> SourceOrderVisitor<'a> for InlayHintVisitor<'a, '_> {
|
||||
source_order::walk_stmt(self, stmt);
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'a Expr) {
|
||||
fn visit_expr(&mut self, expr: &'_ Expr) {
|
||||
match expr {
|
||||
Expr::Name(name) => {
|
||||
if let Some(rhs) = self.assignment_rhs {
|
||||
if self.in_assignment {
|
||||
if name.ctx.is_store() {
|
||||
let ty = expr.inferred_type(&self.model);
|
||||
self.add_type_hint(expr, rhs, ty, !self.in_no_edits_allowed);
|
||||
self.add_type_hint(expr, ty, !self.in_no_edits_allowed);
|
||||
}
|
||||
}
|
||||
source_order::walk_expr(self, expr);
|
||||
}
|
||||
Expr::Attribute(attribute) => {
|
||||
if let Some(rhs) = self.assignment_rhs {
|
||||
if self.in_assignment {
|
||||
if attribute.ctx.is_store() {
|
||||
let ty = expr.inferred_type(&self.model);
|
||||
self.add_type_hint(expr, rhs, ty, !self.in_no_edits_allowed);
|
||||
self.add_type_hint(expr, ty, !self.in_no_edits_allowed);
|
||||
}
|
||||
}
|
||||
source_order::walk_expr(self, expr);
|
||||
@@ -429,26 +416,6 @@ fn arg_matches_name(arg_or_keyword: &ArgOrKeyword, name: &str) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a function call, check if the expression is the "same name"
|
||||
/// as the function being called.
|
||||
///
|
||||
/// This allows us to filter out reptitive inlay hints like `x: T = T(...)`.
|
||||
/// While still allowing non-trivial ones like `x: T[U] = T()`.
|
||||
fn call_matches_name(expr: &Expr, name: &str) -> bool {
|
||||
// Only care about function calls
|
||||
let Expr::Call(call) = expr else {
|
||||
return false;
|
||||
};
|
||||
|
||||
match &*call.func {
|
||||
// `x: T = T()` is a match
|
||||
Expr::Name(expr_name) => expr_name.id.as_str() == name,
|
||||
// `x: T = a.T()` is a match
|
||||
Expr::Attribute(expr_attribute) => expr_attribute.attr.as_str() == name,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Given an expression that's the RHS of an assignment, would it be excessive to
|
||||
/// emit an inlay type hint for the variable assigned to it?
|
||||
///
|
||||
@@ -1862,16 +1829,35 @@ mod tests {
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r"
|
||||
assert_snapshot!(test.inlay_hints(), @r#"
|
||||
class A:
|
||||
def __init__(self, y):
|
||||
self.x = int(1)
|
||||
self.x[: int] = int(1)
|
||||
self.y[: Unknown] = y
|
||||
|
||||
a = A([y=]2)
|
||||
a.y = int(3)
|
||||
a[: A] = A([y=]2)
|
||||
a.y[: int] = int(3)
|
||||
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:348:7
|
||||
|
|
||||
347 | @disjoint_base
|
||||
348 | class int:
|
||||
| ^^^
|
||||
349 | """int([x]) -> integer
|
||||
350 | int(x, base=10) -> integer
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:18
|
||||
|
|
||||
2 | class A:
|
||||
3 | def __init__(self, y):
|
||||
4 | self.x[: int] = int(1)
|
||||
| ^^^
|
||||
5 | self.y[: Unknown] = y
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/ty_extensions.pyi:20:1
|
||||
|
|
||||
@@ -1885,11 +1871,29 @@ mod tests {
|
||||
--> main2.py:5:18
|
||||
|
|
||||
3 | def __init__(self, y):
|
||||
4 | self.x = int(1)
|
||||
4 | self.x[: int] = int(1)
|
||||
5 | self.y[: Unknown] = y
|
||||
| ^^^^^^^
|
||||
6 |
|
||||
7 | a = A([y=]2)
|
||||
7 | a[: A] = A([y=]2)
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:2:7
|
||||
|
|
||||
2 | class A:
|
||||
| ^
|
||||
3 | def __init__(self, y):
|
||||
4 | self.x = int(1)
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:7:5
|
||||
|
|
||||
5 | self.y[: Unknown] = y
|
||||
6 |
|
||||
7 | a[: A] = A([y=]2)
|
||||
| ^
|
||||
8 | a.y[: int] = int(3)
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
@@ -1902,13 +1906,30 @@ mod tests {
|
||||
5 | self.y = y
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:7:8
|
||||
--> main2.py:7:13
|
||||
|
|
||||
5 | self.y[: Unknown] = y
|
||||
6 |
|
||||
7 | a = A([y=]2)
|
||||
| ^
|
||||
8 | a.y = int(3)
|
||||
7 | a[: A] = A([y=]2)
|
||||
| ^
|
||||
8 | a.y[: int] = int(3)
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:348:7
|
||||
|
|
||||
347 | @disjoint_base
|
||||
348 | class int:
|
||||
| ^^^
|
||||
349 | """int([x]) -> integer
|
||||
350 | int(x, base=10) -> integer
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:8:7
|
||||
|
|
||||
7 | a[: A] = A([y=]2)
|
||||
8 | a.y[: int] = int(3)
|
||||
| ^^^
|
||||
|
|
||||
|
||||
---------------------------------------------
|
||||
@@ -1917,12 +1938,12 @@ mod tests {
|
||||
|
||||
class A:
|
||||
def __init__(self, y):
|
||||
self.x = int(1)
|
||||
self.x: int = int(1)
|
||||
self.y: Unknown = y
|
||||
|
||||
a = A(2)
|
||||
a.y = int(3)
|
||||
");
|
||||
a: A = A(2)
|
||||
a.y: int = int(3)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2916,12 +2937,31 @@ mod tests {
|
||||
def __init__(self):
|
||||
self.x: int = 1
|
||||
|
||||
x = MyClass()
|
||||
x[: MyClass] = MyClass()
|
||||
y[: tuple[MyClass, MyClass]] = (MyClass(), MyClass())
|
||||
a[: MyClass], b[: MyClass] = MyClass(), MyClass()
|
||||
c[: MyClass], d[: MyClass] = (MyClass(), MyClass())
|
||||
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:2:7
|
||||
|
|
||||
2 | class MyClass:
|
||||
| ^^^^^^^
|
||||
3 | def __init__(self):
|
||||
4 | self.x: int = 1
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:6:5
|
||||
|
|
||||
4 | self.x: int = 1
|
||||
5 |
|
||||
6 | x[: MyClass] = MyClass()
|
||||
| ^^^^^^^
|
||||
7 | y[: tuple[MyClass, MyClass]] = (MyClass(), MyClass())
|
||||
8 | a[: MyClass], b[: MyClass] = MyClass(), MyClass()
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2695:7
|
||||
|
|
||||
@@ -2933,7 +2973,7 @@ mod tests {
|
||||
info: Source
|
||||
--> main2.py:7:5
|
||||
|
|
||||
6 | x = MyClass()
|
||||
6 | x[: MyClass] = MyClass()
|
||||
7 | y[: tuple[MyClass, MyClass]] = (MyClass(), MyClass())
|
||||
| ^^^^^
|
||||
8 | a[: MyClass], b[: MyClass] = MyClass(), MyClass()
|
||||
@@ -2951,7 +2991,7 @@ mod tests {
|
||||
info: Source
|
||||
--> main2.py:7:11
|
||||
|
|
||||
6 | x = MyClass()
|
||||
6 | x[: MyClass] = MyClass()
|
||||
7 | y[: tuple[MyClass, MyClass]] = (MyClass(), MyClass())
|
||||
| ^^^^^^^
|
||||
8 | a[: MyClass], b[: MyClass] = MyClass(), MyClass()
|
||||
@@ -2969,7 +3009,7 @@ mod tests {
|
||||
info: Source
|
||||
--> main2.py:7:20
|
||||
|
|
||||
6 | x = MyClass()
|
||||
6 | x[: MyClass] = MyClass()
|
||||
7 | y[: tuple[MyClass, MyClass]] = (MyClass(), MyClass())
|
||||
| ^^^^^^^
|
||||
8 | a[: MyClass], b[: MyClass] = MyClass(), MyClass()
|
||||
@@ -2987,7 +3027,7 @@ mod tests {
|
||||
info: Source
|
||||
--> main2.py:8:5
|
||||
|
|
||||
6 | x = MyClass()
|
||||
6 | x[: MyClass] = MyClass()
|
||||
7 | y[: tuple[MyClass, MyClass]] = (MyClass(), MyClass())
|
||||
8 | a[: MyClass], b[: MyClass] = MyClass(), MyClass()
|
||||
| ^^^^^^^
|
||||
@@ -3005,7 +3045,7 @@ mod tests {
|
||||
info: Source
|
||||
--> main2.py:8:19
|
||||
|
|
||||
6 | x = MyClass()
|
||||
6 | x[: MyClass] = MyClass()
|
||||
7 | y[: tuple[MyClass, MyClass]] = (MyClass(), MyClass())
|
||||
8 | a[: MyClass], b[: MyClass] = MyClass(), MyClass()
|
||||
| ^^^^^^^
|
||||
@@ -3054,7 +3094,7 @@ mod tests {
|
||||
def __init__(self):
|
||||
self.x: int = 1
|
||||
|
||||
x = MyClass()
|
||||
x: MyClass = MyClass()
|
||||
y: tuple[MyClass, MyClass] = (MyClass(), MyClass())
|
||||
a, b = MyClass(), MyClass()
|
||||
c, d = (MyClass(), MyClass())
|
||||
@@ -4057,11 +4097,31 @@ mod tests {
|
||||
def __init__(self):
|
||||
self.x: int = 1
|
||||
self.y: int = 2
|
||||
val = MyClass()
|
||||
val[: MyClass] = MyClass()
|
||||
|
||||
foo(val.x)
|
||||
foo([x=]val.y)
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:3:7
|
||||
|
|
||||
2 | def foo(x: int): pass
|
||||
3 | class MyClass:
|
||||
| ^^^^^^^
|
||||
4 | def __init__(self):
|
||||
5 | self.x: int = 1
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:7:7
|
||||
|
|
||||
5 | self.x: int = 1
|
||||
6 | self.y: int = 2
|
||||
7 | val[: MyClass] = MyClass()
|
||||
| ^^^^^^^
|
||||
8 |
|
||||
9 | foo(val.x)
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:2:9
|
||||
|
|
||||
@@ -4077,6 +4137,20 @@ mod tests {
|
||||
10 | foo([x=]val.y)
|
||||
| ^
|
||||
|
|
||||
|
||||
---------------------------------------------
|
||||
info[inlay-hint-edit]: File after edits
|
||||
info: Source
|
||||
|
||||
def foo(x: int): pass
|
||||
class MyClass:
|
||||
def __init__(self):
|
||||
self.x: int = 1
|
||||
self.y: int = 2
|
||||
val: MyClass = MyClass()
|
||||
|
||||
foo(val.x)
|
||||
foo(val.y)
|
||||
");
|
||||
}
|
||||
|
||||
@@ -4102,11 +4176,31 @@ mod tests {
|
||||
def __init__(self):
|
||||
self.x: int = 1
|
||||
self.y: int = 2
|
||||
x = MyClass()
|
||||
x[: MyClass] = MyClass()
|
||||
|
||||
foo(x.x)
|
||||
foo([x=]x.y)
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:3:7
|
||||
|
|
||||
2 | def foo(x: int): pass
|
||||
3 | class MyClass:
|
||||
| ^^^^^^^
|
||||
4 | def __init__(self):
|
||||
5 | self.x: int = 1
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:7:5
|
||||
|
|
||||
5 | self.x: int = 1
|
||||
6 | self.y: int = 2
|
||||
7 | x[: MyClass] = MyClass()
|
||||
| ^^^^^^^
|
||||
8 |
|
||||
9 | foo(x.x)
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:2:9
|
||||
|
|
||||
@@ -4122,6 +4216,20 @@ mod tests {
|
||||
10 | foo([x=]x.y)
|
||||
| ^
|
||||
|
|
||||
|
||||
---------------------------------------------
|
||||
info[inlay-hint-edit]: File after edits
|
||||
info: Source
|
||||
|
||||
def foo(x: int): pass
|
||||
class MyClass:
|
||||
def __init__(self):
|
||||
self.x: int = 1
|
||||
self.y: int = 2
|
||||
x: MyClass = MyClass()
|
||||
|
||||
foo(x.x)
|
||||
foo(x.y)
|
||||
");
|
||||
}
|
||||
|
||||
@@ -4150,11 +4258,31 @@ mod tests {
|
||||
return 1
|
||||
def y() -> int:
|
||||
return 2
|
||||
val = MyClass()
|
||||
val[: MyClass] = MyClass()
|
||||
|
||||
foo(val.x())
|
||||
foo([x=]val.y())
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:3:7
|
||||
|
|
||||
2 | def foo(x: int): pass
|
||||
3 | class MyClass:
|
||||
| ^^^^^^^
|
||||
4 | def __init__(self):
|
||||
5 | def x() -> int:
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:9:7
|
||||
|
|
||||
7 | def y() -> int:
|
||||
8 | return 2
|
||||
9 | val[: MyClass] = MyClass()
|
||||
| ^^^^^^^
|
||||
10 |
|
||||
11 | foo(val.x())
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:2:9
|
||||
|
|
||||
@@ -4170,6 +4298,22 @@ mod tests {
|
||||
12 | foo([x=]val.y())
|
||||
| ^
|
||||
|
|
||||
|
||||
---------------------------------------------
|
||||
info[inlay-hint-edit]: File after edits
|
||||
info: Source
|
||||
|
||||
def foo(x: int): pass
|
||||
class MyClass:
|
||||
def __init__(self):
|
||||
def x() -> int:
|
||||
return 1
|
||||
def y() -> int:
|
||||
return 2
|
||||
val: MyClass = MyClass()
|
||||
|
||||
foo(val.x())
|
||||
foo(val.y())
|
||||
");
|
||||
}
|
||||
|
||||
@@ -4202,11 +4346,31 @@ mod tests {
|
||||
return 1
|
||||
def y() -> List[int]:
|
||||
return 2
|
||||
val = MyClass()
|
||||
val[: MyClass] = MyClass()
|
||||
|
||||
foo(val.x()[0])
|
||||
foo([x=]val.y()[1])
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:5:7
|
||||
|
|
||||
4 | def foo(x: int): pass
|
||||
5 | class MyClass:
|
||||
| ^^^^^^^
|
||||
6 | def __init__(self):
|
||||
7 | def x() -> List[int]:
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:11:7
|
||||
|
|
||||
9 | def y() -> List[int]:
|
||||
10 | return 2
|
||||
11 | val[: MyClass] = MyClass()
|
||||
| ^^^^^^^
|
||||
12 |
|
||||
13 | foo(val.x()[0])
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:4:9
|
||||
|
|
||||
@@ -4224,6 +4388,24 @@ mod tests {
|
||||
14 | foo([x=]val.y()[1])
|
||||
| ^
|
||||
|
|
||||
|
||||
---------------------------------------------
|
||||
info[inlay-hint-edit]: File after edits
|
||||
info: Source
|
||||
|
||||
from typing import List
|
||||
|
||||
def foo(x: int): pass
|
||||
class MyClass:
|
||||
def __init__(self):
|
||||
def x() -> List[int]:
|
||||
return 1
|
||||
def y() -> List[int]:
|
||||
return 2
|
||||
val: MyClass = MyClass()
|
||||
|
||||
foo(val.x()[0])
|
||||
foo(val.y()[1])
|
||||
");
|
||||
}
|
||||
|
||||
@@ -4515,7 +4697,7 @@ mod tests {
|
||||
class Foo:
|
||||
def __init__(self, x: int): pass
|
||||
Foo([x=]1)
|
||||
f = Foo([x=]1)
|
||||
f[: Foo] = Foo([x=]1)
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:3:24
|
||||
@@ -4533,7 +4715,24 @@ mod tests {
|
||||
3 | def __init__(self, x: int): pass
|
||||
4 | Foo([x=]1)
|
||||
| ^
|
||||
5 | f = Foo([x=]1)
|
||||
5 | f[: Foo] = Foo([x=]1)
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:2:7
|
||||
|
|
||||
2 | class Foo:
|
||||
| ^^^
|
||||
3 | def __init__(self, x: int): pass
|
||||
4 | Foo(1)
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:5:5
|
||||
|
|
||||
3 | def __init__(self, x: int): pass
|
||||
4 | Foo([x=]1)
|
||||
5 | f[: Foo] = Foo([x=]1)
|
||||
| ^^^
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
@@ -4546,13 +4745,22 @@ mod tests {
|
||||
5 | f = Foo(1)
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:5:10
|
||||
--> main2.py:5:17
|
||||
|
|
||||
3 | def __init__(self, x: int): pass
|
||||
4 | Foo([x=]1)
|
||||
5 | f = Foo([x=]1)
|
||||
| ^
|
||||
5 | f[: Foo] = Foo([x=]1)
|
||||
| ^
|
||||
|
|
||||
|
||||
---------------------------------------------
|
||||
info[inlay-hint-edit]: File after edits
|
||||
info: Source
|
||||
|
||||
class Foo:
|
||||
def __init__(self, x: int): pass
|
||||
Foo(1)
|
||||
f: Foo = Foo(1)
|
||||
");
|
||||
}
|
||||
|
||||
@@ -4570,7 +4778,7 @@ mod tests {
|
||||
class Foo:
|
||||
def __new__(cls, x: int): pass
|
||||
Foo([x=]1)
|
||||
f = Foo([x=]1)
|
||||
f[: Foo] = Foo([x=]1)
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:3:22
|
||||
@@ -4588,7 +4796,24 @@ mod tests {
|
||||
3 | def __new__(cls, x: int): pass
|
||||
4 | Foo([x=]1)
|
||||
| ^
|
||||
5 | f = Foo([x=]1)
|
||||
5 | f[: Foo] = Foo([x=]1)
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:2:7
|
||||
|
|
||||
2 | class Foo:
|
||||
| ^^^
|
||||
3 | def __new__(cls, x: int): pass
|
||||
4 | Foo(1)
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:5:5
|
||||
|
|
||||
3 | def __new__(cls, x: int): pass
|
||||
4 | Foo([x=]1)
|
||||
5 | f[: Foo] = Foo([x=]1)
|
||||
| ^^^
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
@@ -4601,13 +4826,22 @@ mod tests {
|
||||
5 | f = Foo(1)
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:5:10
|
||||
--> main2.py:5:17
|
||||
|
|
||||
3 | def __new__(cls, x: int): pass
|
||||
4 | Foo([x=]1)
|
||||
5 | f = Foo([x=]1)
|
||||
| ^
|
||||
5 | f[: Foo] = Foo([x=]1)
|
||||
| ^
|
||||
|
|
||||
|
||||
---------------------------------------------
|
||||
info[inlay-hint-edit]: File after edits
|
||||
info: Source
|
||||
|
||||
class Foo:
|
||||
def __new__(cls, x: int): pass
|
||||
Foo(1)
|
||||
f: Foo = Foo(1)
|
||||
");
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ use ty_python_semantic::semantic_index::definition::Definition;
|
||||
use ty_python_semantic::types::ide_support::{
|
||||
CallSignatureDetails, call_signature_details, find_active_signature_from_details,
|
||||
};
|
||||
use ty_python_semantic::types::{ParameterKind, Type};
|
||||
|
||||
// TODO: We may want to add special-case handling for calls to constructors
|
||||
// so the class docstring is used in place of (or inaddition to) any docstring
|
||||
@@ -28,29 +27,25 @@ use ty_python_semantic::types::{ParameterKind, Type};
|
||||
|
||||
/// Information about a function parameter
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ParameterDetails<'db> {
|
||||
pub struct ParameterDetails {
|
||||
/// The parameter name (e.g., "param1")
|
||||
pub name: String,
|
||||
/// The parameter label in the signature (e.g., "param1: str")
|
||||
pub label: String,
|
||||
/// The annotated type of the parameter, if any
|
||||
pub ty: Option<Type<'db>>,
|
||||
/// Documentation specific to the parameter, typically extracted from the
|
||||
/// function's docstring
|
||||
pub documentation: Option<String>,
|
||||
/// True if the parameter is positional-only.
|
||||
pub is_positional_only: bool,
|
||||
}
|
||||
|
||||
/// Information about a function signature
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SignatureDetails<'db> {
|
||||
pub struct SignatureDetails {
|
||||
/// Text representation of the full signature (including input parameters and return type).
|
||||
pub label: String,
|
||||
/// Documentation for the signature, typically from the function's docstring.
|
||||
pub documentation: Option<Docstring>,
|
||||
/// Information about each of the parameters in left-to-right order.
|
||||
pub parameters: Vec<ParameterDetails<'db>>,
|
||||
pub parameters: Vec<ParameterDetails>,
|
||||
/// Index of the parameter that corresponds to the argument where the
|
||||
/// user's cursor is currently positioned.
|
||||
pub active_parameter: Option<usize>,
|
||||
@@ -58,18 +53,18 @@ pub struct SignatureDetails<'db> {
|
||||
|
||||
/// Signature help information for function calls
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SignatureHelpInfo<'db> {
|
||||
pub struct SignatureHelpInfo {
|
||||
/// Information about each of the signatures for the function call. We
|
||||
/// need to handle multiple because of unions, overloads, and composite
|
||||
/// calls like constructors (which invoke both __new__ and __init__).
|
||||
pub signatures: Vec<SignatureDetails<'db>>,
|
||||
pub signatures: Vec<SignatureDetails>,
|
||||
/// Index of the "active signature" which is the first signature where
|
||||
/// all arguments that are currently present in the code map to parameters.
|
||||
pub active_signature: Option<usize>,
|
||||
}
|
||||
|
||||
/// Signature help information for function calls at the given position
|
||||
pub fn signature_help(db: &dyn Db, file: File, offset: TextSize) -> Option<SignatureHelpInfo<'_>> {
|
||||
pub fn signature_help(db: &dyn Db, file: File, offset: TextSize) -> Option<SignatureHelpInfo> {
|
||||
let parsed = parsed_module(db, file).load(db);
|
||||
|
||||
// Get the call expression at the given position.
|
||||
@@ -171,11 +166,11 @@ fn get_argument_index(call_expr: &ast::ExprCall, offset: TextSize) -> usize {
|
||||
}
|
||||
|
||||
/// Create signature details from `CallSignatureDetails`.
|
||||
fn create_signature_details_from_call_signature_details<'db>(
|
||||
fn create_signature_details_from_call_signature_details(
|
||||
db: &dyn crate::Db,
|
||||
details: &CallSignatureDetails<'db>,
|
||||
details: &CallSignatureDetails,
|
||||
current_arg_index: usize,
|
||||
) -> SignatureDetails<'db> {
|
||||
) -> SignatureDetails {
|
||||
let signature_label = details.label.clone();
|
||||
|
||||
let documentation = get_callable_documentation(db, details.definition);
|
||||
@@ -205,8 +200,6 @@ fn create_signature_details_from_call_signature_details<'db>(
|
||||
&signature_label,
|
||||
documentation.as_ref(),
|
||||
&details.parameter_names,
|
||||
&details.parameter_kinds,
|
||||
&details.parameter_types,
|
||||
);
|
||||
SignatureDetails {
|
||||
label: signature_label,
|
||||
@@ -225,14 +218,12 @@ fn get_callable_documentation(
|
||||
}
|
||||
|
||||
/// Create `ParameterDetails` objects from parameter label offsets.
|
||||
fn create_parameters_from_offsets<'db>(
|
||||
fn create_parameters_from_offsets(
|
||||
parameter_offsets: &[TextRange],
|
||||
signature_label: &str,
|
||||
docstring: Option<&Docstring>,
|
||||
parameter_names: &[String],
|
||||
parameter_kinds: &[ParameterKind],
|
||||
parameter_types: &[Option<Type<'db>>],
|
||||
) -> Vec<ParameterDetails<'db>> {
|
||||
) -> Vec<ParameterDetails> {
|
||||
// Extract parameter documentation from the function's docstring if available.
|
||||
let param_docs = if let Some(docstring) = docstring {
|
||||
docstring.parameter_documentation()
|
||||
@@ -254,18 +245,11 @@ fn create_parameters_from_offsets<'db>(
|
||||
|
||||
// Get the parameter name for documentation lookup.
|
||||
let param_name = parameter_names.get(i).map(String::as_str).unwrap_or("");
|
||||
let is_positional_only = matches!(
|
||||
parameter_kinds.get(i),
|
||||
Some(ParameterKind::PositionalOnly { .. })
|
||||
);
|
||||
let ty = parameter_types.get(i).copied().flatten();
|
||||
|
||||
ParameterDetails {
|
||||
name: param_name.to_string(),
|
||||
label,
|
||||
ty,
|
||||
documentation: param_docs.get(param_name).cloned(),
|
||||
is_positional_only,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
@@ -1189,7 +1173,7 @@ def ab(a: int, *, c: int):
|
||||
}
|
||||
|
||||
impl CursorTest {
|
||||
fn signature_help(&self) -> Option<SignatureHelpInfo<'_>> {
|
||||
fn signature_help(&self) -> Option<SignatureHelpInfo> {
|
||||
crate::signature_help::signature_help(&self.db, self.cursor.file, self.cursor.offset)
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,24 @@ async def main():
|
||||
reveal_type(b) # revealed: int
|
||||
```
|
||||
|
||||
### `asynccontextmanager`
|
||||
|
||||
```py
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import AsyncGenerator
|
||||
|
||||
class Session: ...
|
||||
|
||||
@asynccontextmanager
|
||||
async def connect() -> AsyncGenerator[Session]:
|
||||
yield Session()
|
||||
|
||||
async def main():
|
||||
async with connect() as session:
|
||||
# TODO: should be `Session`
|
||||
reveal_type(session) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Under the hood
|
||||
|
||||
```toml
|
||||
|
||||
@@ -9,9 +9,9 @@ python-platform = "linux"
|
||||
dependencies = ["SQLAlchemy==2.0.44"]
|
||||
```
|
||||
|
||||
## ORM Model
|
||||
## Basic model
|
||||
|
||||
This test makes sure that ty understands SQLAlchemy's `dataclass_transform` setup:
|
||||
Here, we mostly make sure that ty understands SQLAlchemy's dataclass-transformer setup:
|
||||
|
||||
```py
|
||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
||||
@@ -40,12 +40,14 @@ reveal_type(User.__init__) # revealed: def __init__(self, **kw: Any) -> Unknown
|
||||
invalid_user = User(invalid_arg=42)
|
||||
```
|
||||
|
||||
## Basic query example
|
||||
## Queries
|
||||
|
||||
First, set up a `Session`:
|
||||
First, the basic setup:
|
||||
|
||||
```py
|
||||
from sqlalchemy import select, Integer, Text, Boolean
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import select, Integer, Text, Boolean, DateTime
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.orm import DeclarativeBase
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
@@ -55,7 +57,7 @@ engine = create_engine("sqlite://example.db")
|
||||
session = Session(engine)
|
||||
```
|
||||
|
||||
And define a simple model:
|
||||
Now we can declare a simple model:
|
||||
|
||||
```py
|
||||
class Base(DeclarativeBase):
|
||||
@@ -69,7 +71,7 @@ class User(Base):
|
||||
is_admin: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
```
|
||||
|
||||
Finally, we can execute queries:
|
||||
And perform simple queries:
|
||||
|
||||
```py
|
||||
stmt = select(User)
|
||||
@@ -82,118 +84,41 @@ for row in session.execute(stmt):
|
||||
reveal_type(row) # revealed: Row[tuple[User]]
|
||||
|
||||
stmt = select(User).where(User.name == "Alice")
|
||||
alice1 = session.scalars(stmt).first()
|
||||
reveal_type(alice1) # revealed: User | None
|
||||
alice = session.scalars(stmt).first()
|
||||
reveal_type(alice) # revealed: User | None
|
||||
|
||||
alice2 = session.scalar(stmt)
|
||||
reveal_type(alice2) # revealed: User | None
|
||||
|
||||
result = session.execute(stmt)
|
||||
row = result.one_or_none()
|
||||
assert row is not None
|
||||
(alice3,) = row._tuple()
|
||||
reveal_type(alice3) # revealed: User
|
||||
```
|
||||
|
||||
This also works with more complex queries:
|
||||
|
||||
```py
|
||||
stmt = select(User).where(User.is_admin == True).order_by(User.name).limit(10)
|
||||
admin_users = session.scalars(stmt).all()
|
||||
reveal_type(admin_users) # revealed: Sequence[User]
|
||||
```
|
||||
|
||||
We can also specify particular columns to select:
|
||||
|
||||
```py
|
||||
reveal_type(User.id) # revealed: InstrumentedAttribute[int]
|
||||
stmt = select(User.id, User.name)
|
||||
reveal_type(stmt) # revealed: Select[tuple[int, str]]
|
||||
|
||||
ids_and_names = session.execute(stmt).all()
|
||||
reveal_type(ids_and_names) # revealed: Sequence[Row[tuple[int, str]]]
|
||||
|
||||
for row in session.execute(stmt):
|
||||
reveal_type(row) # revealed: Row[tuple[int, str]]
|
||||
|
||||
for user_id, name in session.execute(stmt).tuples():
|
||||
reveal_type(user_id) # revealed: int
|
||||
reveal_type(name) # revealed: str
|
||||
|
||||
result = session.execute(stmt)
|
||||
row = result.one_or_none()
|
||||
assert row is not None
|
||||
(user_id, name) = row._tuple()
|
||||
reveal_type(user_id) # revealed: int
|
||||
reveal_type(name) # revealed: str
|
||||
|
||||
stmt = select(User.id).where(User.name == "Alice")
|
||||
|
||||
reveal_type(stmt) # revealed: Select[tuple[int]]
|
||||
|
||||
alice_id = session.scalars(stmt).first()
|
||||
reveal_type(alice_id) # revealed: int | None
|
||||
|
||||
alice_id = session.scalar(stmt)
|
||||
reveal_type(alice_id) # revealed: int | None
|
||||
```
|
||||
|
||||
Using the legacy `query` API also works:
|
||||
This also works with the legacy `query` API:
|
||||
|
||||
```py
|
||||
users_legacy = session.query(User).all()
|
||||
reveal_type(users_legacy) # revealed: list[User]
|
||||
|
||||
query = session.query(User)
|
||||
reveal_type(query) # revealed: Query[User]
|
||||
|
||||
reveal_type(query.all()) # revealed: list[User]
|
||||
|
||||
for row in query:
|
||||
reveal_type(row) # revealed: User
|
||||
```
|
||||
|
||||
And similarly when specifying particular columns:
|
||||
We can also specify particular columns to select:
|
||||
|
||||
```py
|
||||
stmt = select(User.id, User.name)
|
||||
# TODO: should be `Select[tuple[int, str]]`
|
||||
reveal_type(stmt) # revealed: Select[tuple[Unknown, Unknown]]
|
||||
|
||||
for row in session.execute(stmt):
|
||||
# TODO: should be `Row[Tuple[int, str]]`
|
||||
reveal_type(row) # revealed: Row[tuple[Unknown, Unknown]]
|
||||
```
|
||||
|
||||
And similarly with the legacy `query` API:
|
||||
|
||||
```py
|
||||
query = session.query(User.id, User.name)
|
||||
# TODO: should be `RowReturningQuery[tuple[int, str]]`
|
||||
reveal_type(query) # revealed: RowReturningQuery[tuple[Unknown, Unknown]]
|
||||
|
||||
# TODO: should be `list[Row[tuple[int, str]]]`
|
||||
reveal_type(query.all()) # revealed: list[Row[tuple[Unknown, Unknown]]]
|
||||
|
||||
for row in query:
|
||||
# TODO: should be `Row[tuple[int, str]]`
|
||||
for row in query.all():
|
||||
# TODO: should be `Row[Tuple[int, str]]`
|
||||
reveal_type(row) # revealed: Row[tuple[Unknown, Unknown]]
|
||||
```
|
||||
|
||||
## Async API
|
||||
|
||||
The async API is supported as well:
|
||||
|
||||
```py
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, Integer, Text
|
||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
pass
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
name: Mapped[str] = mapped_column(Text)
|
||||
|
||||
async def test_async(session: AsyncSession):
|
||||
stmt = select(User).where(User.name == "Alice")
|
||||
alice = await session.scalar(stmt)
|
||||
reveal_type(alice) # revealed: User | None
|
||||
|
||||
stmt = select(User.id, User.name)
|
||||
result = await session.execute(stmt)
|
||||
for user_id, name in result.tuples():
|
||||
reveal_type(user_id) # revealed: int
|
||||
reveal_type(name) # revealed: str
|
||||
```
|
||||
|
||||
@@ -102,38 +102,6 @@ Other values are invalid.
|
||||
P4 = ParamSpec("P4", default=int)
|
||||
```
|
||||
|
||||
### `default` parameter in `typing_extensions.ParamSpec`
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
The `default` parameter to `ParamSpec` is available from `typing_extensions` in Python 3.12 and
|
||||
earlier.
|
||||
|
||||
```py
|
||||
from typing import ParamSpec
|
||||
from typing_extensions import ParamSpec as ExtParamSpec
|
||||
|
||||
# This shouldn't emit a diagnostic
|
||||
P1 = ExtParamSpec("P1", default=[int, str])
|
||||
|
||||
# But, this should
|
||||
# error: [invalid-paramspec] "The `default` parameter of `typing.ParamSpec` was added in Python 3.13"
|
||||
P2 = ParamSpec("P2", default=[int, str])
|
||||
```
|
||||
|
||||
And, it allows the same set of values as `typing.ParamSpec`.
|
||||
|
||||
```py
|
||||
P3 = ExtParamSpec("P3", default=...)
|
||||
P4 = ExtParamSpec("P4", default=P3)
|
||||
|
||||
# error: [invalid-paramspec]
|
||||
P5 = ExtParamSpec("P5", default=int)
|
||||
```
|
||||
|
||||
### Forward references in stub files
|
||||
|
||||
Stubs natively support forward references, so patterns that would raise `NameError` at runtime are
|
||||
|
||||
@@ -128,16 +128,3 @@ InvalidEmptyUnion = Union[]
|
||||
def _(u: InvalidEmptyUnion):
|
||||
reveal_type(u) # revealed: Unknown
|
||||
```
|
||||
|
||||
### `typing.Annotated`
|
||||
|
||||
```py
|
||||
from typing import Annotated
|
||||
|
||||
# error: [invalid-syntax] "Expected index or slice expression"
|
||||
# error: [invalid-type-form] "Special form `typing.Annotated` expected at least 2 arguments (one type and at least one metadata element)"
|
||||
InvalidEmptyAnnotated = Annotated[]
|
||||
|
||||
def _(a: InvalidEmptyAnnotated):
|
||||
reveal_type(a) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
# `ParamSpec` regression on 3.9
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.9"
|
||||
```
|
||||
|
||||
This used to panic when run on Python 3.9 because `ParamSpec` was introduced in Python 3.10 and the
|
||||
diagnostic message for `invalid-exception-caught` expects to construct `typing.ParamSpec`.
|
||||
|
||||
```py
|
||||
# error: [invalid-syntax]
|
||||
def foo[**P]() -> None:
|
||||
try:
|
||||
pass
|
||||
# error: [invalid-exception-caught] "Invalid object caught in an exception handler: Object has type `typing.ParamSpec`"
|
||||
except P:
|
||||
pass
|
||||
```
|
||||
@@ -58,8 +58,9 @@ pub fn add_inferred_python_version_hint_to_diagnostic(
|
||||
SubDiagnosticSeverity::Info,
|
||||
format_args!("Python {version} was assumed when {action}"),
|
||||
);
|
||||
sub_diagnostic
|
||||
.annotate(Annotation::primary(span).message("Python version configuration"));
|
||||
sub_diagnostic.annotate(Annotation::primary(span).message(format_args!(
|
||||
"Python {version} assumed due to this configuration setting"
|
||||
)));
|
||||
diagnostic.sub(sub_diagnostic);
|
||||
} else {
|
||||
diagnostic.info(format_args!(
|
||||
@@ -75,8 +76,10 @@ pub fn add_inferred_python_version_hint_to_diagnostic(
|
||||
"Python {version} was assumed when {action} because of your virtual environment"
|
||||
),
|
||||
);
|
||||
sub_diagnostic
|
||||
.annotate(Annotation::primary(span).message("Virtual environment metadata"));
|
||||
sub_diagnostic.annotate(
|
||||
Annotation::primary(span)
|
||||
.message("Python version inferred from virtual environment metadata file"),
|
||||
);
|
||||
// TODO: it would also be nice to tell them how we resolved their virtual environment...
|
||||
diagnostic.sub(sub_diagnostic);
|
||||
} else {
|
||||
|
||||
@@ -335,12 +335,6 @@ pub enum KnownModule {
|
||||
#[cfg(test)]
|
||||
Uuid,
|
||||
Warnings,
|
||||
#[strum(serialize = "sqlalchemy.sql.selectable")]
|
||||
SqlalchemySqlSelectable,
|
||||
#[strum(serialize = "sqlalchemy.sql._selectable_constructors")]
|
||||
SqlalchemySqlSelectableConstructors,
|
||||
#[strum(serialize = "sqlalchemy.orm.attributes")]
|
||||
SqlalchemyOrmAttributes,
|
||||
}
|
||||
|
||||
impl KnownModule {
|
||||
@@ -369,9 +363,6 @@ impl KnownModule {
|
||||
#[cfg(test)]
|
||||
Self::Uuid => "uuid",
|
||||
Self::Templatelib => "string.templatelib",
|
||||
Self::SqlalchemySqlSelectable => "sqlalchemy.sql.selectable",
|
||||
Self::SqlalchemySqlSelectableConstructors => "sqlalchemy.sql._selectable_constructors",
|
||||
Self::SqlalchemyOrmAttributes => "sqlalchemy.orm.attributes",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,20 +378,7 @@ impl KnownModule {
|
||||
if search_path.is_standard_library() {
|
||||
Self::from_str(name.as_str()).ok()
|
||||
} else {
|
||||
// For non-stdlib search paths, check for known third-party modules
|
||||
Self::try_from_third_party_name(name)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a known module for third-party packages, if applicable.
|
||||
fn try_from_third_party_name(name: &ModuleName) -> Option<Self> {
|
||||
match name.as_str() {
|
||||
"sqlalchemy.sql.selectable" => Some(Self::SqlalchemySqlSelectable),
|
||||
"sqlalchemy.sql._selectable_constructors" => {
|
||||
Some(Self::SqlalchemySqlSelectableConstructors)
|
||||
}
|
||||
"sqlalchemy.orm.attributes" => Some(Self::SqlalchemyOrmAttributes),
|
||||
_ => None,
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,11 +419,6 @@ mod tests {
|
||||
let stdlib_search_path = SearchPath::vendored_stdlib();
|
||||
|
||||
for module in KnownModule::iter() {
|
||||
// Third-party modules aren't available in the vendored stdlib
|
||||
if module.is_third_party() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let module_name = module.name();
|
||||
|
||||
assert_eq!(
|
||||
|
||||
@@ -30,7 +30,6 @@ pub(crate) use self::infer::{
|
||||
TypeContext, infer_deferred_types, infer_definition_types, infer_expression_type,
|
||||
infer_expression_types, infer_scope_types, static_expression_truthiness,
|
||||
};
|
||||
pub use self::signatures::ParameterKind;
|
||||
pub(crate) use self::signatures::{CallableSignature, Signature};
|
||||
pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType};
|
||||
pub use crate::diagnostic::add_inferred_python_version_hint_to_diagnostic;
|
||||
|
||||
@@ -4168,8 +4168,6 @@ pub enum KnownClass {
|
||||
SpecialForm,
|
||||
TypeVar,
|
||||
ParamSpec,
|
||||
// typing_extensions.ParamSpec
|
||||
ExtensionsParamSpec, // must be distinct from typing.ParamSpec, backports new features
|
||||
ParamSpecArgs,
|
||||
ParamSpecKwargs,
|
||||
ProtocolMeta,
|
||||
@@ -4207,9 +4205,6 @@ pub enum KnownClass {
|
||||
ConstraintSet,
|
||||
GenericContext,
|
||||
Specialization,
|
||||
// sqlalchemy
|
||||
SqlalchemySelect,
|
||||
SqlalchemyInstrumentedAttribute,
|
||||
}
|
||||
|
||||
impl KnownClass {
|
||||
@@ -4244,7 +4239,6 @@ impl KnownClass {
|
||||
| Self::TypeVar
|
||||
| Self::ExtensionsTypeVar
|
||||
| Self::ParamSpec
|
||||
| Self::ExtensionsParamSpec
|
||||
| Self::ParamSpecArgs
|
||||
| Self::ParamSpecKwargs
|
||||
| Self::TypeVarTuple
|
||||
@@ -4318,9 +4312,7 @@ impl KnownClass {
|
||||
| Self::GenericContext
|
||||
| Self::Specialization
|
||||
| Self::ProtocolMeta
|
||||
| Self::TypedDictFallback
|
||||
| Self::SqlalchemySelect
|
||||
| Self::SqlalchemyInstrumentedAttribute => Some(Truthiness::Ambiguous),
|
||||
| Self::TypedDictFallback => Some(Truthiness::Ambiguous),
|
||||
|
||||
Self::Tuple => None,
|
||||
}
|
||||
@@ -4379,7 +4371,6 @@ impl KnownClass {
|
||||
| KnownClass::TypeVar
|
||||
| KnownClass::ExtensionsTypeVar
|
||||
| KnownClass::ParamSpec
|
||||
| KnownClass::ExtensionsParamSpec
|
||||
| KnownClass::ParamSpecArgs
|
||||
| KnownClass::ParamSpecKwargs
|
||||
| KnownClass::TypeVarTuple
|
||||
@@ -4410,9 +4401,7 @@ impl KnownClass {
|
||||
| KnownClass::BuiltinFunctionType
|
||||
| KnownClass::ProtocolMeta
|
||||
| KnownClass::Template
|
||||
| KnownClass::Path
|
||||
| KnownClass::SqlalchemySelect
|
||||
| KnownClass::SqlalchemyInstrumentedAttribute => false,
|
||||
| KnownClass::Path => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4468,7 +4457,6 @@ impl KnownClass {
|
||||
| KnownClass::TypeVar
|
||||
| KnownClass::ExtensionsTypeVar
|
||||
| KnownClass::ParamSpec
|
||||
| KnownClass::ExtensionsParamSpec
|
||||
| KnownClass::ParamSpecArgs
|
||||
| KnownClass::ParamSpecKwargs
|
||||
| KnownClass::TypeVarTuple
|
||||
@@ -4499,9 +4487,7 @@ impl KnownClass {
|
||||
| KnownClass::BuiltinFunctionType
|
||||
| KnownClass::ProtocolMeta
|
||||
| KnownClass::Template
|
||||
| KnownClass::Path
|
||||
| KnownClass::SqlalchemySelect
|
||||
| KnownClass::SqlalchemyInstrumentedAttribute => false,
|
||||
| KnownClass::Path => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4557,7 +4543,6 @@ impl KnownClass {
|
||||
| KnownClass::TypeVar
|
||||
| KnownClass::ExtensionsTypeVar
|
||||
| KnownClass::ParamSpec
|
||||
| KnownClass::ExtensionsParamSpec
|
||||
| KnownClass::ParamSpecArgs
|
||||
| KnownClass::ParamSpecKwargs
|
||||
| KnownClass::TypeVarTuple
|
||||
@@ -4587,9 +4572,7 @@ impl KnownClass {
|
||||
| KnownClass::BuiltinFunctionType
|
||||
| KnownClass::ProtocolMeta
|
||||
| KnownClass::Template
|
||||
| KnownClass::Path
|
||||
| KnownClass::SqlalchemySelect
|
||||
| KnownClass::SqlalchemyInstrumentedAttribute => false,
|
||||
| KnownClass::Path => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4651,7 +4634,6 @@ impl KnownClass {
|
||||
| Self::TypeVar
|
||||
| Self::ExtensionsTypeVar
|
||||
| Self::ParamSpec
|
||||
| Self::ExtensionsParamSpec
|
||||
| Self::ParamSpecArgs
|
||||
| Self::ParamSpecKwargs
|
||||
| Self::TypeVarTuple
|
||||
@@ -4688,9 +4670,7 @@ impl KnownClass {
|
||||
| Self::ProtocolMeta
|
||||
| Self::Template
|
||||
| Self::Path
|
||||
| Self::Mapping
|
||||
| Self::SqlalchemySelect
|
||||
| Self::SqlalchemyInstrumentedAttribute => false,
|
||||
| Self::Mapping => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4753,7 +4733,6 @@ impl KnownClass {
|
||||
| KnownClass::TypeVar
|
||||
| KnownClass::ExtensionsTypeVar
|
||||
| KnownClass::ParamSpec
|
||||
| KnownClass::ExtensionsParamSpec
|
||||
| KnownClass::ParamSpecArgs
|
||||
| KnownClass::ParamSpecKwargs
|
||||
| KnownClass::ProtocolMeta
|
||||
@@ -4779,9 +4758,7 @@ impl KnownClass {
|
||||
| KnownClass::ConstraintSet
|
||||
| KnownClass::GenericContext
|
||||
| KnownClass::Specialization
|
||||
| KnownClass::InitVar
|
||||
| KnownClass::SqlalchemySelect
|
||||
| KnownClass::SqlalchemyInstrumentedAttribute => false,
|
||||
| KnownClass::InitVar => false,
|
||||
KnownClass::NamedTupleFallback | KnownClass::TypedDictFallback => true,
|
||||
}
|
||||
}
|
||||
@@ -4829,7 +4806,6 @@ impl KnownClass {
|
||||
Self::TypeVar => "TypeVar",
|
||||
Self::ExtensionsTypeVar => "TypeVar",
|
||||
Self::ParamSpec => "ParamSpec",
|
||||
Self::ExtensionsParamSpec => "ParamSpec",
|
||||
Self::ParamSpecArgs => "ParamSpecArgs",
|
||||
Self::ParamSpecKwargs => "ParamSpecKwargs",
|
||||
Self::TypeVarTuple => "TypeVarTuple",
|
||||
@@ -4897,8 +4873,6 @@ impl KnownClass {
|
||||
Self::Template => "Template",
|
||||
Self::Path => "Path",
|
||||
Self::ProtocolMeta => "_ProtocolMeta",
|
||||
Self::SqlalchemySelect => "Select",
|
||||
Self::SqlalchemyInstrumentedAttribute => "InstrumentedAttribute",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5165,18 +5139,11 @@ impl KnownClass {
|
||||
Self::TypeAliasType
|
||||
| Self::ExtensionsTypeVar
|
||||
| Self::TypeVarTuple
|
||||
| Self::ExtensionsParamSpec
|
||||
| Self::ParamSpec
|
||||
| Self::ParamSpecArgs
|
||||
| Self::ParamSpecKwargs
|
||||
| Self::Deprecated
|
||||
| Self::NewType => KnownModule::TypingExtensions,
|
||||
Self::ParamSpec => {
|
||||
if Program::get(db).python_version(db) >= PythonVersion::PY310 {
|
||||
KnownModule::Typing
|
||||
} else {
|
||||
KnownModule::TypingExtensions
|
||||
}
|
||||
}
|
||||
Self::NoDefaultType => {
|
||||
let python_version = Program::get(db).python_version(db);
|
||||
|
||||
@@ -5220,8 +5187,6 @@ impl KnownClass {
|
||||
| Self::Specialization => KnownModule::TyExtensions,
|
||||
Self::Template => KnownModule::Templatelib,
|
||||
Self::Path => KnownModule::Pathlib,
|
||||
Self::SqlalchemySelect => KnownModule::SqlalchemySqlSelectable,
|
||||
Self::SqlalchemyInstrumentedAttribute => KnownModule::SqlalchemyOrmAttributes,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5282,7 +5247,6 @@ impl KnownClass {
|
||||
| Self::TypeVar
|
||||
| Self::ExtensionsTypeVar
|
||||
| Self::ParamSpec
|
||||
| Self::ExtensionsParamSpec
|
||||
| Self::ParamSpecArgs
|
||||
| Self::ParamSpecKwargs
|
||||
| Self::TypeVarTuple
|
||||
@@ -5310,9 +5274,7 @@ impl KnownClass {
|
||||
| Self::BuiltinFunctionType
|
||||
| Self::ProtocolMeta
|
||||
| Self::Template
|
||||
| Self::Path
|
||||
| Self::SqlalchemySelect
|
||||
| Self::SqlalchemyInstrumentedAttribute => Some(false),
|
||||
| Self::Path => Some(false),
|
||||
|
||||
Self::Tuple => None,
|
||||
}
|
||||
@@ -5375,7 +5337,6 @@ impl KnownClass {
|
||||
| Self::TypeVar
|
||||
| Self::ExtensionsTypeVar
|
||||
| Self::ParamSpec
|
||||
| Self::ExtensionsParamSpec
|
||||
| Self::ParamSpecArgs
|
||||
| Self::ParamSpecKwargs
|
||||
| Self::TypeVarTuple
|
||||
@@ -5404,9 +5365,7 @@ impl KnownClass {
|
||||
| Self::BuiltinFunctionType
|
||||
| Self::ProtocolMeta
|
||||
| Self::Template
|
||||
| Self::Path
|
||||
| Self::SqlalchemySelect
|
||||
| Self::SqlalchemyInstrumentedAttribute => false,
|
||||
| Self::Path => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5461,7 +5420,7 @@ impl KnownClass {
|
||||
"Iterable" => &[Self::Iterable],
|
||||
"Iterator" => &[Self::Iterator],
|
||||
"Mapping" => &[Self::Mapping],
|
||||
"ParamSpec" => &[Self::ParamSpec, Self::ExtensionsParamSpec],
|
||||
"ParamSpec" => &[Self::ParamSpec],
|
||||
"ParamSpecArgs" => &[Self::ParamSpecArgs],
|
||||
"ParamSpecKwargs" => &[Self::ParamSpecKwargs],
|
||||
"TypeVarTuple" => &[Self::TypeVarTuple],
|
||||
@@ -5512,8 +5471,6 @@ impl KnownClass {
|
||||
"Template" => &[Self::Template],
|
||||
"Path" => &[Self::Path],
|
||||
"_ProtocolMeta" => &[Self::ProtocolMeta],
|
||||
"Select" => &[Self::SqlalchemySelect],
|
||||
"InstrumentedAttribute" => &[Self::SqlalchemyInstrumentedAttribute],
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
@@ -5585,8 +5542,6 @@ impl KnownClass {
|
||||
| Self::TypedDictFallback
|
||||
| Self::TypeVar
|
||||
| Self::ExtensionsTypeVar
|
||||
| Self::ParamSpec
|
||||
| Self::ExtensionsParamSpec
|
||||
| Self::NamedTupleLike
|
||||
| Self::ConstraintSet
|
||||
| Self::GenericContext
|
||||
@@ -5594,14 +5549,13 @@ impl KnownClass {
|
||||
| Self::Awaitable
|
||||
| Self::Generator
|
||||
| Self::Template
|
||||
| Self::Path
|
||||
| Self::SqlalchemySelect
|
||||
| Self::SqlalchemyInstrumentedAttribute => module == self.canonical_module(db),
|
||||
| Self::Path => module == self.canonical_module(db),
|
||||
Self::NoneType => matches!(module, KnownModule::Typeshed | KnownModule::Types),
|
||||
Self::SpecialForm
|
||||
| Self::TypeAliasType
|
||||
| Self::NoDefaultType
|
||||
| Self::SupportsIndex
|
||||
| Self::ParamSpec
|
||||
| Self::ParamSpecArgs
|
||||
| Self::ParamSpecKwargs
|
||||
| Self::TypeVarTuple
|
||||
@@ -5951,10 +5905,6 @@ mod tests {
|
||||
source: PythonVersionSource::default(),
|
||||
});
|
||||
for class in KnownClass::iter() {
|
||||
if class.canonical_module(&db).is_third_party() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let class_name = class.name(&db);
|
||||
let class_module =
|
||||
resolve_module_confident(&db, &class.canonical_module(&db).name()).unwrap();
|
||||
@@ -5983,10 +5933,6 @@ mod tests {
|
||||
});
|
||||
|
||||
for class in KnownClass::iter() {
|
||||
if class.canonical_module(&db).is_third_party() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check the class can be looked up successfully
|
||||
class.try_to_class_literal_without_logging(&db).unwrap();
|
||||
|
||||
@@ -6012,7 +5958,6 @@ mod tests {
|
||||
// This makes the test far faster as it minimizes the number of times
|
||||
// we need to change the Python version in the loop.
|
||||
let mut classes: Vec<(KnownClass, PythonVersion)> = KnownClass::iter()
|
||||
.filter(|class| !class.canonical_module(&db).is_third_party())
|
||||
.map(|class| {
|
||||
let version_added = match class {
|
||||
KnownClass::Template => PythonVersion::PY314,
|
||||
@@ -6025,7 +5970,6 @@ mod tests {
|
||||
KnownClass::Member | KnownClass::Nonmember | KnownClass::StrEnum => {
|
||||
PythonVersion::PY311
|
||||
}
|
||||
KnownClass::ParamSpec => PythonVersion::PY310,
|
||||
_ => PythonVersion::PY37,
|
||||
};
|
||||
(class, version_added)
|
||||
|
||||
@@ -1353,10 +1353,6 @@ pub enum KnownFunction {
|
||||
RevealProtocolInterface,
|
||||
/// `ty_extensions.reveal_mro`
|
||||
RevealMro,
|
||||
|
||||
/// `sqlalchemy.select`
|
||||
#[strum(serialize = "select")]
|
||||
SqlalchemySelect,
|
||||
}
|
||||
|
||||
impl KnownFunction {
|
||||
@@ -1429,9 +1425,6 @@ impl KnownFunction {
|
||||
|
||||
Self::TypeCheckOnly => matches!(module, KnownModule::Typing),
|
||||
Self::NamedTuple => matches!(module, KnownModule::Collections),
|
||||
Self::SqlalchemySelect => {
|
||||
matches!(module, KnownModule::SqlalchemySqlSelectableConstructors)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1903,56 +1896,6 @@ impl KnownFunction {
|
||||
|
||||
overload.set_return_type(Type::module_literal(db, file, module));
|
||||
}
|
||||
|
||||
KnownFunction::SqlalchemySelect => {
|
||||
// Try to extract types from InstrumentedAttribute[T] arguments.
|
||||
// If all arguments are InstrumentedAttribute instances, we construct
|
||||
// Select[tuple[T_1, T_2, ...]] where T_i are the inner types.
|
||||
//
|
||||
// We check the class via `class_literal.known(db)` rather than using
|
||||
// `known_specialization` because the class may be re-exported and not
|
||||
// directly importable from its canonical module.
|
||||
let inner_types: Option<Vec<_>> = parameter_types
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(|param_type| {
|
||||
let Type::NominalInstance(instance) = param_type else {
|
||||
return None;
|
||||
};
|
||||
let class = instance.class(db);
|
||||
let (class_literal, specialization) = class.class_literal(db);
|
||||
if class_literal.known(db)
|
||||
!= Some(KnownClass::SqlalchemyInstrumentedAttribute)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
specialization?.types(db).first().copied()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let Some(inner_types) = inner_types else {
|
||||
// Fall back to whatever we infer from the function signature
|
||||
return;
|
||||
};
|
||||
|
||||
if inner_types.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Construct Select[tuple[T1, T2, ...]]
|
||||
// We get the return type's class from the overload rather than looking
|
||||
// it up via try_to_class_literal, since the class may be re-exported.
|
||||
let Type::NominalInstance(return_instance) = overload.return_type() else {
|
||||
return;
|
||||
};
|
||||
let select_class = return_instance.class(db).class_literal(db).0;
|
||||
let tuple_type = Type::heterogeneous_tuple(db, inner_types);
|
||||
let class_type = select_class.apply_specialization(db, |generic_context| {
|
||||
generic_context.specialize(db, vec![tuple_type].into())
|
||||
});
|
||||
overload.set_return_type(Type::instance(db, class_type));
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -2021,8 +1964,6 @@ pub(crate) mod tests {
|
||||
|
||||
KnownFunction::ImportModule => KnownModule::ImportLib,
|
||||
KnownFunction::NamedTuple => KnownModule::Collections,
|
||||
|
||||
KnownFunction::SqlalchemySelect => continue,
|
||||
};
|
||||
|
||||
let function_definition = known_module_symbol(&db, module, function_name)
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::definition::DefinitionKind;
|
||||
use crate::semantic_index::{attribute_scopes, global_scope, semantic_index, use_def_map};
|
||||
use crate::types::call::{CallArguments, MatchedArgument};
|
||||
use crate::types::signatures::{ParameterKind, Signature};
|
||||
use crate::types::signatures::Signature;
|
||||
use crate::types::{CallDunderError, UnionType};
|
||||
use crate::types::{CallableTypes, ClassBase, KnownClass, Type, TypeContext};
|
||||
use crate::{Db, DisplaySettings, HasType, SemanticModel};
|
||||
@@ -459,12 +459,6 @@ pub struct CallSignatureDetails<'db> {
|
||||
/// This provides easy access to parameter names for documentation lookup.
|
||||
pub parameter_names: Vec<String>,
|
||||
|
||||
/// Parameter kinds, useful to determine correct autocomplete suggestions.
|
||||
pub parameter_kinds: Vec<ParameterKind<'db>>,
|
||||
|
||||
/// Parameter kinds, useful to determine correct autocomplete suggestions.
|
||||
pub parameter_types: Vec<Option<Type<'db>>>,
|
||||
|
||||
/// The definition where this callable was originally defined (useful for
|
||||
/// extracting docstrings).
|
||||
pub definition: Option<Definition<'db>>,
|
||||
@@ -523,12 +517,6 @@ pub fn call_signature_details<'db>(
|
||||
let display_details = signature.display(model.db()).to_string_parts();
|
||||
let parameter_label_offsets = display_details.parameter_ranges;
|
||||
let parameter_names = display_details.parameter_names;
|
||||
let (parameter_kinds, parameter_types): (Vec<ParameterKind>, Vec<Option<Type>>) =
|
||||
signature
|
||||
.parameters()
|
||||
.iter()
|
||||
.map(|param| (param.kind().clone(), param.annotated_type()))
|
||||
.unzip();
|
||||
|
||||
CallSignatureDetails {
|
||||
definition: signature.definition(),
|
||||
@@ -536,8 +524,6 @@ pub fn call_signature_details<'db>(
|
||||
label: display_details.label,
|
||||
parameter_label_offsets,
|
||||
parameter_names,
|
||||
parameter_kinds,
|
||||
parameter_types,
|
||||
argument_to_parameter_mapping,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -5033,15 +5033,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
) => {
|
||||
self.infer_legacy_typevar(target, call_expr, definition, typevar_class)
|
||||
}
|
||||
Some(
|
||||
paramspec_class @ (KnownClass::ParamSpec
|
||||
| KnownClass::ExtensionsParamSpec),
|
||||
) => self.infer_legacy_paramspec(
|
||||
target,
|
||||
call_expr,
|
||||
definition,
|
||||
paramspec_class,
|
||||
),
|
||||
Some(KnownClass::ParamSpec) => {
|
||||
self.infer_paramspec(target, call_expr, definition)
|
||||
}
|
||||
Some(KnownClass::NewType) => {
|
||||
self.infer_newtype_expression(target, call_expr, definition)
|
||||
}
|
||||
@@ -5086,12 +5080,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
target_ty
|
||||
}
|
||||
|
||||
fn infer_legacy_paramspec(
|
||||
fn infer_paramspec(
|
||||
&mut self,
|
||||
target: &ast::Expr,
|
||||
call_expr: &ast::ExprCall,
|
||||
definition: Definition<'db>,
|
||||
known_class: KnownClass,
|
||||
) -> Type<'db> {
|
||||
fn error<'db>(
|
||||
context: &InferContext<'db, '_>,
|
||||
@@ -5108,8 +5101,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
|
||||
let db = self.db();
|
||||
let arguments = &call_expr.arguments;
|
||||
let is_typing_extensions = known_class == KnownClass::ExtensionsParamSpec;
|
||||
let assume_all_features = self.in_stub() || is_typing_extensions;
|
||||
let assume_all_features = self.in_stub();
|
||||
let python_version = Program::get(db).python_version(db);
|
||||
let have_features_from =
|
||||
|version: PythonVersion| assume_all_features || python_version >= version;
|
||||
@@ -5602,10 +5594,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
self.infer_type_expression(&bound.value);
|
||||
}
|
||||
if let Some(default) = arguments.find_keyword("default") {
|
||||
if matches!(
|
||||
known_class,
|
||||
Some(KnownClass::ParamSpec | KnownClass::ExtensionsParamSpec)
|
||||
) {
|
||||
if let Some(KnownClass::ParamSpec) = known_class {
|
||||
self.infer_paramspec_default(&default.value);
|
||||
} else {
|
||||
self.infer_type_expression(&default.value);
|
||||
@@ -8451,7 +8440,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
);
|
||||
}
|
||||
}
|
||||
Some(KnownClass::ParamSpec | KnownClass::ExtensionsParamSpec) => {
|
||||
Some(KnownClass::ParamSpec) => {
|
||||
if let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&INVALID_PARAMSPEC, call_expression)
|
||||
|
||||
@@ -1172,11 +1172,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||
)
|
||||
.in_type_expression(db, self.scope(), None)
|
||||
.unwrap_or_else(|err| err.into_fallback_type(&self.context, subscript, true));
|
||||
// Only store on the tuple slice; non-tuple cases are handled by
|
||||
// `infer_subscript_load_impl` via `infer_expression`.
|
||||
if arguments_slice.is_tuple_expr() {
|
||||
self.store_expression_type(arguments_slice, ty);
|
||||
}
|
||||
self.store_expression_type(arguments_slice, ty);
|
||||
ty
|
||||
}
|
||||
SpecialFormType::Literal => match self.infer_literal_parameter_type(arguments_slice) {
|
||||
|
||||
@@ -2292,7 +2292,7 @@ impl<'db> Parameter<'db> {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update, get_size2::GetSize)]
|
||||
pub enum ParameterKind<'db> {
|
||||
pub(crate) enum ParameterKind<'db> {
|
||||
/// Positional-only parameter, e.g. `def f(x, /): ...`
|
||||
PositionalOnly {
|
||||
/// Parameter name.
|
||||
|
||||
@@ -349,7 +349,6 @@ pub(super) fn to_lsp_diagnostic(
|
||||
sub_diagnostic
|
||||
.annotations()
|
||||
.iter()
|
||||
.filter(|annotation| !annotation.is_primary())
|
||||
.filter_map(|annotation| {
|
||||
annotation_to_related_information(db, annotation, encoding)
|
||||
}),
|
||||
|
||||
@@ -132,44 +132,11 @@ x: Literal[1] = 1
|
||||
";
|
||||
|
||||
let ty_toml = SystemPath::new("ty.toml");
|
||||
let ty_toml_content = "";
|
||||
|
||||
let mut server = TestServerBuilder::new()?
|
||||
.with_workspace(workspace_root, None)?
|
||||
.with_file(ty_toml, ty_toml_content)?
|
||||
.with_file(foo, foo_content)?
|
||||
.enable_pull_diagnostics(true)
|
||||
.build()
|
||||
.wait_until_workspaces_are_initialized();
|
||||
|
||||
server.open_text_document(foo, foo_content, 1);
|
||||
|
||||
// Wait for diagnostics to be computed.
|
||||
let diagnostics = server.document_diagnostic_request(foo, None);
|
||||
let range = full_range(foo_content);
|
||||
let code_action_params = code_actions_at(&server, diagnostics, foo, range);
|
||||
|
||||
// Get code actions
|
||||
let code_action_id = server.send_request::<CodeActionRequest>(code_action_params);
|
||||
let code_actions = server.await_response::<CodeActionRequest>(&code_action_id);
|
||||
|
||||
insta::assert_json_snapshot!(code_actions);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// `Literal` is available from two places so we should suggest two possible imports
|
||||
#[test]
|
||||
fn code_action_undefined_reveal_type() -> Result<()> {
|
||||
let workspace_root = SystemPath::new("src");
|
||||
let foo = SystemPath::new("src/foo.py");
|
||||
let foo_content = "\
|
||||
reveal_type(1)
|
||||
let ty_toml_content = "\
|
||||
[rules]
|
||||
unused-ignore-comment = \"warn\"
|
||||
";
|
||||
|
||||
let ty_toml = SystemPath::new("ty.toml");
|
||||
let ty_toml_content = "";
|
||||
|
||||
let mut server = TestServerBuilder::new()?
|
||||
.with_workspace(workspace_root, None)?
|
||||
.with_file(ty_toml, ty_toml_content)?
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
---
|
||||
source: crates/ty_server/tests/e2e/code_actions.rs
|
||||
expression: code_actions
|
||||
---
|
||||
[
|
||||
{
|
||||
"title": "import typing.reveal_type",
|
||||
"kind": "quickfix",
|
||||
"diagnostics": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
"character": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 0,
|
||||
"character": 11
|
||||
}
|
||||
},
|
||||
"severity": 2,
|
||||
"code": "undefined-reveal",
|
||||
"codeDescription": {
|
||||
"href": "https://ty.dev/rules#undefined-reveal"
|
||||
},
|
||||
"source": "ty",
|
||||
"message": "`reveal_type` used without importing it",
|
||||
"relatedInformation": []
|
||||
}
|
||||
],
|
||||
"edit": {
|
||||
"changes": {
|
||||
"file://<temp_dir>/src/foo.py": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
"character": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 0,
|
||||
"character": 0
|
||||
}
|
||||
},
|
||||
"newText": "from typing import reveal_type\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"isPreferred": true
|
||||
},
|
||||
{
|
||||
"title": "Ignore 'undefined-reveal' for this line",
|
||||
"kind": "quickfix",
|
||||
"diagnostics": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
"character": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 0,
|
||||
"character": 11
|
||||
}
|
||||
},
|
||||
"severity": 2,
|
||||
"code": "undefined-reveal",
|
||||
"codeDescription": {
|
||||
"href": "https://ty.dev/rules#undefined-reveal"
|
||||
},
|
||||
"source": "ty",
|
||||
"message": "`reveal_type` used without importing it",
|
||||
"relatedInformation": []
|
||||
}
|
||||
],
|
||||
"edit": {
|
||||
"changes": {
|
||||
"file://<temp_dir>/src/foo.py": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
"character": 14
|
||||
},
|
||||
"end": {
|
||||
"line": 0,
|
||||
"character": 14
|
||||
}
|
||||
},
|
||||
"newText": " # ty:ignore[undefined-reveal]"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"isPreferred": false
|
||||
}
|
||||
]
|
||||
@@ -427,16 +427,10 @@ mod tests {
|
||||
let mut diag = if self.id == DiagnosticId::RevealedType {
|
||||
Diagnostic::new(self.id, Severity::Error, "Revealed type")
|
||||
} else {
|
||||
Diagnostic::new(self.id, Severity::Error, self.message)
|
||||
Diagnostic::new(self.id, Severity::Error, "")
|
||||
};
|
||||
let span = Span::from(file).with_range(self.range);
|
||||
let mut annotation = Annotation::primary(span);
|
||||
|
||||
if self.id == DiagnosticId::RevealedType {
|
||||
annotation = annotation.message(self.message);
|
||||
}
|
||||
|
||||
diag.annotate(annotation);
|
||||
diag.annotate(Annotation::primary(span).message(self.message));
|
||||
diag
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ from collections.abc import Callable
|
||||
from dataclasses import KW_ONLY, dataclass
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
from typing import Final, NewType, NoReturn, assert_never
|
||||
from typing import NewType, NoReturn, assert_never
|
||||
|
||||
from pysource_codegen import generate as generate_random_code
|
||||
from pysource_minimize import CouldNotMinimize, minimize as minimize_repro
|
||||
@@ -44,12 +44,6 @@ MinimizedSourceCode = NewType("MinimizedSourceCode", str)
|
||||
Seed = NewType("Seed", int)
|
||||
ExitCode = NewType("ExitCode", int)
|
||||
|
||||
TY_TARGET_PLATFORM: Final = "linux"
|
||||
|
||||
# ty supports `--python-version=3.8`, but typeshed only supports 3.9+,
|
||||
# so that's probably the oldest version we can usefully test with.
|
||||
OLDEST_SUPPORTED_PYTHON: Final = "3.9"
|
||||
|
||||
|
||||
def ty_contains_bug(code: str, *, ty_executable: Path) -> bool:
|
||||
"""Return `True` if the code triggers a panic in type-checking code."""
|
||||
@@ -57,17 +51,7 @@ def ty_contains_bug(code: str, *, ty_executable: Path) -> bool:
|
||||
input_file = Path(tempdir, "input.py")
|
||||
input_file.write_text(code)
|
||||
completed_process = subprocess.run(
|
||||
[
|
||||
ty_executable,
|
||||
"check",
|
||||
input_file,
|
||||
"--python-version",
|
||||
OLDEST_SUPPORTED_PYTHON,
|
||||
"--python-platform",
|
||||
TY_TARGET_PLATFORM,
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
[ty_executable, "check", input_file], capture_output=True, text=True
|
||||
)
|
||||
return completed_process.returncode not in {0, 1, 2}
|
||||
|
||||
@@ -153,10 +137,7 @@ class FuzzResult:
|
||||
case Executable.RUFF:
|
||||
panic_message = f"The following code triggers a {new}parser bug:"
|
||||
case Executable.TY:
|
||||
panic_message = (
|
||||
f"The following code triggers a {new}ty panic with "
|
||||
f"`--python-version={OLDEST_SUPPORTED_PYTHON} --python-platform={TY_TARGET_PLATFORM}`:"
|
||||
)
|
||||
panic_message = f"The following code triggers a {new}ty panic:"
|
||||
case _ as unreachable:
|
||||
assert_never(unreachable)
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["mypy", "ruff", "ty"]
|
||||
dev = ["mypy", "ruff"]
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
include = ["fuzz.py"]
|
||||
|
||||
27
python/py-fuzzer/uv.lock
generated
27
python/py-fuzzer/uv.lock
generated
@@ -89,7 +89,6 @@ dependencies = [
|
||||
dev = [
|
||||
{ name = "mypy" },
|
||||
{ name = "ruff" },
|
||||
{ name = "ty" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
@@ -105,7 +104,6 @@ requires-dist = [
|
||||
dev = [
|
||||
{ name = "mypy" },
|
||||
{ name = "ruff" },
|
||||
{ name = "ty" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -198,31 +196,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/bd/de8d508070629b6d84a30d01d57e4a65c69aa7f5abe7560b8fad3b50ea59/termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa", size = 7684, upload-time = "2025-04-30T11:37:52.382Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ty"
|
||||
version = "0.0.1a32"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/26/92/8da015685fb83734a2a83de02080e64d182509de77fa9bcf3eed12eeab4b/ty-0.0.1a32.tar.gz", hash = "sha256:12f62e8a3dd0eaeb9557d74b1c32f0616ae40eae10a4f411e1e2a73225f67ff2", size = 4689151, upload-time = "2025-12-05T21:04:26.885Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/e6/fdc35c9ba047f16afdfedf36fb51c221e0190ccde9f70ee28e77084d6612/ty-0.0.1a32-py3-none-linux_armv6l.whl", hash = "sha256:ffe595eaf616f06f58f951766477830a55c2502d2c9f77dde8f60d9a836e0645", size = 9673128, upload-time = "2025-12-05T21:04:17.702Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/20/eaff31048e2f309f37478f7d715c8de9f9bab03cba4758da27b9311147af/ty-0.0.1a32-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:07f1dce88ad6028fb14665aefe4e6697012c34bd48edd37d02b7eb6a833dbf62", size = 9434094, upload-time = "2025-12-05T21:04:03.383Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/d4/ea8ed57d11b81c459f23561fd6bfb0f54a8d4120cf72541e3bdf71d46202/ty-0.0.1a32-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8fab7ed12528c77ddd600a9638ca859156a53c20f1e381353fa87a255bd397eb", size = 8980296, upload-time = "2025-12-05T21:04:28.912Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/02/3ce98bbfbb3916678d717ee69358d38a404ca9a39391dda8874b66dd5ee7/ty-0.0.1a32-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ace395280fc21e25eff0a53cfbd68170f90a4b8ef2f85dfabe1ecbca2ced456b", size = 9263054, upload-time = "2025-12-05T21:04:05.619Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/be/a639638bcd1664de2d70a87da6c4fe0e3272a60b7fa3f0c108a956a456bd/ty-0.0.1a32-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2bcbeed7f5ed8e3c1c7e525fce541e7b943ac04ee7fe369a926551b5e50ea4a8", size = 9451396, upload-time = "2025-12-05T21:04:01.265Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/a4/2bcf54e842a3d10dc14b369f28a3bab530c5d7ddba624e910b212bda93ee/ty-0.0.1a32-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:60ff2e4493f90f81a260205d87719bb1d3420928a1e4a2a7454af7cbdfed2047", size = 9862726, upload-time = "2025-12-05T21:04:08.806Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/c7/19e6719496e59f2f082f34bcac312698366cf50879fdcc3ef76298bfe6a0/ty-0.0.1a32-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:53cad50a59a0d943b06872e0b10f9f2b564805c2ea93f64c7798852bc1901954", size = 10475051, upload-time = "2025-12-05T21:04:31.059Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/77/bdf0ddb066d2b62f141d058f8a33bb7c8628cdbb8bfa75b20e296b79fb4e/ty-0.0.1a32-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:343d43cdc1d7f649ea2baa64ac2b479da3d679239b94509f1df12f7211561ea9", size = 10232712, upload-time = "2025-12-05T21:04:19.849Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/07/f73260a461762a581a007015c1019d40658828ce41576f8c1db88dee574d/ty-0.0.1a32-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f45483e4a84bcf622413712164ea687ce323a9f7013b9e7977c5d623ed937ca9", size = 10237705, upload-time = "2025-12-05T21:04:35.366Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/57/dbb92206cf2f798d8c51ea16504e8afb90a139d0ff105c31cec9a1db29f9/ty-0.0.1a32-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d452f30d47002a6bafc36d1b6aee42c321e9ec9f7f43a04a2ee7d48c208b86c", size = 9766469, upload-time = "2025-12-05T21:04:22.236Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/5e/143d93bd143abcebcbaa98c8aeec78898553d62d0a5a432cd79e0cf5bd6d/ty-0.0.1a32-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:86c4e31737fe954637890cef1f3e1b479ffb20e836cac3b76050bdbe80005010", size = 9238592, upload-time = "2025-12-05T21:04:11.33Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/b8/225230ae097ed88f3c92ad974dd77f8e4f86f2594d9cd0c729da39769878/ty-0.0.1a32-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:daf15fa03bc39a76a0fbc9c2d81d79d528f584e3fbe08d71981e3f7912db91d6", size = 9502161, upload-time = "2025-12-05T21:04:37.642Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/13/cc89955c9637f25f3aca2dd7749c6008639ef036f0b9bea3e9d89e892ff9/ty-0.0.1a32-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6128f6bab5c6dab3d08689fed1d529dc34f50f221f89c8e16064ed0c549dad7a", size = 9603058, upload-time = "2025-12-05T21:04:39.532Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/77/1fe2793c8065a02d1f70ca7da1b87db49ca621bcbbdb79a18ad79d5d0ab2/ty-0.0.1a32-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:55aab688be1b46776a5a458a1993cae0da7725932c45393399c479c2fa979337", size = 9879903, upload-time = "2025-12-05T21:04:13.567Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/47/fd58e80a3e42310b4b649340d5d97403fe796146cae8678b3a031a414b8e/ty-0.0.1a32-py3-none-win32.whl", hash = "sha256:f55ec25088a09236ad1578b656a07fa009c3a353f5923486905ba48175d142a6", size = 9077703, upload-time = "2025-12-05T21:04:15.849Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/96/209c417c69317339ea8e9b3277fd98364a0e97dd1ffd3585e143ec7b4e57/ty-0.0.1a32-py3-none-win_amd64.whl", hash = "sha256:ed8d5cbd4e47dfed86aaa27e243008aa4e82b6a5434f3ab95c26d3ee5874d9d7", size = 9922426, upload-time = "2025-12-05T21:04:33.289Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/1c/350fd851fb91244f8c80cec218009cbee7564d76c14e2f423b47e69a5cbc/ty-0.0.1a32-py3-none-win_arm64.whl", hash = "sha256:dbb25f9b513d34cee8ce419514eaef03313f45c3f7ab4eb6e6d427ea1f6854af", size = 9453761, upload-time = "2025-12-05T21:04:24.502Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.15.0"
|
||||
|
||||
Reference in New Issue
Block a user