HaxeFoundation / HaxeFoundation/haxe

final vs never vs null

Open
#7,838 7 comments 0 reactions 1 assignee Claimed by @Simn View on GitHub
type-system
Dominant language
Haxe
Stars
6.9k
Forks
715
Avg merge
2d 2h
Merged PRs (30d)
11

Description

Following up on #7818:

```haxe
class Main {
static function main() {
var aFinal:{ final foo:Int; } = { foo: 42 };// works, because of top down inference
var aNever:{ var foo(default, never):Int; } = aFinal;// no problem
var aNull:{ var foo(default, null):Int; } = aNever;// still no problem

aNull = aFinal;// Cannot unify final and non-final fields ... what?
}
}
```

This is weird. One could argue that unifying `never` with `null` creates a type hole: once access is `null`, you can use `@:privateAccess` to read/write the value. You can even start with `final`, promote to `never`, promote to `null` and mutate via `@:privateAccess`. One "solution" would be to disallow `never` to unify with `null` but it'd break a lot of existing code and make things more awkward than they already are.

I would propose that:

1. `null` and `never` get treated as being the same for structures/interfaces and `final` may unify with both. Code written against read-only structures/interfaces should be able to operate on immutable objects (just not the other way around).
2. `@:privateAccess` cannot access `null` on structures/interfaces, only on classes. Reasons:
- it closes the "type hole"
- I'm relatively optimistic that very little code actually uses `@:privateAccess` on structures/interfaces
- it actually makes sense to only allow bypassing access restrictions on concrete implementations, because on structures/interfaces it can go absolutely wrong:

```haxe
interface HasFoo { var foo(get, null):Int; }

class Test implements HasFoo {

@:isVar public var foo(get, set):Int;
function get_foo() return foo;
function set_foo(param:Int)
return
this.foo = if (param > 100) 100;
else if (param < 0) 0;
else param;

function new() {}
static function main() {
var t = new Test();
t.foo = 1000;
trace(t.foo);//100 - of course ... foo will never be > 100
var f:HasFoo = t;
@:privateAccess f.foo = 1000;
trace(t.foo);//1000 ... whahooops!
}
}
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.