Skip to content

Commit 65e4df9

Browse files
committed
Add unwrap macro.
1 parent 6467edf commit 65e4df9

8 files changed

Lines changed: 162 additions & 14 deletions

File tree

firmware/qemu/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ test = false
4848
name = "double-write"
4949
test = false
5050

51+
[[bin]]
52+
name = "unwrap"
53+
test = false
54+
5155
[[bin]]
5256
name = "alloc"
5357
test = false

firmware/qemu/src/bin/unwrap.out

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
0.000000 INFO The answer is 42
2+
0.000000 ERROR panicked at 'unwrap failed: x'
3+
error: `Bar`
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
0.000000 INFO The answer is 42
2+
0.000000 ERROR panicked at 'unwrap failed: x'
3+
error: `Bar`

firmware/qemu/src/bin/unwrap.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#![no_std]
2+
#![no_main]
3+
4+
use cortex_m_rt::entry;
5+
use cortex_m_semihosting::debug;
6+
7+
use defmt_semihosting as _; // global logger
8+
9+
#[derive(defmt::Format)]
10+
enum Error {
11+
Bar,
12+
}
13+
14+
#[entry]
15+
fn main() -> ! {
16+
let x: Result<u32, Error> = Ok(42);
17+
defmt::info!("The answer is {:?}", defmt::unwrap!(x));
18+
let x: Result<u32, Error> = Err(Error::Bar);
19+
defmt::info!("The answer is {:?}", defmt::unwrap!(x));
20+
21+
loop {
22+
debug::exit(debug::EXIT_SUCCESS)
23+
}
24+
}
25+
26+
// like `panic-semihosting` but doesn't print to stdout (that would corrupt the defmt stream)
27+
#[cfg(target_os = "none")]
28+
#[panic_handler]
29+
fn panic(_: &core::panic::PanicInfo) -> ! {
30+
loop {
31+
debug::exit(debug::EXIT_SUCCESS)
32+
}
33+
}

firmware/qemu/test.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ test "assert"
1616
test "assert-eq"
1717
test "assert-ne"
1818
test "double-write"
19+
test "unwrap"
1920
if rustc -V | grep nightly; then
2021
test "alloc" "alloc"
2122
fi

macros/src/lib.rs

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -653,20 +653,7 @@ fn assert_binop(ts: TokenStream, binop: BinOp) -> TokenStream {
653653
};
654654

655655
for val in vals {
656-
let mut segments = Punctuated::new();
657-
segments.push(PathSegment {
658-
ident: Ident2::new(*val, Span2::call_site()),
659-
arguments: PathArguments::None,
660-
});
661-
662-
log_args.push(Expr::Path(ExprPath {
663-
attrs: vec![],
664-
qself: None,
665-
path: Path {
666-
leading_colon: None,
667-
segments,
668-
},
669-
}));
656+
log_args.push(ident_expr(*val));
670657
}
671658

672659
let log_stmt = match binop {
@@ -752,6 +739,71 @@ pub fn debug_assert_ne_(ts: TokenStream) -> TokenStream {
752739
.into()
753740
}
754741

742+
#[proc_macro]
743+
pub fn unwrap(ts: TokenStream) -> TokenStream {
744+
let assert = parse_macro_input!(ts as Assert);
745+
746+
let condition = assert.condition;
747+
let log_stmt = if let Some(args) = assert.args {
748+
log(
749+
Level::Error,
750+
FormatArgs {
751+
litstr: LitStr::new(
752+
&format!("panicked at '{}'", args.litstr.value()),
753+
Span2::call_site(),
754+
),
755+
rest: args.rest,
756+
},
757+
)
758+
} else {
759+
let mut log_args = Punctuated::new();
760+
log_args.push(ident_expr("_unwrap_err"));
761+
762+
log(
763+
Level::Error,
764+
FormatArgs {
765+
litstr: LitStr::new(
766+
&format!(
767+
"panicked at 'unwrap failed: {}'
768+
error: `{{:?}}`",
769+
escape_expr(&condition)
770+
),
771+
Span2::call_site(),
772+
),
773+
rest: Some((syn::token::Comma::default(), log_args)),
774+
},
775+
)
776+
};
777+
778+
quote!(
779+
match defmt::export::into_result(#condition) {
780+
::core::result::Result::Ok(res) => res,
781+
::core::result::Result::Err(_unwrap_err) => {
782+
#log_stmt;
783+
defmt::export::panic()
784+
}
785+
}
786+
)
787+
.into()
788+
}
789+
790+
fn ident_expr(name: &str) -> Expr {
791+
let mut segments = Punctuated::new();
792+
segments.push(PathSegment {
793+
ident: Ident2::new(name, Span2::call_site()),
794+
arguments: PathArguments::None,
795+
});
796+
797+
Expr::Path(ExprPath {
798+
attrs: vec![],
799+
qself: None,
800+
path: Path {
801+
leading_colon: None,
802+
segments,
803+
},
804+
})
805+
}
806+
755807
fn escape_expr(expr: &Expr) -> String {
756808
let q = quote!(#expr);
757809
q.to_string().replace("{", "{{").replace("}", "}}")

src/export.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ pub fn istr(address: usize) -> Str {
7171
}
7272

7373
mod sealed {
74+
use crate::{Format, Formatter};
75+
use defmt_macros::internp;
76+
7477
pub trait Truncate<U> {
7578
fn truncate(self) -> U;
7679
}
@@ -137,12 +140,54 @@ mod sealed {
137140
self
138141
}
139142
}
143+
144+
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
145+
pub struct NoneError;
146+
147+
impl Format for NoneError {
148+
fn format(&self, fmt: &mut Formatter) {
149+
if fmt.needs_tag() {
150+
let t = internp!("Unwrap of a None option value");
151+
fmt.u8(&t);
152+
}
153+
}
154+
}
155+
156+
pub trait IntoResult {
157+
type Ok;
158+
type Error;
159+
fn into_result(self) -> Result<Self::Ok, Self::Error>;
160+
}
161+
162+
impl<T> IntoResult for Option<T> {
163+
type Ok = T;
164+
type Error = NoneError;
165+
166+
#[inline]
167+
fn into_result(self) -> Result<T, NoneError> {
168+
self.ok_or(NoneError)
169+
}
170+
}
171+
172+
impl<T, E> IntoResult for Result<T, E> {
173+
type Ok = T;
174+
type Error = E;
175+
176+
#[inline]
177+
fn into_result(self) -> Self {
178+
self
179+
}
180+
}
140181
}
141182

142183
pub fn truncate<T>(x: impl sealed::Truncate<T>) -> T {
143184
x.truncate()
144185
}
145186

187+
pub fn into_result<T: sealed::IntoResult>(x: T) -> Result<T::Ok, T::Error> {
188+
x.into_result()
189+
}
190+
146191
/// For testing purposes
147192
#[cfg(target_arch = "x86_64")]
148193
pub fn panic() -> ! {

src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,13 @@ pub use defmt_macros::todo_ as unimplemented;
115115
/// [the manual]: https://defmt.ferrous-systems.com/macros.html
116116
pub use defmt_macros::panic_ as panic;
117117

118+
/// todo
119+
///
120+
/// If used, the format string must follow the defmt syntax (documented in [the manual])
121+
///
122+
/// [the manual]: https://defmt.ferrous-systems.com/macros.html
123+
pub use defmt_macros::unwrap;
124+
118125
/// Overrides the panicking behavior of `defmt::panic!`
119126
///
120127
/// By default, `defmt::panic!` calls `core::panic!` after logging the panic message using `defmt`.

0 commit comments

Comments
 (0)