Support invoking JS operators through dart:js_util.

Some JS functionality is only exposed through operators, such as
implicit type conversions and BigInt arithmetic. Other than requiring
some minor additions for the exponentiation (`**`) operator, supporting
these through `dart:js_util` is trivial.

Change-Id: I010674303e4f99b42d43b73095de69b8ddcdeb47
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/242680
Reviewed-by: Joshua Litt <joshualitt@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Stephen Adams <sra@google.com>
This commit is contained in:
Stephen Adams 2022-04-28 21:18:40 +00:00 committed by Commit Bot
parent 3458b041df
commit c50157e099
13 changed files with 343 additions and 4 deletions

View file

@ -678,6 +678,11 @@ class SizeEstimator implements NodeVisitor {
// We cannot remove parenthesis for "*" because of precision issues.
rightPrecedenceRequirement = UNARY;
break;
case "**":
leftPrecedenceRequirement = EXPONENTIATION;
// We cannot remove parenthesis for "**" because of precision issues.
rightPrecedenceRequirement = UNARY;
break;
default:
throw UnsupportedError("Forgot operator: $op");
}

View file

@ -258,4 +258,12 @@ void main() {
bar() {
}
}""");
// Exponentiation operator.
testExpression("a ** b");
//* Parsed with incorrect association:
testExpression("a ** b ** c", "(a ** b) ** c");
testExpression("(a ** b) ** c", "(a ** b) ** c");
testExpression("a ** (b ** c)", "a ** b ** c");
testExpression("a **= b");
}

View file

@ -609,6 +609,7 @@ class MiniJsParser {
'<<=': 17,
'>>=': 17,
'>>>=': 17,
'**=': 17,
'=': 17,
'||': 14,
'&&': 13,
@ -632,7 +633,8 @@ class MiniJsParser {
'-': 6,
'*': 5,
'/': 5,
'%': 5
'%': 5,
'**': 4,
};
static final UNARY_OPERATORS = {
'++',

View file

@ -1291,6 +1291,8 @@ class Binary extends Expression {
int get precedenceLevel {
// TODO(floitsch): switch to constant map.
switch (op) {
case '**':
return EXPONENTIATION;
case '*':
case '/':
case '%':

View file

@ -28,7 +28,8 @@ const RELATIONAL = EQUALITY + 1;
const SHIFT = RELATIONAL + 1;
const ADDITIVE = SHIFT + 1;
const MULTIPLICATIVE = ADDITIVE + 1;
const UNARY = MULTIPLICATIVE + 1;
const EXPONENTIATION = MULTIPLICATIVE + 1;
const UNARY = EXPONENTIATION + 1;
const LEFT_HAND_SIDE = UNARY + 1;
const CALL = LEFT_HAND_SIDE;
// We always emit `new` with parenthesis, so it uses ACCESS as its precedence.

View file

@ -856,6 +856,12 @@ class Printer implements NodeVisitor {
// We cannot remove parenthesis for "*" because of precision issues.
rightPrecedenceRequirement = UNARY;
break;
case '**':
// 'a ** b ** c' parses as 'a ** (b ** c)', so the left must have higher
// precedence.
leftPrecedenceRequirement = UNARY;
rightPrecedenceRequirement = EXPONENTIATION;
break;
default:
leftPrecedenceRequirement = EXPRESSION;
rightPrecedenceRequirement = EXPRESSION;

View file

@ -535,6 +535,7 @@ class MiniJsParser {
'<<=': 17,
'>>=': 17,
'>>>=': 17,
'**=': 17,
'=': 17,
'||': 14,
'&&': 13,
@ -558,7 +559,8 @@ class MiniJsParser {
'-': 6,
'*': 5,
'/': 5,
'%': 5
'%': 5,
'**': 4,
};
static final UNARY_OPERATORS = {
'++',

View file

@ -1638,6 +1638,8 @@ class Binary extends Expression {
int get precedenceLevel {
// TODO(floitsch): switch to constant map.
switch (op) {
case '**':
return EXPONENTIATION;
case '*':
case '/':
case '%':

View file

@ -16,7 +16,8 @@ const RELATIONAL = EQUALITY + 1;
const SHIFT = RELATIONAL + 1;
const ADDITIVE = SHIFT + 1;
const MULTIPLICATIVE = ADDITIVE + 1;
const UNARY = MULTIPLICATIVE + 1;
const EXPONENTIATION = MULTIPLICATIVE + 1;
const UNARY = EXPONENTIATION + 1;
const CALL = UNARY + 1;
const LEFT_HAND_SIDE = CALL + 1;
const PRIMARY = LEFT_HAND_SIDE + 1;

View file

@ -970,6 +970,12 @@ class Printer implements NodeVisitor<void> {
// We cannot remove parenthesis for "*" because of precision issues.
rightPrecedenceRequirement = UNARY;
break;
case '**':
// 'a ** b ** c' parses as 'a ** (b ** c)', so the left must have higher
// precedence.
leftPrecedenceRequirement = UNARY;
rightPrecedenceRequirement = EXPONENTIATION;
break;
default:
leftPrecedenceRequirement = EXPRESSION;
rightPrecedenceRequirement = EXPRESSION;

View file

@ -285,6 +285,90 @@ T _callConstructorUnchecked4<T>(
'Object', 'new #(#, #, #, #)', constr, arg1, arg2, arg3, arg4);
}
/// Perform JavaScript addition (`+`) on two values.
@pragma('dart2js:tryInline')
T add<T>(Object? first, Object? second) {
return JS<dynamic>('Object', '# + #', first, second);
}
/// Perform JavaScript subtraction (`-`) on two values.
@pragma('dart2js:tryInline')
T subtract<T>(Object? first, Object? second) {
return JS<dynamic>('Object', '# - #', first, second);
}
/// Perform JavaScript multiplication (`*`) on two values.
@pragma('dart2js:tryInline')
T multiply<T>(Object? first, Object? second) {
return JS<dynamic>('Object', '# * #', first, second);
}
/// Perform JavaScript division (`/`) on two values.
@pragma('dart2js:tryInline')
T divide<T>(Object? first, Object? second) {
return JS<dynamic>('Object', '# / #', first, second);
}
/// Perform JavaScript exponentiation (`**`) on two values.
@pragma('dart2js:tryInline')
T exponentiate<T>(Object? first, Object? second) {
return JS<dynamic>('Object', '# ** #', first, second);
}
/// Perform JavaScript remainder (`%`) on two values.
@pragma('dart2js:tryInline')
T modulo<T>(Object? first, Object? second) {
return JS<dynamic>('Object', '# % #', first, second);
}
/// Perform JavaScript equality comparison (`==`) on two values.
@pragma('dart2js:tryInline')
bool equal<T>(Object? first, Object? second) {
return JS<bool>('bool', '# == #', first, second);
}
/// Perform JavaScript strict equality comparison (`===`) on two values.
@pragma('dart2js:tryInline')
bool strictEqual<T>(Object? first, Object? second) {
return JS<bool>('bool', '# === #', first, second);
}
/// Perform JavaScript inequality comparison (`!=`) on two values.
@pragma('dart2js:tryInline')
bool notEqual<T>(Object? first, Object? second) {
return JS<bool>('bool', '# != #', first, second);
}
/// Perform JavaScript strict inequality comparison (`!==`) on two values.
@pragma('dart2js:tryInline')
bool strictNotEqual<T>(Object? first, Object? second) {
return JS<bool>('bool', '# !== #', first, second);
}
/// Perform JavaScript greater than comparison (`>`) of two values.
@pragma('dart2js:tryInline')
bool greaterThan<T>(Object? first, Object? second) {
return JS<bool>('bool', '# > #', first, second);
}
/// Perform JavaScript greater than or equal comparison (`>=`) of two values.
@pragma('dart2js:tryInline')
bool greaterThanOrEqual<T>(Object? first, Object? second) {
return JS<bool>('bool', '# >= #', first, second);
}
/// Perform JavaScript less than comparison (`<`) of two values.
@pragma('dart2js:tryInline')
bool lessThan<T>(Object? first, Object? second) {
return JS<bool>('bool', '# < #', first, second);
}
/// Perform JavaScript less than or equal comparison (`<=`) of two values.
@pragma('dart2js:tryInline')
bool lessThanOrEqual<T>(Object? first, Object? second) {
return JS<bool>('bool', '# <= #', first, second);
}
/// Exception for when the promise is rejected with a `null` or `undefined`
/// value.
///

View file

@ -0,0 +1,110 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Tests invoking JS BigInt functionality through js_util. This interop
// requires usage of the operator functions exposed through js_util.
@JS()
library js_util_bigint_test;
import 'package:js/js.dart';
import 'package:js/js_util.dart' as js_util;
import 'package:expect/minitest.dart';
@JS('BigInt')
external Object BigInt(Object value);
main() {
group('bigint', () {
test('addition', () {
final one = BigInt('1');
final two = BigInt('2');
final three = BigInt('3');
expect(js_util.strictEqual(js_util.add(one, two), three), isTrue);
expect(js_util.strictEqual(js_util.add(one, one), three), isFalse);
});
test('subtraction', () {
final one = BigInt('1');
final two = BigInt('2');
final three = BigInt('3');
expect(js_util.strictEqual(js_util.subtract(three, one), two), isTrue);
expect(js_util.strictEqual(js_util.subtract(three, two), two), isFalse);
});
test('multiplication', () {
final two = BigInt('2');
final four = BigInt('4');
expect(js_util.strictEqual(js_util.multiply(two, two), four), isTrue);
expect(js_util.strictEqual(js_util.multiply(two, four), four), isFalse);
});
test('division', () {
final two = BigInt('2');
final four = BigInt('4');
expect(js_util.strictEqual(js_util.divide(four, two), two), isTrue);
expect(js_util.strictEqual(js_util.divide(four, four), two), isFalse);
});
test('exponentiation', () {
final two = BigInt('2');
final three = BigInt('3');
final nine = BigInt('9');
expect(
js_util.strictEqual(js_util.exponentiate(three, two), nine), isTrue);
expect(js_util.strictEqual(js_util.exponentiate(three, three), nine),
isFalse);
});
test('exponentiation2', () {
final two = BigInt('2');
final three = BigInt('3');
final five = BigInt('5');
expect(
js_util.add(
'', js_util.exponentiate(js_util.exponentiate(five, three), two)),
'15625');
expect(
js_util.add(
'', js_util.exponentiate(five, js_util.exponentiate(three, two))),
'1953125');
});
test('modulo', () {
final zero = BigInt('0');
final three = BigInt('3');
final nine = BigInt('9');
expect(js_util.strictEqual(js_util.modulo(nine, three), zero), isTrue);
expect(js_util.strictEqual(js_util.modulo(nine, three), nine), isFalse);
});
test('equality', () {
final one = BigInt('1');
expect(js_util.equal(one, 1), isTrue);
expect(js_util.strictEqual(one, 1), isFalse);
expect(js_util.notEqual(one, 1), isFalse);
expect(js_util.strictNotEqual(one, 1), isTrue);
});
test('comparisons', () {
final zero = BigInt('0');
final one = BigInt('1');
final otherOne = BigInt('1');
expect(js_util.greaterThan(one, zero), isTrue);
expect(js_util.greaterThan(one, 0), isTrue);
expect(js_util.greaterThan(2, one), isTrue);
expect(js_util.greaterThanOrEqual(one, otherOne), isTrue);
expect(js_util.greaterThanOrEqual(one, 1), isTrue);
expect(js_util.greaterThanOrEqual(one, 2), isFalse);
expect(js_util.lessThan(one, zero), isFalse);
expect(js_util.lessThan(zero, one), isTrue);
expect(js_util.lessThan(one, 2), isTrue);
expect(js_util.lessThan(one, 0), isFalse);
expect(js_util.lessThanOrEqual(one, otherOne), isTrue);
expect(js_util.lessThanOrEqual(one, 1), isTrue);
expect(js_util.lessThanOrEqual(2, one), isFalse);
});
});
}

View file

@ -0,0 +1,110 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Tests invoking JS BigInt functionality through js_util. This interop
// requires usage of the operator functions exposed through js_util.
@JS()
library js_util_bigint_test;
import 'package:js/js.dart';
import 'package:js/js_util.dart' as js_util;
import 'package:expect/minitest.dart';
@JS('BigInt')
external Object BigInt(Object value);
main() {
group('bigint', () {
test('addition', () {
final one = BigInt('1');
final two = BigInt('2');
final three = BigInt('3');
expect(js_util.strictEqual(js_util.add(one, two), three), isTrue);
expect(js_util.strictEqual(js_util.add(one, one), three), isFalse);
});
test('subtraction', () {
final one = BigInt('1');
final two = BigInt('2');
final three = BigInt('3');
expect(js_util.strictEqual(js_util.subtract(three, one), two), isTrue);
expect(js_util.strictEqual(js_util.subtract(three, two), two), isFalse);
});
test('multiplication', () {
final two = BigInt('2');
final four = BigInt('4');
expect(js_util.strictEqual(js_util.multiply(two, two), four), isTrue);
expect(js_util.strictEqual(js_util.multiply(two, four), four), isFalse);
});
test('division', () {
final two = BigInt('2');
final four = BigInt('4');
expect(js_util.strictEqual(js_util.divide(four, two), two), isTrue);
expect(js_util.strictEqual(js_util.divide(four, four), two), isFalse);
});
test('exponentiation', () {
final two = BigInt('2');
final three = BigInt('3');
final nine = BigInt('9');
expect(
js_util.strictEqual(js_util.exponentiate(three, two), nine), isTrue);
expect(js_util.strictEqual(js_util.exponentiate(three, three), nine),
isFalse);
});
test('exponentiation2', () {
final two = BigInt('2');
final three = BigInt('3');
final five = BigInt('5');
expect(
js_util.add(
'', js_util.exponentiate(js_util.exponentiate(five, three), two)),
'15625');
expect(
js_util.add(
'', js_util.exponentiate(five, js_util.exponentiate(three, two))),
'1953125');
});
test('modulo', () {
final zero = BigInt('0');
final three = BigInt('3');
final nine = BigInt('9');
expect(js_util.strictEqual(js_util.modulo(nine, three), zero), isTrue);
expect(js_util.strictEqual(js_util.modulo(nine, three), nine), isFalse);
});
test('equality', () {
final one = BigInt('1');
expect(js_util.equal(one, 1), isTrue);
expect(js_util.strictEqual(one, 1), isFalse);
expect(js_util.notEqual(one, 1), isFalse);
expect(js_util.strictNotEqual(one, 1), isTrue);
});
test('comparisons', () {
final zero = BigInt('0');
final one = BigInt('1');
final otherOne = BigInt('1');
expect(js_util.greaterThan(one, zero), isTrue);
expect(js_util.greaterThan(one, 0), isTrue);
expect(js_util.greaterThan(2, one), isTrue);
expect(js_util.greaterThanOrEqual(one, otherOne), isTrue);
expect(js_util.greaterThanOrEqual(one, 1), isTrue);
expect(js_util.greaterThanOrEqual(one, 2), isFalse);
expect(js_util.lessThan(one, zero), isFalse);
expect(js_util.lessThan(zero, one), isTrue);
expect(js_util.lessThan(one, 2), isTrue);
expect(js_util.lessThan(one, 0), isFalse);
expect(js_util.lessThanOrEqual(one, otherOne), isTrue);
expect(js_util.lessThanOrEqual(one, 1), isTrue);
expect(js_util.lessThanOrEqual(2, one), isFalse);
});
});
}