diff --git a/crates/red_knot_python_semantic/resources/mdtest/function/return_type.md b/crates/red_knot_python_semantic/resources/mdtest/function/return_type.md index 168cb77a94..9afeda9f75 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/function/return_type.md +++ b/crates/red_knot_python_semantic/resources/mdtest/function/return_type.md @@ -310,7 +310,7 @@ def f(cond: bool) -> str: return "hello" if cond else NotImplemented def f(cond: bool) -> int: - # error: [invalid-return-type] "Object of type `Literal["hello"]` is not assignable to return type `int`" + # error: [invalid-return-type] "Return type does not match returned value: Expected `int`, found `Literal["hello"]`" return "hello" if cond else NotImplemented ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/generics/functions.md b/crates/red_knot_python_semantic/resources/mdtest/generics/functions.md index 32b4f1971b..935fc47fba 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/generics/functions.md +++ b/crates/red_knot_python_semantic/resources/mdtest/generics/functions.md @@ -107,7 +107,7 @@ def good_return[T: int](x: T) -> T: return x def bad_return[T: int](x: T) -> T: - # error: [invalid-return-type] "Object of type `int` is not assignable to return type `T`" + # error: [invalid-return-type] "Return type does not match returned value: Expected `T`, found `int`" return x + 1 ``` @@ -120,7 +120,7 @@ def different_types[T, S](cond: bool, t: T, s: S) -> T: if cond: return t else: - # error: [invalid-return-type] "Object of type `S` is not assignable to return type `T`" + # error: [invalid-return-type] "Return type does not match returned value: Expected `T`, found `S`" return s def same_types[T](cond: bool, t1: T, t2: T) -> T: diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_return_type.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_return_type.snap index 93309c4fc8..f21ec59ed7 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_return_type.snap +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_conditional_return_type.snap @@ -31,67 +31,56 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/function/return_ty # Diagnostics ``` -error: lint:invalid-return-type - --> /src/mdtest_snippet.py:6:16 - | -4 | else: -5 | # error: [invalid-return-type] -6 | return 1 - | ^ Object of type `Literal[1]` is not assignable to return type `str` -7 | -8 | def f(cond: bool) -> str: - | -info +error: lint:invalid-return-type: Return type does not match returned value --> /src/mdtest_snippet.py:1:22 | 1 | def f(cond: bool) -> str: - | --- Return type is declared here as `str` + | --- Expected `str` because of return type 2 | if cond: 3 | return "a" +4 | else: +5 | # error: [invalid-return-type] +6 | return 1 + | ^ Expected `str`, found `Literal[1]` +7 | +8 | def f(cond: bool) -> str: | ``` ``` -error: lint:invalid-return-type - --> /src/mdtest_snippet.py:11:16 - | - 9 | if cond: -10 | # error: [invalid-return-type] -11 | return 1 - | ^ Object of type `Literal[1]` is not assignable to return type `str` -12 | else: -13 | # error: [invalid-return-type] - | -info +error: lint:invalid-return-type: Return type does not match returned value --> /src/mdtest_snippet.py:8:22 | 6 | return 1 7 | 8 | def f(cond: bool) -> str: - | --- Return type is declared here as `str` + | --- Expected `str` because of return type 9 | if cond: 10 | # error: [invalid-return-type] +11 | return 1 + | ^ Expected `str`, found `Literal[1]` +12 | else: +13 | # error: [invalid-return-type] | ``` ``` -error: lint:invalid-return-type +error: lint:invalid-return-type: Return type does not match returned value --> /src/mdtest_snippet.py:14:16 | 12 | else: 13 | # error: [invalid-return-type] 14 | return 2 - | ^ Object of type `Literal[2]` is not assignable to return type `str` + | ^ Expected `str`, found `Literal[2]` | -info - --> /src/mdtest_snippet.py:8:22 + ::: /src/mdtest_snippet.py:8:22 | 6 | return 1 7 | 8 | def f(cond: bool) -> str: - | --- Return type is declared here as `str` + | --- Expected `str` because of return type 9 | if cond: 10 | # error: [invalid-return-type] | diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_return_type.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_return_type.snap index 9355d2e6d0..e88e7a29a7 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_return_type.snap +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_return_type.snap @@ -40,23 +40,17 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/function/return_ty # Diagnostics ``` -error: lint:invalid-return-type - --> /src/mdtest_snippet.py:4:16 - | -2 | if False: -3 | # error: [invalid-return-type] -4 | return 1 - | ^ Object of type `Literal[1]` is not assignable to return type `None` -5 | -6 | # error: [invalid-return-type] - | -info +error: lint:invalid-return-type: Return type does not match returned value --> /src/mdtest_snippet.py:1:12 | 1 | def f() -> None: - | ---- Return type is declared here as `None` + | ---- Expected `None` because of return type 2 | if False: 3 | # error: [invalid-return-type] +4 | return 1 + | ^ Expected `None`, found `Literal[1]` +5 | +6 | # error: [invalid-return-type] | ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap index 561dee7bbe..b54a9b6abb 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap @@ -47,49 +47,35 @@ error: lint:invalid-return-type ``` ``` -error: lint:invalid-return-type - --> /src/mdtest_snippet.py:7:12 - | -5 | def f() -> str: -6 | # error: [invalid-return-type] -7 | return 1 - | ^ Object of type `Literal[1]` is not assignable to return type `str` -8 | -9 | def f() -> int: - | -info +error: lint:invalid-return-type: Return type does not match returned value --> /src/mdtest_snippet.py:5:12 | 3 | 1 4 | 5 | def f() -> str: - | --- Return type is declared here as `str` + | --- Expected `str` because of return type 6 | # error: [invalid-return-type] 7 | return 1 + | ^ Expected `str`, found `Literal[1]` +8 | +9 | def f() -> int: | ``` ``` -error: lint:invalid-return-type - --> /src/mdtest_snippet.py:11:5 - | - 9 | def f() -> int: -10 | # error: [invalid-return-type] -11 | return - | ^^^^^^ Object of type `None` is not assignable to return type `int` -12 | -13 | from typing import TypeVar - | -info +error: lint:invalid-return-type: Return type does not match returned value --> /src/mdtest_snippet.py:9:12 | 7 | return 1 8 | 9 | def f() -> int: - | --- Return type is declared here as `int` + | --- Expected `int` because of return type 10 | # error: [invalid-return-type] 11 | return + | ^^^^^^ Expected `int`, found `None` +12 | +13 | from typing import TypeVar | ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_in_stub_file.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_in_stub_file.snap index 7d05ec9db6..3514f85b6c 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_in_stub_file.snap +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_in_stub_file.snap @@ -30,23 +30,16 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/function/return_ty # Diagnostics ``` -error: lint:invalid-return-type - --> /src/mdtest_snippet.pyi:3:12 - | -1 | def f() -> int: -2 | # error: [invalid-return-type] -3 | return ... - | ^^^ Object of type `ellipsis` is not assignable to return type `int` -4 | -5 | # error: [invalid-return-type] - | -info +error: lint:invalid-return-type: Return type does not match returned value --> /src/mdtest_snippet.pyi:1:12 | 1 | def f() -> int: - | --- Return type is declared here as `int` + | --- Expected `int` because of return type 2 | # error: [invalid-return-type] 3 | return ... + | ^^^ Expected `int`, found `ellipsis` +4 | +5 | # error: [invalid-return-type] | ``` diff --git a/crates/red_knot_python_semantic/src/types/context.rs b/crates/red_knot_python_semantic/src/types/context.rs index 764554c84d..069ababf8c 100644 --- a/crates/red_knot_python_semantic/src/types/context.rs +++ b/crates/red_knot_python_semantic/src/types/context.rs @@ -2,9 +2,7 @@ use std::fmt; use drop_bomb::DebugDropBomb; use ruff_db::{ - diagnostic::{ - Annotation, Diagnostic, DiagnosticId, OldSecondaryDiagnosticMessage, Severity, Span, - }, + diagnostic::{Annotation, Diagnostic, DiagnosticId, Severity, Span}, files::File, }; use ruff_text_size::Ranged; @@ -83,28 +81,12 @@ impl<'db> InferContext<'db> { message: fmt::Arguments, ) where T: Ranged, - { - self.report_lint_with_secondary_messages(lint, ranged, message, &[]); - } - - /// Reports a lint located at `ranged`. - pub(super) fn report_lint_with_secondary_messages( - &self, - lint: &'static LintMetadata, - ranged: T, - message: fmt::Arguments, - secondary_messages: &[OldSecondaryDiagnosticMessage], - ) where - T: Ranged, { let Some(builder) = self.lint(lint) else { return; }; let mut reporter = builder.build(""); let diag = reporter.diagnostic(); - for secondary_msg in secondary_messages { - diag.sub(secondary_msg.to_sub_diagnostic()); - } let span = Span::from(self.file).with_range(ranged.range()); diag.annotate(Annotation::primary(span).message(message)); } diff --git a/crates/red_knot_python_semantic/src/types/diagnostic.rs b/crates/red_knot_python_semantic/src/types/diagnostic.rs index 154aa3dd71..23f443597f 100644 --- a/crates/red_knot_python_semantic/src/types/diagnostic.rs +++ b/crates/red_knot_python_semantic/src/types/diagnostic.rs @@ -8,7 +8,7 @@ use crate::types::string_annotation::{ RAW_STRING_TYPE_ANNOTATION, }; use crate::types::{KnownInstanceType, Type}; -use ruff_db::diagnostic::{Diagnostic, OldSecondaryDiagnosticMessage, Span}; +use ruff_db::diagnostic::{Annotation, Diagnostic, Span}; use ruff_python_ast::{self as ast, AnyNodeRef}; use ruff_text_size::Ranged; use rustc_hash::FxHashSet; @@ -1085,22 +1085,26 @@ pub(super) fn report_invalid_return_type( expected_ty: Type, actual_ty: Type, ) { + let Some(builder) = context.lint(&INVALID_RETURN_TYPE) else { + return; + }; + + let object_span = Span::from(context.file()).with_range(object_range.range()); let return_type_span = Span::from(context.file()).with_range(return_type_range.range()); - context.report_lint_with_secondary_messages( - &INVALID_RETURN_TYPE, - object_range, - format_args!( - "Object of type `{}` is not assignable to return type `{}`", - actual_ty.display(context.db()), - expected_ty.display(context.db()) - ), - &[OldSecondaryDiagnosticMessage::new( - return_type_span, - format!( - "Return type is declared here as `{}`", - expected_ty.display(context.db()) - ), - )], + + let mut reporter = builder.build("Return type does not match returned value"); + + let diag = reporter.diagnostic(); + diag.annotate(Annotation::primary(object_span).message(format_args!( + "Expected `{expected_ty}`, found `{actual_ty}`", + expected_ty = expected_ty.display(context.db()), + actual_ty = actual_ty.display(context.db()), + ))); + diag.annotate( + Annotation::secondary(return_type_span).message(format_args!( + "Expected `{expected_ty}` because of return type", + expected_ty = expected_ty.display(context.db()), + )), ); } diff --git a/crates/ruff_db/src/diagnostic/mod.rs b/crates/ruff_db/src/diagnostic/mod.rs index 2e9ac9ef34..368a7d1646 100644 --- a/crates/ruff_db/src/diagnostic/mod.rs +++ b/crates/ruff_db/src/diagnostic/mod.rs @@ -6,15 +6,10 @@ use ruff_annotate_snippets::Level as AnnotateLevel; use ruff_text_size::TextRange; pub use self::render::DisplayDiagnostic; -pub use crate::diagnostic::old::OldSecondaryDiagnosticMessage; use crate::files::File; use crate::Db; use self::render::FileResolver; - -// This module should not be exported. We are planning to migrate off -// the APIs in this module. -mod old; mod render; /// A collection of information that can be rendered into a diagnostic. diff --git a/crates/ruff_db/src/diagnostic/old.rs b/crates/ruff_db/src/diagnostic/old.rs deleted file mode 100644 index 77552a9e46..0000000000 --- a/crates/ruff_db/src/diagnostic/old.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::diagnostic::{Annotation, Severity, Span, SubDiagnostic}; - -/// A single secondary message assigned to a `Diagnostic`. -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct OldSecondaryDiagnosticMessage { - span: Span, - message: String, -} - -impl OldSecondaryDiagnosticMessage { - pub fn new(span: Span, message: impl Into) -> OldSecondaryDiagnosticMessage { - OldSecondaryDiagnosticMessage { - span, - message: message.into(), - } - } - - pub fn to_sub_diagnostic(&self) -> SubDiagnostic { - let mut sub = SubDiagnostic::new(Severity::Info, ""); - sub.annotate(Annotation::secondary(self.span.clone()).message(&self.message)); - sub - } -}