Join trivial reasons in one message
diff --git a/gen/src/write.rs b/gen/src/write.rs
index 434ca17..315fb35 100644
--- a/gen/src/write.rs
+++ b/gen/src/write.rs
@@ -4,10 +4,10 @@
 use crate::gen::{builtin, include, Opt};
 use crate::syntax::atom::Atom::{self, *};
 use crate::syntax::symbol::Symbol;
-use crate::syntax::trivial::TrivialReason;
+use crate::syntax::trivial::{self, TrivialReason};
 use crate::syntax::{
     derive, mangle, Api, Enum, ExternFn, ExternType, Pair, RustName, Signature, Struct, Trait,
-    Type, Types, Var,
+    Type, TypeAlias, Types, Var,
 };
 use proc_macro2::Ident;
 use std::collections::{HashMap, HashSet};
@@ -119,7 +119,7 @@
     for api in apis {
         if let Api::TypeAlias(ety) = api {
             if let Some(reasons) = out.types.required_trivial.get(&ety.name.rust) {
-                check_trivial_extern_type(out, &ety.name, reasons)
+                check_trivial_extern_type(out, ety, reasons)
             }
         }
     }
@@ -370,7 +370,7 @@
     }
 }
 
-fn check_trivial_extern_type(out: &mut OutFile, id: &Pair, reasons: &[TrivialReason]) {
+fn check_trivial_extern_type(out: &mut OutFile, alias: &TypeAlias, reasons: &[TrivialReason]) {
     // NOTE: The following static assertion is just nice-to-have and not
     // necessary for soundness. That's because triviality is always declared by
     // the user in the form of an unsafe impl of cxx::ExternType:
@@ -404,18 +404,16 @@
     //    + struct rust::IsRelocatable<MyType> : std::true_type {};
     //
 
-    let id = id.to_fully_qualified();
+    let id = alias.name.to_fully_qualified();
     out.builtin.relocatable = true;
-    for reason in reasons {
-        writeln!(out, "static_assert(");
-        writeln!(out, "    ::rust::IsRelocatable<{}>::value,", id);
-        writeln!(
-            out,
-            "    \"type {} is not move constructible and trivially destructible in C++ yet is used as a trivial type in Rust ({})\");",
-            id.trim_start_matches("::"),
-            reason,
-        );
-    }
+    writeln!(out, "static_assert(");
+    writeln!(out, "    ::rust::IsRelocatable<{}>::value,", id);
+    writeln!(
+        out,
+        "    \"type {} should be trivially move constructible and trivially destructible in C++ to be used as {} in Rust\");",
+        id.trim_start_matches("::"),
+        trivial::as_what(&alias.name, reasons),
+    );
 }
 
 fn write_struct_operator_decls<'a>(out: &mut OutFile<'a>, strct: &'a Struct) {
diff --git a/syntax/check.rs b/syntax/check.rs
index 32d147b..d0b0ba0 100644
--- a/syntax/check.rs
+++ b/syntax/check.rs
@@ -1,8 +1,8 @@
 use crate::syntax::atom::Atom::{self, *};
 use crate::syntax::report::Errors;
 use crate::syntax::{
-    error, ident, Api, Array, Enum, ExternFn, ExternType, Impl, Lang, Receiver, Ref, Signature,
-    SliceRef, Struct, Trait, Ty1, Type, TypeAlias, Types,
+    error, ident, trivial, Api, Array, Enum, ExternFn, ExternType, Impl, Lang, Receiver, Ref,
+    Signature, SliceRef, Struct, Trait, Ty1, Type, TypeAlias, Types,
 };
 use proc_macro2::{Delimiter, Group, Ident, TokenStream};
 use quote::{quote, ToTokens};
@@ -325,14 +325,11 @@
     }
 
     if let Some(reasons) = cx.types.required_trivial.get(&ety.name.rust) {
-        for reason in reasons {
-            let what = reason.describe_in_context(&ety);
-            let msg = format!(
-                "needs a cxx::ExternType impl in order to be used as {}",
-                what,
-            );
-            cx.error(ety, msg);
-        }
+        let msg = format!(
+            "needs a cxx::ExternType impl in order to be used as {}",
+            trivial::as_what(&ety.name, reasons),
+        );
+        cx.error(ety, msg);
     }
 }
 
diff --git a/syntax/set.rs b/syntax/set.rs
index 0618564..0df5f87 100644
--- a/syntax/set.rs
+++ b/syntax/set.rs
@@ -50,11 +50,21 @@
         }
     }
 
+    impl<'a, T> OrderedSet<&'a T> {
+        pub fn is_empty(&self) -> bool {
+            self.vec.is_empty()
+        }
+
+        pub fn iter(&self) -> Iter<'_, 'a, T> {
+            Iter(self.vec.iter())
+        }
+    }
+
     impl<'s, 'a, T> IntoIterator for &'s OrderedSet<&'a T> {
         type Item = &'a T;
         type IntoIter = Iter<'s, 'a, T>;
         fn into_iter(self) -> Self::IntoIter {
-            Iter(self.vec.iter())
+            self.iter()
         }
     }
 }
diff --git a/syntax/trivial.rs b/syntax/trivial.rs
index 7a77937..85184d8 100644
--- a/syntax/trivial.rs
+++ b/syntax/trivial.rs
@@ -1,8 +1,8 @@
 use crate::syntax::set::OrderedSet as Set;
-use crate::syntax::{Api, Enum, ExternFn, ExternType, RustName, Struct, Type};
+use crate::syntax::{Api, Enum, ExternFn, Pair, RustName, Struct, Type};
 use proc_macro2::Ident;
 use std::collections::BTreeMap as Map;
-use std::fmt::Display;
+use std::fmt::{self, Display};
 
 #[derive(Copy, Clone)]
 pub enum TrivialReason<'a> {
@@ -101,31 +101,146 @@
     required_trivial
 }
 
-impl<'a> TrivialReason<'a> {
-    pub fn describe_in_context(&self, ety: &ExternType) -> String {
-        match self {
-            TrivialReason::BoxTarget => format!("Box<{}>", ety.name.rust),
-            TrivialReason::VecElement => format!("a vector element in Vec<{}>", ety.name.rust),
-            _ => self.to_string(),
-        }
+// Context:
+// "type {type} should be trivially move constructible and trivially destructible in C++ to be used as {what} in Rust"
+// "needs a cxx::ExternType impl in order to be used as {what}"
+pub fn as_what<'a>(name: &'a Pair, reasons: &'a [TrivialReason]) -> impl Display + 'a {
+    struct Description<'a> {
+        name: &'a Pair,
+        reasons: &'a [TrivialReason<'a>],
     }
-}
 
-impl<'a> Display for TrivialReason<'a> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            TrivialReason::StructField(strct) => write!(f, "a field of `{}`", strct.name.rust),
-            TrivialReason::FunctionArgument(efn) => write!(f, "an argument of `{}`", efn.name.rust),
-            TrivialReason::FunctionReturn(efn) => {
-                write!(f, "a return value of `{}`", efn.name.rust)
+    impl<'a> Display for Description<'a> {
+        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+            let mut field_of = Set::new();
+            let mut argument_of = Set::new();
+            let mut return_of = Set::new();
+            let mut box_target = false;
+            let mut vec_element = false;
+            let mut unpinned_mut = Set::new();
+
+            for reason in self.reasons {
+                match reason {
+                    TrivialReason::StructField(strct) => {
+                        field_of.insert(&strct.name.rust);
+                    }
+                    TrivialReason::FunctionArgument(efn) => {
+                        argument_of.insert(&efn.name.rust);
+                    }
+                    TrivialReason::FunctionReturn(efn) => {
+                        return_of.insert(&efn.name.rust);
+                    }
+                    TrivialReason::BoxTarget => box_target = true,
+                    TrivialReason::VecElement => vec_element = true,
+                    TrivialReason::UnpinnedMutArg(efn) => {
+                        unpinned_mut.insert(&efn.name.rust);
+                    }
+                }
             }
-            TrivialReason::BoxTarget => write!(f, "in a Box<...>"),
-            TrivialReason::VecElement => write!(f, "a Vec<...> element"),
-            TrivialReason::UnpinnedMutArg(efn) => write!(
-                f,
-                "a non-pinned mutable reference argument of {}",
-                efn.name.rust
-            ),
+
+            let mut clauses = Vec::new();
+            if !field_of.is_empty() {
+                clauses.push(Clause::Set {
+                    article: "a",
+                    desc: "field of",
+                    set: &field_of,
+                });
+            }
+            if !argument_of.is_empty() {
+                clauses.push(Clause::Set {
+                    article: "an",
+                    desc: "argument of",
+                    set: &argument_of,
+                });
+            }
+            if !return_of.is_empty() {
+                clauses.push(Clause::Set {
+                    article: "a",
+                    desc: "return value of",
+                    set: &return_of,
+                });
+            }
+            if box_target {
+                clauses.push(Clause::Ty1 {
+                    article: "type",
+                    desc: "Box",
+                    param: self.name,
+                });
+            }
+            if vec_element {
+                clauses.push(Clause::Ty1 {
+                    article: "a",
+                    desc: "vector element in Vec",
+                    param: self.name,
+                });
+            }
+            if !unpinned_mut.is_empty() {
+                clauses.push(Clause::Set {
+                    article: "a",
+                    desc: "non-pinned mutable reference argument of",
+                    set: &unpinned_mut,
+                });
+            }
+
+            for (i, clause) in clauses.iter().enumerate() {
+                if i == 0 {
+                    write!(f, "{} ", clause.article())?;
+                } else if i + 1 < clauses.len() {
+                    write!(f, ", ")?;
+                } else {
+                    write!(f, " or ")?;
+                }
+                clause.fmt(f)?;
+            }
+
+            Ok(())
         }
     }
+
+    enum Clause<'a> {
+        Set {
+            article: &'a str,
+            desc: &'a str,
+            set: &'a Set<&'a Ident>,
+        },
+        Ty1 {
+            article: &'a str,
+            desc: &'a str,
+            param: &'a Pair,
+        },
+    }
+
+    impl<'a> Clause<'a> {
+        fn article(&self) -> &'a str {
+            match self {
+                Clause::Set { article, .. } | Clause::Ty1 { article, .. } => article,
+            }
+        }
+
+        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+            match self {
+                Clause::Set {
+                    article: _,
+                    desc,
+                    set,
+                } => {
+                    write!(f, "{} ", desc)?;
+                    for (i, ident) in set.iter().take(3).enumerate() {
+                        if i > 0 {
+                            write!(f, ", ")?;
+                        }
+                        write!(f, "`{}`", ident)?;
+                    }
+                    Ok(())
+                }
+                Clause::Ty1 {
+                    article: _,
+                    desc,
+                    param,
+                } => write!(f, "{}<{}>", desc, param.rust),
+            }
+        }
+    }
+
+    Description { name, reasons }
 }
diff --git a/tests/ui/by_value_not_supported.stderr b/tests/ui/by_value_not_supported.stderr
index 4bcce1b..7288c93 100644
--- a/tests/ui/by_value_not_supported.stderr
+++ b/tests/ui/by_value_not_supported.stderr
@@ -16,19 +16,7 @@
 6 |         s: CxxString,
   |         ^^^^^^^^^^^^
 
-error: needs a cxx::ExternType impl in order to be used as a field of `S`
-  --> $DIR/by_value_not_supported.rs:10:9
-   |
-10 |         type C;
-   |         ^^^^^^
-
-error: needs a cxx::ExternType impl in order to be used as an argument of `f`
-  --> $DIR/by_value_not_supported.rs:10:9
-   |
-10 |         type C;
-   |         ^^^^^^
-
-error: needs a cxx::ExternType impl in order to be used as a return value of `f`
+error: needs a cxx::ExternType impl in order to be used as a field of `S`, argument of `f` or return value of `f`
   --> $DIR/by_value_not_supported.rs:10:9
    |
 10 |         type C;
diff --git a/tests/ui/pin_mut_opaque.stderr b/tests/ui/pin_mut_opaque.stderr
index 2de4264..53e651e 100644
--- a/tests/ui/pin_mut_opaque.stderr
+++ b/tests/ui/pin_mut_opaque.stderr
@@ -16,19 +16,7 @@
 9 |         fn v(v: &mut CxxVector<u8>);
   |                 ^^^^^^^^^^^^^^^^^^
 
-error: needs a cxx::ExternType impl in order to be used as a non-pinned mutable reference argument of f
- --> $DIR/pin_mut_opaque.rs:4:9
-  |
-4 |         type Opaque;
-  |         ^^^^^^^^^^^
-
-error: needs a cxx::ExternType impl in order to be used as a non-pinned mutable reference argument of g
- --> $DIR/pin_mut_opaque.rs:4:9
-  |
-4 |         type Opaque;
-  |         ^^^^^^^^^^^
-
-error: needs a cxx::ExternType impl in order to be used as a non-pinned mutable reference argument of h
+error: needs a cxx::ExternType impl in order to be used as a non-pinned mutable reference argument of `f`, `g`, `h`
  --> $DIR/pin_mut_opaque.rs:4:9
   |
 4 |         type Opaque;