Giter Site home page Giter Site logo

Comments (13)

NyxCode avatar NyxCode commented on June 12, 2024 1

Awesome, thanks! I think the best place to add it is here. If there's a #[ts(bound)], then we can just skip the bound-generation we do in generate_where_clause.

from ts-rs.

NyxCode avatar NyxCode commented on June 12, 2024

I'm a bit confused why this doesn't need a where D::Info: TS bound.
Is that implied by the fact that the struct contains an Inner<D>, which has the D::Info: TS, and the bound is transferred to Outer?

from ts-rs.

NyxCode avatar NyxCode commented on June 12, 2024

Ah, never mind - thought i was losing my mind here for a second.
That D::Info: TS bound is indeed still required. I've updated the Issue.

from ts-rs.

WilsonGramer avatar WilsonGramer commented on June 12, 2024

@NyxCode You're right, I meant to write this:

impl ts_rs::TS for Outer<TsDriver>
where /* no bounds */
{
    ...
}

...because D is substituted for TsDriver with #[ts(concrete(D = TsDriver), bound = "/* no bounds */"]. You're right that without concrete, the bound would be needed. I might be missing something though.

from ts-rs.

WilsonGramer avatar WilsonGramer commented on June 12, 2024

Actually it looks like #264 preserves the type parameter in the impl block — is that the correct behavior?

#[derive(TS)]
#[ts(export, concrete(D = TsDriver))]
struct Outer<D: Driver> {
    inner: Inner<D>,
}

Generates this:

impl<D: Driver> ts_rs::TS for Outer<D>
where
    D: ts_rs::TS,
{
    type WithoutGenerics = Outer<TsDriver>;

    ...
}

But shouldn't it generate this?

impl ts_rs::TS for Outer<TsDriver>
{
    ...
}

from ts-rs.

NyxCode avatar NyxCode commented on June 12, 2024

Ah, alright, now I see where the confusion came from.

So the original concrete branch started out with impl<D: Driver> TS for MyStruct<D>.
That worked, but required this annoying type Info: TS bound in the definition of Driver.

Then, we tried actually generating impl TS for MyStruct<TsDriver>. However, we hit a hard wall going down that road. If you're interested in the details, see #264. The gist of it is that we generated <TsDriver::Info as TS>::name() inside the impl, which the trait solver refuses to accept. We'd have had to generate <<TsDriver as Driver>::Info as TS>::name() instead, but that's not possible inside the proc macro (as soon as there's more than one trait bound on D)1.

Therefore, we settled on generating impl<D: Driver> TS for MyStruct<D> where D::Info: TS. That gives us all the benifits we'd have gotten with the other approach (namely, get rid of the type Info: TS bound).

Everything works like it was an impl TS for MyStruct<TsDriver>. However, MyStruct<OtherDriver> still implements TS if OtherDriver::Info: TS. If you'd call MyStruct<OtherDriver>::decl() though, it'll behave as-if it was an MyStruct<TsDriver>.

So the tl;dr why it didnt work is: If you access an associated type of a concrete type (e.g TsDriver::Info), Rust doesn't infer which trait it should use to get Info. That makes sense, since it's possible that TsDriver implemented two traits with an associated type Info.

Footnotes

  1. https://github.com/Aleph-Alpha/ts-rs/pull/264#issuecomment-1991687523

from ts-rs.

WilsonGramer avatar WilsonGramer commented on June 12, 2024

@NyxCode What do you think about requiring that the associated types be spelled out completely (ie. <X as T>::Y)?

#[derive(TS)]
#[ts(export, concrete(D = TsDriver))]
struct Inner<D: Driver> {
    info: <D as Driver>::Info,
}

Then I believe Rust would raise an error whenever it can't determine which associated type to use. That would also solve the issue of multiple trait bounds.

from ts-rs.

NyxCode avatar NyxCode commented on June 12, 2024

Absolutely, that'd have worked! With the codegen as-is, which does <{field_type} as TS>::name(), that'd have generated <<D as Driver>::Info as TS>::name() - exactly what we tried to do "by hand".

We didn't see any downside with generating impl<D: Driver> TS for MyStruct<D> where D::Info, and it doesn't require that you touch the type definition at all.

from ts-rs.

NyxCode avatar NyxCode commented on June 12, 2024

Well, there is one downside, which is that you don't get a compiler error if you do MyStruct::<OtherDriver>::export() if it's #[ts(concrete(D = TsDriver))]. Instead, you just get the definition of MyStruct<TsDriver> according to the #[ts(concrete(..)].

Since (we hope) most users just use #[ts(export)], this seemed like a minor issue.

from ts-rs.

WilsonGramer avatar WilsonGramer commented on June 12, 2024

@NyxCode Got it! I just tested the latest changes on main, but I'm still getting the extra bound when I #[derive(TS)] on Outer:

use ts_rs::TS;

trait Driver {
    type Info: TS;
}

struct MyDriver;

#[derive(TS)]
struct MyInfo;

impl Driver for MyDriver {
    type Info = MyInfo;
}

#[derive(TS)]
#[ts(export, concrete(D = MyDriver))]
struct Inner<D: Driver> {
    info: D::Info,
}

#[derive(TS)]
#[ts(export, concrete(D = MyDriver))]
struct Outer<D: Driver> {
    inner: Inner<D>,
}
  impl<D: Driver> ::ts_rs::TS for Outer<D>
  where
+     D: ::ts_rs::TS,
  {
      ...
  }

Basically, the current implementation requires MyDriver itself implements TS, not just its associated types, since it sees that D is used "directly" in Outer — the goal with #[ts(bound = "")] is to remove this bound (since it's not actually necessary that D itself implements TS, only its associated types).

from ts-rs.

NyxCode avatar NyxCode commented on June 12, 2024

Yup, we're on the same page here!

The tradeoff between the two solutions of generating trait bounds we explored turned out to be:

  • Generate perfect bounds, but fail on self-referential types
  • Overestimate bounds, but allow self-referential types

So you're absolutely right here - The generated bound D: TS is wrong, and it needs to be D::Info: TS instead.
And it needs to be D::Info: TS, since the impl is still generic over <D: Driver>, even with #[ts(concrete)].
And I agree, #[ts(bound = "...")] seems perfect for that.

from ts-rs.

WilsonGramer avatar WilsonGramer commented on June 12, 2024

OK great! I am putting together a bound attribute now and will open a PR soon.

from ts-rs.

WilsonGramer avatar WilsonGramer commented on June 12, 2024

I just implemented the attribute here: main...WilsonGramer:ts-rs:bound-attribute

Let me know what you think!

from ts-rs.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.