blob: 38fa92c9573a7e3896b61ff9239835c90d2b6086 [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,
EnvironmentBoolConstant,
EnvironmentIntConstant,
EnvironmentStringConstant,
Field,
FunctionType,
InvalidType,
InstanceConstant,
IntConstant,
InterfaceType,
ListConstant,
MapConstant,
NullConstant,
PartialInstantiationConstant,
Procedure,
StringConstant,
SymbolConstant,
TearOffConstant,
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<LabeledClassName> names = <LabeledClassName>[];
Map<String, List<LabeledClassName>> nameMap =
<String, List<LabeledClassName>>{};
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 (LabeledClassName 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;
}
LabeledClassName nameForClass(Class classNode) {
List<LabeledClassName> classesForName = nameMap[classNode.name];
if (classesForName == null) {
// First encountered class with this name
LabeledClassName name = new LabeledClassName(classNode, this);
names.add(name);
nameMap[classNode.name] = [name];
return name;
} else {
for (LabeledClassName classForName in classesForName) {
if (classForName.classNode == classNode) {
// Previously encountered class
return classForName;
}
}
// New class with name that was previously encountered
LabeledClassName name = new LabeledClassName(classNode, this);
names.add(name);
classesForName.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) {
result.add(node.parameter.name);
}
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);
}
}
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) {
result.add(nameForClass(node.classNode));
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.klass, node.typeArguments).accept(this);
result.add(" {");
bool first = true;
for (Field field in node.klass.fields) {
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 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(nameForClass(classNode));
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 visitEnvironmentBoolConstant(EnvironmentBoolConstant node) {
unsupported('printing unevaluated constants', -1, null);
}
void visitEnvironmentIntConstant(EnvironmentIntConstant node) {
unsupported('printing unevaluated constants', -1, null);
}
void visitEnvironmentStringConstant(EnvironmentStringConstant node) {
unsupported('printing unevaluated constants', -1, null);
}
void visitUnevaluatedConstant(UnevaluatedConstant node) {
unsupported('printing unevaluated constants', -1, null);
}
}
class LabeledClassName {
Class classNode;
TypeLabeler typeLabeler;
LabeledClassName(this.classNode, this.typeLabeler);
String toString() {
String name = classNode.name;
List<LabeledClassName> classesForName = typeLabeler.nameMap[name];
if (classesForName.length == 1) {
return name;
}
return "$name/*${classesForName.indexOf(this) + 1}*/";
}
String get originMessage {
Uri importUri = classNode.enclosingLibrary.importUri;
if (importUri.scheme == 'dart' && importUri.path == 'core') {
String name = classNode.name;
if (blacklistedCoreClasses.contains(name)) {
// Blacklisted core class. Only print if ambiguous.
List<LabeledClassName> classesForName = typeLabeler.nameMap[name];
if (classesForName.length == 1) {
return "";
}
}
}
Uri fileUri = classNode.enclosingLibrary.fileUri;
Message message = (importUri == fileUri || importUri.scheme == 'dart')
? templateTypeOrigin.withArguments(toString(), importUri)
: templateTypeOriginWithFileUri.withArguments(
toString(), importUri, fileUri);
return "\n - " + message.message;
}
}