-
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
Proposal - Ternary Operator should resolve types that are of the same base type #6814
Comments
I think this comment from #6789 would be relevant here:
|
Yes that's true, and if that is a conscious decision then perhaps this isn't worth going against that rule of thumb. But it's a fairly obvious type resolution here. It's also one of those weird gotchas I've seen people come across quite a few times. Especially since people logically think of the ternary operator like an inline
Can't be made into |
Are you suggesting a change for ternary only, or for all the places that 7.5.2.14 (Finding the best common type of a set of expressions) is used as well? |
@gafter I can't speak for @mirhagk but I would personally appreciate this change in the areas I can think of at the moment:
|
Also match expressions and disjunctive patterns would benefit from this change. |
interface IFoo { ... }
interface IBar { ... }
class A : IFoo, IBar { ... }
class B : IFoo, Ibar { ... } What should |
@orthoxerox I would be ok only extending this inference to non-interfaces only, so the common type in your example would be But if C# and the CLR were to support intersection types then by all means, let them join the party. |
@bondsbw @gafter - For example, let's say you have the following classes:
if I say:
this would be the same as:
But, if I were to say:
this would be the similar to sayin saying (by the way, this doesn't work today):
You'll see that in this case, the return type, T is both Animal and IWild . I have wanted to be able to express constraints/modifiers on regular variables ever since "dynamic" was first introduced. For me, whenever I'm working with dynamic, it is usually on an object that has known and unknown members. For the known members, I want the safety of being able to "dot" into them and for the unknown members, I want "dynamic" to be used for them. To get around that, I usually have a "typed" reference and a dynamic reference to the same variable. I've had a few ideas for expanding syntax to handle a few different scenarios:
|
This proposal seems to be more of a change to how the compiler infers the type of an expression, calculating a common type from a list of potential types. Some of your requests are covered to some extent by the following existing proposals: #227 Proposal for non-nullable references (and safe nullable references) |
@orthoxerox As I mentioned it should ignore interfaces as it would cause conflicts in many situations. (Anything that inherited from something that implemented IDisposable would instantly conflict). So for your example it would indeed by of type @gafter I think I agree with @bondsbw in that those places should also do it. Specifically the lambda case, as that's another instance where you'd just expect it to work since it works fine as a named function. Although looking at the C# 5 spec I see that the ternary operator has a separate definition for type resolution, and does not reference the rule for
Which may or may not actually be wanted (we're stepping into covariance and contravariance territory now and I'll be the first to admit that type theory isn't my strong point) |
@TonyValenti Minor nitpick, but interface IWild { } //It is a wild animal
class Animal {}
class Mammal : Animal {}
class Canine : Mammal {}
class Dog : Canine {}
class Wolf : Canine, IWild {}
class Feline : Mammal {}
class Cat : Feline {}
class Tiger : Feline, IWild {} //I'd also throw 'abstract' on everything but the lowest level classes, but that's just me 😜 |
@mirhagk ignoring interfaces when determining the best common type would make this enhancement useless in a large percentage of cases. As noted by @HaloFour there are a number of existing proposal, specifically #4586 intersection types, which touch on or are directly related to this issue. I think intersection types would be a powerful concept which would allow existing features, such as the ternary syntax, to be enhanced while providing a context to assist in introducing potential new features, such as mixins, in the future. Unfortunately, the current proposal, #4586, does not introduce or present the concept or the use cases clearly. It does not really represent a concrete proposal and provides neither sufficient syntactic nor sufficient semantic information. The original proposer @MouseProducedGames has not participated in it actively for a number of months. I have started writing my own proposal, but I'm not sure if it would be a appropriate to open a redundant issue. |
@aluanhaddad I like intersection types, but I would prefer to go ahead with class-only inference in the event that intersection types become delayed or are never implemented. Curious... without intersection types, would it be possible to still infer the interface if there were only one common most-derived interface (assuming the only common superclass is |
@bondsbw I imagine that it would be just as possible to infer a single common interface as a single common class. This is all speculative, as there is no analogous type inference in C# at present.
Intersection types have no impact on the technical capability to infer an interface vs a class. In the context of variable declarations, inferred or otherwise, they allow one to have a reference to a value which is typed as some aggregate of the types it implements/extends. It's also worth noting that there is not really a notion of a most derived interface. The candidates for interface inference would be all interfaces implemented by all of the values in question, in this case the two values of the ternary expression. (Edit: I guess if there are interfaces that extend other interfaces in the set of types there could be a most derived one depending on how many axes of interface implementation are present on the type in question.) The type inference process could examine all of the types implemented by both values and could infer the most specialized (not most derived) common type as a type which implements the subset of all the types they have in common. However, there is no way to express that type in the language. Intersection types give you just that expressiveness, which is why they are relevant here. Edit: A helpful way to think about intersection types is to compare them to generic constraints. If I have a method: void M<T>(T value) where T: ISomeInterface, ISomeOtherInterface => ... then |
I agree that ideally it would solve the interface issue, but with interfaces it's possible to have multiple types that do not inherit from each other as totally valid types.
Then the type could equally validly be I'm thinking that the only real way to resolve this would be to have intersection types, so perhaps the solution until those are implemented could simply be to throw an error in situations like this. It would still resolve all of the simple cases, and it'd even give a more helpful error message. Perhaps something like:
It'd be something where the user would even understand why that doesn't work. |
@aluanhaddad Again, I'm not against intersection types by any means. I want them in the language. I would just prefer to have this feature regardless of whether intersection types are ever implemented. Adding intersection types later would still be possible and should still support this feature.
The return type could resolve to only |
@bondsbw I agree. I'm not against this proposal. I apologize if I gave the impression I was. |
Duplicate (subset) of #1419 - even the sample taxonomy is identical :). |
Looks like everything is covered in #1419, I'll just add my two cents there. |
If you have
You should be able to write
return likesCats ? new Cat() : new Dog();
which would return an object of typeAnimal
.This should fall under the better betterness category where the compiler can figure out what the author clearly meant to do, and a cast to a base type is a safe implict cast that isn't introducing any side effects. It's also simply fixing code which was not allowed before, so it is backwards compatible (this rule of determining the type should run last, in case the types can already be implicitly converted)
This should be restricted to the case where the two expressions share a common base class, and it should choose the highest base class applicable (ie
Animal
notObject
should be chosen). This avoids situations where the expressions could both be converted to multiple interfaces (ie if they both derived from a class that definedIDisposable
then they could be casted to anIDisposable
but this would be a less useful type and cause conflicts).Potential points of discussion:
object
base type be excluded from this? Everything derives fromobject
which would make every ternary operator valid but returning a less than useful type most of the time. Most likely the user didn't want to do this, but had an error like calling the wrong method or something. The potential error of the invalid cast would likely be caught at the very next level when it tries to return or assign it but this may not always be the case. If the result of this expression went into avar
and then was used to call a generic method or instantiate a generic class then this may go pretty far in the program before producing a compile time error, and may not introduce one at all.The text was updated successfully, but these errors were encountered: