---
# System prepended metadata

title: Nullable types and flat option equivalence

---

# Nullable types and flat option equivalence

Temper has some *optioning backends*, Rust, C# (strangely), and eventually OCaml where a null type translates to an option type.  (Swift calls their nullable types option types, but this seems to be marketing)

`Foo?` is a nullable type; it admits all the values that `Foo` admits but also admits the special value `null`. The `null` value can be differentiated from the other values: `someFoo != null`.

## Goals

**Support optioning languages** by translating nullable types even where there's no `null` value or that, like C#, don't allow its use with type parameters that can bind to both value and reference types.

Provide **flexible container types** like `List<String>` and `List<String?>` for bundling results and for intermediate computations.

Provide **consistent semantics for null checks**: `x == null`. Specifically avoid option nesting that would lead `Some(None)` to be spuriously recognized as distinct from `null`/`None`.

Clarify **which subtype-like relationships are translatable**.  There are a number of type relationships related to subtyping that are used in Temper.  What restrictions do we need to put on them, and which cause translation problems?

Explore the **null / void / unit** relationship in more detail.  Specifically, is it possible to have a *Void* type with *Unit* semantics while still preserving the semantics/translatability of `x == null` where `x` could be null.

## Design Sketch

We can translate `SomeType?` easily enough to an option type.
When a `SomeType` appears in a method return type because it narrows a super-type method that was `SomeType` we can detect that using the same kind of signature adjustment checks we need for Java and C#.

The source of complexity discussed here is generic type parameters.

- Is `T` an optional type?  Do we need to pack an option when passing a value into a `T` or unpack when the return type is `T`?
- Could the type `T?` introduce a nested option type?
- Can a `T` be `null`?  We want to avoid asking `.is_some()` when the value representation is not statically known to be an option.

The rest of this document discusses a 3-way distinction for type parameters.

1. An agnostic type parameter like that for `class List<T>` which is agnostic to whether `T` is nullable.  It could be an option or it could not.  That way, Temper `List<String>` could be a Rust `Vec<Str>` but `List<String?>` would be a `Vec<Option<Str>>`
2. A definite option type parameter.  With this, you can ask if a value of that type is `null`.  Binding a non-nullable type to this type parameter might involve auto-wrapping and unwrapping above.
3. A definite non-null type parameter.  With this, you can introduce `T?` as a distinct type.  Extension functions like `List.getOrNull` could use this.

(Note: A capital letter inside angle brackets `<X>` refers to a type parameter declaration.  A single capital letter name, eg `X`, without the angle brackets denotes a use of that declared type in the signature or body of the declaring type/method.)

These distinctions are made on type parameter definitions, so we can clarify the cases using the syntax below though all three distinctions can be made in-language without extra syntax if we do not infer an *AnyValue* upper bound for type formals without an explicit upper bound and make it illegal to mix nullable and non-nullable upper bounds:

`<T~ extends ...>` means `T` is agnostic.  `T` could be nullable or not.  It does not make sense to call methods on values of type `T~` since any value could be `null`.

`<U! extends ...>` means `U` is not a nullable type, so `U?` is a distinct, usable type.  You can call methods on values of type `U` based on its upper bounds.

`<V? extends ...>` means `V` is nullable and translates to an option type on optioning backends.  It makes sense to ask whether a `V` is null or not.  Inside an `if (myV != null)` context, we could allow `myV.method()` uses based on upper bounds with nullity erased because it's safe to unwrap the option on optioning backends.

## Syntactic Sugar

As a syntactic convenience, `<T extends ...>` is one of the above.

- `<W extends ...>` is `<W! extends ...>` if it has no nullable upper bounds.
- `<W extends ...>` is `<W~ extends ...>` if it has an upper bound and all its upper bounds are nullable.

Since a type parameter can have, as an upper bound, a previously declared type parameter, the below are equivalent because *nullable* above includes references to `<T?>`:

| Unqualified | Equivalent to | Because |
| ---- | ---- | ---- |
| `<W extends AnyValue>` | `<W! extends AnyValue>` | the upper bound is not nullable |
| `<W extends AnyValue?>` | `<W? extends AnyValue?>` | the upper bound is nullable |
| `<W>` | `<W!>` | W has an implicit upper bound of *AnyValue* which is not nullable |
| `<V?, W extends V>` | `<V?, W? extends V>` | W's upper bound is nullable |
| `<U! extends ..., W extends U>` | `<U! extends ..., W! extends U>`| W's upper bound is not nullable |
| `<T~, W extends T>` | *invalid* | W's upper bound is neither clearly nullable nor non nullable |
| `<T~, W~ extends T~>` | *valid* | No guessing needed |

## Rules

As above, `T` is agnostic, `U` is guaranteed non-nullable, and `V` is guaranteed nullable.

1. No asking if a `<T~>` is `null`.
2. `T?` is an illegal type since it could lead to nested option types. 
3. No binding a nullable type to a `<U!>`.  This includes `T` and `V`. 
4. No asking if a `U` is null, but it's ok to ask if a `U?` is null.
5. `V?` could be banned as a type or could be treated as equivalent to `V`.
6. Binding `T` to `<V?>` is illegal since it is not clear when to insert option wrapping/unwrapping instructions.
7. Given a contravariant definition `<in X!>, binding `X` (non-null) to `<V?>` is illegal since option types are covariant on their type parameter.

## Allowed bindings

To make clear what's not prohibited by the rules:

- `U`, `U?`, and `V` can bind to `<T~>`
- `U` and `U?` can bind to `<V?>`.  A contravariant `U` cannot.
- any `T` can bind to any other `<T2~>`
- any `U` can bind to any other `<U2!>`
- any `V` can bind to any other `<V2?>`
- any concrete type can bind to `<T~>`, e.g. `String` and `String?`
- any non-nullable concrete type can bind to `<U!>`, e.g. `String`
- any nullable concrete type can bind to `<V?`, e.g. nullable types like `String?` can bind.

## Example Translations

### An agnostic `<T>` type.

`List<String>` -> `Vec<String>`
`List<String?>` -> `Vec<Opt<String>>`

A reverse operation

```ts
let listOf2<T>(a: T, b: T) { [a, b] }
```

The optioning rust backend doesn't need to care whether a T is an option.

```rust
fn listOf2<T>(a: T, b: T) -> Vec<T> { vec![a, b] }
```

### A definite non-null `<U!>` type

A first-or-null function.
It uses `U?` as a distinct type for the return type.

```ts
let firstOrNull<U!>(ls: List<U>): U? {
  if (ls.isEmpty) { null } else { ls[0] }
}
```

When converting a U to a U? we need a wrapping instruction.

```rust
fn firstOrNull<U>(ls: Vec<U>): Option<U> {
  if (ls.is_empty) { None } else { Some(ls[0]) }
}
```

### A nullable `<V?>` type

```ts
let eitherOr<V?>(a: V, b: V): V {
  if (a != null) { a ) else { b }
}
eitherOr<String?>(null, "${f()}")
eitherOr<String>("${f()}", "${g()}")
```

```rust
fn eitherOr<V>(a: Option<V>, b: Option<V>): Option<V> {
  if (a.is_some()) { a } else { b }
}
// We need to un-option the nullable type to bind it to <V?>
// We need to insert wrapping and unwrapping instruction where a non-nullable type binds to <V?> or just auto-infer promote to a nullable bound.
eitherOr<Str>(None, Some(...))
eitherOr<Str>(Some(...), Some(...)).get()
```

## Subtype-like relationships

### Plain old subtyping
If `X <: X?` then translation of `X` to a language with subtypes should preserve the relationship: the translation of `X` should be a subtype of the translation of `X?`.

This is trivially not the case on optioning backends.

`String <: String?`, but `String </: Option<String>`.

### Assignable to
A *String* is-not-an *Option\<String>* but it may be assignable to a *String* if we can identif

Assignable-to is used in static checks and in type inference?  Can we treat `X?` as like a supertype of `X` for inferring types (`<~:` meaning assignable-to/subtype-ish)?  (Though we might need to insert unpacking instructions later)

```ts
let foo: Foo = f();
let temporary;
let fooOrNull: Foo?;
temporary = foo;
fooOrNull = temporary;
```

To solve the type of *temporary* it might be good to know that *Foo <~: temporary's type <~: Foo?* which makes sense when *Foo <~: Foo?*.

This assignable-to but not a subtype-of is also exactly where we need to insert auto-boxing/unboxing instructions.

The above has two valid translations on an optioning backend:

```ts
// Translation 1: temporary is an Option
let foo: Foo = f();
let temporary/*: Option<Foo>*/;
let fooOrNull: Option<Foo>;
temporary = Option.some(foo);
fooOrNull = temporary;

// Translation 2: temporary is not an Option
let foo: Foo = f();
let temporary/*: Foo*/;
let fooOrNull: Option<Foo>;
temporary = foo;
fooOrNull = Option.some(temporary);
```

Most type solvers prefer lower bounds to upper bounds where both are available, so translation 2 is more likely.

### Signature narrowing in overrides.

When a subtype overrides a method, can it widen input types and/or narrow output type?

```ts
interface I {
  public f(i: Int      ): String?;
}
class C extends I {
  public f(i: AnyValue?): String { "Hello" }
}
```

On Java, a non-optioning backend, that would be fine, but we would need to insert a bridge to allow widening Java `int`, a value type, to `@Nullable Object`, a Java reference type.

```java
interface I {
  @Nullable String f(i: int);
}
class C implements I {
  @Override
  public @Nonnull String f(int i) {
    return f((Object) i);
  }
  public @Nonnull String f(@Nullable Object i) {
    ...
  }
}
```

But on an optioning backend we run into the same subtype problem: an option is not a supertype of its type parameter.

```ts
interface I {
  f(i: Int): Option<String>;
}
class C extends I {
  public f(i: Option<AnyValue>): String { "Hello" }
}
```

Since *Int </: Option\<AnyValue>* and *String </: Option\<String>*, the above signatures would not be compatible between *I*'s declaration and its subtype *C*'s declaration.

Optioning backends that allow method overloading could bridge like the Java above, but how overloading works is very language specific and potentially creates other translation hazards.

TODO: draft rule above for signature narrowing

### Signature specialization

```ts
interface I<T extends AnyValue?> {
  f(): T;
}
class C(public x: String?) extends I<String?> {
  // The output type works because
  // I's <T> is String? in this context.
  public f(): String? { x }
}
```

Below considers each of the 3 types of type variables above (`!`, `?`, and `~`) &times; 5 cases (Nonnullable binding, Nullable binding, binding to a type parameter of the 3 types)

By including other type cases in our binding set, we also explore when one type parameter can have another as an upper bound: `<T, U extends T>`.

For each of these cases, we have a header like "`<T!>` binds to `Foo`."  Here's what each of those lefts and rights mean:

Lefts:
- `<T~>`: a type parameter that translates to an agnostic type: may be an option or not
- `<U!>`: a type parameter that translates to a non-option type on optioning backends
- `<V?>`: a type parameter that translates to an option type on optioning backends

Rights:
- `Foo`: an actual binding for the left type parameter which is a non-nullable type
- `Foo?`: an actual binding which is a nullable type
- `<W~ extends >`: the actual binding for the left parameter is `W` which is itself a reference to a type parameter declared in the same or a narrow scope than the left parameter and which has it as an upper bound
- `<W! extends >`: same but `W` is not an option
- `<W? extends >`: same but `W` is an option type

#### `<T~>` v `Foo`:

OK

```ts
interface I<T~> {
  f(): T;            
}
class C extends I<Foo> {
  f(): Foo {...}
}

//----

interface I<T> {
  f(): T
}
class C extends I<Foo> {
  f(): Foo {...}
}
```

#### `<T~>` v `Foo?`:

OK

```ts
interface I<T~> {
  f(): T;            
}
class C extends I<Foo?> {
  f(): Foo? {...}
}

//----

interface I<T> {
  f(): T
}
class C extends I<Option<Foo>> {
  f(): Option<Foo> {...}
}
```

#### `<T~>` v `<W~ extends >`

OK

```ts
interface I<T~> {
  f(): T;            
}
class C<W~> extends I<W> {
  f(): W {...}
}

//----

interface I<T> {
  f(): T
}
class C extends I<W> {
  f(): W {...}
}
```


#### `<T~>` v `<W! extends >`:

```ts
interface I<T~> {
  f(): T;            
}
class C<W!> extends I<W> {
  f(): W {...}
}

//----

interface I<T> {
  f(): T
}
class C extends I<W> {
  f(): W {...}
}
```

#### `<T~>` v `<W? extends >`: ...

OK.  Option shows up in the extends clause.

```ts
interface I<T~> {
  f(): T;            
}
class C<W?> extends I<W> {
  f(): W {...}
}

//----

interface I<T> {
  f(): T
}
class C<W> extends I<Option<W>> {
  f(): Option<W> {...}
}
```

#### `<U!>` v `Foo`:

OK

```ts
interface I<U!> {
  f(): U;
}
class C extends I<Foo> {
  f(): Foo {...}
}

//----

interface I<U> {
  f(): U
}
class C extends I<Foo> {
  f(): Foo {...}
}
```

#### `<U!>` v `Foo?`:

Banned

```ts
interface I<U!> {
  f(): U;
}
class C extends I<Foo?> {
  f(): Foo? {...}
}
```

#### `<U!>` v `<W~ extends >`:

Banned

```ts
interface I<U!> {
  f(): U;
}
class C<W~> extends I<W> {
  f(): W {...}
}
```

#### `<U!>` v `<W! extends >`:

OK

```ts
interface I<U!> {
  f(): U;
}
class C<W!> extends I<W> {
  f(): W {...}
}

//----

interface I<U> {
  f(): U
}
class C extends I<W> {
  f(): W {...}
}
```

#### `<U!>` v `<W? extends >`:

Banned

```ts
interface I<U!> {
  f(): U;
}
class C<W?> extends I<W> {
  f(): W {...}
}
```

#### `<V?>` v `Foo`:

Banned.  Just use Foo?

```ts
interface I<V?> {
  f(): V;
}
class C extends I<Foo> {
  f(): Foo? {...} // Need `?` here
}

//----

interface I<V> {
  f(): Option<V>
}
class C extends I<Foo> { // No Option here
  f(): Option<Foo> {...} // Option here
}
```

#### `<V?>` v `Foo?`:

```ts
interface I<V?> {
  f(): V;
}
class C extends I<Foo?> {
  f(): Foo? {...}
}

//----

interface I<V> {
  f(): Option<V>
}
class C extends I<Foo> { // No option here
  f(): Option<Foo> {...}
}
```

#### `<V?>` v `<W~ extends >`:

Banned

```ts
interface I<V?> {
  f(): V;
}
class C<W~> extends I<W> {
  f(): W {...}
}

//----

interface I<V> {
  f(): Option<V>
}
class C extends I<W> {
  f(): Option<COULD_NEST> {...}
}
```

#### `<V?>` v `<W! extends >`:

Ok with W? as binding

```ts
interface I<V?> {
  f(): V;
}
class C<W!> extends I<W?> {
  f(): W? {...}
}

//----

interface I<V> {
  f(): Option<V>
}
class C extends I<W> { // No option here
  f(): Option<W> {...}
}
```

#### `<V?>` v `<W? extends >`:

OK

```ts
interface I<V?> {
  f(): V;
}
class C<W?> extends I<W> {
  f(): W {...}
}

//----

interface I<V> {
  f(): Option<V>
}
class C extends I<W> {
  f(): Option<W> {...}
}
```

## Void vs Null

Short answer: No it is not passible to have *Unit* semantics for *Void* because on Python *None* is used for both concepts, and the below Temper code would not preserve semantics:

```ts
let f<T!>(x: T?, y: T?): Int {
  var n = 0;
  if (x != null) { n++ }
  if (y != null) { n++ }
  n
}
f(void, null) == 1
```

That would translate to Python3:

```py
def f[T](x: T | None, y: T | None) -> int:
    n = 0
    if (x is not None): n += 1
    if (y is not None): n += 1
    return n

f(None, None) == 1
```

----

Can Temper transparently have *Unit* style semantics for its *Void* type?

`null` and `void` are both singleton values.

`void` often means a function has no outputs.

*Unit* semantics allow zero-value-returning functions to be unary: have a result. There is a special, stateless, singleton "unit" value that is the output of any function called for its side-effect instead of its result.

This is important in generic programming as when every function has exactly one result, it's easier to use functions with higher-order functions.

In Java, `void` cannot bind to a type parameter, but the pseudo-reference-type, `java.lang.Void`, can.
If you want to use a void returning function-like type, e.g. *Runnable*, where a unary function like *Producer\<Void>* is expected, you have to adapt it.

```java
Runnable r = () -> {};
Supplier<Void> s = () -> { r.run(); return null; };
```

If Temper's *Void* type had *Unit* semantics, then Temper authors could avoid explicit adapting, and backends that have a *Unit* type could use it for *Void*.

For backends that disallow binding their *void* type to type parameters, we could do the following:

1. ensure that any calls to `void` functions that are inputs to other calls are extracted to the block root and replaced with the `void` literal in a way that preserves order of operations
2. identify and mark methods that return `Void` because they specialize a supertype method:
   ```ts
   interface I<T> {
     f(): T;
   }
   class C extends I<Void> {
     f(): Void { ... }
   }
   ```
3. identify where a void returning function value is passed to a `<T>` returning function and mark as needing auto-adaption on some backends.

Unfortunately, we can't have unit semantics internally within Temper's frontend.

Many dynamic languages conflate the concepts of null and void.

- Python3 as noted above uses `None` for both.
- JavaScript uses `undefined` for `void` and assigning distinct semantics to `null` and `undefined` makes for brittle library APIs.
- Lisps often treat the empty list,  `nil`, as both.
- Lua?

As noted above, if Temper `Void` has Unit semantics, then `Void?` is a mentionable type and we need to, at runtime, be able to distinguish the translation of `void` from the translation of `null`.

Temper will probably have a singleton *Unit* type/value (tentatively called *Empty*/*empty*) in its standard library that is distinct from `null`.

Internally, it will reserve *Void* as an output-only type but allow *Unit* as an internal type.

The TmpL middle-end may auto-adapt void functions to unit-returning functions for backends that benefit from that to allow for the illusion of unit semantics, but backends will need to grapple with both concepts.