use rustc_errors::{Applicability, DiagArgValue, E0232, E0264, MultiSpan};
use rustc_hir::AttrPath;
use rustc_macros::{Diagnostic, Subdiagnostic};
use rustc_span::{Span, Symbol};

#[derive(Diagnostic)]
#[diag("`{$name}` attribute cannot be used at crate level")]
pub(crate) struct InvalidAttrAtCrateLevel {
    #[primary_span]
    pub span: Span,
    #[suggestion(
        "perhaps you meant to use an outer attribute",
        code = "#[",
        applicability = "machine-applicable",
        style = "verbose"
    )]
    pub pound_to_opening_bracket: Span,
    pub name: Symbol,
    #[subdiagnostic]
    pub item: Option<ItemFollowingInnerAttr>,
}

#[derive(Clone, Copy, Subdiagnostic)]
#[label("the inner attribute doesn't annotate this item")]
pub(crate) struct ItemFollowingInnerAttr {
    #[primary_span]
    pub span: Span,
}

#[derive(Diagnostic)]
#[diag("unreachable configuration predicate")]
pub(crate) struct UnreachableCfgSelectPredicate {
    #[label("this configuration predicate is never reached")]
    pub span: Span,
}

#[derive(Diagnostic)]
#[diag("most attributes are not supported in `where` clauses")]
#[help("only `#[cfg]` and `#[cfg_attr]` are supported")]
pub(crate) struct UnsupportedAttributesInWhere {
    #[primary_span]
    pub span: MultiSpan,
}

#[derive(Diagnostic)]
#[diag("unreachable configuration predicate")]
pub(crate) struct UnreachableCfgSelectPredicateWildcard {
    #[label("this configuration predicate is never reached")]
    pub span: Span,

    #[label("always matches")]
    pub wildcard_span: Span,
}

#[derive(Diagnostic)]
#[diag("must be a name of an associated function")]
pub(crate) struct MustBeNameOfAssociatedFunction {
    #[primary_span]
    pub span: Span,
}

#[derive(Diagnostic)]
#[diag("unsafe attribute used without unsafe")]
pub(crate) struct UnsafeAttrOutsideUnsafeLint {
    #[label("usage of unsafe attribute")]
    pub span: Span,
    #[subdiagnostic]
    pub suggestion: Option<crate::session_diagnostics::UnsafeAttrOutsideUnsafeSuggestion>,
}

#[derive(Diagnostic)]
#[diag(
    "{$num_suggestions ->
        [1] attribute must be of the form {$suggestions}
        *[other] valid forms for the attribute are {$suggestions}
    }"
)]
pub(crate) struct IllFormedAttributeInput {
    pub num_suggestions: usize,
    pub suggestions: DiagArgValue,
    #[note("for more information, visit <{$docs}>")]
    pub has_docs: bool,
    pub docs: &'static str,
    #[subdiagnostic]
    help: Option<IllFormedAttributeInputHelp>,
}

impl IllFormedAttributeInput {
    pub(crate) fn new(
        suggestions: &[String],
        docs: Option<&'static str>,
        help: Option<&str>,
    ) -> Self {
        Self {
            num_suggestions: suggestions.len(),
            suggestions: DiagArgValue::StrListSepByAnd(
                suggestions.into_iter().map(|s| format!("`{s}`").into()).collect(),
            ),
            has_docs: docs.is_some(),
            docs: docs.unwrap_or(""),
            help: help.map(|h| IllFormedAttributeInputHelp { lint: h.to_string() }),
        }
    }
}

#[derive(Subdiagnostic)]
#[help(
    "if you meant to silence a warning, consider using #![allow({$lint})] or #![expect({$lint})]"
)]
struct IllFormedAttributeInputHelp {
    pub lint: String,
}

#[derive(Diagnostic)]
#[diag("unused attribute")]
#[note(
    "{$valid_without_list ->
        [true] using `{$attr_path}` with an empty list is equivalent to not using a list at all
        *[other] using `{$attr_path}` with an empty list has no effect
    }"
)]
pub(crate) struct EmptyAttributeList {
    #[suggestion(
        "{$valid_without_list ->
            [true] remove these parentheses
            *[other] remove this attribute
        }",
        code = "",
        applicability = "machine-applicable"
    )]
    pub attr_span: Span,
    pub attr_path: String,
    pub valid_without_list: bool,
}

#[derive(Diagnostic)]
#[diag(
    "{$is_used_as_inner ->
        [false] crate-level attribute should be an inner attribute: add an exclamation mark: `#![{$name}]`
        *[other] the `#![{$name}]` attribute can only be used at the crate root
    }"
)]
pub(crate) struct InvalidAttrStyle {
    pub name: String,
    pub is_used_as_inner: bool,
    #[note("this attribute does not have an `!`, which means it is applied to this {$target}")]
    pub target_span: Option<Span>,
    pub target: &'static str,
    pub crate_root_path: String,
    #[help("the crate root is at `{$crate_root_path}`")]
    pub show_crate_root_help: bool,
    #[primary_span]
    pub span: Span,
}

#[derive(Diagnostic)]
#[diag("doc alias is duplicated")]
pub(crate) struct DocAliasDuplicated {
    #[label("first defined here")]
    pub first_definition: Span,
}

#[derive(Diagnostic)]
#[diag("only `hide` or `show` are allowed in `#[doc(auto_cfg(...))]`")]
pub(crate) struct DocAutoCfgExpectsHideOrShow;

#[derive(Diagnostic)]
#[diag("there exists a built-in attribute with the same name")]
pub(crate) struct AmbiguousDeriveHelpers;

#[derive(Diagnostic)]
#[diag("`#![doc(auto_cfg({$attr_name}(...)))]` only accepts identifiers or `values(...)`")]
pub(crate) struct DocAutoCfgHideShowUnexpectedItem {
    pub attr_name: Symbol,
}

#[derive(Diagnostic)]
#[diag("`any()` was used when other values were provided")]
pub(crate) struct DocAutoCfgHideShowValuesMix {
    #[label("value declared here")]
    pub value_span: Span,
}

#[derive(Diagnostic)]
#[diag("unexpected item after `values()`")]
pub(crate) struct DocAutoCfgHideShowUnexpectedItemAfterValues;

#[derive(Diagnostic)]
#[diag("`#![doc(auto_cfg({$attr_name}(...)))]` expects a list of items")]
pub(crate) struct DocAutoCfgHideShowExpectsList {
    pub attr_name: Symbol,
}

#[derive(Diagnostic)]
#[diag("unknown `doc` attribute `include`")]
pub(crate) struct DocUnknownInclude {
    pub inner: &'static str,
    pub value: Symbol,
    #[suggestion(
        "use `doc = include_str!` instead",
        code = "#{inner}[doc = include_str!(\"{value}\")]"
    )]
    pub sugg: (Span, Applicability),
}

#[derive(Diagnostic)]
#[diag("unknown `doc` attribute `spotlight`")]
#[note("`doc(spotlight)` was renamed to `doc(notable_trait)`")]
#[note("`doc(spotlight)` is now a no-op")]
pub(crate) struct DocUnknownSpotlight {
    #[suggestion(
        "use `notable_trait` instead",
        style = "short",
        applicability = "machine-applicable",
        code = "notable_trait"
    )]
    pub sugg_span: Span,
}

#[derive(Diagnostic)]
#[diag("unknown `doc` attribute `{$name}`")]
#[note(
    "`doc` attribute `{$name}` no longer functions; see issue #44136 <https://github.com/rust-lang/rust/issues/44136>"
)]
#[note("`doc({$name})` is now a no-op")]
pub(crate) struct DocUnknownPasses {
    pub name: Symbol,
    #[label("no longer functions")]
    pub note_span: Span,
}

#[derive(Diagnostic)]
#[diag("unknown `doc` attribute `plugins`")]
#[note(
    "`doc` attribute `plugins` no longer functions; see issue #44136 <https://github.com/rust-lang/rust/issues/44136> and CVE-2018-1000622 <https://nvd.nist.gov/vuln/detail/CVE-2018-1000622>"
)]
#[note("`doc(plugins)` is now a no-op")]
pub(crate) struct DocUnknownPlugins {
    #[label("no longer functions")]
    pub label_span: Span,
}

#[derive(Diagnostic)]
#[diag("unknown `doc` attribute `{$name}`")]
pub(crate) struct DocUnknownAny {
    pub name: Symbol,
}

#[derive(Diagnostic)]
#[diag("expected boolean for `#[doc(auto_cfg = ...)]`")]
pub(crate) struct DocAutoCfgWrongLiteral;

#[derive(Diagnostic)]
#[diag("there must be at least one identifier before `values(...)`")]
pub(crate) struct DocAutoCfgHideShowNoIdentBeforeValues;

#[derive(Diagnostic)]
#[diag("`#[doc(test(...)]` takes a list of attributes")]
pub(crate) struct DocTestTakesList;

#[derive(Diagnostic)]
#[diag("unknown `doc(test)` attribute `{$name}`")]
pub(crate) struct DocTestUnknown {
    pub name: Symbol,
}

#[derive(Diagnostic)]
#[diag("`#![doc(test(...)]` does not take a literal")]
pub(crate) struct DocTestLiteral;

#[derive(Diagnostic)]
#[diag("this attribute can only be applied at the crate level")]
#[note(
    "read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level> for more information"
)]
pub(crate) struct AttrCrateLevelOnly;

#[derive(Diagnostic)]
#[diag("`#[diagnostic::do_not_recommend]` does not expect any arguments")]
pub(crate) struct DoNotRecommendDoesNotExpectArgs;

#[derive(Diagnostic)]
#[diag("invalid `crate_type` value")]
pub(crate) struct UnknownCrateTypes {
    #[subdiagnostic]
    pub sugg: Option<UnknownCrateTypesSuggestion>,
}

#[derive(Subdiagnostic)]
#[suggestion("did you mean", code = r#""{snippet}""#, applicability = "maybe-incorrect")]
pub(crate) struct UnknownCrateTypesSuggestion {
    #[primary_span]
    pub span: Span,
    pub snippet: Symbol,
}

#[derive(Diagnostic)]
#[diag("malformed `doc` attribute input")]
#[warning(
    "this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!"
)]
pub(crate) struct MalformedDoc;

#[derive(Diagnostic)]
#[diag("didn't expect any arguments here")]
#[warning(
    "this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!"
)]
pub(crate) struct ExpectedNoArgs;

#[derive(Diagnostic)]
#[diag("expected this to be of the form `... = \"...\"`")]
#[warning(
    "this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!"
)]
pub(crate) struct ExpectedNameValue;

#[derive(Diagnostic)]
#[diag("malformed `{$attribute}` attribute")]
#[help("{$options}")]
pub(crate) struct MalFormedDiagnosticAttributeLint {
    pub attribute: &'static str,
    pub options: &'static str,
    #[label("invalid option found here")]
    pub span: Span,
}

#[derive(Diagnostic)]
#[diag("{$description}")]
pub(crate) struct WrappedParserError {
    pub description: String,
    #[label("{$label}")]
    pub span: Span,
    pub label: String,
}

#[derive(Diagnostic)]
#[diag("`{$option_name}` is ignored due to previous definition of `{$option_name}`")]
pub(crate) struct IgnoredDiagnosticOption {
    pub option_name: Symbol,
    #[label("`{$option_name}` is first declared here")]
    pub first_span: Span,
    #[label("`{$option_name}` is later redundantly declared here")]
    pub later_span: Span,
}

#[derive(Diagnostic)]
#[diag("missing options for `{$attribute}` attribute")]
#[help("{$options}")]
pub(crate) struct MissingOptionsForDiagnosticAttribute {
    pub attribute: &'static str,
    pub options: &'static str,
}

#[derive(Diagnostic)]
#[diag("expected a literal or missing delimiter")]
#[help(
    "only literals are allowed as values for the `message`, `note` and `label` options. These options must be separated by a comma"
)]
pub(crate) struct NonMetaItemDiagnosticAttribute;

#[derive(Diagnostic, Clone, Copy)]
pub(crate) enum FormatWarning {
    #[diag("positional arguments are not permitted in diagnostic attributes")]
    #[help("you can print empty braces by escaping them")]
    PositionalArgument {
        #[label("remove this format argument")]
        span: Span,
    },

    #[diag("indexed format arguments are not permitted in diagnostic attributes")]
    IndexedArgument {
        #[label("remove this format argument")]
        span: Span,
    },

    #[diag("format specifiers are not permitted in diagnostic attributes")]
    InvalidSpecifier {
        #[label("remove this format specifier")]
        span: Span,
    },

    #[diag("this format argument is not allowed in `#[{$attr}]`")]
    #[note("{$allowed}")]
    DisallowedPlaceholder {
        #[label("remove this format argument")]
        span: Span,
        attr: &'static str,
        allowed: &'static str,
    },
}

#[derive(Subdiagnostic)]
pub(crate) enum UnexpectedCfgCargoHelp {
    #[help("consider using a Cargo feature instead")]
    #[help(
        "or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:{$cargo_toml_lint_cfg}"
    )]
    LintCfg { cargo_toml_lint_cfg: String },
    #[help("consider using a Cargo feature instead")]
    #[help(
        "or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:{$cargo_toml_lint_cfg}"
    )]
    #[help("or consider adding `{$build_rs_println}` to the top of the `build.rs`")]
    LintCfgAndBuildRs { cargo_toml_lint_cfg: String, build_rs_println: String },
}

impl UnexpectedCfgCargoHelp {
    fn cargo_toml_lint_cfg(unescaped: &str) -> String {
        format!(
            "\n [lints.rust]\n unexpected_cfgs = {{ level = \"warn\", check-cfg = ['{unescaped}'] }}"
        )
    }

    pub(crate) fn lint_cfg(unescaped: &str) -> Self {
        UnexpectedCfgCargoHelp::LintCfg {
            cargo_toml_lint_cfg: Self::cargo_toml_lint_cfg(unescaped),
        }
    }

    pub(crate) fn lint_cfg_and_build_rs(unescaped: &str, escaped: &str) -> Self {
        UnexpectedCfgCargoHelp::LintCfgAndBuildRs {
            cargo_toml_lint_cfg: Self::cargo_toml_lint_cfg(unescaped),
            build_rs_println: format!("println!(\"cargo::rustc-check-cfg={escaped}\");"),
        }
    }
}

#[derive(Subdiagnostic)]
#[help("to expect this configuration use `{$cmdline_arg}`")]
pub(crate) struct UnexpectedCfgRustcHelp {
    pub cmdline_arg: String,
}

impl UnexpectedCfgRustcHelp {
    pub(crate) fn new(unescaped: &str) -> Self {
        Self { cmdline_arg: format!("--check-cfg={unescaped}") }
    }
}

#[derive(Subdiagnostic)]
#[note(
    "using a cfg inside a {$macro_kind} will use the cfgs from the destination crate and not the ones from the defining crate"
)]
#[help("try referring to `{$macro_name}` crate for guidance on how handle this unexpected cfg")]
pub(crate) struct UnexpectedCfgRustcMacroHelp {
    pub macro_kind: &'static str,
    pub macro_name: Symbol,
}

#[derive(Subdiagnostic)]
#[note(
    "using a cfg inside a {$macro_kind} will use the cfgs from the destination crate and not the ones from the defining crate"
)]
#[help("try referring to `{$macro_name}` crate for guidance on how handle this unexpected cfg")]
pub(crate) struct UnexpectedCfgCargoMacroHelp {
    pub macro_kind: &'static str,
    pub macro_name: Symbol,
}

#[derive(Diagnostic)]
#[diag("unexpected `cfg` condition name: `{$name}`")]
pub(crate) struct UnexpectedCfgName {
    #[subdiagnostic]
    pub code_sugg: unexpected_cfg_name::CodeSuggestion,
    #[subdiagnostic]
    pub invocation_help: unexpected_cfg_name::InvocationHelp,

    pub name: Symbol,
}

pub(crate) mod unexpected_cfg_name {
    use rustc_errors::DiagSymbolList;
    use rustc_macros::Subdiagnostic;
    use rustc_span::{Ident, Span, Symbol};

    #[derive(Subdiagnostic)]
    pub(crate) enum CodeSuggestion {
        #[help("consider defining some features in `Cargo.toml`")]
        DefineFeatures,
        #[multipart_suggestion(
            "there is a similar config predicate: `version(\"..\")`",
            applicability = "machine-applicable"
        )]
        VersionSyntax {
            #[suggestion_part(code = "(")]
            between_name_and_value: Span,
            #[suggestion_part(code = ")")]
            after_value: Span,
        },
        #[suggestion(
            "there is a config with a similar name and value",
            applicability = "maybe-incorrect",
            code = "{code}"
        )]
        SimilarNameAndValue {
            #[primary_span]
            span: Span,
            code: String,
        },
        #[suggestion(
            "there is a config with a similar name and no value",
            applicability = "maybe-incorrect",
            code = "{code}"
        )]
        SimilarNameNoValue {
            #[primary_span]
            span: Span,
            code: String,
        },
        #[suggestion(
            "there is a config with a similar name and different values",
            applicability = "maybe-incorrect",
            code = "{code}"
        )]
        SimilarNameDifferentValues {
            #[primary_span]
            span: Span,
            code: String,
            #[subdiagnostic]
            expected: Option<ExpectedValues>,
        },
        #[suggestion(
            "there is a config with a similar name",
            applicability = "maybe-incorrect",
            code = "{code}"
        )]
        SimilarName {
            #[primary_span]
            span: Span,
            code: String,
            #[subdiagnostic]
            expected: Option<ExpectedValues>,
        },
        SimilarValues {
            #[subdiagnostic]
            with_similar_values: Vec<FoundWithSimilarValue>,
            #[subdiagnostic]
            expected_names: Option<ExpectedNames>,
        },
        #[suggestion(
            "you may have meant to use `{$literal}` (notice the capitalization). Doing so makes this predicate evaluate to `{$literal}` unconditionally",
            applicability = "machine-applicable",
            style = "verbose",
            code = "{literal}"
        )]
        BooleanLiteral {
            #[primary_span]
            span: Span,
            literal: bool,
        },
    }

    #[derive(Subdiagnostic)]
    #[help("expected values for `{$best_match}` are: {$possibilities}")]
    pub(crate) struct ExpectedValues {
        pub best_match: Symbol,
        pub possibilities: DiagSymbolList,
    }

    #[derive(Subdiagnostic)]
    #[suggestion(
        "found config with similar value",
        applicability = "maybe-incorrect",
        code = "{code}"
    )]
    pub(crate) struct FoundWithSimilarValue {
        #[primary_span]
        pub span: Span,
        pub code: String,
    }

    #[derive(Subdiagnostic)]
    #[help_once(
        "expected names are: {$possibilities}{$and_more ->
            [0] {\"\"}
            *[other] {\" \"}and {$and_more} more
        }"
    )]
    pub(crate) struct ExpectedNames {
        pub possibilities: DiagSymbolList<Ident>,
        pub and_more: usize,
    }

    #[derive(Subdiagnostic)]
    pub(crate) enum InvocationHelp {
        #[note(
            "see <https://doc.rust-lang.org/nightly/rustc/check-cfg/cargo-specifics.html> for more information about checking conditional configuration"
        )]
        Cargo {
            #[subdiagnostic]
            macro_help: Option<super::UnexpectedCfgCargoMacroHelp>,
            #[subdiagnostic]
            help: Option<super::UnexpectedCfgCargoHelp>,
        },
        #[note(
            "see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration"
        )]
        Rustc {
            #[subdiagnostic]
            macro_help: Option<super::UnexpectedCfgRustcMacroHelp>,
            #[subdiagnostic]
            help: super::UnexpectedCfgRustcHelp,
        },
    }
}

#[derive(Diagnostic)]
#[diag(
    "unexpected `cfg` condition value: {$has_value ->
        [true] `{$value}`
        *[false] (none)
    }"
)]
pub(crate) struct UnexpectedCfgValue {
    #[subdiagnostic]
    pub code_sugg: unexpected_cfg_value::CodeSuggestion,
    #[subdiagnostic]
    pub invocation_help: unexpected_cfg_value::InvocationHelp,

    pub has_value: bool,
    pub value: String,
}

pub(crate) mod unexpected_cfg_value {
    use rustc_errors::DiagSymbolList;
    use rustc_macros::Subdiagnostic;
    use rustc_span::{Span, Symbol};

    #[derive(Subdiagnostic)]
    pub(crate) enum CodeSuggestion {
        ChangeValue {
            #[subdiagnostic]
            expected_values: ExpectedValues,
            #[subdiagnostic]
            suggestion: Option<ChangeValueSuggestion>,
        },
        #[note("no expected value for `{$name}`")]
        RemoveValue {
            #[subdiagnostic]
            suggestion: Option<RemoveValueSuggestion>,

            name: Symbol,
        },
        #[note("no expected values for `{$name}`")]
        RemoveCondition {
            #[subdiagnostic]
            suggestion: RemoveConditionSuggestion,

            name: Symbol,
        },
        ChangeName {
            #[subdiagnostic]
            suggestions: Vec<ChangeNameSuggestion>,
        },
    }

    #[derive(Subdiagnostic)]
    pub(crate) enum ChangeValueSuggestion {
        #[suggestion(
            "there is a expected value with a similar name",
            code = r#""{best_match}""#,
            applicability = "maybe-incorrect"
        )]
        SimilarName {
            #[primary_span]
            span: Span,
            best_match: Symbol,
        },
        #[suggestion(
            "specify a config value",
            code = r#" = "{first_possibility}""#,
            applicability = "maybe-incorrect"
        )]
        SpecifyValue {
            #[primary_span]
            span: Span,
            first_possibility: Symbol,
        },
    }

    #[derive(Subdiagnostic)]
    #[suggestion("remove the value", code = "", applicability = "maybe-incorrect")]
    pub(crate) struct RemoveValueSuggestion {
        #[primary_span]
        pub span: Span,
    }

    #[derive(Subdiagnostic)]
    #[suggestion("remove the condition", code = "", applicability = "maybe-incorrect")]
    pub(crate) struct RemoveConditionSuggestion {
        #[primary_span]
        pub span: Span,
    }

    #[derive(Subdiagnostic)]
    #[note(
        "expected values for `{$name}` are: {$have_none_possibility ->
            [true] {\"(none), \"}
            *[false] {\"\"}
        }{$possibilities}{$and_more ->
            [0] {\"\"}
            *[other] {\" \"}and {$and_more} more
        }"
    )]
    pub(crate) struct ExpectedValues {
        pub name: Symbol,
        pub have_none_possibility: bool,
        pub possibilities: DiagSymbolList,
        pub and_more: usize,
    }

    #[derive(Subdiagnostic)]
    #[suggestion(
        "`{$value}` is an expected value for `{$name}`",
        code = "{name}",
        applicability = "maybe-incorrect",
        style = "verbose"
    )]
    pub(crate) struct ChangeNameSuggestion {
        #[primary_span]
        pub span: Span,
        pub name: Symbol,
        pub value: Symbol,
    }

    #[derive(Subdiagnostic)]
    pub(crate) enum InvocationHelp {
        #[note(
            "see <https://doc.rust-lang.org/nightly/rustc/check-cfg/cargo-specifics.html> for more information about checking conditional configuration"
        )]
        Cargo {
            #[subdiagnostic]
            help: Option<CargoHelp>,
            #[subdiagnostic]
            macro_help: Option<super::UnexpectedCfgCargoMacroHelp>,
        },
        #[note(
            "see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration"
        )]
        Rustc {
            #[subdiagnostic]
            help: Option<super::UnexpectedCfgRustcHelp>,
            #[subdiagnostic]
            macro_help: Option<super::UnexpectedCfgRustcMacroHelp>,
        },
    }

    #[derive(Subdiagnostic)]
    pub(crate) enum CargoHelp {
        #[help("consider adding `{$value}` as a feature in `Cargo.toml`")]
        AddFeature {
            value: Symbol,
        },
        #[help("consider defining some features in `Cargo.toml`")]
        DefineFeatures,
        Other(#[subdiagnostic] super::UnexpectedCfgCargoHelp),
    }
}

#[derive(Diagnostic)]
pub(crate) enum InvalidOnClause {
    #[diag("empty `on`-clause in `#[rustc_on_unimplemented]`", code = E0232)]
    Empty {
        #[primary_span]
        #[label("empty `on`-clause here")]
        span: Span,
    },
    #[diag("expected a single predicate in `not(..)`", code = E0232)]
    ExpectedOnePredInNot {
        #[primary_span]
        #[label("unexpected quantity of predicates here")]
        span: Span,
    },
    #[diag("literals inside `on`-clauses are not supported", code = E0232)]
    UnsupportedLiteral {
        #[primary_span]
        #[label("unexpected literal here")]
        span: Span,
    },
    #[diag("expected an identifier inside this `on`-clause", code = E0232)]
    ExpectedIdentifier {
        #[primary_span]
        #[label("expected an identifier here, not `{$path}`")]
        span: Span,
        path: AttrPath,
    },
    #[diag("this predicate is invalid", code = E0232)]
    InvalidPredicate {
        #[primary_span]
        #[label("expected one of `any`, `all` or `not` here, not `{$invalid_pred}`")]
        span: Span,
        invalid_pred: Symbol,
    },
    #[diag("invalid flag in `on`-clause", code = E0232)]
    InvalidFlag {
        #[primary_span]
        #[label(
            "expected one of the `crate_local`, `direct` or `from_desugaring` flags, not `{$invalid_flag}`"
        )]
        span: Span,
        invalid_flag: Symbol,
    },
}

#[derive(Diagnostic)]
#[diag(
    "using multiple `rustc_on_unimplemented` (or mixing it with `diagnostic::on_unimplemented`) is not supported"
)]
pub(crate) struct DupesNotAllowed;

#[derive(Diagnostic)]
#[diag("usage of the unsafe `#[{$attr_path}]` attribute")]
#[note("{$note}")]
pub(crate) struct UnsafeAttribute {
    pub attr_path: AttrPath,
    pub note: &'static str,
}

#[derive(Diagnostic)]
#[diag("unknown external lang item: `{$lang_item}`", code = E0264)]
pub(crate) struct UnknownExternLangItem {
    #[primary_span]
    pub span: Span,
    pub lang_item: Symbol,
}
