Skip to content
Draft
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
81 changes: 70 additions & 11 deletions Cargo.lock

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

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ can-dbc = "8.0.1"
embedded-can = "0.4.1"
heck = "0.5.0"
prettyplease = "0.2.37"
quote = "1.0"
proc-macro2 = "1.0.92"
quote = "1.0.44"
syn = "2.0.114"
template-quote = "0.4.2"
typed-builder = "0.23.0"

[dev-dependencies]
Expand All @@ -48,6 +50,7 @@ unused_qualifications = "warn"
[lints.clippy]
cargo = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }
multiple_crate_versions = "allow"
cast_possible_truncation = "allow"
cast_precision_loss = "allow"
missing_errors_doc = "allow"
Expand Down
2 changes: 1 addition & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ bless-generate: (cargo-install 'cargo-insta')
# Generate all snapshots, including forced (.gitignore-d) ones
bless-generate-all:
rm -rf tests-snapshots
FORCE_INSTA=1 {{just}} bless-generate
FORCE_INSTA=1 {{just}} bless-generate bless-compile

# Build the project
build:
Expand Down
52 changes: 30 additions & 22 deletions src/feature_config.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use std::fmt::Display;
use std::io::Write;

use anyhow::{Error, Result};
use proc_macro2::TokenStream;
use template_quote::quote;

/// Configuration for including features in the code generator.
///
Expand All @@ -20,31 +18,41 @@ pub enum FeatureConfig<'a> {
}

impl FeatureConfig<'_> {
pub(crate) fn fmt_attr(&self, w: &mut impl Write, attr: impl Display) -> Result<()> {
/// Generate an attribute token stream (like `#[derive(Debug)]`).
/// The closure is only called when the feature is enabled, so expensive codegen can be skipped when `Never`.
pub(crate) fn attr<F>(&self, f: F) -> TokenStream
where
F: FnOnce() -> TokenStream,
{
match self {
FeatureConfig::Always => writeln!(w, "#[{attr}]")?,
FeatureConfig::Gated(gate) => writeln!(w, "#[cfg_attr(feature = {gate:?}, {attr})]")?,
FeatureConfig::Never => {}
FeatureConfig::Always => {
let tokens = f();
quote! { #[#tokens] }
}
FeatureConfig::Gated(gate) => {
let tokens = f();
quote! { #[cfg_attr(feature = #gate, #tokens)] }
}
FeatureConfig::Never => quote! {},
}
Ok(())
}

pub(crate) fn fmt_cfg<W: Write, E: Into<Error>>(
&self,
mut w: W,
f: impl FnOnce(W) -> Result<(), E>,
) -> Result<()> {
/// Generate a token stream optionally wrapped in a cfg attribute.
/// The closure is only called when the feature is enabled, so expensive codegen can be skipped when `Never`.
pub(crate) fn if_cfg<F>(&self, f: F) -> TokenStream
where
F: FnOnce() -> TokenStream,
{
match self {
// If config is Never, return immediately without calling `f`
FeatureConfig::Never => return Ok(()),
// If config is Gated, prepend `f` with a cfg guard
FeatureConfig::Always => f(),
FeatureConfig::Gated(gate) => {
writeln!(w, "#[cfg(feature = {gate:?})]")?;
let tokens = f();
quote! {
#[cfg(feature = #gate)]
#tokens
}
}
// Otherwise, just call `f`
FeatureConfig::Always => {}
FeatureConfig::Never => quote! {},
}

f(w).map_err(Into::into)
}
}
Loading
Loading