Skip to content

Commit fcfc704

Browse files
Generate enum-typed setters and constructors for signals with value descriptions (#178)
- Updates the generated `new()` constructors and `set_<signal>()` setters to accept enums for signals that have an associated enum type (value description in the DBC). This mirrors the existing enum-typed getters, making the getter and setter APIs symmetrical. - Cleans up the formatting of some generated getter/setter function comments for readability and consistency. This is a breaking change for DBCs that have signals with value descriptions, but should make these signals much cleaner to handle. The best approach is to directly pass the enum-typed value into `set_<signal>()` and `new()`. Raw or undescribed values can still be set through the enum's `_Other` variant, e.g. `set_<signal>(SignalEnum::_Other(value))`. This PR should be stacked on top of #176. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 2d63e0a commit fcfc704

82 files changed

Lines changed: 4715 additions & 4271 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

fuzz/fuzz_targets/fuzz_target_1.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,14 @@ use cantools_messages::{
88
};
99

1010
fuzz_target!(|dbc_codegen_bar: can_messages::Bar| {
11-
let dbc_codegen_bar = can_messages::Bar::new(3, 2.0, 4, 5, false).unwrap();
11+
let dbc_codegen_bar = can_messages::Bar::new(
12+
3,
13+
2.0,
14+
can_messages::BarThree::_Other(4),
15+
can_messages::BarFour::_Other(5),
16+
can_messages::BarType::X0off,
17+
)
18+
.unwrap();
1219

1320
println!(
1421
"{} {} {} {} {}",

src/lib.rs

Lines changed: 86 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -372,14 +372,14 @@ impl Config<'_> {
372372

373373
self.render_attribute_structs(&mut w, msg, dbc)?;
374374

375-
writeln!(w, "/// Construct new {} from values", msg.name)?;
375+
writeln!(w, "/// Construct new '{}' from values", msg.name)?;
376376
let args = msg
377377
.signals
378378
.iter()
379379
.filter_map(|signal| {
380380
if matches!(signal.multiplexer_indicator, Plain | Multiplexor) {
381381
let field = signal.field_name();
382-
let typ = ValType::from_signal(signal);
382+
let typ = signal_pub_type(dbc, msg, signal);
383383
Some(format!("{field}: {typ}"))
384384
} else {
385385
None
@@ -426,7 +426,7 @@ impl Config<'_> {
426426
.render_signal(&mut w, signal, dbc, msg)
427427
.with_context(|| format!("write signal impl `{}`", signal.name))?,
428428
Multiplexor => {
429-
self.render_multiplexor_signal(&mut w, signal, msg)?;
429+
self.render_multiplexor_signal(&mut w, signal, dbc, msg)?;
430430
}
431431
MultiplexedSignal(_) | MultiplexorAndMultiplexedSignal(_) => {}
432432
}
@@ -470,7 +470,7 @@ impl Config<'_> {
470470
self.impl_defmt
471471
.fmt_cfg(&mut *w, |w| render_defmt_impl(w, msg))?;
472472
self.impl_arbitrary
473-
.fmt_cfg(&mut *w, |w| self.render_arbitrary(w, msg))?;
473+
.fmt_cfg(&mut *w, |w| self.render_arbitrary(w, dbc, msg))?;
474474

475475
let enums_for_this_message = dbc.value_descriptions.iter().filter_map(|x| {
476476
if let ValueDescription::Signal {
@@ -685,7 +685,7 @@ impl Config<'_> {
685685
dbc: &Dbc,
686686
msg: &Message,
687687
) -> Result<()> {
688-
writeln!(w, "/// {}", signal.name)?;
688+
writeln!(w, "/// Get value of '{}'", signal.name)?;
689689
if let Some(comment) = dbc.signal_comment(msg.id, &signal.name) {
690690
writeln!(w, "///")?;
691691
for line in comment.trim().lines() {
@@ -756,7 +756,7 @@ impl Config<'_> {
756756
writeln!(w)?;
757757
}
758758

759-
writeln!(w, "/// Get raw value of {}", signal.name)?;
759+
writeln!(w, "/// Get raw value of '{}'", signal.name)?;
760760
writeln!(w, "///")?;
761761
writeln!(w, "/// - Start bit: {}", signal.start_bit)?;
762762
writeln!(w, "/// - Signal size: {} bits", signal.size)?;
@@ -774,15 +774,18 @@ impl Config<'_> {
774774
writeln!(w, "}}")?;
775775
writeln!(w)?;
776776

777-
self.render_set_signal(w, signal, msg)?;
777+
self.render_set_signal(w, signal, dbc, msg)?;
778778

779779
Ok(())
780780
}
781781

782-
fn render_set_signal(&self, w: &mut impl Write, signal: &Signal, msg: &Message) -> Result<()> {
783-
writeln!(w, "/// Set value of {}", signal.name)?;
784-
writeln!(w, "#[inline(always)]")?;
785-
782+
fn render_set_signal(
783+
&self,
784+
w: &mut impl Write,
785+
signal: &Signal,
786+
dbc: &Dbc,
787+
msg: &Message,
788+
) -> Result<()> {
786789
// To avoid accidentally changing the multiplexor value without changing
787790
// the signals accordingly this fn is kept private for multiplexors.
788791
let visibility = if signal.multiplexer_indicator == Multiplexor {
@@ -793,42 +796,60 @@ impl Config<'_> {
793796

794797
let field = signal.field_name();
795798
let typ = ValType::from_signal(signal);
799+
let param_type = signal_pub_type(dbc, msg, signal);
800+
let is_enum_backed = param_type != typ.to_string();
801+
802+
writeln!(w, "/// Set value of '{}'", signal.name)?;
803+
writeln!(w, "#[inline(always)]")?;
796804
writeln!(
797805
w,
798-
"{visibility}fn set_{field}(&mut self, value: {typ}) -> Result<(), CanError> {{",
806+
"{visibility}fn set_{field}(&mut self, value: {param_type}) -> Result<(), CanError> {{",
799807
)?;
800-
801808
{
802809
let mut w = PadAdapter::wrap(w);
810+
// Enum-backed signals accept the value-description enum; convert it to
811+
// the raw primitive before range checks and packing.
812+
if is_enum_backed {
813+
writeln!(w, "let value = {typ}::from(value);")?;
814+
}
815+
self.render_set_signal_body(&mut w, signal, msg)?;
816+
}
817+
writeln!(w, "}}")?;
818+
writeln!(w)?;
803819

804-
if signal.size != 1 {
805-
if let FeatureConfig::Gated(gate) = self.check_ranges {
806-
writeln!(w, r"#[cfg(feature = {gate:?})]")?;
807-
}
820+
Ok(())
821+
}
808822

809-
if let FeatureConfig::Gated(..) | FeatureConfig::Always = self.check_ranges {
810-
let typ = ValType::from_signal(signal);
811-
let min = signal.min;
812-
let max = signal.max;
813-
writeln!(w, r"if value < {min}_{typ} || {max}_{typ} < value {{")?;
823+
fn render_set_signal_body(
824+
&self,
825+
w: &mut impl Write,
826+
signal: &Signal,
827+
msg: &Message,
828+
) -> Result<()> {
829+
if signal.size != 1 {
830+
if let FeatureConfig::Gated(gate) = self.check_ranges {
831+
writeln!(w, r"#[cfg(feature = {gate:?})]")?;
832+
}
814833

815-
{
816-
let mut w = PadAdapter::wrap(&mut w);
817-
let typ = msg.type_name();
818-
writeln!(
819-
w,
820-
r"return Err(CanError::ParameterOutOfRange {{ message_id: {typ}::MESSAGE_ID }});",
821-
)?;
822-
}
834+
if let FeatureConfig::Gated(..) | FeatureConfig::Always = self.check_ranges {
835+
let typ = ValType::from_signal(signal);
836+
let min = signal.min;
837+
let max = signal.max;
838+
writeln!(w, r"if value < {min}_{typ} || {max}_{typ} < value {{")?;
823839

824-
writeln!(w, r"}}")?;
840+
{
841+
let mut w = PadAdapter::wrap(&mut *w);
842+
let typ = msg.type_name();
843+
writeln!(
844+
w,
845+
r"return Err(CanError::ParameterOutOfRange {{ message_id: {typ}::MESSAGE_ID }});",
846+
)?;
825847
}
848+
849+
writeln!(w, r"}}")?;
826850
}
827-
signal_to_payload(&mut w, signal, msg).context("signal to payload")?;
828851
}
829-
830-
writeln!(w, "}}")?;
831-
writeln!(w)?;
852+
signal_to_payload(&mut *w, signal, msg).context("signal to payload")?;
832853

833854
Ok(())
834855
}
@@ -837,9 +858,10 @@ impl Config<'_> {
837858
&self,
838859
w: &mut impl Write,
839860
signal: &Signal,
861+
dbc: &Dbc,
840862
msg: &Message,
841863
) -> Result<()> {
842-
writeln!(w, "/// Get raw value of {}", signal.name)?;
864+
writeln!(w, "/// Get raw value of '{}'", signal.name)?;
843865
writeln!(w, "///")?;
844866
writeln!(w, "/// - Start bit: {}", signal.start_bit)?;
845867
writeln!(w, "/// - Signal size: {} bits", signal.size)?;
@@ -906,7 +928,7 @@ impl Config<'_> {
906928
}
907929
writeln!(w, "}}")?;
908930

909-
self.render_set_signal(w, signal, msg)?;
931+
self.render_set_signal(w, signal, dbc, msg)?;
910932

911933
for switch_index in multiplexer_indexes {
912934
render_set_signal_multiplexer(w, signal, msg, switch_index)?;
@@ -999,7 +1021,7 @@ fn render_set_signal_multiplexer(
9991021
msg: &Message,
10001022
switch_index: u64,
10011023
) -> Result<()> {
1002-
writeln!(w, "/// Set value of {}", multiplexor.name)?;
1024+
writeln!(w, "/// Set value of '{}'", multiplexor.name)?;
10031025
writeln!(w, "#[inline(always)]")?;
10041026
writeln!(
10051027
w,
@@ -1070,6 +1092,21 @@ fn le_start_end_bit(signal: &Signal, msg: &Message) -> Result<(u64, u64)> {
10701092
Ok((start_bit, end_bit))
10711093
}
10721094

1095+
/// Public type of a signal as seen by `new()` and `set_*`. This is the
1096+
/// enum when the signal has one (and isn't the multiplexor selector), otherwise
1097+
/// it is the raw primitive type.
1098+
fn signal_pub_type(dbc: &Dbc, msg: &Message, signal: &Signal) -> String {
1099+
if signal.multiplexer_indicator != Multiplexor
1100+
&& dbc
1101+
.value_descriptions_for_signal(msg.id, &signal.name)
1102+
.is_some()
1103+
{
1104+
enum_name(msg, signal)
1105+
} else {
1106+
ValType::from_signal(signal).to_string()
1107+
}
1108+
}
1109+
10731110
fn signal_from_payload(w: &mut impl Write, signal: &Signal, msg: &Message) -> Result<()> {
10741111
writeln!(w, r"let signal = {};", read_fn(signal, msg)?)?;
10751112
writeln!(w)?;
@@ -1450,7 +1487,7 @@ impl Config<'_> {
14501487
Ok(())
14511488
}
14521489

1453-
fn render_arbitrary(&self, w: &mut impl Write, msg: &Message) -> Result<()> {
1490+
fn render_arbitrary(&self, w: &mut impl Write, dbc: &Dbc, msg: &Message) -> Result<()> {
14541491
writeln!(w, "{ALLOW_LINTS}")?;
14551492
self.write_allow_dead_code(w)?;
14561493
let typ = msg.type_name();
@@ -1481,7 +1518,16 @@ impl Config<'_> {
14811518

14821519
let args: Vec<String> = filtered_signals
14831520
.iter()
1484-
.map(|signal| signal.field_name())
1521+
.map(|signal| {
1522+
let field = signal.field_name();
1523+
let is_enum_backed = signal_pub_type(dbc, msg, signal)
1524+
!= ValType::from_signal(signal).to_string();
1525+
if is_enum_backed {
1526+
format!("{}::_Other({field})", enum_name(msg, signal))
1527+
} else {
1528+
field
1529+
}
1530+
})
14851531
.collect();
14861532

14871533
writeln!(

testing/can-messages/tests/all.rs

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
)]
66

77
use can_messages::{
8-
Amet, Bar, BarThree, CanError, Foo, LargerIntsWithOffsets, MsgExtendedId, MultiplexTest,
9-
MultiplexTestMultiplexorIndex, MultiplexTestMultiplexorM0, NegativeFactorTest,
8+
Amet, Bar, BarFour, BarThree, BarType, CanError, Foo, LargerIntsWithOffsets, MsgExtendedId,
9+
MultiplexTest, MultiplexTestMultiplexorIndex, MultiplexTestMultiplexorM0, NegativeFactorTest,
1010
TruncatedBeSignal, TruncatedLeSignal,
1111
};
1212
use embedded_can::{ExtendedId, Id, StandardId};
1313

1414
#[test]
1515
fn check_range_value_error() {
16-
let result = Bar::new(1, 2.0, 3, 4, true);
16+
let result = Bar::new(1, 2.0, BarThree::Onest, BarFour::_Other(4), BarType::X1on);
1717
assert_eq!(
1818
result.unwrap_err(),
1919
CanError::ParameterOutOfRange {
@@ -24,7 +24,7 @@ fn check_range_value_error() {
2424

2525
#[test]
2626
fn check_range_value_valid() {
27-
let result = Bar::new(1, 2.0, 3, 3, true);
27+
let result = Bar::new(1, 2.0, BarThree::Onest, BarFour::Onest, BarType::X1on);
2828
assert!(result.is_ok());
2929
}
3030

@@ -141,14 +141,14 @@ fn offset_integers() {
141141

142142
#[test]
143143
fn debug_impl() {
144-
let result = Bar::new(1, 2.0, 3, 3, true).unwrap();
144+
let result = Bar::new(1, 2.0, BarThree::Onest, BarFour::Onest, BarType::X1on).unwrap();
145145
let dbg = format!("{result:?}");
146146
assert_eq!(&dbg, "Bar([5, 94, 0, 64, 0, 0, 0, 0])");
147147
}
148148

149149
#[test]
150150
fn debug_alternative_impl() {
151-
let result = Bar::new(1, 2.0, 3, 3, true).unwrap();
151+
let result = Bar::new(1, 2.0, BarThree::Onest, BarFour::Onest, BarType::X1on).unwrap();
152152
let dbg = format!("{result:#?}");
153153
assert_eq!(
154154
&dbg,
@@ -162,6 +162,27 @@ fn from_enum_into_raw() {
162162
assert_eq!(raw, 3);
163163
}
164164

165+
#[test]
166+
fn enum_setters_and_getters_are_symmetrical() {
167+
let mut bar = Bar::new(1, 2.0, BarThree::Onest, BarFour::Onest, BarType::X1on).unwrap();
168+
169+
// The setter accepts the enum directly.
170+
bar.set_three(BarThree::Oner).unwrap();
171+
assert_eq!(bar.three(), BarThree::Oner);
172+
173+
// Raw / undescribed values remain settable via the `_Other` variant.
174+
bar.set_three(BarThree::_Other(3)).unwrap();
175+
assert_eq!(bar.three(), BarThree::Onest);
176+
177+
// Out-of-range raw values still produce an error.
178+
assert_eq!(
179+
bar.set_three(BarThree::_Other(8)),
180+
Err(CanError::ParameterOutOfRange {
181+
message_id: Id::Standard(StandardId::new(512).unwrap())
182+
})
183+
);
184+
}
185+
165186
#[test]
166187
fn negative_factor() {
167188
assert_eq!(

testing/cantools-messages/src/lib.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,14 @@ use msg_bindings::*;
1616

1717
#[test]
1818
fn pack_message() {
19-
let dbc_codegen_bar = can_messages::Bar::new(3, 2.0, 4, 2, false).unwrap();
19+
let dbc_codegen_bar = can_messages::Bar::new(
20+
3,
21+
2.0,
22+
can_messages::BarThree::_Other(4),
23+
can_messages::BarFour::Oner,
24+
can_messages::BarType::X0off,
25+
)
26+
.unwrap();
2027
let one = unsafe { example_bar_one_encode(3.0) };
2128
let two = unsafe { example_bar_two_encode(2.0) };
2229
let three = unsafe { example_bar_three_encode(4.0) };
@@ -61,7 +68,8 @@ fn pack_message_signed_positive() {
6168

6269
#[test]
6370
fn pack_big_endian_signal_with_start_bit_zero() {
64-
let dbc_codegen_bar = can_messages::Dolor::new(0.5).unwrap();
71+
let dbc_codegen_bar =
72+
can_messages::Dolor::new(can_messages::DolorOneFloat::_Other(0.5)).unwrap();
6573
let one_float = unsafe { example_dolor_one_float_encode(0.5) };
6674

6775
let dolor = example_dolor_t { one_float };

0 commit comments

Comments
 (0)