blob: 58353ea5ed13303f6d3c16d25bb0f8f0fb61fd80 [file] [log] [blame]
// Copyright (c) 2019, 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:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/standard_ast_factory.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/ast/token.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/resolver/scope.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/type_system.dart';
/// A visitor that will re-write an AST to support the optional `new` and
/// `const` feature.
class AstRewriteVisitor extends ScopedVisitor {
final bool addConstKeyword;
final TypeSystem typeSystem;
/// Initialize a newly created visitor.
AstRewriteVisitor(
this.typeSystem,
LibraryElement definingLibrary,
Source source,
TypeProvider typeProvider,
AnalysisErrorListener errorListener,
{Scope nameScope,
this.addConstKeyword: false})
: super(definingLibrary, source, typeProvider, errorListener,
nameScope: nameScope);
@override
void visitMethodInvocation(MethodInvocation node) {
super.visitMethodInvocation(node);
SimpleIdentifier methodName = node.methodName;
if (methodName.isSynthetic) {
// This isn't a constructor invocation because the method name is
// synthetic.
return;
}
Expression target = node.target;
if (target == null) {
// Possible cases: C() or C<>()
if (node.realTarget != null) {
// This isn't a constructor invocation because it's in a cascade.
return;
}
Element element = nameScope.lookup(methodName, definingLibrary);
if (element is ClassElement) {
TypeName typeName = astFactory.typeName(methodName, node.typeArguments);
ConstructorName constructorName =
astFactory.constructorName(typeName, null, null);
InstanceCreationExpression instanceCreationExpression =
astFactory.instanceCreationExpression(
_getKeyword(node), constructorName, node.argumentList);
NodeReplacer.replace(node, instanceCreationExpression);
} else if (element is ExtensionElement) {
ExtensionOverride extensionOverride = astFactory.extensionOverride(
extensionName: methodName,
typeArguments: node.typeArguments,
argumentList: node.argumentList);
NodeReplacer.replace(node, extensionOverride);
}
} else if (target is SimpleIdentifier) {
// Possible cases: C.n(), p.C() or p.C<>()
if (node.operator.type == TokenType.QUESTION_PERIOD) {
// This isn't a constructor invocation because a null aware operator is
// being used.
}
Element element = nameScope.lookup(target, definingLibrary);
if (element is ClassElement) {
// Possible case: C.n()
var constructorElement = element.getNamedConstructor(methodName.name);
if (constructorElement != null) {
var typeArguments = node.typeArguments;
if (typeArguments != null) {
errorReporter.reportErrorForNode(
StaticTypeWarningCode
.WRONG_NUMBER_OF_TYPE_ARGUMENTS_CONSTRUCTOR,
typeArguments,
[element.name, constructorElement.name]);
}
TypeName typeName = astFactory.typeName(target, null);
ConstructorName constructorName =
astFactory.constructorName(typeName, node.operator, methodName);
// TODO(scheglov) I think we should drop "typeArguments" below.
InstanceCreationExpression instanceCreationExpression =
astFactory.instanceCreationExpression(
_getKeyword(node), constructorName, node.argumentList,
typeArguments: typeArguments);
NodeReplacer.replace(node, instanceCreationExpression);
}
} else if (element is PrefixElement) {
// Possible cases: p.C() or p.C<>()
Identifier identifier = astFactory.prefixedIdentifier(
astFactory.simpleIdentifier(target.token),
null,
astFactory.simpleIdentifier(methodName.token));
Element prefixedElement = nameScope.lookup(identifier, definingLibrary);
if (prefixedElement is ClassElement) {
TypeName typeName = astFactory.typeName(
astFactory.prefixedIdentifier(target, node.operator, methodName),
node.typeArguments);
ConstructorName constructorName =
astFactory.constructorName(typeName, null, null);
InstanceCreationExpression instanceCreationExpression =
astFactory.instanceCreationExpression(
_getKeyword(node), constructorName, node.argumentList);
NodeReplacer.replace(node, instanceCreationExpression);
} else if (prefixedElement is ExtensionElement) {
PrefixedIdentifier extensionName =
astFactory.prefixedIdentifier(target, node.operator, methodName);
ExtensionOverride extensionOverride = astFactory.extensionOverride(
extensionName: extensionName,
typeArguments: node.typeArguments,
argumentList: node.argumentList);
NodeReplacer.replace(node, extensionOverride);
}
}
} else if (target is PrefixedIdentifier) {
// Possible case: p.C.n()
Element prefixElement = nameScope.lookup(target.prefix, definingLibrary);
target.prefix.staticElement = prefixElement;
if (prefixElement is PrefixElement) {
Element element = nameScope.lookup(target, definingLibrary);
if (element is ClassElement) {
var constructorElement = element.getNamedConstructor(methodName.name);
if (constructorElement != null) {
var typeArguments = node.typeArguments;
if (typeArguments != null) {
errorReporter.reportErrorForNode(
StaticTypeWarningCode
.WRONG_NUMBER_OF_TYPE_ARGUMENTS_CONSTRUCTOR,
typeArguments,
[element.name, constructorElement.name]);
}
TypeName typeName = astFactory.typeName(target, typeArguments);
ConstructorName constructorName =
astFactory.constructorName(typeName, node.operator, methodName);
InstanceCreationExpression instanceCreationExpression =
astFactory.instanceCreationExpression(
_getKeyword(node), constructorName, node.argumentList);
NodeReplacer.replace(node, instanceCreationExpression);
}
}
}
}
}
/// Return the token that should be used in the [InstanceCreationExpression]
/// that corresponds to the given invocation [node].
Token _getKeyword(MethodInvocation node) {
return addConstKeyword
? new KeywordToken(Keyword.CONST, node.offset)
: null;
}
/// Return the type of the given class [element] after substituting any type
/// arguments from the list of [typeArguments] for the class' type parameters.
static InterfaceType getType(TypeSystem typeSystem, ClassElement element,
TypeArgumentList typeArguments) {
DartType type = element.type;
List<TypeParameterElement> typeParameters = element.typeParameters;
if (typeParameters.isEmpty) {
return type;
}
if (typeArguments == null) {
return typeSystem.instantiateToBounds(type);
}
List<DartType> argumentTypes;
if (typeArguments.arguments.length == typeParameters.length) {
argumentTypes = typeArguments.arguments
.map((TypeAnnotation argument) => argument.type)
.toList();
} else {
argumentTypes = List<DartType>.filled(
typeParameters.length, DynamicTypeImpl.instance);
}
List<DartType> parameterTypes = typeParameters
.map((TypeParameterElement parameter) => parameter.type)
.toList();
return type.substitute2(argumentTypes, parameterTypes);
}
}