blob: 52901439a21f0b7ed3c618e90e5914495bd67d3a [file] [log] [blame]
// Copyright (c) 2017, 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.
library js_backend.backend.annotations;
import '../common_elements.dart' show KCommonElements, KElementEnvironment;
import '../constants/values.dart';
import '../diagnostics/diagnostic_listener.dart';
import '../diagnostics/messages.dart';
import '../elements/entities.dart';
import '../kernel/dart2js_target.dart';
import '../serialization/serialization.dart';
import '../util/enumset.dart';
/// Returns `true` if inference of parameter types is disabled for [element].
bool _assumeDynamic(KElementEnvironment elementEnvironment,
KCommonElements commonElements, MemberEntity element) {
return _hasAnnotation(
elementEnvironment, element, commonElements.expectAssumeDynamicClass);
}
/// Returns `true` if [element] is annotated with [annotationClass].
bool _hasAnnotation(KElementEnvironment elementEnvironment,
MemberEntity element, ClassEntity annotationClass) {
if (annotationClass == null) return false;
for (ConstantValue value in elementEnvironment.getMemberMetadata(element)) {
if (value.isConstructedObject) {
ConstructedConstantValue constructedConstant = value;
if (constructedConstant.type.element == annotationClass) {
return true;
}
}
}
return false;
}
class PragmaAnnotation {
final int _index;
final String name;
final bool forFunctionsOnly;
final bool forFieldsOnly;
final bool internalOnly;
const PragmaAnnotation(this._index, this.name,
{this.forFunctionsOnly: false,
this.forFieldsOnly: false,
this.internalOnly: false});
int get index {
assert(_index == values.indexOf(this));
return _index;
}
static const PragmaAnnotation noInline =
const PragmaAnnotation(0, 'noInline', forFunctionsOnly: true);
static const PragmaAnnotation tryInline =
const PragmaAnnotation(1, 'tryInline', forFunctionsOnly: true);
static const PragmaAnnotation disableFinal = const PragmaAnnotation(
2, 'disableFinal',
forFunctionsOnly: true, internalOnly: true);
static const PragmaAnnotation noElision = const PragmaAnnotation(
3, 'noElision',
forFieldsOnly: true, internalOnly: true);
static const PragmaAnnotation noThrows = const PragmaAnnotation(4, 'noThrows',
forFunctionsOnly: true, internalOnly: true);
static const PragmaAnnotation noSideEffects = const PragmaAnnotation(
5, 'noSideEffects',
forFunctionsOnly: true, internalOnly: true);
// TODO(johnniwinther): Remove this.
static const PragmaAnnotation trustTypeAnnotations = const PragmaAnnotation(
6, 'trustTypeAnnotations',
forFunctionsOnly: true, internalOnly: true);
static const PragmaAnnotation assumeDynamic = const PragmaAnnotation(
7, 'assumeDynamic',
forFunctionsOnly: true, internalOnly: true);
static const List<PragmaAnnotation> values = [
noInline,
tryInline,
disableFinal,
noElision,
noThrows,
noSideEffects,
trustTypeAnnotations,
assumeDynamic,
];
}
Set<PragmaAnnotation> processMemberAnnotations(
DiagnosticReporter reporter,
KCommonElements commonElements,
KElementEnvironment elementEnvironment,
AnnotationsDataBuilder annotationsDataBuilder,
MemberEntity element) {
EnumSet<PragmaAnnotation> values = new EnumSet<PragmaAnnotation>();
if (_assumeDynamic(elementEnvironment, commonElements, element)) {
values.add(PragmaAnnotation.assumeDynamic);
}
LibraryEntity library = element.library;
bool platformAnnotationsAllowed = library.canonicalUri.scheme == 'dart' ||
maybeEnableNative(library.canonicalUri);
for (ConstantValue constantValue
in elementEnvironment.getMemberMetadata(element)) {
if (!constantValue.isConstructedObject) continue;
ConstructedConstantValue value = constantValue;
ClassEntity cls = value.type.element;
assert(cls != null); // Unresolved classes null.
if (platformAnnotationsAllowed) {
if (cls == commonElements.forceInlineClass) {
values.add(PragmaAnnotation.tryInline);
if (element is! FunctionEntity) {
reporter.internalError(element,
"@TryInline() is only allowed in methods and constructors.");
}
} else if (cls == commonElements.noInlineClass) {
values.add(PragmaAnnotation.noInline);
if (element is! FunctionEntity) {
reporter.internalError(element,
"@NoInline() is only allowed in methods and constructors.");
}
} else if (cls == commonElements.noThrowsClass) {
values.add(PragmaAnnotation.noThrows);
bool isValid = true;
if (element is FunctionEntity) {
if (element.isTopLevel) {
isValid = true;
} else if (element.isStatic) {
isValid = true;
} else if (element is ConstructorEntity &&
element.isFactoryConstructor) {
isValid = true;
}
} else {
isValid = false;
}
if (!isValid) {
reporter.internalError(
element,
"@NoThrows() is currently limited to top-level"
" or static functions and factory constructors.");
}
} else if (cls == commonElements.noSideEffectsClass) {
values.add(PragmaAnnotation.noSideEffects);
if (element is! FunctionEntity) {
reporter.internalError(element,
"@NoSideEffects() is only allowed in methods and constructors.");
}
}
}
if (cls == commonElements.expectNoInlineClass) {
values.add(PragmaAnnotation.noInline);
if (element is! FunctionEntity) {
reporter.internalError(element,
"@NoInline() is only allowed in methods and constructors.");
}
} else if (cls == commonElements.metaNoInlineClass) {
values.add(PragmaAnnotation.noInline);
if (element is! FunctionEntity) {
reporter.internalError(
element, "@noInline is only allowed in methods and constructors.");
}
} else if (cls == commonElements.metaTryInlineClass) {
values.add(PragmaAnnotation.tryInline);
if (element is! FunctionEntity) {
reporter.internalError(
element, "@tryInline is only allowed in methods and constructors.");
}
} else if (cls == commonElements.pragmaClass) {
// Recognize:
//
// @pragma('dart2js:noInline')
// @pragma('dart2js:tryInline')
//
ConstantValue nameValue =
value.fields[commonElements.pragmaClassNameField];
if (nameValue == null || !nameValue.isString) continue;
String name = (nameValue as StringConstantValue).stringValue;
String prefix = 'dart2js:';
if (!name.startsWith(prefix)) continue;
String suffix = name.substring(prefix.length);
ConstantValue optionsValue =
value.fields[commonElements.pragmaClassOptionsField];
bool found = false;
for (PragmaAnnotation annotation in PragmaAnnotation.values) {
if (annotation.name == suffix) {
found = true;
values.add(annotation);
if (!optionsValue.isNull) {
reporter.reportErrorMessage(element, MessageKind.GENERIC,
{'text': "@pragma('$name') annotation does not take options"});
}
if (annotation.forFunctionsOnly) {
if (element is! FunctionEntity) {
reporter.reportErrorMessage(element, MessageKind.GENERIC, {
'text': "@pragma('$name') annotation is only supported "
"for methods and constructors."
});
}
}
if (annotation.forFieldsOnly) {
if (element is! FieldEntity) {
reporter.reportErrorMessage(element, MessageKind.GENERIC, {
'text': "@pragma('$name') annotation is only supported "
"for fields."
});
}
}
if (annotation.internalOnly && !platformAnnotationsAllowed) {
reporter.reportErrorMessage(element, MessageKind.GENERIC,
{'text': "Unrecognized dart2js pragma @pragma('$name')"});
}
break;
}
}
if (!found) {
reporter.reportErrorMessage(element, MessageKind.GENERIC,
{'text': "Unknown dart2js pragma @pragma('$name')"});
}
}
}
if (values.contains(PragmaAnnotation.tryInline) &&
values.contains(PragmaAnnotation.noInline)) {
reporter.reportErrorMessage(element, MessageKind.GENERIC,
{'text': '@tryInline must not be used with @noInline.'});
values.remove(PragmaAnnotation.tryInline);
}
if (values.contains(PragmaAnnotation.noThrows) &&
!values.contains(PragmaAnnotation.noInline)) {
reporter.internalError(
element, "@NoThrows() should always be combined with @noInline.");
}
if (values.contains(PragmaAnnotation.noSideEffects) &&
!values.contains(PragmaAnnotation.noInline)) {
reporter.internalError(
element, "@NoSideEffects() should always be combined with @noInline.");
}
annotationsDataBuilder.registerPragmaAnnotations(element, values);
return new Set<PragmaAnnotation>.from(
values.iterable(PragmaAnnotation.values));
}
abstract class AnnotationsData {
/// Deserializes a [AnnotationsData] object from [source].
factory AnnotationsData.readFromDataSource(DataSource source) =
AnnotationsDataImpl.readFromDataSource;
/// Serializes this [AnnotationsData] to [sink].
void writeToDataSink(DataSink sink);
/// Returns `true` if [member] has an `@AssumeDynamic()` annotation.
bool hasAssumeDynamic(MemberEntity member);
/// Returns `true` if [member] has a `@NoInline()`, `@noInline`, or
/// `@pragma('dart2js:noInline')` annotation.
bool hasNoInline(MemberEntity member);
/// Returns `true` if [member] has a `@ForceInline()`, `@tryInline`, or
/// `@pragma('dart2js:tryInline')` annotation.
bool hasTryInline(MemberEntity member);
/// Returns `true` if [member] has a `@pragma('dart2js:disableFinal')`
/// annotation.
bool hasDisableFinal(MemberEntity member);
/// Returns `true` if [member] has a `@pragma('dart2js:noElision')` annotation.
bool hasNoElision(MemberEntity member);
/// Returns `true` if [member] has a `@NoThrows()` annotation.
bool hasNoThrows(MemberEntity member);
/// Returns `true` if [member] has a `@NoSideEffects()` annotation.
bool hasNoSideEffects(MemberEntity member);
/// Calls [f] for all functions with a `@NoInline()`, `@noInline`, or
/// `@pragma('dart2js:noInline')` annotation.
void forEachNoInline(void f(FunctionEntity function));
/// Calls [f] for all functions with a `@ForceInline()`, `@tryInline`, or
/// `@pragma('dart2js:tryInline')` annotation.
void forEachTryInline(void f(FunctionEntity function));
/// Calls [f] for all functions with a `@NoThrows()` annotation.
void forEachNoThrows(void f(FunctionEntity function));
/// Calls [f] for all functions with a `@NoSideEffects()` annotation.
void forEachNoSideEffects(void f(FunctionEntity function));
}
class AnnotationsDataImpl implements AnnotationsData {
/// Tag used for identifying serialized [AnnotationsData] objects in a
/// debugging data stream.
static const String tag = 'annotations-data';
final Map<MemberEntity, EnumSet<PragmaAnnotation>> pragmaAnnotations;
AnnotationsDataImpl(this.pragmaAnnotations);
factory AnnotationsDataImpl.readFromDataSource(DataSource source) {
source.begin(tag);
Map<MemberEntity, EnumSet<PragmaAnnotation>> pragmaAnnotations =
source.readMemberMap(() => new EnumSet.fromValue(source.readInt()));
source.end(tag);
return new AnnotationsDataImpl(pragmaAnnotations);
}
void writeToDataSink(DataSink sink) {
sink.begin(tag);
sink.writeMemberMap(pragmaAnnotations, (EnumSet<PragmaAnnotation> set) {
sink.writeInt(set.value);
});
sink.end(tag);
}
bool _hasPragma(MemberEntity member, PragmaAnnotation annotation) {
EnumSet<PragmaAnnotation> set = pragmaAnnotations[member];
return set != null && set.contains(annotation);
}
bool hasAssumeDynamic(MemberEntity member) =>
_hasPragma(member, PragmaAnnotation.assumeDynamic);
bool hasNoInline(MemberEntity member) =>
_hasPragma(member, PragmaAnnotation.noInline);
bool hasTryInline(MemberEntity member) =>
_hasPragma(member, PragmaAnnotation.tryInline);
bool hasDisableFinal(MemberEntity member) =>
_hasPragma(member, PragmaAnnotation.disableFinal);
bool hasNoElision(MemberEntity member) =>
_hasPragma(member, PragmaAnnotation.noElision);
bool hasNoThrows(MemberEntity member) =>
_hasPragma(member, PragmaAnnotation.noThrows);
bool hasNoSideEffects(MemberEntity member) =>
_hasPragma(member, PragmaAnnotation.noSideEffects);
void forEachNoInline(void f(FunctionEntity function)) {
pragmaAnnotations
.forEach((MemberEntity member, EnumSet<PragmaAnnotation> set) {
if (set.contains(PragmaAnnotation.noInline)) {
f(member);
}
});
}
void forEachTryInline(void f(FunctionEntity function)) {
pragmaAnnotations
.forEach((MemberEntity member, EnumSet<PragmaAnnotation> set) {
if (set.contains(PragmaAnnotation.tryInline)) {
f(member);
}
});
}
void forEachNoThrows(void f(FunctionEntity function)) {
pragmaAnnotations
.forEach((MemberEntity member, EnumSet<PragmaAnnotation> set) {
if (set.contains(PragmaAnnotation.noThrows)) {
f(member);
}
});
}
void forEachNoSideEffects(void f(FunctionEntity function)) {
pragmaAnnotations
.forEach((MemberEntity member, EnumSet<PragmaAnnotation> set) {
if (set.contains(PragmaAnnotation.noSideEffects)) {
f(member);
}
});
}
}
class AnnotationsDataBuilder {
Map<MemberEntity, EnumSet<PragmaAnnotation>> pragmaAnnotations = {};
void registerPragmaAnnotations(
MemberEntity member, EnumSet<PragmaAnnotation> annotations) {
if (annotations.isNotEmpty) {
pragmaAnnotations[member] = annotations;
}
}
AnnotationsData close() {
return new AnnotationsDataImpl(pragmaAnnotations);
}
}