| // Copyright (c) 2016, 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 'dart:convert'; |
| import 'dart:io'; |
| |
| import 'package:analyzer/src/generated/utilities_dart.dart'; |
| import 'package:analyzer/src/summary/base.dart'; |
| import 'package:analyzer/src/summary/idl.dart'; |
| |
| /** |
| * Collect the inferred types from all the summary files listed in [args] and |
| * print them in alphabetical order. |
| */ |
| main(List<String> args) { |
| InferredTypeCollector collector = new InferredTypeCollector(); |
| for (String arg in args) { |
| PackageBundle bundle = |
| new PackageBundle.fromBuffer(new File(arg).readAsBytesSync()); |
| collector.visitPackageBundle(bundle, arg); |
| } |
| collector.dumpLibraryIndex(); |
| collector.dumpPartIndex(); |
| collector.dumpCollectedTypes(); |
| } |
| |
| /** |
| * Visitor class that visits the contents of a summary file and collects the |
| * inferred types in it. |
| */ |
| class InferredTypeCollector { |
| UnlinkedUnit unlinkedUnit; |
| LinkedUnit linkedUnit; |
| final Map<String, String> inferredTypes = <String, String>{}; |
| List<String> typeParamsInScope = <String>[]; |
| final Map<String, Set<String>> libraryIndex = <String, Set<String>>{}; |
| final Map<String, Set<String>> partIndex = <String, Set<String>>{}; |
| |
| /** |
| * If an inferred type exists matching the given [slot], record that it is the |
| * type of the entity reachable via [path]. |
| */ |
| void collectInferredType(int slot, String path) { |
| for (EntityRef type in linkedUnit.types) { |
| if (type.slot == slot) { |
| inferredTypes[path] = formatType(type); |
| return; |
| } |
| } |
| } |
| |
| /** |
| * Collect the inferred type in summary object [obj] (if any), which is |
| * reachable via [path]. |
| * |
| * This method may modify [properties] in order to affect how sub-elements |
| * are visited. |
| */ |
| void collectInferredTypes( |
| SummaryClass obj, Map<String, Object> properties, String path) { |
| if (obj is UnlinkedVariable) { |
| collectInferredType(obj.inferredTypeSlot, path); |
| // As a temporary measure, prevent recursion into the variable's |
| // initializer, since AST-based type inference doesn't infer its type |
| // correctly yet. TODO(paulberry): fix. |
| properties.remove('initializer'); |
| } else if (obj is UnlinkedExecutable) { |
| collectInferredType(obj.inferredReturnTypeSlot, path); |
| // As a temporary measure, prevent recursion into the executable's local |
| // variables and local functions, since AST-based type inference doesn't |
| // infer locals correctly yet. TODO(paulberry): fix if necessary. |
| properties.remove('localFunctions'); |
| properties.remove('localVariables'); |
| } else if (obj is UnlinkedParam) { |
| collectInferredType(obj.inferredTypeSlot, path); |
| // As a temporary measure, prevent recursion into the parameter's |
| // initializer, since AST-based type inference doesn't infer its type |
| // correctly yet. TODO(paulberry): fix. |
| properties.remove('initializer'); |
| } |
| } |
| |
| /** |
| * Print out all the inferred types collected so far, in alphabetical order. |
| */ |
| void dumpCollectedTypes() { |
| print('Collected types (${inferredTypes.length}):'); |
| List<String> paths = inferredTypes.keys.toList(); |
| paths.sort(); |
| for (String path in paths) { |
| print('$path -> ${inferredTypes[path]}'); |
| } |
| } |
| |
| /** |
| * Print out an index mapping library names to the summary files containing |
| * them. |
| */ |
| void dumpLibraryIndex() { |
| print('Library index:'); |
| List<String> libraryNames = libraryIndex.keys.toList(); |
| libraryNames.sort(); |
| for (String libraryName in libraryNames) { |
| List<String> summaryFiles = libraryIndex[libraryName].toList(); |
| summaryFiles.sort(); |
| print('$libraryName -> ${summaryFiles.join(', ')}'); |
| } |
| print(''); |
| } |
| |
| /** |
| * Print out an index mapping part file names to the summary files containing |
| * them. |
| */ |
| void dumpPartIndex() { |
| print('Part index:'); |
| List<String> partNames = partIndex.keys.toList(); |
| partNames.sort(); |
| for (String partName in partNames) { |
| List<String> summaryFiles = partIndex[partName].toList(); |
| summaryFiles.sort(); |
| print('$partName -> ${summaryFiles.join(', ')}'); |
| } |
| print(''); |
| } |
| |
| /** |
| * Interpret the given [param] as a parameter in a synthetic typedef, and |
| * format it as a string. |
| */ |
| String formatParam(UnlinkedParam param) { |
| if (param.isFunctionTyped) { |
| // TODO(paulberry): fix this case. |
| return 'BAD(${JSON.encode(param)})'; |
| } |
| String result; |
| if (param.type != null) { |
| result = '${formatType(param.type)} ${param.name}'; |
| } else { |
| result = param.name; |
| } |
| if (param.kind == UnlinkedParamKind.named) { |
| result = '{$result}'; |
| } else if (param.kind == UnlinkedParamKind.positional) { |
| result = '[$result]'; |
| } |
| return result; |
| } |
| |
| /** |
| * Convert the reference with index [index] into a string. If [typeOf] is |
| * `true`, the reference is being used in the context of naming a type, so |
| * if the entity being referenced is not a type, it will be enclosed in |
| * `typeof()` for clarity. |
| */ |
| String formatReference(int index, {bool typeOf: false}) { |
| LinkedReference linkedRef = linkedUnit.references[index]; |
| switch (linkedRef.kind) { |
| case ReferenceKind.classOrEnum: |
| case ReferenceKind.function: |
| case ReferenceKind.propertyAccessor: |
| case ReferenceKind.topLevelFunction: |
| case ReferenceKind.method: |
| case ReferenceKind.typedef: |
| case ReferenceKind.prefix: |
| case ReferenceKind.topLevelPropertyAccessor: |
| break; |
| default: |
| // TODO(paulberry): fix this case. |
| return 'BAD(${JSON.encode(linkedRef.toJson())})'; |
| } |
| int containingReference; |
| String name; |
| if (index < unlinkedUnit.references.length) { |
| containingReference = unlinkedUnit.references[index].prefixReference; |
| name = unlinkedUnit.references[index].name; |
| } else { |
| containingReference = linkedRef.containingReference; |
| name = linkedRef.name; |
| } |
| String result; |
| if (containingReference != 0) { |
| result = '${formatReference(containingReference)}.$name'; |
| } else { |
| result = name; |
| } |
| if (linkedRef.kind == ReferenceKind.function) { |
| assert(name.isEmpty); |
| result += 'localFunction[${linkedRef.localIndex}]'; |
| } |
| if (!typeOf || |
| linkedRef.kind == ReferenceKind.classOrEnum || |
| linkedRef.kind == ReferenceKind.typedef) { |
| return result; |
| } else { |
| return 'typeof($result)'; |
| } |
| } |
| |
| /** |
| * Interpret the given [entityRef] as a reference to a type, and format it as |
| * a string. |
| */ |
| String formatType(EntityRef entityRef) { |
| List<int> implicitFunctionTypeIndices = |
| entityRef.implicitFunctionTypeIndices; |
| if (entityRef.syntheticReturnType != null) { |
| String params = entityRef.syntheticParams.map(formatParam).join(', '); |
| String retType = formatType(entityRef.syntheticReturnType); |
| return '($params) -> $retType'; |
| } |
| if (entityRef.paramReference != 0) { |
| return typeParamsInScope[ |
| typeParamsInScope.length - entityRef.paramReference]; |
| } |
| String result = formatReference(entityRef.reference, typeOf: true); |
| List<EntityRef> typeArguments = entityRef.typeArguments.toList(); |
| while (typeArguments.isNotEmpty && isDynamic(typeArguments.last)) { |
| typeArguments.removeLast(); |
| } |
| if (typeArguments.isNotEmpty) { |
| result += '<${typeArguments.map(formatType).join(', ')}>'; |
| } |
| if (implicitFunctionTypeIndices.isNotEmpty) { |
| result = |
| 'parameterOf($result, ${implicitFunctionTypeIndices.join(', ')})'; |
| } |
| return result; |
| } |
| |
| /** |
| * Determine if the given [entityRef] represents the pseudo-type `dynamic`. |
| */ |
| bool isDynamic(EntityRef entityRef) { |
| if (entityRef.syntheticReturnType != null || |
| entityRef.paramReference != 0) { |
| return false; |
| } |
| return formatReference(entityRef.reference, typeOf: true) == 'dynamic'; |
| } |
| |
| /** |
| * Collect all the inferred types contained in [obj], which is reachable via |
| * [path]. [properties] is the result of calling `obj.toMap()`, and may be |
| * modified before returning. |
| */ |
| void visit(SummaryClass obj, Map<String, Object> properties, String path) { |
| List<String> oldTypeParamsInScope = typeParamsInScope; |
| Object newTypeParams = properties['typeParameters']; |
| if (newTypeParams is List && newTypeParams.isNotEmpty) { |
| typeParamsInScope = typeParamsInScope.toList(); |
| for (Object typeParam in newTypeParams) { |
| if (typeParam is UnlinkedTypeParam) { |
| typeParamsInScope.add(typeParam.name); |
| } else { |
| throw new StateError( |
| 'Unexpected type param type: ${typeParam.runtimeType}'); |
| } |
| } |
| } |
| collectInferredTypes(obj, properties, path); |
| properties.forEach((String key, Object value) { |
| if (value is SummaryClass) { |
| visit(value, value.toMap(), '$path.$key'); |
| } else if (value is List) { |
| for (int i = 0; i < value.length; i++) { |
| Object item = value[i]; |
| if (item is SummaryClass) { |
| Map<String, Object> itemProperties = item.toMap(); |
| String indexOrName = itemProperties['name'] ?? i.toString(); |
| visit(item, itemProperties, '$path.$key[$indexOrName]'); |
| } |
| } |
| } |
| }); |
| typeParamsInScope = oldTypeParamsInScope; |
| } |
| |
| /** |
| * Collect all the inferred types contained in [bundle]. |
| */ |
| void visitPackageBundle(PackageBundle bundle, String summaryPath) { |
| Map<String, LinkedLibrary> linkedLibraries = <String, LinkedLibrary>{}; |
| Map<String, UnlinkedUnit> unlinkedUnits = <String, UnlinkedUnit>{}; |
| for (int i = 0; i < bundle.linkedLibraryUris.length; i++) { |
| linkedLibraries[bundle.linkedLibraryUris[i]] = bundle.linkedLibraries[i]; |
| } |
| for (int i = 0; i < bundle.unlinkedUnitUris.length; i++) { |
| String unitUriString = bundle.unlinkedUnitUris[i]; |
| partIndex |
| .putIfAbsent(unitUriString, () => new Set<String>()) |
| .add(summaryPath); |
| unlinkedUnits[unitUriString] = bundle.unlinkedUnits[i]; |
| } |
| // Figure out which unlinked units are a part of another library so we won't |
| // visit them redundantly. |
| Set<String> partOfUris = new Set<String>(); |
| unlinkedUnits.forEach((String unitUriString, UnlinkedUnit unlinkedUnit) { |
| Uri unitUri = Uri.parse(unitUriString); |
| for (String relativePartUriString in unlinkedUnit.publicNamespace.parts) { |
| partOfUris.add( |
| resolveRelativeUri(unitUri, Uri.parse(relativePartUriString)) |
| .toString()); |
| } |
| }); |
| linkedLibraries |
| .forEach((String libraryUriString, LinkedLibrary linkedLibrary) { |
| if (partOfUris.contains(libraryUriString)) { |
| return; |
| } |
| libraryIndex |
| .putIfAbsent(libraryUriString, () => new Set<String>()) |
| .add(summaryPath); |
| Uri libraryUri = Uri.parse(libraryUriString); |
| UnlinkedUnit definingUnlinkedUnit = unlinkedUnits[libraryUriString]; |
| if (definingUnlinkedUnit != null) { |
| visitUnit( |
| definingUnlinkedUnit, linkedLibrary.units[0], libraryUriString); |
| for (int i = 0; |
| i < definingUnlinkedUnit.publicNamespace.parts.length; |
| i++) { |
| Uri relativePartUri = |
| Uri.parse(definingUnlinkedUnit.publicNamespace.parts[i]); |
| String unitUriString = |
| resolveRelativeUri(libraryUri, relativePartUri).toString(); |
| UnlinkedUnit unlinkedUnit = unlinkedUnits[unitUriString]; |
| if (unlinkedUnit != null) { |
| visitUnit( |
| unlinkedUnit, linkedLibrary.units[i + 1], libraryUriString); |
| } |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Collect all the inferred types contained in the compilation unit described |
| * by [unlinkedUnit] and [linkedUnit], which has URI [libraryUriString]. |
| */ |
| void visitUnit(UnlinkedUnit unlinkedUnit, LinkedUnit linkedUnit, |
| String libraryUriString) { |
| this.unlinkedUnit = unlinkedUnit; |
| this.linkedUnit = linkedUnit; |
| visit(unlinkedUnit, unlinkedUnit.toMap(), libraryUriString); |
| this.unlinkedUnit = null; |
| this.linkedUnit = null; |
| } |
| } |