| // Copyright (c) 2019, 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. | |
| import 'package:args/args.dart'; | |
| import 'package:compiler/src/compiler.dart'; | |
| import 'package:compiler/src/elements/entities.dart'; | |
| import 'package:compiler/src/ir/annotations.dart'; | |
| import 'package:compiler/src/js_backend/native_data.dart'; | |
| import 'package:compiler/src/kernel/kernel_strategy.dart'; | |
| import 'package:compiler/src/kernel/element_map.dart'; | |
| import 'package:expect/async_helper.dart'; | |
| import 'package:expect/expect.dart'; | |
| import 'package:front_end/src/api_prototype/lowering_predicates.dart'; | |
| import 'package:kernel/ast.dart' as ir; | |
| import '../helpers/args_helper.dart'; | |
| import 'package:compiler/src/util/memory_compiler.dart'; | |
| const String pathPrefix = 'sdk/tests/web/native/'; | |
| const Map<String, String> source = { | |
| '$pathPrefix/main.dart': ''' | |
| library lib; | |
| import 'package:meta/dart2js.dart'; | |
| import 'jslib1.dart'; | |
| import 'jslib2.dart'; | |
| import 'nativelib.dart'; | |
| @pragma('dart2js:noInline') | |
| method1() {} | |
| @noInline | |
| method2() {} | |
| @pragma(const String.fromEnvironment('foo', defaultValue: 'dart2js:tryInline')) | |
| method3() {} | |
| @tryInline | |
| method4() {} | |
| main() { | |
| method1(); | |
| method2(); | |
| method3(); | |
| method4(); | |
| JsClass1()..jsMethod1()..jsMethod2(); | |
| JsClass2(); | |
| jsMethod3(); | |
| NativeClass1()..nativeMethod()..nativeField; | |
| NativeClass2()..nativeField; | |
| NativeClass3()..nativeMethod()..nativeGetter; | |
| nativeMethod(); | |
| } | |
| ''', | |
| '$pathPrefix/jslib1.dart': ''' | |
| @JS('lib1') | |
| library lib1; | |
| import 'package:js/js.dart'; | |
| @JS('JsInteropClass1') | |
| class JsClass1 { | |
| @JS('jsInteropMethod1') | |
| external jsMethod1(); | |
| external jsMethod2(); | |
| } | |
| ''', | |
| '$pathPrefix/jslib2.dart': ''' | |
| @JS() | |
| library lib2; | |
| import 'package:js/js.dart'; | |
| @JS() | |
| @anonymous | |
| class JsClass2 { | |
| } | |
| @JS('jsInteropMethod3') | |
| external jsMethod3(); | |
| external jsMethod4(); | |
| ''', | |
| '$pathPrefix/nativelib.dart': ''' | |
| library lib3; | |
| import 'dart:_js_helper'; | |
| @Native('Class1') | |
| class NativeClass1 { | |
| @JSName('field1') | |
| var nativeField; | |
| @JSName('method1') | |
| nativeMethod() native; | |
| } | |
| @Native('Class2,!nonleaf') | |
| class NativeClass2 { | |
| @JSName('field2') | |
| var nativeField; | |
| } | |
| @Native('Class3a,Class3b') | |
| class NativeClass3 { | |
| @JSName('method2') | |
| get nativeGetter native; | |
| @Creates('String') | |
| @Returns('int') | |
| nativeMethod() native; | |
| } | |
| @JSName('method3') | |
| nativeMethod() native; | |
| ''', | |
| }; | |
| const Map<String, String> expectedNativeClassNames = { | |
| '$pathPrefix/nativelib.dart::NativeClass1': 'Class1', | |
| '$pathPrefix/nativelib.dart::NativeClass2': 'Class2,!nonleaf', | |
| '$pathPrefix/nativelib.dart::NativeClass3': 'Class3a,Class3b', | |
| }; | |
| const Map<String, String> expectedNativeMemberNames = { | |
| '$pathPrefix/nativelib.dart::NativeClass1::nativeField': 'field1', | |
| '$pathPrefix/nativelib.dart::NativeClass1::nativeMethod': 'method1', | |
| '$pathPrefix/nativelib.dart::NativeClass2::nativeField': 'field2', | |
| '$pathPrefix/nativelib.dart::NativeClass3::nativeGetter': 'method2', | |
| '$pathPrefix/nativelib.dart::nativeMethod': 'method3', | |
| }; | |
| const Map<String, String> expectedCreates = { | |
| '$pathPrefix/nativelib.dart::NativeClass3::nativeMethod': 'String', | |
| }; | |
| const Map<String, String> expectedReturns = { | |
| '$pathPrefix/nativelib.dart::NativeClass3::nativeMethod': 'int', | |
| }; | |
| const Map<String, String> expectedJsInteropLibraryNames = { | |
| '$pathPrefix/jslib1.dart': 'lib1', | |
| '$pathPrefix/jslib2.dart': '', | |
| }; | |
| const Map<String, String> expectedJsInteropClassNames = { | |
| '$pathPrefix/jslib1.dart::JsClass1': 'JsInteropClass1', | |
| '$pathPrefix/jslib2.dart::JsClass2': '', | |
| }; | |
| const Map<String, String> expectedJsInteropMemberNames = { | |
| '$pathPrefix/jslib1.dart::JsClass1::jsMethod1': 'jsInteropMethod1', | |
| '$pathPrefix/jslib2.dart::jsMethod3': 'jsInteropMethod3', | |
| }; | |
| const Set<String> expectedAnonymousJsInteropClasses = { | |
| '$pathPrefix/jslib2.dart::JsClass2', | |
| }; | |
| const Set<String> expectedNoInlineMethods = { | |
| '$pathPrefix/main.dart::method1', | |
| '$pathPrefix/main.dart::method2', | |
| }; | |
| const Set<String> expectedTryInlineMethods = { | |
| '$pathPrefix/main.dart::method3', | |
| '$pathPrefix/main.dart::method4', | |
| }; | |
| main(List<String> args) { | |
| ArgParser argParser = createArgParser(); | |
| asyncTest(() async { | |
| ArgResults argResults = argParser.parse(args); | |
| Uri? librariesSpecificationUri = getLibrariesSpec(argResults); | |
| Uri? packageConfig = getPackages(argResults); | |
| List<String> options = getOptions(argResults); | |
| runTest({required bool useIr}) async { | |
| CompilationResult result = await runCompiler( | |
| entryPoint: Uri.parse('memory:$pathPrefix/main.dart'), | |
| memorySourceFiles: source, | |
| packageConfig: packageConfig, | |
| librariesSpecificationUri: librariesSpecificationUri, | |
| options: options, | |
| ); | |
| Expect.isTrue(result.isSuccess); | |
| Compiler compiler = result.compiler!; | |
| KernelFrontendStrategy frontendStrategy = compiler.frontendStrategy; | |
| KernelToElementMap elementMap = frontendStrategy.elementMap; | |
| ir.Component component = elementMap.env.mainComponent; | |
| IrAnnotationData annotationData = | |
| frontendStrategy.irAnnotationDataForTesting; | |
| void testAll(NativeData nativeData) { | |
| void testMember( | |
| String idPrefix, | |
| ir.Member member, { | |
| required bool implicitJsInteropMember, | |
| required bool implicitNativeMember, | |
| }) { | |
| if (memberIsIgnorable(member)) return; | |
| String memberId = '$idPrefix::${member.name.text}'; | |
| MemberEntity memberEntity = elementMap.getMember(member); | |
| String? expectedJsInteropMemberName = | |
| expectedJsInteropMemberNames[memberId]; | |
| String? expectedNativeMemberName = | |
| expectedNativeMemberNames[memberId]; | |
| Set<String> expectedPragmaNames = {}; | |
| if (expectedNoInlineMethods.contains(memberId)) { | |
| expectedPragmaNames.add('dart2js:noInline'); | |
| } | |
| if (expectedTryInlineMethods.contains(memberId)) { | |
| expectedPragmaNames.add('dart2js:tryInline'); | |
| } | |
| String? expectedCreatesText = expectedCreates[memberId]; | |
| String? expectedReturnsText = expectedReturns[memberId]; | |
| if (useIr) { | |
| Expect.equals( | |
| expectedJsInteropMemberName, | |
| annotationData.getJsInteropMemberName(member), | |
| "Unexpected js interop member name from IR for $member, " | |
| "id: $memberId", | |
| ); | |
| Expect.equals( | |
| expectedNativeMemberName, | |
| annotationData.getNativeMemberName(member), | |
| "Unexpected js interop member name from IR for $member, " | |
| "id: $memberId", | |
| ); | |
| List<PragmaAnnotationData> pragmaAnnotations = annotationData | |
| .getMemberPragmaAnnotationData(member); | |
| Set<String> pragmaNames = pragmaAnnotations | |
| .map((d) => d.name) | |
| .toSet(); | |
| Expect.setEquals( | |
| expectedPragmaNames, | |
| pragmaNames, | |
| "Unexpected pragmas from IR for $member, " | |
| "id: $memberId", | |
| ); | |
| List<String> createsAnnotations = annotationData | |
| .getCreatesAnnotations(member); | |
| Expect.equals( | |
| expectedCreatesText, | |
| createsAnnotations.isEmpty ? null : createsAnnotations.join(','), | |
| "Unexpected create annotations from IR for $member, " | |
| "id: $memberId", | |
| ); | |
| List<String> returnsAnnotations = annotationData | |
| .getReturnsAnnotations(member); | |
| Expect.equals( | |
| expectedReturnsText, | |
| returnsAnnotations.isEmpty ? null : returnsAnnotations.join(','), | |
| "Unexpected returns annotations from IR for $member, " | |
| "id: $memberId", | |
| ); | |
| } | |
| bool isJsInteropMember = | |
| (implicitJsInteropMember && member.isExternal) || | |
| expectedJsInteropMemberName != null; | |
| Expect.equals( | |
| isJsInteropMember, | |
| nativeData.isJsInteropMember(memberEntity), | |
| "Unexpected js interop member result from native data for $member, " | |
| "id: $memberId", | |
| ); | |
| Expect.equals( | |
| isJsInteropMember | |
| ? expectedJsInteropMemberName ?? memberEntity.name | |
| : null, | |
| nativeData.getJsInteropMemberName(memberEntity), | |
| "Unexpected js interop member name from native data for $member, " | |
| "id: $memberId", | |
| ); | |
| bool isNativeMember = | |
| implicitNativeMember || expectedNativeMemberName != null; | |
| Expect.equals( | |
| isNativeMember || isJsInteropMember, | |
| nativeData.isNativeMember(memberEntity), | |
| "Unexpected native member result from native data for $member, " | |
| "id: $memberId", | |
| ); | |
| Expect.equals( | |
| isNativeMember | |
| ? expectedNativeMemberName ?? memberEntity.name | |
| : (isJsInteropMember | |
| ? expectedJsInteropMemberName ?? memberEntity.name | |
| : null), | |
| nativeData.getFixedBackendName(memberEntity), | |
| "Unexpected fixed backend name from native data for $member, " | |
| "id: $memberId", | |
| ); | |
| if (expectedCreatesText != null) { | |
| String createsText; | |
| if (memberEntity is FieldEntity) { | |
| createsText = nativeData | |
| .getNativeFieldLoadBehavior(memberEntity) | |
| .typesInstantiated | |
| .join(','); | |
| } else { | |
| createsText = nativeData | |
| .getNativeMethodBehavior(memberEntity as FunctionEntity) | |
| .typesInstantiated | |
| .join(','); | |
| } | |
| Expect.equals( | |
| expectedCreatesText, | |
| createsText, | |
| "Unexpected create annotations from native data for $member, " | |
| "id: $memberId", | |
| ); | |
| } | |
| if (expectedReturnsText != null) { | |
| String returnsText; | |
| if (memberEntity is FieldEntity) { | |
| returnsText = nativeData | |
| .getNativeFieldLoadBehavior(memberEntity) | |
| .typesReturned | |
| .join(','); | |
| } else { | |
| returnsText = nativeData | |
| .getNativeMethodBehavior(memberEntity as FunctionEntity) | |
| .typesReturned | |
| .join(','); | |
| } | |
| Expect.equals( | |
| expectedReturnsText, | |
| returnsText, | |
| "Unexpected returns annotations from native data for $member, " | |
| "id: $memberId", | |
| ); | |
| } | |
| List<PragmaAnnotationData> pragmaAnnotations = frontendStrategy | |
| .modularStrategyForTesting | |
| .getPragmaAnnotationData(member); | |
| Set<String> pragmaNames = pragmaAnnotations | |
| .map((d) => d.name) | |
| .toSet(); | |
| Expect.setEquals( | |
| expectedPragmaNames, | |
| pragmaNames, | |
| "Unexpected pragmas from modular strategy for $member, " | |
| "id: $memberId", | |
| ); | |
| } | |
| for (ir.Library library in component.libraries) { | |
| if (library.importUri.isScheme('memory')) { | |
| String libraryId = library.importUri.path; | |
| LibraryEntity libraryEntity = elementMap.getLibrary(library); | |
| String? expectedJsInteropLibraryName = | |
| expectedJsInteropLibraryNames[libraryId]; | |
| if (useIr) { | |
| Expect.equals( | |
| expectedJsInteropLibraryName, | |
| annotationData.getJsInteropLibraryName(library), | |
| "Unexpected js library name from IR for $library", | |
| ); | |
| } | |
| Expect.equals( | |
| expectedJsInteropLibraryName != null, | |
| nativeData.isJsInteropLibrary(libraryEntity), | |
| "Unexpected js library result from native data for $library", | |
| ); | |
| Expect.equals( | |
| expectedJsInteropLibraryName, | |
| nativeData.getJsInteropLibraryName(libraryEntity), | |
| "Unexpected js library name from native data for $library", | |
| ); | |
| for (ir.Class cls in library.classes) { | |
| String clsId = '$libraryId::${cls.name}'; | |
| ClassEntity classEntity = elementMap.getClass(cls); | |
| String? expectedNativeClassName = expectedNativeClassNames[clsId]; | |
| if (useIr) { | |
| Expect.equals( | |
| expectedNativeClassName, | |
| annotationData.getNativeClassName(cls), | |
| "Unexpected native class name from IR for $cls", | |
| ); | |
| } | |
| bool isNativeClass = | |
| nativeData.isNativeClass(classEntity) && | |
| !nativeData.isJsInteropClass(classEntity); | |
| String? nativeDataClassName; | |
| if (isNativeClass) { | |
| nativeDataClassName = nativeData | |
| .getNativeTagsOfClass(classEntity) | |
| .join(','); | |
| if (nativeData.hasNativeTagsForcedNonLeaf(classEntity)) { | |
| nativeDataClassName += ',!nonleaf'; | |
| } | |
| } | |
| Expect.equals( | |
| expectedNativeClassName != null, | |
| isNativeClass, | |
| "Unexpected native class result from native data for $cls", | |
| ); | |
| Expect.equals( | |
| expectedNativeClassName, | |
| nativeDataClassName, | |
| "Unexpected native class name from native data for $cls", | |
| ); | |
| String? expectedJsInteropClassName = | |
| expectedJsInteropClassNames[clsId]; | |
| if (useIr) { | |
| Expect.equals( | |
| expectedJsInteropClassName, | |
| annotationData.getJsInteropClassName(cls), | |
| "Unexpected js class name from IR for $cls", | |
| ); | |
| } | |
| Expect.equals( | |
| expectedJsInteropClassName != null, | |
| nativeData.isJsInteropClass(classEntity), | |
| "Unexpected js class result from native data for $cls", | |
| ); | |
| Expect.equals( | |
| expectedJsInteropClassName, | |
| nativeData.getJsInteropClassName(classEntity), | |
| "Unexpected js class name from native data for $cls", | |
| ); | |
| bool expectedAnonymousJsInteropClass = | |
| expectedAnonymousJsInteropClasses.contains(clsId); | |
| if (useIr) { | |
| Expect.equals( | |
| expectedAnonymousJsInteropClass, | |
| annotationData.isAnonymousJsInteropClass(cls), | |
| "Unexpected js anonymous class result from IR for $cls", | |
| ); | |
| } | |
| Expect.equals( | |
| expectedAnonymousJsInteropClass, | |
| nativeData.isAnonymousJsInteropClass(classEntity), | |
| "Unexpected js anonymous class result from native data for " | |
| "$cls", | |
| ); | |
| for (ir.Member member in cls.members) { | |
| testMember( | |
| clsId, | |
| member, | |
| implicitJsInteropMember: nativeData.isJsInteropClass( | |
| classEntity, | |
| ), | |
| implicitNativeMember: | |
| member is! ir.Constructor && | |
| !isTearOffLowering(member) && | |
| nativeData.isNativeClass(classEntity) && | |
| !nativeData.isJsInteropClass(classEntity), | |
| ); | |
| } | |
| } | |
| for (ir.Member member in library.members) { | |
| testMember( | |
| libraryId, | |
| member, | |
| implicitJsInteropMember: expectedJsInteropLibraryName != null, | |
| implicitNativeMember: false, | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| testAll(compiler.frontendClosedWorldForTesting!.nativeData); | |
| if (useIr) { | |
| // We need to open the environment because creating annotation data | |
| // from IR will create K-model classes and members for all annotations | |
| // in the IR component, and not just the ones queried specifically for | |
| // JS-interop and pragma-like annotations. | |
| elementMap.envIsClosed = false; | |
| testAll(NativeData.fromIr(elementMap, annotationData)); | |
| } | |
| } | |
| print('test annotations from K-model'); | |
| await runTest(useIr: false); | |
| print('test annotations from IR'); | |
| await runTest(useIr: true); | |
| }); | |
| } |