blob: 76eb078cef1e84bd130c24804b7df3131106a8d2 [file] [log] [blame]
// 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: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;
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,
{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.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 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(NativeData.fromIr(elementMap, annotationData));
}
}
print('test annotations from K-model');
await runTest(useIr: false);
print('test annotations from IR');
await runTest(useIr: true);
});
}