blob: 9ae51bc4d2499b527f367c48e9020f11a9a2ef9a [file] [log] [blame]
// Copyright (c) 2015, 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 related_types;
import 'package:compiler/src/commandline_options.dart';
import 'package:compiler/src/compiler.dart';
import 'package:compiler/src/common_elements.dart';
import 'package:compiler/src/elements/resolution_types.dart';
import 'package:compiler/src/diagnostics/diagnostic_listener.dart';
import 'package:compiler/src/diagnostics/messages.dart';
import 'package:compiler/src/elements/elements.dart';
import 'package:compiler/src/elements/names.dart';
import 'package:compiler/src/filenames.dart';
import 'package:compiler/src/resolution/semantic_visitor.dart';
import 'package:compiler/src/tree/tree.dart';
import 'package:compiler/src/universe/call_structure.dart';
import 'package:compiler/src/universe/selector.dart';
import 'package:compiler/src/world.dart';
import 'memory_compiler.dart';
main(List<String> arguments) async {
if (arguments.isNotEmpty) {
Uri entryPoint = Uri.base.resolve(nativeToUriPath(arguments.last));
CompilationResult result = await runCompiler(
entryPoint: entryPoint,
options: [Flags.analyzeOnly, '--categories=Client,Server']);
if (result.isSuccess) {
checkRelatedTypes(result.compiler);
}
} else {
print('Usage dart related_types.dart <entry-point>');
}
}
/// Check all loaded libraries in [compiler] for unrelated types.
void checkRelatedTypes(Compiler compiler) {
compiler.closeResolution(
compiler.frontendStrategy.elementEnvironment.mainFunction);
for (LibraryElement library in compiler.libraryLoader.libraries) {
checkLibraryElement(compiler, library);
}
}
/// Check [library] for unrelated types.
void checkLibraryElement(Compiler compiler, LibraryElement library) {
library.forEachLocalMember((Element element) {
if (element.isClass) {
ClassElement cls = element;
cls.forEachLocalMember((_member) {
MemberElement member = _member;
checkMemberElement(compiler, member);
});
} else if (!element.isTypedef) {
checkMemberElement(compiler, element);
}
});
}
/// Check [member] for unrelated types.
void checkMemberElement(Compiler compiler, MemberElement member) {
if (!compiler.resolution.hasBeenResolved(member)) return;
ResolvedAst resolvedAst = member.resolvedAst;
if (resolvedAst.kind == ResolvedAstKind.PARSED) {
RelatedTypesChecker relatedTypesChecker =
new RelatedTypesChecker(compiler, resolvedAst);
compiler.reporter.withCurrentElement(member.implementation, () {
relatedTypesChecker.apply(resolvedAst.node);
});
}
}
class RelatedTypesChecker
extends TraversalVisitor<ResolutionDartType, dynamic> {
final Compiler compiler;
final ResolvedAst resolvedAst;
RelatedTypesChecker(this.compiler, ResolvedAst resolvedAst)
: this.resolvedAst = resolvedAst,
super(resolvedAst.elements);
ClosedWorld get world =>
compiler.resolutionWorldBuilder.closedWorldForTesting;
CommonElements get commonElements => compiler.resolution.commonElements;
DiagnosticReporter get reporter => compiler.reporter;
ResolutionInterfaceType get thisType =>
resolvedAst.element.enclosingClass.thisType;
/// Returns `true` if there exists no common subtype of [left] and [right].
bool hasEmptyIntersection(ResolutionDartType left, ResolutionDartType right) {
if (left == right) return false;
if (left == null || right == null) return false;
ClassElement leftClass = const ClassFinder().findClass(left);
ClassElement rightClass = const ClassFinder().findClass(right);
if (leftClass != null && rightClass != null) {
return !world.haveAnyCommonSubtypes(leftClass, rightClass);
}
return false;
}
/// Checks that there exists a common subtype of [left] and [right] or report
/// a hint otherwise.
void checkRelated(
Node node, ResolutionDartType left, ResolutionDartType right) {
if (hasEmptyIntersection(left, right)) {
reporter.reportHintMessage(
node, MessageKind.NO_COMMON_SUBTYPES, {'left': left, 'right': right});
}
}
/// Check weakly typed collection methods, like `Map.containsKey`,
/// `Map.containsValue` and `Iterable.contains`.
void checkDynamicInvoke(Node node, ResolutionDartType receiverType,
List<ResolutionDartType> argumentTypes, Selector selector) {
if (selector.name == 'containsKey' &&
selector.callStructure == CallStructure.ONE_ARG) {
ResolutionInterfaceType mapType = findMapType(receiverType);
if (mapType != null) {
ResolutionDartType keyType = findMapKeyType(mapType);
checkRelated(node, keyType, argumentTypes.first);
}
} else if (selector.name == 'containsValue' &&
selector.callStructure == CallStructure.ONE_ARG) {
ResolutionInterfaceType mapType = findMapType(receiverType);
if (mapType != null) {
ResolutionDartType valueType = findMapValueType(mapType);
checkRelated(node, valueType, argumentTypes.first);
}
} else if (selector.name == 'contains' &&
selector.callStructure == CallStructure.ONE_ARG) {
ResolutionInterfaceType iterableType = findIterableType(receiverType);
if (iterableType != null) {
ResolutionDartType elementType = findIterableElementType(iterableType);
checkRelated(node, elementType, argumentTypes.first);
}
} else if (selector.name == 'remove' &&
selector.callStructure == CallStructure.ONE_ARG) {
ResolutionInterfaceType mapType = findMapType(receiverType);
if (mapType != null) {
ResolutionDartType keyType = findMapKeyType(mapType);
checkRelated(node, keyType, argumentTypes.first);
}
ResolutionInterfaceType listType = findListType(receiverType);
if (listType != null) {
ResolutionDartType valueType = findListElementType(listType);
checkRelated(node, valueType, argumentTypes.first);
}
}
}
/// Return the interface type implemented by [type] or `null` if no interface
/// type is implied by [type].
ResolutionInterfaceType findInterfaceType(ResolutionDartType type) {
return Types.computeInterfaceType(compiler.resolution, type);
}
/// Returns the supertype of [receiver] that implements [cls], if any.
ResolutionInterfaceType findClassType(
ResolutionDartType receiver, ClassElement cls) {
ResolutionInterfaceType interfaceType = findInterfaceType(receiver);
if (interfaceType == null) return null;
ResolutionInterfaceType mapType = interfaceType.asInstanceOf(cls);
if (mapType == null) return null;
return mapType;
}
/// Returns the supertype of [receiver] that implements `Iterable`, if any.
ResolutionInterfaceType findIterableType(ResolutionDartType receiver) {
return findClassType(receiver, commonElements.iterableClass);
}
/// Returns the element type of the supertype of [receiver] that implements
/// `Iterable`, if any.
ResolutionDartType findIterableElementType(
ResolutionInterfaceType iterableType) {
if (iterableType == null) return null;
return iterableType.typeArguments[0];
}
/// Returns the supertype of [receiver] that implements `Map`, if any.
ResolutionInterfaceType findMapType(ResolutionDartType receiver) {
return findClassType(receiver, commonElements.mapClass);
}
/// Returns the key type of the supertype of [receiver] that implements
/// `Map`, if any.
ResolutionDartType findMapKeyType(ResolutionInterfaceType mapType) {
if (mapType == null) return null;
return mapType.typeArguments[0];
}
/// Returns the value type of the supertype of [receiver] that implements
/// `Map`, if any.
ResolutionDartType findMapValueType(ResolutionInterfaceType mapType) {
if (mapType == null) return null;
return mapType.typeArguments[1];
}
/// Returns the supertype of [receiver] that implements `List`, if any.
ResolutionInterfaceType findListType(ResolutionDartType receiver) {
return findClassType(receiver, commonElements.listClass);
}
/// Returns the element type of the supertype of [receiver] that implements
/// `List`, if any.
ResolutionDartType findListElementType(ResolutionInterfaceType listType) {
if (listType == null) return null;
return listType.typeArguments[0];
}
/// Returns the implied return type of [type] or `dynamic` if no return type
/// is implied.
ResolutionDartType findReturnType(ResolutionDartType type) {
if (type is ResolutionFunctionType) {
return type.returnType;
}
return const ResolutionDynamicType();
}
/// Visits [arguments] and returns the list of their corresponding types.
List<ResolutionDartType> findArgumentTypes(NodeList arguments) {
List<ResolutionDartType> argumentTypes = <ResolutionDartType>[];
for (Node argument in arguments) {
argumentTypes.add(apply(argument));
}
return argumentTypes;
}
/// Finds the [MemberSignature] of the [name] property on [type], if any.
MemberSignature lookupInterfaceMember(ResolutionDartType type, Name name) {
ResolutionInterfaceType interfaceType = findInterfaceType(type);
if (interfaceType == null) return null;
return interfaceType.lookupInterfaceMember(name);
}
/// Returns the type of an access of the [name] property on [type], or
/// `dynamic` if no property was found.
ResolutionDartType lookupInterfaceMemberAccessType(
ResolutionDartType type, Name name) {
MemberSignature member = lookupInterfaceMember(type, name);
if (member == null) return const ResolutionDynamicType();
return member.type;
}
/// Returns the function type of the [name] property on [type], or
/// `dynamic` if no property was found.
ResolutionFunctionType lookupInterfaceMemberInvocationType(
ResolutionDartType type, Name name) {
MemberSignature member = lookupInterfaceMember(type, name);
if (member == null) return null;
return member.functionType;
}
ResolutionDartType apply(Node node, [_]) {
ResolutionDartType type = node.accept(this);
if (type == null) {
type = const ResolutionDynamicType();
}
return type;
}
@override
ResolutionInterfaceType visitEquals(Send node, Node left, Node right, _) {
ResolutionDartType leftType = apply(left);
ResolutionDartType rightType = apply(right);
checkRelated(node, leftType, rightType);
return commonElements.boolType;
}
@override
ResolutionInterfaceType visitNotEquals(Send node, Node left, Node right, _) {
ResolutionDartType leftType = apply(left);
ResolutionDartType rightType = apply(right);
checkRelated(node, leftType, rightType);
return commonElements.boolType;
}
@override
ResolutionDartType visitIndex(Send node, Node receiver, Node index, _) {
ResolutionDartType receiverType = apply(receiver);
ResolutionDartType indexType = apply(index);
ResolutionInterfaceType mapType = findMapType(receiverType);
ResolutionDartType keyType = findMapKeyType(mapType);
ResolutionDartType valueType = findMapValueType(mapType);
checkRelated(index, keyType, indexType);
return valueType;
}
@override
ResolutionInterfaceType visitLiteralInt(LiteralInt node) {
return commonElements.intType;
}
@override
ResolutionInterfaceType visitLiteralString(LiteralString node) {
return commonElements.stringType;
}
@override
ResolutionInterfaceType visitLiteralBool(LiteralBool node) {
return commonElements.boolType;
}
@override
ResolutionDartType visitLiteralMap(LiteralMap node) {
return elements.getType(node);
}
@override
ResolutionDartType visitLiteralList(LiteralList node) {
return elements.getType(node);
}
@override
ResolutionDartType visitLiteralNull(LiteralNull node) {
return elements.getType(node);
}
@override
ResolutionDartType visitLocalVariableGet(
Send node, LocalVariableElement variable, _) {
return variable.type;
}
@override
ResolutionDartType visitLocalFunctionGet(
Send node, LocalFunctionElement function, _) {
return function.type;
}
@override
ResolutionDartType visitParameterGet(
Send node, ParameterElement parameter, _) {
return parameter.type;
}
@override
ResolutionDartType visitThisPropertyGet(Send node, Name name, _) {
return lookupInterfaceMemberAccessType(thisType, name);
}
@override
ResolutionDartType visitDynamicPropertyGet(
Send node, Node receiver, Name name, _) {
ResolutionDartType receiverType = apply(receiver);
return lookupInterfaceMemberAccessType(receiverType, name);
}
@override
ResolutionDartType visitIfNotNullDynamicPropertyGet(
Send node, Node receiver, Name name, _) {
ResolutionDartType receiverType = apply(receiver);
return lookupInterfaceMemberAccessType(receiverType, name);
}
@override
ResolutionDartType visitStaticFieldGet(Send node, FieldElement field, _) {
return field.type;
}
@override
ResolutionDartType visitTopLevelFieldGet(Send node, FieldElement field, _) {
return field.type;
}
@override
ResolutionDartType visitDynamicPropertyInvoke(
Send node, Node receiver, NodeList arguments, Selector selector, _) {
ResolutionDartType receiverType = apply(receiver);
List<ResolutionDartType> argumentTypes = findArgumentTypes(arguments);
ResolutionFunctionType methodType =
lookupInterfaceMemberInvocationType(receiverType, selector.memberName);
checkDynamicInvoke(node, receiverType, argumentTypes, selector);
return findReturnType(methodType);
}
@override
ResolutionDartType visitThisPropertyInvoke(
Send node, NodeList arguments, Selector selector, _) {
ResolutionDartType receiverType = thisType;
List<ResolutionDartType> argumentTypes = findArgumentTypes(arguments);
ResolutionFunctionType methodType =
lookupInterfaceMemberInvocationType(receiverType, selector.memberName);
checkDynamicInvoke(node, receiverType, argumentTypes, selector);
return findReturnType(methodType);
}
@override
ResolutionDartType visitIfNotNullDynamicPropertyInvoke(
Send node, Node receiver, NodeList arguments, Selector selector, _) {
ResolutionDartType receiverType = apply(receiver);
List<ResolutionDartType> argumentTypes = findArgumentTypes(arguments);
ResolutionFunctionType methodType =
lookupInterfaceMemberInvocationType(receiverType, selector.memberName);
checkDynamicInvoke(node, receiverType, argumentTypes, selector);
return findReturnType(methodType);
}
@override
ResolutionDartType visitTopLevelFunctionInvoke(
Send node,
MethodElement function,
NodeList arguments,
CallStructure callStructure,
_) {
apply(arguments);
return findReturnType(function.type);
}
@override
ResolutionDartType visitStaticFunctionInvoke(
Send node,
MethodElement function,
NodeList arguments,
CallStructure callStructure,
_) {
apply(arguments);
return findReturnType(function.type);
}
}
/// Computes the [ClassElement] implied by a type.
// TODO(johnniwinther): Handle type variables, function types and typedefs.
class ClassFinder extends BaseResolutionDartTypeVisitor<ClassElement, dynamic> {
const ClassFinder();
ClassElement findClass(ResolutionDartType type) => type.accept(this, null);
@override
ClassElement visitType(ResolutionDartType type, _) => null;
@override
ClassElement visitInterfaceType(ResolutionInterfaceType type, _) {
return type.element;
}
}