blob: e72f3ab99070cd181605ff7ae4024e6e4c6bdc78 [file] [log] [blame]
// Copyright (c) 2014, 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:kernel/ast.dart' as ir;
import '../common.dart';
import '../common_elements.dart' show KElementEnvironment;
import '../constants/values.dart';
import '../elements/entities.dart';
import '../ir/annotations.dart';
import '../js_backend/native_data.dart';
/// Interface for computing native members.
abstract class NativeMemberResolver {
/// Computes whether [node] is native or JsInterop.
void resolveNativeMember(ir.Member node, IrAnnotationData annotationData);
}
/// Determines all native classes in a set of libraries.
abstract class NativeClassFinder {
/// Returns the set of all native classes declared in [libraries].
Iterable<ClassEntity> computeNativeClasses(Iterable<Uri> libraries);
}
class BaseNativeClassFinder implements NativeClassFinder {
final KElementEnvironment _elementEnvironment;
final NativeBasicData _nativeBasicData;
Map<String, ClassEntity> _tagOwner = new Map<String, ClassEntity>();
BaseNativeClassFinder(this._elementEnvironment, this._nativeBasicData);
@override
Iterable<ClassEntity> computeNativeClasses(Iterable<Uri> libraries) {
Set<ClassEntity> nativeClasses = new Set<ClassEntity>();
libraries.forEach((uri) => _processNativeClassesInLibrary(
_elementEnvironment.lookupLibrary(uri), nativeClasses));
_processSubclassesOfNativeClasses(libraries, nativeClasses);
return nativeClasses;
}
/// Adds all directly native classes declared in [library] to [nativeClasses].
void _processNativeClassesInLibrary(
LibraryEntity library, Set<ClassEntity> nativeClasses) {
_elementEnvironment.forEachClass(library, (ClassEntity cls) {
if (_nativeBasicData.isNativeClass(cls)) {
_processNativeClass(cls, nativeClasses);
}
});
}
/// Adds [cls] to [nativeClasses] and performs further processing of [cls],
/// if necessary.
void _processNativeClass(
covariant ClassEntity cls, Set<ClassEntity> nativeClasses) {
nativeClasses.add(cls);
// Js Interop interfaces do not have tags.
if (_nativeBasicData.isJsInteropClass(cls)) return;
// Since we map from dispatch tags to classes, a dispatch tag must be used
// on only one native class.
for (String tag in _nativeBasicData.getNativeTagsOfClass(cls)) {
ClassEntity owner = _tagOwner[tag];
if (owner != null) {
if (owner != cls) {
failedAt(cls, "Tag '$tag' already in use by '${owner.name}'");
}
} else {
_tagOwner[tag] = cls;
}
}
}
/// Returns the name of the super class of [cls] or `null` of [cls] has
/// no explicit superclass.
String _findExtendsNameOfClass(covariant ClassEntity cls) {
return _elementEnvironment
.getSuperClass(cls, skipUnnamedMixinApplications: true)
?.name;
}
/// Adds all subclasses of [nativeClasses] found in [libraries] to
/// [nativeClasses].
void _processSubclassesOfNativeClasses(
Iterable<Uri> libraries, Set<ClassEntity> nativeClasses) {
Set<ClassEntity> nativeClassesAndSubclasses = new Set<ClassEntity>();
// Collect potential subclasses, e.g.
//
// class B extends foo.A {}
//
// String "A" has a potential subclass B.
Map<String, Set<ClassEntity>> potentialExtends =
<String, Set<ClassEntity>>{};
libraries.forEach((Uri uri) {
LibraryEntity library = _elementEnvironment.lookupLibrary(uri);
_elementEnvironment.forEachClass(library, (ClassEntity cls) {
String extendsName = _findExtendsNameOfClass(cls);
if (extendsName != null) {
Set<ClassEntity> potentialSubclasses = potentialExtends.putIfAbsent(
extendsName, () => new Set<ClassEntity>());
potentialSubclasses.add(cls);
}
});
});
// Resolve all the native classes and any classes that might extend them in
// [potentialExtends], and then check that the properly resolved class is in
// fact a subclass of a native class.
ClassEntity nativeSuperclassOf(ClassEntity cls) {
if (_nativeBasicData.isNativeClass(cls)) return cls;
ClassEntity superclass = _elementEnvironment.getSuperClass(cls);
if (superclass == null) return null;
return nativeSuperclassOf(superclass);
}
void walkPotentialSubclasses(ClassEntity element) {
if (nativeClassesAndSubclasses.contains(element)) return;
ClassEntity nativeSuperclass = nativeSuperclassOf(element);
if (nativeSuperclass != null) {
nativeClassesAndSubclasses.add(element);
Set<ClassEntity> potentialSubclasses = potentialExtends[element.name];
if (potentialSubclasses != null) {
potentialSubclasses.forEach(walkPotentialSubclasses);
}
}
}
nativeClasses.forEach(walkPotentialSubclasses);
nativeClasses.addAll(nativeClassesAndSubclasses);
}
}
/// Returns `true` if [value] is named annotation based on [annotationClass].
bool isAnnotation(
Spannable spannable, ConstantValue value, ClassEntity annotationClass) {
if (!value.isConstructedObject) return null;
ConstructedConstantValue constructedObject = value;
return constructedObject.type.element == annotationClass;
}
/// Extracts the name if [value] is a named annotation based on
/// [annotationClass], otherwise returns `null`.
String readAnnotationName(
Spannable spannable, ConstantValue value, ClassEntity annotationClass,
{String defaultValue}) {
if (!value.isConstructedObject) return null;
ConstructedConstantValue constructedObject = value;
if (constructedObject.type.element != annotationClass) return null;
Iterable<ConstantValue> fields = constructedObject.fields.values;
// TODO(sra): Better validation of the constant.
if (fields.length != 1) {
failedAt(
spannable, 'Annotations needs one string: ${value.toStructuredText()}');
return null;
} else if (fields.single is StringConstantValue) {
StringConstantValue specStringConstant = fields.single;
return specStringConstant.stringValue;
} else if (defaultValue != null && fields.single is NullConstantValue) {
return defaultValue;
} else {
failedAt(
spannable, 'Annotations needs one string: ${value.toStructuredText()}');
return null;
}
}