Compare commits
1 Commits
jack/newty
...
brent/ty-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5810961b7f |
@@ -34,7 +34,7 @@ task_group()
|
||||
setup()
|
||||
from airflow.decorators import teardown
|
||||
from airflow.io.path import ObjectStoragePath
|
||||
from airflow.io.store import attach
|
||||
from airflow.io.storage import attach
|
||||
from airflow.models import DAG as DAGFromModel
|
||||
from airflow.models import (
|
||||
Connection,
|
||||
@@ -74,36 +74,3 @@ DatasetOrTimeSchedule()
|
||||
|
||||
# airflow.utils.dag_parsing_context
|
||||
get_parsing_context()
|
||||
|
||||
from airflow.decorators.base import (
|
||||
DecoratedMappedOperator,
|
||||
DecoratedOperator,
|
||||
TaskDecorator,
|
||||
get_unique_task_id,
|
||||
task_decorator_factory,
|
||||
)
|
||||
|
||||
# airflow.decorators.base
|
||||
DecoratedMappedOperator()
|
||||
DecoratedOperator()
|
||||
TaskDecorator()
|
||||
get_unique_task_id()
|
||||
task_decorator_factory()
|
||||
|
||||
|
||||
from airflow.models import Param
|
||||
|
||||
# airflow.models
|
||||
Param()
|
||||
|
||||
|
||||
from airflow.sensors.base import (
|
||||
BaseSensorOperator,
|
||||
PokeReturnValue,
|
||||
poke_mode_only,
|
||||
)
|
||||
|
||||
# airflow.sensors.base
|
||||
BaseSensorOperator()
|
||||
PokeReturnValue()
|
||||
poke_mode_only()
|
||||
|
||||
@@ -9,6 +9,7 @@ from airflow.operators.empty import EmptyOperator
|
||||
from airflow.operators.latest_only import LatestOnlyOperator
|
||||
from airflow.operators.trigger_dagrun import TriggerDagRunOperator
|
||||
from airflow.operators.weekday import BranchDayOfWeekOperator
|
||||
from airflow.sensors.date_time import DateTimeSensor
|
||||
|
||||
FSHook()
|
||||
PackageIndexHook()
|
||||
@@ -21,6 +22,7 @@ EmptyOperator()
|
||||
|
||||
LatestOnlyOperator()
|
||||
BranchDayOfWeekOperator()
|
||||
DateTimeSensor()
|
||||
|
||||
from airflow.operators.python import (
|
||||
BranchPythonOperator,
|
||||
@@ -28,23 +30,16 @@ from airflow.operators.python import (
|
||||
PythonVirtualenvOperator,
|
||||
ShortCircuitOperator,
|
||||
)
|
||||
from airflow.sensors.bash import BashSensor
|
||||
from airflow.sensors.date_time import DateTimeSensor
|
||||
|
||||
BranchPythonOperator()
|
||||
PythonOperator()
|
||||
PythonVirtualenvOperator()
|
||||
ShortCircuitOperator()
|
||||
|
||||
BashSensor()
|
||||
DateTimeSensor()
|
||||
from airflow.sensors.date_time import DateTimeSensorAsync
|
||||
from airflow.sensors.external_task import (
|
||||
ExternalTaskMarker,
|
||||
ExternalTaskSensor,
|
||||
)
|
||||
from airflow.sensors.time_sensor import (
|
||||
TimeSensor,
|
||||
TimeSensorAsync,
|
||||
)
|
||||
from airflow.sensors.filesystem import FileSensor
|
||||
from airflow.sensors.python import PythonSensor
|
||||
|
||||
BranchPythonOperator()
|
||||
PythonOperator()
|
||||
@@ -54,13 +49,6 @@ DateTimeSensorAsync()
|
||||
ExternalTaskMarker()
|
||||
ExternalTaskSensor()
|
||||
FileSensor()
|
||||
PythonSensor()
|
||||
|
||||
from airflow.sensors.time_sensor import (
|
||||
TimeSensor,
|
||||
TimeSensorAsync,
|
||||
)
|
||||
|
||||
TimeSensor()
|
||||
TimeSensorAsync()
|
||||
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
def foo():
|
||||
client = httpx.Client()
|
||||
client.close() # Ok
|
||||
client.delete() # Ok
|
||||
client.get() # Ok
|
||||
client.head() # Ok
|
||||
client.options() # Ok
|
||||
client.patch() # Ok
|
||||
client.post() # Ok
|
||||
client.put() # Ok
|
||||
client.request() # Ok
|
||||
client.send() # Ok
|
||||
client.stream() # Ok
|
||||
|
||||
client.anything() # Ok
|
||||
client.build_request() # Ok
|
||||
client.is_closed # Ok
|
||||
|
||||
|
||||
async def foo():
|
||||
client = httpx.Client()
|
||||
client.close() # ASYNC212
|
||||
client.delete() # ASYNC212
|
||||
client.get() # ASYNC212
|
||||
client.head() # ASYNC212
|
||||
client.options() # ASYNC212
|
||||
client.patch() # ASYNC212
|
||||
client.post() # ASYNC212
|
||||
client.put() # ASYNC212
|
||||
client.request() # ASYNC212
|
||||
client.send() # ASYNC212
|
||||
client.stream() # ASYNC212
|
||||
|
||||
client.anything() # Ok
|
||||
client.build_request() # Ok
|
||||
client.is_closed # Ok
|
||||
|
||||
|
||||
async def foo(client: httpx.Client):
|
||||
client.request() # ASYNC212
|
||||
client.anything() # Ok
|
||||
|
||||
|
||||
async def foo(client: httpx.Client | None):
|
||||
client.request() # ASYNC212
|
||||
client.anything() # Ok
|
||||
|
||||
|
||||
async def foo(client: Optional[httpx.Client]):
|
||||
client.request() # ASYNC212
|
||||
client.anything() # Ok
|
||||
|
||||
|
||||
async def foo():
|
||||
client: httpx.Client = ...
|
||||
client.request() # ASYNC212
|
||||
client.anything() # Ok
|
||||
|
||||
|
||||
global_client = httpx.Client()
|
||||
|
||||
|
||||
async def foo():
|
||||
global_client.request() # ASYNC212
|
||||
global_client.anything() # Ok
|
||||
|
||||
|
||||
async def foo():
|
||||
async with httpx.AsyncClient() as client:
|
||||
await client.get() # Ok
|
||||
@@ -17,50 +17,3 @@ info(f"{__name__}")
|
||||
# Don't trigger for t-strings
|
||||
info(t"{name}")
|
||||
info(t"{__name__}")
|
||||
|
||||
count = 5
|
||||
total = 9
|
||||
directory_path = "/home/hamir/ruff/crates/ruff_linter/resources/test/"
|
||||
logging.info(f"{count} out of {total} files in {directory_path} checked")
|
||||
|
||||
|
||||
|
||||
x = 99
|
||||
fmt = "08d"
|
||||
logger.info(f"{x:{'08d'}}")
|
||||
logger.info(f"{x:>10} {x:{fmt}}")
|
||||
|
||||
logging.info(f"")
|
||||
logging.info(f"This message doesn't have any variables.")
|
||||
|
||||
obj = {"key": "value"}
|
||||
logging.info(f"Object: {obj!r}")
|
||||
|
||||
items_count = 3
|
||||
logging.warning(f"Items: {items_count:d}")
|
||||
|
||||
data = {"status": "active"}
|
||||
logging.info(f"Processing {len(data)} items")
|
||||
logging.info(f"Status: {data.get('status', 'unknown').upper()}")
|
||||
|
||||
|
||||
result = 123
|
||||
logging.info(f"Calculated result: {result + 100}")
|
||||
|
||||
temperature = 123
|
||||
logging.info(f"Temperature: {temperature:.1f}°C")
|
||||
|
||||
class FilePath:
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
|
||||
logging.info(f"No changes made to {file_path.name}.")
|
||||
|
||||
user = "tron"
|
||||
balance = 123.45
|
||||
logging.error(f"Error {404}: User {user} has insufficient balance ${balance:.2f}")
|
||||
|
||||
import logging
|
||||
|
||||
x = 1
|
||||
logging.error(f"{x} -> %s", x)
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
"""Test f-string argument order."""
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
X = 1
|
||||
Y = 2
|
||||
logger.error(f"{X} -> %s", Y)
|
||||
logger.error(f"{Y} -> %s", X)
|
||||
@@ -151,39 +151,3 @@ def f():
|
||||
pass
|
||||
except Exception as _:
|
||||
pass
|
||||
|
||||
|
||||
# OK, `__class__` in this case is not the special `__class__` cell, so we don't
|
||||
# emit a diagnostic. (It has its own special semantics -- see
|
||||
# https://github.com/astral-sh/ruff/pull/20048#discussion_r2298338048 -- but
|
||||
# those aren't relevant here.)
|
||||
class A:
|
||||
__class__ = 1
|
||||
|
||||
|
||||
# The following three cases are flagged because they declare local `__class__`
|
||||
# variables that don't refer to the special `__class__` cell.
|
||||
class A:
|
||||
def set_class(self, cls):
|
||||
__class__ = cls # F841
|
||||
|
||||
|
||||
class A:
|
||||
class B:
|
||||
def set_class(self, cls):
|
||||
__class__ = cls # F841
|
||||
|
||||
|
||||
class A:
|
||||
def foo():
|
||||
class B:
|
||||
print(__class__)
|
||||
def set_class(self, cls):
|
||||
__class__ = cls # F841
|
||||
|
||||
|
||||
# OK, the `__class__` cell is nonlocal and declared as such.
|
||||
class NonlocalDunderClass:
|
||||
def foo():
|
||||
nonlocal __class__
|
||||
__class__ = 1
|
||||
|
||||
@@ -44,8 +44,3 @@ def f():
|
||||
def g():
|
||||
nonlocal x
|
||||
x = 2
|
||||
|
||||
# OK
|
||||
class A:
|
||||
def method(self):
|
||||
nonlocal __class__
|
||||
|
||||
@@ -124,19 +124,3 @@ def fun_with_python_syntax():
|
||||
...
|
||||
|
||||
return Foo
|
||||
|
||||
|
||||
@dataclass
|
||||
class C:
|
||||
def __post_init__(self, x: tuple[int, ...] = (
|
||||
1,
|
||||
2,
|
||||
)) -> None:
|
||||
self.x = x
|
||||
|
||||
|
||||
@dataclass
|
||||
class D:
|
||||
def __post_init__(self, x: int = """
|
||||
""") -> None:
|
||||
self.x = x
|
||||
|
||||
@@ -660,9 +660,6 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
if checker.is_rule_enabled(Rule::BlockingHttpCallInAsyncFunction) {
|
||||
flake8_async::rules::blocking_http_call(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::BlockingHttpCallHttpxInAsyncFunction) {
|
||||
flake8_async::rules::blocking_http_call_httpx(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::BlockingOpenCallInAsyncFunction) {
|
||||
flake8_async::rules::blocking_open_call(checker, call);
|
||||
}
|
||||
@@ -1049,6 +1046,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
Rule::PyPath,
|
||||
Rule::Glob,
|
||||
Rule::OsListdir,
|
||||
Rule::OsSymlink,
|
||||
]) {
|
||||
flake8_use_pathlib::rules::replaceable_by_pathlib(checker, call);
|
||||
}
|
||||
|
||||
@@ -703,10 +703,7 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
match scope.kind {
|
||||
ScopeKind::Class(_) | ScopeKind::Lambda(_) => return false,
|
||||
ScopeKind::Function(ast::StmtFunctionDef { is_async, .. }) => return *is_async,
|
||||
ScopeKind::Generator { .. }
|
||||
| ScopeKind::Module
|
||||
| ScopeKind::Type
|
||||
| ScopeKind::DunderClassCell => {}
|
||||
ScopeKind::Generator { .. } | ScopeKind::Module | ScopeKind::Type => {}
|
||||
}
|
||||
}
|
||||
false
|
||||
@@ -717,10 +714,7 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
match scope.kind {
|
||||
ScopeKind::Class(_) => return false,
|
||||
ScopeKind::Function(_) | ScopeKind::Lambda(_) => return true,
|
||||
ScopeKind::Generator { .. }
|
||||
| ScopeKind::Module
|
||||
| ScopeKind::Type
|
||||
| ScopeKind::DunderClassCell => {}
|
||||
ScopeKind::Generator { .. } | ScopeKind::Module | ScopeKind::Type => {}
|
||||
}
|
||||
}
|
||||
false
|
||||
@@ -731,7 +725,7 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
match scope.kind {
|
||||
ScopeKind::Class(_) | ScopeKind::Generator { .. } => return false,
|
||||
ScopeKind::Function(_) | ScopeKind::Lambda(_) => return true,
|
||||
ScopeKind::Module | ScopeKind::Type | ScopeKind::DunderClassCell => {}
|
||||
ScopeKind::Module | ScopeKind::Type => {}
|
||||
}
|
||||
}
|
||||
false
|
||||
@@ -1098,24 +1092,6 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
// Here we add the implicit scope surrounding a method which allows code in the
|
||||
// method to access `__class__` at runtime. See the `ScopeKind::DunderClassCell`
|
||||
// docs for more information.
|
||||
let added_dunder_class_scope = if self.semantic.current_scope().kind.is_class() {
|
||||
self.semantic.push_scope(ScopeKind::DunderClassCell);
|
||||
let binding_id = self.semantic.push_binding(
|
||||
TextRange::default(),
|
||||
BindingKind::DunderClassCell,
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
self.semantic
|
||||
.current_scope_mut()
|
||||
.add("__class__", binding_id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
self.semantic.push_scope(ScopeKind::Type);
|
||||
|
||||
if let Some(type_params) = type_params {
|
||||
@@ -1179,9 +1155,6 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
self.semantic.pop_scope(); // Function scope
|
||||
self.semantic.pop_definition();
|
||||
self.semantic.pop_scope(); // Type parameter scope
|
||||
if added_dunder_class_scope {
|
||||
self.semantic.pop_scope(); // `__class__` cell closure scope
|
||||
}
|
||||
self.add_binding(
|
||||
name,
|
||||
stmt.identifier(),
|
||||
|
||||
@@ -336,7 +336,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Async, "115") => (RuleGroup::Stable, rules::flake8_async::rules::AsyncZeroSleep),
|
||||
(Flake8Async, "116") => (RuleGroup::Preview, rules::flake8_async::rules::LongSleepNotForever),
|
||||
(Flake8Async, "210") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingHttpCallInAsyncFunction),
|
||||
(Flake8Async, "212") => (RuleGroup::Preview, rules::flake8_async::rules::BlockingHttpCallHttpxInAsyncFunction),
|
||||
(Flake8Async, "220") => (RuleGroup::Stable, rules::flake8_async::rules::CreateSubprocessInAsyncFunction),
|
||||
(Flake8Async, "221") => (RuleGroup::Stable, rules::flake8_async::rules::RunProcessInAsyncFunction),
|
||||
(Flake8Async, "222") => (RuleGroup::Stable, rules::flake8_async::rules::WaitForProcessInAsyncFunction),
|
||||
|
||||
@@ -40,11 +40,6 @@ pub(crate) const fn is_bad_version_info_in_non_stub_enabled(settings: &LinterSet
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
/// <https://github.com/astral-sh/ruff/pull/19303>
|
||||
pub(crate) const fn is_fix_f_string_logging_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/16719
|
||||
pub(crate) const fn is_fix_manual_dict_comprehension_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
|
||||
@@ -354,10 +354,7 @@ impl Renamer {
|
||||
))
|
||||
}
|
||||
// Avoid renaming builtins and other "special" bindings.
|
||||
BindingKind::FutureImport
|
||||
| BindingKind::Builtin
|
||||
| BindingKind::Export(_)
|
||||
| BindingKind::DunderClassCell => None,
|
||||
BindingKind::FutureImport | BindingKind::Builtin | BindingKind::Export(_) => None,
|
||||
// By default, replace the binding's name with the target name.
|
||||
BindingKind::Annotation
|
||||
| BindingKind::Argument
|
||||
|
||||
@@ -215,12 +215,6 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan
|
||||
version: "0.0.1",
|
||||
}
|
||||
}
|
||||
["airflow", "sensors", "bash", "BashSensor"] => ProviderReplacement::AutoImport {
|
||||
module: "airflow.providers.standard.sensor.bash",
|
||||
name: "BashSensor",
|
||||
provider: "standard",
|
||||
version: "0.0.1",
|
||||
},
|
||||
[
|
||||
"airflow",
|
||||
"sensors",
|
||||
@@ -249,12 +243,6 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan
|
||||
provider: "standard",
|
||||
version: "0.0.2",
|
||||
},
|
||||
["airflow", "sensors", "python", "PythonSensor"] => ProviderReplacement::AutoImport {
|
||||
module: "airflow.providers.standard.sensors.python",
|
||||
name: "PythonSensor",
|
||||
provider: "standard",
|
||||
version: "0.0.1",
|
||||
},
|
||||
[
|
||||
"airflow",
|
||||
"sensors",
|
||||
|
||||
@@ -227,26 +227,13 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
module: "airflow.sdk",
|
||||
name: (*rest).to_string(),
|
||||
},
|
||||
[
|
||||
"airflow",
|
||||
"decorators",
|
||||
"base",
|
||||
rest @ ("DecoratedMappedOperator"
|
||||
| "DecoratedOperator"
|
||||
| "TaskDecorator"
|
||||
| "get_unique_task_id"
|
||||
| "task_decorator_factory"),
|
||||
] => Replacement::SourceModuleMoved {
|
||||
module: "airflow.sdk.bases.decorator",
|
||||
name: (*rest).to_string(),
|
||||
},
|
||||
|
||||
// airflow.io
|
||||
["airflow", "io", "path", "ObjectStoragePath"] => Replacement::SourceModuleMoved {
|
||||
module: "airflow.sdk",
|
||||
name: "ObjectStoragePath".to_string(),
|
||||
},
|
||||
["airflow", "io", "store", "attach"] => Replacement::SourceModuleMoved {
|
||||
["airflow", "io", "storage", "attach"] => Replacement::SourceModuleMoved {
|
||||
module: "airflow.sdk.io",
|
||||
name: "attach".to_string(),
|
||||
},
|
||||
@@ -258,10 +245,6 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
name: (*rest).to_string(),
|
||||
}
|
||||
}
|
||||
["airflow", "models", "Param"] => Replacement::AutoImport {
|
||||
module: "airflow.sdk.definitions.param",
|
||||
name: "Param",
|
||||
},
|
||||
|
||||
// airflow.models.baseoperator
|
||||
[
|
||||
@@ -277,30 +260,16 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
module: "airflow.sdk",
|
||||
name: "BaseOperatorLink",
|
||||
},
|
||||
|
||||
// airflow.model..DAG
|
||||
["airflow", "models", .., "DAG"] => Replacement::SourceModuleMoved {
|
||||
module: "airflow.sdk",
|
||||
name: "DAG".to_string(),
|
||||
},
|
||||
|
||||
// airflow.sensors.base
|
||||
[
|
||||
"airflow",
|
||||
"sensors",
|
||||
"base",
|
||||
rest @ ("BaseSensorOperator" | "PokeReturnValue" | "poke_mode_only"),
|
||||
] => Replacement::SourceModuleMoved {
|
||||
module: "airflow.sdk",
|
||||
name: (*rest).to_string(),
|
||||
},
|
||||
|
||||
// airflow.timetables
|
||||
["airflow", "timetables", "datasets", "DatasetOrTimeSchedule"] => Replacement::AutoImport {
|
||||
module: "airflow.timetables.assets",
|
||||
name: "AssetOrTimeSchedule",
|
||||
},
|
||||
|
||||
// airflow.utils
|
||||
[
|
||||
"airflow",
|
||||
|
||||
@@ -312,7 +312,7 @@ help: Use `teardown` from `airflow.sdk` instead.
|
||||
34 34 | setup()
|
||||
35 |-from airflow.decorators import teardown
|
||||
36 35 | from airflow.io.path import ObjectStoragePath
|
||||
37 36 | from airflow.io.store import attach
|
||||
37 36 | from airflow.io.storage import attach
|
||||
38 37 | from airflow.models import DAG as DAGFromModel
|
||||
--------------------------------------------------------------------------------
|
||||
43 42 | from airflow.models.baseoperator import chain, chain_linear, cross_downstream
|
||||
@@ -338,7 +338,7 @@ help: Use `ObjectStoragePath` from `airflow.sdk` instead.
|
||||
34 34 | setup()
|
||||
35 35 | from airflow.decorators import teardown
|
||||
36 |-from airflow.io.path import ObjectStoragePath
|
||||
37 36 | from airflow.io.store import attach
|
||||
37 36 | from airflow.io.storage import attach
|
||||
38 37 | from airflow.models import DAG as DAGFromModel
|
||||
39 38 | from airflow.models import (
|
||||
--------------------------------------------------------------------------------
|
||||
@@ -350,7 +350,7 @@ help: Use `ObjectStoragePath` from `airflow.sdk` instead.
|
||||
47 47 | # airflow.decorators
|
||||
48 48 | teardown()
|
||||
|
||||
AIR311 [*] `airflow.io.store.attach` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
AIR311 [*] `airflow.io.storage.attach` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
--> AIR311_names.py:52:1
|
||||
|
|
||||
50 | # # airflow.io
|
||||
@@ -366,7 +366,7 @@ help: Use `attach` from `airflow.sdk.io` instead.
|
||||
34 34 | setup()
|
||||
35 35 | from airflow.decorators import teardown
|
||||
36 36 | from airflow.io.path import ObjectStoragePath
|
||||
37 |-from airflow.io.store import attach
|
||||
37 |-from airflow.io.storage import attach
|
||||
38 37 | from airflow.models import DAG as DAGFromModel
|
||||
39 38 | from airflow.models import (
|
||||
40 39 | Connection,
|
||||
@@ -391,7 +391,7 @@ AIR311 [*] `airflow.models.Connection` is removed in Airflow 3.0; It still works
|
||||
help: Use `Connection` from `airflow.sdk` instead.
|
||||
|
||||
ℹ Unsafe fix
|
||||
37 37 | from airflow.io.store import attach
|
||||
37 37 | from airflow.io.storage import attach
|
||||
38 38 | from airflow.models import DAG as DAGFromModel
|
||||
39 39 | from airflow.models import (
|
||||
40 |- Connection,
|
||||
@@ -614,8 +614,6 @@ AIR311 [*] `airflow.utils.dag_parsing_context.get_parsing_context` is removed in
|
||||
75 | # airflow.utils.dag_parsing_context
|
||||
76 | get_parsing_context()
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
77 |
|
||||
78 | from airflow.decorators.base import (
|
||||
|
|
||||
help: Use `get_parsing_context` from `airflow.sdk` instead.
|
||||
|
||||
@@ -628,211 +626,3 @@ help: Use `get_parsing_context` from `airflow.sdk` instead.
|
||||
71 71 |
|
||||
72 72 | # airflow.timetables.datasets
|
||||
73 73 | DatasetOrTimeSchedule()
|
||||
|
||||
AIR311 [*] `airflow.decorators.base.DecoratedMappedOperator` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
--> AIR311_names.py:87:1
|
||||
|
|
||||
86 | # airflow.decorators.base
|
||||
87 | DecoratedMappedOperator()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
88 | DecoratedOperator()
|
||||
89 | TaskDecorator()
|
||||
|
|
||||
help: Use `DecoratedMappedOperator` from `airflow.sdk.bases.decorator` instead.
|
||||
|
||||
ℹ Unsafe fix
|
||||
76 76 | get_parsing_context()
|
||||
77 77 |
|
||||
78 78 | from airflow.decorators.base import (
|
||||
79 |- DecoratedMappedOperator,
|
||||
80 79 | DecoratedOperator,
|
||||
81 80 | TaskDecorator,
|
||||
82 81 | get_unique_task_id,
|
||||
83 82 | task_decorator_factory,
|
||||
84 83 | )
|
||||
84 |+from airflow.sdk.bases.decorator import DecoratedMappedOperator
|
||||
85 85 |
|
||||
86 86 | # airflow.decorators.base
|
||||
87 87 | DecoratedMappedOperator()
|
||||
|
||||
AIR311 [*] `airflow.decorators.base.DecoratedOperator` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
--> AIR311_names.py:88:1
|
||||
|
|
||||
86 | # airflow.decorators.base
|
||||
87 | DecoratedMappedOperator()
|
||||
88 | DecoratedOperator()
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
89 | TaskDecorator()
|
||||
90 | get_unique_task_id()
|
||||
|
|
||||
help: Use `DecoratedOperator` from `airflow.sdk.bases.decorator` instead.
|
||||
|
||||
ℹ Unsafe fix
|
||||
77 77 |
|
||||
78 78 | from airflow.decorators.base import (
|
||||
79 79 | DecoratedMappedOperator,
|
||||
80 |- DecoratedOperator,
|
||||
81 80 | TaskDecorator,
|
||||
82 81 | get_unique_task_id,
|
||||
83 82 | task_decorator_factory,
|
||||
84 83 | )
|
||||
84 |+from airflow.sdk.bases.decorator import DecoratedOperator
|
||||
85 85 |
|
||||
86 86 | # airflow.decorators.base
|
||||
87 87 | DecoratedMappedOperator()
|
||||
|
||||
AIR311 [*] `airflow.decorators.base.TaskDecorator` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
--> AIR311_names.py:89:1
|
||||
|
|
||||
87 | DecoratedMappedOperator()
|
||||
88 | DecoratedOperator()
|
||||
89 | TaskDecorator()
|
||||
| ^^^^^^^^^^^^^
|
||||
90 | get_unique_task_id()
|
||||
91 | task_decorator_factory()
|
||||
|
|
||||
help: Use `TaskDecorator` from `airflow.sdk.bases.decorator` instead.
|
||||
|
||||
ℹ Unsafe fix
|
||||
78 78 | from airflow.decorators.base import (
|
||||
79 79 | DecoratedMappedOperator,
|
||||
80 80 | DecoratedOperator,
|
||||
81 |- TaskDecorator,
|
||||
82 81 | get_unique_task_id,
|
||||
83 82 | task_decorator_factory,
|
||||
84 83 | )
|
||||
84 |+from airflow.sdk.bases.decorator import TaskDecorator
|
||||
85 85 |
|
||||
86 86 | # airflow.decorators.base
|
||||
87 87 | DecoratedMappedOperator()
|
||||
|
||||
AIR311 [*] `airflow.decorators.base.get_unique_task_id` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
--> AIR311_names.py:90:1
|
||||
|
|
||||
88 | DecoratedOperator()
|
||||
89 | TaskDecorator()
|
||||
90 | get_unique_task_id()
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
91 | task_decorator_factory()
|
||||
|
|
||||
help: Use `get_unique_task_id` from `airflow.sdk.bases.decorator` instead.
|
||||
|
||||
ℹ Unsafe fix
|
||||
79 79 | DecoratedMappedOperator,
|
||||
80 80 | DecoratedOperator,
|
||||
81 81 | TaskDecorator,
|
||||
82 |- get_unique_task_id,
|
||||
83 82 | task_decorator_factory,
|
||||
84 83 | )
|
||||
84 |+from airflow.sdk.bases.decorator import get_unique_task_id
|
||||
85 85 |
|
||||
86 86 | # airflow.decorators.base
|
||||
87 87 | DecoratedMappedOperator()
|
||||
|
||||
AIR311 [*] `airflow.decorators.base.task_decorator_factory` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
--> AIR311_names.py:91:1
|
||||
|
|
||||
89 | TaskDecorator()
|
||||
90 | get_unique_task_id()
|
||||
91 | task_decorator_factory()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Use `task_decorator_factory` from `airflow.sdk.bases.decorator` instead.
|
||||
|
||||
ℹ Unsafe fix
|
||||
80 80 | DecoratedOperator,
|
||||
81 81 | TaskDecorator,
|
||||
82 82 | get_unique_task_id,
|
||||
83 |- task_decorator_factory,
|
||||
84 83 | )
|
||||
84 |+from airflow.sdk.bases.decorator import task_decorator_factory
|
||||
85 85 |
|
||||
86 86 | # airflow.decorators.base
|
||||
87 87 | DecoratedMappedOperator()
|
||||
|
||||
AIR311 [*] `airflow.models.Param` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
--> AIR311_names.py:97:1
|
||||
|
|
||||
96 | # airflow.models
|
||||
97 | Param()
|
||||
| ^^^^^
|
||||
|
|
||||
help: Use `Param` from `airflow.sdk.definitions.param` instead.
|
||||
|
||||
ℹ Unsafe fix
|
||||
91 91 | task_decorator_factory()
|
||||
92 92 |
|
||||
93 93 |
|
||||
94 |-from airflow.models import Param
|
||||
94 |+from airflow.sdk.definitions.param import Param
|
||||
95 95 |
|
||||
96 96 | # airflow.models
|
||||
97 97 | Param()
|
||||
|
||||
AIR311 [*] `airflow.sensors.base.BaseSensorOperator` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
--> AIR311_names.py:107:1
|
||||
|
|
||||
106 | # airflow.sensors.base
|
||||
107 | BaseSensorOperator()
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
108 | PokeReturnValue()
|
||||
109 | poke_mode_only()
|
||||
|
|
||||
help: Use `BaseSensorOperator` from `airflow.sdk` instead.
|
||||
|
||||
ℹ Unsafe fix
|
||||
98 98 |
|
||||
99 99 |
|
||||
100 100 | from airflow.sensors.base import (
|
||||
101 |- BaseSensorOperator,
|
||||
102 101 | PokeReturnValue,
|
||||
103 102 | poke_mode_only,
|
||||
104 103 | )
|
||||
104 |+from airflow.sdk import BaseSensorOperator
|
||||
105 105 |
|
||||
106 106 | # airflow.sensors.base
|
||||
107 107 | BaseSensorOperator()
|
||||
|
||||
AIR311 [*] `airflow.sensors.base.PokeReturnValue` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
--> AIR311_names.py:108:1
|
||||
|
|
||||
106 | # airflow.sensors.base
|
||||
107 | BaseSensorOperator()
|
||||
108 | PokeReturnValue()
|
||||
| ^^^^^^^^^^^^^^^
|
||||
109 | poke_mode_only()
|
||||
|
|
||||
help: Use `PokeReturnValue` from `airflow.sdk` instead.
|
||||
|
||||
ℹ Unsafe fix
|
||||
99 99 |
|
||||
100 100 | from airflow.sensors.base import (
|
||||
101 101 | BaseSensorOperator,
|
||||
102 |- PokeReturnValue,
|
||||
103 102 | poke_mode_only,
|
||||
104 103 | )
|
||||
104 |+from airflow.sdk import PokeReturnValue
|
||||
105 105 |
|
||||
106 106 | # airflow.sensors.base
|
||||
107 107 | BaseSensorOperator()
|
||||
|
||||
AIR311 [*] `airflow.sensors.base.poke_mode_only` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
--> AIR311_names.py:109:1
|
||||
|
|
||||
107 | BaseSensorOperator()
|
||||
108 | PokeReturnValue()
|
||||
109 | poke_mode_only()
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Use `poke_mode_only` from `airflow.sdk` instead.
|
||||
|
||||
ℹ Unsafe fix
|
||||
100 100 | from airflow.sensors.base import (
|
||||
101 101 | BaseSensorOperator,
|
||||
102 102 | PokeReturnValue,
|
||||
103 |- poke_mode_only,
|
||||
104 103 | )
|
||||
104 |+from airflow.sdk import poke_mode_only
|
||||
105 105 |
|
||||
106 106 | # airflow.sensors.base
|
||||
107 107 | BaseSensorOperator()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,6 @@ mod tests {
|
||||
#[test_case(Rule::AsyncZeroSleep, Path::new("ASYNC115.py"))]
|
||||
#[test_case(Rule::LongSleepNotForever, Path::new("ASYNC116.py"))]
|
||||
#[test_case(Rule::BlockingHttpCallInAsyncFunction, Path::new("ASYNC210.py"))]
|
||||
#[test_case(Rule::BlockingHttpCallHttpxInAsyncFunction, Path::new("ASYNC212.py"))]
|
||||
#[test_case(Rule::CreateSubprocessInAsyncFunction, Path::new("ASYNC22x.py"))]
|
||||
#[test_case(Rule::RunProcessInAsyncFunction, Path::new("ASYNC22x.py"))]
|
||||
#[test_case(Rule::WaitForProcessInAsyncFunction, Path::new("ASYNC22x.py"))]
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
use ruff_python_ast::{self as ast, Expr, ExprCall};
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_semantic::analyze::typing::{TypeChecker, check_type, traverse_union_and_optional};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::Violation;
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks that async functions do not use blocking httpx clients.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Blocking an async function via a blocking HTTP call will block the entire
|
||||
/// event loop, preventing it from executing other tasks while waiting for the
|
||||
/// HTTP response, negating the benefits of asynchronous programming.
|
||||
///
|
||||
/// Instead of using the blocking `httpx` client, use the asynchronous client.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import httpx
|
||||
///
|
||||
///
|
||||
/// async def fetch():
|
||||
/// client = httpx.Client()
|
||||
/// response = client.get(...)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import httpx
|
||||
///
|
||||
///
|
||||
/// async def fetch():
|
||||
/// async with httpx.AsyncClient() as client:
|
||||
/// response = await client.get(...)
|
||||
/// ```
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct BlockingHttpCallHttpxInAsyncFunction {
|
||||
name: String,
|
||||
call: String,
|
||||
}
|
||||
|
||||
impl Violation for BlockingHttpCallHttpxInAsyncFunction {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!(
|
||||
"Blocking httpx method {name}.{call}() in async context, use httpx.AsyncClient",
|
||||
name = self.name,
|
||||
call = self.call,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct HttpxClientChecker;
|
||||
|
||||
impl TypeChecker for HttpxClientChecker {
|
||||
fn match_annotation(
|
||||
annotation: &ruff_python_ast::Expr,
|
||||
semantic: &ruff_python_semantic::SemanticModel,
|
||||
) -> bool {
|
||||
// match base annotation directly
|
||||
if semantic
|
||||
.resolve_qualified_name(annotation)
|
||||
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["httpx", "Client"]))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// otherwise traverse any union or optional annotation
|
||||
let mut found = false;
|
||||
traverse_union_and_optional(
|
||||
&mut |inner_expr, _| {
|
||||
if semantic
|
||||
.resolve_qualified_name(inner_expr)
|
||||
.is_some_and(|qualified_name| {
|
||||
matches!(qualified_name.segments(), ["httpx", "Client"])
|
||||
})
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
},
|
||||
semantic,
|
||||
annotation,
|
||||
);
|
||||
found
|
||||
}
|
||||
|
||||
fn match_initializer(
|
||||
initializer: &ruff_python_ast::Expr,
|
||||
semantic: &ruff_python_semantic::SemanticModel,
|
||||
) -> bool {
|
||||
let Expr::Call(ExprCall { func, .. }) = initializer else {
|
||||
return false;
|
||||
};
|
||||
|
||||
semantic
|
||||
.resolve_qualified_name(func)
|
||||
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["httpx", "Client"]))
|
||||
}
|
||||
}
|
||||
|
||||
/// ASYNC212
|
||||
pub(crate) fn blocking_http_call_httpx(checker: &Checker, call: &ExprCall) {
|
||||
let semantic = checker.semantic();
|
||||
if !semantic.in_async_context() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(ast::ExprAttribute { value, attr, .. }) = call.func.as_attribute_expr() else {
|
||||
return;
|
||||
};
|
||||
let Some(name) = value.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
let Some(binding) = semantic.only_binding(name).map(|id| semantic.binding(id)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if check_type::<HttpxClientChecker>(binding, semantic) {
|
||||
if matches!(
|
||||
attr.id.as_str(),
|
||||
"close"
|
||||
| "delete"
|
||||
| "get"
|
||||
| "head"
|
||||
| "options"
|
||||
| "patch"
|
||||
| "post"
|
||||
| "put"
|
||||
| "request"
|
||||
| "send"
|
||||
| "stream"
|
||||
) {
|
||||
checker.report_diagnostic(
|
||||
BlockingHttpCallHttpxInAsyncFunction {
|
||||
name: name.id.to_string(),
|
||||
call: attr.id.to_string(),
|
||||
},
|
||||
call.func.range(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ pub(crate) use async_busy_wait::*;
|
||||
pub(crate) use async_function_with_timeout::*;
|
||||
pub(crate) use async_zero_sleep::*;
|
||||
pub(crate) use blocking_http_call::*;
|
||||
pub(crate) use blocking_http_call_httpx::*;
|
||||
pub(crate) use blocking_open_call::*;
|
||||
pub(crate) use blocking_process_invocation::*;
|
||||
pub(crate) use blocking_sleep::*;
|
||||
@@ -14,7 +13,6 @@ mod async_busy_wait;
|
||||
mod async_function_with_timeout;
|
||||
mod async_zero_sleep;
|
||||
mod blocking_http_call;
|
||||
mod blocking_http_call_httpx;
|
||||
mod blocking_open_call;
|
||||
mod blocking_process_invocation;
|
||||
mod blocking_sleep;
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_async/mod.rs
|
||||
---
|
||||
ASYNC212 Blocking httpx method client.close() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:27:5
|
||||
|
|
||||
25 | async def foo():
|
||||
26 | client = httpx.Client()
|
||||
27 | client.close() # ASYNC212
|
||||
| ^^^^^^^^^^^^
|
||||
28 | client.delete() # ASYNC212
|
||||
29 | client.get() # ASYNC212
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.delete() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:28:5
|
||||
|
|
||||
26 | client = httpx.Client()
|
||||
27 | client.close() # ASYNC212
|
||||
28 | client.delete() # ASYNC212
|
||||
| ^^^^^^^^^^^^^
|
||||
29 | client.get() # ASYNC212
|
||||
30 | client.head() # ASYNC212
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.get() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:29:5
|
||||
|
|
||||
27 | client.close() # ASYNC212
|
||||
28 | client.delete() # ASYNC212
|
||||
29 | client.get() # ASYNC212
|
||||
| ^^^^^^^^^^
|
||||
30 | client.head() # ASYNC212
|
||||
31 | client.options() # ASYNC212
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.head() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:30:5
|
||||
|
|
||||
28 | client.delete() # ASYNC212
|
||||
29 | client.get() # ASYNC212
|
||||
30 | client.head() # ASYNC212
|
||||
| ^^^^^^^^^^^
|
||||
31 | client.options() # ASYNC212
|
||||
32 | client.patch() # ASYNC212
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.options() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:31:5
|
||||
|
|
||||
29 | client.get() # ASYNC212
|
||||
30 | client.head() # ASYNC212
|
||||
31 | client.options() # ASYNC212
|
||||
| ^^^^^^^^^^^^^^
|
||||
32 | client.patch() # ASYNC212
|
||||
33 | client.post() # ASYNC212
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.patch() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:32:5
|
||||
|
|
||||
30 | client.head() # ASYNC212
|
||||
31 | client.options() # ASYNC212
|
||||
32 | client.patch() # ASYNC212
|
||||
| ^^^^^^^^^^^^
|
||||
33 | client.post() # ASYNC212
|
||||
34 | client.put() # ASYNC212
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.post() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:33:5
|
||||
|
|
||||
31 | client.options() # ASYNC212
|
||||
32 | client.patch() # ASYNC212
|
||||
33 | client.post() # ASYNC212
|
||||
| ^^^^^^^^^^^
|
||||
34 | client.put() # ASYNC212
|
||||
35 | client.request() # ASYNC212
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.put() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:34:5
|
||||
|
|
||||
32 | client.patch() # ASYNC212
|
||||
33 | client.post() # ASYNC212
|
||||
34 | client.put() # ASYNC212
|
||||
| ^^^^^^^^^^
|
||||
35 | client.request() # ASYNC212
|
||||
36 | client.send() # ASYNC212
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.request() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:35:5
|
||||
|
|
||||
33 | client.post() # ASYNC212
|
||||
34 | client.put() # ASYNC212
|
||||
35 | client.request() # ASYNC212
|
||||
| ^^^^^^^^^^^^^^
|
||||
36 | client.send() # ASYNC212
|
||||
37 | client.stream() # ASYNC212
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.send() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:36:5
|
||||
|
|
||||
34 | client.put() # ASYNC212
|
||||
35 | client.request() # ASYNC212
|
||||
36 | client.send() # ASYNC212
|
||||
| ^^^^^^^^^^^
|
||||
37 | client.stream() # ASYNC212
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.stream() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:37:5
|
||||
|
|
||||
35 | client.request() # ASYNC212
|
||||
36 | client.send() # ASYNC212
|
||||
37 | client.stream() # ASYNC212
|
||||
| ^^^^^^^^^^^^^
|
||||
38 |
|
||||
39 | client.anything() # Ok
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.request() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:45:5
|
||||
|
|
||||
44 | async def foo(client: httpx.Client):
|
||||
45 | client.request() # ASYNC212
|
||||
| ^^^^^^^^^^^^^^
|
||||
46 | client.anything() # Ok
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.request() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:50:5
|
||||
|
|
||||
49 | async def foo(client: httpx.Client | None):
|
||||
50 | client.request() # ASYNC212
|
||||
| ^^^^^^^^^^^^^^
|
||||
51 | client.anything() # Ok
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.request() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:55:5
|
||||
|
|
||||
54 | async def foo(client: Optional[httpx.Client]):
|
||||
55 | client.request() # ASYNC212
|
||||
| ^^^^^^^^^^^^^^
|
||||
56 | client.anything() # Ok
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.request() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:61:5
|
||||
|
|
||||
59 | async def foo():
|
||||
60 | client: httpx.Client = ...
|
||||
61 | client.request() # ASYNC212
|
||||
| ^^^^^^^^^^^^^^
|
||||
62 | client.anything() # Ok
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method global_client.request() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:69:5
|
||||
|
|
||||
68 | async def foo():
|
||||
69 | global_client.request() # ASYNC212
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
70 | global_client.anything() # Ok
|
||||
|
|
||||
@@ -22,7 +22,6 @@ mod tests {
|
||||
#[test_case(Path::new("G002.py"))]
|
||||
#[test_case(Path::new("G003.py"))]
|
||||
#[test_case(Path::new("G004.py"))]
|
||||
#[test_case(Path::new("G004_arg_order.py"))]
|
||||
#[test_case(Path::new("G010.py"))]
|
||||
#[test_case(Path::new("G101_1.py"))]
|
||||
#[test_case(Path::new("G101_2.py"))]
|
||||
@@ -49,24 +48,4 @@ mod tests {
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::LoggingFString, Path::new("G004.py"))]
|
||||
#[test_case(Rule::LoggingFString, Path::new("G004_arg_order.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
rule_code.noqa_code(),
|
||||
path.to_string_lossy()
|
||||
);
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_logging_format").join(path).as_path(),
|
||||
&settings::LinterSettings {
|
||||
logger_objects: vec!["logging_setup.logger".to_string()],
|
||||
preview: settings::types::PreviewMode::Enabled,
|
||||
..settings::LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
use ruff_python_ast::InterpolatedStringElement;
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, Keyword, Operator, StringFlags};
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, Keyword, Operator};
|
||||
use ruff_python_semantic::analyze::logging;
|
||||
use ruff_python_stdlib::logging::LoggingLevel;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_f_string_logging_enabled;
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::flake8_logging_format::violations::{
|
||||
LoggingExcInfo, LoggingExtraAttrClash, LoggingFString, LoggingPercentFormat,
|
||||
@@ -13,87 +11,6 @@ use crate::rules::flake8_logging_format::violations::{
|
||||
};
|
||||
use crate::{Edit, Fix};
|
||||
|
||||
fn logging_f_string(
|
||||
checker: &Checker,
|
||||
msg: &Expr,
|
||||
f_string: &ast::ExprFString,
|
||||
arguments: &Arguments,
|
||||
msg_pos: usize,
|
||||
) {
|
||||
// Report the diagnostic up-front so we can attach a fix later only when preview is enabled.
|
||||
let mut diagnostic = checker.report_diagnostic(LoggingFString, msg.range());
|
||||
|
||||
// Preview gate for the automatic fix.
|
||||
if !is_fix_f_string_logging_enabled(checker.settings()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there are existing positional arguments after the message, bail out.
|
||||
// This could indicate a mistake or complex usage we shouldn't try to fix.
|
||||
if arguments.args.len() > msg_pos + 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut format_string = String::new();
|
||||
let mut args: Vec<&str> = Vec::new();
|
||||
|
||||
// Try to reuse the first part's quote style when building the replacement.
|
||||
// Default to double quotes if we can't determine it.
|
||||
let quote_str = f_string
|
||||
.value
|
||||
.f_strings()
|
||||
.next()
|
||||
.map(|f| f.flags.quote_str())
|
||||
.unwrap_or("\"");
|
||||
|
||||
for f in f_string.value.f_strings() {
|
||||
for element in &f.elements {
|
||||
match element {
|
||||
InterpolatedStringElement::Literal(lit) => {
|
||||
// If the literal text contains a '%' placeholder, bail out: mixing
|
||||
// f-string interpolation with '%' placeholders is ambiguous for our
|
||||
// automatic conversion, so don't offer a fix for this case.
|
||||
if lit.value.as_ref().contains('%') {
|
||||
return;
|
||||
}
|
||||
format_string.push_str(lit.value.as_ref());
|
||||
}
|
||||
InterpolatedStringElement::Interpolation(interpolated) => {
|
||||
if interpolated.format_spec.is_some()
|
||||
|| !matches!(
|
||||
interpolated.conversion,
|
||||
ruff_python_ast::ConversionFlag::None
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
match interpolated.expression.as_ref() {
|
||||
Expr::Name(name) => {
|
||||
format_string.push_str("%s");
|
||||
args.push(name.id.as_str());
|
||||
}
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if args.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let replacement = format!(
|
||||
"{q}{format_string}{q}, {args}",
|
||||
q = quote_str,
|
||||
format_string = format_string,
|
||||
args = args.join(", ")
|
||||
);
|
||||
|
||||
let fix = Fix::safe_edit(Edit::range_replacement(replacement, msg.range()));
|
||||
diagnostic.set_fix(fix);
|
||||
}
|
||||
|
||||
/// Returns `true` if the attribute is a reserved attribute on the `logging` module's `LogRecord`
|
||||
/// class.
|
||||
fn is_reserved_attr(attr: &str) -> bool {
|
||||
@@ -125,7 +42,7 @@ fn is_reserved_attr(attr: &str) -> bool {
|
||||
}
|
||||
|
||||
/// Check logging messages for violations.
|
||||
fn check_msg(checker: &Checker, msg: &Expr, arguments: &Arguments, msg_pos: usize) {
|
||||
fn check_msg(checker: &Checker, msg: &Expr) {
|
||||
match msg {
|
||||
// Check for string concatenation and percent format.
|
||||
Expr::BinOp(ast::ExprBinOp { op, .. }) => match op {
|
||||
@@ -138,10 +55,8 @@ fn check_msg(checker: &Checker, msg: &Expr, arguments: &Arguments, msg_pos: usiz
|
||||
_ => {}
|
||||
},
|
||||
// Check for f-strings.
|
||||
Expr::FString(f_string) => {
|
||||
if checker.is_rule_enabled(Rule::LoggingFString) {
|
||||
logging_f_string(checker, msg, f_string, arguments, msg_pos);
|
||||
}
|
||||
Expr::FString(_) => {
|
||||
checker.report_diagnostic_if_enabled(LoggingFString, msg.range());
|
||||
}
|
||||
// Check for .format() calls.
|
||||
Expr::Call(ast::ExprCall { func, .. }) => {
|
||||
@@ -253,7 +168,7 @@ pub(crate) fn logging_call(checker: &Checker, call: &ast::ExprCall) {
|
||||
// G001, G002, G003, G004
|
||||
let msg_pos = usize::from(matches!(logging_call_type, LoggingCallType::LogCall));
|
||||
if let Some(format_arg) = call.arguments.find_argument_value("msg", msg_pos) {
|
||||
check_msg(checker, format_arg, &call.arguments, msg_pos);
|
||||
check_msg(checker, format_arg);
|
||||
}
|
||||
|
||||
// G010
|
||||
|
||||
@@ -9,7 +9,6 @@ G004 Logging statement uses f-string
|
||||
| ^^^^^^^^^^^^^^^
|
||||
5 | logging.log(logging.INFO, f"Hello {name}")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:5:27
|
||||
@@ -21,7 +20,6 @@ G004 Logging statement uses f-string
|
||||
6 |
|
||||
7 | _LOGGER = logging.getLogger()
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:8:14
|
||||
@@ -32,7 +30,6 @@ G004 Logging statement uses f-string
|
||||
9 |
|
||||
10 | logging.getLogger().info(f"{name}")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:10:26
|
||||
@@ -44,7 +41,6 @@ G004 Logging statement uses f-string
|
||||
11 |
|
||||
12 | from logging import info
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:14:6
|
||||
@@ -55,7 +51,6 @@ G004 Logging statement uses f-string
|
||||
| ^^^^^^^^^
|
||||
15 | info(f"{__name__}")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:15:6
|
||||
@@ -66,156 +61,3 @@ G004 Logging statement uses f-string
|
||||
16 |
|
||||
17 | # Don't trigger for t-strings
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:24:14
|
||||
|
|
||||
22 | total = 9
|
||||
23 | directory_path = "/home/hamir/ruff/crates/ruff_linter/resources/test/"
|
||||
24 | logging.info(f"{count} out of {total} files in {directory_path} checked")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:30:13
|
||||
|
|
||||
28 | x = 99
|
||||
29 | fmt = "08d"
|
||||
30 | logger.info(f"{x:{'08d'}}")
|
||||
| ^^^^^^^^^^^^^^
|
||||
31 | logger.info(f"{x:>10} {x:{fmt}}")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:31:13
|
||||
|
|
||||
29 | fmt = "08d"
|
||||
30 | logger.info(f"{x:{'08d'}}")
|
||||
31 | logger.info(f"{x:>10} {x:{fmt}}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
32 |
|
||||
33 | logging.info(f"")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:33:14
|
||||
|
|
||||
31 | logger.info(f"{x:>10} {x:{fmt}}")
|
||||
32 |
|
||||
33 | logging.info(f"")
|
||||
| ^^^
|
||||
34 | logging.info(f"This message doesn't have any variables.")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:34:14
|
||||
|
|
||||
33 | logging.info(f"")
|
||||
34 | logging.info(f"This message doesn't have any variables.")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
35 |
|
||||
36 | obj = {"key": "value"}
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:37:14
|
||||
|
|
||||
36 | obj = {"key": "value"}
|
||||
37 | logging.info(f"Object: {obj!r}")
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
38 |
|
||||
39 | items_count = 3
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:40:17
|
||||
|
|
||||
39 | items_count = 3
|
||||
40 | logging.warning(f"Items: {items_count:d}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
41 |
|
||||
42 | data = {"status": "active"}
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:43:14
|
||||
|
|
||||
42 | data = {"status": "active"}
|
||||
43 | logging.info(f"Processing {len(data)} items")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
44 | logging.info(f"Status: {data.get('status', 'unknown').upper()}")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:44:14
|
||||
|
|
||||
42 | data = {"status": "active"}
|
||||
43 | logging.info(f"Processing {len(data)} items")
|
||||
44 | logging.info(f"Status: {data.get('status', 'unknown').upper()}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:48:14
|
||||
|
|
||||
47 | result = 123
|
||||
48 | logging.info(f"Calculated result: {result + 100}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
49 |
|
||||
50 | temperature = 123
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:51:14
|
||||
|
|
||||
50 | temperature = 123
|
||||
51 | logging.info(f"Temperature: {temperature:.1f}°C")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
52 |
|
||||
53 | class FilePath:
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:57:14
|
||||
|
|
||||
55 | self.name = name
|
||||
56 |
|
||||
57 | logging.info(f"No changes made to {file_path.name}.")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
58 |
|
||||
59 | user = "tron"
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:61:15
|
||||
|
|
||||
59 | user = "tron"
|
||||
60 | balance = 123.45
|
||||
61 | logging.error(f"Error {404}: User {user} has insufficient balance ${balance:.2f}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
62 |
|
||||
63 | import logging
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:66:15
|
||||
|
|
||||
65 | x = 1
|
||||
66 | logging.error(f"{x} -> %s", x)
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_logging_format/mod.rs
|
||||
---
|
||||
G004 Logging statement uses f-string
|
||||
--> G004_arg_order.py:9:14
|
||||
|
|
||||
7 | X = 1
|
||||
8 | Y = 2
|
||||
9 | logger.error(f"{X} -> %s", Y)
|
||||
| ^^^^^^^^^^^^
|
||||
10 | logger.error(f"{Y} -> %s", X)
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004_arg_order.py:10:14
|
||||
|
|
||||
8 | Y = 2
|
||||
9 | logger.error(f"{X} -> %s", Y)
|
||||
10 | logger.error(f"{Y} -> %s", X)
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
@@ -1,291 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_logging_format/mod.rs
|
||||
---
|
||||
G004 [*] Logging statement uses f-string
|
||||
--> G004.py:4:14
|
||||
|
|
||||
3 | name = "world"
|
||||
4 | logging.info(f"Hello {name}")
|
||||
| ^^^^^^^^^^^^^^^
|
||||
5 | logging.log(logging.INFO, f"Hello {name}")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import logging
|
||||
2 2 |
|
||||
3 3 | name = "world"
|
||||
4 |-logging.info(f"Hello {name}")
|
||||
4 |+logging.info("Hello %s", name)
|
||||
5 5 | logging.log(logging.INFO, f"Hello {name}")
|
||||
6 6 |
|
||||
7 7 | _LOGGER = logging.getLogger()
|
||||
|
||||
G004 [*] Logging statement uses f-string
|
||||
--> G004.py:5:27
|
||||
|
|
||||
3 | name = "world"
|
||||
4 | logging.info(f"Hello {name}")
|
||||
5 | logging.log(logging.INFO, f"Hello {name}")
|
||||
| ^^^^^^^^^^^^^^^
|
||||
6 |
|
||||
7 | _LOGGER = logging.getLogger()
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 |
|
||||
3 3 | name = "world"
|
||||
4 4 | logging.info(f"Hello {name}")
|
||||
5 |-logging.log(logging.INFO, f"Hello {name}")
|
||||
5 |+logging.log(logging.INFO, "Hello %s", name)
|
||||
6 6 |
|
||||
7 7 | _LOGGER = logging.getLogger()
|
||||
8 8 | _LOGGER.info(f"{__name__}")
|
||||
|
||||
G004 [*] Logging statement uses f-string
|
||||
--> G004.py:8:14
|
||||
|
|
||||
7 | _LOGGER = logging.getLogger()
|
||||
8 | _LOGGER.info(f"{__name__}")
|
||||
| ^^^^^^^^^^^^^
|
||||
9 |
|
||||
10 | logging.getLogger().info(f"{name}")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
ℹ Safe fix
|
||||
5 5 | logging.log(logging.INFO, f"Hello {name}")
|
||||
6 6 |
|
||||
7 7 | _LOGGER = logging.getLogger()
|
||||
8 |-_LOGGER.info(f"{__name__}")
|
||||
8 |+_LOGGER.info("%s", __name__)
|
||||
9 9 |
|
||||
10 10 | logging.getLogger().info(f"{name}")
|
||||
11 11 |
|
||||
|
||||
G004 [*] Logging statement uses f-string
|
||||
--> G004.py:10:26
|
||||
|
|
||||
8 | _LOGGER.info(f"{__name__}")
|
||||
9 |
|
||||
10 | logging.getLogger().info(f"{name}")
|
||||
| ^^^^^^^^^
|
||||
11 |
|
||||
12 | from logging import info
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | _LOGGER = logging.getLogger()
|
||||
8 8 | _LOGGER.info(f"{__name__}")
|
||||
9 9 |
|
||||
10 |-logging.getLogger().info(f"{name}")
|
||||
10 |+logging.getLogger().info("%s", name)
|
||||
11 11 |
|
||||
12 12 | from logging import info
|
||||
13 13 |
|
||||
|
||||
G004 [*] Logging statement uses f-string
|
||||
--> G004.py:14:6
|
||||
|
|
||||
12 | from logging import info
|
||||
13 |
|
||||
14 | info(f"{name}")
|
||||
| ^^^^^^^^^
|
||||
15 | info(f"{__name__}")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
ℹ Safe fix
|
||||
11 11 |
|
||||
12 12 | from logging import info
|
||||
13 13 |
|
||||
14 |-info(f"{name}")
|
||||
14 |+info("%s", name)
|
||||
15 15 | info(f"{__name__}")
|
||||
16 16 |
|
||||
17 17 | # Don't trigger for t-strings
|
||||
|
||||
G004 [*] Logging statement uses f-string
|
||||
--> G004.py:15:6
|
||||
|
|
||||
14 | info(f"{name}")
|
||||
15 | info(f"{__name__}")
|
||||
| ^^^^^^^^^^^^^
|
||||
16 |
|
||||
17 | # Don't trigger for t-strings
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
ℹ Safe fix
|
||||
12 12 | from logging import info
|
||||
13 13 |
|
||||
14 14 | info(f"{name}")
|
||||
15 |-info(f"{__name__}")
|
||||
15 |+info("%s", __name__)
|
||||
16 16 |
|
||||
17 17 | # Don't trigger for t-strings
|
||||
18 18 | info(t"{name}")
|
||||
|
||||
G004 [*] Logging statement uses f-string
|
||||
--> G004.py:24:14
|
||||
|
|
||||
22 | total = 9
|
||||
23 | directory_path = "/home/hamir/ruff/crates/ruff_linter/resources/test/"
|
||||
24 | logging.info(f"{count} out of {total} files in {directory_path} checked")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
ℹ Safe fix
|
||||
21 21 | count = 5
|
||||
22 22 | total = 9
|
||||
23 23 | directory_path = "/home/hamir/ruff/crates/ruff_linter/resources/test/"
|
||||
24 |-logging.info(f"{count} out of {total} files in {directory_path} checked")
|
||||
24 |+logging.info("%s out of %s files in %s checked", count, total, directory_path)
|
||||
25 25 |
|
||||
26 26 |
|
||||
27 27 |
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:30:13
|
||||
|
|
||||
28 | x = 99
|
||||
29 | fmt = "08d"
|
||||
30 | logger.info(f"{x:{'08d'}}")
|
||||
| ^^^^^^^^^^^^^^
|
||||
31 | logger.info(f"{x:>10} {x:{fmt}}")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:31:13
|
||||
|
|
||||
29 | fmt = "08d"
|
||||
30 | logger.info(f"{x:{'08d'}}")
|
||||
31 | logger.info(f"{x:>10} {x:{fmt}}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
32 |
|
||||
33 | logging.info(f"")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:33:14
|
||||
|
|
||||
31 | logger.info(f"{x:>10} {x:{fmt}}")
|
||||
32 |
|
||||
33 | logging.info(f"")
|
||||
| ^^^
|
||||
34 | logging.info(f"This message doesn't have any variables.")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:34:14
|
||||
|
|
||||
33 | logging.info(f"")
|
||||
34 | logging.info(f"This message doesn't have any variables.")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
35 |
|
||||
36 | obj = {"key": "value"}
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:37:14
|
||||
|
|
||||
36 | obj = {"key": "value"}
|
||||
37 | logging.info(f"Object: {obj!r}")
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
38 |
|
||||
39 | items_count = 3
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:40:17
|
||||
|
|
||||
39 | items_count = 3
|
||||
40 | logging.warning(f"Items: {items_count:d}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
41 |
|
||||
42 | data = {"status": "active"}
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:43:14
|
||||
|
|
||||
42 | data = {"status": "active"}
|
||||
43 | logging.info(f"Processing {len(data)} items")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
44 | logging.info(f"Status: {data.get('status', 'unknown').upper()}")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:44:14
|
||||
|
|
||||
42 | data = {"status": "active"}
|
||||
43 | logging.info(f"Processing {len(data)} items")
|
||||
44 | logging.info(f"Status: {data.get('status', 'unknown').upper()}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:48:14
|
||||
|
|
||||
47 | result = 123
|
||||
48 | logging.info(f"Calculated result: {result + 100}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
49 |
|
||||
50 | temperature = 123
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:51:14
|
||||
|
|
||||
50 | temperature = 123
|
||||
51 | logging.info(f"Temperature: {temperature:.1f}°C")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
52 |
|
||||
53 | class FilePath:
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:57:14
|
||||
|
|
||||
55 | self.name = name
|
||||
56 |
|
||||
57 | logging.info(f"No changes made to {file_path.name}.")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
58 |
|
||||
59 | user = "tron"
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:61:15
|
||||
|
|
||||
59 | user = "tron"
|
||||
60 | balance = 123.45
|
||||
61 | logging.error(f"Error {404}: User {user} has insufficient balance ${balance:.2f}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
62 |
|
||||
63 | import logging
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:66:15
|
||||
|
|
||||
65 | x = 1
|
||||
66 | logging.error(f"{x} -> %s", x)
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_logging_format/mod.rs
|
||||
---
|
||||
G004 Logging statement uses f-string
|
||||
--> G004_arg_order.py:9:14
|
||||
|
|
||||
7 | X = 1
|
||||
8 | Y = 2
|
||||
9 | logger.error(f"{X} -> %s", Y)
|
||||
| ^^^^^^^^^^^^
|
||||
10 | logger.error(f"{Y} -> %s", X)
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004_arg_order.py:10:14
|
||||
|
|
||||
8 | Y = 2
|
||||
9 | logger.error(f"{X} -> %s", Y)
|
||||
10 | logger.error(f"{Y} -> %s", X)
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
@@ -327,16 +327,10 @@ impl Violation for LoggingStringConcat {
|
||||
pub(crate) struct LoggingFString;
|
||||
|
||||
impl Violation for LoggingFString {
|
||||
const FIX_AVAILABILITY: crate::FixAvailability = crate::FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"Logging statement uses f-string".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Convert to lazy `%` formatting".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
|
||||
@@ -42,7 +42,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.chmod`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.chmod)
|
||||
/// - [Python documentation: `os.chmod`](https://docs.python.org/3/library/os.html#os.chmod)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -43,7 +43,7 @@ use ruff_text_size::Ranged;
|
||||
/// - [Python documentation: `os.getcwd`](https://docs.python.org/3/library/os.html#os.getcwd)
|
||||
/// - [Python documentation: `os.getcwdb`](https://docs.python.org/3/library/os.html#os.getcwdb)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -46,7 +46,7 @@ use crate::{FixAvailability, Violation};
|
||||
/// - [Python documentation: `Path.mkdir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.mkdir)
|
||||
/// - [Python documentation: `os.makedirs`](https://docs.python.org/3/library/os.html#os.makedirs)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -47,7 +47,7 @@ use crate::{FixAvailability, Violation};
|
||||
/// - [Python documentation: `Path.mkdir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.mkdir)
|
||||
/// - [Python documentation: `os.mkdir`](https://docs.python.org/3/library/os.html#os.mkdir)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
use ruff_diagnostics::{Edit, Fix};
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_abspath_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::importer::ImportRequest;
|
||||
use crate::preview::is_fix_os_path_abspath_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::{
|
||||
has_unknown_keywords_or_starred_expr, is_pathlib_path_call,
|
||||
};
|
||||
use crate::{FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.abspath`.
|
||||
@@ -40,18 +34,13 @@ use crate::{FixAvailability, Violation};
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is always marked as unsafe because `Path.resolve()` resolves symlinks, while
|
||||
/// `os.path.abspath()` does not. If resolving symlinks is important, you may need to use
|
||||
/// `Path.absolute()`. However, `Path.absolute()` also does not remove any `..` components in a
|
||||
/// path, unlike `os.path.abspath()` and `Path.resolve()`, so if that specific combination of
|
||||
/// behaviors is required, there's no existing `pathlib` alternative. See CPython issue
|
||||
/// [#69200](https://github.com/python/cpython/issues/69200).
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.resolve`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.resolve)
|
||||
/// - [Python documentation: `os.path.abspath`](https://docs.python.org/3/library/os.path.html#os.path.abspath)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
@@ -74,44 +63,12 @@ pub(crate) fn os_path_abspath(checker: &Checker, call: &ExprCall, segments: &[&s
|
||||
if segments != ["os", "path", "abspath"] {
|
||||
return;
|
||||
}
|
||||
|
||||
if call.arguments.len() != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(arg) = call.arguments.find_argument_value("path", 0) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let arg_code = checker.locator().slice(arg.range());
|
||||
let range = call.range();
|
||||
|
||||
let mut diagnostic = checker.report_diagnostic(OsPathAbspath, call.func.range());
|
||||
|
||||
if has_unknown_keywords_or_starred_expr(&call.arguments, &["path"]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if !is_fix_os_path_abspath_enabled(checker.settings()) {
|
||||
return;
|
||||
}
|
||||
|
||||
diagnostic.try_set_fix(|| {
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import("pathlib", "Path"),
|
||||
call.start(),
|
||||
checker.semantic(),
|
||||
)?;
|
||||
|
||||
let replacement = if is_pathlib_path_call(checker, arg) {
|
||||
format!("{arg_code}.resolve()")
|
||||
} else {
|
||||
format!("{binding}({arg_code}).resolve()")
|
||||
};
|
||||
|
||||
Ok(Fix::unsafe_edits(
|
||||
Edit::range_replacement(replacement, range),
|
||||
[import_edit],
|
||||
))
|
||||
});
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"resolve()",
|
||||
"path",
|
||||
is_fix_os_path_abspath_enabled(checker.settings()),
|
||||
OsPathAbspath,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `PurePath.name`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.name)
|
||||
/// - [Python documentation: `os.path.basename`](https://docs.python.org/3/library/os.path.html#os.path.basename)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -40,7 +40,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `PurePath.parent`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.parent)
|
||||
/// - [Python documentation: `os.path.dirname`](https://docs.python.org/3/library/os.path.html#os.path.dirname)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -40,7 +40,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.exists`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.exists)
|
||||
/// - [Python documentation: `os.path.exists`](https://docs.python.org/3/library/os.path.html#os.path.exists)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -40,7 +40,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.expanduser`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.expanduser)
|
||||
/// - [Python documentation: `os.path.expanduser`](https://docs.python.org/3/library/os.path.html#os.path.expanduser)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -42,7 +42,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.stat`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat)
|
||||
/// - [Python documentation: `os.path.getatime`](https://docs.python.org/3/library/os.path.html#os.path.getatime)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -42,7 +42,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.stat`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat)
|
||||
/// - [Python documentation: `os.path.getctime`](https://docs.python.org/3/library/os.path.html#os.path.getctime)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -42,7 +42,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.stat`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat)
|
||||
/// - [Python documentation: `os.path.getmtime`](https://docs.python.org/3/library/os.path.html#os.path.getmtime)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -42,7 +42,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.stat`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat)
|
||||
/// - [Python documentation: `os.path.getsize`](https://docs.python.org/3/library/os.path.html#os.path.getsize)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -39,7 +39,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `PurePath.is_absolute`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.is_absolute)
|
||||
/// - [Python documentation: `os.path.isabs`](https://docs.python.org/3/library/os.path.html#os.path.isabs)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -40,7 +40,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.is_dir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_dir)
|
||||
/// - [Python documentation: `os.path.isdir`](https://docs.python.org/3/library/os.path.html#os.path.isdir)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -40,7 +40,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.is_file`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_file)
|
||||
/// - [Python documentation: `os.path.isfile`](https://docs.python.org/3/library/os.path.html#os.path.isfile)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -40,7 +40,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.is_symlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_symlink)
|
||||
/// - [Python documentation: `os.path.islink`](https://docs.python.org/3/library/os.path.html#os.path.islink)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -40,7 +40,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.samefile`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.samefile)
|
||||
/// - [Python documentation: `os.path.samefile`](https://docs.python.org/3/library/os.path.html#os.path.samefile)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -42,7 +42,7 @@ use ruff_python_ast::{ExprCall, PythonVersion};
|
||||
/// - [Python documentation: `Path.readlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.readline)
|
||||
/// - [Python documentation: `os.readlink`](https://docs.python.org/3/library/os.html#os.readlink)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -42,7 +42,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.unlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.unlink)
|
||||
/// - [Python documentation: `os.remove`](https://docs.python.org/3/library/os.html#os.remove)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -42,7 +42,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.rename`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.rename)
|
||||
/// - [Python documentation: `os.rename`](https://docs.python.org/3/library/os.html#os.rename)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -45,7 +45,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.replace`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.replace)
|
||||
/// - [Python documentation: `os.replace`](https://docs.python.org/3/library/os.html#os.replace)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -42,7 +42,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.rmdir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.rmdir)
|
||||
/// - [Python documentation: `os.rmdir`](https://docs.python.org/3/library/os.html#os.rmdir)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -44,7 +44,7 @@ use crate::{FixAvailability, Violation};
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.symlink_to`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.symlink_to)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -42,7 +42,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.unlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.unlink)
|
||||
/// - [Python documentation: `os.unlink`](https://docs.python.org/3/library/os.html#os.unlink)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -13,7 +13,7 @@ PTH100 [*] `os.path.abspath()` should be replaced by `Path.resolve()`
|
||||
|
|
||||
help: Replace with `Path(...).resolve()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
ℹ Safe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
|
||||
@@ -13,7 +13,7 @@ PTH100 [*] `os.path.abspath()` should be replaced by `Path.resolve()`
|
||||
|
|
||||
help: Replace with `Path(...).resolve()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
ℹ Safe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
|
||||
@@ -13,7 +13,7 @@ PTH100 [*] `os.path.abspath()` should be replaced by `Path.resolve()`
|
||||
|
|
||||
help: Replace with `Path(...).resolve()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
ℹ Safe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
|
||||
@@ -13,7 +13,7 @@ PTH100 [*] `os.path.abspath()` should be replaced by `Path.resolve()`
|
||||
|
|
||||
help: Replace with `Path(...).resolve()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
ℹ Safe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
|
||||
@@ -43,7 +43,7 @@ use crate::Violation;
|
||||
/// - [Python documentation: `Path.owner`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.owner)
|
||||
/// - [Python documentation: `os.stat`](https://docs.python.org/3/library/os.html#os.stat)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
@@ -89,7 +89,7 @@ impl Violation for OsStat {
|
||||
/// - [Python documentation: `PurePath.joinpath`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.joinpath)
|
||||
/// - [Python documentation: `os.path.join`](https://docs.python.org/3/library/os.path.html#os.path.join)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
@@ -160,7 +160,7 @@ pub(crate) enum Joiner {
|
||||
/// - [Python documentation: `Path.suffixes`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.suffixes)
|
||||
/// - [Python documentation: `os.path.splitext`](https://docs.python.org/3/library/os.path.html#os.path.splitext)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
@@ -205,7 +205,7 @@ impl Violation for OsPathSplitext {
|
||||
/// - [Python documentation: `Path.open`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.open)
|
||||
/// - [Python documentation: `open`](https://docs.python.org/3/library/functions.html#open)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
@@ -298,7 +298,7 @@ impl Violation for PyPath {
|
||||
/// - [Python documentation: `Path.iterdir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.iterdir)
|
||||
/// - [Python documentation: `os.listdir`](https://docs.python.org/3/library/os.html#os.listdir)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -737,7 +737,6 @@ mod tests {
|
||||
|
||||
/// A re-implementation of the Pyflakes test runner.
|
||||
/// Note that all tests marked with `#[ignore]` should be considered TODOs.
|
||||
#[track_caller]
|
||||
fn flakes(contents: &str, expected: &[Rule]) {
|
||||
let contents = dedent(contents);
|
||||
let source_type = PySourceType::default();
|
||||
|
||||
@@ -251,63 +251,3 @@ F841 Local variable `value` is assigned to but never used
|
||||
128 | print(key)
|
||||
|
|
||||
help: Remove assignment to unused variable `value`
|
||||
|
||||
F841 [*] Local variable `__class__` is assigned to but never used
|
||||
--> F841_0.py:168:9
|
||||
|
|
||||
166 | class A:
|
||||
167 | def set_class(self, cls):
|
||||
168 | __class__ = cls # F841
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
help: Remove assignment to unused variable `__class__`
|
||||
|
||||
ℹ Unsafe fix
|
||||
165 165 | # variables that don't refer to the special `__class__` cell.
|
||||
166 166 | class A:
|
||||
167 167 | def set_class(self, cls):
|
||||
168 |- __class__ = cls # F841
|
||||
168 |+ pass # F841
|
||||
169 169 |
|
||||
170 170 |
|
||||
171 171 | class A:
|
||||
|
||||
F841 [*] Local variable `__class__` is assigned to but never used
|
||||
--> F841_0.py:174:13
|
||||
|
|
||||
172 | class B:
|
||||
173 | def set_class(self, cls):
|
||||
174 | __class__ = cls # F841
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
help: Remove assignment to unused variable `__class__`
|
||||
|
||||
ℹ Unsafe fix
|
||||
171 171 | class A:
|
||||
172 172 | class B:
|
||||
173 173 | def set_class(self, cls):
|
||||
174 |- __class__ = cls # F841
|
||||
174 |+ pass # F841
|
||||
175 175 |
|
||||
176 176 |
|
||||
177 177 | class A:
|
||||
|
||||
F841 [*] Local variable `__class__` is assigned to but never used
|
||||
--> F841_0.py:182:17
|
||||
|
|
||||
180 | print(__class__)
|
||||
181 | def set_class(self, cls):
|
||||
182 | __class__ = cls # F841
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
help: Remove assignment to unused variable `__class__`
|
||||
|
||||
ℹ Unsafe fix
|
||||
179 179 | class B:
|
||||
180 180 | print(__class__)
|
||||
181 181 | def set_class(self, cls):
|
||||
182 |- __class__ = cls # F841
|
||||
182 |+ pass # F841
|
||||
183 183 |
|
||||
184 184 |
|
||||
185 185 | # OK, the `__class__` cell is nonlocal and declared as such.
|
||||
|
||||
@@ -289,65 +289,3 @@ help: Remove assignment to unused variable `_`
|
||||
152 |- except Exception as _:
|
||||
152 |+ except Exception:
|
||||
153 153 | pass
|
||||
154 154 |
|
||||
155 155 |
|
||||
|
||||
F841 [*] Local variable `__class__` is assigned to but never used
|
||||
--> F841_0.py:168:9
|
||||
|
|
||||
166 | class A:
|
||||
167 | def set_class(self, cls):
|
||||
168 | __class__ = cls # F841
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
help: Remove assignment to unused variable `__class__`
|
||||
|
||||
ℹ Unsafe fix
|
||||
165 165 | # variables that don't refer to the special `__class__` cell.
|
||||
166 166 | class A:
|
||||
167 167 | def set_class(self, cls):
|
||||
168 |- __class__ = cls # F841
|
||||
168 |+ pass # F841
|
||||
169 169 |
|
||||
170 170 |
|
||||
171 171 | class A:
|
||||
|
||||
F841 [*] Local variable `__class__` is assigned to but never used
|
||||
--> F841_0.py:174:13
|
||||
|
|
||||
172 | class B:
|
||||
173 | def set_class(self, cls):
|
||||
174 | __class__ = cls # F841
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
help: Remove assignment to unused variable `__class__`
|
||||
|
||||
ℹ Unsafe fix
|
||||
171 171 | class A:
|
||||
172 172 | class B:
|
||||
173 173 | def set_class(self, cls):
|
||||
174 |- __class__ = cls # F841
|
||||
174 |+ pass # F841
|
||||
175 175 |
|
||||
176 176 |
|
||||
177 177 | class A:
|
||||
|
||||
F841 [*] Local variable `__class__` is assigned to but never used
|
||||
--> F841_0.py:182:17
|
||||
|
|
||||
180 | print(__class__)
|
||||
181 | def set_class(self, cls):
|
||||
182 | __class__ = cls # F841
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
help: Remove assignment to unused variable `__class__`
|
||||
|
||||
ℹ Unsafe fix
|
||||
179 179 | class B:
|
||||
180 180 | print(__class__)
|
||||
181 181 | def set_class(self, cls):
|
||||
182 |- __class__ = cls # F841
|
||||
182 |+ pass # F841
|
||||
183 183 |
|
||||
184 184 |
|
||||
185 185 | # OK, the `__class__` cell is nonlocal and declared as such.
|
||||
|
||||
@@ -73,8 +73,7 @@ pub(crate) fn non_ascii_name(checker: &Checker, binding: &Binding) {
|
||||
| BindingKind::SubmoduleImport(_)
|
||||
| BindingKind::Deletion
|
||||
| BindingKind::ConditionalDeletion(_)
|
||||
| BindingKind::UnboundException(_)
|
||||
| BindingKind::DunderClassCell => {
|
||||
| BindingKind::UnboundException(_) => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -186,7 +186,7 @@ fn use_initvar(
|
||||
|
||||
let indentation = indentation_at_offset(post_init_def.start(), checker.source())
|
||||
.context("Failed to calculate leading indentation of `__post_init__` method")?;
|
||||
let content = textwrap::indent_first_line(&content, indentation);
|
||||
let content = textwrap::indent(&content, indentation);
|
||||
|
||||
let initvar_edit = Edit::insertion(
|
||||
content.into_owned(),
|
||||
|
||||
@@ -455,57 +455,3 @@ help: Use `dataclasses.InitVar` instead
|
||||
122 123 | ,
|
||||
123 124 | ) -> None:
|
||||
124 125 | ...
|
||||
|
||||
RUF033 [*] `__post_init__` method with argument defaults
|
||||
--> RUF033.py:131:50
|
||||
|
|
||||
129 | @dataclass
|
||||
130 | class C:
|
||||
131 | def __post_init__(self, x: tuple[int, ...] = (
|
||||
| __________________________________________________^
|
||||
132 | | 1,
|
||||
133 | | 2,
|
||||
134 | | )) -> None:
|
||||
| |_____^
|
||||
135 | self.x = x
|
||||
|
|
||||
help: Use `dataclasses.InitVar` instead
|
||||
|
||||
ℹ Unsafe fix
|
||||
128 128 |
|
||||
129 129 | @dataclass
|
||||
130 130 | class C:
|
||||
131 |- def __post_init__(self, x: tuple[int, ...] = (
|
||||
131 |+ x: InitVar[tuple[int, ...]] = (
|
||||
132 132 | 1,
|
||||
133 133 | 2,
|
||||
134 |- )) -> None:
|
||||
134 |+ )
|
||||
135 |+ def __post_init__(self, x: tuple[int, ...]) -> None:
|
||||
135 136 | self.x = x
|
||||
136 137 |
|
||||
137 138 |
|
||||
|
||||
RUF033 [*] `__post_init__` method with argument defaults
|
||||
--> RUF033.py:140:38
|
||||
|
|
||||
138 | @dataclass
|
||||
139 | class D:
|
||||
140 | def __post_init__(self, x: int = """
|
||||
| ______________________________________^
|
||||
141 | | """) -> None:
|
||||
| |_______^
|
||||
142 | self.x = x
|
||||
|
|
||||
help: Use `dataclasses.InitVar` instead
|
||||
|
||||
ℹ Unsafe fix
|
||||
137 137 |
|
||||
138 138 | @dataclass
|
||||
139 139 | class D:
|
||||
140 |- def __post_init__(self, x: int = """
|
||||
141 |- """) -> None:
|
||||
140 |+ x: InitVar[int] = """
|
||||
141 |+ """
|
||||
142 |+ def __post_init__(self, x: int) -> None:
|
||||
142 143 | self.x = x
|
||||
|
||||
@@ -446,7 +446,7 @@ impl Ranged for Binding<'_> {
|
||||
/// ID uniquely identifying a [Binding] in a program.
|
||||
///
|
||||
/// Using a `u32` to identify [Binding]s should be sufficient because Ruff only supports documents with a
|
||||
/// size smaller than or equal to `u32::MAX`. A document with the size of `u32::MAX` must have fewer than `u32::MAX`
|
||||
/// size smaller than or equal to `u32::max`. A document with the size of `u32::max` must have fewer than `u32::max`
|
||||
/// bindings because bindings must be separated by whitespace (and have an assignment).
|
||||
#[newtype_index]
|
||||
pub struct BindingId;
|
||||
@@ -672,24 +672,6 @@ pub enum BindingKind<'a> {
|
||||
/// Stores the ID of the binding that was shadowed in the enclosing
|
||||
/// scope, if any.
|
||||
UnboundException(Option<BindingId>),
|
||||
|
||||
/// A binding to `__class__` in the implicit closure created around every method in a class
|
||||
/// body, if any method refers to either `__class__` or `super`.
|
||||
///
|
||||
/// ```python
|
||||
/// class C:
|
||||
/// __class__ # NameError: name '__class__' is not defined
|
||||
///
|
||||
/// def f():
|
||||
/// print(__class__) # allowed
|
||||
///
|
||||
/// def g():
|
||||
/// nonlocal __class__ # also allowed because the scope is *not* the function scope
|
||||
/// ```
|
||||
///
|
||||
/// See <https://docs.python.org/3/reference/datamodel.html#creating-the-class-object> for more
|
||||
/// details.
|
||||
DunderClassCell,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
|
||||
@@ -404,11 +404,22 @@ impl<'a> SemanticModel<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
let mut seen_function = false;
|
||||
let mut import_starred = false;
|
||||
let mut class_variables_visible = true;
|
||||
for (index, scope_id) in self.scopes.ancestor_ids(self.scope_id).enumerate() {
|
||||
let scope = &self.scopes[scope_id];
|
||||
if scope.kind.is_class() {
|
||||
// Allow usages of `__class__` within methods, e.g.:
|
||||
//
|
||||
// ```python
|
||||
// class Foo:
|
||||
// def __init__(self):
|
||||
// print(__class__)
|
||||
// ```
|
||||
if seen_function && matches!(name.id.as_str(), "__class__") {
|
||||
return ReadResult::ImplicitGlobal;
|
||||
}
|
||||
// Do not allow usages of class symbols unless it is the immediate parent
|
||||
// (excluding type scopes), e.g.:
|
||||
//
|
||||
@@ -431,13 +442,7 @@ impl<'a> SemanticModel<'a> {
|
||||
// Allow class variables to be visible for an additional scope level
|
||||
// when a type scope is seen — this covers the type scope present between
|
||||
// function and class definitions and their parent class scope.
|
||||
//
|
||||
// Also allow an additional level beyond that to cover the implicit
|
||||
// `__class__` closure created around methods and enclosing the type scope.
|
||||
class_variables_visible = matches!(
|
||||
(scope.kind, index),
|
||||
(ScopeKind::Type, 0) | (ScopeKind::DunderClassCell, 1)
|
||||
);
|
||||
class_variables_visible = scope.kind.is_type() && index == 0;
|
||||
|
||||
if let Some(binding_id) = scope.get(name.id.as_str()) {
|
||||
// Mark the binding as used.
|
||||
@@ -609,6 +614,7 @@ impl<'a> SemanticModel<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
seen_function |= scope.kind.is_function();
|
||||
import_starred = import_starred || scope.uses_star_imports();
|
||||
}
|
||||
|
||||
@@ -652,19 +658,21 @@ impl<'a> SemanticModel<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
let mut seen_function = false;
|
||||
let mut class_variables_visible = true;
|
||||
for (index, scope_id) in self.scopes.ancestor_ids(scope_id).enumerate() {
|
||||
let scope = &self.scopes[scope_id];
|
||||
if scope.kind.is_class() {
|
||||
if seen_function && matches!(symbol, "__class__") {
|
||||
return None;
|
||||
}
|
||||
if !class_variables_visible {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
class_variables_visible = matches!(
|
||||
(scope.kind, index),
|
||||
(ScopeKind::Type, 0) | (ScopeKind::DunderClassCell, 1)
|
||||
);
|
||||
class_variables_visible = scope.kind.is_type() && index == 0;
|
||||
seen_function |= scope.kind.is_function();
|
||||
|
||||
if let Some(binding_id) = scope.get(symbol) {
|
||||
match self.bindings[binding_id].kind {
|
||||
@@ -778,15 +786,15 @@ impl<'a> SemanticModel<'a> {
|
||||
}
|
||||
|
||||
if scope.kind.is_class() {
|
||||
if seen_function && matches!(symbol, "__class__") {
|
||||
return None;
|
||||
}
|
||||
if !class_variables_visible {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
class_variables_visible = matches!(
|
||||
(scope.kind, index),
|
||||
(ScopeKind::Type, 0) | (ScopeKind::DunderClassCell, 1)
|
||||
);
|
||||
class_variables_visible = scope.kind.is_type() && index == 0;
|
||||
seen_function |= scope.kind.is_function();
|
||||
|
||||
if let Some(binding_id) = scope.get(symbol) {
|
||||
@@ -1345,12 +1353,11 @@ impl<'a> SemanticModel<'a> {
|
||||
self.scopes[scope_id].parent
|
||||
}
|
||||
|
||||
/// Returns the first parent of the given [`Scope`] that is not of [`ScopeKind::Type`] or
|
||||
/// [`ScopeKind::DunderClassCell`], if any.
|
||||
/// Returns the first parent of the given [`Scope`] that is not of [`ScopeKind::Type`], if any.
|
||||
pub fn first_non_type_parent_scope(&self, scope: &Scope) -> Option<&Scope<'a>> {
|
||||
let mut current_scope = scope;
|
||||
while let Some(parent) = self.parent_scope(current_scope) {
|
||||
if matches!(parent.kind, ScopeKind::Type | ScopeKind::DunderClassCell) {
|
||||
if parent.kind.is_type() {
|
||||
current_scope = parent;
|
||||
} else {
|
||||
return Some(parent);
|
||||
@@ -1359,15 +1366,11 @@ impl<'a> SemanticModel<'a> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns the first parent of the given [`ScopeId`] that is not of [`ScopeKind::Type`] or
|
||||
/// [`ScopeKind::DunderClassCell`], if any.
|
||||
/// Returns the first parent of the given [`ScopeId`] that is not of [`ScopeKind::Type`], if any.
|
||||
pub fn first_non_type_parent_scope_id(&self, scope_id: ScopeId) -> Option<ScopeId> {
|
||||
let mut current_scope_id = scope_id;
|
||||
while let Some(parent_id) = self.parent_scope_id(current_scope_id) {
|
||||
if matches!(
|
||||
self.scopes[parent_id].kind,
|
||||
ScopeKind::Type | ScopeKind::DunderClassCell
|
||||
) {
|
||||
if self.scopes[parent_id].kind.is_type() {
|
||||
current_scope_id = parent_id;
|
||||
} else {
|
||||
return Some(parent_id);
|
||||
@@ -2646,16 +2649,16 @@ pub enum ReadResult {
|
||||
/// The `x` in `print(x)` is resolved to the binding of `x` in `x = 1`.
|
||||
Resolved(BindingId),
|
||||
|
||||
/// The read reference is resolved to a context-specific, implicit global (e.g., `__qualname__`
|
||||
/// The read reference is resolved to a context-specific, implicit global (e.g., `__class__`
|
||||
/// within a class scope).
|
||||
///
|
||||
/// For example, given:
|
||||
/// ```python
|
||||
/// class C:
|
||||
/// print(__qualname__)
|
||||
/// print(__class__)
|
||||
/// ```
|
||||
///
|
||||
/// The `__qualname__` in `print(__qualname__)` is resolved to the implicit global `__qualname__`.
|
||||
/// The `__class__` in `print(__class__)` is resolved to the implicit global `__class__`.
|
||||
ImplicitGlobal,
|
||||
|
||||
/// The read reference is unresolved, but at least one of the containing scopes contains a
|
||||
|
||||
@@ -166,49 +166,9 @@ bitflags! {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, is_macro::Is)]
|
||||
#[derive(Debug, is_macro::Is)]
|
||||
pub enum ScopeKind<'a> {
|
||||
Class(&'a ast::StmtClassDef),
|
||||
/// The implicit `__class__` scope surrounding a method which allows code in the
|
||||
/// method to access `__class__` at runtime. The closure sits in between the class
|
||||
/// scope and the function scope.
|
||||
///
|
||||
/// Parameter defaults in methods cannot access `__class__`:
|
||||
///
|
||||
/// ```pycon
|
||||
/// >>> class Bar:
|
||||
/// ... def method(self, x=__class__): ...
|
||||
/// ...
|
||||
/// Traceback (most recent call last):
|
||||
/// File "<python-input-6>", line 1, in <module>
|
||||
/// class Bar:
|
||||
/// def method(self, x=__class__): ...
|
||||
/// File "<python-input-6>", line 2, in Bar
|
||||
/// def method(self, x=__class__): ...
|
||||
/// ^^^^^^^^^
|
||||
/// NameError: name '__class__' is not defined
|
||||
/// ```
|
||||
///
|
||||
/// However, type parameters in methods *can* access `__class__`:
|
||||
///
|
||||
/// ```pycon
|
||||
/// >>> class Foo:
|
||||
/// ... def bar[T: __class__](): ...
|
||||
/// ...
|
||||
/// >>> Foo.bar.__type_params__[0].__bound__
|
||||
/// <class '__main__.Foo'>
|
||||
/// ```
|
||||
///
|
||||
/// Note that this is still not 100% accurate! At runtime, the implicit `__class__`
|
||||
/// closure is only added if the name `super` (has to be a name -- `builtins.super`
|
||||
/// and similar don't count!) or the name `__class__` is used in any method of the
|
||||
/// class. However, accurately emulating that would be both complex and probably
|
||||
/// quite expensive unless we moved to a double-traversal of each scope similar to
|
||||
/// ty. It would also only matter in extreme and unlikely edge cases. So we ignore
|
||||
/// that subtlety for now.
|
||||
///
|
||||
/// See <https://docs.python.org/3/reference/datamodel.html#creating-the-class-object>.
|
||||
DunderClassCell,
|
||||
Function(&'a ast::StmtFunctionDef),
|
||||
Generator {
|
||||
kind: GeneratorKind,
|
||||
|
||||
@@ -71,66 +71,6 @@ pub fn indent<'a>(text: &'a str, prefix: &str) -> Cow<'a, str> {
|
||||
Cow::Owned(result)
|
||||
}
|
||||
|
||||
/// Indent only the first line by the given prefix.
|
||||
///
|
||||
/// This function is useful when you want to indent the first line of a multi-line
|
||||
/// expression while preserving the relative indentation of subsequent lines.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use ruff_python_trivia::textwrap::indent_first_line;
|
||||
///
|
||||
/// assert_eq!(indent_first_line("First line.\nSecond line.\n", " "),
|
||||
/// " First line.\nSecond line.\n");
|
||||
/// ```
|
||||
///
|
||||
/// When indenting, trailing whitespace is stripped from the prefix.
|
||||
/// This means that empty lines remain empty afterwards:
|
||||
///
|
||||
/// ```
|
||||
/// # use ruff_python_trivia::textwrap::indent_first_line;
|
||||
///
|
||||
/// assert_eq!(indent_first_line("\n\n\nSecond line.\n", " "),
|
||||
/// "\n\n\nSecond line.\n");
|
||||
/// ```
|
||||
///
|
||||
/// Leading and trailing whitespace coming from the text itself is
|
||||
/// kept unchanged:
|
||||
///
|
||||
/// ```
|
||||
/// # use ruff_python_trivia::textwrap::indent_first_line;
|
||||
///
|
||||
/// assert_eq!(indent_first_line(" \t Foo ", "->"), "-> \t Foo ");
|
||||
/// ```
|
||||
pub fn indent_first_line<'a>(text: &'a str, prefix: &str) -> Cow<'a, str> {
|
||||
if prefix.is_empty() {
|
||||
return Cow::Borrowed(text);
|
||||
}
|
||||
|
||||
let mut lines = text.universal_newlines();
|
||||
let Some(first_line) = lines.next() else {
|
||||
return Cow::Borrowed(text);
|
||||
};
|
||||
|
||||
let mut result = String::with_capacity(text.len() + prefix.len());
|
||||
|
||||
// Indent only the first line
|
||||
if first_line.trim_whitespace().is_empty() {
|
||||
result.push_str(prefix.trim_whitespace_end());
|
||||
} else {
|
||||
result.push_str(prefix);
|
||||
}
|
||||
result.push_str(first_line.as_full_str());
|
||||
|
||||
// Add remaining lines without indentation
|
||||
for line in lines {
|
||||
result.push_str(line.as_full_str());
|
||||
}
|
||||
|
||||
Cow::Owned(result)
|
||||
}
|
||||
|
||||
/// Removes common leading whitespace from each line.
|
||||
///
|
||||
/// This function will look at each non-empty line and determine the
|
||||
@@ -469,61 +409,6 @@ mod tests {
|
||||
assert_eq!(dedent(text), text);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indent_first_line_empty() {
|
||||
assert_eq!(indent_first_line("\n", " "), "\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[rustfmt::skip]
|
||||
fn indent_first_line_nonempty() {
|
||||
let text = [
|
||||
" foo\n",
|
||||
"bar\n",
|
||||
" baz\n",
|
||||
].join("");
|
||||
let expected = [
|
||||
"// foo\n",
|
||||
"bar\n",
|
||||
" baz\n",
|
||||
].join("");
|
||||
assert_eq!(indent_first_line(&text, "// "), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[rustfmt::skip]
|
||||
fn indent_first_line_empty_line() {
|
||||
let text = [
|
||||
" foo",
|
||||
"bar",
|
||||
"",
|
||||
" baz",
|
||||
].join("\n");
|
||||
let expected = [
|
||||
"// foo",
|
||||
"bar",
|
||||
"",
|
||||
" baz",
|
||||
].join("\n");
|
||||
assert_eq!(indent_first_line(&text, "// "), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[rustfmt::skip]
|
||||
fn indent_first_line_mixed_newlines() {
|
||||
let text = [
|
||||
" foo\r\n",
|
||||
"bar\n",
|
||||
" baz\r",
|
||||
].join("");
|
||||
let expected = [
|
||||
"// foo\r\n",
|
||||
"bar\n",
|
||||
" baz\r",
|
||||
].join("");
|
||||
assert_eq!(indent_first_line(&text, "// "), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[rustfmt::skip]
|
||||
fn adjust_indent() {
|
||||
|
||||
155
crates/ty/docs/rules.md
generated
155
crates/ty/docs/rules.md
generated
@@ -36,7 +36,7 @@ def test(): -> "int":
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L113)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L110)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -58,7 +58,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L157)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L154)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -88,7 +88,7 @@ f(int) # error
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L183)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L180)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -117,7 +117,7 @@ a = 1
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L208)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L205)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -147,7 +147,7 @@ class C(A, B): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L234)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L231)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -177,7 +177,7 @@ class B(A): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L299)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L296)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -202,7 +202,7 @@ class B(A, A): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L320)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L317)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -306,7 +306,7 @@ def test(): -> "Literal[5]":
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L523)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L520)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -334,7 +334,7 @@ class C(A, B): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L547)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L544)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -358,7 +358,7 @@ t[3] # IndexError: tuple index out of range
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L352)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L349)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -422,7 +422,7 @@ class D(A, B, C): ...
|
||||
**Known problems**
|
||||
|
||||
Classes that have "dynamic" definitions of `__slots__` (definitions do not consist
|
||||
of string literals, or tuples of string literals) are not currently considered disjoint
|
||||
of string literals, or tuples of string literals) are not currently considered solid
|
||||
bases by ty.
|
||||
|
||||
Additionally, this check is not exhaustive: many C extensions (including several in
|
||||
@@ -445,7 +445,7 @@ an atypical memory layout.
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L592)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L589)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -470,7 +470,7 @@ func("foo") # error: [invalid-argument-type]
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L632)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L629)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -496,7 +496,7 @@ a: int = ''
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1687)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1663)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -528,7 +528,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-await) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L654)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L651)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -562,7 +562,7 @@ asyncio.run(main())
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L684)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L681)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -584,7 +584,7 @@ class A(42): ... # error: [invalid-base]
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L735)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L732)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -609,7 +609,7 @@ with 1:
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L756)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L753)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -636,7 +636,7 @@ a: str
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L779)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L776)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -678,7 +678,7 @@ except ZeroDivisionError:
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L815)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L812)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -709,7 +709,7 @@ class C[U](Generic[T]): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-key) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L567)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L564)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -738,7 +738,7 @@ alice["height"] # KeyError: 'height'
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L841)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L838)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -771,7 +771,7 @@ def f(t: TypeVar("U")): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L911)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L887)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -803,7 +803,7 @@ class B(metaclass=f): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-named-tuple) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L497)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L494)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -828,37 +828,12 @@ in a class's bases list.
|
||||
TypeError: can only inherit from a NamedTuple type and Generic
|
||||
```
|
||||
|
||||
## `invalid-newtype`
|
||||
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-newtype) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L890)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
|
||||
Checks for the creation of invalid `NewType`s
|
||||
|
||||
**Why is this bad?**
|
||||
|
||||
There are several requirements that you must follow when creating a `NewType`.
|
||||
|
||||
**Examples**
|
||||
|
||||
```python
|
||||
from typing import NewType
|
||||
|
||||
Foo = NewType("Foo", int) # okay
|
||||
Bar = NewType(get_name(), int) # error: NewType name must be a string literal
|
||||
```
|
||||
|
||||
## `invalid-overload`
|
||||
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L938)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L914)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -906,7 +881,7 @@ def foo(x: int) -> int: ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L981)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L957)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -930,7 +905,7 @@ def f(a: int = ''): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L434)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L431)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -962,7 +937,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1001)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L977)
|
||||
</small>
|
||||
|
||||
Checks for `raise` statements that raise non-exceptions or use invalid
|
||||
@@ -1009,7 +984,7 @@ def g():
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L613)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L610)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1032,7 +1007,7 @@ def func() -> int:
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1044)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1020)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1086,7 +1061,7 @@ TODO #14889
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L869)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L866)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1111,7 +1086,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1083)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1059)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1139,7 +1114,7 @@ TYPE_CHECKING = ''
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1107)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1083)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1167,7 +1142,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1159)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1135)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1199,7 +1174,7 @@ f(10) # Error
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1131)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1107)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1231,7 +1206,7 @@ class C:
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1187)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1163)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1264,7 +1239,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1216)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1192)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1287,7 +1262,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-typed-dict-key) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1786)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1762)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1318,7 +1293,7 @@ alice["age"] # KeyError
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1235)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1211)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1345,7 +1320,7 @@ func("string") # error: [no-matching-overload]
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1258)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1234)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1367,7 +1342,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1276)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1252)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1391,7 +1366,7 @@ for i in 34: # TypeError: 'int' object is not iterable
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1327)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1303)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1445,7 +1420,7 @@ def test(): -> "int":
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1663)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1639)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1473,7 +1448,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1418)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1394)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1500,7 +1475,7 @@ class B(A): ... # Error raised here
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1463)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1439)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1525,7 +1500,7 @@ f("foo") # Error raised here
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1441)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1417)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1551,7 +1526,7 @@ def _(x: int):
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1484)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1460)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1595,7 +1570,7 @@ class A:
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1541)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1517)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1620,7 +1595,7 @@ f(x=1, y=2) # Error raised here
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1562)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1538)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1646,7 +1621,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1584)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1560)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1669,7 +1644,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1603)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1579)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1692,7 +1667,7 @@ print(x) # NameError: name 'x' is not defined
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1296)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1272)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1727,7 +1702,7 @@ b1 < b2 < b1 # exception raised here
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1622)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1598)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1753,7 +1728,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1644)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1620)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1776,7 +1751,7 @@ l[1:10:0] # ValueError: slice step cannot be zero
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20ambiguous-protocol-member) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L462)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L459)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1815,7 +1790,7 @@ class SubProto(BaseProto, Protocol):
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20deprecated) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L278)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L275)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1868,7 +1843,7 @@ a = 20 / 0 # type: ignore
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1348)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1324)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1894,7 +1869,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L131)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L128)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1924,7 +1899,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1370)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1346)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1954,7 +1929,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1715)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1691)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1979,7 +1954,7 @@ cast(int, f()) # Redundant
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1523)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1499)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -2030,7 +2005,7 @@ a = 20 / 0 # ty: ignore[division-by-zero]
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1736)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1712)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -2084,7 +2059,7 @@ def g():
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L702)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L699)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -2121,7 +2096,7 @@ class D(C): ... # error: [unsupported-base]
|
||||
<small>
|
||||
Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L260)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L257)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -2143,7 +2118,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
|
||||
<small>
|
||||
Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1396)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1372)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
|
||||
@@ -263,9 +263,6 @@ fn cli_arguments_are_relative_to_the_current_directory() -> anyhow::Result<()> {
|
||||
3 |
|
||||
4 | stat = add(10, 15)
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. <temp_dir>/ (first-party code)
|
||||
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
@@ -480,7 +477,7 @@ fn check_specific_paths() -> anyhow::Result<()> {
|
||||
|
||||
assert_cmd_snapshot!(
|
||||
case.command(),
|
||||
@r"
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
@@ -492,9 +489,6 @@ fn check_specific_paths() -> anyhow::Result<()> {
|
||||
3 |
|
||||
4 | print(z)
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. <temp_dir>/ (first-party code)
|
||||
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
@@ -504,9 +498,6 @@ fn check_specific_paths() -> anyhow::Result<()> {
|
||||
2 | import does_not_exist # error: unresolved-import
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. <temp_dir>/ (first-party code)
|
||||
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
@@ -514,7 +505,7 @@ fn check_specific_paths() -> anyhow::Result<()> {
|
||||
|
||||
----- stderr -----
|
||||
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||
"
|
||||
"###
|
||||
);
|
||||
|
||||
// Now check only the `tests` and `other.py` files.
|
||||
@@ -533,9 +524,6 @@ fn check_specific_paths() -> anyhow::Result<()> {
|
||||
3 |
|
||||
4 | print(z)
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. <temp_dir>/ (first-party code)
|
||||
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
@@ -545,9 +533,6 @@ fn check_specific_paths() -> anyhow::Result<()> {
|
||||
2 | import does_not_exist # error: unresolved-import
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. <temp_dir>/ (first-party code)
|
||||
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
|
||||
@@ -333,10 +333,6 @@ import bar",
|
||||
| ^^^
|
||||
2 | import bar
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. <temp_dir>/ (first-party code)
|
||||
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||
info: 3. <temp_dir>/strange-venv-location/lib/python3.13/site-packages (site-packages)
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
@@ -382,11 +378,6 @@ fn lib64_site_packages_directory_on_unix() -> anyhow::Result<()> {
|
||||
1 | import foo, bar, baz
|
||||
| ^^^
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. <temp_dir>/ (first-party code)
|
||||
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||
info: 3. <temp_dir>/.venv/lib/python3.13/site-packages (site-packages)
|
||||
info: 4. <temp_dir>/.venv/lib64/python3.13/site-packages (site-packages)
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
@@ -1282,9 +1273,6 @@ home = ./
|
||||
3 | from package1 import ChildConda
|
||||
4 | from package1 import WorkingVenv
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. <temp_dir>/project (first-party code)
|
||||
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
@@ -1297,9 +1285,6 @@ home = ./
|
||||
4 | from package1 import WorkingVenv
|
||||
5 | from package1 import BaseConda
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. <temp_dir>/project (first-party code)
|
||||
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
@@ -1312,9 +1297,6 @@ home = ./
|
||||
| ^^^^^^^^
|
||||
5 | from package1 import BaseConda
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. <temp_dir>/project (first-party code)
|
||||
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
@@ -1326,9 +1308,6 @@ home = ./
|
||||
5 | from package1 import BaseConda
|
||||
| ^^^^^^^^
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. <temp_dir>/project (first-party code)
|
||||
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
@@ -1737,10 +1716,6 @@ fn default_root_tests_package() -> anyhow::Result<()> {
|
||||
4 |
|
||||
5 | print(f"{foo} {bar}")
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. <temp_dir>/ (first-party code)
|
||||
info: 2. <temp_dir>/src (first-party code)
|
||||
info: 3. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ fn cli_rule_severity() -> anyhow::Result<()> {
|
||||
|
||||
// Assert that there's an `unresolved-reference` diagnostic (error)
|
||||
// and an unresolved-import (error) diagnostic by default.
|
||||
assert_cmd_snapshot!(case.command(), @r"
|
||||
assert_cmd_snapshot!(case.command(), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
@@ -101,9 +101,6 @@ fn cli_rule_severity() -> anyhow::Result<()> {
|
||||
3 |
|
||||
4 | y = 4 / 0
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. <temp_dir>/ (first-party code)
|
||||
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
@@ -121,7 +118,7 @@ fn cli_rule_severity() -> anyhow::Result<()> {
|
||||
|
||||
----- stderr -----
|
||||
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||
");
|
||||
"###);
|
||||
|
||||
assert_cmd_snapshot!(
|
||||
case
|
||||
@@ -144,9 +141,6 @@ fn cli_rule_severity() -> anyhow::Result<()> {
|
||||
3 |
|
||||
4 | y = 4 / 0
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. <temp_dir>/ (first-party code)
|
||||
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` was selected on the command line
|
||||
|
||||
|
||||
@@ -165,11 +165,16 @@ impl<'db> DefinitionsOrTargets<'db> {
|
||||
ty_python_semantic::types::TypeDefinition::Module(module) => {
|
||||
ResolvedDefinition::Module(module.file(db)?)
|
||||
}
|
||||
ty_python_semantic::types::TypeDefinition::Class(definition)
|
||||
| ty_python_semantic::types::TypeDefinition::Function(definition)
|
||||
| ty_python_semantic::types::TypeDefinition::TypeVar(definition)
|
||||
| ty_python_semantic::types::TypeDefinition::TypeAlias(definition)
|
||||
| ty_python_semantic::types::TypeDefinition::NewType(definition) => {
|
||||
ty_python_semantic::types::TypeDefinition::Class(definition) => {
|
||||
ResolvedDefinition::Definition(definition)
|
||||
}
|
||||
ty_python_semantic::types::TypeDefinition::Function(definition) => {
|
||||
ResolvedDefinition::Definition(definition)
|
||||
}
|
||||
ty_python_semantic::types::TypeDefinition::TypeVar(definition) => {
|
||||
ResolvedDefinition::Definition(definition)
|
||||
}
|
||||
ty_python_semantic::types::TypeDefinition::TypeAlias(definition) => {
|
||||
ResolvedDefinition::Definition(definition)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -199,13 +199,14 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:911:7
|
||||
--> stdlib/builtins.pyi:901:7
|
||||
|
|
||||
910 | @disjoint_base
|
||||
911 | class str(Sequence[str]):
|
||||
899 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
900 |
|
||||
901 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
912 | """str(object='') -> str
|
||||
913 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
902 | """str(object='') -> str
|
||||
903 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:1
|
||||
@@ -227,13 +228,14 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:911:7
|
||||
--> stdlib/builtins.pyi:901:7
|
||||
|
|
||||
910 | @disjoint_base
|
||||
911 | class str(Sequence[str]):
|
||||
899 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
900 |
|
||||
901 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
912 | """str(object='') -> str
|
||||
913 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
902 | """str(object='') -> str
|
||||
903 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:10
|
||||
@@ -342,13 +344,14 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:911:7
|
||||
--> stdlib/builtins.pyi:901:7
|
||||
|
|
||||
910 | @disjoint_base
|
||||
911 | class str(Sequence[str]):
|
||||
899 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
900 |
|
||||
901 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
912 | """str(object='') -> str
|
||||
913 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
902 | """str(object='') -> str
|
||||
903 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:6
|
||||
@@ -376,13 +379,14 @@ mod tests {
|
||||
// is an int. Navigating to `str` would match pyright's behavior.
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:344:7
|
||||
--> stdlib/builtins.pyi:337:7
|
||||
|
|
||||
343 | @disjoint_base
|
||||
344 | class int:
|
||||
335 | _LiteralInteger = _PositiveInteger | _NegativeInteger | Literal[0] # noqa: Y026 # TODO: Use TypeAlias once mypy bugs are fixed
|
||||
336 |
|
||||
337 | class int:
|
||||
| ^^^
|
||||
345 | """int([x]) -> integer
|
||||
346 | int(x, base=10) -> integer
|
||||
338 | """int([x]) -> integer
|
||||
339 | int(x, base=10) -> integer
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:6
|
||||
@@ -407,15 +411,16 @@ f(**kwargs<CURSOR>)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
assert_snapshot!(test.goto_type_definition(), @r###"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:2917:7
|
||||
--> stdlib/builtins.pyi:2901:7
|
||||
|
|
||||
2916 | @disjoint_base
|
||||
2917 | class dict(MutableMapping[_KT, _VT]):
|
||||
2899 | """See PEP 585"""
|
||||
2900 |
|
||||
2901 | class dict(MutableMapping[_KT, _VT]):
|
||||
| ^^^^
|
||||
2918 | """dict() -> new empty dictionary
|
||||
2919 | dict(mapping) -> new dictionary initialized from a mapping object's
|
||||
2902 | """dict() -> new empty dictionary
|
||||
2903 | dict(mapping) -> new dictionary initialized from a mapping object's
|
||||
|
|
||||
info: Source
|
||||
--> main.py:6:5
|
||||
@@ -425,7 +430,7 @@ f(**kwargs<CURSOR>)
|
||||
6 | f(**kwargs)
|
||||
| ^^^^^^
|
||||
|
|
||||
"#);
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -439,13 +444,14 @@ f(**kwargs<CURSOR>)
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:911:7
|
||||
--> stdlib/builtins.pyi:901:7
|
||||
|
|
||||
910 | @disjoint_base
|
||||
911 | class str(Sequence[str]):
|
||||
899 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
900 |
|
||||
901 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
912 | """str(object='') -> str
|
||||
913 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
902 | """str(object='') -> str
|
||||
903 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:3:5
|
||||
@@ -531,13 +537,14 @@ f(**kwargs<CURSOR>)
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:911:7
|
||||
--> stdlib/builtins.pyi:901:7
|
||||
|
|
||||
910 | @disjoint_base
|
||||
911 | class str(Sequence[str]):
|
||||
899 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
900 |
|
||||
901 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
912 | """str(object='') -> str
|
||||
913 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
902 | """str(object='') -> str
|
||||
903 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:15
|
||||
@@ -561,13 +568,13 @@ f(**kwargs<CURSOR>)
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/types.pyi:941:11
|
||||
--> stdlib/types.pyi:922:11
|
||||
|
|
||||
939 | if sys.version_info >= (3, 10):
|
||||
940 | @final
|
||||
941 | class NoneType:
|
||||
920 | if sys.version_info >= (3, 10):
|
||||
921 | @final
|
||||
922 | class NoneType:
|
||||
| ^^^^^^^^
|
||||
942 | """The type of the None singleton."""
|
||||
923 | """The type of the None singleton."""
|
||||
|
|
||||
info: Source
|
||||
--> main.py:3:5
|
||||
@@ -578,13 +585,14 @@ f(**kwargs<CURSOR>)
|
||||
|
|
||||
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:911:7
|
||||
--> stdlib/builtins.pyi:901:7
|
||||
|
|
||||
910 | @disjoint_base
|
||||
911 | class str(Sequence[str]):
|
||||
899 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
900 |
|
||||
901 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
912 | """str(object='') -> str
|
||||
913 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
902 | """str(object='') -> str
|
||||
903 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:3:5
|
||||
|
||||
@@ -1,128 +1,62 @@
|
||||
use std::{fmt, vec};
|
||||
|
||||
use crate::Db;
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast::visitor::source_order::{self, SourceOrderVisitor, TraversalSignal};
|
||||
use ruff_python_ast::{AnyNodeRef, Expr, Stmt};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use std::fmt;
|
||||
use std::fmt::Formatter;
|
||||
use ty_python_semantic::types::{Type, inlay_hint_function_argument_details};
|
||||
use ty_python_semantic::{HasType, SemanticModel};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InlayHint {
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct InlayHint<'db> {
|
||||
pub position: TextSize,
|
||||
pub kind: InlayHintKind,
|
||||
pub label: InlayHintLabel,
|
||||
pub content: InlayHintContent<'db>,
|
||||
}
|
||||
|
||||
impl InlayHint {
|
||||
fn variable_type(position: TextSize, ty: Type, db: &dyn Db) -> Self {
|
||||
let label_parts = vec![
|
||||
": ".into(),
|
||||
InlayHintLabelPart::new(ty.display(db).to_string()),
|
||||
];
|
||||
|
||||
Self {
|
||||
position,
|
||||
kind: InlayHintKind::Type,
|
||||
label: InlayHintLabel { parts: label_parts },
|
||||
}
|
||||
}
|
||||
|
||||
fn call_argument_name(position: TextSize, name: &str) -> Self {
|
||||
let label_parts = vec![InlayHintLabelPart::new(name), "=".into()];
|
||||
|
||||
Self {
|
||||
position,
|
||||
kind: InlayHintKind::CallArgumentName,
|
||||
label: InlayHintLabel { parts: label_parts },
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display(&self) -> InlayHintDisplay<'_> {
|
||||
InlayHintDisplay { inlay_hint: self }
|
||||
impl<'db> InlayHint<'db> {
|
||||
pub const fn display(&self, db: &'db dyn Db) -> DisplayInlayHint<'_, 'db> {
|
||||
self.content.display(db)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum InlayHintKind {
|
||||
Type,
|
||||
CallArgumentName,
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum InlayHintContent<'db> {
|
||||
Type(Type<'db>),
|
||||
CallArgumentName(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InlayHintLabel {
|
||||
parts: Vec<InlayHintLabelPart>,
|
||||
}
|
||||
|
||||
impl InlayHintLabel {
|
||||
pub fn parts(&self) -> &[InlayHintLabelPart] {
|
||||
&self.parts
|
||||
impl<'db> InlayHintContent<'db> {
|
||||
pub const fn display(&self, db: &'db dyn Db) -> DisplayInlayHint<'_, 'db> {
|
||||
DisplayInlayHint { db, hint: self }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InlayHintDisplay<'a> {
|
||||
inlay_hint: &'a InlayHint,
|
||||
pub struct DisplayInlayHint<'a, 'db> {
|
||||
db: &'db dyn Db,
|
||||
hint: &'a InlayHintContent<'db>,
|
||||
}
|
||||
|
||||
impl fmt::Display for InlayHintDisplay<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
for part in &self.inlay_hint.label.parts {
|
||||
write!(f, "{}", part.text)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct InlayHintLabelPart {
|
||||
text: String,
|
||||
|
||||
target: Option<crate::NavigationTarget>,
|
||||
}
|
||||
|
||||
impl InlayHintLabelPart {
|
||||
pub fn new(text: impl Into<String>) -> Self {
|
||||
Self {
|
||||
text: text.into(),
|
||||
target: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text(&self) -> &str {
|
||||
&self.text
|
||||
}
|
||||
|
||||
pub fn target(&self) -> Option<&crate::NavigationTarget> {
|
||||
self.target.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for InlayHintLabelPart {
|
||||
fn from(s: String) -> Self {
|
||||
Self {
|
||||
text: s,
|
||||
target: None,
|
||||
impl fmt::Display for DisplayInlayHint<'_, '_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self.hint {
|
||||
InlayHintContent::Type(ty) => {
|
||||
write!(f, ": {}", ty.display(self.db))
|
||||
}
|
||||
InlayHintContent::CallArgumentName(name) => {
|
||||
write!(f, "{name}=")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for InlayHintLabelPart {
|
||||
fn from(s: &str) -> Self {
|
||||
Self {
|
||||
text: s.to_string(),
|
||||
target: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inlay_hints(
|
||||
db: &dyn Db,
|
||||
pub fn inlay_hints<'db>(
|
||||
db: &'db dyn Db,
|
||||
file: File,
|
||||
range: TextRange,
|
||||
settings: &InlayHintSettings,
|
||||
) -> Vec<InlayHint> {
|
||||
) -> Vec<InlayHint<'db>> {
|
||||
let mut visitor = InlayHintVisitor::new(db, file, range, settings);
|
||||
|
||||
let ast = parsed_module(db, file).load(db);
|
||||
@@ -172,7 +106,7 @@ impl Default for InlayHintSettings {
|
||||
struct InlayHintVisitor<'a, 'db> {
|
||||
db: &'db dyn Db,
|
||||
model: SemanticModel<'db>,
|
||||
hints: Vec<InlayHint>,
|
||||
hints: Vec<InlayHint<'db>>,
|
||||
in_assignment: bool,
|
||||
range: TextRange,
|
||||
settings: &'a InlayHintSettings,
|
||||
@@ -194,11 +128,13 @@ impl<'a, 'db> InlayHintVisitor<'a, 'db> {
|
||||
if !self.settings.variable_types {
|
||||
return;
|
||||
}
|
||||
self.hints
|
||||
.push(InlayHint::variable_type(position, ty, self.db));
|
||||
self.hints.push(InlayHint {
|
||||
position,
|
||||
content: InlayHintContent::Type(ty),
|
||||
});
|
||||
}
|
||||
|
||||
fn add_call_argument_name(&mut self, position: TextSize, name: &str) {
|
||||
fn add_call_argument_name(&mut self, position: TextSize, name: String) {
|
||||
if !self.settings.call_argument_names {
|
||||
return;
|
||||
}
|
||||
@@ -207,8 +143,10 @@ impl<'a, 'db> InlayHintVisitor<'a, 'db> {
|
||||
return;
|
||||
}
|
||||
|
||||
self.hints
|
||||
.push(InlayHint::call_argument_name(position, name));
|
||||
self.hints.push(InlayHint {
|
||||
position,
|
||||
content: InlayHintContent::CallArgumentName(name),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,7 +212,10 @@ impl SourceOrderVisitor<'_> for InlayHintVisitor<'_, '_> {
|
||||
|
||||
for (index, arg_or_keyword) in call.arguments.arguments_source_order().enumerate() {
|
||||
if let Some(name) = argument_names.get(&index) {
|
||||
self.add_call_argument_name(arg_or_keyword.range().start(), name);
|
||||
self.add_call_argument_name(
|
||||
arg_or_keyword.range().start(),
|
||||
name.to_string(),
|
||||
);
|
||||
}
|
||||
self.visit_expr(arg_or_keyword.value());
|
||||
}
|
||||
@@ -381,7 +322,7 @@ mod tests {
|
||||
|
||||
for hint in hints {
|
||||
let end_position = (hint.position.to_u32() as usize) + offset;
|
||||
let hint_str = format!("[{}]", hint.display());
|
||||
let hint_str = format!("[{}]", hint.display(&self.db));
|
||||
buf.insert_str(end_position, &hint_str);
|
||||
offset += hint_str.len();
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ pub use document_symbols::document_symbols;
|
||||
pub use goto::{goto_declaration, goto_definition, goto_type_definition};
|
||||
pub use goto_references::goto_references;
|
||||
pub use hover::hover;
|
||||
pub use inlay_hints::{InlayHintKind, InlayHintLabel, InlayHintSettings, inlay_hints};
|
||||
pub use inlay_hints::{InlayHintContent, InlayHintSettings, inlay_hints};
|
||||
pub use markup::MarkupKind;
|
||||
pub use references::ReferencesMode;
|
||||
pub use rename::{can_rename, rename};
|
||||
|
||||
@@ -137,22 +137,6 @@ from unittest.mock import MagicMock
|
||||
x: int = MagicMock()
|
||||
```
|
||||
|
||||
## Runtime properties
|
||||
|
||||
`typing.Any` is a class at runtime on Python 3.11+, and `typing_extensions.Any` is always a class.
|
||||
On earlier versions of Python, `typing.Any` was an instance of `typing._SpecialForm`, but this is
|
||||
not currently modeled by ty. We currently infer `Any` has having all attributes a class would have
|
||||
on all versions of Python:
|
||||
|
||||
```py
|
||||
from typing import Any
|
||||
from ty_extensions import TypeOf, static_assert, is_assignable_to
|
||||
|
||||
reveal_type(Any.__base__) # revealed: type | None
|
||||
reveal_type(Any.__bases__) # revealed: tuple[type, ...]
|
||||
static_assert(is_assignable_to(TypeOf[Any], type))
|
||||
```
|
||||
|
||||
## Invalid
|
||||
|
||||
`Any` cannot be parameterized:
|
||||
@@ -160,32 +144,7 @@ static_assert(is_assignable_to(TypeOf[Any], type))
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
# error: [invalid-type-form] "Special form `typing.Any` expected no type parameter"
|
||||
# error: [invalid-type-form] "Type `typing.Any` expected no type parameter"
|
||||
def f(x: Any[int]):
|
||||
reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
`Any` cannot be called (this leads to a `TypeError` at runtime):
|
||||
|
||||
```py
|
||||
Any() # error: [call-non-callable] "Object of type `typing.Any` is not callable"
|
||||
```
|
||||
|
||||
`Any` also cannot be used as a metaclass (under the hood, this leads to an implicit call to `Any`):
|
||||
|
||||
```py
|
||||
class F(metaclass=Any): ... # error: [invalid-metaclass] "Metaclass type `typing.Any` is not callable"
|
||||
```
|
||||
|
||||
And `Any` cannot be used in `isinstance()` checks:
|
||||
|
||||
```py
|
||||
# error: [invalid-argument-type] "`typing.Any` cannot be used with `isinstance()`: This call will raise `TypeError` at runtime"
|
||||
isinstance("", Any)
|
||||
```
|
||||
|
||||
But `issubclass()` checks are fine:
|
||||
|
||||
```py
|
||||
issubclass(object, Any) # no error!
|
||||
```
|
||||
|
||||
@@ -398,7 +398,7 @@ def f_okay(c: Callable[[], None]):
|
||||
c.__qualname__ = "my_callable"
|
||||
|
||||
result = getattr_static(c, "__qualname__")
|
||||
reveal_type(result) # revealed: property
|
||||
reveal_type(result) # revealed: Never
|
||||
if isinstance(result, property) and result.fset:
|
||||
c.__qualname__ = "my_callable" # okay
|
||||
```
|
||||
|
||||
@@ -48,24 +48,6 @@ def _(
|
||||
reveal_type(h_) # revealed: Unknown
|
||||
reveal_type(i_) # revealed: Unknown
|
||||
reveal_type(j_) # revealed: Unknown
|
||||
|
||||
# Inspired by the conformance test suite at
|
||||
# https://github.com/python/typing/blob/d4f39b27a4a47aac8b6d4019e1b0b5b3156fabdc/conformance/tests/aliases_implicit.py#L88-L122
|
||||
B = [x for x in range(42)]
|
||||
C = {x for x in range(42)}
|
||||
D = {x: y for x, y in enumerate(range(42))}
|
||||
E = (x for x in range(42))
|
||||
|
||||
def _(
|
||||
b: B, # error: [invalid-type-form]
|
||||
c: C, # error: [invalid-type-form]
|
||||
d: D, # error: [invalid-type-form]
|
||||
e: E, # error: [invalid-type-form]
|
||||
):
|
||||
reveal_type(b) # revealed: Unknown
|
||||
reveal_type(c) # revealed: Unknown
|
||||
reveal_type(d) # revealed: Unknown
|
||||
reveal_type(e) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Invalid AST nodes
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# NewType
|
||||
|
||||
Currently, ty doesn't support `typing.NewType` in type annotations.
|
||||
|
||||
## Valid forms
|
||||
|
||||
```py
|
||||
@@ -10,44 +12,13 @@ X = GenericAlias(type, ())
|
||||
A = NewType("A", int)
|
||||
# TODO: typeshed for `typing.GenericAlias` uses `type` for the first argument. `NewType` should be special-cased
|
||||
# to be compatible with `type`
|
||||
# error: [invalid-argument-type] "Argument to function `__new__` is incorrect: Expected `type`, found `A`"
|
||||
# error: [invalid-argument-type] "Argument to function `__new__` is incorrect: Expected `type`, found `NewType`"
|
||||
B = GenericAlias(A, ())
|
||||
|
||||
def _(
|
||||
a: A,
|
||||
b: B,
|
||||
):
|
||||
reveal_type(a) # revealed: A
|
||||
reveal_type(a) # revealed: @Todo(Support for `typing.NewType` instances in type expressions)
|
||||
reveal_type(b) # revealed: @Todo(Support for `typing.GenericAlias` instances in type expressions)
|
||||
```
|
||||
|
||||
## Subtyping
|
||||
|
||||
```py
|
||||
from typing_extensions import NewType
|
||||
|
||||
Foo = NewType("Foo", int)
|
||||
Bar = NewType("Bar", Foo)
|
||||
|
||||
Foo(42)
|
||||
Foo(Foo(42)) # allowed: `Foo` is a subtype of `int`.
|
||||
Foo(Bar(Foo(42))) # allowed: `Bar` is a subtype of `int`.
|
||||
Foo(True) # allowed: `bool` is a subtype of `int`.
|
||||
Foo("fourty-two") # error: [invalid-argument-type]
|
||||
|
||||
def f(_: int): ...
|
||||
def g(_: Foo): ...
|
||||
def h(_: Bar): ...
|
||||
|
||||
f(42)
|
||||
f(Foo(42))
|
||||
f(Bar(Foo(42)))
|
||||
|
||||
g(42) # error: [invalid-argument-type]
|
||||
g(Foo(42))
|
||||
g(Bar(Foo(42)))
|
||||
|
||||
h(42) # error: [invalid-argument-type]
|
||||
h(Foo(42)) # error: [invalid-argument-type]
|
||||
h(Bar(Foo(42)))
|
||||
```
|
||||
|
||||
@@ -75,7 +75,7 @@ a: tuple[()] = (1, 2)
|
||||
# error: [invalid-assignment] "Object of type `tuple[Literal["foo"]]` is not assignable to `tuple[int]`"
|
||||
b: tuple[int] = ("foo",)
|
||||
|
||||
# error: [invalid-assignment]
|
||||
# error: [invalid-assignment] "Object of type `tuple[list[Unknown], Literal["foo"]]` is not assignable to `tuple[str | int, str]`"
|
||||
c: tuple[str | int, str] = ([], "foo")
|
||||
```
|
||||
|
||||
|
||||
@@ -1038,49 +1038,6 @@ def _(int_str: tuple[int, str], int_any: tuple[int, Any], any_any: tuple[Any, An
|
||||
reveal_type(f(*(any_any,))) # revealed: Unknown
|
||||
```
|
||||
|
||||
### `Unknown` passed into an overloaded function annotated with protocols
|
||||
|
||||
`Foo.join()` here has similar annotations to `str.join()` in typeshed:
|
||||
|
||||
`module.pyi`:
|
||||
|
||||
```pyi
|
||||
from typing_extensions import Iterable, overload, LiteralString, Protocol
|
||||
from ty_extensions import Unknown, is_assignable_to
|
||||
|
||||
class Foo:
|
||||
@overload
|
||||
def join(self, iterable: Iterable[LiteralString], /) -> LiteralString: ...
|
||||
@overload
|
||||
def join(self, iterable: Iterable[str], /) -> str: ...
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
from module import Foo
|
||||
from typing_extensions import LiteralString
|
||||
|
||||
def f(a: Foo, b: list[str], c: list[LiteralString], e):
|
||||
reveal_type(e) # revealed: Unknown
|
||||
|
||||
# TODO: we should select the second overload here and reveal `str`
|
||||
# (the incorrect result is due to missing logic in protocol subtyping/assignability)
|
||||
reveal_type(a.join(b)) # revealed: LiteralString
|
||||
|
||||
reveal_type(a.join(c)) # revealed: LiteralString
|
||||
|
||||
# since both overloads match and they have return types that are not equivalent,
|
||||
# step (5) of the overload evaluation algorithm says we must evaluate the result of the
|
||||
# call as `Unknown`.
|
||||
#
|
||||
# Note: although the spec does not state as such (since intersections in general are not
|
||||
# specified currently), `(str | LiteralString) & Unknown` might also be a reasonable type
|
||||
# here (the union of all overload returns, intersected with `Unknown`) -- here that would
|
||||
# simplify to `str & Unknown`.
|
||||
reveal_type(a.join(e)) # revealed: Unknown
|
||||
```
|
||||
|
||||
### Multiple arguments
|
||||
|
||||
`overloaded.pyi`:
|
||||
|
||||
@@ -46,7 +46,7 @@ def delete():
|
||||
del d # error: [unresolved-reference] "Name `d` used when not defined"
|
||||
|
||||
delete()
|
||||
reveal_type(d) # revealed: list[@Todo(list literal element type)]
|
||||
reveal_type(d) # revealed: list[Unknown]
|
||||
|
||||
def delete_element():
|
||||
# When the `del` target isn't a name, it doesn't force local resolution.
|
||||
@@ -62,7 +62,7 @@ def delete_global():
|
||||
|
||||
delete_global()
|
||||
# Again, the variable should have been removed, but we don't check it.
|
||||
reveal_type(d) # revealed: list[@Todo(list literal element type)]
|
||||
reveal_type(d) # revealed: list[Unknown]
|
||||
|
||||
def delete_nonlocal():
|
||||
e = 2
|
||||
|
||||
@@ -1,230 +0,0 @@
|
||||
# Identical type display names in diagnostics
|
||||
|
||||
ty prints the fully qualified name to disambiguate objects with the same name.
|
||||
|
||||
## Nested class
|
||||
|
||||
`test.py`:
|
||||
|
||||
```py
|
||||
class A:
|
||||
class B:
|
||||
pass
|
||||
|
||||
class C:
|
||||
class B:
|
||||
pass
|
||||
|
||||
a: A.B = C.B() # error: [invalid-assignment] "Object of type `test.C.B` is not assignable to `test.A.B`"
|
||||
```
|
||||
|
||||
## Nested class in function
|
||||
|
||||
`test.py`:
|
||||
|
||||
```py
|
||||
class B:
|
||||
pass
|
||||
|
||||
def f(b: B):
|
||||
class B:
|
||||
pass
|
||||
|
||||
# error: [invalid-assignment] "Object of type `test.<locals of function 'f'>.B` is not assignable to `test.B`"
|
||||
b = B()
|
||||
```
|
||||
|
||||
## Class from different modules
|
||||
|
||||
```py
|
||||
import a
|
||||
import b
|
||||
|
||||
df: a.DataFrame = b.DataFrame() # error: [invalid-assignment] "Object of type `b.DataFrame` is not assignable to `a.DataFrame`"
|
||||
|
||||
def _(dfs: list[b.DataFrame]):
|
||||
# TODO should be"Object of type `list[b.DataFrame]` is not assignable to `list[a.DataFrame]`
|
||||
# error: [invalid-assignment] "Object of type `list[DataFrame]` is not assignable to `list[DataFrame]`"
|
||||
dataframes: list[a.DataFrame] = dfs
|
||||
```
|
||||
|
||||
`a.py`:
|
||||
|
||||
```py
|
||||
class DataFrame:
|
||||
pass
|
||||
```
|
||||
|
||||
`b.py`:
|
||||
|
||||
```py
|
||||
class DataFrame:
|
||||
pass
|
||||
```
|
||||
|
||||
## Enum from different modules
|
||||
|
||||
```py
|
||||
import status_a
|
||||
import status_b
|
||||
|
||||
# error: [invalid-assignment] "Object of type `Literal[status_b.Status.ACTIVE]` is not assignable to `status_a.Status`"
|
||||
s: status_a.Status = status_b.Status.ACTIVE
|
||||
```
|
||||
|
||||
`status_a.py`:
|
||||
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
class Status(Enum):
|
||||
ACTIVE = 1
|
||||
INACTIVE = 2
|
||||
```
|
||||
|
||||
`status_b.py`:
|
||||
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
class Status(Enum):
|
||||
ACTIVE = "active"
|
||||
INACTIVE = "inactive"
|
||||
```
|
||||
|
||||
## Nested enum
|
||||
|
||||
`test.py`:
|
||||
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
class A:
|
||||
class B(Enum):
|
||||
ACTIVE = "active"
|
||||
INACTIVE = "inactive"
|
||||
|
||||
class C:
|
||||
class B(Enum):
|
||||
ACTIVE = "active"
|
||||
INACTIVE = "inactive"
|
||||
|
||||
# error: [invalid-assignment] "Object of type `Literal[test.C.B.ACTIVE]` is not assignable to `test.A.B`"
|
||||
a: A.B = C.B.ACTIVE
|
||||
```
|
||||
|
||||
## Class literals
|
||||
|
||||
```py
|
||||
import cls_a
|
||||
import cls_b
|
||||
|
||||
# error: [invalid-assignment] "Object of type `<class 'cls_b.Config'>` is not assignable to `type[cls_a.Config]`"
|
||||
config_class: type[cls_a.Config] = cls_b.Config
|
||||
```
|
||||
|
||||
`cls_a.py`:
|
||||
|
||||
```py
|
||||
class Config:
|
||||
pass
|
||||
```
|
||||
|
||||
`cls_b.py`:
|
||||
|
||||
```py
|
||||
class Config:
|
||||
pass
|
||||
```
|
||||
|
||||
## Generic aliases
|
||||
|
||||
```py
|
||||
import generic_a
|
||||
import generic_b
|
||||
|
||||
# TODO should be error: [invalid-assignment] "Object of type `<class 'generic_b.Container[int]'>` is not assignable to `type[generic_a.Container[int]]`"
|
||||
container: type[generic_a.Container[int]] = generic_b.Container[int]
|
||||
```
|
||||
|
||||
`generic_a.py`:
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class Container(Generic[T]):
|
||||
pass
|
||||
```
|
||||
|
||||
`generic_b.py`:
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class Container(Generic[T]):
|
||||
pass
|
||||
```
|
||||
|
||||
## Protocols
|
||||
|
||||
```py
|
||||
from typing import Protocol
|
||||
import proto_a
|
||||
import proto_b
|
||||
|
||||
# TODO should be error: [invalid-assignment] "Object of type `proto_b.Drawable` is not assignable to `proto_a.Drawable`"
|
||||
def _(drawable_b: proto_b.Drawable):
|
||||
drawable: proto_a.Drawable = drawable_b
|
||||
```
|
||||
|
||||
`proto_a.py`:
|
||||
|
||||
```py
|
||||
from typing import Protocol
|
||||
|
||||
class Drawable(Protocol):
|
||||
def draw(self) -> None: ...
|
||||
```
|
||||
|
||||
`proto_b.py`:
|
||||
|
||||
```py
|
||||
from typing import Protocol
|
||||
|
||||
class Drawable(Protocol):
|
||||
def draw(self) -> int: ...
|
||||
```
|
||||
|
||||
## TypedDict
|
||||
|
||||
```py
|
||||
from typing import TypedDict
|
||||
import dict_a
|
||||
import dict_b
|
||||
|
||||
def _(b_person: dict_b.Person):
|
||||
# TODO should be error: [invalid-assignment] "Object of type `dict_b.Person` is not assignable to `dict_a.Person`"
|
||||
person_var: dict_a.Person = b_person
|
||||
```
|
||||
|
||||
`dict_a.py`:
|
||||
|
||||
```py
|
||||
from typing import TypedDict
|
||||
|
||||
class Person(TypedDict):
|
||||
name: str
|
||||
```
|
||||
|
||||
`dict_b.py`:
|
||||
|
||||
```py
|
||||
from typing import TypedDict
|
||||
|
||||
class Person(TypedDict):
|
||||
name: bytes
|
||||
```
|
||||
@@ -33,6 +33,12 @@ def _():
|
||||
def _():
|
||||
assert_never(None) # error: [type-assertion-failure]
|
||||
|
||||
def _():
|
||||
assert_never([]) # error: [type-assertion-failure]
|
||||
|
||||
def _():
|
||||
assert_never({}) # error: [type-assertion-failure]
|
||||
|
||||
def _():
|
||||
assert_never(()) # error: [type-assertion-failure]
|
||||
|
||||
|
||||
@@ -785,7 +785,7 @@ from subexporter import *
|
||||
|
||||
# TODO: Should be `list[str]`
|
||||
# TODO: Should we avoid including `Unknown` for this case?
|
||||
reveal_type(__all__) # revealed: Unknown | list[@Todo(list literal element type)]
|
||||
reveal_type(__all__) # revealed: Unknown | list[Unknown]
|
||||
|
||||
__all__.append("B")
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ class E( # error: [instance-layout-conflict]
|
||||
): ...
|
||||
```
|
||||
|
||||
## A single "disjoint base"
|
||||
## A single "solid base"
|
||||
|
||||
```py
|
||||
class A:
|
||||
@@ -152,15 +152,14 @@ class Baz(Foo, Bar): ... # fine
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
Certain classes implemented in C extensions also have an extended instance memory layout, in the
|
||||
same way as classes that define non-empty `__slots__`. CPython internally calls all such classes
|
||||
with a unique instance memory layout "solid bases", but [PEP 800](https://peps.python.org/pep-0800/)
|
||||
calls these classes "disjoint bases", and this is the term we generally use. The `@disjoint_base`
|
||||
decorator introduced by this PEP provides a generalised way for type checkers to identify such
|
||||
classes.
|
||||
same way as classes that define non-empty `__slots__`. (CPython internally calls all such classes
|
||||
with a unique instance memory layout "solid bases", and we also borrow this term.) There is
|
||||
currently no generalized way for ty to detect such a C-extension class, as there is currently no way
|
||||
of expressing the fact that a class is a solid base in a stub file. However, ty special-cases
|
||||
certain builtin classes in order to detect that attempting to combine them in a single MRO would
|
||||
fail:
|
||||
|
||||
```py
|
||||
from typing_extensions import disjoint_base
|
||||
|
||||
# fmt: off
|
||||
|
||||
class A( # error: [instance-layout-conflict]
|
||||
@@ -184,17 +183,6 @@ class E( # error: [instance-layout-conflict]
|
||||
|
||||
class F(int, str, bytes, bytearray): ... # error: [instance-layout-conflict]
|
||||
|
||||
@disjoint_base
|
||||
class G: ...
|
||||
|
||||
@disjoint_base
|
||||
class H: ...
|
||||
|
||||
class I( # error: [instance-layout-conflict]
|
||||
G,
|
||||
H
|
||||
): ...
|
||||
|
||||
# fmt: on
|
||||
```
|
||||
|
||||
@@ -205,9 +193,9 @@ We avoid emitting an `instance-layout-conflict` diagnostic for this class defini
|
||||
class Foo(range, str): ... # error: [subclass-of-final-class]
|
||||
```
|
||||
|
||||
## Multiple "disjoint bases" where one is a subclass of the other
|
||||
## Multiple "solid bases" where one is a subclass of the other
|
||||
|
||||
A class is permitted to multiple-inherit from multiple disjoint bases if one is a subclass of the
|
||||
A class is permitted to multiple-inherit from multiple solid bases if one is a subclass of the
|
||||
other:
|
||||
|
||||
```py
|
||||
|
||||
@@ -3,12 +3,5 @@
|
||||
## Empty dictionary
|
||||
|
||||
```py
|
||||
reveal_type({}) # revealed: dict[@Todo(dict literal key type), @Todo(dict literal value type)]
|
||||
```
|
||||
|
||||
## Dict comprehensions
|
||||
|
||||
```py
|
||||
# revealed: dict[@Todo(dict comprehension key type), @Todo(dict comprehension value type)]
|
||||
reveal_type({x: y for x, y in enumerate(range(42))})
|
||||
reveal_type({}) # revealed: dict[Unknown, Unknown]
|
||||
```
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
# Generator expressions
|
||||
|
||||
```py
|
||||
# revealed: GeneratorType[@Todo(generator expression yield type), @Todo(generator expression send type), @Todo(generator expression return type)]
|
||||
reveal_type((x for x in range(42)))
|
||||
```
|
||||
@@ -3,11 +3,5 @@
|
||||
## Empty list
|
||||
|
||||
```py
|
||||
reveal_type([]) # revealed: list[@Todo(list literal element type)]
|
||||
```
|
||||
|
||||
## List comprehensions
|
||||
|
||||
```py
|
||||
reveal_type([x for x in range(42)]) # revealed: list[@Todo(list comprehension element type)]
|
||||
reveal_type([]) # revealed: list[Unknown]
|
||||
```
|
||||
|
||||
@@ -3,11 +3,5 @@
|
||||
## Basic set
|
||||
|
||||
```py
|
||||
reveal_type({1, 2}) # revealed: set[@Todo(set literal element type)]
|
||||
```
|
||||
|
||||
## Set comprehensions
|
||||
|
||||
```py
|
||||
reveal_type({x for x in range(42)}) # revealed: set[@Todo(set comprehension element type)]
|
||||
reveal_type({1, 2}) # revealed: set[Unknown]
|
||||
```
|
||||
|
||||
@@ -207,7 +207,7 @@ dd[0] = 0
|
||||
cm: ChainMap[int, int] = ChainMap({1: 1}, {0: 0})
|
||||
cm[0] = 0
|
||||
# TODO: should be ChainMap[int, int]
|
||||
reveal_type(cm) # revealed: ChainMap[@Todo(dict literal key type), @Todo(dict literal value type)]
|
||||
reveal_type(cm) # revealed: ChainMap[Unknown, Unknown]
|
||||
|
||||
reveal_type(l[0]) # revealed: Literal[0]
|
||||
reveal_type(d[0]) # revealed: Literal[0]
|
||||
|
||||
@@ -318,7 +318,7 @@ def f(l: list[str | None]):
|
||||
l: list[str | None] = [None]
|
||||
def _():
|
||||
# TODO: should be `str | None`
|
||||
reveal_type(l[0]) # revealed: @Todo(list literal element type)
|
||||
reveal_type(l[0]) # revealed: Unknown
|
||||
|
||||
def _():
|
||||
def _():
|
||||
|
||||
@@ -75,32 +75,6 @@ def f(x: T):
|
||||
reveal_type(b) # revealed: str
|
||||
```
|
||||
|
||||
## Scoping
|
||||
|
||||
PEP 695 type aliases delay runtime evaluation of their right-hand side, so they are a lazy (not
|
||||
eager) nested scope.
|
||||
|
||||
```py
|
||||
type Alias = Foo | str
|
||||
|
||||
def f(x: Alias):
|
||||
reveal_type(x) # revealed: Foo | str
|
||||
|
||||
class Foo:
|
||||
pass
|
||||
```
|
||||
|
||||
But narrowing of names used in the type alias is still respected:
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
t = int if flag else None
|
||||
if t is not None:
|
||||
type Alias = t | str
|
||||
def f(x: Alias):
|
||||
reveal_type(x) # revealed: int | str
|
||||
```
|
||||
|
||||
## Generic type aliases
|
||||
|
||||
```py
|
||||
@@ -204,17 +178,13 @@ def f(x: OptNestedInt) -> None:
|
||||
### Invalid self-referential
|
||||
|
||||
```py
|
||||
# TODO emit a diagnostic on these two lines
|
||||
# TODO emit a diagnostic here
|
||||
type IntOr = int | IntOr
|
||||
type OrInt = OrInt | int
|
||||
|
||||
def f(x: IntOr, y: OrInt):
|
||||
def f(x: IntOr):
|
||||
reveal_type(x) # revealed: int
|
||||
reveal_type(y) # revealed: int
|
||||
if not isinstance(x, int):
|
||||
reveal_type(x) # revealed: Never
|
||||
if not isinstance(y, int):
|
||||
reveal_type(y) # revealed: Never
|
||||
```
|
||||
|
||||
### Mutually recursive
|
||||
@@ -238,42 +208,3 @@ from ty_extensions import Intersection
|
||||
def h(x: Intersection[A, B]):
|
||||
reveal_type(x) # revealed: tuple[B] | None
|
||||
```
|
||||
|
||||
### Union inside generic
|
||||
|
||||
#### With old-style union
|
||||
|
||||
```py
|
||||
from typing import Union
|
||||
|
||||
type A = list[Union["A", str]]
|
||||
|
||||
def f(x: A):
|
||||
reveal_type(x) # revealed: list[A | str]
|
||||
for item in x:
|
||||
reveal_type(item) # revealed: list[A | str] | str
|
||||
```
|
||||
|
||||
#### With new-style union
|
||||
|
||||
```py
|
||||
type A = list["A" | str]
|
||||
|
||||
def f(x: A):
|
||||
reveal_type(x) # revealed: list[A | str]
|
||||
for item in x:
|
||||
reveal_type(item) # revealed: list[A | str] | str
|
||||
```
|
||||
|
||||
#### With Optional
|
||||
|
||||
```py
|
||||
from typing import Optional, Union
|
||||
|
||||
type A = list[Optional[Union["A", str]]]
|
||||
|
||||
def f(x: A):
|
||||
reveal_type(x) # revealed: list[A | str | None]
|
||||
for item in x:
|
||||
reveal_type(item) # revealed: list[A | str | None] | str | None
|
||||
```
|
||||
|
||||
@@ -95,20 +95,6 @@ class NotAProtocol: ...
|
||||
reveal_type(is_protocol(NotAProtocol)) # revealed: Literal[False]
|
||||
```
|
||||
|
||||
Note, however, that `is_protocol` returns `False` at runtime for specializations of generic
|
||||
protocols. We still consider these to be "protocol classes" internally, regardless:
|
||||
|
||||
```py
|
||||
class MyGenericProtocol[T](Protocol):
|
||||
x: T
|
||||
|
||||
reveal_type(is_protocol(MyGenericProtocol)) # revealed: Literal[True]
|
||||
|
||||
# We still consider this a protocol class internally,
|
||||
# but the inferred type of the call here reflects the result at runtime:
|
||||
reveal_type(is_protocol(MyGenericProtocol[int])) # revealed: Literal[False]
|
||||
```
|
||||
|
||||
A type checker should follow the typeshed stubs if a non-class is passed in, and typeshed's stubs
|
||||
indicate that the argument passed in must be an instance of `type`.
|
||||
|
||||
@@ -411,38 +397,24 @@ To see the kinds and types of the protocol members, you can use the debugging ai
|
||||
|
||||
```py
|
||||
from ty_extensions import reveal_protocol_interface
|
||||
from typing import SupportsIndex, SupportsAbs, ClassVar, Iterator
|
||||
from typing import SupportsIndex, SupportsAbs, ClassVar
|
||||
|
||||
# error: [revealed-type] "Revealed protocol interface: `{"method_member": MethodMember(`(self) -> bytes`), "x": AttributeMember(`int`), "y": PropertyMember { getter: `def y(self) -> str` }, "z": PropertyMember { getter: `def z(self) -> int`, setter: `def z(self, z: int) -> None` }}`"
|
||||
reveal_protocol_interface(Foo)
|
||||
# error: [revealed-type] "Revealed protocol interface: `{"__index__": MethodMember(`(self) -> int`)}`"
|
||||
reveal_protocol_interface(SupportsIndex)
|
||||
# error: [revealed-type] "Revealed protocol interface: `{"__abs__": MethodMember(`(self) -> Unknown`)}`"
|
||||
# error: [revealed-type] "Revealed protocol interface: `{"__abs__": MethodMember(`(self) -> _T_co@SupportsAbs`)}`"
|
||||
reveal_protocol_interface(SupportsAbs)
|
||||
# error: [revealed-type] "Revealed protocol interface: `{"__iter__": MethodMember(`(self) -> Iterator[Unknown]`), "__next__": MethodMember(`(self) -> Unknown`)}`"
|
||||
reveal_protocol_interface(Iterator)
|
||||
|
||||
# error: [invalid-argument-type] "Invalid argument to `reveal_protocol_interface`: Only protocol classes can be passed to `reveal_protocol_interface`"
|
||||
reveal_protocol_interface(int)
|
||||
# error: [invalid-argument-type] "Argument to function `reveal_protocol_interface` is incorrect: Expected `type`, found `Literal["foo"]`"
|
||||
reveal_protocol_interface("foo")
|
||||
```
|
||||
|
||||
Similar to the way that `typing.is_protocol` returns `False` at runtime for all generic aliases,
|
||||
`typing.get_protocol_members` raises an exception at runtime if you pass it a generic alias, so we
|
||||
do not implement any special handling for generic aliases passed to the function.
|
||||
`ty_extensions.reveal_protocol_interface` can be used on both, however:
|
||||
|
||||
```py
|
||||
# TODO: these fail at runtime, but we don't emit `[invalid-argument-type]` diagnostics
|
||||
# currently due to https://github.com/astral-sh/ty/issues/116
|
||||
reveal_type(get_protocol_members(SupportsAbs[int])) # revealed: frozenset[str]
|
||||
reveal_type(get_protocol_members(Iterator[int])) # revealed: frozenset[str]
|
||||
|
||||
# error: [revealed-type] "Revealed protocol interface: `{"__abs__": MethodMember(`(self) -> int`)}`"
|
||||
# TODO: this should be a `revealed-type` diagnostic rather than `invalid-argument-type`, and it should reveal `{"__abs__": MethodMember(`(self) -> int`)}` for the protocol interface
|
||||
#
|
||||
# error: [invalid-argument-type] "Invalid argument to `reveal_protocol_interface`: Only protocol classes can be passed to `reveal_protocol_interface`"
|
||||
reveal_protocol_interface(SupportsAbs[int])
|
||||
# error: [revealed-type] "Revealed protocol interface: `{"__iter__": MethodMember(`(self) -> Iterator[int]`), "__next__": MethodMember(`(self) -> int`)}`"
|
||||
reveal_protocol_interface(Iterator[int])
|
||||
|
||||
class BaseProto(Protocol):
|
||||
def member(self) -> int: ...
|
||||
@@ -1060,11 +1032,6 @@ class A(Protocol):
|
||||
|
||||
## Equivalence of protocols
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
Two protocols are considered equivalent types if they specify the same interface, even if they have
|
||||
different names:
|
||||
|
||||
@@ -1113,46 +1080,6 @@ static_assert(is_equivalent_to(UnionProto1, UnionProto2))
|
||||
static_assert(is_equivalent_to(UnionProto1 | A | B, B | UnionProto2 | A))
|
||||
```
|
||||
|
||||
Different generic protocols with equivalent specializations can be equivalent, but generic protocols
|
||||
with different specializations are not considered equivalent:
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
S = TypeVar("S")
|
||||
|
||||
class NonGenericProto1(Protocol):
|
||||
x: int
|
||||
y: str
|
||||
|
||||
class NonGenericProto2(Protocol):
|
||||
y: str
|
||||
x: int
|
||||
|
||||
class Nominal1: ...
|
||||
class Nominal2: ...
|
||||
|
||||
class GenericProto[T](Protocol):
|
||||
x: T
|
||||
|
||||
class LegacyGenericProto(Protocol[S]):
|
||||
x: S
|
||||
|
||||
static_assert(is_equivalent_to(GenericProto[int], LegacyGenericProto[int]))
|
||||
static_assert(is_equivalent_to(GenericProto[NonGenericProto1], LegacyGenericProto[NonGenericProto2]))
|
||||
|
||||
static_assert(
|
||||
is_equivalent_to(
|
||||
GenericProto[NonGenericProto1 | Nominal1 | Nominal2], LegacyGenericProto[Nominal2 | Nominal1 | NonGenericProto2]
|
||||
)
|
||||
)
|
||||
|
||||
static_assert(not is_equivalent_to(GenericProto[str], GenericProto[int]))
|
||||
static_assert(not is_equivalent_to(GenericProto[str], LegacyGenericProto[int]))
|
||||
static_assert(not is_equivalent_to(GenericProto, GenericProto[int]))
|
||||
static_assert(not is_equivalent_to(LegacyGenericProto, LegacyGenericProto[int]))
|
||||
```
|
||||
|
||||
## Intersections of protocols
|
||||
|
||||
An intersection of two protocol types `X` and `Y` is equivalent to a protocol type `Z` that inherits
|
||||
@@ -1465,16 +1392,10 @@ static_assert(is_subtype_of(XClassVar, HasXProperty))
|
||||
static_assert(is_assignable_to(XClassVar, HasXProperty))
|
||||
|
||||
class XFinal:
|
||||
x: Final[int] = 42
|
||||
x: Final = 42
|
||||
|
||||
static_assert(is_subtype_of(XFinal, HasXProperty))
|
||||
static_assert(is_assignable_to(XFinal, HasXProperty))
|
||||
|
||||
class XImplicitFinal:
|
||||
x: Final = 42
|
||||
|
||||
static_assert(is_subtype_of(XImplicitFinal, HasXProperty))
|
||||
static_assert(is_assignable_to(XImplicitFinal, HasXProperty))
|
||||
```
|
||||
|
||||
A read-only property on a protocol, unlike a mutable attribute, is covariant: `XSub` in the below
|
||||
@@ -1530,8 +1451,9 @@ static_assert(is_assignable_to(XReadWriteProperty, HasXProperty))
|
||||
class XSub:
|
||||
x: MyInt
|
||||
|
||||
static_assert(not is_subtype_of(XSub, XReadWriteProperty))
|
||||
static_assert(not is_assignable_to(XSub, XReadWriteProperty))
|
||||
# TODO: should pass
|
||||
static_assert(not is_subtype_of(XSub, HasXProperty)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(XSub, HasXProperty)) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
A protocol with a read/write property `x` is exactly equivalent to a protocol with a mutable
|
||||
@@ -1627,7 +1549,7 @@ class Descriptor:
|
||||
def __get__(self, instance, owner) -> MyInt:
|
||||
return MyInt(0)
|
||||
|
||||
def __set__(self, instance, value: int) -> None: ...
|
||||
def __set__(self, value: int) -> None: ...
|
||||
|
||||
class XCustomDescriptor:
|
||||
x: Descriptor = Descriptor()
|
||||
@@ -1673,16 +1595,6 @@ static_assert(is_assignable_to(HasGetAttrAndSetAttr, HasXProperty))
|
||||
# TODO: these should pass
|
||||
static_assert(is_subtype_of(HasGetAttrAndSetAttr, XAsymmetricProperty)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(HasGetAttrAndSetAttr, XAsymmetricProperty)) # error: [static-assert-error]
|
||||
|
||||
class HasSetAttrWithUnsuitableInput:
|
||||
def __getattr__(self, attr: str) -> int:
|
||||
return 1
|
||||
|
||||
def __setattr__(self, attr: str, value: str) -> None: ...
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(not is_subtype_of(HasSetAttrWithUnsuitableInput, HasMutableXProperty)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(HasSetAttrWithUnsuitableInput, HasMutableXProperty)) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
## Subtyping of protocols with method members
|
||||
@@ -1772,12 +1684,11 @@ class Bar:
|
||||
f(Bar()) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
## Equivalence of protocols with method or property members
|
||||
## Equivalence of protocols with method members
|
||||
|
||||
Two protocols `P1` and `P2`, both with a method member `x`, are considered equivalent if the
|
||||
signature of `P1.x` is equivalent to the signature of `P2.x`, even though ty would normally model
|
||||
any two function definitions as inhabiting distinct function-literal types. The same is also true
|
||||
for property members.
|
||||
any two function definitions as inhabiting distinct function-literal types.
|
||||
|
||||
```py
|
||||
from typing import Protocol
|
||||
@@ -1789,26 +1700,7 @@ class P1(Protocol):
|
||||
class P2(Protocol):
|
||||
def x(self, y: int) -> None: ...
|
||||
|
||||
class P3(Protocol):
|
||||
@property
|
||||
def y(self) -> str: ...
|
||||
@property
|
||||
def z(self) -> bytes: ...
|
||||
@z.setter
|
||||
def z(self, value: int) -> None: ...
|
||||
|
||||
class P4(Protocol):
|
||||
@property
|
||||
def y(self) -> str: ...
|
||||
@property
|
||||
def z(self) -> bytes: ...
|
||||
@z.setter
|
||||
def z(self, value: int) -> None: ...
|
||||
|
||||
static_assert(is_equivalent_to(P1, P2))
|
||||
|
||||
# TODO: should pass
|
||||
static_assert(is_equivalent_to(P3, P4)) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
As with protocols that only have non-method members, this also holds true when they appear in
|
||||
@@ -1819,9 +1711,6 @@ class A: ...
|
||||
class B: ...
|
||||
|
||||
static_assert(is_equivalent_to(A | B | P1, P2 | B | A))
|
||||
|
||||
# TODO: should pass
|
||||
static_assert(is_equivalent_to(A | B | P3, P4 | B | A)) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
## Narrowing of protocols
|
||||
@@ -2309,69 +2198,6 @@ def f(value: Iterator):
|
||||
cast(Iterator, value) # error: [redundant-cast]
|
||||
```
|
||||
|
||||
### Recursive generic protocols
|
||||
|
||||
This snippet caused us to stack overflow on an early version of
|
||||
<https://github.com/astral-sh/ruff/pull/19866>:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Protocol, TypeVar
|
||||
|
||||
class A: ...
|
||||
|
||||
class Foo[T](Protocol):
|
||||
def x(self) -> "T | Foo[T]": ...
|
||||
|
||||
y: A | Foo[A]
|
||||
|
||||
# The same thing, but using the legacy syntax:
|
||||
|
||||
S = TypeVar("S")
|
||||
|
||||
class Bar(Protocol[S]):
|
||||
def x(self) -> "S | Bar[S]": ...
|
||||
|
||||
z: S | Bar[S]
|
||||
```
|
||||
|
||||
### Recursive generic protocols with property members
|
||||
|
||||
An early version of <https://github.com/astral-sh/ruff/pull/19936> caused stack overflows on this
|
||||
snippet:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Protocol
|
||||
|
||||
class Foo[T]: ...
|
||||
|
||||
class A(Protocol):
|
||||
@property
|
||||
def _(self: "A") -> Foo: ...
|
||||
|
||||
class B(Protocol):
|
||||
@property
|
||||
def b(self) -> Foo[A]: ...
|
||||
|
||||
class C(Undefined): ... # error: "Name `Undefined` used when not defined"
|
||||
|
||||
class D:
|
||||
b: Foo[C]
|
||||
|
||||
class E[T: B](Protocol): ...
|
||||
|
||||
x: E[D]
|
||||
```
|
||||
|
||||
## Meta-protocols
|
||||
|
||||
Where `P` is a protocol type, a class object `N` can be said to inhabit the type `type[P]` if:
|
||||
|
||||
@@ -25,16 +25,22 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/directives/assert_never.
|
||||
11 | assert_never(None) # error: [type-assertion-failure]
|
||||
12 |
|
||||
13 | def _():
|
||||
14 | assert_never(()) # error: [type-assertion-failure]
|
||||
14 | assert_never([]) # error: [type-assertion-failure]
|
||||
15 |
|
||||
16 | def _(flag: bool, never: Never):
|
||||
17 | assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||
16 | def _():
|
||||
17 | assert_never({}) # error: [type-assertion-failure]
|
||||
18 |
|
||||
19 | def _(any_: Any):
|
||||
20 | assert_never(any_) # error: [type-assertion-failure]
|
||||
19 | def _():
|
||||
20 | assert_never(()) # error: [type-assertion-failure]
|
||||
21 |
|
||||
22 | def _(unknown: Unknown):
|
||||
23 | assert_never(unknown) # error: [type-assertion-failure]
|
||||
22 | def _(flag: bool, never: Never):
|
||||
23 | assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||
24 |
|
||||
25 | def _(any_: Any):
|
||||
26 | assert_never(any_) # error: [type-assertion-failure]
|
||||
27 |
|
||||
28 | def _(unknown: Unknown):
|
||||
29 | assert_never(unknown) # error: [type-assertion-failure]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
@@ -95,14 +101,14 @@ error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||
--> src/mdtest_snippet.py:14:5
|
||||
|
|
||||
13 | def _():
|
||||
14 | assert_never(()) # error: [type-assertion-failure]
|
||||
14 | assert_never([]) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^--^
|
||||
| |
|
||||
| Inferred type of argument is `tuple[()]`
|
||||
| Inferred type of argument is `list[Unknown]`
|
||||
15 |
|
||||
16 | def _(flag: bool, never: Never):
|
||||
16 | def _():
|
||||
|
|
||||
info: `Never` and `tuple[()]` are not equivalent types
|
||||
info: `Never` and `list[Unknown]` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
|
||||
```
|
||||
@@ -111,15 +117,15 @@ info: rule `type-assertion-failure` is enabled by default
|
||||
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||
--> src/mdtest_snippet.py:17:5
|
||||
|
|
||||
16 | def _(flag: bool, never: Never):
|
||||
17 | assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^--------------------^
|
||||
16 | def _():
|
||||
17 | assert_never({}) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^--^
|
||||
| |
|
||||
| Inferred type of argument is `Literal[1]`
|
||||
| Inferred type of argument is `dict[Unknown, Unknown]`
|
||||
18 |
|
||||
19 | def _(any_: Any):
|
||||
19 | def _():
|
||||
|
|
||||
info: `Never` and `Literal[1]` are not equivalent types
|
||||
info: `Never` and `dict[Unknown, Unknown]` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
|
||||
```
|
||||
@@ -128,15 +134,15 @@ info: rule `type-assertion-failure` is enabled by default
|
||||
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||
--> src/mdtest_snippet.py:20:5
|
||||
|
|
||||
19 | def _(any_: Any):
|
||||
20 | assert_never(any_) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^----^
|
||||
19 | def _():
|
||||
20 | assert_never(()) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^--^
|
||||
| |
|
||||
| Inferred type of argument is `Any`
|
||||
| Inferred type of argument is `tuple[()]`
|
||||
21 |
|
||||
22 | def _(unknown: Unknown):
|
||||
22 | def _(flag: bool, never: Never):
|
||||
|
|
||||
info: `Never` and `Any` are not equivalent types
|
||||
info: `Never` and `tuple[()]` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
|
||||
```
|
||||
@@ -145,8 +151,42 @@ info: rule `type-assertion-failure` is enabled by default
|
||||
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||
--> src/mdtest_snippet.py:23:5
|
||||
|
|
||||
22 | def _(unknown: Unknown):
|
||||
23 | assert_never(unknown) # error: [type-assertion-failure]
|
||||
22 | def _(flag: bool, never: Never):
|
||||
23 | assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^--------------------^
|
||||
| |
|
||||
| Inferred type of argument is `Literal[1]`
|
||||
24 |
|
||||
25 | def _(any_: Any):
|
||||
|
|
||||
info: `Never` and `Literal[1]` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||
--> src/mdtest_snippet.py:26:5
|
||||
|
|
||||
25 | def _(any_: Any):
|
||||
26 | assert_never(any_) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^----^
|
||||
| |
|
||||
| Inferred type of argument is `Any`
|
||||
27 |
|
||||
28 | def _(unknown: Unknown):
|
||||
|
|
||||
info: `Never` and `Any` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||
--> src/mdtest_snippet.py:29:5
|
||||
|
|
||||
28 | def _(unknown: Unknown):
|
||||
29 | assert_never(unknown) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^-------^
|
||||
| |
|
||||
| Inferred type of argument is `Unknown`
|
||||
|
||||
@@ -26,9 +26,6 @@ error[unresolved-import]: Cannot resolve imported module `does_not_exist`
|
||||
2 | from does_not_exist import foo, bar, baz
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. /src (first-party code)
|
||||
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
|
||||
@@ -24,9 +24,6 @@ error[unresolved-import]: Cannot resolve imported module `zqzqzqzqzqzqzq`
|
||||
1 | import zqzqzqzqzqzqzq # error: [unresolved-import] "Cannot resolve imported module `zqzqzqzqzqzqzq`"
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. /src (first-party code)
|
||||
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
|
||||
@@ -36,9 +36,6 @@ error[unresolved-import]: Cannot resolve imported module `a.foo`
|
||||
3 |
|
||||
4 | # Topmost component unresolvable:
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. /src (first-party code)
|
||||
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
@@ -52,9 +49,6 @@ error[unresolved-import]: Cannot resolve imported module `b.foo`
|
||||
5 | import b.foo # error: [unresolved-import] "Cannot resolve imported module `b.foo`"
|
||||
| ^^^^^
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. /src (first-party code)
|
||||
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
|
||||
@@ -91,15 +91,15 @@ error[missing-argument]: No argument provided for required parameter `arg` of bo
|
||||
7 | from typing_extensions import deprecated
|
||||
|
|
||||
info: Parameter declared here
|
||||
--> stdlib/typing_extensions.pyi:1000:28
|
||||
|
|
||||
998 | stacklevel: int
|
||||
999 | def __init__(self, message: LiteralString, /, *, category: type[Warning] | None = ..., stacklevel: int = 1) -> None: ...
|
||||
1000 | def __call__(self, arg: _T, /) -> _T: ...
|
||||
| ^^^^^^^
|
||||
1001 |
|
||||
1002 | @final
|
||||
|
|
||||
--> stdlib/typing_extensions.pyi:973:28
|
||||
|
|
||||
971 | stacklevel: int
|
||||
972 | def __init__(self, message: LiteralString, /, *, category: type[Warning] | None = ..., stacklevel: int = 1) -> None: ...
|
||||
973 | def __call__(self, arg: _T, /) -> _T: ...
|
||||
| ^^^^^^^
|
||||
974 |
|
||||
975 | @final
|
||||
|
|
||||
info: rule `missing-argument` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user