mirror of https://github.com/astral-sh/ruff
Retain extra ellipses in protocols and abstract methods (#8769)
## Summary It turns out that some type checkers rely on the presence of ellipses in `Protocol` interfaces and abstract methods, in order to differentiate between default implementations and stubs. This PR modifies the preview behavior of `PIE790` to avoid flagging "unnecessary" ellipses in such cases. Closes https://github.com/astral-sh/ruff/issues/8756. ## Test Plan `cargo test`
This commit is contained in:
parent
00a015ca24
commit
95e2f632e6
|
|
@ -177,3 +177,33 @@ for i in range(10):
|
|||
for i in range(10):
|
||||
...
|
||||
pass
|
||||
|
||||
from typing import Protocol
|
||||
|
||||
|
||||
class Repro(Protocol):
|
||||
def func(self) -> str:
|
||||
"""Docstring"""
|
||||
...
|
||||
|
||||
def impl(self) -> str:
|
||||
"""Docstring"""
|
||||
return self.func()
|
||||
|
||||
|
||||
import abc
|
||||
|
||||
|
||||
class Repro:
|
||||
@abc.abstractmethod
|
||||
def func(self) -> str:
|
||||
"""Docstring"""
|
||||
...
|
||||
|
||||
def impl(self) -> str:
|
||||
"""Docstring"""
|
||||
return self.func()
|
||||
|
||||
def stub(self) -> str:
|
||||
"""Docstring"""
|
||||
...
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
|||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::whitespace::trailing_comment_start_offset;
|
||||
use ruff_python_ast::Stmt;
|
||||
use ruff_python_semantic::{ScopeKind, SemanticModel};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
|
@ -93,6 +94,12 @@ pub(crate) fn unnecessary_placeholder(checker: &mut Checker, body: &[Stmt]) {
|
|||
if expr.value.is_ellipsis_literal_expr()
|
||||
&& checker.settings.preview.is_enabled() =>
|
||||
{
|
||||
// Ellipses are significant in protocol methods and abstract methods. Specifically,
|
||||
// Pyright uses the presence of an ellipsis to indicate that a method is a stub,
|
||||
// rather than a default implementation.
|
||||
if in_protocol_or_abstract_method(checker.semantic()) {
|
||||
return;
|
||||
}
|
||||
Placeholder::Ellipsis
|
||||
}
|
||||
_ => continue,
|
||||
|
|
@ -125,3 +132,21 @@ impl std::fmt::Display for Placeholder {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if the [`SemanticModel`] is in a `typing.Protocol` subclass or an abstract
|
||||
/// method.
|
||||
fn in_protocol_or_abstract_method(semantic: &SemanticModel) -> bool {
|
||||
semantic.current_scopes().any(|scope| match scope.kind {
|
||||
ScopeKind::Class(class_def) => class_def
|
||||
.bases()
|
||||
.iter()
|
||||
.any(|base| semantic.match_typing_expr(base, "Protocol")),
|
||||
ScopeKind::Function(function_def) => {
|
||||
ruff_python_semantic::analyze::visibility::is_abstract(
|
||||
&function_def.decorator_list,
|
||||
semantic,
|
||||
)
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -473,6 +473,8 @@ PIE790.py:179:5: PIE790 [*] Unnecessary `pass` statement
|
|||
178 | ...
|
||||
179 | pass
|
||||
| ^^^^ PIE790
|
||||
180 |
|
||||
181 | from typing import Protocol
|
||||
|
|
||||
= help: Remove unnecessary `pass`
|
||||
|
||||
|
|
@ -481,5 +483,8 @@ PIE790.py:179:5: PIE790 [*] Unnecessary `pass` statement
|
|||
177 177 | for i in range(10):
|
||||
178 178 | ...
|
||||
179 |- pass
|
||||
180 179 |
|
||||
181 180 | from typing import Protocol
|
||||
182 181 |
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -634,6 +634,8 @@ PIE790.py:178:5: PIE790 [*] Unnecessary `...` literal
|
|||
177 177 | for i in range(10):
|
||||
178 |- ...
|
||||
179 178 | pass
|
||||
180 179 |
|
||||
181 180 | from typing import Protocol
|
||||
|
||||
PIE790.py:179:5: PIE790 [*] Unnecessary `pass` statement
|
||||
|
|
||||
|
|
@ -641,6 +643,8 @@ PIE790.py:179:5: PIE790 [*] Unnecessary `pass` statement
|
|||
178 | ...
|
||||
179 | pass
|
||||
| ^^^^ PIE790
|
||||
180 |
|
||||
181 | from typing import Protocol
|
||||
|
|
||||
= help: Remove unnecessary `pass`
|
||||
|
||||
|
|
@ -649,5 +653,23 @@ PIE790.py:179:5: PIE790 [*] Unnecessary `pass` statement
|
|||
177 177 | for i in range(10):
|
||||
178 178 | ...
|
||||
179 |- pass
|
||||
180 179 |
|
||||
181 180 | from typing import Protocol
|
||||
182 181 |
|
||||
|
||||
PIE790.py:209:9: PIE790 [*] Unnecessary `...` literal
|
||||
|
|
||||
207 | def stub(self) -> str:
|
||||
208 | """Docstring"""
|
||||
209 | ...
|
||||
| ^^^ PIE790
|
||||
|
|
||||
= help: Remove unnecessary `...`
|
||||
|
||||
ℹ Safe fix
|
||||
206 206 |
|
||||
207 207 | def stub(self) -> str:
|
||||
208 208 | """Docstring"""
|
||||
209 |- ...
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue