blob: b4e7ecca596c293e9d850cc3890cefcab1ce6ca3 [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.
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:front_end/src/base/instrumentation.dart' as fasta;
import 'package:front_end/src/fasta/compiler_context.dart' as fasta;
import 'package:front_end/src/fasta/testing/validating_instrumentation.dart'
as fasta;
import 'package:kernel/kernel.dart' as fasta;
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'front_end_test_common.dart';
main() {
// Use a group() wrapper to specify the timeout.
group('front_end_inference_test', () {
defineReflectiveSuite(() {
defineReflectiveTests(RunFrontEndInferenceTest);
});
}, timeout: new Timeout(const Duration(seconds: 120)));
}
@reflectiveTest
class RunFrontEndInferenceTest extends RunFrontEndTest {
@override
get testSubdir => 'inference';
@override
void visitUnit(TypeProvider typeProvider, CompilationUnit unit,
fasta.ValidatingInstrumentation validation, Uri uri) {
unit.accept(new _InstrumentationVisitor(validation, uri));
}
}
/**
* Visitor for ASTs that reports instrumentation for types.
*/
class _InstrumentationVisitor extends RecursiveAstVisitor<Null> {
final fasta.Instrumentation _instrumentation;
final Uri uri;
ElementNamer elementNamer = new ElementNamer(null);
_InstrumentationVisitor(this._instrumentation, this.uri);
visitBinaryExpression(BinaryExpression node) {
super.visitBinaryExpression(node);
_recordTarget(node.operator.charOffset, node.staticElement);
}
@override
visitConstructorDeclaration(ConstructorDeclaration node) {
ElementNamer oldElementNamer = elementNamer;
if (node.factoryKeyword != null) {
// Factory constructors are represented in kernel as static methods, so
// their type parameters get replicated, e.g.:
// class C<T> {
// factory C.ctor() {
// T t; // Refers to C::T
// ...
// }
// }
// gets converted to:
// class C<T> {
// static C<T> C.ctor<T>() {
// T t; // Refers to C::ctor::T
// ...
// }
// }
// So to match kernel behavior, we have to arrange for this renaming to
// happen during output.
elementNamer = new ElementNamer(node.element);
}
super.visitConstructorDeclaration(node);
elementNamer = oldElementNamer;
}
@override
visitDeclaredIdentifier(DeclaredIdentifier node) {
super.visitDeclaredIdentifier(node);
if (node.type == null) {
_recordType(node.identifier.offset, node.element.type);
}
}
@override
visitFunctionDeclaration(FunctionDeclaration node) {
super.visitFunctionDeclaration(node);
bool isSetter = node.element.kind == ElementKind.SETTER;
bool isLocalFunction = node.element is LocalElement &&
node.element.enclosingElement is! CompilationUnitElement;
if (isSetter || isLocalFunction) {
if (node.returnType == null) {
_instrumentation.record(
uri,
node.name.offset,
isSetter ? 'topType' : 'returnType',
new InstrumentationValueForType(
node.element.returnType, elementNamer));
}
var parameters = node.functionExpression.parameters;
for (var parameter in parameters.parameters) {
// Note: it's tempting to check `parameter.type == null`, but that
// doesn't work because of function-typed formal parameter syntax.
if (parameter.element.hasImplicitType) {
_recordType(parameter.identifier.offset, parameter.element.type);
}
}
}
}
visitFunctionExpression(FunctionExpression node) {
super.visitFunctionExpression(node);
if (node.parent is! FunctionDeclaration) {
DartType type = node.staticType;
if (type is FunctionType) {
_instrumentation.record(uri, node.parameters.offset, 'returnType',
new InstrumentationValueForType(type.returnType, elementNamer));
List<FormalParameter> parameters = node.parameters.parameters;
for (int i = 0; i < parameters.length; i++) {
FormalParameter parameter = parameters[i];
NormalFormalParameter normalParameter =
parameter is DefaultFormalParameter
? parameter.parameter
: parameter;
if (normalParameter is SimpleFormalParameter &&
normalParameter.type == null) {
_recordType(parameter.offset, type.parameters[i].type);
}
}
}
}
}
@override
visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
super.visitFunctionExpressionInvocation(node);
var receiverType = node.function.staticType;
if (receiverType is InterfaceType) {
// This is a hack since analyzer doesn't record .call targets
var target = receiverType.element.lookUpMethod('call', null) ??
receiverType.element.lookUpGetter('call', null);
if (target != null) {
_recordTarget(node.argumentList.offset, target);
}
}
if (node.typeArguments == null) {
var inferredTypeArguments = _getInferredFunctionTypeArguments(
node.function.staticType,
node.staticInvokeType,
node.typeArguments)
.toList();
if (inferredTypeArguments.isNotEmpty) {
_recordTypeArguments(node.argumentList.offset, inferredTypeArguments);
}
}
}
visitIndexExpression(IndexExpression node) {
super.visitIndexExpression(node);
_recordTarget(node.leftBracket.charOffset, node.staticElement);
}
visitInstanceCreationExpression(InstanceCreationExpression node) {
super.visitInstanceCreationExpression(node);
DartType type = node.staticType;
if (type is InterfaceType) {
if (type.typeParameters.isNotEmpty &&
node.constructorName.type.typeArguments == null) {
_recordTypeArguments(node.constructorName.offset, type.typeArguments);
}
}
}
visitListLiteral(ListLiteral node) {
super.visitListLiteral(node);
if (node.typeArguments == null) {
DartType type = node.staticType;
if (type is InterfaceType) {
_recordTypeArguments(node.offset, type.typeArguments);
}
}
}
visitMapLiteral(MapLiteral node) {
super.visitMapLiteral(node);
if (node.typeArguments == null) {
DartType type = node.staticType;
if (type is InterfaceType) {
_recordTypeArguments(node.offset, type.typeArguments);
}
}
}
@override
visitMethodDeclaration(MethodDeclaration node) {
super.visitMethodDeclaration(node);
if (node.returnType == null) {
_recordTopType(node.name.offset, node.element.returnType);
}
if (node.element.enclosingElement is ClassElement && !node.isStatic) {
if (node.parameters != null) {
for (var parameter in node.parameters.parameters) {
// Note: it's tempting to check `parameter.type == null`, but that
// doesn't work because of function-typed formal parameter syntax.
if (parameter.element.hasImplicitType) {
_recordTopType(parameter.identifier.offset, parameter.element.type);
}
}
}
}
}
visitMethodInvocation(MethodInvocation node) {
super.visitMethodInvocation(node);
if (node.typeArguments == null) {
var inferredTypeArguments = _getInferredFunctionTypeArguments(
node.function.staticType,
node.staticInvokeType,
node.typeArguments)
.toList();
if (inferredTypeArguments.isNotEmpty) {
_recordTypeArguments(node.methodName.offset, inferredTypeArguments);
}
}
var methodElement = node.methodName.staticElement;
if (node.target is SuperExpression &&
methodElement is PropertyAccessorElement) {
// This is a hack since analyzer doesn't record .call targets
var getterClass = methodElement.returnType.element;
if (getterClass is ClassElement) {
var target = getterClass.lookUpMethod('call', null) ??
getterClass.lookUpGetter('call', null);
if (target != null) {
_recordTarget(node.argumentList.offset, target);
}
}
}
}
visitPrefixExpression(PrefixExpression node) {
super.visitPrefixExpression(node);
if (node.operator.type != TokenType.PLUS_PLUS &&
node.operator.type != TokenType.MINUS_MINUS) {
_recordTarget(node.operator.charOffset, node.staticElement);
}
}
visitSimpleIdentifier(SimpleIdentifier node) {
super.visitSimpleIdentifier(node);
Element element = node.staticElement;
if (_elementRequiresMethodDispatch(element) &&
!node.inDeclarationContext() &&
(node.inGetterContext() || node.inSetterContext())) {
_recordTarget(node.offset, element);
}
void recordPromotions(DartType elementType) {
if (node.inGetterContext() && !node.inDeclarationContext()) {
int offset = node.offset;
DartType type = node.staticType;
if (!identical(type, elementType)) {
_instrumentation.record(uri, offset, 'promotedType',
new InstrumentationValueForType(type, elementNamer));
}
}
}
if (element is LocalVariableElement) {
recordPromotions(element.type);
} else if (element is ParameterElement) {
recordPromotions(element.type);
}
}
visitVariableDeclarationList(VariableDeclarationList node) {
super.visitVariableDeclarationList(node);
if (node.type == null) {
for (VariableDeclaration variable in node.variables) {
VariableElement element = variable.element;
if (element is LocalVariableElement) {
_recordType(variable.name.offset, element.type);
} else if (!element.isStatic || element.initializer != null) {
_recordTopType(variable.name.offset, element.type);
}
}
}
}
bool _elementRequiresMethodDispatch(Element element) {
if (element is ConstructorElement) {
return false;
} else if (element is ClassMemberElement) {
return !element.isStatic;
} else if (element is ExecutableElement &&
element.enclosingElement is ClassElement) {
return !element.isStatic;
} else {
return false;
}
}
/// Based on DDC code generator's `_emitFunctionTypeArguments`
Iterable<DartType> _getInferredFunctionTypeArguments(
DartType g, DartType f, TypeArgumentList typeArgs) {
if (g is FunctionType &&
g.typeFormals.isNotEmpty &&
f is FunctionType &&
f.typeFormals.isEmpty) {
return _recoverTypeArguments(g, f);
} else {
return const [];
}
}
void _recordTarget(int offset, Element element) {
if (element is ExecutableElement) {
_instrumentation.record(uri, offset, 'target',
new InstrumentationValueForExecutableElement(element, elementNamer));
}
}
void _recordTopType(int offset, DartType type) {
_instrumentation.record(uri, offset, 'topType',
new InstrumentationValueForType(type, elementNamer));
}
void _recordType(int offset, DartType type) {
_instrumentation.record(uri, offset, 'type',
new InstrumentationValueForType(type, elementNamer));
}
void _recordTypeArguments(int offset, List<DartType> typeArguments) {
_instrumentation.record(uri, offset, 'typeArgs',
new InstrumentationValueForTypeArgs(typeArguments, elementNamer));
}
/// Based on DDC code generator's `_recoverTypeArguments`
Iterable<DartType> _recoverTypeArguments(FunctionType g, FunctionType f) {
assert(identical(g.element, f.element));
assert(g.typeFormals.isNotEmpty && f.typeFormals.isEmpty);
assert(g.typeFormals.length + g.typeArguments.length ==
f.typeArguments.length);
return f.typeArguments.skip(g.typeArguments.length);
}
}