| // 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. |
| |
| // @dart = 2.7 |
| |
| import 'package:args/args.dart'; |
| import 'package:async_helper/async_helper.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:compiler/src/kernel/element_map_impl.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 '../helpers/memory_compiler.dart'; |
| |
| const String pathPrefix = 'sdk/tests/web_2/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(); |
| new JsClass1()..jsMethod1()..jsMethod2(); |
| new JsClass2(); |
| jsMethod3(); |
| new NativeClass1()..nativeMethod()..nativeField; |
| new NativeClass2()..nativeField; |
| new 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({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; |
| KernelToElementMapImpl elementMap = frontendStrategy.elementMap; |
| ir.Component component = elementMap.env.mainComponent; |
| IrAnnotationData annotationData = |
| frontendStrategy.irAnnotationDataForTesting; |
| |
| void testAll(NativeData nativeData) { |
| void testMember(String idPrefix, ir.Member member, |
| {bool implicitJsInteropMember, 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.isField) { |
| createsText = nativeData |
| .getNativeFieldLoadBehavior(memberEntity) |
| .typesInstantiated |
| .join(','); |
| } else { |
| createsText = nativeData |
| .getNativeMethodBehavior(memberEntity) |
| .typesInstantiated |
| .join(','); |
| } |
| Expect.equals( |
| expectedCreatesText, |
| createsText, |
| "Unexpected create annotations from native data for $member, " |
| "id: $memberId"); |
| } |
| |
| if (expectedReturnsText != null) { |
| String returnsText; |
| if (memberEntity.isField) { |
| returnsText = nativeData |
| .getNativeFieldLoadBehavior(memberEntity) |
| .typesReturned |
| .join(','); |
| } else { |
| returnsText = nativeData |
| .getNativeMethodBehavior(memberEntity) |
| .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.scheme == '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 anonymousclass 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(new NativeDataImpl.fromIr(elementMap, annotationData)); |
| } |
| } |
| |
| print('test annotations from K-model'); |
| await runTest(useIr: false); |
| |
| print('test annotations from IR'); |
| await runTest(useIr: true); |
| }); |
| } |