Skip to content

Commit 33ab21b

Browse files
add cfg_if! and cfg_if_expr! macro_rules macros
- allow unreachable code in a branch of a cfg_if_expr! - make main() alone handle process exit status - refactor files_by_unix_mode - allow specifying output type for cfg_if_expr! - use cfg_if! and cfg_if_expr! to rewrite a complex conditional for calculating default compression - add explicit compile_error!() cases to catch subtle fallback scenarios
1 parent 72cce40 commit 33ab21b

File tree

8 files changed

+477
-297
lines changed

8 files changed

+477
-297
lines changed

examples/write_dir.rs

Lines changed: 30 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use anyhow::Context;
44
use clap::{Parser, ValueEnum};
55
use std::io::prelude::*;
6-
use zip::{result::ZipError, write::SimpleFileOptions};
6+
use zip::{cfg_if_expr, result::ZipError, write::SimpleFileOptions};
77

88
use std::fs::File;
99
use std::path::{Path, PathBuf};
@@ -30,59 +30,51 @@ enum CompressionMethod {
3030
Zstd,
3131
}
3232

33-
fn main() {
34-
std::process::exit(real_main());
35-
}
36-
37-
fn real_main() -> i32 {
33+
fn main() -> ! {
3834
let args = Args::parse();
3935
let src_dir = &args.source;
4036
let dst_file = &args.destination;
4137
let method = match args.compression_method {
4238
CompressionMethod::Stored => zip::CompressionMethod::Stored,
43-
CompressionMethod::Deflated => {
44-
#[cfg(not(feature = "deflate-flate2"))]
45-
{
39+
CompressionMethod::Deflated => cfg_if_expr! {
40+
#[cfg(feature = "deflate-flate2")] => zip::CompressionMethod::Deflated,
41+
_ => {
4642
println!("The `deflate-flate2` feature is not enabled");
47-
return 1;
43+
std::process::exit(1)
4844
}
49-
#[cfg(feature = "deflate-flate2")]
50-
zip::CompressionMethod::Deflated
51-
}
52-
CompressionMethod::Bzip2 => {
53-
#[cfg(not(feature = "bzip2"))]
54-
{
45+
},
46+
CompressionMethod::Bzip2 => cfg_if_expr! {
47+
#[cfg(feature = "bzip2")] => zip::CompressionMethod::Bzip2,
48+
_ => {
5549
println!("The `bzip2` feature is not enabled");
56-
return 1;
50+
std::process::exit(1)
5751
}
58-
#[cfg(feature = "bzip2")]
59-
zip::CompressionMethod::Bzip2
60-
}
61-
CompressionMethod::Xz => {
62-
#[cfg(not(feature = "xz"))]
63-
{
52+
},
53+
CompressionMethod::Xz => cfg_if_expr! {
54+
#[cfg(feature = "xz")] => zip::CompressionMethod::Xz,
55+
_ => {
6456
println!("The `xz` feature is not enabled");
65-
return 1;
57+
std::process::exit(1)
6658
}
67-
#[cfg(feature = "xz")]
68-
zip::CompressionMethod::Xz
69-
}
70-
CompressionMethod::Zstd => {
71-
#[cfg(not(feature = "zstd"))]
72-
{
59+
},
60+
CompressionMethod::Zstd => cfg_if_expr! {
61+
#[cfg(feature = "zstd")] => zip::CompressionMethod::Zstd,
62+
_ => {
7363
println!("The `zstd` feature is not enabled");
74-
return 1;
64+
std::process::exit(1)
7565
}
76-
#[cfg(feature = "zstd")]
77-
zip::CompressionMethod::Zstd
78-
}
66+
},
7967
};
8068
match doit(src_dir, dst_file, method) {
81-
Ok(_) => println!("done: {src_dir:?} written to {dst_file:?}"),
82-
Err(e) => eprintln!("Error: {e:?}"),
69+
Ok(_) => {
70+
println!("done: {src_dir:?} written to {dst_file:?}");
71+
std::process::exit(0);
72+
}
73+
Err(e) => {
74+
eprintln!("Error: {e:?}");
75+
std::process::abort();
76+
}
8377
}
84-
85-
0
8678
}
8779

8880
fn zip_dir<T>(

src/compression.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
33
use std::{fmt, io};
44

5+
use crate::cfg_if_expr;
6+
57
#[allow(deprecated)]
68
/// Identifies the storage format used to compress a file within a ZIP archive.
79
///
@@ -228,11 +230,10 @@ impl CompressionMethod {
228230

229231
impl Default for CompressionMethod {
230232
fn default() -> Self {
231-
#[cfg(feature = "_deflate-any")]
232-
return CompressionMethod::Deflated;
233-
234-
#[cfg(not(feature = "_deflate-any"))]
235-
return CompressionMethod::Stored;
233+
cfg_if_expr! {
234+
#[cfg(feature = "_deflate-any")] => CompressionMethod::Deflated,
235+
_ => CompressionMethod::Stored
236+
}
236237
}
237238
}
238239

src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
//!
3232
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
3333
#![warn(missing_docs)]
34-
#![allow(unexpected_cfgs)] // Needed for cfg(fuzzing) on nightly as of 2024-05-06
34+
#![allow(unexpected_cfgs)] // Needed for cfg(fuzzing)
3535
pub use crate::compression::{CompressionMethod, SUPPORTED_COMPRESSION_METHODS};
3636
pub use crate::read::HasZipMetadata;
3737
pub use crate::read::ZipArchive;
@@ -71,3 +71,6 @@ zip = \"="]
7171
#[doc = "\"\n\
7272
```"]
7373
pub mod unstable;
74+
75+
#[doc(hidden)]
76+
pub mod macros;

src/macros.rs

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
//! Macros used internally.
2+
//!
3+
//! These may technically be exported, but that's only to make them available to internal
4+
//! project dependencies. The `#[doc(hidden)]` mark indicates that these are not stable or supported
5+
//! APIs, and should not be relied upon by external dependees.
6+
7+
/// The single macro export of the [`cfg-if`](https://docs.rs/cfg-if) crate.
8+
///
9+
/// It is packaged here to avoid pulling in another dependency. The stdlib does the same[^1].
10+
///
11+
/// [^1]: https://github.com/rust-lang/rust/blob/a2db9280539229a3b8a084a09886670a57bc7e9c/library/compiler-builtins/libm/src/math/support/macros.rs#L1
12+
#[doc(hidden)]
13+
#[macro_export]
14+
macro_rules! cfg_if {
15+
// match if/else chains with a final `else`
16+
(
17+
$(
18+
if #[cfg( $i_meta:meta )] { $( $i_tokens:tt )* }
19+
) else+
20+
else { $( $e_tokens:tt )* }
21+
) => {
22+
$crate::cfg_if! {
23+
@__items () ;
24+
$(
25+
(( $i_meta ) ( $( $i_tokens )* )) ,
26+
)+
27+
(() ( $( $e_tokens )* )) ,
28+
};
29+
};
30+
31+
// match if/else chains lacking a final `else`
32+
(
33+
if #[cfg( $i_meta:meta )] { $( $i_tokens:tt )* }
34+
$(
35+
else if #[cfg( $e_meta:meta )] { $( $e_tokens:tt )* }
36+
)*
37+
) => {
38+
$crate::cfg_if! {
39+
@__items () ;
40+
(( $i_meta ) ( $( $i_tokens )* )) ,
41+
$(
42+
(( $e_meta ) ( $( $e_tokens )* )) ,
43+
)*
44+
};
45+
};
46+
47+
// Internal and recursive macro to emit all the items
48+
//
49+
// Collects all the previous cfgs in a list at the beginning, so they can be
50+
// negated. After the semicolon are all the remaining items.
51+
(@__items ( $( $_:meta , )* ) ; ) => {};
52+
(
53+
@__items ( $( $no:meta , )* ) ;
54+
(( $( $yes:meta )? ) ( $( $tokens:tt )* )) ,
55+
$( $rest:tt , )*
56+
) => {
57+
// Emit all items within one block, applying an appropriate #[cfg]. The
58+
// #[cfg] will require all `$yes` matchers specified and must also negate
59+
// all previous matchers.
60+
#[cfg(all(
61+
$( $yes , )?
62+
not(any( $( $no ),* ))
63+
))]
64+
$crate::cfg_if! { @__identity $( $tokens )* }
65+
66+
// Recurse to emit all other items in `$rest`, and when we do so add all
67+
// our `$yes` matchers to the list of `$no` matchers as future emissions
68+
// will have to negate everything we just matched as well.
69+
$crate::cfg_if! {
70+
@__items ( $( $no , )* $( $yes , )? ) ;
71+
$( $rest , )*
72+
};
73+
};
74+
75+
// Internal macro to make __apply work out right for different match types,
76+
// because of how macros match/expand stuff.
77+
(@__identity $( $tokens:tt )* ) => {
78+
$( $tokens )*
79+
};
80+
}
81+
82+
/// Similar to [`cfg_if`](cfg_if), but accepts a list of expressions, and generates an internal
83+
/// closure to return each value.
84+
///
85+
/// The main reason this is necessary is because attaching `#[cfg(...)]` annotations to certain
86+
/// types of statements requires a nightly feature, or `cfg_if` would be enough on its own. This
87+
/// macro's restricted interface allows it to generate a closure as a circumlocution that is legal
88+
/// on stable rust.
89+
///
90+
/// Note that any `return` operation within the expressions provided to this macro will apply to the
91+
/// generated closure, not the enclosing scope--it cannot be used to interfere with external
92+
/// control flow.
93+
///
94+
/// The generated closure is non-[`const`](const@keyword), so cannot be used inside `const` methods.
95+
#[doc(hidden)]
96+
#[macro_export]
97+
macro_rules! cfg_if_expr {
98+
// Match =>, chains, maybe with a final _ => catchall clause.
99+
(
100+
$( $ret_ty:ty : )?
101+
$(
102+
#[cfg( $i_meta:meta )] => $i_val:expr
103+
),+ ,
104+
_ => $rem_val:expr $(,)?
105+
) => {
106+
(|| $( -> $ret_ty )? {
107+
$crate::cfg_if_expr! {
108+
@__items ();
109+
$(
110+
(( $i_meta ) (
111+
#[allow(unreachable_code)]
112+
return $i_val ;
113+
)) ,
114+
)+
115+
(() (
116+
#[allow(unreachable_code)]
117+
return $rem_val ;
118+
)) ,
119+
}
120+
})()
121+
};
122+
// Match =>, chains *without* any _ => clause.
123+
(
124+
$( $ret_ty:ty : )?
125+
$(
126+
#[cfg( $i_meta:meta )] => $i_val:expr
127+
),+ $(,)?
128+
) => {
129+
(|| $( -> $ret_ty )? {
130+
$crate::cfg_if_expr! {
131+
@__items ();
132+
$(
133+
(( $i_meta ) (
134+
#[allow(unreachable_code)]
135+
return $i_val ;
136+
)) ,
137+
)+
138+
}
139+
})()
140+
};
141+
142+
(@__items ( $( $_:meta , )* ) ; ) => {};
143+
(
144+
@__items ( $( $no:meta , )* ) ;
145+
(( $( $yes:meta )? ) ( $( $tokens:tt )* )) ,
146+
$( $rest:tt , )*
147+
) => {
148+
#[cfg(all(
149+
$( $yes , )?
150+
not(any( $( $no ),* ))
151+
))]
152+
$crate::cfg_if_expr! { @__identity $( $tokens )* }
153+
154+
$crate::cfg_if_expr! {
155+
@__items ( $( $no , )* $( $yes , )? ) ;
156+
$( $rest , )*
157+
};
158+
};
159+
(@__identity $( $tokens:tt )* ) => {
160+
$( $tokens )*
161+
};
162+
}

0 commit comments

Comments
 (0)