Skip to content

New lint: warn about missing variants/fields for non-exhaustive enums/structs #5557

Closed
@ebroto

Description

@ebroto

As suggested in the non_exhaustive RFC, when using non-exhaustive enums and structs in patterns, the lint would warn the user for missing variants or fields despite having a wildcard arm or a rest pattern.

This can be useful when new fields/variants are added by the upstream crate so that they don't go unnoticed.

Enums

Given the following definition

#[non_exhaustive]
pub enum E {
    First,
    Second,
    Third,
}

The following use should be linted:

    match e {
        E::First => {}
        E::Second => {}
        _ => {}
    }

Structs

Given the following definition

#[derive(Default)]
#[non_exhaustive]
pub struct S {
    pub a: i32,
    pub b: i32,
    pub c: i32,
}

The following cases should be linted

    let S { a: _, b: _, .. } = S::default();
    match S::default() {
        S { a: 42, b: 21, .. } => {}
        S { a: _, b: _, .. } => {}
    }
    if let S { a: 42, b: _, .. } = S::default() {}
    while let S { a: 42, b: _, .. } = S::default() {
        break;
    }
    let v = vec![S::default()];
    for S { a: _, b: _, .. } in v {}

In addition to let destructuring in function parameters

pub fn take_s(S { a, b, .. }: S) -> (i32, i32) {
    (a, b)
}

A struct variant of an enum should be linted in the same cases if it's the only variant of the enum. If it's not the only member, it can be linted only in the match, if let and while let cases.

Tuple structs

Tuple structs can't be used in patterns if there are private fields using the T() syntax, but the T{0: _, 1: _} syntax can be used an so it should be linted like with structs.

Given the following definition

#[derive(Default)]
#[non_exhaustive]
pub struct T(pub i32, pub i32, pub i32);

The following cases should be linted

    let T { 0: _, 1: _, .. } = T::default();
    match T::default() {
        T { 0: 42, 1: 21, .. } => {}
        T { 0: _, 1: _, .. } => {}
    }
    if let T { 0: 42, 1: _, .. } = T::default() {}
    while let T { 0: 42, 1: _, .. } = T::default() {
        break;
    }
    let v = vec![T::default()];
    for T { 0: _, 1: _, .. } in v {}

In addition to let destructuring in function parameters

pub fn take_t(T { 0: a, 1: b, .. }: T) -> (i32, i32) {
    (a, b)
}

Notes

  • The non-exhaustive data structure must come from another crate (otherwise the restrictions associated to non-exhaustive data structures do not apply).
  • The lint should not trigger if only the rest pattern is used, so at least one field should be used in the pattern.
  • The lint should be allow-by-default.
  • Although described in the RFC, FRU is not supported currently so constructing a non-exhaustive struct is not possible.
  • Unit structs are not candidates for this lint because new fields can't be added.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-lintArea: New lintsT-ASTType: Requires working with the ASTgood first issueThese issues are a good way to get started with Clippy

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions