AssemblyScript / assemblyscript

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

Enhancement: Emit "plain object" classes as a named (non-generic) interface or type #2284

Open vveisard opened 2 years ago

vveisard commented 2 years ago

Problem

Currently, plain objects (objects without constructors or functions) are emit to the deceleration file as generically named interface types, in the form of __Record{classId}.

// ./assembly/index.ts

class Bar {
  value: u8;
}

export function foo(): Bar {
  return {
    value: 0
  }
}
// ./build/release.d.ts

/** assembly/index/Bar */
declare interface __Record10<TOmittable> {
  /** @type `u8` */
  value: number | TOmittable;
}

/**
 * assembly/index/foo
 * @returns `assembly/index/Bar`
 */
export declare function foo(): __Record10<never>;

This is not expected behavior; I would expect AssemblyScript classes which merely define the shape of a plain object to be emit as interface or type with the same name as the class.

// ./build/release.d.ts

// type version
declare type __Record10<TOmittable>  = {
  /** @type `u8` */
  value: number | TOmittable;
}

// interface version
declare interface __Record10<TOmittable> {
  /** @type `u8` */
  value: number | TOmittable;
}

Proposal

Plain object classes are emit as type/ interface if an annotation is present.

The name of this annotation, and what the new "default" behavior is, is an implementation detail.

Considerations

Plain objects returns/ arguments are "not really an instance"

In TypeScript, an interface or type with no functions merely defines the shape of plain object. On the AssemblyScript side, a class with no constructor or functions also merely defines the shape of a plain object. In both cases, that plain object really is that type in both TypeScript and AssmeblyScript.

Naming Conflicts

As naming collisions are unavoidable, it is the responsibility of the application developer to resolve naming conflicts within their application through alias imports. eg, import { Foo as MyFoo } from 'my-wasm-module';

trusktr commented 2 years ago

Maybe it would be better if AS supported interface instead, and did with it similar to constructor-less classes, rather than investing time down this path.

vveisard commented 2 years ago

Maybe it would be better if AS supported interface instead, and did with it similar to constructor-less classes, rather than investing time down this path.

This makes a lot more sense, as TypeScript interfaces are similar to constructorless classes in AssemblyScript (merely defines the shape of a plain object), and instances can easily be emit as those interfaces without changing their behavior.