Support type variables on function types and intersection types

Change-Id: Ic4f6f26f1e779e9fc084a06f546584f826a6e720
Reviewed-on: https://dart-review.googlesource.com/c/90382
Reviewed-by: Dmitry Stefantsov <dmitryas@google.com>
This commit is contained in:
Peter von der Ahé 2019-01-29 13:29:32 +00:00 committed by commit-bot@chromium.org
parent f822d0abb8
commit 977e5b31e2
5 changed files with 215 additions and 87 deletions

View file

@ -10,6 +10,7 @@ import "package:kernel/ast.dart"
FunctionType,
InterfaceType,
Library,
NamedType,
Node,
Supertype,
TreeNode,
@ -24,6 +25,7 @@ import "type_parser.dart" as type_parser show parse;
import "type_parser.dart"
show
ParsedClass,
ParsedIntersectionType,
ParsedFunctionType,
ParsedInterfaceType,
ParsedType,
@ -36,7 +38,8 @@ Library parseLibrary(Uri uri, String text,
{Uri fileUri, KernelEnvironment environment}) {
fileUri ??= uri;
environment ??= new KernelEnvironment(uri, fileUri);
Library library = new Library(uri, fileUri: fileUri);
Library library =
new Library(uri, fileUri: fileUri, name: uri.path.replaceAll("/", "."));
for (ParsedType type in type_parser.parse(text)) {
Node node = environment.kernelFromParsedType(type);
if (node is Class) {
@ -145,14 +148,35 @@ class KernelFromParsedType implements Visitor<Node, KernelEnvironment> {
FunctionType visitFunctionType(
ParsedFunctionType node, KernelEnvironment environment) {
DartType returnType =
node.returnType?.accept<Node, KernelEnvironment>(this, environment);
List<DartType> arguments = <DartType>[];
for (ParsedType argument in node.arguments.required) {
arguments
.add(argument.accept<Node, KernelEnvironment>(this, environment));
ParameterEnvironment parameterEnvironment =
computeTypeParameterEnvironment(node.typeVariables, environment);
List<DartType> positionalParameters = <DartType>[];
List<NamedType> namedParameters = <NamedType>[];
DartType returnType;
{
KernelEnvironment environment = parameterEnvironment.environment;
returnType =
node.returnType?.accept<Node, KernelEnvironment>(this, environment);
for (ParsedType argument in node.arguments.required) {
positionalParameters
.add(argument.accept<Node, KernelEnvironment>(this, environment));
}
List<Object> optional = node.arguments.optional;
for (int i = 0; i < optional.length; i++) {
ParsedType parsedType = optional[i];
DartType type =
parsedType.accept<Node, KernelEnvironment>(this, environment);
if (node.arguments.optionalAreNamed) {
namedParameters.add(new NamedType(optional[++i], type));
} else {
positionalParameters.add(type);
}
}
}
return new FunctionType(arguments, returnType);
return new FunctionType(positionalParameters, returnType,
namedParameters: namedParameters,
requiredParameterCount: node.arguments.required.length,
typeParameters: parameterEnvironment.parameters);
}
VoidType visitVoidType(ParsedVoidType node, KernelEnvironment environment) {
@ -164,6 +188,14 @@ class KernelFromParsedType implements Visitor<Node, KernelEnvironment> {
throw "not implemented: $node";
}
TypeParameterType visitIntersectionType(
ParsedIntersectionType node, KernelEnvironment environment) {
TypeParameterType type =
node.a.accept<Node, KernelEnvironment>(this, environment);
DartType bound = node.b.accept<Node, KernelEnvironment>(this, environment);
return new TypeParameterType(type.parameter, bound);
}
Supertype toSupertype(InterfaceType type) {
return new Supertype.byReference(type.className, type.typeArguments);
}

View file

@ -14,11 +14,12 @@ import "package:kernel/text/ast_to_text.dart" show Printer;
import "package:kernel/type_environment.dart" show TypeEnvironment;
import "kernel_type_parser.dart" show KernelEnvironment, parseLibrary;
import "kernel_type_parser.dart"
show KernelEnvironment, KernelFromParsedType, parseLibrary;
import "shared_type_tests.dart" show SubtypeTest;
import "type_parser.dart" as type_parser show parse;
import "type_parser.dart" as type_parser show parse, parseTypeVariables;
const String testSdk = """
class Object;
@ -35,7 +36,7 @@ const String testSdk = """
""";
const String expectedSdk = """
library;
library core;
import self as self;
class Object {
@ -76,7 +77,7 @@ main() {
new KernelSubtypeTest(coreTypes, hierarchy, environment).run();
}
class KernelSubtypeTest extends SubtypeTest<DartType> {
class KernelSubtypeTest extends SubtypeTest<DartType, KernelEnvironment> {
final CoreTypes coreTypes;
final ClassHierarchy hierarchy;
@ -85,7 +86,7 @@ class KernelSubtypeTest extends SubtypeTest<DartType> {
KernelSubtypeTest(this.coreTypes, this.hierarchy, this.environment);
DartType toType(String text) {
DartType toType(String text, KernelEnvironment environment) {
return environment.kernelFromParsedType(type_parser.parse(text).single);
}
@ -93,4 +94,12 @@ class KernelSubtypeTest extends SubtypeTest<DartType> {
return new TypeEnvironment(coreTypes, hierarchy, legacyMode: legacyMode)
.isSubtypeOf(subtype, supertype);
}
KernelEnvironment extend(String typeParameters) {
if (typeParameters?.isEmpty ?? true) return environment;
return const KernelFromParsedType()
.computeTypeParameterEnvironment(
type_parser.parseTypeVariables("<$typeParameters>"), environment)
.environment;
}
}

View file

@ -4,29 +4,33 @@
import "package:expect/expect.dart" show Expect;
abstract class SubtypeTest<T> {
abstract class SubtypeTest<T, E> {
void isSubtype(String subtypeString, String supertypeString,
{bool legacyMode: false, String typeParameters}) {
T subtype = toType(subtypeString);
T supertype = toType(supertypeString);
E environment = extend(typeParameters);
T subtype = toType(subtypeString, environment);
T supertype = toType(supertypeString, environment);
String mode = legacyMode ? " (legacy)" : "";
Expect.isTrue(isSubtypeImpl(subtype, supertype, legacyMode),
"$subtype should be a subtype of $supertype$mode.");
"$subtypeString should be a subtype of $supertypeString$mode.");
}
void isNotSubtype(String subtypeString, String supertypeString,
{bool legacyMode: false, String typeParameters}) {
T subtype = toType(subtypeString);
T supertype = toType(supertypeString);
E environment = extend(typeParameters);
T subtype = toType(subtypeString, environment);
T supertype = toType(supertypeString, environment);
String mode = legacyMode ? " (legacy)" : "";
Expect.isFalse(isSubtypeImpl(subtype, supertype, legacyMode),
"$subtype shouldn't be a subtype of $supertype$mode.");
"$subtypeString shouldn't be a subtype of $supertypeString$mode.");
}
T toType(String text);
T toType(String text, E environment);
bool isSubtypeImpl(T subtype, T supertype, bool legacyMode);
E extend(String typeParameters);
void run() {
isSubtype('int', 'num', legacyMode: true);
isSubtype('int', 'Comparable<num>', legacyMode: true);
@ -68,15 +72,12 @@ abstract class SubtypeTest<T> {
isNotSubtype('(num) -> (int) -> int', '(num) -> (num) -> num',
legacyMode: true);
// TODO(ahe): Remove this as the implementation improves.
return;
// ignore: dead_code
isSubtype('(x:num) -> num', '(x:int) -> num',
isSubtype('({num x}) -> num', '({int x}) -> num',
legacyMode: true); // named parameters
isSubtype('(num,x:num) -> num', '(int,x:int) -> num', legacyMode: true);
isSubtype('(x:num) -> int', '(x:num) -> num', legacyMode: true);
isNotSubtype('(x:int) -> int', '(x:num) -> num', legacyMode: true);
isSubtype('(num, {num x}) -> num', '(int, {int x}) -> num',
legacyMode: true);
isSubtype('({num x}) -> int', '({num x}) -> num', legacyMode: true);
isNotSubtype('({int x}) -> int', '({num x}) -> num', legacyMode: true);
isSubtype('<E>(E) -> int', '<E>(E) -> num',
legacyMode: true); // type parameters
@ -94,26 +95,36 @@ abstract class SubtypeTest<T> {
isNotSubtype('<E,F>(E) -> (F) -> E', '<F,E>(E) -> (F) -> E',
legacyMode: true);
isNotSubtype('<E>(E,num) -> E', '<E:num>(E,E) -> E', legacyMode: true);
isNotSubtype('<E:num>(E) -> int', '<E:int>(E) -> int', legacyMode: true);
isNotSubtype('<E:num>(E) -> E', '<E:int>(E) -> E', legacyMode: true);
isNotSubtype('<E:num>(int) -> E', '<E:int>(int) -> E', legacyMode: true);
isSubtype('<E:num>(E) -> E', '<F:num>(F) -> num', legacyMode: true);
isSubtype('<E:int>(E) -> E', '<F:int>(F) -> num', legacyMode: true);
isSubtype('<E:int>(E) -> E', '<F:int>(F) -> int', legacyMode: true);
isNotSubtype('<E>(E,num) -> E', '<E extends num>(E,E) -> E',
legacyMode: true);
isNotSubtype('<E extends num>(E) -> int', '<E extends int>(E) -> int',
legacyMode: true);
isNotSubtype('<E extends num>(E) -> E', '<E extends int>(E) -> E',
legacyMode: true);
isNotSubtype('<E extends num>(int) -> E', '<E extends int>(int) -> E',
legacyMode: true);
isSubtype('<E extends num>(E) -> E', '<F extends num>(F) -> num',
legacyMode: true);
isSubtype('<E extends int>(E) -> E', '<F extends int>(F) -> num',
legacyMode: true);
isSubtype('<E extends int>(E) -> E', '<F extends int>(F) -> int',
legacyMode: true);
isNotSubtype('<E>(int) -> int', '(int) -> int', legacyMode: true);
isNotSubtype('<E,F>(int) -> int', '<E>(int) -> int', legacyMode: true);
isSubtype('<E:List<E>>(E) -> E', '<F:List<F>>(F) -> F', legacyMode: true);
isNotSubtype('<E:Iterable<E>>(E) -> E', '<F:List<F>>(F) -> F',
isSubtype('<E extends List<E>>(E) -> E', '<F extends List<F>>(F) -> F',
legacyMode: true);
isNotSubtype('<E>(E,List<Object>) -> E', '<F:List<F>>(F,F) -> F',
isNotSubtype(
'<E extends Iterable<E>>(E) -> E', '<F extends List<F>>(F) -> F',
legacyMode: true);
isNotSubtype('<E>(E,List<Object>) -> List<E>', '<F:List<F>>(F,F) -> F',
isNotSubtype('<E>(E,List<Object>) -> E', '<F extends List<F>>(F,F) -> F',
legacyMode: true);
isNotSubtype('<E>(E,List<Object>) -> int', '<F:List<F>>(F,F) -> F',
isNotSubtype(
'<E>(E,List<Object>) -> List<E>', '<F extends List<F>>(F,F) -> F',
legacyMode: true);
isNotSubtype('<E>(E,List<Object>) -> E', '<F:List<F>>(F,F) -> void',
isNotSubtype('<E>(E,List<Object>) -> int', '<F extends List<F>>(F,F) -> F',
legacyMode: true);
isNotSubtype('<E>(E,List<Object>) -> E', '<F extends List<F>>(F,F) -> void',
legacyMode: true);
isSubtype('int', 'FutureOr<int>');
@ -130,41 +141,51 @@ abstract class SubtypeTest<T> {
isNotSubtype('FutureOr<int>', 'num');
// T & B <: T & A if B <: A
isSubtype('T & int', 'T & int', legacyMode: true);
isSubtype('T & int', 'T & num', legacyMode: true);
isSubtype('T & num', 'T & num', legacyMode: true);
isNotSubtype('T & num', 'T & int', legacyMode: true);
isSubtype('T & int', 'T & int', legacyMode: true, typeParameters: 'T');
isSubtype('T & int', 'T & num', legacyMode: true, typeParameters: 'T');
isSubtype('T & num', 'T & num', legacyMode: true, typeParameters: 'T');
isNotSubtype('T & num', 'T & int', legacyMode: true, typeParameters: 'T');
// T & B <: T extends A if B <: A
// (Trivially satisfied since promoted bounds are always a isSubtype of the
// original bound)
isSubtype('T & int', 'T', legacyMode: true, typeParameters: 'T: int');
isSubtype('T & int', 'T', legacyMode: true, typeParameters: 'T: num');
isSubtype('T & num', 'T', legacyMode: true, typeParameters: 'T: num');
isSubtype('T & int', 'T',
legacyMode: true, typeParameters: 'T extends int');
isSubtype('T & int', 'T',
legacyMode: true, typeParameters: 'T extends num');
isSubtype('T & num', 'T',
legacyMode: true, typeParameters: 'T extends num');
// T extends B <: T & A if B <: A
isSubtype('T', 'T & int', legacyMode: true, typeParameters: 'T: int');
isSubtype('T', 'T & num', legacyMode: true, typeParameters: 'T: int');
isSubtype('T', 'T & num', legacyMode: true, typeParameters: 'T: num');
isNotSubtype('T', 'T & int', legacyMode: true, typeParameters: 'T: num');
isSubtype('T', 'T & int',
legacyMode: true, typeParameters: 'T extends int');
isSubtype('T', 'T & num',
legacyMode: true, typeParameters: 'T extends int');
isSubtype('T', 'T & num',
legacyMode: true, typeParameters: 'T extends num');
isNotSubtype('T', 'T & int',
legacyMode: true, typeParameters: 'T extends num');
// T extends A <: T extends A
isSubtype('T', 'T', legacyMode: true, typeParameters: 'T: num');
isSubtype('T', 'T', legacyMode: true, typeParameters: 'T extends num');
// S & B <: A if B <: A, A is not S (or a promotion thereof)
isSubtype('S & int', 'int', legacyMode: true);
isSubtype('S & int', 'num', legacyMode: true);
isSubtype('S & num', 'num', legacyMode: true);
isNotSubtype('S & num', 'int', legacyMode: true);
isNotSubtype('S & num', 'T', legacyMode: true);
isNotSubtype('S & num', 'T & num', legacyMode: true);
isSubtype('S & int', 'int', legacyMode: true, typeParameters: 'S');
isSubtype('S & int', 'num', legacyMode: true, typeParameters: 'S');
isSubtype('S & num', 'num', legacyMode: true, typeParameters: 'S');
isNotSubtype('S & num', 'int', legacyMode: true, typeParameters: 'S');
isNotSubtype('S & num', 'T', legacyMode: true, typeParameters: 'S, T');
isNotSubtype('S & num', 'T & num',
legacyMode: true, typeParameters: 'S, T');
// S extends B <: A if B <: A, A is not S (or a promotion thereof)
isSubtype('S', 'int', legacyMode: true, typeParameters: 'S: int');
isSubtype('S', 'num', legacyMode: true, typeParameters: 'S: int');
isSubtype('S', 'num', legacyMode: true, typeParameters: 'S: num');
isNotSubtype('S', 'int', legacyMode: true, typeParameters: 'S: num');
isNotSubtype('S', 'T', legacyMode: true, typeParameters: 'S: num');
isNotSubtype('S', 'T & num', legacyMode: true, typeParameters: 'S: num');
isSubtype('S', 'int', legacyMode: true, typeParameters: 'S extends int');
isSubtype('S', 'num', legacyMode: true, typeParameters: 'S extends int');
isSubtype('S', 'num', legacyMode: true, typeParameters: 'S extends num');
isNotSubtype('S', 'int', legacyMode: true, typeParameters: 'S extends num');
isNotSubtype('S', 'T',
legacyMode: true, typeParameters: 'S extends num, T');
isNotSubtype('S', 'T & num',
legacyMode: true, typeParameters: 'S extends num, T');
}
}

View file

@ -109,14 +109,25 @@ class ParsedTypedef extends ParsedDeclaration {
}
class ParsedFunctionType extends ParsedType {
final List<ParsedTypeVariable> typeVariables;
final ParsedType returnType;
final ParsedArguments arguments;
ParsedFunctionType(this.returnType, this.arguments);
ParsedFunctionType(this.typeVariables, this.returnType, this.arguments);
String toString() {
return "$arguments -> $returnType";
StringBuffer sb = new StringBuffer();
if (typeVariables.isNotEmpty) {
sb.write("<");
sb.writeAll(typeVariables, ", ");
sb.write(">");
}
sb.write(arguments);
sb.write(" -> ");
sb.write(returnType);
return "$sb";
}
R accept<R, A>(Visitor<R, A> visitor, [A a]) {
@ -153,6 +164,26 @@ class ParsedTypeVariable extends ParsedType {
}
}
class ParsedIntersectionType extends ParsedType {
final ParsedType a;
final ParsedType b;
ParsedIntersectionType(this.a, this.b);
String toString() {
StringBuffer sb = new StringBuffer();
sb.write(a);
sb.write(" & ");
sb.write(b);
return "$sb";
}
R accept<R, A>(Visitor<R, A> visitor, [A a]) {
return visitor.visitIntersectionType(this, a);
}
}
class ParsedArguments {
final List<ParsedType> required;
final List optional;
@ -203,11 +234,14 @@ class Parser {
peek = peek.next;
}
String computeLocation() {
return "${source.substring(0, peek.charOffset)}\n>>>"
"\n${source.substring(peek.charOffset)}";
}
void expect(String string) {
if (!identical(string, peek.stringValue)) {
String error = "${source.substring(0, peek.charOffset)}\n>>>"
"\n${source.substring(peek.charOffset)}";
throw "Expected `$string`, but got `${peek.lexeme}`\n$error";
throw "Expected '$string', but got '${peek.lexeme}'\n${computeLocation()}";
}
advance();
}
@ -228,20 +262,33 @@ class Parser {
ParsedType parseType() {
if (optional("class")) return parseClass();
if (optional("typedef")) return parseTypedef();
if (optional("(")) return parseFunctionType();
String name = parseName();
List<ParsedType> arguments = <ParsedType>[];
if (optional("<")) {
advance();
arguments.add(parseType());
while (optional(",")) {
advance();
arguments.add(parseType());
ParsedType result;
do {
ParsedType type;
if (optional("(") || optional("<")) {
type = parseFunctionType();
} else {
String name = parseName();
List<ParsedType> arguments = <ParsedType>[];
if (optional("<")) {
advance();
arguments.add(parseType());
while (optional(",")) {
advance();
arguments.add(parseType());
}
peek = splitCloser(peek) ?? peek;
expect(">");
}
type = new ParsedInterfaceType(name, arguments);
}
peek = splitCloser(peek);
expect(">");
}
return new ParsedInterfaceType(name, arguments);
if (result == null) {
result = type;
} else {
result = new ParsedIntersectionType(result, type);
}
} while (optionalAdvance("&"));
return result;
}
ParsedType parseReturnType() {
@ -250,16 +297,17 @@ class Parser {
}
ParsedFunctionType parseFunctionType() {
List<ParsedTypeVariable> typeVariables = parseTypeVariablesOpt();
ParsedArguments arguments = parseArguments();
expect("-");
expect(">");
ParsedType returnType = parseReturnType();
return new ParsedFunctionType(returnType, arguments);
return new ParsedFunctionType(typeVariables, returnType, arguments);
}
String parseName() {
if (!peek.isIdentifier) {
throw "Expected a name, but got `${peek.stringValue}`";
throw "Expected a name, but got '${peek.stringValue}'\n${computeLocation()}";
}
String result = peek.lexeme;
advance();
@ -302,7 +350,7 @@ class Parser {
do {
typeVariables.add(parseTypeVariable());
} while (optionalAdvance(","));
peek = splitCloser(peek);
peek = splitCloser(peek) ?? peek;
expect(">");
}
return typeVariables;
@ -366,6 +414,16 @@ List<ParsedType> parse(String text) {
return types;
}
List<ParsedTypeVariable> parseTypeVariables(String text) {
Parser parser = new Parser(scanString(text).tokens, text);
List<ParsedType> result = parser.parseTypeVariablesOpt();
if (!parser.atEof) {
throw "Expected EOF, but got '${parser.peek.stringValue}'\n"
"${parser.computeLocation()}";
}
return result;
}
abstract class DefaultAction<R, A> {
R defaultAction(ParsedType node, A a);
@ -403,4 +461,8 @@ abstract class Visitor<R, A> {
R visitTypeVariable(ParsedTypeVariable node, A a) {
return DefaultAction.perform<R, A>(this, node, a);
}
R visitIntersectionType(ParsedIntersectionType node, A a) {
return DefaultAction.perform<R, A>(this, node, a);
}
}

View file

@ -37,5 +37,9 @@ List<List<Object>>
List<List<List<Object>>>
class A<T extends List<Object>>;
class B<T extends List<List<Object>>>;
<E>(E) -> int
S & T
class C;
<E>(E) -> int & <E>(E) -> void
""");
}