From 74bcf35fc83c57fd7e9a31639d7dae31d4d9a050 Mon Sep 17 00:00:00 2001
From: Martin Fischer <martin@push-f.com>
Date: Thu, 25 Nov 2021 16:56:44 +0100
Subject: support dynamized supertraits via attribute

---
 src/lib.rs                                         | 26 ++++++++++++++++---
 src/parse_attrs.rs                                 | 29 +++++++++++++++++++++-
 tests/tests.rs                                     | 15 +++++++++++
 ui-tests/src/bin/attr_dynamized_duplicate.rs       |  6 +++++
 ui-tests/src/bin/attr_dynamized_duplicate.stderr   |  5 ++++
 .../src/bin/attr_dynamized_no_such_supertrait.rs   |  5 ++++
 .../bin/attr_dynamized_no_such_supertrait.stderr   |  5 ++++
 7 files changed, 86 insertions(+), 5 deletions(-)
 create mode 100644 ui-tests/src/bin/attr_dynamized_duplicate.rs
 create mode 100644 ui-tests/src/bin/attr_dynamized_duplicate.stderr
 create mode 100644 ui-tests/src/bin/attr_dynamized_no_such_supertrait.rs
 create mode 100644 ui-tests/src/bin/attr_dynamized_no_such_supertrait.stderr

diff --git a/src/lib.rs b/src/lib.rs
index f8fdde8..017a58b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -83,6 +83,15 @@ pub fn dynamize(_attr: TokenStream, input: TokenStream) -> TokenStream {
         Err(err) => return err.to_compile_error().into(),
     };
 
+    for dyn_supertrait in &method_attrs.dynamized_supertraits {
+        if !trait_bounds(&original_trait.supertraits).any(|t| t.path.is_ident(dyn_supertrait)) {
+            return abort!(
+                dyn_supertrait.span(),
+                "this trait definition has no such supertrait"
+            );
+        }
+    }
+
     let mut type_converter = TypeConverter {
         collections: method_attrs.collections,
         assoc_type_conversions: HashMap::new(),
@@ -185,11 +194,20 @@ pub fn dynamize(_attr: TokenStream, input: TokenStream) -> TokenStream {
         supertraits: original_trait
             .supertraits
             .iter()
-            .filter(|t| match t {
-                TypeParamBound::Trait(t) => !t.path.is_ident("Sized"),
-                TypeParamBound::Lifetime(_) => true,
+            .filter_map(|t| {
+                if let TypeParamBound::Trait(trait_bound) = t {
+                    if let Some(ident) = trait_bound.path.get_ident() {
+                        if ident == "Sized" {
+                            return None;
+                        } else if method_attrs.dynamized_supertraits.contains(ident) {
+                            let mut bound = trait_bound.clone();
+                            bound.path.segments[0].ident = format_ident!("Dyn{}", ident);
+                            return Some(TypeParamBound::Trait(bound));
+                        }
+                    }
+                }
+                Some(t.clone())
             })
-            .cloned()
             .collect(),
         brace_token: Brace::default(),
         items: Vec::new(),
diff --git a/src/parse_attrs.rs b/src/parse_attrs.rs
index 03b5377..d9725ff 100644
--- a/src/parse_attrs.rs
+++ b/src/parse_attrs.rs
@@ -1,4 +1,4 @@
-use std::collections::HashMap;
+use std::collections::{HashMap, HashSet};
 
 use proc_macro2::{Group, TokenStream};
 use quote::quote;
@@ -33,6 +33,21 @@ pub struct TraitAttrs {
     pub blanket_impl_attrs: Vec<TokenStream>,
     pub dyn_trait_attrs: Vec<TokenStream>,
     pub collections: HashMap<Ident, usize>,
+    pub dynamized_supertraits: HashSet<Ident>,
+}
+
+struct Dynamized {
+    ident: Ident,
+}
+
+impl Parse for Dynamized {
+    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        let inner;
+        parenthesized!(inner in input);
+        Ok(Self {
+            ident: inner.parse()?,
+        })
+    }
 }
 
 impl TraitAttrs {
@@ -84,6 +99,18 @@ impl TraitAttrs {
                         ),
                     ));
                 }
+            } else if attrs[i].path.is_ident("dynamized") {
+                let attr = attrs.remove(i);
+                let dynamized: Dynamized = syn::parse2(attr.tokens)?;
+                let span = dynamized.ident.span();
+
+                if !parsed.dynamized_supertraits.insert(dynamized.ident) {
+                    // FUTURE: relax to warning once proc_macro::Diagnostic is stable
+                    return Err(Error::new(
+                        span,
+                        format_args!("dynamized attribute is defined multiple times"),
+                    ));
+                }
             } else {
                 i += 1;
             }
diff --git a/tests/tests.rs b/tests/tests.rs
index e3df519..b75c605 100644
--- a/tests/tests.rs
+++ b/tests/tests.rs
@@ -282,3 +282,18 @@ trait CustomCollection {
 
     fn test(&self) -> MyCollection<Self::A, Self::A, Self::A>;
 }
+
+#[dynamize::dynamize]
+trait SomeTrait {
+    type Y: Into<String>;
+
+    fn foo(&self) -> Self::Y;
+}
+
+#[dynamize::dynamize]
+#[dynamized(SomeTrait)]
+trait OneTrait: SomeTrait {
+    type X: Into<String>;
+
+    fn test(&self) -> Self::X;
+}
diff --git a/ui-tests/src/bin/attr_dynamized_duplicate.rs b/ui-tests/src/bin/attr_dynamized_duplicate.rs
new file mode 100644
index 0000000..fbe0d11
--- /dev/null
+++ b/ui-tests/src/bin/attr_dynamized_duplicate.rs
@@ -0,0 +1,6 @@
+#[dynamize::dynamize]
+#[dynamized(Foo)]
+#[dynamized(Foo)]
+trait Trait {}
+
+fn main() {}
diff --git a/ui-tests/src/bin/attr_dynamized_duplicate.stderr b/ui-tests/src/bin/attr_dynamized_duplicate.stderr
new file mode 100644
index 0000000..6ff67d1
--- /dev/null
+++ b/ui-tests/src/bin/attr_dynamized_duplicate.stderr
@@ -0,0 +1,5 @@
+error: dynamized attribute is defined multiple times
+ --> src/bin/attr_dynamized_duplicate.rs:3:13
+  |
+3 | #[dynamized(Foo)]
+  |             ^^^
diff --git a/ui-tests/src/bin/attr_dynamized_no_such_supertrait.rs b/ui-tests/src/bin/attr_dynamized_no_such_supertrait.rs
new file mode 100644
index 0000000..5b298ea
--- /dev/null
+++ b/ui-tests/src/bin/attr_dynamized_no_such_supertrait.rs
@@ -0,0 +1,5 @@
+#[dynamize::dynamize]
+#[dynamized(Foo)]
+trait Trait {}
+
+fn main() {}
diff --git a/ui-tests/src/bin/attr_dynamized_no_such_supertrait.stderr b/ui-tests/src/bin/attr_dynamized_no_such_supertrait.stderr
new file mode 100644
index 0000000..b7e4cf4
--- /dev/null
+++ b/ui-tests/src/bin/attr_dynamized_no_such_supertrait.stderr
@@ -0,0 +1,5 @@
+error: this trait definition has no such supertrait
+ --> src/bin/attr_dynamized_no_such_supertrait.rs:2:13
+  |
+2 | #[dynamized(Foo)]
+  |             ^^^
-- 
cgit v1.2.3