Skip to content

Commit 5475da6

Browse files
committed
more reliable subdiagnostic
1 parent 9be14c5 commit 5475da6

File tree

4 files changed

+89
-19
lines changed

4 files changed

+89
-19
lines changed

crates/ty_python_semantic/resources/mdtest/liskov.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,4 +264,10 @@ class A:
264264

265265
class B(A):
266266
x = foo.x # error: [invalid-method-override]
267+
268+
class C:
269+
x = foo.x
270+
271+
class D(C):
272+
def x(self, y: int): ... # error: [invalid-method-override]
267273
```

crates/ty_python_semantic/resources/mdtest/snapshots/liskov.md_-_The_Liskov_Substitut…_-_Edge_case___function_…_(c8756a54d1cb8499).snap

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,19 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/liskov.md
1818
## bar.pyi
1919

2020
```
21-
1 | import foo
22-
2 |
23-
3 | class A:
24-
4 | def x(self, y: int): ...
25-
5 |
26-
6 | class B(A):
27-
7 | x = foo.x # error: [invalid-method-override]
21+
1 | import foo
22+
2 |
23+
3 | class A:
24+
4 | def x(self, y: int): ...
25+
5 |
26+
6 | class B(A):
27+
7 | x = foo.x # error: [invalid-method-override]
28+
8 |
29+
9 | class C:
30+
10 | x = foo.x
31+
11 |
32+
12 | class D(C):
33+
13 | def x(self, y: int): ... # error: [invalid-method-override]
2834
```
2935

3036
# Diagnostics
@@ -40,8 +46,27 @@ error[invalid-method-override]: Invalid override of method `x`
4046
6 | class B(A):
4147
7 | x = foo.x # error: [invalid-method-override]
4248
| ^ Definition is incompatible with `A.x`
49+
8 |
50+
9 | class C:
4351
|
4452
info: This violates the Liskov Substitution Principle
4553
info: rule `invalid-method-override` is enabled by default
4654

4755
```
56+
57+
```
58+
error[invalid-method-override]: Invalid override of method `x`
59+
--> src/bar.pyi:10:5
60+
|
61+
9 | class C:
62+
10 | x = foo.x
63+
| --------- `C.x` defined here
64+
11 |
65+
12 | class D(C):
66+
13 | def x(self, y: int): ... # error: [invalid-method-override]
67+
| ^^^^^^^^^^^^^^^ Definition is incompatible with `C.x`
68+
|
69+
info: This violates the Liskov Substitution Principle
70+
info: rule `invalid-method-override` is enabled by default
71+
72+
```

crates/ty_python_semantic/src/types.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,6 +1082,13 @@ impl<'db> Type<'db> {
10821082
}
10831083
}
10841084

1085+
pub(crate) const fn as_bound_method(self) -> Option<BoundMethodType<'db>> {
1086+
match self {
1087+
Type::BoundMethod(bound_method) => Some(bound_method),
1088+
_ => None,
1089+
}
1090+
}
1091+
10851092
#[track_caller]
10861093
pub(crate) const fn expect_class_literal(self) -> ClassLiteral<'db> {
10871094
self.as_class_literal()

crates/ty_python_semantic/src/types/diagnostic.rs

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::diagnostic::format_enumeration;
1010
use crate::lint::{Level, LintRegistryBuilder, LintStatus};
1111
use crate::semantic_index::definition::{Definition, DefinitionKind};
1212
use crate::semantic_index::place::{PlaceTable, ScopedPlaceId};
13-
use crate::semantic_index::{global_scope, place_table};
13+
use crate::semantic_index::{global_scope, place_table, use_def_map};
1414
use crate::suppression::FileSuppressionId;
1515
use crate::types::KnownInstanceType;
1616
use crate::types::call::CallError;
@@ -32,6 +32,7 @@ use crate::{
3232
};
3333
use itertools::Itertools;
3434
use ruff_db::diagnostic::{Annotation, Diagnostic, Span, SubDiagnostic, SubDiagnosticSeverity};
35+
use ruff_db::parsed::parsed_module;
3536
use ruff_python_ast::name::Name;
3637
use ruff_python_ast::{self as ast, AnyNodeRef, Identifier};
3738
use ruff_text_size::{Ranged, TextRange};
@@ -3293,10 +3294,18 @@ pub(super) fn report_invalid_method_override<'db>(
32933294

32943295
let supercls_name = supercls.name(db);
32953296

3296-
let overridden_method = if class.name(db) != supercls_name {
3297-
format!("{}.{}", supercls_name, member.name)
3297+
let overridden_method = if class.name(db) == supercls_name {
3298+
format!(
3299+
"{class}.{member}",
3300+
class = supercls.class_literal(db).0.qualified_name(db),
3301+
member = &member.name
3302+
)
32983303
} else {
3299-
supercls.class_literal(db).0.qualified_name(db).to_string()
3304+
format!(
3305+
"{class}.{member}",
3306+
class = supercls.name(db),
3307+
member = &member.name
3308+
)
33003309
};
33013310

33023311
diagnostic.set_primary_message(format_args!(
@@ -3305,15 +3314,38 @@ pub(super) fn report_invalid_method_override<'db>(
33053314

33063315
diagnostic.info("This violates the Liskov Substitution Principle");
33073316

3308-
if let Type::BoundMethod(method_on_supercls) = type_on_supercls
3309-
&& let Some(spans) = method_on_supercls
3310-
.function(db)
3311-
.literal(db)
3312-
.last_definition(db)
3313-
.spans(db)
3314-
{
3317+
let secondary_span = type_on_supercls
3318+
.as_bound_method()
3319+
.and_then(|method_on_supercls| {
3320+
let supercls_scope = supercls.class_literal(db).0.body_scope(db);
3321+
3322+
if method_on_supercls.function(db).definition(db).scope(db) == supercls_scope {
3323+
method_on_supercls
3324+
.function(db)
3325+
.literal(db)
3326+
.last_definition(db)
3327+
.spans(db)
3328+
.map(|spans| spans.signature)
3329+
} else {
3330+
place_table(db, supercls_scope)
3331+
.symbol_id(&member.name)
3332+
.and_then(|symbol| {
3333+
use_def_map(db, supercls_scope)
3334+
.end_of_scope_bindings(ScopedPlaceId::Symbol(symbol))
3335+
.next()
3336+
})
3337+
.and_then(|binding| binding.binding.definition())
3338+
.map(|definition| {
3339+
definition
3340+
.full_range(db, &parsed_module(db, supercls_scope.file(db)).load(db))
3341+
})
3342+
.map(Span::from)
3343+
}
3344+
});
3345+
3346+
if let Some(secondary_span) = secondary_span {
33153347
diagnostic.annotate(
3316-
Annotation::secondary(spans.signature)
3348+
Annotation::secondary(secondary_span)
33173349
.message(format_args!("`{overridden_method}` defined here")),
33183350
);
33193351
}

0 commit comments

Comments
 (0)