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

Issue: Dart Compile JS Fails to Assign External JS Function #56533

Closed mrtnetwork closed 2 months ago

mrtnetwork commented 2 months ago

Description

I'm encountering an issue when compiling my Dart code to JavaScript using dart compile js. The problem appears to be related to the assignment of an external JavaScript function via dart:js_interop.

Code Example Here's a minimal example of the code causing the issue:

import 'dart:js_interop';

@JS("exportedFunction")
external set exportedFunctions(JSFunction value);

void printStrings(JSString string) {}

void main() {
  print("start!");
  try {
    exportedFunctions = printStrings.toJS;
    print("function initialized.");
  } catch (e) {
    print("something went wrong: $e");
  }
  print("done");
}

Expected Output

start!
function initialized.
done

Actual Output Without the try-catch block:

start!

With the try-catch block:

start!
done

Additional Information Dart SDK Version: 3.5.0 (stable) (Tue Jul 30 02:17:59 2024 -0700) on "macos_x64"

Compilation Command:

dart compile js -o ./build.js ./build.dart

When using the -O0 flag or compiling to WebAssembly (dart compile wasm), the code works perfectly.

Steps Taken to Resolve Removed and reinstalled both the Flutter and Dart SDKs. Cleared cache.

dart-github-bot commented 2 months ago

Summary: The user is experiencing an issue where assigning an external JavaScript function to a Dart variable using dart:js_interop fails when compiling to JavaScript with dart compile js. The code works correctly when using -O0 or compiling to WebAssembly.

srujzs commented 2 months ago

cc @rakudrama @fishythefish

Can reproduce this with 3.6 as well. It looks like it's tied to just calling toJS. For some reason, the function conversion lowering leads dart2js to believe these print statements after toJS can be excluded. If I add a line like:

tmp.toJS.callAsFunction(null, 'hello'.toJS);

after the print statements, the prints are included.

edit: https://dart-review.googlesource.com/c/sdk/+/336626 might be related here. Maybe we just need to add toJS to that list now that we no longer use allowInterop for the implementation of toJS.

srujzs commented 2 months ago

There's a fix @rakudrama landed that should go into 3.6. There's a simple workaround to make this work in the meantime, and that's to call any non-patched interop getter or member. That gets lowered to a JS foreign function call that returns an Object|Null, which then makes the JavaScriptFunction interceptor live and avoids dart2js thinking toJS will always throw.

The one-liner fix is to put:

() {}.toJS.callAsFunction(null);

somewhere in your code. Closing as fixed in 3.6.