Squashed 'third_party/autocxx/' content from commit 629e8fa53

git-subtree-dir: third_party/autocxx
git-subtree-split: 629e8fa531a633164c0b52e2a3cab536d4cd0849
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I62a03b0049f49adf029e0204639cdb5468dde1a1
diff --git a/engine/src/conversion/analysis/abstract_types.rs b/engine/src/conversion/analysis/abstract_types.rs
new file mode 100644
index 0000000..dac9684
--- /dev/null
+++ b/engine/src/conversion/analysis/abstract_types.rs
@@ -0,0 +1,174 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use super::{
+    fun::{
+        FnAnalysis, FnKind, FnPhase, FnPrePhase2, MethodKind, PodAndConstructorAnalysis,
+        TraitMethodKind,
+    },
+    pod::PodAnalysis,
+};
+use crate::conversion::{api::Api, apivec::ApiVec};
+use crate::conversion::{
+    api::TypeKind,
+    error_reporter::{convert_apis, convert_item_apis},
+    ConvertError,
+};
+use indexmap::set::IndexSet as HashSet;
+
+/// Spot types with pure virtual functions and mark them abstract.
+pub(crate) fn mark_types_abstract(mut apis: ApiVec<FnPrePhase2>) -> ApiVec<FnPrePhase2> {
+    let mut abstract_types: HashSet<_> = apis
+        .iter()
+        .filter_map(|api| match &api {
+            Api::Function {
+                analysis:
+                    FnAnalysis {
+                        kind:
+                            FnKind::Method {
+                                impl_for: self_ty_name,
+                                method_kind: MethodKind::PureVirtual(_),
+                                ..
+                            },
+                        ..
+                    },
+                ..
+            } => Some(self_ty_name.clone()),
+            _ => None,
+        })
+        .collect();
+
+    // Spot any derived classes (recursively). Also, any types which have a base
+    // class that's not on the allowlist are presumed to be abstract, because we
+    // have no way of knowing (as they're not on the allowlist, there will be
+    // no methods associated so we won't be able to spot pure virtual methods).
+    let mut iterate = true;
+    while iterate {
+        iterate = false;
+        apis = apis
+            .into_iter()
+            .map(|api| {
+                match api {
+                    Api::Struct {
+                        analysis:
+                            PodAndConstructorAnalysis {
+                                pod:
+                                    PodAnalysis {
+                                        bases,
+                                        kind: TypeKind::Pod | TypeKind::NonPod,
+                                        castable_bases,
+                                        field_deps,
+                                        field_info,
+                                        is_generic,
+                                        in_anonymous_namespace,
+                                    },
+                                constructors,
+                            },
+                        name,
+                        details,
+                    } if abstract_types.contains(&name.name)
+                        || !abstract_types.is_disjoint(&bases) =>
+                    {
+                        abstract_types.insert(name.name.clone());
+                        // Recurse in case there are further dependent types
+                        iterate = true;
+                        Api::Struct {
+                            analysis: PodAndConstructorAnalysis {
+                                pod: PodAnalysis {
+                                    bases,
+                                    kind: TypeKind::Abstract,
+                                    castable_bases,
+                                    field_deps,
+                                    field_info,
+                                    is_generic,
+                                    in_anonymous_namespace,
+                                },
+                                constructors,
+                            },
+                            name,
+                            details,
+                        }
+                    }
+                    _ => api,
+                }
+            })
+            .collect()
+    }
+
+    // We also need to remove any constructors belonging to these
+    // abstract types.
+    apis.retain(|api| {
+        !matches!(&api,
+        Api::Function {
+            analysis:
+                FnAnalysis {
+                    kind: FnKind::Method{impl_for: self_ty, method_kind: MethodKind::Constructor{..}, ..}
+                        | FnKind::TraitMethod{ kind: TraitMethodKind::CopyConstructor | TraitMethodKind::MoveConstructor, impl_for: self_ty, ..},
+                    ..
+                },
+                ..
+        } if abstract_types.contains(self_ty))
+    });
+
+    // Finally, if there are any types which are nested inside other types,
+    // they can't be abstract. This is due to two small limitations in cxx.
+    // Imagine we have class Foo { class Bar }
+    // 1) using "type Foo = super::bindgen::root::Foo_Bar" results
+    //    in the creation of std::unique_ptr code which isn't acceptable
+    //    for an abtract class
+    // 2) using "type Foo;" isn't possible unless Foo is a top-level item
+    //    within its namespace. Any outer names will be interpreted as namespace
+    //    names and result in cxx generating "namespace Foo { class Bar }"".
+    let mut results = ApiVec::new();
+    convert_item_apis(apis, &mut results, |api| match api {
+        Api::Struct {
+            analysis:
+                PodAndConstructorAnalysis {
+                    pod:
+                        PodAnalysis {
+                            kind: TypeKind::Abstract,
+                            ..
+                        },
+                    ..
+                },
+            ..
+        } if api
+            .cpp_name()
+            .as_ref()
+            .map(|n| n.contains("::"))
+            .unwrap_or_default() =>
+        {
+            Err(ConvertError::AbstractNestedType)
+        }
+        _ => Ok(Box::new(std::iter::once(api))),
+    });
+    results
+}
+
+pub(crate) fn discard_ignored_functions(apis: ApiVec<FnPhase>) -> ApiVec<FnPhase> {
+    // Some APIs can't be generated, e.g. because they're protected.
+    // Now we've finished analyzing abstract types and constructors, we'll
+    // convert them to IgnoredItems.
+    let mut apis_new = ApiVec::new();
+    convert_apis(
+        apis,
+        &mut apis_new,
+        |name, fun, analysis| {
+            analysis.ignore_reason.clone()?;
+            Ok(Box::new(std::iter::once(Api::Function {
+                name,
+                fun,
+                analysis,
+            })))
+        },
+        Api::struct_unchanged,
+        Api::enum_unchanged,
+        Api::typedef_unchanged,
+    );
+    apis_new
+}
diff --git a/engine/src/conversion/analysis/allocators.rs b/engine/src/conversion/analysis/allocators.rs
new file mode 100644
index 0000000..3695de0
--- /dev/null
+++ b/engine/src/conversion/analysis/allocators.rs
@@ -0,0 +1,111 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Code to create functions to alloc and free while unitialized.
+
+use syn::{parse_quote, punctuated::Punctuated, token::Comma, FnArg, ReturnType};
+
+use crate::{
+    conversion::{
+        api::{Api, ApiName, CppVisibility, FuncToConvert, Provenance, References, TraitSynthesis},
+        apivec::ApiVec,
+    },
+    types::{make_ident, QualifiedName},
+};
+
+use super::{
+    fun::function_wrapper::{CppFunctionBody, CppFunctionKind},
+    pod::PodPhase,
+};
+
+pub(crate) fn create_alloc_and_frees(apis: ApiVec<PodPhase>) -> ApiVec<PodPhase> {
+    apis.into_iter()
+        .flat_map(|api| -> Box<dyn Iterator<Item = Api<PodPhase>>> {
+            match &api {
+                Api::Struct { name, .. } => {
+                    Box::new(create_alloc_and_free(name.name.clone()).chain(std::iter::once(api)))
+                }
+                Api::Subclass { name, .. } => {
+                    Box::new(create_alloc_and_free(name.cpp()).chain(std::iter::once(api)))
+                }
+                _ => Box::new(std::iter::once(api)),
+            }
+        })
+        .collect()
+}
+
+fn create_alloc_and_free(ty_name: QualifiedName) -> impl Iterator<Item = Api<PodPhase>> {
+    let typ = ty_name.to_type_path();
+    let free_inputs: Punctuated<FnArg, Comma> = parse_quote! {
+        arg0: *mut #typ
+    };
+    let alloc_return: ReturnType = parse_quote! {
+        -> *mut #typ
+    };
+    [
+        (
+            TraitSynthesis::AllocUninitialized(ty_name.clone()),
+            get_alloc_name(&ty_name),
+            Punctuated::new(),
+            alloc_return,
+            CppFunctionBody::AllocUninitialized(ty_name.clone()),
+        ),
+        (
+            TraitSynthesis::FreeUninitialized(ty_name.clone()),
+            get_free_name(&ty_name),
+            free_inputs,
+            ReturnType::Default,
+            CppFunctionBody::FreeUninitialized(ty_name.clone()),
+        ),
+    ]
+    .into_iter()
+    .map(
+        move |(synthesis, name, inputs, output, cpp_function_body)| {
+            let ident = name.get_final_ident();
+            let api_name = ApiName::new_from_qualified_name(name);
+            Api::Function {
+                name: api_name,
+                fun: Box::new(FuncToConvert {
+                    ident,
+                    doc_attrs: Vec::new(),
+                    inputs,
+                    output,
+                    vis: parse_quote! { pub },
+                    virtualness: crate::conversion::api::Virtualness::None,
+                    cpp_vis: CppVisibility::Public,
+                    special_member: None,
+                    unused_template_param: false,
+                    references: References::default(),
+                    original_name: None,
+                    self_ty: None,
+                    synthesized_this_type: None,
+                    synthetic_cpp: Some((cpp_function_body, CppFunctionKind::Function)),
+                    add_to_trait: Some(synthesis),
+                    is_deleted: false,
+                    provenance: Provenance::SynthesizedOther,
+                    variadic: false,
+                }),
+                analysis: (),
+            }
+        },
+    )
+}
+
+pub(crate) fn get_alloc_name(ty_name: &QualifiedName) -> QualifiedName {
+    get_name(ty_name, "alloc")
+}
+
+pub(crate) fn get_free_name(ty_name: &QualifiedName) -> QualifiedName {
+    get_name(ty_name, "free")
+}
+
+fn get_name(ty_name: &QualifiedName, label: &str) -> QualifiedName {
+    let name = format!("{}_{}", ty_name.get_final_item(), label);
+    let name_id = make_ident(name);
+    QualifiedName::new(ty_name.get_namespace(), name_id)
+}
diff --git a/engine/src/conversion/analysis/casts.rs b/engine/src/conversion/analysis/casts.rs
new file mode 100644
index 0000000..5f493f9
--- /dev/null
+++ b/engine/src/conversion/analysis/casts.rs
@@ -0,0 +1,141 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use itertools::Itertools;
+use quote::quote;
+use syn::{parse_quote, FnArg};
+
+use crate::{
+    conversion::{
+        api::{Api, ApiName, CastMutability, Provenance, References, TraitSynthesis},
+        apivec::ApiVec,
+    },
+    types::{make_ident, QualifiedName},
+};
+
+/// If A is a base of B, we might want to be able to cast from
+/// &B to &A, or from Pin<&mut A> to &B, or from Pin<&mut A> to &B.
+/// The first is OK; the others turn out to be hard due to all
+/// the Pin stuff. For now therefore, we simply don't allow them.
+/// But the related code may be useful in future so I'm keeping it around.
+const SUPPORT_MUTABLE_CASTS: bool = false;
+
+use super::{
+    fun::function_wrapper::{CppFunctionBody, CppFunctionKind},
+    pod::{PodAnalysis, PodPhase},
+};
+
+pub(crate) fn add_casts(apis: ApiVec<PodPhase>) -> ApiVec<PodPhase> {
+    apis.into_iter()
+        .flat_map(|api| {
+            let mut resultant_apis = match api {
+                Api::Struct {
+                    ref name,
+                    details: _,
+                    ref analysis,
+                } => create_casts(&name.name, analysis).collect_vec(),
+                _ => Vec::new(),
+            };
+            resultant_apis.push(api);
+            resultant_apis.into_iter()
+        })
+        .collect()
+}
+
+fn create_casts<'a>(
+    name: &'a QualifiedName,
+    analysis: &'a PodAnalysis,
+) -> impl Iterator<Item = Api<PodPhase>> + 'a {
+    // Create casts only to base classes which are on the allowlist
+    // because otherwise we won't know for sure whether they're abstract or not.
+    analysis
+        .castable_bases
+        .iter()
+        .flat_map(move |base| cast_types().map(|mutable| create_cast(name, base, mutable)))
+}
+
+/// Iterate through the types of cast we should make.
+fn cast_types() -> impl Iterator<Item = CastMutability> {
+    if SUPPORT_MUTABLE_CASTS {
+        vec![
+            CastMutability::ConstToConst,
+            CastMutability::MutToConst,
+            CastMutability::MutToMut,
+        ]
+        .into_iter()
+    } else {
+        vec![CastMutability::ConstToConst].into_iter()
+    }
+}
+
+fn create_cast(from: &QualifiedName, to: &QualifiedName, mutable: CastMutability) -> Api<PodPhase> {
+    let name = name_for_cast(from, to, mutable);
+    let ident = name.get_final_ident();
+    let from_typ = from.to_type_path();
+    let to_typ = to.to_type_path();
+    let return_mutability = match mutable {
+        CastMutability::ConstToConst | CastMutability::MutToConst => quote! { const },
+        CastMutability::MutToMut => quote! { mut },
+    };
+    let param_mutability = match mutable {
+        CastMutability::ConstToConst => quote! { const },
+        CastMutability::MutToConst | CastMutability::MutToMut => quote! { mut },
+    };
+    let fnarg: FnArg = parse_quote! {
+        this: * #param_mutability #from_typ
+    };
+    Api::Function {
+        name: ApiName::new_from_qualified_name(name),
+        fun: Box::new(crate::conversion::api::FuncToConvert {
+            ident,
+            doc_attrs: Vec::new(),
+            inputs: [fnarg].into_iter().collect(),
+            output: parse_quote! {
+                -> * #return_mutability #to_typ
+            },
+            vis: parse_quote! { pub },
+            virtualness: crate::conversion::api::Virtualness::None,
+            cpp_vis: crate::conversion::api::CppVisibility::Public,
+            special_member: None,
+            unused_template_param: false,
+            references: References::new_with_this_and_return_as_reference(),
+            original_name: None,
+            self_ty: Some(from.clone()),
+            synthesized_this_type: None,
+            add_to_trait: Some(TraitSynthesis::Cast {
+                to_type: to.clone(),
+                mutable,
+            }),
+            synthetic_cpp: Some((CppFunctionBody::Cast, CppFunctionKind::Function)),
+            is_deleted: false,
+            provenance: Provenance::SynthesizedOther,
+            variadic: false,
+        }),
+        analysis: (),
+    }
+}
+
+fn name_for_cast(
+    from: &QualifiedName,
+    to: &QualifiedName,
+    mutable: CastMutability,
+) -> QualifiedName {
+    let suffix = match mutable {
+        CastMutability::ConstToConst => "",
+        CastMutability::MutToConst => "_to_const",
+        CastMutability::MutToMut => "_mut",
+    };
+    let name = format!(
+        "cast_{}_to_{}{}",
+        from.get_final_item(),
+        to.get_final_item(),
+        suffix
+    );
+    let name = make_ident(name);
+    QualifiedName::new(from.get_namespace(), name)
+}
diff --git a/engine/src/conversion/analysis/constructor_deps.rs b/engine/src/conversion/analysis/constructor_deps.rs
new file mode 100644
index 0000000..63b6dbe
--- /dev/null
+++ b/engine/src/conversion/analysis/constructor_deps.rs
@@ -0,0 +1,105 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use indexmap::map::IndexMap as HashMap;
+
+use crate::{
+    conversion::{
+        api::{Api, ApiName, StructDetails, TypeKind},
+        apivec::ApiVec,
+        convert_error::ConvertErrorWithContext,
+        error_reporter::convert_apis,
+    },
+    types::QualifiedName,
+};
+
+use super::fun::{
+    FnAnalysis, FnKind, FnPhase, FnPrePhase2, PodAndConstructorAnalysis, PodAndDepAnalysis,
+    TraitMethodKind,
+};
+
+/// We've now analyzed all functions (including both implicit and explicit
+/// constructors). Decorate each struct with a note of its constructors,
+/// which will later be used as edges in the garbage collection, because
+/// typically any use of a type will require us to call its copy or move
+/// constructor. The same applies to its alloc/free functions.
+pub(crate) fn decorate_types_with_constructor_deps(apis: ApiVec<FnPrePhase2>) -> ApiVec<FnPhase> {
+    let mut constructors_and_allocators_by_type = find_important_constructors(&apis);
+    let mut results = ApiVec::new();
+    convert_apis(
+        apis,
+        &mut results,
+        Api::fun_unchanged,
+        |name, details, pod| {
+            decorate_struct(name, details, pod, &mut constructors_and_allocators_by_type)
+        },
+        Api::enum_unchanged,
+        Api::typedef_unchanged,
+    );
+    results
+}
+
+fn decorate_struct(
+    name: ApiName,
+    details: Box<StructDetails>,
+    fn_struct: PodAndConstructorAnalysis,
+    constructors_and_allocators_by_type: &mut HashMap<QualifiedName, Vec<QualifiedName>>,
+) -> Result<Box<dyn Iterator<Item = Api<FnPhase>>>, ConvertErrorWithContext> {
+    let pod = fn_struct.pod;
+    let is_abstract = matches!(pod.kind, TypeKind::Abstract);
+    let constructor_and_allocator_deps = if is_abstract || pod.is_generic {
+        Vec::new()
+    } else {
+        constructors_and_allocators_by_type
+            .remove(&name.name)
+            .unwrap_or_default()
+    };
+    Ok(Box::new(std::iter::once(Api::Struct {
+        name,
+        details,
+        analysis: PodAndDepAnalysis {
+            pod,
+            constructor_and_allocator_deps,
+            constructors: fn_struct.constructors,
+        },
+    })))
+}
+
+fn find_important_constructors(
+    apis: &ApiVec<FnPrePhase2>,
+) -> HashMap<QualifiedName, Vec<QualifiedName>> {
+    let mut results: HashMap<QualifiedName, Vec<QualifiedName>> = HashMap::new();
+    for api in apis.iter() {
+        if let Api::Function {
+            name,
+            analysis:
+                FnAnalysis {
+                    kind:
+                        FnKind::TraitMethod {
+                            kind:
+                                TraitMethodKind::Alloc
+                                | TraitMethodKind::Dealloc
+                                | TraitMethodKind::CopyConstructor
+                                | TraitMethodKind::MoveConstructor,
+                            impl_for,
+                            ..
+                        },
+                    ignore_reason: Ok(_),
+                    ..
+                },
+            ..
+        } = api
+        {
+            results
+                .entry(impl_for.clone())
+                .or_default()
+                .push(name.name.clone())
+        }
+    }
+    results
+}
diff --git a/engine/src/conversion/analysis/ctypes.rs b/engine/src/conversion/analysis/ctypes.rs
new file mode 100644
index 0000000..b2ce840
--- /dev/null
+++ b/engine/src/conversion/analysis/ctypes.rs
@@ -0,0 +1,36 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use indexmap::map::IndexMap as HashMap;
+
+use syn::Ident;
+
+use crate::conversion::api::ApiName;
+use crate::conversion::apivec::ApiVec;
+use crate::types::Namespace;
+use crate::{conversion::api::Api, known_types::known_types, types::QualifiedName};
+
+use super::deps::HasDependencies;
+use super::fun::FnPhase;
+
+/// Spot any variable-length C types (e.g. unsigned long)
+/// used in the [Api]s and append those as extra APIs.
+pub(crate) fn append_ctype_information(apis: &mut ApiVec<FnPhase>) {
+    let ctypes: HashMap<Ident, QualifiedName> = apis
+        .iter()
+        .flat_map(|api| api.deps())
+        .filter(|ty| known_types().is_ctype(ty))
+        .map(|ty| (ty.get_final_ident(), ty.clone()))
+        .collect();
+    for (id, typename) in ctypes {
+        apis.push(Api::CType {
+            name: ApiName::new(&Namespace::new(), id),
+            typename,
+        });
+    }
+}
diff --git a/engine/src/conversion/analysis/deps.rs b/engine/src/conversion/analysis/deps.rs
new file mode 100644
index 0000000..7aca96c
--- /dev/null
+++ b/engine/src/conversion/analysis/deps.rs
@@ -0,0 +1,116 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use itertools::Itertools;
+
+use crate::{
+    conversion::api::{Api, TypeKind},
+    types::QualifiedName,
+};
+
+use super::{
+    fun::{FnPhase, FnPrePhase1, PodAndDepAnalysis},
+    pod::PodAnalysis,
+    tdef::TypedefAnalysis,
+};
+
+pub(crate) trait HasDependencies {
+    fn name(&self) -> &QualifiedName;
+    fn deps(&self) -> Box<dyn Iterator<Item = &QualifiedName> + '_>;
+
+    fn format_deps(&self) -> String {
+        self.deps().join(",")
+    }
+}
+
+impl HasDependencies for Api<FnPrePhase1> {
+    fn deps(&self) -> Box<dyn Iterator<Item = &QualifiedName> + '_> {
+        match self {
+            Api::Typedef {
+                old_tyname,
+                analysis: TypedefAnalysis { deps, .. },
+                ..
+            } => Box::new(old_tyname.iter().chain(deps.iter())),
+            Api::Struct {
+                analysis:
+                    PodAnalysis {
+                        kind: TypeKind::Pod,
+                        bases,
+                        field_deps,
+                        ..
+                    },
+                ..
+            } => Box::new(field_deps.iter().chain(bases.iter())),
+            Api::Function { analysis, .. } => Box::new(analysis.deps.iter()),
+            Api::Subclass {
+                name: _,
+                superclass,
+            } => Box::new(std::iter::once(superclass)),
+            Api::RustSubclassFn { details, .. } => Box::new(details.dependencies.iter()),
+            Api::RustFn { receiver, .. } => Box::new(receiver.iter()),
+            _ => Box::new(std::iter::empty()),
+        }
+    }
+
+    fn name(&self) -> &QualifiedName {
+        self.name()
+    }
+}
+
+impl HasDependencies for Api<FnPhase> {
+    /// Any dependencies on other APIs which this API has.
+    fn deps(&self) -> Box<dyn Iterator<Item = &QualifiedName> + '_> {
+        match self {
+            Api::Typedef {
+                old_tyname,
+                analysis: TypedefAnalysis { deps, .. },
+                ..
+            } => Box::new(old_tyname.iter().chain(deps.iter())),
+            Api::Struct {
+                analysis:
+                    PodAndDepAnalysis {
+                        pod:
+                            PodAnalysis {
+                                kind: TypeKind::Pod,
+                                bases,
+                                field_deps,
+                                ..
+                            },
+                        constructor_and_allocator_deps,
+                        ..
+                    },
+                ..
+            } => Box::new(
+                field_deps
+                    .iter()
+                    .chain(bases.iter())
+                    .chain(constructor_and_allocator_deps.iter()),
+            ),
+            Api::Struct {
+                analysis:
+                    PodAndDepAnalysis {
+                        constructor_and_allocator_deps,
+                        ..
+                    },
+                ..
+            } => Box::new(constructor_and_allocator_deps.iter()),
+            Api::Function { analysis, .. } => Box::new(analysis.deps.iter()),
+            Api::Subclass {
+                name: _,
+                superclass,
+            } => Box::new(std::iter::once(superclass)),
+            Api::RustSubclassFn { details, .. } => Box::new(details.dependencies.iter()),
+            Api::RustFn { receiver, .. } => Box::new(receiver.iter()),
+            _ => Box::new(std::iter::empty()),
+        }
+    }
+
+    fn name(&self) -> &QualifiedName {
+        self.name()
+    }
+}
diff --git a/engine/src/conversion/analysis/depth_first.rs b/engine/src/conversion/analysis/depth_first.rs
new file mode 100644
index 0000000..02459e1
--- /dev/null
+++ b/engine/src/conversion/analysis/depth_first.rs
@@ -0,0 +1,98 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use indexmap::set::IndexSet as HashSet;
+use std::collections::VecDeque;
+use std::fmt::Debug;
+
+use itertools::Itertools;
+
+use crate::types::QualifiedName;
+
+use super::deps::HasDependencies;
+
+/// Return APIs in a depth-first order, i.e. those with no dependencies first.
+pub(super) fn depth_first<'a, T: HasDependencies + Debug + 'a>(
+    inputs: impl Iterator<Item = &'a T> + 'a,
+) -> impl Iterator<Item = &'a T> {
+    let queue: VecDeque<_> = inputs.collect();
+    let yet_to_do = queue.iter().map(|api| api.name()).collect();
+    DepthFirstIter { queue, yet_to_do }
+}
+
+struct DepthFirstIter<'a, T: HasDependencies + Debug> {
+    queue: VecDeque<&'a T>,
+    yet_to_do: HashSet<&'a QualifiedName>,
+}
+
+impl<'a, T: HasDependencies + Debug> Iterator for DepthFirstIter<'a, T> {
+    type Item = &'a T;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let first_candidate = self.queue.get(0).map(|api| api.name());
+        while let Some(candidate) = self.queue.pop_front() {
+            if !candidate.deps().any(|d| self.yet_to_do.contains(&d)) {
+                self.yet_to_do.remove(candidate.name());
+                return Some(candidate);
+            }
+            self.queue.push_back(candidate);
+            if self.queue.get(0).map(|api| api.name()) == first_candidate {
+                panic!(
+                    "Failed to find a candidate; there must be a circular dependency. Queue is {}",
+                    self.queue
+                        .iter()
+                        .map(|item| format!("{}: {}", item.name(), item.deps().join(",")))
+                        .join("\n")
+                );
+            }
+        }
+        None
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use crate::types::QualifiedName;
+
+    use super::{depth_first, HasDependencies};
+
+    #[derive(Debug)]
+    struct Thing(QualifiedName, Vec<QualifiedName>);
+
+    impl HasDependencies for Thing {
+        fn name(&self) -> &QualifiedName {
+            &self.0
+        }
+
+        fn deps(&self) -> Box<dyn Iterator<Item = &QualifiedName> + '_> {
+            Box::new(self.1.iter())
+        }
+    }
+
+    #[test]
+    fn test() {
+        let a = Thing(QualifiedName::new_from_cpp_name("a"), vec![]);
+        let b = Thing(
+            QualifiedName::new_from_cpp_name("b"),
+            vec![
+                QualifiedName::new_from_cpp_name("a"),
+                QualifiedName::new_from_cpp_name("c"),
+            ],
+        );
+        let c = Thing(
+            QualifiedName::new_from_cpp_name("c"),
+            vec![QualifiedName::new_from_cpp_name("a")],
+        );
+        let api_list = vec![a, b, c];
+        let mut it = depth_first(api_list.iter());
+        assert_eq!(it.next().unwrap().0, QualifiedName::new_from_cpp_name("a"));
+        assert_eq!(it.next().unwrap().0, QualifiedName::new_from_cpp_name("c"));
+        assert_eq!(it.next().unwrap().0, QualifiedName::new_from_cpp_name("b"));
+        assert!(it.next().is_none());
+    }
+}
diff --git a/engine/src/conversion/analysis/doc_label.rs b/engine/src/conversion/analysis/doc_label.rs
new file mode 100644
index 0000000..767da87
--- /dev/null
+++ b/engine/src/conversion/analysis/doc_label.rs
@@ -0,0 +1,17 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use proc_macro2::Span;
+use syn::{parse_quote, Attribute};
+
+pub(crate) fn make_doc_attrs(label: String) -> Vec<Attribute> {
+    let hexathorpe = syn::token::Pound(Span::call_site());
+    vec![parse_quote! {
+        #hexathorpe [doc = #label]
+    }]
+}
diff --git a/engine/src/conversion/analysis/fun/bridge_name_tracker.rs b/engine/src/conversion/analysis/fun/bridge_name_tracker.rs
new file mode 100644
index 0000000..7e81c59
--- /dev/null
+++ b/engine/src/conversion/analysis/fun/bridge_name_tracker.rs
@@ -0,0 +1,158 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::types::Namespace;
+use itertools::Itertools;
+use std::collections::HashMap;
+
+/// Type to generate unique names for entries in the [cxx::bridge]
+/// mod which is flat.
+///
+/// # All about the names involved in autocxx
+///
+/// A given function may have many, many names. From C++ to Rust...
+///
+/// 1. The actual C++ name. Fixed, obviously.
+/// 2. The name reported by bindgen. Not always the same, as
+///    bindgen may generate a different name for different overloads.
+///    See `overload_tracker` for the conversion here.
+/// 3. The name in the cxx::bridge mod. This is a flat namespace,
+///    and it's the responsibility of this type to generate a
+///    suitable name here.
+///    If this is different from the C++ name in (1), we'll
+///    add a #[cxx_name] attribute to the cxx::bridge declaration.
+/// 4. The name we wish to present to Rust users. Again, we have
+///    to take stock of the fact Rust doesn't support overloading
+///    so two different functions called 'get' may end up being
+///    'get' and 'get1'. Yet, this may not be the same as the
+///    bindgen name in (2) because we wish to generate such number
+///    sequences on a per-type basis, whilst bindgen does it globally.
+///
+/// This fourth name, the final Rust user name, may be finagled
+/// into place in three different ways:
+/// 1. For methods, we generate an 'impl' block where the
+///    method name is the intended name, but it calls the
+///    cxxbridge name.
+/// 2. For simple functions, we use the #[rust_name] attribute.
+/// 3. Occasionally, there can be conflicts in the rust_name
+///    namespace (if there are two identically named functions
+///    in different C++ namespaces). That's detected by
+///    rust_name_tracker.rs. In such a case, we'll omit the
+///    #[rust_name] attribute and instead generate a 'use A = B;'
+///    declaration in the mod which we generate for the output
+///    namespace.
+#[derive(Default)]
+pub(crate) struct BridgeNameTracker {
+    next_cxx_bridge_name_for_prefix: HashMap<String, usize>,
+}
+
+impl BridgeNameTracker {
+    pub(crate) fn new() -> Self {
+        Self::default()
+    }
+
+    /// Figure out the least confusing unique name for this function in the
+    /// cxx::bridge section, which has a flat namespace.
+    /// We mostly just qualify the name with the namespace_with_underscores.
+    /// It doesn't really matter; we'll rebind these things to
+    /// better Rust-side names so it's really just a matter of how it shows up
+    /// in stack traces and for our own sanity as maintainers of autocxx.
+    /// This may become unnecessary if and when cxx supports hierarchic
+    /// namespace mods.
+    /// There is a slight advantage in using the same name as either the
+    /// Rust or C++ symbols as it reduces the amount of rebinding required
+    /// by means of cxx_name or rust_name attributes. In extreme cases it
+    /// may even allow us to remove whole impl blocks. So we may wish to try
+    /// harder to find better names in future instead of always prepending
+    /// the namespace.
+    pub(crate) fn get_unique_cxx_bridge_name(
+        &mut self,
+        type_name: Option<&str>,
+        found_name: &str,
+        ns: &Namespace,
+    ) -> String {
+        let found_name = if found_name == "new" {
+            "new_autocxx"
+        } else {
+            found_name
+        };
+        let count = self
+            .next_cxx_bridge_name_for_prefix
+            .entry(found_name.to_string())
+            .or_default();
+        if *count == 0 {
+            // Oh, good, we can use this function name as-is.
+            *count += 1;
+            return found_name.to_string();
+        }
+        let prefix = ns
+            .iter()
+            .cloned()
+            .chain(type_name.iter().map(|x| x.to_string()))
+            .chain(std::iter::once(found_name.to_string()))
+            .join("_");
+        let count = self
+            .next_cxx_bridge_name_for_prefix
+            .entry(prefix.clone())
+            .or_default();
+        if *count == 0 {
+            *count += 1;
+            prefix
+        } else {
+            let r = format!("{}_autocxx{}", prefix, count);
+            *count += 1;
+            r
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::types::Namespace;
+
+    use super::BridgeNameTracker;
+
+    #[test]
+    fn test() {
+        let mut bnt = BridgeNameTracker::new();
+        let ns_root = Namespace::new();
+        let ns_a = Namespace::from_user_input("A");
+        let ns_b = Namespace::from_user_input("B");
+        let ns_ab = Namespace::from_user_input("A::B");
+        assert_eq!(bnt.get_unique_cxx_bridge_name(None, "do", &ns_root), "do");
+        assert_eq!(
+            bnt.get_unique_cxx_bridge_name(None, "do", &ns_root),
+            "do_autocxx1"
+        );
+        assert_eq!(bnt.get_unique_cxx_bridge_name(None, "did", &ns_root), "did");
+        assert_eq!(
+            bnt.get_unique_cxx_bridge_name(Some("ty1"), "do", &ns_root),
+            "ty1_do"
+        );
+        assert_eq!(
+            bnt.get_unique_cxx_bridge_name(Some("ty1"), "do", &ns_root),
+            "ty1_do_autocxx1"
+        );
+        assert_eq!(
+            bnt.get_unique_cxx_bridge_name(Some("ty2"), "do", &ns_root),
+            "ty2_do"
+        );
+        assert_eq!(
+            bnt.get_unique_cxx_bridge_name(Some("ty"), "do", &ns_a),
+            "A_ty_do"
+        );
+        assert_eq!(
+            bnt.get_unique_cxx_bridge_name(Some("ty"), "do", &ns_b),
+            "B_ty_do"
+        );
+        assert_eq!(
+            bnt.get_unique_cxx_bridge_name(Some("ty"), "do", &ns_ab),
+            "A_B_ty_do"
+        );
+    }
+}
diff --git a/engine/src/conversion/analysis/fun/function_wrapper.rs b/engine/src/conversion/analysis/fun/function_wrapper.rs
new file mode 100644
index 0000000..f2f6aaa
--- /dev/null
+++ b/engine/src/conversion/analysis/fun/function_wrapper.rs
@@ -0,0 +1,213 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::{
+    conversion::api::SubclassName,
+    types::{Namespace, QualifiedName},
+};
+use syn::{parse_quote, Ident, Type};
+
+#[derive(Clone, Debug)]
+pub(crate) enum CppConversionType {
+    None,
+    Move,
+    FromUniquePtrToValue,
+    FromPtrToValue,
+    FromValueToUniquePtr,
+    FromPtrToMove,
+    /// Ignored in the sense that it isn't passed into the C++ function.
+    IgnoredPlacementPtrParameter,
+    FromReturnValueToPlacementPtr,
+}
+
+impl CppConversionType {
+    /// If we've found a function which does X to its parameter, what
+    /// is the opposite of X? This is used for subclasses where calls
+    /// from Rust to C++ might also involve calls from C++ to Rust.
+    fn inverse(&self) -> Self {
+        match self {
+            CppConversionType::None => CppConversionType::None,
+            CppConversionType::FromUniquePtrToValue | CppConversionType::FromPtrToValue => {
+                CppConversionType::FromValueToUniquePtr
+            }
+            CppConversionType::FromValueToUniquePtr => CppConversionType::FromUniquePtrToValue,
+            _ => panic!("Did not expect to have to invert this conversion"),
+        }
+    }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) enum RustConversionType {
+    None,
+    FromStr,
+    ToBoxedUpHolder(SubclassName),
+    FromPinMaybeUninitToPtr,
+    FromPinMoveRefToPtr,
+    FromTypeToPtr,
+    FromValueParamToPtr,
+    FromPlacementParamToNewReturn,
+    FromRValueParamToPtr,
+}
+
+impl RustConversionType {
+    pub(crate) fn requires_mutability(&self) -> Option<syn::token::Mut> {
+        match self {
+            Self::FromPinMoveRefToPtr => Some(parse_quote! { mut }),
+            _ => None,
+        }
+    }
+}
+
+/// A policy for converting types. Conversion may occur on both the Rust and
+/// C++ side. The most complex example is a C++ function which takes
+/// std::string by value, which might do this:
+/// * Client Rust code: `&str`
+/// * Rust wrapper function: converts `&str` to `UniquePtr<CxxString>`
+/// * cxx::bridge mod: refers to `UniquePtr<CxxString>`
+/// * C++ wrapper function converts `std::unique_ptr<std::string>` to just
+///   `std::string`
+/// * Finally, the actual C++ API receives a `std::string` by value.
+/// The implementation here is distributed across this file, and
+/// `function_wrapper_rs` and `function_wrapper_cpp`.
+#[derive(Clone)]
+pub(crate) struct TypeConversionPolicy {
+    pub(crate) unwrapped_type: Type,
+    pub(crate) cpp_conversion: CppConversionType,
+    pub(crate) rust_conversion: RustConversionType,
+}
+
+impl TypeConversionPolicy {
+    pub(crate) fn new_unconverted(ty: Type) -> Self {
+        TypeConversionPolicy {
+            unwrapped_type: ty,
+            cpp_conversion: CppConversionType::None,
+            rust_conversion: RustConversionType::None,
+        }
+    }
+
+    pub(crate) fn new_to_unique_ptr(ty: Type) -> Self {
+        TypeConversionPolicy {
+            unwrapped_type: ty,
+            cpp_conversion: CppConversionType::FromValueToUniquePtr,
+            rust_conversion: RustConversionType::None,
+        }
+    }
+
+    pub(crate) fn new_for_placement_return(ty: Type) -> Self {
+        TypeConversionPolicy {
+            unwrapped_type: ty,
+            cpp_conversion: CppConversionType::FromReturnValueToPlacementPtr,
+            // Rust conversion is marked as none here, since this policy
+            // will be applied to the return value, and the Rust-side
+            // shenanigans applies to the placement new *parameter*
+            rust_conversion: RustConversionType::None,
+        }
+    }
+
+    pub(crate) fn cpp_work_needed(&self) -> bool {
+        !matches!(self.cpp_conversion, CppConversionType::None)
+    }
+
+    pub(crate) fn unconverted_rust_type(&self) -> Type {
+        match self.cpp_conversion {
+            CppConversionType::FromValueToUniquePtr => self.make_unique_ptr_type(),
+            _ => self.unwrapped_type.clone(),
+        }
+    }
+
+    pub(crate) fn converted_rust_type(&self) -> Type {
+        match self.cpp_conversion {
+            CppConversionType::FromUniquePtrToValue => self.make_unique_ptr_type(),
+            CppConversionType::FromPtrToValue => {
+                let innerty = &self.unwrapped_type;
+                parse_quote! {
+                    *mut #innerty
+                }
+            }
+            _ => self.unwrapped_type.clone(),
+        }
+    }
+
+    fn make_unique_ptr_type(&self) -> Type {
+        let innerty = &self.unwrapped_type;
+        parse_quote! {
+            cxx::UniquePtr < #innerty >
+        }
+    }
+
+    pub(crate) fn rust_work_needed(&self) -> bool {
+        !matches!(self.rust_conversion, RustConversionType::None)
+    }
+
+    /// Subclass support involves calls from Rust -> C++, but
+    /// also from C++ -> Rust. Work out the correct argument conversion
+    /// type for the latter call, when given the former.
+    pub(crate) fn inverse(&self) -> Self {
+        Self {
+            unwrapped_type: self.unwrapped_type.clone(),
+            cpp_conversion: self.cpp_conversion.inverse(),
+            rust_conversion: self.rust_conversion.clone(),
+        }
+    }
+
+    pub(crate) fn bridge_unsafe_needed(&self) -> bool {
+        matches!(
+            self.rust_conversion,
+            RustConversionType::FromValueParamToPtr
+                | RustConversionType::FromRValueParamToPtr
+                | RustConversionType::FromPlacementParamToNewReturn
+        )
+    }
+
+    pub(crate) fn is_placement_parameter(&self) -> bool {
+        matches!(
+            self.cpp_conversion,
+            CppConversionType::IgnoredPlacementPtrParameter
+        )
+    }
+
+    pub(crate) fn populate_return_value(&self) -> bool {
+        !matches!(
+            self.cpp_conversion,
+            CppConversionType::FromReturnValueToPlacementPtr
+        )
+    }
+}
+
+#[derive(Clone)]
+pub(crate) enum CppFunctionBody {
+    FunctionCall(Namespace, Ident),
+    StaticMethodCall(Namespace, Ident, Ident),
+    PlacementNew(Namespace, Ident),
+    ConstructSuperclass(String),
+    Cast,
+    Destructor(Namespace, Ident),
+    AllocUninitialized(QualifiedName),
+    FreeUninitialized(QualifiedName),
+}
+
+#[derive(Clone)]
+pub(crate) enum CppFunctionKind {
+    Function,
+    Method,
+    Constructor,
+    ConstMethod,
+    SynthesizedConstructor,
+}
+
+#[derive(Clone)]
+pub(crate) struct CppFunction {
+    pub(crate) payload: CppFunctionBody,
+    pub(crate) wrapper_function_name: Ident,
+    pub(crate) original_cpp_name: String,
+    pub(crate) return_conversion: Option<TypeConversionPolicy>,
+    pub(crate) argument_conversion: Vec<TypeConversionPolicy>,
+    pub(crate) kind: CppFunctionKind,
+    pub(crate) pass_obs_field: bool,
+    pub(crate) qualification: Option<QualifiedName>,
+}
diff --git a/engine/src/conversion/analysis/fun/implicit_constructors.rs b/engine/src/conversion/analysis/fun/implicit_constructors.rs
new file mode 100644
index 0000000..469ed89
--- /dev/null
+++ b/engine/src/conversion/analysis/fun/implicit_constructors.rs
@@ -0,0 +1,695 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use indexmap::map::IndexMap as HashMap;
+use indexmap::{map::Entry, set::IndexSet as HashSet};
+
+use syn::{Type, TypeArray};
+
+use crate::{
+    conversion::{
+        analysis::{depth_first::depth_first, pod::PodAnalysis, type_converter::TypeKind},
+        api::{Api, ApiName, CppVisibility, FuncToConvert, SpecialMemberKind},
+        apivec::ApiVec,
+        convert_error::ConvertErrorWithContext,
+        ConvertError,
+    },
+    known_types::{known_types, KnownTypeConstructorDetails},
+    types::QualifiedName,
+};
+
+use super::{FnAnalysis, FnKind, FnPrePhase1, MethodKind, ReceiverMutability, TraitMethodKind};
+
+/// Indicates what we found out about a category of special member function.
+///
+/// In the end, we only care whether it's public and exists, but we track a bit more information to
+/// support determining the information for dependent classes.
+#[derive(Debug, Copy, Clone)]
+pub(super) enum SpecialMemberFound {
+    /// This covers being deleted in any way:
+    ///   * Explicitly deleted
+    ///   * Implicitly defaulted when that means being deleted
+    ///   * Explicitly defaulted when that means being deleted
+    ///
+    /// It also covers not being either user declared or implicitly defaulted.
+    NotPresent,
+    /// Implicit special member functions, indicated by this, are always public.
+    Implicit,
+    /// This covers being explicitly defaulted (when that is not deleted) or being user-defined.
+    Explicit(CppVisibility),
+}
+
+impl SpecialMemberFound {
+    /// Returns whether code outside of subclasses can call this special member function.
+    pub fn callable_any(&self) -> bool {
+        matches!(self, Self::Explicit(CppVisibility::Public) | Self::Implicit)
+    }
+
+    /// Returns whether code in a subclass can call this special member function.
+    pub fn callable_subclass(&self) -> bool {
+        matches!(
+            self,
+            Self::Explicit(CppVisibility::Public)
+                | Self::Explicit(CppVisibility::Protected)
+                | Self::Implicit
+        )
+    }
+
+    /// Returns whether this exists at all. Note that this will return true even if it's private,
+    /// which is generally not very useful, but does come into play for some rules around which
+    /// default special member functions are deleted vs don't exist.
+    pub fn exists(&self) -> bool {
+        matches!(self, Self::Explicit(_) | Self::Implicit)
+    }
+
+    pub fn exists_implicit(&self) -> bool {
+        matches!(self, Self::Implicit)
+    }
+
+    pub fn exists_explicit(&self) -> bool {
+        matches!(self, Self::Explicit(_))
+    }
+}
+
+/// Information about which special member functions exist based on the C++ rules.
+///
+/// Not all of this information is used directly, but we need to track it to determine the
+/// information we do need for classes which are used as members or base classes.
+#[derive(Debug, Clone)]
+pub(super) struct ItemsFound {
+    pub(super) default_constructor: SpecialMemberFound,
+    pub(super) destructor: SpecialMemberFound,
+    pub(super) const_copy_constructor: SpecialMemberFound,
+    /// Remember that [`const_copy_constructor`] may be used in place of this if it exists.
+    pub(super) non_const_copy_constructor: SpecialMemberFound,
+    pub(super) move_constructor: SpecialMemberFound,
+
+    /// The full name of the type. We identify instances by [`QualifiedName`], because that's
+    /// the only thing which [`FnKind::Method`] has to tie it to, and that's unique enough for
+    /// identification.  However, when generating functions for implicit special members, we need
+    /// the extra information here.
+    ///
+    /// Will always be `Some` if any of the other fields are [`SpecialMemberFound::Implict`],
+    /// otherwise optional.
+    pub(super) name: Option<ApiName>,
+}
+
+impl ItemsFound {
+    /// Returns whether we should generate a default constructor wrapper, because bindgen won't do
+    /// one for the implicit default constructor which exists.
+    pub(super) fn implicit_default_constructor_needed(&self) -> bool {
+        self.default_constructor.exists_implicit()
+    }
+
+    /// Returns whether we should generate a copy constructor wrapper, because bindgen won't do one
+    /// for the implicit copy constructor which exists.
+    pub(super) fn implicit_copy_constructor_needed(&self) -> bool {
+        let any_implicit_copy = self.const_copy_constructor.exists_implicit()
+            || self.non_const_copy_constructor.exists_implicit();
+        let no_explicit_copy = !(self.const_copy_constructor.exists_explicit()
+            || self.non_const_copy_constructor.exists_explicit());
+        any_implicit_copy && no_explicit_copy
+    }
+
+    /// Returns whether we should generate a move constructor wrapper, because bindgen won't do one
+    /// for the implicit move constructor which exists.
+    pub(super) fn implicit_move_constructor_needed(&self) -> bool {
+        self.move_constructor.exists_implicit()
+    }
+
+    /// Returns whether we should generate a destructor wrapper, because bindgen won't do one for
+    /// the implicit destructor which exists.
+    pub(super) fn implicit_destructor_needed(&self) -> bool {
+        self.destructor.exists_implicit()
+    }
+}
+#[derive(Hash, Eq, PartialEq)]
+enum ExplicitKind {
+    DefaultConstructor,
+    ConstCopyConstructor,
+    NonConstCopyConstructor,
+    MoveConstructor,
+    OtherConstructor,
+    Destructor,
+    ConstCopyAssignmentOperator,
+    NonConstCopyAssignmentOperator,
+    MoveAssignmentOperator,
+}
+
+/// Denotes a specific kind of explicit member function that we found.
+#[derive(Hash, Eq, PartialEq)]
+struct ExplicitType {
+    ty: QualifiedName,
+    kind: ExplicitKind,
+}
+
+/// Includes information about an explicit special member function which was found.
+// TODO: Add Defaulted(CppVisibility) for https://github.com/google/autocxx/issues/815.
+#[derive(Copy, Clone, Debug)]
+enum ExplicitFound {
+    UserDefined(CppVisibility),
+    /// Note that this always means explicitly deleted, because this enum only represents
+    /// explicit declarations.
+    Deleted,
+    /// Indicates that we found more than one explicit of this kind. This is possible with most of
+    /// them, and we just bail and mostly act as if they're deleted. We'd have to decide whether
+    /// they're ambiguous to use them, which is really complicated.
+    Multiple,
+}
+
+/// Analyzes which constructors are present for each type.
+///
+/// If a type has explicit constructors, bindgen will generate corresponding
+/// constructor functions, which we'll have already converted to make_unique methods.
+/// For types with implicit constructors, we enumerate them here.
+///
+/// It is tempting to make this a separate analysis phase, to be run later than
+/// the function analysis; but that would make the code much more complex as it
+/// would need to output a `FnAnalysisBody`. By running it as part of this phase
+/// we can simply generate the sort of thing bindgen generates, then ask
+/// the existing code in this phase to figure out what to do with it.
+pub(super) fn find_constructors_present(
+    apis: &ApiVec<FnPrePhase1>,
+) -> HashMap<QualifiedName, ItemsFound> {
+    let (explicits, unknown_types) = find_explicit_items(apis);
+
+    // These contain all the classes we've seen so far with the relevant properties on their
+    // constructors of each kind. We iterate via [`depth_first`], so analyzing later classes
+    // just needs to check these.
+    //
+    // Important only to ask for a depth-first analysis of structs, because
+    // when all APIs are considered there may be reference loops and that would
+    // panic.
+    //
+    // These analyses include all bases and members of each class.
+    let mut all_items_found: HashMap<QualifiedName, ItemsFound> = HashMap::new();
+
+    for api in depth_first(apis.iter()) {
+        if let Api::Struct {
+            name,
+            analysis:
+                PodAnalysis {
+                    bases,
+                    field_info,
+                    is_generic: false,
+                    in_anonymous_namespace: false,
+                    ..
+                },
+            details,
+            ..
+        } = api
+        {
+            let find_explicit = |kind: ExplicitKind| -> Option<&ExplicitFound> {
+                explicits.get(&ExplicitType {
+                    ty: name.name.clone(),
+                    kind,
+                })
+            };
+            let get_items_found = |qn: &QualifiedName| -> Option<ItemsFound> {
+                if let Some(constructor_details) = known_types().get_constructor_details(qn) {
+                    Some(known_type_items_found(constructor_details))
+                } else {
+                    all_items_found.get(qn).cloned()
+                }
+            };
+            let bases_items_found: Vec<_> = bases.iter().map_while(get_items_found).collect();
+            let fields_items_found: Vec<_> = field_info
+                .iter()
+                .filter_map(|field_info| match field_info.type_kind {
+                    TypeKind::Regular | TypeKind::SubclassHolder(_) => match field_info.ty {
+                        Type::Path(ref qn) => get_items_found(&QualifiedName::from_type_path(qn)),
+                        Type::Array(TypeArray { ref elem, .. }) => match elem.as_ref() {
+                            Type::Path(ref qn) => {
+                                get_items_found(&QualifiedName::from_type_path(qn))
+                            }
+                            _ => None,
+                        },
+                        _ => None,
+                    },
+                    // TODO: https://github.com/google/autocxx/issues/865 Figure out how to
+                    // differentiate between pointers and references coming from C++. Pointers
+                    // have a default constructor.
+                    TypeKind::Pointer
+                    | TypeKind::Reference
+                    | TypeKind::MutableReference
+                    | TypeKind::RValueReference => Some(ItemsFound {
+                        default_constructor: SpecialMemberFound::NotPresent,
+                        destructor: SpecialMemberFound::Implicit,
+                        const_copy_constructor: SpecialMemberFound::Implicit,
+                        non_const_copy_constructor: SpecialMemberFound::NotPresent,
+                        move_constructor: SpecialMemberFound::Implicit,
+                        name: Some(name.clone()),
+                    }),
+                })
+                .collect();
+            let has_rvalue_reference_fields = details.has_rvalue_reference_fields;
+
+            // Check that all the bases and field types are known first. This combined with
+            // iterating via [`depth_first`] means we can safely search in `items_found` for all of
+            // them.
+            //
+            // Conservatively, we will not acknowledge the existence of most defaulted or implicit
+            // special member functions for any struct/class where we don't fully understand all
+            // field types.  However, we can still look for explictly declared versions and use
+            // those. See below for destructors.
+            //
+            // We need to extend our knowledge to understand the constructor behavior of things in
+            // known_types.rs, then we'll be able to cope with types which contain strings,
+            // unique_ptrs etc.
+            let items_found = if bases_items_found.len() != bases.len()
+                || fields_items_found.len() != field_info.len()
+                || unknown_types.contains(&name.name)
+            {
+                let is_explicit = |kind: ExplicitKind| -> SpecialMemberFound {
+                    // TODO: For https://github.com/google/autocxx/issues/815, map
+                    // ExplicitFound::Defaulted(_) to NotPresent.
+                    match find_explicit(kind) {
+                        None => SpecialMemberFound::NotPresent,
+                        Some(ExplicitFound::Deleted | ExplicitFound::Multiple) => {
+                            SpecialMemberFound::NotPresent
+                        }
+                        Some(ExplicitFound::UserDefined(visibility)) => {
+                            SpecialMemberFound::Explicit(*visibility)
+                        }
+                    }
+                };
+                let items_found = ItemsFound {
+                    default_constructor: is_explicit(ExplicitKind::DefaultConstructor),
+                    destructor: match find_explicit(ExplicitKind::Destructor) {
+                        // Assume that unknown types have destructors. This is common, and allows
+                        // use to generate UniquePtr wrappers with them.
+                        //
+                        // However, this will generate C++ code that doesn't compile if the unknown
+                        // type does not have an accessible destructor. Maybe we should have a way
+                        // to disable that?
+                        //
+                        // TODO: For https://github.com/google/autocxx/issues/815, map
+                        // ExplicitFound::Defaulted(_) to Explicit.
+                        None => SpecialMemberFound::Implicit,
+                        // If there are multiple destructors, assume that one of them will be
+                        // selected by overload resolution.
+                        Some(ExplicitFound::Multiple) => {
+                            SpecialMemberFound::Explicit(CppVisibility::Public)
+                        }
+                        Some(ExplicitFound::Deleted) => SpecialMemberFound::NotPresent,
+                        Some(ExplicitFound::UserDefined(visibility)) => {
+                            SpecialMemberFound::Explicit(*visibility)
+                        }
+                    },
+                    const_copy_constructor: is_explicit(ExplicitKind::ConstCopyConstructor),
+                    non_const_copy_constructor: is_explicit(ExplicitKind::NonConstCopyConstructor),
+                    move_constructor: is_explicit(ExplicitKind::MoveConstructor),
+                    name: Some(name.clone()),
+                };
+                log::info!(
+                    "Special member functions (explicits only) found for {:?}: {:?}",
+                    name,
+                    items_found
+                );
+                items_found
+            } else {
+                // If no user-declared constructors of any kind are provided for a class type (struct, class, or union),
+                // the compiler will always declare a default constructor as an inline public member of its class.
+                //
+                // The implicitly-declared or defaulted default constructor for class T is defined as deleted if any of the following is true:
+                // T has a member of reference type without a default initializer.
+                // T has a non-const-default-constructible const member without a default member initializer.
+                // T has a member (without a default member initializer) which has a deleted default constructor, or its default constructor is ambiguous or inaccessible from this constructor.
+                // T has a direct or virtual base which has a deleted default constructor, or it is ambiguous or inaccessible from this constructor.
+                // T has a direct or virtual base or a non-static data member which has a deleted destructor, or a destructor that is inaccessible from this constructor.
+                // T is a union with at least one variant member with non-trivial default constructor, and no variant member of T has a default member initializer. // we don't support unions anyway
+                // T is a non-union class with a variant member M with a non-trivial default constructor, and no variant member of the anonymous union containing M has a default member initializer.
+                // T is a union and all of its variant members are const. // we don't support unions anyway
+                //
+                // Variant members are the members of anonymous unions.
+                let default_constructor = {
+                    let explicit = find_explicit(ExplicitKind::DefaultConstructor);
+                    // TODO: For https://github.com/google/autocxx/issues/815, replace the first term with:
+                    //   explicit.map_or(true, |explicit_found| matches!(explicit_found, ExplicitFound::Defaulted(_)))
+                    let have_defaulted = explicit.is_none()
+                        && !explicits.iter().any(|(ExplicitType { ty, kind }, _)| {
+                            ty == &name.name
+                                && match *kind {
+                                    ExplicitKind::DefaultConstructor => false,
+                                    ExplicitKind::ConstCopyConstructor => true,
+                                    ExplicitKind::NonConstCopyConstructor => true,
+                                    ExplicitKind::MoveConstructor => true,
+                                    ExplicitKind::OtherConstructor => true,
+                                    ExplicitKind::Destructor => false,
+                                    ExplicitKind::ConstCopyAssignmentOperator => false,
+                                    ExplicitKind::NonConstCopyAssignmentOperator => false,
+                                    ExplicitKind::MoveAssignmentOperator => false,
+                                }
+                        });
+                    if have_defaulted {
+                        let bases_allow = bases_items_found.iter().all(|items_found| {
+                            items_found.destructor.callable_subclass()
+                                && items_found.default_constructor.callable_subclass()
+                        });
+                        // TODO: Allow member initializers for
+                        // https://github.com/google/autocxx/issues/816.
+                        let members_allow = fields_items_found.iter().all(|items_found| {
+                            items_found.destructor.callable_any()
+                                && items_found.default_constructor.callable_any()
+                        });
+                        if !has_rvalue_reference_fields && bases_allow && members_allow {
+                            // TODO: For https://github.com/google/autocxx/issues/815, grab the
+                            // visibility from an explicit default if present.
+                            SpecialMemberFound::Implicit
+                        } else {
+                            SpecialMemberFound::NotPresent
+                        }
+                    } else if let Some(ExplicitFound::UserDefined(visibility)) = explicit {
+                        SpecialMemberFound::Explicit(*visibility)
+                    } else {
+                        SpecialMemberFound::NotPresent
+                    }
+                };
+
+                // If no user-declared prospective destructor is provided for a class type (struct, class, or union), the compiler will always declare a destructor as an inline public member of its class.
+                //
+                // The implicitly-declared or explicitly defaulted destructor for class T is defined as deleted if any of the following is true:
+                // T has a non-static data member that cannot be destructed (has deleted or inaccessible destructor)
+                // T has direct or virtual base class that cannot be destructed (has deleted or inaccessible destructors)
+                // T is a union and has a variant member with non-trivial destructor. // we don't support unions anyway
+                // The implicitly-declared destructor is virtual (because the base class has a virtual destructor) and the lookup for the deallocation function (operator delete()) results in a call to ambiguous, deleted, or inaccessible function.
+                let destructor = {
+                    let explicit = find_explicit(ExplicitKind::Destructor);
+                    // TODO: For https://github.com/google/autocxx/issues/815, replace the condition with:
+                    //   explicit.map_or(true, |explicit_found| matches!(explicit_found, ExplicitFound::Defaulted(_)))
+                    if explicit.is_none() {
+                        let bases_allow = bases_items_found
+                            .iter()
+                            .all(|items_found| items_found.destructor.callable_subclass());
+                        let members_allow = fields_items_found
+                            .iter()
+                            .all(|items_found| items_found.destructor.callable_any());
+                        if bases_allow && members_allow {
+                            // TODO: For https://github.com/google/autocxx/issues/815, grab the
+                            // visibility from an explicit default if present.
+                            SpecialMemberFound::Implicit
+                        } else {
+                            SpecialMemberFound::NotPresent
+                        }
+                    } else if let Some(ExplicitFound::UserDefined(visibility)) = explicit {
+                        SpecialMemberFound::Explicit(*visibility)
+                    } else {
+                        SpecialMemberFound::NotPresent
+                    }
+                };
+
+                // If no user-defined copy constructors are provided for a class type (struct, class, or union),
+                // the compiler will always declare a copy constructor as a non-explicit inline public member of its class.
+                // This implicitly-declared copy constructor has the form T::T(const T&) if all of the following are true:
+                //  each direct and virtual base B of T has a copy constructor whose parameters are const B& or const volatile B&;
+                //  each non-static data member M of T of class type or array of class type has a copy constructor whose parameters are const M& or const volatile M&.
+                //
+                // The implicitly-declared or defaulted copy constructor for class T is defined as deleted if any of the following conditions are true:
+                // T is a union-like class and has a variant member with non-trivial copy constructor; // we don't support unions anyway
+                // T has a user-defined move constructor or move assignment operator (this condition only causes the implicitly-declared, not the defaulted, copy constructor to be deleted).
+                // T has non-static data members that cannot be copied (have deleted, inaccessible, or ambiguous copy constructors);
+                // T has direct or virtual base class that cannot be copied (has deleted, inaccessible, or ambiguous copy constructors);
+                // T has direct or virtual base class or a non-static data member with a deleted or inaccessible destructor;
+                // T has a data member of rvalue reference type;
+                let (const_copy_constructor, non_const_copy_constructor) = {
+                    let explicit_const = find_explicit(ExplicitKind::ConstCopyConstructor);
+                    let explicit_non_const = find_explicit(ExplicitKind::NonConstCopyConstructor);
+                    let explicit_move = find_explicit(ExplicitKind::MoveConstructor);
+
+                    // TODO: For https://github.com/google/autocxx/issues/815, replace both terms with something like:
+                    //   explicit.map_or(true, |explicit_found| matches!(explicit_found, ExplicitFound::Defaulted(_)))
+                    let have_defaulted = explicit_const.is_none() && explicit_non_const.is_none();
+                    if have_defaulted {
+                        // TODO: For https://github.com/google/autocxx/issues/815, ignore this if
+                        // the relevant (based on bases_are_const) copy constructor is explicitly defaulted.
+                        let class_allows = explicit_move.is_none() && !has_rvalue_reference_fields;
+                        let bases_allow = bases_items_found.iter().all(|items_found| {
+                            items_found.destructor.callable_subclass()
+                                && (items_found.const_copy_constructor.callable_subclass()
+                                    || items_found.non_const_copy_constructor.callable_subclass())
+                        });
+                        let members_allow = fields_items_found.iter().all(|items_found| {
+                            items_found.destructor.callable_any()
+                                && (items_found.const_copy_constructor.callable_any()
+                                    || items_found.non_const_copy_constructor.callable_any())
+                        });
+                        if class_allows && bases_allow && members_allow {
+                            // TODO: For https://github.com/google/autocxx/issues/815, grab the
+                            // visibility and existence of const and non-const from an explicit default if present.
+                            let dependencies_are_const = bases_items_found
+                                .iter()
+                                .chain(fields_items_found.iter())
+                                .all(|items_found| items_found.const_copy_constructor.exists());
+                            if dependencies_are_const {
+                                (SpecialMemberFound::Implicit, SpecialMemberFound::NotPresent)
+                            } else {
+                                (SpecialMemberFound::NotPresent, SpecialMemberFound::Implicit)
+                            }
+                        } else {
+                            (
+                                SpecialMemberFound::NotPresent,
+                                SpecialMemberFound::NotPresent,
+                            )
+                        }
+                    } else {
+                        (
+                            if let Some(ExplicitFound::UserDefined(visibility)) = explicit_const {
+                                SpecialMemberFound::Explicit(*visibility)
+                            } else {
+                                SpecialMemberFound::NotPresent
+                            },
+                            if let Some(ExplicitFound::UserDefined(visibility)) = explicit_non_const
+                            {
+                                SpecialMemberFound::Explicit(*visibility)
+                            } else {
+                                SpecialMemberFound::NotPresent
+                            },
+                        )
+                    }
+                };
+
+                // If no user-defined move constructors are provided for a class type (struct, class, or union), and all of the following is true:
+                // there are no user-declared copy constructors;
+                // there are no user-declared copy assignment operators;
+                // there are no user-declared move assignment operators;
+                // there is no user-declared destructor.
+                // then the compiler will declare a move constructor as a non-explicit inline public member of its class with the signature T::T(T&&).
+                //
+                // A class can have multiple move constructors, e.g. both T::T(const T&&) and T::T(T&&). If some user-defined move constructors are present, the user may still force the generation of the implicitly declared move constructor with the keyword default.
+                //
+                // The implicitly-declared or defaulted move constructor for class T is defined as deleted if any of the following is true:
+                // T has non-static data members that cannot be moved (have deleted, inaccessible, or ambiguous move constructors);
+                // T has direct or virtual base class that cannot be moved (has deleted, inaccessible, or ambiguous move constructors);
+                // T has direct or virtual base class with a deleted or inaccessible destructor;
+                // T is a union-like class and has a variant member with non-trivial move constructor. // we don't support unions anyway
+                let move_constructor = {
+                    let explicit = find_explicit(ExplicitKind::MoveConstructor);
+                    // TODO: For https://github.com/google/autocxx/issues/815, replace relevant terms with something like:
+                    //   explicit.map_or(true, |explicit_found| matches!(explicit_found, ExplicitFound::Defaulted(_)))
+                    let have_defaulted = !(explicit.is_some()
+                        || find_explicit(ExplicitKind::ConstCopyConstructor).is_some()
+                        || find_explicit(ExplicitKind::NonConstCopyConstructor).is_some()
+                        || find_explicit(ExplicitKind::ConstCopyAssignmentOperator).is_some()
+                        || find_explicit(ExplicitKind::NonConstCopyAssignmentOperator).is_some()
+                        || find_explicit(ExplicitKind::MoveAssignmentOperator).is_some()
+                        || find_explicit(ExplicitKind::Destructor).is_some());
+                    if have_defaulted {
+                        let bases_allow = bases_items_found.iter().all(|items_found| {
+                            items_found.destructor.callable_subclass()
+                                && items_found.move_constructor.callable_subclass()
+                        });
+                        let members_allow = fields_items_found
+                            .iter()
+                            .all(|items_found| items_found.move_constructor.callable_any());
+                        if bases_allow && members_allow {
+                            // TODO: For https://github.com/google/autocxx/issues/815, grab the
+                            // visibility from an explicit default if present.
+                            SpecialMemberFound::Implicit
+                        } else {
+                            SpecialMemberFound::NotPresent
+                        }
+                    } else if let Some(ExplicitFound::UserDefined(visibility)) = explicit {
+                        SpecialMemberFound::Explicit(*visibility)
+                    } else {
+                        SpecialMemberFound::NotPresent
+                    }
+                };
+
+                let items_found = ItemsFound {
+                    default_constructor,
+                    destructor,
+                    const_copy_constructor,
+                    non_const_copy_constructor,
+                    move_constructor,
+                    name: Some(name.clone()),
+                };
+                log::info!(
+                    "Special member items found for {:?}: {:?}",
+                    name,
+                    items_found
+                );
+                items_found
+            };
+            assert!(
+                all_items_found
+                    .insert(name.name.clone(), items_found)
+                    .is_none(),
+                "Duplicate struct: {:?}",
+                name
+            );
+        }
+    }
+
+    all_items_found
+}
+
+fn find_explicit_items(
+    apis: &ApiVec<FnPrePhase1>,
+) -> (HashMap<ExplicitType, ExplicitFound>, HashSet<QualifiedName>) {
+    let mut result = HashMap::new();
+    let mut merge_fun = |ty: QualifiedName, kind: ExplicitKind, fun: &FuncToConvert| match result
+        .entry(ExplicitType { ty, kind })
+    {
+        Entry::Vacant(entry) => {
+            entry.insert(if fun.is_deleted {
+                ExplicitFound::Deleted
+            } else {
+                ExplicitFound::UserDefined(fun.cpp_vis)
+            });
+        }
+        Entry::Occupied(mut entry) => {
+            entry.insert(ExplicitFound::Multiple);
+        }
+    };
+    let mut unknown_types = HashSet::new();
+    for api in apis.iter() {
+        match api {
+            Api::Function {
+                analysis:
+                    FnAnalysis {
+                        kind: FnKind::Method { impl_for, .. },
+                        param_details,
+                        ignore_reason:
+                            Ok(()) | Err(ConvertErrorWithContext(ConvertError::AssignmentOperator, _)),
+                        ..
+                    },
+                fun,
+                ..
+            } if matches!(
+                fun.special_member,
+                Some(SpecialMemberKind::AssignmentOperator)
+            ) =>
+            {
+                let is_move_assignment_operator = !fun.references.rvalue_ref_params.is_empty();
+                merge_fun(
+                    impl_for.clone(),
+                    if is_move_assignment_operator {
+                        ExplicitKind::MoveAssignmentOperator
+                    } else {
+                        let receiver_mutability = &param_details
+                            .iter()
+                            .next()
+                            .unwrap()
+                            .self_type
+                            .as_ref()
+                            .unwrap()
+                            .1;
+                        match receiver_mutability {
+                            ReceiverMutability::Const => ExplicitKind::ConstCopyAssignmentOperator,
+                            ReceiverMutability::Mutable => {
+                                ExplicitKind::NonConstCopyAssignmentOperator
+                            }
+                        }
+                    },
+                    fun,
+                )
+            }
+            Api::Function {
+                analysis:
+                    FnAnalysis {
+                        kind: FnKind::Method { impl_for, .. },
+                        ..
+                    },
+                fun,
+                ..
+            } if matches!(
+                fun.special_member,
+                Some(SpecialMemberKind::AssignmentOperator)
+            ) =>
+            {
+                unknown_types.insert(impl_for.clone());
+            }
+            Api::Function {
+                analysis:
+                    FnAnalysis {
+                        kind:
+                            FnKind::Method {
+                                impl_for,
+                                method_kind,
+                                ..
+                            },
+                        ..
+                    },
+                fun,
+                ..
+            } => match method_kind {
+                MethodKind::Constructor { is_default: true } => {
+                    Some(ExplicitKind::DefaultConstructor)
+                }
+                MethodKind::Constructor { is_default: false } => {
+                    Some(ExplicitKind::OtherConstructor)
+                }
+                _ => None,
+            }
+            .map_or((), |explicit_kind| {
+                merge_fun(impl_for.clone(), explicit_kind, fun)
+            }),
+            Api::Function {
+                analysis:
+                    FnAnalysis {
+                        kind: FnKind::TraitMethod { impl_for, kind, .. },
+                        ..
+                    },
+                fun,
+                ..
+            } => match kind {
+                TraitMethodKind::Destructor => Some(ExplicitKind::Destructor),
+                // In `analyze_foreign_fn` we mark non-const copy constructors as not being copy
+                // constructors for now, so we don't have to worry about them.
+                TraitMethodKind::CopyConstructor => Some(ExplicitKind::ConstCopyConstructor),
+                TraitMethodKind::MoveConstructor => Some(ExplicitKind::MoveConstructor),
+                _ => None,
+            }
+            .map_or((), |explicit_kind| {
+                merge_fun(impl_for.clone(), explicit_kind, fun)
+            }),
+            _ => (),
+        }
+    }
+    (result, unknown_types)
+}
+
+/// Returns the information for a given known type.
+fn known_type_items_found(constructor_details: KnownTypeConstructorDetails) -> ItemsFound {
+    let exists_public = SpecialMemberFound::Explicit(CppVisibility::Public);
+    let exists_public_if = |exists| {
+        if exists {
+            exists_public
+        } else {
+            SpecialMemberFound::NotPresent
+        }
+    };
+    ItemsFound {
+        default_constructor: exists_public,
+        destructor: exists_public,
+        const_copy_constructor: exists_public_if(constructor_details.has_const_copy_constructor),
+        non_const_copy_constructor: SpecialMemberFound::NotPresent,
+        move_constructor: exists_public_if(constructor_details.has_move_constructor),
+        name: None,
+    }
+}
diff --git a/engine/src/conversion/analysis/fun/mod.rs b/engine/src/conversion/analysis/fun/mod.rs
new file mode 100644
index 0000000..19340f9
--- /dev/null
+++ b/engine/src/conversion/analysis/fun/mod.rs
@@ -0,0 +1,2140 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+mod bridge_name_tracker;
+pub(crate) mod function_wrapper;
+mod implicit_constructors;
+mod overload_tracker;
+mod subclass;
+
+use crate::{
+    conversion::{
+        analysis::{
+            fun::function_wrapper::{CppConversionType, CppFunctionKind},
+            type_converter::{self, add_analysis, TypeConversionContext, TypeConverter},
+        },
+        api::{
+            ApiName, CastMutability, CppVisibility, FuncToConvert, NullPhase, Provenance,
+            References, SpecialMemberKind, SubclassName, TraitImplSignature, TraitSynthesis,
+            UnsafetyNeeded, Virtualness,
+        },
+        apivec::ApiVec,
+        convert_error::ErrorContext,
+        convert_error::{ConvertErrorWithContext, ErrorContextType},
+        error_reporter::{convert_apis, report_any_error},
+    },
+    known_types::known_types,
+    types::validate_ident_ok_for_rust,
+};
+use indexmap::map::IndexMap as HashMap;
+use indexmap::set::IndexSet as HashSet;
+
+use autocxx_parser::{ExternCppType, IncludeCppConfig, UnsafePolicy};
+use function_wrapper::{CppFunction, CppFunctionBody, TypeConversionPolicy};
+use itertools::Itertools;
+use proc_macro2::Span;
+use quote::quote;
+use syn::{
+    parse_quote, punctuated::Punctuated, token::Comma, FnArg, Ident, Pat, ReturnType, Type,
+    TypePtr, Visibility,
+};
+
+use crate::{
+    conversion::{
+        api::{AnalysisPhase, Api, TypeKind},
+        ConvertError,
+    },
+    types::{make_ident, validate_ident_ok_for_cxx, Namespace, QualifiedName},
+};
+
+use self::{
+    bridge_name_tracker::BridgeNameTracker,
+    function_wrapper::RustConversionType,
+    implicit_constructors::{find_constructors_present, ItemsFound},
+    overload_tracker::OverloadTracker,
+    subclass::{
+        create_subclass_constructor, create_subclass_fn_wrapper, create_subclass_function,
+        create_subclass_trait_item,
+    },
+};
+
+use super::{
+    doc_label::make_doc_attrs,
+    pod::{PodAnalysis, PodPhase},
+    tdef::TypedefAnalysis,
+    type_converter::{Annotated, PointerTreatment},
+};
+
+#[derive(Clone, Debug)]
+pub(crate) enum ReceiverMutability {
+    Const,
+    Mutable,
+}
+
+#[derive(Clone, Debug)]
+pub(crate) enum MethodKind {
+    Normal(ReceiverMutability),
+    Constructor { is_default: bool },
+    Static,
+    Virtual(ReceiverMutability),
+    PureVirtual(ReceiverMutability),
+}
+
+#[derive(Clone)]
+pub(crate) enum TraitMethodKind {
+    CopyConstructor,
+    MoveConstructor,
+    Cast,
+    Destructor,
+    Alloc,
+    Dealloc,
+}
+
+#[derive(Clone)]
+pub(crate) struct TraitMethodDetails {
+    pub(crate) trt: TraitImplSignature,
+    pub(crate) avoid_self: bool,
+    pub(crate) method_name: Ident,
+    /// For traits, where we're trying to implement a specific existing
+    /// interface, we may need to reorder the parameters to fit that
+    /// interface.
+    pub(crate) parameter_reordering: Option<Vec<usize>>,
+    /// The function we're calling from the trait requires unsafe even
+    /// though the trait and its function aren't.
+    pub(crate) trait_call_is_unsafe: bool,
+}
+
+#[derive(Clone)]
+pub(crate) enum FnKind {
+    Function,
+    Method {
+        method_kind: MethodKind,
+        impl_for: QualifiedName,
+    },
+    TraitMethod {
+        kind: TraitMethodKind,
+        /// The name of the type T for which we're implementing a trait,
+        /// though we may be actually implementing the trait for &mut T or
+        /// similar, so we store more details of both the type and the
+        /// method in `details`
+        impl_for: QualifiedName,
+        details: Box<TraitMethodDetails>,
+    },
+}
+
+/// Strategy for ensuring that the final, callable, Rust name
+/// is what the user originally expected.
+#[derive(Clone)]
+
+pub(crate) enum RustRenameStrategy {
+    /// cxx::bridge name matches user expectations
+    None,
+    /// Even the #[rust_name] attribute would cause conflicts, and we need
+    /// to use a 'use XYZ as ABC'
+    RenameInOutputMod(Ident),
+    /// This function requires us to generate a Rust function to do
+    /// parameter conversion.
+    RenameUsingWrapperFunction,
+}
+
+#[derive(Clone)]
+pub(crate) struct FnAnalysis {
+    /// Each entry in the cxx::bridge needs to have a unique name, even if
+    /// (from the perspective of Rust and C++) things are in different
+    /// namespaces/mods.
+    pub(crate) cxxbridge_name: Ident,
+    /// ... so record also the name under which we wish to expose it in Rust.
+    pub(crate) rust_name: String,
+    pub(crate) rust_rename_strategy: RustRenameStrategy,
+    pub(crate) params: Punctuated<FnArg, Comma>,
+    pub(crate) kind: FnKind,
+    pub(crate) ret_type: ReturnType,
+    pub(crate) param_details: Vec<ArgumentAnalysis>,
+    pub(crate) ret_conversion: Option<TypeConversionPolicy>,
+    pub(crate) requires_unsafe: UnsafetyNeeded,
+    pub(crate) vis: Visibility,
+    pub(crate) cpp_wrapper: Option<CppFunction>,
+    pub(crate) deps: HashSet<QualifiedName>,
+    /// Some methods still need to be recorded because we want
+    /// to (a) generate the ability to call superclasses, (b) create
+    /// subclass entries for them. But we do not want to have them
+    /// be externally callable.
+    pub(crate) ignore_reason: Result<(), ConvertErrorWithContext>,
+    /// Whether this can be called by external code. Not so for
+    /// protected methods.
+    pub(crate) externally_callable: bool,
+    /// Whether we need to generate a Rust-side calling function
+    pub(crate) rust_wrapper_needed: bool,
+}
+
+#[derive(Clone)]
+pub(crate) struct ArgumentAnalysis {
+    pub(crate) conversion: TypeConversionPolicy,
+    pub(crate) name: Pat,
+    pub(crate) self_type: Option<(QualifiedName, ReceiverMutability)>,
+    pub(crate) has_lifetime: bool,
+    pub(crate) deps: HashSet<QualifiedName>,
+    pub(crate) requires_unsafe: UnsafetyNeeded,
+    pub(crate) is_placement_return_destination: bool,
+}
+
+struct ReturnTypeAnalysis {
+    rt: ReturnType,
+    conversion: Option<TypeConversionPolicy>,
+    was_reference: bool,
+    deps: HashSet<QualifiedName>,
+    placement_param_needed: Option<(FnArg, ArgumentAnalysis)>,
+}
+
+impl Default for ReturnTypeAnalysis {
+    fn default() -> Self {
+        Self {
+            rt: parse_quote! {},
+            conversion: None,
+            was_reference: false,
+            deps: Default::default(),
+            placement_param_needed: None,
+        }
+    }
+}
+
+pub(crate) struct PodAndConstructorAnalysis {
+    pub(crate) pod: PodAnalysis,
+    pub(crate) constructors: PublicConstructors,
+}
+
+/// An analysis phase where we've analyzed each function, but
+/// haven't yet determined which constructors/etc. belong to each type.
+pub(crate) struct FnPrePhase1;
+
+impl AnalysisPhase for FnPrePhase1 {
+    type TypedefAnalysis = TypedefAnalysis;
+    type StructAnalysis = PodAnalysis;
+    type FunAnalysis = FnAnalysis;
+}
+
+/// An analysis phase where we've analyzed each function, and identified
+/// what implicit constructors/destructors are present in each type.
+pub(crate) struct FnPrePhase2;
+
+impl AnalysisPhase for FnPrePhase2 {
+    type TypedefAnalysis = TypedefAnalysis;
+    type StructAnalysis = PodAndConstructorAnalysis;
+    type FunAnalysis = FnAnalysis;
+}
+
+pub(crate) struct PodAndDepAnalysis {
+    pub(crate) pod: PodAnalysis,
+    pub(crate) constructor_and_allocator_deps: Vec<QualifiedName>,
+    pub(crate) constructors: PublicConstructors,
+}
+
+/// Analysis phase after we've finished analyzing functions and determined
+/// which constructors etc. belong to them.
+pub(crate) struct FnPhase;
+
+/// Indicates which kinds of public constructors are known to exist for a type.
+#[derive(Debug, Default, Copy, Clone)]
+pub(crate) struct PublicConstructors {
+    pub(crate) move_constructor: bool,
+    pub(crate) destructor: bool,
+}
+
+impl PublicConstructors {
+    fn from_items_found(items_found: &ItemsFound) -> Self {
+        Self {
+            move_constructor: items_found.move_constructor.callable_any(),
+            destructor: items_found.destructor.callable_any(),
+        }
+    }
+}
+
+impl AnalysisPhase for FnPhase {
+    type TypedefAnalysis = TypedefAnalysis;
+    type StructAnalysis = PodAndDepAnalysis;
+    type FunAnalysis = FnAnalysis;
+}
+
+/// Whether to allow highly optimized calls because this is a simple Rust->C++ call,
+/// or to use a simpler set of policies because this is a subclass call where
+/// we may have C++->Rust->C++ etc.
+#[derive(Copy, Clone)]
+enum TypeConversionSophistication {
+    Regular,
+    SimpleForSubclasses,
+}
+
+pub(crate) struct FnAnalyzer<'a> {
+    unsafe_policy: UnsafePolicy,
+    extra_apis: ApiVec<NullPhase>,
+    type_converter: TypeConverter<'a>,
+    bridge_name_tracker: BridgeNameTracker,
+    pod_safe_types: HashSet<QualifiedName>,
+    moveit_safe_types: HashSet<QualifiedName>,
+    config: &'a IncludeCppConfig,
+    overload_trackers_by_mod: HashMap<Namespace, OverloadTracker>,
+    subclasses_by_superclass: HashMap<QualifiedName, Vec<SubclassName>>,
+    nested_type_name_map: HashMap<QualifiedName, String>,
+    generic_types: HashSet<QualifiedName>,
+    types_in_anonymous_namespace: HashSet<QualifiedName>,
+    existing_superclass_trait_api_names: HashSet<QualifiedName>,
+}
+
+impl<'a> FnAnalyzer<'a> {
+    pub(crate) fn analyze_functions(
+        apis: ApiVec<PodPhase>,
+        unsafe_policy: UnsafePolicy,
+        config: &'a IncludeCppConfig,
+    ) -> ApiVec<FnPrePhase2> {
+        let mut me = Self {
+            unsafe_policy,
+            extra_apis: ApiVec::new(),
+            type_converter: TypeConverter::new(config, &apis),
+            bridge_name_tracker: BridgeNameTracker::new(),
+            config,
+            overload_trackers_by_mod: HashMap::new(),
+            pod_safe_types: Self::build_pod_safe_type_set(&apis),
+            moveit_safe_types: Self::build_correctly_sized_type_set(&apis),
+            subclasses_by_superclass: subclass::subclasses_by_superclass(&apis),
+            nested_type_name_map: Self::build_nested_type_map(&apis),
+            generic_types: Self::build_generic_type_set(&apis),
+            existing_superclass_trait_api_names: HashSet::new(),
+            types_in_anonymous_namespace: Self::build_types_in_anonymous_namespace(&apis),
+        };
+        let mut results = ApiVec::new();
+        convert_apis(
+            apis,
+            &mut results,
+            |name, fun, _| me.analyze_foreign_fn_and_subclasses(name, fun),
+            Api::struct_unchanged,
+            Api::enum_unchanged,
+            Api::typedef_unchanged,
+        );
+        let mut results = me.add_constructors_present(results);
+        me.add_subclass_constructors(&mut results);
+        results.extend(me.extra_apis.into_iter().map(add_analysis));
+        results
+    }
+
+    fn build_pod_safe_type_set(apis: &ApiVec<PodPhase>) -> HashSet<QualifiedName> {
+        apis.iter()
+            .filter_map(|api| match api {
+                Api::Struct {
+                    analysis:
+                        PodAnalysis {
+                            kind: TypeKind::Pod,
+                            ..
+                        },
+                    ..
+                } => Some(api.name().clone()),
+                Api::Enum { .. } => Some(api.name().clone()),
+                Api::ExternCppType { pod: true, .. } => Some(api.name().clone()),
+                _ => None,
+            })
+            .chain(
+                known_types()
+                    .get_pod_safe_types()
+                    .filter_map(
+                        |(tn, is_pod_safe)| {
+                            if is_pod_safe {
+                                Some(tn)
+                            } else {
+                                None
+                            }
+                        },
+                    ),
+            )
+            .collect()
+    }
+
+    /// Return the set of 'moveit safe' types. That must include only types where
+    /// the size is known to be correct.
+    fn build_correctly_sized_type_set(apis: &ApiVec<PodPhase>) -> HashSet<QualifiedName> {
+        apis.iter()
+            .filter(|api| {
+                matches!(
+                    api,
+                    Api::Struct { .. }
+                        | Api::Enum { .. }
+                        | Api::ExternCppType {
+                            details: ExternCppType { opaque: false, .. },
+                            ..
+                        }
+                )
+            })
+            .map(|api| api.name().clone())
+            .chain(known_types().get_moveit_safe_types())
+            .collect()
+    }
+
+    fn build_generic_type_set(apis: &ApiVec<PodPhase>) -> HashSet<QualifiedName> {
+        apis.iter()
+            .filter_map(|api| match api {
+                Api::Struct {
+                    analysis:
+                        PodAnalysis {
+                            is_generic: true, ..
+                        },
+                    ..
+                } => Some(api.name().clone()),
+                _ => None,
+            })
+            .collect()
+    }
+
+    fn build_types_in_anonymous_namespace(apis: &ApiVec<PodPhase>) -> HashSet<QualifiedName> {
+        apis.iter()
+            .filter_map(|api| match api {
+                Api::Struct {
+                    analysis:
+                        PodAnalysis {
+                            in_anonymous_namespace: true,
+                            ..
+                        },
+                    ..
+                } => Some(api.name().clone()),
+                _ => None,
+            })
+            .collect()
+    }
+
+    /// Builds a mapping from a qualified type name to the last 'nest'
+    /// of its name, if it has multiple elements.
+    fn build_nested_type_map(apis: &ApiVec<PodPhase>) -> HashMap<QualifiedName, String> {
+        apis.iter()
+            .filter_map(|api| match api {
+                Api::Struct { name, .. } | Api::Enum { name, .. } => {
+                    let cpp_name = name
+                        .cpp_name_if_present()
+                        .cloned()
+                        .unwrap_or_else(|| name.name.get_final_item().to_string());
+                    cpp_name
+                        .rsplit_once("::")
+                        .map(|(_, suffix)| (name.name.clone(), suffix.to_string()))
+                }
+                _ => None,
+            })
+            .collect()
+    }
+
+    fn convert_boxed_type(
+        &mut self,
+        ty: Box<Type>,
+        ns: &Namespace,
+        pointer_treatment: PointerTreatment,
+    ) -> Result<Annotated<Box<Type>>, ConvertError> {
+        let ctx = TypeConversionContext::OuterType { pointer_treatment };
+        let mut annotated = self.type_converter.convert_boxed_type(ty, ns, &ctx)?;
+        self.extra_apis.append(&mut annotated.extra_apis);
+        Ok(annotated)
+    }
+
+    fn get_cxx_bridge_name(
+        &mut self,
+        type_name: Option<&str>,
+        found_name: &str,
+        ns: &Namespace,
+    ) -> String {
+        self.bridge_name_tracker
+            .get_unique_cxx_bridge_name(type_name, found_name, ns)
+    }
+
+    fn is_on_allowlist(&self, type_name: &QualifiedName) -> bool {
+        self.config.is_on_allowlist(&type_name.to_cpp_name())
+    }
+
+    fn is_generic_type(&self, type_name: &QualifiedName) -> bool {
+        self.generic_types.contains(type_name)
+    }
+
+    #[allow(clippy::if_same_then_else)] // clippy bug doesn't notice the two
+                                        // closures below are different.
+    fn should_be_unsafe(
+        &self,
+        param_details: &[ArgumentAnalysis],
+        kind: &FnKind,
+    ) -> UnsafetyNeeded {
+        let unsafest_non_placement_param = UnsafetyNeeded::from_param_details(param_details, true);
+        let unsafest_param = UnsafetyNeeded::from_param_details(param_details, false);
+        match kind {
+            // Trait unsafety must always correspond to the norms for the
+            // trait we're implementing.
+            FnKind::TraitMethod {
+                kind:
+                    TraitMethodKind::CopyConstructor
+                    | TraitMethodKind::MoveConstructor
+                    | TraitMethodKind::Alloc
+                    | TraitMethodKind::Dealloc,
+                ..
+            } => UnsafetyNeeded::Always,
+            FnKind::TraitMethod { .. } => match unsafest_param {
+                UnsafetyNeeded::Always => UnsafetyNeeded::JustBridge,
+                _ => unsafest_param,
+            },
+            _ if self.unsafe_policy == UnsafePolicy::AllFunctionsUnsafe => UnsafetyNeeded::Always,
+            _ => match unsafest_non_placement_param {
+                UnsafetyNeeded::Always => UnsafetyNeeded::Always,
+                UnsafetyNeeded::JustBridge => match unsafest_param {
+                    UnsafetyNeeded::Always => UnsafetyNeeded::JustBridge,
+                    _ => unsafest_non_placement_param,
+                },
+                UnsafetyNeeded::None => match unsafest_param {
+                    UnsafetyNeeded::Always => UnsafetyNeeded::JustBridge,
+                    _ => unsafest_param,
+                },
+            },
+        }
+    }
+
+    fn add_subclass_constructors(&mut self, apis: &mut ApiVec<FnPrePhase2>) {
+        let mut results = ApiVec::new();
+
+        // Pre-assemble a list of types with known destructors, to avoid having to
+        // do a O(n^2) nested loop.
+        let types_with_destructors: HashSet<_> = apis
+            .iter()
+            .filter_map(|api| match api {
+                Api::Function {
+                    fun,
+                    analysis:
+                        FnAnalysis {
+                            kind: FnKind::TraitMethod { impl_for, .. },
+                            ..
+                        },
+                    ..
+                } if matches!(
+                    **fun,
+                    FuncToConvert {
+                        special_member: Some(SpecialMemberKind::Destructor),
+                        is_deleted: false,
+                        cpp_vis: CppVisibility::Public,
+                        ..
+                    }
+                ) =>
+                {
+                    Some(impl_for)
+                }
+                _ => None,
+            })
+            .cloned()
+            .collect();
+
+        for api in apis.iter() {
+            if let Api::Function {
+                fun,
+                analysis:
+                    analysis @ FnAnalysis {
+                        kind:
+                            FnKind::Method {
+                                impl_for: sup,
+                                method_kind: MethodKind::Constructor { .. },
+                                ..
+                            },
+                        ..
+                    },
+                ..
+            } = api
+            {
+                // If we don't have an accessible destructor, then std::unique_ptr cannot be
+                // instantiated for this C++ type.
+                if !types_with_destructors.contains(sup) {
+                    continue;
+                }
+
+                for sub in self.subclasses_by_superclass(sup) {
+                    // Create a subclass constructor. This is a synthesized function
+                    // which didn't exist in the original C++.
+                    let (subclass_constructor_func, subclass_constructor_name) =
+                        create_subclass_constructor(sub, analysis, sup, fun);
+                    self.analyze_and_add(
+                        subclass_constructor_name.clone(),
+                        subclass_constructor_func.clone(),
+                        &mut results,
+                        TypeConversionSophistication::Regular,
+                    );
+                }
+            }
+        }
+        apis.extend(results.into_iter());
+    }
+
+    /// Analyze a given function, and any permutations of that function which
+    /// we might additionally generate (e.g. for subclasses.)
+    ///
+    /// Leaves the [`FnKind::Method::type_constructors`] at its default for [`add_constructors_present`]
+    /// to fill out.
+    fn analyze_foreign_fn_and_subclasses(
+        &mut self,
+        name: ApiName,
+        fun: Box<FuncToConvert>,
+    ) -> Result<Box<dyn Iterator<Item = Api<FnPrePhase1>>>, ConvertErrorWithContext> {
+        let (analysis, name) =
+            self.analyze_foreign_fn(name, &fun, TypeConversionSophistication::Regular, None);
+        let mut results = ApiVec::new();
+
+        // Consider whether we need to synthesize subclass items.
+        if let FnKind::Method {
+            impl_for: sup,
+            method_kind:
+                MethodKind::Virtual(receiver_mutability) | MethodKind::PureVirtual(receiver_mutability),
+            ..
+        } = &analysis.kind
+        {
+            let (simpler_analysis, _) = self.analyze_foreign_fn(
+                name.clone(),
+                &fun,
+                TypeConversionSophistication::SimpleForSubclasses,
+                Some(analysis.rust_name.clone()),
+            );
+            for sub in self.subclasses_by_superclass(sup) {
+                // For each subclass, we need to create a plain-C++ method to call its superclass
+                // and a Rust/C++ bridge API to call _that_.
+                // What we're generating here is entirely about the subclass, so the
+                // superclass's namespace is irrelevant. We generate
+                // all subclasses in the root namespace.
+                let is_pure_virtual = matches!(
+                    &simpler_analysis.kind,
+                    FnKind::Method {
+                        method_kind: MethodKind::PureVirtual(..),
+                        ..
+                    }
+                );
+
+                let super_fn_call_name =
+                    SubclassName::get_super_fn_name(&Namespace::new(), &analysis.rust_name);
+                let super_fn_api_name = SubclassName::get_super_fn_name(
+                    &Namespace::new(),
+                    &analysis.cxxbridge_name.to_string(),
+                );
+                let trait_api_name = SubclassName::get_trait_api_name(sup, &analysis.rust_name);
+
+                let mut subclass_fn_deps = vec![trait_api_name.clone()];
+                if !is_pure_virtual {
+                    // Create a C++ API representing the superclass implementation (allowing
+                    // calls from Rust->C++)
+                    let maybe_wrap = create_subclass_fn_wrapper(&sub, &super_fn_call_name, &fun);
+                    let super_fn_name = ApiName::new_from_qualified_name(super_fn_api_name);
+                    let super_fn_call_api_name = self.analyze_and_add(
+                        super_fn_name,
+                        maybe_wrap,
+                        &mut results,
+                        TypeConversionSophistication::SimpleForSubclasses,
+                    );
+                    subclass_fn_deps.push(super_fn_call_api_name);
+                }
+
+                // Create the Rust API representing the subclass implementation (allowing calls
+                // from C++ -> Rust)
+                results.push(create_subclass_function(
+                    // RustSubclassFn
+                    &sub,
+                    &simpler_analysis,
+                    &name,
+                    receiver_mutability,
+                    sup,
+                    subclass_fn_deps,
+                ));
+
+                // Create the trait item for the <superclass>_methods and <superclass>_supers
+                // traits. This is required per-superclass, not per-subclass, so don't
+                // create it if it already exists.
+                if !self
+                    .existing_superclass_trait_api_names
+                    .contains(&trait_api_name)
+                {
+                    self.existing_superclass_trait_api_names
+                        .insert(trait_api_name.clone());
+                    results.push(create_subclass_trait_item(
+                        ApiName::new_from_qualified_name(trait_api_name),
+                        &simpler_analysis,
+                        receiver_mutability,
+                        sup.clone(),
+                        is_pure_virtual,
+                    ));
+                }
+            }
+        }
+
+        results.push(Api::Function {
+            fun,
+            analysis,
+            name,
+        });
+
+        Ok(Box::new(results.into_iter()))
+    }
+
+    /// Adds an API, usually a synthesized API. Returns the final calculated API name, which can be used
+    /// for others to depend on this.
+    fn analyze_and_add<P: AnalysisPhase<FunAnalysis = FnAnalysis>>(
+        &mut self,
+        name: ApiName,
+        new_func: Box<FuncToConvert>,
+        results: &mut ApiVec<P>,
+        sophistication: TypeConversionSophistication,
+    ) -> QualifiedName {
+        let (analysis, name) = self.analyze_foreign_fn(name, &new_func, sophistication, None);
+        results.push(Api::Function {
+            fun: new_func,
+            analysis,
+            name: name.clone(),
+        });
+        name.name
+    }
+
+    /// Determine how to materialize a function.
+    ///
+    /// The main job here is to determine whether a function can simply be noted
+    /// in the [cxx::bridge] mod and passed directly to cxx, or if it needs a Rust-side
+    /// wrapper function, or if it needs a C++-side wrapper function, or both.
+    /// We aim for the simplest case but, for example:
+    /// * We'll need a C++ wrapper for static methods
+    /// * We'll need a C++ wrapper for parameters which need to be wrapped and unwrapped
+    ///   to [cxx::UniquePtr]
+    /// * We'll need a Rust wrapper if we've got a C++ wrapper and it's a method.
+    /// * We may need wrappers if names conflict.
+    /// etc.
+    /// The other major thing we do here is figure out naming for the function.
+    /// This depends on overloads, and what other functions are floating around.
+    /// The output of this analysis phase is used by both Rust and C++ codegen.
+    fn analyze_foreign_fn(
+        &mut self,
+        name: ApiName,
+        fun: &FuncToConvert,
+        sophistication: TypeConversionSophistication,
+        predetermined_rust_name: Option<String>,
+    ) -> (FnAnalysis, ApiName) {
+        let mut cpp_name = name.cpp_name_if_present().cloned();
+        let ns = name.name.get_namespace();
+
+        // Let's gather some pre-wisdom about the name of the function.
+        // We're shortly going to plunge into analyzing the parameters,
+        // and it would be nice to have some idea of the function name
+        // for diagnostics whilst we do that.
+        let initial_rust_name = fun.ident.to_string();
+        let diagnostic_display_name = cpp_name.as_ref().unwrap_or(&initial_rust_name);
+
+        // Now let's analyze all the parameters.
+        // See if any have annotations which our fork of bindgen has craftily inserted...
+        let (param_details, bads): (Vec<_>, Vec<_>) = fun
+            .inputs
+            .iter()
+            .map(|i| {
+                self.convert_fn_arg(
+                    i,
+                    ns,
+                    diagnostic_display_name,
+                    &fun.synthesized_this_type,
+                    &fun.references,
+                    true,
+                    false,
+                    None,
+                    sophistication,
+                    false,
+                )
+            })
+            .partition(Result::is_ok);
+        let (mut params, mut param_details): (Punctuated<_, Comma>, Vec<_>) =
+            param_details.into_iter().map(Result::unwrap).unzip();
+
+        let params_deps: HashSet<_> = param_details
+            .iter()
+            .flat_map(|p| p.deps.iter().cloned())
+            .collect();
+        let self_ty = param_details
+            .iter()
+            .filter_map(|pd| pd.self_type.as_ref())
+            .next()
+            .cloned();
+
+        // End of parameter processing.
+        // Work out naming, part one.
+        // bindgen may have mangled the name either because it's invalid Rust
+        // syntax (e.g. a keyword like 'async') or it's an overload.
+        // If the former, we respect that mangling. If the latter, we don't,
+        // because we'll add our own overload counting mangling later.
+        // Cases:
+        //   function, IRN=foo,    CN=<none>                    output: foo    case 1
+        //   function, IRN=move_,  CN=move   (keyword problem)  output: move_  case 2
+        //   function, IRN=foo1,   CN=foo    (overload)         output: foo    case 3
+        //   method,   IRN=A_foo,  CN=foo                       output: foo    case 4
+        //   method,   IRN=A_move, CN=move   (keyword problem)  output: move_  case 5
+        //   method,   IRN=A_foo1, CN=foo    (overload)         output: foo    case 6
+        let ideal_rust_name = match &cpp_name {
+            None => initial_rust_name, // case 1
+            Some(cpp_name) => {
+                if initial_rust_name.ends_with('_') {
+                    initial_rust_name // case 2
+                } else if validate_ident_ok_for_rust(cpp_name).is_err() {
+                    format!("{}_", cpp_name) // case 5
+                } else {
+                    cpp_name.to_string() // cases 3, 4, 6
+                }
+            }
+        };
+
+        // Let's spend some time figuring out the kind of this function (i.e. method,
+        // virtual function, etc.)
+        // Part one, work out if this is a static method.
+        let (is_static_method, self_ty, receiver_mutability) = match self_ty {
+            None => {
+                // Even if we can't find a 'self' parameter this could conceivably
+                // be a static method.
+                let self_ty = fun.self_ty.clone();
+                (self_ty.is_some(), self_ty, None)
+            }
+            Some((self_ty, receiver_mutability)) => {
+                (false, Some(self_ty), Some(receiver_mutability))
+            }
+        };
+
+        // Part two, work out if this is a function, or method, or whatever.
+        // First determine if this is actually a trait implementation.
+        let trait_details = self.trait_creation_details_for_synthetic_function(
+            &fun.add_to_trait,
+            ns,
+            &ideal_rust_name,
+            &self_ty,
+        );
+        let (kind, error_context, rust_name) = if let Some(trait_details) = trait_details {
+            trait_details
+        } else if let Some(self_ty) = self_ty {
+            // Some kind of method or static method.
+            let type_ident = self_ty.get_final_item();
+            // bindgen generates methods with the name:
+            // {class}_{method name}
+            // It then generates an impl section for the Rust type
+            // with the original name, but we currently discard that impl section.
+            // We want to feed cxx methods with just the method name, so let's
+            // strip off the class name.
+            let mut rust_name = ideal_rust_name;
+            let nested_type_ident = self
+                .nested_type_name_map
+                .get(&self_ty)
+                .map(|s| s.as_str())
+                .unwrap_or_else(|| self_ty.get_final_item());
+            if matches!(
+                fun.special_member,
+                Some(SpecialMemberKind::CopyConstructor | SpecialMemberKind::MoveConstructor)
+            ) {
+                let is_move =
+                    matches!(fun.special_member, Some(SpecialMemberKind::MoveConstructor));
+                if let Some(constructor_suffix) = rust_name.strip_prefix(nested_type_ident) {
+                    rust_name = format!("new{}", constructor_suffix);
+                }
+                rust_name = predetermined_rust_name
+                    .unwrap_or_else(|| self.get_overload_name(ns, type_ident, rust_name));
+                let error_context = self.error_context_for_method(&self_ty, &rust_name);
+
+                // If this is 'None', then something weird is going on. We'll check for that
+                // later when we have enough context to generate useful errors.
+                let arg_is_reference = matches!(
+                    param_details
+                        .get(1)
+                        .map(|param| &param.conversion.unwrapped_type),
+                    Some(Type::Reference(_))
+                );
+                // Some exotic forms of copy constructor have const and/or volatile qualifiers.
+                // These are not sufficient to implement CopyNew, so we just treat them as regular
+                // constructors. We detect them by their argument being translated to Pin at this
+                // point.
+                if is_move || arg_is_reference {
+                    let (kind, method_name, trait_id) = if is_move {
+                        (
+                            TraitMethodKind::MoveConstructor,
+                            "move_new",
+                            quote! { MoveNew },
+                        )
+                    } else {
+                        (
+                            TraitMethodKind::CopyConstructor,
+                            "copy_new",
+                            quote! { CopyNew },
+                        )
+                    };
+                    let ty = Type::Path(self_ty.to_type_path());
+                    (
+                        FnKind::TraitMethod {
+                            kind,
+                            impl_for: self_ty,
+                            details: Box::new(TraitMethodDetails {
+                                trt: TraitImplSignature {
+                                    ty,
+                                    trait_signature: parse_quote! {
+                                        autocxx::moveit::new:: #trait_id
+                                    },
+                                    unsafety: Some(parse_quote! { unsafe }),
+                                },
+                                avoid_self: true,
+                                method_name: make_ident(method_name),
+                                parameter_reordering: Some(vec![1, 0]),
+                                trait_call_is_unsafe: false,
+                            }),
+                        },
+                        error_context,
+                        rust_name,
+                    )
+                } else {
+                    (
+                        FnKind::Method {
+                            impl_for: self_ty,
+                            method_kind: MethodKind::Constructor { is_default: false },
+                        },
+                        error_context,
+                        rust_name,
+                    )
+                }
+            } else if matches!(fun.special_member, Some(SpecialMemberKind::Destructor)) {
+                rust_name = predetermined_rust_name
+                    .unwrap_or_else(|| self.get_overload_name(ns, type_ident, rust_name));
+                let error_context = self.error_context_for_method(&self_ty, &rust_name);
+                let ty = Type::Path(self_ty.to_type_path());
+                (
+                    FnKind::TraitMethod {
+                        kind: TraitMethodKind::Destructor,
+                        impl_for: self_ty,
+                        details: Box::new(TraitMethodDetails {
+                            trt: TraitImplSignature {
+                                ty,
+                                trait_signature: parse_quote! {
+                                    Drop
+                                },
+                                unsafety: None,
+                            },
+                            avoid_self: false,
+                            method_name: make_ident("drop"),
+                            parameter_reordering: None,
+                            trait_call_is_unsafe: false,
+                        }),
+                    },
+                    error_context,
+                    rust_name,
+                )
+            } else {
+                let method_kind = if let Some(constructor_suffix) =
+                    constructor_with_suffix(&rust_name, nested_type_ident)
+                {
+                    // It's a constructor. bindgen generates
+                    // fn Type(this: *mut Type, ...args)
+                    // We want
+                    // fn new(this: *mut Type, ...args)
+                    // Later code will spot this and re-enter us, and we'll make
+                    // a duplicate function in the above 'if' clause like this:
+                    // fn make_unique(...args) -> Type
+                    // which later code will convert to
+                    // fn make_unique(...args) -> UniquePtr<Type>
+                    // If there are multiple constructors, bindgen generates
+                    // new, new1, new2 etc. and we'll keep those suffixes.
+                    rust_name = format!("new{}", constructor_suffix);
+                    MethodKind::Constructor {
+                        is_default: matches!(
+                            fun.special_member,
+                            Some(SpecialMemberKind::DefaultConstructor)
+                        ),
+                    }
+                } else if is_static_method {
+                    MethodKind::Static
+                } else {
+                    let receiver_mutability =
+                        receiver_mutability.expect("Failed to find receiver details");
+                    match fun.virtualness {
+                        Virtualness::None => MethodKind::Normal(receiver_mutability),
+                        Virtualness::Virtual => MethodKind::Virtual(receiver_mutability),
+                        Virtualness::PureVirtual => MethodKind::PureVirtual(receiver_mutability),
+                    }
+                };
+                // Disambiguate overloads.
+                let rust_name = predetermined_rust_name
+                    .unwrap_or_else(|| self.get_overload_name(ns, type_ident, rust_name));
+                let error_context = self.error_context_for_method(&self_ty, &rust_name);
+                (
+                    FnKind::Method {
+                        impl_for: self_ty,
+                        method_kind,
+                    },
+                    error_context,
+                    rust_name,
+                )
+            }
+        } else {
+            // Not a method.
+            // What shall we call this function? It may be overloaded.
+            let rust_name = self.get_function_overload_name(ns, ideal_rust_name);
+            (
+                FnKind::Function,
+                ErrorContext::new_for_item(make_ident(&rust_name)),
+                rust_name,
+            )
+        };
+
+        // If we encounter errors from here on, we can give some context around
+        // where the error occurred such that we can put a marker in the output
+        // Rust code to indicate that a problem occurred (benefiting people using
+        // rust-analyzer or similar). Make a closure to make this easy.
+        let mut ignore_reason = Ok(());
+        let mut set_ignore_reason =
+            |err| ignore_reason = Err(ConvertErrorWithContext(err, Some(error_context.clone())));
+
+        // Now we have figured out the type of function (from its parameters)
+        // we might have determined that we have a constructor. If so,
+        // annoyingly, we need to go back and fiddle with the parameters in a
+        // different way. This is because we want the first parameter to be a
+        // pointer not a reference. For copy + move constructors, we also
+        // enforce Rust-side conversions to comply with moveit traits.
+        match kind {
+            FnKind::Method {
+                method_kind: MethodKind::Constructor { .. },
+                ..
+            } => {
+                self.reanalyze_parameter(
+                    0,
+                    fun,
+                    ns,
+                    &rust_name,
+                    &mut params,
+                    &mut param_details,
+                    None,
+                    sophistication,
+                    true,
+                    false,
+                )
+                .unwrap_or_else(&mut set_ignore_reason);
+            }
+
+            FnKind::TraitMethod {
+                kind: TraitMethodKind::Destructor,
+                ..
+            } => {
+                self.reanalyze_parameter(
+                    0,
+                    fun,
+                    ns,
+                    &rust_name,
+                    &mut params,
+                    &mut param_details,
+                    Some(RustConversionType::FromTypeToPtr),
+                    sophistication,
+                    false,
+                    false,
+                )
+                .unwrap_or_else(&mut set_ignore_reason);
+            }
+            FnKind::TraitMethod {
+                kind: TraitMethodKind::CopyConstructor,
+                ..
+            } => {
+                if param_details.len() < 2 {
+                    set_ignore_reason(ConvertError::ConstructorWithOnlyOneParam);
+                }
+                if param_details.len() > 2 {
+                    set_ignore_reason(ConvertError::ConstructorWithMultipleParams);
+                }
+                self.reanalyze_parameter(
+                    0,
+                    fun,
+                    ns,
+                    &rust_name,
+                    &mut params,
+                    &mut param_details,
+                    Some(RustConversionType::FromPinMaybeUninitToPtr),
+                    sophistication,
+                    false,
+                    false,
+                )
+                .unwrap_or_else(&mut set_ignore_reason);
+            }
+
+            FnKind::TraitMethod {
+                kind: TraitMethodKind::MoveConstructor,
+                ..
+            } => {
+                if param_details.len() < 2 {
+                    set_ignore_reason(ConvertError::ConstructorWithOnlyOneParam);
+                }
+                if param_details.len() > 2 {
+                    set_ignore_reason(ConvertError::ConstructorWithMultipleParams);
+                }
+                self.reanalyze_parameter(
+                    0,
+                    fun,
+                    ns,
+                    &rust_name,
+                    &mut params,
+                    &mut param_details,
+                    Some(RustConversionType::FromPinMaybeUninitToPtr),
+                    sophistication,
+                    false,
+                    false,
+                )
+                .unwrap_or_else(&mut set_ignore_reason);
+                self.reanalyze_parameter(
+                    1,
+                    fun,
+                    ns,
+                    &rust_name,
+                    &mut params,
+                    &mut param_details,
+                    Some(RustConversionType::FromPinMoveRefToPtr),
+                    sophistication,
+                    false,
+                    true,
+                )
+                .unwrap_or_else(&mut set_ignore_reason);
+            }
+            _ => {}
+        }
+
+        // Now we can add context to the error, check for a variety of error
+        // cases. In each case, we continue to record the API, because it might
+        // influence our later decisions to generate synthetic constructors
+        // or note whether the type is abstract.
+        let externally_callable = match fun.cpp_vis {
+            CppVisibility::Private => {
+                set_ignore_reason(ConvertError::PrivateMethod);
+                false
+            }
+            CppVisibility::Protected => false,
+            CppVisibility::Public => true,
+        };
+        if fun.variadic {
+            set_ignore_reason(ConvertError::Variadic);
+        }
+        if let Some(problem) = bads.into_iter().next() {
+            match problem {
+                Ok(_) => panic!("No error in the error"),
+                Err(problem) => set_ignore_reason(problem),
+            }
+        } else if fun.unused_template_param {
+            // This indicates that bindgen essentially flaked out because templates
+            // were too complex.
+            set_ignore_reason(ConvertError::UnusedTemplateParam)
+        } else if matches!(
+            fun.special_member,
+            Some(SpecialMemberKind::AssignmentOperator)
+        ) {
+            // Be careful with the order of this if-else tree. Anything above here means we won't
+            // treat it as an assignment operator, but anything below we still consider when
+            // deciding which other C++ special member functions are implicitly defined.
+            set_ignore_reason(ConvertError::AssignmentOperator)
+        } else if fun.references.rvalue_ref_return {
+            set_ignore_reason(ConvertError::RValueReturn)
+        } else if fun.is_deleted {
+            set_ignore_reason(ConvertError::Deleted)
+        } else {
+            match kind {
+                FnKind::Method {
+                    ref impl_for,
+                    method_kind:
+                        MethodKind::Constructor { .. }
+                        | MethodKind::Normal(..)
+                        | MethodKind::PureVirtual(..)
+                        | MethodKind::Virtual(..),
+                    ..
+                } if !known_types().is_cxx_acceptable_receiver(impl_for) => {
+                    set_ignore_reason(ConvertError::UnsupportedReceiver);
+                }
+                FnKind::Method { ref impl_for, .. } if !self.is_on_allowlist(impl_for) => {
+                    // Bindgen will output methods for types which have been encountered
+                    // virally as arguments on other allowlisted types. But we don't want
+                    // to generate methods unless the user has specifically asked us to.
+                    // It may, for instance, be a private type.
+                    set_ignore_reason(ConvertError::MethodOfNonAllowlistedType);
+                }
+                FnKind::Method { ref impl_for, .. } | FnKind::TraitMethod { ref impl_for, .. } => {
+                    if self.is_generic_type(impl_for) {
+                        set_ignore_reason(ConvertError::MethodOfGenericType);
+                    }
+                    if self.types_in_anonymous_namespace.contains(impl_for) {
+                        set_ignore_reason(ConvertError::MethodInAnonymousNamespace);
+                    }
+                }
+                _ => {}
+            }
+        };
+
+        // The name we use within the cxx::bridge mod may be different
+        // from both the C++ name and the Rust name, because it's a flat
+        // namespace so we might need to prepend some stuff to make it unique.
+        let cxxbridge_name = self.get_cxx_bridge_name(
+            match kind {
+                FnKind::Method { ref impl_for, .. } => Some(impl_for.get_final_item()),
+                FnKind::Function => None,
+                FnKind::TraitMethod { ref impl_for, .. } => Some(impl_for.get_final_item()),
+            },
+            &rust_name,
+            ns,
+        );
+        if cxxbridge_name != rust_name && cpp_name.is_none() {
+            cpp_name = Some(rust_name.clone());
+        }
+        let mut cxxbridge_name = make_ident(&cxxbridge_name);
+
+        // Analyze the return type, just as we previously did for the
+        // parameters.
+        let mut return_analysis = self
+            .convert_return_type(&fun.output, ns, &fun.references, sophistication)
+            .unwrap_or_else(|err| {
+                set_ignore_reason(err);
+                ReturnTypeAnalysis::default()
+            });
+        let mut deps = params_deps;
+        deps.extend(return_analysis.deps.drain(..));
+
+        // Sometimes, the return type will actually be a value type
+        // for which we instead want to _pass_ a pointer into which the value
+        // can be constructed. Handle that case here.
+        if let Some((extra_param, extra_param_details)) = return_analysis.placement_param_needed {
+            param_details.push(extra_param_details);
+            params.push(extra_param);
+        }
+
+        let requires_unsafe = self.should_be_unsafe(&param_details, &kind);
+
+        let num_input_references = param_details.iter().filter(|pd| pd.has_lifetime).count();
+        if num_input_references != 1 && return_analysis.was_reference {
+            // cxx only allows functions to return a reference if they take exactly
+            // one reference as a parameter. Let's see...
+            set_ignore_reason(ConvertError::NotOneInputReference(rust_name.clone()));
+        }
+        let mut ret_type = return_analysis.rt;
+        let ret_type_conversion = return_analysis.conversion;
+
+        // Do we need to convert either parameters or return type?
+        let param_conversion_needed = param_details.iter().any(|b| b.conversion.cpp_work_needed());
+        let ret_type_conversion_needed = ret_type_conversion
+            .as_ref()
+            .map_or(false, |x| x.cpp_work_needed());
+        // See https://github.com/dtolnay/cxx/issues/878 for the reason for this next line.
+        let effective_cpp_name = cpp_name.as_ref().unwrap_or(&rust_name);
+        let cpp_name_incompatible_with_cxx =
+            validate_ident_ok_for_rust(effective_cpp_name).is_err();
+        // If possible, we'll put knowledge of the C++ API directly into the cxx::bridge
+        // mod. However, there are various circumstances where cxx can't work with the existing
+        // C++ API and we need to create a C++ wrapper function which is more cxx-compliant.
+        // That wrapper function is included in the cxx::bridge, and calls through to the
+        // original function.
+        let wrapper_function_needed = match kind {
+            FnKind::Method {
+                method_kind:
+                    MethodKind::Static
+                    | MethodKind::Constructor { .. }
+                    | MethodKind::Virtual(_)
+                    | MethodKind::PureVirtual(_),
+                ..
+            }
+            | FnKind::TraitMethod {
+                kind:
+                    TraitMethodKind::CopyConstructor
+                    | TraitMethodKind::MoveConstructor
+                    | TraitMethodKind::Destructor,
+                ..
+            } => true,
+            FnKind::Method { .. } if cxxbridge_name != rust_name => true,
+            _ if param_conversion_needed => true,
+            _ if ret_type_conversion_needed => true,
+            _ if cpp_name_incompatible_with_cxx => true,
+            _ if fun.synthetic_cpp.is_some() => true,
+            _ => false,
+        };
+
+        let cpp_wrapper = if wrapper_function_needed {
+            // Generate a new layer of C++ code to wrap/unwrap parameters
+            // and return values into/out of std::unique_ptrs.
+            let cpp_construction_ident = make_ident(&effective_cpp_name);
+            let joiner = if cxxbridge_name.to_string().ends_with('_') {
+                ""
+            } else {
+                "_"
+            };
+            cxxbridge_name = make_ident(&format!("{}{}autocxx_wrapper", cxxbridge_name, joiner));
+            let (payload, cpp_function_kind) = match fun.synthetic_cpp.as_ref().cloned() {
+                Some((payload, cpp_function_kind)) => (payload, cpp_function_kind),
+                None => match kind {
+                    FnKind::Method {
+                        ref impl_for,
+                        method_kind: MethodKind::Constructor { .. },
+                        ..
+                    }
+                    | FnKind::TraitMethod {
+                        kind: TraitMethodKind::CopyConstructor | TraitMethodKind::MoveConstructor,
+                        ref impl_for,
+                        ..
+                    } => (
+                        CppFunctionBody::PlacementNew(ns.clone(), impl_for.get_final_ident()),
+                        CppFunctionKind::Constructor,
+                    ),
+                    FnKind::TraitMethod {
+                        kind: TraitMethodKind::Destructor,
+                        ref impl_for,
+                        ..
+                    } => (
+                        CppFunctionBody::Destructor(ns.clone(), impl_for.get_final_ident()),
+                        CppFunctionKind::Function,
+                    ),
+                    FnKind::Method {
+                        ref impl_for,
+                        method_kind: MethodKind::Static,
+                        ..
+                    } => (
+                        CppFunctionBody::StaticMethodCall(
+                            ns.clone(),
+                            impl_for.get_final_ident(),
+                            cpp_construction_ident,
+                        ),
+                        CppFunctionKind::Function,
+                    ),
+                    FnKind::Method { .. } => (
+                        CppFunctionBody::FunctionCall(ns.clone(), cpp_construction_ident),
+                        CppFunctionKind::Method,
+                    ),
+                    _ => (
+                        CppFunctionBody::FunctionCall(ns.clone(), cpp_construction_ident),
+                        CppFunctionKind::Function,
+                    ),
+                },
+            };
+            // Now modify the cxx::bridge entry we're going to make.
+            if let Some(ref conversion) = ret_type_conversion {
+                if conversion.populate_return_value() {
+                    let new_ret_type = conversion.unconverted_rust_type();
+                    ret_type = parse_quote!(
+                        -> #new_ret_type
+                    );
+                }
+            }
+
+            // Amend parameters for the function which we're asking cxx to generate.
+            params.clear();
+            for pd in &param_details {
+                let type_name = pd.conversion.converted_rust_type();
+                let arg_name = if pd.self_type.is_some() {
+                    parse_quote!(autocxx_gen_this)
+                } else {
+                    pd.name.clone()
+                };
+                params.push(parse_quote!(
+                    #arg_name: #type_name
+                ));
+            }
+
+            Some(CppFunction {
+                payload,
+                wrapper_function_name: cxxbridge_name.clone(),
+                original_cpp_name: cpp_name
+                    .as_ref()
+                    .cloned()
+                    .unwrap_or_else(|| cxxbridge_name.to_string()),
+                return_conversion: ret_type_conversion.clone(),
+                argument_conversion: param_details.iter().map(|d| d.conversion.clone()).collect(),
+                kind: cpp_function_kind,
+                pass_obs_field: false,
+                qualification: None,
+            })
+        } else {
+            None
+        };
+
+        let vis = fun.vis.clone();
+
+        let any_param_needs_rust_conversion = param_details
+            .iter()
+            .any(|pd| pd.conversion.rust_work_needed());
+
+        let rust_wrapper_needed = match kind {
+            FnKind::TraitMethod { .. } => true,
+            FnKind::Method { .. } => any_param_needs_rust_conversion || cxxbridge_name != rust_name,
+            _ => any_param_needs_rust_conversion,
+        };
+
+        // Naming, part two.
+        // Work out our final naming strategy.
+        validate_ident_ok_for_cxx(&cxxbridge_name.to_string()).unwrap_or_else(set_ignore_reason);
+        let rust_name_ident = make_ident(&rust_name);
+        let rust_rename_strategy = match kind {
+            _ if rust_wrapper_needed => RustRenameStrategy::RenameUsingWrapperFunction,
+            FnKind::Function if cxxbridge_name != rust_name => {
+                RustRenameStrategy::RenameInOutputMod(rust_name_ident)
+            }
+            _ => RustRenameStrategy::None,
+        };
+
+        let analysis = FnAnalysis {
+            cxxbridge_name: cxxbridge_name.clone(),
+            rust_name: rust_name.clone(),
+            rust_rename_strategy,
+            params,
+            ret_conversion: ret_type_conversion,
+            kind,
+            ret_type,
+            param_details,
+            requires_unsafe,
+            vis,
+            cpp_wrapper,
+            deps,
+            ignore_reason,
+            externally_callable,
+            rust_wrapper_needed,
+        };
+        let name = ApiName::new_with_cpp_name(ns, cxxbridge_name, cpp_name);
+        (analysis, name)
+    }
+
+    fn error_context_for_method(&self, self_ty: &QualifiedName, rust_name: &str) -> ErrorContext {
+        if self.is_generic_type(self_ty) {
+            // A 'method' error context would end up in an
+            //   impl A {
+            //      fn error_thingy
+            //   }
+            // block. We can't impl A if it would need to be impl A<B>
+            ErrorContext::new_for_item(make_ident(rust_name))
+        } else {
+            ErrorContext::new_for_method(self_ty.get_final_ident(), make_ident(rust_name))
+        }
+    }
+
+    /// Applies a specific `force_rust_conversion` to the parameter at index
+    /// `param_idx`. Modifies `param_details` and `params` in place.
+    #[allow(clippy::too_many_arguments)] // it's true, but sticking with it for now
+    fn reanalyze_parameter(
+        &mut self,
+        param_idx: usize,
+        fun: &FuncToConvert,
+        ns: &Namespace,
+        rust_name: &str,
+        params: &mut Punctuated<FnArg, Comma>,
+        param_details: &mut [ArgumentAnalysis],
+        force_rust_conversion: Option<RustConversionType>,
+        sophistication: TypeConversionSophistication,
+        construct_into_self: bool,
+        is_move_constructor: bool,
+    ) -> Result<(), ConvertError> {
+        self.convert_fn_arg(
+            fun.inputs.iter().nth(param_idx).unwrap(),
+            ns,
+            rust_name,
+            &fun.synthesized_this_type,
+            &fun.references,
+            false,
+            is_move_constructor,
+            force_rust_conversion,
+            sophistication,
+            construct_into_self,
+        )
+        .map(|(new_arg, new_analysis)| {
+            param_details[param_idx] = new_analysis;
+            let mut params_before = params.clone().into_iter();
+            let prefix = params_before
+                .by_ref()
+                .take(param_idx)
+                .collect_vec()
+                .into_iter();
+            let suffix = params_before.skip(1);
+            *params = prefix
+                .chain(std::iter::once(new_arg))
+                .chain(suffix)
+                .collect()
+        })
+    }
+
+    fn get_overload_name(&mut self, ns: &Namespace, type_ident: &str, rust_name: String) -> String {
+        let overload_tracker = self.overload_trackers_by_mod.entry(ns.clone()).or_default();
+        overload_tracker.get_method_real_name(type_ident, rust_name)
+    }
+
+    /// Determine if this synthetic function should actually result in the implementation
+    /// of a trait, rather than a function/method.
+    fn trait_creation_details_for_synthetic_function(
+        &mut self,
+        synthesis: &Option<TraitSynthesis>,
+        ns: &Namespace,
+        ideal_rust_name: &str,
+        self_ty: &Option<QualifiedName>,
+    ) -> Option<(FnKind, ErrorContext, String)> {
+        synthesis.as_ref().and_then(|synthesis| match synthesis {
+            TraitSynthesis::Cast { to_type, mutable } => {
+                let rust_name = self.get_function_overload_name(ns, ideal_rust_name.to_string());
+                let from_type = self_ty.as_ref().unwrap();
+                let from_type_path = from_type.to_type_path();
+                let to_type = to_type.to_type_path();
+                let (trait_signature, ty, method_name) = match *mutable {
+                    CastMutability::ConstToConst => (
+                        parse_quote! {
+                            AsRef < #to_type >
+                        },
+                        Type::Path(from_type_path),
+                        "as_ref",
+                    ),
+                    CastMutability::MutToConst => (
+                        parse_quote! {
+                            AsRef < #to_type >
+                        },
+                        parse_quote! {
+                            &'a mut ::std::pin::Pin < &'a mut #from_type_path >
+                        },
+                        "as_ref",
+                    ),
+                    CastMutability::MutToMut => (
+                        parse_quote! {
+                            autocxx::PinMut < #to_type >
+                        },
+                        parse_quote! {
+                            ::std::pin::Pin < &'a mut #from_type_path >
+                        },
+                        "pin_mut",
+                    ),
+                };
+                let method_name = make_ident(method_name);
+                Some((
+                    FnKind::TraitMethod {
+                        kind: TraitMethodKind::Cast,
+                        impl_for: from_type.clone(),
+                        details: Box::new(TraitMethodDetails {
+                            trt: TraitImplSignature {
+                                ty,
+                                trait_signature,
+                                unsafety: None,
+                            },
+                            avoid_self: false,
+                            method_name,
+                            parameter_reordering: None,
+                            trait_call_is_unsafe: false,
+                        }),
+                    },
+                    ErrorContext::new_for_item(make_ident(&rust_name)),
+                    rust_name,
+                ))
+            }
+            TraitSynthesis::AllocUninitialized(ty) => self.generate_alloc_or_deallocate(
+                ideal_rust_name,
+                ty,
+                "allocate_uninitialized_cpp_storage",
+                TraitMethodKind::Alloc,
+            ),
+            TraitSynthesis::FreeUninitialized(ty) => self.generate_alloc_or_deallocate(
+                ideal_rust_name,
+                ty,
+                "free_uninitialized_cpp_storage",
+                TraitMethodKind::Dealloc,
+            ),
+        })
+    }
+
+    fn generate_alloc_or_deallocate(
+        &mut self,
+        ideal_rust_name: &str,
+        ty: &QualifiedName,
+        method_name: &str,
+        kind: TraitMethodKind,
+    ) -> Option<(FnKind, ErrorContext, String)> {
+        let rust_name =
+            self.get_function_overload_name(ty.get_namespace(), ideal_rust_name.to_string());
+        let typ = ty.to_type_path();
+        Some((
+            FnKind::TraitMethod {
+                impl_for: ty.clone(),
+                details: Box::new(TraitMethodDetails {
+                    trt: TraitImplSignature {
+                        ty: Type::Path(typ),
+                        trait_signature: parse_quote! { autocxx::moveit::MakeCppStorage },
+                        unsafety: Some(parse_quote! { unsafe }),
+                    },
+                    avoid_self: false,
+                    method_name: make_ident(method_name),
+                    parameter_reordering: None,
+                    trait_call_is_unsafe: false,
+                }),
+                kind,
+            },
+            ErrorContext::new_for_item(make_ident(&rust_name)),
+            rust_name,
+        ))
+    }
+
+    fn get_function_overload_name(&mut self, ns: &Namespace, ideal_rust_name: String) -> String {
+        let overload_tracker = self.overload_trackers_by_mod.entry(ns.clone()).or_default();
+        overload_tracker.get_function_real_name(ideal_rust_name)
+    }
+
+    fn subclasses_by_superclass(&self, sup: &QualifiedName) -> impl Iterator<Item = SubclassName> {
+        match self.subclasses_by_superclass.get(sup) {
+            Some(subs) => subs.clone().into_iter(),
+            None => Vec::new().into_iter(),
+        }
+    }
+
+    #[allow(clippy::too_many_arguments)] // currently reasonably clear
+    fn convert_fn_arg(
+        &mut self,
+        arg: &FnArg,
+        ns: &Namespace,
+        fn_name: &str,
+        virtual_this: &Option<QualifiedName>,
+        references: &References,
+        treat_this_as_reference: bool,
+        is_move_constructor: bool,
+        force_rust_conversion: Option<RustConversionType>,
+        sophistication: TypeConversionSophistication,
+        construct_into_self: bool,
+    ) -> Result<(FnArg, ArgumentAnalysis), ConvertError> {
+        Ok(match arg {
+            FnArg::Typed(pt) => {
+                let mut pt = pt.clone();
+                let mut self_type = None;
+                let old_pat = *pt.pat;
+                let mut pointer_treatment = PointerTreatment::Pointer;
+                let mut is_placement_return_destination = false;
+                let new_pat = match old_pat {
+                    syn::Pat::Ident(mut pp) if pp.ident == "this" => {
+                        let this_type = match pt.ty.as_ref() {
+                            Type::Ptr(TypePtr {
+                                elem, mutability, ..
+                            }) => match elem.as_ref() {
+                                Type::Path(typ) => {
+                                    let receiver_mutability = if mutability.is_some() {
+                                        ReceiverMutability::Mutable
+                                    } else {
+                                        ReceiverMutability::Const
+                                    };
+
+                                    let this_type = if let Some(virtual_this) = virtual_this {
+                                        let this_type_path = virtual_this.to_type_path();
+                                        let const_token = if mutability.is_some() {
+                                            None
+                                        } else {
+                                            Some(syn::Token![const](Span::call_site()))
+                                        };
+                                        pt.ty = Box::new(parse_quote! {
+                                            * #mutability #const_token #this_type_path
+                                        });
+                                        virtual_this.clone()
+                                    } else {
+                                        QualifiedName::from_type_path(typ)
+                                    };
+                                    Ok((this_type, receiver_mutability))
+                                }
+                                _ => Err(ConvertError::UnexpectedThisType(QualifiedName::new(
+                                    ns,
+                                    make_ident(fn_name),
+                                ))),
+                            },
+                            _ => Err(ConvertError::UnexpectedThisType(QualifiedName::new(
+                                ns,
+                                make_ident(fn_name),
+                            ))),
+                        }?;
+                        self_type = Some(this_type);
+                        is_placement_return_destination = construct_into_self;
+                        if treat_this_as_reference {
+                            pp.ident = Ident::new("self", pp.ident.span());
+                            pointer_treatment = PointerTreatment::Reference;
+                        }
+                        syn::Pat::Ident(pp)
+                    }
+                    syn::Pat::Ident(pp) => {
+                        validate_ident_ok_for_cxx(&pp.ident.to_string())?;
+                        pointer_treatment = references.param_treatment(&pp.ident);
+                        syn::Pat::Ident(pp)
+                    }
+                    _ => old_pat,
+                };
+                let is_placement_return_destination = is_placement_return_destination
+                    || matches!(
+                        force_rust_conversion,
+                        Some(RustConversionType::FromPlacementParamToNewReturn)
+                    );
+                let annotated_type = self.convert_boxed_type(pt.ty, ns, pointer_treatment)?;
+                let conversion = self.argument_conversion_details(
+                    &annotated_type,
+                    is_move_constructor,
+                    force_rust_conversion,
+                    sophistication,
+                );
+                let new_ty = annotated_type.ty;
+                pt.pat = Box::new(new_pat.clone());
+                pt.ty = new_ty;
+                let requires_unsafe =
+                    if matches!(annotated_type.kind, type_converter::TypeKind::Pointer)
+                        && !is_placement_return_destination
+                    {
+                        UnsafetyNeeded::Always
+                    } else if conversion.bridge_unsafe_needed() || is_placement_return_destination {
+                        UnsafetyNeeded::JustBridge
+                    } else {
+                        UnsafetyNeeded::None
+                    };
+                (
+                    FnArg::Typed(pt),
+                    ArgumentAnalysis {
+                        self_type,
+                        name: new_pat,
+                        conversion,
+                        has_lifetime: matches!(
+                            annotated_type.kind,
+                            type_converter::TypeKind::Reference
+                                | type_converter::TypeKind::MutableReference
+                        ),
+                        deps: annotated_type.types_encountered,
+                        requires_unsafe,
+                        is_placement_return_destination,
+                    },
+                )
+            }
+            _ => panic!("Did not expect FnArg::Receiver to be generated by bindgen"),
+        })
+    }
+
+    fn argument_conversion_details(
+        &self,
+        annotated_type: &Annotated<Box<Type>>,
+        is_move_constructor: bool,
+        force_rust_conversion: Option<RustConversionType>,
+        sophistication: TypeConversionSophistication,
+    ) -> TypeConversionPolicy {
+        let is_subclass_holder = match &annotated_type.kind {
+            type_converter::TypeKind::SubclassHolder(holder) => Some(holder),
+            _ => None,
+        };
+        let is_rvalue_ref = matches!(
+            annotated_type.kind,
+            type_converter::TypeKind::RValueReference
+        );
+        let ty = &*annotated_type.ty;
+        if let Some(holder_id) = is_subclass_holder {
+            let subclass = SubclassName::from_holder_name(holder_id);
+            return {
+                let ty = parse_quote! {
+                    rust::Box<#holder_id>
+                };
+                TypeConversionPolicy {
+                    unwrapped_type: ty,
+                    cpp_conversion: CppConversionType::Move,
+                    rust_conversion: RustConversionType::ToBoxedUpHolder(subclass),
+                }
+            };
+        } else if matches!(
+            force_rust_conversion,
+            Some(RustConversionType::FromPlacementParamToNewReturn)
+        ) && matches!(sophistication, TypeConversionSophistication::Regular)
+        {
+            return TypeConversionPolicy {
+                unwrapped_type: ty.clone(),
+                cpp_conversion: CppConversionType::IgnoredPlacementPtrParameter,
+                rust_conversion: RustConversionType::FromPlacementParamToNewReturn,
+            };
+        }
+        match ty {
+            Type::Path(p) => {
+                let ty = ty.clone();
+                let tn = QualifiedName::from_type_path(p);
+                if self.pod_safe_types.contains(&tn) {
+                    if known_types().lacks_copy_constructor(&tn) {
+                        TypeConversionPolicy {
+                            unwrapped_type: ty,
+                            cpp_conversion: CppConversionType::Move,
+                            rust_conversion: RustConversionType::None,
+                        }
+                    } else {
+                        TypeConversionPolicy::new_unconverted(ty)
+                    }
+                } else if known_types().convertible_from_strs(&tn)
+                    && !self.config.exclude_utilities()
+                {
+                    TypeConversionPolicy {
+                        unwrapped_type: ty,
+                        cpp_conversion: CppConversionType::FromUniquePtrToValue,
+                        rust_conversion: RustConversionType::FromStr,
+                    }
+                } else if matches!(
+                    sophistication,
+                    TypeConversionSophistication::SimpleForSubclasses
+                ) {
+                    TypeConversionPolicy {
+                        unwrapped_type: ty,
+                        cpp_conversion: CppConversionType::FromUniquePtrToValue,
+                        rust_conversion: RustConversionType::None,
+                    }
+                } else {
+                    TypeConversionPolicy {
+                        unwrapped_type: ty,
+                        cpp_conversion: CppConversionType::FromPtrToValue,
+                        rust_conversion: RustConversionType::FromValueParamToPtr,
+                    }
+                }
+            }
+            Type::Ptr(tp) => {
+                let rust_conversion = force_rust_conversion.unwrap_or(RustConversionType::None);
+                if is_move_constructor {
+                    TypeConversionPolicy {
+                        unwrapped_type: ty.clone(),
+                        cpp_conversion: CppConversionType::FromPtrToMove,
+                        rust_conversion,
+                    }
+                } else if is_rvalue_ref {
+                    TypeConversionPolicy {
+                        unwrapped_type: *tp.elem.clone(),
+                        cpp_conversion: CppConversionType::FromPtrToValue,
+                        rust_conversion: RustConversionType::FromRValueParamToPtr,
+                    }
+                } else {
+                    TypeConversionPolicy {
+                        unwrapped_type: ty.clone(),
+                        cpp_conversion: CppConversionType::None,
+                        rust_conversion,
+                    }
+                }
+            }
+            _ => {
+                let rust_conversion = force_rust_conversion.unwrap_or(RustConversionType::None);
+                TypeConversionPolicy {
+                    unwrapped_type: ty.clone(),
+                    cpp_conversion: CppConversionType::None,
+                    rust_conversion,
+                }
+            }
+        }
+    }
+
+    fn convert_return_type(
+        &mut self,
+        rt: &ReturnType,
+        ns: &Namespace,
+        references: &References,
+        sophistication: TypeConversionSophistication,
+    ) -> Result<ReturnTypeAnalysis, ConvertError> {
+        Ok(match rt {
+            ReturnType::Default => ReturnTypeAnalysis::default(),
+            ReturnType::Type(rarrow, boxed_type) => {
+                let annotated_type =
+                    self.convert_boxed_type(boxed_type.clone(), ns, references.return_treatment())?;
+                let boxed_type = annotated_type.ty;
+                let ty: &Type = boxed_type.as_ref();
+                match ty {
+                    Type::Path(p)
+                        if !self
+                            .pod_safe_types
+                            .contains(&QualifiedName::from_type_path(p)) =>
+                    {
+                        let tn = QualifiedName::from_type_path(p);
+                        if self.moveit_safe_types.contains(&tn)
+                            && matches!(sophistication, TypeConversionSophistication::Regular)
+                        {
+                            // This is a non-POD type we want to return to Rust as an `impl New` so that callers
+                            // can decide whether to store this on the stack or heap.
+                            // That means, we do not literally _return_ it from C++ to Rust. Instead, our call
+                            // from Rust to C++ will include an extra placement parameter into which the object
+                            // is constructed.
+                            let fnarg = parse_quote! {
+                                placement_return_type: *mut #ty
+                            };
+                            let (fnarg, analysis) = self.convert_fn_arg(
+                                &fnarg,
+                                ns,
+                                "",
+                                &None,
+                                &References::default(),
+                                false,
+                                false,
+                                Some(RustConversionType::FromPlacementParamToNewReturn),
+                                TypeConversionSophistication::Regular,
+                                false,
+                            )?;
+                            ReturnTypeAnalysis {
+                                rt: ReturnType::Default,
+                                conversion: Some(TypeConversionPolicy::new_for_placement_return(
+                                    ty.clone(),
+                                )),
+                                was_reference: false,
+                                deps: annotated_type.types_encountered,
+                                placement_param_needed: Some((fnarg, analysis)),
+                            }
+                        } else {
+                            // There are some types which we can't currently represent within a moveit::new::New.
+                            // That's either because we are obliged to stick to existing protocols for compatibility
+                            // (CxxString) or because they're a concrete type where we haven't attempted to do
+                            // the analysis to work out the type's size. For these, we always return a plain old
+                            // UniquePtr<T>. These restrictions may be fixed in future.
+                            let conversion =
+                                Some(TypeConversionPolicy::new_to_unique_ptr(ty.clone()));
+                            ReturnTypeAnalysis {
+                                rt: ReturnType::Type(*rarrow, boxed_type),
+                                conversion,
+                                was_reference: false,
+                                deps: annotated_type.types_encountered,
+                                placement_param_needed: None,
+                            }
+                        }
+                    }
+                    _ => {
+                        let was_reference = matches!(boxed_type.as_ref(), Type::Reference(_));
+                        let conversion = Some(TypeConversionPolicy::new_unconverted(ty.clone()));
+                        ReturnTypeAnalysis {
+                            rt: ReturnType::Type(*rarrow, boxed_type),
+                            conversion,
+                            was_reference,
+                            deps: annotated_type.types_encountered,
+                            placement_param_needed: None,
+                        }
+                    }
+                }
+            }
+        })
+    }
+
+    /// If a type has explicit constructors, bindgen will generate corresponding
+    /// constructor functions, which we'll have already converted to make_unique methods.
+    /// C++ mandates the synthesis of certain implicit constructors, to which we
+    /// need to create bindings too. We do that here.
+    /// It is tempting to make this a separate analysis phase, to be run later than
+    /// the function analysis; but that would make the code much more complex as it
+    /// would need to output a `FnAnalysisBody`. By running it as part of this phase
+    /// we can simply generate the sort of thing bindgen generates, then ask
+    /// the existing code in this phase to figure out what to do with it.
+    ///
+    /// Also fills out the [`PodAndConstructorAnalysis::constructors`] fields with information useful
+    /// for further analysis phases.
+    fn add_constructors_present(&mut self, mut apis: ApiVec<FnPrePhase1>) -> ApiVec<FnPrePhase2> {
+        let all_items_found = find_constructors_present(&apis);
+        for (self_ty, items_found) in all_items_found.iter() {
+            if self.config.exclude_impls {
+                // Remember that `find_constructors_present` mutates `apis`, so we always have to
+                // call that, even if we don't do anything with the return value. This is kind of
+                // messy, see the comment on this function for why.
+                continue;
+            }
+            if self
+                .config
+                .is_on_constructor_blocklist(&self_ty.to_cpp_name())
+            {
+                continue;
+            }
+            let path = self_ty.to_type_path();
+            if items_found.implicit_default_constructor_needed() {
+                self.synthesize_special_member(
+                    items_found,
+                    "default_ctor",
+                    &mut apis,
+                    SpecialMemberKind::DefaultConstructor,
+                    parse_quote! { this: *mut #path },
+                    References::default(),
+                );
+            }
+            if items_found.implicit_move_constructor_needed() {
+                self.synthesize_special_member(
+                    items_found,
+                    "move_ctor",
+                    &mut apis,
+                    SpecialMemberKind::MoveConstructor,
+                    parse_quote! { this: *mut #path, other: *mut #path },
+                    References {
+                        rvalue_ref_params: [make_ident("other")].into_iter().collect(),
+                        ..Default::default()
+                    },
+                )
+            }
+            if items_found.implicit_copy_constructor_needed() {
+                self.synthesize_special_member(
+                    items_found,
+                    "const_copy_ctor",
+                    &mut apis,
+                    SpecialMemberKind::CopyConstructor,
+                    parse_quote! { this: *mut #path, other: *const #path },
+                    References {
+                        ref_params: [make_ident("other")].into_iter().collect(),
+                        ..Default::default()
+                    },
+                )
+            }
+            if items_found.implicit_destructor_needed() {
+                self.synthesize_special_member(
+                    items_found,
+                    "destructor",
+                    &mut apis,
+                    SpecialMemberKind::Destructor,
+                    parse_quote! { this: *mut #path },
+                    References::default(),
+                );
+            }
+        }
+
+        // Also, annotate each type with the constructors we found.
+        let mut results = ApiVec::new();
+        convert_apis(
+            apis,
+            &mut results,
+            Api::fun_unchanged,
+            |name, details, analysis| {
+                let items_found = all_items_found.get(&name.name);
+                Ok(Box::new(std::iter::once(Api::Struct {
+                    name,
+                    details,
+                    analysis: PodAndConstructorAnalysis {
+                        pod: analysis,
+                        constructors: if let Some(items_found) = items_found {
+                            PublicConstructors::from_items_found(items_found)
+                        } else {
+                            PublicConstructors::default()
+                        },
+                    },
+                })))
+            },
+            Api::enum_unchanged,
+            Api::typedef_unchanged,
+        );
+        results
+    }
+
+    #[allow(clippy::too_many_arguments)] // it's true, but sticking with it for now
+    fn synthesize_special_member(
+        &mut self,
+        items_found: &ItemsFound,
+        label: &str,
+        apis: &mut ApiVec<FnPrePhase1>,
+        special_member: SpecialMemberKind,
+        inputs: Punctuated<FnArg, Comma>,
+        references: References,
+    ) {
+        let self_ty = items_found.name.as_ref().unwrap();
+        let ident = make_ident(self.config.uniquify_name_per_mod(&format!(
+            "{}_synthetic_{}",
+            self_ty.name.get_final_item(),
+            label
+        )));
+        let cpp_name = if matches!(special_member, SpecialMemberKind::DefaultConstructor) {
+            // Constructors (other than move or copy) are identified in `analyze_foreign_fn` by
+            // being suffixed with the cpp_name, so we have to produce that.
+            self.nested_type_name_map
+                .get(&self_ty.name)
+                .cloned()
+                .or_else(|| Some(self_ty.name.get_final_item().to_string()))
+        } else {
+            None
+        };
+        let fake_api_name =
+            ApiName::new_with_cpp_name(self_ty.name.get_namespace(), ident.clone(), cpp_name);
+        let self_ty = &self_ty.name;
+        let ns = self_ty.get_namespace().clone();
+        let mut any_errors = ApiVec::new();
+        apis.extend(
+            report_any_error(&ns, &mut any_errors, || {
+                self.analyze_foreign_fn_and_subclasses(
+                    fake_api_name,
+                    Box::new(FuncToConvert {
+                        self_ty: Some(self_ty.clone()),
+                        ident,
+                        doc_attrs: make_doc_attrs(format!("Synthesized {}.", special_member)),
+                        inputs,
+                        output: ReturnType::Default,
+                        vis: parse_quote! { pub },
+                        virtualness: Virtualness::None,
+                        cpp_vis: CppVisibility::Public,
+                        special_member: Some(special_member),
+                        unused_template_param: false,
+                        references,
+                        original_name: None,
+                        synthesized_this_type: None,
+                        is_deleted: false,
+                        add_to_trait: None,
+                        synthetic_cpp: None,
+                        provenance: Provenance::SynthesizedOther,
+                        variadic: false,
+                    }),
+                )
+            })
+            .into_iter()
+            .flatten(),
+        );
+        apis.append(&mut any_errors);
+    }
+}
+
+/// Attempts to determine whether this function name is a constructor, and if so,
+/// returns the suffix.
+fn constructor_with_suffix<'a>(rust_name: &'a str, nested_type_ident: &str) -> Option<&'a str> {
+    let suffix = rust_name.strip_prefix(nested_type_ident);
+    suffix.and_then(|suffix| {
+        if suffix.is_empty() || suffix.parse::<u32>().is_ok() {
+            Some(suffix)
+        } else {
+            None
+        }
+    })
+}
+
+impl Api<FnPhase> {
+    pub(crate) fn name_for_allowlist(&self) -> QualifiedName {
+        match &self {
+            Api::Function { analysis, .. } => match analysis.kind {
+                FnKind::Method { ref impl_for, .. } => impl_for.clone(),
+                FnKind::TraitMethod { ref impl_for, .. } => impl_for.clone(),
+                FnKind::Function => {
+                    QualifiedName::new(self.name().get_namespace(), make_ident(&analysis.rust_name))
+                }
+            },
+            Api::RustSubclassFn { subclass, .. } => subclass.0.name.clone(),
+            Api::IgnoredItem {
+                name,
+                ctx: Some(ctx),
+                ..
+            } => match ctx.get_type() {
+                ErrorContextType::Method { self_ty, .. } => {
+                    QualifiedName::new(name.name.get_namespace(), self_ty.clone())
+                }
+                ErrorContextType::Item(id) => {
+                    QualifiedName::new(name.name.get_namespace(), id.clone())
+                }
+                _ => name.name.clone(),
+            },
+            _ => self.name().clone(),
+        }
+    }
+
+    /// Whether this API requires generation of additional C++.
+    /// This seems an odd place for this function (as opposed to in the [codegen_cpp]
+    /// module) but, as it happens, even our Rust codegen phase needs to know if
+    /// more C++ is needed (so it can add #includes in the cxx mod).
+    /// And we can't answer the question _prior_ to this function analysis phase.
+    pub(crate) fn needs_cpp_codegen(&self) -> bool {
+        matches!(
+            &self,
+            Api::Function {
+                analysis: FnAnalysis {
+                    cpp_wrapper: Some(..),
+                    ignore_reason: Ok(_),
+                    externally_callable: true,
+                    ..
+                },
+                ..
+            } | Api::StringConstructor { .. }
+                | Api::ConcreteType { .. }
+                | Api::CType { .. }
+                | Api::RustSubclassFn { .. }
+                | Api::Subclass { .. }
+                | Api::Struct {
+                    analysis: PodAndDepAnalysis {
+                        pod: PodAnalysis {
+                            kind: TypeKind::Pod,
+                            ..
+                        },
+                        ..
+                    },
+                    ..
+                }
+        )
+    }
+
+    pub(crate) fn cxxbridge_name(&self) -> Option<Ident> {
+        match self {
+            Api::Function { ref analysis, .. } => Some(analysis.cxxbridge_name.clone()),
+            Api::StringConstructor { .. }
+            | Api::Const { .. }
+            | Api::IgnoredItem { .. }
+            | Api::RustSubclassFn { .. } => None,
+            _ => Some(self.name().get_final_ident()),
+        }
+    }
+}
diff --git a/engine/src/conversion/analysis/fun/overload_tracker.rs b/engine/src/conversion/analysis/fun/overload_tracker.rs
new file mode 100644
index 0000000..6fc532c
--- /dev/null
+++ b/engine/src/conversion/analysis/fun/overload_tracker.rs
@@ -0,0 +1,77 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::collections::HashMap;
+
+type Offsets = HashMap<String, usize>;
+
+/// Registry of all the overloads of a function found within a given
+/// namespace (i.e. mod in bindgen's output). If necessary we'll append
+/// a _nnn suffix to a function's Rust name to disambiguate overloads.
+/// Note that this is NOT necessarily the same as the suffix added by
+/// bindgen to disambiguate overloads it discovers. Its suffix is
+/// global across all functions, whereas ours is local within a given
+/// type.
+/// If bindgen adds a suffix it will be included in 'found_name'
+/// but not 'original_name' which is an annotation added by our autocxx-bindgen
+/// fork.
+#[derive(Default)]
+pub(crate) struct OverloadTracker {
+    offset_by_name: Offsets,
+    offset_by_type_and_name: HashMap<String, Offsets>,
+}
+
+impl OverloadTracker {
+    pub(crate) fn get_function_real_name(&mut self, found_name: String) -> String {
+        self.get_name(None, found_name)
+    }
+
+    pub(crate) fn get_method_real_name(&mut self, type_name: &str, found_name: String) -> String {
+        self.get_name(Some(type_name), found_name)
+    }
+
+    fn get_name(&mut self, type_name: Option<&str>, cpp_method_name: String) -> String {
+        let registry = match type_name {
+            Some(type_name) => self
+                .offset_by_type_and_name
+                .entry(type_name.to_string())
+                .or_default(),
+            None => &mut self.offset_by_name,
+        };
+        let offset = registry.entry(cpp_method_name.clone()).or_default();
+        let this_offset = *offset;
+        *offset += 1;
+        if this_offset == 0 {
+            cpp_method_name
+        } else {
+            format!("{}{}", cpp_method_name, this_offset)
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::OverloadTracker;
+
+    #[test]
+    fn test_by_function() {
+        let mut ot = OverloadTracker::default();
+        assert_eq!(ot.get_function_real_name("bob".into()), "bob");
+        assert_eq!(ot.get_function_real_name("bob".into()), "bob1");
+        assert_eq!(ot.get_function_real_name("bob".into()), "bob2");
+    }
+
+    #[test]
+    fn test_by_method() {
+        let mut ot = OverloadTracker::default();
+        assert_eq!(ot.get_method_real_name("Ty1", "bob".into()), "bob");
+        assert_eq!(ot.get_method_real_name("Ty1", "bob".into()), "bob1");
+        assert_eq!(ot.get_method_real_name("Ty2", "bob".into()), "bob");
+        assert_eq!(ot.get_method_real_name("Ty2", "bob".into()), "bob1");
+    }
+}
diff --git a/engine/src/conversion/analysis/fun/subclass.rs b/engine/src/conversion/analysis/fun/subclass.rs
new file mode 100644
index 0000000..c017249
--- /dev/null
+++ b/engine/src/conversion/analysis/fun/subclass.rs
@@ -0,0 +1,249 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use indexmap::map::IndexMap as HashMap;
+
+use syn::{parse_quote, FnArg, PatType, Type, TypePtr};
+
+use crate::conversion::analysis::fun::{FnKind, MethodKind, ReceiverMutability};
+use crate::conversion::analysis::pod::PodPhase;
+use crate::conversion::api::{
+    CppVisibility, FuncToConvert, Provenance, RustSubclassFnDetails, SubclassConstructorDetails,
+    SubclassName, SuperclassMethod, UnsafetyNeeded, Virtualness,
+};
+use crate::conversion::apivec::ApiVec;
+use crate::{
+    conversion::{
+        analysis::fun::function_wrapper::{
+            CppFunction, CppFunctionBody, CppFunctionKind, TypeConversionPolicy,
+        },
+        api::{Api, ApiName},
+    },
+    types::{make_ident, Namespace, QualifiedName},
+};
+
+use super::{FnAnalysis, FnPrePhase1};
+
+pub(super) fn subclasses_by_superclass(
+    apis: &ApiVec<PodPhase>,
+) -> HashMap<QualifiedName, Vec<SubclassName>> {
+    let mut subclasses_per_superclass: HashMap<QualifiedName, Vec<SubclassName>> = HashMap::new();
+
+    for api in apis.iter() {
+        if let Api::Subclass { name, superclass } = api {
+            subclasses_per_superclass
+                .entry(superclass.clone())
+                .or_default()
+                .push(name.clone());
+        }
+    }
+    subclasses_per_superclass
+}
+
+pub(super) fn create_subclass_fn_wrapper(
+    sub: &SubclassName,
+    super_fn_name: &QualifiedName,
+    fun: &FuncToConvert,
+) -> Box<FuncToConvert> {
+    let self_ty = Some(sub.cpp());
+    Box::new(FuncToConvert {
+        synthesized_this_type: self_ty.clone(),
+        self_ty,
+        ident: super_fn_name.get_final_ident(),
+        doc_attrs: fun.doc_attrs.clone(),
+        inputs: fun.inputs.clone(),
+        output: fun.output.clone(),
+        vis: fun.vis.clone(),
+        virtualness: Virtualness::None,
+        cpp_vis: CppVisibility::Public,
+        special_member: None,
+        unused_template_param: fun.unused_template_param,
+        original_name: None,
+        references: fun.references.clone(),
+        add_to_trait: fun.add_to_trait.clone(),
+        is_deleted: fun.is_deleted,
+        synthetic_cpp: None,
+        provenance: Provenance::SynthesizedOther,
+        variadic: fun.variadic,
+    })
+}
+
+pub(super) fn create_subclass_trait_item(
+    name: ApiName,
+    analysis: &FnAnalysis,
+    receiver_mutability: &ReceiverMutability,
+    receiver: QualifiedName,
+    is_pure_virtual: bool,
+) -> Api<FnPrePhase1> {
+    let param_names = analysis
+        .param_details
+        .iter()
+        .map(|pd| pd.name.clone())
+        .collect();
+    Api::SubclassTraitItem {
+        name,
+        details: SuperclassMethod {
+            name: make_ident(&analysis.rust_name),
+            params: analysis.params.clone(),
+            ret_type: analysis.ret_type.clone(),
+            param_names,
+            receiver_mutability: receiver_mutability.clone(),
+            requires_unsafe: UnsafetyNeeded::from_param_details(&analysis.param_details, false),
+            is_pure_virtual,
+            receiver,
+        },
+    }
+}
+
+pub(super) fn create_subclass_function(
+    sub: &SubclassName,
+    analysis: &super::FnAnalysis,
+    name: &ApiName,
+    receiver_mutability: &ReceiverMutability,
+    superclass: &QualifiedName,
+    dependencies: Vec<QualifiedName>,
+) -> Api<FnPrePhase1> {
+    let cpp = sub.cpp();
+    let holder_name = sub.holder();
+    let rust_call_name = make_ident(format!(
+        "{}_{}",
+        sub.0.name.get_final_item(),
+        name.name.get_final_item()
+    ));
+    let params = std::iter::once(parse_quote! {
+        me: & #holder_name
+    })
+    .chain(analysis.params.iter().skip(1).cloned())
+    .collect();
+    let kind = if matches!(receiver_mutability, ReceiverMutability::Mutable) {
+        CppFunctionKind::Method
+    } else {
+        CppFunctionKind::ConstMethod
+    };
+    let argument_conversion = analysis
+        .param_details
+        .iter()
+        .skip(1)
+        .map(|p| p.conversion.clone())
+        .collect();
+    Api::RustSubclassFn {
+        name: ApiName::new_in_root_namespace(rust_call_name.clone()),
+        subclass: sub.clone(),
+        details: Box::new(RustSubclassFnDetails {
+            params,
+            ret: analysis.ret_type.clone(),
+            method_name: make_ident(&analysis.rust_name),
+            cpp_impl: CppFunction {
+                payload: CppFunctionBody::FunctionCall(Namespace::new(), rust_call_name),
+                wrapper_function_name: make_ident(&analysis.rust_name),
+                original_cpp_name: name.cpp_name(),
+                return_conversion: analysis.ret_conversion.clone(),
+                argument_conversion,
+                kind,
+                pass_obs_field: true,
+                qualification: Some(cpp),
+            },
+            superclass: superclass.clone(),
+            receiver_mutability: receiver_mutability.clone(),
+            dependencies,
+            requires_unsafe: UnsafetyNeeded::from_param_details(&analysis.param_details, false),
+            is_pure_virtual: matches!(
+                analysis.kind,
+                FnKind::Method {
+                    method_kind: MethodKind::PureVirtual(..),
+                    ..
+                }
+            ),
+        }),
+    }
+}
+
+pub(super) fn create_subclass_constructor(
+    sub: SubclassName,
+    analysis: &FnAnalysis,
+    sup: &QualifiedName,
+    fun: &FuncToConvert,
+) -> (Box<FuncToConvert>, ApiName) {
+    let holder = sub.holder();
+    let cpp = sub.cpp();
+    let wrapper_function_name = cpp.get_final_ident();
+    let initial_arg = TypeConversionPolicy::new_unconverted(parse_quote! {
+        rust::Box< #holder >
+    });
+    let args = std::iter::once(initial_arg).chain(
+        analysis
+            .param_details
+            .iter()
+            .skip(1) // skip placement new destination
+            .map(|aa| aa.conversion.clone()),
+    );
+    let cpp_impl = CppFunction {
+        payload: CppFunctionBody::ConstructSuperclass(sup.to_cpp_name()),
+        wrapper_function_name,
+        return_conversion: None,
+        argument_conversion: args.collect(),
+        kind: CppFunctionKind::SynthesizedConstructor,
+        pass_obs_field: false,
+        qualification: Some(cpp.clone()),
+        original_cpp_name: cpp.to_cpp_name(),
+    };
+    let subclass_constructor_details = Box::new(SubclassConstructorDetails {
+        subclass: sub.clone(),
+        is_trivial: analysis.param_details.len() == 1, // just placement new
+        // destination, no other parameters
+        cpp_impl,
+    });
+    let subclass_constructor_name =
+        make_ident(format!("{}_{}", cpp.get_final_item(), cpp.get_final_item()));
+    let mut existing_params = fun.inputs.clone();
+    if let Some(FnArg::Typed(PatType { ty, .. })) = existing_params.first_mut() {
+        if let Type::Ptr(TypePtr { elem, .. }) = &mut **ty {
+            *elem = Box::new(Type::Path(sub.cpp().to_type_path()));
+        } else {
+            panic!("Unexpected self type parameter when creating subclass constructor");
+        }
+    } else {
+        panic!("Unexpected self type parameter when creating subclass constructor");
+    }
+    let mut existing_params = existing_params.into_iter();
+    let self_param = existing_params.next();
+    let boxed_holder_param: FnArg = parse_quote! {
+        peer: rust::Box<#holder>
+    };
+    let inputs = self_param
+        .into_iter()
+        .chain(std::iter::once(boxed_holder_param))
+        .chain(existing_params)
+        .collect();
+    let maybe_wrap = Box::new(FuncToConvert {
+        ident: subclass_constructor_name.clone(),
+        doc_attrs: fun.doc_attrs.clone(),
+        inputs,
+        output: fun.output.clone(),
+        vis: fun.vis.clone(),
+        virtualness: Virtualness::None,
+        cpp_vis: CppVisibility::Public,
+        special_member: fun.special_member.clone(),
+        original_name: None,
+        unused_template_param: fun.unused_template_param,
+        references: fun.references.clone(),
+        synthesized_this_type: Some(cpp.clone()),
+        self_ty: Some(cpp),
+        add_to_trait: None,
+        is_deleted: fun.is_deleted,
+        synthetic_cpp: None,
+        provenance: Provenance::SynthesizedSubclassConstructor(subclass_constructor_details),
+        variadic: fun.variadic,
+    });
+    let subclass_constructor_name = ApiName::new_with_cpp_name(
+        &Namespace::new(),
+        subclass_constructor_name,
+        Some(sub.cpp().get_final_item().to_string()),
+    );
+    (maybe_wrap, subclass_constructor_name)
+}
diff --git a/engine/src/conversion/analysis/gc.rs b/engine/src/conversion/analysis/gc.rs
new file mode 100644
index 0000000..0734f15
--- /dev/null
+++ b/engine/src/conversion/analysis/gc.rs
@@ -0,0 +1,70 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use indexmap::map::IndexMap as HashMap;
+use indexmap::set::IndexSet as HashSet;
+
+use autocxx_parser::IncludeCppConfig;
+
+use crate::{
+    conversion::{api::Api, apivec::ApiVec},
+    types::QualifiedName,
+};
+
+use super::{deps::HasDependencies, fun::FnPhase};
+
+/// This is essentially mark-and-sweep garbage collection of the
+/// [Api]s that we've discovered. Why do we do this, you might wonder?
+/// It seems a bit strange given that we pass an explicit allowlist
+/// to bindgen.
+/// There are two circumstances under which we want to discard
+/// some of the APIs we encounter parsing the bindgen.
+/// 1) We simplify some struct to be non-POD. In this case, we'll
+///    discard all the fields within it. Those fields can be, and
+///    in fact often _are_, stuff which we have trouble converting
+///    e.g. std::string or std::string::value_type or
+///    my_derived_thing<std::basic_string::value_type> or some
+///    other permutation. In such cases, we want to discard those
+///    field types with prejudice.
+/// 2) block! may be used to ban certain APIs. This often eliminates
+///    some methods from a given struct/class. In which case, we
+///    don't care about the other parameter types passed into those
+///    APIs either.
+pub(crate) fn filter_apis_by_following_edges_from_allowlist(
+    apis: ApiVec<FnPhase>,
+    config: &IncludeCppConfig,
+) -> ApiVec<FnPhase> {
+    let mut todos: Vec<QualifiedName> = apis
+        .iter()
+        .filter(|api| {
+            let tnforal = api.name_for_allowlist();
+            config.is_on_allowlist(&tnforal.to_cpp_name())
+        })
+        .map(Api::name)
+        .cloned()
+        .collect();
+    let mut by_typename: HashMap<QualifiedName, ApiVec<FnPhase>> = HashMap::new();
+    for api in apis.into_iter() {
+        let tn = api.name().clone();
+        by_typename.entry(tn).or_default().push(api);
+    }
+    let mut done = HashSet::new();
+    let mut output = ApiVec::new();
+    while !todos.is_empty() {
+        let todo = todos.remove(0);
+        if done.contains(&todo) {
+            continue;
+        }
+        if let Some(mut these_apis) = by_typename.remove(&todo) {
+            todos.extend(these_apis.iter().flat_map(|api| api.deps().cloned()));
+            output.append(&mut these_apis);
+        } // otherwise, probably an intrinsic e.g. uint32_t.
+        done.insert(todo);
+    }
+    output
+}
diff --git a/engine/src/conversion/analysis/mod.rs b/engine/src/conversion/analysis/mod.rs
new file mode 100644
index 0000000..d733011
--- /dev/null
+++ b/engine/src/conversion/analysis/mod.rs
@@ -0,0 +1,28 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+pub(crate) mod abstract_types;
+pub(crate) mod allocators;
+pub(crate) mod casts;
+pub(crate) mod constructor_deps;
+pub(crate) mod ctypes;
+pub(crate) mod deps;
+mod depth_first;
+mod doc_label;
+pub(crate) mod fun;
+pub(crate) mod gc;
+mod name_check;
+pub(crate) mod pod; // hey, that rhymes
+pub(crate) mod remove_ignored;
+mod replace_hopeless_typedef_targets;
+pub(crate) mod tdef;
+mod type_converter;
+
+pub(crate) use name_check::check_names;
+pub(crate) use replace_hopeless_typedef_targets::replace_hopeless_typedef_targets;
+pub(crate) use type_converter::PointerTreatment;
diff --git a/engine/src/conversion/analysis/name_check.rs b/engine/src/conversion/analysis/name_check.rs
new file mode 100644
index 0000000..7547c7c
--- /dev/null
+++ b/engine/src/conversion/analysis/name_check.rs
@@ -0,0 +1,115 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use indexmap::map::IndexMap as HashMap;
+
+use syn::Ident;
+
+use crate::{
+    conversion::{
+        api::{Api, SubclassName},
+        apivec::ApiVec,
+        error_reporter::convert_item_apis,
+        ConvertError,
+    },
+    types::{validate_ident_ok_for_cxx, QualifiedName},
+};
+
+use super::fun::FnPhase;
+
+/// Do some final checks that the names we've come up with can be represented
+/// within cxx.
+pub(crate) fn check_names(apis: ApiVec<FnPhase>) -> ApiVec<FnPhase> {
+    // If any items have names which can't be represented by cxx,
+    // abort. This check should ideally be done at the times we fill in the
+    // `name` field of each `api` in the first place, at parse time, though
+    // as the `name` field of each API may change during various analysis phases,
+    // currently it seems better to do it here to ensure we respect
+    // the output of any such changes.
+    let mut intermediate = ApiVec::new();
+    convert_item_apis(apis, &mut intermediate, |api| match api {
+        Api::Typedef { ref name, .. }
+        | Api::ForwardDeclaration { ref name, .. }
+        | Api::OpaqueTypedef { ref name, .. }
+        | Api::Const { ref name, .. }
+        | Api::Enum { ref name, .. }
+        | Api::Struct { ref name, .. } => {
+            validate_all_segments_ok_for_cxx(name.name.segment_iter())?;
+            if let Some(cpp_name) = name.cpp_name_if_present() {
+                // The C++ name might itself be outer_type::inner_type and thus may
+                // have multiple segments.
+                validate_all_segments_ok_for_cxx(
+                    QualifiedName::new_from_cpp_name(cpp_name).segment_iter(),
+                )?;
+            }
+            Ok(Box::new(std::iter::once(api)))
+        }
+        Api::Subclass {
+            name: SubclassName(ref name),
+            ref superclass,
+        } => {
+            validate_all_segments_ok_for_cxx(name.name.segment_iter())?;
+            validate_all_segments_ok_for_cxx(superclass.segment_iter())?;
+            Ok(Box::new(std::iter::once(api)))
+        }
+        Api::Function { ref name, .. } => {
+            // we don't handle function names here because
+            // the function analysis does an equivalent check. Instead of just rejecting
+            // the function, it creates a wrapper function instead with a more
+            // palatable name. That's preferable to rejecting the API entirely.
+            validate_all_segments_ok_for_cxx(name.name.segment_iter())?;
+            Ok(Box::new(std::iter::once(api)))
+        }
+        Api::ConcreteType { .. }
+        | Api::CType { .. }
+        | Api::StringConstructor { .. }
+        | Api::RustType { .. }
+        | Api::RustSubclassFn { .. }
+        | Api::RustFn { .. }
+        | Api::SubclassTraitItem { .. }
+        | Api::ExternCppType { .. }
+        | Api::IgnoredItem { .. } => Ok(Box::new(std::iter::once(api))),
+    });
+
+    // Reject any names which are duplicates within the cxx bridge mod,
+    // that has a flat namespace.
+    let mut names_found: HashMap<Ident, Vec<String>> = HashMap::new();
+    for api in intermediate.iter() {
+        let my_name = api.cxxbridge_name();
+        if let Some(name) = my_name {
+            let e = names_found.entry(name).or_default();
+            e.push(api.name_info().name.to_string());
+        }
+    }
+    let mut results = ApiVec::new();
+    convert_item_apis(intermediate, &mut results, |api| {
+        let my_name = api.cxxbridge_name();
+        if let Some(name) = my_name {
+            let symbols_for_this_name = names_found.entry(name).or_default();
+            if symbols_for_this_name.len() > 1usize {
+                Err(ConvertError::DuplicateCxxBridgeName(
+                    symbols_for_this_name.clone(),
+                ))
+            } else {
+                Ok(Box::new(std::iter::once(api)))
+            }
+        } else {
+            Ok(Box::new(std::iter::once(api)))
+        }
+    });
+    results
+}
+
+fn validate_all_segments_ok_for_cxx(
+    items: impl Iterator<Item = String>,
+) -> Result<(), ConvertError> {
+    for seg in items {
+        validate_ident_ok_for_cxx(&seg)?;
+    }
+    Ok(())
+}
diff --git a/engine/src/conversion/analysis/pod/byvalue_checker.rs b/engine/src/conversion/analysis/pod/byvalue_checker.rs
new file mode 100644
index 0000000..de72eec
--- /dev/null
+++ b/engine/src/conversion/analysis/pod/byvalue_checker.rs
@@ -0,0 +1,343 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::conversion::apivec::ApiVec;
+use crate::{conversion::ConvertError, known_types::known_types};
+use crate::{
+    conversion::{
+        analysis::tdef::TypedefPhase,
+        api::{Api, TypedefKind},
+    },
+    types::{Namespace, QualifiedName},
+};
+use autocxx_parser::IncludeCppConfig;
+use std::collections::HashMap;
+use syn::{ItemStruct, Type};
+
+#[derive(Clone)]
+enum PodState {
+    UnsafeToBePod(String),
+    SafeToBePod,
+    IsPod,
+    IsAlias(QualifiedName),
+}
+
+#[derive(Clone)]
+struct StructDetails {
+    state: PodState,
+    dependent_structs: Vec<QualifiedName>,
+}
+
+impl StructDetails {
+    fn new(state: PodState) -> Self {
+        StructDetails {
+            state,
+            dependent_structs: Vec::new(),
+        }
+    }
+}
+
+/// Type which is able to check whether it's safe to make a type
+/// fully representable by cxx. For instance if it is a struct containing
+/// a struct containing a std::string, the answer is no, because that
+/// std::string contains a self-referential pointer.
+/// It is possible that this is duplicative of the information stored
+/// elsewhere in the `Api` list and could possibly be removed or simplified.
+pub struct ByValueChecker {
+    // Mapping from type name to whether it is safe to be POD
+    results: HashMap<QualifiedName, StructDetails>,
+}
+
+impl ByValueChecker {
+    pub fn new() -> Self {
+        let mut results = HashMap::new();
+        for (tn, by_value_safe) in known_types().get_pod_safe_types() {
+            let safety = if by_value_safe {
+                PodState::IsPod
+            } else {
+                PodState::UnsafeToBePod(format!("type {} is not safe for POD", tn))
+            };
+            results.insert(tn.clone(), StructDetails::new(safety));
+        }
+        ByValueChecker { results }
+    }
+
+    /// Scan APIs to work out which are by-value safe. Constructs a [ByValueChecker]
+    /// that others can use to query the results.
+    pub(crate) fn new_from_apis(
+        apis: &ApiVec<TypedefPhase>,
+        config: &IncludeCppConfig,
+    ) -> Result<ByValueChecker, ConvertError> {
+        let mut byvalue_checker = ByValueChecker::new();
+        for blocklisted in config.get_blocklist() {
+            let tn = QualifiedName::new_from_cpp_name(blocklisted);
+            let safety = PodState::UnsafeToBePod(format!("type {} is on the blocklist", &tn));
+            byvalue_checker
+                .results
+                .insert(tn, StructDetails::new(safety));
+        }
+        for api in apis.iter() {
+            match api {
+                Api::Typedef { analysis, .. } => {
+                    let name = api.name();
+                    let typedef_type = match analysis.kind {
+                        TypedefKind::Type(ref type_item) => match type_item.ty.as_ref() {
+                            Type::Path(typ) => {
+                                let target_tn = QualifiedName::from_type_path(typ);
+                                known_types().consider_substitution(&target_tn)
+                            }
+                            _ => None,
+                        },
+                        TypedefKind::Use(_, ref ty) => match **ty {
+                            Type::Path(ref typ) => {
+                                let target_tn = QualifiedName::from_type_path(typ);
+                                known_types().consider_substitution(&target_tn)
+                            }
+                            _ => None,
+                        },
+                    };
+                    match &typedef_type {
+                        Some(typ) => {
+                            byvalue_checker.results.insert(
+                                name.clone(),
+                                StructDetails::new(PodState::IsAlias(
+                                    QualifiedName::from_type_path(typ),
+                                )),
+                            );
+                        }
+                        None => byvalue_checker.ingest_nonpod_type(name.clone()),
+                    }
+                }
+                Api::Struct { details, .. } => {
+                    byvalue_checker.ingest_struct(&details.item, api.name().get_namespace())
+                }
+                Api::Enum { .. } => {
+                    byvalue_checker
+                        .results
+                        .insert(api.name().clone(), StructDetails::new(PodState::IsPod));
+                }
+                Api::ExternCppType { pod: true, .. } => {
+                    byvalue_checker
+                        .results
+                        .insert(api.name().clone(), StructDetails::new(PodState::IsPod));
+                }
+                _ => {}
+            }
+        }
+        let pod_requests = config
+            .get_pod_requests()
+            .iter()
+            .map(|ty| QualifiedName::new_from_cpp_name(ty))
+            .collect();
+        byvalue_checker
+            .satisfy_requests(pod_requests)
+            .map_err(ConvertError::UnsafePodType)?;
+        Ok(byvalue_checker)
+    }
+
+    fn ingest_struct(&mut self, def: &ItemStruct, ns: &Namespace) {
+        // For this struct, work out whether it _could_ be safe as a POD.
+        let tyname = QualifiedName::new(ns, def.ident.clone());
+        let mut field_safety_problem = PodState::SafeToBePod;
+        let fieldlist = Self::get_field_types(def);
+        for ty_id in &fieldlist {
+            match self.results.get(ty_id) {
+                None => {
+                    field_safety_problem = PodState::UnsafeToBePod(format!(
+                        "Type {} could not be POD because its dependent type {} isn't known",
+                        tyname, ty_id
+                    ));
+                    break;
+                }
+                Some(deets) => {
+                    if let PodState::UnsafeToBePod(reason) = &deets.state {
+                        let new_reason = format!("Type {} could not be POD because its dependent type {} isn't safe to be POD. Because: {}", tyname, ty_id, reason);
+                        field_safety_problem = PodState::UnsafeToBePod(new_reason);
+                        break;
+                    }
+                }
+            }
+        }
+        if Self::has_vtable(def) {
+            let reason = format!(
+                "Type {} could not be POD because it has virtual functions.",
+                tyname
+            );
+            field_safety_problem = PodState::UnsafeToBePod(reason);
+        }
+        let mut my_details = StructDetails::new(field_safety_problem);
+        my_details.dependent_structs = fieldlist;
+        self.results.insert(tyname, my_details);
+    }
+
+    fn ingest_nonpod_type(&mut self, tyname: QualifiedName) {
+        let new_reason = format!("Type {} is a typedef to a complex type", tyname);
+        self.results.insert(
+            tyname,
+            StructDetails::new(PodState::UnsafeToBePod(new_reason)),
+        );
+    }
+
+    fn satisfy_requests(&mut self, mut requests: Vec<QualifiedName>) -> Result<(), String> {
+        while !requests.is_empty() {
+            let ty_id = requests.remove(requests.len() - 1);
+            let deets = self.results.get_mut(&ty_id);
+            let mut alias_to_consider = None;
+            match deets {
+                None => {
+                    return Err(format!(
+                        "Unable to make {} POD because we never saw a struct definition",
+                        ty_id
+                    ))
+                }
+                Some(deets) => match &deets.state {
+                    PodState::UnsafeToBePod(error_msg) => return Err(error_msg.clone()),
+                    PodState::IsPod => {}
+                    PodState::SafeToBePod => {
+                        deets.state = PodState::IsPod;
+                        requests.extend_from_slice(&deets.dependent_structs);
+                    }
+                    PodState::IsAlias(target_type) => {
+                        alias_to_consider = Some(target_type.clone());
+                    }
+                },
+            }
+            // Do the following outside the match to avoid borrow checker violation.
+            if let Some(alias) = alias_to_consider {
+                match self.results.get(&alias) {
+                    None => requests.extend_from_slice(&[alias, ty_id]), // try again after resolving alias target
+                    Some(alias_target_deets) => {
+                        self.results.get_mut(&ty_id).unwrap().state =
+                            alias_target_deets.state.clone();
+                    }
+                }
+            }
+        }
+        Ok(())
+    }
+
+    /// Return whether a given type is POD (i.e. can be represented by value in Rust) or not.
+    /// Unless we've got a definite record that it _is_, we return false.
+    /// Some types won't be in our `results` map. For example: (a) AutocxxConcrete types
+    /// which we've synthesized; (b) types we couldn't parse but returned ignorable
+    /// errors so that we could continue. Assume non-POD for all such cases.
+    pub fn is_pod(&self, ty_id: &QualifiedName) -> bool {
+        matches!(
+            self.results.get(ty_id),
+            Some(StructDetails {
+                state: PodState::IsPod,
+                dependent_structs: _,
+            })
+        )
+    }
+
+    fn get_field_types(def: &ItemStruct) -> Vec<QualifiedName> {
+        let mut results = Vec::new();
+        for f in &def.fields {
+            let fty = &f.ty;
+            if let Type::Path(p) = fty {
+                results.push(QualifiedName::from_type_path(p));
+            }
+            // TODO handle anything else which bindgen might spit out, e.g. arrays?
+        }
+        results
+    }
+
+    fn has_vtable(def: &ItemStruct) -> bool {
+        for f in &def.fields {
+            if f.ident.as_ref().map(|id| id == "vtable_").unwrap_or(false) {
+                return true;
+            }
+        }
+        false
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::ByValueChecker;
+    use crate::types::{Namespace, QualifiedName};
+    use syn::{parse_quote, Ident, ItemStruct};
+
+    fn ty_from_ident(id: &Ident) -> QualifiedName {
+        QualifiedName::new_from_cpp_name(&id.to_string())
+    }
+
+    #[test]
+    fn test_primitive_by_itself() {
+        let bvc = ByValueChecker::new();
+        let t_id = QualifiedName::new_from_cpp_name("u32");
+        assert!(bvc.is_pod(&t_id));
+    }
+
+    #[test]
+    fn test_primitives() {
+        let mut bvc = ByValueChecker::new();
+        let t: ItemStruct = parse_quote! {
+            struct Foo {
+                a: i32,
+                b: i64,
+            }
+        };
+        let t_id = ty_from_ident(&t.ident);
+        bvc.ingest_struct(&t, &Namespace::new());
+        bvc.satisfy_requests(vec![t_id.clone()]).unwrap();
+        assert!(bvc.is_pod(&t_id));
+    }
+
+    #[test]
+    fn test_nested_primitives() {
+        let mut bvc = ByValueChecker::new();
+        let t: ItemStruct = parse_quote! {
+            struct Foo {
+                a: i32,
+                b: i64,
+            }
+        };
+        bvc.ingest_struct(&t, &Namespace::new());
+        let t: ItemStruct = parse_quote! {
+            struct Bar {
+                a: Foo,
+                b: i64,
+            }
+        };
+        let t_id = ty_from_ident(&t.ident);
+        bvc.ingest_struct(&t, &Namespace::new());
+        bvc.satisfy_requests(vec![t_id.clone()]).unwrap();
+        assert!(bvc.is_pod(&t_id));
+    }
+
+    #[test]
+    fn test_with_up() {
+        let mut bvc = ByValueChecker::new();
+        let t: ItemStruct = parse_quote! {
+            struct Bar {
+                a: cxx::UniquePtr<CxxString>,
+                b: i64,
+            }
+        };
+        let t_id = ty_from_ident(&t.ident);
+        bvc.ingest_struct(&t, &Namespace::new());
+        bvc.satisfy_requests(vec![t_id.clone()]).unwrap();
+        assert!(bvc.is_pod(&t_id));
+    }
+
+    #[test]
+    fn test_with_cxxstring() {
+        let mut bvc = ByValueChecker::new();
+        let t: ItemStruct = parse_quote! {
+            struct Bar {
+                a: CxxString,
+                b: i64,
+            }
+        };
+        let t_id = ty_from_ident(&t.ident);
+        bvc.ingest_struct(&t, &Namespace::new());
+        assert!(bvc.satisfy_requests(vec![t_id]).is_err());
+    }
+}
diff --git a/engine/src/conversion/analysis/pod/mod.rs b/engine/src/conversion/analysis/pod/mod.rs
new file mode 100644
index 0000000..6722c23
--- /dev/null
+++ b/engine/src/conversion/analysis/pod/mod.rs
@@ -0,0 +1,257 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+mod byvalue_checker;
+
+use indexmap::map::IndexMap as HashMap;
+use indexmap::set::IndexSet as HashSet;
+
+use autocxx_parser::IncludeCppConfig;
+use byvalue_checker::ByValueChecker;
+use syn::{ItemEnum, ItemStruct, Type, Visibility};
+
+use crate::{
+    conversion::{
+        analysis::type_converter::{self, add_analysis, TypeConversionContext, TypeConverter},
+        api::{AnalysisPhase, Api, ApiName, CppVisibility, NullPhase, StructDetails, TypeKind},
+        apivec::ApiVec,
+        convert_error::{ConvertErrorWithContext, ErrorContext},
+        error_reporter::convert_apis,
+        parse::BindgenSemanticAttributes,
+        ConvertError,
+    },
+    types::{Namespace, QualifiedName},
+};
+
+use super::tdef::{TypedefAnalysis, TypedefPhase};
+
+pub(crate) struct FieldInfo {
+    pub(crate) ty: Type,
+    pub(crate) type_kind: type_converter::TypeKind,
+}
+
+pub(crate) struct PodAnalysis {
+    pub(crate) kind: TypeKind,
+    pub(crate) bases: HashSet<QualifiedName>,
+    /// Base classes for which we should create casts.
+    /// That's just those which are on the allowlist,
+    /// because otherwise we don't know whether they're
+    /// abstract or not.
+    pub(crate) castable_bases: HashSet<QualifiedName>,
+    pub(crate) field_deps: HashSet<QualifiedName>,
+    pub(crate) field_info: Vec<FieldInfo>,
+    pub(crate) is_generic: bool,
+    pub(crate) in_anonymous_namespace: bool,
+}
+
+pub(crate) struct PodPhase;
+
+impl AnalysisPhase for PodPhase {
+    type TypedefAnalysis = TypedefAnalysis;
+    type StructAnalysis = PodAnalysis;
+    type FunAnalysis = ();
+}
+
+/// In our set of APIs, work out which ones are safe to represent
+/// by value in Rust (e.g. they don't have a destructor) and record
+/// as such. Return a set of APIs annotated with extra metadata,
+/// and an object which can be used to query the POD status of any
+/// type whether or not it's one of the [Api]s.
+pub(crate) fn analyze_pod_apis(
+    apis: ApiVec<TypedefPhase>,
+    config: &IncludeCppConfig,
+) -> Result<ApiVec<PodPhase>, ConvertError> {
+    // This next line will return an error if any of the 'generate_pod'
+    // directives from the user can't be met because, for instance,
+    // a type contains a std::string or some other type which can't be
+    // held safely by value in Rust.
+    let byvalue_checker = ByValueChecker::new_from_apis(&apis, config)?;
+    let mut extra_apis = ApiVec::new();
+    let mut type_converter = TypeConverter::new(config, &apis);
+    let mut results = ApiVec::new();
+    convert_apis(
+        apis,
+        &mut results,
+        Api::fun_unchanged,
+        |name, details, _| {
+            analyze_struct(
+                &byvalue_checker,
+                &mut type_converter,
+                &mut extra_apis,
+                name,
+                details,
+                config,
+            )
+        },
+        analyze_enum,
+        Api::typedef_unchanged,
+    );
+    // Conceivably, the process of POD-analysing the first set of APIs could result
+    // in us creating new APIs to concretize generic types.
+    let extra_apis: ApiVec<PodPhase> = extra_apis.into_iter().map(add_analysis).collect();
+    let mut more_extra_apis = ApiVec::new();
+    convert_apis(
+        extra_apis,
+        &mut results,
+        Api::fun_unchanged,
+        |name, details, _| {
+            analyze_struct(
+                &byvalue_checker,
+                &mut type_converter,
+                &mut more_extra_apis,
+                name,
+                details,
+                config,
+            )
+        },
+        analyze_enum,
+        Api::typedef_unchanged,
+    );
+    assert!(more_extra_apis.is_empty());
+    Ok(results)
+}
+
+fn analyze_enum(
+    name: ApiName,
+    mut item: ItemEnum,
+) -> Result<Box<dyn Iterator<Item = Api<PodPhase>>>, ConvertErrorWithContext> {
+    let metadata = BindgenSemanticAttributes::new_retaining_others(&mut item.attrs);
+    metadata.check_for_fatal_attrs(&name.name.get_final_ident())?;
+    Ok(Box::new(std::iter::once(Api::Enum { name, item })))
+}
+
+fn analyze_struct(
+    byvalue_checker: &ByValueChecker,
+    type_converter: &mut TypeConverter,
+    extra_apis: &mut ApiVec<NullPhase>,
+    name: ApiName,
+    mut details: Box<StructDetails>,
+    config: &IncludeCppConfig,
+) -> Result<Box<dyn Iterator<Item = Api<PodPhase>>>, ConvertErrorWithContext> {
+    let id = name.name.get_final_ident();
+    if details.vis != CppVisibility::Public {
+        return Err(ConvertErrorWithContext(
+            ConvertError::NonPublicNestedType,
+            Some(ErrorContext::new_for_item(id)),
+        ));
+    }
+    let metadata = BindgenSemanticAttributes::new_retaining_others(&mut details.item.attrs);
+    metadata.check_for_fatal_attrs(&id)?;
+    let bases = get_bases(&details.item);
+    let mut field_deps = HashSet::new();
+    let mut field_info = Vec::new();
+    let field_conversion_errors = get_struct_field_types(
+        type_converter,
+        name.name.get_namespace(),
+        &details.item,
+        &mut field_deps,
+        &mut field_info,
+        extra_apis,
+    );
+    let type_kind = if byvalue_checker.is_pod(&name.name) {
+        // It's POD so any errors encountered parsing its fields are important.
+        // Let's not allow anything to be POD if it's got rvalue reference fields.
+        if details.has_rvalue_reference_fields {
+            return Err(ConvertErrorWithContext(
+                ConvertError::RValueReferenceField,
+                Some(ErrorContext::new_for_item(id)),
+            ));
+        }
+        if let Some(err) = field_conversion_errors.into_iter().next() {
+            return Err(ConvertErrorWithContext(
+                err,
+                Some(ErrorContext::new_for_item(id)),
+            ));
+        }
+        TypeKind::Pod
+    } else {
+        TypeKind::NonPod
+    };
+    let castable_bases = bases
+        .iter()
+        .filter(|(_, is_public)| **is_public)
+        .map(|(base, _)| base)
+        .filter(|base| config.is_on_allowlist(&base.to_cpp_name()))
+        .cloned()
+        .collect();
+    let is_generic = !details.item.generics.params.is_empty();
+    let in_anonymous_namespace = name
+        .name
+        .ns_segment_iter()
+        .any(|ns| ns.starts_with("_bindgen_mod"));
+    Ok(Box::new(std::iter::once(Api::Struct {
+        name,
+        details,
+        analysis: PodAnalysis {
+            kind: type_kind,
+            bases: bases.into_keys().collect(),
+            castable_bases,
+            field_deps,
+            field_info,
+            is_generic,
+            in_anonymous_namespace,
+        },
+    })))
+}
+
+fn get_struct_field_types(
+    type_converter: &mut TypeConverter,
+    ns: &Namespace,
+    s: &ItemStruct,
+    field_deps: &mut HashSet<QualifiedName>,
+    field_info: &mut Vec<FieldInfo>,
+    extra_apis: &mut ApiVec<NullPhase>,
+) -> Vec<ConvertError> {
+    let mut convert_errors = Vec::new();
+    for f in &s.fields {
+        let annotated =
+            type_converter.convert_type(f.ty.clone(), ns, &TypeConversionContext::WithinReference);
+        match annotated {
+            Ok(mut r) => {
+                extra_apis.append(&mut r.extra_apis);
+                // Skip base classes represented as fields. Anything which wants to include bases can chain
+                // those to the list we're building.
+                if !f
+                    .ident
+                    .as_ref()
+                    .map(|id| {
+                        id.to_string().starts_with("_base")
+                            || id.to_string().starts_with("__bindgen_padding")
+                    })
+                    .unwrap_or(false)
+                {
+                    field_deps.extend(r.types_encountered);
+                    field_info.push(FieldInfo {
+                        ty: r.ty,
+                        type_kind: r.kind,
+                    });
+                }
+            }
+            Err(e) => convert_errors.push(e),
+        };
+    }
+    convert_errors
+}
+
+/// Map to whether the bases are public.
+fn get_bases(item: &ItemStruct) -> HashMap<QualifiedName, bool> {
+    item.fields
+        .iter()
+        .filter_map(|f| {
+            let is_public = matches!(f.vis, Visibility::Public(_));
+            match &f.ty {
+                Type::Path(typ) => f
+                    .ident
+                    .as_ref()
+                    .filter(|id| id.to_string().starts_with("_base"))
+                    .map(|_| (QualifiedName::from_type_path(typ), is_public)),
+                _ => None,
+            }
+        })
+        .collect()
+}
diff --git a/engine/src/conversion/analysis/remove_ignored.rs b/engine/src/conversion/analysis/remove_ignored.rs
new file mode 100644
index 0000000..bd11b13
--- /dev/null
+++ b/engine/src/conversion/analysis/remove_ignored.rs
@@ -0,0 +1,95 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use indexmap::set::IndexSet as HashSet;
+
+use super::deps::HasDependencies;
+use super::fun::{FnAnalysis, FnKind, FnPhase};
+use crate::conversion::apivec::ApiVec;
+use crate::conversion::{convert_error::ErrorContext, ConvertError};
+use crate::{conversion::api::Api, known_types};
+
+/// Remove any APIs which depend on other items which have been ignored.
+/// We also eliminate any APIs that depend on some type that we just don't
+/// know about at all. In either case, we don't simply remove the type, but instead
+/// replace it with an error marker.
+pub(crate) fn filter_apis_by_ignored_dependents(mut apis: ApiVec<FnPhase>) -> ApiVec<FnPhase> {
+    let (ignored_items, valid_items): (Vec<&Api<_>>, Vec<&Api<_>>) = apis
+        .iter()
+        .partition(|api| matches!(api, Api::IgnoredItem { .. }));
+    let mut ignored_items: HashSet<_> = ignored_items
+        .into_iter()
+        .map(|api| api.name().clone())
+        .collect();
+    let valid_types: HashSet<_> = valid_items
+        .into_iter()
+        .flat_map(|api| api.valid_types())
+        .collect();
+    let mut iterate_again = true;
+    while iterate_again {
+        iterate_again = false;
+        apis = apis
+            .into_iter()
+            .map(|api| {
+                let ignored_dependents: HashSet<_> = api
+                    .deps()
+                    .filter(|dep| ignored_items.contains(*dep))
+                    .cloned()
+                    .collect();
+                if !ignored_dependents.is_empty() {
+                    iterate_again = true;
+                    ignored_items.insert(api.name().clone());
+                    create_ignore_item(api, ConvertError::IgnoredDependent(ignored_dependents))
+                } else {
+                    let mut missing_deps = api.deps().filter(|dep| {
+                        !valid_types.contains(*dep) && !known_types().is_known_type(dep)
+                    });
+                    let first = missing_deps.next();
+                    std::mem::drop(missing_deps);
+                    if let Some(missing_dep) = first.cloned() {
+                        create_ignore_item(api, ConvertError::UnknownDependentType(missing_dep))
+                    } else {
+                        api
+                    }
+                }
+            })
+            .collect();
+    }
+    apis
+}
+
+fn create_ignore_item(api: Api<FnPhase>, err: ConvertError) -> Api<FnPhase> {
+    let id = api.name().get_final_ident();
+    log::info!("Marking as ignored: {} because {}", id.to_string(), err);
+    Api::IgnoredItem {
+        name: api.name_info().clone(),
+        err,
+        ctx: match api {
+            Api::Function {
+                analysis:
+                    FnAnalysis {
+                        kind: FnKind::TraitMethod { .. },
+                        ..
+                    },
+                ..
+            } => None,
+            Api::Function {
+                analysis:
+                    FnAnalysis {
+                        kind:
+                            FnKind::Method {
+                                impl_for: self_ty, ..
+                            },
+                        ..
+                    },
+                ..
+            } => Some(ErrorContext::new_for_method(self_ty.get_final_ident(), id)),
+            _ => Some(ErrorContext::new_for_item(id)),
+        },
+    }
+}
diff --git a/engine/src/conversion/analysis/replace_hopeless_typedef_targets.rs b/engine/src/conversion/analysis/replace_hopeless_typedef_targets.rs
new file mode 100644
index 0000000..8d5d033
--- /dev/null
+++ b/engine/src/conversion/analysis/replace_hopeless_typedef_targets.rs
@@ -0,0 +1,100 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use autocxx_parser::IncludeCppConfig;
+use indexmap::set::IndexSet as HashSet;
+
+use crate::{
+    conversion::{
+        analysis::tdef::TypedefAnalysis,
+        api::Api,
+        apivec::ApiVec,
+        convert_error::{ConvertErrorWithContext, ErrorContext},
+        ConvertError,
+    },
+    types::QualifiedName,
+};
+
+use super::pod::PodPhase;
+/// Where we find a typedef pointing at something we can't represent,
+/// e.g. because it uses too many template parameters, break the link.
+/// Use the typedef as a first-class type.
+pub(crate) fn replace_hopeless_typedef_targets(
+    config: &IncludeCppConfig,
+    apis: ApiVec<PodPhase>,
+) -> ApiVec<PodPhase> {
+    let ignored_types: HashSet<QualifiedName> = apis
+        .iter()
+        .filter_map(|api| match api {
+            Api::IgnoredItem { .. } => Some(api.name()),
+            _ => None,
+        })
+        .cloned()
+        .collect();
+    let ignored_forward_declarations: HashSet<QualifiedName> = apis
+        .iter()
+        .filter_map(|api| match api {
+            Api::ForwardDeclaration { err: Some(_), .. } => Some(api.name()),
+            _ => None,
+        })
+        .cloned()
+        .collect();
+    // Convert any Typedefs which depend on these things into OpaqueTypedefs
+    // instead.
+    // And, after this point we no longer need special knowledge of forward
+    // declarations with errors, so just convert them into regular IgnoredItems too.
+    apis.into_iter()
+        .map(|api| match api {
+            Api::Typedef {
+                ref name,
+                analysis: TypedefAnalysis { ref deps, .. },
+                ..
+            } if !ignored_types.is_disjoint(deps) =>
+            // This typedef depended on something we ignored.
+            // Ideally, we'd turn it into an opaque item.
+            // We can't do that if this is an inner type,
+            // because we have no way to know if it's abstract or not,
+            // and we can't represent inner types in cxx without knowing
+            // that.
+            {
+                let name_id = name.name.get_final_ident();
+                if api
+                    .cpp_name()
+                    .as_ref()
+                    .map(|n| n.contains("::"))
+                    .unwrap_or_default()
+                {
+                    Api::IgnoredItem {
+                        name: api.name_info().clone(),
+                        err: ConvertError::NestedOpaqueTypedef,
+                        ctx: Some(ErrorContext::new_for_item(name_id)),
+                    }
+                } else {
+                    Api::OpaqueTypedef {
+                        name: api.name_info().clone(),
+                        forward_declaration: !config
+                            .instantiable
+                            .contains(&name.name.to_cpp_name()),
+                    }
+                }
+            }
+            Api::Typedef {
+                analysis: TypedefAnalysis { ref deps, .. },
+                ..
+            } if !ignored_forward_declarations.is_disjoint(deps) => Api::OpaqueTypedef {
+                name: api.name_info().clone(),
+                forward_declaration: true,
+            },
+            Api::ForwardDeclaration {
+                name,
+                err: Some(ConvertErrorWithContext(err, ctx)),
+            } => Api::IgnoredItem { name, err, ctx },
+            _ => api,
+        })
+        .collect()
+}
diff --git a/engine/src/conversion/analysis/tdef.rs b/engine/src/conversion/analysis/tdef.rs
new file mode 100644
index 0000000..5a635f8
--- /dev/null
+++ b/engine/src/conversion/analysis/tdef.rs
@@ -0,0 +1,128 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use indexmap::set::IndexSet as HashSet;
+
+use autocxx_parser::IncludeCppConfig;
+use syn::ItemType;
+
+use crate::{
+    conversion::{
+        analysis::type_converter::{add_analysis, Annotated, TypeConversionContext, TypeConverter},
+        api::{AnalysisPhase, Api, ApiName, NullPhase, TypedefKind},
+        apivec::ApiVec,
+        convert_error::{ConvertErrorWithContext, ErrorContext},
+        error_reporter::convert_apis,
+        parse::BindgenSemanticAttributes,
+        ConvertError,
+    },
+    types::QualifiedName,
+};
+
+pub(crate) struct TypedefAnalysis {
+    pub(crate) kind: TypedefKind,
+    pub(crate) deps: HashSet<QualifiedName>,
+}
+
+/// Analysis phase where typedef analysis has been performed but no other
+/// analyses just yet.
+pub(crate) struct TypedefPhase;
+
+impl AnalysisPhase for TypedefPhase {
+    type TypedefAnalysis = TypedefAnalysis;
+    type StructAnalysis = ();
+    type FunAnalysis = ();
+}
+
+#[allow(clippy::needless_collect)] // we need the extra collect because the closure borrows extra_apis
+pub(crate) fn convert_typedef_targets(
+    config: &IncludeCppConfig,
+    apis: ApiVec<NullPhase>,
+) -> ApiVec<TypedefPhase> {
+    let mut type_converter = TypeConverter::new(config, &apis);
+    let mut extra_apis = ApiVec::new();
+    let mut results = ApiVec::new();
+    convert_apis(
+        apis,
+        &mut results,
+        Api::fun_unchanged,
+        Api::struct_unchanged,
+        Api::enum_unchanged,
+        |name, item, old_tyname, _| {
+            Ok(Box::new(std::iter::once(match item {
+                TypedefKind::Type(ity) => get_replacement_typedef(
+                    name,
+                    ity,
+                    old_tyname,
+                    &mut type_converter,
+                    &mut extra_apis,
+                )?,
+                TypedefKind::Use { .. } => Api::Typedef {
+                    name,
+                    item: item.clone(),
+                    old_tyname,
+                    analysis: TypedefAnalysis {
+                        kind: item,
+                        deps: HashSet::new(),
+                    },
+                },
+            })))
+        },
+    );
+    results.extend(extra_apis.into_iter().map(add_analysis));
+    results
+}
+
+fn get_replacement_typedef(
+    name: ApiName,
+    ity: ItemType,
+    old_tyname: Option<QualifiedName>,
+    type_converter: &mut TypeConverter,
+    extra_apis: &mut ApiVec<NullPhase>,
+) -> Result<Api<TypedefPhase>, ConvertErrorWithContext> {
+    if !ity.generics.params.is_empty() {
+        return Err(ConvertErrorWithContext(
+            ConvertError::TypedefTakesGenericParameters,
+            Some(ErrorContext::new_for_item(name.name.get_final_ident())),
+        ));
+    }
+    let mut converted_type = ity.clone();
+    let metadata = BindgenSemanticAttributes::new_retaining_others(&mut converted_type.attrs);
+    metadata.check_for_fatal_attrs(&ity.ident)?;
+    let type_conversion_results = type_converter.convert_type(
+        (*ity.ty).clone(),
+        name.name.get_namespace(),
+        &TypeConversionContext::WithinReference,
+    );
+    match type_conversion_results {
+        Err(err) => Err(ConvertErrorWithContext(
+            err,
+            Some(ErrorContext::new_for_item(name.name.get_final_ident())),
+        )),
+        Ok(Annotated {
+            ty: syn::Type::Path(ref typ),
+            ..
+        }) if QualifiedName::from_type_path(typ) == name.name => Err(ConvertErrorWithContext(
+            ConvertError::InfinitelyRecursiveTypedef(name.name.clone()),
+            Some(ErrorContext::new_for_item(name.name.get_final_ident())),
+        )),
+        Ok(mut final_type) => {
+            converted_type.ty = Box::new(final_type.ty.clone());
+            extra_apis.append(&mut final_type.extra_apis);
+            Ok(Api::Typedef {
+                name,
+                item: TypedefKind::Type(ity),
+                old_tyname,
+                analysis: TypedefAnalysis {
+                    kind: TypedefKind::Type(converted_type),
+                    deps: final_type.types_encountered,
+                },
+            })
+        }
+    }
+}
diff --git a/engine/src/conversion/analysis/type_converter.rs b/engine/src/conversion/analysis/type_converter.rs
new file mode 100644
index 0000000..afdda8a
--- /dev/null
+++ b/engine/src/conversion/analysis/type_converter.rs
@@ -0,0 +1,686 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::{
+    conversion::{
+        api::{AnalysisPhase, Api, ApiName, NullPhase, TypedefKind, UnanalyzedApi},
+        apivec::ApiVec,
+        codegen_cpp::type_to_cpp::type_to_cpp,
+        ConvertError,
+    },
+    known_types::{known_types, CxxGenericType},
+    types::{make_ident, Namespace, QualifiedName},
+};
+use autocxx_parser::IncludeCppConfig;
+use indexmap::map::IndexMap as HashMap;
+use indexmap::set::IndexSet as HashSet;
+use itertools::Itertools;
+use proc_macro2::Ident;
+use quote::ToTokens;
+use syn::{
+    parse_quote, punctuated::Punctuated, token::Comma, GenericArgument, PathArguments, PathSegment,
+    Type, TypePath, TypePtr,
+};
+
+use super::tdef::TypedefAnalysis;
+
+/// Certain kinds of type may require special handling by callers.
+#[derive(Debug)]
+pub(crate) enum TypeKind {
+    Regular,
+    Pointer,
+    SubclassHolder(Ident),
+    Reference,
+    RValueReference,
+    MutableReference,
+}
+
+/// Results of some type conversion, annotated with a list of every type encountered,
+/// and optionally any extra APIs we need in order to use this type.
+pub(crate) struct Annotated<T> {
+    pub(crate) ty: T,
+    pub(crate) types_encountered: HashSet<QualifiedName>,
+    pub(crate) extra_apis: ApiVec<NullPhase>,
+    pub(crate) kind: TypeKind,
+}
+
+impl<T> Annotated<T> {
+    fn new(
+        ty: T,
+        types_encountered: HashSet<QualifiedName>,
+        extra_apis: ApiVec<NullPhase>,
+        kind: TypeKind,
+    ) -> Self {
+        Self {
+            ty,
+            types_encountered,
+            extra_apis,
+            kind,
+        }
+    }
+
+    fn map<T2, F: FnOnce(T) -> T2>(self, fun: F) -> Annotated<T2> {
+        Annotated {
+            ty: fun(self.ty),
+            types_encountered: self.types_encountered,
+            extra_apis: self.extra_apis,
+            kind: self.kind,
+        }
+    }
+}
+
+/// How to interpret a pointer which we encounter during type conversion.
+#[derive(Clone, Copy)]
+pub(crate) enum PointerTreatment {
+    Pointer,
+    Reference,
+    RValueReference,
+}
+
+/// Options when converting a type.
+/// It's possible we could add more policies here in future.
+/// For example, Rust in general allows type names containing
+/// __, whereas cxx doesn't. If we could identify cases where
+/// a type will only ever be used in a bindgen context,
+/// we could be more liberal. At the moment though, all outputs
+/// from [TypeConverter] _might_ be used in the [cxx::bridge].
+pub(crate) enum TypeConversionContext {
+    WithinReference,
+    WithinContainer,
+    OuterType { pointer_treatment: PointerTreatment },
+}
+
+impl TypeConversionContext {
+    fn pointer_treatment(&self) -> PointerTreatment {
+        match self {
+            Self::WithinReference | Self::WithinContainer => PointerTreatment::Pointer,
+            Self::OuterType { pointer_treatment } => *pointer_treatment,
+        }
+    }
+    fn allow_instantiation_of_forward_declaration(&self) -> bool {
+        matches!(self, Self::WithinReference)
+    }
+}
+
+/// A type which can convert from a type encountered in `bindgen`
+/// output to the sort of type we should represeent to `cxx`.
+/// As a simple example, `std::string` should be replaced
+/// with [CxxString]. This also involves keeping track
+/// of typedefs, and any instantiated concrete types.
+///
+/// To do this conversion correctly, this type relies on
+/// inspecting the pre-existing list of APIs.
+pub(crate) struct TypeConverter<'a> {
+    types_found: HashSet<QualifiedName>,
+    typedefs: HashMap<QualifiedName, Type>,
+    concrete_templates: HashMap<String, QualifiedName>,
+    forward_declarations: HashSet<QualifiedName>,
+    ignored_types: HashSet<QualifiedName>,
+    config: &'a IncludeCppConfig,
+}
+
+impl<'a> TypeConverter<'a> {
+    pub(crate) fn new<A: AnalysisPhase>(config: &'a IncludeCppConfig, apis: &ApiVec<A>) -> Self
+    where
+        A::TypedefAnalysis: TypedefTarget,
+    {
+        Self {
+            types_found: find_types(apis),
+            typedefs: Self::find_typedefs(apis),
+            concrete_templates: Self::find_concrete_templates(apis),
+            forward_declarations: Self::find_incomplete_types(apis),
+            ignored_types: Self::find_ignored_types(apis),
+            config,
+        }
+    }
+
+    pub(crate) fn convert_boxed_type(
+        &mut self,
+        ty: Box<Type>,
+        ns: &Namespace,
+        ctx: &TypeConversionContext,
+    ) -> Result<Annotated<Box<Type>>, ConvertError> {
+        Ok(self.convert_type(*ty, ns, ctx)?.map(Box::new))
+    }
+
+    pub(crate) fn convert_type(
+        &mut self,
+        ty: Type,
+        ns: &Namespace,
+        ctx: &TypeConversionContext,
+    ) -> Result<Annotated<Type>, ConvertError> {
+        let result = match ty {
+            Type::Path(p) => {
+                let newp = self.convert_type_path(p, ns)?;
+                if let Type::Path(newpp) = &newp.ty {
+                    let qn = QualifiedName::from_type_path(newpp);
+                    if !ctx.allow_instantiation_of_forward_declaration()
+                        && self.forward_declarations.contains(&qn)
+                    {
+                        return Err(ConvertError::TypeContainingForwardDeclaration(qn));
+                    }
+                    // Special handling because rust_Str (as emitted by bindgen)
+                    // doesn't simply get renamed to a different type _identifier_.
+                    // This plain type-by-value (as far as bindgen is concerned)
+                    // is actually a &str.
+                    if known_types().should_dereference_in_cpp(&qn) {
+                        Annotated::new(
+                            Type::Reference(parse_quote! {
+                                &str
+                            }),
+                            newp.types_encountered,
+                            newp.extra_apis,
+                            TypeKind::Reference,
+                        )
+                    } else {
+                        newp
+                    }
+                } else {
+                    newp
+                }
+            }
+            Type::Reference(mut r) => {
+                let innerty =
+                    self.convert_boxed_type(r.elem, ns, &TypeConversionContext::WithinReference)?;
+                r.elem = innerty.ty;
+                Annotated::new(
+                    Type::Reference(r),
+                    innerty.types_encountered,
+                    innerty.extra_apis,
+                    TypeKind::Reference,
+                )
+            }
+            Type::Array(mut arr) => {
+                let innerty =
+                    self.convert_type(*arr.elem, ns, &TypeConversionContext::WithinReference)?;
+                arr.elem = Box::new(innerty.ty);
+                Annotated::new(
+                    Type::Array(arr),
+                    innerty.types_encountered,
+                    innerty.extra_apis,
+                    TypeKind::Regular,
+                )
+            }
+            Type::Ptr(ptr) => self.convert_ptr(ptr, ns, ctx.pointer_treatment())?,
+            _ => return Err(ConvertError::UnknownType(ty.to_token_stream().to_string())),
+        };
+        Ok(result)
+    }
+
+    fn convert_type_path(
+        &mut self,
+        mut typ: TypePath,
+        ns: &Namespace,
+    ) -> Result<Annotated<Type>, ConvertError> {
+        // First, qualify any unqualified paths.
+        if typ.path.segments.iter().next().unwrap().ident != "root" {
+            let ty = QualifiedName::from_type_path(&typ);
+            // If the type looks like it is unqualified, check we know it
+            // already, and if not, qualify it according to the current
+            // namespace. This is a bit of a shortcut compared to having a full
+            // resolution pass which can search all known namespaces.
+            if !known_types().is_known_type(&ty) {
+                let num_segments = typ.path.segments.len();
+                if num_segments > 1 {
+                    return Err(ConvertError::UnsupportedBuiltInType(ty));
+                }
+                if !self.types_found.contains(&ty) {
+                    typ.path.segments = std::iter::once(&"root".to_string())
+                        .chain(ns.iter())
+                        .map(|s| {
+                            let i = make_ident(s);
+                            parse_quote! { #i }
+                        })
+                        .chain(typ.path.segments.into_iter())
+                        .collect();
+                }
+            }
+        }
+
+        let original_tn = QualifiedName::from_type_path(&typ);
+        original_tn.validate_ok_for_cxx()?;
+        if self.config.is_on_blocklist(&original_tn.to_cpp_name()) {
+            return Err(ConvertError::Blocked(original_tn));
+        }
+        let mut deps = HashSet::new();
+
+        // Now convert this type itself.
+        deps.insert(original_tn.clone());
+        // First let's see if this is a typedef.
+        let (typ, tn) = match self.resolve_typedef(&original_tn)? {
+            None => (typ, original_tn),
+            Some(Type::Path(resolved_tp)) => {
+                let resolved_tn = QualifiedName::from_type_path(resolved_tp);
+                deps.insert(resolved_tn.clone());
+                (resolved_tp.clone(), resolved_tn)
+            }
+            Some(Type::Ptr(resolved_tp)) => {
+                return Ok(Annotated::new(
+                    Type::Ptr(resolved_tp.clone()),
+                    deps,
+                    ApiVec::new(),
+                    TypeKind::Pointer,
+                ))
+            }
+            Some(other) => {
+                return Ok(Annotated::new(
+                    other.clone(),
+                    deps,
+                    ApiVec::new(),
+                    TypeKind::Regular,
+                ))
+            }
+        };
+
+        // Now let's see if it's a known type.
+        // (We may entirely reject some types at this point too.)
+        let mut typ = match known_types().consider_substitution(&tn) {
+            Some(mut substitute_type) => {
+                if let Some(last_seg_args) =
+                    typ.path.segments.into_iter().last().map(|ps| ps.arguments)
+                {
+                    let last_seg = substitute_type.path.segments.last_mut().unwrap();
+                    last_seg.arguments = last_seg_args;
+                }
+                substitute_type
+            }
+            None => typ,
+        };
+
+        let mut extra_apis = ApiVec::new();
+        let mut kind = TypeKind::Regular;
+
+        // Finally let's see if it's generic.
+        if let Some(last_seg) = Self::get_generic_args(&mut typ) {
+            let generic_behavior = known_types().cxx_generic_behavior(&tn);
+            let forward_declarations_ok = generic_behavior == CxxGenericType::Rust;
+            if generic_behavior != CxxGenericType::Not {
+                // this is a type of generic understood by cxx (e.g. CxxVector)
+                // so let's convert any generic type arguments. This recurses.
+                if let PathArguments::AngleBracketed(ref mut ab) = last_seg.arguments {
+                    let mut innerty = self.convert_punctuated(
+                        ab.args.clone(),
+                        ns,
+                        &TypeConversionContext::WithinContainer,
+                    )?;
+                    ab.args = innerty.ty;
+                    kind = self.confirm_inner_type_is_acceptable_generic_payload(
+                        &ab.args,
+                        &tn,
+                        generic_behavior,
+                        forward_declarations_ok,
+                    )?;
+                    deps.extend(innerty.types_encountered.drain(..));
+                } else {
+                    return Err(ConvertError::TemplatedTypeContainingNonPathArg(tn.clone()));
+                }
+            } else {
+                // Oh poop. It's a generic type which cxx won't be able to handle.
+                // We'll have to come up with a concrete type in both the cxx::bridge (in Rust)
+                // and a corresponding typedef in C++.
+                // Let's first see if this is a concrete version of a templated type
+                // which we already rejected. Some, but possibly not all, of the reasons
+                // for its rejection would also apply to any concrete types we
+                // make. Err on the side of caution. In future we may be able to relax
+                // this a bit.
+                let qn = QualifiedName::from_type_path(&typ); // ignores generic params
+                if self.ignored_types.contains(&qn) {
+                    return Err(ConvertError::ConcreteVersionOfIgnoredTemplate);
+                }
+                let (new_tn, api) = self.get_templated_typename(&Type::Path(typ))?;
+                extra_apis.extend(api.into_iter());
+                deps.remove(&tn);
+                typ = new_tn.to_type_path();
+                deps.insert(new_tn);
+            }
+        }
+        Ok(Annotated::new(Type::Path(typ), deps, extra_apis, kind))
+    }
+
+    fn get_generic_args(typ: &mut TypePath) -> Option<&mut PathSegment> {
+        match typ.path.segments.last_mut() {
+            Some(s) if !s.arguments.is_empty() => Some(s),
+            _ => None,
+        }
+    }
+
+    fn convert_punctuated<P>(
+        &mut self,
+        pun: Punctuated<GenericArgument, P>,
+        ns: &Namespace,
+        ctx: &TypeConversionContext,
+    ) -> Result<Annotated<Punctuated<GenericArgument, P>>, ConvertError>
+    where
+        P: Default,
+    {
+        let mut new_pun = Punctuated::new();
+        let mut types_encountered = HashSet::new();
+        let mut extra_apis = ApiVec::new();
+        for arg in pun.into_iter() {
+            new_pun.push(match arg {
+                GenericArgument::Type(t) => {
+                    let mut innerty = self.convert_type(t, ns, ctx)?;
+                    types_encountered.extend(innerty.types_encountered.drain(..));
+                    extra_apis.append(&mut innerty.extra_apis);
+                    GenericArgument::Type(innerty.ty)
+                }
+                _ => arg,
+            });
+        }
+        Ok(Annotated::new(
+            new_pun,
+            types_encountered,
+            extra_apis,
+            TypeKind::Regular,
+        ))
+    }
+
+    fn resolve_typedef<'b>(&'b self, tn: &QualifiedName) -> Result<Option<&'b Type>, ConvertError> {
+        let mut encountered = HashSet::new();
+        let mut tn = tn.clone();
+        let mut previous_typ = None;
+        loop {
+            let r = self.typedefs.get(&tn);
+            match r {
+                Some(Type::Path(typ)) => {
+                    previous_typ = r;
+                    let new_tn = QualifiedName::from_type_path(typ);
+                    if encountered.contains(&new_tn) {
+                        return Err(ConvertError::InfinitelyRecursiveTypedef(tn.clone()));
+                    }
+                    encountered.insert(new_tn.clone());
+                    tn = new_tn;
+                }
+                None => return Ok(previous_typ),
+                _ => return Ok(r),
+            }
+        }
+    }
+
+    fn convert_ptr(
+        &mut self,
+        mut ptr: TypePtr,
+        ns: &Namespace,
+        pointer_treatment: PointerTreatment,
+    ) -> Result<Annotated<Type>, ConvertError> {
+        match pointer_treatment {
+            PointerTreatment::Pointer => {
+                crate::known_types::ensure_pointee_is_valid(&ptr)?;
+                let innerty =
+                    self.convert_boxed_type(ptr.elem, ns, &TypeConversionContext::WithinReference)?;
+                ptr.elem = innerty.ty;
+                Ok(Annotated::new(
+                    Type::Ptr(ptr),
+                    innerty.types_encountered,
+                    innerty.extra_apis,
+                    TypeKind::Pointer,
+                ))
+            }
+            PointerTreatment::Reference => {
+                let mutability = ptr.mutability;
+                let elem =
+                    self.convert_boxed_type(ptr.elem, ns, &TypeConversionContext::WithinReference)?;
+                // TODO - in the future, we should check if this is a rust::Str and throw
+                // a wobbler if not. rust::Str should only be seen _by value_ in C++
+                // headers; it manifests as &str in Rust but on the C++ side it must
+                // be a plain value. We should detect and abort.
+                let mut outer = elem.map(|elem| match mutability {
+                    Some(_) => Type::Path(parse_quote! {
+                        ::std::pin::Pin < & #mutability #elem >
+                    }),
+                    None => Type::Reference(parse_quote! {
+                        & #elem
+                    }),
+                });
+                outer.kind = if mutability.is_some() {
+                    TypeKind::MutableReference
+                } else {
+                    TypeKind::Reference
+                };
+                Ok(outer)
+            }
+            PointerTreatment::RValueReference => {
+                crate::known_types::ensure_pointee_is_valid(&ptr)?;
+                let innerty =
+                    self.convert_boxed_type(ptr.elem, ns, &TypeConversionContext::WithinReference)?;
+                ptr.elem = innerty.ty;
+                Ok(Annotated::new(
+                    Type::Ptr(ptr),
+                    innerty.types_encountered,
+                    innerty.extra_apis,
+                    TypeKind::RValueReference,
+                ))
+            }
+        }
+    }
+
+    fn get_templated_typename(
+        &mut self,
+        rs_definition: &Type,
+    ) -> Result<(QualifiedName, Option<UnanalyzedApi>), ConvertError> {
+        let count = self.concrete_templates.len();
+        // We just use this as a hash key, essentially.
+        // TODO: Once we've completed the TypeConverter refactoring (see #220),
+        // pass in an actual original_name_map here.
+        let cpp_definition = type_to_cpp(rs_definition, &HashMap::new())?;
+        let e = self.concrete_templates.get(&cpp_definition);
+        match e {
+            Some(tn) => Ok((tn.clone(), None)),
+            None => {
+                let synthetic_ident = format!(
+                    "{}_AutocxxConcrete",
+                    cpp_definition.replace(|c: char| !(c.is_ascii_alphanumeric() || c == '_'), "_")
+                );
+                // Remove runs of multiple _s. Trying to avoid a dependency on
+                // regex.
+                let synthetic_ident = synthetic_ident
+                    .split('_')
+                    .filter(|s| !s.is_empty())
+                    .join("_");
+                // Ensure we're not duplicating some existing concrete template name.
+                // If so, we'll invent a name which is guaranteed to be unique.
+                let synthetic_ident = match self
+                    .concrete_templates
+                    .values()
+                    .map(|n| n.get_final_item())
+                    .find(|s| s == &synthetic_ident)
+                {
+                    None => synthetic_ident,
+                    Some(_) => format!("AutocxxConcrete{}", count),
+                };
+                let api = UnanalyzedApi::ConcreteType {
+                    name: ApiName::new_in_root_namespace(make_ident(&synthetic_ident)),
+                    cpp_definition: cpp_definition.clone(),
+                    rs_definition: Some(Box::new(rs_definition.clone())),
+                };
+                self.concrete_templates
+                    .insert(cpp_definition, api.name().clone());
+                Ok((api.name().clone(), Some(api)))
+            }
+        }
+    }
+
+    fn confirm_inner_type_is_acceptable_generic_payload(
+        &self,
+        path_args: &Punctuated<GenericArgument, Comma>,
+        desc: &QualifiedName,
+        generic_behavior: CxxGenericType,
+        forward_declarations_ok: bool,
+    ) -> Result<TypeKind, ConvertError> {
+        for inner in path_args {
+            match inner {
+                GenericArgument::Type(Type::Path(typ)) => {
+                    let inner_qn = QualifiedName::from_type_path(typ);
+                    if !forward_declarations_ok && self.forward_declarations.contains(&inner_qn) {
+                        return Err(ConvertError::TypeContainingForwardDeclaration(inner_qn));
+                    }
+                    match generic_behavior {
+                        CxxGenericType::Rust => {
+                            if !inner_qn.get_namespace().is_empty() {
+                                return Err(ConvertError::RustTypeWithAPath(inner_qn));
+                            }
+                            if !self.config.is_rust_type(&inner_qn.get_final_ident()) {
+                                return Err(ConvertError::BoxContainingNonRustType(inner_qn));
+                            }
+                            if self
+                                .config
+                                .is_subclass_holder(&inner_qn.get_final_ident().to_string())
+                            {
+                                return Ok(TypeKind::SubclassHolder(inner_qn.get_final_ident()));
+                            } else {
+                                return Ok(TypeKind::Regular);
+                            }
+                        }
+                        CxxGenericType::CppPtr => {
+                            if !known_types().permissible_within_unique_ptr(&inner_qn) {
+                                return Err(ConvertError::InvalidTypeForCppPtr(inner_qn));
+                            }
+                        }
+                        CxxGenericType::CppVector => {
+                            if !known_types().permissible_within_vector(&inner_qn) {
+                                return Err(ConvertError::InvalidTypeForCppVector(inner_qn));
+                            }
+                            if matches!(
+                                typ.path.segments.last().map(|ps| &ps.arguments),
+                                Some(
+                                    PathArguments::Parenthesized(_)
+                                        | PathArguments::AngleBracketed(_)
+                                )
+                            ) {
+                                return Err(ConvertError::GenericsWithinVector);
+                            }
+                        }
+                        _ => {}
+                    }
+                }
+                _ => {
+                    return Err(ConvertError::TemplatedTypeContainingNonPathArg(
+                        desc.clone(),
+                    ))
+                }
+            }
+        }
+        Ok(TypeKind::Regular)
+    }
+
+    fn find_typedefs<A: AnalysisPhase>(apis: &ApiVec<A>) -> HashMap<QualifiedName, Type>
+    where
+        A::TypedefAnalysis: TypedefTarget,
+    {
+        apis.iter()
+            .filter_map(|api| match &api {
+                Api::Typedef { analysis, .. } => analysis
+                    .get_target()
+                    .cloned()
+                    .map(|ty| (api.name().clone(), ty)),
+                _ => None,
+            })
+            .collect()
+    }
+
+    fn find_concrete_templates<A: AnalysisPhase>(
+        apis: &ApiVec<A>,
+    ) -> HashMap<String, QualifiedName> {
+        apis.iter()
+            .filter_map(|api| match &api {
+                Api::ConcreteType { cpp_definition, .. } => {
+                    Some((cpp_definition.clone(), api.name().clone()))
+                }
+                _ => None,
+            })
+            .collect()
+    }
+
+    fn find_incomplete_types<A: AnalysisPhase>(apis: &ApiVec<A>) -> HashSet<QualifiedName> {
+        apis.iter()
+            .filter_map(|api| match api {
+                Api::ForwardDeclaration { .. }
+                | Api::OpaqueTypedef {
+                    forward_declaration: true,
+                    ..
+                } => Some(api.name()),
+                _ => None,
+            })
+            .cloned()
+            .collect()
+    }
+
+    fn find_ignored_types<A: AnalysisPhase>(apis: &ApiVec<A>) -> HashSet<QualifiedName> {
+        apis.iter()
+            .filter_map(|api| match api {
+                Api::IgnoredItem { .. } => Some(api.name()),
+                _ => None,
+            })
+            .cloned()
+            .collect()
+    }
+}
+
+/// Processing functions sometimes results in new types being materialized.
+/// These types haven't been through the analysis phases (chicken and egg
+/// problem) but fortunately, don't need to. We need to keep the type
+/// system happy by adding an [ApiAnalysis] but in practice, for the sorts
+/// of things that get created, it's always blank.
+pub(crate) fn add_analysis<A: AnalysisPhase>(api: UnanalyzedApi) -> Api<A> {
+    match api {
+        Api::ConcreteType {
+            name,
+            rs_definition,
+            cpp_definition,
+        } => Api::ConcreteType {
+            name,
+            rs_definition,
+            cpp_definition,
+        },
+        Api::IgnoredItem { name, err, ctx } => Api::IgnoredItem { name, err, ctx },
+        _ => panic!("Function analysis created an unexpected type of extra API"),
+    }
+}
+pub(crate) trait TypedefTarget {
+    fn get_target(&self) -> Option<&Type>;
+}
+
+impl TypedefTarget for () {
+    fn get_target(&self) -> Option<&Type> {
+        None
+    }
+}
+
+impl TypedefTarget for TypedefAnalysis {
+    fn get_target(&self) -> Option<&Type> {
+        Some(match self.kind {
+            TypedefKind::Type(ref ty) => &ty.ty,
+            TypedefKind::Use(_, ref ty) => ty,
+        })
+    }
+}
+
+pub(crate) fn find_types<A: AnalysisPhase>(apis: &ApiVec<A>) -> HashSet<QualifiedName> {
+    apis.iter()
+        .filter_map(|api| match api {
+            Api::ForwardDeclaration { .. }
+            | Api::OpaqueTypedef { .. }
+            | Api::ConcreteType { .. }
+            | Api::Typedef { .. }
+            | Api::Enum { .. }
+            | Api::Struct { .. }
+            | Api::Subclass { .. }
+            | Api::ExternCppType { .. }
+            | Api::RustType { .. } => Some(api.name()),
+            Api::StringConstructor { .. }
+            | Api::Function { .. }
+            | Api::Const { .. }
+            | Api::CType { .. }
+            | Api::RustSubclassFn { .. }
+            | Api::IgnoredItem { .. }
+            | Api::SubclassTraitItem { .. }
+            | Api::RustFn { .. } => None,
+        })
+        .cloned()
+        .collect()
+}