Fix handling of nested typedefs (for real this time).

This is a re-fix of dartbug.com/21912, which I previously fixed
incorrectly.  Previously, our approach to avoiding infinite loops when
comparing types was to maintain a set of typedefs being expanded on
the stack, and prune the comparison whenever an attempt was made to
expand a typedef that was already being expanded.  However, this was
too strict, since there are legal (non-circular) types which invole
expanding a given typedef in reentrant fashion; we can't prune these
types without producing incorrect semantics.  An example (from the bug
report) is the type of f in the code below:

    typedef T Function2<S, T>(S z);
    Function2<Function2<A, B>, Function2<B, A>> f;

The solution is to maintain the list of typedefs being expanded inside
each FunctionTypeImpl object (and InterfaceTypeImpl object) rather
than on the stack during the comparison; this allows us to distinguish
the situations where we need to prune (those having to do exclusively
with expansion of a typedef) from the situations where we shouldn't
prune (those having to do with substitution of a type parameter).

A beneficial side effect of this change is that code that interacts
with types no longer needs to worry about typedef circularities, since
the circularities will automatically be pruned while exploring the
type definitions.  This simplifies the implementation of
isAssignableTo, isSubtypeOf, operator==, and hashCode.  (Note,
however, that code still needs to cope with circularities in the
inheritance hierarchy).

BUG=dartbug.com/21912
R=brianwilkerson@google.com

Review URL: https://codereview.chromium.org//1143003007
This commit is contained in:
Paul Berry 2015-06-01 12:45:27 -07:00
parent 21f6f1aaca
commit 62f7ac8c5d
6 changed files with 737 additions and 519 deletions

File diff suppressed because it is too large Load diff

View file

@ -55,7 +55,7 @@ class ElementFactory {
new List<TypeParameterTypeImpl>(count);
for (int i = 0; i < count; i++) {
TypeParameterElementImpl typeParameter =
new TypeParameterElementImpl(parameterNames[i], 0);
typeParameterElement(parameterNames[i]);
typeParameters[i] = typeParameter;
typeParameterTypes[i] = new TypeParameterTypeImpl(typeParameter);
typeParameter.type = typeParameterTypes[i];
@ -346,6 +346,14 @@ class ElementFactory {
return functionElement;
}
static FunctionTypeAliasElementImpl functionTypeAliasElement(String name) {
FunctionTypeAliasElementImpl functionTypeAliasElement =
new FunctionTypeAliasElementImpl(name, -1);
functionTypeAliasElement.type =
new FunctionTypeImpl.forTypedef(functionTypeAliasElement);
return functionTypeAliasElement;
}
static PropertyAccessorElementImpl getterElement(
String name, bool isStatic, DartType type) {
FieldElementImpl field = new FieldElementImpl(name, -1);
@ -537,4 +545,7 @@ class ElementFactory {
}
return variable;
}
static TypeParameterElementImpl typeParameterElement(String name) =>
new TypeParameterElementImpl(name, 0);
}

View file

@ -1258,6 +1258,24 @@ class FunctionTypeImplTest extends EngineTestCase {
isNotNull);
}
void test_equality_recursive() {
FunctionTypeAliasElementImpl s =
ElementFactory.functionTypeAliasElement('s');
FunctionTypeAliasElementImpl t =
ElementFactory.functionTypeAliasElement('t');
FunctionTypeAliasElementImpl u =
ElementFactory.functionTypeAliasElement('u');
FunctionTypeAliasElementImpl v =
ElementFactory.functionTypeAliasElement('v');
s.returnType = t.type;
t.returnType = s.type;
u.returnType = v.type;
v.returnType = u.type;
// We don't care whether the types compare equal or not. We just need the
// computation to terminate.
expect(s.type == u.type, new isInstanceOf<bool>());
}
void test_getElement() {
FunctionElementImpl typeElement =
new FunctionElementImpl.forNode(AstFactory.identifier3("f"));
@ -1307,6 +1325,18 @@ class FunctionTypeImplTest extends EngineTestCase {
type.hashCode;
}
void test_hashCode_recursive() {
FunctionTypeAliasElementImpl s =
ElementFactory.functionTypeAliasElement('s');
FunctionTypeAliasElementImpl t =
ElementFactory.functionTypeAliasElement('t');
s.returnType = t.type;
t.returnType = s.type;
// We don't care what the hash code is. We just need its computation to
// terminate.
expect(t.type.hashCode, new isInstanceOf<int>());
}
void test_isAssignableTo_normalAndPositionalArgs() {
// ([a]) -> void <: (a) -> void
ClassElement a = ElementFactory.classElement2("A");
@ -1691,6 +1721,141 @@ class FunctionTypeImplTest extends EngineTestCase {
expect(s.isSubtypeOf(t), isFalse);
}
void test_namedParameterTypes_pruned_no_type_arguments() {
FunctionTypeAliasElementImpl f =
ElementFactory.functionTypeAliasElement('f');
FunctionTypeAliasElementImpl g =
ElementFactory.functionTypeAliasElement('g');
f.parameters = [ElementFactory.namedParameter2('x', g.type)];
FunctionTypeImpl paramType = f.type.namedParameterTypes['x'];
expect(paramType.prunedTypedefs, hasLength(1));
expect(paramType.prunedTypedefs[0], same(f));
}
void test_namedParameterTypes_pruned_with_type_arguments() {
FunctionTypeAliasElementImpl f =
ElementFactory.functionTypeAliasElement('f');
FunctionTypeAliasElementImpl g =
ElementFactory.functionTypeAliasElement('g');
f.typeParameters = [ElementFactory.typeParameterElement('T')];
f.parameters = [ElementFactory.namedParameter2('x', g.type)];
FunctionTypeImpl paramType = f.type.namedParameterTypes['x'];
expect(paramType.prunedTypedefs, hasLength(1));
expect(paramType.prunedTypedefs[0], same(f));
}
void test_newPrune_no_previous_prune() {
FunctionTypeAliasElementImpl f =
ElementFactory.functionTypeAliasElement('f');
FunctionTypeImpl type = f.type;
List<FunctionTypeAliasElement> pruneList = type.newPrune;
expect(pruneList, hasLength(1));
expect(pruneList[0], same(f));
}
void test_newPrune_non_typedef() {
// No pruning needs to be done for function types that aren't associated
// with typedefs because those types can't be directly referred to by the
// user (and hence can't participate in circularities).
FunctionElementImpl f = ElementFactory.functionElement('f');
FunctionTypeImpl type = f.type;
expect(type.newPrune, isNull);
}
void test_newPrune_synthetic_typedef() {
// No pruning needs to be done for function types that are associated with
// synthetic typedefs because those types are only created for
// function-typed formal parameters, which can't be directly referred to by
// the user (and hence can't participate in circularities).
FunctionTypeAliasElementImpl f =
ElementFactory.functionTypeAliasElement('f');
f.synthetic = true;
FunctionTypeImpl type = f.type;
expect(type.newPrune, isNull);
}
void test_newPrune_with_previous_prune() {
FunctionTypeAliasElementImpl f =
ElementFactory.functionTypeAliasElement('f');
FunctionTypeAliasElementImpl g =
ElementFactory.functionTypeAliasElement('g');
FunctionTypeImpl type = f.type;
FunctionTypeImpl prunedType = type.pruned([g]);
List<FunctionTypeAliasElement> pruneList = prunedType.newPrune;
expect(pruneList, hasLength(2));
expect(pruneList, contains(f));
expect(pruneList, contains(g));
}
void test_normalParameterTypes_pruned_no_type_arguments() {
FunctionTypeAliasElementImpl f =
ElementFactory.functionTypeAliasElement('f');
FunctionTypeAliasElementImpl g =
ElementFactory.functionTypeAliasElement('g');
f.parameters = [ElementFactory.requiredParameter2('x', g.type)];
FunctionTypeImpl paramType = f.type.normalParameterTypes[0];
expect(paramType.prunedTypedefs, hasLength(1));
expect(paramType.prunedTypedefs[0], same(f));
}
void test_normalParameterTypes_pruned_with_type_arguments() {
FunctionTypeAliasElementImpl f =
ElementFactory.functionTypeAliasElement('f');
FunctionTypeAliasElementImpl g =
ElementFactory.functionTypeAliasElement('g');
f.typeParameters = [ElementFactory.typeParameterElement('T')];
f.parameters = [ElementFactory.requiredParameter2('x', g.type)];
FunctionTypeImpl paramType = f.type.normalParameterTypes[0];
expect(paramType.prunedTypedefs, hasLength(1));
expect(paramType.prunedTypedefs[0], same(f));
}
void test_optionalParameterTypes_pruned_no_type_arguments() {
FunctionTypeAliasElementImpl f =
ElementFactory.functionTypeAliasElement('f');
FunctionTypeAliasElementImpl g =
ElementFactory.functionTypeAliasElement('g');
f.parameters = [ElementFactory.positionalParameter2('x', g.type)];
FunctionTypeImpl paramType = f.type.optionalParameterTypes[0];
expect(paramType.prunedTypedefs, hasLength(1));
expect(paramType.prunedTypedefs[0], same(f));
}
void test_optionalParameterTypes_pruned_with_type_arguments() {
FunctionTypeAliasElementImpl f =
ElementFactory.functionTypeAliasElement('f');
FunctionTypeAliasElementImpl g =
ElementFactory.functionTypeAliasElement('g');
f.typeParameters = [ElementFactory.typeParameterElement('T')];
f.parameters = [ElementFactory.positionalParameter2('x', g.type)];
FunctionTypeImpl paramType = f.type.optionalParameterTypes[0];
expect(paramType.prunedTypedefs, hasLength(1));
expect(paramType.prunedTypedefs[0], same(f));
}
void test_returnType_pruned_no_type_arguments() {
FunctionTypeAliasElementImpl f =
ElementFactory.functionTypeAliasElement('f');
FunctionTypeAliasElementImpl g =
ElementFactory.functionTypeAliasElement('g');
f.returnType = g.type;
FunctionTypeImpl paramType = f.type.returnType;
expect(paramType.prunedTypedefs, hasLength(1));
expect(paramType.prunedTypedefs[0], same(f));
}
void test_returnType_pruned_with_type_arguments() {
FunctionTypeAliasElementImpl f =
ElementFactory.functionTypeAliasElement('f');
FunctionTypeAliasElementImpl g =
ElementFactory.functionTypeAliasElement('g');
f.typeParameters = [ElementFactory.typeParameterElement('T')];
f.returnType = g.type;
FunctionTypeImpl paramType = f.type.returnType;
expect(paramType.prunedTypedefs, hasLength(1));
expect(paramType.prunedTypedefs[0], same(f));
}
void test_setTypeArguments() {
ClassElementImpl enclosingClass = ElementFactory.classElement2("C", ["E"]);
MethodElementImpl methodElement =
@ -1773,12 +1938,22 @@ class FunctionTypeImplTest extends EngineTestCase {
}
void test_toString_recursive() {
FunctionElementImpl t = ElementFactory.functionElement("t");
FunctionElementImpl s = ElementFactory.functionElement("s");
FunctionTypeAliasElementImpl t =
ElementFactory.functionTypeAliasElement("t");
FunctionTypeAliasElementImpl s =
ElementFactory.functionTypeAliasElement("s");
t.returnType = s.type;
s.returnType = t.type;
expect(t.type.toString(), '() \u2192 () \u2192 ...');
}
void test_toString_recursive_via_interface_type() {
FunctionTypeAliasElementImpl f =
ElementFactory.functionTypeAliasElement('f');
ClassElementImpl c = ElementFactory.classElement2('C', ['T']);
f.returnType = c.type.substitute4([f.type]);
expect(f.type.toString(), '() \u2192 C<...>');
}
}
@reflectiveTest

View file

@ -237,6 +237,40 @@ f(A a) {
verify([source]);
}
void test_assignability_function_expr_rettype_from_typedef_cls() {
// In the code below, the type of (() => f()) has a return type which is
// a class, and that class is inferred from the return type of the typedef
// F.
Source source = addSource('''
class C {}
typedef C F();
F f;
main() {
F f2 = (() => f());
}
''');
resolve(source);
assertNoErrors(source);
verify([source]);
}
void test_assignability_function_expr_rettype_from_typedef_typedef() {
// In the code below, the type of (() => f()) has a return type which is
// a typedef, and that typedef is inferred from the return type of the
// typedef F.
Source source = addSource('''
typedef G F();
typedef G();
F f;
main() {
F f2 = (() => f());
}
''');
resolve(source);
assertNoErrors(source);
verify([source]);
}
void test_assignmentToFinal_prefixNegate() {
Source source = addSource(r'''
f() {

View file

@ -80,6 +80,35 @@ f() async {
verify([source]);
}
void test_bug21912() {
Source source = addSource('''
class A {}
class B extends A {}
typedef T Function2<S, T>(S z);
typedef B AToB(A x);
typedef A BToA(B x);
void main() {
{
Function2<Function2<A, B>, Function2<B, A>> t1;
Function2<AToB, BToA> t2;
Function2<Function2<int, double>, Function2<int, double>> left;
left = t1;
left = t2;
}
}
''');
resolve(source);
assertErrors(source, [
StaticTypeWarningCode.INVALID_ASSIGNMENT,
StaticTypeWarningCode.INVALID_ASSIGNMENT
]);
verify([source]);
}
void test_expectedOneListTypeArgument() {
Source source = addSource(r'''
main() {

View file

@ -16,7 +16,7 @@ void main() {
Function2<Function2<A, B>, Function2<B, A>> t1;
Function2<AToB, BToA> t2;
Function2<Function2<int, double>, Function2<int, double>> left;
left = t1; /// 01: ok
left = t2; /// 02: ok
left = t1; /// 01: static type warning
left = t2; /// 02: static type warning
}
}