Import 'zerofrom-derive' crate

Request Document: go/android-rust-importing-crates
For CL Reviewers: go/android3p#cl-review
Bug: 391960687
Test: m libzerofrom_derive

Change-Id: Icd0acae6b2c61f4be1c5c2f17e09276fd90e56b6
diff --git a/crates/zerofrom-derive/.android-checksum.json b/crates/zerofrom-derive/.android-checksum.json
new file mode 100644
index 0000000..39fa149
--- /dev/null
+++ b/crates/zerofrom-derive/.android-checksum.json
@@ -0,0 +1 @@
+{"package":null,"files":{".cargo-checksum.json":"830b604c511c2cdd30dcb7d21615b6f6abe4d8ceff9ffa1c47d3366954eb797c","Android.bp":"3d0ac18ddb6351f2c2b4d6e2a81a1613e46abdb48a7b78d41bf9259afb2c407b","Cargo.lock":"0eb8ecbe5cc0de2dbb57dafe1e224f2ad9f1355f1c20b278e2f7205d1d408a0d","Cargo.toml":"10da25dcb06b91a2f32d4258903fdc67373f26a9f98aa6de3f1f1b81b05b1425","LICENSE":"dc5bf54a256c44c7030f620e2b6abca67aa66635f54789432989d96836c552b9","METADATA":"9abd1090ee9d406090b9dcc1115735d45ec66f7edd4beb1a80e2f56379d375f9","README.md":"dc7e72fa618572b8a335636e66e7c818ee0c26114044118e5fd7285f03395ce8","cargo_embargo.json":"0be745f01ba4955b20f2cb5011168c4d8cd396af75c007035ec693c67b17bce7","examples/zf_derive.rs":"9b05764709a74aa0326d27b56254b662eb4c9362d1e93c8361c9ab7fd8ce312b","src/lib.rs":"86e4487637e7197701a87dd86b318c16badec531bb40c89191d2fe6fb39a2ddd","src/visitor.rs":"a7d668a43484c7fd77c3836591fee0c2c1e4f69b6b4fb297267d647117a12777"}}
\ No newline at end of file
diff --git a/crates/zerofrom-derive/.cargo-checksum.json b/crates/zerofrom-derive/.cargo-checksum.json
new file mode 100644
index 0000000..94e9cb1
--- /dev/null
+++ b/crates/zerofrom-derive/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.lock":"fc6219b76e4c1734055495c69f5e3bca1c38f65610a2c70456f1bdd401fb19e8","Cargo.toml":"5af9fbb144d61447f00349f3b4cd4688cbd4317ba87051ddc4f1b384e444e9a8","LICENSE":"f367c1b8e1aa262435251e442901da4607b4650e0e63a026f5044473ecfb90f2","README.md":"2ef78913a98fb26c912c47c3c96167b9b57524754dc4857fe18a2122fec4985e","examples/zf_derive.rs":"39342f4658da4c6e475a290028b7cbab161e1fb91ee49c79cda4799a8614bc9b","src/lib.rs":"2f746c88a50e96e358529ba8f5d73a5941296dacecee405e2b4b1489c6a59ae6","src/visitor.rs":"6eb54d2e1c4f9211bcaa5d6e768d668063a94411bfbed610b0a0419ac37bf545"},"package":"595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"}
\ No newline at end of file
diff --git a/crates/zerofrom-derive/Android.bp b/crates/zerofrom-derive/Android.bp
new file mode 100644
index 0000000..4bed0f0
--- /dev/null
+++ b/crates/zerofrom-derive/Android.bp
@@ -0,0 +1,31 @@
+// This file is generated by cargo_embargo.
+// Do not modify this file because the changes will be overridden on upgrade.
+
+package {
+    default_applicable_licenses: ["external_rust_crates_zerofrom-derive_license"],
+    default_team: "trendy_team_android_rust",
+}
+
+license {
+    name: "external_rust_crates_zerofrom-derive_license",
+    visibility: [":__subpackages__"],
+    license_kinds: ["SPDX-license-identifier-Unicode-3.0"],
+    license_text: ["LICENSE"],
+}
+
+rust_proc_macro {
+    name: "libzerofrom_derive",
+    crate_name: "zerofrom_derive",
+    cargo_env_compat: true,
+    cargo_pkg_version: "0.1.5",
+    crate_root: "src/lib.rs",
+    edition: "2021",
+    rustlibs: [
+        "libproc_macro2",
+        "libquote",
+        "libsyn",
+        "libsynstructure",
+    ],
+    product_available: true,
+    vendor_available: true,
+}
diff --git a/crates/zerofrom-derive/Cargo.lock b/crates/zerofrom-derive/Cargo.lock
new file mode 100644
index 0000000..a180399
--- /dev/null
+++ b/crates/zerofrom-derive/Cargo.lock
@@ -0,0 +1,59 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
diff --git a/crates/zerofrom-derive/Cargo.toml b/crates/zerofrom-derive/Cargo.toml
new file mode 100644
index 0000000..eb06006
--- /dev/null
+++ b/crates/zerofrom-derive/Cargo.toml
@@ -0,0 +1,64 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "zerofrom-derive"
+version = "0.1.5"
+authors = ["Manish Goregaokar <manishsmail@gmail.com>"]
+build = false
+autobins = false
+autoexamples = false
+autotests = false
+autobenches = false
+description = "Custom derive for the zerofrom crate"
+readme = "README.md"
+keywords = [
+    "zerocopy",
+    "serialization",
+    "lifetime",
+    "borrow",
+]
+categories = [
+    "data-structures",
+    "memory-management",
+    "caching",
+    "no-std",
+]
+license = "Unicode-3.0"
+repository = "https://github.com/unicode-org/icu4x"
+
+[package.metadata.workspaces]
+independent = true
+
+[lib]
+name = "zerofrom_derive"
+path = "src/lib.rs"
+proc-macro = true
+
+[[example]]
+name = "zf_derive"
+path = "examples/zf_derive.rs"
+
+[dependencies.proc-macro2]
+version = "1.0.61"
+
+[dependencies.quote]
+version = "1.0.28"
+
+[dependencies.syn]
+version = "2.0.21"
+features = ["fold"]
+
+[dependencies.synstructure]
+version = "0.13.0"
+
+[dev-dependencies]
diff --git a/crates/zerofrom-derive/LICENSE b/crates/zerofrom-derive/LICENSE
new file mode 100644
index 0000000..c9be601
--- /dev/null
+++ b/crates/zerofrom-derive/LICENSE
@@ -0,0 +1,46 @@
+UNICODE LICENSE V3
+
+COPYRIGHT AND PERMISSION NOTICE
+
+Copyright © 2020-2024 Unicode, Inc.
+
+NOTICE TO USER: Carefully read the following legal agreement. BY
+DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
+SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
+TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
+DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of data files and any associated documentation (the "Data Files") or
+software and any associated documentation (the "Software") to deal in the
+Data Files or Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, and/or sell
+copies of the Data Files or Software, and to permit persons to whom the
+Data Files or Software are furnished to do so, provided that either (a)
+this copyright and permission notice appear with all copies of the Data
+Files or Software, or (b) this copyright and permission notice appear in
+associated Documentation.
+
+THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
+THIRD PARTY RIGHTS.
+
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
+BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
+OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
+FILES OR SOFTWARE.
+
+Except as contained in this notice, the name of a copyright holder shall
+not be used in advertising or otherwise to promote the sale, use or other
+dealings in these Data Files or Software without prior written
+authorization of the copyright holder.
+
+SPDX-License-Identifier: Unicode-3.0
+
+—
+
+Portions of ICU4X may have been adapted from ICU4C and/or ICU4J.
+ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others.
diff --git a/crates/zerofrom-derive/METADATA b/crates/zerofrom-derive/METADATA
new file mode 100644
index 0000000..c1fec7e
--- /dev/null
+++ b/crates/zerofrom-derive/METADATA
@@ -0,0 +1,17 @@
+name: "zerofrom-derive"
+description: "Custom derive for the zerofrom crate"
+third_party {
+  version: "0.1.5"
+  license_type: NOTICE
+  last_upgrade_date {
+    year: 2025
+    month: 1
+    day: 24
+  }
+  homepage: "https://crates.io/crates/zerofrom-derive"
+  identifier {
+    type: "Archive"
+    value: "https://static.crates.io/crates/zerofrom-derive/zerofrom-derive-0.1.5.crate"
+    version: "0.1.5"
+  }
+}
diff --git a/crates/zerofrom-derive/README.md b/crates/zerofrom-derive/README.md
new file mode 100644
index 0000000..ad803c5
--- /dev/null
+++ b/crates/zerofrom-derive/README.md
@@ -0,0 +1,11 @@
+# zerofrom-derive [![crates.io](https://img.shields.io/crates/v/zerofrom-derive)](https://crates.io/crates/zerofrom-derive)
+
+<!-- cargo-rdme start -->
+
+Custom derives for `ZeroFrom` from the `zerofrom` crate.
+
+<!-- cargo-rdme end -->
+
+## More Information
+
+For more information on development, authorship, contributing etc. please visit [`ICU4X home page`](https://github.com/unicode-org/icu4x).
diff --git a/crates/zerofrom-derive/cargo_embargo.json b/crates/zerofrom-derive/cargo_embargo.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/crates/zerofrom-derive/cargo_embargo.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/crates/zerofrom-derive/examples/zf_derive.rs b/crates/zerofrom-derive/examples/zf_derive.rs
new file mode 100644
index 0000000..d3eb2a6
--- /dev/null
+++ b/crates/zerofrom-derive/examples/zf_derive.rs
@@ -0,0 +1,115 @@
+// This file is part of ICU4X. For terms of use, please see the file
+// called LICENSE at the top level of the ICU4X source tree
+// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
+
+#![allow(unused)]
+
+use std::{borrow::Cow, marker::PhantomData};
+use zerofrom::ZeroFrom;
+use zerovec::{maps::ZeroMapKV, ule::AsULE, VarZeroVec, ZeroMap, ZeroVec};
+
+#[derive(ZeroFrom, Copy, Clone)]
+pub struct IntExample {
+    x: u32,
+}
+
+#[derive(ZeroFrom, Copy, Clone)]
+pub struct GenericsExample<T> {
+    x: u32,
+    y: T,
+}
+
+#[derive(ZeroFrom, Copy, Clone)]
+pub struct GenericsExampleWithDefault<T, U = usize> {
+    x: T,
+    y: U,
+}
+
+#[derive(ZeroFrom)]
+pub struct CowExample<'a> {
+    x: u8,
+    y: &'a str,
+    z: Cow<'a, str>,
+    w: Cow<'a, [u8]>,
+}
+
+#[derive(ZeroFrom)]
+pub struct ZeroVecExample<'a> {
+    var: VarZeroVec<'a, str>,
+    vec: ZeroVec<'a, u16>,
+}
+
+#[derive(ZeroFrom)]
+pub struct ZeroVecExampleWithGenerics<'a, T: AsULE> {
+    gen: ZeroVec<'a, T>,
+    vec: ZeroVec<'a, u16>,
+    bare: T,
+}
+
+#[derive(ZeroFrom)]
+pub struct HasTuples<'data> {
+    pub bar: (&'data str, &'data str),
+}
+
+pub fn assert_zf_tuples<'b>(x: &'b HasTuples) -> HasTuples<'b> {
+    HasTuples::zero_from(x)
+}
+pub fn assert_zf_generics<'a, 'b>(
+    x: &'b ZeroVecExampleWithGenerics<'a, u8>,
+) -> ZeroVecExampleWithGenerics<'b, u8> {
+    ZeroVecExampleWithGenerics::<'b, u8>::zero_from(x)
+}
+
+#[derive(ZeroFrom)]
+pub struct ZeroMapGenericExample<'a, T: for<'b> ZeroMapKV<'b> + ?Sized> {
+    map: ZeroMap<'a, str, T>,
+}
+
+pub fn assert_zf_map<'b>(x: &'b ZeroMapGenericExample<str>) -> ZeroMapGenericExample<'b, str> {
+    ZeroMapGenericExample::zero_from(x)
+}
+
+#[derive(Clone, ZeroFrom)]
+pub struct CloningZF1 {
+    #[zerofrom(clone)] // Vec is not ZeroFrom, so it needs to be cloned
+    vec: Vec<u8>,
+}
+
+#[derive(Clone, ZeroFrom)]
+pub struct CloningZF2<'data> {
+    #[zerofrom(clone)] // Cow is ZeroFrom, but we force a clone
+    cow: Cow<'data, str>,
+}
+
+#[derive(ZeroFrom)]
+pub enum CloningZF3<'data> {
+    Cow(#[zerofrom(clone)] Cow<'data, str>),
+}
+
+#[derive(ZeroFrom)]
+#[zerofrom(may_borrow(T, Q))] // instead of treating T as a copy type, we want to allow zerofromming T too
+pub struct GenericsThatAreAlsoZf<T, Q: ?Sized, Ignored: ?Sized> {
+    x: T,
+    y: Option<T>,
+    ignored: PhantomData<Ignored>,
+    q: Q,
+}
+
+pub fn assert_zf_generics_may_borrow<'a, 'b>(
+    x: &'b GenericsThatAreAlsoZf<&'a str, usize, str>,
+) -> GenericsThatAreAlsoZf<&'b str, usize, str> {
+    GenericsThatAreAlsoZf::<&'b str, usize, str>::zero_from(x)
+}
+
+#[derive(ZeroFrom)]
+pub struct UsesGenericsThatAreAlsoZf<'a> {
+    x: GenericsThatAreAlsoZf<&'a str, usize, str>,
+}
+
+// Ensure it works with invariant types too
+#[derive(ZeroFrom)]
+pub struct UsesGenericsThatAreAlsoZfWithMap<'a> {
+    x: GenericsThatAreAlsoZf<ZeroMap<'a, str, str>, usize, str>,
+}
+
+fn main() {}
diff --git a/crates/zerofrom-derive/src/lib.rs b/crates/zerofrom-derive/src/lib.rs
new file mode 100644
index 0000000..67d4085
--- /dev/null
+++ b/crates/zerofrom-derive/src/lib.rs
@@ -0,0 +1,308 @@
+// This file is part of ICU4X. For terms of use, please see the file
+// called LICENSE at the top level of the ICU4X source tree
+// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
+
+//! Custom derives for `ZeroFrom` from the `zerofrom` crate.
+
+// https://github.com/unicode-org/icu4x/blob/main/documents/process/boilerplate.md#library-annotations
+#![cfg_attr(
+    not(test),
+    deny(
+        clippy::indexing_slicing,
+        clippy::unwrap_used,
+        clippy::expect_used,
+        clippy::panic,
+        clippy::exhaustive_structs,
+        clippy::exhaustive_enums,
+        missing_debug_implementations,
+    )
+)]
+
+use core::mem;
+use proc_macro::TokenStream;
+use proc_macro2::{Span, TokenStream as TokenStream2};
+use quote::quote;
+use std::collections::{HashMap, HashSet};
+use syn::fold::{self, Fold};
+use syn::punctuated::Punctuated;
+use syn::spanned::Spanned;
+use syn::{
+    parse_macro_input, parse_quote, DeriveInput, Ident, Lifetime, MetaList, Token,
+    TraitBoundModifier, Type, TypeParamBound, TypePath, WherePredicate,
+};
+use synstructure::Structure;
+mod visitor;
+
+/// Custom derive for `zerofrom::ZeroFrom`,
+///
+/// This implements `ZeroFrom<Ty> for Ty` for types
+/// without a lifetime parameter, and `ZeroFrom<Ty<'data>> for Ty<'static>`
+/// for types with a lifetime parameter.
+///
+/// Apply the `#[zerofrom(clone)]` attribute to a field if it doesn't implement
+/// Copy or ZeroFrom; this data will be cloned when the struct is zero_from'ed.
+///
+/// Apply the `#[zerofrom(maybe_borrow(T, U, V))]` attribute to the struct to indicate
+/// that certain type parameters may themselves contain borrows (by default
+/// the derives assume that type parameters perform no borrows and can be copied or cloned).
+///
+/// In rust versions where [this issue](https://github.com/rust-lang/rust/issues/114393) is fixed,
+/// `#[zerofrom(may_borrow)]` can be applied directly to type parameters.
+#[proc_macro_derive(ZeroFrom, attributes(zerofrom))]
+pub fn zf_derive(input: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(input as DeriveInput);
+    TokenStream::from(zf_derive_impl(&input))
+}
+
+fn has_attr(attrs: &[syn::Attribute], name: &str) -> bool {
+    attrs.iter().any(|a| {
+        if let Ok(i) = a.parse_args::<Ident>() {
+            if i == name {
+                return true;
+            }
+        }
+        false
+    })
+}
+
+// Collects all idents from #[zerofrom(may_borrow(A, B, C, D))]
+// needed since #[zerofrom(may_borrow)] doesn't work yet
+// (https://github.com/rust-lang/rust/issues/114393)
+fn get_may_borrow_attr(attrs: &[syn::Attribute]) -> Result<HashSet<Ident>, Span> {
+    let mut params = HashSet::new();
+    for attr in attrs {
+        if let Ok(list) = attr.parse_args::<MetaList>() {
+            if list.path.is_ident("may_borrow") {
+                if let Ok(list) =
+                    list.parse_args_with(Punctuated::<Ident, Token![,]>::parse_terminated)
+                {
+                    params.extend(list)
+                } else {
+                    return Err(attr.span());
+                }
+            }
+        }
+    }
+    Ok(params)
+}
+
+fn zf_derive_impl(input: &DeriveInput) -> TokenStream2 {
+    let mut tybounds = input
+        .generics
+        .type_params()
+        .map(|ty| {
+            // Strip out param defaults, we don't need them in the impl
+            let mut ty = ty.clone();
+            ty.eq_token = None;
+            ty.default = None;
+            ty
+        })
+        .collect::<Vec<_>>();
+    let typarams = tybounds
+        .iter()
+        .map(|ty| ty.ident.clone())
+        .collect::<Vec<_>>();
+    let lts = input.generics.lifetimes().count();
+    let name = &input.ident;
+    let structure = Structure::new(input);
+
+    let may_borrow_attrs = match get_may_borrow_attr(&input.attrs) {
+        Ok(mb) => mb,
+        Err(span) => {
+            return syn::Error::new(
+            span,
+            "#[zerofrom(may_borrow)] on the struct takes in a comma separated list of type parameters, like so: `#[zerofrom(may_borrow(A, B, C, D)]`",
+        ).to_compile_error();
+        }
+    };
+
+    // This contains every generic type introduced in this code.
+    // If the gneeric type is may_borrow, this additionally contains the identifier corresponding to
+    // a newly introduced mirror type parameter that we are borrowing from, similar to C in the original trait.
+    // For convenience, we are calling these "C types"
+    let generics_env: HashMap<Ident, Option<Ident>> = tybounds
+        .iter_mut()
+        .map(|param| {
+            // First one doesn't work yet https://github.com/rust-lang/rust/issues/114393
+            let maybe_new_param = if has_attr(&param.attrs, "may_borrow")
+                || may_borrow_attrs.contains(&param.ident)
+            {
+                // Remove `?Sized`` bound because we need a param to be Sized in order to take a ZeroFrom of it.
+                // This only applies to fields marked as `may_borrow`.
+                let mut bounds = core::mem::take(&mut param.bounds);
+                while let Some(bound_pair) = bounds.pop() {
+                    let bound = bound_pair.into_value();
+                    if let TypeParamBound::Trait(ref trait_bound) = bound {
+                        if trait_bound.path.get_ident().map(|ident| ident == "Sized") == Some(true)
+                            && matches!(trait_bound.modifier, TraitBoundModifier::Maybe(_))
+                        {
+                            continue;
+                        }
+                    }
+                    param.bounds.push(bound);
+                }
+                Some(Ident::new(
+                    &format!("{}ZFParamC", param.ident),
+                    param.ident.span(),
+                ))
+            } else {
+                None
+            };
+            (param.ident.clone(), maybe_new_param)
+        })
+        .collect();
+
+    // Do any of the generics potentially borrow?
+    let generics_may_borrow = generics_env.values().any(|x| x.is_some());
+
+    if lts == 0 && !generics_may_borrow {
+        let has_clone = structure
+            .variants()
+            .iter()
+            .flat_map(|variant| variant.bindings().iter())
+            .any(|binding| has_attr(&binding.ast().attrs, "clone"));
+        let (clone, clone_trait) = if has_clone {
+            (quote!(this.clone()), quote!(Clone))
+        } else {
+            (quote!(*this), quote!(Copy))
+        };
+        let bounds: Vec<WherePredicate> = typarams
+            .iter()
+            .map(|ty| parse_quote!(#ty: #clone_trait + 'static))
+            .collect();
+        quote! {
+            impl<'zf, #(#tybounds),*> zerofrom::ZeroFrom<'zf, #name<#(#typarams),*>> for #name<#(#typarams),*> where #(#bounds),* {
+                fn zero_from(this: &'zf Self) -> Self {
+                    #clone
+                }
+            }
+        }
+    } else {
+        if lts > 1 {
+            return syn::Error::new(
+                input.generics.span(),
+                "derive(ZeroFrom) cannot have multiple lifetime parameters",
+            )
+            .to_compile_error();
+        }
+
+        let mut zf_bounds: Vec<WherePredicate> = vec![];
+        let body = structure.each_variant(|vi| {
+            vi.construct(|f, i| {
+                let binding = format!("__binding_{i}");
+                let field = Ident::new(&binding, Span::call_site());
+
+                if has_attr(&f.attrs, "clone") {
+                    quote! {
+                        #field.clone()
+                    }
+                } else {
+                    // the field type
+                    let fty = replace_lifetime(&f.ty, custom_lt("'zf"));
+                    // the corresponding lifetimey type we are borrowing from (effectively, the C type)
+                    let lifetime_ty =
+                        replace_lifetime_and_type(&f.ty, custom_lt("'zf_inner"), &generics_env);
+
+                    let (has_ty, has_lt) = visitor::check_type_for_parameters(&f.ty, &generics_env);
+                    if has_ty {
+                        // For types without type parameters, the compiler can figure out that the field implements
+                        // ZeroFrom on its own. However, if there are type parameters, there may be complex preconditions
+                        // to `FieldTy: ZeroFrom` that need to be satisfied. We get them to be satisfied by requiring
+                        // `FieldTy<'zf>: ZeroFrom<'zf, FieldTy<'zf_inner>>`
+                        if has_lt {
+                            zf_bounds
+                                .push(parse_quote!(#fty: zerofrom::ZeroFrom<'zf, #lifetime_ty>));
+                        } else {
+                            zf_bounds.push(parse_quote!(#fty: zerofrom::ZeroFrom<'zf, #fty>));
+                        }
+                    }
+                    if has_ty || has_lt {
+                        // By doing this we essentially require ZF to be implemented
+                        // on all fields
+                        quote! {
+                            <#fty as zerofrom::ZeroFrom<'zf, #lifetime_ty>>::zero_from(#field)
+                        }
+                    } else {
+                        // No lifetimes, so we can just copy
+                        quote! { *#field }
+                    }
+                }
+            })
+        });
+        // Due to the possibility of generics_may_borrow, we might reach here with no lifetimes on self,
+        // don't accidentally feed them to self later
+        let (maybe_zf_lifetime, maybe_zf_inner_lifetime) = if lts == 0 {
+            (quote!(), quote!())
+        } else {
+            (quote!('zf,), quote!('zf_inner,))
+        };
+
+        // Array of C types. Only different if generics are allowed to borrow
+        let mut typarams_c = typarams.clone();
+
+        if generics_may_borrow {
+            for typaram_c in &mut typarams_c {
+                if let Some(Some(replacement)) = generics_env.get(typaram_c) {
+                    // we use mem::replace here so we can be really clear about the C vs the T type
+                    let typaram_t = mem::replace(typaram_c, replacement.clone());
+                    zf_bounds
+                        .push(parse_quote!(#typaram_c: zerofrom::ZeroFrom<'zf_inner, #typaram_t>));
+                    tybounds.push(parse_quote!(#typaram_c));
+                }
+            }
+        }
+
+        quote! {
+            impl<'zf, 'zf_inner, #(#tybounds),*> zerofrom::ZeroFrom<'zf, #name<#maybe_zf_inner_lifetime #(#typarams_c),*>> for #name<#maybe_zf_lifetime #(#typarams),*>
+                where
+                #(#zf_bounds,)* {
+                fn zero_from(this: &'zf #name<#maybe_zf_inner_lifetime #(#typarams_c),*>) -> Self {
+                    match *this { #body }
+                }
+            }
+        }
+    }
+}
+
+fn custom_lt(s: &str) -> Lifetime {
+    Lifetime::new(s, Span::call_site())
+}
+
+/// Replace all lifetimes in a type with a specified one
+fn replace_lifetime(x: &Type, lt: Lifetime) -> Type {
+    struct ReplaceLifetime(Lifetime);
+
+    impl Fold for ReplaceLifetime {
+        fn fold_lifetime(&mut self, _: Lifetime) -> Lifetime {
+            self.0.clone()
+        }
+    }
+    ReplaceLifetime(lt).fold_type(x.clone())
+}
+
+/// Replace all lifetimes in a type with a specified one, AND replace all types that have a corresponding C type
+/// with the C type
+fn replace_lifetime_and_type(
+    x: &Type,
+    lt: Lifetime,
+    generics_env: &HashMap<Ident, Option<Ident>>,
+) -> Type {
+    struct ReplaceLifetimeAndTy<'a>(Lifetime, &'a HashMap<Ident, Option<Ident>>);
+
+    impl Fold for ReplaceLifetimeAndTy<'_> {
+        fn fold_lifetime(&mut self, _: Lifetime) -> Lifetime {
+            self.0.clone()
+        }
+        fn fold_type_path(&mut self, i: TypePath) -> TypePath {
+            if i.qself.is_none() {
+                if let Some(ident) = i.path.get_ident() {
+                    if let Some(Some(replacement)) = self.1.get(ident) {
+                        return parse_quote!(#replacement);
+                    }
+                }
+            }
+            fold::fold_type_path(self, i)
+        }
+    }
+    ReplaceLifetimeAndTy(lt, generics_env).fold_type(x.clone())
+}
diff --git a/crates/zerofrom-derive/src/visitor.rs b/crates/zerofrom-derive/src/visitor.rs
new file mode 100644
index 0000000..0cc44e9
--- /dev/null
+++ b/crates/zerofrom-derive/src/visitor.rs
@@ -0,0 +1,120 @@
+// This file is part of ICU4X. For terms of use, please see the file
+// called LICENSE at the top level of the ICU4X source tree
+// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
+
+//! Visitor for determining whether a type has type and non-static lifetime parameters
+//! (duplicated in yoke/derive/src/visitor.rs)
+
+use std::collections::HashMap;
+use syn::visit::{visit_lifetime, visit_type, visit_type_path, Visit};
+use syn::{Ident, Lifetime, Type, TypePath};
+
+struct TypeVisitor<'a> {
+    /// The type parameters in scope
+    typarams: &'a HashMap<Ident, Option<Ident>>,
+    /// Whether we found a type parameter
+    found_typarams: bool,
+    /// Whether we found a non-'static lifetime parameter
+    found_lifetimes: bool,
+}
+
+impl<'ast> Visit<'ast> for TypeVisitor<'_> {
+    fn visit_lifetime(&mut self, lt: &'ast Lifetime) {
+        if lt.ident != "static" {
+            self.found_lifetimes = true;
+        }
+        visit_lifetime(self, lt)
+    }
+    fn visit_type_path(&mut self, ty: &'ast TypePath) {
+        // We only need to check ty.path.get_ident() and not ty.qself or any
+        // generics in ty.path because the visitor will eventually visit those
+        // types on its own
+        if let Some(ident) = ty.path.get_ident() {
+            if let Some(maybe_borrowed) = self.typarams.get(ident) {
+                self.found_typarams = true;
+                if maybe_borrowed.is_some() {
+                    self.found_lifetimes = true;
+                }
+            }
+        }
+
+        visit_type_path(self, ty)
+    }
+}
+
+/// Checks if a type has type or lifetime parameters, given the local context of
+/// named type parameters. Returns (has_type_params, has_lifetime_params)
+pub fn check_type_for_parameters(
+    ty: &Type,
+    typarams: &HashMap<Ident, Option<Ident>>,
+) -> (bool, bool) {
+    let mut visit = TypeVisitor {
+        typarams,
+        found_typarams: false,
+        found_lifetimes: false,
+    };
+    visit_type(&mut visit, ty);
+
+    (visit.found_typarams, visit.found_lifetimes)
+}
+
+#[cfg(test)]
+mod tests {
+    use proc_macro2::Span;
+    use std::collections::HashMap;
+    use syn::{parse_quote, Ident};
+
+    use super::check_type_for_parameters;
+    fn make_typarams(params: &[&str]) -> HashMap<Ident, Option<Ident>> {
+        params
+            .iter()
+            .map(|x| (Ident::new(x, Span::call_site()), None))
+            .collect()
+    }
+
+    #[test]
+    fn test_simple_type() {
+        let environment = make_typarams(&["T", "U", "V"]);
+
+        let ty = parse_quote!(Foo<'a, T>);
+        let check = check_type_for_parameters(&ty, &environment);
+        assert_eq!(check, (true, true));
+
+        let ty = parse_quote!(Foo<T>);
+        let check = check_type_for_parameters(&ty, &environment);
+        assert_eq!(check, (true, false));
+
+        let ty = parse_quote!(Foo<'static, T>);
+        let check = check_type_for_parameters(&ty, &environment);
+        assert_eq!(check, (true, false));
+
+        let ty = parse_quote!(Foo<'a>);
+        let check = check_type_for_parameters(&ty, &environment);
+        assert_eq!(check, (false, true));
+
+        let ty = parse_quote!(Foo<'a, Bar<U>, Baz<(V, u8)>>);
+        let check = check_type_for_parameters(&ty, &environment);
+        assert_eq!(check, (true, true));
+
+        let ty = parse_quote!(Foo<'a, W>);
+        let check = check_type_for_parameters(&ty, &environment);
+        assert_eq!(check, (false, true));
+    }
+
+    #[test]
+    fn test_assoc_types() {
+        let environment = make_typarams(&["T"]);
+
+        let ty = parse_quote!(<Foo as SomeTrait<'a, T>>::Output);
+        let check = check_type_for_parameters(&ty, &environment);
+        assert_eq!(check, (true, true));
+
+        let ty = parse_quote!(<Foo as SomeTrait<'static, T>>::Output);
+        let check = check_type_for_parameters(&ty, &environment);
+        assert_eq!(check, (true, false));
+
+        let ty = parse_quote!(<T as SomeTrait<'static, Foo>>::Output);
+        let check = check_type_for_parameters(&ty, &environment);
+        assert_eq!(check, (true, false));
+    }
+}
diff --git a/pseudo_crate/Cargo.lock b/pseudo_crate/Cargo.lock
index 69a2a3b..1e1ee30 100644
--- a/pseudo_crate/Cargo.lock
+++ b/pseudo_crate/Cargo.lock
@@ -488,6 +488,7 @@
  "yaml-rust",
  "zerocopy 0.8.14",
  "zerocopy-derive 0.8.14",
+ "zerofrom-derive",
  "zeroize",
  "zeroize_derive",
  "zerovec-derive",
@@ -6660,6 +6661,18 @@
 ]
 
 [[package]]
+name = "zerofrom-derive"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
+dependencies = [
+ "proc-macro2 1.0.93",
+ "quote 1.0.38",
+ "syn 2.0.96",
+ "synstructure",
+]
+
+[[package]]
 name = "zeroize"
 version = "1.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/pseudo_crate/Cargo.toml b/pseudo_crate/Cargo.toml
index 986e7a5..3927826 100644
--- a/pseudo_crate/Cargo.toml
+++ b/pseudo_crate/Cargo.toml
@@ -397,6 +397,7 @@
 yaml-rust = "=0.4.5"
 zerocopy = "=0.8.14"
 zerocopy-derive = "=0.8.14"
+zerofrom-derive = "=0.1.5"
 zeroize = "=1.8.1"
 zeroize_derive = "=1.4.2"
 zerovec-derive = "=0.11.0"
diff --git a/pseudo_crate/crate-list.txt b/pseudo_crate/crate-list.txt
index 906fe94..169816a 100644
--- a/pseudo_crate/crate-list.txt
+++ b/pseudo_crate/crate-list.txt
@@ -396,6 +396,7 @@
 yaml-rust
 zerocopy
 zerocopy-derive
+zerofrom-derive
 zeroize
 zeroize_derive
 zerovec-derive