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):
|
for i in range(10):
|
||||||
...
|
...
|
||||||
pass
|
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_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::whitespace::trailing_comment_start_offset;
|
use ruff_python_ast::whitespace::trailing_comment_start_offset;
|
||||||
use ruff_python_ast::Stmt;
|
use ruff_python_ast::Stmt;
|
||||||
|
use ruff_python_semantic::{ScopeKind, SemanticModel};
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
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()
|
if expr.value.is_ellipsis_literal_expr()
|
||||||
&& checker.settings.preview.is_enabled() =>
|
&& 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
|
Placeholder::Ellipsis
|
||||||
}
|
}
|
||||||
_ => continue,
|
_ => 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 | ...
|
178 | ...
|
||||||
179 | pass
|
179 | pass
|
||||||
| ^^^^ PIE790
|
| ^^^^ PIE790
|
||||||
|
180 |
|
||||||
|
181 | from typing import Protocol
|
||||||
|
|
|
|
||||||
= help: Remove unnecessary `pass`
|
= help: Remove unnecessary `pass`
|
||||||
|
|
||||||
|
|
@ -481,5 +483,8 @@ PIE790.py:179:5: PIE790 [*] Unnecessary `pass` statement
|
||||||
177 177 | for i in range(10):
|
177 177 | for i in range(10):
|
||||||
178 178 | ...
|
178 178 | ...
|
||||||
179 |- pass
|
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):
|
177 177 | for i in range(10):
|
||||||
178 |- ...
|
178 |- ...
|
||||||
179 178 | pass
|
179 178 | pass
|
||||||
|
180 179 |
|
||||||
|
181 180 | from typing import Protocol
|
||||||
|
|
||||||
PIE790.py:179:5: PIE790 [*] Unnecessary `pass` statement
|
PIE790.py:179:5: PIE790 [*] Unnecessary `pass` statement
|
||||||
|
|
|
|
||||||
|
|
@ -641,6 +643,8 @@ PIE790.py:179:5: PIE790 [*] Unnecessary `pass` statement
|
||||||
178 | ...
|
178 | ...
|
||||||
179 | pass
|
179 | pass
|
||||||
| ^^^^ PIE790
|
| ^^^^ PIE790
|
||||||
|
180 |
|
||||||
|
181 | from typing import Protocol
|
||||||
|
|
|
|
||||||
= help: Remove unnecessary `pass`
|
= help: Remove unnecessary `pass`
|
||||||
|
|
||||||
|
|
@ -649,5 +653,23 @@ PIE790.py:179:5: PIE790 [*] Unnecessary `pass` statement
|
||||||
177 177 | for i in range(10):
|
177 177 | for i in range(10):
|
||||||
178 178 | ...
|
178 178 | ...
|
||||||
179 |- pass
|
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