From 5212a483fe42b5625b3444286a1abd20eb0013bf Mon Sep 17 00:00:00 2001 From: Justin Kovacich Date: Wed, 20 May 2026 12:18:27 -0400 Subject: [PATCH] fix(derive): make example doctests actually compile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The doctests on `Identifier`-derive were aspirational pseudocode — they imported types from `uds_protocol`, but `uds_protocol_derive` had no `[dev-dependencies]` entry at all, so the snippets failed their `cargo test --doc` run with "unresolved import `uds_protocol`". Fixing the imports also surfaced several semantic issues in the example code itself: 1. The example imported `Identifier` as both the trait (from `uds_protocol::traits`) and the derive macro (re-exported from `uds_protocol_derive`), tripping E0252 on the macro-namespace collision. `uds_protocol::Identifier` brings both into scope at once (trait in type namespace, macro in macro namespace) — drop the duplicate `use uds_protocol_derive::Identifier`. 2. The enum example used `Serialize` in its `#[derive(...)]` list but `UDSRoutineIdentifier` doesn't implement `Serialize`, so the wrapping `derive` failed. Dropped — the example is about `Identifier`, not serde, and the serde gating was incidental noise. 3. The fallthrough arm used `UDSRoutineIdentifier::try_from(value)?`, but `UDSRoutineIdentifier` has a *total* `impl From` (every u16 maps to one of its variants, including reserved ranges) — so `try_from` returns `Result<_, Infallible>` and the `?` couldn't convert to `Error`. Swapped to the infallible `::from(value)`. 4. The struct example previously named the type `ProtocolIdentifier`, which collides with the existing `uds_protocol::ProtocolIdentifier` re-export at `lib.rs:13`. Renamed to `WrappedIdentifier` to avoid shadowing in the doctest namespace, and added the matching `TryFrom` / `From<_> for u16` impls that the enum example demonstrates — keeping the illustrative pattern intact across both examples. Added `[dev-dependencies]` entry pointing back at `uds_protocol`. This forms a logical cycle (the crate `uds_protocol` already depends on `uds_protocol_derive`), but cargo permits dev-dep cycles because dev-deps are only resolved when building *this* crate's tests, not when something else consumes the proc-macro. Same pattern `serde_derive` uses to dev-dep `serde`. Local: `cargo test --doc -p uds_protocol_derive` → 2 passed, was 2 failed. Co-Authored-By: Claude Opus 4.7 (1M context) --- uds_protocol_derive/Cargo.toml | 9 ++++ uds_protocol_derive/src/lib.rs | 85 ++++++++++++++++++++++++---------- 2 files changed, 70 insertions(+), 24 deletions(-) diff --git a/uds_protocol_derive/Cargo.toml b/uds_protocol_derive/Cargo.toml index cf79e1a..fbdbd2e 100644 --- a/uds_protocol_derive/Cargo.toml +++ b/uds_protocol_derive/Cargo.toml @@ -17,3 +17,12 @@ proc-macro = true proc-macro2 = { version="1", features = ["proc-macro"] } quote = "1" syn = "2" + +# Dev-deps only — used by the doctest examples in `src/lib.rs`. The +# crate is a dependency of `uds_protocol`, so depending back on +# `uds_protocol` here forms a *logical* cycle, but cargo permits this +# because dev-deps are only resolved when building *this crate's own* +# tests, not when something else consumes the proc-macro. Same pattern +# `serde_derive` uses to dev-dep `serde`. +[dev-dependencies] +uds_protocol = { path = ".." } diff --git a/uds_protocol_derive/src/lib.rs b/uds_protocol_derive/src/lib.rs index 64d4a94..556eee2 100644 --- a/uds_protocol_derive/src/lib.rs +++ b/uds_protocol_derive/src/lib.rs @@ -6,48 +6,85 @@ use syn::{DeriveInput, parse_macro_input}; /// Derive Identifier and implement `TryFrom`, `Into` traits /// +/// The derive itself emits only `impl Identifier for #name {}` — it +/// asserts trait conformance and nothing else. Callers supply their +/// own `TryFrom` / `From<#name> for u16` so the wire-format +/// dispatch matches the application's own identifier-space layout. +/// The examples below show that hand-written pattern alongside the +/// derive. +/// /// ## Enum Example +/// +/// Adds a custom `VerifySignature` routine identifier on top of the +/// standard ISO UDS set via a fallthrough variant. The +/// `UDSRoutineIdentifier` type has a total `From` (every u16 +/// maps to one of its variants, including reserved ranges), so the +/// fallthrough arm uses the infallible `::from` rather than `?`. +/// /// ```rust -/// use uds_protocol::{UDSRoutineIdentifier, Identifier, Error}; +/// use uds_protocol::{Error, Identifier, UDSRoutineIdentifier}; /// -/// #[derive(Clone, Copy, Identifier, Serialize)] +/// #[derive(Clone, Copy, Identifier)] /// pub enum MyRoutineIdentifier { -/// /// 0x0101 (example) -/// VerifySignature, +/// /// 0x0101 (example) +/// VerifySignature, /// -/// // Standard ISO UDS routine fallthrough -/// UDSRoutineIdentifier(UDSRoutineIdentifier), +/// /// Standard ISO UDS routine fallthrough. +/// UDSRoutineIdentifier(UDSRoutineIdentifier), /// } /// /// impl TryFrom for MyRoutineIdentifier { -/// type Error = uds_protocol::Error; -/// fn try_from(value: u16) -> Result { -/// match value { -/// 0x0101 => Ok(MyRoutineIdentifier::VerifySignature), -/// _ => Ok(MyRoutineIdentifier::UDSRoutineIdentifier(UDSRoutineIdentifier::try_from(value)?)), -/// } -/// } +/// type Error = Error; +/// fn try_from(value: u16) -> Result { +/// match value { +/// 0x0101 => Ok(MyRoutineIdentifier::VerifySignature), +/// // `UDSRoutineIdentifier::from` is total over u16, +/// // so the fallthrough never fails. +/// _ => Ok(MyRoutineIdentifier::UDSRoutineIdentifier( +/// UDSRoutineIdentifier::from(value), +/// )), +/// } +/// } /// } /// /// impl From for u16 { -/// fn from(value: MyRoutineIdentifier) -> Self { -/// match value { -/// MyRoutineIdentifier::VerifySignature => 0x0101, -/// MyRoutineIdentifier::UDSRoutineIdentifier(identifier) => u16::from(identifier), -/// } -/// } +/// fn from(value: MyRoutineIdentifier) -> Self { +/// match value { +/// MyRoutineIdentifier::VerifySignature => 0x0101, +/// MyRoutineIdentifier::UDSRoutineIdentifier(identifier) => u16::from(identifier), +/// } +/// } /// } /// ``` /// /// ## Struct definition Example -/// Structs can only contain a single value to be used as an identifier to constrain the type +/// +/// Structs can only contain a single value to be used as an identifier +/// to constrain the type. `UDSIdentifier` has a real `TryFrom` +/// (returning `uds_protocol::Error` for out-of-range values), so the +/// struct wrapper's own `TryFrom` propagates via `?`. +/// /// ```rust +/// use uds_protocol::{Error, Identifier, UDSIdentifier}; +/// +/// #[derive(Clone, Copy, Identifier)] +/// pub struct WrappedIdentifier { +/// identifier: UDSIdentifier, +/// } /// -/// use uds_protocol::{UDSIdentifier, Identifier}; +/// impl TryFrom for WrappedIdentifier { +/// type Error = Error; +/// fn try_from(value: u16) -> Result { +/// Ok(WrappedIdentifier { +/// identifier: UDSIdentifier::try_from(value)?, +/// }) +/// } +/// } /// -/// #[derive(Clone, Copy, Identifier, Serialize)] -/// pub struct ProtocolIdentifier { -/// identifier: UDSIdentifier, +/// impl From for u16 { +/// fn from(value: WrappedIdentifier) -> Self { +/// u16::from(value.identifier) +/// } /// } /// ``` ///