Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document new #[expect] attribute and reasons parameter (RFC 2383) #1237

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ The following is an index of all built-in attributes.
- [`proc_macro_derive`] — Defines a derive macro.
- [`proc_macro_attribute`] — Defines an attribute macro.
- Diagnostics
- [`allow`], [`warn`], [`deny`], [`forbid`] — Alters the default lint level.
- [`allow`], [`expect`], [`warn`], [`deny`], [`forbid`] — Alters the default lint level.
- [`deprecated`] — Generates deprecation notices.
- [`must_use`] — Generates a lint for unused values.
- [`diagnostic::on_unimplemented`] — Hints the compiler to emit a certain error
Expand Down Expand Up @@ -303,6 +303,7 @@ The following is an index of all built-in attributes.
[`deprecated`]: attributes/diagnostics.md#the-deprecated-attribute
[`derive`]: attributes/derive.md
[`export_name`]: abi.md#the-export_name-attribute
[`expect`]: attributes/diagnostics.md#lint-check-attributes
[`forbid`]: attributes/diagnostics.md#lint-check-attributes
[`global_allocator`]: runtime.md#the-global_allocator-attribute
[`ignore`]: attributes/testing.md#the-ignore-attribute
Expand Down
141 changes: 131 additions & 10 deletions src/attributes/diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,20 @@ messages during compilation.

A lint check names a potentially undesirable coding pattern, such as
unreachable code or omitted documentation. The lint attributes `allow`,
`warn`, `deny`, and `forbid` use the [_MetaListPaths_] syntax to specify a
list of lint names to change the lint level for the entity to which the
attribute applies.
`expect`, `warn`, `deny`, and `forbid` use the [_MetaListPaths_] syntax
to specify a list of lint names to change the lint level for the entity
to which the attribute applies.

For any lint check `C`:

* `allow(C)` overrides the check for `C` so that violations will go
unreported,
* `warn(C)` warns about violations of `C` but continues compilation.
* `deny(C)` signals an error after encountering a violation of `C`,
* `forbid(C)` is the same as `deny(C)`, but also forbids changing the lint
* `#[allow(C)]` overrides the check for `C` so that violations will go
unreported.
* `#[expect(C)]` indicates that lint `C` is expected to be emitted. The
attribute will suppress the emission of `C` or issue a warning, if the
expectation is unfulfilled.
* `#[warn(C)]` warns about violations of `C` but continues compilation.
* `#[deny(C)]` signals an error after encountering a violation of `C`,
* `#[forbid(C)]` is the same as `deny(C)`, but also forbids changing the lint
level afterwards,

> Note: The lint checks supported by `rustc` can be found via `rustc -W help`,
Expand Down Expand Up @@ -66,8 +69,8 @@ pub mod m2 {
}
```

This example shows how one can use `forbid` to disallow uses of `allow` for
that lint check:
This example shows how one can use `forbid` to disallow uses of `allow` or
`expect` for that lint check:

```rust,compile_fail
#[forbid(missing_docs)]
Expand All @@ -83,6 +86,124 @@ pub mod m3 {
> [command-line][rustc-lint-cli], and also supports [setting
> caps][rustc-lint-caps] on the lints that are reported.

### Lint Reasons

All lint attributes support an additional `reason` parameter, to give context why
xFrednet marked this conversation as resolved.
Show resolved Hide resolved
a certain attribute was added. This reason will be displayed as part of the lint
message if the lint is emitted at the defined level.

```rust,edition2015,compile_fail
// `keyword_idents` is allowed by default. Here we deny it to
// avoid migration of identifiers when we update the edition.
#![deny(
keyword_idents,
reason = "we want to avoid these idents to be future compatible"
)]

// This name was allowed in Rust's 2015 edition. We still aim to avoid
// this to be future compatible and not confuse end users.
fn dyn() {}
```

Here is another example, where the lint is allowed with a reason:

```rust
use std::path::PathBuf;

pub fn get_path() -> PathBuf {
// The `reason` parameter on `allow` attributes acts as documentation for the reader.
#[allow(unused_mut, reason = "this is only modified on some platforms")]
xFrednet marked this conversation as resolved.
Show resolved Hide resolved
let mut file_name = PathBuf::from("git");

#[cfg(target_os = "windows")]
file_name.set_extension("exe");

file_name
}
```

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might help to include a second example where the reason field would have an effect. Perhaps something like this:

#![deny(
    unsafe_op_in_unsafe_fn,
    reason = "using explicit unsafe blocks can help us better review where unsafe operations are being used"
)]
# use std::num::FpCategory;

pub unsafe fn partial_classify(value: f32) -> FpCategory {
    const EXP_MASK: u32 = 0x7f800000;
    const MAN_MASK: u32 = 0x007fffff;

    // ERROR: This function call should be wrapped in an unsafe block.
    // The error message will include the `reason` message so that the author
    // knows why it is not allowed.
    let b = std::mem::transmute::<f32, u32>(value);
    match (b & MAN_MASK, b & EXP_MASK) {
        (0, 0) => FpCategory::Zero,
        (_, 0) => FpCategory::Subnormal,
        _ => FpCategory::Normal,
    }
}

It's tough to come up with a realistic example that isn't too large. I stole this one from the standard library, but if you have a better example, that would be good.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add another example in the next commit :)

### The `#[expect]` attribute

The `#[expect(C)]` attribute creates a lint expectation for lint `C`. The
expectation will be fulfilled, if a `#[warn(C)]` attribute at the same location
would result in a lint emission. If the expectation is unfulfilled, because
lint `C` would not be emitted, the `unfulfilled_lint_expectations` lint will
be emitted at the attribute.

```rust
fn main() {
// This `#[expect]` attribute creates a lint expectation, that the `unused_variables`
// lint would be emitted by the following statement. This expectation is
// unfulfilled, since the `question` variable is used by the `println!` macro.
// Therefore, the `unfulfilled_lint_expectations` lint will be emitted at the
// attribute.
#[expect(unused_variables)]
let question = "who lives in a pineapple under the sea?";
println!("{question}");

// This `#[expect]` attribute creates a lint expectation that will be fulfilled, since
// the `answer` variable is never used. The `unused_variables` lint, that would usually
// be emitted, is suppressed. No warning will be issued for the statement or attribute.
#[expect(unused_variables)]
let answer = "SpongeBob SquarePants!";
}
```

The lint expectation is only fulfilled by lint emissions which have been suppressed by
the `expect` attribute. If the lint level is modified in the scope with other level
attributes like `allow` or `warn`, the lint emission will be handled accordingly and the
expectation will remain unfulfilled.

```rust
#[expect(unused_variables)]
fn select_song() {
// This will emit the `unused_variables` lint at the warn level
// as defined by the `warn` attribute. This will not fulfill the
// expectation above the function.
#[warn(unused_variables)]
let song_name = "Crab Rave";

// The `allow` attribute suppresses the lint emission. This will not
// fulfill the expectation as it has been suppressed by the `allow`
// attribute and not the `expect` attribute above the function.
#[allow(unused_variables)]
let song_creator = "Noisestorm";

// This `expect` attribute will suppress the `unused_variables` lint emission
// at the variable. The `expect` attribute above the function will still not
// be fulfilled, since this lint emission has been suppressed by the local
// expect attribute.
#[expect(unused_variables)]
let song_version = "Monstercat Release";
}
```

If the `expect` attribute contains several lints, each one is expected separately. For a
lint group it's enough if one lint inside the group has been emitted:

```rust
// This expectation will be fulfilled by the unused value inside the function
// since the emitted `unused_variables` lint is inside the `unused` lint group.
#[expect(unused)]
pub fn thoughts() {
let unused = "I'm running out of examples";
}

pub fn another_example() {
// This attribute creates two lint expectations. The `unused_mut` lint will be
// suppressed and with that fulfill the first expectation. The `unused_variables`
// wouldn't be emitted, since the variable is used. That expectation will therefore
// be unsatisfied, and a warning will be emitted.
#[expect(unused_mut, unused_variables)]
let mut link = "https://www.rust-lang.org/";

println!("Welcome to our community: {link}");
}
```

> Note: The behavior of `#[expect(unfulfilled_lint_expectations)]` is currently
> defined to always generate the `unfulfilled_lint_expectations` lint.

### Lint groups

Lints may be organized into named groups so that the level of related lints
Expand Down