Skip to content

Commit

Permalink
Add stress test with 2^16 variants (#90)
Browse files Browse the repository at this point in the history
This is very slow to compile, but should not crash the compiler (as it did before #89)

Closes #83
  • Loading branch information
illicitonion authored Jan 10, 2023
1 parent b5e85f6 commit 4cd978d
Show file tree
Hide file tree
Showing 8 changed files with 65,613 additions and 14 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/presubmit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ jobs:
command: build
args: --manifest-path=serde_example/Cargo.toml --target thumbv6m-none-eabi -p serde_example --lib --no-default-features
toolchain: ${{ matrix.toolchain }}
- name: Regression test for https://github.com/illicitonion/num_enum/issues/83
uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path=stress_tests/Cargo.toml
toolchain: ${{ matrix.toolchain }}
- name: Doc
uses: actions-rs/cargo@v1
with:
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["num_enum", "num_enum_derive", "renamed_num_enum", "serde_example"]
members = ["num_enum", "num_enum_derive", "renamed_num_enum", "serde_example", "stress_tests"]
# Exclude num_enum_derive because its useful doc comments import num_enum, which the crate doesn't do (because it would
# cause a circular dependency), so the doc tests don't actually compile.
default-members = ["num_enum", "renamed_num_enum", "serde_example"]
default-members = ["num_enum", "renamed_num_enum", "serde_example", "stress_tests"]
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: #[derive(FromPrimitive)] requires a variant marked with `#[default]`, `#[num_enum(default)]`, or `#[num_enum(catch_all)`
error: #[derive(num_enum::FromPrimitive)] requires enum to be exhaustive, or a variant marked with `#[default]`, `#[num_enum(default)]`, or `#[num_enum(catch_all)`
--> $DIR/exhaustive_enum.rs:1:10
|
1 | #[derive(num_enum::FromPrimitive)]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: #[derive(FromPrimitive)] requires a variant marked with `#[default]`, `#[num_enum(default)]`, or `#[num_enum(catch_all)`
error: #[derive(num_enum::FromPrimitive)] requires enum to be exhaustive, or a variant marked with `#[default]`, `#[num_enum(default)]`, or `#[num_enum(catch_all)`
--> $DIR/missing_default.rs:1:10
|
1 | #[derive(num_enum::FromPrimitive)]
Expand Down
47 changes: 37 additions & 10 deletions num_enum_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,23 @@ struct EnumInfo {
}

impl EnumInfo {
/// Returns whether the number of variants (ignoring defaults, catch-alls, etc) is the same as
/// the capacity of the repr.
fn is_naturally_exhaustive(&self) -> Result<bool> {
let repr_str = self.repr.to_string();
if !repr_str.is_empty() {
let suffix = repr_str
.strip_prefix('i')
.or_else(|| repr_str.strip_prefix('u'));
if let Some(suffix) = suffix {
if let Ok(bits) = suffix.parse::<u8>() {
return Ok(1 << bits == self.variants.len());
}
}
}
die!(self.repr.clone() => "Failed to parse repr into bit size");
}

fn has_default_variant(&self) -> bool {
self.default().is_some()
}
Expand Down Expand Up @@ -563,15 +580,25 @@ pub fn derive_from_primitive(input: TokenStream) -> TokenStream {
let enum_info: EnumInfo = parse_macro_input!(input);
let krate = Ident::new(&get_crate_name(), Span::call_site());

let catch_all_body = if let Some(default_ident) = enum_info.default() {
quote! { Self::#default_ident }
} else if let Some(catch_all_ident) = enum_info.catch_all() {
quote! { Self::#catch_all_ident(number) }
} else {
let span = Span::call_site();
let message =
"#[derive(FromPrimitive)] requires a variant marked with `#[default]`, `#[num_enum(default)]`, or `#[num_enum(catch_all)`";
return syn::Error::new(span, message).to_compile_error().into();
let is_naturally_exhaustive = enum_info.is_naturally_exhaustive();
let catch_all_body = match is_naturally_exhaustive {
Ok(is_naturally_exhaustive) => {
if is_naturally_exhaustive {
quote! { unreachable!("exhaustive enum") }
} else if let Some(default_ident) = enum_info.default() {
quote! { Self::#default_ident }
} else if let Some(catch_all_ident) = enum_info.catch_all() {
quote! { Self::#catch_all_ident(number) }
} else {
let span = Span::call_site();
let message =
"#[derive(num_enum::FromPrimitive)] requires enum to be exhaustive, or a variant marked with `#[default]`, `#[num_enum(default)]`, or `#[num_enum(catch_all)`";
return syn::Error::new(span, message).to_compile_error().into();
}
}
Err(err) => {
return err.to_compile_error().into();
}
};

let EnumInfo {
Expand Down Expand Up @@ -881,7 +908,7 @@ pub fn derive_default(stream: TokenStream) -> TokenStream {
None => {
let span = Span::call_site();
let message =
"#[derive(num_enum::Default)] requires a variant marked with `#[default]` or `#[num_enum(default)]`";
"#[derive(num_enum::Default)] requires enum to be exhaustive, or a variant marked with `#[default]` or `#[num_enum(default)]`";
return syn::Error::new(span, message).to_compile_error().into();
}
};
Expand Down
16 changes: 16 additions & 0 deletions stress_tests/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "stress_tests"
version = "0.1.0"
authors = [
"Daniel Wagner-Hall <dawagner@gmail.com>",
"Daniel Henry-Mantilla <daniel.henry.mantilla@gmail.com>",
"Vincent Esche <regexident@gmail.com>",
]
description = "Tests using large enums. Regression test for https://github.com/illicitonion/num_enum/issues/83."
edition = "2018"
repository = "https://github.com/illicitonion/num_enum"
publish = false

[dev-dependencies]
num_enum = { path = "../num_enum" }
trybuild = "1.0.31"
7 changes: 7 additions & 0 deletions stress_tests/tests/stress_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use std::path::Path;

#[test]
fn stress_test() {
let pass = trybuild::TestCases::new();
pass.pass(Path::new("tests/stress_tests/*.rs"));
}
Loading

0 comments on commit 4cd978d

Please sign in to comment.