From 25fd4177dde8e34fe3ae743d22c36f5cafe1a00b Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Wed, 18 Dec 2024 16:40:27 +0100 Subject: [PATCH 1/5] Initial type: ignore support --- .../mdtest/suppressions/type-ignore.md | 83 ++++++++++++++ crates/red_knot_python_semantic/src/lib.rs | 1 + .../src/suppression.rs | 108 +++++++++++++----- .../src/types/context.rs | 10 ++ crates/ruff_index/src/slice.rs | 24 ++++ 5 files changed, 197 insertions(+), 29 deletions(-) create mode 100644 crates/red_knot_python_semantic/resources/mdtest/suppressions/type-ignore.md diff --git a/crates/red_knot_python_semantic/resources/mdtest/suppressions/type-ignore.md b/crates/red_knot_python_semantic/resources/mdtest/suppressions/type-ignore.md new file mode 100644 index 0000000000000..d7480760e8a98 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/suppressions/type-ignore.md @@ -0,0 +1,83 @@ +# Suppressing errors with `type: ignore` + +Type check errors can be suppressed by a `type: ignore` comment on the same line as the violation. + +## Simple `type: ignore` + +```py +a = 4 + test # type: ignore +``` + +## In parenthesized expression + +```py +a = ( + 4 + test # type: ignore +) # fmt: skip +``` + +## Before opening parentheses + +A suppression that applies to all errors before the openign parentheses. + +```py +a: Test = ( # type: ignore + 5 +) # fmt: skip +``` + +## Multiline string + +```py +a: int = 4 +a = """ + This is a multiline string and the suppression is at its end +""" # type: ignore +``` + +## Line continuations + +Suppressions after a line continuation apply to all previous lines. + +```py +# fmt: off +a = test \ + + 2 # type: ignore + +a = test \ + + a \ + + 2 # type: ignore +``` + +## Nested comments + +TODO: We should support this for better interopability with other suppression comments. + +```py +# fmt: off +# error: [unresolved-reference] +a = test \ + + 2 # fmt: skip # type: ignore + +a = test \ + + 2 # type: ignore # fmt: skip +``` + +## Misspelled `type: ignore` + +```py +# error: [unresolved-reference] +a = test + 2 # type: ignoree +``` + +## Invalid - ignore on opening parentheses + +`type: ignore` comments after an opening parentheses suppress any type errors inside the parentheses +in Pyright. Neither Ruff, nor mypy support this and neither does Red Knot. + +```py +# fmt: off +a = ( # type: ignore + test + 4 # error: [unresolved-reference] +) +``` diff --git a/crates/red_knot_python_semantic/src/lib.rs b/crates/red_knot_python_semantic/src/lib.rs index 6f975eea018f4..5836de2a14692 100644 --- a/crates/red_knot_python_semantic/src/lib.rs +++ b/crates/red_knot_python_semantic/src/lib.rs @@ -22,6 +22,7 @@ pub mod semantic_index; mod semantic_model; pub(crate) mod site_packages; mod stdlib; +mod suppression; pub(crate) mod symbol; pub mod types; mod unpack; diff --git a/crates/red_knot_python_semantic/src/suppression.rs b/crates/red_knot_python_semantic/src/suppression.rs index c87cbfc8bc7e0..40b812cbb7b31 100644 --- a/crates/red_knot_python_semantic/src/suppression.rs +++ b/crates/red_knot_python_semantic/src/suppression.rs @@ -1,50 +1,100 @@ -use salsa; +use std::cmp::Ordering; -use ruff_db::{files::File, parsed::comment_ranges, source::source_text}; +use ruff_python_parser::TokenKind; +use ruff_source_file::LineRanges; +use ruff_text_size::{Ranged, TextRange, TextSize}; + +use ruff_db::{files::File, parsed::parsed_module, source::source_text}; use ruff_index::{newtype_index, IndexVec}; use crate::{lint::LintId, Db}; #[salsa::tracked(return_ref)] -pub(crate) fn suppressions(db: &dyn Db, file: File) -> IndexVec { - let comments = comment_ranges(db.upcast(), file); +pub(crate) fn suppressions(db: &dyn Db, file: File) -> Suppressions { let source = source_text(db.upcast(), file); + let parsed = parsed_module(db.upcast(), file); let mut suppressions = IndexVec::default(); + let mut line_start = source.bom_start_offset(); + + for token in parsed.tokens() { + match token.kind() { + TokenKind::Comment => { + let text = &source[token.range()]; + + let suppressed_range = TextRange::new(line_start, token.end()); - for range in comments { - let text = &source[range]; - - if text.starts_with("# type: ignore") { - suppressions.push(Suppression { - target: None, - kind: SuppressionKind::TypeIgnore, - }); - } else if text.starts_with("# knot: ignore") { - suppressions.push(Suppression { - target: None, - kind: SuppressionKind::KnotIgnore, - }); + if text.strip_prefix("# type: ignore").is_some_and(|suffix| { + suffix.is_empty() || suffix.starts_with(char::is_whitespace) + }) { + suppressions.push(Suppression { suppressed_range }); + } + } + TokenKind::Newline | TokenKind::NonLogicalNewline => { + line_start = token.range().end(); + } + _ => {} } } - suppressions + Suppressions { suppressions } +} + +/// The suppression comments of a single file. +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) struct Suppressions { + /// The suppressions sorted by the suppressed range. + suppressions: IndexVec, +} + +impl Suppressions { + pub(crate) fn find_suppression( + &self, + range: TextRange, + _id: LintId, + ) -> Option { + let enclosing_index = self.enclosing_suppression(range.end())?; + + // TODO(micha): + // * Test if the suppression suppresses the passed lint + + Some(enclosing_index) + } + + fn enclosing_suppression(&self, offset: TextSize) -> Option { + self.suppressions + .binary_search_by(|suppression| { + if suppression.suppressed_range.contains(offset) { + Ordering::Equal + } else if suppression.suppressed_range.end() < offset { + Ordering::Less + } else { + Ordering::Greater + } + }) + .ok() + } +} + +impl std::ops::Index for Suppressions { + type Output = Suppression; + + fn index(&self, index: SuppressionIndex) -> &Self::Output { + &self.suppressions[index] + } } #[newtype_index] pub(crate) struct SuppressionIndex; +/// A `type: ignore` or `knot: ignore` suppression comment. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub(crate) struct Suppression { - target: Option, - kind: SuppressionKind, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub(crate) enum SuppressionKind { - /// A `type: ignore` comment - TypeIgnore, - - /// A `knot: ignore` comment - KnotIgnore, + /// The range for which this suppression applies. + /// Most of the time, this is the range of the comment's line. + /// However, there are few cases where the range gets expanted to + /// cover multiple lines: + /// * multiline strings: `expr + """multiline\nstring""" # type: ignore` + /// * line continuations: `expr \ + "test" # type: ignore` + suppressed_range: TextRange, } diff --git a/crates/red_knot_python_semantic/src/types/context.rs b/crates/red_knot_python_semantic/src/types/context.rs index efd0c651bee7c..9c98fdd6504b9 100644 --- a/crates/red_knot_python_semantic/src/types/context.rs +++ b/crates/red_knot_python_semantic/src/types/context.rs @@ -10,6 +10,7 @@ use ruff_text_size::Ranged; use crate::{ lint::{LintId, LintMetadata}, + suppression::suppressions, Db, }; @@ -74,6 +75,15 @@ impl<'db> InferContext<'db> { return; }; + let suppressions = suppressions(self.db, self.file); + + if suppressions + .find_suppression(node.range(), LintId::of(lint)) + .is_some() + { + return; + } + self.report_diagnostic(node, DiagnosticId::Lint(lint.name()), severity, message); } diff --git a/crates/ruff_index/src/slice.rs b/crates/ruff_index/src/slice.rs index 9b3f9523f7a9c..4702e6a1c4d02 100644 --- a/crates/ruff_index/src/slice.rs +++ b/crates/ruff_index/src/slice.rs @@ -1,5 +1,6 @@ use crate::vec::IndexVec; use crate::Idx; +use std::cmp::Ordering; use std::fmt::{Debug, Formatter}; use std::marker::PhantomData; use std::ops::{Index, IndexMut, Range}; @@ -117,6 +118,29 @@ impl IndexSlice { Err(i) => Err(Idx::new(i)), } } + + #[inline] + pub fn binary_search_by<'a, F>(&'a self, f: F) -> Result + where + F: FnMut(&'a T) -> Ordering, + { + match self.raw.binary_search_by(f) { + Ok(i) => Ok(Idx::new(i)), + Err(i) => Err(Idx::new(i)), + } + } + + #[inline] + pub fn binary_search_by_key<'a, B, F>(&'a self, key: &B, f: F) -> Result + where + F: FnMut(&'a T) -> B, + B: Ord, + { + match self.raw.binary_search_by_key(key, f) { + Ok(i) => Ok(Idx::new(i)), + Err(i) => Err(Idx::new(i)), + } + } } impl Debug for IndexSlice From 95eec8816f48ed297e83995a68e69582fe60e22d Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 19 Dec 2024 10:08:52 +0100 Subject: [PATCH 2/5] Apply suggestions from code review Co-authored-by: Carl Meyer Co-authored-by: Alex Waygood --- .../resources/mdtest/suppressions/type-ignore.md | 5 +++-- crates/red_knot_python_semantic/src/suppression.rs | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/suppressions/type-ignore.md b/crates/red_knot_python_semantic/resources/mdtest/suppressions/type-ignore.md index d7480760e8a98..fbec4a8f39de3 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/suppressions/type-ignore.md +++ b/crates/red_knot_python_semantic/resources/mdtest/suppressions/type-ignore.md @@ -16,9 +16,9 @@ a = ( ) # fmt: skip ``` -## Before opening parentheses +## Before opening parenthesis -A suppression that applies to all errors before the openign parentheses. +A suppression that applies to all errors before the opening parenthesis. ```py a: Test = ( # type: ignore @@ -55,6 +55,7 @@ TODO: We should support this for better interopability with other suppression co ```py # fmt: off +# TODO this error should be suppressed # error: [unresolved-reference] a = test \ + 2 # fmt: skip # type: ignore diff --git a/crates/red_knot_python_semantic/src/suppression.rs b/crates/red_knot_python_semantic/src/suppression.rs index 40b812cbb7b31..d789bb74d280b 100644 --- a/crates/red_knot_python_semantic/src/suppression.rs +++ b/crates/red_knot_python_semantic/src/suppression.rs @@ -31,7 +31,7 @@ pub(crate) fn suppressions(db: &dyn Db, file: File) -> Suppressions { } } TokenKind::Newline | TokenKind::NonLogicalNewline => { - line_start = token.range().end(); + line_start = token.end(); } _ => {} } @@ -92,7 +92,7 @@ pub(crate) struct SuppressionIndex; pub(crate) struct Suppression { /// The range for which this suppression applies. /// Most of the time, this is the range of the comment's line. - /// However, there are few cases where the range gets expanted to + /// However, there are few cases where the range gets expanded to /// cover multiple lines: /// * multiline strings: `expr + """multiline\nstring""" # type: ignore` /// * line continuations: `expr \ + "test" # type: ignore` From 15eb044c93df00a0ee214dc3b895f1c0ff616f58 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 19 Dec 2024 12:06:09 +0100 Subject: [PATCH 3/5] Provisional support for testing the start and end ranges --- .../mdtest/suppressions/type-ignore.md | 53 ++++++++- .../src/suppression.rs | 103 +++++++++++------- crates/red_knot_test/src/lib.rs | 10 +- 3 files changed, 121 insertions(+), 45 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/suppressions/type-ignore.md b/crates/red_knot_python_semantic/resources/mdtest/suppressions/type-ignore.md index fbec4a8f39de3..d54a04fa93e1a 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/suppressions/type-ignore.md +++ b/crates/red_knot_python_semantic/resources/mdtest/suppressions/type-ignore.md @@ -8,14 +8,50 @@ Type check errors can be suppressed by a `type: ignore` comment on the same line a = 4 + test # type: ignore ``` -## In parenthesized expression +## Multiline ranges + +A diagnostic with a multiline range can be suppressed +by a comment on the same line as the diagnostic's start or end. +This is the same behavior as Mypy's. ```py -a = ( - 4 + test # type: ignore -) # fmt: skip +# fmt: off +y = ( + 4 / 0 # type: ignore +) + +y = ( + 4 / # type: ignore + 0 +) + +y = ( + 4 / + 0 # type: ignore +) +``` + +This is different from Pyright where Pyright uses the intersection between +the diagnostic range and any suppression comment. This can be problematic for nested expressions +because a suppression in a child expression now suppresses errors in the outer expression. + +For example, the `type: ignore` comment in this example +suppresses the error of adding `2` to `"test"` and +adding `"other"` to the result of the cast. + +```py path=nested.py +from typing import cast + +y = ( + cast(int, "test" + + 2 # type: ignore + ) + + "other" # TODO: expected-error[invalid-operator] +) ``` +Mypy flags the second usage. + ## Before opening parenthesis A suppression that applies to all errors before the opening parenthesis. @@ -49,6 +85,15 @@ a = test \ + 2 # type: ignore ``` +## Codes + +Mypy supports `type: ignore[code]`. Red Knot doesn't understand mypy's +rule names. Therefore, ignore the codes and suppress all errors. + +```py +a = test # type: ignore[name-defined] +``` + ## Nested comments TODO: We should support this for better interopability with other suppression comments. diff --git a/crates/red_knot_python_semantic/src/suppression.rs b/crates/red_knot_python_semantic/src/suppression.rs index d789bb74d280b..2bc7b2f9f84db 100644 --- a/crates/red_knot_python_semantic/src/suppression.rs +++ b/crates/red_knot_python_semantic/src/suppression.rs @@ -1,11 +1,9 @@ -use std::cmp::Ordering; - use ruff_python_parser::TokenKind; use ruff_source_file::LineRanges; -use ruff_text_size::{Ranged, TextRange, TextSize}; +use ruff_text_size::{Ranged, TextRange}; +use std::ops::RangeBounds; use ruff_db::{files::File, parsed::parsed_module, source::source_text}; -use ruff_index::{newtype_index, IndexVec}; use crate::{lint::LintId, Db}; @@ -14,7 +12,7 @@ pub(crate) fn suppressions(db: &dyn Db, file: File) -> Suppressions { let source = source_text(db.upcast(), file); let parsed = parsed_module(db.upcast(), file); - let mut suppressions = IndexVec::default(); + let mut suppressions = Vec::default(); let mut line_start = source.bom_start_offset(); for token in parsed.tokens() { @@ -25,7 +23,9 @@ pub(crate) fn suppressions(db: &dyn Db, file: File) -> Suppressions { let suppressed_range = TextRange::new(line_start, token.end()); if text.strip_prefix("# type: ignore").is_some_and(|suffix| { - suffix.is_empty() || suffix.starts_with(char::is_whitespace) + suffix.is_empty() + || suffix.starts_with(char::is_whitespace) + || suffix.starts_with('[') }) { suppressions.push(Suppression { suppressed_range }); } @@ -44,49 +44,76 @@ pub(crate) fn suppressions(db: &dyn Db, file: File) -> Suppressions { #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) struct Suppressions { /// The suppressions sorted by the suppressed range. - suppressions: IndexVec, + suppressions: Vec, } impl Suppressions { - pub(crate) fn find_suppression( - &self, - range: TextRange, - _id: LintId, - ) -> Option { - let enclosing_index = self.enclosing_suppression(range.end())?; - + /// Finds a suppression for the specified lint. + /// + /// Returns the first matching suppression if more than one suppression apply for the current line. + pub(crate) fn find_suppression(&self, range: TextRange, _id: LintId) -> Option<&Suppression> { // TODO(micha): // * Test if the suppression suppresses the passed lint - - Some(enclosing_index) + self.for_range(range).next() } - fn enclosing_suppression(&self, offset: TextSize) -> Option { - self.suppressions - .binary_search_by(|suppression| { - if suppression.suppressed_range.contains(offset) { - Ordering::Equal - } else if suppression.suppressed_range.end() < offset { - Ordering::Less - } else { - Ordering::Greater - } + /// Returns all suppression comments that apply for `range`. + /// + /// A suppression applies for the given range if it contains the range's + /// start or end offset. This means the suppression is on the same line + /// as the diagnostic's start or end. + fn for_range(&self, range: TextRange) -> impl Iterator + '_ { + // First find the index of the suppression that starts closest to the range's start. + let start_offset = self + .suppressions + .binary_search_by_key(&range.start(), |suppression| { + suppression.suppressed_range.start() + }) + .unwrap_or_else(|index| index); + + // Search backward for suppressions that start before the range's start + // but overlap with `range`. + // + // ```python + // y = ( + // 4 / 0 # type: ignore + // ^----^ range + // ^--- suppression range --^ + // ) + // ``` + self.suppressions[..start_offset] + .iter() + .rev() + .take_while(move |suppression| range.end() >= suppression.suppressed_range.start()) + .chain( + // Search forward for suppressions that start at or after the range's start + // but overlap with `range`. + // + // ```python + // y = ( + // 4 / + // ^--- range start + // ^------ suppression start + // 0 # type: ignore + // ^- range end ^---suppression end + // ) + // ``` + // + self.suppressions[start_offset..] + .iter() + .take_while(move |suppression| { + range.end() >= suppression.suppressed_range.start() + }), + ) + .filter(move |suppression| { + // Don't use intersect to avoid that suppressions on inner-expression + // ignore errors for outer expressions + suppression.suppressed_range.contains(range.start()) + || suppression.suppressed_range.contains(range.end()) }) - .ok() - } -} - -impl std::ops::Index for Suppressions { - type Output = Suppression; - - fn index(&self, index: SuppressionIndex) -> &Self::Output { - &self.suppressions[index] } } -#[newtype_index] -pub(crate) struct SuppressionIndex; - /// A `type: ignore` or `knot: ignore` suppression comment. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub(crate) struct Suppression { diff --git a/crates/red_knot_test/src/lib.rs b/crates/red_knot_test/src/lib.rs index f2a7639e29ec7..eaa59e6c283d9 100644 --- a/crates/red_knot_test/src/lib.rs +++ b/crates/red_knot_test/src/lib.rs @@ -97,7 +97,11 @@ fn run_test(db: &mut db::Db, test: &parser::MarkdownTest) -> Result<(), Failures let test_files: Vec<_> = test .files() - .map(|embedded| { + .filter_map(|embedded| { + if embedded.lang == "ignore" { + return None; + } + assert!( matches!(embedded.lang, "py" | "pyi"), "Non-Python files not supported yet." @@ -106,10 +110,10 @@ fn run_test(db: &mut db::Db, test: &parser::MarkdownTest) -> Result<(), Failures db.write_file(&full_path, embedded.code).unwrap(); let file = system_path_to_file(db, full_path).unwrap(); - TestFile { + Some(TestFile { file, backtick_offset: embedded.md_offset, - } + }) }) .collect(); From 5e2cd3762824c216b1b12d0c65e4d854ae8f5434 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 19 Dec 2024 12:23:06 +0100 Subject: [PATCH 4/5] Support suppression comments on the start or end of the range --- .../mdtest/suppressions/type-ignore.md | 25 +++++---- .../src/suppression.rs | 51 +++++-------------- 2 files changed, 26 insertions(+), 50 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/suppressions/type-ignore.md b/crates/red_knot_python_semantic/resources/mdtest/suppressions/type-ignore.md index d54a04fa93e1a..23f8ec628fa45 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/suppressions/type-ignore.md +++ b/crates/red_knot_python_semantic/resources/mdtest/suppressions/type-ignore.md @@ -10,9 +10,8 @@ a = 4 + test # type: ignore ## Multiline ranges -A diagnostic with a multiline range can be suppressed -by a comment on the same line as the diagnostic's start or end. -This is the same behavior as Mypy's. +A diagnostic with a multiline range can be suppressed by a comment on the same line as the +diagnostic's start or end. This is the same behavior as Mypy's. ```py # fmt: off @@ -31,16 +30,16 @@ y = ( ) ``` -This is different from Pyright where Pyright uses the intersection between -the diagnostic range and any suppression comment. This can be problematic for nested expressions -because a suppression in a child expression now suppresses errors in the outer expression. +Pyright diverges from this behavior and instead applies a suppression if its range intersects with +the diagnostic range. This can be problematic for nested expressions because a suppression in a +child expression now suppresses errors in the outer expression. -For example, the `type: ignore` comment in this example -suppresses the error of adding `2` to `"test"` and -adding `"other"` to the result of the cast. +For example, the `type: ignore` comment in this example suppresses the error of adding `2` to +`"test"` and adding `"other"` to the result of the cast. ```py path=nested.py -from typing import cast +# fmt: off +from typing import cast y = ( cast(int, "test" + @@ -58,7 +57,7 @@ A suppression that applies to all errors before the opening parenthesis. ```py a: Test = ( # type: ignore - 5 + Test() # error: [unresolved-reference] ) # fmt: skip ``` @@ -87,8 +86,8 @@ a = test \ ## Codes -Mypy supports `type: ignore[code]`. Red Knot doesn't understand mypy's -rule names. Therefore, ignore the codes and suppress all errors. +Mypy supports `type: ignore[code]`. Red Knot doesn't understand mypy's rule names. Therefore, ignore +the codes and suppress all errors. ```py a = test # type: ignore[name-defined] diff --git a/crates/red_knot_python_semantic/src/suppression.rs b/crates/red_knot_python_semantic/src/suppression.rs index 2bc7b2f9f84db..69e266f87cb21 100644 --- a/crates/red_knot_python_semantic/src/suppression.rs +++ b/crates/red_knot_python_semantic/src/suppression.rs @@ -1,7 +1,6 @@ use ruff_python_parser::TokenKind; use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; -use std::ops::RangeBounds; use ruff_db::{files::File, parsed::parsed_module, source::source_text}; @@ -12,6 +11,8 @@ pub(crate) fn suppressions(db: &dyn Db, file: File) -> Suppressions { let source = source_text(db.upcast(), file); let parsed = parsed_module(db.upcast(), file); + // TODO: Support `type: ignore` comments at the + // [start of the file](https://typing.readthedocs.io/en/latest/spec/directives.html#type-ignore-comments). let mut suppressions = Vec::default(); let mut line_start = source.bom_start_offset(); @@ -50,7 +51,9 @@ pub(crate) struct Suppressions { impl Suppressions { /// Finds a suppression for the specified lint. /// - /// Returns the first matching suppression if more than one suppression apply for the current line. + /// Returns the first matching suppression if more than one suppression apply to `range` and `id`. + /// + /// Returns `None` if the lint isn't suppressed. pub(crate) fn find_suppression(&self, range: TextRange, _id: LintId) -> Option<&Suppression> { // TODO(micha): // * Test if the suppression suppresses the passed lint @@ -63,48 +66,22 @@ impl Suppressions { /// start or end offset. This means the suppression is on the same line /// as the diagnostic's start or end. fn for_range(&self, range: TextRange) -> impl Iterator + '_ { - // First find the index of the suppression that starts closest to the range's start. - let start_offset = self + // First find the index of the suppression comment that ends right before the range + // starts. This allows us to skip suppressions that are not relevant for the range. + let end_offset = self .suppressions .binary_search_by_key(&range.start(), |suppression| { - suppression.suppressed_range.start() + suppression.suppressed_range.end() }) .unwrap_or_else(|index| index); - // Search backward for suppressions that start before the range's start - // but overlap with `range`. - // - // ```python - // y = ( - // 4 / 0 # type: ignore - // ^----^ range - // ^--- suppression range --^ - // ) - // ``` - self.suppressions[..start_offset] + // From here, search the remaining suppression comments for one that + // contains the range's start or end offset. Stop the search + // as soon as the suppression's range and the range no longer overlap. + self.suppressions[end_offset..] .iter() - .rev() + // Stop searching if the suppression starts after the range we're looking for. .take_while(move |suppression| range.end() >= suppression.suppressed_range.start()) - .chain( - // Search forward for suppressions that start at or after the range's start - // but overlap with `range`. - // - // ```python - // y = ( - // 4 / - // ^--- range start - // ^------ suppression start - // 0 # type: ignore - // ^- range end ^---suppression end - // ) - // ``` - // - self.suppressions[start_offset..] - .iter() - .take_while(move |suppression| { - range.end() >= suppression.suppressed_range.start() - }), - ) .filter(move |suppression| { // Don't use intersect to avoid that suppressions on inner-expression // ignore errors for outer expressions From bd1145f4d415b376ab6b0729f01de5c18876d52c Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 19 Dec 2024 12:34:12 +0100 Subject: [PATCH 5/5] Discard changes to crates/ruff_index/src/slice.rs --- crates/ruff_index/src/slice.rs | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/crates/ruff_index/src/slice.rs b/crates/ruff_index/src/slice.rs index 4702e6a1c4d02..9b3f9523f7a9c 100644 --- a/crates/ruff_index/src/slice.rs +++ b/crates/ruff_index/src/slice.rs @@ -1,6 +1,5 @@ use crate::vec::IndexVec; use crate::Idx; -use std::cmp::Ordering; use std::fmt::{Debug, Formatter}; use std::marker::PhantomData; use std::ops::{Index, IndexMut, Range}; @@ -118,29 +117,6 @@ impl IndexSlice { Err(i) => Err(Idx::new(i)), } } - - #[inline] - pub fn binary_search_by<'a, F>(&'a self, f: F) -> Result - where - F: FnMut(&'a T) -> Ordering, - { - match self.raw.binary_search_by(f) { - Ok(i) => Ok(Idx::new(i)), - Err(i) => Err(Idx::new(i)), - } - } - - #[inline] - pub fn binary_search_by_key<'a, B, F>(&'a self, key: &B, f: F) -> Result - where - F: FnMut(&'a T) -> B, - B: Ord, - { - match self.raw.binary_search_by_key(key, f) { - Ok(i) => Ok(Idx::new(i)), - Err(i) => Err(Idx::new(i)), - } - } } impl Debug for IndexSlice