Skip to content

Commit

Permalink
Switch non exhaustive syntax tree enums to use #[non_exhaustive]
Browse files Browse the repository at this point in the history
This follows through on the comments on the __TestExhaustive variants
promising that their purpose will be substituted with a deny(reachable)
rustc lint once one is available.

That lint is now landing as non_exhaustive_omitted_patterns in Rust 1.57.
The correct way to use it for checking exhaustivity of a match is:

    match expr {
        Expr::Array(e) => {...}
        Expr::Assign(e) => {...}
        ...
        Expr::Yield(e) => {...}

        #[cfg_attr(test, deny(non_exhaustive_omitted_patterns))]
        _ => { /* some sane fallback */ }
    }
  • Loading branch information
dtolnay committed Mar 27, 2022
1 parent 390e0c1 commit c6ce512
Show file tree
Hide file tree
Showing 19 changed files with 167 additions and 113 deletions.
4 changes: 4 additions & 0 deletions build.rs
Expand Up @@ -19,6 +19,10 @@ fn main() {
println!("cargo:rustc-cfg=syn_no_const_vec_new");
}

if compiler.minor < 40 {
println!("cargo:rustc-cfg=syn_no_non_exhaustive");
}

if compiler.minor < 56 {
println!("cargo:rustc-cfg=syn_no_negative_literal_parse");
}
Expand Down
10 changes: 9 additions & 1 deletion codegen/src/clone.rs
Expand Up @@ -42,8 +42,16 @@ fn expand_impl_body(defs: &Definitions, node: &Node) -> TokenStream {
});
let nonexhaustive = if node.exhaustive {
None
} else if node.ident == "Expr" {
Some(quote! {
#[cfg(any(syn_no_non_exhaustive, not(feature = "full")))]
_ => unreachable!(),
})
} else {
Some(quote!(_ => unreachable!()))
Some(quote! {
#[cfg(syn_no_non_exhaustive)]
_ => unreachable!(),
})
};
quote! {
match self {
Expand Down
10 changes: 9 additions & 1 deletion codegen/src/debug.rs
Expand Up @@ -42,8 +42,16 @@ fn expand_impl_body(defs: &Definitions, node: &Node) -> TokenStream {
});
let nonexhaustive = if node.exhaustive {
None
} else if node.ident == "Expr" {
Some(quote! {
#[cfg(any(syn_no_non_exhaustive, not(feature = "full")))]
_ => unreachable!(),
})
} else {
Some(quote!(_ => unreachable!()))
Some(quote! {
#[cfg(syn_no_non_exhaustive)]
_ => unreachable!(),
})
};
quote! {
match self {
Expand Down
5 changes: 4 additions & 1 deletion codegen/src/fold.rs
Expand Up @@ -154,7 +154,10 @@ fn node(traits: &mut TokenStream, impls: &mut TokenStream, s: &Node, defs: &Defi
let nonexhaustive = if s.exhaustive {
None
} else {
Some(quote!(_ => unreachable!()))
Some(quote! {
#[cfg(syn_no_non_exhaustive)]
_ => unreachable!(),
})
};

fold_impl.extend(quote! {
Expand Down
10 changes: 9 additions & 1 deletion codegen/src/hash.rs
Expand Up @@ -78,8 +78,16 @@ fn expand_impl_body(defs: &Definitions, node: &Node) -> TokenStream {
});
let nonexhaustive = if node.exhaustive {
None
} else if node.ident == "Expr" {
Some(quote! {
#[cfg(any(syn_no_non_exhaustive, not(feature = "full")))]
_ => unreachable!(),
})
} else {
Some(quote!(_ => unreachable!()))
Some(quote! {
#[cfg(syn_no_non_exhaustive)]
_ => unreachable!(),
})
};
quote! {
match self {
Expand Down
5 changes: 4 additions & 1 deletion codegen/src/visit.rs
Expand Up @@ -166,7 +166,10 @@ fn node(traits: &mut TokenStream, impls: &mut TokenStream, s: &Node, defs: &Defi
let nonexhaustive = if s.exhaustive {
None
} else {
Some(quote!(_ => unreachable!()))
Some(quote! {
#[cfg(syn_no_non_exhaustive)]
_ => unreachable!(),
})
};

visit_impl.extend(quote! {
Expand Down
5 changes: 4 additions & 1 deletion codegen/src/visit_mut.rs
Expand Up @@ -166,7 +166,10 @@ fn node(traits: &mut TokenStream, impls: &mut TokenStream, s: &Node, defs: &Defi
let nonexhaustive = if s.exhaustive {
None
} else {
Some(quote!(_ => unreachable!()))
Some(quote! {
#[cfg(syn_no_non_exhaustive)]
_ => unreachable!(),
})
};

visit_mut_impl.extend(quote! {
Expand Down
25 changes: 9 additions & 16 deletions src/expr.rs
Expand Up @@ -87,6 +87,7 @@ ast_enum_of_structs! {
/// see names getting repeated in your code, like accessing
/// `receiver.receiver` or `pat.pat` or `cond.cond`.
#[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))]
#[cfg_attr(not(syn_no_non_exhaustive), non_exhaustive)]
pub enum Expr {
/// A slice literal expression: `[a, b, c, d]`.
Array(ExprArray),
Expand Down Expand Up @@ -224,31 +225,27 @@ ast_enum_of_structs! {
/// A yield expression: `yield expr`.
Yield(ExprYield),

// The following is the only supported idiom for exhaustive matching of
// this enum.
// Not public API.
//
// For testing exhaustiveness in downstream code, use the following idiom:
//
// match expr {
// Expr::Array(expr) => {...}
// Expr::Assign(expr) => {...}
// ...
// Expr::Yield(expr) => {...}
//
// #[cfg(test)]
// Expr::__TestExhaustive(_) => unimplemented!(),
// #[cfg(not(test))]
// #[cfg_attr(test, deny(non_exhaustive_omitted_patterns))]
// _ => { /* some sane fallback */ }
// }
//
// This way we fail your tests but don't break your library when adding
// a variant. You will be notified by a test failure when a variant is
// added, so that you can add code to handle it, but your library will
// continue to compile and work for downstream users in the interim.
//
// Once `deny(reachable)` is available in rustc, Expr will be
// reimplemented as a non_exhaustive enum.
// https://github.com/rust-lang/rust/issues/44109#issuecomment-521781237
#[cfg(syn_no_non_exhaustive)]
#[doc(hidden)]
__TestExhaustive(crate::private),
__NonExhaustive,
}
}

Expand Down Expand Up @@ -838,9 +835,7 @@ impl Expr {
| Expr::Yield(ExprYield { attrs, .. }) => mem::replace(attrs, new),
Expr::Verbatim(_) => Vec::new(),

#[cfg(test)]
Expr::__TestExhaustive(_) => unimplemented!(),
#[cfg(not(test))]
#[cfg(syn_no_non_exhaustive)]
_ => unreachable!(),
}
}
Expand Down Expand Up @@ -2510,9 +2505,7 @@ pub(crate) mod parsing {
Pat::Verbatim(_) => {}
Pat::Wild(pat) => pat.attrs = attrs,

#[cfg(test)]
Pat::__TestExhaustive(_) => unimplemented!(),
#[cfg(not(test))]
#[cfg(syn_no_non_exhaustive)]
_ => unreachable!(),
}
Ok(pat)
Expand Down
7 changes: 7 additions & 0 deletions src/gen/clone.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions src/gen/debug.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions src/gen/fold.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions src/gen/hash.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit c6ce512

Please sign in to comment.