|  | 
|  | 1 | +# Diagnostic Items | 
|  | 2 | + | 
|  | 3 | +## Background | 
|  | 4 | + | 
|  | 5 | +While writing lints it's common to check for specific types, traits and functions. This raises | 
|  | 6 | +the question on how to check for these. Types can be checked by their complete type path. | 
|  | 7 | +However, this requires hard coding paths and can lead to misclassifications in some edge cases. | 
|  | 8 | +To counteract this, rustc has introduced diagnostic items that are used to identify types via | 
|  | 9 | +[`Symbol`]s. | 
|  | 10 | + | 
|  | 11 | +## How To Find Diagnostic Items | 
|  | 12 | + | 
|  | 13 | +Diagnostic items are added to items inside `rustc`/`std`/`core` with the `rustc_diagnostic_item` | 
|  | 14 | +attribute. The item for a specific type can be found by opening the source code in the | 
|  | 15 | +documentation and looking for this attribute. Note that it's often added with the `cfg_attr` | 
|  | 16 | +attribute to avoid compilation errors during tests. A definition often looks like this: | 
|  | 17 | + | 
|  | 18 | +```rs | 
|  | 19 | +// This is the diagnostic item for this type   vvvvvvv | 
|  | 20 | +#[cfg_attr(not(test), rustc_diagnostic_item = "Penguin")] | 
|  | 21 | +struct Penguin; | 
|  | 22 | +``` | 
|  | 23 | + | 
|  | 24 | +Diagnostic items are usually only added to traits, types and standalone functions. If the goal | 
|  | 25 | +is to check for an associated type or method, please use the diagnostic item of the item and | 
|  | 26 | +reference [*Using Diagnostic Items*](#using-diagnostic-items). | 
|  | 27 | + | 
|  | 28 | +## How To Add Diagnostic Items | 
|  | 29 | + | 
|  | 30 | +A new diagnostic item can be added with these two steps: | 
|  | 31 | + | 
|  | 32 | +1. Find the target item inside the rust repo. Now add the diagnostic item as a string via the  | 
|  | 33 | +    `rustc_diagnostic_item` attribute. This can sometimes cause compilation errors while running | 
|  | 34 | +    tests. These errors can be avoided by using the `cfg_attr` attribute with the `not(test)` | 
|  | 35 | +    condition (it's fine adding then for all `rustc_diagnostic_item` attributes as a preventive | 
|  | 36 | +    manner). At the end, it should look like this: | 
|  | 37 | + | 
|  | 38 | +    ```rs | 
|  | 39 | +    // This will be the new diagnostic item        vvv | 
|  | 40 | +    #[cfg_attr(not(test), rustc_diagnostic_item = "Cat")] | 
|  | 41 | +    struct Cat; | 
|  | 42 | +    ``` | 
|  | 43 | + | 
|  | 44 | +    For the naming conventions of diagnostic items, please refer to | 
|  | 45 | +    [*Naming Conventions*](#naming-conventions). | 
|  | 46 | + | 
|  | 47 | +2. As of August 2021 <!-- date: 2021-08 --> diagnostic items in code are accessed via symbols in | 
|  | 48 | +    [`rustc_span::symbol::sym`]. To add your newly created diagnostic item simply open the | 
|  | 49 | +    module file and add the name (In this case `Cat`) at the correct point in the list. | 
|  | 50 | + | 
|  | 51 | +Now you can create a pull request with your changes. :tada: (Note that when using diagnostic | 
|  | 52 | +items in other projects like Clippy, it might take some time until the repos get synchronized.) | 
|  | 53 | + | 
|  | 54 | +## Naming Conventions | 
|  | 55 | + | 
|  | 56 | +Diagnostic items don't have a set in stone naming convention yet. These are some guidelines that | 
|  | 57 | +should be used for the future, but might differ from existing names: | 
|  | 58 | + | 
|  | 59 | +* Types, traits and enums are named using UpperCamelCase (Examples: `Iterator`, `HashMap`, ...) | 
|  | 60 | +* For type names that are used multiple times like `Writer` it's good to choose a more precise | 
|  | 61 | +  name, maybe by adding the module to it. (Example: `IoWriter`) | 
|  | 62 | +* Associated items should not get their own diagnostic items, but instead be accessed indirectly | 
|  | 63 | +  by the diagnostic item of the type they're originating from. | 
|  | 64 | +* Freestanding functions like `std::mem::swap()` should be named using `snake_case` with one | 
|  | 65 | +  important (export) module as a prefix (Example: `mem_swap`, `cmp_max`) | 
|  | 66 | +* Modules should usually not have a diagnostic item attached to them. Diagnostic items were | 
|  | 67 | +  added to avoid the usage of paths, using them on modules would therefore most likely to be | 
|  | 68 | +  counterproductive. | 
|  | 69 | + | 
|  | 70 | +## How To Use Diagnostic Items | 
|  | 71 | + | 
|  | 72 | +In rustc, diagnostic items are looked up via [`Symbol`]s from inside the | 
|  | 73 | +[`rustc_span::symbol::sym`] module. These can then be mapped to [`DefId`]s using | 
|  | 74 | +[`TyCtxt::get_diagnostic_item()`] or checked if they match a [`DefId`] using | 
|  | 75 | +[`TyCtxt::is_diagnostic_item()`]. When mapping from a diagnostic item to a [`DefId`] the method | 
|  | 76 | +will return a `Option<DefId>`. This can be `None` if either the symbol isn't a diagnostic item | 
|  | 77 | +or the type is not registered, for instance when compiling with `#[no_std]`. All following | 
|  | 78 | +examples are based on [`DefId`]s and their usage. | 
|  | 79 | + | 
|  | 80 | +### Check For A Type  | 
|  | 81 | + | 
|  | 82 | +```rust | 
|  | 83 | +use rustc_span::symbol::sym; | 
|  | 84 | + | 
|  | 85 | +/// This example checks if the given type (`ty`) has the type `HashMap` using | 
|  | 86 | +/// `TyCtxt::is_diagnostic_item()` | 
|  | 87 | +fn example_1(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { | 
|  | 88 | +    match ty.kind() { | 
|  | 89 | +        ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(sym::HashMap, adt.did), | 
|  | 90 | +        _ => false, | 
|  | 91 | +    } | 
|  | 92 | +} | 
|  | 93 | +``` | 
|  | 94 | + | 
|  | 95 | +### Check For A Trait Implementation | 
|  | 96 | + | 
|  | 97 | +```rust | 
|  | 98 | +/// This example checks if a given [`DefId`] from a method is part of a trait | 
|  | 99 | +/// implementation defined by a diagnostic item. | 
|  | 100 | +fn is_diag_trait_item( | 
|  | 101 | +    cx: &LateContext<'_>, | 
|  | 102 | +    def_id: DefId, | 
|  | 103 | +    diag_item: Symbol | 
|  | 104 | +) -> bool { | 
|  | 105 | +    if let Some(trait_did) = cx.tcx.trait_of_item(def_id) { | 
|  | 106 | +        return cx.tcx.is_diagnostic_item(diag_item, trait_did); | 
|  | 107 | +    } | 
|  | 108 | +    false | 
|  | 109 | +} | 
|  | 110 | +``` | 
|  | 111 | + | 
|  | 112 | +### Associated Types | 
|  | 113 | + | 
|  | 114 | +Associated types of diagnostic items can be accessed indirectly by first getting the [`DefId`] | 
|  | 115 | +of the trait and then calling [`TyCtxt::associated_items()`]. This returns an [`AssocItems`] | 
|  | 116 | +object which can be used for further checks. Checkout  | 
|  | 117 | +[`clippy_utils::ty::get_iterator_item_ty()`] for an example usage of this. | 
|  | 118 | + | 
|  | 119 | +### Usage In Clippy | 
|  | 120 | + | 
|  | 121 | +Clippy tries to use diagnostic items where possible and has developed some wrapper and utility | 
|  | 122 | +functions. Please also refer to its documentation when using diagnostic items in Clippy. (See | 
|  | 123 | +[*Common tools for writing lints*][clippy-Common-tools-for-writing-lints].) | 
|  | 124 | + | 
|  | 125 | +## Related Issues | 
|  | 126 | + | 
|  | 127 | +This lists some related issues. These are probably only interesting to people who really want to | 
|  | 128 | +take a deep dive into the topic :) | 
|  | 129 | + | 
|  | 130 | +* [rust#60966]: The Rust PR that introduced diagnostic items  | 
|  | 131 | +* [rust-clippy#5393]: Clippy's tracking issue for moving away from hard coded paths to | 
|  | 132 | +  diagnostic item | 
|  | 133 | + | 
|  | 134 | +<!-- Links --> | 
|  | 135 | + | 
|  | 136 | +[`rustc_span::symbol::sym`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/sym/index.html | 
|  | 137 | +[`Symbol`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/struct.Symbol.html | 
|  | 138 | +[`DefId`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir/def_id/struct.DefId.html | 
|  | 139 | +[`TyCtxt::get_diagnostic_item()`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html#method.get_diagnostic_item | 
|  | 140 | +[`TyCtxt::is_diagnostic_item()`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html#method.is_diagnostic_item | 
|  | 141 | +[`TyCtxt::associated_items()`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html#method.associated_items | 
|  | 142 | +[`AssocItems`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/assoc/struct.AssocItems.html | 
|  | 143 | +[`clippy_utils::ty::get_iterator_item_ty()`]: https://github.com/rust-lang/rust-clippy/blob/305177342fbc622c0b3cb148467bab4b9524c934/clippy_utils/src/ty.rs#L55-L72 | 
|  | 144 | +[clippy-Common-tools-for-writing-lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/common_tools_writing_lints.md | 
|  | 145 | +[rust#60966]: https://github.com/rust-lang/rust/pull/60966 | 
|  | 146 | +[rust-clippy#5393]: https://github.com/rust-lang/rust-clippy/issues/5393 | 
0 commit comments