-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Stronger inference of a common type of a set of expressions #1419
Comments
As I understand it, you are requesting a modification to the best common type of a set of expressions specification (C# language spec section 7.5.2.14). |
It looks to me like you have two proposals:
The two seem to be completely independent to me, so I think they should have separate issues. |
@dsaf Yes, a separate issue would be a good idea, please. |
I'd rather not see this feature implemented. |
Well, yes this is the most straightforward assumption to make. If you create a list of an apple a banana and an orange than most likely you want a list of fruit. This is what I would personally expect and Scala seems to be confirming it for me.
I cannot see how what you have described could be a problem. Can you please give a sample of the situation and code? |
@dsaf //correctly inferred as Vegetable[]
var vegetable = new[] { new Carrot(), new Cucumber() }
//uses Person.Eat(Vegetable v) overload
foreach (var v in vegetable) person.Eat(v); Then you try to add Tomato forgeting that it is technically a Fruit //infers Object[] assuming that Vegetable and Fruit have no other common base class
var vegetable = new[] { new Carrot(), new Cucumber(), new Tomato() }
//error: no overload Person.Eat(Object o) found
foreach (var v in vegetable) person.Eat(v); So error that should have appeared at declaration appears further in the code as different error. I can even imagine cases where similar mistake results in runtime error and I fear some developers will rather fall back to using explicitly typed locals rather than dealing with these problems. |
@jveselka I see your point and it's a valid concern. There could be ways a modern IDE would circumvent this (e.g. showing a warning 'Suspicious best type inferred - object' together with the error you have given as an example). This issue needs more opinions. |
Related to #438. |
Not to mention how to determine what that best type is. Do you go up three levels of hierarchy to find a common base class, or two to find a common implemented interface? I think that there are too many questions and concerns to start having the compiler guess. I don't think that type inference should do much more than be a simple convenience so that the developer can type less in those cases where there is no possible ambiguity, but as soon as the intent isn't absolutely crystal-clear the developer should be required to explicitly state their intent through declaration. Not to mention, I'm going to have to be able to read that same code again at some point in the future and I don't want to have to make the same best guess that the compiler will have to. |
If you are worried about typing and reading code in the future why not offer a automatic "make explicit mode" and compile with "Strict On". It solves both issues you raise. Whenever you type
The IDE replaces it with
|
@paul1956 C# doesn't have a |
@gafter My example was VB but the concept applies.to both languages. |
@paul1956 The languages are different enough that your suggestion could be interpreted to mean one of a number of things in the context of C#. |
I don't know C# well enough to construct examples. The concept is once I finish typing the statement the IDE goes back and adds the missing "Type" information. In VB's case the "As integer", this gets more interesting when the Type is IEnumerable(Of INamedTypeSymbol).. Today at least for VB there are Code Refactoring's to do it but they require more keystrokes. |
Why doesn't C# already do thisThere was an explicit decision to have the C# compiler, in type inference, only produce types that were input to the inference process. The reason is that expanding the type inference to other types can result in very complex and nonintuitive results. See, for example, a stackoverflow question about infinite types in Java that result from type inference. It is all well and good to say that we should generalize the common-supertype computation, but actually doing so in practice - in a way that is intuitive to users - is very hard. We would need a specific concrete proposal (not just a list of options) along with a prototype to play with in order to move this kind of proposal forward. If the proposal includes special cases like "if there happens to be only a single common interface..." then it is unlikely to be seen as a good solution, as it will be fragile in the face of adding an interface to an existing class - a kind of fragility we like to avoid. I suspect any solution would require adding the concept of intersection types ala Java. Any solution for C# would likely be applied equally to VB, so we're treating this issue as covering both languages. |
@paul1956 A refactoring might help to convert correct code into more explicit correct code. But in this code the original code won't typecheck, so the compiler can't select an appropriate type for the variable. Generally, C# programmers prefer not to have refactorings and code normalization (other than whitespace) applied automatically. |
I was responding to the comment by @HaloFour where he said he didn't want to guess what the compiler did. Maybe I should open a new issue and move my comments to a VB thread. I am sensitive to this issue because I just spend the last 3 days refactoring someone else's code to add type information and the bug I was chasing was only found when a String was changed to its correct Type Integer. I found existing refactoring extensions to be very slow and in many cases inaccurate. |
@gafter Does this apply numeric promotions for integrals or implicit type conversions for other types? |
@alrz It doesn't have a design or implementation, so it doesn't do anything yet. |
@gafter Couldn't you read that more literally? 😄 |
@alrz if/when we design this, we probably will go through all the implicit conversions and ask those questions. |
What if the type is declared like: Animal[] animalsArray = new[] { new Dog(), new Cat() };
Animal animal = dog ? new Dog() : new Cat();
Animal GetAnimal(bool dog) => dog ? new Dog() : new Cat(); This still won't compile today, but the compile have enough knowledge about the inferred type. |
We (the language design team) looked at this briefly last week. To summarize our understanding of the proposal, we begin with the two existing sections
and
The proposal is to modify the penultimate bullet of 7.5.2.11 to be more generous in when it succeeds. The LDM notes that this is a technically incompatible change to overload resolution. As such it is unlikely that we could accept the change as proposed. Users tend to tar and feather us if we change the meaning of existing programs, and this change would do just that. A demonstration of that fact is left to the reader. Having said that, all is not lost. Another approach would be to leave type inference alone, and modify 7.5.2.14 to define best common type in terms of the desired new rule, instead of deferring to 7.5.2.11. Alternatively, we could make the desired change to the language, and provide a tool for users to migrate (or check) their code from one language version to the next, warning if there is a change in the semantic meaning of their code between language versions. Neither option is as attractive as the original proposal, presuming it were not a breaking change. |
Makes sense only when/if a critical mass of similarly invasive changes gets accumulated.
I am personally leaning towards @HaloFour 's opinion:
|
Please don't implement this
What is the type of collections here? If you'd allow this kind of type inference, adding an interface in newer framework versions breaks existing code. |
I think interfaces should never be used in this "common-type" inference. Reason: interfaces are somewhat a form of multiple inheritance. Multiple inheritance resolving in typing should always be explicit.
Use type priority order for some primitive types. If any other primitive type encountered - use
Any So var a = new [] { 1, 1.0 }; // having type double[]
var b = new [] { 1, 1.0, null }; // having type double?[]
var c = new [] { 1, 1.0f }; // having type object[] because of float and compiler throw warning
var d = new [] { 1, 1.0f, null }; // having type object[] because of float and compiler throw warning Structures of same type in array with any
Never take interfaces into consideration when inferring types. See above. |
@dsaf @realbart But use common parent class for type inference isn't such unreliable nor dangerous. For your example var array = new string[]{};
var list = new List<string>();
var collections = new[] { array, list }; Type of |
One part of this proposal (finding best common type between Btw, I'll move this discussion over to csharplang repo. Feel free to continue discussion there. |
Issue moved to dotnet/csharplang #450 via ZenHub |
This code will currently not work:
It could go one step further and assume the first common type in the inheritance hierarchy.
Several situations possible:
Related SO question: http://stackoverflow.com/questions/2799902/implicit-typing-of-arrays-that-implement-interfaces.
The text was updated successfully, but these errors were encountered: