Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JS: Add a new "undefined" type (or proxy it as "void")? #31515

Open
matanlurey opened this issue Dec 1, 2017 · 3 comments
Open

JS: Add a new "undefined" type (or proxy it as "void")? #31515

matanlurey opened this issue Dec 1, 2017 · 3 comments
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. area-web Use area-web for Dart web related issues, including the DDC and dart2js compilers and JS interop. library-js web-js-interop Issues that impact all js interop

Comments

@matanlurey
Copy link
Contributor

Would supercede #24088.

In JavaScript some APIs have a distinction between undefined and null, i.e. they do:

function storeVar(name, value) {
  if (value == undefined) {
    value = _someDefault;
  }
}

This isn't terribly different from how we treat optional parameters today, i.e. the call site determines if it uses the default expression, if any, otherwise it is null. Unfortunately we lack the way to:

  1. Access the undefined JS type (for JS-interop) - see Need a way of passing undefined to JS via dart:js #24088.
  2. Use the undefined type ourselves as a type of (pun intended) sentinel value.

The second use case is extra interesting, because some JavaScript VM's can understand that null and undefined are sentinel-like values, but they can't tell that some arbitrary Dart object is (believing it is polymorphic) - but more importantly, because we'd like to retain type inference and types wherever possible.

final _notFoundSentinel = new Object();

abstract class DynamicInjector {
  dynamic get(Type token) {
    var result = _getImpl(token, _notFoundSentinel);
    if (identical(result, _notFoundSentinel)) {
      throw 'Not found';
    }
    return result;
  }

  dynamic _getImpl(Object token, Object defaultIfNotFound);
}

This works, OK (minus potential perf consequences of using an arbitrary object), but notice that I am forced to use a dynamic type for practically everything. In Dart 2, I might be able to "tighten" this up a bit:

final _notFoundSentinel = new Object();

abstract class DynamicInjector {
  T get<T>() {
    assert(T != dynamic, 'Type parameter T, which is the token, is required!');
    // Runtime error: _notFoundSentinel (Object) is not of type T (SomeClass)
    var result = _getImpl(token, _notFoundSentinel);
    if (identical(result, _notFoundSentinel)) {
      throw 'Not found';
    }
    return result;
  }

  Object _getImpl(Object token, Object defaultIfNotFound);
}

If I had an undefined type (or could use void for this purpose):

import 'package:js/js.dart' as js show undefined;

const _notFoundSentinel = js.undefined;

abstract class DynamicInjector {
  T get<T>() {
    assert(T != dynamic, 'Type parameter T, which is the token, is required!');
    // No runtime error, "undefined" is allowed for any type.
    var result = _getImpl(token, _notFoundSentinel);
    if (identical(result, _notFoundSentinel)) {
      throw 'Not found';
    }
    return result;
  }

  Object _getImpl(Object token, Object defaultIfNotFound);
}

It gets a little tricky with non-nullability, but again, when you're dealing with JS APIs you don't have the luxury of pretending everything is 100% Dart. This would be no less "bad" than writing the following code yourself:

final dynamic undefined = js.eval('void 0');

// Use undefined.
void someInteropFunction(JsChartLib chart, [String name]) {
  chart.name = name ?? undefined;
}
@rakudrama
Copy link
Member

One of the reasons dart2js and DDC treat both JavaScript null and JavaScript undefined as Dart null is so that Lists (Arrays) do not have to be explicitly null-filled. new List(10) can compile to new Array(10).

There are two values that are Dart null, just like there might be two Strings with value "a".
As a consequence, identical(js.null, js.undefined) --> true.
You would need another predicate to test for the value in Dart code.

If you want undefined to not be considered as Dart null, but be another null-like value that does not force you to use dynamic/Object, that would be a major change to the type system.

@matanlurey
Copy link
Contributor Author

I mean we can be (clever?) here, most of the time (99%) you don't care.

I'd be fine if it "seemed" like null to Dart2JS/DDC, but there was a way of telling what it is:

library js;

external bool isUndefined(Null nullOrUndefined);

Example use:

import 'package:js/js.dart' as js;

void callWithArg(dynamic arg) {
  if (arg == null) {
    if (js.isUndefined(arg)) {
      // This is actually, at runtime, `undefined`, not `null`.
    } else {
      // This is actually, at runtime, `null`.
    }
  }
}

Would that be OK? I'd still need a way to assign undefined:

library js;

/// Returns the JS "undefined" type, i.e. equivalent to `void 0`.
external Null get undefined;
import 'package:js/js.dart' as js;

void useJsInterop(ChartJs chart) {
  chart.someApi(js.undefined);
}

@anders-sandholm anders-sandholm added area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-js labels Dec 4, 2017
nshahan pushed a commit to angulardart/angular_components that referenced this issue Nov 12, 2018
Add a default value and try/catch block around WheelEvent.delta(X/Y/Mode).

html_dart2js does not throw error when deltaX/deltaY is null. So null handling is also added.

Add test for null deltaX/deltaY/deltaMode because 'undefined' cannot be passed as value.
dart-lang/sdk#24088
dart-lang/sdk#31515

PiperOrigin-RevId: 218253658
nshahan pushed a commit to angulardart/angular_components that referenced this issue Nov 15, 2018
Add a default value and try/catch block around WheelEvent.delta(X/Y/Mode).

html_dart2js does not throw error when deltaX/deltaY is null. So null handling is also added.

Add test for null deltaX/deltaY/deltaMode because 'undefined' cannot be passed as value.
dart-lang/sdk#24088
dart-lang/sdk#31515

PiperOrigin-RevId: 218253658
@mdebbar
Copy link
Contributor

mdebbar commented Nov 27, 2018

Adding insult to the injury here regarding @rakudrama's comment, new Array(10) in JS creates an array that has 10 "holes" not "undefined".

let a = new Array(10);

a.forEach((x) => {
  console.log('foo');
});

for (let i = 0; i < a.length; i++) {
  console.log('bar');
}

The above code prints "bar" 10 times but no "foo". The forEach callback (and map and filter) are only called on concrete values in the array, but holes are not, so they are skipped.

@vsmenon vsmenon added the area-web Use area-web for Dart web related issues, including the DDC and dart2js compilers and JS interop. label Jul 20, 2019
@natebosch natebosch added the web-js-interop Issues that impact all js interop label Jan 29, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. area-web Use area-web for Dart web related issues, including the DDC and dart2js compilers and JS interop. library-js web-js-interop Issues that impact all js interop
Projects
None yet
Development

No branches or pull requests

6 participants