AssemblyScript / assemblyscript

A TypeScript-like language for WebAssembly.
https://www.assemblyscript.org
Apache License 2.0
16.79k stars 655 forks source link

Optimize operator (==) overloads when `rhs` or `lhs` is null #1891

Open saulecabrera opened 3 years ago

saulecabrera commented 3 years ago

Considering a class that defines operator overloads, especially those around equality, would it make sense to optimize the case where the rhs or the lhs or both are null?

class Foo {
  @operator("==")
  static __op(lhs: Foo, rhs: Foo) {
    // define equality
  }

I know that internally this desugars to a normal static function call and thus the overload is probably responsible for handling the null case. Even though this is true, from a proper equality comparison in the case of classes, this behaviour seems counterintuitive given the equality should be performed based on the object properties and not necessarily on its type (T | null).

This case is especially noticeable when performing a comparison between instances of classes that have nested properties that are instances of classes defining overloads, i.e.

class A {
   b: B | null;
   // defines == overload
}

class B {
  // defines == overload
}

const a: A = {b: null};
const a1: A = {b: new B()};

Comparing the two instances above will fail, because the equality overload defined in B doesn't expect null.

All this to say: given that the overload is defined on a per-class basis, would it make sense to only invoke the overload if the value is not null and return false otherwise? I agree that having the overload be invoked with null is probably the most flexible option, but at least with classes I doubt that there's a valid case in which a class instance will compare itself to null and be true

dcodeIO commented 3 years ago

Quick thought: There is also a bit of relation here to the case of ===, in that if we manage to make null-ness precomputable in == and !=, despite there being an overload, it may become more feasible to tackle === as well. How would you design Wasm codegen here? Say we have a left operand (local.get $left) and a right operand (i32.const 0), or vice-versa, we'd want to generate code that allows us to statically eliminate untaken branches.

evaporei commented 2 years ago

@dcodeIO shouldn't calls to operator overloading methods give compile time error if called with nullable values? This already happens if the right hand side is nullable, but not for the left hand one.

Example:

class Number {
  public constructor(
    public value: i32
  ) {}

  @operator('+')
  plus(other: Number): Number { // can be called with left hand side being nullable, should give compile time error
    return new Number(this.value + other.value)
  }
}

let a: Number | null = null
let b = new Number(5)

a + b; // goes silently
b + a; // compile time error

Note: this only happens when it's a property access on the left hand side. If we're talking about two plain variables (one of them being nullable), the compiler gives the proper error message.

dcodeIO commented 2 years ago

This looks like a bug on first glimpse, going to create an issue for it :)