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.21k stars 1.57k forks source link

[JS-interop] Extension for JSBigint #56539

Open redDwarf03 opened 2 months ago

redDwarf03 commented 2 months ago

Why JSBigint and Bigint have no extension in js-interop like


/// [JSBigInt] <-> [BigInt]
extension JSBigIntToBigInt on JSBigInt {
  external BigInt get toDart;
}

extension BigIntToJSBigInt on BigInt {
  external JSBigInt get toJS;
}

I try to create in my project these extensions but i have this error:

Error: Type 'BigInt' is not a valid type in the signature of `dart:js_interop` external APIs or APIs converted via `toJS`. The only valid types are: JS types from `dart:js_interop`, @staticInterop types, void, bool, num, double, int, String, extension types that erases to one of these types, or a type parameter that is bound to a static interop type.
[        ]  - 'BigInt' is from 'dart:core'.
[        ] Use one of the valid types instead.
[        ]   external BigInt get toDart;
[        ]                       ^
[        ] lib/main.dart:224:25: Error: JS interop or Native class required for 'external' extension members.
[        ] Try adding a JS interop annotation to the on type class of the extension.
[        ]   external JSBigInt get toJS;
[        ]                         ^
dart-github-bot commented 2 months ago

Summary: The user is requesting extensions for JSBigInt and BigInt in js-interop to enable seamless conversion between these types. They are encountering errors related to the use of BigInt in the external extension members, which is not allowed in dart:js_interop.

kaumudpa commented 2 months ago

@redDwarf03 have you tried using js- like js: ^0.6.4 and creating an interop?

import 'package:js/js.dart';

@JS('BigInt')
class JSBigInt {
  external JSBigInt(String value);
  external JSBigInt add(JSBigInt other);
  external JSBigInt multiply(JSBigInt other);
}

class MyBigInt extends JSBigInt {
  MyBigInt(String value) : super(value);

  MyBigInt addWithDart(MyBigInt other) {
    return this.add(other);
  }

  MyBigInt multiplyWithDart(MyBigInt other) {
    return this.multiply(other);
  }
}

void main() {
  var a = MyBigInt('123456789123456789');
  var b = MyBigInt('987654321987654321');

  var resultAdd = a.addWithDart(b);
  var resultMultiply = a.multiplyWithDart(b);

  print('Addition Result: ${resultAdd.toString()}');
  print('Multiplication Result: ${resultMultiply.toString()}');
}
redDwarf03 commented 2 months ago

@redDwarf03 have you tried using js- like js: ^0.6.4 and creating an interop?

import 'package:js/js.dart';

@JS('BigInt')
class JSBigInt {
  external JSBigInt(String value);
  external JSBigInt add(JSBigInt other);
  external JSBigInt multiply(JSBigInt other);
}

class MyBigInt extends JSBigInt {
  MyBigInt(String value) : super(value);

  MyBigInt addWithDart(MyBigInt other) {
    return this.add(other);
  }

  MyBigInt multiplyWithDart(MyBigInt other) {
    return this.multiply(other);
  }
}

void main() {
  var a = MyBigInt('123456789123456789');
  var b = MyBigInt('987654321987654321');

  var resultAdd = a.addWithDart(b);
  var resultMultiply = a.multiplyWithDart(b);

  print('Addition Result: ${resultAdd.toString()}');
  print('Multiplication Result: ${resultMultiply.toString()}');
}

No because js is not supported with new JS Interoperability and WASM https://dart.dev/interop/js-interop

So i don't want to use js. Sorry

redDwarf03 commented 2 months ago

I create this dart class based on dart test classes but i think dart core team should expose a method to manipulate Bigint and JSBigInt

import 'dart:js_interop';

@JS()
external JSBigInt bigInt;

@JS('BigInt')
external JSBigInt createBigInt(String value);

extension on JSBigInt {
  @JS('toString')
  external String toStringExternal();
}
kevmoo commented 2 months ago

Great request, @redDwarf03.

redDwarf03 commented 2 months ago

@kevmoo The same with JSFunction if possible :)

srujzs commented 2 months ago

The toDart case is fairly straightforward:

JSBigInt b = ...;
BigInt b2 = BigInt.parse(b.toString()); // toString calls the external toString

The toJS case requires a few more lines but is also straightforward:

@JS('BigInt')
external JSBigInt bigInt(String s);

...

BigInt b = ...;
bigInt(b.toString());

Of course, it'd be useful to add functions that do this for you and have them in dart:js_interop, so we can track this request.

The same with JSFunction if possible :)

Can you elaborate a bit more here? We have Function.toJS and JSExportedDartFunction.toDart today. JSExportedDartFunction is a separate type since not all JSFunctions were converted Dart functions. If you want to call arbitrary JSFunctions, one solution is using JSFunction.callAsFunction.

redDwarf03 commented 2 months ago

thx. i will explore your comment on JSFunction

redDwarf03 commented 2 months ago

@srujzs about JSBigint and JSArray:

I have an JSArray

2 cases:

1 - I manage conversion directly in dart

@JS()
extension type WriteContractParameters._(JSObject _) implements JSObject {
  external WriteContractParameters({
    JSArray? args,
  });

  external JSArray? args;
}

@JS()
external JSBigInt bigInt;

@JS('BigInt')
external JSBigInt createBigInt(String value);

extension on JSBigInt {
  @JS('toString')
  external String toStringExternal();
}
final writeContractParameters = WriteContractParameters(
   args: [
       '0x08Bfc8BA9fD137Fb632F79548B150FE0Be493254'.toJS,
       createBigInt('498500000000000'),
   ].toJS,
);

When i debug javascript, i have my bigint correctly managed

args: Array(2)
0: "0x08Bfc8BA9fD137Fb632F79548B150FE0Be493254"
1: 498500000000000n

2 - I don't manage conversion directly in dart

class WriteContractParameters {
  WriteContractParameters({
    this.args,
  });

  List<dynamic>? args;

  JSWriteContractParameters get toJS => JSWriteContractParameters(
        args: args?.jsify() as JSArray<JSObject>?,
       );
}
final writeContractParameters = wagmi.WriteContractParameters(
   args: [
      '0x08Bfc8BA9fD137Fb632F79548B150FE0Be493254',
      BigInt.from(498500000000000),
    ],
);

When i debug javascript, i have my bigint NOT correctly managed

args: 
Array(2)
0: "0x08Bfc8BA9fD137Fb632F79548B150FE0Be493254"
1: core._BigIntImpl.__ {Symbol(_used): 4, Symbol(_digits): Uint16Array(4), Symbol(_isNegative): false}
length: 2
Symbol(dartx.arrayRti): (...)
Symbol(dartx.first): (...)
Symbol(dartx.hashCode): (...)
Symbol(dartx.isEmpty): (...)
Symbol(dartx.isNotEmpty): (...)
Symbol(dartx.iterator): (...)
Symbol(dartx.last): (...)
Symbol(dartx.length): (...)
Symbol(dartx.reversed): (...)
Symbol(dartx.runtimeType): (...)
Symbol(dartx.single): (...)
[[Prototype]]: Array(0)

So i would like to fix my case 2

srujzs commented 2 months ago

Yeah, we currently don't treat BigInts specially in jsify and treat them as other Dart members, so it'll be a bit more of a manual process. There's a blanket bug here: https://github.com/dart-lang/sdk/issues/55222 to be clear about what jsify/dartify can and can't do (right now we only technically support whatever's in the doc comment) as well as make it consistent. One of the options there is allow users to expose the low-level conversion methods they need so they can write their own variants that convert types as they need.

redDwarf03 commented 2 months ago

@srujzs i tried something like that but the add method seems to add nothing...


  JSArray<JSObject>? _convertArgs(List<dynamic>? args) {
    if (args == null) {
      return null;
    }
    final jsArgs = JSArray<JSObject>();
    for (final arg in args) {
      if (arg is String) {
        jsArgs.add(arg.toJS);
      } else if (arg is int) {
        jsArgs.add(arg.toJS);
      } else if (arg is bool) {
        jsArgs.add(arg.toJS);
      }
    }
    return jsArgs;
  }
redDwarf03 commented 1 month ago

@srujzs i tried something like that but the add method seems to add nothing...


  JSArray<JSObject>? _convertArgs(List<dynamic>? args) {
    if (args == null) {
      return null;
    }
    final jsArgs = JSArray<JSObject>();
    for (final arg in args) {
      if (arg is String) {
        jsArgs.add(arg.toJS);
      } else if (arg is int) {
        jsArgs.add(arg.toJS);
      } else if (arg is bool) {
        jsArgs.add(arg.toJS);
      }
    }
    return jsArgs;
  }

@srujzs is it possible to add BigInt in the condition https://github.com/dart-lang/sdk/blob/ee0b971dbd6ff51d8ec740294fb0a2730df6bb2b/sdk/lib/_internal/wasm/lib/js_util_patch.dart#L32

srujzs commented 1 month ago

i tried something like that but the add method seems to add nothing...

There's no add method on a JSArray. You may want push e.g.

extension JSArrayExtension<T extends JSAny?> on JSArray<T> {
  external void push(T _);
}

Besides that, the approach seems fine, but it is a bit of reinventing jsify alas.

is it possible to add BigInt in the condition

One of the things we want to do for jsify/dartify is expose it in such a way that users can define their own conversions if needed. Maybe a callback would be useful here for types that aren't supported by default, but it'd be nice to customize jsify/dartify as needed at any rate.