blob: a964675ff922dcb50784b50a4f297820e4cf3cee [file] [log] [blame]
// 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;
}
}