Handling of generic classes

Issue #47 resolved
Nicholas Oxhøj
created an issue

Hi

If I have a class like

namespace TypeLiteTest
{
    using System.Collections.Generic;
    using TypeLite;

    [TsClass]
    public class Container<T>
    {
        public List<T> Elements { get; set; }
    }
}

I will get TypeScript declarations like

declare module TypeLiteTest {
interface Container {
  Elements: TypeLiteTest.T[];
}
interface T {
}
}

This doesn't seem to make much sense. Instead I would have expected to get a generic TypeScript declaration, something like

interface Container<T> {
    Elements: T[];
}

Comments (19)

  1. Simon Lovely

    I've created a fork (https://bitbucket.org/slovely/typelite/commits/all) that improves the handling of generics - it doesn't deal with everything yet, but can act as a starting point if it looks ok?

    For a class like:

    class GenerateSpecifyGenericTypesTestClass {
        public KeyValuePair<string, int> StringToInt { get; set; }
    }
    

    it will generate:

    interface GenerateSpecifyGenericTypesTestClass {
      StringToInt: System.Collections.Generic.KeyValuePair<string, number>;
    }
    
    declare module System.Collections.Generic {
    interface KeyValuePair<TKey,TValue> {
      Key: TKey;
      Value: TValue;
    }
    }
    

    There are a few scenarios not yet covered that I've created failing (currently skipped) tests for (see GenericsTests.cs), but I don't think it will be too much work to fix those.

    Let me know if you are interested in a pull request - I warn you, it would be my first on BitBucket and using HG but hopefully I can figure it out! :-)

  2. Simon Lovely

    This fork has been updated now to support enumerable generic types e.g.

    public List<KeyValuePair<int, string>> ListOfIntToString { get; set; }
    

    will generate this:

    ListOfIntToString: System.Collections.Generic.KeyValuePair<number,string>[]
    

    There is also a possible fix in this fork for issue #51 too (Generation of 'array of array' broken).

  3. Robert Birmitt

    Hey,

    good work Simon. For most cases it works very well.

    But a support for inheritance of generic classes, like the following, would be nice.

        class TestC : TestB<int, string>
        {
            public object Data { get; set; }
        }
    
        class TestB<TKey, TValue> : TestA<TValue>
        {
            public TKey Key { get; set; }
        }
    
        class TestA<TValue>
        {
            public TValue Value { get; set; }
        }
    

    Currently the generic arguments of base class will be ignored, although there are specified in c#. That causes type script generating compiling errors, because the arguments are expected.

  4. Simon Lovely

    Thanks Robert,

    Hmm, I've not had a need for generic inheritance yet... might be a bit tricky to do. I'm thinking for my use-case that the extension points in TypeLite don't quite provide enough information (e.g. the TypeNameFormatter only supplies the string name - I'd really find a MemberInfo useful). Also some way of adding properties that only exist in JS would be nice. I'm just not sure what the right way to approach these larger changes in an oss friendly way is, when Lukas has done such a great job already?

  5. Lukas Kabrt

    Just to note ... support for the generics is on the top of my TODO list, but it is quite complex task (as you might have already discovered :-) )

    I am watching this conversation, but I don't have time to test suggested solutions or to implement my own. Hopefully after the summer I will be able to spent more time on this project again.

  6. Simon Lovely

    Thanks for the update Lukas! Fully understand that, my fork currently does enough for my purposes, but would like to make it more robust then hopefully some of it will be useful to you. Please let me know if I can assist at all.

  7. Simon Lovely

    Lukas,

    Turned out that we really needed better support for generics from TypeLite, so I ditched my previous fork and started again from the latest commit. It now covers the scenario @Robert Birmitt mentions above with a class that extends a generic class, and supports nested generic classes/properties. The fork contains a number of tests of various complexity, but as an example the following c# definition:

    private class ClassWithComplexNestedGenericProperty {
        public Tuple<KeyValuePair<int, string>, BaseGeneric<string>, decimal, KeyValuePair<int, DummyNamespace.Test>> GenericsHell { get; set; }
    }
    

    ... generates the expected TypeScript of:

    declare module TypeLite.Tests.GenericsTests {
    interface ClassWithComplexNestedGenericProperty {
      GenericsHell: System.Tuple<System.Collections.Generic.KeyValuePair<number, string>, TypeLite.Tests.GenericsTests.BaseGeneric<string>, number, System.Collections.Generic.KeyValuePair<number, DummyNamespace.Test>>;
    }
    

    (The other references classes are generated correctly also, but I excluded from the comment, see here for the full output).

    Fork is here: https://bitbucket.org/slovely/typelite/

    Would love to be able to get this back into your repository, if you are interested.

    Simon.

  8. Simon Lovely

    Thanks Lukas! Do you need me to create a pull request or anything?

    There are some issues with nested generic collections I found after my post. So this KeyValuePair<int, List<string>> doesn't generate quite right, it misses the "[]" from the string parameter. Hoping to get to look at that in the next couple of days.

    The implementation might not be the best way, but it did involve the least changes to the existing code. Please let me know if you would like me to change anything!

  9. Lukas Kabrt repo owner

    No need to create a pull request, I have already merged your fork.

    KeyValuePair<int, List<string>> is probably some corner case and I haven't even caught that. I am going to file it as a bug so it isn't forgotten. but version 1.0 is already out :-)

    Thanks for your contribution.

  10. Simon Lovely

    Great stuff, thanks Lukas!

    Please let me know if there's anything else I can look at (I am thinking of creating something to allow generation of server methods, for example, TypeScript wrappers around WebAPI actions). Will let you know if I can make something work!

    Thanks for the awesome library, really helping making TypeScript better.

  11. Robert Birmitt

    Hey Simon,

    good job. Now I get the correct definition for my previously posted sample classes, great.

    But I have found (hopefully) a little bug.

    ClassB defines the generic parameter T which is passed to the base class ClassA.

    ClassA defines the generic paramter TValue. So in this inheritance T is TValue . That's no problem in C# .

    In the TypeScript definition ClassB "tries" to pass the not existing paramater TValue to ClassA. In fact it should pass the parameter T.

    Do you know what I mean?

        class TestB<TKey, T> : TestA<T>
        {
            public TKey Key { get; set; }
        }
    
        class TestA<TValue>
        {
            public TValue Value { get; set; }
        }
    
      interface TestB<TKey, T> extends TestA<TValue> {
        Key: TKey;
      }
      interface TestA<TValue> {
        Value: TValue;
      }
    
  12. Simon Lovely

    Hi @Robert Birmitt - yes, I know what you mean. I've had a quick look at this and think I've created a suitable fix here: https://bitbucket.org/slovely/typelite/commits/3b3e26396572f5f0fa0206e6f591bb3c668bd49d?at=default

    This creates output like:

    declare module TypeLite.Tests.GenericsTests {
        interface DerivedClassWithNameGenericParameterName<TNewParam, TSomethingNew> extends TypeLite.Tests.GenericsTests.BaseGeneric<TSomethingNew> {
            SomeProperty: TNewParam;
        }
        interface BaseGeneric<TType> {
            SomeGenericProperty: TType;
            SomeGenericArrayProperty: TType[];
        }
    }
    

    @Lukas Kabrt - let me know if that is suitable and you want a PR

    Thanks, Simon.

  13. Robert Birmitt

    Hey @Simon Lovely, sorry for my late response. You have fixed it very well.

    I have found out, that if I'm using the "TsTypeFormatter" with "withFormatter", all generic parameters are ignored in the class declaration. However they are in the property declaration.

    var ts = TypeScript.Definitions()
            .ForLoadedAssemblies()
            .WithReference("Enums.ts")
            .WithIndentation("  ")
            .WithFormatter((type, f) => "I" + ((TypeLite.TsModels.TsClass)type).Name);
    

    I have this from this toturial http://type.litesolutions.net/Tutorials/Customization.

    class TestB<TKey, T> : TestA<T>
    {
        public TKey Key { get; set; }
    }
    
    class TestA<TValue>
    {
        public TValue Value { get; set; }
    }
    
    interface ITestB extends ITestA {
        Key: ITKey;
    }
    interface ITestA {
        Value: ITValue;
    }
    
  14. James Jensen

    @Robert Birmitt I was able to get the output correct using the following code:

        .WithTypeFormatter((type, f) => {
                var classType = (TypeLite.TsModels.TsClass)type;
                var interfaceName = "I" + classType.Name;
                var genericSignature = classType.GenericArguments.Any() ? $"<{string.Join(", ", classType.GenericArguments.Select(f.FormatType))}>" : "";
                return interfaceName + genericSignature;
                })
    

    @Lukas Kabrt Is there a more elegant way to go about this? Should we update the tutorial accordingly? Maybe we sh

  15. Log in to comment