blob: 0bb51ac842bba942fd84f2d6bfaba3d1fe1589c5 [file] [log] [blame]
// Copyright (c) 2018, 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' show json;
import 'package:kernel/ast.dart'
show
BoolConstant,
BottomType,
Class,
Constant,
ConstantMapEntry,
DartType,
DoubleConstant,
DynamicType,
Field,
FunctionType,
InvalidType,
InstanceConstant,
IntConstant,
InterfaceType,
Library,
ListConstant,
MapConstant,
NullConstant,
PartialInstantiationConstant,
Procedure,
SetConstant,
StringConstant,
SymbolConstant,
TearOffConstant,
TreeNode,
TypedefType,
TypeLiteralConstant,
TypeParameter,
TypeParameterType,
UnevaluatedConstant,
VoidType;
import 'package:kernel/visitor.dart' show ConstantVisitor, DartTypeVisitor;
import '../blacklisted_classes.dart' show blacklistedCoreClasses;
import '../fasta_codes.dart'
show Message, templateTypeOrigin, templateTypeOriginWithFileUri;
import '../problems.dart' show unsupported;
/// A pretty-printer for Kernel types and constants with the ability to label
/// raw types with numeric markers in Dart comments (e.g. `/*1*/`) to
/// distinguish different types with the same name. This is used in diagnostic
/// messages to indicate the origins of types occurring in the message.
class TypeLabeler implements DartTypeVisitor<void>, ConstantVisitor<void> {
List<LabeledNode> names = <LabeledNode>[];
Map<String, List<LabeledNode>> nameMap = <String, List<LabeledNode>>{};
List<Object> result;
/// Pretty-print a type.
/// When all types and constants appearing in the same message have been
/// pretty-printed, the returned list can be converted to its string
/// representation (with labels on duplicated names) by the `join()` method.
List<Object> labelType(DartType type) {
// TODO(askesc): Remove null check when we are completely null clean here.
if (type == null) return ["null-type"];
result = [];
type.accept(this);
return result;
}
/// Pretty-print a constant.
/// When all types and constants appearing in the same message have been
/// pretty-printed, the returned list can be converted to its string
/// representation (with labels on duplicated names) by the `join()` method.
List<Object> labelConstant(Constant constant) {
// TODO(askesc): Remove null check when we are completely null clean here.
if (constant == null) return ["null-constant"];
result = [];
constant.accept(this);
return result;
}
/// Get a textual description of the origins of the raw types appearing in
/// types and constants that have been pretty-printed using this labeler.
String get originMessages {
StringBuffer messages = new StringBuffer();
for (LabeledNode name in names) {
messages.write(name.originMessage);
}
return messages.toString();
}
// We don't have access to coreTypes here, so we have our own Object check.
static bool isObject(DartType type) {
if (type is InterfaceType && type.classNode.name == 'Object') {
Uri importUri = type.classNode.enclosingLibrary.importUri;
return importUri.scheme == 'dart' && importUri.path == 'core';
}
return false;
}
LabeledNode nameForEntity(
TreeNode node, String nodeName, Uri importUri, Uri fileUri) {
List<LabeledNode> labelsForName = nameMap[nodeName];
if (labelsForName == null) {
// First encountered entity with this name
LabeledNode name =
new LabeledNode(node, nodeName, importUri, fileUri, this);
names.add(name);
nameMap[nodeName] = [name];
return name;
} else {
for (LabeledNode entityForName in labelsForName) {
if (entityForName.node == node) {
// Previously encountered entity
return entityForName;
}
}
// New entity with name that was previously encountered
LabeledNode name =
new LabeledNode(node, nodeName, importUri, fileUri, this);
names.add(name);
labelsForName.add(name);
return name;
}
}
void defaultDartType(DartType type) {}
void visitTypedefType(TypedefType node) {}
void visitInvalidType(InvalidType node) {
// TODO(askesc): Throw internal error if InvalidType appears in diagnostics.
result.add("invalid-type");
}
void visitBottomType(BottomType node) {
// TODO(askesc): Throw internal error if BottomType appears in diagnostics.
result.add("bottom-type");
}
void visitDynamicType(DynamicType node) {
result.add("dynamic");
}
void visitVoidType(VoidType node) {
result.add("void");
}
void visitTypeParameterType(TypeParameterType node) {
TreeNode parent = node.parameter;
while (parent is! Library && parent != null) {
parent = parent.parent;
}
// Note that this can be null if, for instance, the erroneous code is not
// actually in the tree - then we don't know where it comes from!
Library enclosingLibrary = parent;
result.add(nameForEntity(
node.parameter,
node.parameter.name,
enclosingLibrary == null ? unknownUri : enclosingLibrary.importUri,
enclosingLibrary == null ? unknownUri : enclosingLibrary.fileUri));
}
void visitFunctionType(FunctionType node) {
node.returnType.accept(this);
result.add(" Function");
if (node.typeParameters.isNotEmpty) {
result.add("<");
bool first = true;
for (TypeParameter param in node.typeParameters) {
if (!first) result.add(", ");
result.add(param.name);
if (isObject(param.bound) && param.defaultType is DynamicType) {
// Bound was not specified, and therefore should not be printed.
} else {
result.add(" extends ");
param.bound.accept(this);
}
first = false;
}
result.add(">");
}
result.add("(");
bool first = true;
for (int i = 0; i < node.requiredParameterCount; i++) {
if (!first) result.add(", ");
node.positionalParameters[i].accept(this);
first = false;
}
if (node.positionalParameters.length > node.requiredParameterCount) {
if (node.requiredParameterCount > 0) result.add(", ");
result.add("[");
first = true;
for (int i = node.requiredParameterCount;
i < node.positionalParameters.length;
i++) {
if (!first) result.add(", ");
node.positionalParameters[i].accept(this);
first = false;
}
result.add("]");
}
if (node.namedParameters.isNotEmpty) {
if (node.positionalParameters.isNotEmpty) result.add(", ");
result.add("{");
first = true;
for (int i = 0; i < node.namedParameters.length; i++) {
if (!first) result.add(", ");
node.namedParameters[i].type.accept(this);
result.add(" ${node.namedParameters[i].name}");
first = false;
}
result.add("}");
}
result.add(")");
}
void visitInterfaceType(InterfaceType node) {
Class classNode = node.classNode;
result.add(nameForEntity(
classNode,
classNode.name,
classNode.enclosingLibrary.importUri,
classNode.enclosingLibrary.fileUri));
if (node.typeArguments.isNotEmpty) {
result.add("<");
bool first = true;
for (DartType typeArg in node.typeArguments) {
if (!first) result.add(", ");
typeArg.accept(this);
first = false;
}
result.add(">");
}
}
void defaultConstant(Constant node) {}
void visitNullConstant(NullConstant node) {
result.add(node);
}
void visitBoolConstant(BoolConstant node) {
result.add(node);
}
void visitIntConstant(IntConstant node) {
result.add(node);
}
void visitDoubleConstant(DoubleConstant node) {
result.add(node);
}
void visitSymbolConstant(SymbolConstant node) {
result.add(node);
}
void visitStringConstant(StringConstant node) {
result.add(json.encode(node.value));
}
void visitInstanceConstant(InstanceConstant node) {
new InterfaceType(node.classNode, node.typeArguments).accept(this);
result.add(" {");
bool first = true;
for (Field field in node.classNode.fields) {
if (field.isStatic) continue;
if (!first) result.add(", ");
result.add("${field.name}: ");
node.fieldValues[field.reference].accept(this);
first = false;
}
result.add("}");
}
void visitListConstant(ListConstant node) {
result.add("<");
node.typeArgument.accept(this);
result.add(">[");
bool first = true;
for (Constant constant in node.entries) {
if (!first) result.add(", ");
constant.accept(this);
first = false;
}
result.add("]");
}
void visitSetConstant(SetConstant node) {
result.add("<");
node.typeArgument.accept(this);
result.add(">{");
bool first = true;
for (Constant constant in node.entries) {
if (!first) result.add(", ");
constant.accept(this);
first = false;
}
result.add("}");
}
void visitMapConstant(MapConstant node) {
result.add("<");
node.keyType.accept(this);
result.add(", ");
node.valueType.accept(this);
result.add(">{");
bool first = true;
for (ConstantMapEntry entry in node.entries) {
if (!first) result.add(", ");
entry.key.accept(this);
result.add(": ");
entry.value.accept(this);
first = false;
}
result.add("}");
}
void visitTearOffConstant(TearOffConstant node) {
Procedure procedure = node.procedure;
Class classNode = procedure.enclosingClass;
if (classNode != null) {
result.add(nameForEntity(
classNode,
classNode.name,
classNode.enclosingLibrary.importUri,
classNode.enclosingLibrary.fileUri));
result.add(".");
}
result.add(procedure.name.name);
}
void visitPartialInstantiationConstant(PartialInstantiationConstant node) {
node.tearOffConstant.accept(this);
if (node.types.isNotEmpty) {
result.add("<");
bool first = true;
for (DartType typeArg in node.types) {
if (!first) result.add(", ");
typeArg.accept(this);
first = false;
}
result.add(">");
}
}
void visitTypeLiteralConstant(TypeLiteralConstant node) {
node.type.accept(this);
}
void visitUnevaluatedConstant(UnevaluatedConstant node) {
unsupported('printing unevaluated constants', -1, null);
}
}
final Uri unknownUri = Uri.parse("unknown");
class LabeledNode {
final TreeNode node;
final TypeLabeler typeLabeler;
final String name;
final Uri importUri;
final Uri fileUri;
LabeledNode(
this.node, this.name, this.importUri, this.fileUri, this.typeLabeler);
String toString() {
List<LabeledNode> entityForName = typeLabeler.nameMap[name];
if (entityForName.length == 1) {
return name;
}
return "$name/*${entityForName.indexOf(this) + 1}*/";
}
String get originMessage {
if (importUri.scheme == 'dart' && importUri.path == 'core') {
if (node is Class && blacklistedCoreClasses.contains(name)) {
// Blacklisted core class. Only print if ambiguous.
List<LabeledNode> entityForName = typeLabeler.nameMap[name];
if (entityForName.length == 1) {
return "";
}
}
}
if (importUri == unknownUri || node is! Class) {
// We don't know where it comes from and/or it's not a class.
// Only print if ambiguous.
List<LabeledNode> entityForName = typeLabeler.nameMap[name];
if (entityForName.length == 1) {
return "";
}
}
Message message = (importUri == fileUri || importUri.scheme == 'dart')
? templateTypeOrigin.withArguments(toString(), importUri)
: templateTypeOriginWithFileUri.withArguments(
toString(), importUri, fileUri);
return "\n - " + message.message;
}
}