blob: 86ff27fefdf588029e407cd6453ddb1b0ebb0ef8 [file] [log] [blame]
// Copyright (c) 2016, 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:_fe_analyzer_shared/src/scanner/scanner.dart' show Token;
import 'package:_fe_analyzer_shared/src/metadata/expressions.dart' as shared;
import 'package:kernel/ast.dart';
import 'package:kernel/clone.dart';
import '../base/loader.dart';
import '../base/scope.dart' show LookupScope;
import '../kernel/body_builder.dart' show BodyBuilder;
import '../kernel/body_builder_context.dart';
import '../kernel/macro/metadata.dart';
import '../source/source_library_builder.dart' show SourceLibraryBuilder;
bool computeSharedExpressionForTesting = false;
bool delaySharedExpressionLookupForTesting = false;
class MetadataBuilder {
/// Token for `@` for annotations that have not yet been parsed.
Token? _atToken;
final int atOffset;
/// `true` if the annotation begins with 'patch'.
///
/// This is used for detecting `@patch` annotations in patch libraries where
/// it can be assumed that it implies that it _is_ a `@patch` annotation.
final bool hasPatch;
/// Expression for an already parsed annotation.
Expression? _expression;
final Uri fileUri;
MetadataBuilder(Token this._atToken, this.fileUri)
: atOffset = _atToken.charOffset,
hasPatch = _atToken.next?.lexeme == 'patch';
// Coverage-ignore(suite): Not run.
Token? get beginToken => _atToken;
shared.Expression? _sharedExpression;
shared.Expression? _unresolvedSharedExpressionForTesting;
// Coverage-ignore(suite): Not run.
shared.Expression? get expression => _sharedExpression;
// Coverage-ignore(suite): Not run.
shared.Expression? get unresolvedExpressionForTesting =>
_unresolvedSharedExpressionForTesting;
static void buildAnnotations({
required Annotatable annotatable,
required Uri annotatableFileUri,
required List<MetadataBuilder>? metadata,
required BodyBuilderContext bodyBuilderContext,
required SourceLibraryBuilder libraryBuilder,
required LookupScope scope,
}) {
if (metadata == null) return;
// [BodyBuilder] used to build annotations from [Token]s.
BodyBuilder? bodyBuilder;
// Cloner used to clone already parsed annotations.
CloneVisitorNotMembers? cloner;
// Map from annotation builder of parsed annotations to the index of the
// corresponding annotation in `parent.annotations`.
//
// This is used to read the fully inferred annotation from [parent] and
// store it in `_expression` of the corresponding [MetadataBuilder].
Map<MetadataBuilder, int> parsedAnnotationBuilders = {};
for (int i = 0; i < metadata.length; ++i) {
MetadataBuilder annotationBuilder = metadata[i];
bool createFileUriExpression =
annotatableFileUri != annotationBuilder.fileUri;
Token? beginToken = annotationBuilder._atToken;
if (beginToken != null) {
if (computeSharedExpressionForTesting) {
// Coverage-ignore-block(suite): Not run.
annotationBuilder._sharedExpression = _parseSharedExpression(
libraryBuilder.loader,
beginToken,
libraryBuilder.importUri,
annotationBuilder.fileUri,
scope,
);
if (delaySharedExpressionLookupForTesting) {
annotationBuilder._unresolvedSharedExpressionForTesting =
_parseSharedExpression(
libraryBuilder.loader,
beginToken,
libraryBuilder.importUri,
annotationBuilder.fileUri,
scope,
delayLookupForTesting: true,
);
}
}
bodyBuilder ??= libraryBuilder.loader
.createBodyBuilderForOutlineExpression(
libraryBuilder,
bodyBuilderContext,
scope,
annotationBuilder.fileUri,
);
Expression annotation = bodyBuilder.parseAnnotation(beginToken);
annotationBuilder._atToken = null;
if (createFileUriExpression) {
annotation = new FileUriExpression(
annotation,
annotationBuilder.fileUri,
)..fileOffset = annotationBuilder.atOffset;
}
// Record the index of [annotation] in `parent.annotations`.
parsedAnnotationBuilders[annotationBuilder] =
annotatable.annotations.length;
// It is important for the inference and backlog computations that the
// annotation is already a child of [parent].
annotatable.addAnnotation(annotation);
} else {
// The annotation is needed for multiple declarations so we need to
// clone the expression to use it more than once. For instance
//
// abstract class Class {
// @annotation
// abstract int field;
// }
//
// will be compiled to
//
// abstract class Class {
// @annotation
// int get field;
// @annotation
// void set field(int value);
// }
//
cloner ??= new CloneVisitorNotMembers();
Expression annotation = cloner.cloneInContext(
annotationBuilder._expression!,
);
// Coverage-ignore(suite): Not run.
if (createFileUriExpression && annotation is! FileUriExpression) {
annotation = new FileUriExpression(
annotation,
annotationBuilder.fileUri,
)..fileOffset = annotationBuilder.atOffset;
}
annotatable.addAnnotation(annotation);
}
}
if (bodyBuilder != null) {
// TODO(johnniwinther): Avoid potentially inferring annotations multiple
// times.
bodyBuilder.inferAnnotations(annotatable, annotatable.annotations);
bodyBuilder.performBacklogComputations();
for (MapEntry<MetadataBuilder, int> entry
in parsedAnnotationBuilders.entries) {
MetadataBuilder annotationBuilder = entry.key;
int index = entry.value;
annotationBuilder._expression = annotatable.annotations[index];
}
}
}
}
// Coverage-ignore(suite): Not run.
shared.Expression _parseSharedExpression(
Loader loader,
Token atToken,
Uri importUri,
Uri fileUri,
LookupScope scope, {
bool delayLookupForTesting = false,
}) {
return parseAnnotation(
loader,
atToken,
importUri,
fileUri,
scope,
delayLookupForTesting: delayLookupForTesting,
);
}