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

Try to infer member and return types if omitted #2996

Closed
rotemdan opened this issue Nov 29, 2019 · 7 comments
Closed

Try to infer member and return types if omitted #2996

rotemdan opened this issue Nov 29, 2019 · 7 comments

Comments

@rotemdan
Copy link

rotemdan commented Nov 29, 2019

Related to #2227, #1129

Since, if I understand correctly, the var keyword has been reserved for implicitly typed variables only, a different keyword might be needed for inferred member, method and local function return types - say, one analogous to auto in C++. One major downside would be that it would result in having two redundant keywords having practically the same semantics.

An alternative approach would be to optionally allow member / return types to be omitted (somewhat analogous to the approach taken by TypeScript). I'll try to explore it in detail here.

Overall, it doesn't seem to conflict with existing syntax, though might require some initial subjective adjustment on the part of the user:

public class MyClass
{
	public publicField = 42; // Field type inferred as int
	public AutoProperty { get; set; } = new List<int>(); // Property type inferred as List<int>
	privateField = 12.5; // Field type inferred as double

	public MyClass() // Constructor is now identified solely by matching its name with the class name
	{
		...
	}

	public SomeMethod(int x) => x * 0.5; // Return type inferred as double
	public SomeOtherMethod(int x, int y) => x * y; // Return type inferred as int
	public SomeProperty => "Hi"; // Property type inferred as string

	PrivateMethod() => DateTime.Now(); // Return type inferred as DateTime

	EmptyMethod() // Return type inferred as void
	{
	}

	AmbiguousMethodReturnType() // Possible error as return type cannot be inferred
	                            // (was the intended return type int or double?)
	{
		if (.. some condition ..)
		{
			return 1;
		}
		else
		{
			return 0.5;
		}
	}

	RecursiveMethod(int count) // Possible error as circular referencing method return type cannot be inferred
	{
		if (.. some condition ..)
		{
			return count;
		}
		else
		{
			return RecursiveMethod(count + 1);
		}
	}
}

Overloaded methods should not be impacted, since return types are not used for overload resolution:

public class MyClass
{
	public OverloadedMethod(int x) => x; // Return type inferred as int
	public OverloadedMethod(int x, int y) => x / (float) y; // Return type inferred as float
}

Static members and operators:

public class MyInt
{
	public Data { get; set; } = 0; // Property type inferred as int

	public static Add(MyInt a, MyInt b) => new MyInt { Data = a.Data + b.Data }; // Method return type inferred as MyInt
	public static operator +(MyInt a, MyInt b) => Add(a, b); // Operator return type inferred as MyInt
}

Indexers:

public class SampleCollection<T>
{
	private arr = new T[100]; // Field type inferred as T[]

	public this[int i] // Indexer type inferred as T
	{
		get { return arr[i]; }
		set { arr[i] = value; }
	}
}

Same applies to struct members, when applicable:

public struct SampleStruct
{
	public int Val { get; set; };
	public ValPlusSomething(int something) => Val + something; // Method return type inferred as int
	public ValPlusOne => ValPlusSomething(1); // Property type inferred as int
}

Analogous syntax could apply to local functions as well, though it might visually appear to resemble function invocation (thus might require some getting used to):

public Main()
{
	LocalFunction() => 5;

	var x = LocalFunction(); // x has type int
}

The non-shorthand form of a local function might require some lookahead to parse (I'm not currently sure if it'd be possible to do so unambiguously):

public Main()
{
	// Can this be easily parsed?
	LocalFunction()
	{
		return 5;
	}

	var x = LocalFunction(); // x has type int
}
@HaloFour
Copy link
Contributor

Unlike local variables class members can forward reference other members which can lead to very complicated circular and ambiguous scenarios. The team has expressed multiple times in the past that it's not worth the effort it would require to put into such a feature. Also, when designing local functions the choice was between allowing for return type inference or hoisting, the latter allowing for recursive scenarios. The team choose hoisting.

https://blogs.msdn.microsoft.com/ericlippert/2009/01/26/why-no-var-on-fields/
dotnet/roslyn#5168

@rotemdan
Copy link
Author

rotemdan commented Nov 29, 2019

I understand the technical challange involved, though I personally feel it would be worth the effort to at least try (I believe the blog post was written before the Roslyn compiler was put to use).

I believe the TypeScript compiler is able to successfuly deal with similar level of circularity, so maybe hope shouldn't be lost.

I guess this suggestion is intended to investigate a possible approach for syntax if at some future point they decide to to retry and tackle the challange.

@HaloFour
Copy link
Contributor

Per the following comment and linked design meeting notes (post-Roslyn) it sounds like the team isn't interested:

dotnet/roslyn#17 (comment)

IMO, technical considerations aside, it's not a good idea. The return type of a member is a part of the public contract of that type. Making it subject to type inference can both make it very difficult to discern what that contract is by reading the source as well as makes it too easy to accidentally change by modifying the body of the member.

@rotemdan
Copy link
Author

rotemdan commented Nov 29, 2019

Well, I guess that's mostly a subjective view (as a longtime TypeScript user I found it to be tremendously practical).

Anyway, I experimented a little with the TypeScript compiler to see how it handles circularity when inferring method return types:

class Test
{
    test1()
    {
        return this.test2();
    }

    test2()
    {
        return this.test1();
    }
}

The error it gives is:

'test1' implicitly has return type 'any' because it 
does not have a return type annotation and is 
referenced directly or indirectly in one of its return expressions.

(a similar error is given for test2)

To be honest, for years using TypeScript I have never actually encountered this error.. :) But it seems like it was able to deal with the situation pretty well so I won't lose hope that C# might be able to do the same one day (I guess..?).

@CyrusNajmabadi
Copy link
Member

Note that TS and C# are very differnet languages. For example, TS types have no actual bearing on runtime behavior. The types can be wrong, or inferred differently, and that won't break the runtime behavior of any existing apps.

That's not hte case with C#/.net

@rotemdan
Copy link
Author

Well, the compiler can always fail to infer the type given it decides it can't figure it out (or it's too expensive or complicated), and require the user to annotate it directly, so the inference is not guaranteed to be consistently available (that's why I titled the issue "Try to infer..").

@rotemdan rotemdan changed the title Try to infer class member types if omitted Try to infer member types if omitted Nov 29, 2019
@rotemdan rotemdan changed the title Try to infer member types if omitted Try to infer member and return types if omitted Nov 29, 2019
@gafter
Copy link
Member

gafter commented Dec 10, 2019

The type of a member is part of its contract in C#, and requiring being explicit about contracts is an intended and desirable design point of C# that we do not intend to change.

@gafter gafter closed this as completed Dec 10, 2019
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

No branches or pull requests

4 participants