diff --git a/crates/ty_python_semantic/resources/mdtest/metaclass.md b/crates/ty_python_semantic/resources/mdtest/metaclass.md index 2a05f779f1061..eeba85e4b3bd6 100644 --- a/crates/ty_python_semantic/resources/mdtest/metaclass.md +++ b/crates/ty_python_semantic/resources/mdtest/metaclass.md @@ -221,6 +221,18 @@ reveal_type(D) # revealed: reveal_type(D.__class__) # revealed: ``` +## Diagnostic range + + + +```py +def _(n: int): + # error: [invalid-metaclass] + class B(metaclass=n): + x = 1 + y = 2 +``` + ## Cyclic Retrieving the metaclass of a cyclically defined class should not cause an infinite loop. diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/metaclass.md_-_Diagnostic_range_(4940b37ce546ecbf).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/metaclass.md_-_Diagnostic_range_(4940b37ce546ecbf).snap new file mode 100644 index 0000000000000..ab71d97a08342 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/metaclass.md_-_Diagnostic_range_(4940b37ce546ecbf).snap @@ -0,0 +1,38 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- + +--- +mdtest name: metaclass.md - Diagnostic range +mdtest path: crates/ty_python_semantic/resources/mdtest/metaclass.md +--- + +# Python source files + +## mdtest_snippet.py + +``` +1 | def _(n: int): +2 | # error: [invalid-metaclass] +3 | class B(metaclass=n): +4 | x = 1 +5 | y = 2 +``` + +# Diagnostics + +``` +error[invalid-metaclass]: Metaclass type `int` is not callable + --> src/mdtest_snippet.py:3:13 + | +1 | def _(n: int): +2 | # error: [invalid-metaclass] +3 | class B(metaclass=n): + | ^^^^^^^^^^^ +4 | x = 1 +5 | y = 2 + | +info: rule `invalid-metaclass` is enabled by default + +``` diff --git a/crates/ty_python_semantic/src/types/infer/deferred/static_class.rs b/crates/ty_python_semantic/src/types/infer/deferred/static_class.rs index 8f29e86b9653e..fe45e34c9cb85 100644 --- a/crates/ty_python_semantic/src/types/infer/deferred/static_class.rs +++ b/crates/ty_python_semantic/src/types/infer/deferred/static_class.rs @@ -572,6 +572,12 @@ pub(crate) fn check_static_class_definitions<'db>( // Check that the class's metaclass can be determined without error. if let Err(metaclass_error) = class.try_metaclass(db) { + let invalid_metaclass_range = class_node + .arguments + .as_ref() + .and_then(|arguments| arguments.find_keyword("metaclass")) + .map(Ranged::range) + .unwrap_or_else(|| class.header_range(db)); match metaclass_error.reason() { MetaclassErrorKind::Cycle => { if let Some(builder) = context.report_lint(&CYCLIC_CLASS_DEFINITION, class_node) { @@ -580,12 +586,16 @@ pub(crate) fn check_static_class_definitions<'db>( } } MetaclassErrorKind::GenericMetaclass => { - if let Some(builder) = context.report_lint(&INVALID_METACLASS, class_node) { + if let Some(builder) = + context.report_lint(&INVALID_METACLASS, invalid_metaclass_range) + { builder.into_diagnostic("Generic metaclasses are not supported"); } } MetaclassErrorKind::NotCallable(ty) => { - if let Some(builder) = context.report_lint(&INVALID_METACLASS, class_node) { + if let Some(builder) = + context.report_lint(&INVALID_METACLASS, invalid_metaclass_range) + { builder.into_diagnostic(format_args!( "Metaclass type `{}` is not callable", ty.display(db) @@ -593,7 +603,9 @@ pub(crate) fn check_static_class_definitions<'db>( } } MetaclassErrorKind::PartlyNotCallable(ty) => { - if let Some(builder) = context.report_lint(&INVALID_METACLASS, class_node) { + if let Some(builder) = + context.report_lint(&INVALID_METACLASS, invalid_metaclass_range) + { builder.into_diagnostic(format_args!( "Metaclass type `{}` is partly not callable", ty.display(db)