dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.11k stars 1.56k forks source link

Better factory anonymous constructors in JS interop #51201

Open jodinathan opened 1 year ago

jodinathan commented 1 year ago

The js_bindings uses a factory constructor replacement for anonymous classes when the class has enum values.
The problem is that we can't send null as argument in some cases. For example, the RequestInit can't receive a null value for the cache argument, just undefined.
I made it work by doing some checks, however, it generates a verbose JS code.

Is there anything I could do to make this simpler?

The dart interface

@anonymous
@JS()
@staticInterop
class RequestInit {
  external factory RequestInit._(
      {String? method,
      dynamic headers,
      dynamic body,
      String? referrer,
      String? referrerPolicy,
      String? mode,
      String? credentials,
      String? cache,
      String? redirect,
      String? integrity,
      bool? keepalive,
      AbortSignal? signal,
      String? duplex,
      dynamic window});

  factory RequestInit(
          {String? method,
          dynamic headers,
          dynamic body,
          String? referrer,
          ReferrerPolicy? referrerPolicy,
          RequestMode? mode,
          RequestCredentials? credentials,
          RequestCache? cache,
          RequestRedirect? redirect,
          String? integrity,
          bool? keepalive,
          AbortSignal? signal,
          RequestDuplex? duplex,
          dynamic window}) =>
      RequestInit._(
          method: method ?? undefined,
          headers: headers ?? undefined,
          body: body ?? undefined,
          referrer: referrer ?? undefined,
          referrerPolicy: referrerPolicy?.value ?? undefined,
          mode: mode?.value ?? undefined,
          credentials: credentials?.value ?? undefined,
          cache: cache?.value ?? undefined,
          redirect: redirect?.value ?? undefined,
          integrity: integrity ?? undefined,
          keepalive: keepalive ?? undefined,
          signal: signal ?? undefined,
          duplex: duplex?.value ?? undefined,
          window: window ?? undefined);
}

The dart call:

window.fetch(
        'https://jsonplaceholder.typicode.com/todos/1', RequestInit());

The generated output:

A.V(window.window,"fetch",["https://jsonplaceholder.typicode.com/todos/1",A.KA(null,null,null,null,null,null,null,null,null,null)]),t.e);

The A.KA function:

KA(a,b,c,d,a0,a1,a2,a3,a4,a5){var s,r,q,p,o,n,m,l,k,j=null,i=a1==null?window.undefined:a1,h=window.undefined,g=window.undefined,f=a4==null?window.undefined:a4,e=a5==null?j:"origin"
if(e==null)e=window.undefined
s=a2==null?j:"cors"
if(s==null)s=window.undefined
r=b==null?j:"omit"
if(r==null)r=window.undefined
q=a==null?j:"no-cache"
if(q==null)q=window.undefined
p=a3==null?j:"follow"
if(p==null)p=window.undefined
o=d==null?window.undefined:d
n=a0==null?window.undefined:a0
m=window.undefined
l=c==null?j:"half"
if(l==null)l=window.undefined
k=window.undefined
return t.e.a({method:i,headers:h,body:g,referrer:f,referrerPolicy:e,mode:s,credentials:r,cache:q,redirect:p,integrity:o,keepalive:n,signal:m,duplex:l,window:k})},
srujzs commented 1 year ago

Yeah, the complication is that we have two contradictory semantics here:

  1. Create an object literal using the invocation. This is the way @anonymous constructors work today - all they do is take the invocation and translate it to a JS object literal. Any arguments that are not passed are not included and default values are ignored. This is more efficient than jsify and doesn't undergo any conversions.

  2. Create an object literal using the declaration. This is more of what you want. This always creates an object literal with every single parameter as a key, but it's up to the invocation to determine whether each key will have the default value or a custom value.

In order to support 2, users need an option that says "don't ignore the defaults". It could be a variant of @anonymous, for example. The same also applies for whatever annotation we use for inline classes. Currently, such a way doesn't exist, so the best option is what you wrote above, which obviously is very verbose. I think such an option is worth adding though, and I'm leaning towards exploring it after some of the work is done for Dart 3.

jodinathan commented 1 year ago

@srujzs could you please point the files commonly used by the SDK to compile an anonymous item. I am curious to take a soft look on how it works

srujzs commented 1 year ago

Sure, here are the links for each backend:

dart2js: https://github.com/dart-lang/sdk/blob/93ecc28e016044cd5ffbfa88b23d984f368afcbf/pkg/compiler/lib/src/ssa/builder.dart#L5421

ddc: https://github.com/dart-lang/sdk/blob/93ecc28e016044cd5ffbfa88b23d984f368afcbf/pkg/dev_compiler/lib/src/kernel/compiler.dart#L6441

dart2wasm: https://github.com/dart-lang/sdk/blob/93ecc28e016044cd5ffbfa88b23d984f368afcbf/pkg/dart2wasm/lib/js_runtime_generator.dart#L68