| // 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); |
| } |
| } |