Repository: astral-sh/ruff
File: crates/ty_python_semantic/src/types/diagnostic.rs (Rust)

Code before:
        "A protocol class can only be used in `{function_name}` checks if it is decorated \
            with `@typing.runtime_checkable` or `@typing_extensions.runtime_checkable`"
    ));
    diagnostic.info("See https://docs.python.org/3/library/typing.html#typing.runtime_checkable");
}

pub(crate) fn report_match_pattern_against_non_runtime_checkable_protocol<T: Ranged>(
    context: &InferContext,
    pattern_cls: T,
    protocol: ProtocolClass,
) {
    let Some(builder) = context.report_lint(&ISINSTANCE_AGAINST_PROTOCOL, pattern_cls) else {
        return;
    };
    let db = context.db();
    let class_name = protocol.name(db);
    let mut diagnostic = builder.into_diagnostic(format_args!(
        "Class `{class_name}` cannot be used in a class pattern",
    ));
    diagnostic.set_primary_message("This will raise `TypeError` at runtime");
    add_non_runtime_checkable_protocol_context(db, &mut diagnostic, protocol);
    diagnostic.info(
        "A protocol class can only be used in a match class pattern if it is decorated \
            with `@typing.runtime_checkable` or `@typing_extensions.runtime_checkable`",
    );
    diagnostic.info("See https://docs.python.org/3/library/typing.html#typing.runtime_checkable");
}

fn add_non_runtime_checkable_protocol_context<'db, 'ctx>(
    db: &'db dyn Db,
    diagnostic: &mut LintDiagnosticGuard<'db, 'ctx>,
    protocol: ProtocolClass,
) {
    let class_name = protocol.name(db);
    let mut class_def_diagnostic = SubDiagnostic::new(
        SubDiagnosticSeverity::Info,
        format_args!(
            "`{class_name}` is declared as a protocol class, \
                but it is not declared as runtime-checkable"
        ),
    );
    class_def_diagnostic.annotate(
        Annotation::primary(protocol.definition_span(db))
            .message(format_args!("`{class_name}` declared here")),
    );
    diagnostic.sub(class_def_diagnostic);
}

pub(crate) fn report_attempted_protocol_instantiation(
    context: &InferContext,
    call: &ast::ExprCall,

Review comment (refactor):
@AlexWaygood I didn't put these two lines into a helper since it might be overabstraction but let me know

We would pass something like `usage: impl fmt::Display,` with either `format_args!("in {function_name} checks")` or `"in a match class pattern"`

Code after:
    let db = context.db();
    let mut diagnostic = builder.into_diagnostic("Invalid argument to `reveal_protocol_interface`");
    diagnostic
        .set_primary_message("Only protocol classes can be passed to `reveal_protocol_interface`");

    if let Some(class) = param_type.to_class_type(context.db()) {
        let mut class_def_diagnostic = SubDiagnostic::new(
            SubDiagnosticSeverity::Info,
            format_args!(
                "`{}` is declared here, but it is not a protocol class:",
                class.name(db)
            ),
        );
        if let Some((class_literal, _)) = class.static_class_literal(db) {
            class_def_diagnostic.annotate(Annotation::primary(class_literal.header_span(db)));
        }
        diagnostic.sub(class_def_diagnostic);
    }

    diagnostic.info(
        "A class is only a protocol class if it directly inherits \
            from `typing.Protocol` or `typing_extensions.Protocol`",
    );
    // See TODO in `report_bad_argument_to_get_protocol_members` above
    diagnostic.info("See https://typing.python.org/en/latest/spec/protocol.html");
}

pub(crate) fn report_invalid_arguments_to_callable(
    context: &InferContext,
    subscript: &ast::ExprSubscript,
) {
    let Some(builder) = context.report_lint(&INVALID_TYPE_FORM, subscript) else {
        return;
    };
    builder.into_diagnostic(format_args!(
        "Special form `typing.Callable` expected exactly two arguments (parameter types and return type)",
    ));
}

pub(crate) fn add_type_expression_reference_link<'db, 'ctx>(
    mut diag: LintDiagnosticGuard<'db, 'ctx>,
) -> LintDiagnosticGuard<'db, 'ctx> {
    diag.info("See the following page for a reference on valid type expressions:");
    diag.info(
        "https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions",
    );
    diag
}

pub(crate) fn report_runtime_check_against_non_runtime_checkable_protocol(
    context: &InferContext,