blob: da29e5641d6b986822fdfab8d03b31c873d128f3 [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.
// Generates the type tables used by DartFuzz.
//
// Usage:
// dart gen_type_table.dart > dartfuzz_type_table.dart
//
// Reformat:
// tools/sdks/dart-sdk/bin/dart format \
// 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: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 'package:analyzer/dart/element/type.dart';
import 'package:args/args.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 =
<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 =
<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 =
<String, Map<String, Set<List<String>>>>{};
// Map type to a list of available unary operators.
Map<String, Set<String>> uniOps = <String, Set<String>>{};
//
// Type grouping.
//
// All Set<E> types: SET_INT, SET_STRING, etc.
Set<String> setTypes = <String>{};
// All Map<K, V> types: MAP_INT_STRING, MAP_DOUBLE_BOOL, etc.
Set<String> mapTypes = <String>{};
// All List<E> types: LIST_INT, LIST_STRING, etc.
Set<String> listTypes = <String>{};
// All floating point types: DOUBLE, SET_DOUBLE, MAP_X_DOUBLE, etc.
Set<String> fpTypes = <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 = <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 = <String>{};
// Complex types: Collection types instantiated with nested argument
// e.g Map<List<>, >.
Set<String> complexTypes = <String>{};
//
// Type relations.
//
// Map type to the resulting type when subscripted.
// Example: List<String> subscripts to String.
Map<String, String> subscriptsTo = <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 = <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 = <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 = <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 = <String, Set<String>>{};
// Convert analyzer's displayName to constant name used by dartfuzz.
// Example: Set<int, String> -> SET_INT_STRING
String getConstName(String displayName) {
var 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 (var 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}) {
var filteredParams = <List<String>>{};
for (var 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}) {
var filteredParams = <String>{};
for (var 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}) {
var filteredOps = <String, Set<List<String>>>{};
operators.forEach((op, parameterList) {
var 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}) {
var 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}) {
var filteredOps = <String, Set<String>>{};
operators.forEach((op, parameterSet) {
var 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}) {
var filteredTypes = <String, Map<String, Set<List<String>>>>{};
types.forEach((baseType, ops) {
if (shouldFilterType(baseType, fp: fp, flatTp: flatTp)) {
return;
}
var 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 subclass = !fp || flatTp;
final prefix = "${subclass ? "DartType." : ""}";
print(' static const Map<DartType, Map<String, '
'Set<List<DartType>>>> $name = {');
var 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 (var 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}) {
var filteredTypes = <String, Map<String, Set<String>>>{};
types.forEach((baseType, ops) {
if (shouldFilterType(baseType, fp: fp, flatTp: flatTp)) {
return;
}
var 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 subclass = !fp || flatTp;
final prefix = "${subclass ? "DartType." : ""}";
print(
' static const Map<DartType, ' 'Map<String, Set<DartType>>> $name = {');
var 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 (var 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}) {
var filteredTypes = <String, Map<String, List<String>>>{};
types.forEach((baseType, ops) {
if (shouldFilterType(baseType, fp: fp, flatTp: flatTp)) {
return;
}
var 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 subclass = !fp || flatTp;
final 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 (var 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}) {
var filteredTypes = <String, Set<String>>{};
types.forEach((baseType, parameterSet) {
if (shouldFilterType(baseType, fp: fp, flatTp: flatTp)) {
return;
}
var 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 subclass = !fp || flatTp;
final 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 (var 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}) {
var filteredTypes = <String, String>{};
types.forEach((baseType, paramType) {
if (shouldFilterType(baseType, fp: fp, flatTp: flatTp)) {
return;
}
if (shouldFilterType(paramType, fp: fp, flatTp: flatTp)) {
return;
}
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 subclass = !fp || flatTp;
final 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}) {
var filteredTypes = <String>{};
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 subclass = !fp || flatTp;
final prefix = "${subclass ? "DartType." : ""}";
stdout.write(' static const Set<DartType> $name = {');
for (var typName in filterTypesSet(types, fp: fp, flatTp: flatTp).toList()
..sort()) {
stdout.write('$prefix$typName, ');
stdout.write('$prefix${typName}_NULLABLE, ');
}
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}) {
var filteredTypes = <String, Set<String>>{};
types.forEach((baseType, params) {
if (shouldFilterType(baseType, fp: fp, flatTp: flatTp)) {
return;
}
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 subclass = !fp || flatTp;
final 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 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.
// ignore_for_file: annotate_overrides
// ignore_for_file: unused_element
// ignore_for_file: unused_field
/// Class that represents some common Dart types.
///
/// NOTE: this code has been generated automatically.
///''');
}
var className = 'DartType${fp ? "" : "NoFp"}${flatTp ? "FlatTp" : ""}';
print('class $className'
'${subclass ? " extends DartType" : ""} {');
print(' final String name;');
print(' final bool isNullable;');
if (!subclass) {
print(' const DartType._withName(this.name, this.isNullable);');
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, this.isNullable) : super._withName(name, isNullable);');
}
print('''
const $className() : name = "!", isNullable = false;
String get dartName { return name + (isNullable ? "?" : ""); }
String toString() { return "DartType(\$dartName)"; }
static bool isListType(DartType tp) {
return DartType._listTypes.contains(tp.toNonNullable());
}
static bool isSetType(DartType tp) {
return DartType._setTypes.contains(tp.toNonNullable());
}
static bool isMapType(DartType tp) {
return DartType._mapTypes.contains(tp.toNonNullable());
}
static bool isCollectionType(DartType tp) {
return DartType._collectionTypes.contains(tp.toNonNullable());
}
static bool isGrowableType(DartType tp) {
return DartType._growableTypes.contains(tp.toNonNullable());
}
static bool isComplexType(DartType tp) {
return DartType._complexTypes.contains(tp.toNonNullable());
}
DartType toNullable() {
if (isNullable) {
return this;
}
for (var tp in _allTypes) {
if (tp.isNullable && tp.name == name) return tp;
}
throw 'Fail toNullable \$name \$isNullable';
}
DartType toNonNullable() {
if (!isNullable) {
return this;
}
for (var tp in _allTypes) {
if (!tp.isNullable && tp.name == name) return tp;
}
throw 'Fail toNonNullable \$name \$isNullable';
}
bool isInterfaceOfType(DartType tp, DartType iTp) {
tp = tp.toNonNullable();
iTp = iTp.toNonNullable();
return _interfaceRels.containsKey(iTp) && _interfaceRels[iTp]!.contains(tp);
}
Set<DartType> get mapTypes {
return _mapTypes;
}
bool isSpecializable(DartType tp) {
return _interfaceRels.containsKey(tp.toNonNullable());
}
Set<DartType> interfaces(DartType tp) {
tp = tp.toNonNullable();
if (_interfaceRels.containsKey(tp)) {
return _interfaceRels[tp]!;
}
throw "NotFound";
}
DartType indexType(DartType tp) {
tp = tp.toNonNullable();
if (_indexedBy.containsKey(tp)) {
return _indexedBy[tp]!;
}
throw "NotFound";
}
Set<DartType> indexableElementTypes(DartType tp) {
if (_indexableElementOf.containsKey(tp)) {
return _indexableElementOf[tp]!;
}
throw "NotFound";
}
bool isIndexableElementType(DartType tp) {
return _indexableElementOf.containsKey(tp);
}
DartType elementType(DartType tp) {
tp = tp.toNonNullable();
if (_subscriptsTo.containsKey(tp)) {
return _subscriptsTo[tp]!;
}
throw "NotFound";
}
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]!;
}
throw "NotFound";
}
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) {
tp = tp.toNonNullable();
return _constructors.containsKey(tp);
}
Set<String> constructors(DartType tp) {
tp = tp.toNonNullable();
if (_constructors.containsKey(tp)) {
return _constructors[tp]!.keys.toSet();
}
return <String>{};
}
List<DartType> constructorParameters(DartType tp, String constructor) {
tp = tp.toNonNullable();
if (_constructors.containsKey(tp) &&
_constructors[tp]!.containsKey(constructor)) {
return _constructors[tp]![constructor]!;
}
throw "NotFound";
}
Set<DartType> get allTypes {
return _allTypes;
}
''');
print(" static const VOID = DartType._withName('void', false);");
print(" static const VOID_NULLABLE = VOID;");
var instTypes = <String>{};
// Generate one static DartType instance for all instantiable types.
// TODO (felih): maybe add void type?
allTypes.forEach((baseType) {
var constName = getConstName(baseType.displayName);
instTypes.add(constName);
if (!subclass) {
print(' static const $constName = '
"DartType._withName('${baseType.displayName}', false);");
print(' static const ${constName}_NULLABLE = '
"DartType._withName('${baseType.displayName}', true);");
}
});
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;
print(' static const $constName = '
"DartType._withName('__$constName', false);");
print(' static const ${constName}_NULLABLE = '
"DartType._withName('__$constName', true);");
});
}
// 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(" ${subclass ? "DartType." : ""}${constName}_NULLABLE,");
});
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) {
var 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) {
var newOp = <List<String>>{};
if (op.length < 2) return op;
for (var params1 in op) {
var keep = false;
for (var params2 in op) {
for (var 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 (var tp in allTypes) {
var typName = getConstName(tp.displayName);
if (!binOps.containsKey(typName)) continue;
for (var 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 (var 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'.
var 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.
var retTypName = getConstName(method.returnType.displayName);
if (method.returnType.nullabilitySuffix != NullabilitySuffix.none) {
retTypName += '_NULLABLE';
}
binOps[retTypName] ??= <String, Set<List<String>>>{};
binOps[retTypName]![method.name] ??= <List<String>>{};
var rhsTypName = getConstName(method.parameters[0].type.displayName);
if (method.parameters[0].type.nullabilitySuffix !=
NullabilitySuffix.none) {
rhsTypName += '_NULLABLE';
}
// TODO (felih): no hashing for List<String> ?
// if i remove this test i will get duplicates even though it is a Set.
var present = false;
for (var 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] ??= <String, Set<String>>{};
var ao = method.name + '=';
assignOps[retTypName]![ao] ??= <String>{};
assignOps[retTypName]![ao]!.add(rhsTypName);
}
} else {
// Get unary operators.
uniOps[typName] ??= <String>{};
var 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\'');
}
}
}
}
bool typesEqualModuloNullability(DartType a, DartType b) {
// The analyzer's DartType doesn't provide conversions to change nullability.
// Comparing by name isn't correct in general, but it works for the core
// libraries because there are no simple name conflicts.
return a.displayName == b.displayName;
}
// 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]) ).
var fromLiteral = <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 (var tp in allTypes) {
var typName = getConstName(tp.displayName);
// Manually add basic assignment operators which each type supports.
assignOps[typName] ??= <String, Set<String>>{};
assignOps[typName]!['='] = {typName};
getOperatorsForTyp(typName, tp, fromLiteral);
var typNameNullable = typName + "_NULLABLE";
assignOps[typNameNullable] ??= <String, Set<String>>{};
assignOps[typNameNullable]!['='] = {typName, typNameNullable};
assignOps[typNameNullable]!['??='] = {typName, typNameNullable};
}
// Add some static ops not extractable from dart:core/typed_data.
for (var typName in binOps.keys.toList()) {
if (typName.endsWith("_NULLABLE")) continue;
var typNameNullable = typName + "_NULLABLE";
binOps[typNameNullable] ??= <String, Set<List<String>>>{};
binOps[typNameNullable]!['??'] = {
[typNameNullable, typName],
[typNameNullable, typNameNullable],
};
binOps[typName] ??= <String, Set<List<String>>>{};
binOps[typName]!['??'] = {
[typNameNullable, typName],
};
}
binOps['BOOL'] ??= <String, Set<List<String>>>{};
binOps['BOOL']!['&&'] = {
['BOOL', 'BOOL'],
};
binOps['BOOL']!['||'] = {
['BOOL', 'BOOL'],
};
uniOps['BOOL'] ??= <String>{};
uniOps['BOOL']!.add('\'!\'');
// Get constructors.
for (var tp in allTypes) {
var typName = getConstName(tp.displayName);
// Skip types that are constructable from a literal.
if (fromLiteral.contains(typName)) {
continue;
}
for (var constructor in tp.constructors) {
if (shouldFilterConstructor(tp, constructor)) continue;
var params = <String>[];
var canConstruct = true;
for (var p in constructor.parameters) {
var tstr = getConstName(p.type.displayName);
if (tstr == 'DYNAMIC' || tstr == 'OBJECT') {
tstr = 'INT';
} else if (!allTypes
.any((a) => typesEqualModuloNullability(a, 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] ??= <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 (var 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 (var tp in allTypes) {
final typName = getConstName(tp.displayName);
// Find topmost interface type, e.g. List<int> is interface for Int8List.
var iTyp = tp;
while (iTyp.typeArguments.isEmpty && iTyp.interfaces.isNotEmpty) {
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.
var argName = getConstName(iTyp.typeArguments[0].displayName);
subscriptsTo[typName] = argName;
elementOf[argName] ??= <String>{};
elementOf[argName]!.add(typName);
if (isIndexable(iTyp)) {
indexableElementOf[argName] ??= <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.
var argName0 = getConstName(iTyp.typeArguments[0].displayName);
var argName1 = getConstName(iTyp.typeArguments[1].displayName);
subscriptsTo[typName] = argName1;
elementOf[argName1] ??= <String>{};
elementOf[argName1]!.add(typName);
indexableElementOf[argName1] ??= <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.
var newITypes = <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.toDouble();
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;
}
InterfaceType ptx = pType.element.instantiate(
typeArguments: [iType],
nullabilitySuffix: NullabilitySuffix.star,
);
newITypes.add(ptx);
if (iType.typeArguments.isNotEmpty) {
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;
}
InterfaceType ptx = pType.element.instantiate(
typeArguments: [iType1, iType2],
nullabilitySuffix: NullabilitySuffix.star,
);
newITypes.add(ptx);
if (iType1.typeArguments.isNotEmpty ||
iType2.typeArguments.isNotEmpty) {
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).
var pTypes1 = <InterfaceType>{};
// Types with two parameters (Map).
var pTypes2 = <InterfaceType>{};
// Types with no parameters or parameters of which have already been
// instantiated.
var iTypes = <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.
var filteredITypes = <InterfaceType>{};
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 (var i = 0; i < depth + 1; ++i) {
double mn = max(1.0, maxNew / (depth + 1.0));
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) {
var 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 (var ci in ce.interfaces) {
no += countOperators(ci.element);
}
});
return no;
}
// Analyze typed_data and core libraries to extract data types.
Future<void> getDataTypes(Set<InterfaceType> allTypes, String? dartTop) async {
final session = GenUtil.createAnalysisSession(dartTop);
// Visit libraries for type table generation.
await visitLibraryAtUri(session, 'dart:typed_data', allTypes);
await visitLibraryAtUri(session, 'dart:core', allTypes);
}
Future<void> visitLibraryAtUri(
AnalysisSession session, String uri, Set<InterfaceType> allTypes) async {
var libPath = session.uriConverter.uriToPath(Uri.parse(uri));
var result = await session.getResolvedLibrary(libPath!);
if (result is ResolvedLibraryResult) {
visitLibrary(result.element, allTypes);
} else {
throw StateError('Unable to resolve "$uri"');
}
}
void visitLibrary(LibraryElement library, Set<InterfaceType> allTypes) {
// 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 (var unit in library.units) {
visitCompilationUnit(unit, allTypes);
}
}
void 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 (var classElement in unit.classes) {
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) {
var iTypes = <String>{};
for (var iTyp in tp.interfaces) {
var 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 (var tp in allTypes) {
var typName = getConstName(tp.displayName);
for (var ifTypName in getInterfaces(tp)) {
interfaceRels[ifTypName] ??= <String>{};
interfaceRels[ifTypName]!.add(typName);
if (ifTypName.contains('ITERABLE')) {
iterableTypes1.add(typName);
}
}
for (var it in tp.element.allSupertypes) {
var ifTypName = getConstName(it.displayName);
interfaceRels[ifTypName] ??= <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 (var tp in allTypes) {
var typName = getConstName(tp.displayName);
if (interfaceRels.containsKey(typName)) {
interfaceRels[typName]!.add(typName);
}
}
}
void main(List<String> arguments) async {
final parser = 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');
var results;
try {
results = parser.parse(arguments);
} catch (e) {
print('Usage: dart gen_type_table.dart [OPTIONS]\n${parser.usage}\n$e');
exitCode = 255;
return;
}
var depth = int.parse(results['depth']);
var allTypes = <InterfaceType>{};
// Filter types to instantiate parameterized types with, this excludes
// complex types like Int8List (might be added later).
var iTypeFilter = <String>{'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);
}