blob: e0eec046f1ba03dbcd59e67cebb000573229a77d [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 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/generated/utilities_dart.dart';
import 'package:analyzer/src/summary/base.dart';
import 'package:analyzer/src/summary/idl.dart';
import 'package:analyzer/src/summary/link.dart';
import 'package:analyzer/src/summary/package_bundle_reader.dart';
/**
* Collect the inferred types from all the summary files listed in [args] and
* print them in alphabetical order.
*/
main(List<String> args) {
SummaryDataStore summaryDataStore = new SummaryDataStore(args);
InferredTypeCollector collector = new InferredTypeCollector(
(String uri) => summaryDataStore.linkedMap[uri],
(String uri) => summaryDataStore.unlinkedMap[uri]);
collector.visitSummaryDataStore(summaryDataStore);
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;
CompilationUnitElementForLink unitForLink;
final Map<String, String> inferredTypes = <String, String>{};
List<String> typeParamsInScope = <String>[];
final Linker _linker;
InferredTypeCollector(
GetDependencyCallback getDependency, GetUnitCallback getUnit)
: _linker = new Linker({}, getDependency, getUnit);
/**
* 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]}');
}
}
/**
* Format the given [type] as a string. Unlike the type's [toString] method,
* this formats types using their complete URI to avoid ambiguity.
*/
String formatDartType(DartType type) {
if (type is FunctionType) {
List<String> argStrings =
type.normalParameterTypes.map(formatDartType).toList();
List<DartType> optionalParameterTypes = type.optionalParameterTypes;
if (optionalParameterTypes.isNotEmpty) {
List<String> optionalArgStrings =
optionalParameterTypes.map(formatDartType).toList();
argStrings.add('[${optionalArgStrings.join(', ')}]');
}
Map<String, DartType> namedParameterTypes = type.namedParameterTypes;
if (namedParameterTypes.isNotEmpty) {
List<String> namedArgStrings = <String>[];
namedParameterTypes.forEach((String name, DartType type) {
namedArgStrings.add('$name: ${formatDartType(type)}');
});
argStrings.add('{${namedArgStrings.join(', ')}}');
}
return '(${argStrings.join(', ')}) → ${formatDartType(type.returnType)}';
} else if (type is InterfaceType) {
if (type.typeArguments.isNotEmpty) {
// TODO(paulberry): implement.
throw new UnimplementedError('type args');
}
return formatElement(type.element);
} else if (type is DynamicTypeImpl) {
return type.toString();
} else {
// TODO(paulberry): implement.
throw new UnimplementedError(
"Don't know how to format type of type ${type.runtimeType}");
}
}
/**
* Format the given [element] as a string, assuming it represents a type.
* Unlike the element's [toString] method, this formats elements using their
* complete URI to avoid ambiguity.
*/
String formatElement(Element element) {
if (element is ClassElementForLink_Class ||
element is MethodElementForLink ||
element is ClassElementForLink_Enum ||
element is SpecialTypeElementForLink ||
element is FunctionTypeAliasElementForLink ||
element is TopLevelFunctionElementForLink) {
return element.toString();
} else if (element is FunctionElementForLink_Local_NonSynthetic) {
return formatDartType(element.type);
} else if (element is UndefinedElementForLink) {
return '???';
} else {
throw new UnimplementedError(
"Don't know how to format reference of type ${element.runtimeType}");
}
}
/**
* 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}) {
ReferenceableElementForLink element = unitForLink.resolveRef(index);
return formatElement(element);
}
/**
* 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 [summaryDataStore].
*/
void visitSummaryDataStore(SummaryDataStore summaryDataStore) {
// Figure out which unlinked units are a part of another library so we won't
// visit them redundantly.
Set<String> partOfUris = new Set<String>();
summaryDataStore.unlinkedMap
.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());
}
});
summaryDataStore.linkedMap
.forEach((String libraryUriString, LinkedLibrary linkedLibrary) {
if (partOfUris.contains(libraryUriString)) {
return;
}
if (libraryUriString.startsWith('dart:')) {
// Don't bother dumping inferred types from the SDK.
return;
}
Uri libraryUri = Uri.parse(libraryUriString);
UnlinkedUnit definingUnlinkedUnit =
summaryDataStore.unlinkedMap[libraryUriString];
if (definingUnlinkedUnit != null) {
visitUnit(
definingUnlinkedUnit, linkedLibrary.units[0], libraryUriString, 0);
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 =
summaryDataStore.unlinkedMap[unitUriString];
if (unlinkedUnit != null) {
visitUnit(unlinkedUnit, linkedLibrary.units[i + 1],
libraryUriString, i + 1);
}
}
}
});
}
/**
* 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, int unitNum) {
this.unlinkedUnit = unlinkedUnit;
this.linkedUnit = linkedUnit;
this.unitForLink =
_linker.getLibrary(Uri.parse(libraryUriString)).units[unitNum];
visit(unlinkedUnit, unlinkedUnit.toMap(), libraryUriString);
this.unlinkedUnit = null;
this.linkedUnit = null;
this.unitForLink = null;
}
}