blob: 20876cf8ab0fca1d0149eeb01ba04ad27bce449e [file] [log] [blame]
// Copyright (c) 2019, 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.
// @dart = 2.9
// Generates the type tables used by DartFuzz.
//
// Usage:
// dart gen_type_table.dart > dartfuzz_type_table.dart
//
// Reformat:
// tools/sdks/dart-sdk/bin/dartfmt -w \
// runtime/tools/dartfuzz/dartfuzz_type_table.dart
//
// Then send out modified dartfuzz_type_table.dart for review together
// with a modified dartfuzz.dart that increases the version.
import 'dart:io';
import 'dart:math';
import 'package:args/args.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'gen_util.dart';
// Minimum number of operators that are required for a type to be included in
// the type table.
final int operatorCountThreshold = 2;
//
// Operators and functions.
//
// Map type to a list of constructors names with a list of constructor
// parameter types.
Map<String, Map<String, List<String>>> constructors =
new Map<String, Map<String, List<String>>>();
// Map type to a list of assignment operators with a set of the
// assignable right hand side types.
Map<String, Map<String, Set<String>>> assignOps =
new Map<String, Map<String, Set<String>>>();
// Map type to a list of binary operators with set of the respective
// types for the first and second operand.
Map<String, Map<String, Set<List<String>>>> binOps =
new Map<String, Map<String, Set<List<String>>>>();
// Map type to a list of available unary operators.
Map<String, Set<String>> uniOps = new Map<String, Set<String>>();
//
// Type grouping.
//
// All Set<E> types: SET_INT, SET_STRING, etc.
Set<String> setTypes = new Set<String>();
// All Map<K, V> types: MAP_INT_STRING, MAP_DOUBLE_BOOL, etc.
Set<String> mapTypes = new Set<String>();
// All List<E> types: LIST_INT, LIST_STRING, etc.
Set<String> listTypes = new Set<String>();
// All floating point types: DOUBLE, SET_DOUBLE, MAP_X_DOUBLE, etc.
Set<String> fpTypes = new Set<String>();
// All iterable types: Set types + List types.
// These can be used in for(x in <iterable type>),
// therefore Map is not included.
Set<String> iterableTypes1 = new Set<String>();
// All trivially indexable types: Map types and List types.
// Elements of these can be written and read by [], unlike Set
// which uses getElementAt to access individual elements
Set<String> indexableTypes = new Set<String>();
// Complex types: Collection types instantiated with nested argument
// e.g Map<List<>, >.
Set<String> complexTypes = new Set<String>();
//
// Type relations.
//
// Map type to the resulting type when subscripted.
// Example: List<String> subscripts to String.
Map<String, String> subscriptsTo = new Map<String, String>();
// Map type to a Set of types that contain it as an element.
// Example: String is element of List<String> and Map<int, String>
Map<String, Set<String>> elementOf = new Map<String, Set<String>>();
// Map type to a Set of types that contain it as an indexable element.
// Same as element of, but without Set types.
Map<String, Set<String>> indexableElementOf = new Map<String, Set<String>>();
// Map type to type required as index.
// Example: List<String> is indexed by int,
// Map<String, double> indexed by String.
Map<String, String> indexedBy = new Map<String, String>();
//
// Interface relationships.
//
// Map Interface type to Set of types that implement it.
// Example: interface num is implemented by int and double.
Map<String, Set<String>> interfaceRels = new Map<String, Set<String>>();
// Convert analyzer's displayName to constant name used by dartfuzz.
// Example: Set<int, String> -> SET_INT_STRING
String getConstName(String displayName) {
String constName = displayName;
constName = constName.replaceAll('<', '_');
constName = constName.replaceAll('>', '');
constName = constName.replaceAll(', ', '_');
constName = constName.toUpperCase();
return constName;
}
// Returns true if the given type fails the filter criteria.
bool shouldFilterType(String typName, {bool fp = true, bool flatTp = false}) {
if (!fp && fpTypes.contains(typName)) {
return true;
}
if (flatTp && complexTypes.contains(typName)) {
return true;
}
return false;
}
// Returns true if any of the paramters in the list fails the
// filter criteria.
bool shouldFilterParameterList(List<String> parameters,
{bool fp = true, bool flatTp = false}) {
for (String parameter in parameters) {
if (shouldFilterType(parameter, fp: fp, flatTp: flatTp)) {
return true;
}
}
return false;
}
// Filter a set of a list of parameters according to their type.
// A paramter list is only retained if all of the contained paramters
// pass the filter criteria.
Set<List<String>> filterParameterList(Set<List<String>> parameterList,
{bool fp = true, bool flatTp = false}) {
Set<List<String>> filteredParams = <List<String>>{};
for (List<String> parameters in parameterList) {
if (!shouldFilterParameterList(parameters, fp: fp, flatTp: flatTp)) {
filteredParams.add(parameters);
}
}
return filteredParams;
}
// Filter a set of parameters according to their type.
Set<String> filterParameterSet(Set<String> parameterSet,
{bool fp = true, bool flatTp = false}) {
Set<String> filteredParams = <String>{};
for (String parameter in parameterSet) {
if (!shouldFilterType(parameter, fp: fp, flatTp: flatTp)) {
filteredParams.add(parameter);
}
}
return filteredParams;
}
// Filter map of operators to a set of a list of parameter types
// as used for binary operators.
Map<String, Set<List<String>>> filterOperatorMapSetList(
Map<String, Set<List<String>>> operators,
{bool fp = true,
bool flatTp = false}) {
Map<String, Set<List<String>>> filteredOps = <String, Set<List<String>>>{};
operators.forEach((op, parameterList) {
Set<List<String>> filteredParams =
filterParameterList(parameterList, fp: fp, flatTp: flatTp);
if (filteredParams.isNotEmpty) {
filteredOps[op] = filteredParams;
}
});
return filteredOps;
}
// Filter map of operators to a List of parameter types as used for
// constructors.
Map<String, List<String>> filterOperatorMapList(
Map<String, List<String>> operators,
{bool fp = true,
bool flatTp = false}) {
Map<String, List<String>> filteredOps = <String, List<String>>{};
operators.forEach((op, parameterList) {
if (!shouldFilterParameterList(parameterList, fp: fp, flatTp: flatTp)) {
filteredOps[op] = parameterList;
}
});
return filteredOps;
}
// Filter map of operators to a set of rhs types as used for assignment
// operators.
Map<String, Set<String>> filterOperatorMapSet(
Map<String, Set<String>> operators,
{bool fp = true,
bool flatTp = false}) {
Map<String, Set<String>> filteredOps = <String, Set<String>>{};
operators.forEach((op, parameterSet) {
Set<String> filteredParams =
filterParameterSet(parameterSet, fp: fp, flatTp: flatTp);
if (filteredParams.isNotEmpty) {
filteredOps[op] = filteredParams;
}
});
return filteredOps;
}
// Filter map of type to map of operators as used for binary operators.
Map<String, Map<String, Set<List<String>>>> filterTypesMap4(
Map<String, Map<String, Set<List<String>>>> types,
{bool fp = true,
bool flatTp = false}) {
Map<String, Map<String, Set<List<String>>>> filteredTypes =
<String, Map<String, Set<List<String>>>>{};
types.forEach((baseType, ops) {
if (shouldFilterType(baseType, fp: fp, flatTp: flatTp)) {
return true;
}
Map<String, Set<List<String>>> filteredOps =
filterOperatorMapSetList(ops, fp: fp, flatTp: flatTp);
if (filteredOps.isNotEmpty) {
filteredTypes[baseType] = filteredOps;
}
});
return filteredTypes;
}
// Print map of type to map of operators as used for binary operators.
void printTypeMap4(
String name, Map<String, Map<String, Set<List<String>>>> types,
{bool fp = true, bool flatTp = false}) {
final bool subclass = !fp || flatTp;
final String prefix = "${subclass ? "DartType." : ""}";
print(" static const Map<DartType, Map<String, " +
"Set<List<DartType>>>> ${name} = {");
Map<String, Map<String, Set<List<String>>>> filteredTypes =
filterTypesMap4(types, fp: fp, flatTp: flatTp);
for (var baseType in filteredTypes.keys.toList()..sort()) {
var ops = filteredTypes[baseType];
print(" ${prefix}${baseType}: {");
for (var op in ops.keys.toList()..sort()) {
var paramTypeL = ops[op];
print(" '${op}': {");
for (var paramTypes in paramTypeL) {
stdout.write(" [");
for (String paramType in paramTypes) {
stdout.write("${prefix}${paramType}, ");
}
print("],");
}
print(" },");
}
print(" },");
}
print(" };");
}
// Filter map of type to map of operators as used for assignment operators.
Map<String, Map<String, Set<String>>> filterTypesMap3Set(
Map<String, Map<String, Set<String>>> types,
{bool fp = true,
bool flatTp = false}) {
Map<String, Map<String, Set<String>>> filteredTypes =
<String, Map<String, Set<String>>>{};
types.forEach((baseType, ops) {
if (shouldFilterType(baseType, fp: fp, flatTp: flatTp)) {
return true;
}
Map<String, Set<String>> filteredOps =
filterOperatorMapSet(ops, fp: fp, flatTp: flatTp);
if (filteredOps.isNotEmpty) {
filteredTypes[baseType] = filteredOps;
}
});
return filteredTypes;
}
// Print map of type to map of operators as used for assignment operators.
void printTypeMap3Set(String name, Map<String, Map<String, Set<String>>> types,
{bool fp = true, bool flatTp = false}) {
final bool subclass = !fp || flatTp;
final String prefix = "${subclass ? "DartType." : ""}";
print(" static const Map<DartType, " +
"Map<String, Set<DartType>>> ${name} = {");
Map<String, Map<String, Set<String>>> filteredTypes =
filterTypesMap3Set(types, fp: fp, flatTp: flatTp);
for (var baseType in filteredTypes.keys.toList()..sort()) {
var ops = filteredTypes[baseType];
print(" ${prefix}${baseType}: {");
for (var op in ops.keys.toList()) {
var paramTypes = ops[op];
stdout.write(" '${op}': {");
for (String paramType in paramTypes.toList()..sort()) {
stdout.write("${prefix}${paramType}, ");
}
print("}, ");
}
print(" },");
}
print(" };");
}
// Filter map of type to map of operators as used for constructors.
Map<String, Map<String, List<String>>> filterTypesMap3(
Map<String, Map<String, List<String>>> types,
{bool fp = true,
bool flatTp = false}) {
Map<String, Map<String, List<String>>> filteredTypes =
<String, Map<String, List<String>>>{};
types.forEach((baseType, ops) {
if (shouldFilterType(baseType, fp: fp, flatTp: flatTp)) {
return true;
}
Map<String, List<String>> filteredOps =
filterOperatorMapList(ops, fp: fp, flatTp: flatTp);
if (filteredOps.isNotEmpty) {
filteredTypes[baseType] = filteredOps;
}
});
return filteredTypes;
}
// Print map of type to map of operators as used for constructors.
void printTypeMap3(String name, Map<String, Map<String, List<String>>> types,
{bool fp = true, bool flatTp = false}) {
final bool subclass = !fp || flatTp;
final String prefix = "${subclass ? "DartType." : ""}";
print(" static const Map<DartType, Map<String, " +
"List<DartType>>> ${name} = {");
var filteredTypes = filterTypesMap3(types, fp: fp, flatTp: flatTp);
for (var baseType in filteredTypes.keys.toList()..sort()) {
var ops = filteredTypes[baseType];
print(" ${prefix}${baseType}: {");
for (var op in ops.keys.toList()..sort()) {
var paramTypes = ops[op];
stdout.write(" '${op}': [");
for (String paramType in paramTypes.toList()) {
stdout.write("${prefix}${paramType}, ");
}
print("], ");
}
print(" },");
}
print(" };");
}
// Filter map of type collection name to set of types.
Map<String, Set<String>> filterTypesMap2(Map<String, Set<String>> types,
{bool fp = true, bool flatTp = false}) {
Map<String, Set<String>> filteredTypes = <String, Set<String>>{};
types.forEach((baseType, parameterSet) {
if (shouldFilterType(baseType, fp: fp, flatTp: flatTp)) {
return true;
}
Set<String> filteredParams =
filterParameterSet(parameterSet, fp: fp, flatTp: flatTp);
if (filteredParams.isNotEmpty) {
filteredTypes[baseType] = filteredParams;
}
});
return filteredTypes;
}
// Print map of type collection name to set of types.
void printTypeMap2(String name, Map<String, Set<String>> types,
{bool fp = true, bool flatTp = false}) {
final bool subclass = !fp || flatTp;
final String prefix = "${subclass ? "DartType." : ""}";
print(" static const Map<DartType, Set<DartType>> ${name} = {");
var filteredTypes = filterTypesMap2(types, fp: fp, flatTp: flatTp);
for (var baseType in filteredTypes.keys.toList()..sort()) {
var paramTypes = filteredTypes[baseType];
stdout.write(" ${prefix}${baseType}: { ");
for (String paramType in paramTypes.toList()..sort()) {
stdout.write("${prefix}${paramType}, ");
}
print("},");
}
print(" };");
}
// Filter map of type to type.
Map<String, String> filterTypesMap1(Map<String, String> types,
{bool fp = true, bool flatTp = false}) {
Map<String, String> filteredTypes = <String, String>{};
types.forEach((baseType, paramType) {
if (shouldFilterType(baseType, fp: fp, flatTp: flatTp)) {
return true;
}
if (shouldFilterType(paramType, fp: fp, flatTp: flatTp)) {
return true;
}
filteredTypes[baseType] = paramType;
});
return filteredTypes;
}
// Print map of type to type.
void printTypeMap1(String name, Map<String, String> types,
{bool fp = true, bool flatTp = false}) {
final bool subclass = !fp || flatTp;
final String prefix = "${subclass ? "DartType." : ""}";
print(" static const Map<DartType, DartType> ${name} = {");
var filteredTypes = filterTypesMap1(types, fp: fp, flatTp: flatTp);
for (var baseType in filteredTypes.keys.toList()..sort()) {
var paramType = filteredTypes[baseType];
print(" ${prefix}"
"${baseType}: ${prefix}${paramType}, ");
}
print(" };");
}
// Filter set of types.
Set<String> filterTypesSet(Set<String> choices,
{bool fp = true, bool flatTp = false}) {
Set<String> filteredTypes = {};
filteredTypes.addAll(choices);
if (!fp) {
filteredTypes = filteredTypes.difference(fpTypes);
}
if (flatTp) {
filteredTypes = filteredTypes.difference(complexTypes);
}
return filteredTypes;
}
// Print set of types.
void printTypeSet(String name, Set<String> types,
{bool fp = true, bool flatTp = false}) {
final bool subclass = !fp || flatTp;
final String prefix = "${subclass ? "DartType." : ""}";
stdout.write(" static const Set<DartType> ${name} = {");
for (String typName in filterTypesSet(types, fp: fp, flatTp: flatTp).toList()
..sort()) {
stdout.write("${prefix}$typName, ");
}
print("};");
}
// Filter map to type to set of operators as used for unitary operators.
Map<String, Set<String>> filterTypeMapSet(Map<String, Set<String>> types,
{bool fp = true, bool flatTp = false}) {
Map<String, Set<String>> filteredTypes = <String, Set<String>>{};
types.forEach((baseType, params) {
if (shouldFilterType(baseType, fp: fp, flatTp: flatTp)) {
return true;
}
filteredTypes[baseType] = params;
});
return filteredTypes;
}
// Print map to type to set of operators as used for unitary operators.
void printTypeMapSet(String name, Map<String, Set<String>> types,
{bool fp = true, bool flatTp = false}) {
final bool subclass = !fp || flatTp;
final String prefix = "${subclass ? "DartType." : ""}";
print(" static const Map<DartType, Set<String>> $name = {");
var filteredTypes = filterTypeMapSet(types, fp: fp, flatTp: flatTp);
for (var baseType in filteredTypes.keys.toList()..sort()) {
var paramTypes = filteredTypes[baseType].toList()..sort();
print(" ${prefix}${baseType}: {" + paramTypes.join(", ") + "},");
}
print(" };");
}
// Print all generated and collected types, operators and type collections.
void printTypeTable(Set<InterfaceType> allTypes,
{bool fp = true, bool flatTp = false}) {
final bool subclass = !fp || flatTp;
if (!subclass) {
print("""
// Copyright (c) 2019, 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.
// @dart = 2.9
/// Class that represents some common Dart types.
///
/// NOTE: this code has been generated automatically.
///""");
}
String className = 'DartType${fp ? "" : "NoFp"}${flatTp ? "FlatTp" : ""}';
print('class $className'
'${subclass ? " extends DartType" : ""} {');
print(" final String name;");
if (!subclass) {
print(" const DartType._withName(this.name);");
print("""
factory $className.fromDartConfig(
{bool enableFp = false, bool disableNesting =
false}) {
if (enableFp && !disableNesting) {
return DartType();
} else if (!enableFp && !disableNesting) {
return DartTypeNoFp();
} else if (enableFp && disableNesting) {
return DartTypeFlatTp();
} else {
return DartTypeNoFpFlatTp();
}
}""");
} else {
print(' const $className'
'._withName(this.name) : super._withName(name);');
}
print("""
const $className() : name = null;
static bool isListType(DartType tp) {
return DartType._listTypes.contains(tp);
}
static bool isMapType(DartType tp) {
return DartType._mapTypes.contains(tp);
}
static bool isCollectionType(DartType tp) {
return DartType._collectionTypes.contains(tp);
}
static bool isGrowableType(DartType tp) {
return DartType._growableTypes.contains(tp);
}
static bool isComplexType(DartType tp) {
return DartType._complexTypes.contains(tp);
}
bool isInterfaceOfType(DartType tp, DartType iTp) {
return _interfaceRels.containsKey(iTp) && _interfaceRels[iTp].contains(tp);
}
Set<DartType> get mapTypes {
return _mapTypes;
}
bool isSpecializable(DartType tp) {
return _interfaceRels.containsKey(tp);
}
Set<DartType> interfaces(DartType tp) {
if (_interfaceRels.containsKey(tp)) {
return _interfaceRels[tp];
}
return null;
}
DartType indexType(DartType tp) {
if (_indexedBy.containsKey(tp)) {
return _indexedBy[tp];
}
return null;
}
Set<DartType> indexableElementTypes(DartType tp) {
if (_indexableElementOf.containsKey(tp)) {
return _indexableElementOf[tp];
}
return null;
}
bool isIndexableElementType(DartType tp) {
return _indexableElementOf.containsKey(tp);
}
DartType elementType(DartType tp) {
if (_subscriptsTo.containsKey(tp)) {
return _subscriptsTo[tp];
}
return null;
}
Set<DartType> get iterableTypes1 {
return _iterableTypes1;
}
Set<String> uniOps(DartType tp) {
if (_uniOps.containsKey(tp)) {
return _uniOps[tp];
}
return <String>{};
}
Set<String> binOps(DartType tp) {
if (_binOps.containsKey(tp)) {
return _binOps[tp].keys.toSet();
}
return <String>{};
}
Set<List<DartType>> binOpParameters(DartType tp, String op) {
if (_binOps.containsKey(tp) &&
_binOps[tp].containsKey(op)) {
return _binOps[tp][op];
}
return null;
}
Set<String> assignOps(DartType tp) {
if (_assignOps.containsKey(tp)) {
return _assignOps[tp].keys.toSet();
}
return <String>{};
}
Set<DartType> assignOpRhs(DartType tp, String op) {
if (_assignOps.containsKey(tp) &&
_assignOps[tp].containsKey(op)) {
return _assignOps[tp][op];
}
return <DartType>{};
}
bool hasConstructor(DartType tp) {
return _constructors.containsKey(tp);
}
Set<String> constructors(DartType tp) {
if (_constructors.containsKey(tp)) {
return _constructors[tp].keys.toSet();
}
return <String>{};
}
List<DartType> constructorParameters(DartType tp, String constructor) {
if (_constructors.containsKey(tp) &&
_constructors[tp].containsKey(constructor)) {
return _constructors[tp][constructor];
}
return null;
}
Set<DartType> get allTypes {
return _allTypes;
}
""");
print(" static const VOID = const " + "DartType._withName(\"void\");");
Set<String> instTypes = {};
// Generate one static DartType instance for all instantiable types.
// TODO (felih): maybe add void type?
allTypes.forEach((baseType) {
String constName = getConstName(baseType.displayName);
instTypes.add(constName);
if (!subclass) {
print(" static const ${constName} = const " +
"DartType._withName(\"${baseType.displayName}\");");
}
});
if (!subclass) {
// Generate one static DartType instance for all non instantiable types.
// These are required to resolve interface relations, but should not be
// used directly to generate dart programs.
print("");
print(" // NON INSTANTIABLE" "");
interfaceRels.keys.forEach((constName) {
if (instTypes.contains(constName)) return true;
print(" static const ${constName} = const " +
"DartType._withName(\"__${constName}\");");
});
}
// Generate a list of all instantiable types.
print("");
print("""
// All types extracted from analyzer.
static const _allTypes = {""");
filterTypesSet(instTypes, fp: fp, flatTp: flatTp).forEach((constName) {
print(" ${subclass ? "DartType." : ""}${constName},");
});
print(" };");
print("");
print("""
// All List<E> types: LIST_INT, LIST_STRING, etc.""");
printTypeSet("_listTypes", listTypes, fp: fp, flatTp: flatTp);
print("");
print("""
// All Set types: SET_INT, SET_STRING, etc.""");
printTypeSet("_setTypes", setTypes, fp: fp, flatTp: flatTp);
print("");
print("""
// All Map<K, V> types: MAP_INT_STRING, MAP_DOUBLE_BOOL, etc.""");
printTypeSet("_mapTypes", mapTypes, fp: fp, flatTp: flatTp);
print("");
print("""
// All collection types: list, map and set types.""");
printTypeSet("_collectionTypes", {...listTypes, ...setTypes, ...mapTypes},
fp: fp, flatTp: flatTp);
print("");
print("""
// All growable types: list, map, set and string types.""");
printTypeSet(
"_growableTypes", {...listTypes, ...setTypes, ...mapTypes, 'STRING'},
fp: fp, flatTp: flatTp);
if (!subclass) {
print("");
print(
" // All floating point types: DOUBLE, SET_DOUBLE, MAP_X_DOUBLE, etc.");
printTypeSet("_fpTypes", fpTypes);
}
print("");
print("""
// All trivially indexable types: Map types and List types.
// Elements of these can be written and read by [], unlike Set
// which uses getElementAt to access individual elements.""");
printTypeSet("_indexableTypes", indexableTypes, fp: fp, flatTp: flatTp);
print("");
print("""
// Map type to the resulting type when subscripted.
// Example: List<String> subscripts to String.""");
printTypeMap1("_subscriptsTo", subscriptsTo, fp: fp, flatTp: flatTp);
print("");
print("""
// Map type to type required as index.
// Example: List<String> is indexed by int,
// Map<String, double> indexed by String.""");
printTypeMap1("_indexedBy", indexedBy, fp: fp, flatTp: flatTp);
print("");
print("""
// Map type to a Set of types that contain it as an element.
// Example: String is element of List<String> and Map<int, String>""");
printTypeMap2("_elementOf", elementOf, fp: fp, flatTp: flatTp);
print("");
print("""
// Map type to a Set of types that contain it as an indexable element.
// Same as element of, but without Set types.""");
printTypeMap2("_indexableElementOf", indexableElementOf,
fp: fp, flatTp: flatTp);
print("");
print("""
// All iterable types: Set types + List types.
// These can be used in for(x in <iterable type>),
// therefore Map is not included.""");
printTypeSet("_iterableTypes1", iterableTypes1, fp: fp, flatTp: flatTp);
if (!subclass) {
print("");
print("""
// Complex types: Collection types instantiated with nested argument
// e.g Map<List<>, >.""");
printTypeSet("_complexTypes", complexTypes);
}
print("");
print("""
// Map Interface type to Set of types that implement it.
// Example: interface num is implemented by int and double.""");
printTypeMap2("_interfaceRels", interfaceRels, fp: fp, flatTp: flatTp);
print("");
print("""
// Map type to a list of constructors names with a list of constructor
// parameter types.""");
printTypeMap3("_constructors", constructors, fp: fp, flatTp: flatTp);
print("");
print("""
// Map type to a list of binary operators with set of the respective
// types for the first and second operand.""");
printTypeMap4("_binOps", binOps, fp: fp, flatTp: flatTp);
print("");
print("""
// Map type to a list of available unary operators.""");
printTypeMapSet("_uniOps", uniOps, fp: fp, flatTp: flatTp);
print("");
print("""
// Map type to a list of assignment operators with a set of the
// assignable right hand side types.""");
printTypeMap3Set("_assignOps", assignOps, fp: fp, flatTp: flatTp);
print("}");
print("");
}
// Returns true if type can be set and get via [].
bool isIndexable(InterfaceType tp) {
bool isIndexable = false;
for (var method in tp.methods) {
if (method.name == "[]") {
isIndexable = true;
break;
}
}
return isIndexable;
}
// Returns true if iTypeName == typeName or if
// iTypeName is an interface type that is implemented by typeName.
// List<int> is interface of Int8List
bool isInterfaceOf(String iTypName, String typName) {
return iTypName == typName ||
(interfaceRels.containsKey(iTypName) &&
interfaceRels[iTypName].contains(typName));
}
// Filter operator parameters so that the more specific types are discarded if
// the respective interface type is already in the list.
// This is required not only to give each parameter type equal probability but
// also so that dartfuzz can efficiently filter floating point types from the
// interface relations.
Set<List<String>> filterOperator(Set<List<String>> op) {
Set<List<String>> newOp = new Set<List<String>>();
if (op.length < 2) return op;
for (List<String> params1 in op) {
bool keep = false;
for (List<String> params2 in op) {
for (int k = 0; k < params1.length; ++k) {
if (!isInterfaceOf(params2[k], params1[k])) {
keep = true;
break;
}
}
if (keep) break;
}
if (keep) {
newOp.add(params1);
}
}
return newOp;
}
// See comment on filterOpterator.
void filterOperators(Set<InterfaceType> allTypes) {
for (InterfaceType tp in allTypes) {
String typName = getConstName(tp.displayName);
if (!binOps.containsKey(typName)) continue;
for (String op in binOps[typName].keys) {
binOps[typName][op] = filterOperator(binOps[typName][op]);
}
}
}
// Filters methods based on a manually maintained exclude list.
//
// Excluded methods should be associated with an issue number so they can be
// re-enabled after the issue is resolved.
bool isExcludedMethod(InterfaceType tp, MethodElement method) {
// TODO(bkonyi): Enable operator / for these types after we resolve
// https://github.com/dart-lang/sdk/issues/39890
if (((tp.displayName == 'Float32x4') && (method.name == '/')) ||
((tp.displayName == 'Float64x2') && (method.name == '/'))) {
return true;
}
return false;
}
// Extract all binary and unary operators for tp.
// Operators are stored by return type in the following way:
// return type: { operator: { parameter types } }
// Example: bool: { == : { [int, int], [double, double] }}
// int: {'~', '-'},
// Does not recurse into interfaces and superclasses of tp.
void getOperatorsForTyp(String typName, InterfaceType tp, fromLiteral) {
for (MethodElement method in tp.methods) {
// If the method is manually excluded, skip it.
if (isExcludedMethod(tp, method)) continue;
// Detect whether tp can be parsed from a literal (dartfuzz.dart can
// already handle that).
// This is usually indicated by the presence of the static constructor
// 'castFrom' or 'parse'.
if (method.isStatic &&
(method.name == 'castFrom' || method.name == 'parse')) {
fromLiteral.add(getConstName(tp.displayName));
}
// Hack: dartfuzz.dart already handles subscripts, therefore we exclude
// them from the generated type table.
if (method.name.startsWith('[]')) continue;
if (method.isOperator) {
// TODO (felih): Include support for type 'dynamic'.
bool skip = false;
for (var p in method.parameters) {
if (getConstName(p.type.displayName) == 'DYNAMIC') {
skip = true;
break;
}
}
if (skip) continue;
if (method.parameters.length == 1) {
// Get binary operators.
String retTypName = getConstName(method.returnType.displayName);
binOps[retTypName] ??= new Map<String, Set<List<String>>>();
binOps[retTypName][method.name] ??= new Set<List<String>>();
String rhsTypName = getConstName(method.parameters[0].type.displayName);
// TODO (felih): no hashing for List<String> ?
// if i remove this test i will get duplicates even though it is a Set.
bool present = false;
for (List<String> o in binOps[retTypName][method.name]) {
if (o[0] == typName && o[1] == rhsTypName) {
present = true;
break;
}
}
if (!present)
binOps[retTypName][method.name].add([typName, rhsTypName]);
// Add some assignment operators corresponding to the binary operators.
// Example: for '+' add '+='.
// Bool types have to be filtered because boolean binary operators
// can not be used to derive assignment operators in this way, e.g.
// <= is not a valid assignment operator for bool types.
if (retTypName != 'BOOL') {
assignOps[retTypName] ??= new Map<String, Set<String>>();
String ao = method.name + '=';
assignOps[retTypName][ao] ??= new Set<String>();
assignOps[retTypName][ao].add(rhsTypName);
}
} else {
// Get unary operators.
uniOps[typName] ??= new Set<String>();
String uo = method.name;
// Hack: remove unary from operator so that the operator name can be
// used directly for source code generation.
if (uo.startsWith('unary')) uo = '-';
uniOps[typName].add('\'$uo\'');
}
}
}
}
// Extract all binary and unary operators for all types.
void getOperators(Set<InterfaceType> allTypes) {
// Set of types that can be constructed directly from literals and do
// not need special constructors (e.g. List<int> = [1, 2] as opposed to
// Int8List int8list = Int8List.fromList([1, 2]) ).
Set<String> fromLiteral = new Set<String>();
// getOperatorsForTyp uses a heuristic to detect which types can be
// constructed from a literal, but the heuristic misses the String type
// so we have to add it manually.
fromLiteral.add('STRING');
// Get binary, unary and assignment operators.
for (InterfaceType tp in allTypes) {
String typName = getConstName(tp.displayName);
// Manually add basic assignment operators which each type supports.
assignOps[typName] ??= new Map<String, Set<String>>();
assignOps[typName]['='] = {typName};
assignOps[typName]['??='] = {typName};
getOperatorsForTyp(typName, tp, fromLiteral);
}
// Add some static ops not extractable from dart:core/typed_data.
for (String typName in binOps.keys) {
binOps[typName] ??= new Map<String, Set<List<String>>>();
binOps[typName]['??'] = {
[typName, typName],
};
}
binOps['BOOL'] ??= new Map<String, Set<List<String>>>();
binOps['BOOL']['&&'] = {
['BOOL', 'BOOL'],
};
binOps['BOOL']['||'] = {
['BOOL', 'BOOL'],
};
uniOps['BOOL'] ??= new Set<String>();
uniOps['BOOL'].add('\'!\'');
// Get constructors.
for (InterfaceType tp in allTypes) {
String typName = getConstName(tp.displayName);
// Skip types that are constructable from a literal.
if (fromLiteral.contains(typName)) {
continue;
}
for (ConstructorElement constructor in tp.constructors) {
if (shouldFilterConstructor(tp, constructor)) continue;
List<String> params = new List<String>();
bool canConstruct = true;
for (var p in constructor.parameters) {
String tstr = getConstName(p.type.displayName);
if (tstr == "DYNAMIC" || tstr == "OBJECT") {
tstr = "INT";
} else if (!allTypes.contains(p.type)) {
// Exclude constructors that have an unsupported parameter type.
canConstruct = false;
break;
}
// Only add positional required parameters.
// TODO (felih): include named and optional parameters.
if (!p.isNamed) params.add(tstr);
}
if (!canConstruct) continue;
constructors[typName] ??= new Map<String, List<String>>();
constructors[typName][constructor.name] = params;
}
}
// Removed redundant specialized parameter types.
// E.g. if num is already contained remove bool and int.
filterOperators(allTypes);
}
bool shouldFilterConstructor(InterfaceType tp, ConstructorElement cons) {
// Filter private constructors.
if (cons.name.startsWith('_')) {
return true;
}
// Constructor exclude list
// TODO(bkonyi): Enable Float32x4.fromInt32x4Bits after we resolve
// https://github.com/dart-lang/sdk/issues/39890
if ((tp.displayName == 'Float32x4') && (cons.name == 'fromInt32x4Bits')) {
return true;
}
return false;
}
// Analyze types to extract element and subscript relations
// as well as precision type attributes.
void analyzeTypes(Set<InterfaceType> allTypes) {
// Extract basic floating point types.
for (InterfaceType tp in allTypes) {
if (tp.displayName.contains('Float') ||
tp.displayName.contains('float') ||
tp.displayName.contains('double') ||
tp.displayName.contains('Double'))
fpTypes.add(getConstName(tp.displayName));
}
// Analyze all types to extract information useful for dart code generation.
for (InterfaceType tp in allTypes) {
final typName = getConstName(tp.displayName);
// Find topmost interface type, e.g. List<int> is interface for Int8List.
InterfaceType iTyp = tp;
while (iTyp.typeArguments.isEmpty && !iTyp.interfaces.isEmpty) {
iTyp = tp.interfaces[0];
}
// Group current type with their respective type group.
if (iTyp.name == "Set") setTypes.add(typName);
if (iTyp.name == "List") listTypes.add(typName);
if (iTyp.name == "Map") mapTypes.add(typName);
if (iTyp.typeArguments.length == 1) {
// Analyze Array, List and Set types.
String argName = getConstName(iTyp.typeArguments[0].displayName);
subscriptsTo[typName] = argName;
elementOf[argName] ??= new Set<String>();
elementOf[argName].add(typName);
if (isIndexable(iTyp)) {
indexableElementOf[argName] ??= new Set<String>();
indexableElementOf[argName].add(typName);
indexedBy[typName] = 'INT';
indexableTypes.add(typName);
}
// Check if type is floating point precision.
if (fpTypes.contains(typName) || fpTypes.contains(argName)) {
fpTypes.add(typName);
}
} else if (iTyp.typeArguments.length == 2) {
if (isIndexable(iTyp)) {
// Analyze Map and MapEntry types.
String argName0 = getConstName(iTyp.typeArguments[0].displayName);
String argName1 = getConstName(iTyp.typeArguments[1].displayName);
subscriptsTo[typName] = argName1;
elementOf[argName1] ??= new Set<String>();
elementOf[argName1].add(typName);
indexableElementOf[argName1] ??= new Set<String>();
indexableElementOf[argName1].add(typName);
indexedBy[typName] = argName0;
indexableTypes.add(typName);
// Check if type is floating point precision.
if (fpTypes.contains(typName) ||
fpTypes.contains(argName0) ||
fpTypes.contains(argName1)) {
fpTypes.add(typName);
}
}
}
}
}
// Split types into sets of types with none, one and two parameters
// respectively.
void getParameterizedTypes(
Set<InterfaceType> allTypes, // Set of all types.
Set<InterfaceType> pTypes1, // Out: types with one parameter e.g. List.
Set<InterfaceType> pTypes2, // Out: types with two parameters e.g. Map.
Set<InterfaceType> iTypes) {
// Out: types with no parameters.
for (var tp in allTypes) {
if (tp.typeArguments.length == 1 &&
(tp.typeArguments[0].name == 'E' || tp.typeArguments[0].name == 'T'))
pTypes1.add(tp);
else if (tp.typeArguments.length == 2 &&
tp.typeArguments[0].name == 'K' &&
tp.typeArguments[1].name == 'V')
pTypes2.add(tp);
else
iTypes.add(tp);
}
}
// Generate new types by instantiating types with one and two parameters
// with the types having no parameters (or the parameters of which have
// already been instantiated).
// There will be no more then maxNew types generated.
Set<InterfaceType> instantiatePTypes(
Set<InterfaceType> pTypes1, // Types with one parameter.
Set<InterfaceType> pTypes2, // Types with two parameters.
Set<InterfaceType> iTypes, // Types with no parameters.
{double maxNew = 10000.0}) {
// Maximum number of newly generated types.
Set<InterfaceType> newITypes = new Set<InterfaceType>();
// Calculate the total number of new types if all combinations were used.
int nNew = pTypes1.length * iTypes.length +
pTypes2.length * iTypes.length * iTypes.length;
// Calculate how many generated types have to be skipped in order to stay
// under the maximum number set for generating new types (maxNew).
double step = maxNew / nNew;
double cntr = 0.0;
// Instantiate List and Set types.
pTypes1.forEach((pType) {
iTypes.forEach((iType) {
cntr += step;
if (cntr >= 1.0) {
cntr -= 1.0;
} else {
return true;
}
ParameterizedType ptx = pType.element.instantiate(
typeArguments: [iType],
nullabilitySuffix: NullabilitySuffix.star,
);
newITypes.add(ptx);
if (iType.typeArguments.length >= 1) {
complexTypes.add(getConstName(ptx.displayName));
}
});
});
// Instantiate Map types.
pTypes2.forEach((pType) {
iTypes.forEach((iType1) {
iTypes.forEach((iType2) {
cntr += step;
if (cntr >= 1.0) {
cntr -= 1.0;
} else {
return true;
}
ParameterizedType ptx = pType.element.instantiate(
typeArguments: [iType1, iType2],
nullabilitySuffix: NullabilitySuffix.star,
);
newITypes.add(ptx);
if (iType1.typeArguments.length >= 1 ||
iType2.typeArguments.length >= 1) {
complexTypes.add(getConstName(ptx.displayName));
}
});
});
});
// Add instantiated types to the set of types with no free parameters.
return iTypes.union(newITypes);
}
Set<InterfaceType> instantiateAllTypes(
Set<InterfaceType> allTypes, Set<String> iTypeFilter, int depth,
{double maxNew = 10000.0}) {
// Types with one parameter (List, Set).
Set<InterfaceType> pTypes1 = new Set<InterfaceType>();
// Types with two parameters (Map).
Set<InterfaceType> pTypes2 = new Set<InterfaceType>();
// Types with no parameters or parameters of which have already been
// instantiated.
Set<InterfaceType> iTypes = new Set<InterfaceType>();
// Fill type sets with respective parameter types.
getParameterizedTypes(allTypes, pTypes1, pTypes2, iTypes);
// Filter the list of zero parameter types to exclude
// complex types like Int8List.
Set<InterfaceType> filteredITypes = {};
for (var iType in iTypes) {
if (iTypeFilter.contains(iType.displayName)) {
filteredITypes.add(iType);
}
}
// Instantiate types with one or two free parameters.
// Concatenate the newly instantiated types with the previous set of
// instantiated or zero parameter types to be used as input for the next
// round or instantiation.
// Each iteration constructs more nested types like
// Map<Map<int, int>, List<int>>.
for (int i = 0; i < depth + 1; ++i) {
double mn = max(1, maxNew / (depth + 1));
filteredITypes = filteredITypes
.union(instantiatePTypes(pTypes1, pTypes2, filteredITypes, maxNew: mn));
}
return iTypes.union(filteredITypes);
}
// Heuristic of which types to include:
// count the number of operators and
// check if the type can be constructed from a literal.
int countOperators(ClassElement ce) {
int no = 0;
ce.methods.forEach((method) {
if (method.isOperator) {
no++;
}
// Detect whether type can be parsed from a literal (dartfuzz.dart can
// already handle that).
// This is usually indicated by the presence of the static constructor
// 'castFrom' or 'parse'.
if (method.isStatic &&
(method.name == 'castFrom' || method.name == 'parse')) {
no += 100;
}
for (InterfaceType ci in ce.interfaces) {
no += countOperators(ci.element);
}
});
return no;
}
// Analyze typed_data and core libraries to extract data types.
void getDataTypes(Set<InterfaceType> allTypes, String dartTop) async {
final AnalysisSession session = GenUtil.createAnalysisSession(dartTop);
// Visit libraries for type table generation.
await visitLibraryAtUri(session, 'dart:typed_data', allTypes);
await visitLibraryAtUri(session, 'dart:core', allTypes);
}
visitLibraryAtUri(
AnalysisSession session, String uri, Set<InterfaceType> allTypes) async {
String libPath = session.uriConverter.uriToPath(Uri.parse(uri));
ResolvedLibraryResult result = await session.getResolvedLibrary(libPath);
if (result.state != ResultState.VALID) {
throw new StateError('Unable to resolve "$uri"');
}
visitLibrary(result.element, allTypes);
}
visitLibrary(LibraryElement library, Set<InterfaceType> allTypes) async {
// This uses the element model to traverse the code. The element model
// represents the semantic structure of the code. A library consists of
// one or more compilation units.
for (CompilationUnitElement unit in library.units) {
visitCompilationUnit(unit, allTypes);
}
}
visitCompilationUnit(CompilationUnitElement unit, Set<InterfaceType> allTypes) {
// Each compilation unit contains elements for all of the top-level
// declarations in a single file, such as variables, functions, and
// classes. Note that `types` only returns classes. You can use
// `mixins` to visit mixins, `enums` to visit enum, `functionTypeAliases`
// to visit typedefs, etc.
for (ClassElement classElement in unit.types) {
if (classElement.isPublic) {
// Hack: Filter out some difficult types, abstract types and types that
// have methods with abstract type parameters.
// TODO (felih): include filtered types.
if (classElement.name.startsWith('Unmodifiable') ||
classElement.name.startsWith('Iterable') ||
classElement.name.startsWith('BigInt') ||
classElement.name.startsWith('DateTime') ||
classElement.name.startsWith('Uri') ||
(classElement.name == 'Function') ||
(classElement.name == 'Object') ||
(classElement.name == 'Match') ||
(classElement.name == 'RegExpMatch') ||
(classElement.name == 'pragma') ||
(classElement.name == 'LateInitializationError') ||
(classElement.name == 'ByteBuffer') ||
(classElement.name == 'TypedData') ||
(classElement.name == 'Sink') ||
(classElement.name == 'Pattern') ||
(classElement.name == 'StackTrace') ||
(classElement.name == 'StringSink') ||
(classElement.name == 'Type') ||
(classElement.name == 'Pattern') ||
(classElement.name == 'Invocation') ||
(classElement.name == 'StackTrace') ||
(classElement.name == 'NoSuchMethodError') ||
(classElement.name == 'Comparable') ||
(classElement.name == 'BidirectionalIterator') ||
(classElement.name == 'Iterator') ||
(classElement.name == 'Stopwatch') ||
(classElement.name == 'OutOfMemoryError')) {
continue;
}
allTypes.add(classElement.thisType);
}
}
}
// Get all interface implemented by type.
Set<String> getInterfaces(InterfaceType tp) {
Set<String> iTypes = new Set<String>();
for (InterfaceType iTyp in tp.interfaces) {
String ifTypName = getConstName(iTyp.displayName);
iTypes.add(ifTypName);
iTypes = iTypes.union(getInterfaces(iTyp));
}
return iTypes;
}
// Get interface and inheritance relationships for all types.
void getInterfaceRels(Set<InterfaceType> allTypes) {
for (InterfaceType tp in allTypes) {
String typName = getConstName(tp.displayName);
for (String ifTypName in getInterfaces(tp)) {
interfaceRels[ifTypName] ??= new Set<String>();
interfaceRels[ifTypName].add(typName);
if (ifTypName.contains('ITERABLE')) {
iterableTypes1.add(typName);
}
}
for (InterfaceType it in tp.element.allSupertypes) {
String ifTypName = getConstName(it.displayName);
interfaceRels[ifTypName] ??= new Set<String>();
interfaceRels[ifTypName].add(typName);
}
}
// If interface can be instantiated,
// add it to the relation list so that we can
// do tp1 = oneof(interfaceRels[tp2]) in dartfuzz with a chance of
// tp1 == tp1.
for (InterfaceType tp in allTypes) {
String typName = getConstName(tp.displayName);
if (interfaceRels.containsKey(typName)) {
interfaceRels[typName].add(typName);
}
}
}
main(List<String> arguments) async {
final parser = new ArgParser()
..addOption('dart-top', help: 'explicit value for \$DART_TOP')
..addOption('depth',
help: 'Nesting depth, e.g. List<String> is depth 0, ' +
'List<List<String>>' +
'is depth 1. Remark: dart type tables grow ' +
'exponentially with this, ' +
'therefore types with higher nesting ' +
'depth are partially filtered.',
defaultsTo: '1');
try {
final results = parser.parse(arguments);
int depth = int.parse(results['depth']);
Set<InterfaceType> allTypes = new Set<InterfaceType>();
// Filter types to instantiate parameterized types with, this excludes
// complex types like Int8List (might be added later).
Set<String> iTypeFilter = {'int', 'bool', 'double', 'String'};
// Extract basic types from dart::core and dart::typed_data.
await getDataTypes(allTypes, results['dart-top']);
// Instantiate parameterized types like List<E>.
allTypes =
instantiateAllTypes(allTypes, iTypeFilter, depth, maxNew: 10000.0);
// Extract interface Relations between types.
getInterfaceRels(allTypes);
// Extract operators from instantiated types.
getOperators(allTypes);
// Analyze instantiated types to get elementof/subscript and floating point
// information.
analyzeTypes(allTypes);
// Print everything.
printTypeTable(allTypes);
printTypeTable(allTypes, fp: false);
printTypeTable(allTypes, flatTp: true);
printTypeTable(allTypes, fp: false, flatTp: true);
} catch (e) {
print('Usage: dart gen_type_table.dart [OPTIONS]\n${parser.usage}\n$e');
exitCode = 255;
}
}