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

[Umbrella] Generics Improvements #253

Closed
alrz opened this issue Mar 11, 2017 · 14 comments
Closed

[Umbrella] Generics Improvements #253

alrz opened this issue Mar 11, 2017 · 14 comments

Comments

@alrz
Copy link
Member

alrz commented Mar 11, 2017

default type parameters

Proposal: dotnet/roslyn#6248

class X<TService = DefaultService> {}

Since types with identical names can be defined with different arities, it might be easier to require the diamond syntax (or missing type arguments) to disambiguate type arities. But not requiring it would provide source-compat such that you can add a type parameter with a default value, without changing use sites.

class Y : X {} // X<DefaultService>

One possible restriction to simplify binding rules is to disallow identical type names where there exists a type with default type parameters.

//ERROR
class X {}
class X<T = C> {}

However, even with this restriction there can be ambiguous type names, in those cases we prefer the non-generic type.

namespace N0 { class X {} }
namespace N1 { class X<T = C> {} }
namespace N2 {
    using N0;
    using N1;
    class C : X {} // N0.X
}

If we want to select the default parameter we can use X<> syntax or named type arguments.

We can also disallow leading default type parameters just like method default parameters,

class X<T = C, U> {} // ERROR

this as default type parameter

In some cases it's useful to define a default type parameter of the inheriting type (if any).

interface IEquitable<T = this> {}

class X : IEquitable {} // IEquitable<X>

It'd be nice to also constrain such types to the inheriting type, in case that it's explicitly specified. (#169)

interface IEquitable<T = this> where T : this {}

class X : IEquitable<Y> {} // ERROR

But this would be a C#-only restriction as CLR does not support such constraints. Note that this constraint only applies to the derived types so it might be preferable to restrict it to interfaces and abstract classes.

Named type arguments

Proposal: dotnet/roslyn#6207

Named type arguments are useful for various use cases:

  • When only a few type parameters cannot be inferred.
U F<T, U>(T t) { .. }

var x = F<U:short>(1); // F<int, short>
  • To specify a few default type parameters,
class C<T = X, U = Y> {}

new C<U: Z>();
  • Or just for better readability,
var x = F<TResult: int>();

Again, just like named arguments, named type arguments do not need to be in the same order that they are declared, as long as all non-default type parameters are specified.

void F<T, U, V = C>() {}

F<U: A, T: B>();

It will be possible to mix positional and named type arguments,

F<A, U:B>();

Inline type parameter constraints

Proposal: dotnet/roslyn#7671

With numerous type parameters, type constraints require duplicating every type parameter to the point that it's somehow confusing,

internal abstract class AbstractUseCoalesceExpressionForNullableDiagnosticAnalyzer<
  TSyntaxKind,
  TExpressionSyntax,
  TConditionalExpressionSyntax,
  TBinaryExpressionSyntax,
  TMemberAccessExpression,
  TPrefixUnaryExpressinSyntax> : AbstractCodeStyleDiagnosticAnalyzer
  where TSyntaxKind : struct
  where TExpressionSyntax : SyntaxNode
  where TConditionalExpressionSyntax : TExpressionSyntax
  where TBinaryExpressionSyntax : TExpressionSyntax
  where TMemberAccessExpression : TExpressionSyntax
  where TPrefixUnaryExpressinSyntax : TExpressinSyntax

Inlining type parameters could significantly reduce noise around the type declaration,

internal abstract class AbstractUseCoalesceExpressionForNullableDiagnosticAnalyzer<
  TSyntaxKind : struct,
  TExpressionSyntax : SyntaxNode,
  TConditionalExpressionSyntax : TExpressionSyntax,
  TBinaryExpressionSyntax : TExpressionSyntax,
  TMemberAccessExpression : TExpressionSyntax,
  TPrefixUnaryExpressinSyntax : TExpressionSyntax> : AbstractCodeStyleDiagnosticAnalyzer

This syntax would bring type parameter constraints closer to their declaration,

void F<T, U : IEnumerable<T>>(U source) {}

Inlined type constraints can be mixed with type parameters, e.g.

class IEquitable<T : this = this> {}

Multiple type constraints can be inlined using "intersection types", using whatever syntax that they're going to use,

void F<T : Interface1 | Interface2>() {}

However, it might be actually preferable to only allow "simple" type constraints in this manner.

Inferred type arguments

Even if a type parameter does not have a default, the compiler can infer it in some circumstances,

new Nullable(1); // Nullable<int>
new List { 1 } // List<int>
class X<T> { public static void F(T x) {} }

X.F(1); // X<int>
T F<T>() { .. }

int x = F(); // F<int>
  • Inference from the first usage (locals only)
var x = new List();
x.Add(1); // infers List<int>
  • Inference from assignment
private readonly Dictionary _map = new() { { "key", 1 } }; // Dictionary<string, int>

As for binding, we will prefer non-generic types just like default type parameters described above. So if there exist multiple type with the same name it wouldn't be possible to use type inference in the use-site.

@iam3yal
Copy link
Contributor

iam3yal commented Mar 11, 2017

Related to #151

p.s. The first part of the post reminds me TypeScript's generic defaults. :)

@SamPruden
Copy link

SamPruden commented Mar 12, 2017

I'm the original poster of #169 (this constraint) and it's nice to see that getting a little attention, thanks for including it. I'd avoided talking about defaults because it was a little out of scope and I was considering name collisions to be a pretty big no-go, but if that was in there that would really tidy usage of that feature up and make it nearly seamless.

I'm not entirely clear on the state of the CLR and how feasible requests requiring changes are right now on a short timeframe, but I would prefer to see this implemented as a CLR constraint rather than a C# only one. Does anyone know if they'd consider it a no-go breaking change to implement a feature like this as C# only and then move it to a CLR restriction at a later time? I think that would mostly be fine. (I wasn't entirely sure how on-topic this paragraph could be considered, so I've gone ahead and started a separate discussion about the status of the CLR at #255.)

I also have a few other generics requests lounging about in my head, so if his has a chance to grow into a big discussion on generics I may try to get those posted soon.

@dmitriyse
Copy link

Please also consider to merge this proposal to the umbrella #193
In short words, this simplifies "heavy generic methods" declaration (for very strict typing).

@dmitriyse
Copy link

Please also consider to cover by the umbrella this proposal #266 .

@GeirGrusom
Copy link

Partially inferred generic arguments:

    TResult GetValue<TKey, TResult>(TKey key) { ... }

    var result = GetValue<int>("abc");

alternatively

    var result = GetValue<, int>("abc");

Partially resolved generic aliases:

    using StringDictionary<T> = Dictionary<string, T>;

@alrz
Copy link
Member Author

alrz commented Mar 14, 2017

@GeirGrusom For the first example you could use named type arguments. For generic aliases see #90.

@GeirGrusom
Copy link

@alrz Ah yes, I missed that part.

@gulshan
Copy link

gulshan commented Mar 14, 2017

#268 proposing x is Some<var> i type pattern for generic types.

@gafter
Copy link
Member

gafter commented Mar 14, 2017

Umbrella topics/proposals like this make it difficult for the individual ideas under the umbrella to be considered by the LDM.

@alrz
Copy link
Member Author

alrz commented Mar 14, 2017

Right. I'll try to separate these out into multiple issues.

@weitzhandler
Copy link

@alrz Did you split them up yet? If so, please link them here.

@alrz
Copy link
Member Author

alrz commented May 22, 2017

@weitzhandler #278, #279, #280, #281

@ymassad
Copy link

ymassad commented Apr 14, 2018

@alrz , did you create an issue for "Inference from the first usage"?

@alrz
Copy link
Member Author

alrz commented Apr 14, 2018

@ymassad No, I didn't think that would worth it. If you think it does, feel free to open an issue for it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants