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

Lowest common supertype in conditional expression #1857

Closed
FstTesla opened this issue Apr 8, 2015 · 10 comments
Closed

Lowest common supertype in conditional expression #1857

FstTesla opened this issue Apr 8, 2015 · 10 comments
Labels
Area-Language Design Resolution-Duplicate The described behavior is tracked in another issue Verified

Comments

@FstTesla
Copy link

FstTesla commented Apr 8, 2015

In a conditional expression b ? x : y, the last two operands determine the type of the whole expression. Essentially the procedure consists in taking the type of x and checking whether y is implicitly convertible to it, or viceversa, otherwise a compile-time error occurs.

This implies that the following expression does not compile:

b ? new ArgumentException() : new NullReferenceException()

even if it could (should) reasonably compile, with type SystemException, which is the lowest (most specific) common supertype of the type of the last two operands.
The only way to have such expression compile is casting any of the two expressions to the desired supertype.

A lowest common supertype (LCS) always exists (at least object) and using the LCS to determine the type of a conditional expression is indeed a generalization of the current specification.

This tecnique can be usefully applied to any kind of type, including nullable types, interfaces and dynamic types:

b ? 42 : null // int?
b ? "hello" : 3.14 // "intersection type" IConvertible & IComparable
b ? dynamicObject : otherObject // dynamic

As the second example suggests, it's not always possible to infer a "simple" LCS. Anyway, the obtained intersection type would be just a compile-time compiler-level abstraction that does not need run-time support.
Briefly, among the properties of an expression typed as an intersection type T & S we have:

  • it can be used wherever an expression typed as T or S is expected;
  • it inherits all the members of T and S --- conflicting signatures can be handled as usual.

Being LCS an associative operation, it can be used to infer the type of an implicitly-typed array when no implicit conversion exists.

@default0
Copy link

default0 commented Apr 8, 2015

It isn't clear to me how a lowest common supertype can be used to infer that (b ? 42 : null) should be of type int?.
int itself inherits from a ton of Interfaces and System.ValueType, which in turn inherits from System.Object, while null is specified to have no type. Where does int? come from in this case? To me it sounds like the lowest common supertype in this case would be an intersection type of all of int's Interfaces, because variables referring to Interfaces are the strongest supertype that can be null, but maybe I am missing something here.

@FstTesla
Copy link
Author

FstTesla commented Apr 8, 2015

From a formal type-system point of view, it isn't quite right that null has no type: null's type, let's call it Nulltype, is a subtype of any reference type and, most important, is implicitly convertible to Nullable<T> for any T.
You're right if you say that the intersection of all of int's interfaces is a common supertype between int and Nulltype, but it's not the lowest. A simple proof is the fact that you can assign an expression typed as int? to all of ValueType, IComparable, IFormattable, etc.

Maybe, having to deal with both value types and reference types, it would be better to talk about "lowest common type-implicitly-convertible-to", because the actual criterion is implicit convertibility (therefore including implicit operator definitions), not strict subtyping. Anyway I used the phrase "lowest common supertype" because it is a more common concept... and sounds nicer 😉

@GeirGrusom
Copy link

let's call it Nulltype, is a subtype of any reference type and

Or null is a type with a single value null which is implicitly convertible to any reference type.

@default0
Copy link

default0 commented Apr 8, 2015

I think my issue with understanding was being focused on the supertype thing, instead of implicit convertibility. Thanks for Clearing that up!

public class A
{
    public static implicit operator A(int val)
    {
        return new A();
    }
    public void M() 
    {
        var result = b ? 5 : null;
    }
}

What about this case though? What type should result have? int? or A? Or should this be a compiler error?

@FstTesla
Copy link
Author

FstTesla commented Apr 8, 2015

In my opinion the expression b ? 5 : null should have type Nullable<int> and, consequently, so should the result variable. In fact, user-defined implicit conversions should be considered only after other kinds of implicit conversions fail, as already happens in most context (such as overload resolution). This also means that if one explicitly typed result as A no compile-time error should occur, because the expression can be converted afterwards.

@svick
Copy link
Contributor

svick commented Apr 8, 2015

Anyway, the obtained intersection type would be just a compile-time compiler-level abstraction that does not need run-time support.

If you write var x = b ? "hello" : 3.14;, what is the type of x? If it's the intersection type and x is compiled as CIL local, then that local would need to have some type that's supported by the runtime. Or are you saying the compiler should use casts to make it appear as if intersection types were real?

@AdamSpeight2008
Copy link
Contributor

@svick var x = b ? "hello" : 3.14; That should be `object'.
Similar to how VB.net uses LCS on array literals.

Dim x = {"hello", 3.14}

@FstTesla
Copy link
Author

FstTesla commented Apr 9, 2015

That should be object.

That is the easiest way, but not the only one nor the cleanest. For instance you could choose a more specific type by picking the first of the calculated intersection.

An effective run-time support for var v = b ? x : y, given that the expression type is an intersection T1 & ... & Tn, could take two forms:

  1. After evaluating the expression (so that it's on top of the stack), it is dup-ed n-1 times and put in n synthetic CIL locals, each one with type Ti.
    • Read access to compile-time variable v gets translated into an access to the corresponding run-time local, according to the requested type.
    • Write access gets translated into the same instructions as variable initialization.
    • ref access is not allowed.
  2. Similar to the previous case, the compiler generates a wrapper class with n fields/properties, each typed as Ti, and assigns it to the actual CIL local. The class could also have a unary constructor that accepts the expression value and initializes all the fields.
    • Read access to variable v gets translated into an access to the corresponding field, according to the requested type.
    • Write access gets translated into creating a new instance of the wrapper class ans assigning it to the run-time local.
    • ref access is not allowed.

Maybe the second solution is more elegant, because it does not clutter the method space with fake locals, but instead synthesizes a class (which is a common practice).

For intersection-typed arrays the tecnique could be the same as the second solution (i.e. an array whose element type is the wrapper class), although I admit that I haven't meditated on it very much.

Finally, I'd like to underline that LCS causes such trouble only in case of partially incompatible types, while nevertheless it represents an enrichment in expressivity when the conditional expression subexpressions already have a "simple" common convertible type.

@dsaf
Copy link

dsaf commented Apr 9, 2015

Related to #1419 and #1470. Good implementation of intersection types would answer most of the questions in this sort of scenarios.

@gafter
Copy link
Member

gafter commented Apr 9, 2015

This is a dup of #1419.

@gafter gafter closed this as completed Apr 9, 2015
@gafter gafter added the Resolution-Duplicate The described behavior is tracked in another issue label Apr 9, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Language Design Resolution-Duplicate The described behavior is tracked in another issue Verified
Projects
None yet
Development

No branches or pull requests

8 participants