diff --git a/CHANGELOG.md b/CHANGELOG.md index 669a1531fb2..a3842e25224 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,10 @@ opaque Dart value instead of only externalizing the value. Like the JS backends, you'll now get a more useful error when trying to use it in another Dart runtime. +- Added `isA` helper to make type checks easier with interop types. See + [#54138][] for more details. + +[#54138]: https://github.com/dart-lang/sdk/issues/54138 ## 3.3.0 diff --git a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart index 9e1e5ad326c..9050539f88b 100644 --- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart +++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart @@ -8857,6 +8857,16 @@ const MessageCode messageJsInteropInvalidStaticClassMemberName = const MessageCo problemMessage: r"""JS interop static class members cannot have '.' in their JS name."""); +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Code codeJsInteropIsATearoff = messageJsInteropIsATearoff; + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const MessageCode messageJsInteropIsATearoff = const MessageCode( + "JsInteropIsATearoff", + problemMessage: r"""'isA' can't be torn off.""", + correctionMessage: + r"""Use a method that calls 'isA' and tear off that method instead."""); + // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. const Template< Message Function( diff --git a/pkg/_js_interop_checks/lib/js_interop_checks.dart b/pkg/_js_interop_checks/lib/js_interop_checks.dart index 887585289c4..566f330d202 100644 --- a/pkg/_js_interop_checks/lib/js_interop_checks.dart +++ b/pkg/_js_interop_checks/lib/js_interop_checks.dart @@ -60,7 +60,7 @@ import 'src/js_interop.dart'; class JsInteropChecks extends RecursiveVisitor { final Set _constantCache = {}; final CoreTypes _coreTypes; - late final ExtensionIndex _extensionIndex; + late final ExtensionIndex extensionIndex; final Procedure _functionToJSTarget; // Errors on constants need source information, so we use the surrounding // `ConstantExpression` as the source. @@ -163,7 +163,7 @@ class JsInteropChecks extends RecursiveVisitor { 'dart:js_interop', 'FunctionToJSExportedDartFunction|get#toJS'), _staticTypeContext = StatefulStaticTypeContext.stacked( TypeEnvironment(_coreTypes, hierarchy)) { - _extensionIndex = + extensionIndex = ExtensionIndex(_coreTypes, _staticTypeContext.typeEnvironment); } @@ -209,7 +209,7 @@ class JsInteropChecks extends RecursiveVisitor { node.fileOffset, node.name.length, node.fileUri); } if (hasDartJSInteropAnnotation(node) && - !_extensionIndex.isInteropExtensionType(node)) { + !extensionIndex.isInteropExtensionType(node)) { _reporter.report( templateJsInteropExtensionTypeNotInterop.withArguments( node.name, node.declaredRepresentationType, true), @@ -352,9 +352,9 @@ class JsInteropChecks extends RecursiveVisitor { // can only have named parameters, and every other interop member can only // have positional parameters. final isObjectLiteralConstructor = node.isExtensionTypeMember && - (_extensionIndex.getExtensionTypeDescriptor(node)!.kind == + (extensionIndex.getExtensionTypeDescriptor(node)!.kind == ExtensionTypeMemberKind.Constructor || - _extensionIndex.getExtensionTypeDescriptor(node)!.kind == + extensionIndex.getExtensionTypeDescriptor(node)!.kind == ExtensionTypeMemberKind.Factory) && node.function.namedParameters.isNotEmpty; final isAnonymousFactory = _classHasAnonymousAnnotation && node.isFactory; @@ -381,13 +381,13 @@ class JsInteropChecks extends RecursiveVisitor { _checkNoParamInitializersForStaticInterop(node.function); late Annotatable? annotatable; if (node.isExtensionTypeMember) { - annotatable = _extensionIndex.getExtensionType(node); + annotatable = extensionIndex.getExtensionType(node); } else if (node.isExtensionMember) { - annotatable = _extensionIndex.getExtensionAnnotatable(node); + annotatable = extensionIndex.getExtensionAnnotatable(node); if (annotatable != null) { // We do not support external extension members with the 'static' // keyword currently. - if (_extensionIndex.getExtensionDescriptor(node)!.isStatic) { + if (extensionIndex.getExtensionDescriptor(node)!.isStatic) { report( messageJsInteropExternalExtensionMemberWithStaticDisallowed); } @@ -644,13 +644,13 @@ class JsInteropChecks extends RecursiveVisitor { if (member.isExternal) { if (_isAllowedExternalUsage(member)) return; if (member.isExtensionMember) { - final annotatable = _extensionIndex.getExtensionAnnotatable(member); + final annotatable = extensionIndex.getExtensionAnnotatable(member); if (annotatable == null) { _reporter.report(messageJsInteropExternalExtensionMemberOnTypeInvalid, member.fileOffset, member.name.text.length, member.fileUri); } } else if (member.isExtensionTypeMember) { - final extensionType = _extensionIndex.getExtensionType(member); + final extensionType = extensionIndex.getExtensionType(member); if (extensionType == null) { _reporter.report(messageJsInteropExtensionTypeMemberNotInterop, member.fileOffset, member.name.text.length, member.fileUri); @@ -680,25 +680,27 @@ class JsInteropChecks extends RecursiveVisitor { /// Returns whether an error was triggered. bool _checkDisallowedTearoff(Member member, TreeNode? context) { if (context == null || context.location == null) return false; - if (member.isExternal) { + // TODO(srujzs): Delete the check for patched member once + // https://github.com/dart-lang/sdk/issues/53367 is resolved. + if (member.isExternal && !JsInteropChecks.isPatchedMember(member)) { var memberKind = ''; var memberName = ''; if (member.isExtensionTypeMember) { // Extension type interop members can not be torn off. - if (_extensionIndex.getExtensionType(member) == null) { + if (extensionIndex.getExtensionType(member) == null) { return false; } memberKind = 'extension type interop member'; memberName = - _extensionIndex.getExtensionTypeDescriptor(member)!.name.text; + extensionIndex.getExtensionTypeDescriptor(member)!.name.text; if (memberName.isEmpty) memberName = 'new'; } else if (member.isExtensionMember) { // JS interop members can not be torn off. - if (_extensionIndex.getExtensionAnnotatable(member) == null) { + if (extensionIndex.getExtensionAnnotatable(member) == null) { return false; } memberKind = 'extension interop member'; - memberName = _extensionIndex.getExtensionDescriptor(member)!.name.text; + memberName = extensionIndex.getExtensionDescriptor(member)!.name.text; } else if (member.enclosingClass != null) { // @staticInterop members can not be torn off. final enclosingClass = member.enclosingClass!; @@ -783,14 +785,14 @@ class JsInteropChecks extends RecursiveVisitor { var isInvalidOperator = false; var operatorHasRenaming = false; if ((node.isExtensionTypeMember && - _extensionIndex.getExtensionTypeDescriptor(node)?.kind == + extensionIndex.getExtensionTypeDescriptor(node)?.kind == ExtensionTypeMemberKind.Operator) || (node.isExtensionMember && - _extensionIndex.getExtensionDescriptor(node)?.kind == + extensionIndex.getExtensionDescriptor(node)?.kind == ExtensionMemberKind.Operator)) { final operator = - _extensionIndex.getExtensionTypeDescriptor(node)?.name.text ?? - _extensionIndex.getExtensionDescriptor(node)?.name.text; + extensionIndex.getExtensionTypeDescriptor(node)?.name.text ?? + extensionIndex.getExtensionDescriptor(node)?.name.text; isInvalidOperator = operator != '[]' && operator != '[]='; operatorHasRenaming = getJSName(node).isNotEmpty; } else if (!node.isStatic && node.kind == ProcedureKind.Operator) { @@ -858,8 +860,8 @@ class JsInteropChecks extends RecursiveVisitor { /// Otherwise, return null. Member? _getTornOffFromGeneratedTearOff(Procedure procedure) { final tornOff = - _extensionIndex.getExtensionTypeMemberForTearOff(procedure) ?? - _extensionIndex.getExtensionMemberForTearOff(procedure); + extensionIndex.getExtensionTypeMemberForTearOff(procedure) ?? + extensionIndex.getExtensionMemberForTearOff(procedure); if (tornOff != null) return tornOff.asMember; final name = extractConstructorNameFromTearOff(procedure.name); if (name == null) return null; @@ -895,10 +897,10 @@ class JsInteropChecks extends RecursiveVisitor { if (member.isExternal) { if (_classHasJSAnnotation) return true; if (member.isExtensionMember) { - return _extensionIndex.getExtensionAnnotatable(member) != null; + return extensionIndex.getExtensionAnnotatable(member) != null; } if (member.isExtensionTypeMember) { - return _extensionIndex.getExtensionType(member) != null; + return extensionIndex.getExtensionType(member) != null; } if (member.enclosingClass == null) { // dart:js_interop requires top-levels to be @JS-annotated. package:js @@ -926,11 +928,11 @@ class JsInteropChecks extends RecursiveVisitor { // TODO(srujzs): We may want to support type parameters with primitive // bounds that are themselves allowed e.g. `num`. If so, we should handle // that change in dart2wasm. - if (_extensionIndex.isAllowedRepresentationType(bound)) return true; + if (extensionIndex.isAllowedRepresentationType(bound)) return true; } // If it can be used as a representation type of an interop extension type, // it is okay to be used on an external member. - if (_extensionIndex.isAllowedRepresentationType(type)) return true; + if (extensionIndex.isAllowedRepresentationType(type)) return true; if (type is InterfaceType) { final cls = type.classNode; // Primitive types are okay. diff --git a/pkg/_js_interop_checks/lib/src/transformations/export_creator.dart b/pkg/_js_interop_checks/lib/src/transformations/export_creator.dart deleted file mode 100644 index e9179c639d3..00000000000 --- a/pkg/_js_interop_checks/lib/src/transformations/export_creator.dart +++ /dev/null @@ -1,384 +0,0 @@ -// Copyright (c) 2022, 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. - -// ignore_for_file: implementation_imports - -import 'package:_fe_analyzer_shared/src/messages/codes.dart' - show templateJsInteropExportClassNotMarkedExportable; -import 'package:_js_interop_checks/js_interop_checks.dart' - show JsInteropDiagnosticReporter; -import 'package:_js_interop_checks/src/js_interop.dart' as js_interop; -import 'package:front_end/src/fasta/fasta_codes.dart' - show - templateJsInteropExportInvalidInteropTypeArgument, - templateJsInteropExportInvalidTypeArgument; -import 'package:kernel/ast.dart'; -import 'package:kernel/type_environment.dart'; - -import 'export_checker.dart'; -import 'static_interop_mock_validator.dart'; - -class ExportCreator extends Transformer { - final Procedure _callMethodVarArgs; - final Procedure _createDartExport; - final Procedure _createJSInteropWrapper; - final Procedure _createStaticInteropMock; - final JsInteropDiagnosticReporter _diagnosticReporter; - final ExportChecker _exportChecker; - final Procedure _functionToJS; - final Procedure _getProperty; - final Procedure _globalContext; - final ExtensionTypeDeclaration _jsAny; - final ExtensionTypeDeclaration _jsObject; - final Procedure _setProperty; - final Procedure _stringToJS; - final StaticInteropMockValidator _staticInteropMockValidator; - final TypeEnvironment _typeEnvironment; - - ExportCreator( - this._typeEnvironment, this._diagnosticReporter, this._exportChecker) - : _callMethodVarArgs = _typeEnvironment.coreTypes.index - .getTopLevelProcedure('dart:js_interop_unsafe', - 'JSObjectUnsafeUtilExtension|callMethodVarArgs'), - _createDartExport = _typeEnvironment.coreTypes.index - .getTopLevelProcedure('dart:js_util', 'createDartExport'), - _createJSInteropWrapper = _typeEnvironment.coreTypes.index - .getTopLevelProcedure('dart:js_interop', 'createJSInteropWrapper'), - _createStaticInteropMock = _typeEnvironment.coreTypes.index - .getTopLevelProcedure('dart:js_util', 'createStaticInteropMock'), - _functionToJS = _typeEnvironment.coreTypes.index.getTopLevelProcedure( - 'dart:js_interop', 'FunctionToJSExportedDartFunction|get#toJS'), - _getProperty = _typeEnvironment.coreTypes.index.getTopLevelProcedure( - 'dart:js_interop_unsafe', 'JSObjectUnsafeUtilExtension|[]'), - _globalContext = _typeEnvironment.coreTypes.index - .getTopLevelProcedure('dart:js_interop', 'get:globalContext'), - _jsAny = _typeEnvironment.coreTypes.index - .getExtensionType('dart:js_interop', 'JSAny'), - _jsObject = _typeEnvironment.coreTypes.index - .getExtensionType('dart:js_interop', 'JSObject'), - _setProperty = _typeEnvironment.coreTypes.index.getTopLevelProcedure( - 'dart:js_interop_unsafe', 'JSObjectUnsafeUtilExtension|[]='), - _stringToJS = _typeEnvironment.coreTypes.index.getTopLevelProcedure( - 'dart:js_interop', 'StringToJSString|get#toJS'), - _staticInteropMockValidator = StaticInteropMockValidator( - _diagnosticReporter, _exportChecker, _typeEnvironment); - - @override - TreeNode visitStaticInvocation(StaticInvocation node) { - final target = node.target; - if (target == _createDartExport || target == _createJSInteropWrapper) { - final typeArguments = node.arguments.types; - assert(typeArguments.length == 1); - if (_verifyExportable(node, typeArguments[0])) { - final interface = typeArguments[0] as InterfaceType; - if (target == _createJSInteropWrapper) { - return _createExport(node, interface, - ExtensionType(_jsObject, Nullability.nonNullable)); - } - return _createExport(node, interface); - } - } else if (target == _createStaticInteropMock) { - final typeArguments = node.arguments.types; - assert(typeArguments.length == 2); - final staticInteropType = typeArguments[0]; - final dartType = typeArguments[1]; - - final exportable = _verifyExportable(node, dartType); - final staticInteropTypeArgumentCorrect = _staticInteropMockValidator - .validateStaticInteropTypeArgument(node, staticInteropType); - final dartTypeArgumentCorrect = - _staticInteropMockValidator.validateDartTypeArgument(node, dartType); - if (exportable && - staticInteropTypeArgumentCorrect && - dartTypeArgumentCorrect && - _staticInteropMockValidator.validateCreateStaticInteropMock( - node, - (staticInteropType as InterfaceType).classNode, - (dartType as InterfaceType).classNode)) { - final arguments = node.arguments.positional; - assert(arguments.length == 1 || arguments.length == 2); - final proto = arguments.length == 2 ? arguments[1] : null; - - return _createExport(node, dartType, staticInteropType, proto); - } - } - node.transformChildren(this); - return node; - } - - /// Validate that the [dartType] provided via `createDartExport` or - /// `createJSInteropWrapper` can be exported safely. - /// - /// Checks that: - /// - Type argument is a valid Dart interface type. - /// - Type argument is not a JS interop type. - /// - Type argument was not marked as non-exportable. - /// - /// If there were no errors with processing the class, returns true. - /// Otherwise, returns false. - bool _verifyExportable(StaticInvocation node, DartType dartType) { - if (dartType is! InterfaceType) { - _diagnosticReporter.report( - templateJsInteropExportInvalidTypeArgument.withArguments( - dartType, true), - node.fileOffset, - node.name.text.length, - node.location?.file); - return false; - } - var dartClass = dartType.classNode; - if (js_interop.hasJSInteropAnnotation(dartClass) || - js_interop.hasStaticInteropAnnotation(dartClass) || - js_interop.hasAnonymousAnnotation(dartClass)) { - _diagnosticReporter.report( - templateJsInteropExportInvalidInteropTypeArgument.withArguments( - dartType, true), - node.fileOffset, - node.name.text.length, - node.location?.file); - return false; - } - if (!_exportChecker.exportStatus.containsKey(dartClass.reference)) { - // This occurs when we deserialize previously compiled modules. Those - // modules may contain export classes, so we need to revisit the classes - // in those previously compiled modules if they are used. - for (var member in dartClass.procedures) { - _exportChecker.visitMember(member); - } - for (var member in dartClass.fields) { - _exportChecker.visitMember(member); - } - _exportChecker.visitClass(dartClass); - } - var exportStatus = _exportChecker.exportStatus[dartClass.reference]; - if (exportStatus == ExportStatus.nonExportable) { - _diagnosticReporter.report( - templateJsInteropExportClassNotMarkedExportable - .withArguments(dartClass.name), - node.fileOffset, - node.name.text.length, - node.location?.file); - return false; - } - return exportStatus == ExportStatus.exportable; - } - - /// Create the object literal using the export map that was computed from the - /// interface in [dartType]. - /// - /// [node] is either a call to `createStaticInteropMock`, `createDartExport`, - /// or `createJSInteropWrapper`. [dartType] is assumed to be a valid - /// exportable class. [returnType] is the type that the object literal will be - /// casted to. [proto] is an optional prototype object that users can pass to - /// instantiate the object literal. - /// - /// The export map is already validated, so this method simply iterates over - /// it and either assigns a method for a given property name, or assigns a - /// getter and/or setter. - /// - /// Returns a call to the block of code that instantiates this object literal - /// and returns it. - TreeNode _createExport(StaticInvocation node, InterfaceType dartType, - [DartType? returnType, Expression? proto]) { - Expression asJSObject(Expression object, [bool nullable = false]) => - AsExpression( - object, - ExtensionType(_jsObject, - nullable ? Nullability.nullable : Nullability.nonNullable)) - ..fileOffset = node.fileOffset; - - Expression toJSString(String string) => - StaticInvocation(_stringToJS, Arguments([StringLiteral(string)])) - ..fileOffset = node.fileOffset; - - StaticInvocation callMethodVarArgs(Expression jsObject, String methodName, - List args, DartType returnType) { - // `jsObject.callMethodVarArgs(methodName.toJS, args)` - return StaticInvocation( - _callMethodVarArgs, - Arguments([ - jsObject, - toJSString(methodName), - ListLiteral(args, - typeArgument: ExtensionType(_jsAny, Nullability.nullable)) - ], types: [ - returnType - ])) - ..fileOffset = node.fileOffset; - } - - // Get the global 'Object' property. - Expression getObjectProperty() => asJSObject(StaticInvocation(_getProperty, - Arguments([StaticGet(_globalContext), StringLiteral('Object')]))) - ..fileOffset = node.fileOffset; - - // Get a fresh object literal, using the proto to create it if one was - // given. - StaticInvocation getLiteral([Expression? proto]) { - return callMethodVarArgs( - getObjectProperty(), - 'create', - [asJSObject(proto ?? NullLiteral(), true)], - ExtensionType(_jsObject, Nullability.nonNullable)); - } - - var exportMap = - _exportChecker.exportClassToMemberMap[dartType.classNode.reference]!; - - var block = []; - returnType ??= _typeEnvironment.coreTypes.objectNonNullableRawType; - - var dartInstance = VariableDeclaration('#dartInstance', - initializer: node.arguments.positional[0], - type: dartType, - isSynthesized: true) - ..fileOffset = node.fileOffset - ..parent = node.parent; - block.add(dartInstance); - - var jsExporter = VariableDeclaration('#jsExporter', - initializer: getLiteral(proto), - type: ExtensionType(_jsObject, Nullability.nonNullable), - isSynthesized: true) - ..fileOffset = node.fileOffset - ..parent = node.parent; - block.add(jsExporter); - - for (var exportName in exportMap.keys) { - var exports = exportMap[exportName]!; - ExpressionStatement setProperty( - VariableGet jsObject, String propertyName, StaticInvocation jsValue) { - // `jsObject[propertyName] = jsValue` - return ExpressionStatement(StaticInvocation(_setProperty, - Arguments([jsObject, StringLiteral(propertyName), jsValue]))) - ..fileOffset = node.fileOffset - ..parent = node.parent; - } - - var firstExport = exports.first; - // With methods, there's only one export per export name. - if (firstExport is Procedure && - firstExport.kind == ProcedureKind.Method) { - // `jsExport[jsName] = dartMock.tearoffMethod.toJS` - block.add(setProperty( - VariableGet(jsExporter), - exportName, - StaticInvocation( - _functionToJS, - Arguments([ - InstanceTearOff(InstanceAccessKind.Instance, - VariableGet(dartInstance), firstExport.name, - interfaceTarget: firstExport, - resultType: _staticInteropMockValidator - .typeParameterResolver - .resolve(firstExport.getterType)) - ])))); - } else { - // Create the mapping from `get` and `set` to their `dartInstance` calls - // to be used in `Object.defineProperty`. - - // Add the given exports to the mapping that corresponds to the given - // exportName that is used by `Object.defineProperty`. In order to - // conform to that API, this function defines 'get' or 'set' properties - // on a given object literal. - // The AST code looks like: - // - // ``` - // getSetMap['get'] = () { - // return dartInstance.getter; - // }.toJS; - // ``` - // - // in the case of a getter and: - // - // ``` - // getSetMap['set'] = (val) { - // dartInstance.setter = val; - // }.toJS; - // ``` - // - // in the case of a setter. - // - // A new map VariableDeclaration is created and added to the block of - // statements for each export name. - var getSetMap = VariableDeclaration('#${exportName}Mapping', - initializer: getLiteral(), - type: ExtensionType(_jsObject, Nullability.nonNullable), - isSynthesized: true) - ..fileOffset = node.fileOffset - ..parent = node.parent; - block.add(getSetMap); - var getSet = _exportChecker.getGetterSetter(exports); - var getter = getSet.getter; - var setter = getSet.setter; - if (getter != null) { - final resultType = _staticInteropMockValidator.typeParameterResolver - .resolve(getter.getterType); - block.add(setProperty( - VariableGet(getSetMap), - 'get', - StaticInvocation( - _functionToJS, - Arguments([ - FunctionExpression(FunctionNode( - ReturnStatement(InstanceGet(InstanceAccessKind.Instance, - VariableGet(dartInstance), getter.name, - interfaceTarget: getter, resultType: resultType)), - returnType: resultType)) - ])))); - } - if (setter != null) { - var setterParameter = VariableDeclaration('#val', - type: _staticInteropMockValidator.typeParameterResolver - .resolve(setter.setterType), - isSynthesized: true) - ..fileOffset = node.fileOffset - ..parent = node.parent; - block.add(setProperty( - VariableGet(getSetMap), - 'set', - StaticInvocation( - _functionToJS, - Arguments([ - FunctionExpression(FunctionNode( - ExpressionStatement(InstanceSet( - InstanceAccessKind.Instance, - VariableGet(dartInstance), - setter.name, - VariableGet(setterParameter), - interfaceTarget: setter)), - positionalParameters: [setterParameter], - returnType: const VoidType())) - ])))); - } - // Call `Object.defineProperty` to define the export name with the - // 'get' and/or 'set' mapping. This allows us to treat get/set - // semantics as methods. - block.add(ExpressionStatement(callMethodVarArgs( - getObjectProperty(), - 'defineProperty', - [ - VariableGet(jsExporter), - toJSString(exportName), - VariableGet(getSetMap) - ], - VoidType())) - ..fileOffset = node.fileOffset - ..parent = node.parent); - } - } - - block.add(ReturnStatement(AsExpression(VariableGet(jsExporter), returnType) - ..fileOffset = node.fileOffset)); - // Return a call to evaluate the entire block of code and return the JS mock - // that was created. - return FunctionInvocation( - FunctionAccessKind.Function, - FunctionExpression(FunctionNode(Block(block), returnType: returnType)), - Arguments([]), - functionType: FunctionType([], returnType, Nullability.nonNullable)) - ..fileOffset = node.fileOffset - ..parent = node.parent; - } -} diff --git a/pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart b/pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart index 60eff5a753f..b4efe8246c0 100644 --- a/pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart +++ b/pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart @@ -68,7 +68,8 @@ class JsUtilOptimizer extends Transformer { late final ExtensionIndex _extensionIndex; - JsUtilOptimizer(this._coreTypes, ClassHierarchy hierarchy) + JsUtilOptimizer( + this._coreTypes, ClassHierarchy hierarchy, this._extensionIndex) : _callMethodTarget = _coreTypes.index.getTopLevelProcedure('dart:js_util', 'callMethod'), _callMethodTrustTypeTarget = _coreTypes.index @@ -108,10 +109,7 @@ class JsUtilOptimizer extends Transformer { _listEmptyFactory = _coreTypes.index.getProcedure('dart:core', 'List', 'empty'), _staticTypeContext = StatefulStaticTypeContext.stacked( - TypeEnvironment(_coreTypes, hierarchy)) { - _extensionIndex = - ExtensionIndex(_coreTypes, _staticTypeContext.typeEnvironment); - } + TypeEnvironment(_coreTypes, hierarchy)); @override visitLibrary(Library node) { @@ -200,7 +198,7 @@ class JsUtilOptimizer extends Transformer { if (jsName.isNotEmpty) { var lastDotIndex = jsName.lastIndexOf('.'); if (lastDotIndex != -1) { - dottedPrefix = _concatenateJSNames( + dottedPrefix = concatenateJSNames( dottedPrefix, jsName.substring(0, lastDotIndex)); } } @@ -231,15 +229,17 @@ class JsUtilOptimizer extends Transformer { ? enclosingClass.name : (enclosingClass as ExtensionTypeDeclaration).name; } - dottedPrefix = _concatenateJSNames(dottedPrefix, className); + dottedPrefix = concatenateJSNames(dottedPrefix, className); } return dottedPrefix; } + // TODO(srujzs): It feels weird to have this be public, but it's weirder to + // have this method elsewhere for now. Figure out a better place for this. /// Given two `@JS` values, combines them into a concatenated name using '.'. /// /// If either parameters are empty, returns the other. - String _concatenateJSNames(String prefix, String suffix) { + static String concatenateJSNames(String prefix, String suffix) { if (prefix.isEmpty) return suffix; if (suffix.isEmpty) return prefix; return '$prefix.$suffix'; @@ -473,6 +473,8 @@ class JsUtilOptimizer extends Transformer { invocation = _lowerCallMethod(node, shouldTrustType: false); } else if (target == _callConstructorTarget) { invocation = _lowerCallConstructor(node); + // TODO(srujzs): Delete the `isPatchedMember` check once + // https://github.com/dart-lang/sdk/issues/53367 is resolved. } else if (target.isExternal && !JsInteropChecks.isPatchedMember(target)) { final builder = _externalInvocationBuilders.putIfAbsent( target, () => _getExternalInvocationBuilder(target)); @@ -827,8 +829,7 @@ class ExtensionIndex { if (_coreInteropTypeIndex.containsKey(reference)) { return _coreInteropTypeIndex[reference]; } - if (declaration.enclosingLibrary.importUri.toString() == - 'dart:js_interop') { + if (isJSType(declaration)) { return _coreInteropTypeIndex[reference] = reference; } // Note that we recurse instead of using the erasure, as JS types are @@ -991,4 +992,7 @@ class ExtensionIndex { } return false; } + + bool isJSType(ExtensionTypeDeclaration decl) => + decl.enclosingLibrary.importUri.toString() == 'dart:js_interop'; } diff --git a/pkg/_js_interop_checks/lib/src/transformations/shared_interop_transformer.dart b/pkg/_js_interop_checks/lib/src/transformations/shared_interop_transformer.dart new file mode 100644 index 00000000000..62b19300177 --- /dev/null +++ b/pkg/_js_interop_checks/lib/src/transformations/shared_interop_transformer.dart @@ -0,0 +1,587 @@ +// Copyright (c) 2024, 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. + +// ignore_for_file: implementation_imports + +import 'package:_fe_analyzer_shared/src/messages/codes.dart' + show + messageJsInteropIsATearoff, + templateJsInteropExportClassNotMarkedExportable; +import 'package:_js_interop_checks/js_interop_checks.dart' + show JsInteropDiagnosticReporter; +import 'package:_js_interop_checks/src/js_interop.dart' as js_interop; +import 'package:front_end/src/fasta/fasta_codes.dart' + show + templateJsInteropExportInvalidInteropTypeArgument, + templateJsInteropExportInvalidTypeArgument, + templateJsInteropIsAInvalidType, + templateJsInteropIsAObjectLiteralType, + templateJsInteropIsAPrimitiveExtensionType; +import 'package:kernel/ast.dart'; +import 'package:kernel/library_index.dart'; +import 'package:kernel/type_environment.dart'; + +import 'export_checker.dart'; +import 'js_util_optimizer.dart'; +import 'static_interop_mock_validator.dart'; + +class SharedInteropTransformer extends Transformer { + final Procedure _callMethodVarArgs; + final Procedure _createDartExport; + final Procedure _createJSInteropWrapper; + final Procedure _createStaticInteropMock; + final JsInteropDiagnosticReporter _diagnosticReporter; + final ExportChecker _exportChecker; + final ExtensionIndex _extensionIndex; + final Procedure _functionToJS; + final Procedure _getProperty; + final Procedure _globalContext; + late bool _inIsATearoff; + final Procedure _instanceof; + final Procedure _instanceOfString; + late StaticInvocation? _invocation; + final Procedure _isA; + final Procedure _isATearoff; + final ExtensionTypeDeclaration _jsAny; + final ExtensionTypeDeclaration _jsFunction; + final ExtensionTypeDeclaration _jsObject; + final Procedure _setProperty; + final Procedure _stringToJS; + final StaticInteropMockValidator _staticInteropMockValidator; + final TypeEnvironment _typeEnvironment; + final Procedure _typeofEquals; + + StaticInvocation get invocation => _invocation!; + + SharedInteropTransformer(this._typeEnvironment, this._diagnosticReporter, + this._exportChecker, this._extensionIndex) + : _callMethodVarArgs = _typeEnvironment.coreTypes.index + .getTopLevelProcedure('dart:js_interop_unsafe', + 'JSObjectUnsafeUtilExtension|callMethodVarArgs'), + _createDartExport = _typeEnvironment.coreTypes.index + .getTopLevelProcedure('dart:js_util', 'createDartExport'), + _createJSInteropWrapper = _typeEnvironment.coreTypes.index + .getTopLevelProcedure('dart:js_interop', 'createJSInteropWrapper'), + _createStaticInteropMock = _typeEnvironment.coreTypes.index + .getTopLevelProcedure('dart:js_util', 'createStaticInteropMock'), + _functionToJS = _typeEnvironment.coreTypes.index.getTopLevelProcedure( + 'dart:js_interop', 'FunctionToJSExportedDartFunction|get#toJS'), + _getProperty = _typeEnvironment.coreTypes.index.getTopLevelProcedure( + 'dart:js_interop_unsafe', 'JSObjectUnsafeUtilExtension|[]'), + _globalContext = _typeEnvironment.coreTypes.index + .getTopLevelProcedure('dart:js_interop', 'get:globalContext'), + _instanceof = _typeEnvironment.coreTypes.index.getTopLevelProcedure( + 'dart:js_interop', 'JSAnyUtilityExtension|instanceof'), + _instanceOfString = _typeEnvironment.coreTypes.index + .getTopLevelProcedure( + 'dart:js_interop', 'JSAnyUtilityExtension|instanceOfString'), + _isA = _typeEnvironment.coreTypes.index.getTopLevelProcedure( + 'dart:js_interop', 'JSAnyUtilityExtension|isA'), + _isATearoff = _typeEnvironment.coreTypes.index.getTopLevelProcedure( + 'dart:js_interop', + 'JSAnyUtilityExtension|${LibraryIndex.tearoffPrefix}isA'), + _jsAny = _typeEnvironment.coreTypes.index + .getExtensionType('dart:js_interop', 'JSAny'), + _jsFunction = _typeEnvironment.coreTypes.index + .getExtensionType('dart:js_interop', 'JSFunction'), + _jsObject = _typeEnvironment.coreTypes.index + .getExtensionType('dart:js_interop', 'JSObject'), + _setProperty = _typeEnvironment.coreTypes.index.getTopLevelProcedure( + 'dart:js_interop_unsafe', 'JSObjectUnsafeUtilExtension|[]='), + _stringToJS = _typeEnvironment.coreTypes.index.getTopLevelProcedure( + 'dart:js_interop', 'StringToJSString|get#toJS'), + _staticInteropMockValidator = StaticInteropMockValidator( + _diagnosticReporter, _exportChecker, _typeEnvironment), + _typeofEquals = _typeEnvironment.coreTypes.index.getTopLevelProcedure( + 'dart:js_interop', 'JSAnyUtilityExtension|typeofEquals'); + + @override + TreeNode visitStaticInvocation(StaticInvocation node) { + _invocation = node; + TreeNode replacement = invocation; + final target = invocation.target; + if (target == _createDartExport || target == _createJSInteropWrapper) { + final typeArguments = invocation.arguments.types; + assert(typeArguments.length == 1); + if (_verifyExportable(typeArguments[0])) { + final interface = typeArguments[0] as InterfaceType; + if (target == _createJSInteropWrapper) { + replacement = _createExport( + interface, ExtensionType(_jsObject, Nullability.nonNullable)); + } + replacement = _createExport(interface); + } + } else if (target == _createStaticInteropMock) { + final typeArguments = invocation.arguments.types; + assert(typeArguments.length == 2); + final staticInteropType = typeArguments[0]; + final dartType = typeArguments[1]; + + final exportable = _verifyExportable(dartType); + final staticInteropTypeArgumentCorrect = _staticInteropMockValidator + .validateStaticInteropTypeArgument(invocation, staticInteropType); + final dartTypeArgumentCorrect = _staticInteropMockValidator + .validateDartTypeArgument(invocation, dartType); + if (exportable && + staticInteropTypeArgumentCorrect && + dartTypeArgumentCorrect && + _staticInteropMockValidator.validateCreateStaticInteropMock( + invocation, + (staticInteropType as InterfaceType).classNode, + (dartType as InterfaceType).classNode)) { + final arguments = invocation.arguments.positional; + assert(arguments.length == 1 || arguments.length == 2); + final proto = arguments.length == 2 ? arguments[1] : null; + + replacement = _createExport(dartType, staticInteropType, proto); + } + } else if (target == _isA) { + final receiver = invocation.arguments.positional[0]; + final typeArguments = invocation.arguments.types; + assert(typeArguments.length == 1); + final interopType = typeArguments[0]; + final coreInteropType = + _extensionIndex.getCoreInteropType(interopType)?.node; + if (coreInteropType is ExtensionTypeDeclaration && + _extensionIndex.isJSType(coreInteropType)) { + replacement = _createIsACheck( + receiver, interopType as ExtensionType, coreInteropType); + } else { + // Generated tear-offs call the original method, so ignore that + // invocation. + if (!_inIsATearoff) { + _diagnosticReporter.report( + templateJsInteropIsAInvalidType.withArguments(interopType, true), + invocation.fileOffset, + invocation.name.text.length, + invocation.location?.file); + } + replacement = invocation; + } + } else if (target == _isATearoff) { + // Calling the generated tear-off is still bad, however. + _diagnosticReporter.report( + messageJsInteropIsATearoff, + invocation.fileOffset, + invocation.name.text.length, + invocation.location?.file); + } + replacement.transformChildren(this); + _invocation = null; + return replacement; + } + + @override + TreeNode visitProcedure(Procedure node) { + _inIsATearoff = node == _isATearoff; + node.transformChildren(this); + _inIsATearoff = false; + return node; + } + + /// Validate that the [dartType] provided via `createDartExport` or + /// `createJSInteropWrapper` can be exported safely. + /// + /// Checks that: + /// - Type argument is a valid Dart interface type. + /// - Type argument is not a JS interop type. + /// - Type argument was not marked as non-exportable. + /// + /// If there were no errors with processing the class, returns true. + /// Otherwise, returns false. + bool _verifyExportable(DartType dartType) { + if (dartType is! InterfaceType) { + _diagnosticReporter.report( + templateJsInteropExportInvalidTypeArgument.withArguments( + dartType, true), + invocation.fileOffset, + invocation.name.text.length, + invocation.location?.file); + return false; + } + var dartClass = dartType.classNode; + if (js_interop.hasJSInteropAnnotation(dartClass) || + js_interop.hasStaticInteropAnnotation(dartClass) || + js_interop.hasAnonymousAnnotation(dartClass)) { + _diagnosticReporter.report( + templateJsInteropExportInvalidInteropTypeArgument.withArguments( + dartType, true), + invocation.fileOffset, + invocation.name.text.length, + invocation.location?.file); + return false; + } + if (!_exportChecker.exportStatus.containsKey(dartClass.reference)) { + // This occurs when we deserialize previously compiled modules. Those + // modules may contain export classes, so we need to revisit the classes + // in those previously compiled modules if they are used. + for (var member in dartClass.procedures) { + _exportChecker.visitMember(member); + } + for (var member in dartClass.fields) { + _exportChecker.visitMember(member); + } + _exportChecker.visitClass(dartClass); + } + var exportStatus = _exportChecker.exportStatus[dartClass.reference]; + if (exportStatus == ExportStatus.nonExportable) { + _diagnosticReporter.report( + templateJsInteropExportClassNotMarkedExportable + .withArguments(dartClass.name), + invocation.fileOffset, + invocation.name.text.length, + invocation.location?.file); + return false; + } + return exportStatus == ExportStatus.exportable; + } + + /// Create the object literal using the export map that was computed from the + /// interface in [dartType]. + /// + /// [dartType] is assumed to be a valid exportable class. [returnType] is the + /// type that the object literal will be casted to. [proto] is an optional + /// prototype object that users can pass to instantiate the object literal. + /// + /// The export map is already validated, so this method simply iterates over + /// it and either assigns a method for a given property name, or assigns a + /// getter and/or setter. + /// + /// Returns a call to the block of code that instantiates this object literal + /// and returns it. + TreeNode _createExport(InterfaceType dartType, + [DartType? returnType, Expression? proto]) { + var exportMap = + _exportChecker.exportClassToMemberMap[dartType.classNode.reference]!; + + var block = []; + returnType ??= _typeEnvironment.coreTypes.objectNonNullableRawType; + + // TODO(srujzs): We can avoid creating a variable if the passed-in value is + // already a variable for both these declarations. + // TODO(srujzs): Change these to `VariableDeclaration.forValue` once + // https://github.com/dart-lang/sdk/issues/54734 is resolved. + var dartInstance = VariableDeclaration('#dartInstance', + initializer: invocation.arguments.positional[0], + type: dartType, + isSynthesized: true) + ..fileOffset = invocation.fileOffset; + block.add(dartInstance); + + var jsExporter = VariableDeclaration('#jsExporter', + initializer: getLiteral(proto), + type: ExtensionType(_jsObject, Nullability.nonNullable), + isSynthesized: true) + ..fileOffset = invocation.fileOffset; + block.add(jsExporter); + + for (var exportName in exportMap.keys) { + var exports = exportMap[exportName]!; + ExpressionStatement setProperty( + VariableGet jsObject, String propertyName, StaticInvocation jsValue) { + // `jsObject[propertyName] = jsValue` + return ExpressionStatement(StaticInvocation(_setProperty, + Arguments([jsObject, StringLiteral(propertyName), jsValue]))) + ..fileOffset = invocation.fileOffset; + } + + var firstExport = exports.first; + // With methods, there's only one export per export name. + if (firstExport is Procedure && + firstExport.kind == ProcedureKind.Method) { + // `jsExport[jsName] = dartMock.tearoffMethod.toJS` + block.add(setProperty( + VariableGet(jsExporter), + exportName, + StaticInvocation( + _functionToJS, + Arguments([ + InstanceTearOff(InstanceAccessKind.Instance, + VariableGet(dartInstance), firstExport.name, + interfaceTarget: firstExport, + resultType: _staticInteropMockValidator + .typeParameterResolver + .resolve(firstExport.getterType)) + ])))); + } else { + // Create the mapping from `get` and `set` to their `dartInstance` calls + // to be used in `Object.defineProperty`. + + // Add the given exports to the mapping that corresponds to the given + // exportName that is used by `Object.defineProperty`. In order to + // conform to that API, this function defines 'get' or 'set' properties + // on a given object literal. + // The AST code looks like: + // + // ``` + // getSetMap['get'] = () { + // return dartInstance.getter; + // }.toJS; + // ``` + // + // in the case of a getter and: + // + // ``` + // getSetMap['set'] = (val) { + // dartInstance.setter = val; + // }.toJS; + // ``` + // + // in the case of a setter. + // + // A new map VariableDeclaration is created and added to the block of + // statements for each export name. + var getSetMap = VariableDeclaration('#${exportName}Mapping', + initializer: getLiteral(), + type: ExtensionType(_jsObject, Nullability.nonNullable), + isSynthesized: true) + ..fileOffset = invocation.fileOffset; + block.add(getSetMap); + var getSet = _exportChecker.getGetterSetter(exports); + var getter = getSet.getter; + var setter = getSet.setter; + if (getter != null) { + final resultType = _staticInteropMockValidator.typeParameterResolver + .resolve(getter.getterType); + block.add(setProperty( + VariableGet(getSetMap), + 'get', + StaticInvocation( + _functionToJS, + Arguments([ + FunctionExpression(FunctionNode( + ReturnStatement(InstanceGet(InstanceAccessKind.Instance, + VariableGet(dartInstance), getter.name, + interfaceTarget: getter, resultType: resultType)), + returnType: resultType)) + ])))); + } + if (setter != null) { + var setterParameter = VariableDeclaration('#val', + type: _staticInteropMockValidator.typeParameterResolver + .resolve(setter.setterType), + isSynthesized: true) + ..fileOffset = invocation.fileOffset; + block.add(setProperty( + VariableGet(getSetMap), + 'set', + StaticInvocation( + _functionToJS, + Arguments([ + FunctionExpression(FunctionNode( + ExpressionStatement(InstanceSet( + InstanceAccessKind.Instance, + VariableGet(dartInstance), + setter.name, + VariableGet(setterParameter), + interfaceTarget: setter)), + positionalParameters: [setterParameter], + returnType: const VoidType())) + ])))); + } + // Call `Object.defineProperty` to define the export name with the + // 'get' and/or 'set' mapping. This allows us to treat get/set + // semantics as methods. + block.add(ExpressionStatement(callMethodVarArgs( + getObjectProperty(), + 'defineProperty', + [ + VariableGet(jsExporter), + toJSString(exportName), + VariableGet(getSetMap) + ], + VoidType())) + ..fileOffset = invocation.fileOffset); + } + } + + block.add(ReturnStatement(AsExpression(VariableGet(jsExporter), returnType) + ..fileOffset = invocation.fileOffset)); + // Return a call to evaluate the entire block of code and return the JS mock + // that was created. + return FunctionInvocation( + FunctionAccessKind.Function, + FunctionExpression(FunctionNode(Block(block), returnType: returnType)), + Arguments([]), + functionType: FunctionType([], returnType, Nullability.nonNullable)) + ..fileOffset = invocation.fileOffset + ..parent = invocation.parent; + } + + /// Create and return an appropriate type-check for the given [receiver] and + /// [interopType]. + /// + /// [jsType] is the JS type that [interopType] is or wraps. + /// + /// If [interopType] is an erroneous type like a type that wraps a JS + /// primitive type or one with an object literal constructor, an error is + /// reported. + TreeNode _createIsACheck(Expression receiver, ExtensionType interopType, + ExtensionTypeDeclaration jsType) { + // In the case where the receiver wasn't a variable to begin with, + // synthesize a var so that we don't evaluate the receiver multiple times. + final any = receiver is VariableGet + ? receiver.variable + : (VariableDeclaration.forValue(receiver, + type: ExtensionType(_jsAny, Nullability.nullable)) + ..fileOffset = invocation.fileOffset); + + Expression? check; + final interopTypeDecl = interopType.extensionTypeDeclaration; + final jsTypeName = jsType.name; + String? typeofString; + String? instanceOfString; + switch (jsTypeName) { + case 'JSAny' when interopTypeDecl == jsType: + // Only avoids any non null-related checks when users are referring + // directly to the `dart:js_interop` type and not some wrapper. + break; + case 'JSNumber': + typeofString = 'number'; + break; + case 'JSBoolean': + typeofString = 'boolean'; + break; + case 'JSString': + typeofString = 'string'; + break; + case 'JSBigInt': + typeofString = 'bigint'; + break; + case 'JSSymbol': + typeofString = 'symbol'; + break; + case 'JSTypedArray' when interopTypeDecl == jsType: + // Only do this special case when users are referring directly to + // the `dart:js_interop` type and not some wrapper. + + // `TypedArray` doesn't exist as a property in JS, but rather as a + // superclass of all typed arrays. In order to do the most sensible + // thing here, we can use the prototype of some typed array, and + // check that the receiver is an `instanceof` that prototype. + // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray#description + // for more details. + check = StaticInvocation(_instanceof, + Arguments([VariableGet(any), getInt8ArrayPrototype()])); + break; + default: + for (final descriptor in interopTypeDecl.memberDescriptors) { + final descriptorNode = descriptor.memberReference.node; + if (descriptorNode is Procedure && + _extensionIndex.isLiteralConstructor(descriptorNode)) { + _diagnosticReporter.report( + templateJsInteropIsAObjectLiteralType.withArguments( + interopType, true), + invocation.fileOffset, + invocation.name.text.length, + invocation.location?.file); + break; + } + } + // Currently, any type that is not recursively a JS primitive type or + // `JSTypedArray` will use the written type (with renames) to do a + // type check. This means using any extension type that wraps + // `JSArray` in `isA` will use the extension type's name instead of + // `Array`. This may lead to confusion when a user writes an + // extension type that wraps `JSArray` but did not intend to give it + // a separate JS type. We could change this, but that means users + // can't use any actual subtypes of `JSArray`. Users who want to do + // a check against `Array` can always use `isA()` or use + // `@JS('Array')` (with no library scoping) to provide the right + // type. + var typeName = js_interop.getJSName(interopTypeDecl); + if (typeName.isEmpty) typeName = interopTypeDecl.name; + instanceOfString = JsUtilOptimizer.concatenateJSNames( + js_interop.getJSName(interopTypeDecl.enclosingLibrary), typeName); + } + if (typeofString != null) { + if (interopTypeDecl != jsType) { + _diagnosticReporter.report( + templateJsInteropIsAPrimitiveExtensionType.withArguments( + interopType, jsTypeName, true), + invocation.fileOffset, + invocation.name.text.length, + invocation.location?.file); + } else { + check = StaticInvocation(_typeofEquals, + Arguments([VariableGet(any), StringLiteral(typeofString)])); + } + } else if (instanceOfString != null) { + check = StaticInvocation(_instanceOfString, + Arguments([VariableGet(any), StringLiteral(instanceOfString)])); + } + final nullable = interopType.nullability == Nullability.nullable; + Expression nullCheck = EqualsNull(VariableGet(any)); + Expression notNullCheck = Not(EqualsNull(VariableGet(any))); + if (check != null) { + check = nullable + ? LogicalExpression(nullCheck, LogicalExpressionOperator.OR, check) + : LogicalExpression( + notNullCheck, LogicalExpressionOperator.AND, check); + } else if (!nullable) { + // `JSAny` + check = notNullCheck; + } else { + // `JSAny?` + check = BoolLiteral(true); + } + return receiver is VariableGet ? check : Let(any, check) + ..fileOffset = invocation.fileOffset + ..parent = invocation.parent; + } + + // Various shared helpers to make calls to `dart:js_interop`/ + // `dart:js_interop_unsafe` members easier. These helpers need to access the + // current node's file offset so that the CFE verifier is happy. + + Expression asJSObject(Expression object, + [bool nullable = false]) => + AsExpression( + object, + ExtensionType(_jsObject, + nullable ? Nullability.nullable : Nullability.nonNullable)) + ..fileOffset = invocation.fileOffset; + + Expression toJSString(String string) => + StaticInvocation(_stringToJS, Arguments([StringLiteral(string)])) + ..fileOffset = invocation.fileOffset; + + StaticInvocation callMethodVarArgs(Expression jsObject, String methodName, + List args, DartType returnType) { + // `jsObject.callMethodVarArgs(methodName.toJS, args)` + return StaticInvocation( + _callMethodVarArgs, + Arguments([ + jsObject, + toJSString(methodName), + ListLiteral(args, + typeArgument: ExtensionType(_jsAny, Nullability.nullable)) + ], types: [ + returnType + ])) + ..fileOffset = invocation.fileOffset; + } + + // Get the object with the given [property] off of the global context. + Expression getGlobalProperty(String property) => asJSObject(StaticInvocation( + _getProperty, + Arguments([StaticGet(_globalContext), StringLiteral(property)]))) + ..fileOffset = invocation.fileOffset; + + Expression getObjectProperty() => getGlobalProperty('Object'); + + Expression getInt8ArrayPrototype() => callMethodVarArgs( + getObjectProperty(), + 'getPrototypeOf', + [getGlobalProperty('Int8Array')], + ExtensionType(_jsFunction, Nullability.nonNullable)); + + // Get a fresh object literal, using the proto to create it if one was + // given. + StaticInvocation getLiteral([Expression? proto]) => callMethodVarArgs( + getObjectProperty(), + 'create', + [asJSObject(proto ?? NullLiteral(), true)], + ExtensionType(_jsObject, Nullability.nonNullable)); +} diff --git a/pkg/compiler/lib/src/kernel/dart2js_target.dart b/pkg/compiler/lib/src/kernel/dart2js_target.dart index 3b82a542510..96b4964df46 100644 --- a/pkg/compiler/lib/src/kernel/dart2js_target.dart +++ b/pkg/compiler/lib/src/kernel/dart2js_target.dart @@ -9,8 +9,8 @@ library compiler.src.kernel.dart2js_target; import 'package:_fe_analyzer_shared/src/messages/codes.dart' show Message, LocatedMessage; import 'package:_js_interop_checks/js_interop_checks.dart'; -import 'package:_js_interop_checks/src/transformations/export_creator.dart'; import 'package:_js_interop_checks/src/transformations/js_util_optimizer.dart'; +import 'package:_js_interop_checks/src/transformations/shared_interop_transformer.dart'; import 'package:kernel/ast.dart' as ir; import 'package:kernel/class_hierarchy.dart'; import 'package:kernel/core_types.dart'; @@ -155,13 +155,17 @@ class Dart2jsTarget extends Target { for (var library in libraries) { jsInteropChecks.visitLibrary(library); } - var exportCreator = ExportCreator(TypeEnvironment(coreTypes, hierarchy), - jsInteropReporter, jsInteropChecks.exportChecker); - var jsUtilOptimizer = JsUtilOptimizer(coreTypes, hierarchy); + var sharedInteropTransformer = SharedInteropTransformer( + TypeEnvironment(coreTypes, hierarchy), + jsInteropReporter, + jsInteropChecks.exportChecker, + jsInteropChecks.extensionIndex); + var jsUtilOptimizer = + JsUtilOptimizer(coreTypes, hierarchy, jsInteropChecks.extensionIndex); for (var library in libraries) { - // Export creator has static checks, so we still visit even if there are - // errors. - exportCreator.visitLibrary(library); + // Shared transformer has static checks, so we still visit even if there + // are errors. + sharedInteropTransformer.visitLibrary(library); // TODO (rileyporter): Merge js_util optimizations with other lowerings // in the single pass in `transformations/lowering.dart`. if (!jsInteropReporter.hasJsInteropErrors) { diff --git a/pkg/dart2wasm/lib/target.dart b/pkg/dart2wasm/lib/target.dart index d845d0ff36b..7f1bdc99f22 100644 --- a/pkg/dart2wasm/lib/target.dart +++ b/pkg/dart2wasm/lib/target.dart @@ -6,7 +6,7 @@ import 'package:_fe_analyzer_shared/src/messages/codes.dart' show Message, LocatedMessage; import 'package:_js_interop_checks/js_interop_checks.dart'; import 'package:_js_interop_checks/src/js_interop.dart' as jsInteropHelper; -import 'package:_js_interop_checks/src/transformations/export_creator.dart'; +import 'package:_js_interop_checks/src/transformations/shared_interop_transformer.dart'; import 'package:kernel/ast.dart'; import 'package:kernel/class_hierarchy.dart'; import 'package:kernel/clone.dart'; @@ -215,10 +215,13 @@ class WasmTarget extends Target { for (Library library in interopDependentLibraries) { jsInteropChecks.visitLibrary(library); } - final exportCreator = ExportCreator(TypeEnvironment(coreTypes, hierarchy), - jsInteropReporter, jsInteropChecks.exportChecker); + final sharedInteropTransformer = SharedInteropTransformer( + TypeEnvironment(coreTypes, hierarchy), + jsInteropReporter, + jsInteropChecks.exportChecker, + jsInteropChecks.extensionIndex); for (Library library in interopDependentLibraries) { - exportCreator.visitLibrary(library); + sharedInteropTransformer.visitLibrary(library); } } diff --git a/pkg/dev_compiler/lib/src/kernel/target.dart b/pkg/dev_compiler/lib/src/kernel/target.dart index 29f7904d3a1..59c7c79c6ee 100644 --- a/pkg/dev_compiler/lib/src/kernel/target.dart +++ b/pkg/dev_compiler/lib/src/kernel/target.dart @@ -7,8 +7,8 @@ import 'dart:collection'; import 'package:_fe_analyzer_shared/src/messages/codes.dart' show Message, LocatedMessage; import 'package:_js_interop_checks/js_interop_checks.dart'; -import 'package:_js_interop_checks/src/transformations/export_creator.dart'; import 'package:_js_interop_checks/src/transformations/js_util_optimizer.dart'; +import 'package:_js_interop_checks/src/transformations/shared_interop_transformer.dart'; import 'package:kernel/class_hierarchy.dart'; import 'package:kernel/core_types.dart'; import 'package:kernel/kernel.dart' hide Pattern; @@ -206,13 +206,17 @@ class DevCompilerTarget extends Target { // Process and validate first before doing anything with exports. node.accept(jsInteropChecks); } - final exportCreator = ExportCreator(TypeEnvironment(coreTypes, hierarchy), - jsInteropReporter, jsInteropChecks.exportChecker); - final jsUtilOptimizer = JsUtilOptimizer(coreTypes, hierarchy); + final sharedInteropTransformer = SharedInteropTransformer( + TypeEnvironment(coreTypes, hierarchy), + jsInteropReporter, + jsInteropChecks.exportChecker, + jsInteropChecks.extensionIndex); + final jsUtilOptimizer = + JsUtilOptimizer(coreTypes, hierarchy, jsInteropChecks.extensionIndex); for (var node in nodes) { _CovarianceTransformer(node).transform(); - // Export creator has static checks, so we still visit. - node.accept(exportCreator); + // Shared interop transformer has static checks, so we still visit. + node.accept(sharedInteropTransformer); if (!jsInteropReporter.hasJsInteropErrors) { // We can't guarantee calls are well-formed, so don't transform. node.accept(jsUtilOptimizer); diff --git a/pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart b/pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart index 686aa3e6896..3c128076f2e 100644 --- a/pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart +++ b/pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart @@ -4108,6 +4108,109 @@ Message _withArgumentsJsInteropFunctionToJSRequiresStaticType( arguments: {'type': _type}); } +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Template + templateJsInteropIsAInvalidType = const Template< + Message Function(DartType _type, bool isNonNullableByDefault)>( + "JsInteropIsAInvalidType", + problemMessageTemplate: + r"""Type argument '#type' needs to be an interop 'ExtensionType'.""", + correctionMessageTemplate: + r"""Use a valid interop extension type as the type argument instead.""", + withArguments: _withArgumentsJsInteropIsAInvalidType); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Code + codeJsInteropIsAInvalidType = + const Code( + "JsInteropIsAInvalidType", +); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +Message _withArgumentsJsInteropIsAInvalidType( + DartType _type, bool isNonNullableByDefault) { + TypeLabeler labeler = new TypeLabeler(isNonNullableByDefault); + List typeParts = labeler.labelType(_type); + String type = typeParts.join(); + return new Message(codeJsInteropIsAInvalidType, + problemMessage: + """Type argument '${type}' needs to be an interop 'ExtensionType'.""" + + labeler.originMessages, + correctionMessage: """Use a valid interop extension type as the type argument instead.""", + arguments: {'type': _type}); +} + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Template + templateJsInteropIsAObjectLiteralType = const Template< + Message Function(DartType _type, bool isNonNullableByDefault)>( + "JsInteropIsAObjectLiteralType", + problemMessageTemplate: + r"""Type argument '#type' has an object literal constructor. Because 'isA' uses the type's name or '@JS()' rename, this may result in an incorrect type check.""", + correctionMessageTemplate: + r"""Use 'JSObject' as the type argument instead.""", + withArguments: _withArgumentsJsInteropIsAObjectLiteralType); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Code + codeJsInteropIsAObjectLiteralType = + const Code( + "JsInteropIsAObjectLiteralType", +); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +Message _withArgumentsJsInteropIsAObjectLiteralType( + DartType _type, bool isNonNullableByDefault) { + TypeLabeler labeler = new TypeLabeler(isNonNullableByDefault); + List typeParts = labeler.labelType(_type); + String type = typeParts.join(); + return new Message(codeJsInteropIsAObjectLiteralType, + problemMessage: + """Type argument '${type}' has an object literal constructor. Because 'isA' uses the type's name or '@JS()' rename, this may result in an incorrect type check.""" + + labeler.originMessages, + correctionMessage: """Use 'JSObject' as the type argument instead.""", + arguments: {'type': _type}); +} + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Template< + Message Function( + DartType _type, String string, bool isNonNullableByDefault)> + templateJsInteropIsAPrimitiveExtensionType = const Template< + Message Function( + DartType _type, String string, bool isNonNullableByDefault)>( + "JsInteropIsAPrimitiveExtensionType", + problemMessageTemplate: + r"""Type argument '#type' wraps primitive JS type '#string', which is specially handled using 'typeof'.""", + correctionMessageTemplate: + r"""Use the primitive JS type '#string' as the type argument instead.""", + withArguments: _withArgumentsJsInteropIsAPrimitiveExtensionType); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +const Code< + Message Function( + DartType _type, String string, bool isNonNullableByDefault)> + codeJsInteropIsAPrimitiveExtensionType = const Code< + Message Function( + DartType _type, String string, bool isNonNullableByDefault)>( + "JsInteropIsAPrimitiveExtensionType", +); + +// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. +Message _withArgumentsJsInteropIsAPrimitiveExtensionType( + DartType _type, String string, bool isNonNullableByDefault) { + TypeLabeler labeler = new TypeLabeler(isNonNullableByDefault); + List typeParts = labeler.labelType(_type); + if (string.isEmpty) throw 'No string provided'; + String type = typeParts.join(); + return new Message(codeJsInteropIsAPrimitiveExtensionType, + problemMessage: + """Type argument '${type}' wraps primitive JS type '${string}', which is specially handled using 'typeof'.""" + + labeler.originMessages, + correctionMessage: """Use the primitive JS type '${string}' as the type argument instead.""", + arguments: {'type': _type, 'string': string}); +} + // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. const Template templateJsInteropStaticInteropExternalTypeViolation = const Template< diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status index a7382f3bda9..3b16793279d 100644 --- a/pkg/front_end/messages.status +++ b/pkg/front_end/messages.status @@ -631,6 +631,14 @@ JsInteropExternalExtensionMemberWithStaticDisallowed/analyzerCode: Fail # Web co JsInteropExternalExtensionMemberWithStaticDisallowed/example: Fail # Web compiler specific JsInteropExternalMemberNotJSAnnotated/analyzerCode: Fail # Web compiler specific JsInteropExternalMemberNotJSAnnotated/example: Fail # Web compiler specific +JsInteropIsAInvalidType/analyzerCode: Fail # Web compiler specific +JsInteropIsAInvalidType/example: Fail # Web compiler specific +JsInteropIsAObjectLiteralType/analyzerCode: Fail # Web compiler specific +JsInteropIsAObjectLiteralType/example: Fail # Web compiler specific +JsInteropIsAPrimitiveExtensionType/analyzerCode: Fail # Web compiler specific +JsInteropIsAPrimitiveExtensionType/example: Fail # Web compiler specific +JsInteropIsATearoff/analyzerCode: Fail # Web compiler specific +JsInteropIsATearoff/example: Fail # Web compiler specific JsInteropFunctionToJSRequiresStaticType/analyzerCode: Fail # Web compiler specific JsInteropFunctionToJSRequiresStaticType/example: Fail # Web compiler specific JsInteropFunctionToJSTypeParameters/analyzerCode: Fail # Web compiler specific diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml index d2a6b5bf22f..5adbced5ec6 100644 --- a/pkg/front_end/messages.yaml +++ b/pkg/front_end/messages.yaml @@ -5845,6 +5845,22 @@ JsInteropExportNoExportableMembers: problemMessage: "Class '#name' has no exportable members in the class or the inheritance chain." correctionMessage: "Using `@JSExport`, annotate at least one instance member with a body or annotate a class that has such a member in the inheritance chain." +JsInteropIsAInvalidType: + problemMessage: "Type argument '#type' needs to be an interop 'ExtensionType'." + correctionMessage: "Use a valid interop extension type as the type argument instead." + +JsInteropIsAObjectLiteralType: + problemMessage: "Type argument '#type' has an object literal constructor. Because 'isA' uses the type's name or '@JS()' rename, this may result in an incorrect type check." + correctionMessage: "Use 'JSObject' as the type argument instead." + +JsInteropIsAPrimitiveExtensionType: + problemMessage: "Type argument '#type' wraps primitive JS type '#string', which is specially handled using 'typeof'." + correctionMessage: "Use the primitive JS type '#string' as the type argument instead." + +JsInteropIsATearoff: + problemMessage: "'isA' can't be torn off." + correctionMessage: "Use a method that calls 'isA' and tear off that method instead." + JsInteropExtensionTypeNotInterop: problemMessage: "Extension type '#name' is marked with a '@JS' annotation, but its representation type is not a valid JS interop type: '#type'." correctionMessage: "Try declaring a valid JS interop representation type, which may include 'dart:js_interop' types, '@staticInterop' types, 'dart:html' types, or other interop extension types." diff --git a/pkg/front_end/test/spell_checking_list_messages.txt b/pkg/front_end/test/spell_checking_list_messages.txt index f432a84edc1..9a2f0d6a77f 100644 --- a/pkg/front_end/test/spell_checking_list_messages.txt +++ b/pkg/front_end/test/spell_checking_list_messages.txt @@ -51,6 +51,7 @@ e.g enclose exhaustively exportable +extensiontype f ffi finality @@ -66,6 +67,7 @@ interact interop intervening irrefutable +isa js_util jsany jsexport @@ -113,6 +115,7 @@ resource sdksummary size solutions +specially stacktrace stand staticinterop @@ -130,7 +133,9 @@ this.namedconstructor this.x tojs trusttypes +type's type3.#name +typeof u unavailable unsound diff --git a/pkg/front_end/test/spell_checking_list_tests.txt b/pkg/front_end/test/spell_checking_list_tests.txt index dc650c21b75..ab0b23d4f33 100644 --- a/pkg/front_end/test/spell_checking_list_tests.txt +++ b/pkg/front_end/test/spell_checking_list_tests.txt @@ -590,6 +590,7 @@ prerequisite press pretends preventing +primitives printouts processes processors diff --git a/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart b/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart new file mode 100644 index 00000000000..f7b381431d2 --- /dev/null +++ b/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart @@ -0,0 +1,52 @@ +// Copyright (c) 2024, 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. + +@JS('library1') +library; + +import 'dart:js_interop'; + +extension type CustomJSAny(JSAny _) implements JSAny {} + +extension type CustomJSObject(JSObject _) implements JSObject {} + +extension type CustomTypedArray(JSTypedArray _) implements JSObject {} + +@JS('RenamedJSArray') +extension type CustomJSArray(JSArray _) implements JSObject {} + +void test(JSAny any) { + // Top type. + any.isA(); + any.isA(); + + // Primitives. + any.isA(); + any.isA(); + any.isA(); + any.isA(); + any.isA(); + any.isA(); + + // Objects. + any.isA(); + any.isA(); + any.isA(); + // JSTypedArray is handled differently. + any.isA(); + + // User-defined types. + any.isA(); + any.isA(); + any.isA(); + any.isA(); + + // Non-variable expression should avoid evaluating twice. + () { + return any; + }() + .isA(); +} + +void main() {} diff --git a/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.strong.expect b/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.strong.expect new file mode 100644 index 00000000000..2ad34d66c11 --- /dev/null +++ b/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.strong.expect @@ -0,0 +1,87 @@ +@#C2 +library; +import self as self; +import "dart:js_interop" as js_; + +import "dart:js_interop"; + +extension type CustomJSAny(js_::JSAny /* = dart.core::Object */ _) implements js_::JSAny /* = dart.core::Object */ { + abstract extension-type-member representation-field get _() → js_::JSAny /* = dart.core::Object */; + constructor • = self::CustomJSAny|constructor#; + constructor tearoff • = self::CustomJSAny|constructor#_#new#tearOff; +} +extension type CustomJSObject(js_::JSObject /* = _interceptors::JSObject */ _) implements js_::JSObject /* = _interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSObject /* = _interceptors::JSObject */; + constructor • = self::CustomJSObject|constructor#; + constructor tearoff • = self::CustomJSObject|constructor#_#new#tearOff; +} +extension type CustomTypedArray(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) implements js_::JSObject /* = _interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */; + constructor • = self::CustomTypedArray|constructor#; + constructor tearoff • = self::CustomTypedArray|constructor#_#new#tearOff; +} +@#C4 +extension type CustomJSArray(js_::JSArray /* = _interceptors::JSArray */ _) implements js_::JSObject /* = _interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSArray /* = _interceptors::JSArray */; + constructor • = self::CustomJSArray|constructor#; + constructor tearoff • = self::CustomJSArray|constructor#_#new#tearOff; +} +static extension-type-member method CustomJSAny|constructor#(js_::JSAny /* = dart.core::Object */ _) → self::CustomJSAny /* = dart.core::Object */ { + lowered final self::CustomJSAny /* = dart.core::Object */ #this = _; + return #this; +} +static extension-type-member method CustomJSAny|constructor#_#new#tearOff(js_::JSAny /* = dart.core::Object */ _) → self::CustomJSAny /* = dart.core::Object */ + return self::CustomJSAny|constructor#(_); +static extension-type-member method CustomJSObject|constructor#(js_::JSObject /* = _interceptors::JSObject */ _) → self::CustomJSObject /* = _interceptors::JSObject */ { + lowered final self::CustomJSObject /* = _interceptors::JSObject */ #this = _; + return #this; +} +static extension-type-member method CustomJSObject|constructor#_#new#tearOff(js_::JSObject /* = _interceptors::JSObject */ _) → self::CustomJSObject /* = _interceptors::JSObject */ + return self::CustomJSObject|constructor#(_); +static extension-type-member method CustomTypedArray|constructor#(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) → self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ { + lowered final self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ #this = _; + return #this; +} +static extension-type-member method CustomTypedArray|constructor#_#new#tearOff(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) → self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ + return self::CustomTypedArray|constructor#(_); +static extension-type-member method CustomJSArray|constructor#(js_::JSArray /* = _interceptors::JSArray */ _) → self::CustomJSArray /* = _interceptors::JSArray */ { + lowered final self::CustomJSArray /* = _interceptors::JSArray */ #this = _; + return #this; +} +static extension-type-member method CustomJSArray|constructor#_#new#tearOff(js_::JSArray /* = _interceptors::JSArray */ _) → self::CustomJSArray /* = _interceptors::JSArray */ + return self::CustomJSArray|constructor#(_); +static method test(js_::JSAny /* = dart.core::Object */ any) → void { + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA /* = _interceptors::JSArray */>(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA */>(any); + js_::JSAnyUtilityExtension|isA((() → js_::JSAny /* = dart.core::Object */ { + return any; + })(){() → js_::JSAny /* = dart.core::Object */}); +} +static method main() → void {} + +constants { + #C1 = "library1" + #C2 = js_::JS {name:#C1} + #C3 = "RenamedJSArray" + #C4 = js_::JS {name:#C3} +} + + +Constructor coverage from constants: +org-dartlang-testcase:///isa.dart: +- JS. (from org-dartlang-sdk:///lib/js_interop/js_interop.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) diff --git a/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.strong.transformed.expect new file mode 100644 index 00000000000..e48806d966e --- /dev/null +++ b/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.strong.transformed.expect @@ -0,0 +1,88 @@ +@#C2 +library; +import self as self; +import "dart:js_interop" as js_; +import "dart:js_interop_unsafe" as js_2; + +import "dart:js_interop"; + +extension type CustomJSAny(js_::JSAny /* = dart.core::Object */ _) implements js_::JSAny /* = dart.core::Object */ { + abstract extension-type-member representation-field get _() → js_::JSAny /* = dart.core::Object */; + constructor • = self::CustomJSAny|constructor#; + constructor tearoff • = self::CustomJSAny|constructor#_#new#tearOff; +} +extension type CustomJSObject(js_::JSObject /* = _interceptors::JSObject */ _) implements js_::JSObject /* = _interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSObject /* = _interceptors::JSObject */; + constructor • = self::CustomJSObject|constructor#; + constructor tearoff • = self::CustomJSObject|constructor#_#new#tearOff; +} +extension type CustomTypedArray(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) implements js_::JSObject /* = _interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */; + constructor • = self::CustomTypedArray|constructor#; + constructor tearoff • = self::CustomTypedArray|constructor#_#new#tearOff; +} +@#C4 +extension type CustomJSArray(js_::JSArray /* = _interceptors::JSArray */ _) implements js_::JSObject /* = _interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSArray /* = _interceptors::JSArray */; + constructor • = self::CustomJSArray|constructor#; + constructor tearoff • = self::CustomJSArray|constructor#_#new#tearOff; +} +static extension-type-member method CustomJSAny|constructor#(js_::JSAny /* = dart.core::Object */ _) → self::CustomJSAny /* = dart.core::Object */ { + lowered final self::CustomJSAny /* = dart.core::Object */ #this = _; + return #this; +} +static extension-type-member method CustomJSAny|constructor#_#new#tearOff(js_::JSAny /* = dart.core::Object */ _) → self::CustomJSAny /* = dart.core::Object */ + return self::CustomJSAny|constructor#(_); +static extension-type-member method CustomJSObject|constructor#(js_::JSObject /* = _interceptors::JSObject */ _) → self::CustomJSObject /* = _interceptors::JSObject */ { + lowered final self::CustomJSObject /* = _interceptors::JSObject */ #this = _; + return #this; +} +static extension-type-member method CustomJSObject|constructor#_#new#tearOff(js_::JSObject /* = _interceptors::JSObject */ _) → self::CustomJSObject /* = _interceptors::JSObject */ + return self::CustomJSObject|constructor#(_); +static extension-type-member method CustomTypedArray|constructor#(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) → self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ { + lowered final self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ #this = _; + return #this; +} +static extension-type-member method CustomTypedArray|constructor#_#new#tearOff(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) → self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ + return self::CustomTypedArray|constructor#(_); +static extension-type-member method CustomJSArray|constructor#(js_::JSArray /* = _interceptors::JSArray */ _) → self::CustomJSArray /* = _interceptors::JSArray */ { + lowered final self::CustomJSArray /* = _interceptors::JSArray */ #this = _; + return #this; +} +static extension-type-member method CustomJSArray|constructor#_#new#tearOff(js_::JSArray /* = _interceptors::JSArray */ _) → self::CustomJSArray /* = _interceptors::JSArray */ + return self::CustomJSArray|constructor#(_); +static method test(js_::JSAny /* = dart.core::Object */ any) → void { + !(any == null); + true; + !(any == null) && js_::JSAnyUtilityExtension|typeofEquals(any, "string"); + any == null || js_::JSAnyUtilityExtension|typeofEquals(any, "string"); + !(any == null) && js_::JSAnyUtilityExtension|typeofEquals(any, "number"); + !(any == null) && js_::JSAnyUtilityExtension|typeofEquals(any, "boolean"); + !(any == null) && js_::JSAnyUtilityExtension|typeofEquals(any, "symbol"); + !(any == null) && js_::JSAnyUtilityExtension|typeofEquals(any, "bigint"); + !(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "Object"); + any == null || js_::JSAnyUtilityExtension|instanceOfString(any, "Object"); + !(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "Array"); + !(any == null) && js_::JSAnyUtilityExtension|instanceof(any, js_2::JSObjectUnsafeUtilExtension|callMethodVarArgs(js_2::JSObjectUnsafeUtilExtension|[](js_::globalContext, "Object") as{ForLegacy} js_::JSObject /* = _interceptors::JSObject */, js_::StringToJSString|get#toJS("getPrototypeOf"), [js_2::JSObjectUnsafeUtilExtension|[](js_::globalContext, "Int8Array") as{ForLegacy} js_::JSObject /* = _interceptors::JSObject */])); + !(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "library1.CustomJSAny"); + !(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "library1.CustomJSObject"); + !(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "library1.CustomTypedArray"); + !(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "library1.RenamedJSArray"); + let final js_::JSAny? /* = dart.core::Object? */ #t1 = (() → js_::JSAny /* = dart.core::Object */ { + return any; + })(){() → js_::JSAny /* = dart.core::Object */} in !(#t1 == null); +} +static method main() → void {} + +constants { + #C1 = "library1" + #C2 = js_::JS {name:#C1} + #C3 = "RenamedJSArray" + #C4 = js_::JS {name:#C3} +} + + +Constructor coverage from constants: +org-dartlang-testcase:///isa.dart: +- JS. (from org-dartlang-sdk:///lib/js_interop/js_interop.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) diff --git a/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.textual_outline.expect b/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.textual_outline.expect new file mode 100644 index 00000000000..a233327699d --- /dev/null +++ b/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.textual_outline.expect @@ -0,0 +1,18 @@ +@JS('library1') + +library; + +import 'dart:js_interop'; + +extension type CustomJSAny(JSAny _) implements JSAny {} + +extension type CustomJSObject(JSObject _) implements JSObject {} + +extension type CustomTypedArray(JSTypedArray _) implements JSObject {} + +@JS('RenamedJSArray') +extension type CustomJSArray(JSArray _) implements JSObject {} + +void test(JSAny any) {} + +void main() {} diff --git a/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.textual_outline_modelled.expect new file mode 100644 index 00000000000..3cf133c4c6a --- /dev/null +++ b/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.textual_outline_modelled.expect @@ -0,0 +1,17 @@ +@JS('library1') +library; + +import 'dart:js_interop'; + +extension type CustomJSAny(JSAny _) implements JSAny {} + +@JS('RenamedJSArray') +extension type CustomJSArray(JSArray _) implements JSObject {} + +extension type CustomJSObject(JSObject _) implements JSObject {} + +extension type CustomTypedArray(JSTypedArray _) implements JSObject {} + +void main() {} + +void test(JSAny any) {} diff --git a/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.weak.expect b/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.weak.expect new file mode 100644 index 00000000000..2ad34d66c11 --- /dev/null +++ b/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.weak.expect @@ -0,0 +1,87 @@ +@#C2 +library; +import self as self; +import "dart:js_interop" as js_; + +import "dart:js_interop"; + +extension type CustomJSAny(js_::JSAny /* = dart.core::Object */ _) implements js_::JSAny /* = dart.core::Object */ { + abstract extension-type-member representation-field get _() → js_::JSAny /* = dart.core::Object */; + constructor • = self::CustomJSAny|constructor#; + constructor tearoff • = self::CustomJSAny|constructor#_#new#tearOff; +} +extension type CustomJSObject(js_::JSObject /* = _interceptors::JSObject */ _) implements js_::JSObject /* = _interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSObject /* = _interceptors::JSObject */; + constructor • = self::CustomJSObject|constructor#; + constructor tearoff • = self::CustomJSObject|constructor#_#new#tearOff; +} +extension type CustomTypedArray(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) implements js_::JSObject /* = _interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */; + constructor • = self::CustomTypedArray|constructor#; + constructor tearoff • = self::CustomTypedArray|constructor#_#new#tearOff; +} +@#C4 +extension type CustomJSArray(js_::JSArray /* = _interceptors::JSArray */ _) implements js_::JSObject /* = _interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSArray /* = _interceptors::JSArray */; + constructor • = self::CustomJSArray|constructor#; + constructor tearoff • = self::CustomJSArray|constructor#_#new#tearOff; +} +static extension-type-member method CustomJSAny|constructor#(js_::JSAny /* = dart.core::Object */ _) → self::CustomJSAny /* = dart.core::Object */ { + lowered final self::CustomJSAny /* = dart.core::Object */ #this = _; + return #this; +} +static extension-type-member method CustomJSAny|constructor#_#new#tearOff(js_::JSAny /* = dart.core::Object */ _) → self::CustomJSAny /* = dart.core::Object */ + return self::CustomJSAny|constructor#(_); +static extension-type-member method CustomJSObject|constructor#(js_::JSObject /* = _interceptors::JSObject */ _) → self::CustomJSObject /* = _interceptors::JSObject */ { + lowered final self::CustomJSObject /* = _interceptors::JSObject */ #this = _; + return #this; +} +static extension-type-member method CustomJSObject|constructor#_#new#tearOff(js_::JSObject /* = _interceptors::JSObject */ _) → self::CustomJSObject /* = _interceptors::JSObject */ + return self::CustomJSObject|constructor#(_); +static extension-type-member method CustomTypedArray|constructor#(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) → self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ { + lowered final self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ #this = _; + return #this; +} +static extension-type-member method CustomTypedArray|constructor#_#new#tearOff(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) → self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ + return self::CustomTypedArray|constructor#(_); +static extension-type-member method CustomJSArray|constructor#(js_::JSArray /* = _interceptors::JSArray */ _) → self::CustomJSArray /* = _interceptors::JSArray */ { + lowered final self::CustomJSArray /* = _interceptors::JSArray */ #this = _; + return #this; +} +static extension-type-member method CustomJSArray|constructor#_#new#tearOff(js_::JSArray /* = _interceptors::JSArray */ _) → self::CustomJSArray /* = _interceptors::JSArray */ + return self::CustomJSArray|constructor#(_); +static method test(js_::JSAny /* = dart.core::Object */ any) → void { + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA /* = _interceptors::JSArray */>(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA */>(any); + js_::JSAnyUtilityExtension|isA((() → js_::JSAny /* = dart.core::Object */ { + return any; + })(){() → js_::JSAny /* = dart.core::Object */}); +} +static method main() → void {} + +constants { + #C1 = "library1" + #C2 = js_::JS {name:#C1} + #C3 = "RenamedJSArray" + #C4 = js_::JS {name:#C3} +} + + +Constructor coverage from constants: +org-dartlang-testcase:///isa.dart: +- JS. (from org-dartlang-sdk:///lib/js_interop/js_interop.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) diff --git a/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.weak.modular.expect b/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.weak.modular.expect new file mode 100644 index 00000000000..2ad34d66c11 --- /dev/null +++ b/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.weak.modular.expect @@ -0,0 +1,87 @@ +@#C2 +library; +import self as self; +import "dart:js_interop" as js_; + +import "dart:js_interop"; + +extension type CustomJSAny(js_::JSAny /* = dart.core::Object */ _) implements js_::JSAny /* = dart.core::Object */ { + abstract extension-type-member representation-field get _() → js_::JSAny /* = dart.core::Object */; + constructor • = self::CustomJSAny|constructor#; + constructor tearoff • = self::CustomJSAny|constructor#_#new#tearOff; +} +extension type CustomJSObject(js_::JSObject /* = _interceptors::JSObject */ _) implements js_::JSObject /* = _interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSObject /* = _interceptors::JSObject */; + constructor • = self::CustomJSObject|constructor#; + constructor tearoff • = self::CustomJSObject|constructor#_#new#tearOff; +} +extension type CustomTypedArray(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) implements js_::JSObject /* = _interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */; + constructor • = self::CustomTypedArray|constructor#; + constructor tearoff • = self::CustomTypedArray|constructor#_#new#tearOff; +} +@#C4 +extension type CustomJSArray(js_::JSArray /* = _interceptors::JSArray */ _) implements js_::JSObject /* = _interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSArray /* = _interceptors::JSArray */; + constructor • = self::CustomJSArray|constructor#; + constructor tearoff • = self::CustomJSArray|constructor#_#new#tearOff; +} +static extension-type-member method CustomJSAny|constructor#(js_::JSAny /* = dart.core::Object */ _) → self::CustomJSAny /* = dart.core::Object */ { + lowered final self::CustomJSAny /* = dart.core::Object */ #this = _; + return #this; +} +static extension-type-member method CustomJSAny|constructor#_#new#tearOff(js_::JSAny /* = dart.core::Object */ _) → self::CustomJSAny /* = dart.core::Object */ + return self::CustomJSAny|constructor#(_); +static extension-type-member method CustomJSObject|constructor#(js_::JSObject /* = _interceptors::JSObject */ _) → self::CustomJSObject /* = _interceptors::JSObject */ { + lowered final self::CustomJSObject /* = _interceptors::JSObject */ #this = _; + return #this; +} +static extension-type-member method CustomJSObject|constructor#_#new#tearOff(js_::JSObject /* = _interceptors::JSObject */ _) → self::CustomJSObject /* = _interceptors::JSObject */ + return self::CustomJSObject|constructor#(_); +static extension-type-member method CustomTypedArray|constructor#(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) → self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ { + lowered final self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ #this = _; + return #this; +} +static extension-type-member method CustomTypedArray|constructor#_#new#tearOff(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) → self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ + return self::CustomTypedArray|constructor#(_); +static extension-type-member method CustomJSArray|constructor#(js_::JSArray /* = _interceptors::JSArray */ _) → self::CustomJSArray /* = _interceptors::JSArray */ { + lowered final self::CustomJSArray /* = _interceptors::JSArray */ #this = _; + return #this; +} +static extension-type-member method CustomJSArray|constructor#_#new#tearOff(js_::JSArray /* = _interceptors::JSArray */ _) → self::CustomJSArray /* = _interceptors::JSArray */ + return self::CustomJSArray|constructor#(_); +static method test(js_::JSAny /* = dart.core::Object */ any) → void { + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA /* = _interceptors::JSArray */>(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA */>(any); + js_::JSAnyUtilityExtension|isA((() → js_::JSAny /* = dart.core::Object */ { + return any; + })(){() → js_::JSAny /* = dart.core::Object */}); +} +static method main() → void {} + +constants { + #C1 = "library1" + #C2 = js_::JS {name:#C1} + #C3 = "RenamedJSArray" + #C4 = js_::JS {name:#C3} +} + + +Constructor coverage from constants: +org-dartlang-testcase:///isa.dart: +- JS. (from org-dartlang-sdk:///lib/js_interop/js_interop.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) diff --git a/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.weak.outline.expect b/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.weak.outline.expect new file mode 100644 index 00000000000..8b029201445 --- /dev/null +++ b/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.weak.outline.expect @@ -0,0 +1,54 @@ +@dart.js_interop::JS::•("library1") +library; +import self as self; +import "dart:js_interop" as js_; + +import "dart:js_interop"; + +extension type CustomJSAny(js_::JSAny /* = dart.core::Object */ _) implements js_::JSAny /* = dart.core::Object */ { + abstract extension-type-member representation-field get _() → js_::JSAny /* = dart.core::Object */; + constructor • = self::CustomJSAny|constructor#; + constructor tearoff • = self::CustomJSAny|constructor#_#new#tearOff; +} +extension type CustomJSObject(js_::JSObject /* = _interceptors::JSObject */ _) implements js_::JSObject /* = _interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSObject /* = _interceptors::JSObject */; + constructor • = self::CustomJSObject|constructor#; + constructor tearoff • = self::CustomJSObject|constructor#_#new#tearOff; +} +extension type CustomTypedArray(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) implements js_::JSObject /* = _interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */; + constructor • = self::CustomTypedArray|constructor#; + constructor tearoff • = self::CustomTypedArray|constructor#_#new#tearOff; +} +@js_::JS::•("RenamedJSArray") +extension type CustomJSArray(js_::JSArray /* = _interceptors::JSArray */ _) implements js_::JSObject /* = _interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSArray /* = _interceptors::JSArray */; + constructor • = self::CustomJSArray|constructor#; + constructor tearoff • = self::CustomJSArray|constructor#_#new#tearOff; +} +static extension-type-member method CustomJSAny|constructor#(js_::JSAny /* = dart.core::Object */ _) → self::CustomJSAny /* = dart.core::Object */ + ; +static extension-type-member method CustomJSAny|constructor#_#new#tearOff(js_::JSAny /* = dart.core::Object */ _) → self::CustomJSAny /* = dart.core::Object */ + return self::CustomJSAny|constructor#(_); +static extension-type-member method CustomJSObject|constructor#(js_::JSObject /* = _interceptors::JSObject */ _) → self::CustomJSObject /* = _interceptors::JSObject */ + ; +static extension-type-member method CustomJSObject|constructor#_#new#tearOff(js_::JSObject /* = _interceptors::JSObject */ _) → self::CustomJSObject /* = _interceptors::JSObject */ + return self::CustomJSObject|constructor#(_); +static extension-type-member method CustomTypedArray|constructor#(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) → self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ + ; +static extension-type-member method CustomTypedArray|constructor#_#new#tearOff(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) → self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ + return self::CustomTypedArray|constructor#(_); +static extension-type-member method CustomJSArray|constructor#(js_::JSArray /* = _interceptors::JSArray */ _) → self::CustomJSArray /* = _interceptors::JSArray */ + ; +static extension-type-member method CustomJSArray|constructor#_#new#tearOff(js_::JSArray /* = _interceptors::JSArray */ _) → self::CustomJSArray /* = _interceptors::JSArray */ + return self::CustomJSArray|constructor#(_); +static method test(js_::JSAny /* = dart.core::Object */ any) → void + ; +static method main() → void + ; + + +Extra constant evaluation status: +Evaluated: ConstructorInvocation @ org-dartlang-testcase:///isa.dart:5:2 -> InstanceConstant(const JS{JS.name: "library1"}) +Evaluated: ConstructorInvocation @ org-dartlang-testcase:///isa.dart:16:2 -> InstanceConstant(const JS{JS.name: "RenamedJSArray"}) +Extra constant evaluation: evaluated: 10, effectively constant: 2 diff --git a/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.weak.transformed.expect b/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.weak.transformed.expect new file mode 100644 index 00000000000..e48806d966e --- /dev/null +++ b/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.weak.transformed.expect @@ -0,0 +1,88 @@ +@#C2 +library; +import self as self; +import "dart:js_interop" as js_; +import "dart:js_interop_unsafe" as js_2; + +import "dart:js_interop"; + +extension type CustomJSAny(js_::JSAny /* = dart.core::Object */ _) implements js_::JSAny /* = dart.core::Object */ { + abstract extension-type-member representation-field get _() → js_::JSAny /* = dart.core::Object */; + constructor • = self::CustomJSAny|constructor#; + constructor tearoff • = self::CustomJSAny|constructor#_#new#tearOff; +} +extension type CustomJSObject(js_::JSObject /* = _interceptors::JSObject */ _) implements js_::JSObject /* = _interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSObject /* = _interceptors::JSObject */; + constructor • = self::CustomJSObject|constructor#; + constructor tearoff • = self::CustomJSObject|constructor#_#new#tearOff; +} +extension type CustomTypedArray(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) implements js_::JSObject /* = _interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */; + constructor • = self::CustomTypedArray|constructor#; + constructor tearoff • = self::CustomTypedArray|constructor#_#new#tearOff; +} +@#C4 +extension type CustomJSArray(js_::JSArray /* = _interceptors::JSArray */ _) implements js_::JSObject /* = _interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSArray /* = _interceptors::JSArray */; + constructor • = self::CustomJSArray|constructor#; + constructor tearoff • = self::CustomJSArray|constructor#_#new#tearOff; +} +static extension-type-member method CustomJSAny|constructor#(js_::JSAny /* = dart.core::Object */ _) → self::CustomJSAny /* = dart.core::Object */ { + lowered final self::CustomJSAny /* = dart.core::Object */ #this = _; + return #this; +} +static extension-type-member method CustomJSAny|constructor#_#new#tearOff(js_::JSAny /* = dart.core::Object */ _) → self::CustomJSAny /* = dart.core::Object */ + return self::CustomJSAny|constructor#(_); +static extension-type-member method CustomJSObject|constructor#(js_::JSObject /* = _interceptors::JSObject */ _) → self::CustomJSObject /* = _interceptors::JSObject */ { + lowered final self::CustomJSObject /* = _interceptors::JSObject */ #this = _; + return #this; +} +static extension-type-member method CustomJSObject|constructor#_#new#tearOff(js_::JSObject /* = _interceptors::JSObject */ _) → self::CustomJSObject /* = _interceptors::JSObject */ + return self::CustomJSObject|constructor#(_); +static extension-type-member method CustomTypedArray|constructor#(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) → self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ { + lowered final self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ #this = _; + return #this; +} +static extension-type-member method CustomTypedArray|constructor#_#new#tearOff(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) → self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ + return self::CustomTypedArray|constructor#(_); +static extension-type-member method CustomJSArray|constructor#(js_::JSArray /* = _interceptors::JSArray */ _) → self::CustomJSArray /* = _interceptors::JSArray */ { + lowered final self::CustomJSArray /* = _interceptors::JSArray */ #this = _; + return #this; +} +static extension-type-member method CustomJSArray|constructor#_#new#tearOff(js_::JSArray /* = _interceptors::JSArray */ _) → self::CustomJSArray /* = _interceptors::JSArray */ + return self::CustomJSArray|constructor#(_); +static method test(js_::JSAny /* = dart.core::Object */ any) → void { + !(any == null); + true; + !(any == null) && js_::JSAnyUtilityExtension|typeofEquals(any, "string"); + any == null || js_::JSAnyUtilityExtension|typeofEquals(any, "string"); + !(any == null) && js_::JSAnyUtilityExtension|typeofEquals(any, "number"); + !(any == null) && js_::JSAnyUtilityExtension|typeofEquals(any, "boolean"); + !(any == null) && js_::JSAnyUtilityExtension|typeofEquals(any, "symbol"); + !(any == null) && js_::JSAnyUtilityExtension|typeofEquals(any, "bigint"); + !(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "Object"); + any == null || js_::JSAnyUtilityExtension|instanceOfString(any, "Object"); + !(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "Array"); + !(any == null) && js_::JSAnyUtilityExtension|instanceof(any, js_2::JSObjectUnsafeUtilExtension|callMethodVarArgs(js_2::JSObjectUnsafeUtilExtension|[](js_::globalContext, "Object") as{ForLegacy} js_::JSObject /* = _interceptors::JSObject */, js_::StringToJSString|get#toJS("getPrototypeOf"), [js_2::JSObjectUnsafeUtilExtension|[](js_::globalContext, "Int8Array") as{ForLegacy} js_::JSObject /* = _interceptors::JSObject */])); + !(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "library1.CustomJSAny"); + !(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "library1.CustomJSObject"); + !(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "library1.CustomTypedArray"); + !(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "library1.RenamedJSArray"); + let final js_::JSAny? /* = dart.core::Object? */ #t1 = (() → js_::JSAny /* = dart.core::Object */ { + return any; + })(){() → js_::JSAny /* = dart.core::Object */} in !(#t1 == null); +} +static method main() → void {} + +constants { + #C1 = "library1" + #C2 = js_::JS {name:#C1} + #C3 = "RenamedJSArray" + #C4 = js_::JS {name:#C3} +} + + +Constructor coverage from constants: +org-dartlang-testcase:///isa.dart: +- JS. (from org-dartlang-sdk:///lib/js_interop/js_interop.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) diff --git a/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart b/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart new file mode 100644 index 00000000000..f7b381431d2 --- /dev/null +++ b/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart @@ -0,0 +1,52 @@ +// Copyright (c) 2024, 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. + +@JS('library1') +library; + +import 'dart:js_interop'; + +extension type CustomJSAny(JSAny _) implements JSAny {} + +extension type CustomJSObject(JSObject _) implements JSObject {} + +extension type CustomTypedArray(JSTypedArray _) implements JSObject {} + +@JS('RenamedJSArray') +extension type CustomJSArray(JSArray _) implements JSObject {} + +void test(JSAny any) { + // Top type. + any.isA(); + any.isA(); + + // Primitives. + any.isA(); + any.isA(); + any.isA(); + any.isA(); + any.isA(); + any.isA(); + + // Objects. + any.isA(); + any.isA(); + any.isA(); + // JSTypedArray is handled differently. + any.isA(); + + // User-defined types. + any.isA(); + any.isA(); + any.isA(); + any.isA(); + + // Non-variable expression should avoid evaluating twice. + () { + return any; + }() + .isA(); +} + +void main() {} diff --git a/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.strong.expect b/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.strong.expect new file mode 100644 index 00000000000..4e974852c95 --- /dev/null +++ b/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.strong.expect @@ -0,0 +1,87 @@ +@#C2 +library; +import self as self; +import "dart:js_interop" as js_; + +import "dart:js_interop"; + +extension type CustomJSAny(js_::JSAny /* = dart.core::Object */ _) implements js_::JSAny /* = dart.core::Object */ { + abstract extension-type-member representation-field get _() → js_::JSAny /* = dart.core::Object */; + constructor • = self::CustomJSAny|constructor#; + constructor tearoff • = self::CustomJSAny|constructor#_#new#tearOff; +} +extension type CustomJSObject(js_::JSObject /* = dart._interceptors::JSObject */ _) implements js_::JSObject /* = dart._interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSObject /* = dart._interceptors::JSObject */; + constructor • = self::CustomJSObject|constructor#; + constructor tearoff • = self::CustomJSObject|constructor#_#new#tearOff; +} +extension type CustomTypedArray(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) implements js_::JSObject /* = dart._interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */; + constructor • = self::CustomTypedArray|constructor#; + constructor tearoff • = self::CustomTypedArray|constructor#_#new#tearOff; +} +@#C4 +extension type CustomJSArray(js_::JSArray /* = dart._interceptors::JSArray */ _) implements js_::JSObject /* = dart._interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSArray /* = dart._interceptors::JSArray */; + constructor • = self::CustomJSArray|constructor#; + constructor tearoff • = self::CustomJSArray|constructor#_#new#tearOff; +} +static extension-type-member method CustomJSAny|constructor#(js_::JSAny /* = dart.core::Object */ _) → self::CustomJSAny /* = dart.core::Object */ { + lowered final self::CustomJSAny /* = dart.core::Object */ #this = _; + return #this; +} +static extension-type-member method CustomJSAny|constructor#_#new#tearOff(js_::JSAny /* = dart.core::Object */ _) → self::CustomJSAny /* = dart.core::Object */ + return self::CustomJSAny|constructor#(_); +static extension-type-member method CustomJSObject|constructor#(js_::JSObject /* = dart._interceptors::JSObject */ _) → self::CustomJSObject /* = dart._interceptors::JSObject */ { + lowered final self::CustomJSObject /* = dart._interceptors::JSObject */ #this = _; + return #this; +} +static extension-type-member method CustomJSObject|constructor#_#new#tearOff(js_::JSObject /* = dart._interceptors::JSObject */ _) → self::CustomJSObject /* = dart._interceptors::JSObject */ + return self::CustomJSObject|constructor#(_); +static extension-type-member method CustomTypedArray|constructor#(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) → self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ { + lowered final self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ #this = _; + return #this; +} +static extension-type-member method CustomTypedArray|constructor#_#new#tearOff(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) → self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ + return self::CustomTypedArray|constructor#(_); +static extension-type-member method CustomJSArray|constructor#(js_::JSArray /* = dart._interceptors::JSArray */ _) → self::CustomJSArray /* = dart._interceptors::JSArray */ { + lowered final self::CustomJSArray /* = dart._interceptors::JSArray */ #this = _; + return #this; +} +static extension-type-member method CustomJSArray|constructor#_#new#tearOff(js_::JSArray /* = dart._interceptors::JSArray */ _) → self::CustomJSArray /* = dart._interceptors::JSArray */ + return self::CustomJSArray|constructor#(_); +static method test(js_::JSAny /* = dart.core::Object */ any) → void { + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA /* = dart._interceptors::JSArray */>(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA */>(any); + js_::JSAnyUtilityExtension|isA((() → js_::JSAny /* = dart.core::Object */ { + return any; + })(){() → js_::JSAny /* = dart.core::Object */}); +} +static method main() → void {} + +constants { + #C1 = "library1" + #C2 = js_::JS {name:#C1} + #C3 = "RenamedJSArray" + #C4 = js_::JS {name:#C3} +} + + +Constructor coverage from constants: +org-dartlang-testcase:///isa.dart: +- JS. (from org-dartlang-sdk:///lib/js_interop/js_interop.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) diff --git a/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.strong.transformed.expect b/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.strong.transformed.expect new file mode 100644 index 00000000000..e0f22479b88 --- /dev/null +++ b/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.strong.transformed.expect @@ -0,0 +1,88 @@ +@#C2 +library; +import self as self; +import "dart:js_interop" as js_; +import "dart:js_interop_unsafe" as js_2; + +import "dart:js_interop"; + +extension type CustomJSAny(js_::JSAny /* = dart.core::Object */ _) implements js_::JSAny /* = dart.core::Object */ { + abstract extension-type-member representation-field get _() → js_::JSAny /* = dart.core::Object */; + constructor • = self::CustomJSAny|constructor#; + constructor tearoff • = self::CustomJSAny|constructor#_#new#tearOff; +} +extension type CustomJSObject(js_::JSObject /* = dart._interceptors::JSObject */ _) implements js_::JSObject /* = dart._interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSObject /* = dart._interceptors::JSObject */; + constructor • = self::CustomJSObject|constructor#; + constructor tearoff • = self::CustomJSObject|constructor#_#new#tearOff; +} +extension type CustomTypedArray(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) implements js_::JSObject /* = dart._interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */; + constructor • = self::CustomTypedArray|constructor#; + constructor tearoff • = self::CustomTypedArray|constructor#_#new#tearOff; +} +@#C4 +extension type CustomJSArray(js_::JSArray /* = dart._interceptors::JSArray */ _) implements js_::JSObject /* = dart._interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSArray /* = dart._interceptors::JSArray */; + constructor • = self::CustomJSArray|constructor#; + constructor tearoff • = self::CustomJSArray|constructor#_#new#tearOff; +} +static extension-type-member method CustomJSAny|constructor#(js_::JSAny /* = dart.core::Object */ _) → self::CustomJSAny /* = dart.core::Object */ { + lowered final self::CustomJSAny /* = dart.core::Object */ #this = _; + return #this; +} +static extension-type-member method CustomJSAny|constructor#_#new#tearOff(js_::JSAny /* = dart.core::Object */ _) → self::CustomJSAny /* = dart.core::Object */ + return self::CustomJSAny|constructor#(_); +static extension-type-member method CustomJSObject|constructor#(js_::JSObject /* = dart._interceptors::JSObject */ _) → self::CustomJSObject /* = dart._interceptors::JSObject */ { + lowered final self::CustomJSObject /* = dart._interceptors::JSObject */ #this = _; + return #this; +} +static extension-type-member method CustomJSObject|constructor#_#new#tearOff(js_::JSObject /* = dart._interceptors::JSObject */ _) → self::CustomJSObject /* = dart._interceptors::JSObject */ + return self::CustomJSObject|constructor#(_); +static extension-type-member method CustomTypedArray|constructor#(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) → self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ { + lowered final self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ #this = _; + return #this; +} +static extension-type-member method CustomTypedArray|constructor#_#new#tearOff(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) → self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ + return self::CustomTypedArray|constructor#(_); +static extension-type-member method CustomJSArray|constructor#(js_::JSArray /* = dart._interceptors::JSArray */ _) → self::CustomJSArray /* = dart._interceptors::JSArray */ { + lowered final self::CustomJSArray /* = dart._interceptors::JSArray */ #this = _; + return #this; +} +static extension-type-member method CustomJSArray|constructor#_#new#tearOff(js_::JSArray /* = dart._interceptors::JSArray */ _) → self::CustomJSArray /* = dart._interceptors::JSArray */ + return self::CustomJSArray|constructor#(_); +static method test(js_::JSAny /* = dart.core::Object */ any) → void { + !(any == null); + true; + !(any == null) && js_::JSAnyUtilityExtension|typeofEquals(any, "string"); + any == null || js_::JSAnyUtilityExtension|typeofEquals(any, "string"); + !(any == null) && js_::JSAnyUtilityExtension|typeofEquals(any, "number"); + !(any == null) && js_::JSAnyUtilityExtension|typeofEquals(any, "boolean"); + !(any == null) && js_::JSAnyUtilityExtension|typeofEquals(any, "symbol"); + !(any == null) && js_::JSAnyUtilityExtension|typeofEquals(any, "bigint"); + !(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "Object"); + any == null || js_::JSAnyUtilityExtension|instanceOfString(any, "Object"); + !(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "Array"); + !(any == null) && js_::JSAnyUtilityExtension|instanceof(any, js_2::JSObjectUnsafeUtilExtension|callMethodVarArgs(js_2::JSObjectUnsafeUtilExtension|[](js_::globalContext, "Object") as{ForLegacy} js_::JSObject /* = dart._interceptors::JSObject */, js_::StringToJSString|get#toJS("getPrototypeOf"), [js_2::JSObjectUnsafeUtilExtension|[](js_::globalContext, "Int8Array") as{ForLegacy} js_::JSObject /* = dart._interceptors::JSObject */])); + !(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "library1.CustomJSAny"); + !(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "library1.CustomJSObject"); + !(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "library1.CustomTypedArray"); + !(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "library1.RenamedJSArray"); + let final js_::JSAny? /* = dart.core::Object? */ #t1 = (() → js_::JSAny /* = dart.core::Object */ { + return any; + })(){() → js_::JSAny /* = dart.core::Object */} in !(#t1 == null); +} +static method main() → void {} + +constants { + #C1 = "library1" + #C2 = js_::JS {name:#C1} + #C3 = "RenamedJSArray" + #C4 = js_::JS {name:#C3} +} + + +Constructor coverage from constants: +org-dartlang-testcase:///isa.dart: +- JS. (from org-dartlang-sdk:///lib/js_interop/js_interop.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) diff --git a/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.textual_outline.expect b/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.textual_outline.expect new file mode 100644 index 00000000000..a233327699d --- /dev/null +++ b/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.textual_outline.expect @@ -0,0 +1,18 @@ +@JS('library1') + +library; + +import 'dart:js_interop'; + +extension type CustomJSAny(JSAny _) implements JSAny {} + +extension type CustomJSObject(JSObject _) implements JSObject {} + +extension type CustomTypedArray(JSTypedArray _) implements JSObject {} + +@JS('RenamedJSArray') +extension type CustomJSArray(JSArray _) implements JSObject {} + +void test(JSAny any) {} + +void main() {} diff --git a/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.textual_outline_modelled.expect new file mode 100644 index 00000000000..3cf133c4c6a --- /dev/null +++ b/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.textual_outline_modelled.expect @@ -0,0 +1,17 @@ +@JS('library1') +library; + +import 'dart:js_interop'; + +extension type CustomJSAny(JSAny _) implements JSAny {} + +@JS('RenamedJSArray') +extension type CustomJSArray(JSArray _) implements JSObject {} + +extension type CustomJSObject(JSObject _) implements JSObject {} + +extension type CustomTypedArray(JSTypedArray _) implements JSObject {} + +void main() {} + +void test(JSAny any) {} diff --git a/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.weak.expect b/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.weak.expect new file mode 100644 index 00000000000..4e974852c95 --- /dev/null +++ b/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.weak.expect @@ -0,0 +1,87 @@ +@#C2 +library; +import self as self; +import "dart:js_interop" as js_; + +import "dart:js_interop"; + +extension type CustomJSAny(js_::JSAny /* = dart.core::Object */ _) implements js_::JSAny /* = dart.core::Object */ { + abstract extension-type-member representation-field get _() → js_::JSAny /* = dart.core::Object */; + constructor • = self::CustomJSAny|constructor#; + constructor tearoff • = self::CustomJSAny|constructor#_#new#tearOff; +} +extension type CustomJSObject(js_::JSObject /* = dart._interceptors::JSObject */ _) implements js_::JSObject /* = dart._interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSObject /* = dart._interceptors::JSObject */; + constructor • = self::CustomJSObject|constructor#; + constructor tearoff • = self::CustomJSObject|constructor#_#new#tearOff; +} +extension type CustomTypedArray(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) implements js_::JSObject /* = dart._interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */; + constructor • = self::CustomTypedArray|constructor#; + constructor tearoff • = self::CustomTypedArray|constructor#_#new#tearOff; +} +@#C4 +extension type CustomJSArray(js_::JSArray /* = dart._interceptors::JSArray */ _) implements js_::JSObject /* = dart._interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSArray /* = dart._interceptors::JSArray */; + constructor • = self::CustomJSArray|constructor#; + constructor tearoff • = self::CustomJSArray|constructor#_#new#tearOff; +} +static extension-type-member method CustomJSAny|constructor#(js_::JSAny /* = dart.core::Object */ _) → self::CustomJSAny /* = dart.core::Object */ { + lowered final self::CustomJSAny /* = dart.core::Object */ #this = _; + return #this; +} +static extension-type-member method CustomJSAny|constructor#_#new#tearOff(js_::JSAny /* = dart.core::Object */ _) → self::CustomJSAny /* = dart.core::Object */ + return self::CustomJSAny|constructor#(_); +static extension-type-member method CustomJSObject|constructor#(js_::JSObject /* = dart._interceptors::JSObject */ _) → self::CustomJSObject /* = dart._interceptors::JSObject */ { + lowered final self::CustomJSObject /* = dart._interceptors::JSObject */ #this = _; + return #this; +} +static extension-type-member method CustomJSObject|constructor#_#new#tearOff(js_::JSObject /* = dart._interceptors::JSObject */ _) → self::CustomJSObject /* = dart._interceptors::JSObject */ + return self::CustomJSObject|constructor#(_); +static extension-type-member method CustomTypedArray|constructor#(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) → self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ { + lowered final self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ #this = _; + return #this; +} +static extension-type-member method CustomTypedArray|constructor#_#new#tearOff(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) → self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ + return self::CustomTypedArray|constructor#(_); +static extension-type-member method CustomJSArray|constructor#(js_::JSArray /* = dart._interceptors::JSArray */ _) → self::CustomJSArray /* = dart._interceptors::JSArray */ { + lowered final self::CustomJSArray /* = dart._interceptors::JSArray */ #this = _; + return #this; +} +static extension-type-member method CustomJSArray|constructor#_#new#tearOff(js_::JSArray /* = dart._interceptors::JSArray */ _) → self::CustomJSArray /* = dart._interceptors::JSArray */ + return self::CustomJSArray|constructor#(_); +static method test(js_::JSAny /* = dart.core::Object */ any) → void { + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA /* = dart._interceptors::JSArray */>(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA */>(any); + js_::JSAnyUtilityExtension|isA((() → js_::JSAny /* = dart.core::Object */ { + return any; + })(){() → js_::JSAny /* = dart.core::Object */}); +} +static method main() → void {} + +constants { + #C1 = "library1" + #C2 = js_::JS {name:#C1} + #C3 = "RenamedJSArray" + #C4 = js_::JS {name:#C3} +} + + +Constructor coverage from constants: +org-dartlang-testcase:///isa.dart: +- JS. (from org-dartlang-sdk:///lib/js_interop/js_interop.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) diff --git a/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.weak.modular.expect b/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.weak.modular.expect new file mode 100644 index 00000000000..4e974852c95 --- /dev/null +++ b/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.weak.modular.expect @@ -0,0 +1,87 @@ +@#C2 +library; +import self as self; +import "dart:js_interop" as js_; + +import "dart:js_interop"; + +extension type CustomJSAny(js_::JSAny /* = dart.core::Object */ _) implements js_::JSAny /* = dart.core::Object */ { + abstract extension-type-member representation-field get _() → js_::JSAny /* = dart.core::Object */; + constructor • = self::CustomJSAny|constructor#; + constructor tearoff • = self::CustomJSAny|constructor#_#new#tearOff; +} +extension type CustomJSObject(js_::JSObject /* = dart._interceptors::JSObject */ _) implements js_::JSObject /* = dart._interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSObject /* = dart._interceptors::JSObject */; + constructor • = self::CustomJSObject|constructor#; + constructor tearoff • = self::CustomJSObject|constructor#_#new#tearOff; +} +extension type CustomTypedArray(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) implements js_::JSObject /* = dart._interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */; + constructor • = self::CustomTypedArray|constructor#; + constructor tearoff • = self::CustomTypedArray|constructor#_#new#tearOff; +} +@#C4 +extension type CustomJSArray(js_::JSArray /* = dart._interceptors::JSArray */ _) implements js_::JSObject /* = dart._interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSArray /* = dart._interceptors::JSArray */; + constructor • = self::CustomJSArray|constructor#; + constructor tearoff • = self::CustomJSArray|constructor#_#new#tearOff; +} +static extension-type-member method CustomJSAny|constructor#(js_::JSAny /* = dart.core::Object */ _) → self::CustomJSAny /* = dart.core::Object */ { + lowered final self::CustomJSAny /* = dart.core::Object */ #this = _; + return #this; +} +static extension-type-member method CustomJSAny|constructor#_#new#tearOff(js_::JSAny /* = dart.core::Object */ _) → self::CustomJSAny /* = dart.core::Object */ + return self::CustomJSAny|constructor#(_); +static extension-type-member method CustomJSObject|constructor#(js_::JSObject /* = dart._interceptors::JSObject */ _) → self::CustomJSObject /* = dart._interceptors::JSObject */ { + lowered final self::CustomJSObject /* = dart._interceptors::JSObject */ #this = _; + return #this; +} +static extension-type-member method CustomJSObject|constructor#_#new#tearOff(js_::JSObject /* = dart._interceptors::JSObject */ _) → self::CustomJSObject /* = dart._interceptors::JSObject */ + return self::CustomJSObject|constructor#(_); +static extension-type-member method CustomTypedArray|constructor#(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) → self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ { + lowered final self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ #this = _; + return #this; +} +static extension-type-member method CustomTypedArray|constructor#_#new#tearOff(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) → self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ + return self::CustomTypedArray|constructor#(_); +static extension-type-member method CustomJSArray|constructor#(js_::JSArray /* = dart._interceptors::JSArray */ _) → self::CustomJSArray /* = dart._interceptors::JSArray */ { + lowered final self::CustomJSArray /* = dart._interceptors::JSArray */ #this = _; + return #this; +} +static extension-type-member method CustomJSArray|constructor#_#new#tearOff(js_::JSArray /* = dart._interceptors::JSArray */ _) → self::CustomJSArray /* = dart._interceptors::JSArray */ + return self::CustomJSArray|constructor#(_); +static method test(js_::JSAny /* = dart.core::Object */ any) → void { + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA /* = dart._interceptors::JSArray */>(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA(any); + js_::JSAnyUtilityExtension|isA */>(any); + js_::JSAnyUtilityExtension|isA((() → js_::JSAny /* = dart.core::Object */ { + return any; + })(){() → js_::JSAny /* = dart.core::Object */}); +} +static method main() → void {} + +constants { + #C1 = "library1" + #C2 = js_::JS {name:#C1} + #C3 = "RenamedJSArray" + #C4 = js_::JS {name:#C3} +} + + +Constructor coverage from constants: +org-dartlang-testcase:///isa.dart: +- JS. (from org-dartlang-sdk:///lib/js_interop/js_interop.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) diff --git a/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.weak.outline.expect b/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.weak.outline.expect new file mode 100644 index 00000000000..36024fa95ae --- /dev/null +++ b/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.weak.outline.expect @@ -0,0 +1,54 @@ +@dart.js_interop::JS::•("library1") +library; +import self as self; +import "dart:js_interop" as js_; + +import "dart:js_interop"; + +extension type CustomJSAny(js_::JSAny /* = dart.core::Object */ _) implements js_::JSAny /* = dart.core::Object */ { + abstract extension-type-member representation-field get _() → js_::JSAny /* = dart.core::Object */; + constructor • = self::CustomJSAny|constructor#; + constructor tearoff • = self::CustomJSAny|constructor#_#new#tearOff; +} +extension type CustomJSObject(js_::JSObject /* = dart._interceptors::JSObject */ _) implements js_::JSObject /* = dart._interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSObject /* = dart._interceptors::JSObject */; + constructor • = self::CustomJSObject|constructor#; + constructor tearoff • = self::CustomJSObject|constructor#_#new#tearOff; +} +extension type CustomTypedArray(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) implements js_::JSObject /* = dart._interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */; + constructor • = self::CustomTypedArray|constructor#; + constructor tearoff • = self::CustomTypedArray|constructor#_#new#tearOff; +} +@js_::JS::•("RenamedJSArray") +extension type CustomJSArray(js_::JSArray /* = dart._interceptors::JSArray */ _) implements js_::JSObject /* = dart._interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSArray /* = dart._interceptors::JSArray */; + constructor • = self::CustomJSArray|constructor#; + constructor tearoff • = self::CustomJSArray|constructor#_#new#tearOff; +} +static extension-type-member method CustomJSAny|constructor#(js_::JSAny /* = dart.core::Object */ _) → self::CustomJSAny /* = dart.core::Object */ + ; +static extension-type-member method CustomJSAny|constructor#_#new#tearOff(js_::JSAny /* = dart.core::Object */ _) → self::CustomJSAny /* = dart.core::Object */ + return self::CustomJSAny|constructor#(_); +static extension-type-member method CustomJSObject|constructor#(js_::JSObject /* = dart._interceptors::JSObject */ _) → self::CustomJSObject /* = dart._interceptors::JSObject */ + ; +static extension-type-member method CustomJSObject|constructor#_#new#tearOff(js_::JSObject /* = dart._interceptors::JSObject */ _) → self::CustomJSObject /* = dart._interceptors::JSObject */ + return self::CustomJSObject|constructor#(_); +static extension-type-member method CustomTypedArray|constructor#(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) → self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ + ; +static extension-type-member method CustomTypedArray|constructor#_#new#tearOff(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) → self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ + return self::CustomTypedArray|constructor#(_); +static extension-type-member method CustomJSArray|constructor#(js_::JSArray /* = dart._interceptors::JSArray */ _) → self::CustomJSArray /* = dart._interceptors::JSArray */ + ; +static extension-type-member method CustomJSArray|constructor#_#new#tearOff(js_::JSArray /* = dart._interceptors::JSArray */ _) → self::CustomJSArray /* = dart._interceptors::JSArray */ + return self::CustomJSArray|constructor#(_); +static method test(js_::JSAny /* = dart.core::Object */ any) → void + ; +static method main() → void + ; + + +Extra constant evaluation status: +Evaluated: ConstructorInvocation @ org-dartlang-testcase:///isa.dart:5:2 -> InstanceConstant(const JS{JS.name: "library1"}) +Evaluated: ConstructorInvocation @ org-dartlang-testcase:///isa.dart:16:2 -> InstanceConstant(const JS{JS.name: "RenamedJSArray"}) +Extra constant evaluation: evaluated: 10, effectively constant: 2 diff --git a/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.weak.transformed.expect b/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.weak.transformed.expect new file mode 100644 index 00000000000..e0f22479b88 --- /dev/null +++ b/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.weak.transformed.expect @@ -0,0 +1,88 @@ +@#C2 +library; +import self as self; +import "dart:js_interop" as js_; +import "dart:js_interop_unsafe" as js_2; + +import "dart:js_interop"; + +extension type CustomJSAny(js_::JSAny /* = dart.core::Object */ _) implements js_::JSAny /* = dart.core::Object */ { + abstract extension-type-member representation-field get _() → js_::JSAny /* = dart.core::Object */; + constructor • = self::CustomJSAny|constructor#; + constructor tearoff • = self::CustomJSAny|constructor#_#new#tearOff; +} +extension type CustomJSObject(js_::JSObject /* = dart._interceptors::JSObject */ _) implements js_::JSObject /* = dart._interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSObject /* = dart._interceptors::JSObject */; + constructor • = self::CustomJSObject|constructor#; + constructor tearoff • = self::CustomJSObject|constructor#_#new#tearOff; +} +extension type CustomTypedArray(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) implements js_::JSObject /* = dart._interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */; + constructor • = self::CustomTypedArray|constructor#; + constructor tearoff • = self::CustomTypedArray|constructor#_#new#tearOff; +} +@#C4 +extension type CustomJSArray(js_::JSArray /* = dart._interceptors::JSArray */ _) implements js_::JSObject /* = dart._interceptors::JSObject */ { + abstract extension-type-member representation-field get _() → js_::JSArray /* = dart._interceptors::JSArray */; + constructor • = self::CustomJSArray|constructor#; + constructor tearoff • = self::CustomJSArray|constructor#_#new#tearOff; +} +static extension-type-member method CustomJSAny|constructor#(js_::JSAny /* = dart.core::Object */ _) → self::CustomJSAny /* = dart.core::Object */ { + lowered final self::CustomJSAny /* = dart.core::Object */ #this = _; + return #this; +} +static extension-type-member method CustomJSAny|constructor#_#new#tearOff(js_::JSAny /* = dart.core::Object */ _) → self::CustomJSAny /* = dart.core::Object */ + return self::CustomJSAny|constructor#(_); +static extension-type-member method CustomJSObject|constructor#(js_::JSObject /* = dart._interceptors::JSObject */ _) → self::CustomJSObject /* = dart._interceptors::JSObject */ { + lowered final self::CustomJSObject /* = dart._interceptors::JSObject */ #this = _; + return #this; +} +static extension-type-member method CustomJSObject|constructor#_#new#tearOff(js_::JSObject /* = dart._interceptors::JSObject */ _) → self::CustomJSObject /* = dart._interceptors::JSObject */ + return self::CustomJSObject|constructor#(_); +static extension-type-member method CustomTypedArray|constructor#(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) → self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ { + lowered final self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ #this = _; + return #this; +} +static extension-type-member method CustomTypedArray|constructor#_#new#tearOff(js_::JSTypedArray /* = dart.typed_data.implementation::NativeTypedData */ _) → self::CustomTypedArray /* = dart.typed_data.implementation::NativeTypedData */ + return self::CustomTypedArray|constructor#(_); +static extension-type-member method CustomJSArray|constructor#(js_::JSArray /* = dart._interceptors::JSArray */ _) → self::CustomJSArray /* = dart._interceptors::JSArray */ { + lowered final self::CustomJSArray /* = dart._interceptors::JSArray */ #this = _; + return #this; +} +static extension-type-member method CustomJSArray|constructor#_#new#tearOff(js_::JSArray /* = dart._interceptors::JSArray */ _) → self::CustomJSArray /* = dart._interceptors::JSArray */ + return self::CustomJSArray|constructor#(_); +static method test(js_::JSAny /* = dart.core::Object */ any) → void { + !(any == null); + true; + !(any == null) && js_::JSAnyUtilityExtension|typeofEquals(any, "string"); + any == null || js_::JSAnyUtilityExtension|typeofEquals(any, "string"); + !(any == null) && js_::JSAnyUtilityExtension|typeofEquals(any, "number"); + !(any == null) && js_::JSAnyUtilityExtension|typeofEquals(any, "boolean"); + !(any == null) && js_::JSAnyUtilityExtension|typeofEquals(any, "symbol"); + !(any == null) && js_::JSAnyUtilityExtension|typeofEquals(any, "bigint"); + !(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "Object"); + any == null || js_::JSAnyUtilityExtension|instanceOfString(any, "Object"); + !(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "Array"); + !(any == null) && js_::JSAnyUtilityExtension|instanceof(any, js_2::JSObjectUnsafeUtilExtension|callMethodVarArgs(js_2::JSObjectUnsafeUtilExtension|[](js_::globalContext, "Object") as{ForLegacy} js_::JSObject /* = dart._interceptors::JSObject */, js_::StringToJSString|get#toJS("getPrototypeOf"), [js_2::JSObjectUnsafeUtilExtension|[](js_::globalContext, "Int8Array") as{ForLegacy} js_::JSObject /* = dart._interceptors::JSObject */])); + !(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "library1.CustomJSAny"); + !(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "library1.CustomJSObject"); + !(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "library1.CustomTypedArray"); + !(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "library1.RenamedJSArray"); + let final js_::JSAny? /* = dart.core::Object? */ #t1 = (() → js_::JSAny /* = dart.core::Object */ { + return any; + })(){() → js_::JSAny /* = dart.core::Object */} in !(#t1 == null); +} +static method main() → void {} + +constants { + #C1 = "library1" + #C2 = js_::JS {name:#C1} + #C3 = "RenamedJSArray" + #C4 = js_::JS {name:#C3} +} + + +Constructor coverage from constants: +org-dartlang-testcase:///isa.dart: +- JS. (from org-dartlang-sdk:///lib/js_interop/js_interop.dart) +- Object. (from org-dartlang-sdk:///lib/core/object.dart) diff --git a/sdk/lib/_internal/js_shared/lib/js_interop_patch.dart b/sdk/lib/_internal/js_shared/lib/js_interop_patch.dart index d33d6ee4d17..9a5a6eac56f 100644 --- a/sdk/lib/_internal/js_shared/lib/js_interop_patch.dart +++ b/sdk/lib/_internal/js_shared/lib/js_interop_patch.dart @@ -43,6 +43,11 @@ extension JSAnyUtilityExtension on JSAny? { bool instanceof(JSFunction constructor) => foreign_helper.JS('bool', '# instanceof #', this, constructor); + @patch + bool isA() => throw UnimplementedError( + "This should never be called. Calls to 'isA' should have been " + 'transformed by the interop transformer.'); + @patch @pragma('dart2js:prefer-inline') Object? dartify() => js_util.dartify(this); diff --git a/sdk/lib/_internal/wasm/lib/js_interop_patch.dart b/sdk/lib/_internal/wasm/lib/js_interop_patch.dart index 8f9c2926d8a..abaa09d6d71 100644 --- a/sdk/lib/_internal/wasm/lib/js_interop_patch.dart +++ b/sdk/lib/_internal/wasm/lib/js_interop_patch.dart @@ -61,6 +61,11 @@ extension JSAnyUtilityExtension on JSAny? { '(o, c) => o instanceof c', toExternRef, constructor.toExternRef) .toBool(); + @patch + bool isA() => throw UnimplementedError( + "This should never be called. Calls to 'isA' should have been " + 'transformed by the interop transformer.'); + @patch Object? dartify() => js_util.dartify(this); } @@ -90,8 +95,8 @@ extension JSExportedDartFunctionToFunction on JSExportedDartFunction { extension FunctionToJSExportedDartFunction on Function { @patch JSExportedDartFunction get toJS => throw UnimplementedError( - 'This should never be called. Calls to toJS should have been transformed ' - 'by the interop transformer.'); + "This should never be called. Calls to 'toJS' should have been " + 'transformed by the interop transformer.'); } /// Embedded global property for wrapped Dart objects passed via JS interop. diff --git a/sdk/lib/js_interop/js_interop.dart b/sdk/lib/js_interop/js_interop.dart index 280c3b3e624..785fd1a6e6e 100644 --- a/sdk/lib/js_interop/js_interop.dart +++ b/sdk/lib/js_interop/js_interop.dart @@ -19,6 +19,7 @@ /// {@category Web} library dart.js_interop; +import "dart:_internal" show Since; import 'dart:_js_types'; import 'dart:js_interop_unsafe'; import 'dart:typed_data'; @@ -73,6 +74,7 @@ extension type JSAny._(JSAnyRepType _jsAny) implements Object {} /// This is the supertype of all JS objects, but not other JS types, like /// primitives. See https://dart.dev/web/js-interop for more details on how to /// use JS interop. +@JS('Object') extension type JSObject._(JSObjectRepType _jsObject) implements JSAny { /// Constructor to go from an object from previous interop, like the types /// from `package:js` or `dart:html`, to [JSObject]. @@ -91,12 +93,14 @@ extension type JSObject._(JSObjectRepType _jsObject) implements JSAny { external JSObjectRepType _createObjectLiteral(); /// The type of all JS functions. +@JS('Function') extension type JSFunction._(JSFunctionRepType _jsFunction) implements JSObject {} /// The type of all Dart functions adapted to be callable from JS. We only allow /// a subset of Dart functions to be callable from JS. // TODO(joshualitt): Detail exactly what are the requirements. +@JS('Function') extension type JSExportedDartFunction._( JSExportedDartFunctionRepType _jsExportedDartFunction) implements JSFunction {} @@ -143,14 +147,17 @@ extension type JSPromise._(JSPromiseRepType _jsPromise) /// The type of the boxed Dart object that can be passed to JS safely. There is /// no interface specified of this boxed object, and you may get a new box each /// time you box the same Dart object. +@JS('Object') extension type JSBoxedDartObject._(JSBoxedDartObjectRepType _jsBoxedDartObject) implements JSObject {} /// The type of JS' `ArrayBuffer`. +@JS('ArrayBuffer') extension type JSArrayBuffer._(JSArrayBufferRepType _jsArrayBuffer) implements JSObject {} /// The type of JS' `DataView`. +@JS('DataView') extension type JSDataView._(JSDataViewRepType _jsDataView) implements JSObject {} @@ -159,38 +166,47 @@ extension type JSTypedArray._(JSTypedArrayRepType _jsTypedArray) implements JSObject {} /// The type of JS' `Int8Array`. +@JS('Int8Array') extension type JSInt8Array._(JSInt8ArrayRepType _jsInt8Array) implements JSTypedArray {} /// The type of JS' `Uint8Array`. +@JS('Uint8Array') extension type JSUint8Array._(JSUint8ArrayRepType _jsUint8Array) implements JSTypedArray {} /// The type of JS' `Uint8ClampedArray`. +@JS('Uint8ClampedArray') extension type JSUint8ClampedArray._( JSUint8ClampedArrayRepType _jsUint8ClampedArray) implements JSTypedArray {} /// The type of JS' `Int16Array`. +@JS('Int16Array') extension type JSInt16Array._(JSInt16ArrayRepType _jsInt16Array) implements JSTypedArray {} /// The type of JS' `Uint16Array`. +@JS('Uint16Array') extension type JSUint16Array._(JSUint16ArrayRepType _jsUint16Array) implements JSTypedArray {} /// The type of JS' `Int32Array`. +@JS('Int32Array') extension type JSInt32Array._(JSInt32ArrayRepType _jsInt32Array) implements JSTypedArray {} /// The type of JS' `Uint32Array`. +@JS('Uint32Array') extension type JSUint32Array._(JSUint32ArrayRepType _jsUint32Array) implements JSTypedArray {} /// The type of JS' `Float32Array`. +@JS('Float32Array') extension type JSFloat32Array._(JSFloat32ArrayRepType _jsFloat32Array) implements JSTypedArray {} /// The type of JS' `Float64Array`. +@JS('Float64Array') extension type JSFloat64Array._(JSFloat64ArrayRepType _jsFloat64Array) implements JSTypedArray {} @@ -258,13 +274,62 @@ extension JSAnyUtilityExtension on JSAny? { /// Returns whether this [JSAny]? is an `instanceof` [constructor]. external bool instanceof(JSFunction constructor); - /// Like [instanceof], but only takes a [String] for the constructor name, - /// which is then looked up in the [globalContext]. + /// Returns whether this [JSAny]? is an `instanceof` the constructor that is + /// defined by [constructorName], which is looked up in the [globalContext]. + /// + /// If [constructorName] contains '.'s, we split the name into several parts + /// in order to get the constructor. For example, `library1.JSClass` would + /// involve fetching `library1` off of the [globalContext], and then fetching + /// `JSClass` off of `library1` to get the constructor. + /// + /// If [constructorName] is empty or any of the parts or the constructor don't + /// exist, returns false. bool instanceOfString(String constructorName) { - final constructor = globalContext[constructorName] as JSFunction?; - return constructor != null && instanceof(constructor); + if (constructorName.isEmpty) return false; + final parts = constructorName.split('.'); + JSObject? constructor = globalContext; + for (final part in parts) { + constructor = constructor?[part] as JSObject?; + if (constructor == null) return false; + } + return instanceof(constructor as JSFunction); } + /// Returns whether this [JSAny]? is the actual JS type that is declared by + /// [T]. + /// + /// This method uses a combination of null, `typeof`, and `instanceof` checks + /// in order to do this check. Use this instead of `is` checks. + /// + /// If [T] is a primitive JS type e.g. [JSString], this uses a `typeof` check + /// that corresponds to that primitive type e.g. `typeofEquals('string')`. + /// + /// If [T] is a non-primitive JS type e.g. [JSArray] or an interop extension + /// type on one, this uses an `instanceof` check using the name or @[JS] + /// rename of the given type e.g. `instanceOfString('Array')`. Note that if + /// you rename the library using the @[JS] annotation, this uses the rename in + /// the `instanceof` check e.g. `instanceOfString('library1.JSClass')`. + /// + /// In order to determine the right value for the `instanceof` check, this + /// uses the name or the rename of the extension type. So, if you had an + /// interop extension type `JSClass` that wraps `JSArray`, this does an + /// `instanceOfString('JSClass')` check and not an `instanceOfString('Array')` + /// check. + /// + /// There are two expeptions to this rule. The first exception is + /// `JSTypedArray`. As `TypedArray` does not exist as a property in JS, this + /// does some prototype checking to make `isA` do the right + /// thing. The other exception is `JSAny`. If you do a `isA` check, it + /// will only do a null-check. + /// + /// Using this method with a [T] that has an object literal constructor will + /// result in an error as you likely want to use [JSObject] instead. + /// + /// Using this method with a [T] that wraps a primitive JS type will result in + /// an error telling you to use the primitive JS type instead. + @Since('3.4') + external bool isA(); + /// Effectively the inverse of [jsify], [dartify] Takes a JavaScript object, /// and converts it to a Dart based object. Only JS primitives, arrays, or /// 'map' like JS objects are supported. diff --git a/tests/lib/js/static_interop_test/isa/functional_test.dart b/tests/lib/js/static_interop_test/isa/functional_test.dart new file mode 100644 index 00000000000..1efdc44132b --- /dev/null +++ b/tests/lib/js/static_interop_test/isa/functional_test.dart @@ -0,0 +1,274 @@ +// Copyright (c) 2024, 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. + +// Test `dart:js_interop`'s `isA` method. + +import 'dart:js_interop'; +import 'dart:typed_data'; + +import 'package:expect/expect.dart'; + +@JS() +external void eval(String code); + +extension type ObjectLiteral._(JSObject _) implements JSObject { + external ObjectLiteral({int a}); + + @JS('toString') + external JSFunction get toStr; +} + +extension type CustomJSAny(JSAny _) implements JSAny {} + +extension type Date._(JSObject _) implements JSObject { + external Date(); +} + +extension type CustomTypedArray(JSTypedArray _) implements JSObject {} + +@JS('SubtypeArray') +extension type ArraySubtype._(JSArray _) implements JSObject { + external ArraySubtype(); +} + +@JS() +external JSBigInt BigInt(String val); + +@JS() +external JSSymbol Symbol(String val); + +void testIsJSTypedArray(JSTypedArray any) { + Expect.isTrue(any.isA()); + Expect.isTrue(any.isA()); + testIsJSObject(any); +} + +void testIsJSObject(JSObject any) { + Expect.isTrue(any.isA()); + Expect.isTrue(any.isA()); + testIsJSAny(any); +} + +void testIsJSAny(JSAny any) { + Expect.isTrue(any.isA()); + Expect.isTrue(any.isA()); +} + +void main() { + // Null. + JSAny? nil = null; + Expect.isTrue(nil.isA()); + Expect.isTrue(nil.isA()); + Expect.isTrue(nil.isA()); + Expect.isFalse(nil.isA()); + Expect.isFalse(nil.isA()); + Expect.isFalse(nil.isA()); + + // Test inheritance chain, nullability, and one false case. + + // Primitives. + final jsString = ''.toJS; + Expect.isTrue(jsString.isA()); + Expect.isTrue(jsString.isA()); + testIsJSAny(jsString); + Expect.isFalse(jsString.isA()); + + final jsNum = 0.toJS; + Expect.isTrue(jsNum.isA()); + Expect.isTrue(jsNum.isA()); + testIsJSAny(jsNum); + Expect.isFalse(jsNum.isA()); + + final jsBool = true.toJS; + Expect.isTrue(jsBool.isA()); + Expect.isTrue(jsBool.isA()); + testIsJSAny(jsBool); + Expect.isFalse(jsBool.isA()); + + final jsBigInt = BigInt('0'); + Expect.isTrue(jsBigInt.isA()); + Expect.isTrue(jsBigInt.isA()); + testIsJSAny(jsBigInt); + Expect.isFalse(jsBigInt.isA()); + + final jsSymbol = Symbol('symbol'); + Expect.isTrue(jsSymbol.isA()); + Expect.isTrue(jsSymbol.isA()); + testIsJSAny(jsSymbol); + Expect.isFalse(jsSymbol.isA()); + + // Objects. + final jsObject = ObjectLiteral(a: 0); + testIsJSObject(jsObject); + Expect.isFalse(jsObject.isA()); + // Note that this is true even though it's a supertype - we can't + // differentiate between the two types. This is okay because users will get a + // consistent error when trying to internalize it. + Expect.isTrue(jsObject.isA()); + + final jsBox = ''.toJSBox; + Expect.isTrue(jsBox.isA()); + Expect.isTrue(jsBox.isA()); + testIsJSObject(jsBox); + Expect.isFalse(jsBox.isA()); + + final jsArray = [].toJS; + Expect.isTrue(jsArray.isA()); + Expect.isTrue(jsArray.isA()); + // Can't differentiate generics. + Expect.isTrue(jsArray.isA>()); + testIsJSObject(jsArray); + Expect.isFalse(jsArray.isA()); + + final jsPromise = Future.delayed(Duration.zero).toJS; + Expect.isTrue(jsPromise.isA()); + Expect.isTrue(jsPromise.isA()); + // Can't differentiate generics. + Expect.isTrue(jsPromise.isA>()); + testIsJSObject(jsPromise); + Expect.isFalse(jsPromise.isA()); + + final jsFunction = jsObject.toStr; + Expect.isTrue(jsFunction.isA()); + Expect.isTrue(jsFunction.isA()); + testIsJSObject(jsFunction); + Expect.isFalse(jsFunction.isA()); + // Note that this is true even though it's a supertype - we can't + // differentiate between the two types. This is okay because users will get a + // consistent error when trying to internalize it. + Expect.isTrue(jsFunction.isA()); + + final jsExportedDartFunction = () {}.toJS; + Expect.isTrue(jsExportedDartFunction.isA()); + Expect.isTrue(jsExportedDartFunction.isA()); + Expect.isTrue(jsExportedDartFunction.isA()); + testIsJSObject(jsExportedDartFunction); + Expect.isFalse(jsExportedDartFunction.isA()); + + // Typed data. + final jsArrayBuffer = Uint8List(1).buffer.toJS; + Expect.isTrue(jsArrayBuffer.isA()); + Expect.isTrue(jsArrayBuffer.isA()); + Expect.isFalse(jsArrayBuffer.isA()); + testIsJSObject(jsArrayBuffer); + Expect.isFalse(jsArrayBuffer.isA()); + + final jsDataView = ByteData(1).toJS; + Expect.isTrue(jsDataView.isA()); + Expect.isTrue(jsDataView.isA()); + Expect.isFalse(jsDataView.isA()); + testIsJSObject(jsDataView); + Expect.isFalse(jsDataView.isA()); + + final jsUint8Array = Uint8List(1).toJS; + Expect.isTrue(jsUint8Array.isA()); + Expect.isTrue(jsUint8Array.isA()); + testIsJSTypedArray(jsUint8Array); + Expect.isFalse(jsUint8Array.isA()); + + final jsUint16Array = Uint16List(1).toJS; + Expect.isTrue(jsUint16Array.isA()); + Expect.isTrue(jsUint16Array.isA()); + testIsJSTypedArray(jsUint16Array); + Expect.isFalse(jsUint16Array.isA()); + + final jsUint32Array = Uint32List(1).toJS; + Expect.isTrue(jsUint32Array.isA()); + Expect.isTrue(jsUint32Array.isA()); + testIsJSTypedArray(jsUint32Array); + Expect.isFalse(jsUint32Array.isA()); + + final jsUint8ClampedArray = Uint8ClampedList(1).toJS; + Expect.isTrue(jsUint8ClampedArray.isA()); + Expect.isTrue(jsUint8ClampedArray.isA()); + testIsJSTypedArray(jsUint8ClampedArray); + Expect.isFalse(jsUint8ClampedArray.isA()); + + final jsInt8Array = Int8List(1).toJS; + Expect.isTrue(jsInt8Array.isA()); + Expect.isTrue(jsInt8Array.isA()); + testIsJSTypedArray(jsInt8Array); + Expect.isFalse(jsInt8Array.isA()); + + final jsInt16Array = Int16List(1).toJS; + Expect.isTrue(jsInt16Array.isA()); + Expect.isTrue(jsInt16Array.isA()); + testIsJSTypedArray(jsInt16Array); + Expect.isFalse(jsInt16Array.isA()); + + final jsInt32Array = Int32List(1).toJS; + Expect.isTrue(jsInt32Array.isA()); + Expect.isTrue(jsInt32Array.isA()); + testIsJSTypedArray(jsInt32Array); + Expect.isFalse(jsInt32Array.isA()); + + final jsFloat32Array = Float32List(1).toJS; + Expect.isTrue(jsFloat32Array.isA()); + Expect.isTrue(jsFloat32Array.isA()); + testIsJSTypedArray(jsFloat32Array); + Expect.isFalse(jsFloat32Array.isA()); + + final jsFloat64Array = Float64List(1).toJS; + Expect.isTrue(jsFloat64Array.isA()); + Expect.isTrue(jsFloat64Array.isA()); + testIsJSTypedArray(jsFloat64Array); + Expect.isFalse(jsFloat64Array.isA()); + + // User types. If they are a subtype of `JSObject`, we will use the + // declaration name with any renaming. If not, we will use the core type. + eval(''' + class SubtypeArray extends Array {} + globalThis.SubtypeArray = SubtypeArray; + '''); + + final customJsAny = CustomJSAny(jsArray); + Expect.isFalse(customJsAny.isA()); + Expect.isFalse(customJsAny.isA()); + Expect.isFalse(jsArray.isA()); + Expect.isFalse(jsArray.isA()); + Expect.isTrue(nil.isA()); + Expect.isFalse(nil.isA()); + Expect.isTrue(customJsAny.isA()); + Expect.isTrue(customJsAny.isA()); + + final date = Date(); + Expect.isTrue(date.isA()); + Expect.isTrue(date.isA()); + Expect.isFalse(jsObject.isA()); + Expect.isFalse(jsObject.isA()); + Expect.isTrue(nil.isA()); + Expect.isFalse(nil.isA()); + Expect.isTrue(date.isA()); + Expect.isTrue(date.isA()); + + final customTypedArray = CustomTypedArray(jsUint8Array); + Expect.isFalse(customTypedArray.isA()); + Expect.isFalse(customTypedArray.isA()); + Expect.isFalse(jsUint8Array.isA()); + Expect.isFalse(jsUint8Array.isA()); + Expect.isTrue(nil.isA()); + Expect.isFalse(nil.isA()); + Expect.isTrue(customTypedArray.isA()); + Expect.isTrue(customTypedArray.isA()); + + final arraySubtype = ArraySubtype(); + Expect.isTrue(arraySubtype.isA()); + Expect.isTrue(arraySubtype.isA()); + Expect.isFalse(jsArray.isA()); + Expect.isFalse(jsArray.isA()); + Expect.isTrue(nil.isA()); + Expect.isFalse(nil.isA()); + Expect.isTrue(arraySubtype.isA()); + Expect.isTrue(arraySubtype.isA()); + + // Make sure we recurse and don't reevaluate the receiver. + var count = 0; + Expect.isTrue((() { + Expect.isTrue(jsObject.isA()); + count++; + return jsObject; + })() + .isA()); + Expect.equals(count, 1); +} diff --git a/tests/lib/js/static_interop_test/isa/library_renaming_test.dart b/tests/lib/js/static_interop_test/isa/library_renaming_test.dart new file mode 100644 index 00000000000..c0ba9fac363 --- /dev/null +++ b/tests/lib/js/static_interop_test/isa/library_renaming_test.dart @@ -0,0 +1,54 @@ +// Copyright (c) 2024, 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. + +// Test `dart:js_interop`'s `isA` method with library renames. + +@JS('library1.library2') +library; + +import 'dart:js_interop'; + +import 'package:expect/expect.dart'; + +import 'functional_test.dart' as functional_test; + +extension type Date._(JSObject _) implements JSObject { + external Date(); +} + +@JS('Date') +extension type RenamedDate._(JSObject _) implements JSObject { + external RenamedDate(); +} + +void main() { + functional_test.eval(''' + globalThis.library1 = {}; + globalThis.library1.library2 = {}; + globalThis.library1.library2.Date = function Date() {} + '''); + + final date = Date(); + final unscopedDate = functional_test.Date(); + Expect.isTrue(date.isA()); + Expect.isTrue(date.isA()); + Expect.isFalse(unscopedDate.isA()); + Expect.isFalse(unscopedDate.isA()); + Expect.isFalse(date.isA()); + Expect.isFalse(date.isA()); + Expect.isTrue(date.isA()); + Expect.isTrue(date.isA()); + + final renamedDate = RenamedDate(); + Expect.isTrue(renamedDate.isA()); + Expect.isTrue(renamedDate.isA()); + Expect.isTrue(date.isA()); + Expect.isTrue(date.isA()); + Expect.isTrue(renamedDate.isA()); + Expect.isTrue(renamedDate.isA()); + Expect.isFalse(unscopedDate.isA()); + Expect.isFalse(unscopedDate.isA()); + Expect.isFalse(renamedDate.isA()); + Expect.isFalse(renamedDate.isA()); +} diff --git a/tests/lib/js/static_interop_test/isa/static_test.dart b/tests/lib/js/static_interop_test/isa/static_test.dart new file mode 100644 index 00000000000..1836a25a07d --- /dev/null +++ b/tests/lib/js/static_interop_test/isa/static_test.dart @@ -0,0 +1,59 @@ +// Copyright (c) 2024, 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. + +// Test `dart:js_interop`'s `isA` method returns the right errors. + +import 'dart:js_interop'; + +extension type NonLiteral._(JSObject o) implements JSObject {} + +extension type ObjectLiteral._(JSObject o) implements JSObject { + external ObjectLiteral({int a}); +} + +extension type BothConstructors._(JSObject o) implements JSObject { + external BothConstructors({int a}); + external BothConstructors.fact(int a); +} + +extension type WrapObjectLiteral._(ObjectLiteral o) implements ObjectLiteral {} + +extension type CustomJSString(JSString _) implements JSString {} + +void test(JSAny? any) { + any.isA(); + // ^ + // [web] Type argument 'T' needs to be an interop 'ExtensionType'. + any.isA(); + // ^ + // [web] Type argument 'U' needs to be an interop 'ExtensionType'. + + final tearoff = 0.toJS.isA; + // ^ + // [web] 'isA' can't be torn off. + tearoff(); + final tearoffWithTypeParam = 0.toJS.isA; + // ^ + // [web] 'isA' can't be torn off. + tearoffWithTypeParam(); + final tearoffWithGenericTypeParam = 0.toJS.isA; + // ^ + // [web] 'isA' can't be torn off. + tearoffWithGenericTypeParam(); + + any.isA(); + any.isA(); + // ^ + // [web] Type argument 'ObjectLiteral' has an object literal constructor. Because 'isA' uses the type's name or '@JS()' rename, this may result in an incorrect type check. + any.isA(); + // ^ + // [web] Type argument 'BothConstructors' has an object literal constructor. Because 'isA' uses the type's name or '@JS()' rename, this may result in an incorrect type check. + any.isA(); + + CustomJSString(''.toJS).isA(); + // ^ + // [web] Type argument 'CustomJSString' wraps primitive JS type 'JSString', which is specially handled using 'typeof'. +} + +void main() {} diff --git a/tests/lib/lib.status b/tests/lib/lib.status index b0d28858053..5d7532c7111 100644 --- a/tests/lib/lib.status +++ b/tests/lib/lib.status @@ -85,6 +85,8 @@ js/static_interop_test/external_extension_members_test: SkipByDesign # Issue 420 js/static_interop_test/external_static_member_lowerings_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code js/static_interop_test/external_static_member_lowerings_trusttypes_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code js/static_interop_test/external_static_member_lowerings_with_namespaces_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code +js/static_interop_test/isa/functional_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code +js/static_interop_test/isa/library_renaming_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code js/static_interop_test/js_array_proxy_or_ref_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code js/static_interop_test/js_array_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code js/static_interop_test/js_function_conversions_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code