Skip to content

Commit 2e46c8d

Browse files
committed
Merge remote-tracking branch 'origin/main' into dcreager/callable-return
* origin/main: [ty] Reachability constraints: minor documentation fixes (#21774) [ty] Fix non-determinism in `ConstraintSet.specialize_constrained` (#21744) [ty] Improve `@override`, `@final` and Liskov checks in cases where there are multiple reachable definitions (#21767) [ty] Extend `invalid-explicit-override` to also cover properties decorated with `@override` that do not override anything (#21756) [ty] Enable LRU collection for parsed module (#21749) [ty] Support typevar-specialized dynamic types in generic type aliases (#21730) Add token based `parenthesized_ranges` implementation (#21738) [ty] Default-specialization of generic type aliases (#21765) [ty] Suppress false positives when `dataclasses.dataclass(...)(cls)` is called imperatively (#21729) [syntax-error] Default type parameter followed by non-default type parameter (#21657)
2 parents d3fd988 + d6e472f commit 2e46c8d

File tree

55 files changed

+2582
-649
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+2582
-649
lines changed

crates/ruff_db/src/parsed.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ use crate::source::source_text;
2121
/// reflected in the changed AST offsets.
2222
/// The other reason is that Ruff's AST doesn't implement `Eq` which Salsa requires
2323
/// for determining if a query result is unchanged.
24-
#[salsa::tracked(returns(ref), no_eq, heap_size=ruff_memory_usage::heap_size)]
24+
///
25+
/// The LRU capacity of 200 was picked without any empirical evidence that it's optimal,
26+
/// instead it's a wild guess that it should be unlikely that incremental changes involve
27+
/// more than 200 modules. Parsed ASTs within the same revision are never evicted by Salsa.
28+
#[salsa::tracked(returns(ref), no_eq, heap_size=ruff_memory_usage::heap_size, lru=200)]
2529
pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule {
2630
let _span = tracing::trace_span!("parsed_module", ?file).entered();
2731

@@ -92,14 +96,9 @@ impl ParsedModule {
9296
self.inner.store(None);
9397
}
9498

95-
/// Returns the pointer address of this [`ParsedModule`].
96-
///
97-
/// The pointer uniquely identifies the module within the current Salsa revision,
98-
/// regardless of whether particular [`ParsedModuleRef`] instances are garbage collected.
99-
pub fn addr(&self) -> usize {
100-
// Note that the outer `Arc` in `inner` is stable across garbage collection, while the inner
101-
// `Arc` within the `ArcSwap` may change.
102-
Arc::as_ptr(&self.inner).addr()
99+
/// Returns the file to which this module belongs.
100+
pub fn file(&self) -> File {
101+
self.file
103102
}
104103
}
105104

crates/ruff_linter/src/checkers/ast/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,7 @@ impl SemanticSyntaxContext for Checker<'_> {
747747
| SemanticSyntaxErrorKind::LoadBeforeNonlocalDeclaration { .. }
748748
| SemanticSyntaxErrorKind::NonlocalAndGlobal(_)
749749
| SemanticSyntaxErrorKind::AnnotatedGlobal(_)
750+
| SemanticSyntaxErrorKind::TypeParameterDefaultOrder(_)
750751
| SemanticSyntaxErrorKind::AnnotatedNonlocal(_) => {
751752
self.semantic_errors.borrow_mut().push(error);
752753
}

crates/ruff_memory_usage/src/lib.rs

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,46 @@
1-
use std::sync::{LazyLock, Mutex};
1+
use std::cell::RefCell;
22

33
use get_size2::{GetSize, StandardTracker};
44
use ordermap::{OrderMap, OrderSet};
55

6+
thread_local! {
7+
pub static TRACKER: RefCell<Option<StandardTracker>>= const { RefCell::new(None) };
8+
}
9+
10+
struct TrackerGuard(Option<StandardTracker>);
11+
12+
impl Drop for TrackerGuard {
13+
fn drop(&mut self) {
14+
TRACKER.set(self.0.take());
15+
}
16+
}
17+
18+
pub fn attach_tracker<R>(tracker: StandardTracker, f: impl FnOnce() -> R) -> R {
19+
let prev = TRACKER.replace(Some(tracker));
20+
let _guard = TrackerGuard(prev);
21+
f()
22+
}
23+
24+
fn with_tracker<F, R>(f: F) -> R
25+
where
26+
F: FnOnce(Option<&mut StandardTracker>) -> R,
27+
{
28+
TRACKER.with(|tracker| {
29+
let mut tracker = tracker.borrow_mut();
30+
f(tracker.as_mut())
31+
})
32+
}
33+
634
/// Returns the memory usage of the provided object, using a global tracker to avoid
735
/// double-counting shared objects.
836
pub fn heap_size<T: GetSize>(value: &T) -> usize {
9-
static TRACKER: LazyLock<Mutex<StandardTracker>> =
10-
LazyLock::new(|| Mutex::new(StandardTracker::new()));
11-
12-
value
13-
.get_heap_size_with_tracker(&mut *TRACKER.lock().unwrap())
14-
.0
37+
with_tracker(|tracker| {
38+
if let Some(tracker) = tracker {
39+
value.get_heap_size_with_tracker(tracker).0
40+
} else {
41+
value.get_heap_size()
42+
}
43+
})
1544
}
1645

1746
/// An implementation of [`GetSize::get_heap_size`] for [`OrderSet`].

crates/ruff_python_ast/src/parenthesize.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ use crate::ExprRef;
1111
/// Note that without a parent the range can be inaccurate, e.g. `f(a)` we falsely return a set of
1212
/// parentheses around `a` even if the parentheses actually belong to `f`. That is why you should
1313
/// generally prefer [`parenthesized_range`].
14+
///
15+
/// Prefer [`crate::token::parentheses_iterator`] if you have access to [`crate::token::Tokens`].
1416
pub fn parentheses_iterator<'a>(
1517
expr: ExprRef<'a>,
1618
parent: Option<AnyNodeRef>,
@@ -57,6 +59,8 @@ pub fn parentheses_iterator<'a>(
5759

5860
/// Returns the [`TextRange`] of a given expression including parentheses, if the expression is
5961
/// parenthesized; or `None`, if the expression is not parenthesized.
62+
///
63+
/// Prefer [`crate::token::parenthesized_range`] if you have access to [`crate::token::Tokens`].
6064
pub fn parenthesized_range(
6165
expr: ExprRef,
6266
parent: AnyNodeRef,

crates/ruff_python_ast/src/token.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ use crate::str_prefix::{
1616
use crate::{AnyStringFlags, BoolOp, Operator, StringFlags, UnaryOp};
1717
use ruff_text_size::{Ranged, TextRange};
1818

19+
mod parentheses;
1920
mod tokens;
2021

22+
pub use parentheses::{parentheses_iterator, parenthesized_range};
2123
pub use tokens::{TokenAt, TokenIterWithContext, Tokens};
2224

2325
#[derive(Clone, Copy, PartialEq, Eq)]
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use ruff_text_size::{Ranged, TextLen, TextRange};
2+
3+
use super::{TokenKind, Tokens};
4+
use crate::{AnyNodeRef, ExprRef};
5+
6+
/// Returns an iterator over the ranges of the optional parentheses surrounding an expression.
7+
///
8+
/// E.g. for `((f()))` with `f()` as expression, the iterator returns the ranges (1, 6) and (0, 7).
9+
///
10+
/// Note that without a parent the range can be inaccurate, e.g. `f(a)` we falsely return a set of
11+
/// parentheses around `a` even if the parentheses actually belong to `f`. That is why you should
12+
/// generally prefer [`parenthesized_range`].
13+
pub fn parentheses_iterator<'a>(
14+
expr: ExprRef<'a>,
15+
parent: Option<AnyNodeRef>,
16+
tokens: &'a Tokens,
17+
) -> impl Iterator<Item = TextRange> + 'a {
18+
let after_tokens = if let Some(parent) = parent {
19+
// If the parent is a node that brings its own parentheses, exclude the closing parenthesis
20+
// from our search range. Otherwise, we risk matching on calls, like `func(x)`, for which
21+
// the open and close parentheses are part of the `Arguments` node.
22+
let exclusive_parent_end = if parent.is_arguments() {
23+
parent.end() - ")".text_len()
24+
} else {
25+
parent.end()
26+
};
27+
28+
tokens.in_range(TextRange::new(expr.end(), exclusive_parent_end))
29+
} else {
30+
tokens.after(expr.end())
31+
};
32+
33+
let right_parens = after_tokens
34+
.iter()
35+
.filter(|token| !token.kind().is_trivia())
36+
.take_while(move |token| token.kind() == TokenKind::Rpar);
37+
38+
let left_parens = tokens
39+
.before(expr.start())
40+
.iter()
41+
.rev()
42+
.filter(|token| !token.kind().is_trivia())
43+
.take_while(|token| token.kind() == TokenKind::Lpar);
44+
45+
right_parens
46+
.zip(left_parens)
47+
.map(|(right, left)| TextRange::new(left.start(), right.end()))
48+
}
49+
50+
/// Returns the [`TextRange`] of a given expression including parentheses, if the expression is
51+
/// parenthesized; or `None`, if the expression is not parenthesized.
52+
pub fn parenthesized_range(
53+
expr: ExprRef,
54+
parent: AnyNodeRef,
55+
tokens: &Tokens,
56+
) -> Option<TextRange> {
57+
parentheses_iterator(expr, Some(parent), tokens).last()
58+
}
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
//! Tests for [`ruff_python_ast::tokens::parentheses_iterator`] and
2+
//! [`ruff_python_ast::tokens::parenthesized_range`].
3+
4+
use ruff_python_ast::{
5+
self as ast, Expr,
6+
token::{parentheses_iterator, parenthesized_range},
7+
};
8+
use ruff_python_parser::parse_module;
9+
10+
#[test]
11+
fn test_no_parentheses() {
12+
let source = "x = 2 + 2";
13+
let parsed = parse_module(source).expect("should parse valid python");
14+
let tokens = parsed.tokens();
15+
let module = parsed.syntax();
16+
17+
let stmt = module.body.first().expect("module should have a statement");
18+
let ast::Stmt::Assign(assign) = stmt else {
19+
panic!("expected `Assign` statement, got {stmt:?}");
20+
};
21+
22+
let result = parenthesized_range(assign.value.as_ref().into(), stmt.into(), tokens);
23+
assert_eq!(result, None);
24+
}
25+
26+
#[test]
27+
fn test_single_parentheses() {
28+
let source = "x = (2 + 2)";
29+
let parsed = parse_module(source).expect("should parse valid python");
30+
let tokens = parsed.tokens();
31+
let module = parsed.syntax();
32+
33+
let stmt = module.body.first().expect("module should have a statement");
34+
let ast::Stmt::Assign(assign) = stmt else {
35+
panic!("expected `Assign` statement, got {stmt:?}");
36+
};
37+
38+
let result = parenthesized_range(assign.value.as_ref().into(), stmt.into(), tokens);
39+
let range = result.expect("should find parentheses");
40+
assert_eq!(&source[range], "(2 + 2)");
41+
}
42+
43+
#[test]
44+
fn test_double_parentheses() {
45+
let source = "x = ((2 + 2))";
46+
let parsed = parse_module(source).expect("should parse valid python");
47+
let tokens = parsed.tokens();
48+
let module = parsed.syntax();
49+
50+
let stmt = module.body.first().expect("module should have a statement");
51+
let ast::Stmt::Assign(assign) = stmt else {
52+
panic!("expected `Assign` statement, got {stmt:?}");
53+
};
54+
55+
let result = parenthesized_range(assign.value.as_ref().into(), stmt.into(), tokens);
56+
let range = result.expect("should find parentheses");
57+
assert_eq!(&source[range], "((2 + 2))");
58+
}
59+
60+
#[test]
61+
fn test_parentheses_with_whitespace() {
62+
let source = "x = ( 2 + 2 )";
63+
let parsed = parse_module(source).expect("should parse valid python");
64+
let tokens = parsed.tokens();
65+
let module = parsed.syntax();
66+
67+
let stmt = module.body.first().expect("module should have a statement");
68+
let ast::Stmt::Assign(assign) = stmt else {
69+
panic!("expected `Assign` statement, got {stmt:?}");
70+
};
71+
72+
let result = parenthesized_range(assign.value.as_ref().into(), stmt.into(), tokens);
73+
let range = result.expect("should find parentheses");
74+
assert_eq!(&source[range], "( 2 + 2 )");
75+
}
76+
77+
#[test]
78+
fn test_parentheses_with_comments() {
79+
let source = "x = ( # comment\n 2 + 2\n)";
80+
let parsed = parse_module(source).expect("should parse valid python");
81+
let tokens = parsed.tokens();
82+
let module = parsed.syntax();
83+
84+
let stmt = module.body.first().expect("module should have a statement");
85+
let ast::Stmt::Assign(assign) = stmt else {
86+
panic!("expected `Assign` statement, got {stmt:?}");
87+
};
88+
89+
let result = parenthesized_range(assign.value.as_ref().into(), stmt.into(), tokens);
90+
let range = result.expect("should find parentheses");
91+
assert_eq!(&source[range], "( # comment\n 2 + 2\n)");
92+
}
93+
94+
#[test]
95+
fn test_parenthesized_range_multiple() {
96+
let source = "x = (((2 + 2)))";
97+
let parsed = parse_module(source).expect("should parse valid python");
98+
let tokens = parsed.tokens();
99+
let module = parsed.syntax();
100+
101+
let stmt = module.body.first().expect("module should have a statement");
102+
let ast::Stmt::Assign(assign) = stmt else {
103+
panic!("expected `Assign` statement, got {stmt:?}");
104+
};
105+
106+
let result = parenthesized_range(assign.value.as_ref().into(), stmt.into(), tokens);
107+
let range = result.expect("should find parentheses");
108+
assert_eq!(&source[range], "(((2 + 2)))");
109+
}
110+
111+
#[test]
112+
fn test_parentheses_iterator_multiple() {
113+
let source = "x = (((2 + 2)))";
114+
let parsed = parse_module(source).expect("should parse valid python");
115+
let tokens = parsed.tokens();
116+
let module = parsed.syntax();
117+
118+
let stmt = module.body.first().expect("module should have a statement");
119+
let ast::Stmt::Assign(assign) = stmt else {
120+
panic!("expected `Assign` statement, got {stmt:?}");
121+
};
122+
123+
let ranges: Vec<_> =
124+
parentheses_iterator(assign.value.as_ref().into(), Some(stmt.into()), tokens).collect();
125+
assert_eq!(ranges.len(), 3);
126+
assert_eq!(&source[ranges[0]], "(2 + 2)");
127+
assert_eq!(&source[ranges[1]], "((2 + 2))");
128+
assert_eq!(&source[ranges[2]], "(((2 + 2)))");
129+
}
130+
131+
#[test]
132+
fn test_call_arguments_not_counted() {
133+
let source = "f(x)";
134+
let parsed = parse_module(source).expect("should parse valid python");
135+
let tokens = parsed.tokens();
136+
let module = parsed.syntax();
137+
138+
let stmt = module.body.first().expect("module should have a statement");
139+
let ast::Stmt::Expr(expr_stmt) = stmt else {
140+
panic!("expected `Expr` statement, got {stmt:?}");
141+
};
142+
143+
let Expr::Call(call) = expr_stmt.value.as_ref() else {
144+
panic!("expected Call expression, got {:?}", expr_stmt.value);
145+
};
146+
147+
let arg = call
148+
.arguments
149+
.args
150+
.first()
151+
.expect("call should have an argument");
152+
let result = parenthesized_range(arg.into(), (&call.arguments).into(), tokens);
153+
// The parentheses belong to the call, not the argument
154+
assert_eq!(result, None);
155+
}
156+
157+
#[test]
158+
fn test_call_with_parenthesized_argument() {
159+
let source = "f((x))";
160+
let parsed = parse_module(source).expect("should parse valid python");
161+
let tokens = parsed.tokens();
162+
let module = parsed.syntax();
163+
164+
let stmt = module.body.first().expect("module should have a statement");
165+
let ast::Stmt::Expr(expr_stmt) = stmt else {
166+
panic!("expected Expr statement, got {stmt:?}");
167+
};
168+
169+
let Expr::Call(call) = expr_stmt.value.as_ref() else {
170+
panic!("expected `Call` expression, got {:?}", expr_stmt.value);
171+
};
172+
173+
let arg = call
174+
.arguments
175+
.args
176+
.first()
177+
.expect("call should have an argument");
178+
let result = parenthesized_range(arg.into(), (&call.arguments).into(), tokens);
179+
180+
let range = result.expect("should find parentheses around argument");
181+
assert_eq!(&source[range], "(x)");
182+
}
183+
184+
#[test]
185+
fn test_multiline_with_parentheses() {
186+
let source = "x = (\n 2 + 2 + 2\n)";
187+
let parsed = parse_module(source).expect("should parse valid python");
188+
let tokens = parsed.tokens();
189+
let module = parsed.syntax();
190+
191+
let stmt = module.body.first().expect("module should have a statement");
192+
let ast::Stmt::Assign(assign) = stmt else {
193+
panic!("expected `Assign` statement, got {stmt:?}");
194+
};
195+
196+
let result = parenthesized_range(assign.value.as_ref().into(), stmt.into(), tokens);
197+
let range = result.expect("should find parentheses");
198+
assert_eq!(&source[range], "(\n 2 + 2 + 2\n)");
199+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class C[T = int, U]: ...
2+
class C[T1, T2 = int, T3, T4]: ...
3+
type Alias[T = int, U] = ...

0 commit comments

Comments
 (0)