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

Static interop for DateTime / JS Date #52021

Open navaronbracke opened 1 year ago

navaronbracke commented 1 year ago

Static interop currently works for primitives, lists and JS Objects.

However, it does not work for Dart's DateTime type.

Consider the following code sample:

// JS
function getDate() {
    return new Date();
}

window.getDate = getDate;
// Dart
@JS()
library static_interop;

// ignore: avoid_web_libraries_in_flutter
import 'dart:html' as html;

import 'package:js/js.dart';

@JS()
@staticInterop
class JSWindow {}

extension JSWindowExtension on JSWindow {
  external DateTime Function() getDate;
}

void main() {
  var jsWindow = html.window as JSWindow;

  final date = jsWindow.getDate();
  print(date.runtimeType);
  print(date);
  print(date.isUtc);
  print(date.timeZoneName);
  print(date.timeZoneOffset);
  print(date.weekday);
  print(date.isAfter(DateTime(1900)));
  print(date.toIso8601String());
}

This produces the following log:

LegacyJavaScriptObject
Wed Apr 12 2023 15:52:42 GMT+0200 (Central European Summer Time)
null
null
null
null
TypeError: date.isAfter is not a function
packages/js_interop_sample/main.dart 27:13                                        main$
web_entrypoint.dart 24:31                                                         <fn>
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 367:37  _checkAndCall
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 372:39  dcall
lib/ui/initialization.dart 77:15                                                  <fn>
dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 45:50                <fn>
dart-sdk/lib/async/zone.dart 1660:54                                              runUnary
dart-sdk/lib/async/future_impl.dart 147:18                                        handleValue
dart-sdk/lib/async/future_impl.dart 784:44                                        handleValueCallback
dart-sdk/lib/async/future_impl.dart 813:13                                        _propagateToListeners
dart-sdk/lib/async/future_impl.dart 584:5                                         [_completeWithValue]
dart-sdk/lib/async/future_impl.dart 657:7                                         callback
dart-sdk/lib/async/schedule_microtask.dart 40:11                                  _microtaskLoop
dart-sdk/lib/async/schedule_microtask.dart 49:5                                   _startMicrotaskLoop
dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 177:15               <fn>

Since we already get int / double from DDC/Dart2js (despite JS only having num), can we do something similar for DateTime? I'd rather avoid having to write a static interop definition for Date itself. A naive way would be to call .toISOString() and redirect that to DateTime.parse().

Dart SDK version: 3.0.0-290.3.beta (beta) (Tue Mar 21 16:51:50 2023 +0000) on "macos_x64"

srujzs commented 1 year ago

I think this is a feature request? Specifically to make DateTime compatible with JavaScript's Date type?

That example naturally won't work since DateTime is a Dart class and not a JS interop class. I'm surprised that it printed anything as the cast to DateTime should have failed.

I think there are several ways to do this (one of which you mention) and considering the effort it would take for this class to be interpreted differently, I'm not sure we'll fulfill this request. In the future, I believe there will be a Date interface exposed through package:web as well if writing the interface is cumbersome (which is reasonable, considering the number of members in the class).

navaronbracke commented 1 year ago

I think this is a feature request? Specifically to make DateTime compatible with JavaScript's Date type?

Yes, that's exactly what I meant. The type system does give unexpected results with external that claims to return one type, but in reality it can't return said type. Another example is external List<int> get foo; with JS interop. The compiler allows this (even though if some list is clearly a @JS() type, it shouldn't).

srujzs commented 1 year ago

Like generics, I believe our compilers can't determine the return type of the function since it wasn't created in Dart, so they might be just playing it safe here. If we did some kind of cast after the call with date, that fails, but that's not very ergonomic. Instead of a DateTime Function(), if you made the return type a DateTime and returned a Date, that will also fail correctly. IIRC, you can use List because it's a JSArray under the hood.

For what it's worth, the generics issue (and similarly, this issue) is something we're addressing when we're adding a separate type hierarchy for JavaScript types. Instead of a List or Function, external APIs should eventually use JSArray or JSFunction.