| // 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/core_types.dart'; |
| import 'package:compiler/src/dart_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/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) { |
| 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((MemberElement 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; |
| RelatedTypesChecker relatedTypesChecker = |
| new RelatedTypesChecker(compiler, resolvedAst); |
| if (resolvedAst.node != null) { |
| compiler.reporter.withCurrentElement(member.implementation, () { |
| relatedTypesChecker.apply(resolvedAst.node); |
| }); |
| } |
| } |
| |
| class RelatedTypesChecker extends TraversalVisitor<DartType, dynamic> { |
| final Compiler compiler; |
| final ResolvedAst resolvedAst; |
| |
| RelatedTypesChecker(this.compiler, ResolvedAst resolvedAst) |
| : this.resolvedAst = resolvedAst, |
| super(resolvedAst.elements); |
| |
| ClassWorld get world => compiler.world; |
| |
| CoreClasses get coreClasses => compiler.coreClasses; |
| |
| CoreTypes get coreTypes => compiler.coreTypes; |
| |
| DiagnosticReporter get reporter => compiler.reporter; |
| |
| InterfaceType get thisType => resolvedAst.element.enclosingClass.thisType; |
| |
| /// Returns `true` if there exists no common subtype of [left] and [right]. |
| bool hasEmptyIntersection(DartType left, DartType 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, DartType left, DartType 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, |
| DartType receiverType, |
| List<DartType> argumentTypes, |
| Selector selector) { |
| if (selector.name == 'containsKey' && |
| selector.callStructure == CallStructure.ONE_ARG) { |
| InterfaceType mapType = findMapType(receiverType); |
| if (mapType != null) { |
| DartType keyType = findMapKeyType(mapType); |
| checkRelated(node, keyType, argumentTypes.first); |
| } |
| } else if (selector.name == 'containsValue' && |
| selector.callStructure == CallStructure.ONE_ARG) { |
| InterfaceType mapType = findMapType(receiverType); |
| if (mapType != null) { |
| DartType valueType = findMapValueType(mapType); |
| checkRelated(node, valueType, argumentTypes.first); |
| } |
| } else if (selector.name == 'contains' && |
| selector.callStructure == CallStructure.ONE_ARG) { |
| InterfaceType iterableType = findIterableType(receiverType); |
| if (iterableType != null) { |
| DartType elementType = findIterableElementType(iterableType); |
| checkRelated(node, elementType, argumentTypes.first); |
| } |
| } else if (selector.name == 'remove' && |
| selector.callStructure == CallStructure.ONE_ARG) { |
| InterfaceType mapType = findMapType(receiverType); |
| if (mapType != null) { |
| DartType keyType = findMapKeyType(mapType); |
| checkRelated(node, keyType, argumentTypes.first); |
| } |
| InterfaceType listType = findListType(receiverType); |
| if (listType != null) { |
| DartType 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]. |
| InterfaceType findInterfaceType(DartType type) { |
| return Types.computeInterfaceType(compiler.resolution, type); |
| } |
| |
| /// Returns the supertype of [receiver] that implements [cls], if any. |
| InterfaceType findClassType(DartType receiver, ClassElement cls) { |
| InterfaceType interfaceType = findInterfaceType(receiver); |
| if (interfaceType == null) return null; |
| InterfaceType mapType = interfaceType.asInstanceOf(cls); |
| if (mapType == null) return null; |
| return mapType; |
| } |
| |
| /// Returns the supertype of [receiver] that implements `Iterable`, if any. |
| InterfaceType findIterableType(DartType receiver) { |
| return findClassType(receiver, coreClasses.iterableClass); |
| } |
| |
| /// Returns the element type of the supertype of [receiver] that implements |
| /// `Iterable`, if any. |
| DartType findIterableElementType(InterfaceType iterableType) { |
| if (iterableType == null) return null; |
| return iterableType.typeArguments[0]; |
| } |
| |
| /// Returns the supertype of [receiver] that implements `Map`, if any. |
| InterfaceType findMapType(DartType receiver) { |
| return findClassType(receiver, coreClasses.mapClass); |
| } |
| |
| /// Returns the key type of the supertype of [receiver] that implements |
| /// `Map`, if any. |
| DartType findMapKeyType(InterfaceType mapType) { |
| if (mapType == null) return null; |
| return mapType.typeArguments[0]; |
| } |
| |
| /// Returns the value type of the supertype of [receiver] that implements |
| /// `Map`, if any. |
| DartType findMapValueType(InterfaceType mapType) { |
| if (mapType == null) return null; |
| return mapType.typeArguments[1]; |
| } |
| |
| /// Returns the supertype of [receiver] that implements `List`, if any. |
| InterfaceType findListType(DartType receiver) { |
| return findClassType(receiver, coreClasses.listClass); |
| } |
| |
| /// Returns the element type of the supertype of [receiver] that implements |
| /// `List`, if any. |
| DartType findListElementType(InterfaceType 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. |
| DartType findReturnType(DartType type) { |
| if (type is FunctionType) { |
| return type.returnType; |
| } |
| return const DynamicType(); |
| } |
| |
| /// Visits [arguments] and returns the list of their corresponding types. |
| List<DartType> findArgumentTypes(NodeList arguments) { |
| List<DartType> argumentTypes = <DartType>[]; |
| for (Node argument in arguments) { |
| argumentTypes.add(apply(argument)); |
| } |
| return argumentTypes; |
| } |
| |
| /// Finds the [MemberSignature] of the [name] property on [type], if any. |
| MemberSignature lookupInterfaceMember(DartType type, Name name) { |
| InterfaceType 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. |
| DartType lookupInterfaceMemberAccessType(DartType type, Name name) { |
| MemberSignature member = lookupInterfaceMember(type, name); |
| if (member == null) return const DynamicType(); |
| return member.type; |
| } |
| |
| /// Returns the function type of the [name] property on [type], or |
| /// `dynamic` if no property was found. |
| FunctionType lookupInterfaceMemberInvocationType(DartType type, Name name) { |
| MemberSignature member = lookupInterfaceMember(type, name); |
| if (member == null) return null; |
| return member.functionType; |
| } |
| |
| DartType apply(Node node, [_]) { |
| DartType type = node.accept(this); |
| if (type == null) { |
| type = const DynamicType(); |
| } |
| return type; |
| } |
| |
| @override |
| DartType visitEquals(Send node, Node left, Node right, _) { |
| DartType leftType = apply(left); |
| DartType rightType = apply(right); |
| checkRelated(node, leftType, rightType); |
| return coreTypes.boolType; |
| } |
| |
| @override |
| DartType visitNotEquals(Send node, Node left, Node right, _) { |
| DartType leftType = apply(left); |
| DartType rightType = apply(right); |
| checkRelated(node, leftType, rightType); |
| return coreTypes.boolType; |
| } |
| |
| @override |
| DartType visitIndex(Send node, Node receiver, Node index, _) { |
| DartType receiverType = apply(receiver); |
| DartType indexType = apply(index); |
| InterfaceType mapType = findMapType(receiverType); |
| DartType keyType = findMapKeyType(mapType); |
| DartType valueType = findMapValueType(mapType); |
| checkRelated(index, keyType, indexType); |
| return valueType; |
| } |
| |
| @override |
| DartType visitLiteralInt(LiteralInt node) { |
| return coreTypes.intType; |
| } |
| |
| @override |
| DartType visitLiteralString(LiteralString node) { |
| return coreTypes.stringType; |
| } |
| |
| @override |
| DartType visitLiteralBool(LiteralBool node) { |
| return coreTypes.boolType; |
| } |
| |
| @override |
| DartType visitLiteralMap(LiteralMap node) { |
| return elements.getType(node); |
| } |
| |
| @override |
| DartType visitLiteralList(LiteralList node) { |
| return elements.getType(node); |
| } |
| |
| @override |
| DartType visitLiteralNull(LiteralNull node) { |
| return elements.getType(node); |
| } |
| |
| @override |
| DartType visitLocalVariableGet(Send node, LocalVariableElement variable, _) { |
| return variable.type; |
| } |
| |
| @override |
| DartType visitLocalFunctionGet(Send node, LocalFunctionElement function, _) { |
| return function.type; |
| } |
| |
| @override |
| DartType visitParameterGet(Send node, ParameterElement parameter, _) { |
| return parameter.type; |
| } |
| |
| @override |
| DartType visitThisPropertyGet(Send node, Name name, _) { |
| return lookupInterfaceMemberAccessType(thisType, name); |
| } |
| |
| @override |
| DartType visitDynamicPropertyGet(Send node, Node receiver, Name name, _) { |
| DartType receiverType = apply(receiver); |
| return lookupInterfaceMemberAccessType(receiverType, name); |
| } |
| |
| @override |
| DartType visitIfNotNullDynamicPropertyGet( |
| Send node, Node receiver, Name name, _) { |
| DartType receiverType = apply(receiver); |
| return lookupInterfaceMemberAccessType(receiverType, name); |
| } |
| |
| @override |
| DartType visitStaticFieldGet(Send node, FieldElement field, _) { |
| return field.type; |
| } |
| |
| @override |
| DartType visitTopLevelFieldGet(Send node, FieldElement field, _) { |
| return field.type; |
| } |
| |
| @override |
| DartType visitDynamicPropertyInvoke( |
| Send node, |
| Node receiver, |
| NodeList arguments, |
| Selector selector, _) { |
| DartType receiverType = apply(receiver); |
| List<DartType> argumentTypes = findArgumentTypes(arguments); |
| FunctionType methodType = lookupInterfaceMemberInvocationType( |
| receiverType, selector.memberName); |
| checkDynamicInvoke(node, receiverType, argumentTypes, selector); |
| return findReturnType(methodType); |
| } |
| |
| @override |
| DartType visitThisPropertyInvoke( |
| Send node, |
| NodeList arguments, |
| Selector selector, _) { |
| DartType receiverType = thisType; |
| List<DartType> argumentTypes = findArgumentTypes(arguments); |
| FunctionType methodType = lookupInterfaceMemberInvocationType( |
| receiverType, selector.memberName); |
| checkDynamicInvoke(node, receiverType, argumentTypes, selector); |
| return findReturnType(methodType); |
| } |
| |
| @override |
| DartType visitIfNotNullDynamicPropertyInvoke( |
| Send node, |
| Node receiver, |
| NodeList arguments, |
| Selector selector, _) { |
| DartType receiverType = apply(receiver); |
| List<DartType> argumentTypes = findArgumentTypes(arguments); |
| FunctionType methodType = lookupInterfaceMemberInvocationType( |
| receiverType, selector.memberName); |
| checkDynamicInvoke(node, receiverType, argumentTypes, selector); |
| return findReturnType(methodType); |
| } |
| |
| @override |
| DartType visitTopLevelFunctionInvoke( |
| Send node, |
| MethodElement function, |
| NodeList arguments, |
| CallStructure callStructure, _) { |
| apply(arguments); |
| return findReturnType(function.type); |
| } |
| |
| @override |
| DartType 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 BaseDartTypeVisitor<ClassElement, dynamic> { |
| const ClassFinder(); |
| |
| ClassElement findClass(DartType type) => type.accept(this, null); |
| |
| @override |
| ClassElement visitType(DartType type, _) => null; |
| |
| @override |
| ClassElement visitInterfaceType(InterfaceType type, _) { |
| return type.element; |
| } |
| } |