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

Proposal: Infer generic type based on target #5429

Closed
HaloFour opened this issue Sep 24, 2015 · 13 comments
Closed

Proposal: Infer generic type based on target #5429

HaloFour opened this issue Sep 24, 2015 · 13 comments
Labels
Area-Language Design Discussion Need More Info The issue needs more information to proceed.

Comments

@HaloFour
Copy link

Problem

When calling generic methods where the generic type parameter is only used as the return value of the method there is currently no inference which leads to repetition.

DataTable table = ...;
var people = table.Rows
    .Select(row => new Person {
        Id = row.Field<int>("id"),
        Name = row.Field<string>("name"),
        Birthdate = row.Field<DateTime>("dob")
    })
    .ToList();

Proposal

It would be convenient if in the specific case of the generic type parameter being the return type if the compiler could infer the generic type argument based on the destination of the result. This would only be permitted if the method is called in an unambiguous fashion. The return value must be assigned to a destination with an explicit type and there can be no other non-generic methods with the same name and parameters.

int id = row.Field("id"); // legal, generic type argument inferred to be int
var id = row.Field("id"); // not legal, circular inference
row.Field("id"); // not legal, no assignment to a type
@orthoxerox
Copy link
Contributor

Is it really that much easier/neater to write the first line instead of the second?

LocationComponent loс = entity.Get();
//vs
var loc = entity.Get<LocationComponent>();

I personally feel betrayed by the compiler every time I have to write the type of a local instead of using var (out parameters, conditional assignment etc). :)

@alrz
Copy link
Member

alrz commented Sep 25, 2015

Additional use case in properties and/or methods based on return type:

 // calls fields.Get<int>("Id") utilizing CallerMemberNameAttribute
public int Id => fields.Get();

@HaloFour
Copy link
Author

@orthoxerox

In that scenario it's definitely not an improvement. I'm thinking specifically of scenarios where the target is already defined, whether it be an existing variable or a property. The inspiration for this is in the proposal itself, someone I work with complaining about having to explicitly specify the generic type argument when calling the DataRow.Field<T> extension method when the result was being directly assigned to a property in an object initializer.

@dsaf
Copy link

dsaf commented Sep 25, 2015

What if there is a non-generic overload of the Field method? Would you do what you have suggested in #2319 (which is somewhat related):

new Something
{
    Id = row.Field<>("id"),
    ObjValue = row.Field("obj")
}

@dsaf
Copy link

dsaf commented Sep 25, 2015

I think this proposal could make a good impact when using mocking frameworks such as NSubstitute and FakeItEasy:

A.CallTo(() => foo.Bar("hello", A<int>.Ignored)).MustHaveHappened();

calculator.Add(Arg.Any<int>(), 5).Returns(7);

vs

A.CallTo(() => foo.Bar("hello", A.Ignored)).MustHaveHappened();

calculator.Add(Arg.Any(), 5).Returns(7);

@alrz
Copy link
Member

alrz commented Oct 17, 2015

Also for default(T) this could be useful.

obj match(... case * : default);

@gafter gafter added the Need More Info The issue needs more information to proceed. label Oct 21, 2015
@gafter
Copy link
Member

gafter commented Oct 21, 2015

I marked this as "Need More Info", as we'd like you to suggest what modifications to the type inference specification you'd like to see to address this.

@HaloFour
Copy link
Author

HaloFour commented Dec 3, 2015

@gafter

I'll try to do my best to hammer that out. I don't think I've seen any examples in the spec of type inference occurring in this direction so I don't know how well it can be accomplished through minor updates to the spec. Spec-writing is far from my strong-suit so please be patient with me. 😁

Under §7.5.2:

If the supplied number of arguments is different than the number of parameters and the return value in the method, then inference immediately fails. Otherwise, assume that the generic method has the following signature:

Tr M<X1…Xn>(T1 x1 … Tm xm)

With a method call of the form Er = M(E1 …Em) the task of type inference is to find unique type arguments S1…Sn for each of the type parameters X1…Xn so that the call Er = M<S1…Sn>(E1…Em) becomes valid.

The use of the assignment operator is only to indicate that the assignment of the return value is to play a part in the type inference. That may not be the best syntax but the goal is to indicate that as long as the type of the target of the assignment is known that it can be used for type inference. The simplest scenario would be when the return value is being assigned to an explicitly typed variable, field or property. Expanding that to method arguments would be ideal but I can see situations where that would result in circular inference.

Under §7.5.2.1:

For the method return value Er:

  • If the value to which Er is being assigned to has a type U then an upper-bound inference is made from U to Tr.

I'm not sure changes to the other section would apply as I don't intend to alter how existing type inference works, only apply the target type in the cases where current inference would fail. For example:

// given:
T M1<T>(T x1) { ... };
T M2<T>(params T[] x) { ... }
TR M3<T, TR>(T x) { ... }

// infers M1<string>, compiler error
int x1 = M1("foo");

// infers M2<int>
int x2 = M2();

// infers M2<string>, compiler error
int x3 = M2("foo");

// infers M3<string, int>
int x4 = M3("foo");

@rsheptolut
Copy link

My 2 cents. I don't think this should be all about assignments, however. Consider the following example:

public class Test
{
    public void Test()
    {
        // Would be great to avoid specifying <string> to that method over here too
        Method(ExampleGetDefault());
    }

    private T ExampleGetDefault<T>() { return default(T); }

    private void Method(string param) { /* ... */ }
}

The above code should infer T based on the return type expected during static overload resolve. If there would have been two overloads of Method(), then the compiler should have issued an ambiguous match exception.

What I mean is: generic parameters should be inferred based on return type as they are now inferred based on parameter types. If T is used as a return type only, it should use "the type expected by the parent expression" to infer T. And this includes: assignment (type of a field, property or a local variable), returning a value (return type of a current method or property), calling a method (if and only if this parameter has the same type in all of the overloads).

@HaloFour
Copy link
Author

@rsheptolut

I agree, I'd treat assignment to an argument of a method call the same as I would an assignment to a property or a variable. My only concern is that methods open up a can of worms as the generic type inference will have to deal with overloads and generic method calls as well. The easy answer there would be to fail inference if there is any potential ambiguity like that. This is what Java does. But Java doesn't have to deal with inference in both directions.

@alrz
Copy link
Member

alrz commented Mar 29, 2016

Somehow unrelated but I wonder why this can't be inferred,

void M(object o) {}
void F<T>(Action<T> a) {}

F((object o) => {}); // fine
F(M);  // nope

Is there a specific reason?

EDIT: Probably because M is a method group and could have been overloaded.

@gafter
Copy link
Member

gafter commented Mar 24, 2017

We are now taking language feature discussion in other repositories:

Features that are under active design or development, or which are "championed" by someone on the language design team, have already been moved either as issues or as checked-in design documents. For example, the proposal in this repo "Proposal: Partial interface implementation a.k.a. Traits" (issue 16139 and a few other issues that request the same thing) are now tracked by the language team at issue 52 in https://github.com/dotnet/csharplang/issues, and there is a draft spec at https://github.com/dotnet/csharplang/blob/master/proposals/default-interface-methods.md and further discussion at issue 288 in https://github.com/dotnet/csharplang/issues. Prototyping of the compiler portion of language features is still tracked here; see, for example, https://github.com/dotnet/roslyn/tree/features/DefaultInterfaceImplementation and issue 17952.

In order to facilitate that transition, we have started closing language design discussions from the roslyn repo with a note briefly explaining why. When we are aware of an existing discussion for the feature already in the new repo, we are adding a link to that. But we're not adding new issues to the new repos for existing discussions in this repo that the language design team does not currently envision taking on. Our intent is to eventually close the language design issues in the Roslyn repo and encourage discussion in one of the new repos instead.

Our intent is not to shut down discussion on language design - you can still continue discussion on the closed issues if you want - but rather we would like to encourage people to move discussion to where we are more likely to be paying attention (the new repo), or to abandon discussions that are no longer of interest to you.

If you happen to notice that one of the closed issues has a relevant issue in the new repo, and we have not added a link to the new issue, we would appreciate you providing a link from the old to the new discussion. That way people who are still interested in the discussion can start paying attention to the new issue.

Also, we'd welcome any ideas you might have on how we could better manage the transition. Comments and discussion about closing and/or moving issues should be directed to #18002. Comments and discussion about this issue can take place here or on an issue in the relevant repo.

I am not moving this particular issue because I don't have confidence that the LDM would likely consider doing this. The way the language specification (and therefore compiler) is structured, the type of the target of the assignment of an invocation isn't available when type inference is performed. Target typing in C# works in a completely different way than imagined by this request.

@gafter gafter closed this as completed Mar 24, 2017
@gafter
Copy link
Member

gafter commented Mar 24, 2017

Discussion can continue at dotnet/csharplang#92 .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Language Design Discussion Need More Info The issue needs more information to proceed.
Projects
None yet
Development

No branches or pull requests

7 participants