blob: 346d9bbe5d991e729aa633fece6149b0a45a6746 [file] [log] [blame]
// Copyright (c) 2024, 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/metadata/ast.dart' as shared;
import 'package:_fe_analyzer_shared/src/metadata/parser.dart' as shared;
import 'package:_fe_analyzer_shared/src/metadata/scope.dart' as shared;
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/scope.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/scope.dart';
/// Returns the [shared.Expression] for the constant initializer of [reference].
shared.Expression? getFieldInitializer(shared.FieldReference reference) {
if (reference is _VariableReference) {
var element = reference._element;
if (element is VariableElementImpl) {
return parseFieldInitializer(element);
}
}
assert(false,
"Unexpected field reference $reference (${reference.runtimeType})");
return null;
}
/// Creates a [shared.Expression] for the [annotation].
///
/// If [delayLookupForTesting] is `true`, identifiers are not looked up in their
/// corresponding scopes. This means that the return expression will contain
/// [shared.UnresolvedIdentifier] nodes, as if the identifier wasn't in scope.
/// A subsequent call to [shared.Expression.resolve] will perform the lookup
/// a create the resolved expression. This is used in testing to mimic the
/// scenario in which the declaration is added to the scope via macros.
shared.Expression parseAnnotation(ElementAnnotationImpl annotation,
{bool delayLookupForTesting = false}) {
var compilationUnit = annotation.compilationUnit;
var annotationImpl = annotation.annotationAst;
var uri = compilationUnit.source.uri;
// TODO(johnniwinther): Find the right scope for non-top-level annotations.
var scope = _Scope(compilationUnit);
var references = _References();
// The token stream might have been detached, so we ensure an EOF while
// parsing the annotation.
var endTokenNext = annotationImpl.endToken.next;
annotationImpl.endToken.next ??= Token.eof(-1);
var expression = shared.parseAnnotation(
annotationImpl.atSign, uri, scope, references,
isDartLibrary: uri.isScheme("dart") || uri.isScheme("org-dartlang-sdk"),
delayLookupForTesting: delayLookupForTesting);
annotationImpl.endToken.next = endTokenNext;
return expression;
}
/// Creates a [shared.Expression] for the initializer of the constant
/// [variable].
///
/// If [delayLookupForTesting] is `true`, identifiers are not looked up in their
/// corresponding scopes. This means that the return expression will contain
/// [shared.UnresolvedIdentifier] nodes, as if the identifier wasn't in scope.
/// A subsequent call to [shared.Expression.resolve] will perform the lookup
/// a create the resolved expression. This is used in testing to mimic the
/// scenario in which the declaration is added to the scope via macros.
shared.Expression? parseFieldInitializer(VariableElementImpl variable,
{bool delayLookupForTesting = false}) {
var initializer = variable.constantInitializer;
if (initializer == null) return null;
var enclosingElement = variable.enclosingElement3;
while (enclosingElement != null) {
if (enclosingElement is CompilationUnitElementImpl) {
var uri = enclosingElement.source.uri;
// TODO(johnniwinther): Find the right scope for class members.
var scope = _Scope(enclosingElement);
var references = _References();
// The token stream might have been detached, so we ensure an EOF while
// parsing the annotation.
var endTokenNext = initializer.endToken.next;
initializer.endToken.next ??= Token.eof(-1);
var expression = shared.parseExpression(
initializer.beginToken, uri, scope, references,
isDartLibrary:
uri.isScheme("dart") || uri.isScheme("org-dartlang-sdk"),
delayLookupForTesting: delayLookupForTesting);
initializer.endToken.next = endTokenNext;
return expression;
}
enclosingElement = enclosingElement.enclosingElement3;
}
return null;
}
shared.Proto _elementToProto(Element element, String name) {
if (element is ClassElement) {
var reference = _ClassReference(element);
return shared.ClassProto(reference, _ClassScope(element, reference));
} else if (element is PropertyAccessorElement) {
VariableElement? variableElement = element.variable2;
if (variableElement != null) {
return shared.FieldProto(_VariableReference(variableElement));
}
} else if (element is FunctionElement) {
return shared.FunctionProto(_FunctionReference(element));
} else if (element is MethodElement) {
return shared.FunctionProto(_FunctionReference(element));
} else if (element is VariableElement) {
return shared.FieldProto(_VariableReference(element));
} else if (element is PrefixElement) {
return shared.PrefixProto(name, _PrefixScope(element));
} else if (element.kind == ElementKind.DYNAMIC) {
return shared.DynamicProto(const _DynamicReference());
} else if (element is TypeAliasElement) {
var reference = _TypedefReference(element);
return shared.TypedefProto(reference, _TypedefScope(element, reference));
} else if (element is ExtensionElement) {
var reference = _ExtensionReference(element);
return shared.ExtensionProto(
reference, _ExtensionScope(element, reference));
} else if (element is ExtensionTypeElement) {
var reference = _ExtensionTypeReference(element);
return shared.ExtensionTypeProto(
reference, _ExtensionTypeScope(element, reference));
} else if (element is EnumElement) {
var reference = _EnumReference(element);
return shared.EnumProto(reference, _EnumScope(element, reference));
} else if (element is MixinElement) {
var reference = _MixinReference(element);
return shared.MixinProto(reference, _MixinScope(element, reference));
}
// TODO(johnniwinther): Support extension types.
throw UnsupportedError(
"Unsupported element $element (${element.runtimeType}) for '$name'.");
}
class _ClassReference extends shared.ClassReference {
final ClassElement _element;
_ClassReference(this._element);
@override
String get name => _element.name;
}
final class _ClassScope extends shared.BaseClassScope {
final ClassElement _classElement;
@override
final _ClassReference classReference;
_ClassScope(this._classElement, this.classReference);
@override
shared.Proto lookup(String name,
[List<shared.TypeAnnotation>? typeArguments]) {
var constructor = _classElement.getNamedConstructor(name);
if (constructor != null) {
return createConstructorProto(
typeArguments, _ConstructorReference(constructor));
}
Element? member = _classElement.augmented.getField(name);
member ??= _classElement.augmented.getMethod(name);
return createMemberProto(typeArguments, name, member, _elementToProto);
}
}
class _ConstructorReference extends shared.ConstructorReference {
final ConstructorElement _element;
_ConstructorReference(this._element);
@override
String get name => _element.name.isEmpty ? 'new' : _element.name;
}
class _DynamicReference extends shared.TypeReference {
const _DynamicReference();
@override
String get name => 'dynamic';
}
class _EnumReference extends shared.EnumReference {
final EnumElement _element;
_EnumReference(this._element);
@override
String get name => _element.name;
}
final class _EnumScope extends shared.BaseEnumScope {
final EnumElement _enumElement;
@override
final _EnumReference enumReference;
_EnumScope(this._enumElement, this.enumReference);
@override
shared.Proto lookup(String name,
[List<shared.TypeAnnotation>? typeArguments]) {
Element? member = _enumElement.augmented.getField(name);
member ??= _enumElement.augmented.getMethod(name);
return createMemberProto(typeArguments, name, member, _elementToProto);
}
}
class _ExtensionReference extends shared.ExtensionReference {
final ExtensionElement _element;
_ExtensionReference(this._element);
@override
String get name => _element.name!;
}
final class _ExtensionScope extends shared.BaseExtensionScope {
final ExtensionElement _extensionElement;
@override
final _ExtensionReference extensionReference;
_ExtensionScope(this._extensionElement, this.extensionReference);
@override
shared.Proto lookup(String name,
[List<shared.TypeAnnotation>? typeArguments]) {
Element? member = _extensionElement.augmented.getField(name);
member ??= _extensionElement.augmented.getMethod(name);
return createMemberProto(typeArguments, name, member, _elementToProto);
}
}
class _ExtensionTypeReference extends shared.ExtensionTypeReference {
final ExtensionTypeElement _element;
_ExtensionTypeReference(this._element);
@override
String get name => _element.name;
}
final class _ExtensionTypeScope extends shared.BaseExtensionTypeScope {
final ExtensionTypeElement _extensionTypeElement;
@override
final _ExtensionTypeReference extensionTypeReference;
_ExtensionTypeScope(this._extensionTypeElement, this.extensionTypeReference);
@override
shared.Proto lookup(String name,
[List<shared.TypeAnnotation>? typeArguments]) {
var constructor = _extensionTypeElement.getNamedConstructor(name);
if (constructor != null) {
return createConstructorProto(
typeArguments, _ConstructorReference(constructor));
}
Element? member = _extensionTypeElement.augmented.getField(name);
member ??= _extensionTypeElement.augmented.getMethod(name);
return createMemberProto(typeArguments, name, member, _elementToProto);
}
}
class _FunctionReference extends shared.FunctionReference {
final ExecutableElement _element;
_FunctionReference(this._element);
@override
String get name => _element.name;
}
class _MixinReference extends shared.MixinReference {
final MixinElement _element;
_MixinReference(this._element);
@override
String get name => _element.name;
}
final class _MixinScope extends shared.BaseMixinScope {
final MixinElement _mixinElement;
@override
final _MixinReference mixinReference;
_MixinScope(this._mixinElement, this.mixinReference);
@override
shared.Proto lookup(String name,
[List<shared.TypeAnnotation>? typeArguments]) {
Element? member = _mixinElement.augmented.getField(name);
member ??= _mixinElement.augmented.getMethod(name);
return createMemberProto(typeArguments, name, member, _elementToProto);
}
}
class _PrefixScope implements shared.Scope {
final PrefixElement _prefixElement;
_PrefixScope(this._prefixElement);
@override
shared.Proto lookup(String name) {
ScopeLookupResult result = _prefixElement.scope.lookup(name);
Element? getter = result.getter;
if (getter == null) {
return shared.UnresolvedIdentifier(this, name);
} else {
return _elementToProto(getter, name);
}
}
}
class _References implements shared.References {
@override
shared.TypeReference get dynamicReference => const _DynamicReference();
@override
shared.TypeReference get voidReference => const _VoidReference();
}
class _Scope implements shared.Scope {
final LibraryFragmentScope _libraryFragmentScope;
_Scope(CompilationUnitElementImpl compilationUnit)
: _libraryFragmentScope = LibraryFragmentScope(compilationUnit);
@override
shared.Proto lookup(String name) {
ScopeLookupResult result = _libraryFragmentScope.lookup(name);
Element? getter = result.getter;
if (getter == null) {
return shared.UnresolvedIdentifier(this, name);
} else {
return _elementToProto(getter, name);
}
}
}
class _TypedefReference implements shared.TypedefReference {
final TypeAliasElement _element;
_TypedefReference(this._element);
@override
String get name => _element.name;
}
final class _TypedefScope extends shared.BaseTypedefScope {
final TypeAliasElement _typeAliasElement;
@override
final shared.TypedefReference typedefReference;
_TypedefScope(this._typeAliasElement, this.typedefReference);
@override
shared.Proto lookup(String name,
[List<shared.TypeAnnotation>? typeArguments]) {
DartType aliasedType = _typeAliasElement.aliasedType;
if (aliasedType is InterfaceType) {
var constructor = aliasedType.element.getNamedConstructor(name);
if (constructor != null) {
return createConstructorProto(
typeArguments, _ConstructorReference(constructor));
}
}
return createMemberProto(typeArguments, name);
}
}
class _VariableReference extends shared.FieldReference {
final VariableElement _element;
_VariableReference(this._element);
@override
String get name => _element.name;
}
class _VoidReference extends shared.TypeReference {
const _VoidReference();
@override
String get name => 'void';
}