blob: 48265aa894161de35ed2c1b21b3103dab50ac2d6 [file] [log] [blame]
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// @dart = 2.9
import 'dart:io';
// By convention:
//
// T: generic type of typedef.
// A: generic type of returned function.
// B: generic type of function.
//
// Example:
// typedef F<T>: Function<A> Function<B>();
//
// We only use: Function, List, int (and function types).
// We import 'dart:core' directly and with prefix 'core'.
abstract class Printable {
/// Builds a descriptive string that can be used as an identifier.
///
/// The string is mainly used for disambiguation, and not for its readability.
void writeIdentifier(StringBuffer buffer);
}
abstract class TypeLike implements Printable {
/// Prints `this` as valid Dart code for a Type.
void writeType(StringBuffer buffer);
/// Whether this type uses T in some way.
bool get usesT;
/// Whether this type uses T in a return (covariant) position.
///
/// For example: `T`, `List<T>`, `T Function()`, or `Function(Function(T))`.
bool get returnsT;
/// Whether this type uses T in a parameter (contravariant) position.
///
/// For example, `Function(T)`, `Function(List<T>)`, or
/// `Function(T Function())`.
bool get takesT;
}
/// Provides a unique integer for every parameter in a function.
int parameterNameCounter = 0;
/// Whether `T` should be replaced with `int`.
bool shouldReplaceTWithInt = false;
class Parameter implements TypeLike {
final TypeLike type;
final String name;
Parameter(this.type, this.name);
// Type or name can be null.
@override
writeIdentifier(buffer) {
if (type == null) {
buffer.write("null");
} else {
type.writeIdentifier(buffer);
}
buffer.write("_");
buffer.write(name);
}
void writeType(StringBuffer buffer) {
assert(type != null || name != null);
if (name == null) {
type.writeType(buffer);
} else if (type == null) {
buffer.write(name);
} else {
type.writeType(buffer);
buffer.write(" ");
buffer.write(name);
}
}
void writeInFunction(StringBuffer buffer) {
assert(type != null || name != null);
if (name == null) {
type.writeType(buffer);
buffer.write(" x");
buffer.write(parameterNameCounter++);
} else if (type == null) {
buffer.write(name);
} else {
type.writeType(buffer);
buffer.write(" ");
buffer.write(name);
}
}
bool operator ==(other) {
return other is Parameter && name == other.name && type == other.type;
}
int get hashCode {
return ((name.hashCode * 37) ^ type.hashCode) & 0xFFFFFFFF;
}
bool get usesT => type?.usesT ?? false;
bool get takesT => type?.takesT ?? false;
bool get returnsT => type?.returnsT ?? false;
}
class GenericParameter implements TypeLike {
final String name;
final TypeLike bound;
GenericParameter(this.name, [this.bound]);
// Bound may be null.
@override
writeIdentifier(buffer) {
buffer.write(name);
buffer.write("_");
if (bound == null) {
buffer.write("null");
} else {
bound.writeIdentifier(buffer);
}
}
@override
writeType(buffer) {
buffer.write(name);
if (bound != null) {
buffer.write(" extends ");
bound.writeType(buffer);
}
}
bool operator ==(other) {
return other is GenericParameter &&
name == other.name &&
bound == other.bound;
}
int get hashCode {
return ((name.hashCode * 23) ^ bound.hashCode) & 0xFFFFFFFF;
}
bool get usesT => bound?.usesT ?? false;
bool get takesT => bound?.takesT ?? false;
bool get returnsT => bound?.returnsT ?? false;
}
void _describeList(StringBuffer buffer, List<Printable> list) {
if (list == null) {
buffer.write("0");
return;
}
buffer.write(list.length.toString());
buffer.write("_");
for (int i = 0; i < list.length; i++) {
if (i != 0) buffer.write("_");
list[i].writeIdentifier(buffer);
}
}
void _writeTypes(StringBuffer buffer, List<TypeLike> list,
[String prefix = "", String postfix = ""]) {
if (list == null || list.isEmpty) return;
buffer.write(prefix);
for (int i = 0; i < list.length; i++) {
if (i != 0) buffer.write(", ");
list[i].writeType(buffer);
}
buffer.write(postfix);
}
void _writeParameters(
StringBuffer buffer, List<Parameter> list, bool inFunction,
[String prefix = "", String postfix = ""]) {
if (list == null || list.isEmpty) return;
buffer.write(prefix);
for (int i = 0; i < list.length; i++) {
if (i != 0) buffer.write(", ");
if (inFunction) {
list[i].writeInFunction(buffer);
} else {
list[i].writeType(buffer);
}
}
buffer.write(postfix);
}
bool _listUsesT(List elements) {
if (elements == null) return false;
return elements.any((p) => p.usesT);
}
bool _listEquals(List list1, List list2) {
if (list1 == list2) return true; // Also covers both being null.
if (list1 == null || list2 == null) return false;
for (int i = 0; i < list1.length; i++) {
if (list1[i] != list2[i]) return false;
}
return true;
}
int _listHash(List list) {
if (list == null) return null.hashCode;
int result = 71;
for (int i = 0; i < list.length; i++) {
result = ((result * 11) ^ list[i].hashCode) & 0xFFFFFFFF;
}
return result;
}
class FunctionType implements TypeLike {
final TypeLike returnType;
final List<GenericParameter> generic;
final List<Parameter> required;
final List<Parameter> optional;
final List<Parameter> named;
FunctionType(this.returnType, this.generic, this.required,
[this.optional, this.named]);
@override
writeIdentifier(buffer) {
buffer.write("Fun_");
if (returnType == null) {
buffer.write("null");
} else {
returnType.writeIdentifier(buffer);
}
buffer.write("_");
_describeList(buffer, generic);
buffer.write("_");
_describeList(buffer, required);
buffer.write("_");
_describeList(buffer, optional);
buffer.write("_");
_describeList(buffer, named);
}
@override
writeType(buffer) {
if (returnType != null) {
returnType.writeType(buffer);
buffer.write(" ");
}
buffer.write("Function");
if (generic != null) _writeTypes(buffer, generic, "<", ">");
buffer.write("(");
bool notInFunction = true;
_writeParameters(buffer, required, notInFunction);
if ((optional != null || named != null) &&
required != null &&
required.isNotEmpty) {
buffer.write(", ");
}
_writeParameters(buffer, optional, notInFunction, "[", "]");
_writeParameters(buffer, named, notInFunction, "{", "}");
buffer.write(")");
}
/// Writes this type as if it was a function.
void writeFunction(StringBuffer buffer, String name, {bool replaceT: true}) {
shouldReplaceTWithInt = replaceT;
parameterNameCounter = 0;
if (returnType != null) {
returnType.writeType(buffer);
buffer.write(" ");
}
buffer.write(name);
if (generic != null) _writeTypes(buffer, generic, "<", ">");
buffer.write("(");
bool inFunction = true;
_writeParameters(buffer, required, inFunction);
if ((optional != null || named != null) &&
required != null &&
required.isNotEmpty) {
buffer.write(", ");
}
_writeParameters(buffer, optional, inFunction, "[", "]");
_writeParameters(buffer, named, inFunction, "{", "}");
buffer.write(") => null;");
shouldReplaceTWithInt = false;
}
bool operator ==(other) {
return returnType == other.returnType &&
_listEquals(generic, other.generic) &&
_listEquals(required, other.required) &&
_listEquals(optional, other.optional) &&
_listEquals(named, other.named);
}
int get hashCode {
return ((returnType.hashCode * 13) ^
(_listHash(generic) * 17) ^
(_listHash(required) * 53) ^
(_listHash(optional) ^ 31) ^
(_listHash(named) * 87)) &
0xFFFFFFFF;
}
bool get usesT {
return (returnType?.usesT ?? false) ||
[generic, required, optional, named].any(_listUsesT);
}
bool get returnsT {
return (returnType?.returnsT ?? false) ||
[generic, required, optional, named]
.any((l) => l?.any((p) => p.takesT) ?? false);
}
bool get takesT {
return (returnType?.takesT ?? false) ||
[generic, required, optional, named]
.any((l) => l?.any((p) => p.returnsT) ?? false);
}
bool get reifiedTypeUsesT {
return returnType?.usesT ?? returnsT;
}
}
class NominalType implements TypeLike {
final String prefix;
final String name;
final List<TypeLike> generic;
NominalType(this.name, [this.prefix, this.generic]);
@override
writeIdentifier(buffer) {
buffer.write(prefix);
buffer.write("_");
buffer.write(name);
_describeList(buffer, generic);
}
@override
writeType(buffer) {
if (prefix != null && prefix != "") {
buffer.write(prefix);
buffer.write(".");
}
if (shouldReplaceTWithInt && name == "T") {
buffer.write("int");
} else {
buffer.write(name);
}
_writeTypes(buffer, generic, "<", ">");
}
bool operator ==(other) {
return other is NominalType && prefix == other.prefix && name == other.name;
}
int get hashCode {
return ((prefix.hashCode * 37) ^ name.hashCode) & 0xFFFFFFFF;
}
bool get usesT => name == "T" || _listUsesT(generic);
bool get returnsT => name == "T" || generic?.any((t) => t.returnsT) ?? false;
bool get takesT => generic?.any((t) => t.takesT) ?? false;
}
List<FunctionType> buildFunctionTypes() {
List<GenericParameter> as = [
new GenericParameter("A"),
// new GenericParameter("A", new NominalType("int")),
// new GenericParameter("A", new NominalType("int", "core")),
];
List<GenericParameter> bs = [
// new GenericParameter("B"),
// new GenericParameter("B", new NominalType("int")),
new GenericParameter("B", new NominalType("int", "core")),
];
List<TypeLike> basicTypes = [
new NominalType("int"),
// new NominalType("int", "core"),
// new NominalType("List"),
// new NominalType("List", "core"),
new NominalType("Function"),
new NominalType("List", "", [new NominalType("Function")]),
new NominalType("List", "core", [new NominalType("int", "core")]),
new NominalType("List", "", [new NominalType("T")]),
// new NominalType("List", "", [new NominalType("Function")]),
];
List<TypeLike> basicsPlusNull = [
basicTypes,
<TypeLike>[null]
].expand((x) => x).toList();
List<TypeLike> basicsPlusNullPlusVoid = [
basicsPlusNull,
[new NominalType("void")],
].expand((x) => x).toList();
List<TypeLike> basicsPlusNullPlusB = [
basicsPlusNull,
[
new NominalType("B"),
new NominalType("List", "", [new NominalType("B")])
]
].expand((x) => x).toList();
List<TypeLike> basicsPlusNullPlusBPlusVoid = [
basicsPlusNullPlusB,
[new NominalType("void")],
].expand((x) => x).toList();
List<TypeLike> basicsPlusNullPlusA = [
basicsPlusNull,
[
new NominalType("A"),
new NominalType("List", "", [new NominalType("A")])
]
].expand((x) => x).toList();
List<TypeLike> basicsPlusNullPlusAPlusVoid = [
basicsPlusNullPlusA,
[new NominalType("void")],
].expand((x) => x).toList();
List<TypeLike> buildFunctionTypes(TypeLike returnType, TypeLike parameterType,
[List<GenericParameter> generics,
bool generateMoreCombinations = false]) {
List<TypeLike> result = [];
if (parameterType == null) {
// int Function().
result.add(new FunctionType(returnType, generics, null));
return result;
}
// int Function(int x).
result.add(new FunctionType(
returnType, generics, [new Parameter(parameterType, "x")]));
if (!generateMoreCombinations) return result;
// int Function([int x]).
result.add(new FunctionType(
returnType, generics, null, [new Parameter(parameterType, "x")]));
// int Function(int, [int x])
result.add(new FunctionType(
returnType,
generics,
[new Parameter(new NominalType("int"), null)],
[new Parameter(parameterType, "x")]));
// int Function(int x, [int x])
result.add(new FunctionType(
returnType,
generics,
[new Parameter(new NominalType("int"), "y")],
[new Parameter(parameterType, "x")]));
// int Function(int);
result.add(new FunctionType(
returnType, generics, [new Parameter(parameterType, null)]));
// int Function([int]);
result.add(new FunctionType(
returnType, generics, null, [new Parameter(parameterType, null)]));
// int Function(int, [int])
result.add(new FunctionType(
returnType,
generics,
[new Parameter(new NominalType("int"), null)],
[new Parameter(parameterType, null)]));
// int Function(int x, [int])
result.add(new FunctionType(
returnType,
generics,
[new Parameter(new NominalType("int"), "x")],
[new Parameter(parameterType, null)]));
// int Function({int x}).
result.add(new FunctionType(
returnType, generics, null, null, [new Parameter(parameterType, "x")]));
// int Function(int, {int x})
result.add(new FunctionType(
returnType,
generics,
[new Parameter(new NominalType("int"), null)],
null,
[new Parameter(parameterType, "x")]));
// int Function(int x, {int x})
result.add(new FunctionType(
returnType,
generics,
[new Parameter(new NominalType("int"), "y")],
null,
[new Parameter(parameterType, "x")]));
return result;
}
// The "smaller" function types. May also be used non-nested.
List<TypeLike> functionTypes = [];
for (TypeLike returnType in basicsPlusNullPlusVoid) {
for (TypeLike parameterType in basicsPlusNull) {
bool generateMoreCombinations = true;
functionTypes.addAll(buildFunctionTypes(
returnType, parameterType, null, generateMoreCombinations));
}
}
// These use `B` from the generic type of the enclosing function.
List<TypeLike> returnFunctionTypesB = [];
for (TypeLike returnType in basicsPlusNullPlusBPlusVoid) {
TypeLike parameterType = new NominalType("B");
returnFunctionTypesB.addAll(buildFunctionTypes(returnType, parameterType));
}
for (TypeLike parameterType in basicsPlusNull) {
TypeLike returnType = new NominalType("B");
returnFunctionTypesB.addAll(buildFunctionTypes(returnType, parameterType));
}
for (TypeLike returnType in basicsPlusNullPlusAPlusVoid) {
for (TypeLike parameterType in basicsPlusNullPlusA) {
for (GenericParameter a in as) {
functionTypes
.addAll(buildFunctionTypes(returnType, parameterType, [a]));
}
}
}
List<TypeLike> types = [];
types.addAll(functionTypes);
// Now add some higher-order function types.
for (TypeLike returnType in functionTypes) {
types.addAll(buildFunctionTypes(returnType, null));
types.addAll(buildFunctionTypes(returnType, new NominalType("int")));
for (var b in bs) {
types.addAll(buildFunctionTypes(returnType, null, [b]));
types.addAll(buildFunctionTypes(returnType, new NominalType("int"), [b]));
}
}
for (TypeLike returnType in returnFunctionTypesB) {
for (var b in bs) {
types.addAll(buildFunctionTypes(returnType, null, [b]));
types.addAll(buildFunctionTypes(returnType, new NominalType("int"), [b]));
}
}
return types;
}
final String HEADER = """
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// GENERATED - DON'T EDIT.
// GENERATED - DON'T EDIT.
// GENERATED - DON'T EDIT.
// GENERATED - DON'T EDIT.
// GENERATED - DON'T EDIT.
// GENERATED - DON'T EDIT.
// GENERATED - DON'T EDIT.
// GENERATED - DON'T EDIT.
import 'dart:core';
import 'dart:core' as core;
import 'package:expect/expect.dart';
@pragma('dart2js:noInline')
@pragma('dart2js:assumeDynamic')
confuse(f) => f;
""";
class Unit {
int typeCounter = 0;
final String name;
final StringBuffer typedefs = new StringBuffer();
final StringBuffer globals = new StringBuffer();
final StringBuffer tests = new StringBuffer();
final StringBuffer fields = new StringBuffer();
final StringBuffer statics = new StringBuffer();
final StringBuffer testMethods = new StringBuffer();
final StringBuffer methods = new StringBuffer();
Unit(this.name);
void write(StringBuffer buffer) {
buffer.write("""
$HEADER
$typedefs
$globals
class $name<T> {
final bool tIsBool;
final bool tIsInt;
final bool tIsDynamic;
$fields
$name({this.tIsBool: false, this.tIsInt: false})
: tIsDynamic = !tIsBool && !tIsInt;
$methods
runTests() {\n$tests }
$testMethods
}
void main() {
new $name().runTests();
new $name<int>(tIsInt: true).runTests();
new $name<bool>(tIsBool: true).runTests();
}
""");
}
}
final TEST_METHOD_HEADER = """
/// #typeCode
void #testName() {""";
// Tests that apply for every type.
final COMMON_TESTS_TEMPLATE = """
Expect.isTrue(#staticFunName is #typeName<int>);
Expect.isTrue(confuse(#staticFunName) is #typeName<int>);
// In checked mode, verifies the type.
#typeCode #localName;
// The static function #staticFunName sets `T` to `int`.
if (tIsInt) {
#fieldName = #staticFunName as dynamic;
#localName = #staticFunName as dynamic;
#fieldName = confuse(#staticFunName);
#localName = confuse(#staticFunName);
}
Expect.isTrue(#methodFunName is #typeName<T>);
Expect.isTrue(#methodFunName is #typeCode);
Expect.isTrue(confuse(#methodFunName) is #typeName<T>);
// In checked mode, verifies the type.
#fieldName = #methodFunName;
#localName = #methodFunName;
#fieldName = confuse(#methodFunName);
#localName = confuse(#methodFunName);""";
// Tests that depend on the typedef "T" argument.
//
// These tests are executed when the surrounding class is instantiated with
// its generic type set to `int`, `dynamic` or `bool`. In the latter case, the
// class field `tIsBool` is set to true.
// While the types themselves are not affected by the class` `T`, the methods
// of the class may use `T`:
//
// For example:
// class A<T> {
// f(List<T> x) {}
// }
final TYPEDEF_T_TESTS_TEMPLATE = """
// The static function has its T always set to int.
Expect.isTrue(#staticFunName is #typeName<int>);
Expect.isFalse(#staticFunName is #typeName<bool>);
Expect.isTrue(confuse(#staticFunName) is #typeName<int>);
Expect.isFalse(confuse(#staticFunName) is #typeName<bool>);
if (tIsBool) {
Expect.throws(() { #fieldName = (#staticFunName as dynamic); });
Expect.throws(() { #fieldName = confuse(#staticFunName); });
Expect.throws(() { #localName = (#staticFunName as dynamic); });
Expect.throws(() { #localName = confuse(#staticFunName); });
}
if (tIsInt || tIsBool) {
Expect.equals(#isIntValue, #methodFunName is #typeName<int>);
Expect.equals(#isBoolValue, #methodFunName is #typeName<bool>);
Expect.equals(#isIntValue, confuse(#methodFunName) is #typeName<int>);
Expect.equals(#isBoolValue, confuse(#methodFunName) is #typeName<bool>);
}
""";
final TEST_METHOD_FOOTER = " }";
String createTypeName(int id) => "F$id";
String createStaticFunName(int id) => "f$id";
String createMethodFunName(int id) => "m$id";
String createFieldName(int id) => "x$id";
String createLocalName(int id) => "l$id";
String createTestName(int id) => "test${createTypeName(id)}";
String createTypeCode(FunctionType type) {
StringBuffer typeBuffer = new StringBuffer();
type.writeType(typeBuffer);
return typeBuffer.toString();
}
String createStaticFunCode(FunctionType type, int id) {
StringBuffer staticFunBuffer = new StringBuffer();
type.writeFunction(staticFunBuffer, createStaticFunName(id));
return staticFunBuffer.toString();
}
String createMethodFunCode(FunctionType type, int id) {
StringBuffer methodFunBuffer = new StringBuffer();
type.writeFunction(methodFunBuffer, createMethodFunName(id), replaceT: false);
return methodFunBuffer.toString();
}
String createTestMethodFunCode(FunctionType type, String typeCode, int id) {
var tIsInt = type.reifiedTypeUsesT ? 'tIsInt' : 'true';
var tIsBool = type.reifiedTypeUsesT ? 'tIsBool' : 'true';
String fillTemplate(String template, int id) {
var result = template
.replaceAll("#typeName", createTypeName(id))
.replaceAll("#staticFunName", createStaticFunName(id))
.replaceAll("#methodFunName", createMethodFunName(id))
.replaceAll("#fieldName", createFieldName(id))
.replaceAll("#localName", createLocalName(id))
.replaceAll("#testName", createTestName(id))
.replaceAll("#typeCode", typeCode)
.replaceAll("#isIntValue", tIsInt)
.replaceAll("#isBoolValue", tIsBool);
assert(!result.contains("#"));
return result;
}
String commonTests = fillTemplate(COMMON_TESTS_TEMPLATE, id);
String genericTTests = "";
if (type.usesT) {
genericTTests = fillTemplate(TYPEDEF_T_TESTS_TEMPLATE, id);
}
return """
${fillTemplate(TEST_METHOD_HEADER, id)}
$commonTests
$genericTTests
$TEST_METHOD_FOOTER
""";
}
void generateTests() {
// Keep methods and classes smaller by distributing over several different
// classes.
List<Unit> units = [];
for (int i = 0; i < 100; i++) {
units.add(new Unit("U$i"));
}
var types = buildFunctionTypes();
int unitCounter = 0;
types.forEach((FunctionType type) {
Unit unit = units[unitCounter % units.length];
unitCounter++;
int typeCounter = unit.typeCounter++;
String typeName = createTypeName(typeCounter);
String fieldName = createFieldName(typeCounter);
String testName = createTestName(typeCounter);
String typeCode = createTypeCode(type);
String staticFunCode = createStaticFunCode(type, typeCounter);
String methodFunCode = createMethodFunCode(type, typeCounter);
String testMethodCode =
createTestMethodFunCode(type, typeCode, typeCounter);
unit.typedefs.writeln("typedef $typeName<T> = $typeCode;");
unit.globals.writeln(staticFunCode);
unit.fields.writeln(" $typeCode $fieldName;");
unit.methods.writeln(" $methodFunCode");
unit.testMethods.writeln("$testMethodCode");
unit.tests.writeln(" $testName();");
});
for (int i = 0; i < units.length; i++) {
var unit = units[i];
var buffer = new StringBuffer();
unit.write(buffer);
var path = Platform.script.resolve("function_type${i}_test.dart").path;
new File(path).writeAsStringSync(buffer.toString());
}
}
void printUsage() {
print("""
Generates function type tests.
All tests are generated in the same directory as this script.
""");
}
void main(List<String> arguments) {
if (arguments.length != 0) {
printUsage();
return;
}
generateTests();
}