
# C# Language Design for April 8, 2020

## Agenda

1. `e is dynamic` pure null check
2. Target typing `?:`
3. Inferred type of an `or` pattern
4. Module initializers

## Discussion

### `e is dynamic` pure null check

We warn that doing `e is dynamic` is equivalent to `e is object`, but `e is object` is a pure
null check, while `e is dynamic` is not. Should we make `is dynamic` a pure null check for
consistency?

**Conclusion**

Yes.

### Target-typing `?:`

The simplest example where we have a breaking change is

```C#
void M(short)
void M(long)
M(b ? 1 : 2)
```

Previously this would choose `long`, because the expressions are effectively typed separately,
and when inferring `1 : 2` we would choose `int`, and then rule out `short` during overload
resolution as invalid.

Target-typing converts each arm in turn to find the best possible type, selecting `short` instead
of `long`. That means the first overload is selected instead with target-typing.

It's hard to easily avoid this breaking change. The obvious mechanism, running overload
resolution twice, is problematic because having multiple arguments with `conditional` expressions
produces exponential growth in the number of passes of overload resolution.

**Conclusion**

Let's talk about this again in a separate meeting. Since whatever we choose here will probably
be the final decision (any further changes will be breaking), we want to be sure we're making the
best choice.

### Inferred type of an `or` pattern is the common type of two inferred types

```C#
object o = 1;
if (o is (1 or 3L) and var x)
    // what is the type of `x`?
```

The problem here is that the existing common type algorithm allows conversion that are forbidden
in pattern matching. In the above case we would choose `long`, because `3L` is a long, and `1`
can be converted to `long`. However, in pattern matching the widening conversion from `int` to
`long` is illegal.

The proposal is to narrow the set of conversions only to implicit reference conversions or
boxing conversions. Why this could be useful is the example

```C#
class B { }
class C : B { }
if (o is (B or C) and var x)
    // x is `B`
```

A follow-up question is about ordering. The proposed rules only infer types mentioned in
the checks, meaning that the following

```C#
if (o is (Derived1 or Derived2 or Base { ... }) and var x)
```

looks like this today

```C#
((Derived1 or Derived2) or Base)

-> ((object) or Base)

-> (object)
```

On the other hand, if the example were parameterized in the opposite way,

```C#
(Derived1 or (Derived2 or Base))

-> (Derived1 or (Base))

-> (Base)
```

So the ordering seemingly matters if the `or` pattern is a simple binary expression.

The first question is if

```C#
if (o is (Derived1 or Derived2 or Base { ... }))
```

should produce `Base`, and the second question is if parenthesizing affects this, e.g. explicitly saying

```C#
if (o is ((Derived1 or Derived2) or Base { ... }))
```

produces `object`.

**Conclusion**

We're not seeing a lot of scenarios that depend on these features, but not doing it feels like
leaving information on the table. Functionally, the compiler can infer a stronger type, so why
not do so? The proposed modified common type algorithm is accepted.

We also think that type narrowing for the `or` operation should use all the `or` operations
in a series as the arguments to the common type algorithm.

### Module Initializers

Proposal: https://github.com/RikkiGibson/csharplang/blob/module-initializers/proposals/module-initializers.md

There are a few places where module initializers today. The most common is a shared resource
that is used by multiple types, but the program would like to initialize the shared resource
before the static constructors of the types are run.

The question for the implementation is to how to indicate where the code for the module
initializer will go, and how the compiler will recognize it.

The proposal provides a mechanism to identify the module initializer via an attribute on the module
that points to a type, and type contains a static constructor that acts as the module initializer.
From a conceptual level, this seems more complicated than necessary. Can we put the attribute on the
type or, even the method, that holds the code for the module initializer?

If we go that route, why require the code be in the static constructor at all? Can any static method
be a target? One reason why we may prefer a static constructor is that if the emitted module initializer
calls the target static constructor method, it's easy to insert code before that method is invoked
by writing a static constructor for the containing type. On the other hand, even static constructor
can have code inserted before them, for example with static field initializers.

The last question is whether to allow only one module initializer method, or allow multiple and specify
that the compiler will call them in a stable, but implementation-defined order.

**Conclusion**

Let's let any static method be a module initializer, and mark that method using a well-known attribute.
We'll also allow multiple module initializer methods, and they will each be called in a reserved, but
deterministic order.