From 0e597dd21d527d43848750d3e8467d676cc1ae2c Mon Sep 17 00:00:00 2001
From: Mike Boutin <mike.boutin@gmail.com>
Date: Mon, 5 Oct 2020 21:05:41 -0400
Subject: [PATCH] Allow new units to be defined using `unit!` outside of
 `quantity!`.

The new `unit!` macro allows for new units to be defined outside of the
`quantity!` macro. Units defined using this macro will not be included
in the quantity unit enum or associated functions, or in the `FromStr`
implementation. Using this macro will create submodules for the
underlying storage types that are enabled (e.g. `mod f32`).

Resolves #173.
---
 Cargo.toml       |   4 +
 README.md        |   2 +
 examples/unit.rs |  34 ++++++
 src/lib.rs       |   3 +
 src/quantity.rs  | 215 ++++++-------------------------------
 src/system.rs    |   4 +-
 src/unit.rs      | 274 +++++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 350 insertions(+), 186 deletions(-)
 create mode 100644 examples/unit.rs
 create mode 100644 src/unit.rs

diff --git a/Cargo.toml b/Cargo.toml
index 20b02cd4..61218894 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -91,3 +91,7 @@ required-features = ["f32"]
 [[example]]
 name = "si"
 required-features = ["f32", "si"]
+
+[[example]]
+name = "unit"
+required-features = ["f32", "si"]
diff --git a/README.md b/README.md
index e66b8e2c..a01b7e4e 100644
--- a/README.md
+++ b/README.md
@@ -66,6 +66,8 @@ See the [examples](examples) directory for more advanced usage:
    different set of base units. See the [Design](#design) section for implications of choosing
    different base units.
  * [mks.rs](examples/mks.rs) -- Shows how to create a custom system of quantities.
+ * [unit.rs](examples/unit.rs) -- Shows how to add new units to existing quantities in the
+   pre-build SI system.
 
 ## Features
 `uom` has multiple `Cargo` features for controlling available underlying storage types, the
diff --git a/examples/unit.rs b/examples/unit.rs
new file mode 100644
index 00000000..a3c4179b
--- /dev/null
+++ b/examples/unit.rs
@@ -0,0 +1,34 @@
+//! Example showing how to use the `unit!` macro to add new units to existing quantities.
+//!
+//! [Pull requests](https://github.com/iliekturtles/uom/pulls) for new units are always greatly
+//! appreciated.
+
+#[macro_use]
+extern crate uom;
+
+use uom::fmt::DisplayStyle::*;
+use uom::si::f32::*;
+use uom::si::length::meter;
+
+unit! {
+    system: uom::si;
+    quantity: uom::si::length;
+
+    @smoot: 1.702; "smoot", "smoot", "smoots";
+}
+
+fn main() {
+    let l1 = Length::new::<meter>(15.0);
+    let l2 = Length::new::<smoot>(1.0);
+
+    println!(
+        "{} = {}",
+        l1.into_format_args(meter, Abbreviation),
+        l1.into_format_args(smoot, Abbreviation)
+    );
+    println!(
+        "{} = {}",
+        l2.into_format_args(smoot, Abbreviation),
+        l2.into_format_args(meter, Abbreviation)
+    );
+}
diff --git a/src/lib.rs b/src/lib.rs
index 0125f5db..b30f2ca8 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -356,6 +356,9 @@ mod system;
 #[macro_use]
 mod quantity;
 
+#[macro_use]
+mod unit;
+
 #[cfg(feature = "si")]
 #[macro_use]
 pub mod si;
diff --git a/src/quantity.rs b/src/quantity.rs
index 3464b899..f278ad6b 100644
--- a/src/quantity.rs
+++ b/src/quantity.rs
@@ -99,42 +99,30 @@ macro_rules! quantity {
     (
         $(#[$quantity_attr:meta])* quantity: $quantity:ident; $description:expr;
         $(#[$dim_attr:meta])* dimension: $system:ident<$($dimension:ident),+>;
-        units {
-            $($(#[$unit_attr:meta])* @$unit:ident: $($conversion:expr),+;
-                $abbreviation:expr, $singular:expr, $plural:expr;)+
-        }
-    ) => {
-        quantity! {
-            $(#[$quantity_attr])* quantity: $quantity; $description;
-            $(#[$dim_attr])* dimension: $system<$($dimension),+>;
-            kind: dyn $crate::Kind;
-            units {
-                $($(#[$unit_attr])* @$unit: $($conversion),+; $abbreviation, $singular, $plural;)+
-            }
-        }
-    };
-    (
-        $(#[$quantity_attr:meta])* quantity: $quantity:ident; $description:expr;
-        $(#[$dim_attr:meta])* dimension: $system:ident<$($dimension:ident),+>;
-        kind: $kind:ty;
+        $(kind: $kind:ty;)?
         units {
             $($(#[$unit_attr:meta])* @$unit:ident: $($conversion:expr),+; $abbreviation:expr,
                 $singular:expr, $plural:expr;)+
         }
     ) => {
+        mod __system {
+            pub use super::super::*;
+        }
+
         $(#[$dim_attr])*
-        pub type Dimension = super::$system<$($crate::typenum::$dimension),+, $kind>;
+        pub type Dimension = __system::$system<$($crate::typenum::$dimension),+,
+            quantity!(@kind $($kind)?)>;
 
         $(#[$quantity_attr])*
         ///
         /// ## Generic Parameters
         /// * `U`: Base units.
         /// * `V`: Underlying storage type.
-        pub type $quantity<U, V> = super::Quantity<Dimension, U, V>;
+        pub type $quantity<U, V> = __system::Quantity<Dimension, U, V>;
 
         /// Marker trait to identify measurement units for the quantity. See
         /// [`Unit`](../trait.Unit.html).
-        pub trait Unit: super::Unit {}
+        pub trait Unit: __system::Unit {}
 
         /// Trait to identify [units][units] which have a [conversion factor][factor] for the
         /// `Quantity`. See [`Conversion<V>`](../../trait.Conversion.html).
@@ -150,131 +138,9 @@ macro_rules! quantity {
         {
         }
 
-        $(quantity!(@unit $(#[$unit_attr])* @$unit $plural);
-
-        impl super::Unit for $unit {
-            #[inline(always)]
-            fn abbreviation() -> &'static str {
-                $abbreviation
-            }
-
-            #[inline(always)]
-            fn singular() -> &'static str {
-                $singular
-            }
-
-            #[inline(always)]
-            fn plural() -> &'static str {
-                $plural
-            }
-        }
-
-        impl Unit for $unit {})+
-
-        storage_types! {
-            types: Float;
-
-            $(impl $crate::Conversion<V> for super::$unit {
-                type T = V;
-
-                #[inline(always)]
-                fn coefficient() -> Self::T {
-                    quantity!(@coefficient $($conversion),+)
-                }
-
-                #[inline(always)]
-                #[allow(unused_variables)]
-                fn constant(op: $crate::ConstantOp) -> Self::T {
-                    quantity!(@constant op $($conversion),+)
-                }
-            }
-
-            impl super::Conversion<V> for super::$unit {})+
-        }
-
-        storage_types! {
-            types: PrimInt, BigInt;
-            pub type T = $crate::num::rational::Ratio<V>;
-
-            #[inline(always)]
-            fn from_f64(value: f64) -> T {
-                <T as $crate::num::FromPrimitive>::from_f64(value).unwrap()
-            }
-
-            $(impl $crate::Conversion<V> for super::$unit {
-                type T = T;
-
-                #[inline(always)]
-                fn coefficient() -> Self::T {
-                    from_f64(quantity!(@coefficient $($conversion),+))
-                }
-
-                #[inline(always)]
-                #[allow(unused_variables)]
-                fn constant(op: $crate::ConstantOp) -> Self::T {
-                    from_f64(quantity!(@constant op $($conversion),+))
-                }
-            }
-
-            impl super::Conversion<V> for super::$unit {})+
-        }
-
-        storage_types! {
-            types: BigUint;
-            pub type T = $crate::num::rational::Ratio<V>;
-
-            #[inline(always)]
-            fn from_f64(value: f64) -> T {
-                use $crate::num::FromPrimitive;
-
-                let c = $crate::num::rational::Ratio::<$crate::num::BigInt>::from_f64(value)
-                    .unwrap();
-
-                T::new(c.numer().to_biguint().unwrap(), c.denom().to_biguint().unwrap())
-            }
-
-            $(impl $crate::Conversion<V> for super::$unit {
-                type T = T;
-
-                #[inline(always)]
-                fn coefficient() -> Self::T {
-                    from_f64(quantity!(@coefficient $($conversion),+))
-                }
-
-                #[inline(always)]
-                #[allow(unused_variables)]
-                fn constant(op: $crate::ConstantOp) -> Self::T {
-                    from_f64(quantity!(@constant op $($conversion),+))
-                }
-            }
-
-            impl super::Conversion<V> for super::$unit {})+
-        }
-
-        storage_types! {
-            types: Ratio;
-
-            #[inline(always)]
-            fn from_f64(value: f64) -> V {
-                <V as $crate::num::FromPrimitive>::from_f64(value).unwrap()
-            }
-
-            $(impl $crate::Conversion<V> for super::$unit {
-                type T = V;
-
-                #[inline(always)]
-                fn coefficient() -> Self::T {
-                    from_f64(quantity!(@coefficient $($conversion),+))
-                }
-
-                #[inline(always)]
-                #[allow(unused_variables)]
-                fn constant(op: $crate::ConstantOp) -> Self::T {
-                    from_f64(quantity!(@constant op $($conversion),+))
-                }
-            }
-
-            impl super::Conversion<V> for super::$unit {})+
+        unit! {
+            @units $($(#[$unit_attr])* @$unit: $($conversion),+;
+                $abbreviation, $singular, $plural;)+
         }
 
         /// Quantity description.
@@ -302,7 +168,7 @@ macro_rules! quantity {
             #[allow(dead_code)]
             pub fn abbreviation(&self) -> &'static str {
                 match self {
-                    $(Units::$unit(_) => <$unit as super::Unit>::abbreviation(),)+
+                    $(Units::$unit(_) => <$unit as __system::Unit>::abbreviation(),)+
 
                     Units::__nonexhaustive => "unknown",
                 }
@@ -312,7 +178,7 @@ macro_rules! quantity {
             #[allow(dead_code)]
             pub fn singular(&self) -> &'static str {
                 match self {
-                    $(Units::$unit(_) => <$unit as super::Unit>::singular(),)+
+                    $(Units::$unit(_) => <$unit as __system::Unit>::singular(),)+
 
                     Units::__nonexhaustive => "unknown",
                 }
@@ -322,7 +188,7 @@ macro_rules! quantity {
             #[allow(dead_code)]
             pub fn plural(&self) -> &'static str {
                 match self {
-                    $(Units::$unit(_) => <$unit as super::Unit>::plural(),)+
+                    $(Units::$unit(_) => <$unit as __system::Unit>::plural(),)+
 
                     Units::__nonexhaustive => "unknown",
                 }
@@ -341,7 +207,7 @@ macro_rules! quantity {
 
         impl<U, V> $quantity<U, V>
         where
-            U: super::Units<V> + ?Sized,
+            U: __system::Units<V> + ?Sized,
             V: $crate::num::Num + $crate::Conversion<V>,
         {
             /// Create a new quantity from the given value and measurement unit.
@@ -356,7 +222,7 @@ macro_rules! quantity {
                 $quantity {
                     dimension: $crate::lib::marker::PhantomData,
                     units: $crate::lib::marker::PhantomData,
-                    value: super::to_base::<Dimension, U, V, N>(&v),
+                    value: __system::to_base::<Dimension, U, V, N>(&v),
                 }
             }
 
@@ -369,7 +235,7 @@ macro_rules! quantity {
             where
                 N: Unit + $crate::Conversion<V, T = V::T>,
             {
-                super::from_base::<Dimension, U, V, N>(&self.value)
+                __system::from_base::<Dimension, U, V, N>(&self.value)
             }
 
             /// Returns the largest integer less than or equal to a number in the given
@@ -470,11 +336,11 @@ macro_rules! quantity {
             pub fn format_args<N>(
                 unit: N,
                 style: $crate::fmt::DisplayStyle
-            ) -> super::fmt::Arguments<Dimension, N>
+            ) -> __system::fmt::Arguments<Dimension, N>
             where
                 N: Unit
             {
-                super::fmt::Arguments {
+                __system::fmt::Arguments {
                     dimension: $crate::lib::marker::PhantomData,
                     unit,
                     style,
@@ -510,12 +376,12 @@ macro_rules! quantity {
                 self,
                 unit: N,
                 style: $crate::fmt::DisplayStyle
-            ) -> super::fmt::QuantityArguments<Dimension, U, V, N>
+            ) -> __system::fmt::QuantityArguments<Dimension, U, V, N>
             where
                 N: Unit
             {
-                super::fmt::QuantityArguments {
-                    arguments: super::fmt::Arguments {
+                __system::fmt::QuantityArguments {
+                    arguments: __system::fmt::Arguments {
                         dimension: $crate::lib::marker::PhantomData,
                         unit,
                         style,
@@ -525,9 +391,9 @@ macro_rules! quantity {
             }
         }
 
-        impl<N> super::fmt::Arguments<Dimension, N>
+        impl<N> __system::fmt::Arguments<Dimension, N>
         where
-            N: super::Unit + Unit,
+            N: __system::Unit + Unit,
         {
             /// Specifies a quantity to display.
             ///
@@ -537,12 +403,12 @@ macro_rules! quantity {
             pub fn with<U, V>(
                 self,
                 quantity: $quantity<U, V>
-            ) -> super::fmt::QuantityArguments<Dimension, U, V, N>
+            ) -> __system::fmt::QuantityArguments<Dimension, U, V, N>
             where
-                U: super::Units<V> + ?Sized,
+                U: __system::Units<V> + ?Sized,
                 V: $crate::num::Num + $crate::Conversion<V>,
             {
-                super::fmt::QuantityArguments {
+                __system::fmt::QuantityArguments {
                     arguments: self,
                     quantity,
                 }
@@ -556,7 +422,7 @@ macro_rules! quantity {
 
                 impl<U> FromStr for super::super::$quantity<U, V>
                 where
-                    U: super::super::super::Units<V> + ?Sized,
+                    U: super::super::__system::Units<V> + ?Sized,
                 {
                     type Err = $crate::str::ParseQuantityError;
 
@@ -576,25 +442,6 @@ macro_rules! quantity {
             }
         }
     };
-    (@unit $(#[$unit_attr:meta])+ @$unit:ident $plural:expr) => {
-        $(#[$unit_attr])*
-        #[allow(non_camel_case_types)]
-        #[derive(Clone, Copy, Debug, Hash)]
-        pub struct $unit;
-    };
-    (@unit @$unit:ident $plural:expr) => {
-        #[doc = $plural]
-        #[allow(non_camel_case_types)]
-        #[derive(Clone, Copy, Debug, Hash)]
-        pub struct $unit;
-    };
-    (@coefficient $factor:expr, $const:expr) => { $factor };
-    (@coefficient $factor:expr) => { $factor };
-    (@constant $op:ident $factor:expr, $const:expr) => { $const };
-    (@constant $op:ident $factor:expr) => {
-        match $op {
-            $crate::ConstantOp::Add => -0.0,
-            $crate::ConstantOp::Sub => 0.0,
-        }
-    };
+    (@kind $kind:ty) => { $kind };
+    (@kind) => { dyn $crate::Kind };
 }
diff --git a/src/system.rs b/src/system.rs
index 006f3de3..0e8ad421 100644
--- a/src/system.rs
+++ b/src/system.rs
@@ -1486,8 +1486,8 @@ macro_rules! system {
         /// Macro to implement [`quantity`](si/struct.Quantity.html) type aliases for a specific
         /// [system of units][units] and value storage type.
         ///
-        /// * `$path`: Path to the module where the [`system!`](macro.system.html) macro was run
-        ///   (e.g. `::uom::si`).
+        /// * `$system`: Path to the module where the [`system!`](macro.system.html) macro was run
+        ///   (e.g. `uom::si`).
         /// * `$V`: Underlying value storage type (e.g. `f32`).
         /// * `$U`: Optional. Base units. Pass as a tuple with the desired units: `(meter, kilogram,
         ///   second, ampere, kelvin, mole, candela)`. The system's base units will be used if no
diff --git a/src/unit.rs b/src/unit.rs
new file mode 100644
index 00000000..3f2fcf60
--- /dev/null
+++ b/src/unit.rs
@@ -0,0 +1,274 @@
+/// Macro to implement a set of [measurement units][measurement]. Note that units manually defined
+/// using this macro will not be included in the quantity unit enum or associated functions, or in
+/// the `FromStr` implementation. Using this macro will create submodules for the underlying storage
+/// types that are enabled (e.g. `mod f32`). `@...` match arms are considered private.
+///
+/// When using the pre-built [SI](si) system included with `uom` this macro allows for new units to
+/// quickly be defined without requiring a release. [Pull requests](pr) to add new units upstream
+/// area always greatly appreciated.
+///
+/// * `$system`: Path to the module where the [`system!`](macro.system.html) macro was run (e.g.
+///   `uom::si`).
+/// * `quantity`: Path to the module where the [`quantity!`](macro.quantity.html) macro was run
+///   (e.g. `uom::si::length`).
+/// * `$unit`: Unit name (e.g. `meter`, `foot`).
+/// * `$conversion`: Conversion (coefficient and constant factor) from the unit to the base unit of
+///   the quantity (e.g. `3.048_E-1` to convert `foot` to `meter`. `1.0_E0, 273.15_E0` to convert
+///   `celsius` to `kelvin`.). The coefficient is required and the constant factor is optional.
+///   Note that using a unit with a non-zero constant factor is not currently supported as a base
+///   unit.
+/// * `$abbreviation`: Unit abbreviation (e.g. `"m"`).
+/// * `$singular`: Singular unit description (e.g. `"meter"`).
+/// * `$plural`: Plural unit description (e.g. `"meters"`).
+///
+/// An example invocation is given below to add kilometers to length in a meter-kilogram-second
+/// system. The `#[macro_use]` attribute must be used when including the `uom` crate to make the
+/// `unit!` macro available.
+///
+/// ```
+/// #[macro_use]
+/// extern crate uom;
+///
+/// # fn main() { }
+/// # mod mks {
+/// #     #[macro_use]
+/// #     mod length {
+/// #         quantity! {
+/// #             /// Length (base unit meter, m).
+/// #             quantity: Length; "length";
+/// #             /// Length dimension, m.
+/// #             dimension: Q<P1 /*length*/, Z0 /*mass*/, Z0 /*time*/>;
+/// #             units {
+/// #                 @meter: 1.0E0; "m", "meter", "meters";
+/// #                 @foot: 3.048E-1; "ft", "foot", "feet";
+/// #             }
+/// #         }
+/// #     }
+/// #     #[macro_use]
+/// #     mod mass {
+/// #         quantity! {
+/// #             /// Mass (base unit kilogram, kg).
+/// #             quantity: Mass; "mass";
+/// #             /// Mass dimension, kg.
+/// #             dimension: Q<Z0 /*length*/, P1 /*mass*/, Z0 /*time*/>;
+/// #             units {
+/// #                 @kilogram: 1.0; "kg", "kilogram", "kilograms";
+/// #             }
+/// #         }
+/// #     }
+/// #     #[macro_use]
+/// #     mod time {
+/// #         quantity! {
+/// #             /// Time (base unit second, s).
+/// #             quantity: Time; "time";
+/// #             /// Time dimension, s.
+/// #             dimension: Q<Z0 /*length*/, Z0 /*mass*/, P1 /*time*/>;
+/// #             units {
+/// #                 @second: 1.0; "s", "second", "seconds";
+/// #             }
+/// #         }
+/// #     }
+/// #     system! {
+/// #         /// System of quantities, Q.
+/// #         quantities: Q {
+/// #             length: meter, L;
+/// #             mass: kilogram, M;
+/// #             time: second, T;
+/// #         }
+/// #         /// System of units, U.
+/// #         units: U {
+/// #             mod length::Length,
+/// #             mod mass::Mass,
+/// #             mod time::Time,
+/// #         }
+/// #     }
+/// #     mod f32 {
+/// #         Q!(crate::mks, f32/*, (centimeter, gram, second)*/);
+/// #     }
+/// #     mod unit {
+/// unit! {
+///     system: crate::mks;
+///     quantity: crate::mks::length;
+///
+///     @kilometer: 1.0E-03; "km", "kilometer", "kilometers";
+/// }
+/// #     }
+/// # }
+/// ```
+///
+/// [si]: http://jcgm.bipm.org/vim/en/1.16.html
+/// [quantity]: http://jcgm.bipm.org/vim/en/1.1.html
+/// [measurement]: http://jcgm.bipm.org/vim/en/1.9.html
+/// [kind]: https://jcgm.bipm.org/vim/en/1.2.html
+/// [pr]: https://github.com/iliekturtles/uom/pulls
+#[macro_export]
+macro_rules! unit {
+    (
+        system: $system:path;
+        quantity: $quantity:path;
+
+        $($(#[$unit_attr:meta])* @$unit:ident: $($conversion:expr),+;
+            $abbreviation:expr, $singular:expr, $plural:expr;)+
+    ) => {
+        use $system as __system;
+        use $quantity as __quantity;
+        use __quantity::{Conversion, Unit};
+
+        unit!(@units $($(#[$unit_attr])* @$unit: $($conversion),+;
+            $abbreviation, $singular, $plural;)+);
+    };
+    (
+        @units $($(#[$unit_attr:meta])* @$unit:ident: $($conversion:expr),+;
+            $abbreviation:expr, $singular:expr, $plural:expr;)+
+    ) => {
+        $(unit!(@unit $(#[$unit_attr])* @$unit $plural);
+
+        impl __system::Unit for $unit {
+            #[inline(always)]
+            fn abbreviation() -> &'static str {
+                $abbreviation
+            }
+
+            #[inline(always)]
+            fn singular() -> &'static str {
+                $singular
+            }
+
+            #[inline(always)]
+            fn plural() -> &'static str {
+                $plural
+            }
+        }
+
+        impl Unit for $unit {})+
+
+        storage_types! {
+            types: Float;
+
+            $(impl $crate::Conversion<V> for super::$unit {
+                type T = V;
+
+                #[inline(always)]
+                #[allow(clippy::inconsistent_digit_grouping)]
+                fn coefficient() -> Self::T {
+                    unit!(@coefficient $($conversion),+)
+                }
+
+                #[inline(always)]
+                #[allow(unused_variables)]
+                #[allow(clippy::inconsistent_digit_grouping)]
+                fn constant(op: $crate::ConstantOp) -> Self::T {
+                    unit!(@constant op $($conversion),+)
+                }
+            }
+
+            impl super::Conversion<V> for super::$unit {})+
+        }
+
+        storage_types! {
+            types: PrimInt, BigInt;
+            pub type T = $crate::num::rational::Ratio<V>;
+
+            #[inline(always)]
+            fn from_f64(value: f64) -> T {
+                <T as $crate::num::FromPrimitive>::from_f64(value).unwrap()
+            }
+
+            $(impl $crate::Conversion<V> for super::$unit {
+                type T = T;
+
+                #[inline(always)]
+                fn coefficient() -> Self::T {
+                    from_f64(unit!(@coefficient $($conversion),+))
+                }
+
+                #[inline(always)]
+                #[allow(unused_variables)]
+                fn constant(op: $crate::ConstantOp) -> Self::T {
+                    from_f64(unit!(@constant op $($conversion),+))
+                }
+            }
+
+            impl super::Conversion<V> for super::$unit {})+
+        }
+
+        storage_types! {
+            types: BigUint;
+            pub type T = $crate::num::rational::Ratio<V>;
+
+            #[inline(always)]
+            fn from_f64(value: f64) -> T {
+                use $crate::num::FromPrimitive;
+
+                let c = $crate::num::rational::Ratio::<$crate::num::BigInt>::from_f64(value)
+                    .unwrap();
+
+                T::new(c.numer().to_biguint().unwrap(), c.denom().to_biguint().unwrap())
+            }
+
+            $(impl $crate::Conversion<V> for super::$unit {
+                type T = T;
+
+                #[inline(always)]
+                fn coefficient() -> Self::T {
+                    from_f64(unit!(@coefficient $($conversion),+))
+                }
+
+                #[inline(always)]
+                #[allow(unused_variables)]
+                fn constant(op: $crate::ConstantOp) -> Self::T {
+                    from_f64(unit!(@constant op $($conversion),+))
+                }
+            }
+
+            impl super::Conversion<V> for super::$unit {})+
+        }
+
+        storage_types! {
+            types: Ratio;
+
+            #[inline(always)]
+            fn from_f64(value: f64) -> V {
+                <V as $crate::num::FromPrimitive>::from_f64(value).unwrap()
+            }
+
+            $(impl $crate::Conversion<V> for super::$unit {
+                type T = V;
+
+                #[inline(always)]
+                fn coefficient() -> Self::T {
+                    from_f64(unit!(@coefficient $($conversion),+))
+                }
+
+                #[inline(always)]
+                #[allow(unused_variables)]
+                fn constant(op: $crate::ConstantOp) -> Self::T {
+                    from_f64(unit!(@constant op $($conversion),+))
+                }
+            }
+
+            impl super::Conversion<V> for super::$unit {})+
+        }
+    };
+    (@unit $(#[$unit_attr:meta])+ @$unit:ident $plural:expr) => {
+        $(#[$unit_attr])*
+        #[allow(non_camel_case_types)]
+        #[derive(Clone, Copy, Debug, Hash)]
+        pub struct $unit;
+    };
+    (@unit @$unit:ident $plural:expr) => {
+        #[doc = $plural]
+        #[allow(non_camel_case_types)]
+        #[derive(Clone, Copy, Debug, Hash)]
+        pub struct $unit;
+    };
+    (@coefficient $factor:expr, $const:expr) => { $factor };
+    (@coefficient $factor:expr) => { $factor };
+    (@constant $op:ident $factor:expr, $const:expr) => { $const };
+    (@constant $op:ident $factor:expr) => {
+        match $op {
+            $crate::ConstantOp::Add => -0.0,
+            $crate::ConstantOp::Sub => 0.0,
+        }
+    };
+}