Compare commits
18 Commits
0.14.6
...
alex/from-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b549d6d47c | ||
|
|
3410041b4c | ||
|
|
f2ce5e561a | ||
|
|
f495c6d4ae | ||
|
|
768bb24cdf | ||
|
|
492d676736 | ||
|
|
ddc1417f22 | ||
|
|
040aa7463b | ||
|
|
09d457aa52 | ||
|
|
438ef334d3 | ||
|
|
6cc502781f | ||
|
|
e2a1d1a8eb | ||
|
|
040b482cf7 | ||
|
|
03dfbf21eb | ||
|
|
e3c78d8203 | ||
|
|
a9b3caf181 | ||
|
|
629258241f | ||
|
|
762c44527e |
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -3588,7 +3588,7 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
[[package]]
|
||||
name = "salsa"
|
||||
version = "0.24.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=a885bb4c4c192741b8a17418fef81a71e33d111e#a885bb4c4c192741b8a17418fef81a71e33d111e"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=17bc55d699565e5a1cb1bd42363b905af2f9f3e7#17bc55d699565e5a1cb1bd42363b905af2f9f3e7"
|
||||
dependencies = [
|
||||
"boxcar",
|
||||
"compact_str",
|
||||
@@ -3612,12 +3612,12 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "salsa-macro-rules"
|
||||
version = "0.24.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=a885bb4c4c192741b8a17418fef81a71e33d111e#a885bb4c4c192741b8a17418fef81a71e33d111e"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=17bc55d699565e5a1cb1bd42363b905af2f9f3e7#17bc55d699565e5a1cb1bd42363b905af2f9f3e7"
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macros"
|
||||
version = "0.24.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=a885bb4c4c192741b8a17418fef81a71e33d111e#a885bb4c4c192741b8a17418fef81a71e33d111e"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=17bc55d699565e5a1cb1bd42363b905af2f9f3e7#17bc55d699565e5a1cb1bd42363b905af2f9f3e7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -146,7 +146,7 @@ regex-automata = { version = "0.4.9" }
|
||||
rustc-hash = { version = "2.0.0" }
|
||||
rustc-stable-hash = { version = "0.1.2" }
|
||||
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "a885bb4c4c192741b8a17418fef81a71e33d111e", default-features = false, features = [
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "17bc55d699565e5a1cb1bd42363b905af2f9f3e7", default-features = false, features = [
|
||||
"compact_str",
|
||||
"macros",
|
||||
"salsa_unstable",
|
||||
|
||||
@@ -208,3 +208,17 @@ _ = t"b {f"c" f"d {t"e" t"f"} g"} h"
|
||||
_ = f"b {t"abc" \
|
||||
t"def"} g"
|
||||
|
||||
|
||||
# Explicit concatenation with either operand being
|
||||
# a string literal that wraps across multiple lines (in parentheses)
|
||||
# reports diagnostic - no autofix.
|
||||
# See https://github.com/astral-sh/ruff/issues/19757
|
||||
_ = "abc" + (
|
||||
"def"
|
||||
"ghi"
|
||||
)
|
||||
|
||||
_ = (
|
||||
"abc"
|
||||
"def"
|
||||
) + "ghi"
|
||||
|
||||
@@ -30,3 +30,23 @@ for a, b in d_tuple:
|
||||
pass
|
||||
for a, b in d_tuple_annotated:
|
||||
pass
|
||||
|
||||
# Empty dict cases
|
||||
empty_dict = {}
|
||||
empty_dict["x"] = 1
|
||||
for k, v in empty_dict:
|
||||
pass
|
||||
|
||||
empty_dict_annotated_tuple_keys: dict[tuple[int, str], bool] = {}
|
||||
for k, v in empty_dict_annotated_tuple_keys:
|
||||
pass
|
||||
|
||||
empty_dict_unannotated = {}
|
||||
empty_dict_unannotated[("x", "y")] = True
|
||||
for k, v in empty_dict_unannotated:
|
||||
pass
|
||||
|
||||
empty_dict_annotated_str_keys: dict[str, int] = {}
|
||||
empty_dict_annotated_str_keys["x"] = 1
|
||||
for k, v in empty_dict_annotated_str_keys:
|
||||
pass
|
||||
|
||||
@@ -129,3 +129,26 @@ def generator_with_lambda():
|
||||
yield 1
|
||||
func = lambda x: x # Just a regular lambda
|
||||
yield 2
|
||||
|
||||
# See: https://github.com/astral-sh/ruff/issues/21162
|
||||
def foo():
|
||||
def g():
|
||||
yield 1
|
||||
raise StopIteration # Should not trigger
|
||||
|
||||
|
||||
def foo():
|
||||
def g():
|
||||
raise StopIteration # Should not trigger
|
||||
yield 1
|
||||
|
||||
# https://github.com/astral-sh/ruff/pull/21177#pullrequestreview-3430209718
|
||||
def foo():
|
||||
yield 1
|
||||
class C:
|
||||
raise StopIteration # Should trigger
|
||||
yield C
|
||||
|
||||
# https://github.com/astral-sh/ruff/pull/21177#discussion_r2539702728
|
||||
def foo():
|
||||
raise StopIteration((yield 1)) # Should trigger
|
||||
109
crates/ruff_linter/resources/test/fixtures/ruff/RUF052_1.py
vendored
Normal file
109
crates/ruff_linter/resources/test/fixtures/ruff/RUF052_1.py
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
# Correct usage in loop and comprehension
|
||||
def process_data():
|
||||
return 42
|
||||
def test_correct_dummy_usage():
|
||||
my_list = [{"foo": 1}, {"foo": 2}]
|
||||
|
||||
# Should NOT detect - dummy variable is not used
|
||||
[process_data() for _ in my_list] # OK: `_` is ignored by rule
|
||||
|
||||
# Should NOT detect - dummy variable is not used
|
||||
[item["foo"] for item in my_list] # OK: not a dummy variable name
|
||||
|
||||
# Should NOT detect - dummy variable is not used
|
||||
[42 for _unused in my_list] # OK: `_unused` is not accessed
|
||||
|
||||
# Regular For Loops
|
||||
def test_for_loops():
|
||||
my_list = [{"foo": 1}, {"foo": 2}]
|
||||
|
||||
# Should detect used dummy variable
|
||||
for _item in my_list:
|
||||
print(_item["foo"]) # RUF052: Local dummy variable `_item` is accessed
|
||||
|
||||
# Should detect used dummy variable
|
||||
for _index, _value in enumerate(my_list):
|
||||
result = _index + _value["foo"] # RUF052: Both `_index` and `_value` are accessed
|
||||
|
||||
# List Comprehensions
|
||||
def test_list_comprehensions():
|
||||
my_list = [{"foo": 1}, {"foo": 2}]
|
||||
|
||||
# Should detect used dummy variable
|
||||
result = [_item["foo"] for _item in my_list] # RUF052: Local dummy variable `_item` is accessed
|
||||
|
||||
# Should detect used dummy variable in nested comprehension
|
||||
nested = [[_item["foo"] for _item in _sublist] for _sublist in [my_list, my_list]]
|
||||
# RUF052: Both `_item` and `_sublist` are accessed
|
||||
|
||||
# Should detect with conditions
|
||||
filtered = [_item["foo"] for _item in my_list if _item["foo"] > 0]
|
||||
# RUF052: Local dummy variable `_item` is accessed
|
||||
|
||||
# Dict Comprehensions
|
||||
def test_dict_comprehensions():
|
||||
my_list = [{"key": "a", "value": 1}, {"key": "b", "value": 2}]
|
||||
|
||||
# Should detect used dummy variable
|
||||
result = {_item["key"]: _item["value"] for _item in my_list}
|
||||
# RUF052: Local dummy variable `_item` is accessed
|
||||
|
||||
# Should detect with enumerate
|
||||
indexed = {_index: _item["value"] for _index, _item in enumerate(my_list)}
|
||||
# RUF052: Both `_index` and `_item` are accessed
|
||||
|
||||
# Should detect in nested dict comprehension
|
||||
nested = {_outer: {_inner["key"]: _inner["value"] for _inner in sublist}
|
||||
for _outer, sublist in enumerate([my_list])}
|
||||
# RUF052: `_outer`, `_inner` are accessed
|
||||
|
||||
# Set Comprehensions
|
||||
def test_set_comprehensions():
|
||||
my_list = [{"foo": 1}, {"foo": 2}, {"foo": 1}] # Note: duplicate values
|
||||
|
||||
# Should detect used dummy variable
|
||||
unique_values = {_item["foo"] for _item in my_list}
|
||||
# RUF052: Local dummy variable `_item` is accessed
|
||||
|
||||
# Should detect with conditions
|
||||
filtered_set = {_item["foo"] for _item in my_list if _item["foo"] > 0}
|
||||
# RUF052: Local dummy variable `_item` is accessed
|
||||
|
||||
# Should detect with complex expression
|
||||
processed = {_item["foo"] * 2 for _item in my_list}
|
||||
# RUF052: Local dummy variable `_item` is accessed
|
||||
|
||||
# Generator Expressions
|
||||
def test_generator_expressions():
|
||||
my_list = [{"foo": 1}, {"foo": 2}]
|
||||
|
||||
# Should detect used dummy variable
|
||||
gen = (_item["foo"] for _item in my_list)
|
||||
# RUF052: Local dummy variable `_item` is accessed
|
||||
|
||||
# Should detect when passed to function
|
||||
total = sum(_item["foo"] for _item in my_list)
|
||||
# RUF052: Local dummy variable `_item` is accessed
|
||||
|
||||
# Should detect with multiple generators
|
||||
pairs = ((_x, _y) for _x in range(3) for _y in range(3) if _x != _y)
|
||||
# RUF052: Both `_x` and `_y` are accessed
|
||||
|
||||
# Should detect in nested generator
|
||||
nested_gen = (sum(_inner["foo"] for _inner in sublist) for _sublist in [my_list] for sublist in _sublist)
|
||||
# RUF052: `_inner` and `_sublist` are accessed
|
||||
|
||||
# Complex Examples with Multiple Comprehension Types
|
||||
def test_mixed_comprehensions():
|
||||
data = [{"items": [1, 2, 3]}, {"items": [4, 5, 6]}]
|
||||
|
||||
# Should detect in mixed comprehensions
|
||||
result = [
|
||||
{_key: [_val * 2 for _val in _record["items"]] for _key in ["doubled"]}
|
||||
for _record in data
|
||||
]
|
||||
# RUF052: `_key`, `_val`, and `_record` are all accessed
|
||||
|
||||
# Should detect in generator passed to list constructor
|
||||
gen_list = list(_item["items"][0] for _item in data)
|
||||
# RUF052: Local dummy variable `_item` is accessed
|
||||
@@ -131,6 +131,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.is_rule_enabled(Rule::GeneratorReturnFromIterMethod) {
|
||||
flake8_pyi::rules::bad_generator_return_type(function_def, checker);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::StopIterationReturn) {
|
||||
pylint::rules::stop_iteration_return(checker, function_def);
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.is_rule_enabled(Rule::StrOrReprDefinedInStub) {
|
||||
flake8_pyi::rules::str_or_repr_defined_in_stub(checker, stmt);
|
||||
@@ -950,9 +953,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.is_rule_enabled(Rule::MisplacedBareRaise) {
|
||||
pylint::rules::misplaced_bare_raise(checker, raise);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::StopIterationReturn) {
|
||||
pylint::rules::stop_iteration_return(checker, raise);
|
||||
}
|
||||
}
|
||||
Stmt::AugAssign(aug_assign @ ast::StmtAugAssign { target, .. }) => {
|
||||
if checker.is_rule_enabled(Rule::GlobalStatement) {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::{self as ast, Expr, Operator};
|
||||
use ruff_python_trivia::is_python_whitespace;
|
||||
use ruff_source_file::LineRanges;
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::AlwaysFixableViolation;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::{Edit, Fix};
|
||||
use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for string literals that are explicitly concatenated (using the
|
||||
@@ -36,14 +36,16 @@ use crate::{Edit, Fix};
|
||||
#[violation_metadata(stable_since = "v0.0.201")]
|
||||
pub(crate) struct ExplicitStringConcatenation;
|
||||
|
||||
impl AlwaysFixableViolation for ExplicitStringConcatenation {
|
||||
impl Violation for ExplicitStringConcatenation {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"Explicitly concatenated string should be implicitly concatenated".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Remove redundant '+' operator to implicitly concatenate".to_string()
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Remove redundant '+' operator to implicitly concatenate".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,9 +84,27 @@ pub(crate) fn explicit(checker: &Checker, expr: &Expr) {
|
||||
.locator()
|
||||
.contains_line_break(TextRange::new(left.end(), right.start()))
|
||||
{
|
||||
checker
|
||||
.report_diagnostic(ExplicitStringConcatenation, expr.range())
|
||||
.set_fix(generate_fix(checker, bin_op));
|
||||
let mut diagnostic =
|
||||
checker.report_diagnostic(ExplicitStringConcatenation, expr.range());
|
||||
|
||||
let is_parenthesized = |expr: &Expr| {
|
||||
parenthesized_range(
|
||||
expr.into(),
|
||||
bin_op.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
)
|
||||
.is_some()
|
||||
};
|
||||
// If either `left` or `right` is parenthesized, generating
|
||||
// a fix would be too involved. Just report the diagnostic.
|
||||
// Currently, attempting `generate_fix` would result in
|
||||
// an invalid code. See: #19757
|
||||
if is_parenthesized(left) || is_parenthesized(right) {
|
||||
return;
|
||||
}
|
||||
|
||||
diagnostic.set_fix(generate_fix(checker, bin_op));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,3 +357,33 @@ help: Remove redundant '+' operator to implicitly concatenate
|
||||
203 | )
|
||||
204 |
|
||||
205 | # nested examples with both t and f-strings
|
||||
|
||||
ISC003 Explicitly concatenated string should be implicitly concatenated
|
||||
--> ISC.py:216:5
|
||||
|
|
||||
214 | # reports diagnostic - no autofix.
|
||||
215 | # See https://github.com/astral-sh/ruff/issues/19757
|
||||
216 | _ = "abc" + (
|
||||
| _____^
|
||||
217 | | "def"
|
||||
218 | | "ghi"
|
||||
219 | | )
|
||||
| |_^
|
||||
220 |
|
||||
221 | _ = (
|
||||
|
|
||||
help: Remove redundant '+' operator to implicitly concatenate
|
||||
|
||||
ISC003 Explicitly concatenated string should be implicitly concatenated
|
||||
--> ISC.py:221:5
|
||||
|
|
||||
219 | )
|
||||
220 |
|
||||
221 | _ = (
|
||||
| _____^
|
||||
222 | | "abc"
|
||||
223 | | "def"
|
||||
224 | | ) + "ghi"
|
||||
| |_________^
|
||||
|
|
||||
help: Remove redundant '+' operator to implicitly concatenate
|
||||
|
||||
@@ -89,3 +89,24 @@ ISC002 Implicitly concatenated string literals over multiple lines
|
||||
209 | | t"def"} g"
|
||||
| |__________^
|
||||
|
|
||||
|
||||
ISC002 Implicitly concatenated string literals over multiple lines
|
||||
--> ISC.py:217:5
|
||||
|
|
||||
215 | # See https://github.com/astral-sh/ruff/issues/19757
|
||||
216 | _ = "abc" + (
|
||||
217 | / "def"
|
||||
218 | | "ghi"
|
||||
| |_________^
|
||||
219 | )
|
||||
|
|
||||
|
||||
ISC002 Implicitly concatenated string literals over multiple lines
|
||||
--> ISC.py:222:5
|
||||
|
|
||||
221 | _ = (
|
||||
222 | / "abc"
|
||||
223 | | "def"
|
||||
| |_________^
|
||||
224 | ) + "ghi"
|
||||
|
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{Expr, Stmt};
|
||||
use ruff_python_ast::{self as ast, Expr, Stmt};
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_semantic::analyze::typing::is_dict;
|
||||
@@ -108,15 +108,77 @@ fn is_dict_key_tuple_with_two_elements(binding: &Binding, semantic: &SemanticMod
|
||||
return false;
|
||||
};
|
||||
|
||||
let Stmt::Assign(assign_stmt) = statement else {
|
||||
let (value, annotation) = match statement {
|
||||
Stmt::Assign(assign_stmt) => (assign_stmt.value.as_ref(), None),
|
||||
Stmt::AnnAssign(ast::StmtAnnAssign {
|
||||
value: Some(value),
|
||||
annotation,
|
||||
..
|
||||
}) => (value.as_ref(), Some(annotation.as_ref())),
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
let Expr::Dict(dict_expr) = value else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Expr::Dict(dict_expr) = &*assign_stmt.value else {
|
||||
return false;
|
||||
};
|
||||
// Check if dict is empty
|
||||
let is_empty = dict_expr.is_empty();
|
||||
|
||||
if is_empty {
|
||||
// For empty dicts, check type annotation
|
||||
return annotation
|
||||
.is_some_and(|annotation| is_annotation_dict_with_tuple_keys(annotation, semantic));
|
||||
}
|
||||
|
||||
// For non-empty dicts, check if all keys are 2-tuples
|
||||
dict_expr
|
||||
.iter_keys()
|
||||
.all(|key| matches!(key, Some(Expr::Tuple(tuple)) if tuple.len() == 2))
|
||||
}
|
||||
|
||||
/// Returns true if the annotation is `dict[tuple[T1, T2], ...]` where tuple has exactly 2 elements.
|
||||
fn is_annotation_dict_with_tuple_keys(annotation: &Expr, semantic: &SemanticModel) -> bool {
|
||||
// Check if it's a subscript: dict[...]
|
||||
let Expr::Subscript(subscript) = annotation else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Check if it's dict or typing.Dict
|
||||
if !semantic.match_builtin_expr(subscript.value.as_ref(), "dict")
|
||||
&& !semantic.match_typing_expr(subscript.value.as_ref(), "Dict")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract the slice (should be a tuple: (key_type, value_type))
|
||||
let Expr::Tuple(tuple) = subscript.slice.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// dict[K, V] format - check if K is tuple with 2 elements
|
||||
if let [key, _value] = tuple.elts.as_slice() {
|
||||
return is_tuple_type_with_two_elements(key, semantic);
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns true if the expression represents a tuple type with exactly 2 elements.
|
||||
fn is_tuple_type_with_two_elements(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
// Handle tuple[...] subscript
|
||||
if let Expr::Subscript(subscript) = expr {
|
||||
// Check if it's tuple or typing.Tuple
|
||||
if semantic.match_builtin_expr(subscript.value.as_ref(), "tuple")
|
||||
|| semantic.match_typing_expr(subscript.value.as_ref(), "Tuple")
|
||||
{
|
||||
// Check the slice - tuple[T1, T2]
|
||||
if let Expr::Tuple(tuple_slice) = subscript.slice.as_ref() {
|
||||
return tuple_slice.elts.len() == 2;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::visitor::{Visitor, walk_expr, walk_stmt};
|
||||
use ruff_python_ast::{
|
||||
self as ast,
|
||||
helpers::map_callable,
|
||||
visitor::{Visitor, walk_expr, walk_stmt},
|
||||
};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::Violation;
|
||||
@@ -50,65 +53,54 @@ impl Violation for StopIterationReturn {
|
||||
}
|
||||
|
||||
/// PLR1708
|
||||
pub(crate) fn stop_iteration_return(checker: &Checker, raise_stmt: &ast::StmtRaise) {
|
||||
// Fast-path: only continue if this is `raise StopIteration` (with or without args)
|
||||
let Some(exc) = &raise_stmt.exc else {
|
||||
return;
|
||||
pub(crate) fn stop_iteration_return(checker: &Checker, function_def: &ast::StmtFunctionDef) {
|
||||
let mut analyzer = GeneratorAnalyzer {
|
||||
checker,
|
||||
has_yield: false,
|
||||
stop_iteration_raises: Vec::new(),
|
||||
};
|
||||
|
||||
let is_stop_iteration = match exc.as_ref() {
|
||||
ast::Expr::Call(ast::ExprCall { func, .. }) => {
|
||||
checker.semantic().match_builtin_expr(func, "StopIteration")
|
||||
analyzer.visit_body(&function_def.body);
|
||||
|
||||
if analyzer.has_yield {
|
||||
for raise_stmt in analyzer.stop_iteration_raises {
|
||||
checker.report_diagnostic(StopIterationReturn, raise_stmt.range());
|
||||
}
|
||||
expr => checker.semantic().match_builtin_expr(expr, "StopIteration"),
|
||||
};
|
||||
|
||||
if !is_stop_iteration {
|
||||
return;
|
||||
}
|
||||
|
||||
// Now check the (more expensive) generator context
|
||||
if !in_generator_context(checker) {
|
||||
return;
|
||||
}
|
||||
|
||||
checker.report_diagnostic(StopIterationReturn, raise_stmt.range());
|
||||
}
|
||||
|
||||
/// Returns true if we're inside a function that contains any `yield`/`yield from`.
|
||||
fn in_generator_context(checker: &Checker) -> bool {
|
||||
for scope in checker.semantic().current_scopes() {
|
||||
if let ruff_python_semantic::ScopeKind::Function(function_def) = scope.kind {
|
||||
if contains_yield_statement(&function_def.body) {
|
||||
return true;
|
||||
struct GeneratorAnalyzer<'a, 'b> {
|
||||
checker: &'a Checker<'b>,
|
||||
has_yield: bool,
|
||||
stop_iteration_raises: Vec<&'a ast::StmtRaise>,
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'a> for GeneratorAnalyzer<'a, '_> {
|
||||
fn visit_stmt(&mut self, stmt: &'a ast::Stmt) {
|
||||
match stmt {
|
||||
ast::Stmt::FunctionDef(_) => {}
|
||||
ast::Stmt::Raise(raise @ ast::StmtRaise { exc: Some(exc), .. }) => {
|
||||
if self
|
||||
.checker
|
||||
.semantic()
|
||||
.match_builtin_expr(map_callable(exc), "StopIteration")
|
||||
{
|
||||
self.stop_iteration_raises.push(raise);
|
||||
}
|
||||
walk_stmt(self, stmt);
|
||||
}
|
||||
_ => walk_stmt(self, stmt),
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Check if a statement list contains any yield statements
|
||||
fn contains_yield_statement(body: &[ast::Stmt]) -> bool {
|
||||
struct YieldFinder {
|
||||
found: bool,
|
||||
}
|
||||
|
||||
impl Visitor<'_> for YieldFinder {
|
||||
fn visit_expr(&mut self, expr: &ast::Expr) {
|
||||
if matches!(expr, ast::Expr::Yield(_) | ast::Expr::YieldFrom(_)) {
|
||||
self.found = true;
|
||||
} else {
|
||||
fn visit_expr(&mut self, expr: &'a ast::Expr) {
|
||||
match expr {
|
||||
ast::Expr::Lambda(_) => {}
|
||||
ast::Expr::Yield(_) | ast::Expr::YieldFrom(_) => {
|
||||
self.has_yield = true;
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
_ => walk_expr(self, expr),
|
||||
}
|
||||
}
|
||||
|
||||
let mut finder = YieldFinder { found: false };
|
||||
for stmt in body {
|
||||
walk_stmt(&mut finder, stmt);
|
||||
if finder.found {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
@@ -39,3 +39,61 @@ help: Add a call to `.items()`
|
||||
18 |
|
||||
19 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLE1141 [*] Unpacking a dictionary in iteration without calling `.items()`
|
||||
--> dict_iter_missing_items.py:37:13
|
||||
|
|
||||
35 | empty_dict = {}
|
||||
36 | empty_dict["x"] = 1
|
||||
37 | for k, v in empty_dict:
|
||||
| ^^^^^^^^^^
|
||||
38 | pass
|
||||
|
|
||||
help: Add a call to `.items()`
|
||||
34 | # Empty dict cases
|
||||
35 | empty_dict = {}
|
||||
36 | empty_dict["x"] = 1
|
||||
- for k, v in empty_dict:
|
||||
37 + for k, v in empty_dict.items():
|
||||
38 | pass
|
||||
39 |
|
||||
40 | empty_dict_annotated_tuple_keys: dict[tuple[int, str], bool] = {}
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLE1141 [*] Unpacking a dictionary in iteration without calling `.items()`
|
||||
--> dict_iter_missing_items.py:46:13
|
||||
|
|
||||
44 | empty_dict_unannotated = {}
|
||||
45 | empty_dict_unannotated[("x", "y")] = True
|
||||
46 | for k, v in empty_dict_unannotated:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
47 | pass
|
||||
|
|
||||
help: Add a call to `.items()`
|
||||
43 |
|
||||
44 | empty_dict_unannotated = {}
|
||||
45 | empty_dict_unannotated[("x", "y")] = True
|
||||
- for k, v in empty_dict_unannotated:
|
||||
46 + for k, v in empty_dict_unannotated.items():
|
||||
47 | pass
|
||||
48 |
|
||||
49 | empty_dict_annotated_str_keys: dict[str, int] = {}
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLE1141 [*] Unpacking a dictionary in iteration without calling `.items()`
|
||||
--> dict_iter_missing_items.py:51:13
|
||||
|
|
||||
49 | empty_dict_annotated_str_keys: dict[str, int] = {}
|
||||
50 | empty_dict_annotated_str_keys["x"] = 1
|
||||
51 | for k, v in empty_dict_annotated_str_keys:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
52 | pass
|
||||
|
|
||||
help: Add a call to `.items()`
|
||||
48 |
|
||||
49 | empty_dict_annotated_str_keys: dict[str, int] = {}
|
||||
50 | empty_dict_annotated_str_keys["x"] = 1
|
||||
- for k, v in empty_dict_annotated_str_keys:
|
||||
51 + for k, v in empty_dict_annotated_str_keys.items():
|
||||
52 | pass
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
@@ -107,3 +107,24 @@ PLR1708 Explicit `raise StopIteration` in generator
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Use `return` instead
|
||||
|
||||
PLR1708 Explicit `raise StopIteration` in generator
|
||||
--> stop_iteration_return.py:149:9
|
||||
|
|
||||
147 | yield 1
|
||||
148 | class C:
|
||||
149 | raise StopIteration # Should trigger
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
150 | yield C
|
||||
|
|
||||
help: Use `return` instead
|
||||
|
||||
PLR1708 Explicit `raise StopIteration` in generator
|
||||
--> stop_iteration_return.py:154:5
|
||||
|
|
||||
152 | # https://github.com/astral-sh/ruff/pull/21177#discussion_r2539702728
|
||||
153 | def foo():
|
||||
154 | raise StopIteration((yield 1)) # Should trigger
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Use `return` instead
|
||||
|
||||
@@ -97,7 +97,8 @@ mod tests {
|
||||
#[test_case(Rule::MapIntVersionParsing, Path::new("RUF048_1.py"))]
|
||||
#[test_case(Rule::DataclassEnum, Path::new("RUF049.py"))]
|
||||
#[test_case(Rule::IfKeyInDictDel, Path::new("RUF051.py"))]
|
||||
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"))]
|
||||
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052_0.py"))]
|
||||
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052_1.py"))]
|
||||
#[test_case(Rule::ClassWithMixedTypeVars, Path::new("RUF053.py"))]
|
||||
#[test_case(Rule::FalsyDictGetFallback, Path::new("RUF056.py"))]
|
||||
#[test_case(Rule::UnnecessaryRound, Path::new("RUF057.py"))]
|
||||
@@ -621,8 +622,8 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"), r"^_+", 1)]
|
||||
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"), r"", 2)]
|
||||
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052_0.py"), r"^_+", 1)]
|
||||
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052_0.py"), r"", 2)]
|
||||
fn custom_regexp_preset(
|
||||
rule_code: Rule,
|
||||
path: &Path,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::helpers::is_dunder;
|
||||
use ruff_python_semantic::{Binding, BindingId};
|
||||
use ruff_python_semantic::{Binding, BindingId, BindingKind, ScopeKind};
|
||||
use ruff_python_stdlib::identifiers::is_identifier;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -111,7 +111,7 @@ pub(crate) fn used_dummy_variable(checker: &Checker, binding: &Binding, binding_
|
||||
return;
|
||||
}
|
||||
|
||||
// We only emit the lint on variables defined via assignments.
|
||||
// We only emit the lint on local variables.
|
||||
//
|
||||
// ## Why not also emit the lint on function parameters?
|
||||
//
|
||||
@@ -127,8 +127,30 @@ pub(crate) fn used_dummy_variable(checker: &Checker, binding: &Binding, binding_
|
||||
// autofixing the diagnostic for assignments. See:
|
||||
// - <https://github.com/astral-sh/ruff/issues/14790>
|
||||
// - <https://github.com/astral-sh/ruff/issues/14799>
|
||||
if !binding.kind.is_assignment() {
|
||||
return;
|
||||
match binding.kind {
|
||||
BindingKind::Annotation
|
||||
| BindingKind::Argument
|
||||
| BindingKind::NamedExprAssignment
|
||||
| BindingKind::Assignment
|
||||
| BindingKind::LoopVar
|
||||
| BindingKind::WithItemVar
|
||||
| BindingKind::BoundException
|
||||
| BindingKind::UnboundException(_) => {}
|
||||
|
||||
BindingKind::TypeParam
|
||||
| BindingKind::Global(_)
|
||||
| BindingKind::Nonlocal(_, _)
|
||||
| BindingKind::Builtin
|
||||
| BindingKind::ClassDefinition(_)
|
||||
| BindingKind::FunctionDefinition(_)
|
||||
| BindingKind::Export(_)
|
||||
| BindingKind::FutureImport
|
||||
| BindingKind::Import(_)
|
||||
| BindingKind::FromImport(_)
|
||||
| BindingKind::SubmoduleImport(_)
|
||||
| BindingKind::Deletion
|
||||
| BindingKind::ConditionalDeletion(_)
|
||||
| BindingKind::DunderClassCell => return,
|
||||
}
|
||||
|
||||
// This excludes `global` and `nonlocal` variables.
|
||||
@@ -138,9 +160,12 @@ pub(crate) fn used_dummy_variable(checker: &Checker, binding: &Binding, binding_
|
||||
|
||||
let semantic = checker.semantic();
|
||||
|
||||
// Only variables defined in function scopes
|
||||
// Only variables defined in function and generator scopes
|
||||
let scope = &semantic.scopes[binding.scope];
|
||||
if !scope.kind.is_function() {
|
||||
if !matches!(
|
||||
scope.kind,
|
||||
ScopeKind::Function(_) | ScopeKind::Generator { .. }
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
RUF052 [*] Local dummy variable `_var` is accessed
|
||||
--> RUF052.py:92:9
|
||||
--> RUF052_0.py:92:9
|
||||
|
|
||||
90 | class Class_:
|
||||
91 | def fun(self):
|
||||
@@ -24,7 +24,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_list` is accessed
|
||||
--> RUF052.py:99:5
|
||||
--> RUF052_0.py:99:5
|
||||
|
|
||||
98 | def fun():
|
||||
99 | _list = "built-in" # [RUF052]
|
||||
@@ -45,7 +45,7 @@ help: Prefer using trailing underscores to avoid shadowing a built-in
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_x` is accessed
|
||||
--> RUF052.py:106:5
|
||||
--> RUF052_0.py:106:5
|
||||
|
|
||||
104 | def fun():
|
||||
105 | global x
|
||||
@@ -67,7 +67,7 @@ help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_x` is accessed
|
||||
--> RUF052.py:113:5
|
||||
--> RUF052_0.py:113:5
|
||||
|
|
||||
111 | def bar():
|
||||
112 | nonlocal x
|
||||
@@ -90,7 +90,7 @@ help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_x` is accessed
|
||||
--> RUF052.py:120:5
|
||||
--> RUF052_0.py:120:5
|
||||
|
|
||||
118 | def fun():
|
||||
119 | x = "local"
|
||||
@@ -112,7 +112,7 @@ help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 Local dummy variable `_GLOBAL_1` is accessed
|
||||
--> RUF052.py:128:5
|
||||
--> RUF052_0.py:128:5
|
||||
|
|
||||
127 | def unfixables():
|
||||
128 | _GLOBAL_1 = "foo"
|
||||
@@ -123,7 +123,7 @@ RUF052 Local dummy variable `_GLOBAL_1` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_local` is accessed
|
||||
--> RUF052.py:136:5
|
||||
--> RUF052_0.py:136:5
|
||||
|
|
||||
135 | # unfixable because the rename would shadow a local variable
|
||||
136 | _local = "local3" # [RUF052]
|
||||
@@ -133,7 +133,7 @@ RUF052 Local dummy variable `_local` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_GLOBAL_1` is accessed
|
||||
--> RUF052.py:140:9
|
||||
--> RUF052_0.py:140:9
|
||||
|
|
||||
139 | def nested():
|
||||
140 | _GLOBAL_1 = "foo"
|
||||
@@ -144,7 +144,7 @@ RUF052 Local dummy variable `_GLOBAL_1` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_local` is accessed
|
||||
--> RUF052.py:145:9
|
||||
--> RUF052_0.py:145:9
|
||||
|
|
||||
144 | # unfixable because the rename would shadow a variable from the outer function
|
||||
145 | _local = "local4"
|
||||
@@ -154,7 +154,7 @@ RUF052 Local dummy variable `_local` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 [*] Local dummy variable `_P` is accessed
|
||||
--> RUF052.py:153:5
|
||||
--> RUF052_0.py:153:5
|
||||
|
|
||||
151 | from collections import namedtuple
|
||||
152 |
|
||||
@@ -184,7 +184,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_T` is accessed
|
||||
--> RUF052.py:154:5
|
||||
--> RUF052_0.py:154:5
|
||||
|
|
||||
153 | _P = ParamSpec("_P")
|
||||
154 | _T = TypeVar(name="_T", covariant=True, bound=int|str)
|
||||
@@ -213,7 +213,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_NT` is accessed
|
||||
--> RUF052.py:155:5
|
||||
--> RUF052_0.py:155:5
|
||||
|
|
||||
153 | _P = ParamSpec("_P")
|
||||
154 | _T = TypeVar(name="_T", covariant=True, bound=int|str)
|
||||
@@ -242,7 +242,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_E` is accessed
|
||||
--> RUF052.py:156:5
|
||||
--> RUF052_0.py:156:5
|
||||
|
|
||||
154 | _T = TypeVar(name="_T", covariant=True, bound=int|str)
|
||||
155 | _NT = NamedTuple("_NT", [("foo", int)])
|
||||
@@ -270,7 +270,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_NT2` is accessed
|
||||
--> RUF052.py:157:5
|
||||
--> RUF052_0.py:157:5
|
||||
|
|
||||
155 | _NT = NamedTuple("_NT", [("foo", int)])
|
||||
156 | _E = Enum("_E", ["a", "b", "c"])
|
||||
@@ -297,7 +297,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_NT3` is accessed
|
||||
--> RUF052.py:158:5
|
||||
--> RUF052_0.py:158:5
|
||||
|
|
||||
156 | _E = Enum("_E", ["a", "b", "c"])
|
||||
157 | _NT2 = namedtuple("_NT2", ['x', 'y', 'z'])
|
||||
@@ -323,7 +323,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_DynamicClass` is accessed
|
||||
--> RUF052.py:159:5
|
||||
--> RUF052_0.py:159:5
|
||||
|
|
||||
157 | _NT2 = namedtuple("_NT2", ['x', 'y', 'z'])
|
||||
158 | _NT3 = namedtuple(typename="_NT3", field_names=['x', 'y', 'z'])
|
||||
@@ -347,7 +347,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_NotADynamicClass` is accessed
|
||||
--> RUF052.py:160:5
|
||||
--> RUF052_0.py:160:5
|
||||
|
|
||||
158 | _NT3 = namedtuple(typename="_NT3", field_names=['x', 'y', 'z'])
|
||||
159 | _DynamicClass = type("_DynamicClass", (), {})
|
||||
@@ -371,7 +371,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_dummy_var` is accessed
|
||||
--> RUF052.py:182:5
|
||||
--> RUF052_0.py:182:5
|
||||
|
|
||||
181 | def foo():
|
||||
182 | _dummy_var = 42
|
||||
@@ -396,7 +396,7 @@ help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 Local dummy variable `_dummy_var` is accessed
|
||||
--> RUF052.py:192:5
|
||||
--> RUF052_0.py:192:5
|
||||
|
|
||||
190 | # Unfixable because both possible candidates for the new name are shadowed
|
||||
191 | # in the scope of one of the references to the variable
|
||||
@@ -0,0 +1,494 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
RUF052 [*] Local dummy variable `_item` is accessed
|
||||
--> RUF052_1.py:21:9
|
||||
|
|
||||
20 | # Should detect used dummy variable
|
||||
21 | for _item in my_list:
|
||||
| ^^^^^
|
||||
22 | print(_item["foo"]) # RUF052: Local dummy variable `_item` is accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
18 | my_list = [{"foo": 1}, {"foo": 2}]
|
||||
19 |
|
||||
20 | # Should detect used dummy variable
|
||||
- for _item in my_list:
|
||||
- print(_item["foo"]) # RUF052: Local dummy variable `_item` is accessed
|
||||
21 + for item in my_list:
|
||||
22 + print(item["foo"]) # RUF052: Local dummy variable `_item` is accessed
|
||||
23 |
|
||||
24 | # Should detect used dummy variable
|
||||
25 | for _index, _value in enumerate(my_list):
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_index` is accessed
|
||||
--> RUF052_1.py:25:9
|
||||
|
|
||||
24 | # Should detect used dummy variable
|
||||
25 | for _index, _value in enumerate(my_list):
|
||||
| ^^^^^^
|
||||
26 | result = _index + _value["foo"] # RUF052: Both `_index` and `_value` are accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
22 | print(_item["foo"]) # RUF052: Local dummy variable `_item` is accessed
|
||||
23 |
|
||||
24 | # Should detect used dummy variable
|
||||
- for _index, _value in enumerate(my_list):
|
||||
- result = _index + _value["foo"] # RUF052: Both `_index` and `_value` are accessed
|
||||
25 + for index, _value in enumerate(my_list):
|
||||
26 + result = index + _value["foo"] # RUF052: Both `_index` and `_value` are accessed
|
||||
27 |
|
||||
28 | # List Comprehensions
|
||||
29 | def test_list_comprehensions():
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_value` is accessed
|
||||
--> RUF052_1.py:25:17
|
||||
|
|
||||
24 | # Should detect used dummy variable
|
||||
25 | for _index, _value in enumerate(my_list):
|
||||
| ^^^^^^
|
||||
26 | result = _index + _value["foo"] # RUF052: Both `_index` and `_value` are accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
22 | print(_item["foo"]) # RUF052: Local dummy variable `_item` is accessed
|
||||
23 |
|
||||
24 | # Should detect used dummy variable
|
||||
- for _index, _value in enumerate(my_list):
|
||||
- result = _index + _value["foo"] # RUF052: Both `_index` and `_value` are accessed
|
||||
25 + for _index, value in enumerate(my_list):
|
||||
26 + result = _index + value["foo"] # RUF052: Both `_index` and `_value` are accessed
|
||||
27 |
|
||||
28 | # List Comprehensions
|
||||
29 | def test_list_comprehensions():
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_item` is accessed
|
||||
--> RUF052_1.py:33:32
|
||||
|
|
||||
32 | # Should detect used dummy variable
|
||||
33 | result = [_item["foo"] for _item in my_list] # RUF052: Local dummy variable `_item` is accessed
|
||||
| ^^^^^
|
||||
34 |
|
||||
35 | # Should detect used dummy variable in nested comprehension
|
||||
|
|
||||
help: Remove leading underscores
|
||||
30 | my_list = [{"foo": 1}, {"foo": 2}]
|
||||
31 |
|
||||
32 | # Should detect used dummy variable
|
||||
- result = [_item["foo"] for _item in my_list] # RUF052: Local dummy variable `_item` is accessed
|
||||
33 + result = [item["foo"] for item in my_list] # RUF052: Local dummy variable `_item` is accessed
|
||||
34 |
|
||||
35 | # Should detect used dummy variable in nested comprehension
|
||||
36 | nested = [[_item["foo"] for _item in _sublist] for _sublist in [my_list, my_list]]
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_item` is accessed
|
||||
--> RUF052_1.py:36:33
|
||||
|
|
||||
35 | # Should detect used dummy variable in nested comprehension
|
||||
36 | nested = [[_item["foo"] for _item in _sublist] for _sublist in [my_list, my_list]]
|
||||
| ^^^^^
|
||||
37 | # RUF052: Both `_item` and `_sublist` are accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
33 | result = [_item["foo"] for _item in my_list] # RUF052: Local dummy variable `_item` is accessed
|
||||
34 |
|
||||
35 | # Should detect used dummy variable in nested comprehension
|
||||
- nested = [[_item["foo"] for _item in _sublist] for _sublist in [my_list, my_list]]
|
||||
36 + nested = [[item["foo"] for item in _sublist] for _sublist in [my_list, my_list]]
|
||||
37 | # RUF052: Both `_item` and `_sublist` are accessed
|
||||
38 |
|
||||
39 | # Should detect with conditions
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_sublist` is accessed
|
||||
--> RUF052_1.py:36:56
|
||||
|
|
||||
35 | # Should detect used dummy variable in nested comprehension
|
||||
36 | nested = [[_item["foo"] for _item in _sublist] for _sublist in [my_list, my_list]]
|
||||
| ^^^^^^^^
|
||||
37 | # RUF052: Both `_item` and `_sublist` are accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
33 | result = [_item["foo"] for _item in my_list] # RUF052: Local dummy variable `_item` is accessed
|
||||
34 |
|
||||
35 | # Should detect used dummy variable in nested comprehension
|
||||
- nested = [[_item["foo"] for _item in _sublist] for _sublist in [my_list, my_list]]
|
||||
36 + nested = [[_item["foo"] for _item in sublist] for sublist in [my_list, my_list]]
|
||||
37 | # RUF052: Both `_item` and `_sublist` are accessed
|
||||
38 |
|
||||
39 | # Should detect with conditions
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_item` is accessed
|
||||
--> RUF052_1.py:40:34
|
||||
|
|
||||
39 | # Should detect with conditions
|
||||
40 | filtered = [_item["foo"] for _item in my_list if _item["foo"] > 0]
|
||||
| ^^^^^
|
||||
41 | # RUF052: Local dummy variable `_item` is accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
37 | # RUF052: Both `_item` and `_sublist` are accessed
|
||||
38 |
|
||||
39 | # Should detect with conditions
|
||||
- filtered = [_item["foo"] for _item in my_list if _item["foo"] > 0]
|
||||
40 + filtered = [item["foo"] for item in my_list if item["foo"] > 0]
|
||||
41 | # RUF052: Local dummy variable `_item` is accessed
|
||||
42 |
|
||||
43 | # Dict Comprehensions
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_item` is accessed
|
||||
--> RUF052_1.py:48:48
|
||||
|
|
||||
47 | # Should detect used dummy variable
|
||||
48 | result = {_item["key"]: _item["value"] for _item in my_list}
|
||||
| ^^^^^
|
||||
49 | # RUF052: Local dummy variable `_item` is accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
45 | my_list = [{"key": "a", "value": 1}, {"key": "b", "value": 2}]
|
||||
46 |
|
||||
47 | # Should detect used dummy variable
|
||||
- result = {_item["key"]: _item["value"] for _item in my_list}
|
||||
48 + result = {item["key"]: item["value"] for item in my_list}
|
||||
49 | # RUF052: Local dummy variable `_item` is accessed
|
||||
50 |
|
||||
51 | # Should detect with enumerate
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_index` is accessed
|
||||
--> RUF052_1.py:52:43
|
||||
|
|
||||
51 | # Should detect with enumerate
|
||||
52 | indexed = {_index: _item["value"] for _index, _item in enumerate(my_list)}
|
||||
| ^^^^^^
|
||||
53 | # RUF052: Both `_index` and `_item` are accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
49 | # RUF052: Local dummy variable `_item` is accessed
|
||||
50 |
|
||||
51 | # Should detect with enumerate
|
||||
- indexed = {_index: _item["value"] for _index, _item in enumerate(my_list)}
|
||||
52 + indexed = {index: _item["value"] for index, _item in enumerate(my_list)}
|
||||
53 | # RUF052: Both `_index` and `_item` are accessed
|
||||
54 |
|
||||
55 | # Should detect in nested dict comprehension
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_item` is accessed
|
||||
--> RUF052_1.py:52:51
|
||||
|
|
||||
51 | # Should detect with enumerate
|
||||
52 | indexed = {_index: _item["value"] for _index, _item in enumerate(my_list)}
|
||||
| ^^^^^
|
||||
53 | # RUF052: Both `_index` and `_item` are accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
49 | # RUF052: Local dummy variable `_item` is accessed
|
||||
50 |
|
||||
51 | # Should detect with enumerate
|
||||
- indexed = {_index: _item["value"] for _index, _item in enumerate(my_list)}
|
||||
52 + indexed = {_index: item["value"] for _index, item in enumerate(my_list)}
|
||||
53 | # RUF052: Both `_index` and `_item` are accessed
|
||||
54 |
|
||||
55 | # Should detect in nested dict comprehension
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_inner` is accessed
|
||||
--> RUF052_1.py:56:59
|
||||
|
|
||||
55 | # Should detect in nested dict comprehension
|
||||
56 | nested = {_outer: {_inner["key"]: _inner["value"] for _inner in sublist}
|
||||
| ^^^^^^
|
||||
57 | for _outer, sublist in enumerate([my_list])}
|
||||
58 | # RUF052: `_outer`, `_inner` are accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
53 | # RUF052: Both `_index` and `_item` are accessed
|
||||
54 |
|
||||
55 | # Should detect in nested dict comprehension
|
||||
- nested = {_outer: {_inner["key"]: _inner["value"] for _inner in sublist}
|
||||
56 + nested = {_outer: {inner["key"]: inner["value"] for inner in sublist}
|
||||
57 | for _outer, sublist in enumerate([my_list])}
|
||||
58 | # RUF052: `_outer`, `_inner` are accessed
|
||||
59 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_outer` is accessed
|
||||
--> RUF052_1.py:57:19
|
||||
|
|
||||
55 | # Should detect in nested dict comprehension
|
||||
56 | nested = {_outer: {_inner["key"]: _inner["value"] for _inner in sublist}
|
||||
57 | for _outer, sublist in enumerate([my_list])}
|
||||
| ^^^^^^
|
||||
58 | # RUF052: `_outer`, `_inner` are accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
53 | # RUF052: Both `_index` and `_item` are accessed
|
||||
54 |
|
||||
55 | # Should detect in nested dict comprehension
|
||||
- nested = {_outer: {_inner["key"]: _inner["value"] for _inner in sublist}
|
||||
- for _outer, sublist in enumerate([my_list])}
|
||||
56 + nested = {outer: {_inner["key"]: _inner["value"] for _inner in sublist}
|
||||
57 + for outer, sublist in enumerate([my_list])}
|
||||
58 | # RUF052: `_outer`, `_inner` are accessed
|
||||
59 |
|
||||
60 | # Set Comprehensions
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_item` is accessed
|
||||
--> RUF052_1.py:65:39
|
||||
|
|
||||
64 | # Should detect used dummy variable
|
||||
65 | unique_values = {_item["foo"] for _item in my_list}
|
||||
| ^^^^^
|
||||
66 | # RUF052: Local dummy variable `_item` is accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
62 | my_list = [{"foo": 1}, {"foo": 2}, {"foo": 1}] # Note: duplicate values
|
||||
63 |
|
||||
64 | # Should detect used dummy variable
|
||||
- unique_values = {_item["foo"] for _item in my_list}
|
||||
65 + unique_values = {item["foo"] for item in my_list}
|
||||
66 | # RUF052: Local dummy variable `_item` is accessed
|
||||
67 |
|
||||
68 | # Should detect with conditions
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_item` is accessed
|
||||
--> RUF052_1.py:69:38
|
||||
|
|
||||
68 | # Should detect with conditions
|
||||
69 | filtered_set = {_item["foo"] for _item in my_list if _item["foo"] > 0}
|
||||
| ^^^^^
|
||||
70 | # RUF052: Local dummy variable `_item` is accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
66 | # RUF052: Local dummy variable `_item` is accessed
|
||||
67 |
|
||||
68 | # Should detect with conditions
|
||||
- filtered_set = {_item["foo"] for _item in my_list if _item["foo"] > 0}
|
||||
69 + filtered_set = {item["foo"] for item in my_list if item["foo"] > 0}
|
||||
70 | # RUF052: Local dummy variable `_item` is accessed
|
||||
71 |
|
||||
72 | # Should detect with complex expression
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_item` is accessed
|
||||
--> RUF052_1.py:73:39
|
||||
|
|
||||
72 | # Should detect with complex expression
|
||||
73 | processed = {_item["foo"] * 2 for _item in my_list}
|
||||
| ^^^^^
|
||||
74 | # RUF052: Local dummy variable `_item` is accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
70 | # RUF052: Local dummy variable `_item` is accessed
|
||||
71 |
|
||||
72 | # Should detect with complex expression
|
||||
- processed = {_item["foo"] * 2 for _item in my_list}
|
||||
73 + processed = {item["foo"] * 2 for item in my_list}
|
||||
74 | # RUF052: Local dummy variable `_item` is accessed
|
||||
75 |
|
||||
76 | # Generator Expressions
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_item` is accessed
|
||||
--> RUF052_1.py:81:29
|
||||
|
|
||||
80 | # Should detect used dummy variable
|
||||
81 | gen = (_item["foo"] for _item in my_list)
|
||||
| ^^^^^
|
||||
82 | # RUF052: Local dummy variable `_item` is accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
78 | my_list = [{"foo": 1}, {"foo": 2}]
|
||||
79 |
|
||||
80 | # Should detect used dummy variable
|
||||
- gen = (_item["foo"] for _item in my_list)
|
||||
81 + gen = (item["foo"] for item in my_list)
|
||||
82 | # RUF052: Local dummy variable `_item` is accessed
|
||||
83 |
|
||||
84 | # Should detect when passed to function
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_item` is accessed
|
||||
--> RUF052_1.py:85:34
|
||||
|
|
||||
84 | # Should detect when passed to function
|
||||
85 | total = sum(_item["foo"] for _item in my_list)
|
||||
| ^^^^^
|
||||
86 | # RUF052: Local dummy variable `_item` is accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
82 | # RUF052: Local dummy variable `_item` is accessed
|
||||
83 |
|
||||
84 | # Should detect when passed to function
|
||||
- total = sum(_item["foo"] for _item in my_list)
|
||||
85 + total = sum(item["foo"] for item in my_list)
|
||||
86 | # RUF052: Local dummy variable `_item` is accessed
|
||||
87 |
|
||||
88 | # Should detect with multiple generators
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_x` is accessed
|
||||
--> RUF052_1.py:89:27
|
||||
|
|
||||
88 | # Should detect with multiple generators
|
||||
89 | pairs = ((_x, _y) for _x in range(3) for _y in range(3) if _x != _y)
|
||||
| ^^
|
||||
90 | # RUF052: Both `_x` and `_y` are accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
86 | # RUF052: Local dummy variable `_item` is accessed
|
||||
87 |
|
||||
88 | # Should detect with multiple generators
|
||||
- pairs = ((_x, _y) for _x in range(3) for _y in range(3) if _x != _y)
|
||||
89 + pairs = ((x, _y) for x in range(3) for _y in range(3) if x != _y)
|
||||
90 | # RUF052: Both `_x` and `_y` are accessed
|
||||
91 |
|
||||
92 | # Should detect in nested generator
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_y` is accessed
|
||||
--> RUF052_1.py:89:46
|
||||
|
|
||||
88 | # Should detect with multiple generators
|
||||
89 | pairs = ((_x, _y) for _x in range(3) for _y in range(3) if _x != _y)
|
||||
| ^^
|
||||
90 | # RUF052: Both `_x` and `_y` are accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
86 | # RUF052: Local dummy variable `_item` is accessed
|
||||
87 |
|
||||
88 | # Should detect with multiple generators
|
||||
- pairs = ((_x, _y) for _x in range(3) for _y in range(3) if _x != _y)
|
||||
89 + pairs = ((_x, y) for _x in range(3) for y in range(3) if _x != y)
|
||||
90 | # RUF052: Both `_x` and `_y` are accessed
|
||||
91 |
|
||||
92 | # Should detect in nested generator
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_inner` is accessed
|
||||
--> RUF052_1.py:93:41
|
||||
|
|
||||
92 | # Should detect in nested generator
|
||||
93 | nested_gen = (sum(_inner["foo"] for _inner in sublist) for _sublist in [my_list] for sublist in _sublist)
|
||||
| ^^^^^^
|
||||
94 | # RUF052: `_inner` and `_sublist` are accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
90 | # RUF052: Both `_x` and `_y` are accessed
|
||||
91 |
|
||||
92 | # Should detect in nested generator
|
||||
- nested_gen = (sum(_inner["foo"] for _inner in sublist) for _sublist in [my_list] for sublist in _sublist)
|
||||
93 + nested_gen = (sum(inner["foo"] for inner in sublist) for _sublist in [my_list] for sublist in _sublist)
|
||||
94 | # RUF052: `_inner` and `_sublist` are accessed
|
||||
95 |
|
||||
96 | # Complex Examples with Multiple Comprehension Types
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_sublist` is accessed
|
||||
--> RUF052_1.py:93:64
|
||||
|
|
||||
92 | # Should detect in nested generator
|
||||
93 | nested_gen = (sum(_inner["foo"] for _inner in sublist) for _sublist in [my_list] for sublist in _sublist)
|
||||
| ^^^^^^^^
|
||||
94 | # RUF052: `_inner` and `_sublist` are accessed
|
||||
|
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
90 | # RUF052: Both `_x` and `_y` are accessed
|
||||
91 |
|
||||
92 | # Should detect in nested generator
|
||||
- nested_gen = (sum(_inner["foo"] for _inner in sublist) for _sublist in [my_list] for sublist in _sublist)
|
||||
93 + nested_gen = (sum(_inner["foo"] for _inner in sublist) for sublist_ in [my_list] for sublist in sublist_)
|
||||
94 | # RUF052: `_inner` and `_sublist` are accessed
|
||||
95 |
|
||||
96 | # Complex Examples with Multiple Comprehension Types
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_val` is accessed
|
||||
--> RUF052_1.py:102:30
|
||||
|
|
||||
100 | # Should detect in mixed comprehensions
|
||||
101 | result = [
|
||||
102 | {_key: [_val * 2 for _val in _record["items"]] for _key in ["doubled"]}
|
||||
| ^^^^
|
||||
103 | for _record in data
|
||||
104 | ]
|
||||
|
|
||||
help: Remove leading underscores
|
||||
99 |
|
||||
100 | # Should detect in mixed comprehensions
|
||||
101 | result = [
|
||||
- {_key: [_val * 2 for _val in _record["items"]] for _key in ["doubled"]}
|
||||
102 + {_key: [val * 2 for val in _record["items"]] for _key in ["doubled"]}
|
||||
103 | for _record in data
|
||||
104 | ]
|
||||
105 | # RUF052: `_key`, `_val`, and `_record` are all accessed
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_key` is accessed
|
||||
--> RUF052_1.py:102:60
|
||||
|
|
||||
100 | # Should detect in mixed comprehensions
|
||||
101 | result = [
|
||||
102 | {_key: [_val * 2 for _val in _record["items"]] for _key in ["doubled"]}
|
||||
| ^^^^
|
||||
103 | for _record in data
|
||||
104 | ]
|
||||
|
|
||||
help: Remove leading underscores
|
||||
99 |
|
||||
100 | # Should detect in mixed comprehensions
|
||||
101 | result = [
|
||||
- {_key: [_val * 2 for _val in _record["items"]] for _key in ["doubled"]}
|
||||
102 + {key: [_val * 2 for _val in _record["items"]] for key in ["doubled"]}
|
||||
103 | for _record in data
|
||||
104 | ]
|
||||
105 | # RUF052: `_key`, `_val`, and `_record` are all accessed
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_record` is accessed
|
||||
--> RUF052_1.py:103:13
|
||||
|
|
||||
101 | result = [
|
||||
102 | {_key: [_val * 2 for _val in _record["items"]] for _key in ["doubled"]}
|
||||
103 | for _record in data
|
||||
| ^^^^^^^
|
||||
104 | ]
|
||||
105 | # RUF052: `_key`, `_val`, and `_record` are all accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
99 |
|
||||
100 | # Should detect in mixed comprehensions
|
||||
101 | result = [
|
||||
- {_key: [_val * 2 for _val in _record["items"]] for _key in ["doubled"]}
|
||||
- for _record in data
|
||||
102 + {_key: [_val * 2 for _val in record["items"]] for _key in ["doubled"]}
|
||||
103 + for record in data
|
||||
104 | ]
|
||||
105 | # RUF052: `_key`, `_val`, and `_record` are all accessed
|
||||
106 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_item` is accessed
|
||||
--> RUF052_1.py:108:43
|
||||
|
|
||||
107 | # Should detect in generator passed to list constructor
|
||||
108 | gen_list = list(_item["items"][0] for _item in data)
|
||||
| ^^^^^
|
||||
109 | # RUF052: Local dummy variable `_item` is accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
105 | # RUF052: `_key`, `_val`, and `_record` are all accessed
|
||||
106 |
|
||||
107 | # Should detect in generator passed to list constructor
|
||||
- gen_list = list(_item["items"][0] for _item in data)
|
||||
108 + gen_list = list(item["items"][0] for item in data)
|
||||
109 | # RUF052: Local dummy variable `_item` is accessed
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
@@ -2,7 +2,7 @@
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
RUF052 [*] Local dummy variable `_var` is accessed
|
||||
--> RUF052.py:92:9
|
||||
--> RUF052_0.py:92:9
|
||||
|
|
||||
90 | class Class_:
|
||||
91 | def fun(self):
|
||||
@@ -24,7 +24,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_list` is accessed
|
||||
--> RUF052.py:99:5
|
||||
--> RUF052_0.py:99:5
|
||||
|
|
||||
98 | def fun():
|
||||
99 | _list = "built-in" # [RUF052]
|
||||
@@ -45,7 +45,7 @@ help: Prefer using trailing underscores to avoid shadowing a built-in
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_x` is accessed
|
||||
--> RUF052.py:106:5
|
||||
--> RUF052_0.py:106:5
|
||||
|
|
||||
104 | def fun():
|
||||
105 | global x
|
||||
@@ -67,7 +67,7 @@ help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_x` is accessed
|
||||
--> RUF052.py:113:5
|
||||
--> RUF052_0.py:113:5
|
||||
|
|
||||
111 | def bar():
|
||||
112 | nonlocal x
|
||||
@@ -90,7 +90,7 @@ help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_x` is accessed
|
||||
--> RUF052.py:120:5
|
||||
--> RUF052_0.py:120:5
|
||||
|
|
||||
118 | def fun():
|
||||
119 | x = "local"
|
||||
@@ -112,7 +112,7 @@ help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 Local dummy variable `_GLOBAL_1` is accessed
|
||||
--> RUF052.py:128:5
|
||||
--> RUF052_0.py:128:5
|
||||
|
|
||||
127 | def unfixables():
|
||||
128 | _GLOBAL_1 = "foo"
|
||||
@@ -123,7 +123,7 @@ RUF052 Local dummy variable `_GLOBAL_1` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_local` is accessed
|
||||
--> RUF052.py:136:5
|
||||
--> RUF052_0.py:136:5
|
||||
|
|
||||
135 | # unfixable because the rename would shadow a local variable
|
||||
136 | _local = "local3" # [RUF052]
|
||||
@@ -133,7 +133,7 @@ RUF052 Local dummy variable `_local` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_GLOBAL_1` is accessed
|
||||
--> RUF052.py:140:9
|
||||
--> RUF052_0.py:140:9
|
||||
|
|
||||
139 | def nested():
|
||||
140 | _GLOBAL_1 = "foo"
|
||||
@@ -144,7 +144,7 @@ RUF052 Local dummy variable `_GLOBAL_1` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_local` is accessed
|
||||
--> RUF052.py:145:9
|
||||
--> RUF052_0.py:145:9
|
||||
|
|
||||
144 | # unfixable because the rename would shadow a variable from the outer function
|
||||
145 | _local = "local4"
|
||||
@@ -154,7 +154,7 @@ RUF052 Local dummy variable `_local` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 [*] Local dummy variable `_P` is accessed
|
||||
--> RUF052.py:153:5
|
||||
--> RUF052_0.py:153:5
|
||||
|
|
||||
151 | from collections import namedtuple
|
||||
152 |
|
||||
@@ -184,7 +184,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_T` is accessed
|
||||
--> RUF052.py:154:5
|
||||
--> RUF052_0.py:154:5
|
||||
|
|
||||
153 | _P = ParamSpec("_P")
|
||||
154 | _T = TypeVar(name="_T", covariant=True, bound=int|str)
|
||||
@@ -213,7 +213,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_NT` is accessed
|
||||
--> RUF052.py:155:5
|
||||
--> RUF052_0.py:155:5
|
||||
|
|
||||
153 | _P = ParamSpec("_P")
|
||||
154 | _T = TypeVar(name="_T", covariant=True, bound=int|str)
|
||||
@@ -242,7 +242,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_E` is accessed
|
||||
--> RUF052.py:156:5
|
||||
--> RUF052_0.py:156:5
|
||||
|
|
||||
154 | _T = TypeVar(name="_T", covariant=True, bound=int|str)
|
||||
155 | _NT = NamedTuple("_NT", [("foo", int)])
|
||||
@@ -270,7 +270,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_NT2` is accessed
|
||||
--> RUF052.py:157:5
|
||||
--> RUF052_0.py:157:5
|
||||
|
|
||||
155 | _NT = NamedTuple("_NT", [("foo", int)])
|
||||
156 | _E = Enum("_E", ["a", "b", "c"])
|
||||
@@ -297,7 +297,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_NT3` is accessed
|
||||
--> RUF052.py:158:5
|
||||
--> RUF052_0.py:158:5
|
||||
|
|
||||
156 | _E = Enum("_E", ["a", "b", "c"])
|
||||
157 | _NT2 = namedtuple("_NT2", ['x', 'y', 'z'])
|
||||
@@ -323,7 +323,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_DynamicClass` is accessed
|
||||
--> RUF052.py:159:5
|
||||
--> RUF052_0.py:159:5
|
||||
|
|
||||
157 | _NT2 = namedtuple("_NT2", ['x', 'y', 'z'])
|
||||
158 | _NT3 = namedtuple(typename="_NT3", field_names=['x', 'y', 'z'])
|
||||
@@ -347,7 +347,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_NotADynamicClass` is accessed
|
||||
--> RUF052.py:160:5
|
||||
--> RUF052_0.py:160:5
|
||||
|
|
||||
158 | _NT3 = namedtuple(typename="_NT3", field_names=['x', 'y', 'z'])
|
||||
159 | _DynamicClass = type("_DynamicClass", (), {})
|
||||
@@ -371,7 +371,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_dummy_var` is accessed
|
||||
--> RUF052.py:182:5
|
||||
--> RUF052_0.py:182:5
|
||||
|
|
||||
181 | def foo():
|
||||
182 | _dummy_var = 42
|
||||
@@ -396,7 +396,7 @@ help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 Local dummy variable `_dummy_var` is accessed
|
||||
--> RUF052.py:192:5
|
||||
--> RUF052_0.py:192:5
|
||||
|
|
||||
190 | # Unfixable because both possible candidates for the new name are shadowed
|
||||
191 | # in the scope of one of the references to the variable
|
||||
@@ -2,7 +2,7 @@
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
RUF052 Local dummy variable `_var` is accessed
|
||||
--> RUF052.py:92:9
|
||||
--> RUF052_0.py:92:9
|
||||
|
|
||||
90 | class Class_:
|
||||
91 | def fun(self):
|
||||
@@ -13,7 +13,7 @@ RUF052 Local dummy variable `_var` is accessed
|
||||
help: Remove leading underscores
|
||||
|
||||
RUF052 Local dummy variable `_list` is accessed
|
||||
--> RUF052.py:99:5
|
||||
--> RUF052_0.py:99:5
|
||||
|
|
||||
98 | def fun():
|
||||
99 | _list = "built-in" # [RUF052]
|
||||
@@ -23,7 +23,7 @@ RUF052 Local dummy variable `_list` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a built-in
|
||||
|
||||
RUF052 Local dummy variable `_x` is accessed
|
||||
--> RUF052.py:106:5
|
||||
--> RUF052_0.py:106:5
|
||||
|
|
||||
104 | def fun():
|
||||
105 | global x
|
||||
@@ -34,7 +34,7 @@ RUF052 Local dummy variable `_x` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `x` is accessed
|
||||
--> RUF052.py:110:3
|
||||
--> RUF052_0.py:110:3
|
||||
|
|
||||
109 | def foo():
|
||||
110 | x = "outer"
|
||||
@@ -44,7 +44,7 @@ RUF052 Local dummy variable `x` is accessed
|
||||
|
|
||||
|
||||
RUF052 Local dummy variable `_x` is accessed
|
||||
--> RUF052.py:113:5
|
||||
--> RUF052_0.py:113:5
|
||||
|
|
||||
111 | def bar():
|
||||
112 | nonlocal x
|
||||
@@ -56,7 +56,7 @@ RUF052 Local dummy variable `_x` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_x` is accessed
|
||||
--> RUF052.py:120:5
|
||||
--> RUF052_0.py:120:5
|
||||
|
|
||||
118 | def fun():
|
||||
119 | x = "local"
|
||||
@@ -67,7 +67,7 @@ RUF052 Local dummy variable `_x` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_GLOBAL_1` is accessed
|
||||
--> RUF052.py:128:5
|
||||
--> RUF052_0.py:128:5
|
||||
|
|
||||
127 | def unfixables():
|
||||
128 | _GLOBAL_1 = "foo"
|
||||
@@ -78,7 +78,7 @@ RUF052 Local dummy variable `_GLOBAL_1` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_local` is accessed
|
||||
--> RUF052.py:136:5
|
||||
--> RUF052_0.py:136:5
|
||||
|
|
||||
135 | # unfixable because the rename would shadow a local variable
|
||||
136 | _local = "local3" # [RUF052]
|
||||
@@ -88,7 +88,7 @@ RUF052 Local dummy variable `_local` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_GLOBAL_1` is accessed
|
||||
--> RUF052.py:140:9
|
||||
--> RUF052_0.py:140:9
|
||||
|
|
||||
139 | def nested():
|
||||
140 | _GLOBAL_1 = "foo"
|
||||
@@ -99,7 +99,7 @@ RUF052 Local dummy variable `_GLOBAL_1` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_local` is accessed
|
||||
--> RUF052.py:145:9
|
||||
--> RUF052_0.py:145:9
|
||||
|
|
||||
144 | # unfixable because the rename would shadow a variable from the outer function
|
||||
145 | _local = "local4"
|
||||
@@ -109,7 +109,7 @@ RUF052 Local dummy variable `_local` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_P` is accessed
|
||||
--> RUF052.py:153:5
|
||||
--> RUF052_0.py:153:5
|
||||
|
|
||||
151 | from collections import namedtuple
|
||||
152 |
|
||||
@@ -121,7 +121,7 @@ RUF052 Local dummy variable `_P` is accessed
|
||||
help: Remove leading underscores
|
||||
|
||||
RUF052 Local dummy variable `_T` is accessed
|
||||
--> RUF052.py:154:5
|
||||
--> RUF052_0.py:154:5
|
||||
|
|
||||
153 | _P = ParamSpec("_P")
|
||||
154 | _T = TypeVar(name="_T", covariant=True, bound=int|str)
|
||||
@@ -132,7 +132,7 @@ RUF052 Local dummy variable `_T` is accessed
|
||||
help: Remove leading underscores
|
||||
|
||||
RUF052 Local dummy variable `_NT` is accessed
|
||||
--> RUF052.py:155:5
|
||||
--> RUF052_0.py:155:5
|
||||
|
|
||||
153 | _P = ParamSpec("_P")
|
||||
154 | _T = TypeVar(name="_T", covariant=True, bound=int|str)
|
||||
@@ -144,7 +144,7 @@ RUF052 Local dummy variable `_NT` is accessed
|
||||
help: Remove leading underscores
|
||||
|
||||
RUF052 Local dummy variable `_E` is accessed
|
||||
--> RUF052.py:156:5
|
||||
--> RUF052_0.py:156:5
|
||||
|
|
||||
154 | _T = TypeVar(name="_T", covariant=True, bound=int|str)
|
||||
155 | _NT = NamedTuple("_NT", [("foo", int)])
|
||||
@@ -156,7 +156,7 @@ RUF052 Local dummy variable `_E` is accessed
|
||||
help: Remove leading underscores
|
||||
|
||||
RUF052 Local dummy variable `_NT2` is accessed
|
||||
--> RUF052.py:157:5
|
||||
--> RUF052_0.py:157:5
|
||||
|
|
||||
155 | _NT = NamedTuple("_NT", [("foo", int)])
|
||||
156 | _E = Enum("_E", ["a", "b", "c"])
|
||||
@@ -168,7 +168,7 @@ RUF052 Local dummy variable `_NT2` is accessed
|
||||
help: Remove leading underscores
|
||||
|
||||
RUF052 Local dummy variable `_NT3` is accessed
|
||||
--> RUF052.py:158:5
|
||||
--> RUF052_0.py:158:5
|
||||
|
|
||||
156 | _E = Enum("_E", ["a", "b", "c"])
|
||||
157 | _NT2 = namedtuple("_NT2", ['x', 'y', 'z'])
|
||||
@@ -180,7 +180,7 @@ RUF052 Local dummy variable `_NT3` is accessed
|
||||
help: Remove leading underscores
|
||||
|
||||
RUF052 Local dummy variable `_DynamicClass` is accessed
|
||||
--> RUF052.py:159:5
|
||||
--> RUF052_0.py:159:5
|
||||
|
|
||||
157 | _NT2 = namedtuple("_NT2", ['x', 'y', 'z'])
|
||||
158 | _NT3 = namedtuple(typename="_NT3", field_names=['x', 'y', 'z'])
|
||||
@@ -191,7 +191,7 @@ RUF052 Local dummy variable `_DynamicClass` is accessed
|
||||
help: Remove leading underscores
|
||||
|
||||
RUF052 Local dummy variable `_NotADynamicClass` is accessed
|
||||
--> RUF052.py:160:5
|
||||
--> RUF052_0.py:160:5
|
||||
|
|
||||
158 | _NT3 = namedtuple(typename="_NT3", field_names=['x', 'y', 'z'])
|
||||
159 | _DynamicClass = type("_DynamicClass", (), {})
|
||||
@@ -202,8 +202,18 @@ RUF052 Local dummy variable `_NotADynamicClass` is accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
|
||||
RUF052 Local dummy variable `other` is accessed
|
||||
--> RUF052_0.py:177:13
|
||||
|
|
||||
175 | return
|
||||
176 | _seen.add(self)
|
||||
177 | for other in self.connected:
|
||||
| ^^^^^
|
||||
178 | other.recurse(_seen=_seen)
|
||||
|
|
||||
|
||||
RUF052 Local dummy variable `_dummy_var` is accessed
|
||||
--> RUF052.py:182:5
|
||||
--> RUF052_0.py:182:5
|
||||
|
|
||||
181 | def foo():
|
||||
182 | _dummy_var = 42
|
||||
@@ -214,7 +224,7 @@ RUF052 Local dummy variable `_dummy_var` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_dummy_var` is accessed
|
||||
--> RUF052.py:192:5
|
||||
--> RUF052_0.py:192:5
|
||||
|
|
||||
190 | # Unfixable because both possible candidates for the new name are shadowed
|
||||
191 | # in the scope of one of the references to the variable
|
||||
@@ -283,24 +283,27 @@ fn to_lsp_diagnostic(
|
||||
range = diagnostic_range.to_range(source_kind.source_code(), index, encoding);
|
||||
}
|
||||
|
||||
let (severity, tags, code) = if let Some(code) = code {
|
||||
let code = code.to_string();
|
||||
(
|
||||
Some(severity(&code)),
|
||||
tags(diagnostic),
|
||||
Some(lsp_types::NumberOrString::String(code)),
|
||||
)
|
||||
let (severity, code) = if let Some(code) = code {
|
||||
(severity(code), code.to_string())
|
||||
} else {
|
||||
(None, None, None)
|
||||
(
|
||||
match diagnostic.severity() {
|
||||
ruff_db::diagnostic::Severity::Info => lsp_types::DiagnosticSeverity::INFORMATION,
|
||||
ruff_db::diagnostic::Severity::Warning => lsp_types::DiagnosticSeverity::WARNING,
|
||||
ruff_db::diagnostic::Severity::Error => lsp_types::DiagnosticSeverity::ERROR,
|
||||
ruff_db::diagnostic::Severity::Fatal => lsp_types::DiagnosticSeverity::ERROR,
|
||||
},
|
||||
diagnostic.id().to_string(),
|
||||
)
|
||||
};
|
||||
|
||||
(
|
||||
cell,
|
||||
lsp_types::Diagnostic {
|
||||
range,
|
||||
severity,
|
||||
tags,
|
||||
code,
|
||||
severity: Some(severity),
|
||||
tags: tags(diagnostic),
|
||||
code: Some(lsp_types::NumberOrString::String(code)),
|
||||
code_description: diagnostic.documentation_url().and_then(|url| {
|
||||
Some(lsp_types::CodeDescription {
|
||||
href: lsp_types::Url::parse(url).ok()?,
|
||||
|
||||
@@ -106,6 +106,25 @@ impl TextSize {
|
||||
pub fn checked_sub(self, rhs: TextSize) -> Option<TextSize> {
|
||||
self.raw.checked_sub(rhs.raw).map(|raw| TextSize { raw })
|
||||
}
|
||||
|
||||
/// Saturating addition. Returns maximum `TextSize` if overflow occurred.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn saturating_add(self, rhs: TextSize) -> TextSize {
|
||||
TextSize {
|
||||
raw: self.raw.saturating_add(rhs.raw),
|
||||
}
|
||||
}
|
||||
|
||||
/// Saturating subtraction. Returns minimum `TextSize` if overflow
|
||||
/// occurred.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn saturating_sub(self, rhs: TextSize) -> TextSize {
|
||||
TextSize {
|
||||
raw: self.raw.saturating_sub(rhs.raw),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for TextSize {
|
||||
|
||||
@@ -8,7 +8,7 @@ use ruff_python_ast as ast;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_parser::{Token, TokenAt, TokenKind, Tokens};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
use ty_python_semantic::{
|
||||
Completion as SemanticCompletion, ModuleName, NameKind, SemanticModel,
|
||||
types::{CycleDetector, Type},
|
||||
@@ -329,7 +329,7 @@ pub fn completion<'db>(
|
||||
let tokens = tokens_start_before(parsed.tokens(), offset);
|
||||
let typed = find_typed_text(db, file, &parsed, offset);
|
||||
|
||||
if is_in_no_completions_place(db, file, tokens, typed.as_deref()) {
|
||||
if is_in_no_completions_place(db, file, &parsed, offset, tokens, typed.as_deref()) {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
@@ -1270,10 +1270,14 @@ fn find_typed_text(
|
||||
fn is_in_no_completions_place(
|
||||
db: &dyn Db,
|
||||
file: File,
|
||||
parsed: &ParsedModuleRef,
|
||||
offset: TextSize,
|
||||
tokens: &[Token],
|
||||
typed: Option<&str>,
|
||||
) -> bool {
|
||||
is_in_comment(tokens) || is_in_string(tokens) || is_in_definition_place(db, file, tokens, typed)
|
||||
is_in_comment(tokens)
|
||||
|| is_in_string(tokens)
|
||||
|| is_in_definition_place(db, file, parsed, offset, tokens, typed)
|
||||
}
|
||||
|
||||
/// Whether the last token is within a comment or not.
|
||||
@@ -1296,11 +1300,18 @@ fn is_in_string(tokens: &[Token]) -> bool {
|
||||
|
||||
/// Returns true when the tokens indicate that the definition of a new
|
||||
/// name is being introduced at the end.
|
||||
fn is_in_definition_place(db: &dyn Db, file: File, tokens: &[Token], typed: Option<&str>) -> bool {
|
||||
fn is_in_definition_place(
|
||||
db: &dyn Db,
|
||||
file: File,
|
||||
parsed: &ParsedModuleRef,
|
||||
offset: TextSize,
|
||||
tokens: &[Token],
|
||||
typed: Option<&str>,
|
||||
) -> bool {
|
||||
fn is_definition_token(token: &Token) -> bool {
|
||||
matches!(
|
||||
token.kind(),
|
||||
TokenKind::Def | TokenKind::Class | TokenKind::Type | TokenKind::As
|
||||
TokenKind::Def | TokenKind::Class | TokenKind::Type | TokenKind::As | TokenKind::For
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1314,11 +1325,37 @@ fn is_in_definition_place(db: &dyn Db, file: File, tokens: &[Token], typed: Opti
|
||||
false
|
||||
}
|
||||
};
|
||||
match tokens {
|
||||
if match tokens {
|
||||
[.., penultimate, _] if typed.is_some() => is_definition_keyword(penultimate),
|
||||
[.., last] if typed.is_none() => is_definition_keyword(last),
|
||||
_ => false,
|
||||
} {
|
||||
return true;
|
||||
}
|
||||
// Analyze the AST if token matching is insufficient
|
||||
// to determine if we're inside a name definition.
|
||||
is_in_variable_binding(parsed, offset, typed)
|
||||
}
|
||||
|
||||
/// Returns true when the cursor sits on a binding statement.
|
||||
/// E.g. naming a parameter, type parameter, or `for` <name>).
|
||||
fn is_in_variable_binding(parsed: &ParsedModuleRef, offset: TextSize, typed: Option<&str>) -> bool {
|
||||
let range = if let Some(typed) = typed {
|
||||
let start = offset.saturating_sub(typed.text_len());
|
||||
TextRange::new(start, offset)
|
||||
} else {
|
||||
TextRange::empty(offset)
|
||||
};
|
||||
|
||||
let covering = covering_node(parsed.syntax().into(), range);
|
||||
covering.ancestors().any(|node| match node {
|
||||
ast::AnyNodeRef::Parameter(param) => param.name.range.contains_range(range),
|
||||
ast::AnyNodeRef::TypeParamTypeVar(type_param) => {
|
||||
type_param.name.range.contains_range(range)
|
||||
}
|
||||
ast::AnyNodeRef::StmtFor(stmt_for) => stmt_for.target.range().contains_range(range),
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Order completions according to the following rules:
|
||||
@@ -5174,6 +5211,96 @@ match status:
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_completions_in_empty_for_variable_binding() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
for <CURSOR>
|
||||
",
|
||||
);
|
||||
assert_snapshot!(
|
||||
builder.build().snapshot(),
|
||||
@"<No completions found>",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_completions_in_for_variable_binding() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
for foo<CURSOR>
|
||||
",
|
||||
);
|
||||
assert_snapshot!(
|
||||
builder.build().snapshot(),
|
||||
@"<No completions found>",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_completions_in_for_tuple_variable_binding() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
for foo, bar<CURSOR>
|
||||
",
|
||||
);
|
||||
assert_snapshot!(
|
||||
builder.build().snapshot(),
|
||||
@"<No completions found>",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_completions_in_function_param() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
def foo(p<CURSOR>
|
||||
",
|
||||
);
|
||||
assert_snapshot!(
|
||||
builder.build().snapshot(),
|
||||
@"<No completions found>",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_completions_in_function_type_param() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
def foo[T<CURSOR>]
|
||||
",
|
||||
);
|
||||
assert_snapshot!(
|
||||
builder.build().snapshot(),
|
||||
@"<No completions found>",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completions_in_function_type_param_bound() {
|
||||
completion_test_builder(
|
||||
"\
|
||||
def foo[T: s<CURSOR>]
|
||||
",
|
||||
)
|
||||
.build()
|
||||
.contains("str");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completions_in_function_param_type_annotation() {
|
||||
// Ensure that completions are no longer
|
||||
// suppressed when have left the name
|
||||
// definition block.
|
||||
completion_test_builder(
|
||||
"\
|
||||
def foo(param: s<CURSOR>)
|
||||
",
|
||||
)
|
||||
.build()
|
||||
.contains("str");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn favour_symbols_currently_imported() {
|
||||
let snapshot = CursorTest::builder()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -245,14 +245,11 @@ mod tests {
|
||||
) -> Unknown
|
||||
```
|
||||
---
|
||||
```text
|
||||
This is such a great func!!
|
||||
|
||||
Args:
|
||||
a: first for a reason
|
||||
b: coming for `a`'s title
|
||||
|
||||
```
|
||||
This is such a great func!!
|
||||
|
||||
Args:
|
||||
a: first for a reason
|
||||
b: coming for `a`'s title
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:11:1
|
||||
@@ -303,14 +300,11 @@ mod tests {
|
||||
) -> Unknown
|
||||
```
|
||||
---
|
||||
```text
|
||||
This is such a great func!!
|
||||
|
||||
Args:
|
||||
a: first for a reason
|
||||
b: coming for `a`'s title
|
||||
|
||||
```
|
||||
This is such a great func!!
|
||||
|
||||
Args:
|
||||
a: first for a reason
|
||||
b: coming for `a`'s title
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:5
|
||||
@@ -369,14 +363,11 @@ mod tests {
|
||||
<class 'MyClass'>
|
||||
```
|
||||
---
|
||||
```text
|
||||
This is such a great class!!
|
||||
|
||||
Don't you know?
|
||||
|
||||
This is such a great class!!
|
||||
|
||||
Don't you know?
|
||||
|
||||
Everyone loves my class!!
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:24:1
|
||||
@@ -434,14 +425,11 @@ mod tests {
|
||||
<class 'MyClass'>
|
||||
```
|
||||
---
|
||||
```text
|
||||
This is such a great class!!
|
||||
|
||||
Don't you know?
|
||||
|
||||
This is such a great class!!
|
||||
|
||||
Don't you know?
|
||||
|
||||
Everyone loves my class!!
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:7
|
||||
@@ -497,10 +485,7 @@ mod tests {
|
||||
<class 'MyClass'>
|
||||
```
|
||||
---
|
||||
```text
|
||||
initializes MyClass (perfectly)
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:24:5
|
||||
@@ -556,10 +541,7 @@ mod tests {
|
||||
<class 'MyClass'>
|
||||
```
|
||||
---
|
||||
```text
|
||||
initializes MyClass (perfectly)
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:4:11
|
||||
@@ -618,14 +600,11 @@ mod tests {
|
||||
<class 'MyClass'>
|
||||
```
|
||||
---
|
||||
```text
|
||||
This is such a great class!!
|
||||
|
||||
Don't you know?
|
||||
|
||||
This is such a great class!!
|
||||
|
||||
Don't you know?
|
||||
|
||||
Everyone loves my class!!
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:23:5
|
||||
@@ -692,14 +671,11 @@ mod tests {
|
||||
) -> Unknown
|
||||
```
|
||||
---
|
||||
```text
|
||||
This is such a great func!!
|
||||
|
||||
Args:
|
||||
a: first for a reason
|
||||
b: coming for `a`'s title
|
||||
|
||||
```
|
||||
This is such a great func!!
|
||||
|
||||
Args:
|
||||
a: first for a reason
|
||||
b: coming for `a`'s title
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:25:3
|
||||
@@ -973,10 +949,7 @@ def ab(a: str): ...
|
||||
(a: int) -> Unknown
|
||||
```
|
||||
---
|
||||
```text
|
||||
the int overload
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:4:1
|
||||
@@ -1036,10 +1009,7 @@ def ab(a: str):
|
||||
(a: str) -> Unknown
|
||||
```
|
||||
---
|
||||
```text
|
||||
the int overload
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:4:1
|
||||
@@ -1105,10 +1075,7 @@ def ab(a: int):
|
||||
) -> Unknown
|
||||
```
|
||||
---
|
||||
```text
|
||||
the two arg overload
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:4:1
|
||||
@@ -1168,10 +1135,7 @@ def ab(a: int):
|
||||
(a: int) -> Unknown
|
||||
```
|
||||
---
|
||||
```text
|
||||
the two arg overload
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:4:1
|
||||
@@ -1243,10 +1207,7 @@ def ab(a: int, *, c: int):
|
||||
) -> Unknown
|
||||
```
|
||||
---
|
||||
```text
|
||||
keywordless overload
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:4:1
|
||||
@@ -1318,10 +1279,7 @@ def ab(a: int, *, c: int):
|
||||
) -> Unknown
|
||||
```
|
||||
---
|
||||
```text
|
||||
keywordless overload
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:4:1
|
||||
@@ -1386,10 +1344,7 @@ def ab(a: int, *, c: int):
|
||||
) -> Unknown
|
||||
```
|
||||
---
|
||||
```text
|
||||
The first overload
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:19:1
|
||||
@@ -1441,10 +1396,7 @@ def ab(a: int, *, c: int):
|
||||
(a: str) -> Unknown
|
||||
```
|
||||
---
|
||||
```text
|
||||
The first overload
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:19:1
|
||||
@@ -1494,12 +1446,9 @@ def ab(a: int, *, c: int):
|
||||
<module 'lib'>
|
||||
```
|
||||
---
|
||||
```text
|
||||
The cool lib_py module!
|
||||
|
||||
The cool lib/_py module!
|
||||
|
||||
Wow this module rocks.
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:4:1
|
||||
@@ -1544,12 +1493,9 @@ def ab(a: int, *, c: int):
|
||||
Wow this module rocks.
|
||||
|
||||
---------------------------------------------
|
||||
```text
|
||||
The cool lib_py module!
|
||||
|
||||
The cool lib/_py module!
|
||||
|
||||
Wow this module rocks.
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:8
|
||||
@@ -2499,10 +2445,7 @@ def ab(a: int, *, c: int):
|
||||
bound method int.__add__(value: int, /) -> int
|
||||
```
|
||||
---
|
||||
```text
|
||||
Return self+value.
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:12
|
||||
@@ -2618,10 +2561,7 @@ def ab(a: int, *, c: int):
|
||||
int | float
|
||||
```
|
||||
---
|
||||
```text
|
||||
Convert a string or number to a floating-point number, if possible.
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:4
|
||||
|
||||
@@ -426,7 +426,7 @@ mod tests {
|
||||
|
||||
use crate::NavigationTarget;
|
||||
use crate::tests::IntoDiagnostic;
|
||||
use insta::assert_snapshot;
|
||||
use insta::{assert_snapshot, internals::SettingsBindDropGuard};
|
||||
use ruff_db::{
|
||||
diagnostic::{
|
||||
Annotation, Diagnostic, DiagnosticFormat, DiagnosticId, DisplayDiagnosticConfig,
|
||||
@@ -473,13 +473,26 @@ mod tests {
|
||||
|
||||
let file = system_path_to_file(&db, "main.py").expect("newly written file to existing");
|
||||
|
||||
InlayHintTest { db, file, range }
|
||||
let mut insta_settings = insta::Settings::clone_current();
|
||||
insta_settings.add_filter(r#"\\(\w\w|\.|")"#, "/$1");
|
||||
// Filter out TODO types because they are different between debug and release builds.
|
||||
insta_settings.add_filter(r"@Todo\(.+\)", "@Todo");
|
||||
|
||||
let insta_settings_guard = insta_settings.bind_to_scope();
|
||||
|
||||
InlayHintTest {
|
||||
db,
|
||||
file,
|
||||
range,
|
||||
_insta_settings_guard: insta_settings_guard,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct InlayHintTest {
|
||||
pub(super) db: ty_project::TestDb,
|
||||
pub(super) file: File,
|
||||
pub(super) range: TextRange,
|
||||
_insta_settings_guard: SettingsBindDropGuard,
|
||||
}
|
||||
|
||||
impl InlayHintTest {
|
||||
@@ -570,10 +583,7 @@ mod tests {
|
||||
write!(buf, "{}", diag.display(&self.db, &config)).unwrap();
|
||||
}
|
||||
|
||||
// Windows path normalization for typeshed references
|
||||
// "hey why is \x08 getting clobbered to /x08?"
|
||||
// no it's not I don't know what you're talking about
|
||||
buf.replace('\\', "/")
|
||||
buf
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1830,7 +1840,7 @@ mod tests {
|
||||
f = 'there'
|
||||
g = f"{e} {f}"
|
||||
h = t"wow %d"
|
||||
i = b'\x00'
|
||||
i = b'/x00'
|
||||
j = +1
|
||||
k = -1.0
|
||||
"#);
|
||||
@@ -1863,7 +1873,7 @@ mod tests {
|
||||
f = ('the', 're')
|
||||
g = (f"{ft}", f"{ft}")
|
||||
h = (t"wow %d", t"wow %d")
|
||||
i = (b'\x01', b'\x02')
|
||||
i = (b'/x01', b'/x02')
|
||||
j = (+1, +2.0)
|
||||
k = (-1, -2.0)
|
||||
"#);
|
||||
@@ -1896,7 +1906,7 @@ mod tests {
|
||||
f1, f2 = ('the', 're')
|
||||
g1, g2 = (f"{ft}", f"{ft}")
|
||||
h1, h2 = (t"wow %d", t"wow %d")
|
||||
i1, i2 = (b'\x01', b'\x02')
|
||||
i1, i2 = (b'/x01', b'/x02')
|
||||
j1, j2 = (+1, +2.0)
|
||||
k1, k2 = (-1, -2.0)
|
||||
"#);
|
||||
@@ -1929,7 +1939,7 @@ mod tests {
|
||||
f1, f2 = 'the', 're'
|
||||
g1, g2 = f"{ft}", f"{ft}"
|
||||
h1, h2 = t"wow %d", t"wow %d"
|
||||
i1, i2 = b'\x01', b'\x02'
|
||||
i1, i2 = b'/x01', b'/x02'
|
||||
j1, j2 = +1, +2.0
|
||||
k1, k2 = -1, -2.0
|
||||
"#);
|
||||
@@ -1962,7 +1972,7 @@ mod tests {
|
||||
f[: list[Unknown | str]] = ['the', 're']
|
||||
g[: list[Unknown | str]] = [f"{ft}", f"{ft}"]
|
||||
h[: list[Unknown | Template]] = [t"wow %d", t"wow %d"]
|
||||
i[: list[Unknown | bytes]] = [b'\x01', b'\x02']
|
||||
i[: list[Unknown | bytes]] = [b'/x01', b'/x02']
|
||||
j[: list[Unknown | int | float]] = [+1, +2.0]
|
||||
k[: list[Unknown | int | float]] = [-1, -2.0]
|
||||
|
||||
|
||||
@@ -2683,6 +2683,39 @@ reveal_type(datetime.UTC) # revealed: Unknown
|
||||
reveal_type(datetime.fakenotreal) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Unimported submodule incorrectly accessed as attribute
|
||||
|
||||
We give special diagnostics for this common case too:
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
`foo/__init__.py`:
|
||||
|
||||
```py
|
||||
```
|
||||
|
||||
`foo/bar.py`:
|
||||
|
||||
```py
|
||||
```
|
||||
|
||||
`baz/bar.py`:
|
||||
|
||||
```py
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import foo
|
||||
import baz
|
||||
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(foo.bar) # revealed: Unknown
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(baz.bar) # revealed: Unknown
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
Some of the tests in the *Class and instance variables* section draw inspiration from
|
||||
|
||||
@@ -137,84 +137,26 @@ class Sub(Base): ...
|
||||
class Unrelated: ...
|
||||
|
||||
def unbounded_unconstrained[T, U](t: T, u: U) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, T))
|
||||
static_assert(is_assignable_to(T, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, object))
|
||||
static_assert(is_assignable_to(T, object))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, Super))
|
||||
static_assert(not is_assignable_to(T, Super))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Any))
|
||||
static_assert(is_assignable_to(T, Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Any, T))
|
||||
static_assert(is_assignable_to(Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(U, U))
|
||||
static_assert(is_assignable_to(U, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(U, object))
|
||||
static_assert(is_assignable_to(U, object))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(U, Super))
|
||||
static_assert(not is_assignable_to(U, Super))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, U))
|
||||
static_assert(not is_assignable_to(T, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(U, T))
|
||||
static_assert(not is_assignable_to(U, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(T, T))
|
||||
static_assert(is_subtype_of(T, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(T, object))
|
||||
static_assert(is_subtype_of(T, object))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Super))
|
||||
static_assert(not is_subtype_of(T, Super))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Any))
|
||||
static_assert(not is_subtype_of(T, Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Any, T))
|
||||
static_assert(not is_subtype_of(Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(U, U))
|
||||
static_assert(is_subtype_of(U, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(U, object))
|
||||
static_assert(is_subtype_of(U, object))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(U, Super))
|
||||
static_assert(not is_subtype_of(U, Super))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, U))
|
||||
static_assert(not is_subtype_of(T, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(U, T))
|
||||
static_assert(not is_subtype_of(U, T))
|
||||
```
|
||||
|
||||
@@ -229,137 +171,47 @@ from typing import Any
|
||||
from typing_extensions import final
|
||||
|
||||
def bounded[T: Super](t: T) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Any))
|
||||
static_assert(is_assignable_to(T, Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Any, T))
|
||||
static_assert(is_assignable_to(Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Super))
|
||||
static_assert(is_assignable_to(T, Super))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, Sub))
|
||||
static_assert(not is_assignable_to(T, Sub))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(Super, T))
|
||||
static_assert(not is_assignable_to(Super, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(Sub, T))
|
||||
static_assert(not is_assignable_to(Sub, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Any))
|
||||
static_assert(not is_subtype_of(T, Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Any, T))
|
||||
static_assert(not is_subtype_of(Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(T, Super))
|
||||
static_assert(is_subtype_of(T, Super))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Sub))
|
||||
static_assert(not is_subtype_of(T, Sub))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Super, T))
|
||||
static_assert(not is_subtype_of(Super, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Sub, T))
|
||||
static_assert(not is_subtype_of(Sub, T))
|
||||
|
||||
def bounded_by_gradual[T: Any](t: T) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Any))
|
||||
static_assert(is_assignable_to(T, Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Any, T))
|
||||
static_assert(is_assignable_to(Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Super))
|
||||
static_assert(is_assignable_to(T, Super))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(Super, T))
|
||||
static_assert(not is_assignable_to(Super, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Sub))
|
||||
static_assert(is_assignable_to(T, Sub))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(Sub, T))
|
||||
static_assert(not is_assignable_to(Sub, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Any))
|
||||
static_assert(not is_subtype_of(T, Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Any, T))
|
||||
static_assert(not is_subtype_of(Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Super))
|
||||
static_assert(not is_subtype_of(T, Super))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Super, T))
|
||||
static_assert(not is_subtype_of(Super, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Sub))
|
||||
static_assert(not is_subtype_of(T, Sub))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Sub, T))
|
||||
static_assert(not is_subtype_of(Sub, T))
|
||||
|
||||
@final
|
||||
class FinalClass: ...
|
||||
|
||||
def bounded_final[T: FinalClass](t: T) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Any))
|
||||
static_assert(is_assignable_to(T, Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Any, T))
|
||||
static_assert(is_assignable_to(Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, FinalClass))
|
||||
static_assert(is_assignable_to(T, FinalClass))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(FinalClass, T))
|
||||
static_assert(not is_assignable_to(FinalClass, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Any))
|
||||
static_assert(not is_subtype_of(T, Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Any, T))
|
||||
static_assert(not is_subtype_of(Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(T, FinalClass))
|
||||
static_assert(is_subtype_of(T, FinalClass))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(FinalClass, T))
|
||||
static_assert(not is_subtype_of(FinalClass, T))
|
||||
```
|
||||
|
||||
@@ -370,37 +222,17 @@ typevars to `Never` in addition to that final class.
|
||||
|
||||
```py
|
||||
def two_bounded[T: Super, U: Super](t: T, u: U) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, U))
|
||||
static_assert(not is_assignable_to(T, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(U, T))
|
||||
static_assert(not is_assignable_to(U, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, U))
|
||||
static_assert(not is_subtype_of(T, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(U, T))
|
||||
static_assert(not is_subtype_of(U, T))
|
||||
|
||||
def two_final_bounded[T: FinalClass, U: FinalClass](t: T, u: U) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, U))
|
||||
static_assert(not is_assignable_to(T, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(U, T))
|
||||
static_assert(not is_assignable_to(U, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, U))
|
||||
static_assert(not is_subtype_of(T, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(U, T))
|
||||
static_assert(not is_subtype_of(U, T))
|
||||
```
|
||||
|
||||
@@ -412,237 +244,67 @@ intersection of all of its constraints is a subtype of the typevar.
|
||||
from ty_extensions import Intersection
|
||||
|
||||
def constrained[T: (Base, Unrelated)](t: T) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, Super))
|
||||
static_assert(not is_assignable_to(T, Super))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, Base))
|
||||
static_assert(not is_assignable_to(T, Base))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, Sub))
|
||||
static_assert(not is_assignable_to(T, Sub))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, Unrelated))
|
||||
static_assert(not is_assignable_to(T, Unrelated))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Any))
|
||||
static_assert(is_assignable_to(T, Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Super | Unrelated))
|
||||
static_assert(is_assignable_to(T, Super | Unrelated))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Base | Unrelated))
|
||||
static_assert(is_assignable_to(T, Base | Unrelated))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, Sub | Unrelated))
|
||||
static_assert(not is_assignable_to(T, Sub | Unrelated))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Any, T))
|
||||
static_assert(is_assignable_to(Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(Super, T))
|
||||
static_assert(not is_assignable_to(Super, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(Unrelated, T))
|
||||
static_assert(not is_assignable_to(Unrelated, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(Super | Unrelated, T))
|
||||
static_assert(not is_assignable_to(Super | Unrelated, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Intersection[Base, Unrelated], T))
|
||||
static_assert(is_assignable_to(Intersection[Base, Unrelated], T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Super))
|
||||
static_assert(not is_subtype_of(T, Super))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Base))
|
||||
static_assert(not is_subtype_of(T, Base))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Sub))
|
||||
static_assert(not is_subtype_of(T, Sub))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Unrelated))
|
||||
static_assert(not is_subtype_of(T, Unrelated))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Any))
|
||||
static_assert(not is_subtype_of(T, Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(T, Super | Unrelated))
|
||||
static_assert(is_subtype_of(T, Super | Unrelated))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(T, Base | Unrelated))
|
||||
static_assert(is_subtype_of(T, Base | Unrelated))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Sub | Unrelated))
|
||||
static_assert(not is_subtype_of(T, Sub | Unrelated))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Any, T))
|
||||
static_assert(not is_subtype_of(Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Super, T))
|
||||
static_assert(not is_subtype_of(Super, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Unrelated, T))
|
||||
static_assert(not is_subtype_of(Unrelated, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Super | Unrelated, T))
|
||||
static_assert(not is_subtype_of(Super | Unrelated, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(Intersection[Base, Unrelated], T))
|
||||
static_assert(is_subtype_of(Intersection[Base, Unrelated], T))
|
||||
|
||||
def constrained_by_gradual[T: (Base, Any)](t: T) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Super))
|
||||
static_assert(is_assignable_to(T, Super))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Base))
|
||||
static_assert(is_assignable_to(T, Base))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, Sub))
|
||||
static_assert(not is_assignable_to(T, Sub))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, Unrelated))
|
||||
static_assert(not is_assignable_to(T, Unrelated))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Any))
|
||||
static_assert(is_assignable_to(T, Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Super | Any))
|
||||
static_assert(is_assignable_to(T, Super | Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Super | Unrelated))
|
||||
static_assert(is_assignable_to(T, Super | Unrelated))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(Super, T))
|
||||
static_assert(not is_assignable_to(Super, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Base, T))
|
||||
static_assert(is_assignable_to(Base, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(Unrelated, T))
|
||||
static_assert(not is_assignable_to(Unrelated, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Any, T))
|
||||
static_assert(is_assignable_to(Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(Super | Any, T))
|
||||
static_assert(not is_assignable_to(Super | Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Base | Any, T))
|
||||
static_assert(is_assignable_to(Base | Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(Super | Unrelated, T))
|
||||
static_assert(not is_assignable_to(Super | Unrelated, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Intersection[Base, Unrelated], T))
|
||||
static_assert(is_assignable_to(Intersection[Base, Unrelated], T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Intersection[Base, Any], T))
|
||||
static_assert(is_assignable_to(Intersection[Base, Any], T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Super))
|
||||
static_assert(not is_subtype_of(T, Super))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Base))
|
||||
static_assert(not is_subtype_of(T, Base))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Sub))
|
||||
static_assert(not is_subtype_of(T, Sub))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Unrelated))
|
||||
static_assert(not is_subtype_of(T, Unrelated))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Any))
|
||||
static_assert(not is_subtype_of(T, Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Super | Any))
|
||||
static_assert(not is_subtype_of(T, Super | Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Super | Unrelated))
|
||||
static_assert(not is_subtype_of(T, Super | Unrelated))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Super, T))
|
||||
static_assert(not is_subtype_of(Super, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Base, T))
|
||||
static_assert(not is_subtype_of(Base, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Unrelated, T))
|
||||
static_assert(not is_subtype_of(Unrelated, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Any, T))
|
||||
static_assert(not is_subtype_of(Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Super | Any, T))
|
||||
static_assert(not is_subtype_of(Super | Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Base | Any, T))
|
||||
static_assert(not is_subtype_of(Base | Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Super | Unrelated, T))
|
||||
static_assert(not is_subtype_of(Super | Unrelated, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Intersection[Base, Unrelated], T))
|
||||
static_assert(not is_subtype_of(Intersection[Base, Unrelated], T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Intersection[Base, Any], T))
|
||||
static_assert(not is_subtype_of(Intersection[Base, Any], T))
|
||||
```
|
||||
|
||||
@@ -653,40 +315,20 @@ the same type.
|
||||
|
||||
```py
|
||||
def two_constrained[T: (int, str), U: (int, str)](t: T, u: U) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, U))
|
||||
static_assert(not is_assignable_to(T, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(U, T))
|
||||
static_assert(not is_assignable_to(U, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, U))
|
||||
static_assert(not is_subtype_of(T, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(U, T))
|
||||
static_assert(not is_subtype_of(U, T))
|
||||
|
||||
@final
|
||||
class AnotherFinalClass: ...
|
||||
|
||||
def two_final_constrained[T: (FinalClass, AnotherFinalClass), U: (FinalClass, AnotherFinalClass)](t: T, u: U) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, U))
|
||||
static_assert(not is_assignable_to(T, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(U, T))
|
||||
static_assert(not is_assignable_to(U, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, U))
|
||||
static_assert(not is_subtype_of(T, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(U, T))
|
||||
static_assert(not is_subtype_of(U, T))
|
||||
```
|
||||
|
||||
@@ -694,20 +336,10 @@ A bound or constrained typevar is a subtype of itself in a union:
|
||||
|
||||
```py
|
||||
def union[T: Base, U: (Base, Unrelated)](t: T, u: U) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, T | None))
|
||||
static_assert(is_assignable_to(T, T | None))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(U, U | None))
|
||||
static_assert(is_assignable_to(U, U | None))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(T, T | None))
|
||||
static_assert(is_subtype_of(T, T | None))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(U, U | None))
|
||||
static_assert(is_subtype_of(U, U | None))
|
||||
```
|
||||
|
||||
@@ -715,20 +347,10 @@ A bound or constrained typevar in a union with a dynamic type is assignable to t
|
||||
|
||||
```py
|
||||
def union_with_dynamic[T: Base, U: (Base, Unrelated)](t: T, u: U) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T | Any, T))
|
||||
static_assert(is_assignable_to(T | Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(U | Any, U))
|
||||
static_assert(is_assignable_to(U | Any, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T | Any, T))
|
||||
static_assert(not is_subtype_of(T | Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(U | Any, U))
|
||||
static_assert(not is_subtype_of(U | Any, U))
|
||||
```
|
||||
|
||||
@@ -740,20 +362,9 @@ from ty_extensions import Intersection, Not, is_disjoint_from
|
||||
class A: ...
|
||||
|
||||
def inter[T: Base, U: (Base, Unrelated)](t: T, u: U) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Intersection[T, Unrelated], T))
|
||||
static_assert(is_assignable_to(Intersection[T, Unrelated], T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(Intersection[T, Unrelated], T))
|
||||
static_assert(is_subtype_of(Intersection[T, Unrelated], T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Intersection[U, A], U))
|
||||
static_assert(is_assignable_to(Intersection[U, A], U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(Intersection[U, A], U))
|
||||
static_assert(is_subtype_of(Intersection[U, A], U))
|
||||
|
||||
static_assert(is_disjoint_from(Not[T], T))
|
||||
@@ -1054,20 +665,10 @@ of) itself.
|
||||
from ty_extensions import is_assignable_to, is_subtype_of, Not, static_assert
|
||||
|
||||
def intersection_is_assignable[T](t: T) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Intersection[T, None], T))
|
||||
static_assert(is_assignable_to(Intersection[T, None], T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Intersection[T, Not[None]], T))
|
||||
static_assert(is_assignable_to(Intersection[T, Not[None]], T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(Intersection[T, None], T))
|
||||
static_assert(is_subtype_of(Intersection[T, None], T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(Intersection[T, Not[None]], T))
|
||||
static_assert(is_subtype_of(Intersection[T, Not[None]], T))
|
||||
```
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ Y: int = 47
|
||||
import mypackage
|
||||
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
# error: "has no member `fails`"
|
||||
# error: [unresolved-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -90,7 +90,7 @@ Y: int = 47
|
||||
import mypackage
|
||||
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
# error: "has no member `fails`"
|
||||
# error: [unresolved-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -125,7 +125,7 @@ Y: int = 47
|
||||
import mypackage
|
||||
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
# error: "has no member `fails`"
|
||||
# error: [unresolved-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -155,7 +155,7 @@ Y: int = 47
|
||||
import mypackage
|
||||
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
# error: "has no member `fails`"
|
||||
# error: [unresolved-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -184,7 +184,7 @@ X: int = 42
|
||||
import mypackage
|
||||
|
||||
# TODO: this could work and would be nice to have?
|
||||
# error: "has no member `imported`"
|
||||
# error: [unresolved-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -208,14 +208,14 @@ X: int = 42
|
||||
import mypackage
|
||||
|
||||
# TODO: this could work and would be nice to have
|
||||
# error: "has no member `imported`"
|
||||
# error: [unresolved-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Relative `from` Import of Nested Submodule in `__init__`
|
||||
|
||||
`from .submodule import nested` in an `__init__.pyi` does not re-export `mypackage.submodule`,
|
||||
`mypackage.submodule.nested`, or `nested`.
|
||||
`from .submodule import nested` in an `__init__.pyi` does re-export `mypackage.submodule`, but not
|
||||
`mypackage.submodule.nested` or `nested`.
|
||||
|
||||
### In Stub
|
||||
|
||||
@@ -241,15 +241,14 @@ X: int = 42
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
|
||||
# error: [unresolved-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
# error: [unresolved-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
# error: "has no member `nested`"
|
||||
# error: [unresolved-attribute] "has no member `nested`"
|
||||
reveal_type(mypackage.nested) # revealed: Unknown
|
||||
# error: "has no member `nested`"
|
||||
# error: [unresolved-attribute] "has no member `nested`"
|
||||
reveal_type(mypackage.nested.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -281,9 +280,9 @@ import mypackage
|
||||
|
||||
reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
|
||||
# TODO: this would be nice to support
|
||||
# error: "has no member `nested`"
|
||||
# error: [unresolved-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: "has no member `nested`"
|
||||
# error: [unresolved-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
reveal_type(mypackage.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
reveal_type(mypackage.nested.X) # revealed: int
|
||||
@@ -318,16 +317,14 @@ X: int = 42
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
# TODO: this could work and would be nice to have
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
|
||||
# error: [unresolved-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
# error: [unresolved-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
# error: "has no member `nested`"
|
||||
# error: [unresolved-attribute] "has no member `nested`"
|
||||
reveal_type(mypackage.nested) # revealed: Unknown
|
||||
# error: "has no member `nested`"
|
||||
# error: [unresolved-attribute] "has no member `nested`"
|
||||
reveal_type(mypackage.nested.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -359,9 +356,9 @@ import mypackage
|
||||
|
||||
reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
|
||||
# TODO: this would be nice to support
|
||||
# error: "has no member `nested`"
|
||||
# error: [unresolved-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: "has no member `nested`"
|
||||
# error: [unresolved-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
reveal_type(mypackage.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
reveal_type(mypackage.nested.X) # revealed: int
|
||||
@@ -396,11 +393,11 @@ X: int = 42
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
# error: "has no member `submodule`"
|
||||
# error: [unresolved-attribute] "Submodule `submodule` may not be available"
|
||||
reveal_type(mypackage.submodule) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
# error: [unresolved-attribute] "Submodule `submodule` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
# error: [unresolved-attribute] "Submodule `submodule` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -432,11 +429,11 @@ X: int = 42
|
||||
import mypackage
|
||||
|
||||
# TODO: this would be nice to support
|
||||
# error: "has no member `submodule`"
|
||||
# error: [unresolved-attribute] "Submodule `submodule` may not be available"
|
||||
reveal_type(mypackage.submodule) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
# error: [unresolved-attribute] "Submodule `submodule` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
# error: [unresolved-attribute] "Submodule `submodule` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -463,9 +460,9 @@ X: int = 42
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
# error: "has no member `imported`"
|
||||
# error: [unresolved-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
# error: "has no member `imported_m`"
|
||||
# error: [unresolved-attribute] "has no member `imported_m`"
|
||||
reveal_type(mypackage.imported_m.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -489,7 +486,7 @@ X: int = 42
|
||||
import mypackage
|
||||
|
||||
# TODO: this would be nice to support, as it works at runtime
|
||||
# error: "has no member `imported`"
|
||||
# error: [unresolved-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
reveal_type(mypackage.imported_m.X) # revealed: int
|
||||
```
|
||||
@@ -569,7 +566,7 @@ X: int = 42
|
||||
from mypackage import *
|
||||
|
||||
# TODO: this would be nice to support
|
||||
# error: "`imported` used when not defined"
|
||||
# error: [unresolved-reference] "`imported` used when not defined"
|
||||
reveal_type(imported.X) # revealed: Unknown
|
||||
reveal_type(Z) # revealed: int
|
||||
```
|
||||
@@ -623,8 +620,7 @@ X: int = 42
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
# error: "no member `imported`"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
```
|
||||
|
||||
### In Non-Stub
|
||||
@@ -673,10 +669,11 @@ X: int = 42
|
||||
import mypackage
|
||||
from mypackage import imported
|
||||
|
||||
reveal_type(imported.X) # revealed: int
|
||||
|
||||
# TODO: this would be nice to support, but it's dangerous with available_submodule_attributes
|
||||
# for details, see: https://github.com/astral-sh/ty/issues/1488
|
||||
reveal_type(imported.X) # revealed: int
|
||||
# error: "has no member `imported`"
|
||||
# error: [unresolved-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -699,9 +696,10 @@ X: int = 42
|
||||
import mypackage
|
||||
from mypackage import imported
|
||||
|
||||
# TODO: this would be nice to support, as it works at runtime
|
||||
reveal_type(imported.X) # revealed: int
|
||||
# error: "has no member `imported`"
|
||||
|
||||
# TODO: this would be nice to support, as it works at runtime
|
||||
# error: [unresolved-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -737,9 +735,9 @@ import mypackage
|
||||
from mypackage import imported
|
||||
|
||||
reveal_type(imported.X) # revealed: int
|
||||
# error: "has no member `fails`"
|
||||
# error: [unresolved-attribute] "has no member `fails`"
|
||||
reveal_type(imported.fails.Y) # revealed: Unknown
|
||||
# error: "has no member `fails`"
|
||||
# error: [unresolved-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -772,7 +770,7 @@ from mypackage import imported
|
||||
|
||||
reveal_type(imported.X) # revealed: int
|
||||
reveal_type(imported.fails.Y) # revealed: int
|
||||
# error: "has no member `fails`"
|
||||
# error: [unresolved-attribute] "Submodule `fails`"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
```
|
||||
|
||||
|
||||
@@ -247,7 +247,7 @@ X: int = 42
|
||||
from . import foo
|
||||
import package
|
||||
|
||||
# error: [unresolved-attribute] "Module `package` has no member `foo`"
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(package.foo.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
|
||||
@@ -335,6 +335,9 @@ reveal_type(x19) # revealed: list[Literal[1]]
|
||||
x20: list[Literal[1]] | None = [1]
|
||||
reveal_type(x20) # revealed: list[Literal[1]]
|
||||
|
||||
x21: X[Literal[1]] | None = x(1)
|
||||
x21: X[Literal[1]] | None = X(1)
|
||||
reveal_type(x21) # revealed: X[Literal[1]]
|
||||
|
||||
x22: X[Literal[1]] | None = x(1)
|
||||
reveal_type(x22) # revealed: X[Literal[1]]
|
||||
```
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: attributes.md - Attributes - Unimported submodule incorrectly accessed as attribute
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/attributes.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## foo/__init__.py
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
## foo/bar.py
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
## baz/bar.py
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
## main.py
|
||||
|
||||
```
|
||||
1 | import foo
|
||||
2 | import baz
|
||||
3 |
|
||||
4 | # error: [unresolved-attribute]
|
||||
5 | reveal_type(foo.bar) # revealed: Unknown
|
||||
6 | # error: [unresolved-attribute]
|
||||
7 | reveal_type(baz.bar) # revealed: Unknown
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[unresolved-attribute]: Submodule `bar` may not be available as an attribute on module `foo`
|
||||
--> src/main.py:5:13
|
||||
|
|
||||
4 | # error: [unresolved-attribute]
|
||||
5 | reveal_type(foo.bar) # revealed: Unknown
|
||||
| ^^^^^^^
|
||||
6 | # error: [unresolved-attribute]
|
||||
7 | reveal_type(baz.bar) # revealed: Unknown
|
||||
|
|
||||
help: Consider explicitly importing `foo.bar`
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-attribute]: Submodule `bar` may not be available as an attribute on module `baz`
|
||||
--> src/main.py:7:13
|
||||
|
|
||||
5 | reveal_type(foo.bar) # revealed: Unknown
|
||||
6 | # error: [unresolved-attribute]
|
||||
7 | reveal_type(baz.bar) # revealed: Unknown
|
||||
| ^^^^^^^
|
||||
|
|
||||
help: Consider explicitly importing `baz.bar`
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
```
|
||||
@@ -628,7 +628,7 @@ import imported
|
||||
from module2 import imported as other_imported
|
||||
from ty_extensions import TypeOf, static_assert, is_equivalent_to
|
||||
|
||||
# error: [unresolved-attribute] "Module `imported` has no member `abc`"
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(imported.abc) # revealed: Unknown
|
||||
|
||||
reveal_type(other_imported.abc) # revealed: <module 'imported.abc'>
|
||||
|
||||
@@ -318,7 +318,7 @@ impl ModuleName {
|
||||
db: &dyn Db,
|
||||
importing_file: File,
|
||||
) -> Result<Self, ModuleNameResolutionError> {
|
||||
Self::from_identifier_parts(db, importing_file, None, 1)
|
||||
relative_module_name(db, importing_file, None, NonZeroU32::new(1).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1513,33 +1513,42 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
||||
// that `x` can be freely overwritten, and that we don't assume that an import
|
||||
// in one function is visible in another function.
|
||||
let mut is_self_import = false;
|
||||
if self.file.is_package(self.db)
|
||||
&& let Ok(module_name) = ModuleName::from_identifier_parts(
|
||||
self.db,
|
||||
self.file,
|
||||
node.module.as_deref(),
|
||||
node.level,
|
||||
)
|
||||
&& let Ok(thispackage) = ModuleName::package_for_file(self.db, self.file)
|
||||
{
|
||||
let is_package = self.file.is_package(self.db);
|
||||
let this_package = ModuleName::package_for_file(self.db, self.file);
|
||||
|
||||
if let Ok(module_name) = ModuleName::from_identifier_parts(
|
||||
self.db,
|
||||
self.file,
|
||||
node.module.as_deref(),
|
||||
node.level,
|
||||
) {
|
||||
// Record whether this is equivalent to `from . import ...`
|
||||
is_self_import = module_name == thispackage;
|
||||
if is_package && let Ok(thispackage) = this_package.as_ref() {
|
||||
is_self_import = &module_name == thispackage;
|
||||
}
|
||||
|
||||
if node.module.is_some()
|
||||
&& let Some(relative_submodule) = module_name.relative_to(&thispackage)
|
||||
&& let Some(direct_submodule) = relative_submodule.components().next()
|
||||
&& !self.seen_submodule_imports.contains(direct_submodule)
|
||||
&& self.current_scope().is_global()
|
||||
{
|
||||
self.seen_submodule_imports
|
||||
.insert(direct_submodule.to_owned());
|
||||
if self.current_scope().is_global() && node.module.is_some() {
|
||||
if let Ok(thispackage) = this_package
|
||||
&& let Some(relative_submodule) = module_name.relative_to(&thispackage)
|
||||
{
|
||||
if is_package
|
||||
&& let Some(direct_submodule) =
|
||||
relative_submodule.components().next()
|
||||
&& !self.seen_submodule_imports.contains(direct_submodule)
|
||||
{
|
||||
self.seen_submodule_imports
|
||||
.insert(direct_submodule.to_owned());
|
||||
|
||||
let direct_submodule_name = Name::new(direct_submodule);
|
||||
let symbol = self.add_symbol(direct_submodule_name);
|
||||
self.add_definition(
|
||||
symbol.into(),
|
||||
ImportFromSubmoduleDefinitionNodeRef { node },
|
||||
);
|
||||
let direct_submodule_name = Name::new(direct_submodule);
|
||||
let symbol = self.add_symbol(direct_submodule_name);
|
||||
self.add_definition(
|
||||
symbol.into(),
|
||||
ImportFromSubmoduleDefinitionNodeRef { node },
|
||||
);
|
||||
}
|
||||
} else {
|
||||
self.imported_modules.insert(module_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -364,10 +364,12 @@ pub(crate) struct ImportFromDefinitionNodeRef<'ast> {
|
||||
pub(crate) alias_index: usize,
|
||||
pub(crate) is_reexported: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) struct ImportFromSubmoduleDefinitionNodeRef<'ast> {
|
||||
pub(crate) node: &'ast ast::StmtImportFrom,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) struct AssignmentDefinitionNodeRef<'ast, 'db> {
|
||||
pub(crate) unpack: Option<(UnpackPosition, Unpack<'db>)>,
|
||||
@@ -702,7 +704,7 @@ impl DefinitionKind<'_> {
|
||||
match self {
|
||||
DefinitionKind::Import(import) => import.is_reexported(),
|
||||
DefinitionKind::ImportFrom(import) => import.is_reexported(),
|
||||
DefinitionKind::ImportFromSubmodule(_) => false,
|
||||
DefinitionKind::ImportFromSubmodule(_) => true,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3504,9 +3504,10 @@ impl<'db> Type<'db> {
|
||||
return;
|
||||
};
|
||||
|
||||
let tcx_specialization = tcx
|
||||
.annotation
|
||||
.and_then(|tcx| tcx.specialization_of(db, class_literal));
|
||||
let tcx_specialization = tcx.annotation.and_then(|tcx| {
|
||||
tcx.filter_union(db, |ty| ty.specialization_of(db, class_literal).is_some())
|
||||
.specialization_of(db, class_literal)
|
||||
});
|
||||
|
||||
for (typevar, ty) in specialization
|
||||
.generic_context(db)
|
||||
|
||||
@@ -32,7 +32,7 @@ use ruff_db::diagnostic::{Annotation, Diagnostic, Span, SubDiagnostic, SubDiagno
|
||||
use ruff_db::source::source_text;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_python_ast::parenthesize::parentheses_iterator;
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef, Identifier};
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use rustc_hash::FxHashSet;
|
||||
@@ -2436,6 +2436,7 @@ pub(super) fn report_possibly_missing_attribute(
|
||||
pub(super) fn report_invalid_exception_tuple_caught<'db, 'ast>(
|
||||
context: &InferContext<'db, 'ast>,
|
||||
node: &'ast ast::ExprTuple,
|
||||
node_type: Type<'db>,
|
||||
invalid_tuple_nodes: impl IntoIterator<Item = (&'ast ast::Expr, Type<'db>)>,
|
||||
) {
|
||||
let Some(builder) = context.report_lint(&INVALID_EXCEPTION_CAUGHT, node) else {
|
||||
@@ -2443,6 +2444,10 @@ pub(super) fn report_invalid_exception_tuple_caught<'db, 'ast>(
|
||||
};
|
||||
|
||||
let mut diagnostic = builder.into_diagnostic("Invalid tuple caught in an exception handler");
|
||||
diagnostic.set_concise_message(format_args!(
|
||||
"Cannot catch object of type `{}` in an exception handler",
|
||||
node_type.display(context.db())
|
||||
));
|
||||
|
||||
for (sub_node, ty) in invalid_tuple_nodes {
|
||||
let span = context.span(sub_node);
|
||||
@@ -3427,7 +3432,7 @@ pub(super) fn hint_if_stdlib_attribute_exists_on_other_versions(
|
||||
db: &dyn Db,
|
||||
mut diagnostic: LintDiagnosticGuard,
|
||||
value_type: &Type,
|
||||
attr: &Identifier,
|
||||
attr: &str,
|
||||
) {
|
||||
// Currently we limit this analysis to attributes of stdlib modules,
|
||||
// as this covers the most important cases while not being too noisy
|
||||
@@ -3461,6 +3466,6 @@ pub(super) fn hint_if_stdlib_attribute_exists_on_other_versions(
|
||||
add_inferred_python_version_hint_to_diagnostic(
|
||||
db,
|
||||
&mut diagnostic,
|
||||
&format!("accessing `{}`", attr.id),
|
||||
&format!("accessing `{attr}`"),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -190,6 +190,11 @@ impl<'a, 'b, 'db> TypeWriter<'a, 'b, 'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience for `with_detail(TypeDetail::Type(ty))`
|
||||
fn with_type<'c>(&'c mut self, ty: Type<'db>) -> TypeDetailGuard<'a, 'b, 'c, 'db> {
|
||||
self.with_detail(TypeDetail::Type(ty))
|
||||
}
|
||||
|
||||
fn join<'c>(&'c mut self, separator: &'static str) -> Join<'a, 'b, 'c, 'db> {
|
||||
Join {
|
||||
fmt: self,
|
||||
@@ -469,10 +474,8 @@ impl<'db> FmtDetailed<'db> for DisplayType<'db> {
|
||||
| Type::StringLiteral(_)
|
||||
| Type::BytesLiteral(_)
|
||||
| Type::EnumLiteral(_) => {
|
||||
f.with_detail(TypeDetail::Type(Type::SpecialForm(
|
||||
SpecialFormType::Literal,
|
||||
)))
|
||||
.write_str("Literal")?;
|
||||
f.with_type(Type::SpecialForm(SpecialFormType::Literal))
|
||||
.write_str("Literal")?;
|
||||
f.write_char('[')?;
|
||||
representation.fmt_detailed(f)?;
|
||||
f.write_str("]")
|
||||
@@ -565,7 +568,7 @@ impl<'db> FmtDetailed<'db> for ClassDisplay<'db> {
|
||||
f.write_char('.')?;
|
||||
}
|
||||
}
|
||||
f.with_detail(TypeDetail::Type(Type::ClassLiteral(self.class)))
|
||||
f.with_type(Type::ClassLiteral(self.class))
|
||||
.write_str(self.class.name(self.db))?;
|
||||
if qualification_level == Some(&QualificationLevel::FileAndLineNumber) {
|
||||
let file = self.class.file(self.db);
|
||||
@@ -611,14 +614,14 @@ impl Display for DisplayRepresentation<'_> {
|
||||
impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||
fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result {
|
||||
match self.ty {
|
||||
Type::Dynamic(dynamic) => write!(f.with_detail(TypeDetail::Type(self.ty)), "{dynamic}"),
|
||||
Type::Never => f.with_detail(TypeDetail::Type(self.ty)).write_str("Never"),
|
||||
Type::Dynamic(dynamic) => write!(f.with_type(self.ty), "{dynamic}"),
|
||||
Type::Never => f.with_type(self.ty).write_str("Never"),
|
||||
Type::NominalInstance(instance) => {
|
||||
let class = instance.class(self.db);
|
||||
|
||||
match (class, class.known(self.db)) {
|
||||
(_, Some(KnownClass::NoneType)) => f.with_detail(TypeDetail::Type(self.ty)).write_str("None"),
|
||||
(_, Some(KnownClass::NoDefaultType)) => f.with_detail(TypeDetail::Type(self.ty)).write_str("NoDefault"),
|
||||
(_, Some(KnownClass::NoneType)) => f.with_type(self.ty).write_str("None"),
|
||||
(_, Some(KnownClass::NoDefaultType)) => f.with_type(self.ty).write_str("NoDefault"),
|
||||
(ClassType::Generic(alias), Some(KnownClass::Tuple)) => alias
|
||||
.specialization(self.db)
|
||||
.tuple(self.db)
|
||||
@@ -642,10 +645,8 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||
},
|
||||
Protocol::Synthesized(synthetic) => {
|
||||
f.write_char('<')?;
|
||||
f.with_detail(TypeDetail::Type(Type::SpecialForm(
|
||||
SpecialFormType::Protocol,
|
||||
)))
|
||||
.write_str("Protocol")?;
|
||||
f.with_type(Type::SpecialForm(SpecialFormType::Protocol))
|
||||
.write_str("Protocol")?;
|
||||
f.write_str(" with members ")?;
|
||||
let interface = synthetic.interface();
|
||||
let member_list = interface.members(self.db);
|
||||
@@ -660,18 +661,16 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||
f.write_char('>')
|
||||
}
|
||||
},
|
||||
Type::PropertyInstance(_) => f
|
||||
.with_detail(TypeDetail::Type(self.ty))
|
||||
.write_str("property"),
|
||||
Type::PropertyInstance(_) => f.with_type(self.ty).write_str("property"),
|
||||
Type::ModuleLiteral(module) => {
|
||||
write!(
|
||||
f.with_detail(TypeDetail::Type(self.ty)),
|
||||
f.with_type(self.ty),
|
||||
"<module '{}'>",
|
||||
module.module(self.db).name(self.db)
|
||||
)
|
||||
}
|
||||
Type::ClassLiteral(class) => {
|
||||
let mut f = f.with_detail(TypeDetail::Type(self.ty));
|
||||
let mut f = f.with_type(self.ty);
|
||||
f.write_str("<class '")?;
|
||||
class
|
||||
.display_with(self.db, self.settings.clone())
|
||||
@@ -679,7 +678,7 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||
f.write_str("'>")
|
||||
}
|
||||
Type::GenericAlias(generic) => {
|
||||
let mut f = f.with_detail(TypeDetail::Type(self.ty));
|
||||
let mut f = f.with_type(self.ty);
|
||||
f.write_str("<class '")?;
|
||||
generic
|
||||
.display_with(self.db, self.settings.clone())
|
||||
@@ -688,7 +687,7 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||
}
|
||||
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
|
||||
SubclassOfInner::Class(ClassType::NonGeneric(class)) => {
|
||||
f.with_detail(TypeDetail::Type(KnownClass::Type.to_class_literal(self.db)))
|
||||
f.with_type(KnownClass::Type.to_class_literal(self.db))
|
||||
.write_str("type")?;
|
||||
f.write_char('[')?;
|
||||
class
|
||||
@@ -697,7 +696,7 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||
f.write_char(']')
|
||||
}
|
||||
SubclassOfInner::Class(ClassType::Generic(alias)) => {
|
||||
f.with_detail(TypeDetail::Type(KnownClass::Type.to_class_literal(self.db)))
|
||||
f.with_type(KnownClass::Type.to_class_literal(self.db))
|
||||
.write_str("type")?;
|
||||
f.write_char('[')?;
|
||||
alias
|
||||
@@ -706,24 +705,19 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||
f.write_char(']')
|
||||
}
|
||||
SubclassOfInner::Dynamic(dynamic) => {
|
||||
f.with_detail(TypeDetail::Type(KnownClass::Type.to_class_literal(self.db)))
|
||||
f.with_type(KnownClass::Type.to_class_literal(self.db))
|
||||
.write_str("type")?;
|
||||
f.write_char('[')?;
|
||||
write!(
|
||||
f.with_detail(TypeDetail::Type(Type::Dynamic(dynamic))),
|
||||
"{dynamic}"
|
||||
)?;
|
||||
write!(f.with_type(Type::Dynamic(dynamic)), "{dynamic}")?;
|
||||
f.write_char(']')
|
||||
}
|
||||
},
|
||||
Type::SpecialForm(special_form) => {
|
||||
write!(f.with_detail(TypeDetail::Type(self.ty)), "{special_form}")
|
||||
write!(f.with_type(self.ty), "{special_form}")
|
||||
}
|
||||
Type::KnownInstance(known_instance) => {
|
||||
write!(f.with_type(self.ty), "{}", known_instance.repr(self.db))
|
||||
}
|
||||
Type::KnownInstance(known_instance) => write!(
|
||||
f.with_detail(TypeDetail::Type(self.ty)),
|
||||
"{}",
|
||||
known_instance.repr(self.db)
|
||||
),
|
||||
Type::FunctionLiteral(function) => function
|
||||
.display_with(self.db, self.settings.clone())
|
||||
.fmt_detailed(f),
|
||||
@@ -748,8 +742,7 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||
.display_with(self.db, self.settings.singleline())
|
||||
.fmt_detailed(f)?;
|
||||
f.write_char('.')?;
|
||||
f.with_detail(TypeDetail::Type(self.ty))
|
||||
.write_str(function.name(self.db))?;
|
||||
f.with_type(self.ty).write_str(function.name(self.db))?;
|
||||
type_parameters.fmt_detailed(f)?;
|
||||
signature
|
||||
.bind_self(self.db, Some(typing_self_ty))
|
||||
@@ -845,13 +838,14 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||
Type::Intersection(intersection) => intersection
|
||||
.display_with(self.db, self.settings.clone())
|
||||
.fmt_detailed(f),
|
||||
Type::IntLiteral(n) => write!(f.with_detail(TypeDetail::Type(self.ty)), "{n}"),
|
||||
Type::BooleanLiteral(boolean) => f
|
||||
.with_detail(TypeDetail::Type(self.ty))
|
||||
.write_str(if boolean { "True" } else { "False" }),
|
||||
Type::IntLiteral(n) => write!(f.with_type(self.ty), "{n}"),
|
||||
Type::BooleanLiteral(boolean) => {
|
||||
f.with_type(self.ty)
|
||||
.write_str(if boolean { "True" } else { "False" })
|
||||
}
|
||||
Type::StringLiteral(string) => {
|
||||
write!(
|
||||
f.with_detail(TypeDetail::Type(self.ty)),
|
||||
f.with_type(self.ty),
|
||||
"{}",
|
||||
string.display_with(self.db, self.settings.clone())
|
||||
)
|
||||
@@ -861,14 +855,12 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||
// inlay hint, but that seems less useful than the definition of `str` for a variable that is
|
||||
// inferred as an *inhabitant* of `LiteralString` (since that variable will just be a string
|
||||
// at runtime)
|
||||
Type::LiteralString => f
|
||||
.with_detail(TypeDetail::Type(self.ty))
|
||||
.write_str("LiteralString"),
|
||||
Type::LiteralString => f.with_type(self.ty).write_str("LiteralString"),
|
||||
Type::BytesLiteral(bytes) => {
|
||||
let escape = AsciiEscape::with_preferred_quote(bytes.value(self.db), Quote::Double);
|
||||
|
||||
write!(
|
||||
f.with_detail(TypeDetail::Type(self.ty)),
|
||||
f.with_type(self.ty),
|
||||
"{}",
|
||||
escape.bytes_repr(TripleQuotes::No)
|
||||
)
|
||||
@@ -883,12 +875,8 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||
Type::TypeVar(bound_typevar) => {
|
||||
write!(f, "{}", bound_typevar.identity(self.db).display(self.db))
|
||||
}
|
||||
Type::AlwaysTruthy => f
|
||||
.with_detail(TypeDetail::Type(self.ty))
|
||||
.write_str("AlwaysTruthy"),
|
||||
Type::AlwaysFalsy => f
|
||||
.with_detail(TypeDetail::Type(self.ty))
|
||||
.write_str("AlwaysFalsy"),
|
||||
Type::AlwaysTruthy => f.with_type(self.ty).write_str("AlwaysTruthy"),
|
||||
Type::AlwaysFalsy => f.with_type(self.ty).write_str("AlwaysFalsy"),
|
||||
Type::BoundSuper(bound_super) => {
|
||||
f.write_str("<super: ")?;
|
||||
Type::from(bound_super.pivot_class(self.db))
|
||||
@@ -901,7 +889,7 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||
f.write_str(">")
|
||||
}
|
||||
Type::TypeIs(type_is) => {
|
||||
f.with_detail(TypeDetail::Type(Type::SpecialForm(SpecialFormType::TypeIs)))
|
||||
f.with_type(Type::SpecialForm(SpecialFormType::TypeIs))
|
||||
.write_str("TypeIs")?;
|
||||
f.write_char('[')?;
|
||||
type_is
|
||||
@@ -929,9 +917,7 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||
.fmt_detailed(f),
|
||||
}
|
||||
}
|
||||
Type::NewTypeInstance(newtype) => f
|
||||
.with_detail(TypeDetail::Type(self.ty))
|
||||
.write_str(newtype.name(self.db)),
|
||||
Type::NewTypeInstance(newtype) => f.with_type(self.ty).write_str(newtype.name(self.db)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -982,10 +968,8 @@ pub(crate) struct DisplayTuple<'a, 'db> {
|
||||
|
||||
impl<'db> FmtDetailed<'db> for DisplayTuple<'_, 'db> {
|
||||
fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result {
|
||||
f.with_detail(TypeDetail::Type(
|
||||
KnownClass::Tuple.to_class_literal(self.db),
|
||||
))
|
||||
.write_str("tuple")?;
|
||||
f.with_type(KnownClass::Tuple.to_class_literal(self.db))
|
||||
.write_str("tuple")?;
|
||||
f.write_char('[')?;
|
||||
match self.tuple {
|
||||
TupleSpec::Fixed(tuple) => {
|
||||
@@ -1024,10 +1008,8 @@ impl<'db> FmtDetailed<'db> for DisplayTuple<'_, 'db> {
|
||||
if !tuple.prefix.is_empty() || !tuple.suffix.is_empty() {
|
||||
f.write_char('*')?;
|
||||
// Might as well link the type again here too
|
||||
f.with_detail(TypeDetail::Type(
|
||||
KnownClass::Tuple.to_class_literal(self.db),
|
||||
))
|
||||
.write_str("tuple")?;
|
||||
f.with_type(KnownClass::Tuple.to_class_literal(self.db))
|
||||
.write_str("tuple")?;
|
||||
f.write_char('[')?;
|
||||
}
|
||||
tuple
|
||||
@@ -1218,8 +1200,7 @@ impl<'db> FmtDetailed<'db> for DisplayGenericAlias<'db> {
|
||||
Some(_) => "]",
|
||||
};
|
||||
if let Some((name, form)) = prefix_details {
|
||||
f.with_detail(TypeDetail::Type(Type::SpecialForm(form)))
|
||||
.write_str(name)?;
|
||||
f.with_type(Type::SpecialForm(form)).write_str(name)?;
|
||||
f.write_char('[')?;
|
||||
}
|
||||
self.origin
|
||||
@@ -1890,10 +1871,8 @@ const LITERAL_POLICY: TruncationPolicy = TruncationPolicy {
|
||||
|
||||
impl<'db> FmtDetailed<'db> for DisplayLiteralGroup<'db> {
|
||||
fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result {
|
||||
f.with_detail(TypeDetail::Type(Type::SpecialForm(
|
||||
SpecialFormType::Literal,
|
||||
)))
|
||||
.write_str("Literal")?;
|
||||
f.with_type(Type::SpecialForm(SpecialFormType::Literal))
|
||||
.write_str("Literal")?;
|
||||
f.write_char('[')?;
|
||||
|
||||
let total_entries = self.literals.len();
|
||||
|
||||
@@ -113,13 +113,12 @@ pub(crate) fn infer_definition_types<'db>(
|
||||
|
||||
fn definition_cycle_recover<'db>(
|
||||
db: &'db dyn Db,
|
||||
_id: salsa::Id,
|
||||
cycle: &salsa::Cycle,
|
||||
last_provisional_value: &DefinitionInference<'db>,
|
||||
value: DefinitionInference<'db>,
|
||||
count: u32,
|
||||
definition: Definition<'db>,
|
||||
) -> DefinitionInference<'db> {
|
||||
if &value == last_provisional_value || count != ITERATIONS_BEFORE_FALLBACK {
|
||||
if &value == last_provisional_value || cycle.iteration() != ITERATIONS_BEFORE_FALLBACK {
|
||||
value
|
||||
} else {
|
||||
DefinitionInference::cycle_fallback(definition.scope(db))
|
||||
@@ -227,13 +226,12 @@ pub(crate) fn infer_isolated_expression<'db>(
|
||||
|
||||
fn expression_cycle_recover<'db>(
|
||||
db: &'db dyn Db,
|
||||
_id: salsa::Id,
|
||||
cycle: &salsa::Cycle,
|
||||
last_provisional_value: &ExpressionInference<'db>,
|
||||
value: ExpressionInference<'db>,
|
||||
count: u32,
|
||||
input: InferExpression<'db>,
|
||||
) -> ExpressionInference<'db> {
|
||||
if &value == last_provisional_value || count != ITERATIONS_BEFORE_FALLBACK {
|
||||
if &value == last_provisional_value || cycle.iteration() != ITERATIONS_BEFORE_FALLBACK {
|
||||
value
|
||||
} else {
|
||||
ExpressionInference::cycle_fallback(input.expression(db).scope(db))
|
||||
|
||||
@@ -3053,7 +3053,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
.iter()
|
||||
.map(|(index, ty)| (&tuple.elts[*index], **ty));
|
||||
|
||||
report_invalid_exception_tuple_caught(&self.context, tuple, invalid_elements);
|
||||
report_invalid_exception_tuple_caught(
|
||||
&self.context,
|
||||
tuple,
|
||||
node_ty,
|
||||
invalid_elements,
|
||||
);
|
||||
} else {
|
||||
report_invalid_exception_caught(&self.context, node, node_ty);
|
||||
}
|
||||
@@ -9012,7 +9017,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
assigned_type = Some(ty);
|
||||
}
|
||||
}
|
||||
let fallback_place = value_type.member(db, &attr.id);
|
||||
let mut fallback_place = value_type.member(db, &attr.id);
|
||||
// Exclude non-definitely-bound places for purposes of reachability
|
||||
// analysis. We currently do not perform boundness analysis for implicit
|
||||
// instance attributes, so we exclude them here as well.
|
||||
@@ -9024,91 +9029,118 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
self.all_definitely_bound = false;
|
||||
}
|
||||
|
||||
let resolved_type =
|
||||
fallback_place.map_type(|ty| {
|
||||
fallback_place = fallback_place.map_type(|ty| {
|
||||
self.narrow_expr_with_applicable_constraints(attribute, ty, &constraint_keys)
|
||||
}).unwrap_with_diagnostic(|lookup_error| match lookup_error {
|
||||
LookupError::Undefined(_) => {
|
||||
let report_unresolved_attribute = self.is_reachable(attribute);
|
||||
});
|
||||
|
||||
if report_unresolved_attribute {
|
||||
let bound_on_instance = match value_type {
|
||||
Type::ClassLiteral(class) => {
|
||||
!class.instance_member(db, None, attr).is_undefined()
|
||||
}
|
||||
Type::SubclassOf(subclass_of @ SubclassOfType { .. }) => {
|
||||
match subclass_of.subclass_of() {
|
||||
SubclassOfInner::Class(class) => {
|
||||
!class.instance_member(db, attr).is_undefined()
|
||||
}
|
||||
SubclassOfInner::Dynamic(_) => unreachable!(
|
||||
"Attribute lookup on a dynamic `SubclassOf` type should always return a bound symbol"
|
||||
),
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
let attr_name = &attr.id;
|
||||
|
||||
if let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&UNRESOLVED_ATTRIBUTE, attribute)
|
||||
{
|
||||
if bound_on_instance {
|
||||
builder.into_diagnostic(
|
||||
format_args!(
|
||||
"Attribute `{}` can only be accessed on instances, \
|
||||
not on the class object `{}` itself.",
|
||||
attr.id,
|
||||
value_type.display(db)
|
||||
),
|
||||
);
|
||||
} else {
|
||||
let diagnostic = match value_type {
|
||||
Type::ModuleLiteral(module) => builder.into_diagnostic(format_args!(
|
||||
"Module `{}` has no member `{}`",
|
||||
module.module(db).name(db),
|
||||
&attr.id
|
||||
)),
|
||||
Type::ClassLiteral(class) => builder.into_diagnostic(format_args!(
|
||||
"Class `{}` has no attribute `{}`",
|
||||
class.name(db),
|
||||
&attr.id
|
||||
)),
|
||||
Type::GenericAlias(alias) => builder.into_diagnostic(format_args!(
|
||||
"Class `{}` has no attribute `{}`",
|
||||
alias.display(db),
|
||||
&attr.id
|
||||
)),
|
||||
Type::FunctionLiteral(function) => builder.into_diagnostic(format_args!(
|
||||
"Function `{}` has no attribute `{}`",
|
||||
function.name(db),
|
||||
&attr.id
|
||||
)),
|
||||
_ => builder.into_diagnostic(format_args!(
|
||||
"Object of type `{}` has no attribute `{}`",
|
||||
value_type.display(db),
|
||||
&attr.id
|
||||
)),
|
||||
};
|
||||
hint_if_stdlib_attribute_exists_on_other_versions(db, diagnostic, &value_type, attr);
|
||||
let resolved_type = fallback_place.unwrap_with_diagnostic(|lookup_err| match lookup_err {
|
||||
LookupError::Undefined(_) => {
|
||||
let fallback = || {
|
||||
TypeAndQualifiers::new(
|
||||
Type::unknown(),
|
||||
TypeOrigin::Inferred,
|
||||
TypeQualifiers::empty(),
|
||||
)
|
||||
};
|
||||
|
||||
if !self.is_reachable(attribute) {
|
||||
return fallback();
|
||||
}
|
||||
|
||||
let bound_on_instance = match value_type {
|
||||
Type::ClassLiteral(class) => {
|
||||
!class.instance_member(db, None, attr).is_undefined()
|
||||
}
|
||||
Type::SubclassOf(subclass_of @ SubclassOfType { .. }) => {
|
||||
match subclass_of.subclass_of() {
|
||||
SubclassOfInner::Class(class) => {
|
||||
!class.instance_member(db, attr).is_undefined()
|
||||
}
|
||||
SubclassOfInner::Dynamic(_) => unreachable!(
|
||||
"Attribute lookup on a dynamic `SubclassOf` type \
|
||||
should always return a bound symbol"
|
||||
),
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
TypeAndQualifiers::new(Type::unknown(), TypeOrigin::Inferred, TypeQualifiers::empty())
|
||||
}
|
||||
LookupError::PossiblyUndefined(type_when_bound) => {
|
||||
report_possibly_missing_attribute(
|
||||
&self.context,
|
||||
attribute,
|
||||
&attr.id,
|
||||
value_type,
|
||||
);
|
||||
let Some(builder) = self.context.report_lint(&UNRESOLVED_ATTRIBUTE, attribute)
|
||||
else {
|
||||
return fallback();
|
||||
};
|
||||
|
||||
type_when_bound
|
||||
if bound_on_instance {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Attribute `{attr_name}` can only be accessed on instances, \
|
||||
not on the class object `{}` itself.",
|
||||
value_type.display(db)
|
||||
));
|
||||
return fallback();
|
||||
}
|
||||
})
|
||||
.inner_type();
|
||||
|
||||
let diagnostic = match value_type {
|
||||
Type::ModuleLiteral(module) => {
|
||||
let module = module.module(db);
|
||||
let module_name = module.name(db);
|
||||
if module.kind(db).is_package()
|
||||
&& let Some(relative_submodule) = ModuleName::new(attr_name)
|
||||
{
|
||||
let mut maybe_submodule_name = module_name.clone();
|
||||
maybe_submodule_name.extend(&relative_submodule);
|
||||
if resolve_module(db, &maybe_submodule_name).is_some() {
|
||||
let mut diag = builder.into_diagnostic(format_args!(
|
||||
"Submodule `{attr_name}` may not be available as an attribute \
|
||||
on module `{module_name}`"
|
||||
));
|
||||
diag.help(format_args!(
|
||||
"Consider explicitly importing `{maybe_submodule_name}`"
|
||||
));
|
||||
return fallback();
|
||||
}
|
||||
}
|
||||
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Module `{module_name}` has no member `{attr_name}`",
|
||||
))
|
||||
}
|
||||
Type::ClassLiteral(class) => builder.into_diagnostic(format_args!(
|
||||
"Class `{}` has no attribute `{attr_name}`",
|
||||
class.name(db),
|
||||
)),
|
||||
Type::GenericAlias(alias) => builder.into_diagnostic(format_args!(
|
||||
"Class `{}` has no attribute `{attr_name}`",
|
||||
alias.display(db),
|
||||
)),
|
||||
Type::FunctionLiteral(function) => builder.into_diagnostic(format_args!(
|
||||
"Function `{}` has no attribute `{attr_name}`",
|
||||
function.name(db),
|
||||
)),
|
||||
_ => builder.into_diagnostic(format_args!(
|
||||
"Object of type `{}` has no attribute `{attr_name}`",
|
||||
value_type.display(db),
|
||||
)),
|
||||
};
|
||||
|
||||
hint_if_stdlib_attribute_exists_on_other_versions(
|
||||
db,
|
||||
diagnostic,
|
||||
&value_type,
|
||||
attr_name,
|
||||
);
|
||||
|
||||
fallback()
|
||||
}
|
||||
LookupError::PossiblyUndefined(type_when_bound) => {
|
||||
report_possibly_missing_attribute(&self.context, attribute, &attr.id, value_type);
|
||||
|
||||
type_when_bound
|
||||
}
|
||||
});
|
||||
|
||||
let resolved_type = resolved_type.inner_type();
|
||||
|
||||
self.check_deprecated(attr, resolved_type);
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ ty_python_semantic = { path = "../crates/ty_python_semantic" }
|
||||
ty_vendored = { path = "../crates/ty_vendored" }
|
||||
|
||||
libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer", default-features = false }
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "a885bb4c4c192741b8a17418fef81a71e33d111e", default-features = false, features = [
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "17bc55d699565e5a1cb1bd42363b905af2f9f3e7", default-features = false, features = [
|
||||
"compact_str",
|
||||
"macros",
|
||||
"salsa_unstable",
|
||||
|
||||
Reference in New Issue
Block a user