Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fully qualify Static associated type in ToStatic derive macro #93

Merged
merged 2 commits into from
Sep 13, 2024

Conversation

xkikeg
Copy link
Contributor

@xkikeg xkikeg commented Sep 10, 2024

When the compiler cannot deduce the actual type on expanding derive(ToStatic) macro, it can have an error because T::Static can be ambiguous. This could be solved with qualifying like <T as IntoBoundedStatic>::Static.

Context:
Currently I'm working on applying Generic Associated Type to my struct, and then the compiler complies that ::Static is ambiguous. I suppose this could be solved by fully qualifying the Static type association.

See xkikeg/okane#183 for context.

@xkikeg
Copy link
Contributor Author

xkikeg commented Sep 11, 2024

Minimal reproducible code:

#[derive(ToStatic)]
pub struct Foo<T: bounded_static::IntoBoundedStatic + bounded_static::ToBoundedStatic>(T);

@xkikeg
Copy link
Contributor Author

xkikeg commented Sep 11, 2024

I added a test case above, which failed for some recursive types.

78 |     #[derive(ToStatic)]
   |              ^^^^^^^^ the trait `ToBoundedStatic` is not implemented for `<T as IntoBoundedStatic>::Static`
79 |     struct Foo<T: IntoBoundedStatic + ToBoundedStatic>(T);
   |            --- required by a bound introduced by this call
   |
note: required by a bound in `test_struct_named_fields_bounded_generics::Foo`
  --> bounded-static-derive/tests/integration_tests.rs:79:39
   |
79 |     struct Foo<T: IntoBoundedStatic + ToBoundedStatic>(T);
   |                                       ^^^^^^^^^^^^^^^ required by this bound in `Foo`

However, I cannot add + IntoBoundedStatic or the other into Static: ... associated type to cope up with this error,
because this will cause infite recursion on HashMap like object.

@fujiapple852
Copy link
Owner

fujiapple852 commented Sep 11, 2024

@xkikeg I like the idea of fully qualifying the Static associated type.

When I expand the test case with cargo expand:

#[derive(ToStatic)]
struct Foo<T: IntoBoundedStatic + ToBoundedStatic>(T);

I get:

struct Foo<T: IntoBoundedStatic + ToBoundedStatic>(T);
impl<T: IntoBoundedStatic + ToBoundedStatic + ToBoundedStatic> ToBoundedStatic for Foo<T>
where
    T::Static: IntoBoundedStatic + ToBoundedStatic,
{
    type Static = Foo<<T as ToBoundedStatic>::Static>;
    fn to_static(&self) -> Self::Static {
        Foo(self.0.to_static())
    }
}
impl<T: IntoBoundedStatic + ToBoundedStatic + IntoBoundedStatic> IntoBoundedStatic for Foo<T>
where
    T::Static: IntoBoundedStatic + ToBoundedStatic,
{
    type Static = Foo<<T as IntoBoundedStatic>::Static>;
    fn into_static(self) -> Self::Static {
        Foo(self.0.into_static())
    }
}

When still fails with (different to the error you reported):

error[E0221]: ambiguous associated type `Static` in bounds of `T`
  --> bounded-static-derive/tests/integration_tests.rs:84:9
   |
84 |         T::Static: IntoBoundedStatic + ToBoundedStatic,
   |         ^^^^^^^^^ ambiguous associated type `Static`
   |
   = note: associated type `Static` could derive from `ToBoundedStatic`
   = note: associated type `Static` could derive from `IntoBoundedStatic`

It works if I do:

  struct Foo<T: IntoBoundedStatic + ToBoundedStatic>(T);
  impl<T: IntoBoundedStatic + ToBoundedStatic + ToBoundedStatic> ToBoundedStatic for Foo<T>
  where
      <T as ToBoundedStatic>::Static: IntoBoundedStatic + ToBoundedStatic,
  {
      type Static = Foo<<T as ToBoundedStatic>::Static>;
      fn to_static(&self) -> Self::Static {
          Foo(self.0.to_static())
      }
  }
  impl<T: IntoBoundedStatic + ToBoundedStatic + IntoBoundedStatic> IntoBoundedStatic for Foo<T>
  where
      <T as IntoBoundedStatic>::Static: IntoBoundedStatic + ToBoundedStatic,
  {
      type Static = Foo<<T as IntoBoundedStatic>::Static>;
      fn into_static(self) -> Self::Static {
          Foo(self.0.into_static())
      }
  }

However it is not clear if it should be <T as ToBoundedStatic>::Static: or <T as IntoBoundedStatic>::Static: or in fact both?

@fujiapple852
Copy link
Owner

fujiapple852 commented Sep 11, 2024

I think this test case also highlights a bug; note the duplicate bound on:

impl<T: IntoBoundedStatic + ToBoundedStatic + IntoBoundedStatic> IntoBoundedStatic for Foo<T>

Even if we simplify the test case to be:

#[derive(ToStatic)]
struct Foo<T: ToBoundedStatic>(T);

We still get:

impl<T: ToBoundedStatic + ToBoundedStatic> ToBoundedStatic for Foo<T>
where
    <T as ToBoundedStatic>::Static: ToBoundedStatic,
{
    type Static = Foo<<T as ToBoundedStatic>::Static>;
    fn to_static(&self) -> Self::Static {
        Foo(self.0.to_static())
    }
}
impl<T: ToBoundedStatic + IntoBoundedStatic> IntoBoundedStatic for Foo<T>
where
    <T as IntoBoundedStatic>::Static: ToBoundedStatic,
{
    type Static = Foo<<T as IntoBoundedStatic>::Static>;
    fn into_static(self) -> Self::Static {
        Foo(self.0.into_static())
    }
}

The macro should probably have special handling to filter out the duplicate trait bounds.

@fujiapple852
Copy link
Owner

fujiapple852 commented Sep 11, 2024

I tried adding TargetTrait to make_static_generic_predicates:

fn make_static_generic_predicates(generics: &Generics, target: TargetTrait) -> Vec<WherePredicate> {
let target_bound = target.bound();
generics
    .params
    .iter()
    .filter_map(|param| match param {
        GenericParam::Type(param_ty) => {
            let var = &param_ty.ident;
            let param_ty_bounds = &param_ty.bounds;
            match find_predicate(generics.where_clause.as_ref(), var) {
                None if param_ty_bounds.is_empty() => None,
                None => Some(parse_quote!(<#var as #target_bound>::Static: #param_ty_bounds)),
                Some(predicate_ty) => {
                    let predicate_bounds = &predicate_ty.bounds;
                    if param_ty_bounds.is_empty() {
                        Some(parse_quote!(<#var as #target_bound>::Static: #predicate_bounds))
                    } else {
                        Some(parse_quote!(<#var as #target_bound>::Static: #param_ty_bounds + #predicate_bounds))
                    }
                }
            }
        }
        _ => None,
    })
    .collect()
}

Then this test case works:

#[test]
fn test_struct_named_fields_bounded_generics() {
    #[derive(ToStatic)]
    struct Foo<T: IntoBoundedStatic + ToBoundedStatic>(T);
    let inner = "foo";
    let owned = Foo(inner).into_static();
    ensure_static(owned);
}

Which expands as:

impl<T: IntoBoundedStatic + ToBoundedStatic + ::bounded_static::ToBoundedStatic>
    ::bounded_static::ToBoundedStatic for Foo<T>
where
    <T as ToBoundedStatic>::Static: IntoBoundedStatic + ToBoundedStatic,
{
    type Static = Foo<<T as ToBoundedStatic>::Static>;
    fn to_static(&self) -> Self::Static {
        Foo(self.0.to_static())
    }
}
impl<T: IntoBoundedStatic + ToBoundedStatic + ::bounded_static::IntoBoundedStatic>
    ::bounded_static::IntoBoundedStatic for Foo<T>
where
    <T as IntoBoundedStatic>::Static: IntoBoundedStatic + ToBoundedStatic,
{
    type Static = Foo<<T as IntoBoundedStatic>::Static>;
    fn into_static(self) -> Self::Static {
        Foo(self.0.into_static())
    }
}

@fujiapple852 fujiapple852 force-pushed the fix/use_qualified_static branch from 466a0a0 to 62f8d48 Compare September 11, 2024 11:01
@fujiapple852
Copy link
Owner

Pushed an update with my change. Note that the documentation and examples will need to be updated as well.

@xkikeg
Copy link
Contributor Author

xkikeg commented Sep 12, 2024

Thanks for taking & fixing the issue!

When the compiler cannot deduce the actual type on expanding
derive(ToStatic) macro, it can have an error because T::Static
can be ambiguous. This could be solved with qualifying like
<T as IntoBoundedStatic>::Static.
@fujiapple852 fujiapple852 force-pushed the fix/use_qualified_static branch from 62f8d48 to 34294f8 Compare September 12, 2024 09:06
@fujiapple852 fujiapple852 force-pushed the fix/use_qualified_static branch from 34294f8 to d3ee256 Compare September 12, 2024 09:18
@fujiapple852
Copy link
Owner

@xkikeg I think this is ready to merge; I trust it works for your use case?

@fujiapple852 fujiapple852 changed the title Quality ::Static associated type in ToStatic derive macro implementation. Fully qualify Static associated type in ToStatic derive macro Sep 12, 2024
@xkikeg
Copy link
Contributor Author

xkikeg commented Sep 12, 2024

I briefly checked and mostly looked good to me, but maybe ToStatic derive may not be working for the case ToBoundedStatic/IntoBoundedStatic traits are not used. Let me check tomorrow when I have a time

@xkikeg
Copy link
Contributor Author

xkikeg commented Sep 13, 2024

LGTM with 2 above comments

use std::borrow::Cow;

use bounded_static::ToStatic;

#[derive(ToStatic)]
struct Foo<T: bounded_static::IntoBoundedStatic + bounded_static::ToBoundedStatic>(T);

#[test]
fn test() {
    let v: Foo<_> = Foo(Cow::Borrowed("foo"));
    use bounded_static::ToBoundedStatic;
    v.to_static();
}

If you have this test (without using Into/ToBoundedStatic) it can be covered.

@fujiapple852 fujiapple852 merged commit cccc9fa into fujiapple852:master Sep 13, 2024
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants