[dart:js_interop] Remove ObjectLiteral

This annotation is unneeded as named arg external constructors
is enough to determine if an object literal is created. The one
exception is an empty object literal, but our guidance is to use
utility functions to create one instead. This is a better fit for
that purpose too as an interface for an empty object literal is
more uncommon.

CoreLibraryReviewExempt: Backend-specific library.
Change-Id: I10cf891601b28ff7e56129842d099ea28863626d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/307506
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Reviewed-by: Joshua Litt <joshualitt@google.com>
Commit-Queue: Srujan Gaddam <srujzs@google.com>
This commit is contained in:
Srujan Gaddam 2023-06-06 20:50:36 +00:00 committed by Commit Queue
parent 30b9ac9f87
commit 5b3a57908d
15 changed files with 99 additions and 185 deletions

View file

@ -27,6 +27,15 @@
[#51486]: https://github.com/dart-lang/sdk/issues/51486
[#52027]: https://github.com/dart-lang/sdk/issues/52027
#### `dart:js_interop`
- **Object literal constructors**:
`ObjectLiteral` is removed from `dart:js_interop`. It's no longer needed in
order to declare an object literal constructor with inline classes. As long as
an external constructor has at least one named parameter, it'll be treated as
an object literal constructor. If you want to create an object literal with no
named members, use `{}.jsify()`.
### Other libraries
#### `package:js`

View file

@ -326,8 +326,12 @@ class JsInteropChecks extends RecursiveVisitor {
// Check JS Interop positional and named parameters. Literal constructors
// can only have named parameters, and every other interop member can only
// have positional parameters.
final isObjectLiteralConstructor =
node.isInlineClassMember && hasObjectLiteralAnnotation(node);
final isObjectLiteralConstructor = node.isInlineClassMember &&
(_inlineExtensionIndex.getInlineDescriptor(node)!.kind ==
InlineClassMemberKind.Constructor ||
_inlineExtensionIndex.getInlineDescriptor(node)!.kind ==
InlineClassMemberKind.Factory) &&
node.function.namedParameters.isNotEmpty;
final isAnonymousFactory = _classHasAnonymousAnnotation && node.isFactory;
if (isObjectLiteralConstructor || isAnonymousFactory) {
_checkLiteralConstructorHasNoPositionalParams(node,

View file

@ -47,11 +47,6 @@ bool hasJSExportAnnotation(Annotatable a) =>
bool hasNativeAnnotation(Annotatable a) =>
a.annotations.any(_isNativeAnnotation);
/// Returns true iff the node has an `@ObjectLiteral(...)` annotation from
/// `dart:js_interop`.
bool hasObjectLiteralAnnotation(Annotatable a) =>
a.annotations.any(_isObjectLiteralAnnotation);
/// If [a] has a `@JS('...')` annotation, returns the value inside the
/// parentheses.
///
@ -158,15 +153,6 @@ bool _isNativeAnnotation(Expression value) {
c.enclosingLibrary.importUri == _jsHelper;
}
/// Returns true if [value] is the `ObjectLiteral` annotation from
/// `dart:js_interop`.
bool _isObjectLiteralAnnotation(Expression value) {
final c = annotationClass(value);
return c != null &&
c.name == 'ObjectLiteral' &&
c.enclosingLibrary.importUri == _jsInterop;
}
/// Returns the class of the instance referred to by metadata annotation [node].
///
/// For example:

View file

@ -16,7 +16,6 @@ import '../js_interop.dart'
hasDartJSInteropAnnotation,
hasJSInteropAnnotation,
hasNativeAnnotation,
hasObjectLiteralAnnotation,
hasStaticInteropAnnotation,
hasTrustTypesAnnotation;
@ -162,7 +161,7 @@ class JsUtilOptimizer extends Transformer {
} else if (_inlineExtensionIndex.isMethod(node)) {
return _getExternalMethodInvocationBuilder(
node, shouldTrustType, receiver);
} else if (_isNonLiteralConstructor(node)) {
} else if (_inlineExtensionIndex.isNonLiteralConstructor(node)) {
// Get the constructor object using the class name.
return _getExternalConstructorInvocationBuilder(
node, _getObjectOffGlobalThis(node, dottedPrefix.split('.')));
@ -173,19 +172,6 @@ class JsUtilOptimizer extends Transformer {
return null;
}
bool _isNonLiteralConstructor(Procedure node) {
if (node.isInlineClassMember) {
var kind = _inlineExtensionIndex.getInlineDescriptor(node)?.kind;
return (kind == InlineClassMemberKind.Constructor ||
kind == InlineClassMemberKind.Factory) &&
!hasObjectLiteralAnnotation(node);
} else {
return node.kind == ProcedureKind.Factory &&
node.enclosingClass != null &&
!hasAnonymousAnnotation(node.enclosingClass!);
}
}
/// Returns the prefixed JS name for the given [node] using the enclosing
/// library's, enclosing class' (if any), and member's `@JS` values.
///
@ -898,4 +884,33 @@ class InlineExtensionIndex {
InlineClassMemberKind.Operator,
ExtensionMemberKind.Operator,
ProcedureKind.Operator);
/// Return whether [node] is an external static interop constructor/factory.
///
/// If [literal] is true, we check if [node] is an object literal constructor,
/// and if not, we check that it's a non-literal constructor.
bool _isStaticInteropConstructor(Procedure node, {required bool literal}) {
if (!node.isExternal) return false;
if (node.isInlineClassMember) {
final kind = getInlineDescriptor(node)?.kind;
final namedParams = node.function.namedParameters;
return (kind == InlineClassMemberKind.Constructor ||
kind == InlineClassMemberKind.Factory) &&
literal
? namedParams.isNotEmpty
: namedParams.isEmpty;
} else if (node.kind == ProcedureKind.Factory &&
node.enclosingClass != null &&
hasJSInteropAnnotation(node.enclosingClass!)) {
final isAnonymous = hasAnonymousAnnotation(node.enclosingClass!);
return literal ? isAnonymous : !isAnonymous;
}
return false;
}
bool isLiteralConstructor(Procedure node) =>
_isStaticInteropConstructor(node, literal: true);
bool isNonLiteralConstructor(Procedure node) =>
_isStaticInteropConstructor(node, literal: false);
}

View file

@ -19,7 +19,6 @@ class IrAnnotationData {
final Set<ir.Class> _anonymousJsInteropClasses = {};
final Map<ir.Member, String> _jsInteropMemberNames = {};
final Set<ir.Class> _staticInteropClasses = {};
final Set<ir.Member> _jsInteropObjectLiterals = {};
final Map<ir.Member, List<PragmaAnnotationData>> _memberPragmaAnnotations =
{};
@ -58,10 +57,6 @@ class IrAnnotationData {
bool isStaticInteropClass(ir.Class node) =>
_staticInteropClasses.contains(node);
// Returns `true` if [node] is annotated with `@ObjectLiteral`.
bool isJsInteropObjectLiteral(ir.Member node) =>
_jsInteropObjectLiterals.contains(node);
// Returns the text from the `@JS(<text>)` annotation of [node], if any.
String? getJsInteropMemberName(ir.Member node) => _jsInteropMemberNames[node];
@ -88,17 +83,11 @@ class IrAnnotationData {
});
}
void forEachJsInteropMember(
void Function(ir.Member, String?,
{required bool isJsInteropObjectLiteral})
f) {
void forEachJsInteropMember(void Function(ir.Member, String?) f) {
_jsInteropLibraryNames.forEach((ir.Library library, _) {
for (ir.Member member in library.members) {
// `@ObjectLiteral` constructors are processed below as they can exist
// with or without a library with a `@JS` annotation.
if (member.isExternal && !isJsInteropObjectLiteral(member)) {
f(member, _jsInteropMemberNames[member] ?? member.name.text,
isJsInteropObjectLiteral: false);
if (member.isExternal) {
f(member, _jsInteropMemberNames[member] ?? member.name.text);
}
}
});
@ -109,13 +98,9 @@ class IrAnnotationData {
if (member.isExternal) {
name ??= member.name.text;
}
f(member, name, isJsInteropObjectLiteral: false);
f(member, name);
}
});
_jsInteropObjectLiterals.forEach((ir.Member member) {
f(member, _jsInteropMemberNames[member] ?? member.name.text,
isJsInteropObjectLiteral: true);
});
}
void forEachNativeMethodData(
@ -166,13 +151,6 @@ IrAnnotationData processAnnotations(ModularCore modularCore) {
data._jsInteropMemberNames[member] = jsName;
}
bool isJsInteropObjectLiteralMember =
_isJsInteropObjectLiteral(constant);
if (isJsInteropObjectLiteralMember) {
data._jsInteropObjectLiterals.add(member);
data._jsInteropMemberNames[member] = member.name.text;
}
bool isNativeMember = _isNativeMember(constant);
if (isNativeMember) {
data._nativeMembers.add(member);
@ -364,12 +342,6 @@ bool _isStaticInterop(ir.Constant constant) {
Uris.dart__js_annotations);
}
bool _isJsInteropObjectLiteral(ir.Constant constant) {
return constant is ir.InstanceConstant &&
constant.classNode.name == 'ObjectLiteral' &&
constant.classNode.enclosingLibrary.importUri == Uris.dart__js_interop;
}
class PragmaAnnotationData {
// TODO(johnniwinther): Support non 'dart2js:' pragma names if necessary.
final String suffix;

View file

@ -38,9 +38,6 @@ class NativeBasicDataBuilder {
/// The JavaScript members implemented via typed JavaScript interop.
final Map<MemberEntity, String> _jsInteropMembers = {};
/// The JavaScript interop members annotated with `@ObjectLiteral`.
final Set<MemberEntity> _jsInteropObjectLiterals = {};
/// Sets the native tag info for [cls].
///
/// The tag info string contains comma-separated 'words' which are either
@ -110,8 +107,7 @@ class NativeBasicDataBuilder {
/// Marks [element] as an explicit part of js interop and sets the explicit js
/// interop [name] for the member [element].
void markAsJsInteropMember(MemberEntity element, String name,
{required bool isJsInteropObjectLiteral}) {
void markAsJsInteropMember(MemberEntity element, String name) {
assert(
!_closed,
failedAt(
@ -119,7 +115,6 @@ class NativeBasicDataBuilder {
"NativeBasicDataBuilder is closed. "
"Trying to mark $element as a js-interop member."));
_jsInteropMembers[element] = name;
if (isJsInteropObjectLiteral) _jsInteropObjectLiterals.add(element);
}
/// Creates the [NativeBasicData] object for the data collected in this
@ -134,8 +129,7 @@ class NativeBasicDataBuilder {
_jsInteropClasses,
_anonymousJsInteropClasses,
_staticInteropClasses,
_jsInteropMembers,
_jsInteropObjectLiterals);
_jsInteropMembers);
}
void reopenForTesting() {
@ -174,9 +168,6 @@ class NativeBasicData {
/// The JavaScript members implemented via typed JavaScript interop.
final Map<MemberEntity, String?> _jsInteropMembers;
/// JavaScript interop constructors annotated with `@ObjectLiteral`.
final Set<MemberEntity> _jsInteropObjectLiterals;
NativeBasicData(
this._env,
this._isAllowInteropUsed,
@ -185,8 +176,7 @@ class NativeBasicData {
this._jsInteropClasses,
this._anonymousJsInteropClasses,
this._staticInteropClasses,
this._jsInteropMembers,
this._jsInteropObjectLiterals);
this._jsInteropMembers);
factory NativeBasicData.fromIr(
KernelToElementMap map, IrAnnotationData data) {
@ -197,7 +187,6 @@ class NativeBasicData {
Set<ClassEntity> anonymousJsInteropClasses = {};
Set<ClassEntity> staticInteropClasses = {};
Map<MemberEntity, String?> jsInteropMembers = {};
Set<MemberEntity> jsInteropObjectLiterals = {};
data.forEachNativeClass((ir.Class node, String text) {
nativeClassTagInfo[map.getClass(node)] = NativeClassTag(text);
@ -217,17 +206,13 @@ class NativeBasicData {
staticInteropClasses.add(cls);
}
});
data.forEachJsInteropMember((ir.Member node, String? name,
{required bool isJsInteropObjectLiteral}) {
data.forEachJsInteropMember((ir.Member node, String? name) {
// TODO(49428): Are there other members that we should ignore here?
// There are non-external and unannotated members because the source code
// doesn't contain them. (e.g. default constructor) Does it make sense to
// consider these valid JS members?
if (memberIsIgnorable(node)) return;
jsInteropMembers[map.getMember(node)] = name;
if (isJsInteropObjectLiteral) {
jsInteropObjectLiterals.add(map.getMember(node));
}
});
return NativeBasicData(
@ -238,8 +223,7 @@ class NativeBasicData {
jsInteropClasses,
anonymousJsInteropClasses,
staticInteropClasses,
jsInteropMembers,
jsInteropObjectLiterals);
jsInteropMembers);
}
/// Deserializes a [NativeBasicData] object from [source].
@ -261,7 +245,6 @@ class NativeBasicData {
Set<ClassEntity> staticInteropClasses = source.readClasses().toSet();
Map<MemberEntity, String?> jsInteropMembers = source
.readMemberMap((MemberEntity member) => source.readStringOrNull());
Set<MemberEntity> jsInteropObjectLiterals = source.readMembers().toSet();
source.end(tag);
return NativeBasicData(
elementEnvironment,
@ -271,8 +254,7 @@ class NativeBasicData {
jsInteropClasses,
anonymousJsInteropClasses,
staticInteropClasses,
jsInteropMembers,
jsInteropObjectLiterals);
jsInteropMembers);
}
/// Serializes this [NativeBasicData] to [sink].
@ -289,7 +271,6 @@ class NativeBasicData {
sink.writeClasses(_staticInteropClasses);
sink.writeMemberMap(_jsInteropMembers,
(MemberEntity member, String? name) => sink.writeStringOrNull(name));
sink.writeMembers(_jsInteropObjectLiterals);
sink.end(tag);
}
@ -396,8 +377,6 @@ class NativeBasicData {
map.toBackendClassSet(_staticInteropClasses);
Map<MemberEntity, String?> jsInteropMembers =
map.toBackendMemberMap(_jsInteropMembers, identity);
Set<MemberEntity> jsInteropObjectLiterals =
map.toBackendMemberSet(_jsInteropObjectLiterals);
return NativeBasicData(
environment,
isAllowInteropUsed,
@ -406,8 +385,7 @@ class NativeBasicData {
jsInteropClasses,
anonymousJsInteropClasses,
staticInteropClasses,
jsInteropMembers,
jsInteropObjectLiterals);
jsInteropMembers);
}
}
@ -625,10 +603,6 @@ class NativeData implements NativeBasicData {
Map<MemberEntity, String?> get _jsInteropMembers =>
_nativeBasicData._jsInteropMembers;
@override
Set<MemberEntity> get _jsInteropObjectLiterals =>
_nativeBasicData._jsInteropObjectLiterals;
/// Returns `true` if [element] has an `@Anonymous` annotation.
bool isAnonymousJsInteropClass(ClassEntity element) {
return _anonymousJsInteropClasses.contains(element);
@ -639,11 +613,6 @@ class NativeData implements NativeBasicData {
return _staticInteropClasses.contains(element);
}
/// Returns `true` if [element] has an `@ObjectLiteral` annotation.
bool isJsInteropObjectLiteral(MemberEntity element) {
return _jsInteropObjectLiterals.contains(element);
}
@override
bool isNativeClass(ClassEntity element) =>
_nativeBasicData.isNativeClass(element);

View file

@ -95,9 +95,7 @@ class KernelAnnotationProcessor {
/*reporter.reportErrorMessage(
function, MessageKind.JS_INTEROP_NON_EXTERNAL_MEMBER);*/
} else {
_nativeBasicDataBuilder.markAsJsInteropMember(function, memberName,
isJsInteropObjectLiteral:
annotationData.isJsInteropObjectLiteral(memberNode));
_nativeBasicDataBuilder.markAsJsInteropMember(function, memberName);
// TODO(johnniwinther): It is unclear whether library can be
// implicitly js-interop. For now we allow it.
isJsLibrary = true;
@ -142,8 +140,7 @@ class KernelAnnotationProcessor {
// TODO(johnniwinther): The documentation states that explicit
// member name annotations are not allowed on instance members.
_nativeBasicDataBuilder.markAsJsInteropMember(
function, memberName,
isJsInteropObjectLiteral: false);
function, memberName);
}
}
});
@ -160,8 +157,7 @@ class KernelAnnotationProcessor {
// TODO(johnniwinther): The documentation states that explicit
// member name annotations are not allowed on instance members.
_nativeBasicDataBuilder.markAsJsInteropMember(
constructor, memberName,
isJsInteropObjectLiteral: false);
constructor, memberName);
}
});
}

View file

@ -4026,13 +4026,17 @@ class KernelSsaGraphBuilder extends ir.Visitor<void> with ir.VisitorVoidMixin {
// Only anonymous factory or inline class literal constructors involving
// JS interop are allowed to have named parameters. Otherwise, throw an
// error.
final function =
_elementMap.getMember(target.parent as ir.Member) as FunctionEntity;
if (function is ConstructorEntity &&
function.isFactoryConstructor &&
_nativeData.isAnonymousJsInteropClass(function.enclosingClass) ||
function.isTopLevel &&
_nativeData.isJsInteropObjectLiteral(function)) {
final member = target.parent as ir.Member;
final function = _elementMap.getMember(member) as FunctionEntity;
bool isAnonymousFactory = function is ConstructorEntity &&
function.isFactoryConstructor &&
_nativeData.isAnonymousJsInteropClass(function.enclosingClass);
// JS interop checks assert that the only inline class interop member that
// has named parameters is an object literal constructor. We could do a
// more robust check by visiting all inline classes and recording
// descriptors, but that's expensive.
bool isObjectLiteralConstructor = member.isInlineClassMember;
if (isAnonymousFactory || isObjectLiteralConstructor) {
// TODO(sra): Have a "CompiledArguments" structure to just update with
// what values we have rather than creating a map and de-populating it.
Map<String, HInstruction> namedValues = _visitNamedArguments(arguments);
@ -5471,10 +5475,18 @@ class KernelSsaGraphBuilder extends ir.Visitor<void> with ir.VisitorVoidMixin {
FunctionEntity element, List<HInstruction?> arguments) {
assert(closedWorld.nativeData.isJsInteropMember(element));
if (element is ConstructorEntity &&
element.isFactoryConstructor &&
_nativeData.isAnonymousJsInteropClass(element.enclosingClass) ||
element.isTopLevel && _nativeData.isJsInteropObjectLiteral(element)) {
bool isAnonymousFactory = element is ConstructorEntity &&
element.isFactoryConstructor &&
_nativeData.isAnonymousJsInteropClass(element.enclosingClass);
ir.Node node = _elementMap.getMemberDefinition(element).node;
// JS interop checks assert that the only inline class interop member that
// has named parameters is an object literal constructor. We could do a more
// robust check by visiting all inline classes and recording descriptors,
// but that's expensive.
bool isObjectLiteralConstructor = node is ir.Procedure &&
node.isInlineClassMember &&
node.function.namedParameters.isNotEmpty;
if (isAnonymousFactory || isObjectLiteralConstructor) {
// Constructor that is syntactic sugar for creating a JavaScript object
// literal.
int i = 0;
@ -5487,10 +5499,10 @@ class KernelSsaGraphBuilder extends ir.Visitor<void> with ir.VisitorVoidMixin {
// (including factory constructors.)
// TODO(johnniwinther): can we elide those parameters? This should be
// consistent with what we do with instance methods.
final node =
_elementMap.getMemberDefinition(element).node as ir.Procedure;
final procedure = node as ir.Procedure;
List<ir.VariableDeclaration> namedParameters =
node.function.namedParameters.toList();
procedure.function.namedParameters.toList();
namedParameters.sort(nativeOrdering);
for (ir.VariableDeclaration variable in namedParameters) {
String parameterName = variable.name!;

View file

@ -3,11 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:_js_interop_checks/src/js_interop.dart'
show
getJSName,
hasAnonymousAnnotation,
hasJSInteropAnnotation,
hasObjectLiteralAnnotation;
show getJSName, hasAnonymousAnnotation, hasJSInteropAnnotation;
import 'package:_js_interop_checks/src/transformations/js_util_optimizer.dart'
show InlineExtensionIndex;
import 'package:dart2wasm/js/method_collector.dart';
@ -443,7 +439,10 @@ class InteropSpecializerFactory {
if ((kind == InlineClassMemberKind.Constructor ||
kind == InlineClassMemberKind.Factory)) {
return _getSpecializerForConstructor(
hasObjectLiteralAnnotation(node), node, clsString, invocation);
_inlineExtensionIndex.isLiteralConstructor(node),
node,
clsString,
invocation);
} else {
final memberSelectorString =
_getJSString(node, nodeDescriptor.name.text);

View file

@ -6187,10 +6187,11 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
}
if (target.isExternal &&
target.isInlineClassMember &&
hasObjectLiteralAnnotation(target)) {
// Only JS interop inline class object literal constructors have the
// `@ObjectLiteral(...)` annotation.
assert(node.arguments.positional.isEmpty);
target.function.namedParameters.isNotEmpty) {
// JS interop checks assert that only external inline class factories have
// named parameters. We could do a more robust check by visiting all
// inline classes and recording descriptors, but that's expensive.
assert(target.function.positionalParameters.isEmpty);
return _emitObjectLiteral(
Arguments(node.arguments.positional,
types: node.arguments.types, named: node.arguments.named),

View file

@ -120,18 +120,6 @@ bool isStaticInteropType(Class namedClass) {
bool isUndefinedAnnotation(Expression value) =>
isBuiltinAnnotation(value, '_js_helper', '_Undefined');
bool isObjectLiteralAnnotation(Expression value) {
final c = getAnnotationClass(value);
return c != null &&
c.name == 'ObjectLiteral' &&
_isLibrary(c.enclosingLibrary, ['dart:js_interop']);
}
/// Returns whether [a] is annotated with the `@ObjectLiteral(...)` annotation
/// from `dart:js_interop`.
bool hasObjectLiteralAnnotation(Annotatable a) =>
a.annotations.any(isObjectLiteralAnnotation);
/// Returns true iff the class has an `@JS(...)` annotation from
/// `package:js`, `dart:_js_annotations`, or `dart:js_interop`.
///

View file

@ -51,21 +51,6 @@ class JS {
const JS([this.name]);
}
/// The annotation for object literal constructors.
///
/// Use this on an external constructor for an interop inline class that only
/// has named args in order to create object literals performantly. The
/// resulting object literal will use the parameter names as keys and the
/// provided arguments as the values.
///
/// Note that object literal constructors ignore the default values of
/// parameters and only include the arguments you provide in the invocation of
/// the constructor. This is similar to the `@anonymous` annotation in
/// `package:js`.
class ObjectLiteral {
const ObjectLiteral();
}
/// The JS types users should use to write their external APIs.
///
/// These are meant to separate the Dart and JS type hierarchies statically.

View file

@ -14,12 +14,10 @@ inline class Inline {
final JSObject obj;
external Inline();
external Inline.named();
@ObjectLiteral()
external Inline.literal({JSNumber? a});
Inline.nonExternal(this.obj);
// TODO(srujzs): Once we have inline class factories, test these.
// external factory Inline.fact();
// @ObjectLiteral()
// external factory Inline.literalFact({JSNumber? a});
// factory Inline.nonExternalFact() => Inline();

View file

@ -15,8 +15,7 @@ import 'package:expect/minitest.dart';
@JS()
inline class Literal {
final JSObject obj;
@ObjectLiteral()
external Literal({double? a, String b = 'unused', bool? c = null});
external Literal({double? a, String b, bool? c});
}
@JS('Object.keys')

View file

@ -13,35 +13,16 @@ external void topLevel({JSNumber named});
@JS()
inline class Inline {
external Inline({JSNumber named});
// ^
// [web] Named parameters for JS interop functions are only allowed in object literal constructors or @anonymous factories.
final JSObject obj;
external Inline.positionalNamed(JSNumber positional, {JSNumber named});
// ^
// [web] Named parameters for JS interop functions are only allowed in object literal constructors or @anonymous factories.
// ^
// [web] Object literal constructors should not contain any positional parameters.
external static void staticMethod({JSNumber named});
// ^
// [web] Named parameters for JS interop functions are only allowed in object literal constructors or @anonymous factories.
external void method({JSNumber named});
// ^
// [web] Named parameters for JS interop functions are only allowed in object literal constructors or @anonymous factories.
@ObjectLiteral()
external Inline.positionalLiteral(JSNumber positional);
// ^
// [web] Object literal constructors should not contain any positional parameters.
@ObjectLiteral()
external Inline.optionalLiteral([JSNumber optional]);
// ^
// [web] Object literal constructors should not contain any positional parameters.
@ObjectLiteral()
external Inline.positionalOptionalLiteral(JSNumber positional,
// ^
// [web] Object literal constructors should not contain any positional parameters.
[JSNumber optional]);
@ObjectLiteral()
external Inline.positionalNamedLiteral(JSNumber positional, {JSNumber named});
// ^
// [web] Object literal constructors should not contain any positional parameters.
}
extension on Inline {